summaryrefslogtreecommitdiffstats
path: root/chart2/source/view
diff options
context:
space:
mode:
Diffstat (limited to '')
-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
-rw-r--r--chart2/source/view/charttypes/AreaChart.cxx952
-rw-r--r--chart2/source/view/charttypes/AreaChart.hxx86
-rw-r--r--chart2/source/view/charttypes/BarChart.cxx976
-rw-r--r--chart2/source/view/charttypes/BarChart.hxx118
-rw-r--r--chart2/source/view/charttypes/BarPositionHelper.cxx73
-rw-r--r--chart2/source/view/charttypes/BarPositionHelper.hxx44
-rw-r--r--chart2/source/view/charttypes/BubbleChart.cxx362
-rw-r--r--chart2/source/view/charttypes/BubbleChart.hxx60
-rw-r--r--chart2/source/view/charttypes/CandleStickChart.cxx314
-rw-r--r--chart2/source/view/charttypes/CandleStickChart.hxx54
-rw-r--r--chart2/source/view/charttypes/CategoryPositionHelper.cxx82
-rw-r--r--chart2/source/view/charttypes/CategoryPositionHelper.hxx57
-rw-r--r--chart2/source/view/charttypes/ConfigAccess.cxx74
-rw-r--r--chart2/source/view/charttypes/NetChart.cxx642
-rw-r--r--chart2/source/view/charttypes/NetChart.hxx74
-rw-r--r--chart2/source/view/charttypes/PieChart.cxx1719
-rw-r--r--chart2/source/view/charttypes/PieChart.hxx145
-rw-r--r--chart2/source/view/charttypes/Splines.cxx872
-rw-r--r--chart2/source/view/charttypes/Splines.hxx48
-rw-r--r--chart2/source/view/charttypes/VSeriesPlotter.cxx2847
-rw-r--r--chart2/source/view/diagram/VDiagram.cxx703
-rw-r--r--chart2/source/view/inc/Clipping.hxx61
-rw-r--r--chart2/source/view/inc/ConfigAccess.hxx35
-rw-r--r--chart2/source/view/inc/DateHelper.hxx43
-rw-r--r--chart2/source/view/inc/LabelAlignment.hxx38
-rw-r--r--chart2/source/view/inc/LabelPositionHelper.hxx66
-rw-r--r--chart2/source/view/inc/LegendEntryProvider.hxx89
-rw-r--r--chart2/source/view/inc/Linear3DTransformation.hxx46
-rw-r--r--chart2/source/view/inc/MinimumAndMaximumSupplier.hxx92
-rw-r--r--chart2/source/view/inc/PlotterBase.hxx79
-rw-r--r--chart2/source/view/inc/PlottingPositionHelper.hxx467
-rw-r--r--chart2/source/view/inc/PolarLabelPositionHelper.hxx70
-rw-r--r--chart2/source/view/inc/PropertyMapper.hxx125
-rw-r--r--chart2/source/view/inc/ScaleAutomatism.hxx144
-rw-r--r--chart2/source/view/inc/ShapeFactory.hxx301
-rw-r--r--chart2/source/view/inc/Stripe.hxx71
-rw-r--r--chart2/source/view/inc/VCoordinateSystem.hxx205
-rw-r--r--chart2/source/view/inc/VDataSeries.hxx267
-rw-r--r--chart2/source/view/inc/VDiagram.hxx117
-rw-r--r--chart2/source/view/inc/VLegendSymbolFactory.hxx53
-rw-r--r--chart2/source/view/inc/VLineProperties.hxx49
-rw-r--r--chart2/source/view/inc/VPolarTransformation.hxx45
-rw-r--r--chart2/source/view/inc/VSeriesPlotter.hxx436
-rw-r--r--chart2/source/view/inc/ViewDefines.hxx35
-rw-r--r--chart2/source/view/main/AxisUsage.hxx143
-rw-r--r--chart2/source/view/main/ChartItemPool.cxx226
-rw-r--r--chart2/source/view/main/ChartItemPool.hxx48
-rw-r--r--chart2/source/view/main/ChartView.cxx2035
-rw-r--r--chart2/source/view/main/Clipping.cxx425
-rw-r--r--chart2/source/view/main/DataPointSymbolSupplier.cxx44
-rw-r--r--chart2/source/view/main/DrawModelWrapper.cxx335
-rw-r--r--chart2/source/view/main/ExplicitValueProvider.cxx211
-rw-r--r--chart2/source/view/main/LabelPositionHelper.cxx467
-rw-r--r--chart2/source/view/main/Linear3DTransformation.cxx124
-rw-r--r--chart2/source/view/main/PlotterBase.cxx106
-rw-r--r--chart2/source/view/main/PlottingPositionHelper.cxx708
-rw-r--r--chart2/source/view/main/PolarLabelPositionHelper.cxx158
-rw-r--r--chart2/source/view/main/PropertyMapper.cxx561
-rw-r--r--chart2/source/view/main/SeriesPlotterContainer.cxx739
-rw-r--r--chart2/source/view/main/SeriesPlotterContainer.hxx159
-rw-r--r--chart2/source/view/main/ShapeFactory.cxx2551
-rw-r--r--chart2/source/view/main/Stripe.cxx347
-rw-r--r--chart2/source/view/main/VButton.cxx136
-rw-r--r--chart2/source/view/main/VButton.hxx86
-rw-r--r--chart2/source/view/main/VDataSeries.cxx1118
-rw-r--r--chart2/source/view/main/VLegend.cxx1099
-rw-r--r--chart2/source/view/main/VLegend.hxx91
-rw-r--r--chart2/source/view/main/VLegendSymbolFactory.cxx188
-rw-r--r--chart2/source/view/main/VLineProperties.cxx85
-rw-r--r--chart2/source/view/main/VPolarTransformation.cxx88
-rw-r--r--chart2/source/view/main/VTitle.cxx158
-rw-r--r--chart2/source/view/main/VTitle.hxx72
107 files changed, 34351 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: */
diff --git a/chart2/source/view/charttypes/AreaChart.cxx b/chart2/source/view/charttypes/AreaChart.cxx
new file mode 100644
index 000000000..9cb2e06ba
--- /dev/null
+++ b/chart2/source/view/charttypes/AreaChart.cxx
@@ -0,0 +1,952 @@
+/* -*- 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 "AreaChart.hxx"
+#include <PlottingPositionHelper.hxx>
+#include <ShapeFactory.hxx>
+#include <CommonConverters.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <ObjectIdentifier.hxx>
+#include "Splines.hxx"
+#include <ChartType.hxx>
+#include <ChartTypeHelper.hxx>
+#include <LabelPositionHelper.hxx>
+#include <Clipping.hxx>
+#include <Stripe.hxx>
+#include <DateHelper.hxx>
+#include <unonames.hxx>
+#include <ConfigAccess.hxx>
+
+#include <com/sun/star/chart2/Symbol.hpp>
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+#include <com/sun/star/chart/MissingValueTreatment.hpp>
+
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <officecfg/Office/Compatibility.hxx>
+
+#include <limits>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+AreaChart::AreaChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bCategoryXAxis
+ , bool bNoArea
+ )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount, bCategoryXAxis )
+ , m_pMainPosHelper(new PlottingPositionHelper())
+ , m_bArea(!bNoArea)
+ , m_bLine(bNoArea)
+ , m_bSymbol( ChartTypeHelper::isSupportingSymbolProperties(xChartTypeModel,nDimensionCount) )
+ , m_eCurveStyle(CurveStyle_LINES)
+ , m_nCurveResolution(20)
+ , m_nSplineOrder(3)
+{
+ m_pMainPosHelper->AllowShiftXAxisPos(true);
+ m_pMainPosHelper->AllowShiftZAxisPos(true);
+
+ PlotterBase::m_pPosHelper = m_pMainPosHelper.get();
+ VSeriesPlotter::m_pMainPosHelper = m_pMainPosHelper.get();
+
+ try
+ {
+ if( m_xChartTypeModel.is() )
+ {
+ m_xChartTypeModel->getPropertyValue(CHART_UNONAME_CURVE_STYLE) >>= m_eCurveStyle;
+ m_xChartTypeModel->getPropertyValue(CHART_UNONAME_CURVE_RESOLUTION) >>= m_nCurveResolution;
+ m_xChartTypeModel->getPropertyValue(CHART_UNONAME_SPLINE_ORDER) >>= m_nSplineOrder;
+ }
+ }
+ catch( uno::Exception& e )
+ {
+ //the above properties are not supported by all charttypes supported by this class (e.g. area or net chart)
+ //in that cases this exception is ok
+ e.Context.is();//to have debug information without compilation warnings
+ }
+}
+
+AreaChart::~AreaChart()
+{
+}
+
+bool AreaChart::isSeparateStackingForDifferentSigns( sal_Int32 /*nDimensionIndex*/ )
+{
+ // no separate stacking in all types of line/area charts
+ return false;
+}
+
+LegendSymbolStyle AreaChart::getLegendSymbolStyle()
+{
+ if( m_bArea || m_nDimension == 3 )
+ return LegendSymbolStyle::Box;
+ return LegendSymbolStyle::Line;
+}
+
+uno::Any AreaChart::getExplicitSymbol( const VDataSeries& rSeries, sal_Int32 nPointIndex )
+{
+ uno::Any aRet;
+
+ Symbol* pSymbolProperties = rSeries.getSymbolProperties( nPointIndex );
+ if( pSymbolProperties )
+ {
+ aRet <<= *pSymbolProperties;
+ }
+
+ return aRet;
+}
+
+drawing::Direction3D AreaChart::getPreferredDiagramAspectRatio() const
+{
+ drawing::Direction3D aRet(1,-1,1);
+ if( m_nDimension == 2 )
+ aRet = drawing::Direction3D(-1,-1,-1);
+ else if (m_pPosHelper)
+ {
+ drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
+ aRet.DirectionZ = aScale.DirectionZ*0.2;
+ if(aRet.DirectionZ>1.0)
+ aRet.DirectionZ=1.0;
+ if(aRet.DirectionZ>10)
+ aRet.DirectionZ=10;
+ }
+ return aRet;
+}
+
+void AreaChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
+{
+ if( m_bArea && pSeries )
+ {
+ sal_Int32 nMissingValueTreatment = pSeries->getMissingValueTreatment();
+ if( nMissingValueTreatment == css::chart::MissingValueTreatment::LEAVE_GAP )
+ pSeries->setMissingValueTreatment( css::chart::MissingValueTreatment::USE_ZERO );
+ }
+ if( m_nDimension == 3 && !m_bCategoryXAxis )
+ {
+ //3D xy always deep
+ OSL_ENSURE( zSlot==-1,"3D xy charts should be deep stacked in model also" );
+ zSlot=-1;
+ xSlot=0;
+ ySlot=0;
+ }
+ VSeriesPlotter::addSeries( std::move(pSeries), zSlot, xSlot, ySlot );
+}
+
+static void lcl_removeDuplicatePoints( std::vector<std::vector<css::drawing::Position3D>>& rPolyPoly, PlottingPositionHelper& rPosHelper )
+{
+ sal_Int32 nPolyCount = rPolyPoly.size();
+ if(!nPolyCount)
+ return;
+
+ // TODO we could do with without a temporary array
+ std::vector<std::vector<css::drawing::Position3D>> aTmp;
+ aTmp.resize(nPolyCount);
+
+ for( sal_Int32 nPolygonIndex = 0; nPolygonIndex<nPolyCount; nPolygonIndex++ )
+ {
+ std::vector<css::drawing::Position3D>* pOuterSource = &rPolyPoly[nPolygonIndex];
+ std::vector<css::drawing::Position3D>* pOuterTarget = &aTmp[nPolygonIndex];
+
+ sal_Int32 nPointCount = pOuterSource->size();
+ if( !nPointCount )
+ continue;
+
+ pOuterTarget->resize(nPointCount);
+
+ css::drawing::Position3D* pSource = pOuterSource->data();
+ css::drawing::Position3D* pTarget = pOuterTarget->data();
+
+ //copy first point
+ *pTarget=*pSource++;
+ sal_Int32 nTargetPointCount=1;
+
+ for( sal_Int32 nSource=1; nSource<nPointCount; nSource++ )
+ {
+ if( !rPosHelper.isSameForGivenResolution( pTarget->PositionX, pTarget->PositionY, pTarget->PositionZ
+ , pSource->PositionX, pSource->PositionY, pSource->PositionZ ) )
+ {
+ pTarget++;
+ *pTarget=*pSource;
+ nTargetPointCount++;
+ }
+ pSource++;
+ }
+
+ //free unused space
+ if( nTargetPointCount<nPointCount )
+ {
+ pOuterTarget->resize(nTargetPointCount);
+ }
+
+ pOuterSource->clear();
+ }
+
+ //free space
+ rPolyPoly.resize(nPolyCount);
+
+ rPolyPoly = std::move(aTmp);
+}
+
+bool AreaChart::create_stepped_line(
+ std::vector<std::vector<css::drawing::Position3D>> aStartPoly,
+ chart2::CurveStyle eCurveStyle,
+ PlottingPositionHelper const * pPosHelper,
+ std::vector<std::vector<css::drawing::Position3D>> &aPoly )
+{
+ sal_uInt32 nOuterCount = aStartPoly.size();
+ if ( !nOuterCount )
+ return false;
+
+ std::vector<std::vector<css::drawing::Position3D>> aSteppedPoly;
+ aSteppedPoly.resize(nOuterCount);
+
+ auto pSequence = aSteppedPoly.data();
+
+ for( sal_uInt32 nOuter = 0; nOuter < nOuterCount; ++nOuter )
+ {
+ if( aStartPoly[nOuter].size() <= 1 )
+ continue; //we need at least two points
+
+ sal_uInt32 nMaxIndexPoints = aStartPoly[nOuter].size()-1; // is >1
+ sal_uInt32 nNewIndexPoints = 0;
+ if ( eCurveStyle==CurveStyle_STEP_START || eCurveStyle==CurveStyle_STEP_END)
+ nNewIndexPoints = nMaxIndexPoints * 2 + 1;
+ else
+ nNewIndexPoints = nMaxIndexPoints * 3 + 1;
+
+ const css::drawing::Position3D* pOld = aStartPoly[nOuter].data();
+
+ pSequence[nOuter].resize( nNewIndexPoints );
+
+ css::drawing::Position3D* pNew = pSequence[nOuter].data();
+
+ pNew[0] = pOld[0];
+ for( sal_uInt32 oi = 0; oi < nMaxIndexPoints; oi++ )
+ {
+ switch ( eCurveStyle )
+ {
+ case CurveStyle_STEP_START:
+ /** O
+ |
+ |
+ |
+ O-----+
+ */
+ // create the intermediate point
+ pNew[1+oi*2].PositionX = pOld[oi+1].PositionX;
+ pNew[1+oi*2].PositionY = pOld[oi].PositionY;
+ pNew[1+oi*2].PositionZ = pOld[oi].PositionZ;
+ // and now the normal one
+ pNew[1+oi*2+1] = pOld[oi+1];
+ break;
+ case CurveStyle_STEP_END:
+ /** +------O
+ |
+ |
+ |
+ O
+ */
+ // create the intermediate point
+ pNew[1+oi*2].PositionX = pOld[oi].PositionX;
+ pNew[1+oi*2].PositionY = pOld[oi+1].PositionY;
+ pNew[1+oi*2].PositionZ = pOld[oi].PositionZ;
+ // and now the normal one
+ pNew[1+oi*2+1] = pOld[oi+1];
+ break;
+ case CurveStyle_STEP_CENTER_X:
+ /** +--O
+ |
+ |
+ |
+ O--+
+ */
+ // create the first intermediate point
+ pNew[1+oi*3].PositionX = (pOld[oi].PositionX + pOld[oi+1].PositionX) / 2;
+ pNew[1+oi*3].PositionY = pOld[oi].PositionY;
+ pNew[1+oi*3].PositionZ = pOld[oi].PositionZ;
+ // create the second intermediate point
+ pNew[1+oi*3+1].PositionX = (pOld[oi].PositionX + pOld[oi+1].PositionX) / 2;
+ pNew[1+oi*3+1].PositionY = pOld[oi+1].PositionY;
+ pNew[1+oi*3+1].PositionZ = pOld[oi].PositionZ;
+ // and now the normal one
+ pNew[1+oi*3+2] = pOld[oi+1];
+ break;
+ case CurveStyle_STEP_CENTER_Y:
+ /** O
+ |
+ +-----+
+ |
+ O
+ */
+ // create the first intermediate point
+ pNew[1+oi*3].PositionX = pOld[oi].PositionX;
+ pNew[1+oi*3].PositionY = (pOld[oi].PositionY + pOld[oi+1].PositionY) / 2;
+ pNew[1+oi*3].PositionZ = pOld[oi].PositionZ;
+ // create the second intermediate point
+ pNew[1+oi*3+1].PositionX = pOld[oi+1].PositionX;
+ pNew[1+oi*3+1].PositionY = (pOld[oi].PositionY + pOld[oi+1].PositionY) / 2;
+ pNew[1+oi*3+1].PositionZ = pOld[oi].PositionZ;
+ // and now the normal one
+ pNew[1+oi*3+2] = pOld[oi+1];
+ break;
+ default:
+ // this should never be executed
+ OSL_FAIL("Unknown curvestyle in AreaChart::create_stepped_line");
+ }
+ }
+ }
+ Clipping::clipPolygonAtRectangle( aSteppedPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+
+ return true;
+}
+
+bool AreaChart::impl_createLine( VDataSeries* pSeries
+ , std::vector<std::vector<css::drawing::Position3D>> const * pSeriesPoly
+ , PlottingPositionHelper* pPosHelper )
+{
+ //return true if a line was created successfully
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget);
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+ if(m_eCurveStyle==CurveStyle_CUBIC_SPLINES)
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aSplinePoly;
+ SplineCalculater::CalculateCubicSplines( *pSeriesPoly, aSplinePoly, m_nCurveResolution );
+ lcl_removeDuplicatePoints( aSplinePoly, *pPosHelper );
+ Clipping::clipPolygonAtRectangle( aSplinePoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+ }
+ else if(m_eCurveStyle==CurveStyle_B_SPLINES)
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aSplinePoly;
+ SplineCalculater::CalculateBSplines( *pSeriesPoly, aSplinePoly, m_nCurveResolution, m_nSplineOrder );
+ lcl_removeDuplicatePoints( aSplinePoly, *pPosHelper );
+ Clipping::clipPolygonAtRectangle( aSplinePoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+ }
+ else if (m_eCurveStyle==CurveStyle_STEP_START ||
+ m_eCurveStyle==CurveStyle_STEP_END ||
+ m_eCurveStyle==CurveStyle_STEP_CENTER_Y ||
+ m_eCurveStyle==CurveStyle_STEP_CENTER_X
+ )
+ {
+ if (!create_stepped_line(*pSeriesPoly, m_eCurveStyle, pPosHelper, aPoly))
+ {
+ return false;
+ }
+ }
+ else
+ { // default to creating a straight line
+ SAL_WARN_IF(m_eCurveStyle != CurveStyle_LINES, "chart2.areachart", "Unknown curve style");
+ Clipping::clipPolygonAtRectangle( *pSeriesPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+ }
+
+ if(!ShapeFactory::hasPolygonAnyLines(aPoly))
+ return false;
+
+ //transformation 3) -> 4)
+ pPosHelper->transformScaledLogicToScene( aPoly );
+
+ //create line:
+ rtl::Reference< SvxShape > xShape;
+ if(m_nDimension==3)
+ {
+ double fDepth = getTransformedDepth();
+ sal_Int32 nPolyCount = aPoly.size();
+ for(sal_Int32 nPoly=0;nPoly<nPolyCount;nPoly++)
+ {
+ sal_Int32 nPointCount = aPoly[nPoly].size();
+ for(sal_Int32 nPoint=0;nPoint<nPointCount-1;nPoint++)
+ {
+ drawing::Position3D aPoint1, aPoint2;
+ aPoint1 = aPoly[nPoly][nPoint+1];
+ aPoint2 = aPoly[nPoly][nPoint];
+
+ ShapeFactory::createStripe(xSeriesGroupShape_Shapes
+ , Stripe( aPoint1, aPoint2, fDepth )
+ , pSeries->getPropertiesOfSeries(), PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), true, 1 );
+ }
+ }
+ }
+ else //m_nDimension!=3
+ {
+ xShape = ShapeFactory::createLine2D( xSeriesGroupShape_Shapes, aPoly );
+ PropertyMapper::setMappedProperties( *xShape
+ , pSeries->getPropertiesOfSeries()
+ , PropertyMapper::getPropertyNameMapForLineSeriesProperties() );
+ //because of this name this line will be used for marking
+ ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles");
+ }
+ return true;
+}
+
+bool AreaChart::impl_createArea( VDataSeries* pSeries
+ , std::vector<std::vector<css::drawing::Position3D>> const * pSeriesPoly
+ , std::vector<std::vector<css::drawing::Position3D>> const * pPreviousSeriesPoly
+ , PlottingPositionHelper const * pPosHelper )
+{
+ //return true if an area was created successfully
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget);
+ double zValue = pSeries->m_fLogicZPos;
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly( *pSeriesPoly );
+ //add second part to the polygon (grounding points or previous series points)
+ if(!pPreviousSeriesPoly)
+ {
+ double fMinX = pSeries->m_fLogicMinX;
+ double fMaxX = pSeries->m_fLogicMaxX;
+ double fY = pPosHelper->getBaseValueY();//logic grounding
+ if( m_nDimension==3 )
+ fY = pPosHelper->getLogicMinY();
+
+ //clip to scale
+ if(fMaxX<pPosHelper->getLogicMinX() || fMinX>pPosHelper->getLogicMaxX())
+ return false;//no visible shape needed
+ pPosHelper->clipLogicValues( &fMinX, &fY, nullptr );
+ pPosHelper->clipLogicValues( &fMaxX, nullptr, nullptr );
+
+ //apply scaling
+ {
+ pPosHelper->doLogicScaling( &fMinX, &fY, &zValue );
+ pPosHelper->doLogicScaling( &fMaxX, nullptr, nullptr );
+ }
+
+ AddPointToPoly( aPoly, drawing::Position3D( fMaxX,fY,zValue) );
+ AddPointToPoly( aPoly, drawing::Position3D( fMinX,fY,zValue) );
+ }
+ else
+ {
+ appendPoly( aPoly, *pPreviousSeriesPoly );
+ }
+ ShapeFactory::closePolygon(aPoly);
+
+ //apply clipping
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aClippedPoly;
+ Clipping::clipPolygonAtRectangle( aPoly, pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly, false );
+ ShapeFactory::closePolygon(aClippedPoly); //again necessary after clipping
+ aPoly = aClippedPoly;
+ }
+
+ if(!ShapeFactory::hasPolygonAnyLines(aPoly))
+ return false;
+
+ //transformation 3) -> 4)
+ pPosHelper->transformScaledLogicToScene( aPoly );
+
+ //create area:
+ rtl::Reference< SvxShape > xShape;
+ if(m_nDimension==3)
+ {
+ xShape = ShapeFactory::createArea3D( xSeriesGroupShape_Shapes
+ , aPoly, getTransformedDepth() );
+ }
+ else //m_nDimension!=3
+ {
+ xShape = ShapeFactory::createArea2D( xSeriesGroupShape_Shapes
+ , aPoly );
+ }
+ PropertyMapper::setMappedProperties( *xShape
+ , pSeries->getPropertiesOfSeries()
+ , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ //because of this name this line will be used for marking
+ ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles");
+ return true;
+}
+
+void AreaChart::impl_createSeriesShapes()
+{
+ //the polygon shapes for each series need to be created before
+
+ //iterate through all series again to create the series shapes
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ for( auto const& rXSlot : rZSlot )
+ {
+ std::map< sal_Int32, std::vector<std::vector<css::drawing::Position3D>>* > aPreviousSeriesPolyMap;//a PreviousSeriesPoly for each different nAttachedAxisIndex
+ std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly = nullptr;
+
+ //iterate through all series
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ PlottingPositionHelper& rPosHelper = getPlottingPositionHelper(nAttachedAxisIndex);
+ m_pPosHelper = &rPosHelper;
+
+ createRegressionCurvesShapes( *pSeries, m_xErrorBarTarget, m_xRegressionCurveEquationTarget,
+ m_pPosHelper->maySkipPointsInRegressionCalculation());
+
+ pSeriesPoly = &pSeries->m_aPolyPolygonShape3D;
+ if( m_bArea )
+ {
+ if (!impl_createArea(pSeries.get(), pSeriesPoly,
+ aPreviousSeriesPolyMap[nAttachedAxisIndex], &rPosHelper))
+ continue;
+ }
+ if( m_bLine )
+ {
+ if (!impl_createLine(pSeries.get(), pSeriesPoly, &rPosHelper))
+ continue;
+ }
+ aPreviousSeriesPolyMap[nAttachedAxisIndex] = pSeriesPoly;
+ }//next series in x slot (next y slot)
+ }//next x slot
+ }//next z slot
+}
+
+namespace
+{
+
+void lcl_reorderSeries( std::vector< std::vector< VDataSeriesGroup > >& rZSlots )
+{
+ std::vector< std::vector< VDataSeriesGroup > > aRet;
+ aRet.reserve( rZSlots.size() );
+
+ std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZIt( rZSlots.rbegin() );
+ std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZEnd( rZSlots.rend() );
+ for( ; aZIt != aZEnd; ++aZIt )
+ {
+ std::vector< VDataSeriesGroup > aXSlot;
+ aXSlot.reserve( aZIt->size() );
+
+ std::vector< VDataSeriesGroup >::reverse_iterator aXIt( aZIt->rbegin() );
+ std::vector< VDataSeriesGroup >::reverse_iterator aXEnd( aZIt->rend() );
+ for( ; aXIt != aXEnd; ++aXIt )
+ aXSlot.push_back(std::move(*aXIt));
+
+ aRet.push_back(std::move(aXSlot));
+ }
+
+ rZSlots = std::move(aRet);
+}
+
+//better performance for big data
+struct FormerPoint
+{
+ FormerPoint( double fX, double fY, double fZ )
+ : m_fX(fX), m_fY(fY), m_fZ(fZ)
+ {}
+ FormerPoint()
+ : m_fX(std::numeric_limits<double>::quiet_NaN())
+ , m_fY(std::numeric_limits<double>::quiet_NaN())
+ , m_fZ(std::numeric_limits<double>::quiet_NaN())
+ {
+ }
+
+ double m_fX;
+ double m_fY;
+ double m_fZ;
+};
+
+}//anonymous namespace
+
+void AreaChart::createShapes()
+{
+ if( m_aZSlots.empty() ) //no series
+ return;
+
+ //tdf#127813 Don't reverse the series in OOXML-heavy environments
+ if( officecfg::Office::Compatibility::View::ReverseSeriesOrderAreaAndNetChart::get() && m_nDimension == 2 && ( m_bArea || !m_bCategoryXAxis ) )
+ lcl_reorderSeries( m_aZSlots );
+
+ OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"AreaChart is not proper initialized");
+ if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
+ return;
+
+ //the text labels should be always on top of the other series shapes
+ //for area chart the error bars should be always on top of the other series shapes
+
+ //therefore create an own group for the texts and the error bars to move them to front
+ //(because the text group is created after the series group the texts are displayed on top)
+ m_xSeriesTarget = createGroupShape( m_xLogicTarget );
+ if( m_bArea )
+ m_xErrorBarTarget = createGroupShape( m_xLogicTarget );
+ else
+ m_xErrorBarTarget = m_xSeriesTarget;
+ m_xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+ m_xRegressionCurveEquationTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+
+ //check necessary here that different Y axis can not be stacked in the same group? ... hm?
+
+ //update/create information for current group
+ double fLogicZ = 1.0;//as defined
+
+ sal_Int32 nStartIndex = 0; // inclusive ;..todo get somehow from x scale
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ if(nEndIndex<=0)
+ nEndIndex=1;
+
+ //better performance for big data
+ std::map< VDataSeries*, FormerPoint > aSeriesFormerPointMap;
+ m_bPointsWereSkipped = false;
+ sal_Int32 nSkippedPoints = 0;
+ sal_Int32 nCreatedPoints = 0;
+
+ bool bDateCategory = (m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis());
+
+ std::vector<std::map< sal_Int32, double > > aLogicYSumMapByX(nEndIndex);//one for each different nAttachedAxisIndex
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ //iterate through all x slots in this category to get 100percent sum
+ for( auto const& rXSlot : rZSlot )
+ {
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ if (bDateCategory)
+ pSeries->doSortByXValues();
+
+ for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ )
+ {
+ std::map< sal_Int32, double >& rLogicYSumMap = aLogicYSumMapByX[nIndex];
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ rLogicYSumMap.insert({nAttachedAxisIndex, 0.0});
+
+ m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex);
+
+ double fAdd = pSeries->getYValue( nIndex );
+ if( !std::isnan(fAdd) && !std::isinf(fAdd) )
+ rLogicYSumMap[nAttachedAxisIndex] += fabs( fAdd );
+ }
+ }
+ }
+ }
+
+ const bool bUseErrorRectangle = ConfigAccess::getUseErrorRectangle();
+
+ sal_Int32 nZ=1;
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ //for the area chart there should be at most one x slot (no side by side stacking available)
+ //attention different: xSlots are always interpreted as independent areas one behind the other: @todo this doesn't work why not???
+ for( auto const& rXSlot : rZSlot )
+ {
+ std::vector<std::map< sal_Int32, double > > aLogicYForNextSeriesMapByX(nEndIndex); //one for each different nAttachedAxisIndex
+ //iterate through all series
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeFrontChild(pSeries.get(), m_xSeriesTarget);
+
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ double fXMin, fXMax;
+ pSeries->getMinMaxXValue(fXMin, fXMax);
+ PlottingPositionHelper& rPosHelper = getPlottingPositionHelper(nAttachedAxisIndex);
+ m_pPosHelper = &rPosHelper;
+
+ if(m_nDimension==3)
+ fLogicZ = nZ+0.5;
+ pSeries->m_fLogicZPos = fLogicZ;
+
+ for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ )
+ {
+
+ /* #i70133# ignore points outside of series length in standard area
+ charts. Stacked area charts will use missing points as zeros. In
+ standard charts, pSeriesList contains only one series. */
+ if( m_bArea && (rXSlot.m_aSeriesVector.size() == 1) && (nIndex >= pSeries->getTotalPointCount()) )
+ continue;
+
+ //collect data point information (logic coordinates, style ):
+ double fLogicX = pSeries->getXValue(nIndex);
+ if (bDateCategory)
+ {
+ if (std::isnan(fLogicX) || (fLogicX < fXMin || fLogicX > fXMax))
+ continue;
+
+ fLogicX = DateHelper::RasterizeDateValue( fLogicX, m_aNullDate, m_nTimeResolution );
+ }
+ double fLogicY = pSeries->getYValue(nIndex);
+
+ if( m_nDimension==3 && m_bArea && rXSlot.m_aSeriesVector.size()!=1 )
+ fLogicY = fabs( fLogicY );
+
+ double fLogicValueForLabeDisplay = fLogicY;
+ std::map< sal_Int32, double >& rLogicYSumMap = aLogicYSumMapByX[nIndex];
+ if (rPosHelper.isPercentY() && rLogicYSumMap[nAttachedAxisIndex] != 0.0)
+ {
+ fLogicY = fabs( fLogicY )/rLogicYSumMap[nAttachedAxisIndex];
+ }
+
+ if( std::isnan(fLogicX) || std::isinf(fLogicX)
+ || std::isnan(fLogicY) || std::isinf(fLogicY)
+ || std::isnan(fLogicZ) || std::isinf(fLogicZ) )
+ {
+ if( pSeries->getMissingValueTreatment() == css::chart::MissingValueTreatment::LEAVE_GAP )
+ {
+ std::vector<std::vector<css::drawing::Position3D>>& rPolygon = pSeries->m_aPolyPolygonShape3D;
+ sal_Int32& rIndex = pSeries->m_nPolygonIndex;
+ if( 0<= rIndex && o3tl::make_unsigned(rIndex) < rPolygon.size() )
+ {
+ if( !rPolygon[ rIndex ].empty() )
+ rIndex++; //start a new polygon for the next point if the current poly is not empty
+ }
+ }
+ continue;
+ }
+
+ std::map< sal_Int32, double >& rLogicYForNextSeriesMap = aLogicYForNextSeriesMapByX[nIndex];
+ rLogicYForNextSeriesMap.try_emplace(nAttachedAxisIndex, 0.0);
+
+ double fPreviousYValue = rLogicYForNextSeriesMap[nAttachedAxisIndex];
+ fLogicY += rLogicYForNextSeriesMap[nAttachedAxisIndex];
+ rLogicYForNextSeriesMap[nAttachedAxisIndex] = fLogicY;
+
+ bool bIsVisible = rPosHelper.isLogicVisible(fLogicX, fLogicY, fLogicZ);
+
+ //remind minimal and maximal x values for area 'grounding' points
+ //only for filled area
+ {
+ double& rfMinX = pSeries->m_fLogicMinX;
+ if(!nIndex||fLogicX<rfMinX)
+ rfMinX=fLogicX;
+ double& rfMaxX = pSeries->m_fLogicMaxX;
+ if(!nIndex||fLogicX>rfMaxX)
+ rfMaxX=fLogicX;
+ }
+
+ drawing::Position3D aUnscaledLogicPosition( fLogicX, fLogicY, fLogicZ );
+ drawing::Position3D aScaledLogicPosition(aUnscaledLogicPosition);
+ rPosHelper.doLogicScaling(aScaledLogicPosition);
+
+ //transformation 3) -> 4)
+ drawing::Position3D aScenePosition(
+ rPosHelper.transformLogicToScene(fLogicX, fLogicY, fLogicZ, false));
+
+ //better performance for big data
+ FormerPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] );
+ rPosHelper.setCoordinateSystemResolution(m_aCoordinateSystemResolution);
+ if (!pSeries->isAttributedDataPoint(nIndex)
+ && rPosHelper.isSameForGivenResolution(
+ aFormerPoint.m_fX, aFormerPoint.m_fY, aFormerPoint.m_fZ,
+ aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY,
+ aScaledLogicPosition.PositionZ))
+ {
+ ++nSkippedPoints;
+ m_bPointsWereSkipped = true;
+ continue;
+ }
+ aSeriesFormerPointMap[pSeries.get()] = FormerPoint(aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, aScaledLogicPosition.PositionZ);
+
+ //store point information for series polygon
+ //for area and/or line (symbols only do not need this)
+ if( isValidPosition(aScaledLogicPosition) )
+ {
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aScaledLogicPosition, pSeries->m_nPolygonIndex );
+ }
+
+ //create a single datapoint if point is visible
+ //apply clipping:
+ if( !bIsVisible )
+ continue;
+
+ bool bCreateYErrorBar = false, bCreateXErrorBar = false;
+ {
+ uno::Reference< beans::XPropertySet > xErrorBarProp(pSeries->getYErrorBarProperties(nIndex));
+ if( xErrorBarProp.is() )
+ {
+ bool bShowPositive = false;
+ bool bShowNegative = false;
+ xErrorBarProp->getPropertyValue("ShowPositiveError") >>= bShowPositive;
+ xErrorBarProp->getPropertyValue("ShowNegativeError") >>= bShowNegative;
+ bCreateYErrorBar = bShowPositive || bShowNegative;
+ }
+
+ xErrorBarProp = pSeries->getXErrorBarProperties(nIndex);
+ if ( xErrorBarProp.is() )
+ {
+ bool bShowPositive = false;
+ bool bShowNegative = false;
+ xErrorBarProp->getPropertyValue("ShowPositiveError") >>= bShowPositive;
+ xErrorBarProp->getPropertyValue("ShowNegativeError") >>= bShowNegative;
+ bCreateXErrorBar = bShowPositive || bShowNegative;
+ }
+ }
+
+ Symbol* pSymbolProperties = m_bSymbol ? pSeries->getSymbolProperties( nIndex ) : nullptr;
+ bool bCreateSymbol = pSymbolProperties && (pSymbolProperties->Style != SymbolStyle_NONE);
+
+ if( !bCreateSymbol && !bCreateYErrorBar &&
+ !bCreateXErrorBar && !pSeries->getDataPointLabelIfLabel(nIndex) )
+ continue;
+
+ {
+ nCreatedPoints++;
+
+ //create data point
+ drawing::Direction3D aSymbolSize(0,0,0);
+ if( bCreateSymbol )
+ {
+ if(m_nDimension!=3)
+ {
+ //create a group shape for this point and add to the series shape:
+ OUString aPointCID = ObjectIdentifier::createPointCID(
+ pSeries->getPointCID_Stub(), nIndex );
+ rtl::Reference<SvxShapeGroupAnyD> xPointGroupShape_Shapes;
+ if (pSymbolProperties->Style == SymbolStyle_STANDARD || pSymbolProperties->Style == SymbolStyle_GRAPHIC)
+ xPointGroupShape_Shapes = createGroupShape(xSeriesGroupShape_Shapes,aPointCID);
+
+ if (pSymbolProperties->Style != SymbolStyle_NONE)
+ {
+ aSymbolSize.DirectionX = pSymbolProperties->Size.Width;
+ aSymbolSize.DirectionY = pSymbolProperties->Size.Height;
+ }
+
+ if (pSymbolProperties->Style == SymbolStyle_STANDARD)
+ {
+ sal_Int32 nSymbol = pSymbolProperties->StandardSymbol;
+ ShapeFactory::createSymbol2D(
+ xPointGroupShape_Shapes, aScenePosition, aSymbolSize,
+ nSymbol, pSymbolProperties->BorderColor,
+ pSymbolProperties->FillColor);
+ }
+ else if (pSymbolProperties->Style == SymbolStyle_GRAPHIC)
+ {
+ ShapeFactory::createGraphic2D(xPointGroupShape_Shapes,
+ aScenePosition, aSymbolSize,
+ pSymbolProperties->Graphic);
+ }
+ //@todo other symbol styles
+ }
+ }
+ //create error bars or rectangles, depending on configuration
+ if ( bUseErrorRectangle )
+ {
+ if ( bCreateXErrorBar || bCreateYErrorBar )
+ {
+ createErrorRectangle(
+ aUnscaledLogicPosition,
+ *pSeries,
+ nIndex,
+ m_xErrorBarTarget,
+ bCreateXErrorBar,
+ bCreateYErrorBar );
+ }
+ }
+ else
+ {
+ if (bCreateXErrorBar)
+ createErrorBar_X( aUnscaledLogicPosition, *pSeries, nIndex, m_xErrorBarTarget );
+
+ if (bCreateYErrorBar)
+ createErrorBar_Y( aUnscaledLogicPosition, *pSeries, nIndex, m_xErrorBarTarget, nullptr );
+ }
+
+ //create data point label
+ if( pSeries->getDataPointLabelIfLabel(nIndex) )
+ {
+ LabelAlignment eAlignment = LABEL_ALIGN_TOP;
+ sal_Int32 nLabelPlacement = pSeries->getLabelPlacement(
+ nIndex, m_xChartTypeModel, rPosHelper.isSwapXAndY());
+
+ if (m_bArea && nLabelPlacement == css::chart::DataLabelPlacement::CENTER)
+ {
+ if (fPreviousYValue)
+ fLogicY -= (fLogicY - fPreviousYValue) / 2.0;
+ else
+ fLogicY = (fLogicY + rPosHelper.getLogicMinY()) / 2.0;
+ aScenePosition = rPosHelper.transformLogicToScene(fLogicX, fLogicY, fLogicZ, false);
+ }
+
+ drawing::Position3D aScenePosition3D( aScenePosition.PositionX
+ , aScenePosition.PositionY
+ , aScenePosition.PositionZ+getTransformedDepth() );
+
+ switch(nLabelPlacement)
+ {
+ case css::chart::DataLabelPlacement::TOP:
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ case css::chart::DataLabelPlacement::BOTTOM:
+ aScenePosition3D.PositionY += (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_BOTTOM;
+ break;
+ case css::chart::DataLabelPlacement::LEFT:
+ aScenePosition3D.PositionX -= (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_LEFT;
+ break;
+ case css::chart::DataLabelPlacement::RIGHT:
+ aScenePosition3D.PositionX += (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_RIGHT;
+ break;
+ case css::chart::DataLabelPlacement::CENTER:
+ eAlignment = LABEL_ALIGN_CENTER;
+ break;
+ default:
+ OSL_FAIL("this label alignment is not implemented yet");
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ }
+
+ awt::Point aScreenPosition2D;//get the screen position for the labels
+ sal_Int32 nOffset = 100; //todo maybe calculate this font height dependent
+ {
+ if(eAlignment==LABEL_ALIGN_CENTER || m_nDimension == 3 )
+ nOffset = 0;
+ aScreenPosition2D = LabelPositionHelper(m_nDimension,m_xLogicTarget)
+ .transformSceneToScreenPosition( aScenePosition3D );
+ }
+
+ createDataLabel( m_xTextTarget, *pSeries, nIndex
+ , fLogicValueForLabeDisplay
+ , rLogicYSumMap[nAttachedAxisIndex], aScreenPosition2D, eAlignment, nOffset );
+ }
+ }
+ }
+
+ }//next series in x slot (next y slot)
+ }//next x slot
+ ++nZ;
+ }//next z slot
+
+ impl_createSeriesShapes();
+
+ /* @todo remove series shapes if empty
+ //remove and delete point-group-shape if empty
+ if(!xSeriesGroupShape_Shapes->getCount())
+ {
+ pSeries->m_xShape.set(NULL);
+ m_xLogicTarget->remove(xSeriesGroupShape_Shape);
+ }
+ */
+
+ //remove and delete series-group-shape if empty
+
+ //... todo
+
+ SAL_INFO(
+ "chart2",
+ "skipped points: " << nSkippedPoints << " created points: "
+ << nCreatedPoints);
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/AreaChart.hxx b/chart2/source/view/charttypes/AreaChart.hxx
new file mode 100644
index 000000000..2f7434f3c
--- /dev/null
+++ b/chart2/source/view/charttypes/AreaChart.hxx
@@ -0,0 +1,86 @@
+/* -*- 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 <memory>
+#include <VSeriesPlotter.hxx>
+#include <com/sun/star/chart2/CurveStyle.hpp>
+
+namespace chart
+{
+
+class AreaChart : public VSeriesPlotter
+{
+ // public methods
+public:
+ AreaChart() = delete;
+
+ AreaChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bCategoryXAxis, bool bNoArea=false
+ );
+ virtual ~AreaChart() override;
+
+ virtual void createShapes() override;
+ virtual void addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot ) override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+
+ // MinimumAndMaximumSupplier
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+ virtual LegendSymbolStyle getLegendSymbolStyle() override;
+ virtual css::uno::Any getExplicitSymbol( const VDataSeries& rSeries, sal_Int32 nPointIndex/*-1 for series symbol*/ ) override;
+
+private: //methods
+ void impl_createSeriesShapes();
+ bool impl_createArea( VDataSeries* pSeries
+ , std::vector<std::vector<css::drawing::Position3D>> const * pSeriesPoly
+ , std::vector<std::vector<css::drawing::Position3D>> const * pPreviousSeriesPoly
+ , PlottingPositionHelper const * pPosHelper );
+ bool impl_createLine( VDataSeries* pSeries
+ , std::vector<std::vector<css::drawing::Position3D>> const * pSeriesPoly
+ , PlottingPositionHelper* pPosHelper );
+ static bool create_stepped_line( std::vector<std::vector<css::drawing::Position3D>> aStartPoly
+ , css::chart2::CurveStyle eCurveStyle
+ , PlottingPositionHelper const * pPosHelper
+ , std::vector<std::vector<css::drawing::Position3D>> &aPoly );
+
+private: //member
+ std::unique_ptr<PlottingPositionHelper>
+ m_pMainPosHelper;
+
+ bool m_bArea;//false -> line or symbol only
+ bool m_bLine;
+ bool m_bSymbol;
+
+ //Properties for splines:
+ css::chart2::CurveStyle m_eCurveStyle;
+ sal_Int32 m_nCurveResolution;
+ sal_Int32 m_nSplineOrder;
+
+ rtl::Reference<SvxShapeGroupAnyD> m_xSeriesTarget;
+ rtl::Reference<SvxShapeGroupAnyD> m_xErrorBarTarget;
+ rtl::Reference<SvxShapeGroupAnyD> m_xTextTarget;
+ rtl::Reference<SvxShapeGroupAnyD> m_xRegressionCurveEquationTarget;
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BarChart.cxx b/chart2/source/view/charttypes/BarChart.cxx
new file mode 100644
index 000000000..b15706d76
--- /dev/null
+++ b/chart2/source/view/charttypes/BarChart.cxx
@@ -0,0 +1,976 @@
+/* -*- 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 "BarChart.hxx"
+#include "BarPositionHelper.hxx"
+
+#include <ChartType.hxx>
+#include <ShapeFactory.hxx>
+#include <CommonConverters.hxx>
+#include <ObjectIdentifier.hxx>
+#include <LabelPositionHelper.hxx>
+#include <AxisIndexDefines.hxx>
+#include <Clipping.hxx>
+#include <DateHelper.hxx>
+#include <svx/scene3d.hxx>
+#include <comphelper/scopeguard.hxx>
+
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+
+#include <com/sun/star/chart2/DataPointGeometry3D.hpp>
+#include <rtl/math.hxx>
+#include <tools/diagnose_ex.h>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::rtl::math;
+using namespace ::com::sun::star::chart2;
+
+BarChart::BarChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount )
+ , m_pMainPosHelper( new BarPositionHelper() )
+{
+ PlotterBase::m_pPosHelper = m_pMainPosHelper.get();
+ VSeriesPlotter::m_pMainPosHelper = m_pMainPosHelper.get();
+
+ try
+ {
+ if( m_xChartTypeModel.is() )
+ {
+ m_xChartTypeModel->getPropertyValue( "OverlapSequence" ) >>= m_aOverlapSequence;
+ m_xChartTypeModel->getPropertyValue( "GapwidthSequence" ) >>= m_aGapwidthSequence;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+BarChart::~BarChart()
+{
+}
+
+PlottingPositionHelper& BarChart::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
+{
+ PlottingPositionHelper& rPosHelper = VSeriesPlotter::getPlottingPositionHelper( nAxisIndex );
+ BarPositionHelper* pBarPosHelper = dynamic_cast<BarPositionHelper*>(&rPosHelper);
+ if( pBarPosHelper && nAxisIndex >= 0 )
+ {
+ if( nAxisIndex < m_aOverlapSequence.getLength() )
+ pBarPosHelper->setInnerDistance( -m_aOverlapSequence[nAxisIndex]/100.0 );
+ if( nAxisIndex < m_aGapwidthSequence.getLength() )
+ pBarPosHelper->setOuterDistance( m_aGapwidthSequence[nAxisIndex]/100.0 );
+ }
+ return rPosHelper;
+}
+
+drawing::Direction3D BarChart::getPreferredDiagramAspectRatio() const
+{
+ drawing::Direction3D aRet(1.0,1.0,1.0);
+ if( m_nDimension == 3 )
+ {
+ aRet = drawing::Direction3D(1.0,-1.0,1.0);
+ BarPositionHelper* pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( MAIN_AXIS_INDEX) ) );
+ if (pPosHelper)
+ {
+ drawing::Direction3D aScale( pPosHelper->getScaledLogicWidth() );
+ if(aScale.DirectionX!=0.0)
+ {
+ double fXSlotCount = 1.0;
+ if(!m_aZSlots.empty())
+ {
+ fXSlotCount = m_aZSlots.begin()->size();
+ }
+ aRet.DirectionZ = aScale.DirectionZ /
+ (aScale.DirectionX + aScale.DirectionX * (fXSlotCount-1.0) * pPosHelper->getScaledSlotWidth());
+ }
+ else
+ {
+ return VSeriesPlotter::getPreferredDiagramAspectRatio();
+ }
+ }
+ else
+ {
+ return VSeriesPlotter::getPreferredDiagramAspectRatio();
+ }
+
+ if(aRet.DirectionZ<0.05)
+ {
+ aRet.DirectionZ=0.05;
+ }
+ else if(aRet.DirectionZ>10)
+ {
+ aRet.DirectionZ=10;
+ }
+ if( m_pMainPosHelper && m_pMainPosHelper->isSwapXAndY() )
+ {
+ double fTemp = aRet.DirectionX;
+ aRet.DirectionX = aRet.DirectionY;
+ aRet.DirectionY = fTemp;
+ }
+ }
+ else
+ aRet = drawing::Direction3D(-1,-1,-1);
+ return aRet;
+}
+
+awt::Point BarChart::getLabelScreenPositionAndAlignment(
+ LabelAlignment& rAlignment, sal_Int32 nLabelPlacement
+ , double fScaledX, double fScaledLowerYValue, double fScaledUpperYValue, double fScaledZ
+ , double fScaledLowerBarDepth, double fScaledUpperBarDepth, double fBaseValue
+ , BarPositionHelper const * pPosHelper
+ ) const
+{
+ double fX = fScaledX;
+ double fY = fScaledUpperYValue;
+ double fZ = fScaledZ;
+ bool bReverse = !pPosHelper->isMathematicalOrientationY();
+ bool bNormalOutside = (!bReverse == (fBaseValue < fScaledUpperYValue));
+ double fDepth = fScaledUpperBarDepth;
+
+ switch(nLabelPlacement)
+ {
+ case css::chart::DataLabelPlacement::TOP:
+ {
+ if( !pPosHelper->isSwapXAndY() )
+ {
+ fY = bReverse ? fScaledLowerYValue : fScaledUpperYValue;
+ rAlignment = LABEL_ALIGN_TOP;
+ if(m_nDimension==3)
+ fDepth = bReverse ? fabs(fScaledLowerBarDepth) : fabs(fScaledUpperBarDepth);
+ }
+ else
+ {
+ fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
+ rAlignment = LABEL_ALIGN_CENTER;
+ OSL_FAIL( "top label placement is not really supported by horizontal bar charts" );
+ }
+ }
+ break;
+ case css::chart::DataLabelPlacement::BOTTOM:
+ {
+ if(!pPosHelper->isSwapXAndY())
+ {
+ fY = bReverse ? fScaledUpperYValue : fScaledLowerYValue;
+ rAlignment = LABEL_ALIGN_BOTTOM;
+ if(m_nDimension==3)
+ fDepth = bReverse ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
+ }
+ else
+ {
+ fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
+ rAlignment = LABEL_ALIGN_CENTER;
+ OSL_FAIL( "bottom label placement is not supported by horizontal bar charts" );
+ }
+ }
+ break;
+ case css::chart::DataLabelPlacement::LEFT:
+ {
+ if( pPosHelper->isSwapXAndY() )
+ {
+ fY = bReverse ? fScaledUpperYValue : fScaledLowerYValue;
+ rAlignment = LABEL_ALIGN_LEFT;
+ if(m_nDimension==3)
+ fDepth = bReverse ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
+ }
+ else
+ {
+ fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
+ rAlignment = LABEL_ALIGN_CENTER;
+ OSL_FAIL( "left label placement is not supported by column charts" );
+ }
+ }
+ break;
+ case css::chart::DataLabelPlacement::RIGHT:
+ {
+ if( pPosHelper->isSwapXAndY() )
+ {
+ fY = bReverse ? fScaledLowerYValue : fScaledUpperYValue;
+ rAlignment = LABEL_ALIGN_RIGHT;
+ if(m_nDimension==3)
+ fDepth = bReverse ? fabs(fScaledLowerBarDepth) : fabs(fScaledUpperBarDepth);
+ }
+ else
+ {
+ fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
+ rAlignment = LABEL_ALIGN_CENTER;
+ OSL_FAIL( "right label placement is not supported by column charts" );
+ }
+ }
+ break;
+ case css::chart::DataLabelPlacement::OUTSIDE:
+ {
+ fY = (fBaseValue < fScaledUpperYValue) ? fScaledUpperYValue : fScaledLowerYValue;
+ if( pPosHelper->isSwapXAndY() )
+ // if datapoint value is 0 the label will appear RIGHT in case of Bar Chart
+ if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
+ rAlignment = LABEL_ALIGN_RIGHT;
+ else
+ rAlignment = bNormalOutside ? LABEL_ALIGN_RIGHT : LABEL_ALIGN_LEFT;
+ else
+ // if datapoint value is 0 the label will appear TOP in case of Column Chart
+ if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
+ rAlignment = LABEL_ALIGN_TOP;
+ else
+ rAlignment = bNormalOutside ? LABEL_ALIGN_TOP : LABEL_ALIGN_BOTTOM;
+ if(m_nDimension==3)
+ fDepth = (fBaseValue < fScaledUpperYValue) ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
+ }
+ break;
+ case css::chart::DataLabelPlacement::INSIDE:
+ {
+ fY = (fBaseValue < fScaledUpperYValue) ? fScaledUpperYValue : fScaledLowerYValue;
+ if( pPosHelper->isSwapXAndY() )
+ rAlignment = bNormalOutside ? LABEL_ALIGN_LEFT : LABEL_ALIGN_RIGHT;
+ else
+ rAlignment = bNormalOutside ? LABEL_ALIGN_BOTTOM : LABEL_ALIGN_TOP;
+ if(m_nDimension==3)
+ fDepth = (fBaseValue < fScaledUpperYValue) ? fabs(fScaledUpperBarDepth) : fabs(fScaledLowerBarDepth);
+ }
+ break;
+ case css::chart::DataLabelPlacement::NEAR_ORIGIN:
+ {
+ fY = (fBaseValue < fScaledUpperYValue) ? fScaledLowerYValue : fScaledUpperYValue;
+ if( pPosHelper->isSwapXAndY() )
+ // if datapoint value is 0 the label will appear RIGHT in case of Bar Chart
+ if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
+ rAlignment = LABEL_ALIGN_RIGHT;
+ else
+ rAlignment = bNormalOutside ? LABEL_ALIGN_RIGHT : LABEL_ALIGN_LEFT;
+ else
+ // if datapoint value is 0 the label will appear TOP in case of Column Chart
+ if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
+ rAlignment = LABEL_ALIGN_TOP;
+ else
+ rAlignment = bNormalOutside ? LABEL_ALIGN_TOP : LABEL_ALIGN_BOTTOM;
+ if(m_nDimension==3)
+ fDepth = (fBaseValue < fScaledUpperYValue) ? fabs(fScaledLowerBarDepth) : fabs(fScaledUpperBarDepth);
+ }
+ break;
+ case css::chart::DataLabelPlacement::CENTER:
+ fY -= (fScaledUpperYValue-fScaledLowerYValue)/2.0;
+ // if datapoint value is 0 the label will appear TOP/RIGHT in case of Column/Bar Charts
+ if( fBaseValue == fScaledUpperYValue && fBaseValue == fScaledLowerYValue )
+ if( pPosHelper->isSwapXAndY() )
+ rAlignment = LABEL_ALIGN_RIGHT;
+ else
+ rAlignment = LABEL_ALIGN_TOP;
+ else
+ rAlignment = LABEL_ALIGN_CENTER;
+ if(m_nDimension==3)
+ fDepth = fabs(fScaledUpperBarDepth-fScaledLowerBarDepth)/2.0;
+ break;
+ default:
+ OSL_FAIL("this label alignment is not implemented yet");
+
+ break;
+ }
+ if(m_nDimension==3)
+ fZ -= fDepth/2.0;
+
+ drawing::Position3D aScenePosition3D( pPosHelper->
+ transformScaledLogicToScene( fX, fY, fZ, true ) );
+ return LabelPositionHelper(m_nDimension,m_xLogicTarget)
+ .transformSceneToScreenPosition( aScenePosition3D );
+}
+
+rtl::Reference< SvxShape > BarChart::createDataPoint3D_Bar(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize
+ , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree
+ , const uno::Reference< beans::XPropertySet >& xObjectProperties
+ , sal_Int32 nGeometry3D )
+{
+ bool bRoundedEdges = true;
+ try
+ {
+ if( xObjectProperties.is() )
+ {
+ sal_Int16 nPercentDiagonal = 0;
+ xObjectProperties->getPropertyValue( "PercentDiagonal" ) >>= nPercentDiagonal;
+ if( nPercentDiagonal < 5 )
+ bRoundedEdges = false;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ rtl::Reference< SvxShape > xShape;
+ switch( nGeometry3D )
+ {
+ case DataPointGeometry3D::CYLINDER:
+ xShape = ShapeFactory::createCylinder( xTarget, rPosition, rSize, nRotateZAngleHundredthDegree );
+ break;
+ case DataPointGeometry3D::CONE:
+ xShape = ShapeFactory::createCone( xTarget, rPosition, rSize, fTopHeight, nRotateZAngleHundredthDegree );
+ break;
+ case DataPointGeometry3D::PYRAMID:
+ xShape = ShapeFactory::createPyramid( xTarget, rPosition, rSize, fTopHeight, nRotateZAngleHundredthDegree>0
+ , xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ break;
+ case DataPointGeometry3D::CUBOID:
+ default:
+ xShape = ShapeFactory::createCube( xTarget, rPosition, rSize
+ , nRotateZAngleHundredthDegree, xObjectProperties
+ , PropertyMapper::getPropertyNameMapForFilledSeriesProperties(), bRoundedEdges );
+ return xShape;
+ }
+ if( nGeometry3D != DataPointGeometry3D::PYRAMID )
+ PropertyMapper::setMappedProperties( *xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ return xShape;
+}
+
+namespace
+{
+bool lcl_hasGeometry3DVariableWidth( sal_Int32 nGeometry3D )
+{
+ bool bRet = false;
+ switch( nGeometry3D )
+ {
+ case DataPointGeometry3D::PYRAMID:
+ case DataPointGeometry3D::CONE:
+ bRet = true;
+ break;
+ case DataPointGeometry3D::CUBOID:
+ case DataPointGeometry3D::CYLINDER:
+ default:
+ bRet = false;
+ break;
+ }
+ return bRet;
+}
+}// end anonymous namespace
+
+void BarChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
+{
+ if( !pSeries )
+ return;
+ if(m_nDimension==2)
+ {
+ //2ND_AXIS_IN_BARS put series on second scales to different z slot as temporary workaround
+ //this needs to be redesigned if 3d bars are also able to display secondary axes
+
+ sal_Int32 nAxisIndex = pSeries->getAttachedAxisIndex();
+ zSlot = nAxisIndex;
+
+ if( !pSeries->getGroupBarsPerAxis() )
+ zSlot = 0;
+ if(zSlot>=static_cast<sal_Int32>(m_aZSlots.size()))
+ m_aZSlots.resize(zSlot+1);
+ }
+ VSeriesPlotter::addSeries( std::move(pSeries), zSlot, xSlot, ySlot );
+}
+
+void BarChart::adaptOverlapAndGapwidthForGroupBarsPerAxis()
+{
+ //adapt m_aOverlapSequence and m_aGapwidthSequence for the groupBarsPerAxis feature
+ //thus the different series use the same settings
+
+ VDataSeries* pFirstSeries = getFirstSeries();
+ if(!pFirstSeries || pFirstSeries->getGroupBarsPerAxis())
+ return;
+
+ sal_Int32 nAxisIndex = pFirstSeries->getAttachedAxisIndex();
+ sal_Int32 nN = 0;
+ sal_Int32 nUseThisIndex = nAxisIndex;
+ if( nUseThisIndex < 0 || nUseThisIndex >= m_aOverlapSequence.getLength() )
+ nUseThisIndex = 0;
+ auto aOverlapSequenceRange = asNonConstRange(m_aOverlapSequence);
+ for( nN = 0; nN < m_aOverlapSequence.getLength(); nN++ )
+ {
+ if(nN!=nUseThisIndex)
+ aOverlapSequenceRange[nN] = m_aOverlapSequence[nUseThisIndex];
+ }
+
+ nUseThisIndex = nAxisIndex;
+ if( nUseThisIndex < 0 || nUseThisIndex >= m_aGapwidthSequence.getLength() )
+ nUseThisIndex = 0;
+ auto aGapwidthSequenceRange = asNonConstRange(m_aGapwidthSequence);
+ for( nN = 0; nN < m_aGapwidthSequence.getLength(); nN++ )
+ {
+ if(nN!=nUseThisIndex)
+ aGapwidthSequenceRange[nN] = m_aGapwidthSequence[nUseThisIndex];
+ }
+}
+
+void BarChart::createShapes()
+{
+ if( m_aZSlots.empty() ) //no series
+ return;
+
+ OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"BarChart is not proper initialized");
+ if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
+ return;
+
+ //the text labels should be always on top of the other series shapes
+ //therefore create an own group for the texts to move them to front
+ //(because the text group is created after the series group the texts are displayed on top)
+
+ //the regression curves should always be on top of the bars but beneath the text labels
+ //to achieve this the regression curve target is created after the series target and before the text target
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTarget = createGroupShape( m_xLogicTarget );
+ rtl::Reference<SvxShapeGroupAnyD> xRegressionCurveTarget = createGroupShape( m_xLogicTarget );
+ rtl::Reference<SvxShapeGroupAnyD> xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+
+ rtl::Reference<SvxShapeGroupAnyD> xRegressionCurveEquationTarget =
+ ShapeFactory::createGroup2D( m_xFinalTarget );
+ //check necessary here that different Y axis can not be stacked in the same group? ... hm?
+
+ bool bDrawConnectionLines = false;
+ bool bDrawConnectionLinesInited = false;
+
+ std::unordered_set<rtl::Reference<SvxShape>> aShapeSet;
+
+ const comphelper::ScopeGuard aGuard([aShapeSet]() {
+
+ std::unordered_set<E3dScene*> aSceneSet;
+
+ for (rtl::Reference<SvxShape> const & rShape : aShapeSet)
+ {
+ E3dScene* pScene = dynamic_cast<E3dScene*>(rShape->GetSdrObject());
+ if(nullptr != pScene)
+ {
+ aSceneSet.insert(pScene->getRootE3dSceneFromE3dObject());
+ }
+ }
+ for (E3dScene* pScene : aSceneSet)
+ {
+ pScene->ResumeReportingDirtyRects();
+ pScene->SetAllSceneRectsDirty();
+ }
+ });
+
+ adaptOverlapAndGapwidthForGroupBarsPerAxis();
+
+ //better performance for big data
+ std::map< VDataSeries*, FormerBarPoint > aSeriesFormerPointMap;
+ m_bPointsWereSkipped = false;
+
+ sal_Int32 nStartIndex = 0;
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ //iterate through all x values per indices
+ for( sal_Int32 nPointIndex = nStartIndex; nPointIndex < nEndIndex; nPointIndex++ )
+ {
+ //sum up the values for all series in a complete z slot per attached axis
+ std::map< sal_Int32, double > aLogicYSumMap;
+ for( auto& rZSlot : m_aZSlots )
+ {
+ for( auto& rXSlot : rZSlot )
+ {
+ sal_Int32 nAttachedAxisIndex = rXSlot.getAttachedAxisIndexForFirstSeries();
+ aLogicYSumMap.insert({nAttachedAxisIndex, 0.0});
+
+ const sal_Int32 nSlotPoints = rXSlot.getPointCount();
+ if( nPointIndex >= nSlotPoints )
+ continue;
+
+ double fMinimumY = 0.0, fMaximumY = 0.0;
+ rXSlot.calculateYMinAndMaxForCategory( nPointIndex
+ , isSeparateStackingForDifferentSigns( 1 ), fMinimumY, fMaximumY, nAttachedAxisIndex );
+
+ if( !std::isnan( fMaximumY ) && fMaximumY > 0)
+ aLogicYSumMap[nAttachedAxisIndex] += fMaximumY;
+ if( !std::isnan( fMinimumY ) && fMinimumY < 0)
+ aLogicYSumMap[nAttachedAxisIndex] += fabs(fMinimumY);
+ }
+ }
+
+ sal_Int32 nZ=1;
+ for( auto& rZSlot : m_aZSlots )
+ {
+ doZSlot(bDrawConnectionLines, bDrawConnectionLinesInited, rZSlot, nZ, nPointIndex, nStartIndex,
+ xSeriesTarget, xRegressionCurveTarget, xRegressionCurveEquationTarget, xTextTarget,
+ aShapeSet, aSeriesFormerPointMap, aLogicYSumMap);
+ ++nZ;
+ }//next z slot
+ }//next category
+ if( bDrawConnectionLines )
+ {
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ BarPositionHelper* pPosHelper = m_pMainPosHelper.get();
+ if( !rZSlot.empty() )
+ {
+ sal_Int32 nAttachedAxisIndex = rZSlot.front().getAttachedAxisIndexForFirstSeries();
+ //2ND_AXIS_IN_BARS so far one can assume to have the same plotter for each z slot
+ pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( nAttachedAxisIndex ) ) );
+ if(!pPosHelper)
+ pPosHelper = m_pMainPosHelper.get();
+ }
+ PlotterBase::m_pPosHelper = pPosHelper;
+
+ //iterate through all x slots in this category
+ for( auto const& rXSlot : rZSlot )
+ {
+ //iterate through all series in this x slot
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+ std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly = &pSeries->m_aPolyPolygonShape3D;
+ if(!ShapeFactory::hasPolygonAnyLines(*pSeriesPoly))
+ continue;
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+ Clipping::clipPolygonAtRectangle( *pSeriesPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+
+ if(!ShapeFactory::hasPolygonAnyLines(aPoly))
+ continue;
+
+ //transformation 3) -> 4)
+ pPosHelper->transformScaledLogicToScene( aPoly );
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes(
+ getSeriesGroupShape(pSeries.get(), xSeriesTarget) );
+ rtl::Reference<SvxShapePolyPolygon> xShape( ShapeFactory::createLine2D(
+ xSeriesGroupShape_Shapes, aPoly ) );
+ PropertyMapper::setMappedProperties( *xShape, pSeries->getPropertiesOfSeries()
+ , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ }
+ }
+ }
+ }
+
+ /* @todo remove series shapes if empty
+ */
+}
+
+void BarChart::doZSlot(
+ bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited,
+ const std::vector< VDataSeriesGroup >& rZSlot,
+ const sal_Int32 nZ, const sal_Int32 nPointIndex, const sal_Int32 nStartIndex,
+ const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet,
+ std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap,
+ std::map< sal_Int32, double >& aLogicYSumMap)
+{
+ //iterate through all x slots in this category
+ double fSlotX=0;
+ for( auto& rXSlot : rZSlot )
+ {
+ sal_Int32 nAttachedAxisIndex = rXSlot.getAttachedAxisIndexForFirstSeries();
+ //2ND_AXIS_IN_BARS so far one can assume to have the same plotter for each z slot
+ BarPositionHelper* pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( nAttachedAxisIndex ) ) );
+ if(!pPosHelper)
+ pPosHelper = m_pMainPosHelper.get();
+
+ PlotterBase::m_pPosHelper = pPosHelper;
+
+ //update/create information for current group
+ pPosHelper->updateSeriesCount( rZSlot.size() );
+ double fLogicBaseWidth = pPosHelper->getScaledSlotWidth();
+
+ // get distance from base value to maximum and minimum
+
+ double fMinimumY = 0.0, fMaximumY = 0.0;
+ if( nPointIndex < rXSlot.getPointCount())
+ rXSlot.calculateYMinAndMaxForCategory( nPointIndex
+ , isSeparateStackingForDifferentSigns( 1 ), fMinimumY, fMaximumY, nAttachedAxisIndex );
+
+ double fLogicPositiveYSum = 0.0;
+ if( !std::isnan( fMaximumY ) )
+ fLogicPositiveYSum = fMaximumY;
+
+ double fLogicNegativeYSum = 0.0;
+ if( !std::isnan( fMinimumY ) )
+ fLogicNegativeYSum = fMinimumY;
+
+ if( pPosHelper->isPercentY() )
+ {
+ /* #i70395# fLogicPositiveYSum contains sum of all positive
+ values, if any, otherwise the highest negative value.
+ fLogicNegativeYSum contains sum of all negative values,
+ if any, otherwise the lowest positive value.
+ Afterwards, fLogicPositiveYSum will contain the maximum
+ (positive) value that is related to 100%. */
+
+ // do nothing if there are positive values only
+ if( fLogicNegativeYSum < 0.0 )
+ {
+ // fLogicPositiveYSum<0 => negative values only, use absolute of negative sum
+ if( fLogicPositiveYSum < 0.0 )
+ fLogicPositiveYSum = -fLogicNegativeYSum;
+ // otherwise there are positive and negative values, calculate total distance
+ else
+ fLogicPositiveYSum -= fLogicNegativeYSum;
+ }
+ fLogicNegativeYSum = 0.0;
+ }
+
+ doXSlot(rXSlot, bDrawConnectionLines, bDrawConnectionLinesInited, nZ, nPointIndex, nStartIndex,
+ xSeriesTarget, xRegressionCurveTarget, xRegressionCurveEquationTarget, xTextTarget,
+ aShapeSet, aSeriesFormerPointMap, aLogicYSumMap,
+ fLogicBaseWidth, fSlotX, pPosHelper, fLogicPositiveYSum, fLogicNegativeYSum, nAttachedAxisIndex);
+
+ fSlotX+=1.0;
+ }//next x slot
+}
+
+
+void BarChart::doXSlot(
+ const VDataSeriesGroup& rXSlot,
+ bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited,
+ const sal_Int32 nZ, const sal_Int32 nPointIndex, const sal_Int32 nStartIndex,
+ const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet,
+ std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap,
+ std::map< sal_Int32, double >& aLogicYSumMap,
+ const double fLogicBaseWidth, const double fSlotX,
+ BarPositionHelper* const pPosHelper,
+ const double fLogicPositiveYSum, const double fLogicNegativeYSum,
+ const sal_Int32 nAttachedAxisIndex)
+{
+ double fBaseValue = 0.0;
+ if( !pPosHelper->isPercentY() && rXSlot.m_aSeriesVector.size()<=1 )
+ fBaseValue = pPosHelper->getBaseValueY();
+ double fPositiveLogicYForNextSeries = fBaseValue;
+ double fNegativeLogicYForNextSeries = fBaseValue;
+
+ //iterate through all series in this x slot
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
+
+ bool bOnlyConnectionLinesForThisPoint = false;
+
+ if(nPointIndex==nStartIndex)//do not create a regression line for each point
+ createRegressionCurvesShapes( *pSeries, xRegressionCurveTarget, xRegressionCurveEquationTarget,
+ m_pPosHelper->maySkipPointsInRegressionCalculation());
+
+ if( !bDrawConnectionLinesInited )
+ {
+ bDrawConnectionLines = pSeries->getConnectBars();
+ if( m_nDimension==3 )
+ bDrawConnectionLines = false;
+ if( bDrawConnectionLines && rXSlot.m_aSeriesVector.size()==1 )
+ {
+ //detect whether we have a stacked chart or not:
+ StackingDirection eDirection = pSeries->getStackingDirection();
+ if( eDirection != StackingDirection_Y_STACKING )
+ bDrawConnectionLines = false;
+ }
+ bDrawConnectionLinesInited = true;
+ }
+
+ // Use another XShapes for background, so we can avoid needing to set the Z-order on all of them,
+ // which is expensive in bulk.
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes(getSeriesGroupShape(pSeries.get(), xSeriesTarget));
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesBackgroundShape_Shapes(getSeriesGroupShape(pSeries.get(), xSeriesTarget));
+ aShapeSet.insert(xSeriesGroupShape_Shapes);
+ aShapeSet.insert(xSeriesBackgroundShape_Shapes);
+ // Suspend setting rects dirty for the duration of this call
+ E3dScene* pScene = dynamic_cast<E3dScene*>(xSeriesGroupShape_Shapes->GetSdrObject());
+ if (pScene)
+ pScene->SuspendReportingDirtyRects();
+ pScene = dynamic_cast<E3dScene*>(xSeriesBackgroundShape_Shapes->GetSdrObject());
+ if (pScene)
+ pScene->SuspendReportingDirtyRects();
+
+ //collect data point information (logic coordinates, style ):
+ double fUnscaledLogicX = pSeries->getXValue( nPointIndex );
+ fUnscaledLogicX = DateHelper::RasterizeDateValue( fUnscaledLogicX, m_aNullDate, m_nTimeResolution );
+ if(std::isnan(fUnscaledLogicX))
+ continue;//point not visible
+ if(fUnscaledLogicX<pPosHelper->getLogicMinX())
+ continue;//point not visible
+ if(fUnscaledLogicX>pPosHelper->getLogicMaxX())
+ continue;//point not visible
+ if(pPosHelper->isStrongLowerRequested(0) && fUnscaledLogicX==pPosHelper->getLogicMaxX())
+ continue;//point not visible
+ double fLogicX = pPosHelper->getScaledSlotPos( fUnscaledLogicX, fSlotX );
+
+ double fLogicBarHeight = pSeries->getYValue( nPointIndex );
+ if( std::isnan( fLogicBarHeight )) //no value at this category
+ continue;
+
+ double fLogicValueForLabeDisplay = fLogicBarHeight;
+ fLogicBarHeight-=fBaseValue;
+
+ if( pPosHelper->isPercentY() )
+ {
+ if(fLogicPositiveYSum!=0.0)
+ fLogicBarHeight = fabs( fLogicBarHeight )/fLogicPositiveYSum;
+ else
+ fLogicBarHeight = 0.0;
+ }
+
+ // tdf#114141 to draw the top of the zero height 3D bar
+ // we set a small positive value, here the smallest one for the type double (DBL_MIN)
+ if( fLogicBarHeight == 0.0 )
+ fLogicBarHeight = DBL_MIN;
+
+ //sort negative and positive values, to display them on different sides of the x axis
+ bool bPositive = fLogicBarHeight >= 0.0;
+ double fLowerYValue = bPositive ? fPositiveLogicYForNextSeries : fNegativeLogicYForNextSeries;
+ double fUpperYValue = fLowerYValue+fLogicBarHeight;
+ if( bPositive )
+ fPositiveLogicYForNextSeries += fLogicBarHeight;
+ else
+ fNegativeLogicYForNextSeries += fLogicBarHeight;
+
+ double fLogicZ = 1.0;//as defined
+ if(m_nDimension==3)
+ fLogicZ = nZ+0.5;
+
+ drawing::Position3D aUnscaledLogicPosition( fUnscaledLogicX, fUpperYValue, fLogicZ );
+
+ //@todo ... start an iteration over the different breaks of the axis
+ //each subsystem may add an additional shape to form the whole point
+ //create a group shape for this point and add to the series shape:
+// uno::Reference< drawing::XShapes > xPointGroupShape_Shapes( createGroupShape(xSeriesGroupShape_Shapes) );
+// uno::Reference<drawing::XShape> xPointGroupShape_Shape =
+// uno::Reference<drawing::XShape>( xPointGroupShape_Shapes, uno::UNO_QUERY );
+ //as long as we do not iterate we do not need to create an additional group for each point
+ uno::Reference< beans::XPropertySet > xDataPointProperties( pSeries->getPropertiesOfPoint( nPointIndex ) );
+ sal_Int32 nGeometry3D = DataPointGeometry3D::CUBOID;
+ if(m_nDimension==3) try
+ {
+ xDataPointProperties->getPropertyValue( "Geometry3D") >>= nGeometry3D;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ //@todo iterate through all subsystems to create partial points
+ {
+ //@todo select a suitable PositionHelper for this subsystem
+ BarPositionHelper* pSubPosHelper = pPosHelper;
+
+ double fUnclippedUpperYValue = fUpperYValue;
+
+ //apply clipping to Y
+ if( !pPosHelper->clipYRange(fLowerYValue,fUpperYValue) )
+ {
+ if( bDrawConnectionLines )
+ bOnlyConnectionLinesForThisPoint = true;
+ else
+ continue;
+ }
+ //@todo clipping of X and Z is not fully integrated so far, as there is a need to create different objects
+
+ //apply scaling to Y before calculating width (necessary to maintain gradient in clipped objects)
+ pSubPosHelper->doLogicScaling(nullptr,&fLowerYValue,nullptr);
+ pSubPosHelper->doLogicScaling(nullptr,&fUpperYValue,nullptr);
+ //scaling of X and Z is not provided as the created objects should be symmetric in that dimensions
+
+ pSubPosHelper->doLogicScaling(nullptr,&fUnclippedUpperYValue,nullptr);
+
+ //calculate resulting width
+ double fCompleteHeight = bPositive ? fLogicPositiveYSum : fLogicNegativeYSum;
+ if( pPosHelper->isPercentY() )
+ fCompleteHeight = 1.0;
+ double fLogicBarWidth = fLogicBaseWidth;
+ double fTopHeight=approxSub(fCompleteHeight,fUpperYValue);
+ if(!bPositive)
+ fTopHeight=approxSub(fCompleteHeight,fLowerYValue);
+ double fLogicYStart = bPositive ? fLowerYValue : fUpperYValue;
+ double fMiddleHeight = fUpperYValue-fLowerYValue;
+ if(!bPositive)
+ fMiddleHeight*=-1.0;
+ double fLogicBarDepth = 0.5;
+ if(m_nDimension==3)
+ {
+ if( lcl_hasGeometry3DVariableWidth(nGeometry3D) && fCompleteHeight!=0.0 )
+ {
+ double fHeight = fCompleteHeight-fLowerYValue;
+ if(!bPositive)
+ fHeight = fCompleteHeight-fUpperYValue;
+ fLogicBarWidth = fLogicBaseWidth*fHeight/fCompleteHeight;
+ if(fLogicBarWidth<=0.0)
+ fLogicBarWidth=fLogicBaseWidth;
+ fLogicBarDepth = fLogicBarDepth*fHeight/fCompleteHeight;
+ if(fLogicBarDepth<=0.0)
+ fLogicBarDepth*=-1.0;
+ }
+ }
+
+ //better performance for big data
+ FormerBarPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] );
+ pPosHelper->setCoordinateSystemResolution( m_aCoordinateSystemResolution );
+ if( !pSeries->isAttributedDataPoint(nPointIndex)
+ &&
+ pPosHelper->isSameForGivenResolution( aFormerPoint.m_fX, aFormerPoint.m_fUpperY, aFormerPoint.m_fZ
+ , fLogicX, fUpperYValue, fLogicZ )
+ &&
+ pPosHelper->isSameForGivenResolution( aFormerPoint.m_fX, aFormerPoint.m_fLowerY, aFormerPoint.m_fZ
+ , fLogicX, fLowerYValue, fLogicZ )
+ )
+ {
+ m_bPointsWereSkipped = true;
+ continue;
+ }
+ aSeriesFormerPointMap[pSeries.get()] = FormerBarPoint(fLogicX,fUpperYValue,fLowerYValue,fLogicZ);
+
+ if( bDrawConnectionLines )
+ {
+ //store point information for connection lines
+
+ drawing::Position3D aLeftUpperPoint( fLogicX-fLogicBarWidth/2.0,fUnclippedUpperYValue,fLogicZ );
+ drawing::Position3D aRightUpperPoint( fLogicX+fLogicBarWidth/2.0,fUnclippedUpperYValue,fLogicZ );
+
+ if( isValidPosition(aLeftUpperPoint) )
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aLeftUpperPoint );
+ if( isValidPosition(aRightUpperPoint) )
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aRightUpperPoint );
+ }
+
+ if( bOnlyConnectionLinesForThisPoint )
+ continue;
+
+ //maybe additional possibility for performance improvement
+ //bool bCreateLineInsteadOfComplexGeometryDueToMissingSpace = false;
+ //pPosHelper->isSameForGivenResolution( fLogicX-fLogicBarWidth/2.0, fLowerYValue, fLogicZ
+ // , fLogicX+fLogicBarWidth/2.0, fLowerYValue, fLogicZ );
+
+ //create partial point
+ if( !approxEqual(fLowerYValue,fUpperYValue) )
+ {
+ rtl::Reference< SvxShape > xShape;
+ if( m_nDimension==3 )
+ {
+ drawing::Position3D aLogicBottom (fLogicX,fLogicYStart,fLogicZ);
+ drawing::Position3D aLogicLeftBottomFront (fLogicX+fLogicBarWidth/2.0,fLogicYStart,fLogicZ-fLogicBarDepth/2.0);
+ drawing::Position3D aLogicRightDeepTop (fLogicX-fLogicBarWidth/2.0,fLogicYStart+fMiddleHeight,fLogicZ+fLogicBarDepth/2.0);
+ drawing::Position3D aLogicTopTop (fLogicX,fLogicYStart+fMiddleHeight+fTopHeight,fLogicZ);
+
+ ::chart::XTransformation2* pTransformation = pSubPosHelper->getTransformationScaledLogicToScene();
+
+ //transformation 3) -> 4)
+ drawing::Position3D aTransformedBottom ( pTransformation->transform( aLogicBottom ) );
+ drawing::Position3D aTransformedLeftBottomFront ( pTransformation->transform( aLogicLeftBottomFront ) );
+ drawing::Position3D aTransformedRightDeepTop ( pTransformation->transform( aLogicRightDeepTop ) );
+ drawing::Position3D aTransformedTopTop ( pTransformation->transform( aLogicTopTop ) );
+
+ drawing::Direction3D aSize = aTransformedRightDeepTop - aTransformedLeftBottomFront;
+ drawing::Direction3D aTopSize( aTransformedTopTop - aTransformedRightDeepTop );
+ fTopHeight = aTopSize.DirectionY;
+
+ sal_Int32 nRotateZAngleHundredthDegree = 0;
+ if( pPosHelper->isSwapXAndY() )
+ {
+ fTopHeight = aTopSize.DirectionX;
+ nRotateZAngleHundredthDegree = 90*100;
+ aSize = drawing::Direction3D(aSize.DirectionY,aSize.DirectionX,aSize.DirectionZ);
+ }
+
+ if( aSize.DirectionX < 0 )
+ aSize.DirectionX *= -1.0;
+ if( aSize.DirectionZ < 0 )
+ aSize.DirectionZ *= -1.0;
+ if( fTopHeight < 0 )
+ fTopHeight *= -1.0;
+
+ xShape = createDataPoint3D_Bar(
+ xSeriesGroupShape_Shapes, aTransformedBottom, aSize, fTopHeight, nRotateZAngleHundredthDegree
+ , xDataPointProperties, nGeometry3D );
+ }
+ else //m_nDimension!=3
+ {
+ drawing::Position3D aLeftUpperPoint( fLogicX-fLogicBarWidth/2.0,fUpperYValue,fLogicZ );
+ drawing::Position3D aRightUpperPoint( fLogicX+fLogicBarWidth/2.0,fUpperYValue,fLogicZ );
+ std::vector<std::vector<css::drawing::Position3D>> aPoly
+ {
+ { // inner vector
+ drawing::Position3D( fLogicX-fLogicBarWidth/2.0,fLowerYValue,fLogicZ),
+ drawing::Position3D( fLogicX+fLogicBarWidth/2.0,fLowerYValue,fLogicZ),
+ aRightUpperPoint,
+ aLeftUpperPoint,
+ drawing::Position3D( fLogicX-fLogicBarWidth/2.0,fLowerYValue,fLogicZ)
+ }
+ };
+ pPosHelper->transformScaledLogicToScene( aPoly );
+ xShape = ShapeFactory::createArea2D( xSeriesGroupShape_Shapes, aPoly );
+ PropertyMapper::setMappedProperties( *xShape, xDataPointProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ }
+
+ if(bHasFillColorMapping)
+ {
+ double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor");
+ if(!std::isnan(nPropVal))
+ {
+ xShape->setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32>(nPropVal)));
+ }
+ }
+ //set name/classified ObjectID (CID)
+ ShapeFactory::setShapeName(xShape
+ , ObjectIdentifier::createPointCID(
+ pSeries->getPointCID_Stub(),nPointIndex) );
+ }
+
+ //create error bar
+ createErrorBar_Y( aUnscaledLogicPosition, *pSeries, nPointIndex, m_xLogicTarget, &fLogicX );
+
+ //create data point label
+ if( pSeries->getDataPointLabelIfLabel(nPointIndex) )
+ {
+ double fLogicSum = aLogicYSumMap[nAttachedAxisIndex];
+
+ LabelAlignment eAlignment(LABEL_ALIGN_CENTER);
+ sal_Int32 nLabelPlacement = pSeries->getLabelPlacement( nPointIndex, m_xChartTypeModel, pPosHelper->isSwapXAndY() );
+
+ double fLowerBarDepth = fLogicBarDepth;
+ double fUpperBarDepth = fLogicBarDepth;
+ {
+ if( lcl_hasGeometry3DVariableWidth(nGeometry3D) && fCompleteHeight!=0.0 )
+ {
+ double fOuterBarDepth = fLogicBarDepth * fTopHeight/(fabs(fCompleteHeight));
+ fLowerBarDepth = (fBaseValue < fUpperYValue) ? fabs(fLogicBarDepth) : fabs(fOuterBarDepth);
+ fUpperBarDepth = (fBaseValue < fUpperYValue) ? fabs(fOuterBarDepth) : fabs(fLogicBarDepth);
+ }
+ }
+
+ awt::Point aScreenPosition2D = getLabelScreenPositionAndAlignment(
+ eAlignment, nLabelPlacement, fLogicX, fLowerYValue, fUpperYValue, fLogicZ,
+ fLowerBarDepth, fUpperBarDepth, fBaseValue, pPosHelper);
+ sal_Int32 nOffset = 0;
+ if(eAlignment!=LABEL_ALIGN_CENTER)
+ {
+ nOffset = 100;//add some spacing //@todo maybe get more intelligent values
+ if( m_nDimension == 3 )
+ nOffset = 260;
+ }
+ createDataLabel(
+ xTextTarget, *pSeries, nPointIndex,
+ fLogicValueForLabeDisplay, fLogicSum, aScreenPosition2D, eAlignment, nOffset);
+ }
+
+ }//end iteration through partial points
+
+ }//next series in x slot (next y slot)
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BarChart.hxx b/chart2/source/view/charttypes/BarChart.hxx
new file mode 100644
index 000000000..52c3b6177
--- /dev/null
+++ b/chart2/source/view/charttypes/BarChart.hxx
@@ -0,0 +1,118 @@
+/* -*- 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 <memory>
+#include <VSeriesPlotter.hxx>
+
+namespace chart
+{
+class BarPositionHelper;
+
+class BarChart : public VSeriesPlotter
+{
+ // public methods
+public:
+ BarChart() = delete;
+
+ BarChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount );
+ virtual ~BarChart() override;
+
+ virtual void createShapes() override;
+ virtual void addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot ) override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+
+private: //methods
+ static rtl::Reference< SvxShape >
+ createDataPoint3D_Bar(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPosition
+ , const css::drawing::Direction3D& rSize
+ , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree
+ , const css::uno::Reference< css::beans::XPropertySet >& xObjectProperties
+ , sal_Int32 nGeometry3D );
+
+ css::awt::Point getLabelScreenPositionAndAlignment(
+ LabelAlignment& rAlignment, sal_Int32 nLabelPlacement
+ , double fScaledX, double fScaledLowerYValue, double fScaledUpperYValue, double fScaledZ
+ , double fScaledLowerBarDepth, double fScaledUpperBarDepth, double fBaseValue
+ , BarPositionHelper const * pPosHelper ) const;
+
+ virtual PlottingPositionHelper& getPlottingPositionHelper( sal_Int32 nAxisIndex ) const override;//nAxisIndex indicates whether the position belongs to the main axis ( nAxisIndex==0 ) or secondary axis ( nAxisIndex==1 )
+
+ void adaptOverlapAndGapwidthForGroupBarsPerAxis();
+
+ //better performance for big data
+ struct FormerBarPoint
+ {
+ FormerBarPoint( double fX, double fUpperY, double fLowerY, double fZ )
+ : m_fX(fX), m_fUpperY(fUpperY), m_fLowerY(fLowerY), m_fZ(fZ)
+ {}
+ FormerBarPoint()
+ : m_fX(std::numeric_limits<double>::quiet_NaN())
+ , m_fUpperY(std::numeric_limits<double>::quiet_NaN())
+ , m_fLowerY(std::numeric_limits<double>::quiet_NaN())
+ , m_fZ(std::numeric_limits<double>::quiet_NaN())
+ {
+ }
+
+ double m_fX;
+ double m_fUpperY;
+ double m_fLowerY;
+ double m_fZ;
+ };
+
+ void doZSlot(
+ bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited, const std::vector< VDataSeriesGroup >& rZSlot,
+ sal_Int32 nZ, sal_Int32 nPointIndex, sal_Int32 nStartIndex,
+ const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet,
+ std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap,
+ std::map< sal_Int32, double >& aLogicYSumMap);
+
+ void doXSlot(
+ const VDataSeriesGroup& rXSlot,
+ bool& bDrawConnectionLines, bool& bDrawConnectionLinesInited,
+ sal_Int32 nZ, sal_Int32 nPointIndex, sal_Int32 nStartIndex,
+ const rtl::Reference<SvxShapeGroupAnyD>& xSeriesTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xRegressionCurveEquationTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ std::unordered_set<rtl::Reference<SvxShape>>& aShapeSet,
+ std::map< VDataSeries*, FormerBarPoint >& aSeriesFormerPointMap,
+ std::map< sal_Int32, double >& aLogicYSumMap,
+ double fLogicBaseWidth, double fSlotX,
+ BarPositionHelper* const pPosHelper,
+ double fLogicPositiveYSum, double fLogicNegativeYSum,
+ sal_Int32 nAttachedAxisIndex);
+
+private: //member
+ std::unique_ptr<BarPositionHelper> m_pMainPosHelper;
+ css::uno::Sequence< sal_Int32 > m_aOverlapSequence;
+ css::uno::Sequence< sal_Int32 > m_aGapwidthSequence;
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BarPositionHelper.cxx b/chart2/source/view/charttypes/BarPositionHelper.cxx
new file mode 100644
index 000000000..f8ac3e7d6
--- /dev/null
+++ b/chart2/source/view/charttypes/BarPositionHelper.cxx
@@ -0,0 +1,73 @@
+/* -*- 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 "BarPositionHelper.hxx"
+#include <DateHelper.hxx>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+BarPositionHelper::BarPositionHelper()
+ : CategoryPositionHelper( 1 )
+{
+ AllowShiftXAxisPos(true);
+ AllowShiftZAxisPos(true);
+}
+
+BarPositionHelper::BarPositionHelper( const BarPositionHelper& rSource )
+ : CategoryPositionHelper( rSource )
+ , PlottingPositionHelper( rSource )
+{
+}
+
+BarPositionHelper::~BarPositionHelper()
+{
+}
+
+std::unique_ptr<PlottingPositionHelper> BarPositionHelper::clone() const
+{
+ return std::make_unique<BarPositionHelper>(*this);
+}
+
+void BarPositionHelper::updateSeriesCount( double fSeriesCount )
+{
+ m_fSeriesCount = fSeriesCount;
+}
+
+double BarPositionHelper::getScaledSlotPos( double fUnscaledLogicX, double fSeriesNumber ) const
+{
+ if( m_bDateAxis )
+ fUnscaledLogicX = DateHelper::RasterizeDateValue( fUnscaledLogicX, m_aNullDate, m_nTimeResolution );
+ double fScaledLogicX(fUnscaledLogicX);
+ doLogicScaling(&fScaledLogicX,nullptr,nullptr);
+ fScaledLogicX = CategoryPositionHelper::getScaledSlotPos( fScaledLogicX, fSeriesNumber );
+ return fScaledLogicX;
+
+}
+
+void BarPositionHelper::setScaledCategoryWidth( double fScaledCategoryWidth )
+{
+ m_fScaledCategoryWidth = fScaledCategoryWidth;
+ CategoryPositionHelper::setCategoryWidth( m_fScaledCategoryWidth );
+}
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BarPositionHelper.hxx b/chart2/source/view/charttypes/BarPositionHelper.hxx
new file mode 100644
index 000000000..961ea988f
--- /dev/null
+++ b/chart2/source/view/charttypes/BarPositionHelper.hxx
@@ -0,0 +1,44 @@
+/* -*- 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 <PlottingPositionHelper.hxx>
+#include "CategoryPositionHelper.hxx"
+
+namespace chart
+{
+class BarPositionHelper : public CategoryPositionHelper, public PlottingPositionHelper
+{
+public:
+ explicit BarPositionHelper();
+ BarPositionHelper(const BarPositionHelper& rSource);
+ virtual ~BarPositionHelper() override;
+
+ virtual std::unique_ptr<PlottingPositionHelper> clone() const override;
+
+ void updateSeriesCount(double fSeriesCount); /*only enter the size of x stacked series*/
+
+ virtual double getScaledSlotPos(double fCategoryX, double fSeriesNumber) const override;
+ virtual void setScaledCategoryWidth(double fScaledCategoryWidth) override;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BubbleChart.cxx b/chart2/source/view/charttypes/BubbleChart.cxx
new file mode 100644
index 000000000..803cf73b2
--- /dev/null
+++ b/chart2/source/view/charttypes/BubbleChart.cxx
@@ -0,0 +1,362 @@
+/* -*- 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 "BubbleChart.hxx"
+#include <PlottingPositionHelper.hxx>
+#include <ShapeFactory.hxx>
+#include <ObjectIdentifier.hxx>
+#include <LabelPositionHelper.hxx>
+#include <ChartType.hxx>
+
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <limits>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+BubbleChart::BubbleChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount, false )
+ , m_fMaxLogicBubbleSize( 0.0 )
+ , m_fBubbleSizeFactorToScreen( 1.0 )
+{
+ // We only support 2 dimensional bubble charts
+ assert(nDimensionCount == 2);
+
+ if( !m_pMainPosHelper )
+ m_pMainPosHelper = new PlottingPositionHelper();
+ PlotterBase::m_pPosHelper = m_pMainPosHelper;
+}
+
+BubbleChart::~BubbleChart()
+{
+ delete m_pMainPosHelper;
+}
+
+void BubbleChart::calculateMaximumLogicBubbleSize()
+{
+ double fMaxSize = 0.0;
+
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ for( sal_Int32 nIndex = 0; nIndex < nEndIndex; nIndex++ )
+ {
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ for( auto const& rXSlot : rZSlot )
+ {
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ double fSize = pSeries->getBubble_Size( nIndex );
+ if( fSize > fMaxSize )
+ fMaxSize = fSize;
+ }
+ }
+ }
+ }
+
+ m_fMaxLogicBubbleSize = fMaxSize;
+}
+
+void BubbleChart::calculateBubbleSizeScalingFactor()
+{
+ double fLogicZ=1.0;
+ drawing::Position3D aSceneMinPos( m_pMainPosHelper->transformLogicToScene( m_pMainPosHelper->getLogicMinX(),m_pMainPosHelper->getLogicMinY(),fLogicZ, false ) );
+ drawing::Position3D aSceneMaxPos( m_pMainPosHelper->transformLogicToScene( m_pMainPosHelper->getLogicMaxX(),m_pMainPosHelper->getLogicMaxY(),fLogicZ, false ) );
+
+ awt::Point aScreenMinPos( LabelPositionHelper(m_nDimension,m_xLogicTarget).transformSceneToScreenPosition( aSceneMinPos ) );
+ awt::Point aScreenMaxPos( LabelPositionHelper(m_nDimension,m_xLogicTarget).transformSceneToScreenPosition( aSceneMaxPos ) );
+
+ sal_Int32 nWidth = abs( aScreenMaxPos.X - aScreenMinPos.X );
+ sal_Int32 nHeight = abs( aScreenMaxPos.Y - aScreenMinPos.Y );
+
+ sal_Int32 nMinExtend = std::min( nWidth, nHeight );
+ m_fBubbleSizeFactorToScreen = nMinExtend * 0.25;//max bubble size is 25 percent of diagram size
+}
+
+drawing::Direction3D BubbleChart::transformToScreenBubbleSize( double fLogicSize )
+{
+ drawing::Direction3D aRet(0,0,0);
+
+ if( std::isnan(fLogicSize) || std::isinf(fLogicSize) )
+ return aRet;
+
+ double fMaxSize = m_fMaxLogicBubbleSize;
+
+ double fMaxRadius = sqrt( fMaxSize / M_PI );
+ double fRadius = sqrt( fLogicSize / M_PI );
+
+ aRet.DirectionX = m_fBubbleSizeFactorToScreen * fRadius / fMaxRadius;
+ aRet.DirectionY = aRet.DirectionX;
+
+ return aRet;
+}
+
+bool BubbleChart::isExpandIfValuesCloseToBorder( sal_Int32 /*nDimensionIndex*/ )
+{
+ return true;
+}
+
+bool BubbleChart::isSeparateStackingForDifferentSigns( sal_Int32 /*nDimensionIndex*/ )
+{
+ return false;
+}
+
+LegendSymbolStyle BubbleChart::getLegendSymbolStyle()
+{
+ return LegendSymbolStyle::Circle;
+}
+
+drawing::Direction3D BubbleChart::getPreferredDiagramAspectRatio() const
+{
+ return drawing::Direction3D(-1,-1,-1);
+}
+
+namespace {
+
+//better performance for big data
+struct FormerPoint
+{
+ FormerPoint( double fX, double fY, double fZ )
+ : m_fX(fX), m_fY(fY), m_fZ(fZ)
+ {}
+ FormerPoint()
+ : m_fX(std::numeric_limits<double>::quiet_NaN())
+ , m_fY(std::numeric_limits<double>::quiet_NaN())
+ , m_fZ(std::numeric_limits<double>::quiet_NaN())
+ {
+ }
+
+ double m_fX;
+ double m_fY;
+ double m_fZ;
+};
+
+}
+
+void BubbleChart::createShapes()
+{
+ if( m_aZSlots.empty() ) //no series
+ return;
+
+ OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"BubbleChart is not proper initialized");
+ if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
+ return;
+
+ //therefore create an own group for the texts and the error bars to move them to front
+ //(because the text group is created after the series group the texts are displayed on top)
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTarget = createGroupShape( m_xLogicTarget );
+ rtl::Reference< SvxShapeGroup > xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+
+ //update/create information for current group
+ double fLogicZ = 1.0;//as defined
+
+ sal_Int32 const nStartIndex = 0; // inclusive ;..todo get somehow from x scale
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ if(nEndIndex<=0)
+ nEndIndex=1;
+
+ //better performance for big data
+ std::map< VDataSeries*, FormerPoint > aSeriesFormerPointMap;
+ m_bPointsWereSkipped = false;
+ sal_Int32 nSkippedPoints = 0;
+ sal_Int32 nCreatedPoints = 0;
+
+ calculateMaximumLogicBubbleSize();
+ calculateBubbleSizeScalingFactor();
+ if( m_fMaxLogicBubbleSize <= 0 || m_fBubbleSizeFactorToScreen <= 0 )
+ return;
+
+ //iterate through all x values per indices
+ for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ )
+ {
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ for( auto const& rXSlot : rZSlot )
+ {
+ //iterate through all series
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
+ bool bHasBorderColorMapping = pSeries->hasPropertyMapping("LineColor");
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries.get(), xSeriesTarget);
+
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ PlottingPositionHelper& rPosHelper
+ = getPlottingPositionHelper(nAttachedAxisIndex);
+ m_pPosHelper = &rPosHelper;
+
+ //collect data point information (logic coordinates, style ):
+ double fLogicX = pSeries->getXValue(nIndex);
+ double fLogicY = pSeries->getYValue(nIndex);
+ double fBubbleSize = pSeries->getBubble_Size( nIndex );
+
+ if( fBubbleSize<0.0 )
+ continue;
+
+ if( fBubbleSize == 0.0 || std::isnan(fBubbleSize) )
+ continue;
+
+ if( std::isnan(fLogicX) || std::isinf(fLogicX)
+ || std::isnan(fLogicY) || std::isinf(fLogicY) )
+ continue;
+
+ bool bIsVisible = rPosHelper.isLogicVisible(fLogicX, fLogicY, fLogicZ);
+
+ drawing::Position3D aUnscaledLogicPosition( fLogicX, fLogicY, fLogicZ );
+ drawing::Position3D aScaledLogicPosition(aUnscaledLogicPosition);
+ rPosHelper.doLogicScaling(aScaledLogicPosition);
+
+ //transformation 3) -> 4)
+ drawing::Position3D aScenePosition(
+ rPosHelper.transformLogicToScene(fLogicX, fLogicY, fLogicZ, false));
+
+ //better performance for big data
+ FormerPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] );
+ rPosHelper.setCoordinateSystemResolution(m_aCoordinateSystemResolution);
+ if (!pSeries->isAttributedDataPoint(nIndex)
+ && rPosHelper.isSameForGivenResolution(
+ aFormerPoint.m_fX, aFormerPoint.m_fY, aFormerPoint.m_fZ,
+ aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY,
+ aScaledLogicPosition.PositionZ))
+ {
+ nSkippedPoints++;
+ m_bPointsWereSkipped = true;
+ continue;
+ }
+ aSeriesFormerPointMap[pSeries.get()] = FormerPoint(aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, aScaledLogicPosition.PositionZ);
+
+ //create a single datapoint if point is visible
+ if( !bIsVisible )
+ continue;
+
+ //create a group shape for this point and add to the series shape:
+ OUString aPointCID = ObjectIdentifier::createPointCID(
+ pSeries->getPointCID_Stub(), nIndex );
+ rtl::Reference<SvxShapeGroupAnyD> xPointGroupShape_Shapes(
+ createGroupShape(xSeriesGroupShape_Shapes,aPointCID) );
+
+ {
+ nCreatedPoints++;
+
+ //create data point
+ drawing::Direction3D aSymbolSize = transformToScreenBubbleSize( fBubbleSize );
+ rtl::Reference<SvxShapeCircle> xShape = ShapeFactory::createCircle2D( xPointGroupShape_Shapes
+ , aScenePosition, aSymbolSize );
+
+ PropertyMapper::setMappedProperties( *xShape
+ , pSeries->getPropertiesOfPoint( nIndex )
+ , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+
+ if(bHasFillColorMapping)
+ {
+ double nPropVal = pSeries->getValueByProperty(nIndex, "FillColor");
+ if(!std::isnan(nPropVal))
+ {
+ xShape->SvxShape::setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32>(nPropVal)));
+ }
+ }
+ if(bHasBorderColorMapping)
+ {
+ double nPropVal = pSeries->getValueByProperty(nIndex, "LineColor");
+ if(!std::isnan(nPropVal))
+ {
+ xShape->SvxShape::setPropertyValue("LineColor", uno::Any(static_cast<sal_Int32>(nPropVal)));
+ }
+ }
+
+ ::chart::ShapeFactory::setShapeName( xShape, "MarkHandles" );
+
+ //create data point label
+ if( pSeries->getDataPointLabelIfLabel(nIndex) )
+ {
+ LabelAlignment eAlignment = LABEL_ALIGN_TOP;
+ drawing::Position3D aScenePosition3D( aScenePosition.PositionX
+ , aScenePosition.PositionY
+ , aScenePosition.PositionZ+getTransformedDepth() );
+
+ sal_Int32 nLabelPlacement = pSeries->getLabelPlacement(
+ nIndex, m_xChartTypeModel, rPosHelper.isSwapXAndY());
+
+ switch(nLabelPlacement)
+ {
+ case css::chart::DataLabelPlacement::TOP:
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ case css::chart::DataLabelPlacement::BOTTOM:
+ aScenePosition3D.PositionY += (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_BOTTOM;
+ break;
+ case css::chart::DataLabelPlacement::LEFT:
+ aScenePosition3D.PositionX -= (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_LEFT;
+ break;
+ case css::chart::DataLabelPlacement::RIGHT:
+ aScenePosition3D.PositionX += (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_RIGHT;
+ break;
+ case css::chart::DataLabelPlacement::CENTER:
+ eAlignment = LABEL_ALIGN_CENTER;
+ break;
+ default:
+ OSL_FAIL("this label alignment is not implemented yet");
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ }
+
+ awt::Point aScreenPosition2D( LabelPositionHelper(m_nDimension,m_xLogicTarget)
+ .transformSceneToScreenPosition( aScenePosition3D ) );
+ sal_Int32 nOffset = 0;
+ if(eAlignment!=LABEL_ALIGN_CENTER)
+ nOffset = 100;//add some spacing //@todo maybe get more intelligent values
+ createDataLabel( xTextTarget, *pSeries, nIndex
+ , fBubbleSize, fBubbleSize, aScreenPosition2D, eAlignment, nOffset );
+ }
+ }
+
+ //remove PointGroupShape if empty
+ if(!xPointGroupShape_Shapes->getCount())
+ xSeriesGroupShape_Shapes->remove(xPointGroupShape_Shapes);
+
+ }//next series in x slot (next y slot)
+ }//next x slot
+ }//next z slot
+ }//next category
+ SAL_INFO(
+ "chart2",
+ "skipped points: " << nSkippedPoints << " created points: "
+ << nCreatedPoints);
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/BubbleChart.hxx b/chart2/source/view/charttypes/BubbleChart.hxx
new file mode 100644
index 000000000..c25e5b634
--- /dev/null
+++ b/chart2/source/view/charttypes/BubbleChart.hxx
@@ -0,0 +1,60 @@
+/* -*- 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 <VSeriesPlotter.hxx>
+#include <com/sun/star/drawing/Direction3D.hpp>
+
+namespace chart
+{
+
+class BubbleChart : public VSeriesPlotter
+{
+ // public methods
+public:
+ BubbleChart() = delete;
+
+ BubbleChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount );
+ virtual ~BubbleChart() override;
+
+ virtual void createShapes() override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+
+ // MinimumAndMaximumSupplier
+ virtual bool isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex ) override;
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+ virtual LegendSymbolStyle getLegendSymbolStyle() override;
+
+private: //methods
+ void calculateMaximumLogicBubbleSize();
+ void calculateBubbleSizeScalingFactor();
+
+ css::drawing::Direction3D transformToScreenBubbleSize( double fLogicSize );
+
+private: //member
+
+ double m_fMaxLogicBubbleSize;//calculated values
+ double m_fBubbleSizeFactorToScreen;//calculated values
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/CandleStickChart.cxx b/chart2/source/view/charttypes/CandleStickChart.cxx
new file mode 100644
index 000000000..5c8497a5d
--- /dev/null
+++ b/chart2/source/view/charttypes/CandleStickChart.cxx
@@ -0,0 +1,314 @@
+/* -*- 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 "CandleStickChart.hxx"
+#include <ChartType.hxx>
+#include <ShapeFactory.hxx>
+#include <CommonConverters.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <ObjectIdentifier.hxx>
+#include "BarPositionHelper.hxx"
+#include <DateHelper.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <tools/diagnose_ex.h>
+#include <osl/diagnose.h>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+CandleStickChart::CandleStickChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount )
+ , m_pMainPosHelper( new BarPositionHelper() )
+{
+ PlotterBase::m_pPosHelper = m_pMainPosHelper.get();
+ VSeriesPlotter::m_pMainPosHelper = m_pMainPosHelper.get();
+}
+
+CandleStickChart::~CandleStickChart()
+{
+}
+
+// MinimumAndMaximumSupplier
+
+bool CandleStickChart::isSeparateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+LegendSymbolStyle CandleStickChart::getLegendSymbolStyle()
+{
+ return LegendSymbolStyle::Line;
+}
+
+drawing::Direction3D CandleStickChart::getPreferredDiagramAspectRatio() const
+{
+ return drawing::Direction3D(-1,-1,-1);
+}
+
+void CandleStickChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 /* zSlot */, sal_Int32 xSlot, sal_Int32 ySlot )
+{
+ //ignore y stacking for candle stick chart
+ VSeriesPlotter::addSeries( std::move(pSeries), 0, xSlot, ySlot );
+}
+
+void CandleStickChart::createShapes()
+{
+ if( m_aZSlots.empty() ) //no series
+ return;
+
+ if( m_nDimension!=2 )
+ return;
+
+ OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"CandleStickChart is not proper initialized");
+ if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
+ return;
+
+ //the text labels should be always on top of the other series shapes
+ //therefore create an own group for the texts to move them to front
+ //(because the text group is created after the series group the texts are displayed on top)
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTarget =
+ createGroupShape( m_xLogicTarget );
+ rtl::Reference<SvxShapeGroupAnyD> xLossTarget =
+ createGroupShape( m_xLogicTarget, ObjectIdentifier::createClassifiedIdentifier(
+ OBJECTTYPE_DATA_STOCK_LOSS, u"" ));
+ rtl::Reference<SvxShapeGroupAnyD> xGainTarget =
+ createGroupShape( m_xLogicTarget, ObjectIdentifier::createClassifiedIdentifier(
+ OBJECTTYPE_DATA_STOCK_GAIN, u"" ));
+ rtl::Reference< SvxShapeGroup > xTextTarget =
+ ShapeFactory::createGroup2D( m_xFinalTarget );
+
+ //check necessary here that different Y axis can not be stacked in the same group? ... hm?
+
+ bool bJapaneseStyle=true;//@todo is this the correct default?
+ bool bShowFirst = true;//is only important if bJapaneseStyle == false
+ tNameSequence aWhiteBox_Names, aBlackBox_Names;
+ tAnySequence aWhiteBox_Values, aBlackBox_Values;
+ try
+ {
+ if( m_xChartTypeModel.is() )
+ {
+ m_xChartTypeModel->getPropertyValue( "ShowFirst" ) >>= bShowFirst;
+
+ uno::Reference< beans::XPropertySet > xWhiteDayProps;
+ uno::Reference< beans::XPropertySet > xBlackDayProps;
+ m_xChartTypeModel->getPropertyValue( "Japanese" ) >>= bJapaneseStyle;
+ m_xChartTypeModel->getPropertyValue( "WhiteDay" ) >>= xWhiteDayProps;
+ m_xChartTypeModel->getPropertyValue( "BlackDay" ) >>= xBlackDayProps;
+
+ tPropertyNameValueMap aWhiteBox_Map;
+ PropertyMapper::getValueMap( aWhiteBox_Map, PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xWhiteDayProps );
+ PropertyMapper::getMultiPropertyListsFromValueMap( aWhiteBox_Names, aWhiteBox_Values, aWhiteBox_Map );
+
+ tPropertyNameValueMap aBlackBox_Map;
+ PropertyMapper::getValueMap( aBlackBox_Map, PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xBlackDayProps );
+ PropertyMapper::getMultiPropertyListsFromValueMap( aBlackBox_Names, aBlackBox_Values, aBlackBox_Map );
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ //(@todo maybe different iteration for breaks in axis ?)
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ double fLogicZ = 1.5;//as defined
+ //iterate through all x values per indices
+ for( sal_Int32 nIndex = 0; nIndex < nEndIndex; nIndex++ )
+ {
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ BarPositionHelper* pPosHelper = m_pMainPosHelper.get();
+ if( !rZSlot.empty() )
+ {
+ sal_Int32 nAttachedAxisIndex = rZSlot.front().getAttachedAxisIndexForFirstSeries();
+ //2ND_AXIS_IN_BARS so far one can assume to have the same plotter for each z slot
+ pPosHelper = dynamic_cast<BarPositionHelper*>(&( getPlottingPositionHelper( nAttachedAxisIndex ) ) );
+ if(!pPosHelper)
+ pPosHelper = m_pMainPosHelper.get();
+ }
+ PlotterBase::m_pPosHelper = pPosHelper;
+
+ //update/create information for current group
+ pPosHelper->updateSeriesCount( rZSlot.size() );
+ double fSlotX=0;
+ //iterate through all x slots in this category
+ for( auto const& rXSlot : rZSlot )
+ {
+ //iterate through all series in this x slot
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ //collect data point information (logic coordinates, style ):
+ double fUnscaledX = pSeries->getXValue( nIndex );
+ if( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() )
+ fUnscaledX = DateHelper::RasterizeDateValue( fUnscaledX, m_aNullDate, m_nTimeResolution );
+ if(fUnscaledX<pPosHelper->getLogicMinX() || fUnscaledX>pPosHelper->getLogicMaxX())
+ continue;//point not visible
+ double fScaledX = pPosHelper->getScaledSlotPos( fUnscaledX, fSlotX );
+
+ double fUnscaledY_First = pSeries->getY_First( nIndex );
+ double fUnscaledY_Last = pSeries->getY_Last( nIndex );
+ double fUnscaledY_Min = pSeries->getY_Min( nIndex );
+ double fUnscaledY_Max = pSeries->getY_Max( nIndex );
+
+ bool bBlack=false;
+ if(fUnscaledY_Last<=fUnscaledY_First)
+ {
+ std::swap(fUnscaledY_First,fUnscaledY_Last);
+ bBlack=true;
+ }
+ if(fUnscaledY_Max<fUnscaledY_Min)
+ std::swap(fUnscaledY_Min,fUnscaledY_Max);
+ //transformation 3) -> 4)
+ double fHalfScaledWidth = pPosHelper->getScaledSlotWidth()/2.0;
+
+ double fScaledY_First(fUnscaledY_First);
+ double fScaledY_Last(fUnscaledY_Last);
+ double fScaledY_Min(fUnscaledY_Min);
+ double fScaledY_Max(fUnscaledY_Max);
+ pPosHelper->clipLogicValues( nullptr,&fScaledY_First,nullptr );
+ pPosHelper->clipLogicValues( nullptr,&fScaledY_Last,nullptr );
+ pPosHelper->clipLogicValues( nullptr,&fScaledY_Min,nullptr );
+ pPosHelper->clipLogicValues( nullptr,&fScaledY_Max,nullptr );
+ pPosHelper->doLogicScaling( nullptr,&fScaledY_First,nullptr );
+ pPosHelper->doLogicScaling( nullptr,&fScaledY_Last,nullptr );
+ pPosHelper->doLogicScaling( nullptr,&fScaledY_Min,nullptr );
+ pPosHelper->doLogicScaling( nullptr,&fScaledY_Max,nullptr );
+
+ drawing::Position3D aPosLeftFirst( pPosHelper->transformScaledLogicToScene( fScaledX-fHalfScaledWidth, fScaledY_First ,0 ,true ) );
+ drawing::Position3D aPosRightLast( pPosHelper->transformScaledLogicToScene( fScaledX+fHalfScaledWidth, fScaledY_Last ,0 ,true ) );
+ drawing::Position3D aPosMiddleFirst( pPosHelper->transformScaledLogicToScene( fScaledX, fScaledY_First ,0 ,true ) );
+ drawing::Position3D aPosMiddleLast( pPosHelper->transformScaledLogicToScene( fScaledX, fScaledY_Last ,0 ,true ) );
+ drawing::Position3D aPosMiddleMinimum( pPosHelper->transformScaledLogicToScene( fScaledX, fScaledY_Min ,0 ,true ) );
+ drawing::Position3D aPosMiddleMaximum( pPosHelper->transformScaledLogicToScene( fScaledX, fScaledY_Max ,0 ,true ) );
+
+ rtl::Reference<SvxShapeGroupAnyD> xLossGainTarget( xGainTarget );
+ if(bBlack)
+ xLossGainTarget = xLossTarget;
+
+ uno::Reference< beans::XPropertySet > xPointProp( pSeries->getPropertiesOfPoint( nIndex ));
+ rtl::Reference<SvxShapeGroupAnyD> xPointGroupShape_Shapes;
+ {
+ OUString aPointCID = ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nIndex );
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes( getSeriesGroupShape(pSeries.get(), xSeriesTarget) );
+ xPointGroupShape_Shapes = createGroupShape(xSeriesGroupShape_Shapes,aPointCID);
+ }
+
+ //create min-max line
+ if( isValidPosition(aPosMiddleMinimum) && isValidPosition(aPosMiddleMaximum) )
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aPoly
+ {
+ { aPosMiddleMinimum, aPosMiddleMaximum }
+ };
+
+ rtl::Reference<SvxShapePolyPolygon> xShape =
+ ShapeFactory::createLine2D( xPointGroupShape_Shapes, aPoly);
+ PropertyMapper::setMappedProperties( *xShape, xPointProp, PropertyMapper::getPropertyNameMapForLineSeriesProperties() );
+ }
+
+ //create first-last shape
+ if(bJapaneseStyle && isValidPosition(aPosLeftFirst) && isValidPosition(aPosRightLast) )
+ {
+ drawing::Direction3D aDiff = aPosRightLast-aPosLeftFirst;
+ awt::Size aAWTSize( Direction3DToAWTSize( aDiff ));
+ // workaround for bug in drawing: if height is 0 the box gets infinitely large
+ if( aAWTSize.Height == 0 )
+ aAWTSize.Height = 1;
+
+ tNameSequence aNames;
+ tAnySequence aValues;
+
+ rtl::Reference<SvxShapeRect> xShape =
+ ShapeFactory::createRectangle( xLossGainTarget,
+ aAWTSize, Position3DToAWTPoint( aPosLeftFirst ),
+ aNames, aValues);
+
+ if(bBlack)
+ PropertyMapper::setMultiProperties( aBlackBox_Names, aBlackBox_Values, *xShape );
+ else
+ PropertyMapper::setMultiProperties( aWhiteBox_Names, aWhiteBox_Values, *xShape );
+ }
+ else
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+
+ sal_Int32 nLineIndex = 0;
+ if( bShowFirst && pPosHelper->isLogicVisible( fUnscaledX, fUnscaledY_First ,fLogicZ )
+ && isValidPosition(aPosLeftFirst) && isValidPosition(aPosMiddleFirst) )
+ {
+ AddPointToPoly( aPoly, aPosLeftFirst, nLineIndex );
+ AddPointToPoly( aPoly, aPosMiddleFirst, nLineIndex++ );
+ }
+ if( pPosHelper->isLogicVisible( fUnscaledX, fUnscaledY_Last ,fLogicZ )
+ && isValidPosition(aPosMiddleLast) && isValidPosition(aPosRightLast) )
+ {
+ AddPointToPoly( aPoly, aPosMiddleLast, nLineIndex );
+ AddPointToPoly( aPoly, aPosRightLast, nLineIndex );
+ }
+
+ if( !aPoly.empty() )
+ {
+ rtl::Reference<SvxShapePolyPolygon> xShape =
+ ShapeFactory::createLine2D( xPointGroupShape_Shapes, aPoly );
+ PropertyMapper::setMappedProperties( *xShape, xPointProp, PropertyMapper::getPropertyNameMapForLineSeriesProperties() );
+ }
+ }
+
+ //create data point label
+ if( pSeries->getDataPointLabelIfLabel(nIndex) )
+ {
+ if(isValidPosition(aPosMiddleFirst))
+ createDataLabel( xTextTarget, *pSeries, nIndex
+ , fUnscaledY_First, 1.0, Position3DToAWTPoint(aPosMiddleFirst), LABEL_ALIGN_LEFT_BOTTOM );
+ if(isValidPosition(aPosMiddleLast))
+ createDataLabel( xTextTarget, *pSeries, nIndex
+ , fUnscaledY_Last, 1.0, Position3DToAWTPoint(aPosMiddleLast), LABEL_ALIGN_RIGHT_TOP );
+ if(isValidPosition(aPosMiddleMinimum))
+ createDataLabel( xTextTarget, *pSeries, nIndex
+ , fUnscaledY_Min, 1.0, Position3DToAWTPoint(aPosMiddleMinimum), LABEL_ALIGN_BOTTOM );
+ if(isValidPosition(aPosMiddleMaximum))
+ createDataLabel( xTextTarget, *pSeries, nIndex
+ , fUnscaledY_Max, 1.0, Position3DToAWTPoint(aPosMiddleMaximum), LABEL_ALIGN_TOP );
+ }
+ }//next series in x slot (next y slot)
+ fSlotX+=1.0;
+ }//next x slot
+ }//next z slot
+ }//next category
+ /* @todo remove series shapes if empty
+ //remove and delete point-group-shape if empty
+ if(!xSeriesGroupShape_Shapes->getCount())
+ {
+ pSeries->m_xShape.set(NULL);
+ m_xLogicTarget->remove(xSeriesGroupShape_Shape);
+ }
+ */
+
+ //remove and delete series-group-shape if empty
+
+ //... todo
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/CandleStickChart.hxx b/chart2/source/view/charttypes/CandleStickChart.hxx
new file mode 100644
index 000000000..93571889e
--- /dev/null
+++ b/chart2/source/view/charttypes/CandleStickChart.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 <memory>
+#include <VSeriesPlotter.hxx>
+
+namespace chart
+{
+class BarPositionHelper;
+
+class CandleStickChart : public VSeriesPlotter
+{
+ // public methods
+public:
+ CandleStickChart() = delete;
+
+ CandleStickChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount );
+ virtual ~CandleStickChart() override;
+
+ virtual void createShapes() override;
+ virtual void addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot ) override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+
+ // MinimumAndMaximumSupplier
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+ virtual LegendSymbolStyle getLegendSymbolStyle() override;
+
+private: //member
+ std::unique_ptr<BarPositionHelper> m_pMainPosHelper;
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/CategoryPositionHelper.cxx b/chart2/source/view/charttypes/CategoryPositionHelper.cxx
new file mode 100644
index 000000000..d7412d3cb
--- /dev/null
+++ b/chart2/source/view/charttypes/CategoryPositionHelper.cxx
@@ -0,0 +1,82 @@
+/* -*- 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 "CategoryPositionHelper.hxx"
+
+namespace chart
+{
+
+CategoryPositionHelper::CategoryPositionHelper( double fSeriesCount, double fCategoryWidth )
+ : m_fSeriesCount(fSeriesCount)
+ , m_fCategoryWidth(fCategoryWidth)
+ , m_fInnerDistance(0.0)
+ , m_fOuterDistance(1.0)
+{
+}
+
+CategoryPositionHelper::~CategoryPositionHelper()
+{
+}
+
+double CategoryPositionHelper::getScaledSlotWidth() const
+{
+ double fWidth = m_fCategoryWidth /
+ ( m_fSeriesCount
+ + m_fOuterDistance
+ + m_fInnerDistance*( m_fSeriesCount - 1.0) );
+ return fWidth;
+}
+
+double CategoryPositionHelper::getScaledSlotPos( double fScaledXPos, double fSeriesNumber ) const
+{
+ //the returned position is in the middle of the rect
+ //fSeriesNumber 0...n-1
+ double fPos = fScaledXPos
+ - (m_fCategoryWidth/2.0)
+ + (m_fOuterDistance/2.0 + fSeriesNumber*(1.0+m_fInnerDistance)) * getScaledSlotWidth()
+ + getScaledSlotWidth()/2.0;
+ return fPos;
+}
+
+void CategoryPositionHelper::setInnerDistance( double fInnerDistance )
+{
+ if( fInnerDistance < -1.0 )
+ fInnerDistance = -1.0;
+ if( fInnerDistance > 1.0 )
+ fInnerDistance = 1.0;
+ m_fInnerDistance = fInnerDistance;
+}
+
+void CategoryPositionHelper::setOuterDistance( double fOuterDistance )
+{
+ if( fOuterDistance < 0.0 )
+ fOuterDistance = 0.0;
+ if( fOuterDistance > 6.0 )
+ fOuterDistance = 6.0;
+ m_fOuterDistance = fOuterDistance;
+}
+
+void CategoryPositionHelper::setCategoryWidth( double fCategoryWidth )
+{
+ m_fCategoryWidth = fCategoryWidth;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/CategoryPositionHelper.hxx b/chart2/source/view/charttypes/CategoryPositionHelper.hxx
new file mode 100644
index 000000000..ebc784ef6
--- /dev/null
+++ b/chart2/source/view/charttypes/CategoryPositionHelper.hxx
@@ -0,0 +1,57 @@
+/* -*- 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
+
+namespace chart
+{
+class CategoryPositionHelper
+{
+public:
+ CategoryPositionHelper(double fSeriesCount, double CategoryWidth = 1.0);
+ virtual ~CategoryPositionHelper();
+
+ CategoryPositionHelper(CategoryPositionHelper const&) = default;
+ CategoryPositionHelper(CategoryPositionHelper&&) = default;
+ CategoryPositionHelper& operator=(CategoryPositionHelper const&) = default;
+ CategoryPositionHelper& operator=(CategoryPositionHelper&&) = default;
+
+ double getScaledSlotWidth() const;
+ virtual double getScaledSlotPos(double fCategoryX, double fSeriesNumber) const;
+ void setCategoryWidth(double fCategoryWidth);
+
+ //Distance between two neighboring bars in same category, seen relative to width of the bar
+ void setInnerDistance(double fInnerDistance);
+
+ //Distance between two neighboring bars in different category, seen relative to width of the bar:
+ void setOuterDistance(double fOuterDistance);
+
+protected:
+ double m_fSeriesCount;
+ double m_fCategoryWidth;
+ //Distance between two neighboring bars in same category, seen relative to width of the bar:
+ double
+ m_fInnerDistance; //[-1,1] m_fInnerDistance=1 --> distance == width; m_fInnerDistance=-1-->all rects are painted on the same position
+ //Distance between two neighboring bars in different category, seen relative to width of the bar:
+ double m_fOuterDistance; //>=0 m_fOuterDistance=1 --> distance == width
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/ConfigAccess.cxx b/chart2/source/view/charttypes/ConfigAccess.cxx
new file mode 100644
index 000000000..ce02c4817
--- /dev/null
+++ b/chart2/source/view/charttypes/ConfigAccess.cxx
@@ -0,0 +1,74 @@
+/* -*- 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 <ConfigAccess.hxx>
+
+#include <unotools/configitem.hxx>
+#include <o3tl/any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+
+namespace
+{
+class ChartConfigItem : public ::utl::ConfigItem
+{
+private:
+ virtual void ImplCommit() override;
+
+public:
+ ChartConfigItem();
+
+ bool getUseErrorRectangle();
+ virtual void Notify(const uno::Sequence<OUString>& aPropertyNames) override;
+};
+}
+
+ChartConfigItem::ChartConfigItem()
+ : ConfigItem("Office.Chart/ErrorProperties")
+{
+}
+
+void ChartConfigItem::ImplCommit() {}
+void ChartConfigItem::Notify(const uno::Sequence<OUString>&) {}
+
+bool ChartConfigItem::getUseErrorRectangle()
+{
+ uno::Sequence<OUString> aNames{ "ErrorRectangle" };
+
+ auto b = o3tl::tryAccess<bool>(GetProperties(aNames)[0]);
+ return b && *b;
+}
+
+namespace ConfigAccess
+{
+bool getUseErrorRectangle()
+{
+ //a ChartConfigItem Singleton
+ static ChartConfigItem SINGLETON;
+ bool bResult(SINGLETON.getUseErrorRectangle());
+ return bResult;
+}
+} //namespace ConfigAccess
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/NetChart.cxx b/chart2/source/view/charttypes/NetChart.cxx
new file mode 100644
index 000000000..5b8f1db34
--- /dev/null
+++ b/chart2/source/view/charttypes/NetChart.cxx
@@ -0,0 +1,642 @@
+/* -*- 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 "NetChart.hxx"
+#include <PlottingPositionHelper.hxx>
+#include <ShapeFactory.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <CommonConverters.hxx>
+#include <ObjectIdentifier.hxx>
+#include <LabelPositionHelper.hxx>
+#include <Clipping.hxx>
+#include <PolarLabelPositionHelper.hxx>
+#include <DateHelper.hxx>
+#include <ChartType.hxx>
+
+#include <com/sun/star/chart2/Symbol.hpp>
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+#include <com/sun/star/chart/MissingValueTreatment.hpp>
+
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <officecfg/Office/Compatibility.hxx>
+
+#include <limits>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+NetChart::NetChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bNoArea
+ , std::unique_ptr<PlottingPositionHelper> pPlottingPositionHelper
+ )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount, true )
+ , m_pMainPosHelper(std::move(pPlottingPositionHelper))
+ , m_bArea(!bNoArea)
+ , m_bLine(bNoArea)
+{
+ // we only support 2D Net charts
+ assert(nDimensionCount == 2);
+
+ m_pMainPosHelper->AllowShiftXAxisPos(true);
+ m_pMainPosHelper->AllowShiftZAxisPos(true);
+
+ PlotterBase::m_pPosHelper = m_pMainPosHelper.get();
+ VSeriesPlotter::m_pMainPosHelper = m_pMainPosHelper.get();
+}
+
+NetChart::~NetChart()
+{
+}
+
+double NetChart::getMaximumX()
+{
+ double fMax = VSeriesPlotter::getMaximumX() + 1.0;
+ return fMax;
+}
+
+bool NetChart::isExpandIfValuesCloseToBorder( sal_Int32 )
+{
+ return false;
+}
+
+bool NetChart::isSeparateStackingForDifferentSigns( sal_Int32 /*nDimensionIndex*/ )
+{
+ // no separate stacking in all types of line/area charts
+ return false;
+}
+
+LegendSymbolStyle NetChart::getLegendSymbolStyle()
+{
+ if( m_bArea )
+ return LegendSymbolStyle::Box;
+ return LegendSymbolStyle::Line;
+}
+
+uno::Any NetChart::getExplicitSymbol( const VDataSeries& rSeries, sal_Int32 nPointIndex )
+{
+ uno::Any aRet;
+
+ Symbol* pSymbolProperties = rSeries.getSymbolProperties( nPointIndex );
+ if( pSymbolProperties )
+ {
+ aRet <<= *pSymbolProperties;
+ }
+
+ return aRet;
+}
+
+drawing::Direction3D NetChart::getPreferredDiagramAspectRatio() const
+{
+ return drawing::Direction3D(1,1,1);
+}
+
+bool NetChart::impl_createLine( VDataSeries* pSeries
+ , const std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly
+ , PlottingPositionHelper const * pPosHelper )
+{
+ //return true if a line was created successfully
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget);
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+ {
+ bool bIsClipped = false;
+ if( !ShapeFactory::isPolygonEmptyOrSinglePoint(*pSeriesPoly) )
+ {
+ // do NOT connect last and first point, if one is NAN, and NAN handling is NAN_AS_GAP
+ double fFirstY = pSeries->getYValue( 0 );
+ double fLastY = pSeries->getYValue( VSeriesPlotter::getPointCount() - 1 );
+ if( (pSeries->getMissingValueTreatment() != css::chart::MissingValueTreatment::LEAVE_GAP)
+ || (std::isfinite( fFirstY ) && std::isfinite( fLastY )) )
+ {
+ // connect last point in last polygon with first point in first polygon
+ ::basegfx::B2DRectangle aScaledLogicClipDoubleRect( pPosHelper->getScaledLogicClipDoubleRect() );
+ std::vector<std::vector<css::drawing::Position3D>> aTmpPoly(*pSeriesPoly);
+ drawing::Position3D aLast(aScaledLogicClipDoubleRect.getMaxX(),aTmpPoly[0][0].PositionY,aTmpPoly[0][0].PositionZ);
+ // add connector line to last polygon
+ AddPointToPoly( aTmpPoly, aLast, pSeriesPoly->size() - 1 );
+ Clipping::clipPolygonAtRectangle( aTmpPoly, aScaledLogicClipDoubleRect, aPoly );
+ bIsClipped = true;
+ }
+ }
+
+ if( !bIsClipped )
+ Clipping::clipPolygonAtRectangle( *pSeriesPoly, pPosHelper->getScaledLogicClipDoubleRect(), aPoly );
+ }
+
+ if(!ShapeFactory::hasPolygonAnyLines(aPoly))
+ return false;
+
+ //transformation 3) -> 4)
+ pPosHelper->transformScaledLogicToScene( aPoly );
+
+ //create line:
+ rtl::Reference<SvxShapePolyPolygon> xShape;
+ {
+ xShape = ShapeFactory::createLine2D( xSeriesGroupShape_Shapes, aPoly );
+ PropertyMapper::setMappedProperties( *xShape
+ , pSeries->getPropertiesOfSeries()
+ , PropertyMapper::getPropertyNameMapForLineSeriesProperties() );
+ //because of this name this line will be used for marking
+ ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles");
+ }
+ return true;
+}
+
+bool NetChart::impl_createArea( VDataSeries* pSeries
+ , const std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly
+ , std::vector<std::vector<css::drawing::Position3D>> const * pPreviousSeriesPoly
+ , PlottingPositionHelper const * pPosHelper )
+{
+ //return true if an area was created successfully
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeBackChild(pSeries, m_xSeriesTarget);
+ double zValue = pSeries->m_fLogicZPos;
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly( *pSeriesPoly );
+ //add second part to the polygon (grounding points or previous series points)
+ if( !ShapeFactory::isPolygonEmptyOrSinglePoint(*pSeriesPoly) )
+ {
+ if( pPreviousSeriesPoly )
+ addPolygon( aPoly, *pPreviousSeriesPoly );
+ }
+ else if(!pPreviousSeriesPoly)
+ {
+ double fMinX = pSeries->m_fLogicMinX;
+ double fMaxX = pSeries->m_fLogicMaxX;
+ double fY = pPosHelper->getBaseValueY();//logic grounding
+
+ //clip to scale
+ if(fMaxX<pPosHelper->getLogicMinX() || fMinX>pPosHelper->getLogicMaxX())
+ return false;//no visible shape needed
+ pPosHelper->clipLogicValues( &fMinX, &fY, nullptr );
+ pPosHelper->clipLogicValues( &fMaxX, nullptr, nullptr );
+
+ //apply scaling
+ {
+ pPosHelper->doLogicScaling( &fMinX, &fY, &zValue );
+ pPosHelper->doLogicScaling( &fMaxX, nullptr, nullptr );
+ }
+
+ AddPointToPoly( aPoly, drawing::Position3D( fMaxX,fY,zValue) );
+ AddPointToPoly( aPoly, drawing::Position3D( fMinX,fY,zValue) );
+ }
+ else
+ {
+ appendPoly( aPoly, *pPreviousSeriesPoly );
+ }
+ ShapeFactory::closePolygon(aPoly);
+
+ //apply clipping
+ {
+ std::vector<std::vector<css::drawing::Position3D>> aClippedPoly;
+ Clipping::clipPolygonAtRectangle( aPoly, pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly, false );
+ ShapeFactory::closePolygon(aClippedPoly); //again necessary after clipping
+ aPoly = aClippedPoly;
+ }
+
+ if(!ShapeFactory::hasPolygonAnyLines(aPoly))
+ return false;
+
+ //transformation 3) -> 4)
+ pPosHelper->transformScaledLogicToScene( aPoly );
+
+ //create area:
+ rtl::Reference<SvxShapePolyPolygon>
+ xShape = ShapeFactory::createArea2D( xSeriesGroupShape_Shapes
+ , aPoly );
+ PropertyMapper::setMappedProperties( *xShape
+ , pSeries->getPropertiesOfSeries()
+ , PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ //because of this name this line will be used for marking
+ ::chart::ShapeFactory::setShapeName(xShape, "MarkHandles");
+ return true;
+}
+
+void NetChart::impl_createSeriesShapes()
+{
+ //the polygon shapes for each series need to be created before
+
+ //iterate through all series again to create the series shapes
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ for( auto const& rXSlot : rZSlot )
+ {
+ std::map< sal_Int32, std::vector<std::vector<css::drawing::Position3D>>* > aPreviousSeriesPolyMap;//a PreviousSeriesPoly for each different nAttachedAxisIndex
+ std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly = nullptr;
+
+ //iterate through all series
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex);
+
+ pSeriesPoly = &pSeries->m_aPolyPolygonShape3D;
+ if( m_bArea )
+ {
+ if (!impl_createArea(pSeries.get(), pSeriesPoly,
+ aPreviousSeriesPolyMap[nAttachedAxisIndex], m_pPosHelper))
+ continue;
+ }
+ if( m_bLine )
+ {
+ if (!impl_createLine(pSeries.get(), pSeriesPoly, m_pPosHelper))
+ continue;
+ }
+ aPreviousSeriesPolyMap[nAttachedAxisIndex] = pSeriesPoly;
+ }//next series in x slot (next y slot)
+ }//next x slot
+ }//next z slot
+}
+
+namespace
+{
+
+void lcl_reorderSeries( std::vector< std::vector< VDataSeriesGroup > >& rZSlots )
+{
+ std::vector< std::vector< VDataSeriesGroup > > aRet;
+ aRet.reserve( rZSlots.size() );
+
+ std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZIt( rZSlots.rbegin() );
+ std::vector< std::vector< VDataSeriesGroup > >::reverse_iterator aZEnd( rZSlots.rend() );
+ for( ; aZIt != aZEnd; ++aZIt )
+ {
+ std::vector< VDataSeriesGroup > aXSlot;
+
+ std::vector< VDataSeriesGroup >::reverse_iterator aXIt( aZIt->rbegin() );
+ std::vector< VDataSeriesGroup >::reverse_iterator aXEnd( aZIt->rend() );
+ for( ; aXIt != aXEnd; ++aXIt )
+ aXSlot.push_back(std::move(*aXIt));
+
+ aRet.push_back(std::move(aXSlot));
+ }
+
+ rZSlots = std::move(aRet);
+}
+
+//better performance for big data
+struct FormerPoint
+{
+ FormerPoint( double fX, double fY, double fZ )
+ : m_fX(fX), m_fY(fY), m_fZ(fZ)
+ {}
+ FormerPoint()
+ : m_fX(std::numeric_limits<double>::quiet_NaN())
+ , m_fY(std::numeric_limits<double>::quiet_NaN())
+ , m_fZ(std::numeric_limits<double>::quiet_NaN())
+
+ {
+ }
+
+ double m_fX;
+ double m_fY;
+ double m_fZ;
+};
+
+}//anonymous namespace
+
+void NetChart::createShapes()
+{
+ if( m_aZSlots.empty() ) //no series
+ return;
+
+ //tdf#127813 Don't reverse the series in OOXML-heavy environments
+ if (officecfg::Office::Compatibility::View::ReverseSeriesOrderAreaAndNetChart::get() && m_bArea)
+ lcl_reorderSeries( m_aZSlots );
+
+ OSL_ENSURE(m_xLogicTarget.is()&&m_xFinalTarget.is(),"NetChart is not proper initialized");
+ if(!(m_xLogicTarget.is()&&m_xFinalTarget.is()))
+ return;
+
+ //the text labels should be always on top of the other series shapes
+ //for area chart the error bars should be always on top of the other series shapes
+
+ //therefore create an own group for the texts and the error bars to move them to front
+ //(because the text group is created after the series group the texts are displayed on top)
+ m_xSeriesTarget = createGroupShape( m_xLogicTarget );
+ m_xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+
+ //check necessary here that different Y axis can not be stacked in the same group? ... hm?
+
+ //update/create information for current group
+ double fLogicZ = 1.0;//as defined
+
+ sal_Int32 const nStartIndex = 0; // inclusive ;..todo get somehow from x scale
+ sal_Int32 nEndIndex = VSeriesPlotter::getPointCount();
+ if(nEndIndex<=0)
+ nEndIndex=1;
+
+ //better performance for big data
+ std::map< VDataSeries*, FormerPoint > aSeriesFormerPointMap;
+ m_bPointsWereSkipped = false;
+
+ bool bDateCategory = (m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis());
+
+ //iterate through all x values per indices
+ for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ )
+ {
+ std::map< sal_Int32, double > aLogicYSumMap;//one for each different nAttachedAxisIndex
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ //iterate through all x slots in this category to get 100percent sum
+ for( auto const& rXSlot : rZSlot )
+ {
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ if (bDateCategory)
+ pSeries->doSortByXValues();
+
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ aLogicYSumMap.insert({nAttachedAxisIndex, 0.0});
+
+ m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex);
+
+ double fAdd = pSeries->getYValue( nIndex );
+ if( !std::isnan(fAdd) && !std::isinf(fAdd) )
+ aLogicYSumMap[nAttachedAxisIndex] += fabs( fAdd );
+ }
+ }
+ }
+
+ for( auto const& rZSlot : m_aZSlots )
+ {
+ //for the area chart there should be at most one x slot (no side by side stacking available)
+ //attention different: xSlots are always interpreted as independent areas one behind the other: @todo this doesn't work why not???
+ for( auto const& rXSlot : rZSlot )
+ {
+ std::map< sal_Int32, double > aLogicYForNextSeriesMap;//one for each different nAttachedAxisIndex
+ //iterate through all series
+ for( std::unique_ptr<VDataSeries> const & pSeries : rXSlot.m_aSeriesVector )
+ {
+ if(!pSeries)
+ continue;
+
+ /* #i70133# ignore points outside of series length in standard area
+ charts. Stacked area charts will use missing points as zeros. In
+ standard charts, pSeriesList contains only one series. */
+ if( m_bArea && (rXSlot.m_aSeriesVector.size() == 1) && (nIndex >= pSeries->getTotalPointCount()) )
+ continue;
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShapeFrontChild(pSeries.get(), m_xSeriesTarget);
+
+ sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex();
+ m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex);
+
+ pSeries->m_fLogicZPos = fLogicZ;
+
+ //collect data point information (logic coordinates, style ):
+ double fLogicX = pSeries->getXValue(nIndex);
+ if (bDateCategory)
+ fLogicX = DateHelper::RasterizeDateValue( fLogicX, m_aNullDate, m_nTimeResolution );
+ double fLogicY = pSeries->getYValue(nIndex);
+
+ if( m_bArea && ( std::isnan(fLogicY) || std::isinf(fLogicY) ) )
+ {
+ if( pSeries->getMissingValueTreatment() == css::chart::MissingValueTreatment::LEAVE_GAP )
+ {
+ if( rXSlot.m_aSeriesVector.size() == 1 || pSeries == rXSlot.m_aSeriesVector.front() )
+ {
+ fLogicY = m_pPosHelper->getLogicMinY();
+ if (!m_pPosHelper->isMathematicalOrientationY())
+ fLogicY = m_pPosHelper->getLogicMaxY();
+ }
+ else
+ fLogicY = 0.0;
+ }
+ }
+
+ if (m_pPosHelper->isPercentY() && aLogicYSumMap[nAttachedAxisIndex] != 0.0)
+ {
+ fLogicY = fabs( fLogicY )/aLogicYSumMap[nAttachedAxisIndex];
+ }
+
+ if( std::isnan(fLogicX) || std::isinf(fLogicX)
+ || std::isnan(fLogicY) || std::isinf(fLogicY)
+ || std::isnan(fLogicZ) || std::isinf(fLogicZ) )
+ {
+ if( pSeries->getMissingValueTreatment() == css::chart::MissingValueTreatment::LEAVE_GAP )
+ {
+ std::vector<std::vector<css::drawing::Position3D>>& rPolygon = pSeries->m_aPolyPolygonShape3D;
+ sal_Int32& rIndex = pSeries->m_nPolygonIndex;
+ if( 0<= rIndex && o3tl::make_unsigned(rIndex) < rPolygon.size() )
+ {
+ if( !rPolygon[ rIndex ].empty() )
+ rIndex++; //start a new polygon for the next point if the current poly is not empty
+ }
+ }
+ continue;
+ }
+
+ aLogicYForNextSeriesMap.try_emplace(nAttachedAxisIndex, 0.0);
+
+ double fLogicValueForLabeDisplay = fLogicY;
+
+ fLogicY += aLogicYForNextSeriesMap[nAttachedAxisIndex];
+ aLogicYForNextSeriesMap[nAttachedAxisIndex] = fLogicY;
+
+ bool bIsVisible = m_pPosHelper->isLogicVisible(fLogicX, fLogicY, fLogicZ);
+
+ //remind minimal and maximal x values for area 'grounding' points
+ //only for filled area
+ {
+ double& rfMinX = pSeries->m_fLogicMinX;
+ if(!nIndex||fLogicX<rfMinX)
+ rfMinX=fLogicX;
+ double& rfMaxX = pSeries->m_fLogicMaxX;
+ if(!nIndex||fLogicX>rfMaxX)
+ rfMaxX=fLogicX;
+ }
+
+ drawing::Position3D aUnscaledLogicPosition( fLogicX, fLogicY, fLogicZ );
+ drawing::Position3D aScaledLogicPosition(aUnscaledLogicPosition);
+ m_pPosHelper->doLogicScaling(aScaledLogicPosition);
+
+ //transformation 3) -> 4)
+ drawing::Position3D aScenePosition(
+ m_pPosHelper->transformLogicToScene(fLogicX, fLogicY, fLogicZ, false));
+
+ //better performance for big data
+ FormerPoint aFormerPoint( aSeriesFormerPointMap[pSeries.get()] );
+ m_pPosHelper->setCoordinateSystemResolution(m_aCoordinateSystemResolution);
+ if( !pSeries->isAttributedDataPoint(nIndex)
+ && m_pPosHelper->isSameForGivenResolution(
+ aFormerPoint.m_fX, aFormerPoint.m_fY, aFormerPoint.m_fZ
+ , aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, aScaledLogicPosition.PositionZ ) )
+ {
+ m_bPointsWereSkipped = true;
+ continue;
+ }
+ aSeriesFormerPointMap[pSeries.get()] = FormerPoint(aScaledLogicPosition.PositionX, aScaledLogicPosition.PositionY, aScaledLogicPosition.PositionZ);
+
+ //store point information for series polygon
+ //for area and/or line (symbols only do not need this)
+ if( isValidPosition(aScaledLogicPosition) )
+ {
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aScaledLogicPosition, pSeries->m_nPolygonIndex );
+
+ //prepare clipping for filled net charts
+ if( !bIsVisible && m_bArea )
+ {
+ drawing::Position3D aClippedPos(aScaledLogicPosition);
+ m_pPosHelper->clipScaledLogicValues(nullptr, &aClippedPos.PositionY,
+ nullptr);
+ if (m_pPosHelper->isLogicVisible(aClippedPos.PositionX,
+ aClippedPos.PositionY,
+ aClippedPos.PositionZ))
+ {
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aClippedPos, pSeries->m_nPolygonIndex );
+ AddPointToPoly( pSeries->m_aPolyPolygonShape3D, aScaledLogicPosition, pSeries->m_nPolygonIndex );
+ }
+ }
+ }
+
+ //create a single datapoint if point is visible
+ //apply clipping:
+ if( !bIsVisible )
+ continue;
+
+ Symbol* pSymbolProperties = pSeries->getSymbolProperties( nIndex );
+ bool bCreateSymbol = pSymbolProperties && (pSymbolProperties->Style != SymbolStyle_NONE);
+
+ if( !bCreateSymbol && !pSeries->getDataPointLabelIfLabel(nIndex) )
+ continue;
+
+ //create a group shape for this point and add to the series shape:
+ OUString aPointCID = ObjectIdentifier::createPointCID(
+ pSeries->getPointCID_Stub(), nIndex );
+ rtl::Reference<SvxShapeGroupAnyD> xPointGroupShape_Shapes(
+ createGroupShape(xSeriesGroupShape_Shapes,aPointCID) );
+
+ {
+ //create data point
+ drawing::Direction3D aSymbolSize(0,0,0);
+ if (bCreateSymbol) // implies pSymbolProperties
+ {
+ if (pSymbolProperties->Style != SymbolStyle_NONE)
+ {
+ aSymbolSize.DirectionX = pSymbolProperties->Size.Width;
+ aSymbolSize.DirectionY = pSymbolProperties->Size.Height;
+ }
+
+ if (pSymbolProperties->Style == SymbolStyle_STANDARD)
+ {
+ sal_Int32 nSymbol = pSymbolProperties->StandardSymbol;
+ ShapeFactory::createSymbol2D(
+ xPointGroupShape_Shapes, aScenePosition, aSymbolSize, nSymbol,
+ pSymbolProperties->BorderColor, pSymbolProperties->FillColor);
+ }
+ else if (pSymbolProperties->Style == SymbolStyle_GRAPHIC)
+ {
+ ShapeFactory::createGraphic2D(xPointGroupShape_Shapes,
+ aScenePosition, aSymbolSize,
+ pSymbolProperties->Graphic);
+ }
+ //@todo other symbol styles
+ }
+
+ //create data point label
+ if( pSeries->getDataPointLabelIfLabel(nIndex) )
+ {
+ LabelAlignment eAlignment = LABEL_ALIGN_TOP;
+ drawing::Position3D aScenePosition3D( aScenePosition.PositionX
+ , aScenePosition.PositionY
+ , aScenePosition.PositionZ+getTransformedDepth() );
+
+ sal_Int32 nLabelPlacement = pSeries->getLabelPlacement(
+ nIndex, m_xChartTypeModel, m_pPosHelper->isSwapXAndY());
+
+ switch(nLabelPlacement)
+ {
+ case css::chart::DataLabelPlacement::TOP:
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ case css::chart::DataLabelPlacement::BOTTOM:
+ aScenePosition3D.PositionY += (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_BOTTOM;
+ break;
+ case css::chart::DataLabelPlacement::LEFT:
+ aScenePosition3D.PositionX -= (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_LEFT;
+ break;
+ case css::chart::DataLabelPlacement::RIGHT:
+ aScenePosition3D.PositionX += (aSymbolSize.DirectionX/2+1);
+ eAlignment = LABEL_ALIGN_RIGHT;
+ break;
+ case css::chart::DataLabelPlacement::CENTER:
+ eAlignment = LABEL_ALIGN_CENTER;
+ //todo implement this different for area charts
+ break;
+ default:
+ OSL_FAIL("this label alignment is not implemented yet");
+ aScenePosition3D.PositionY -= (aSymbolSize.DirectionY/2+1);
+ eAlignment = LABEL_ALIGN_TOP;
+ break;
+ }
+
+ awt::Point aScreenPosition2D;//get the screen position for the labels
+ sal_Int32 nOffset = 100; //todo maybe calculate this font height dependent
+ if( nLabelPlacement == css::chart::DataLabelPlacement::OUTSIDE )
+ {
+ PolarPlottingPositionHelper* pPolarPosHelper
+ = dynamic_cast<PolarPlottingPositionHelper*>(m_pPosHelper);
+ if( pPolarPosHelper )
+ {
+ PolarLabelPositionHelper aPolarLabelPositionHelper(pPolarPosHelper,m_nDimension,m_xLogicTarget);
+ aScreenPosition2D = aPolarLabelPositionHelper.getLabelScreenPositionAndAlignmentForLogicValues(
+ eAlignment, fLogicX, fLogicY, fLogicZ, nOffset );
+ }
+ }
+ else
+ {
+ if(eAlignment==LABEL_ALIGN_CENTER )
+ nOffset = 0;
+ aScreenPosition2D = LabelPositionHelper(m_nDimension,m_xLogicTarget)
+ .transformSceneToScreenPosition( aScenePosition3D );
+ }
+
+ createDataLabel( m_xTextTarget, *pSeries, nIndex
+ , fLogicValueForLabeDisplay
+ , aLogicYSumMap[nAttachedAxisIndex], aScreenPosition2D, eAlignment, nOffset );
+ }
+ }
+
+ //remove PointGroupShape if empty
+ if(!xPointGroupShape_Shapes->getCount())
+ xSeriesGroupShape_Shapes->remove(xPointGroupShape_Shapes);
+
+ }//next series in x slot (next y slot)
+ }//next x slot
+ }//next z slot
+ }//next category
+
+ impl_createSeriesShapes();
+
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/NetChart.hxx b/chart2/source/view/charttypes/NetChart.hxx
new file mode 100644
index 000000000..a536daf15
--- /dev/null
+++ b/chart2/source/view/charttypes/NetChart.hxx
@@ -0,0 +1,74 @@
+/* -*- 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 <memory>
+#include <VSeriesPlotter.hxx>
+
+namespace chart
+{
+
+class NetChart : public VSeriesPlotter
+{
+ // public methods
+public:
+ NetChart() = delete;
+
+ NetChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bNoArea
+ , std::unique_ptr<PlottingPositionHelper> pPlottingPositionHelper //takes ownership
+ );
+ virtual ~NetChart() override;
+
+ virtual void createShapes() override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+
+ // MinimumAndMaximumSupplier
+ virtual double getMaximumX() override;
+ virtual bool isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex ) override;
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+ virtual LegendSymbolStyle getLegendSymbolStyle() override;
+ virtual css::uno::Any getExplicitSymbol( const VDataSeries& rSeries, sal_Int32 nPointIndex/*-1 for series symbol*/ ) override;
+
+private: //methods
+ void impl_createSeriesShapes();
+ bool impl_createArea( VDataSeries* pSeries
+ , const std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly
+ , std::vector<std::vector<css::drawing::Position3D>> const * pPreviousSeriesPoly
+ , PlottingPositionHelper const * pPosHelper );
+ bool impl_createLine( VDataSeries* pSeries
+ , const std::vector<std::vector<css::drawing::Position3D>>* pSeriesPoly
+ , PlottingPositionHelper const * pPosHelper );
+
+private: //member
+ std::unique_ptr<PlottingPositionHelper> m_pMainPosHelper;
+
+ bool m_bArea;//false -> line or symbol only
+ bool m_bLine;
+
+ rtl::Reference<SvxShapeGroupAnyD> m_xSeriesTarget;
+ rtl::Reference<SvxShapeGroupAnyD> m_xTextTarget;
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/PieChart.cxx b/chart2/source/view/charttypes/PieChart.cxx
new file mode 100644
index 000000000..a81428c24
--- /dev/null
+++ b/chart2/source/view/charttypes/PieChart.cxx
@@ -0,0 +1,1719 @@
+/* -*- 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 <VLineProperties.hxx>
+#include "PieChart.hxx"
+#include <PlottingPositionHelper.hxx>
+#include <ShapeFactory.hxx>
+#include <PolarLabelPositionHelper.hxx>
+#include <CommonConverters.hxx>
+#include <ObjectIdentifier.hxx>
+#include <ChartType.hxx>
+
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+#include <com/sun/star/chart2/XColorScheme.hpp>
+
+#include <com/sun/star/drawing/XShapes.hpp>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/diagnose_ex.h>
+#include <tools/helpers.hxx>
+
+#include <limits>
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+namespace chart {
+
+struct PieChart::ShapeParam
+{
+ /** the start angle of the slice
+ */
+ double mfUnitCircleStartAngleDegree;
+
+ /** the angle width of the slice
+ */
+ double mfUnitCircleWidthAngleDegree;
+
+ /** the normalized outer radius of the ring the slice belongs to.
+ */
+ double mfUnitCircleOuterRadius;
+
+ /** the normalized inner radius of the ring the slice belongs to
+ */
+ double mfUnitCircleInnerRadius;
+
+ /** relative distance offset of a slice from the pie center;
+ * this parameter is used for instance when the user performs manual
+ * dragging of a slice (the drag operation is possible only for slices that
+ * belong to the outer ring and only along the ray bisecting the slice);
+ * the value for the given entry in the data series is obtained by the
+ * `Offset` property attached to each entry; note that the value
+ * provided by the `Offset` property is used both as a logical value in
+ * `PiePositionHelper::getInnerAndOuterRadius` and as a percentage value in
+ * the `PieChart::createDataPoint` and `PieChart::createTextLabelShape`
+ * methods; since the logical height of a ring is always 1, this duality
+ * does not cause any incorrect behavior;
+ */
+ double mfExplodePercentage;
+
+ /** sum of all Y values in a single series
+ */
+ double mfLogicYSum;
+
+ /** for 3D pie chart: label z coordinate
+ */
+ double mfLogicZ;
+
+ /** for 3D pie chart: height
+ */
+ double mfDepth;
+
+ ShapeParam() :
+ mfUnitCircleStartAngleDegree(0.0),
+ mfUnitCircleWidthAngleDegree(0.0),
+ mfUnitCircleOuterRadius(0.0),
+ mfUnitCircleInnerRadius(0.0),
+ mfExplodePercentage(0.0),
+ mfLogicYSum(0.0),
+ mfLogicZ(0.0),
+ mfDepth(0.0) {}
+};
+
+namespace
+{
+::basegfx::B2IRectangle lcl_getRect(const rtl::Reference<SvxShape>& xShape)
+{
+ ::basegfx::B2IRectangle aRect;
+ if (xShape.is())
+ aRect = BaseGFXHelper::makeRectangle(xShape->getPosition(), xShape->getSize());
+ return aRect;
+}
+
+bool lcl_isInsidePage(const awt::Point& rPos, const awt::Size& rSize, const awt::Size& rPageSize)
+{
+ if (rPos.X < 0 || rPos.Y < 0)
+ return false;
+ if ((rPos.X + rSize.Width) > rPageSize.Width)
+ return false;
+ if ((rPos.Y + rSize.Height) > rPageSize.Height)
+ return false;
+ return true;
+}
+
+} //end anonymous namespace
+
+class PiePositionHelper : public PolarPlottingPositionHelper
+{
+public:
+ PiePositionHelper( double fAngleDegreeOffset );
+
+ bool getInnerAndOuterRadius( double fCategoryX, double& fLogicInnerRadius, double& fLogicOuterRadius, bool bUseRings, double fMaxOffset ) const;
+
+public:
+ //Distance between different category rings, seen relative to width of a ring:
+ double m_fRingDistance; //>=0 m_fRingDistance=1 --> distance == width
+};
+
+PiePositionHelper::PiePositionHelper( double fAngleDegreeOffset )
+ : m_fRingDistance(0.0)
+{
+ m_fRadiusOffset = 0.0;
+ m_fAngleDegreeOffset = fAngleDegreeOffset;
+}
+
+/** Compute the outer and the inner radius for the current ring (not for the
+ * whole donut!), in general it is:
+ * inner_radius = (ring_index + 1) - 0.5 + max_offset,
+ * outer_radius = (ring_index + 1) + 0.5 + max_offset.
+ * When orientation for the radius axis is reversed these values are swapped.
+ * (Indeed the orientation for the radius axis is always reversed!
+ * See `PieChartTypeTemplate::adaptScales`.)
+ * The maximum relative offset (see notes for `PieChart::getMaxOffset`) is
+ * added to both the inner and the outer radius.
+ * It returns true if the ring is visible (that is not out of the radius
+ * axis scale range).
+ */
+bool PiePositionHelper::getInnerAndOuterRadius( double fCategoryX
+ , double& fLogicInnerRadius, double& fLogicOuterRadius
+ , bool bUseRings, double fMaxOffset ) const
+{
+ if( !bUseRings )
+ fCategoryX = 1.0;
+
+ double fLogicInner = fCategoryX -0.5+m_fRingDistance/2.0;
+ double fLogicOuter = fCategoryX +0.5-m_fRingDistance/2.0;
+
+ if( !isMathematicalOrientationRadius() )
+ {
+ //in this case the given getMaximumX() was not correct instead the minimum should have been smaller by fMaxOffset
+ //but during getMaximumX and getMimumX we do not know the axis orientation
+ fLogicInner += fMaxOffset;
+ fLogicOuter += fMaxOffset;
+ }
+
+ if( fLogicInner >= getLogicMaxX() )
+ return false;
+ if( fLogicOuter <= getLogicMinX() )
+ return false;
+
+ if( fLogicInner < getLogicMinX() )
+ fLogicInner = getLogicMinX();
+ if( fLogicOuter > getLogicMaxX() )
+ fLogicOuter = getLogicMaxX();
+
+ fLogicInnerRadius = fLogicInner;
+ fLogicOuterRadius = fLogicOuter;
+ if( !isMathematicalOrientationRadius() )
+ std::swap(fLogicInnerRadius,fLogicOuterRadius);
+ return true;
+}
+
+PieChart::PieChart( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bExcludingPositioning )
+ : VSeriesPlotter( xChartTypeModel, nDimensionCount )
+ , m_pPosHelper( new PiePositionHelper( (m_nDimension==3) ? 0.0 : 90.0 ) )
+ , m_bUseRings(false)
+ , m_bSizeExcludesLabelsAndExplodedSegments(bExcludingPositioning)
+ , m_fMaxOffset(std::numeric_limits<double>::quiet_NaN())
+{
+ PlotterBase::m_pPosHelper = m_pPosHelper.get();
+ VSeriesPlotter::m_pMainPosHelper = m_pPosHelper.get();
+ m_pPosHelper->m_fRadiusOffset = 0.0;
+ m_pPosHelper->m_fRingDistance = 0.0;
+
+ if( !xChartTypeModel.is() )
+ return;
+
+ try
+ {
+ xChartTypeModel->getPropertyValue( "UseRings") >>= m_bUseRings;
+ if( m_bUseRings )
+ {
+ m_pPosHelper->m_fRadiusOffset = 1.0;
+ if( nDimensionCount==3 )
+ m_pPosHelper->m_fRingDistance = 0.1;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+PieChart::~PieChart()
+{
+}
+
+void PieChart::setScales( std::vector< ExplicitScaleData >&& rScales, bool /* bSwapXAndYAxis */ )
+{
+ OSL_ENSURE(m_nDimension<=static_cast<sal_Int32>(rScales.size()),"Dimension of Plotter does not fit two dimension of given scale sequence");
+ m_pPosHelper->setScales( std::move(rScales), true );
+}
+
+drawing::Direction3D PieChart::getPreferredDiagramAspectRatio() const
+{
+ if( m_nDimension == 3 )
+ return drawing::Direction3D(1,1,0.10);
+ return drawing::Direction3D(1,1,1);
+}
+
+bool PieChart::shouldSnapRectToUsedArea()
+{
+ return true;
+}
+
+rtl::Reference<SvxShape> PieChart::createDataPoint(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const uno::Reference<beans::XPropertySet>& xObjectProperties,
+ const ShapeParam& rParam,
+ const sal_Int32 nPointCount,
+ const bool bConcentricExplosion)
+{
+ //transform position:
+ drawing::Direction3D aOffset;
+ double fExplodedInnerRadius = rParam.mfUnitCircleInnerRadius;
+ double fExplodedOuterRadius = rParam.mfUnitCircleOuterRadius;
+ double fStartAngle = rParam.mfUnitCircleStartAngleDegree;
+ double fWidthAngle = rParam.mfUnitCircleWidthAngleDegree;
+
+ if (rParam.mfExplodePercentage != 0.0) {
+ double fRadius = (fExplodedOuterRadius-fExplodedInnerRadius)*rParam.mfExplodePercentage;
+
+ if (bConcentricExplosion) {
+
+ // For concentric explosion, increase the radius but retain the original
+ // arc length of all ring segments together. This results in a gap
+ // that's evenly divided among all segments, assuming they all have
+ // the same explosion percentage
+ assert(fExplodedInnerRadius >= 0 && fExplodedOuterRadius > 0);
+ double fAngleRatio = (fExplodedInnerRadius + fExplodedOuterRadius) /
+ (fExplodedInnerRadius + fExplodedOuterRadius + 2 * fRadius);
+
+ assert(nPointCount > 0);
+ double fAngleGap = 360 * (1.0 - fAngleRatio) / nPointCount;
+ fStartAngle += fAngleGap / 2;
+ fWidthAngle -= fAngleGap;
+
+ fExplodedInnerRadius += fRadius;
+ fExplodedOuterRadius += fRadius;
+
+ } else {
+ // For the non-concentric explosion case, keep the original radius
+ // but shift the circle origin
+ double fAngle = fStartAngle + fWidthAngle/2.0;
+
+ drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene(0, 0, rParam.mfLogicZ);
+ drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene(fAngle, fRadius, rParam.mfLogicZ);
+ aOffset = aNewOrigin - aOrigin;
+ }
+ }
+
+ //create point
+ rtl::Reference<SvxShape> xShape;
+ if(m_nDimension==3)
+ {
+ xShape = ShapeFactory::createPieSegment( xTarget
+ , fStartAngle, fWidthAngle
+ , fExplodedInnerRadius, fExplodedOuterRadius
+ , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() )
+ , rParam.mfDepth );
+ }
+ else
+ {
+ xShape = ShapeFactory::createPieSegment2D( xTarget
+ , fStartAngle, fWidthAngle
+ , fExplodedInnerRadius, fExplodedOuterRadius
+ , aOffset, B3DHomMatrixToHomogenMatrix( m_pPosHelper->getUnitCartesianToScene() ) );
+ }
+ PropertyMapper::setMappedProperties( *xShape, xObjectProperties, PropertyMapper::getPropertyNameMapForFilledSeriesProperties() );
+ return xShape;
+}
+
+void PieChart::createTextLabelShape(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ VDataSeries& rSeries, sal_Int32 nPointIndex, ShapeParam& rParam )
+{
+ if (!rSeries.getDataPointLabelIfLabel(nPointIndex))
+ // There is no text label for this data point. Nothing to do.
+ return;
+
+ ///by using the `mfExplodePercentage` parameter a normalized offset is added
+ ///to both normalized radii. (See notes for
+ ///`PolarPlottingPositionHelper::transformToRadius`, especially example 3,
+ ///and related comments).
+ if (rParam.mfExplodePercentage != 0.0)
+ {
+ double fExplodeOffset = (rParam.mfUnitCircleOuterRadius-rParam.mfUnitCircleInnerRadius)*rParam.mfExplodePercentage;
+ rParam.mfUnitCircleInnerRadius += fExplodeOffset;
+ rParam.mfUnitCircleOuterRadius += fExplodeOffset;
+ }
+
+ ///get the required label placement type. Available placements are
+ ///`AVOID_OVERLAP`, `CENTER`, `OUTSIDE` and `INSIDE`;
+ sal_Int32 nLabelPlacement = rSeries.getLabelPlacement(
+ nPointIndex, m_xChartTypeModel, m_pPosHelper->isSwapXAndY());
+
+ ///when the placement is of `AVOID_OVERLAP` type a later rearrangement of
+ ///the label position is allowed; the `createTextLabelShape` treats the
+ ///`AVOID_OVERLAP` as if it was of `CENTER` type;
+
+ double nVal = rSeries.getYValue(nPointIndex);
+ //AVOID_OVERLAP is in fact "Best fit" in the UI.
+ bool bMovementAllowed = nLabelPlacement == css::chart::DataLabelPlacement::AVOID_OVERLAP
+ || nLabelPlacement == css::chart::DataLabelPlacement::CUSTOM;
+ if( bMovementAllowed )
+ nLabelPlacement = css::chart::DataLabelPlacement::CENTER;
+
+ ///for `OUTSIDE` (`INSIDE`) label placements an offset of 150 (-150), in the
+ ///radius direction, is added to the final screen position of the label
+ ///anchor point. This is required in order to ensure that the label is
+ ///completely outside (inside) the related slice. Indeed this value should
+ ///depend on the font height;
+ ///pay attention: 150 is not a big offset, in fact the screen position
+ ///coordinates for label anchor points are in the 10000-20000 range, hence
+ ///these are coordinates of a virtual screen and 150 is a small value;
+ LabelAlignment eAlignment(LABEL_ALIGN_CENTER);
+ sal_Int32 nScreenValueOffsetInRadiusDirection = 0 ;
+ if( nLabelPlacement == css::chart::DataLabelPlacement::OUTSIDE )
+ nScreenValueOffsetInRadiusDirection = (m_nDimension!=3) ? 150 : 0;//todo maybe calculate this font height dependent
+ else if( nLabelPlacement == css::chart::DataLabelPlacement::INSIDE )
+ nScreenValueOffsetInRadiusDirection = (m_nDimension!=3) ? -150 : 0;//todo maybe calculate this font height dependent
+
+ ///the scene position of the label anchor point is calculated (see notes for
+ ///`PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForUnitCircleValues`),
+ ///and immediately transformed into the screen position.
+ PolarLabelPositionHelper aPolarPosHelper(m_pPosHelper.get(),m_nDimension,m_xLogicTarget);
+ awt::Point aScreenPosition2D(
+ aPolarPosHelper.getLabelScreenPositionAndAlignmentForUnitCircleValues(eAlignment, nLabelPlacement
+ , rParam.mfUnitCircleStartAngleDegree, rParam.mfUnitCircleWidthAngleDegree
+ , rParam.mfUnitCircleInnerRadius, rParam.mfUnitCircleOuterRadius, rParam.mfLogicZ+0.5, 0 ));
+
+ ///the screen position of the pie/donut center is calculated.
+ PieLabelInfo aPieLabelInfo;
+ aPieLabelInfo.aFirstPosition = basegfx::B2IVector( aScreenPosition2D.X, aScreenPosition2D.Y );
+ awt::Point aOrigin( aPolarPosHelper.transformSceneToScreenPosition( m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, rParam.mfLogicZ+1.0 ) ) );
+ aPieLabelInfo.aOrigin = basegfx::B2IVector( aOrigin.X, aOrigin.Y );
+
+ ///add a scaling independent Offset if requested
+ if( nScreenValueOffsetInRadiusDirection != 0)
+ {
+ basegfx::B2IVector aDirection( aScreenPosition2D.X- aOrigin.X, aScreenPosition2D.Y- aOrigin.Y );
+ aDirection.setLength(nScreenValueOffsetInRadiusDirection);
+ aScreenPosition2D.X += aDirection.getX();
+ aScreenPosition2D.Y += aDirection.getY();
+ }
+
+ // compute outer pie radius
+ awt::Point aOuterCirclePoint = PlottingPositionHelper::transformSceneToScreenPosition(
+ m_pPosHelper->transformUnitCircleToScene(
+ 0,
+ rParam.mfUnitCircleOuterRadius,
+ 0 ),
+ m_xLogicTarget, m_nDimension );
+ basegfx::B2IVector aRadiusVector(
+ aOuterCirclePoint.X - aPieLabelInfo.aOrigin.getX(),
+ aOuterCirclePoint.Y - aPieLabelInfo.aOrigin.getY() );
+ double fSquaredPieRadius = aRadiusVector.scalar(aRadiusVector);
+ double fPieRadius = sqrt( fSquaredPieRadius );
+ double fAngleDegree
+ = rParam.mfUnitCircleStartAngleDegree + rParam.mfUnitCircleWidthAngleDegree / 2.0;
+ while (fAngleDegree > 360.0)
+ fAngleDegree -= 360.0;
+ while (fAngleDegree < 0.0)
+ fAngleDegree += 360.0;
+
+ awt::Point aOuterPosition = PlottingPositionHelper::transformSceneToScreenPosition(
+ m_pPosHelper->transformUnitCircleToScene(fAngleDegree, rParam.mfUnitCircleOuterRadius, 0),
+ m_xLogicTarget, m_nDimension);
+ aPieLabelInfo.aOuterPosition = basegfx::B2IVector(aOuterPosition.X, aOuterPosition.Y);
+
+ // set the maximum text width to be used when text wrapping is enabled
+ double fTextMaximumFrameWidth = 0.8 * fPieRadius;
+ if (nLabelPlacement == css::chart::DataLabelPlacement::OUTSIDE
+ && m_aAvailableOuterRect.getWidth())
+ {
+ if ((fAngleDegree >= 67.5 && fAngleDegree <= 112.5)
+ || (fAngleDegree >= 247.5 && fAngleDegree <= 292.5))
+ fTextMaximumFrameWidth = m_aAvailableOuterRect.getWidth() / 3.0;
+ else
+ fTextMaximumFrameWidth = 0.85 * (m_aAvailableOuterRect.getWidth() / 2.0 - fPieRadius);
+ }
+ sal_Int32 nTextMaximumFrameWidth = ceil(fTextMaximumFrameWidth);
+
+ ///the text shape for the label is created
+ aPieLabelInfo.xTextShape = createDataLabel(
+ xTextTarget, rSeries, nPointIndex, nVal, rParam.mfLogicYSum,
+ aScreenPosition2D, eAlignment, 0, nTextMaximumFrameWidth);
+
+ ///a new `PieLabelInfo` instance is initialized with all the info related to
+ ///the current label in order to simplify later label position rearrangement;
+ rtl::Reference< SvxShape > xChild = aPieLabelInfo.xTextShape;
+
+ ///text shape could be empty; in that case there is no need to add label info
+ if( !xChild.is() )
+ return;
+
+ aPieLabelInfo.xLabelGroupShape = dynamic_cast<SvxShapeGroupAnyD*>(xChild->getParent().get());
+
+ if (bMovementAllowed && !m_bUseRings)
+ {
+ /** Handle the placement of the label in the best fit case.
+ * First off the routine try to place the label inside the related pie slice,
+ * if this is not possible the label is placed outside.
+ */
+ if (rSeries.getLabelPlacement(nPointIndex, m_xChartTypeModel, m_pPosHelper->isSwapXAndY())
+ == css::chart::DataLabelPlacement::CUSTOM
+ || !performLabelBestFitInnerPlacement(rParam, aPieLabelInfo))
+ {
+ if (m_aAvailableOuterRect.getWidth())
+ {
+ if ((fAngleDegree >= 67.5 && fAngleDegree <= 112.5)
+ || (fAngleDegree >= 247.5 && fAngleDegree <= 292.5))
+ fTextMaximumFrameWidth = m_aAvailableOuterRect.getWidth() / 3.0;
+ else
+ fTextMaximumFrameWidth
+ = 0.85 * (m_aAvailableOuterRect.getWidth() / 2.0 - fPieRadius);
+ nTextMaximumFrameWidth = ceil(fTextMaximumFrameWidth);
+ }
+
+ nScreenValueOffsetInRadiusDirection = (m_nDimension != 3) ? 150 : 0;
+ aScreenPosition2D
+ = aPolarPosHelper.getLabelScreenPositionAndAlignmentForUnitCircleValues(
+ eAlignment, css::chart::DataLabelPlacement::OUTSIDE,
+ rParam.mfUnitCircleStartAngleDegree,
+ rParam.mfUnitCircleWidthAngleDegree, rParam.mfUnitCircleInnerRadius,
+ rParam.mfUnitCircleOuterRadius, rParam.mfLogicZ + 0.5, 0);
+ aPieLabelInfo.aFirstPosition
+ = basegfx::B2IVector(aScreenPosition2D.X, aScreenPosition2D.Y);
+
+ //add a scaling independent Offset if requested
+ if (nScreenValueOffsetInRadiusDirection != 0)
+ {
+ basegfx::B2IVector aDirection(aScreenPosition2D.X - aOrigin.X,
+ aScreenPosition2D.Y - aOrigin.Y);
+ aDirection.setLength(nScreenValueOffsetInRadiusDirection);
+ aScreenPosition2D.X += aDirection.getX();
+ aScreenPosition2D.Y += aDirection.getY();
+ }
+
+ uno::Reference<drawing::XShapes> xShapes(xChild->getParent(), uno::UNO_QUERY);
+ xShapes->remove(aPieLabelInfo.xTextShape);
+ aPieLabelInfo.xTextShape
+ = createDataLabel(xTextTarget, rSeries, nPointIndex, nVal, rParam.mfLogicYSum,
+ aScreenPosition2D, eAlignment, 0, nTextMaximumFrameWidth);
+ xChild = aPieLabelInfo.xTextShape;
+ if (!xChild.is())
+ return;
+
+ aPieLabelInfo.xLabelGroupShape = dynamic_cast<SvxShapeGroupAnyD*>(xChild->getParent().get());
+ }
+ }
+
+ bool bShowLeaderLine = rSeries.getPropertiesOfSeries()
+ ->getPropertyValue("ShowCustomLeaderLines")
+ .get<sal_Bool>();
+ if (m_bPieLabelsAllowToMove)
+ {
+ ::basegfx::B2IRectangle aRect(lcl_getRect(aPieLabelInfo.xLabelGroupShape));
+ sal_Int32 nPageWidth = m_aPageReferenceSize.Width;
+ sal_Int32 nPageHeight = m_aPageReferenceSize.Height;
+
+ // the data label should be inside the chart area
+ awt::Point aShapePos = aPieLabelInfo.xLabelGroupShape->getPosition();
+ if (aRect.getMinX() < 0)
+ aPieLabelInfo.xLabelGroupShape->setPosition(
+ awt::Point(aShapePos.X - aRect.getMinX(), aShapePos.Y));
+ if (aRect.getMinY() < 0)
+ aPieLabelInfo.xLabelGroupShape->setPosition(
+ awt::Point(aShapePos.X, aShapePos.Y - aRect.getMinY()));
+ if (aRect.getMaxX() > nPageWidth)
+ aPieLabelInfo.xLabelGroupShape->setPosition(
+ awt::Point(aShapePos.X - (aRect.getMaxX() - nPageWidth), aShapePos.Y));
+ if (aRect.getMaxY() > nPageHeight)
+ aPieLabelInfo.xLabelGroupShape->setPosition(
+ awt::Point(aShapePos.X, aShapePos.Y - (aRect.getMaxY() - nPageHeight)));
+
+ if (rSeries.isLabelCustomPos(nPointIndex) && bShowLeaderLine)
+ {
+ sal_Int32 nX1 = aPieLabelInfo.aOuterPosition.getX();
+ sal_Int32 nY1 = aPieLabelInfo.aOuterPosition.getY();
+ sal_Int32 nX2 = nX1;
+ sal_Int32 nY2 = nY1;
+ if (nX1 < aRect.getMinX())
+ nX2 = aRect.getMinX();
+ else if (nX1 > aRect.getMaxX())
+ nX2 = aRect.getMaxX();
+
+ if (nY1 < aRect.getMinY())
+ nY2 = aRect.getMinY();
+ else if (nY1 > aRect.getMaxY())
+ nY2 = aRect.getMaxY();
+
+ sal_Int32 nSquaredDistanceFromOrigin
+ = (nX2 - aOrigin.X) * (nX2 - aOrigin.X) + (nY2 - aOrigin.Y) * (nY2 - aOrigin.Y);
+
+ // tdf#138018 Don't show leader line when custom positioned data label is inside pie chart
+ if (nSquaredDistanceFromOrigin > fSquaredPieRadius)
+ {
+ //when the line is very short compared to the page size don't create one
+ ::basegfx::B2DVector aLength(nX1 - nX2, nY1 - nY2);
+ double fPageDiagonaleLength = std::hypot(nPageWidth, nPageHeight);
+ if ((aLength.getLength() / fPageDiagonaleLength) >= 0.01)
+ {
+ drawing::PointSequenceSequence aPoints{ { {nX1, nY1}, {nX2, nY2} } };
+
+ VLineProperties aVLineProperties;
+ if (aPieLabelInfo.xTextShape.is())
+ {
+ sal_Int32 nColor = 0;
+ aPieLabelInfo.xTextShape->SvxShape::getPropertyValue("CharColor") >>= nColor;
+ //automatic font color does not work for lines -> fallback to black
+ if (nColor != -1)
+ aVLineProperties.Color <<= nColor;
+ }
+ ShapeFactory::createLine2D(xTextTarget, aPoints, &aVLineProperties);
+ }
+ }
+ }
+ }
+
+ aPieLabelInfo.fValue = nVal;
+ aPieLabelInfo.bMovementAllowed = bMovementAllowed;
+ aPieLabelInfo.bMoved = false;
+ aPieLabelInfo.xTextTarget = xTextTarget;
+ aPieLabelInfo.bShowLeaderLine = bShowLeaderLine && !rSeries.isLabelCustomPos(nPointIndex);
+
+ m_aLabelInfoList.push_back(aPieLabelInfo);
+}
+
+void PieChart::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 /* zSlot */, sal_Int32 /* xSlot */, sal_Int32 /* ySlot */ )
+{
+ VSeriesPlotter::addSeries( std::move(pSeries), 0, -1, 0 );
+}
+
+double PieChart::getMinimumX()
+{
+ return 0.5;
+}
+double PieChart::getMaxOffset()
+{
+ if (!std::isnan(m_fMaxOffset))
+ // Value already cached. Use it.
+ return m_fMaxOffset;
+
+ m_fMaxOffset = 0.0;
+ if( m_aZSlots.empty() )
+ return m_fMaxOffset;
+ if( m_aZSlots.front().empty() )
+ return m_fMaxOffset;
+
+ const std::vector< std::unique_ptr<VDataSeries> >& rSeriesList( m_aZSlots.front().front().m_aSeriesVector );
+ if(rSeriesList.empty())
+ return m_fMaxOffset;
+
+ VDataSeries* pSeries = rSeriesList.front().get();
+ uno::Reference< beans::XPropertySet > xSeriesProp( pSeries->getPropertiesOfSeries() );
+ if( !xSeriesProp.is() )
+ return m_fMaxOffset;
+
+ double fExplodePercentage=0.0;
+ xSeriesProp->getPropertyValue( "Offset") >>= fExplodePercentage;
+ if(fExplodePercentage>m_fMaxOffset)
+ m_fMaxOffset=fExplodePercentage;
+
+ if(!m_bSizeExcludesLabelsAndExplodedSegments)
+ {
+ uno::Sequence< sal_Int32 > aAttributedDataPointIndexList;
+ if( xSeriesProp->getPropertyValue( "AttributedDataPoints" ) >>= aAttributedDataPointIndexList )
+ {
+ for(sal_Int32 nN=aAttributedDataPointIndexList.getLength();nN--;)
+ {
+ uno::Reference< beans::XPropertySet > xPointProp( pSeries->getPropertiesOfPoint(aAttributedDataPointIndexList[nN]) );
+ if(xPointProp.is())
+ {
+ fExplodePercentage=0.0;
+ xPointProp->getPropertyValue( "Offset") >>= fExplodePercentage;
+ if(fExplodePercentage>m_fMaxOffset)
+ m_fMaxOffset=fExplodePercentage;
+ }
+ }
+ }
+ }
+ return m_fMaxOffset;
+}
+double PieChart::getMaximumX()
+{
+ double fMaxOffset = getMaxOffset();
+ if( !m_aZSlots.empty() && m_bUseRings)
+ return m_aZSlots.front().size()+0.5+fMaxOffset;
+ return 1.5+fMaxOffset;
+}
+double PieChart::getMinimumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
+{
+ return 0.0;
+}
+
+double PieChart::getMaximumYInRange( double /* fMinimumX */, double /* fMaximumX */, sal_Int32 /* nAxisIndex */ )
+{
+ return 1.0;
+}
+
+bool PieChart::isExpandBorderToIncrementRhythm( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+bool PieChart::isExpandIfValuesCloseToBorder( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+bool PieChart::isExpandWideValuesToZero( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+bool PieChart::isExpandNarrowValuesTowardZero( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+bool PieChart::isSeparateStackingForDifferentSigns( sal_Int32 /* nDimensionIndex */ )
+{
+ return false;
+}
+
+void PieChart::createShapes()
+{
+ ///a ZSlot is a vector< vector< VDataSeriesGroup > >. There is only one
+ ///ZSlot: m_aZSlots[0] which has a number of elements equal to the total
+ ///number of data series (in fact, even if m_aZSlots[0][i] is an object of
+ ///type `VDataSeriesGroup`, in the current implementation, there is only one
+ ///data series in each data series group).
+ if (m_aZSlots.empty())
+ // No series to plot.
+ return;
+
+ ///m_xLogicTarget is where the group of all data series shapes (e.g. a pie
+ ///slice) is added (xSeriesTarget);
+
+ ///m_xFinalTarget is where the group of all text shapes (labels) is added
+ ///(xTextTarget).
+
+ ///both have been already created and added to the same root shape
+ ///( a member of a VDiagram object); this initialization occurs in
+ ///`ChartView::impl_createDiagramAndContent`.
+
+ OSL_ENSURE(m_xLogicTarget.is() && m_xFinalTarget.is(), "PieChart is not properly initialized.");
+ if (!m_xLogicTarget.is() || !m_xFinalTarget.is())
+ return;
+
+ ///the text labels should be always on top of the other series shapes
+ ///therefore create an own group for the texts to move them to front
+ ///(because the text group is created after the series group the texts are
+ ///displayed on top)
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTarget = createGroupShape( m_xLogicTarget );
+ rtl::Reference<SvxShapeGroup> xTextTarget = ShapeFactory::createGroup2D( m_xFinalTarget );
+ //check necessary here that different Y axis can not be stacked in the same group? ... hm?
+
+ ///pay attention that the `m_bSwapXAndY` parameter used by the polar
+ ///plotting position helper is always set to true for pie/donut charts
+ ///(see PieChart::setScales). This fact causes that `createShapes` expects
+ ///that the radius axis scale is the one with index 0 and the angle axis
+ ///scale is the one with index 1.
+
+ std::vector< VDataSeriesGroup >::iterator aXSlotIter = m_aZSlots.front().begin();
+ const std::vector< VDataSeriesGroup >::const_iterator aXSlotEnd = m_aZSlots.front().end();
+
+ ///m_bUseRings == true if chart type is `donut`, == false if chart type is
+ ///`pie`; if the chart is of `donut` type we have as many rings as many data
+ ///series, else we have a single ring (a pie) representing the first data
+ ///series;
+ ///for what I can see the radius axis orientation is always reversed and
+ ///the angle axis orientation is always non-reversed;
+ ///the radius axis scale range is [0.5, number of rings + 0.5 + max_offset],
+ ///the angle axis scale range is [0, 1]. The max_offset parameter is used
+ ///for exploded pie chart and its value is 0.5.
+
+ ///the `explodeable` ring is the first one except when the radius axis
+ ///orientation is reversed (always!?) and we are dealing with a donut: in
+ ///such a case the `explodeable` ring is the last one.
+ std::vector< VDataSeriesGroup >::size_type nExplodeableSlot = 0;
+ if( m_pPosHelper->isMathematicalOrientationRadius() && m_bUseRings )
+ nExplodeableSlot = m_aZSlots.front().size()-1;
+
+ m_aLabelInfoList.clear();
+ m_fMaxOffset = std::numeric_limits<double>::quiet_NaN();
+ sal_Int32 n3DRelativeHeight = 100;
+ if ( (m_nDimension==3) && m_xChartTypeModel.is())
+ {
+ try
+ {
+ uno::Any aAny = m_xChartTypeModel->getPropertyValue( "3DRelativeHeight" );
+ aAny >>= n3DRelativeHeight;
+ }
+ catch (const uno::Exception&) { }
+ }
+ ///iterate over each xslot, that is on each data series (there is
+ ///only one data series in each data series group!); note that if the chart
+ ///type is a pie the loop iterates only over the first data series
+ ///(m_bUseRings||fSlotX<0.5)
+ for( double fSlotX=0; aXSlotIter != aXSlotEnd && (m_bUseRings||fSlotX<0.5 ); ++aXSlotIter, fSlotX+=1.0 )
+ {
+ ShapeParam aParam;
+
+ std::vector< std::unique_ptr<VDataSeries> >* pSeriesList = &(aXSlotIter->m_aSeriesVector);
+ if(pSeriesList->empty())//there should be only one series in each x slot
+ continue;
+ VDataSeries* pSeries = pSeriesList->front().get();
+ if(!pSeries)
+ continue;
+
+ bool bHasFillColorMapping = pSeries->hasPropertyMapping("FillColor");
+
+ /// The angle degree offset is set by the same property of the
+ /// data series.
+ /// Counter-clockwise offset from the 3 o'clock position.
+ m_pPosHelper->m_fAngleDegreeOffset = pSeries->getStartingAngle();
+
+ ///iterate through all points to get the sum of all entries of
+ ///the current data series
+ sal_Int32 nPointIndex=0;
+ sal_Int32 nPointCount=pSeries->getTotalPointCount();
+ for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
+ {
+ double fY = pSeries->getYValue( nPointIndex );
+ if(fY<0.0)
+ {
+ //@todo warn somehow that negative values are treated as positive
+ }
+ if( std::isnan(fY) )
+ continue;
+ aParam.mfLogicYSum += fabs(fY);
+ }
+
+ if (aParam.mfLogicYSum == 0.0)
+ // Total sum of all Y values in this series is zero. Skip the whole series.
+ continue;
+
+ double fLogicYForNextPoint = 0.0;
+ ///iterate through all points to create shapes
+ for( nPointIndex = 0; nPointIndex < nPointCount; nPointIndex++ )
+ {
+ double fLogicInnerRadius, fLogicOuterRadius;
+
+ ///compute the maximum relative distance offset of the current slice
+ ///from the pie center
+ ///it is worth noting that after the first invocation the maximum
+ ///offset value is cached, so it is evaluated only once per each
+ ///call to `createShapes`
+ double fOffset = getMaxOffset();
+
+ ///compute the outer and the inner radius for the current ring slice
+ bool bIsVisible = m_pPosHelper->getInnerAndOuterRadius( fSlotX+1.0, fLogicInnerRadius, fLogicOuterRadius, m_bUseRings, fOffset );
+ if( !bIsVisible )
+ continue;
+
+ aParam.mfDepth = getTransformedDepth() * (n3DRelativeHeight / 100.0);
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesGroupShape_Shapes = getSeriesGroupShape(pSeries, xSeriesTarget);
+ ///collect data point information (logic coordinates, style ):
+ double fLogicYValue = fabs(pSeries->getYValue( nPointIndex ));
+ if( std::isnan(fLogicYValue) )
+ continue;
+ if(fLogicYValue==0.0)//@todo: continue also if the resolution is too small
+ continue;
+ double fLogicYPos = fLogicYForNextPoint;
+ fLogicYForNextPoint += fLogicYValue;
+
+ uno::Reference< beans::XPropertySet > xPointProperties = pSeries->getPropertiesOfPoint( nPointIndex );
+
+ //iterate through all subsystems to create partial points
+ {
+ //logic values on angle axis:
+ double fLogicStartAngleValue = fLogicYPos / aParam.mfLogicYSum;
+ double fLogicEndAngleValue = (fLogicYPos+fLogicYValue) / aParam.mfLogicYSum;
+
+ ///note that the explode percentage is set to the `Offset`
+ ///property of the current data series entry only for slices
+ ///belonging to the outer ring
+ aParam.mfExplodePercentage = 0.0;
+ bool bDoExplode = ( nExplodeableSlot == static_cast< std::vector< VDataSeriesGroup >::size_type >(fSlotX) );
+ if(bDoExplode) try
+ {
+ xPointProperties->getPropertyValue( "Offset") >>= aParam.mfExplodePercentage;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ ///see notes for `PolarPlottingPositionHelper` methods
+ ///transform to unit circle:
+ aParam.mfUnitCircleWidthAngleDegree = m_pPosHelper->getWidthAngleDegree( fLogicStartAngleValue, fLogicEndAngleValue );
+ aParam.mfUnitCircleStartAngleDegree = m_pPosHelper->transformToAngleDegree( fLogicStartAngleValue );
+ aParam.mfUnitCircleInnerRadius = m_pPosHelper->transformToRadius( fLogicInnerRadius );
+ aParam.mfUnitCircleOuterRadius = m_pPosHelper->transformToRadius( fLogicOuterRadius );
+
+ ///create data point
+ aParam.mfLogicZ = -1.0; // For 3D pie chart label position
+
+ // Do concentric explosion if it's a donut chart with more than one series
+ const bool bConcentricExplosion = m_bUseRings && (m_aZSlots.front().size() > 1);
+ rtl::Reference<SvxShape> xPointShape =
+ createDataPoint(
+ xSeriesGroupShape_Shapes, xPointProperties, aParam, nPointCount,
+ bConcentricExplosion);
+
+ ///point color:
+ if (!pSeries->hasPointOwnColor(nPointIndex) && m_xColorScheme.is())
+ {
+ xPointShape->setPropertyValue("FillColor",
+ uno::Any(m_xColorScheme->getColorByIndex( nPointIndex )));
+ }
+
+
+ if(bHasFillColorMapping)
+ {
+ double nPropVal = pSeries->getValueByProperty(nPointIndex, "FillColor");
+ if(!std::isnan(nPropVal))
+ {
+ xPointShape->setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32>( nPropVal)));
+ }
+ }
+
+ ///create label
+ createTextLabelShape(xTextTarget, *pSeries, nPointIndex, aParam);
+
+ if(!bDoExplode)
+ {
+ ShapeFactory::setShapeName( xPointShape
+ , ObjectIdentifier::createPointCID( pSeries->getPointCID_Stub(), nPointIndex ) );
+ }
+ else try
+ {
+ ///enable dragging of outer segments
+
+ double fAngle = aParam.mfUnitCircleStartAngleDegree + aParam.mfUnitCircleWidthAngleDegree/2.0;
+ double fMaxDeltaRadius = aParam.mfUnitCircleOuterRadius-aParam.mfUnitCircleInnerRadius;
+ drawing::Position3D aOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius, aParam.mfLogicZ );
+ drawing::Position3D aNewOrigin = m_pPosHelper->transformUnitCircleToScene( fAngle, aParam.mfUnitCircleOuterRadius + fMaxDeltaRadius, aParam.mfLogicZ );
+
+ sal_Int32 nOffsetPercent( static_cast<sal_Int32>(aParam.mfExplodePercentage * 100.0) );
+
+ awt::Point aMinimumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
+ aOrigin, m_xLogicTarget, m_nDimension ) );
+ awt::Point aMaximumPosition( PlottingPositionHelper::transformSceneToScreenPosition(
+ aNewOrigin, m_xLogicTarget, m_nDimension ) );
+
+ //enable dragging of piesegments
+ OUString aPointCIDStub( ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT
+ , pSeries->getSeriesParticle()
+ , ObjectIdentifier::getPieSegmentDragMethodServiceName()
+ , ObjectIdentifier::createPieSegmentDragParameterString(
+ nOffsetPercent, aMinimumPosition, aMaximumPosition )
+ ) );
+
+ ShapeFactory::setShapeName( xPointShape
+ , ObjectIdentifier::createPointCID( aPointCIDStub, nPointIndex ) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }//next series in x slot (next y slot)
+ }//next category
+ }//next x slot
+}
+
+PieChart::PieLabelInfo::PieLabelInfo()
+ : fValue(0.0)
+ , bMovementAllowed(false), bMoved(false)
+ , bShowLeaderLine(false), pPrevious(nullptr)
+ , pNext(nullptr)
+{
+}
+
+/** In case this label and the passed label overlap the routine moves this
+ * label in order to fix the issue. After the label position has been
+ * rearranged it is checked that the moved label is still inside the page
+ * document, if the test is positive the routine returns true else returns
+ * false.
+ */
+bool PieChart::PieLabelInfo::moveAwayFrom( const PieChart::PieLabelInfo* pFix, const awt::Size& rPageSize, bool bMoveHalfWay, bool bMoveClockwise )
+{
+ //return true if the move was successful
+ if(!bMovementAllowed)
+ return false;
+
+ const sal_Int32 nLabelDistanceX = rPageSize.Width/50;
+ const sal_Int32 nLabelDistanceY = rPageSize.Height/50;
+
+ ///compute the rectangle representing the intersection of the label bounding
+ ///boxes (`aOverlap`).
+ ::basegfx::B2IRectangle aOverlap( lcl_getRect( xLabelGroupShape ) );
+ aOverlap.intersect( lcl_getRect( pFix->xLabelGroupShape ) );
+ if( aOverlap.isEmpty() )
+ return true;
+
+ //TODO: alternative move direction
+
+ ///the label is shifted along the direction orthogonal to the vector
+ ///starting at the pie/donut center and ending at this label anchor
+ ///point;
+
+ ///named `aTangentialDirection` the unit vector related to such a
+ ///direction, the magnitude of the shift along such a direction is
+ ///calculated in this way: if the horizontal component of
+ ///`aTangentialDirection` is greater than the vertical component,
+ ///the magnitude of the shift is equal to `aOverlap.Width` else to
+ ///`aOverlap.Height`;
+ basegfx::B2IVector aRadiusDirection = aFirstPosition - aOrigin;
+ aRadiusDirection.setLength(1.0);
+ basegfx::B2IVector aTangentialDirection( -aRadiusDirection.getY(), aRadiusDirection.getX() );
+ bool bShiftHorizontal = abs(aTangentialDirection.getX()) > abs(aTangentialDirection.getY());
+ sal_Int32 nShift = bShiftHorizontal ? static_cast<sal_Int32>(aOverlap.getWidth()) : static_cast<sal_Int32>(aOverlap.getHeight());
+ ///the magnitude of the shift is also increased by 1/50-th of the width
+ ///or the height of the document page;
+ nShift += (bShiftHorizontal ? nLabelDistanceX : nLabelDistanceY);
+ ///in case the `bMoveHalfWay` parameter is true the magnitude of
+ ///the shift is halved.
+ if( bMoveHalfWay )
+ nShift/=2;
+ ///in case the `bMoveClockwise` parameter is false the direction of
+ ///`aTangentialDirection` is reversed;
+ if(!bMoveClockwise)
+ nShift*=-1;
+ awt::Point aOldPos( xLabelGroupShape->getPosition() );
+ basegfx::B2IVector aNewPos = basegfx::B2IVector( aOldPos.X, aOldPos.Y ) + nShift*aTangentialDirection;
+
+ ///a final check is performed in order to be sure that the moved label
+ ///is still inside the page document;
+ awt::Point aNewAWTPos( aNewPos.getX(), aNewPos.getY() );
+ if( !lcl_isInsidePage( aNewAWTPos, xLabelGroupShape->getSize(), rPageSize ) )
+ return false;
+
+ xLabelGroupShape->setPosition( aNewAWTPos );
+ bMoved = true;
+
+ return true;
+
+ ///note that no further test is performed in order to check that the
+ ///overlap is really fixed: this result is surely achieved if the shift
+ ///would occur in the horizontal or vertical direction (since, in such a
+ ///direction, the magnitude of the shift would be greater than the length
+ ///of the overlap), but in general this is not true;
+ ///adding a constant term equal to 1/50-th of the width or the height of
+ ///the document page increases the probability of success, anyway it is
+ ///worth noting that the method can return true even if the overlap issue
+ ///is not (completely) fixed;
+}
+
+void PieChart::resetLabelPositionsToPreviousState()
+{
+ for (auto const& labelInfo : m_aLabelInfoList)
+ labelInfo.xLabelGroupShape->setPosition(labelInfo.aPreviousPosition);
+}
+
+bool PieChart::detectLabelOverlapsAndMove( const awt::Size& rPageSize )
+{
+ ///the routine tries to individuate a chain of overlapping labels and
+ ///assigns the first and the last of them to `pFirstBorder` and
+ ///`pSecondBorder`;
+ ///this result is achieved by performing two consecutive while loop.
+
+ ///find borders of a group of overlapping labels
+
+ ///a first while loop is started on the collection of `PieLabelInfo` objects;
+ ///the bounding box of each label is checked for overlap against the bounding
+ ///box of the previous and of the next label;
+ ///when an overlap is found `bOverlapFound` is set to true, however the
+ ///iteration is break only if the overlap occurs against only the next label
+ ///and not against the previous label: so we exit from the loop whenever an
+ ///overlap occurs except when the loop initial label overlaps with the
+ ///previous one;
+ bool bOverlapFound = false;
+ PieLabelInfo* pStart = &(*(m_aLabelInfoList.rbegin()));
+ PieLabelInfo* pFirstBorder = nullptr;
+ PieLabelInfo* pSecondBorder = nullptr;
+ PieLabelInfo* pCurrent = pStart;
+ do
+ {
+ ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
+ ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
+ aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
+ aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
+
+ bool bPreviousOverlap = !aPreviousOverlap.isEmpty();
+ bool bNextOverlap = !aNextOverlap.isEmpty();
+ if( bPreviousOverlap || bNextOverlap )
+ bOverlapFound = true;
+ if( !bPreviousOverlap && bNextOverlap )
+ {
+ pFirstBorder = pCurrent;
+ break;
+ }
+ pCurrent = pCurrent->pNext;
+ }
+ while( pCurrent != pStart );
+
+ if( !bOverlapFound )
+ return false;
+
+ ///in case we found a label (`pFirstBorder`) which overlaps with the next
+ ///label and not with the previous label a second while loop is started with
+ ///`pFirstBorder` as initial label; one more time the bounding box of each
+ ///label is checked for overlap against the bounding box of the previous and
+ ///of the next label, however this time we exit from the loop only if the
+ ///current label overlaps with the previous one but does not with the next
+ ///one (the opposite of what is required in the former loop);
+ ///in case such a label is found it is assigned to `pSecondBorder` and the
+ ///iteration is stopped; so in case there is a chain of overlapping labels
+ ///we end up having the first label of the chain pointed by `pFirstBorder`
+ ///and the last label of the chain pointed by `pSecondBorder`;
+ if( pFirstBorder )
+ {
+ pCurrent = pFirstBorder;
+ do
+ {
+ ::basegfx::B2IRectangle aPreviousOverlap( lcl_getRect( pCurrent->xLabelGroupShape ) );
+ ::basegfx::B2IRectangle aNextOverlap( aPreviousOverlap );
+ aPreviousOverlap.intersect( lcl_getRect( pCurrent->pPrevious->xLabelGroupShape ) );
+ aNextOverlap.intersect( lcl_getRect( pCurrent->pNext->xLabelGroupShape ) );
+
+ if( !aPreviousOverlap.isEmpty() && aNextOverlap.isEmpty() )
+ {
+ pSecondBorder = pCurrent;
+ break;
+ }
+ pCurrent = pCurrent->pNext;
+ }
+ while( pCurrent != pFirstBorder );
+ }
+
+ ///when two labels satisfying the required conditions are not found
+ ///(`pFirstBorder == 0 || pSecondBorder == 0`) but still an overlap occurs
+ ///(`bOverlapFound == true`) we are in the situation where each label
+ ///overlaps with both the previous and the next one; so `pFirstBorder` is
+ ///set to point to the last `PieLabelInfo` object in the collection and
+ ///`pSecondBorder` is set to point to the first one;
+ if( !pFirstBorder || !pSecondBorder )
+ {
+ pFirstBorder = &(*(m_aLabelInfoList.rbegin()));
+ pSecondBorder = &(*(m_aLabelInfoList.begin()));
+ }
+
+ ///the total number of labels that made up the chain is calculated and used
+ ///for getting a pointer to the central label (`pCenter`);
+ PieLabelInfo* pCenter = pFirstBorder;
+ sal_Int32 nOverlapGroupCount = 1;
+ for( pCurrent = pFirstBorder ;pCurrent != pSecondBorder; pCurrent = pCurrent->pNext )
+ nOverlapGroupCount++;
+ sal_Int32 nCenterPos = nOverlapGroupCount/2;
+ bool bSingleCenter = nOverlapGroupCount%2 != 0;
+ if( bSingleCenter )
+ nCenterPos++;
+ if(nCenterPos>1)
+ {
+ pCurrent = pFirstBorder;
+ while( --nCenterPos )
+ pCurrent = pCurrent->pNext;
+ pCenter = pCurrent;
+ }
+
+ ///the current position of each label in the collection is saved in
+ ///`PieLabelInfo.aPreviousPosition`, so that it is possible to undo the label
+ ///move action if it is needed; the undo action is provided by the
+ ///`PieChart::resetLabelPositionsToPreviousState` method.
+ pCurrent = pStart;
+ do
+ {
+ pCurrent->aPreviousPosition = pCurrent->xLabelGroupShape->getPosition();
+ pCurrent = pCurrent->pNext;
+ }
+ while( pCurrent != pStart );
+
+ ///the `PieChart::tryMoveLabels` method is invoked with
+ ///`rbAlternativeMoveDirection` boolean parameter set to false, such a method
+ ///tries to remove all overlaps that occur in the list of labels going from
+ ///`pFirstBorder` to `pSecondBorder`;
+ ///if the `PieChart::tryMoveLabels` returns true no further action is
+ ///performed, however it is worth noting that it does not mean that all
+ ///overlap issues have been surely fixed, but only that all moved labels are
+ ///at least completely inside the page document;
+ ///when `PieChart::tryMoveLabels` returns false, it means that the attempt
+ ///to fix one of the overlap issues caused that a label has been moved
+ ///(partially) outside the page document (anyway the `PieChart::tryMoveLabels`
+ ///method takes care to restore the position of all labels to their initial
+ ///position, and to set the `rbAlternativeMoveDirection` in/out parameter to
+ ///true); in such a case a second invocation of `PieChart::tryMoveLabels` is
+ ///performed (and this time the `rbAlternativeMoveDirection` boolean
+ ///parameter is true) and independently by what the `PieChart::tryMoveLabels`
+ ///method returns no further action is performed;
+ ///(see notes for `PieChart::tryMoveLabels`);
+ bool bAlternativeMoveDirection = false;
+ if( !tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize ) )
+ tryMoveLabels( pFirstBorder, pSecondBorder, pCenter, bSingleCenter, bAlternativeMoveDirection, rPageSize );
+
+ ///in both cases (one or two invocations of `PieChart::tryMoveLabels`) the
+ ///`detectLabelOverlapsAndMove` method ends returning true.
+ return true;
+}
+
+
+/** Try to remove all overlaps that occur in the list of labels going from
+ * `pFirstBorder` to `pSecondBorder`
+ */
+bool PieChart::tryMoveLabels( PieLabelInfo const * pFirstBorder, PieLabelInfo const * pSecondBorder
+ , PieLabelInfo* pCenter
+ , bool bSingleCenter, bool& rbAlternativeMoveDirection, const awt::Size& rPageSize )
+{
+
+ PieLabelInfo* p1 = bSingleCenter ? pCenter->pPrevious : pCenter;
+ PieLabelInfo* p2 = pCenter->pNext;
+ //return true when successful
+
+ bool bLabelOrderIsAntiClockWise = m_pPosHelper->isMathematicalOrientationAngle();
+
+ ///two loops are performed simultaneously: the outer loop iterates on
+ ///`PieLabelInfo` objects in the list starting from the central element
+ ///(`pCenter`) and moving forward until the last element (`pSecondBorder`);
+ ///the inner loop starts from the previous element of `pCenter` and moves
+ ///forward until the current `PieLabelInfo` object of the outer loop is
+ ///reached
+ PieLabelInfo* pCurrent = nullptr;
+ for( pCurrent = p2 ;pCurrent->pPrevious != pSecondBorder; pCurrent = pCurrent->pNext )
+ {
+ PieLabelInfo* pFix = nullptr;
+ for( pFix = p2->pPrevious ;pFix != pCurrent; pFix = pFix->pNext )
+ {
+ ///on the current `PieLabelInfo` object of the outer loop the
+ ///`moveAwayFrom` method is invoked by passing the current
+ ///`PieLabelInfo` object of the inner loop as argument.
+
+ ///so each label going from the central one to the last one is
+ ///checked for overlapping against all previous labels (that comes
+ ///after the central label) and in case the overlap occurs the
+ ///`moveAwayFrom` method tries to fix the issue;
+ ///if `moveAwayFrom` returns true (pay attention: that does not
+ ///mean that the overlap issue has been surely fixed but only that
+ ///the moved label is at least completely inside the page document:
+ ///see notes on `PieChart::PieLabelInfo::moveAwayFrom`), the inner
+ ///loop starts a new iteration else the `rbAlternativeMoveDirection`
+ ///boolean parameter is tested: if it is false the parameter is set
+ ///to true, the position of all labels is restored to the initial
+ ///one (through the `PieChart::resetLabelPositionsToPreviousState`
+ ///method) and the method ends by returning false, else the inner
+ ///loop starts a new iteration step;
+ ///so when `rbAlternativeMoveDirection` is true the method goes on
+ ///trying to fix left overlap issues even if the last `moveAwayFrom`
+ ///invocation has moved a label in a position that it is not
+ ///completely inside the page document
+
+ if( !pCurrent->moveAwayFrom( pFix, rPageSize, !bSingleCenter && pCurrent == p2, !bLabelOrderIsAntiClockWise ) )
+ {
+ if( !rbAlternativeMoveDirection )
+ {
+ rbAlternativeMoveDirection = true;
+ resetLabelPositionsToPreviousState();
+ return false;
+ }
+ }
+ }
+ }
+
+ ///if the method does not return before ending the first pair of loops,
+ ///a second pair of simultaneous loops is performed in the opposite
+ ///direction (respect with the previous case): the outer loop iterates on
+ ///`PieLabelInfo` objects in the list starting from the central element
+ ///(`pCenter`) and moving backward until the first element (`pFirstBorder`);
+ ///the inner loop starts from the next element of `pCenter` and moves
+ ///backward until the current `PieLabelInfo` object of the outer loop is
+ ///reached
+
+ ///like in the previous case on the current `PieLabelInfo` object of
+ ///the outer loop the `moveAwayFrom` method is invoked by passing
+ ///the current `PieLabelInfo` object of the inner loop as argument
+
+ ///so each label going from the central one to the first one is checked for
+ ///overlapping on all subsequent labels (that come before the central label)
+ ///and in case the overlap occurs the `moveAwayFrom` method tries to fix
+ ///the issue. The subsequent actions performed after the invocation
+ ///`moveAwayFrom` are the same detailed above for the first pair of loops
+
+ for( pCurrent = p1 ;pCurrent->pNext != pFirstBorder; pCurrent = pCurrent->pPrevious )
+ {
+ PieLabelInfo* pFix = nullptr;
+ for( pFix = p2->pNext ;pFix != pCurrent; pFix = pFix->pPrevious )
+ {
+ if( !pCurrent->moveAwayFrom( pFix, rPageSize, false, bLabelOrderIsAntiClockWise ) )
+ {
+ if( !rbAlternativeMoveDirection )
+ {
+ rbAlternativeMoveDirection = true;
+ resetLabelPositionsToPreviousState();
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+void PieChart::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& rPageSize )
+{
+ ///this method is invoked by `ChartView::impl_createDiagramAndContent` for
+ ///pie and donut charts after text label creation;
+ ///it tries to rearrange labels only when the label placement type is
+ ///`AVOID_OVERLAP`.
+ // no need to do anything when we only have one label
+ if (m_aLabelInfoList.size() < 2)
+ return;
+
+ ///check whether there are any labels that should be moved
+ bool bMoveableFound = false;
+ for (auto const& labelInfo : m_aLabelInfoList)
+ {
+ if(labelInfo.bMovementAllowed)
+ {
+ bMoveableFound = true;
+ break;
+ }
+ }
+ if(!bMoveableFound)
+ return;
+
+ double fPageDiagonaleLength = std::hypot(rPageSize.Width, rPageSize.Height);
+ if( fPageDiagonaleLength == 0.0 )
+ return;
+
+ ///initialize next and previous member of `PieLabelInfo` objects
+ auto aIt1 = m_aLabelInfoList.begin();
+ auto aEnd = m_aLabelInfoList.end();
+ std::vector< PieLabelInfo >::iterator aIt2 = aIt1;
+ aIt1->pPrevious = &(*(m_aLabelInfoList.rbegin()));
+ ++aIt2;
+ for( ;aIt2!=aEnd; ++aIt1, ++aIt2 )
+ {
+ PieLabelInfo& rInfo1( *aIt1 );
+ PieLabelInfo& rInfo2( *aIt2 );
+ rInfo1.pNext = &rInfo2;
+ rInfo2.pPrevious = &rInfo1;
+ }
+ aIt1->pNext = &(*(m_aLabelInfoList.begin()));
+
+ ///detect overlaps and move
+ sal_Int32 nMaxIterations = 50;
+ while( detectLabelOverlapsAndMove( rPageSize ) && nMaxIterations > 0 )
+ nMaxIterations--;
+
+ ///create connection lines for the moved labels
+ VLineProperties aVLineProperties;
+ for (auto const& labelInfo : m_aLabelInfoList)
+ {
+ if( labelInfo.bMoved && labelInfo.bShowLeaderLine )
+ {
+ sal_Int32 nX1 = labelInfo.aOuterPosition.getX();
+ sal_Int32 nY1 = labelInfo.aOuterPosition.getY();
+ sal_Int32 nX2 = nX1;
+ sal_Int32 nY2 = nY1;
+ ::basegfx::B2IRectangle aRect( lcl_getRect( labelInfo.xLabelGroupShape ) );
+ if( nX1 < aRect.getMinX() )
+ nX2 = aRect.getMinX();
+ else if( nX1 > aRect.getMaxX() )
+ nX2 = aRect.getMaxX();
+
+ if( nY1 < aRect.getMinY() )
+ nY2 = aRect.getMinY();
+ else if( nY1 > aRect.getMaxY() )
+ nY2 = aRect.getMaxY();
+
+ //when the line is very short compared to the page size don't create one
+ ::basegfx::B2DVector aLength(nX1-nX2, nY1-nY2);
+ if( (aLength.getLength()/fPageDiagonaleLength) < 0.01 )
+ continue;
+
+ drawing::PointSequenceSequence aPoints{ { {nX1, nY1}, {nX2, nY2} } };
+
+ if( labelInfo.xTextShape.is() )
+ {
+ sal_Int32 nColor = 0;
+ labelInfo.xTextShape->SvxShape::getPropertyValue("CharColor") >>= nColor;
+ if( nColor != -1 )//automatic font color does not work for lines -> fallback to black
+ aVLineProperties.Color <<= nColor;
+ }
+ ShapeFactory::createLine2D( labelInfo.xTextTarget, aPoints, &aVLineProperties );
+ }
+ }
+}
+
+
+/** Handle the placement of the label in the best fit case:
+ * the routine try to place the label inside the related pie slice,
+ * in case of success it returns true else returns false.
+ *
+ * Notation:
+ * C: the pie center
+ * s: the bisector ray of the current pie slice
+ * alpha: the angle between the horizontal axis and the bisector ray s
+ * N: the vertex of the label b.b. which is nearest to C
+ * F: the vertex of the label b.b. not adjacent to N; F lies on the pie border
+ * P, Q: the intersection points between the label b.b. and the bisector ray s;
+ * P is the one at minimum distance respect with C
+ * e: the edge of the label b.b. where P lies (the nearest edge to C)
+ * M: the vertex of e that is not N
+ * G: the vertex of the label b.b. which is adjacent to N and that is not M
+ * beta: the angle MPF
+ * theta: the angle CPF
+ *
+ *
+ * |
+ * | /s
+ * | /
+ * | /
+ * | G _________________________/____________________________ F
+ * | | /Q ..|
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / d. . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . . |
+ * | | / . \ beta . |
+ * | |__________/._\___|_______.____________________________|
+ * | N /P / . M
+ * | /___/theta .
+ * | / .
+ * | / . r
+ * | / .
+ * | / .
+ * | / .
+ * | / .
+ * | / .
+ * | / .
+ * | / .
+ * | / .
+ * | /\. alpha
+ * __|/__|_____________________________________________________________
+ * |C
+ * |
+ *
+ *
+ * When alpha = 45k (k integer) s crosses the label b.b. at N exactly.
+ * In such a case the nearest edge e is defined as the edge having N as the
+ * start vertex and that is covered in the counterclockwise direction when
+ * we move from N to the adjacent vertex.
+ *
+ * The nearest vertex N is:
+ * 1. the bottom left vertex when 0 < alpha < 90
+ * 2. the bottom right vertex when 90 < alpha < 180
+ * 3. the top right vertex when 180 < alpha < 270
+ * 4. the top left vertex when 270 < alpha < 360.
+ *
+ * The nearest edge e is:
+ * 1. the left edge when −45 < alpha < 45
+ * 2. the bottom edge when 45 < alpha <135
+ * 3. the right edge when 135 < alpha < 225
+ * 4. the top edge when 225 < alpha < 315.
+ *
+ **/
+bool PieChart::performLabelBestFitInnerPlacement(ShapeParam& rShapeParam, PieLabelInfo const & rPieLabelInfo)
+{
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ "** PieChart::performLabelBestFitInnerPlacement invoked **" );
+
+ // get pie slice properties
+ double fStartAngleDeg = NormAngle360(rShapeParam.mfUnitCircleStartAngleDegree);
+ double fWidthAngleDeg = rShapeParam.mfUnitCircleWidthAngleDegree;
+ double fHalfWidthAngleDeg = fWidthAngleDeg / 2.0;
+ double fBisectingRayAngleDeg = NormAngle360(fStartAngleDeg + fHalfWidthAngleDeg);
+
+ // get the middle point of the arc representing the pie slice border
+ double fLogicZ = rShapeParam.mfLogicZ + 1.0;
+ awt::Point aMiddleArcPoint = PlottingPositionHelper::transformSceneToScreenPosition(
+ m_pPosHelper->transformUnitCircleToScene(
+ fBisectingRayAngleDeg,
+ rShapeParam.mfUnitCircleOuterRadius,
+ fLogicZ ),
+ m_xLogicTarget, m_nDimension );
+
+ // compute the pie radius
+ basegfx::B2IVector aPieCenter = rPieLabelInfo.aOrigin;
+ basegfx::B2IVector aRadiusVector(
+ aMiddleArcPoint.X - aPieCenter.getX(),
+ aMiddleArcPoint.Y - aPieCenter.getY() );
+ double fSquaredPieRadius = aRadiusVector.scalar(aRadiusVector);
+ double fPieRadius = sqrt( fSquaredPieRadius );
+
+ // the bb is moved as much as possible near to the border of the pie,
+ // anyway a small offset from the border is present (0.025 * pie radius)
+ const double fPieBorderOffset = 0.025;
+ fPieRadius = fPieRadius - fPieRadius * fPieBorderOffset;
+
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " pie sector:" );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " start angle = " << fStartAngleDeg );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " angle width = " << fWidthAngleDeg );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " bisecting ray angle = " << fBisectingRayAngleDeg );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " pie radius = " << fPieRadius );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " pie center = " << rPieLabelInfo.aOrigin );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " middle arc point = (" << aMiddleArcPoint.X << ","
+ << aMiddleArcPoint.Y << ")" );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " label bounding box:" );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " old anchor point = " << rPieLabelInfo.aFirstPosition );
+
+
+ if( fPieRadius == 0.0 )
+ return false;
+
+ // get label b.b. width and height
+ ::basegfx::B2IRectangle aBb( lcl_getRect( rPieLabelInfo.xLabelGroupShape ) );
+ double fLabelWidth = aBb.getWidth();
+ double fLabelHeight = aBb.getHeight();
+
+ // -45 <= fAlphaDeg < 315
+ double fAlphaDeg = NormAngle360(fBisectingRayAngleDeg + 45) - 45;
+ double fAlphaRad = basegfx::deg2rad(fAlphaDeg);
+
+ // compute nearest edge index
+ // 0 left
+ // 1 bottom
+ // 2 right
+ // 3 top
+ int nSectorIndex = floor( (fAlphaDeg + 45) / 45.0 );
+ int nNearestEdgeIndex = nSectorIndex / 2;
+
+ // compute lengths of the nearest edge and of the orthogonal edges
+ double fNearestEdgeLength = fLabelWidth;
+ double fOrthogonalEdgeLength = fLabelHeight;
+ basegfx::Axis2D eAxis = basegfx::Axis2D::X;
+ basegfx::Axis2D eOrthogonalAxis = basegfx::Axis2D::Y;
+ if( nNearestEdgeIndex % 2 == 0 ) // nearest edge is vertical
+ {
+ fNearestEdgeLength = fLabelHeight;
+ fOrthogonalEdgeLength = fLabelWidth;
+ eAxis = basegfx::Axis2D::Y;
+ eOrthogonalAxis = basegfx::Axis2D::X;
+ }
+
+ // compute the distance between N and P
+ // such a distance is piece wise linear respect with alpha:
+ // given 45k <= alpha < 45(k+1) we have
+ // when k is even: d(N,P) = (length(e) / 2) * (1 - (alpha - 45k)/45)
+ // when k is odd: d(N,P) = (length(e) / 2) * (1 - (45(k+1) - alpha)/45)
+ int nIndex = nSectorIndex -1; // nIndex = -1...6
+ double fIndexMod2 = (nIndex + 8) % 2; // fIndexMod2 must be non negative
+ double fSgn = 2.0 * (fIndexMod2 - 0.5); // 0 -> -1, 1 -> 1
+ double fDistanceNP = (fNearestEdgeLength / 2.0) * (1 + fSgn * ((fAlphaDeg - 45 * (nIndex + fIndexMod2)) / 45.0));
+ double fDistancePM = fNearestEdgeLength - fDistanceNP;
+
+ // compute the length of the diagonal vector d,
+ // that is the distance between P and F
+ double fDistancePF = std::hypot(fDistancePM, fOrthogonalEdgeLength);
+
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " width = " << fLabelWidth );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " height = " << fLabelHeight );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " nearest edge index = " << nNearestEdgeIndex );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " alpha = " << fAlphaDeg );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " distance(N,P) = " << fDistanceNP );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " nIndex = " << nIndex );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " fIndexMod2 = " << fIndexMod2 );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " fSgn = " << fSgn );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " distance(P,F) = " << fDistancePF );
+
+
+ // we check that the condition length(d) <= pie radius holds
+ if (fDistancePF > fPieRadius)
+ {
+ return false;
+ }
+
+ // compute beta: the angle of the diagonal vector d,
+ // that is, the angle in P respect with the triangle PMF;
+ // since both arguments are non negative the returned value is in [0, PI/2]
+ double fBetaRad = atan2( fOrthogonalEdgeLength, fDistancePM );
+
+ // compute the theta angle, that is the angle in P
+ // respect with the triangle CFP;
+ // when the second intersection edge is opposite to the nearest edge,
+ // theta depends on alpha and beta according to the following relation:
+ // theta = f(alpha, beta) = s * alpha + 90 * (1 - s * i) + beta
+ // where i is the nearest edge index and s is the sign of (alpha' - 45),
+ // with alpha' = (alpha + 45) mod 90;
+ // when the second intersection edge is adjacent to the nearest edge,
+ // we have theta = 360 - f(alpha, beta);
+ // note that in the former case 0 <= f(alpha, beta) <= 180,
+ // whilst in the latter case 180 <= f(alpha, beta) <= 360;
+ double fAlphaMod90 = fmod( fAlphaDeg + 45, 90.0 ) - 45;
+ double fSign = fAlphaMod90 == 0.0
+ ? 0.0
+ : ( fAlphaMod90 < 0 ) ? -1.0 : 1.0;
+ double fThetaRad = fSign * fAlphaRad + M_PI_2 * (1 - fSign * nNearestEdgeIndex) + fBetaRad;
+ if( fThetaRad > M_PI )
+ {
+ fThetaRad = 2 * M_PI - fThetaRad;
+ }
+
+ // compute the length of the positional vector,
+ // that is the distance between C and P
+ double fDistanceCP;
+ // when the bisector ray intersects the b.b. in F we have theta mod 180 == 0
+ if( fmod(fThetaRad, M_PI) == 0.0 )
+ {
+ fDistanceCP = fPieRadius - fDistancePF;
+ }
+ else // general case
+ {
+ // we can compute d(C,P) by applying some trigonometric formula to
+ // the triangle CFP : we know length(d) and length(r) = r and we have
+ // computed the angle in P (theta); so named delta the angle in C and
+ // gamma the angle in F, by the relation:
+ //
+ // r d(P,F) d(C,P)
+ // --------- = --------- = ---------
+ // sin theta sin delta sin gamma
+ //
+ // we get the wanted distance
+ double fSinTheta = sin( fThetaRad );
+ double fSinDelta = fDistancePF * fSinTheta / fPieRadius;
+ double fDeltaRad = asin( fSinDelta );
+ double fGammaRad = M_PI - (fThetaRad + fDeltaRad);
+ double fSinGamma = sin( fGammaRad );
+ fDistanceCP = fPieRadius * fSinGamma / fSinTheta;
+ }
+
+ // define the positional vector
+ basegfx::B2DVector aPositionalVector( cos(fAlphaRad), sin(fAlphaRad) );
+ aPositionalVector.setLength(fDistanceCP);
+
+ // we define a direction vector in order to know
+ // in which quadrant we are working
+ basegfx::B2DVector aDirection(1.0, 1.0);
+ if( 90 <= fBisectingRayAngleDeg && fBisectingRayAngleDeg < 270 )
+ {
+ aDirection.setX(-1.0);
+ }
+ if( fBisectingRayAngleDeg >= 180 )
+ {
+ aDirection.setY(-1.0);
+ }
+
+ // compute vertices N, M and G respect with pie center C
+ basegfx::B2DVector aNearestVertex(aPositionalVector);
+ aNearestVertex.set(eAxis, aNearestVertex.get(eAxis) - aDirection.get(eAxis) * fDistanceNP);
+ basegfx::B2DVector aVertexM(aNearestVertex);
+ aVertexM.set(eAxis, aVertexM.get(eAxis) + aDirection.get(eAxis) * fNearestEdgeLength);
+ basegfx::B2DVector aVertexG(aNearestVertex);
+ aVertexG.set(eOrthogonalAxis, aVertexG.get(eOrthogonalAxis) + aDirection.get(eOrthogonalAxis) * fOrthogonalEdgeLength);
+
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " beta = " << basegfx::rad2deg(fBetaRad) );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " theta = " << basegfx::rad2deg(fThetaRad) );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " fAlphaMod90 = " << fAlphaMod90 );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " fSign = " << fSign );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " distance(C,P) = " << fDistanceCP );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " direction vector = " << aDirection );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " N = " << aNearestVertex );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " M = " << aVertexM );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " G = " << aVertexG );
+
+ // in order to be able to place the label inside the pie slice we need
+ // to check that each angle between s and the ray starting from C and
+ // passing through a b.b. vertex is less than half width of the pie slice;
+ // when the nearest edge e crosses a Cartesian axis it is sufficient
+ // to test only the vertices belonging to e, else we need to test
+ // the 2 vertices that aren't either N or F. Note that if a b.b. edge
+ // crosses a Cartesian axis then it is the nearest edge to C
+
+ // check the angle between CP and CM
+ double fAngleRad = aPositionalVector.angle(aVertexM);
+ double fAngleDeg = NormAngle360(basegfx::rad2deg(fAngleRad));
+ if( fAngleDeg > 180 ) // in case the wrong angle has been computed
+ fAngleDeg = 360 - fAngleDeg;
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " angle between CP and CM: " << fAngleDeg );
+ if( fAngleDeg > fHalfWidthAngleDeg )
+ {
+ return false;
+ }
+
+ if( ( aNearestVertex.get(eAxis) >= 0 && aVertexM.get(eAxis) <= 0 )
+ || ( aNearestVertex.get(eAxis) <= 0 && aVertexM.get(eAxis) >= 0 ) )
+ {
+ // check the angle between CP and CN
+ fAngleRad = aPositionalVector.angle(aNearestVertex);
+ fAngleDeg = NormAngle360(basegfx::rad2deg(fAngleRad));
+ if( fAngleDeg > 180 ) // in case the wrong angle has been computed
+ fAngleDeg = 360 - fAngleDeg;
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " angle between CP and CN: " << fAngleDeg );
+ if( fAngleDeg > fHalfWidthAngleDeg )
+ {
+ return false;
+ }
+ }
+ else
+ {
+ // check the angle between CP and CG
+ fAngleRad = aPositionalVector.angle(aVertexG);
+ fAngleDeg = NormAngle360(basegfx::rad2deg(fAngleRad));
+ if( fAngleDeg > 180 ) // in case the wrong angle has been computed
+ fAngleDeg = 360 - fAngleDeg;
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " angle between CP and CG: " << fAngleDeg );
+ if( fAngleDeg > fHalfWidthAngleDeg )
+ {
+ return false;
+ }
+ }
+
+ // compute the b.b. center respect with the pie center
+ basegfx::B2DVector aBBCenter(aNearestVertex);
+ aBBCenter.set(eAxis, aBBCenter.get(eAxis) + aDirection.get(eAxis) * fNearestEdgeLength / 2);
+ aBBCenter.set(eOrthogonalAxis, aBBCenter.get(eOrthogonalAxis) + aDirection.get(eOrthogonalAxis) * fOrthogonalEdgeLength / 2);
+
+ // compute the b.b. anchor point
+ basegfx::B2IVector aNewAnchorPoint = aPieCenter;
+ aNewAnchorPoint.setX(aNewAnchorPoint.getX() + floor(aBBCenter.getX()));
+ aNewAnchorPoint.setY(aNewAnchorPoint.getY() - floor(aBBCenter.getY())); // the Y axis on the screen points downward
+
+ // compute the translation vector for moving the label from the current
+ // screen position to the new one
+ basegfx::B2IVector aTranslationVector = aNewAnchorPoint - rPieLabelInfo.aFirstPosition;
+
+ // compute the new screen position and move the label
+ // XShape::getPosition returns the top left vertex of the b.b. of the shape
+ awt::Point aOldPos( rPieLabelInfo.xLabelGroupShape->getPosition() );
+ awt::Point aNewPos( aOldPos.X + aTranslationVector.getX(),
+ aOldPos.Y + aTranslationVector.getY() );
+ rPieLabelInfo.xLabelGroupShape->setPosition(aNewPos);
+
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " center = " << aBBCenter );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " new anchor point = " << aNewAnchorPoint );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " translation vector = " << aTranslationVector );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " old position = (" << aOldPos.X << "," << aOldPos.Y << ")" );
+ SAL_INFO( "chart2.pie.label.bestfit.inside",
+ " new position = (" << aNewPos.X << "," << aNewPos.Y << ")" );
+
+ return true;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/PieChart.hxx b/chart2/source/view/charttypes/PieChart.hxx
new file mode 100644
index 000000000..9a5b7fb4c
--- /dev/null
+++ b/chart2/source/view/charttypes/PieChart.hxx
@@ -0,0 +1,145 @@
+/* -*- 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 <memory>
+#include <VSeriesPlotter.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <com/sun/star/awt/Point.hpp>
+
+namespace chart
+{
+class PiePositionHelper;
+
+class PieChart : public VSeriesPlotter
+{
+ struct ShapeParam;
+
+public:
+ PieChart() = delete;
+
+ PieChart( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount, bool bExcludingPositioning );
+ virtual ~PieChart() override;
+
+ /** This method creates all shapes needed for representing the pie chart.
+ */
+ virtual void createShapes() override;
+ virtual void rearrangeLabelToAvoidOverlapIfRequested( const css::awt::Size& rPageSize ) override;
+
+ virtual void setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis ) override;
+ virtual void addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot ) override;
+
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const override;
+ virtual bool shouldSnapRectToUsedArea() override;
+
+ //MinimumAndMaximumSupplier
+ virtual double getMinimumX() override;
+ virtual double getMaximumX() override;
+ virtual double getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) override;
+ virtual double getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) override;
+
+ virtual bool isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandWideValuesToZero( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex ) override;
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+private: //methods
+ rtl::Reference<SvxShape>
+ createDataPoint(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const css::uno::Reference<css::beans::XPropertySet>& xObjectProperties,
+ const ShapeParam& rParam,
+ const sal_Int32 nPointCount,
+ const bool bConcentricExplosion);
+
+ /** This method creates a text shape for a label of a data point.
+ *
+ * @param xTextTarget
+ * where to append the new created text shape.
+ * @param rSeries
+ * the data series, the data point belongs to.
+ * @param nPointIndex
+ * the index of the data point the label is related to.
+ * @param rParam
+ * ShapeParam object.
+ */
+ void createTextLabelShape(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget,
+ VDataSeries& rSeries, sal_Int32 nPointIndex, ShapeParam& rParam );
+
+ /** This method sets `m_fMaxOffset` to the maximum `Offset` property and
+ * returns it. There is a `Offset` property for each entry in a data
+ * series, moreover there exists a shared `Offset` property attached to
+ * the whole data series. The `Offset` property represents the
+ * relative distance offset of a slice from the pie center.
+ * The shared property is used for exploded pie chart, while the property
+ * attached to single data series entries is used for manual dragging of
+ * a slice.
+ * `m_fMaxOffset` is used by `PiePositionHelper::getInnerAndOuterRadius`.
+ * Note that only the `Offset` properties of the first (x slot) data series
+ * and its entries are utilized for computing the maximum offset.
+ */
+ double getMaxOffset();
+ bool detectLabelOverlapsAndMove(const css::awt::Size& rPageSize);//returns true when there might be more to do
+ void resetLabelPositionsToPreviousState();
+struct PieLabelInfo;
+ bool tryMoveLabels( PieLabelInfo const * pFirstBorder, PieLabelInfo const * pSecondBorder
+ , PieLabelInfo* pCenter, bool bSingleCenter, bool& rbAlternativeMoveDirection
+ , const css::awt::Size& rPageSize );
+
+ bool performLabelBestFitInnerPlacement( ShapeParam& rShapeParam
+ , PieLabelInfo const & rPieLabelInfo );
+
+private: //member
+ std::unique_ptr<PiePositionHelper>
+ m_pPosHelper;
+ bool m_bUseRings;
+ bool m_bSizeExcludesLabelsAndExplodedSegments;
+
+ struct PieLabelInfo
+ {
+ PieLabelInfo();
+ bool moveAwayFrom( const PieLabelInfo* pFix, const css::awt::Size& rPageSize
+ , bool bMoveHalfWay, bool bMoveClockwise );
+
+ rtl::Reference< SvxShapeText > xTextShape;
+ rtl::Reference< SvxShapeGroupAnyD > xLabelGroupShape;
+ ::basegfx::B2IVector aFirstPosition;
+ ::basegfx::B2IVector aOuterPosition;
+ ::basegfx::B2IVector aOrigin;
+ double fValue;
+ bool bMovementAllowed;
+ bool bMoved;
+ bool bShowLeaderLine;
+ rtl::Reference<SvxShapeGroupAnyD> xTextTarget;
+ PieLabelInfo* pPrevious;
+ PieLabelInfo* pNext;
+ css::awt::Point aPreviousPosition;
+ };
+
+ std::vector< PieLabelInfo > m_aLabelInfoList;
+
+ double m_fMaxOffset; /// cached max offset value (init'ed to NaN)
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/Splines.cxx b/chart2/source/view/charttypes/Splines.cxx
new file mode 100644
index 000000000..2aac96929
--- /dev/null
+++ b/chart2/source/view/charttypes/Splines.cxx
@@ -0,0 +1,872 @@
+/* -*- 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 "Splines.hxx"
+#include <osl/diagnose.h>
+#include <com/sun/star/drawing/Position3D.hpp>
+
+#include <vector>
+#include <algorithm>
+#include <memory>
+#include <cmath>
+#include <limits>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+
+namespace
+{
+
+typedef std::pair< double, double > tPointType;
+typedef std::vector< tPointType > tPointVecType;
+typedef tPointVecType::size_type lcl_tSizeType;
+
+class lcl_SplineCalculation
+{
+public:
+ /** @descr creates an object that calculates cubic splines on construction
+
+ @param rSortedPoints the points for which splines shall be calculated, they need to be sorted in x values
+ @param fY1FirstDerivation the resulting spline should have the first
+ derivation equal to this value at the x-value of the first point
+ of rSortedPoints. If fY1FirstDerivation is set to infinity, a natural
+ spline is calculated.
+ @param fYnFirstDerivation the resulting spline should have the first
+ derivation equal to this value at the x-value of the last point
+ of rSortedPoints
+ */
+ lcl_SplineCalculation( tPointVecType && rSortedPoints,
+ double fY1FirstDerivation,
+ double fYnFirstDerivation );
+
+ /** @descr creates an object that calculates cubic splines on construction
+ for the special case of periodic cubic spline
+
+ @param rSortedPoints the points for which splines shall be calculated,
+ they need to be sorted in x values. First and last y value must be equal
+ */
+ explicit lcl_SplineCalculation( tPointVecType && rSortedPoints);
+
+ /** @descr this function corresponds to the function splint in [1].
+
+ [1] Numerical Recipes in C, 2nd edition
+ William H. Press, et al.,
+ Section 3.3, page 116
+ */
+ double GetInterpolatedValue( double x );
+
+private:
+ /// a copy of the points given in the CTOR
+ tPointVecType m_aPoints;
+
+ /// the result of the Calculate() method
+ std::vector< double > m_aSecDerivY;
+
+ double m_fYp1;
+ double m_fYpN;
+
+ // these values are cached for performance reasons
+ lcl_tSizeType m_nKLow;
+ lcl_tSizeType m_nKHigh;
+ double m_fLastInterpolatedValue;
+
+ /** @descr this function corresponds to the function spline in [1].
+
+ [1] Numerical Recipes in C, 2nd edition
+ William H. Press, et al.,
+ Section 3.3, page 115
+ */
+ void Calculate();
+
+ /** @descr this function corresponds to the algorithm 4.76 in [2] and
+ theorem 5.3.7 in [3]
+
+ [2] Engeln-Müllges, Gisela: Numerik-Algorithmen: Verfahren, Beispiele, Anwendungen
+ Springer, Berlin; Auflage: 9., überarb. und erw. A. (8. Dezember 2004)
+ Section 4.10.2, page 175
+
+ [3] Hanrath, Wilhelm: Mathematik III / Numerik, Vorlesungsskript zur
+ Veranstaltung im WS 2007/2008
+ Fachhochschule Aachen, 2009-09-19
+ Numerik_01.pdf, downloaded 2011-04-19 via
+ http://www.fh-aachen.de/index.php?id=11424&no_cache=1&file=5016&uid=44191
+ Section 5.3, page 129
+ */
+ void CalculatePeriodic();
+};
+
+lcl_SplineCalculation::lcl_SplineCalculation(
+ tPointVecType && rSortedPoints,
+ double fY1FirstDerivation,
+ double fYnFirstDerivation )
+ : m_aPoints( std::move(rSortedPoints) ),
+ m_fYp1( fY1FirstDerivation ),
+ m_fYpN( fYnFirstDerivation ),
+ m_nKLow( 0 ),
+ m_nKHigh( m_aPoints.size() - 1 ),
+ m_fLastInterpolatedValue(std::numeric_limits<double>::infinity())
+{
+ Calculate();
+}
+
+lcl_SplineCalculation::lcl_SplineCalculation(
+ tPointVecType && rSortedPoints)
+ : m_aPoints( std::move(rSortedPoints) ),
+ m_fYp1( 0.0 ), /*dummy*/
+ m_fYpN( 0.0 ), /*dummy*/
+ m_nKLow( 0 ),
+ m_nKHigh( m_aPoints.size() - 1 ),
+ m_fLastInterpolatedValue(std::numeric_limits<double>::infinity())
+{
+ CalculatePeriodic();
+}
+
+void lcl_SplineCalculation::Calculate()
+{
+ if( m_aPoints.size() <= 1 )
+ return;
+
+ // n is the last valid index to m_aPoints
+ const lcl_tSizeType n = m_aPoints.size() - 1;
+ std::vector< double > u( n );
+ m_aSecDerivY.resize( n + 1, 0.0 );
+
+ if( std::isinf( m_fYp1 ) )
+ {
+ // natural spline
+ m_aSecDerivY[ 0 ] = 0.0;
+ u[ 0 ] = 0.0;
+ }
+ else
+ {
+ m_aSecDerivY[ 0 ] = -0.5;
+ double xDiff = m_aPoints[ 1 ].first - m_aPoints[ 0 ].first;
+ u[ 0 ] = ( 3.0 / xDiff ) *
+ ((( m_aPoints[ 1 ].second - m_aPoints[ 0 ].second ) / xDiff ) - m_fYp1 );
+ }
+
+ for( lcl_tSizeType i = 1; i < n; ++i )
+ {
+ tPointType
+ p_i = m_aPoints[ i ],
+ p_im1 = m_aPoints[ i - 1 ],
+ p_ip1 = m_aPoints[ i + 1 ];
+
+ double sig = ( p_i.first - p_im1.first ) /
+ ( p_ip1.first - p_im1.first );
+ double p = sig * m_aSecDerivY[ i - 1 ] + 2.0;
+
+ m_aSecDerivY[ i ] = ( sig - 1.0 ) / p;
+ u[ i ] =
+ ( ( p_ip1.second - p_i.second ) /
+ ( p_ip1.first - p_i.first ) ) -
+ ( ( p_i.second - p_im1.second ) /
+ ( p_i.first - p_im1.first ) );
+ u[ i ] =
+ ( 6.0 * u[ i ] / ( p_ip1.first - p_im1.first )
+ - sig * u[ i - 1 ] ) / p;
+ }
+
+ // initialize to values for natural splines (used for m_fYpN equal to
+ // infinity)
+ double qn = 0.0;
+ double un = 0.0;
+
+ if( ! std::isinf( m_fYpN ) )
+ {
+ qn = 0.5;
+ double xDiff = m_aPoints[ n ].first - m_aPoints[ n - 1 ].first;
+ un = ( 3.0 / xDiff ) *
+ ( m_fYpN - ( m_aPoints[ n ].second - m_aPoints[ n - 1 ].second ) / xDiff );
+ }
+
+ m_aSecDerivY[ n ] = ( un - qn * u[ n - 1 ] ) / ( qn * m_aSecDerivY[ n - 1 ] + 1.0 );
+
+ // note: the algorithm in [1] iterates from n-1 to 0, but as size_type
+ // may be (usually is) an unsigned type, we can not write k >= 0, as this
+ // is always true.
+ for( lcl_tSizeType k = n; k > 0; --k )
+ {
+ m_aSecDerivY[ k - 1 ] = (m_aSecDerivY[ k - 1 ] * m_aSecDerivY[ k ] ) + u[ k - 1 ];
+ }
+}
+
+void lcl_SplineCalculation::CalculatePeriodic()
+{
+ if( m_aPoints.size() <= 1 )
+ return;
+
+ // n is the last valid index to m_aPoints
+ const lcl_tSizeType n = m_aPoints.size() - 1;
+
+ // u is used for vector f in A*c=f in [3], vector a in Ax=a in [2],
+ // vector z in Rtranspose z = a and Dr=z in [2]
+ std::vector< double > u( n + 1, 0.0 );
+
+ // used for vector c in A*c=f and vector x in Ax=a in [2]
+ m_aSecDerivY.resize( n + 1, 0.0 );
+
+ // diagonal of matrix A, used index 1 to n
+ std::vector< double > Adiag( n + 1, 0.0 );
+
+ // secondary diagonal of matrix A with index 1 to n-1 and upper right element in A[n]
+ std::vector< double > Aupper( n + 1, 0.0 );
+
+ // diagonal of matrix D in A=(R transpose)*D*R in [2], used index 1 to n
+ std::vector< double > Ddiag( n+1, 0.0 );
+
+ // right column of matrix R, used index 1 to n-2
+ std::vector< double > Rright( n-1, 0.0 );
+
+ // secondary diagonal of matrix R, used index 1 to n-1
+ std::vector< double > Rupper( n, 0.0 );
+
+ if (n<4)
+ {
+ if (n==3)
+ { // special handling of three polynomials, that are four points
+ double xDiff0 = m_aPoints[ 1 ].first - m_aPoints[ 0 ].first ;
+ double xDiff1 = m_aPoints[ 2 ].first - m_aPoints[ 1 ].first ;
+ double xDiff2 = m_aPoints[ 3 ].first - m_aPoints[ 2 ].first ;
+ double xDiff2p1 = xDiff2 + xDiff1;
+ double xDiff0p2 = xDiff0 + xDiff2;
+ double xDiff1p0 = xDiff1 + xDiff0;
+ double fFactor = 1.5 / (xDiff0*xDiff1 + xDiff1*xDiff2 + xDiff2*xDiff0);
+ double yDiff0 = (m_aPoints[ 1 ].second - m_aPoints[ 0 ].second) / xDiff0;
+ double yDiff1 = (m_aPoints[ 2 ].second - m_aPoints[ 1 ].second) / xDiff1;
+ double yDiff2 = (m_aPoints[ 0 ].second - m_aPoints[ 2 ].second) / xDiff2;
+ m_aSecDerivY[ 1 ] = fFactor * (yDiff1*xDiff2p1 - yDiff0*xDiff0p2);
+ m_aSecDerivY[ 2 ] = fFactor * (yDiff2*xDiff0p2 - yDiff1*xDiff1p0);
+ m_aSecDerivY[ 3 ] = fFactor * (yDiff0*xDiff1p0 - yDiff2*xDiff2p1);
+ m_aSecDerivY[ 0 ] = m_aSecDerivY[ 3 ];
+ }
+ else if (n==2)
+ {
+ // special handling of two polynomials, that are three points
+ double xDiff0 = m_aPoints[ 1 ].first - m_aPoints[ 0 ].first;
+ double xDiff1 = m_aPoints[ 2 ].first - m_aPoints[ 1 ].first;
+ double fHelp = 3.0 * (m_aPoints[ 0 ].second - m_aPoints[ 1 ].second) / (xDiff0*xDiff1);
+ m_aSecDerivY[ 1 ] = fHelp ;
+ m_aSecDerivY[ 2 ] = -fHelp ;
+ m_aSecDerivY[ 0 ] = m_aSecDerivY[ 2 ] ;
+ }
+ else
+ {
+ // should be handled with natural spline, periodic not possible.
+ }
+ }
+ else
+ {
+ double xDiff_i =1.0; // values are dummy;
+ double xDiff_im1 =1.0;
+ double yDiff_i = 1.0;
+ double yDiff_im1 = 1.0;
+ // fill matrix A and fill right side vector u
+ for( lcl_tSizeType i=1; i<n; ++i )
+ {
+ xDiff_im1 = m_aPoints[ i ].first - m_aPoints[ i-1 ].first;
+ xDiff_i = m_aPoints[ i+1 ].first - m_aPoints[ i ].first;
+ yDiff_im1 = (m_aPoints[ i ].second - m_aPoints[ i-1 ].second) / xDiff_im1;
+ yDiff_i = (m_aPoints[ i+1 ].second - m_aPoints[ i ].second) / xDiff_i;
+ Adiag[ i ] = 2 * (xDiff_im1 + xDiff_i);
+ Aupper[ i ] = xDiff_i;
+ u [ i ] = 3 * (yDiff_i - yDiff_im1);
+ }
+ xDiff_im1 = m_aPoints[ n ].first - m_aPoints[ n-1 ].first;
+ xDiff_i = m_aPoints[ 1 ].first - m_aPoints[ 0 ].first;
+ yDiff_im1 = (m_aPoints[ n ].second - m_aPoints[ n-1 ].second) / xDiff_im1;
+ yDiff_i = (m_aPoints[ 1 ].second - m_aPoints[ 0 ].second) / xDiff_i;
+ Adiag[ n ] = 2 * (xDiff_im1 + xDiff_i);
+ Aupper[ n ] = xDiff_i;
+ u [ n ] = 3 * (yDiff_i - yDiff_im1);
+
+ // decomposite A=(R transpose)*D*R
+ Ddiag[1] = Adiag[1];
+ Rupper[1] = Aupper[1] / Ddiag[1];
+ Rright[1] = Aupper[n] / Ddiag[1];
+ for( lcl_tSizeType i=2; i<=n-2; ++i )
+ {
+ Ddiag[i] = Adiag[i] - Aupper[ i-1 ] * Rupper[ i-1 ];
+ Rupper[ i ] = Aupper[ i ] / Ddiag[ i ];
+ Rright[ i ] = - Rright[ i-1 ] * Aupper[ i-1 ] / Ddiag[ i ];
+ }
+ Ddiag[ n-1 ] = Adiag[ n-1 ] - Aupper[ n-2 ] * Rupper[ n-2 ];
+ Rupper[ n-1 ] = ( Aupper[ n-1 ] - Aupper[ n-2 ] * Rright[ n-2] ) / Ddiag[ n-1 ];
+ double fSum = 0.0;
+ for ( lcl_tSizeType i=1; i<=n-2; ++i )
+ {
+ fSum += Ddiag[ i ] * Rright[ i ] * Rright[ i ];
+ }
+ Ddiag[ n ] = Adiag[ n ] - fSum - Ddiag[ n-1 ] * Rupper[ n-1 ] * Rupper[ n-1 ]; // bug in [2]!
+
+ // solve forward (R transpose)*z=u, overwrite u with z
+ for ( lcl_tSizeType i=2; i<=n-1; ++i )
+ {
+ u[ i ] -= u[ i-1 ]* Rupper[ i-1 ];
+ }
+ fSum = 0.0;
+ for ( lcl_tSizeType i=1; i<=n-2; ++i )
+ {
+ fSum += Rright[ i ] * u[ i ];
+ }
+ u[ n ] = u[ n ] - fSum - Rupper[ n - 1] * u[ n-1 ];
+
+ // solve forward D*r=z, z is in u, overwrite u with r
+ for ( lcl_tSizeType i=1; i<=n; ++i )
+ {
+ u[ i ] = u[i] / Ddiag[ i ];
+ }
+
+ // solve backward R*x= r, r is in u
+ m_aSecDerivY[ n ] = u[ n ];
+ m_aSecDerivY[ n-1 ] = u[ n-1 ] - Rupper[ n-1 ] * m_aSecDerivY[ n ];
+ for ( lcl_tSizeType i=n-2; i>=1; --i)
+ {
+ m_aSecDerivY[ i ] = u[ i ] - Rupper[ i ] * m_aSecDerivY[ i+1 ] - Rright[ i ] * m_aSecDerivY[ n ];
+ }
+ // periodic
+ m_aSecDerivY[ 0 ] = m_aSecDerivY[ n ];
+ }
+
+ // adapt m_aSecDerivY for usage in GetInterpolatedValue()
+ for( lcl_tSizeType i = 0; i <= n ; ++i )
+ {
+ m_aSecDerivY[ i ] *= 2.0;
+ }
+
+}
+
+double lcl_SplineCalculation::GetInterpolatedValue( double x )
+{
+ OSL_PRECOND( ( m_aPoints[ 0 ].first <= x ) &&
+ ( x <= m_aPoints[ m_aPoints.size() - 1 ].first ),
+ "Trying to extrapolate" );
+
+ const lcl_tSizeType n = m_aPoints.size() - 1;
+ if( x < m_fLastInterpolatedValue )
+ {
+ m_nKLow = 0;
+ m_nKHigh = n;
+
+ // calculate m_nKLow and m_nKHigh
+ // first initialization is done in CTOR
+ while( m_nKHigh - m_nKLow > 1 )
+ {
+ lcl_tSizeType k = ( m_nKHigh + m_nKLow ) / 2;
+ if( m_aPoints[ k ].first > x )
+ m_nKHigh = k;
+ else
+ m_nKLow = k;
+ }
+ }
+ else
+ {
+ while( ( m_nKHigh <= n ) &&
+ ( m_aPoints[ m_nKHigh ].first < x ) )
+ {
+ ++m_nKHigh;
+ ++m_nKLow;
+ }
+ OSL_ENSURE( m_nKHigh <= n, "Out of Bounds" );
+ }
+ m_fLastInterpolatedValue = x;
+
+ double h = m_aPoints[ m_nKHigh ].first - m_aPoints[ m_nKLow ].first;
+ OSL_ENSURE( h != 0, "Bad input to GetInterpolatedValue()" );
+
+ double a = ( m_aPoints[ m_nKHigh ].first - x ) / h;
+ double b = ( x - m_aPoints[ m_nKLow ].first ) / h;
+
+ return ( a * m_aPoints[ m_nKLow ].second +
+ b * m_aPoints[ m_nKHigh ].second +
+ (( a*a*a - a ) * m_aSecDerivY[ m_nKLow ] +
+ ( b*b*b - b ) * m_aSecDerivY[ m_nKHigh ] ) *
+ ( h*h ) / 6.0 );
+}
+
+// helper methods for B-spline
+
+// Create parameter t_0 to t_n using the centripetal method with a power of 0.5
+bool createParameterT(const tPointVecType& rUniquePoints, double* t)
+{ // precondition: no adjacent identical points
+ // postcondition: 0 = t_0 < t_1 < ... < t_n = 1
+ bool bIsSuccessful = true;
+ const lcl_tSizeType n = rUniquePoints.size() - 1;
+ t[0]=0.0;
+ double fDenominator = 0.0; // initialized for summing up
+ for (lcl_tSizeType i=1; i<=n ; ++i)
+ { // 4th root(dx^2+dy^2)
+ double dx = rUniquePoints[i].first - rUniquePoints[i-1].first;
+ double dy = rUniquePoints[i].second - rUniquePoints[i-1].second;
+ if (dx == 0 && dy == 0)
+ {
+ bIsSuccessful = false;
+ break;
+ }
+ else
+ {
+ fDenominator += sqrt(std::hypot(dx, dy));
+ }
+ }
+ if (fDenominator == 0.0)
+ {
+ bIsSuccessful = false;
+ }
+ if (bIsSuccessful)
+ {
+ for (lcl_tSizeType j=1; j<=n ; ++j)
+ {
+ double fNumerator = 0.0;
+ for (lcl_tSizeType i=1; i<=j ; ++i)
+ {
+ double dx = rUniquePoints[i].first - rUniquePoints[i-1].first;
+ double dy = rUniquePoints[i].second - rUniquePoints[i-1].second;
+ fNumerator += sqrt(std::hypot(dx, dy));
+ }
+ t[j] = fNumerator / fDenominator;
+
+ }
+ // postcondition check
+ t[n] = 1.0;
+ double fPrevious = 0.0;
+ for (lcl_tSizeType i=1; i <= n && bIsSuccessful ; ++i)
+ {
+ if (fPrevious >= t[i])
+ {
+ bIsSuccessful = false;
+ }
+ else
+ {
+ fPrevious = t[i];
+ }
+ }
+ }
+ return bIsSuccessful;
+}
+
+void createKnotVector(const lcl_tSizeType n, const sal_uInt32 p, const double* t, double* u)
+{ // precondition: 0 = t_0 < t_1 < ... < t_n = 1
+ for (lcl_tSizeType j = 0; j <= p; ++j)
+ {
+ u[j] = 0.0;
+ }
+ for (lcl_tSizeType j = 1; j <= n-p; ++j )
+ {
+ double fSum = 0.0;
+ for (lcl_tSizeType i = j; i <= j+p-1; ++i)
+ {
+ fSum += t[i];
+ }
+ assert(p != 0);
+ u[j+p] = fSum / p ;
+ }
+ for (lcl_tSizeType j = n+1; j <= n+1+p; ++j)
+ {
+ u[j] = 1.0;
+ }
+}
+
+void applyNtoParameterT(const lcl_tSizeType i,const double tk,const sal_uInt32 p,const double* u, double* rowN)
+{
+ // get N_p(t_k) recursively, only N_(i-p) till N_(i) are relevant, all other N_# are zero
+
+ // initialize with indicator function degree 0
+ rowN[p] = 1.0; // all others are zero
+
+ // calculate up to degree p
+ for (sal_uInt32 s = 1; s <= p; ++s)
+ {
+ // first element
+ double fLeftFactor = 0.0;
+ double fRightFactor = ( u[i+1] - tk ) / ( u[i+1]- u[i-s+1] );
+ // i-s "true index" - (i-p)"shift" = p-s
+ rowN[p-s] = fRightFactor * rowN[p-s+1];
+
+ // middle elements
+ for (sal_uInt32 j = s-1; j>=1 ; --j)
+ {
+ fLeftFactor = ( tk - u[i-j] ) / ( u[i-j+s] - u[i-j] ) ;
+ fRightFactor = ( u[i-j+s+1] - tk ) / ( u[i-j+s+1] - u[i-j+1] );
+ // i-j "true index" - (i-p)"shift" = p-j
+ rowN[p-j] = fLeftFactor * rowN[p-j] + fRightFactor * rowN[p-j+1];
+ }
+
+ // last element
+ fLeftFactor = ( tk - u[i] ) / ( u[i+s] - u[i] );
+ // i "true index" - (i-p)"shift" = p
+ rowN[p] = fLeftFactor * rowN[p];
+ }
+}
+
+} // anonymous namespace
+
+// Calculates uniform parametric splines with subinterval length 1,
+// according ODF1.2 part 1, chapter 'chart interpolation'.
+void SplineCalculater::CalculateCubicSplines(
+ const std::vector<std::vector<css::drawing::Position3D>>& rInput
+ , std::vector<std::vector<css::drawing::Position3D>>& rResult
+ , sal_uInt32 nGranularity )
+{
+ OSL_PRECOND( nGranularity > 0, "Granularity is invalid" );
+
+ sal_uInt32 nOuterCount = rInput.size();
+
+ rResult.resize(nOuterCount);
+
+ auto pSequence = rResult.data();
+
+ if( !nOuterCount )
+ return;
+
+ for( sal_uInt32 nOuter = 0; nOuter < nOuterCount; ++nOuter )
+ {
+ if( rInput[nOuter].size() <= 1 )
+ continue; //we need at least two points
+
+ sal_uInt32 nMaxIndexPoints = rInput[nOuter].size()-1; // is >=1
+ const css::drawing::Position3D* pOld = rInput[nOuter].data();
+
+ std::vector < double > aParameter(nMaxIndexPoints+1);
+ aParameter[0]=0.0;
+ for( sal_uInt32 nIndex=1; nIndex<=nMaxIndexPoints; nIndex++ )
+ {
+ aParameter[nIndex]=aParameter[nIndex-1]+1;
+ }
+
+ // Split the calculation to X, Y and Z coordinate
+ tPointVecType aInputX;
+ aInputX.resize(nMaxIndexPoints+1);
+ tPointVecType aInputY;
+ aInputY.resize(nMaxIndexPoints+1);
+ tPointVecType aInputZ;
+ aInputZ.resize(nMaxIndexPoints+1);
+ for (sal_uInt32 nN=0;nN<=nMaxIndexPoints; nN++ )
+ {
+ aInputX[ nN ].first=aParameter[nN];
+ aInputX[ nN ].second=pOld[ nN ].PositionX;
+ aInputY[ nN ].first=aParameter[nN];
+ aInputY[ nN ].second=pOld[ nN ].PositionY;
+ aInputZ[ nN ].first=aParameter[nN];
+ aInputZ[ nN ].second=pOld[ nN ].PositionZ;
+ }
+
+ // generate a spline for each coordinate. It holds the complete
+ // information to calculate each point of the curve
+ std::unique_ptr<lcl_SplineCalculation> aSplineX;
+ std::unique_ptr<lcl_SplineCalculation> aSplineY;
+ // lcl_SplineCalculation* aSplineZ; the z-coordinates of all points in
+ // a data series are equal. No spline calculation needed, but copy
+ // coordinate to output
+
+ if( pOld[ 0 ].PositionX == pOld[nMaxIndexPoints].PositionX &&
+ pOld[ 0 ].PositionY == pOld[nMaxIndexPoints].PositionY &&
+ pOld[ 0 ].PositionZ == pOld[nMaxIndexPoints].PositionZ &&
+ nMaxIndexPoints >=2 )
+ { // periodic spline
+ aSplineX = std::make_unique<lcl_SplineCalculation>(std::move(aInputX));
+ aSplineY = std::make_unique<lcl_SplineCalculation>(std::move(aInputY));
+ }
+ else // generate the kind "natural spline"
+ {
+ double fXDerivation = std::numeric_limits<double>::infinity();
+ double fYDerivation = std::numeric_limits<double>::infinity();
+ aSplineX = std::make_unique<lcl_SplineCalculation>(std::move(aInputX), fXDerivation, fXDerivation);
+ aSplineY = std::make_unique<lcl_SplineCalculation>(std::move(aInputY), fYDerivation, fYDerivation);
+ }
+
+ // fill result polygon with calculated values
+ pSequence[nOuter].resize( nMaxIndexPoints*nGranularity + 1);
+
+ css::drawing::Position3D* pNew = pSequence[nOuter].data();
+
+ sal_uInt32 nNewPointIndex = 0; // Index in result points
+
+ for( sal_uInt32 ni = 0; ni < nMaxIndexPoints; ni++ )
+ {
+ // given point is surely a curve point
+ pNew[nNewPointIndex].PositionX = pOld[ni].PositionX;
+ pNew[nNewPointIndex].PositionY = pOld[ni].PositionY;
+ pNew[nNewPointIndex].PositionZ = pOld[ni].PositionZ;
+ nNewPointIndex++;
+
+ // calculate intermediate points
+ double fInc = ( aParameter[ ni+1 ] - aParameter[ni] ) / static_cast< double >( nGranularity );
+ for(sal_uInt32 nj = 1; nj < nGranularity; nj++)
+ {
+ double fParam = aParameter[ni] + ( fInc * static_cast< double >( nj ) );
+
+ pNew[nNewPointIndex].PositionX = aSplineX->GetInterpolatedValue( fParam );
+ pNew[nNewPointIndex].PositionY = aSplineY->GetInterpolatedValue( fParam );
+ // pNewZ[nNewPointIndex]=aSplineZ->GetInterpolatedValue( fParam );
+ pNew[nNewPointIndex].PositionZ = pOld[ni].PositionZ;
+ nNewPointIndex++;
+ }
+ }
+ // add last point
+ pNew[nNewPointIndex] = pOld[nMaxIndexPoints];
+ }
+}
+
+void SplineCalculater::CalculateBSplines(
+ const std::vector<std::vector<css::drawing::Position3D>>& rInput
+ , std::vector<std::vector<css::drawing::Position3D>>& rResult
+ , sal_uInt32 nResolution
+ , sal_uInt32 nDegree )
+{
+ // nResolution is ODF1.2 file format attribute chart:spline-resolution and
+ // ODF1.2 spec variable k. Causion, k is used as index in the spec in addition.
+ // nDegree is ODF1.2 file format attribute chart:spline-order and
+ // ODF1.2 spec variable p
+ OSL_ASSERT( nResolution > 1 );
+ OSL_ASSERT( nDegree >= 1 );
+
+ // limit the b-spline degree at 15 to prevent insanely large sets of points
+ sal_uInt32 p = std::min<sal_uInt32>(nDegree, 15);
+
+ sal_Int32 nOuterCount = rInput.size();
+
+ rResult.resize(nOuterCount);
+
+ auto pSequence = rResult.data();
+
+ if( !nOuterCount )
+ return; // no input
+
+ for( sal_Int32 nOuter = 0; nOuter < nOuterCount; ++nOuter )
+ {
+ if( rInput[nOuter].size() <= 1 )
+ continue; // need at least 2 points, next piece of the series
+
+ // Copy input to vector of points and remove adjacent double points. The
+ // Z-coordinate is equal for all points in a series and holds the depth
+ // in 3D mode, simple copying is enough.
+ lcl_tSizeType nMaxIndexPoints = rInput[nOuter].size()-1; // is >=1
+ const css::drawing::Position3D* pOld = rInput[nOuter].data();
+ double fZCoordinate = pOld[0].PositionZ;
+ tPointVecType aPointsIn;
+ aPointsIn.resize(nMaxIndexPoints+1);
+ for (lcl_tSizeType i = 0; i <= nMaxIndexPoints; ++i )
+ {
+ aPointsIn[ i ].first = pOld[i].PositionX;
+ aPointsIn[ i ].second = pOld[i].PositionY;
+ }
+ aPointsIn.erase( std::unique( aPointsIn.begin(), aPointsIn.end()),
+ aPointsIn.end() );
+
+ // n is the last valid index to the reduced aPointsIn
+ // There are n+1 valid data points.
+ const lcl_tSizeType n = aPointsIn.size() - 1;
+ if (n < 1 || p > n)
+ continue; // need at least 2 points, degree p needs at least n+1 points
+ // next piece of series
+
+ std::vector<double> t(n + 1);
+ if (!createParameterT(aPointsIn, t.data()))
+ {
+ continue; // next piece of series
+ }
+
+ lcl_tSizeType m = n + p + 1;
+ std::vector<double> u(m + 1);
+ createKnotVector(n, p, t.data(), u.data());
+
+ // The matrix N contains the B-spline basis functions applied to parameters.
+ // In each row only p+1 adjacent elements are non-zero. The starting
+ // column in a higher row is equal or greater than in the lower row.
+ // To store this matrix the non-zero elements are shifted to column 0
+ // and the amount of shifting is remembered in an array.
+ std::vector<std::vector<double>> aMatN(n + 1, std::vector<double>(p + 1));
+ std::vector<lcl_tSizeType> aShift(n + 1);
+ aMatN[0][0] = 1.0; //all others are zero
+ aShift[0] = 0;
+ aMatN[n][0] = 1.0;
+ aShift[n] = n;
+ for (lcl_tSizeType k = 1; k<=n-1; ++k)
+ { // all basis functions are applied to t_k,
+ // results are elements in row k in matrix N
+
+ // find the one interval with u_i <= t_k < u_(i+1)
+ // remember u_0 = ... = u_p = 0.0 and u_(m-p) = ... u_m = 1.0 and 0<t_k<1
+ lcl_tSizeType i = p;
+ while (u[i] > t[k] || t[k] >= u[i+1])
+ {
+ ++i;
+ }
+
+ // index in reduced matrix aMatN = (index in full matrix N) - (i-p)
+ aShift[k] = i - p;
+
+ applyNtoParameterT(i, t[k], p, u.data(), aMatN[k].data());
+ } // next row k
+
+ // Get matrix C of control points from the matrix equation aMatN * C = aPointsIn
+ // aPointsIn is overwritten with C.
+ // Gaussian elimination is possible without pivoting, see reference
+ lcl_tSizeType r = 0; // true row index
+ lcl_tSizeType c = 0; // true column index
+ double fDivisor = 1.0; // used for diagonal element
+ double fEliminate = 1.0; // used for the element, that will become zero
+ bool bIsSuccessful = true;
+ for (c = 0 ; c <= n && bIsSuccessful; ++c)
+ {
+ // search for first non-zero downwards
+ r = c;
+ while ( r < n && aMatN[r][c-aShift[r]] == 0 )
+ {
+ ++r;
+ }
+ if (aMatN[r][c-aShift[r]] == 0.0)
+ {
+ // Matrix N is singular, although this is mathematically impossible
+ bIsSuccessful = false;
+ }
+ else
+ {
+ // exchange total row r with total row c if necessary
+ if (r != c)
+ {
+ std::swap( aMatN[r], aMatN[c] );
+ std::swap( aPointsIn[r], aPointsIn[c] );
+ std::swap( aShift[r], aShift[c] );
+ }
+
+ // divide row c, so that element(c,c) becomes 1
+ fDivisor = aMatN[c][c-aShift[c]]; // not zero, see above
+ for (sal_uInt32 i = 0; i <= p; ++i)
+ {
+ aMatN[c][i] /= fDivisor;
+ }
+ aPointsIn[c].first /= fDivisor;
+ aPointsIn[c].second /= fDivisor;
+
+ // eliminate forward, examine row c+1 to n-1 (worst case)
+ // stop if first non-zero element in row has an higher column as c
+ // look at nShift for that, elements in nShift are equal or increasing
+ for ( r = c+1; r < n && aShift[r]<=c ; ++r)
+ {
+ fEliminate = aMatN[r][0];
+ if (fEliminate != 0.0) // else accidentally zero, nothing to do
+ {
+ for (sal_uInt32 i = 1; i <= p; ++i)
+ {
+ aMatN[r][i-1] = aMatN[r][i] - fEliminate * aMatN[c][i];
+ }
+ aMatN[r][p]=0;
+ aPointsIn[r].first -= fEliminate * aPointsIn[c].first;
+ aPointsIn[r].second -= fEliminate * aPointsIn[c].second;
+ ++aShift[r];
+ }
+ }
+ }
+ }// upper triangle form is reached
+ if( bIsSuccessful)
+ {
+ // eliminate backwards, begin with last column
+ for (lcl_tSizeType cc = n; cc >= 1; --cc )
+ {
+ // In row cc the diagonal element(cc,cc) == 1 and all elements left from
+ // diagonal are zero and do not influence other rows.
+ // Full matrix N has semibandwidth < p, therefore element(r,c) is
+ // zero, if abs(r-cc)>=p. abs(r-cc)=cc-r, because r<cc.
+ r = cc - 1;
+ while ( r !=0 && cc-r < p )
+ {
+ fEliminate = aMatN[r][ cc - aShift[r] ];
+ if ( fEliminate != 0.0) // else element is accidentally zero, no action needed
+ {
+ // row r -= fEliminate * row cc only relevant for right side
+ aMatN[r][cc - aShift[r]] = 0.0;
+ aPointsIn[r].first -= fEliminate * aPointsIn[cc].first;
+ aPointsIn[r].second -= fEliminate * aPointsIn[cc].second;
+ }
+ --r;
+ }
+ }
+ // aPointsIn contains the control points now.
+
+ // calculate the intermediate points according given resolution
+ // using deBoor-Cox algorithm
+ lcl_tSizeType nNewSize = nResolution * n + 1;
+ pSequence[nOuter].resize(nNewSize);
+ css::drawing::Position3D* pNew = pSequence[nOuter].data();
+ pNew[0].PositionX = aPointsIn[0].first;
+ pNew[0].PositionY = aPointsIn[0].second;
+ pNew[0].PositionZ = fZCoordinate; // Precondition: z-coordinates of all points of a series are equal
+ pNew[nNewSize -1 ].PositionX = aPointsIn[n].first;
+ pNew[nNewSize -1 ].PositionY = aPointsIn[n].second;
+ pNew[nNewSize -1 ].PositionZ = fZCoordinate;
+ std::vector<double> aP(m + 1);
+ lcl_tSizeType nLow = 0;
+ for ( lcl_tSizeType nTIndex = 0; nTIndex <= n-1; ++nTIndex)
+ {
+ for (sal_uInt32 nResolutionStep = 1;
+ nResolutionStep <= nResolution && ( nTIndex != n-1 || nResolutionStep != nResolution);
+ ++nResolutionStep)
+ {
+ lcl_tSizeType nNewIndex = nTIndex * nResolution + nResolutionStep;
+ double ux = t[nTIndex] + nResolutionStep * ( t[nTIndex+1] - t[nTIndex]) /nResolution;
+
+ // get index nLow, so that u[nLow]<= ux < u[nLow +1]
+ // continue from previous nLow
+ while ( u[nLow] <= ux)
+ {
+ ++nLow;
+ }
+ --nLow;
+
+ // x-coordinate
+ for (lcl_tSizeType i = nLow-p; i <= nLow; ++i)
+ {
+ aP[i] = aPointsIn[i].first;
+ }
+ for (sal_uInt32 lcl_Degree = 1; lcl_Degree <= p; ++lcl_Degree)
+ {
+ for (lcl_tSizeType i = nLow; i >= nLow + lcl_Degree - p; --i)
+ {
+ double fFactor = ( ux - u[i] ) / ( u[i+p+1-lcl_Degree] - u[i]);
+ aP[i] = (1 - fFactor)* aP[i-1] + fFactor * aP[i];
+ }
+ }
+ pNew[nNewIndex].PositionX = aP[nLow];
+
+ // y-coordinate
+ for (lcl_tSizeType i = nLow - p; i <= nLow; ++i)
+ {
+ aP[i] = aPointsIn[i].second;
+ }
+ for (sal_uInt32 lcl_Degree = 1; lcl_Degree <= p; ++lcl_Degree)
+ {
+ for (lcl_tSizeType i = nLow; i >= nLow +lcl_Degree - p; --i)
+ {
+ double fFactor = ( ux - u[i] ) / ( u[i+p+1-lcl_Degree] - u[i]);
+ aP[i] = (1 - fFactor)* aP[i-1] + fFactor * aP[i];
+ }
+ }
+ pNew[nNewIndex].PositionY = aP[nLow];
+ pNew[nNewIndex].PositionZ = fZCoordinate;
+ }
+ }
+ }
+ } // next piece of the series
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/Splines.hxx b/chart2/source/view/charttypes/Splines.hxx
new file mode 100644
index 000000000..b83c13931
--- /dev/null
+++ b/chart2/source/view/charttypes/Splines.hxx
@@ -0,0 +1,48 @@
+/* -*- 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 <sal/types.h>
+#include <vector>
+
+namespace com::sun::star::drawing { struct PolyPolygonShape3D; }
+namespace com::sun::star::drawing { struct Position3D; }
+
+namespace chart
+{
+
+class SplineCalculater
+{
+public:
+ static void CalculateCubicSplines(
+ const std::vector<std::vector<css::drawing::Position3D>>& rPoints
+ , std::vector<std::vector<css::drawing::Position3D>>& rResult
+ , sal_uInt32 nGranularity );
+
+ static void CalculateBSplines(
+ const std::vector<std::vector<css::drawing::Position3D>>& rPoints
+ , std::vector<std::vector<css::drawing::Position3D>>& rResult
+ , sal_uInt32 nGranularity
+ , sal_uInt32 nSplineDepth );
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/charttypes/VSeriesPlotter.cxx b/chart2/source/view/charttypes/VSeriesPlotter.cxx
new file mode 100644
index 000000000..fd3a1fe1f
--- /dev/null
+++ b/chart2/source/view/charttypes/VSeriesPlotter.cxx
@@ -0,0 +1,2847 @@
+/* -*- 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 <cstddef>
+#include <limits>
+#include <memory>
+#include <VSeriesPlotter.hxx>
+#include <BaseGFXHelper.hxx>
+#include <VLineProperties.hxx>
+#include <ShapeFactory.hxx>
+#include <Diagram.hxx>
+#include <BaseCoordinateSystem.hxx>
+#include <DataSeries.hxx>
+
+#include <CommonConverters.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <ObjectIdentifier.hxx>
+#include <StatisticsHelper.hxx>
+#include <PlottingPositionHelper.hxx>
+#include <LabelPositionHelper.hxx>
+#include <ChartType.hxx>
+#include <ChartTypeHelper.hxx>
+#include <Clipping.hxx>
+#include <servicenames_charttypes.hxx>
+#include <NumberFormatterWrapper.hxx>
+#include <DataSeriesHelper.hxx>
+#include <RegressionCurveModel.hxx>
+#include <RegressionCurveHelper.hxx>
+#include <VLegendSymbolFactory.hxx>
+#include <FormattedStringHelper.hxx>
+#include <RelativePositionHelper.hxx>
+#include <DateHelper.hxx>
+#include <DiagramHelper.hxx>
+#include <defines.hxx>
+#include <ChartModel.hxx>
+
+//only for creation: @todo remove if all plotter are uno components and instantiated via servicefactory
+#include "BarChart.hxx"
+#include "PieChart.hxx"
+#include "AreaChart.hxx"
+#include "CandleStickChart.hxx"
+#include "BubbleChart.hxx"
+#include "NetChart.hxx"
+#include <unonames.hxx>
+#include <SpecialCharacters.hxx>
+
+#include <com/sun/star/chart2/DataPointLabel.hpp>
+#include <com/sun/star/chart/ErrorBarStyle.hpp>
+#include <com/sun/star/chart/TimeUnit.hpp>
+#include <com/sun/star/chart2/MovingAverageType.hpp>
+#include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
+#include <com/sun/star/container/XChild.hpp>
+#include <com/sun/star/chart2/RelativePosition.hpp>
+#include <o3tl/safeint.hxx>
+#include <tools/color.hxx>
+#include <tools/UnitConversion.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/math.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/util/XCloneable.hpp>
+
+#include <unotools/localedatawrapper.hxx>
+#include <comphelper/sequence.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+
+#include <functional>
+#include <map>
+#include <unordered_map>
+
+
+namespace chart {
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart;
+using namespace ::com::sun::star::chart2;
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::uno::Sequence;
+
+VDataSeriesGroup::CachedYValues::CachedYValues()
+ : m_bValuesDirty(true)
+ , m_fMinimumY(0.0)
+ , m_fMaximumY(0.0)
+{
+}
+
+VDataSeriesGroup::VDataSeriesGroup( std::unique_ptr<VDataSeries> pSeries )
+ : m_aSeriesVector(1)
+ , m_bMaxPointCountDirty(true)
+ , m_nMaxPointCount(0)
+{
+ m_aSeriesVector[0] = std::move(pSeries);
+}
+
+VDataSeriesGroup::VDataSeriesGroup(VDataSeriesGroup&& other) noexcept
+ : m_aSeriesVector( std::move(other.m_aSeriesVector) )
+ , m_bMaxPointCountDirty( other.m_bMaxPointCountDirty )
+ , m_nMaxPointCount( other.m_nMaxPointCount )
+ , m_aListOfCachedYValues( std::move(other.m_aListOfCachedYValues) )
+{
+}
+
+VDataSeriesGroup::~VDataSeriesGroup()
+{
+}
+
+void VDataSeriesGroup::deleteSeries()
+{
+ //delete all data series help objects:
+ m_aSeriesVector.clear();
+}
+
+void VDataSeriesGroup::addSeries( std::unique_ptr<VDataSeries> pSeries )
+{
+ m_aSeriesVector.push_back(std::move(pSeries));
+ m_bMaxPointCountDirty=true;
+}
+
+sal_Int32 VDataSeriesGroup::getSeriesCount() const
+{
+ return m_aSeriesVector.size();
+}
+
+VSeriesPlotter::VSeriesPlotter( const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount, bool bCategoryXAxis )
+ : PlotterBase( nDimensionCount )
+ , m_pMainPosHelper( nullptr )
+ , m_xChartTypeModel(xChartTypeModel)
+ , m_bCategoryXAxis(bCategoryXAxis)
+ , m_nTimeResolution(css::chart::TimeUnit::DAY)
+ , m_aNullDate(30,12,1899)
+ , m_pExplicitCategoriesProvider(nullptr)
+ , m_bPointsWereSkipped(false)
+ , m_bPieLabelsAllowToMove(false)
+ , m_aAvailableOuterRect(0, 0, 0, 0)
+{
+ SAL_WARN_IF(!m_xChartTypeModel.is(),"chart2","no XChartType available in view, fallback to default values may be wrong");
+}
+
+VSeriesPlotter::~VSeriesPlotter()
+{
+ //delete all data series help objects:
+ for (std::vector<VDataSeriesGroup> & rGroupVector : m_aZSlots)
+ {
+ for (VDataSeriesGroup & rGroup : rGroupVector)
+ {
+ rGroup.deleteSeries();
+ }
+ rGroupVector.clear();
+ }
+ m_aZSlots.clear();
+
+ m_aSecondaryPosHelperMap.clear();
+
+ m_aSecondaryValueScales.clear();
+}
+
+void VSeriesPlotter::addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot )
+{
+ //take ownership of pSeries
+
+ OSL_PRECOND( pSeries, "series to add is NULL" );
+ if(!pSeries)
+ return;
+
+ if(m_bCategoryXAxis)
+ {
+ if( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() )
+ pSeries->setXValues( m_pExplicitCategoriesProvider->getOriginalCategories() );
+ else
+ pSeries->setCategoryXAxis();
+ }
+ else
+ {
+ if( m_pExplicitCategoriesProvider )
+ pSeries->setXValuesIfNone( m_pExplicitCategoriesProvider->getOriginalCategories() );
+ }
+
+ if(zSlot<0 || o3tl::make_unsigned(zSlot)>=m_aZSlots.size())
+ {
+ //new z slot
+ std::vector< VDataSeriesGroup > aZSlot;
+ aZSlot.emplace_back( std::move(pSeries) );
+ m_aZSlots.push_back( std::move(aZSlot) );
+ }
+ else
+ {
+ //existing zslot
+ std::vector< VDataSeriesGroup >& rXSlots = m_aZSlots[zSlot];
+
+ if(xSlot<0 || o3tl::make_unsigned(xSlot)>=rXSlots.size())
+ {
+ //append the series to already existing x series
+ rXSlots.emplace_back( std::move(pSeries) );
+ }
+ else
+ {
+ //x slot is already occupied
+ //y slot decides what to do:
+
+ VDataSeriesGroup& rYSlots = rXSlots[xSlot];
+ sal_Int32 nYSlotCount = rYSlots.getSeriesCount();
+
+ if( ySlot < -1 )
+ {
+ //move all existing series in the xSlot to next slot
+ //@todo
+ OSL_FAIL( "Not implemented yet");
+ }
+ else if( ySlot == -1 || ySlot >= nYSlotCount)
+ {
+ //append the series to already existing y series
+ rYSlots.addSeries( std::move(pSeries) );
+ }
+ else
+ {
+ //y slot is already occupied
+ //insert at given y and x position
+
+ //@todo
+ OSL_FAIL( "Not implemented yet");
+ }
+ }
+ }
+}
+
+drawing::Direction3D VSeriesPlotter::getPreferredDiagramAspectRatio() const
+{
+ drawing::Direction3D aRet(1.0,1.0,1.0);
+ if (!m_pPosHelper)
+ return aRet;
+
+ drawing::Direction3D aScale( m_pPosHelper->getScaledLogicWidth() );
+ aRet.DirectionZ = aScale.DirectionZ*0.2;
+ if(aRet.DirectionZ>1.0)
+ aRet.DirectionZ=1.0;
+ if(aRet.DirectionZ>10)
+ aRet.DirectionZ=10;
+ return aRet;
+}
+
+void VSeriesPlotter::releaseShapes()
+{
+ for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
+ {
+ for (VDataSeriesGroup const & rGroup : rGroupVector)
+ {
+ //iterate through all series in this x slot
+ for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
+ {
+ pSeries->releaseShapes();
+ }
+ }
+ }
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShape( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if( !pDataSeries->m_xGroupShape )
+ //create a group shape for this series and add to logic target:
+ pDataSeries->m_xGroupShape = createGroupShape( xTarget,pDataSeries->getCID() );
+ return pDataSeries->m_xGroupShape;
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShapeFrontChild( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if(!pDataSeries->m_xFrontSubGroupShape)
+ {
+ //ensure that the series group shape is already created
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
+ //ensure that the back child is created first
+ getSeriesGroupShapeBackChild( pDataSeries, xTarget );
+ //use series group shape as parent for the new created front group shape
+ pDataSeries->m_xFrontSubGroupShape = createGroupShape( xSeriesShapes );
+ }
+ return pDataSeries->m_xFrontSubGroupShape;
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getSeriesGroupShapeBackChild( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if(!pDataSeries->m_xBackSubGroupShape)
+ {
+ //ensure that the series group shape is already created
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesShapes( getSeriesGroupShape( pDataSeries, xTarget ) );
+ //use series group shape as parent for the new created back group shape
+ pDataSeries->m_xBackSubGroupShape = createGroupShape( xSeriesShapes );
+ }
+ return pDataSeries->m_xBackSubGroupShape;
+}
+
+rtl::Reference<SvxShapeGroup> VSeriesPlotter::getLabelsGroupShape( VDataSeries& rDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTextTarget )
+{
+ //xTextTarget needs to be a 2D shape container always!
+ if(!rDataSeries.m_xLabelsGroupShape)
+ {
+ //create a 2D group shape for texts of this series and add to text target:
+ rDataSeries.m_xLabelsGroupShape = ShapeFactory::createGroup2D( xTextTarget, rDataSeries.getLabelsCID() );
+ }
+ return rDataSeries.m_xLabelsGroupShape;
+}
+
+rtl::Reference<SvxShapeGroupAnyD> VSeriesPlotter::getErrorBarsGroupShape( VDataSeries& rDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , bool bYError )
+{
+ rtl::Reference<SvxShapeGroupAnyD> &rShapeGroup =
+ bYError ? rDataSeries.m_xErrorYBarsGroupShape : rDataSeries.m_xErrorXBarsGroupShape;
+
+ if(!rShapeGroup)
+ {
+ //create a group shape for this series and add to logic target:
+ rShapeGroup = createGroupShape( xTarget,rDataSeries.getErrorBarsCID(bYError) );
+ }
+ return rShapeGroup;
+
+}
+
+OUString VSeriesPlotter::getLabelTextForValue( VDataSeries const & rDataSeries
+ , sal_Int32 nPointIndex
+ , double fValue
+ , bool bAsPercentage )
+{
+ OUString aNumber;
+
+ if (m_apNumberFormatterWrapper)
+ {
+ sal_Int32 nNumberFormatKey = 0;
+ if( rDataSeries.hasExplicitNumberFormat(nPointIndex,bAsPercentage) )
+ nNumberFormatKey = rDataSeries.getExplicitNumberFormat(nPointIndex,bAsPercentage);
+ else if( bAsPercentage )
+ {
+ sal_Int32 nPercentFormat = DiagramHelper::getPercentNumberFormat( m_apNumberFormatterWrapper->getNumberFormatsSupplier() );
+ if( nPercentFormat != -1 )
+ nNumberFormatKey = nPercentFormat;
+ }
+ else
+ {
+ nNumberFormatKey = rDataSeries.detectNumberFormatKey( nPointIndex );
+ }
+ if(nNumberFormatKey<0)
+ nNumberFormatKey=0;
+
+ Color nLabelCol;
+ bool bColChanged;
+ aNumber = m_apNumberFormatterWrapper->getFormattedString(
+ nNumberFormatKey, fValue, nLabelCol, bColChanged );
+ //@todo: change color of label if bColChanged is true
+ }
+ else
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+ const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
+ assert(aNumDecimalSep.getLength() > 0);
+ sal_Unicode cDecSeparator = aNumDecimalSep[0];
+ aNumber = ::rtl::math::doubleToUString( fValue, rtl_math_StringFormat_G /*rtl_math_StringFormat*/
+ , 3/*DecPlaces*/ , cDecSeparator );
+ }
+ return aNumber;
+}
+
+rtl::Reference<SvxShapeText> VSeriesPlotter::createDataLabel( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , VDataSeries& rDataSeries
+ , sal_Int32 nPointIndex
+ , double fValue
+ , double fSumValue
+ , const awt::Point& rScreenPosition2D
+ , LabelAlignment eAlignment
+ , sal_Int32 nOffset
+ , sal_Int32 nTextWidth )
+{
+ rtl::Reference<SvxShapeText> xTextShape;
+ Sequence<uno::Reference<XDataPointCustomLabelField>> aCustomLabels;
+
+ try
+ {
+ const uno::Reference< css::beans::XPropertySet >& xPropertySet(
+ rDataSeries.getPropertiesOfPoint( nPointIndex ) );
+ if( xPropertySet.is() )
+ {
+ uno::Any aAny = xPropertySet->getPropertyValue( CHART_UNONAME_CUSTOM_LABEL_FIELDS );
+ if( aAny.hasValue() )
+ {
+ aAny >>= aCustomLabels;
+ }
+ }
+
+ awt::Point aScreenPosition2D(rScreenPosition2D);
+ if(eAlignment==LABEL_ALIGN_LEFT)
+ aScreenPosition2D.X -= nOffset;
+ else if(eAlignment==LABEL_ALIGN_RIGHT)
+ aScreenPosition2D.X += nOffset;
+ else if(eAlignment==LABEL_ALIGN_TOP)
+ aScreenPosition2D.Y -= nOffset;
+ else if(eAlignment==LABEL_ALIGN_BOTTOM)
+ aScreenPosition2D.Y += nOffset;
+
+ rtl::Reference<SvxShapeGroup> xTarget_ =
+ ShapeFactory::createGroup2D(
+ getLabelsGroupShape(rDataSeries, xTarget),
+ ObjectIdentifier::createPointCID( rDataSeries.getLabelCID_Stub(), nPointIndex));
+
+ //check whether the label needs to be created and how:
+ DataPointLabel* pLabel = rDataSeries.getDataPointLabelIfLabel( nPointIndex );
+
+ if( !pLabel )
+ return xTextShape;
+
+ //prepare legend symbol
+
+ // get the font size for the label through the "CharHeight" property
+ // attached to the passed data series entry.
+ // (By tracing font height values it results that for pie chart the
+ // font size is not the same for all labels, but here no font size
+ // modification occurs).
+ float fViewFontSize( 10.0 );
+ {
+ uno::Reference< beans::XPropertySet > xProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
+ if( xProps.is() )
+ xProps->getPropertyValue( "CharHeight") >>= fViewFontSize;
+ fViewFontSize = convertPointToMm100(fViewFontSize);
+ }
+
+ // the font height is used for computing the size of an optional legend
+ // symbol to be prepended to the text label.
+ rtl::Reference< SvxShapeGroup > xSymbol;
+ if(pLabel->ShowLegendSymbol)
+ {
+ sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
+ awt::Size aCurrentRatio = getPreferredLegendKeyAspectRatio();
+ sal_Int32 nSymbolWidth = aCurrentRatio.Width;
+ if( aCurrentRatio.Height > 0 )
+ {
+ nSymbolWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
+ }
+ awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
+
+ if( rDataSeries.isVaryColorsByPoint() )
+ xSymbol = VSeriesPlotter::createLegendSymbolForPoint( aMaxSymbolExtent, rDataSeries, nPointIndex, xTarget_ );
+ else
+ xSymbol = VSeriesPlotter::createLegendSymbolForSeries( aMaxSymbolExtent, rDataSeries, xTarget_ );
+ }
+
+ //prepare text
+ bool bTextWrap = false;
+ OUString aSeparator(" ");
+ double fRotationDegrees = 0.0;
+ try
+ {
+ uno::Reference< beans::XPropertySet > xPointProps( rDataSeries.getPropertiesOfPoint( nPointIndex ) );
+ if(xPointProps.is())
+ {
+ xPointProps->getPropertyValue( "TextWordWrap" ) >>= bTextWrap;
+ xPointProps->getPropertyValue( "LabelSeparator" ) >>= aSeparator;
+ // Extract the optional text rotation through the
+ // "TextRotation" property attached to the passed data point.
+ xPointProps->getPropertyValue( "TextRotation" ) >>= fRotationDegrees;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ sal_Int32 nLineCountForSymbolsize = 0;
+ sal_uInt32 nTextListLength = 4;
+ sal_uInt32 nCustomLabelsCount = aCustomLabels.getLength();
+ Sequence< OUString > aTextList( nTextListLength );
+
+ bool bUseCustomLabel = nCustomLabelsCount > 0;
+ if( bUseCustomLabel )
+ {
+ nTextListLength = ( nCustomLabelsCount > 3 ) ? nCustomLabelsCount : 3;
+ aSeparator = "";
+ aTextList = Sequence< OUString >( nTextListLength );
+ auto pTextList = aTextList.getArray();
+ for( sal_uInt32 i = 0; i < nCustomLabelsCount; ++i )
+ {
+ switch( aCustomLabels[i]->getFieldType() )
+ {
+ case DataPointCustomLabelFieldType_VALUE:
+ {
+ pTextList[i] = getLabelTextForValue( rDataSeries, nPointIndex, fValue, false );
+ break;
+ }
+ case DataPointCustomLabelFieldType_CATEGORYNAME:
+ {
+ pTextList[i] = getCategoryName( nPointIndex );
+ break;
+ }
+ case DataPointCustomLabelFieldType_SERIESNAME:
+ {
+ OUString aRole;
+ if ( m_xChartTypeModel )
+ aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
+ const rtl::Reference< DataSeries >& xSeries( rDataSeries.getModel() );
+ pTextList[i] = DataSeriesHelper::getDataSeriesLabel( xSeries, aRole );
+ break;
+ }
+ case DataPointCustomLabelFieldType_PERCENTAGE:
+ {
+ if(fSumValue == 0.0)
+ fSumValue = 1.0;
+ fValue /= fSumValue;
+ if(fValue < 0)
+ fValue *= -1.0;
+
+ pTextList[i] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
+ break;
+ }
+ case DataPointCustomLabelFieldType_CELLRANGE:
+ {
+ if (aCustomLabels[i]->getDataLabelsRange())
+ pTextList[i] = aCustomLabels[i]->getString();
+ else
+ pTextList[i] = OUString();
+ break;
+ }
+ case DataPointCustomLabelFieldType_CELLREF:
+ {
+ // TODO: for now doesn't show placeholder
+ pTextList[i] = OUString();
+ break;
+ }
+ case DataPointCustomLabelFieldType_TEXT:
+ {
+ pTextList[i] = aCustomLabels[i]->getString();
+ break;
+ }
+ case DataPointCustomLabelFieldType_NEWLINE:
+ {
+ pTextList[i] = "\n";
+ break;
+ }
+ default:
+ break;
+ }
+ aCustomLabels[i]->setString( aTextList[i] );
+ }
+ }
+ else
+ {
+ auto pTextList = aTextList.getArray();
+ if( pLabel->ShowCategoryName )
+ {
+ pTextList[0] = getCategoryName( nPointIndex );
+ }
+
+ if( pLabel->ShowSeriesName )
+ {
+ OUString aRole;
+ if ( m_xChartTypeModel )
+ aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
+ const rtl::Reference< DataSeries >& xSeries( rDataSeries.getModel() );
+ pTextList[1] = DataSeriesHelper::getDataSeriesLabel( xSeries, aRole );
+ }
+
+ if( pLabel->ShowNumber )
+ {
+ pTextList[2] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, false);
+ }
+
+ if( pLabel->ShowNumberInPercent )
+ {
+ if(fSumValue==0.0)
+ fSumValue=1.0;
+ fValue /= fSumValue;
+ if( fValue < 0 )
+ fValue*=-1.0;
+
+ pTextList[3] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true);
+ }
+ }
+
+ for( auto const & line : std::as_const(aTextList) )
+ {
+ if( !line.isEmpty() )
+ {
+ ++nLineCountForSymbolsize;
+ }
+ }
+
+ //prepare properties for multipropertyset-interface of shape
+ tNameSequence* pPropNames;
+ tAnySequence* pPropValues;
+ if( !rDataSeries.getTextLabelMultiPropertyLists( nPointIndex, pPropNames, pPropValues ) )
+ return xTextShape;
+
+ // set text alignment for the text shape to be created.
+ LabelPositionHelper::changeTextAdjustment( *pPropValues, *pPropNames, eAlignment );
+
+ // check if data series entry percent value and absolute value have to
+ // be appended to the text label, and what should be the separator
+ // character (comma, space, new line). In case it is a new line we get
+ // a multi-line label.
+ bool bMultiLineLabel = ( aSeparator == "\n" );
+
+ if( bUseCustomLabel )
+ {
+ Sequence< uno::Reference< XFormattedString > > aFormattedLabels(
+ comphelper::containerToSequence<uno::Reference<XFormattedString>>(aCustomLabels));
+
+ // create text shape
+ xTextShape = ShapeFactory::
+ createText( xTarget_, aFormattedLabels, *pPropNames, *pPropValues,
+ ShapeFactory::makeTransformation( aScreenPosition2D ) );
+ }
+ else
+ {
+ // join text list elements
+ OUStringBuffer aText;
+ for( sal_uInt32 nN = 0; nN < nTextListLength; ++nN)
+ {
+ if( !aTextList[nN].isEmpty() )
+ {
+ if( !aText.isEmpty() )
+ {
+ aText.append(aSeparator);
+ }
+ aText.append( aTextList[nN] );
+ }
+ }
+
+ //create text shape
+ xTextShape = ShapeFactory::
+ createText( xTarget_, aText.makeStringAndClear(), *pPropNames, *pPropValues,
+ ShapeFactory::makeTransformation( aScreenPosition2D ) );
+ }
+
+ if( !xTextShape.is() )
+ return xTextShape;
+
+ // we need to use a default value for the maximum width property ?
+ if( nTextWidth == 0 && bTextWrap )
+ {
+ sal_Int32 nMinSize =
+ (m_aPageReferenceSize.Height < m_aPageReferenceSize.Width)
+ ? m_aPageReferenceSize.Height
+ : m_aPageReferenceSize.Width;
+ nTextWidth = nMinSize / 3;
+ }
+
+ // in case text must be wrapped set the maximum width property
+ // for the text shape
+ if( nTextWidth != 0 && bTextWrap )
+ {
+ // compute the height of a line of text
+ if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
+ {
+ nLineCountForSymbolsize = 1;
+ }
+ awt::Size aTextSize = xTextShape->getSize();
+ sal_Int32 aTextLineHeight = aTextSize.Height / nLineCountForSymbolsize;
+
+ // set maximum text width
+ uno::Any aTextMaximumFrameWidth( nTextWidth );
+ xTextShape->SvxShape::setPropertyValue( "TextMaximumFrameWidth", aTextMaximumFrameWidth );
+
+ // compute the total lines of text
+ aTextSize = xTextShape->getSize();
+ nLineCountForSymbolsize = aTextSize.Height / aTextLineHeight;
+ }
+
+ // in case text is rotated, the transformation property of the text
+ // shape is modified.
+ if( fRotationDegrees != 0.0 )
+ {
+ const double fDegreesPi( -basegfx::deg2rad(fRotationDegrees) );
+ xTextShape->SvxShape::setPropertyValue( "Transformation", ShapeFactory::makeTransformation( aScreenPosition2D, fDegreesPi ) );
+ LabelPositionHelper::correctPositionForRotation( xTextShape, eAlignment, fRotationDegrees, true /*bRotateAroundCenter*/ );
+ }
+
+ awt::Point aTextShapePos(xTextShape->getPosition());
+ if( m_bPieLabelsAllowToMove && rDataSeries.isLabelCustomPos(nPointIndex) )
+ {
+ awt::Point aRelPos = rDataSeries.getLabelPosition(aTextShapePos, nPointIndex);
+ if( aRelPos.X != -1 )
+ {
+ xTextShape->setPosition(aRelPos);
+ if( !m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) &&
+ rDataSeries.getPropertiesOfSeries()->getPropertyValue( "ShowCustomLeaderLines" ).get<sal_Bool>())
+ {
+ sal_Int32 nX1 = rScreenPosition2D.X;
+ sal_Int32 nY1 = rScreenPosition2D.Y;
+ sal_Int32 nX2 = nX1;
+ sal_Int32 nY2 = nY1;
+ ::basegfx::B2IRectangle aRect(BaseGFXHelper::makeRectangle(aRelPos, xTextShape->getSize()));
+ if (nX1 < aRelPos.X)
+ nX2 = aRelPos.X;
+ else if (nX1 > aRect.getMaxX())
+ nX2 = aRect.getMaxX();
+
+ if (nY1 < aRect.getMinY())
+ nY2 = aRect.getMinY();
+ else if (nY1 > aRect.getMaxY())
+ nY2 = aRect.getMaxY();
+
+ //when the line is very short compared to the page size don't create one
+ ::basegfx::B2DVector aLength(nX1 - nX2, nY1 - nY2);
+ double fPageDiagonaleLength
+ = std::hypot(m_aPageReferenceSize.Width, m_aPageReferenceSize.Height);
+ if ((aLength.getLength() / fPageDiagonaleLength) >= 0.01)
+ {
+ drawing::PointSequenceSequence aPoints{ { {nX1, nY1}, {nX2, nY2} } };
+
+ VLineProperties aVLineProperties;
+ ShapeFactory::createLine2D(xTarget, aPoints, &aVLineProperties);
+ }
+ }
+ }
+ }
+
+ // in case legend symbol has to be displayed, text shape position is
+ // slightly changed.
+ const awt::Point aUnrotatedTextPos(xTextShape->getPosition());
+ if( xSymbol.is() )
+ {
+ const awt::Point aOldTextPos( xTextShape->getPosition() );
+ awt::Point aNewTextPos( aOldTextPos );
+
+ awt::Point aSymbolPosition( aUnrotatedTextPos );
+ awt::Size aSymbolSize( xSymbol->getSize() );
+ awt::Size aTextSize = xTextShape->getSize();
+
+ sal_Int32 nXDiff = aSymbolSize.Width + static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
+ if( !bMultiLineLabel || nLineCountForSymbolsize <= 0 )
+ nLineCountForSymbolsize = 1;
+ aSymbolPosition.Y += ((aTextSize.Height/nLineCountForSymbolsize)/4);
+
+ if(eAlignment==LABEL_ALIGN_LEFT
+ || eAlignment==LABEL_ALIGN_LEFT_TOP
+ || eAlignment==LABEL_ALIGN_LEFT_BOTTOM)
+ {
+ aSymbolPosition.X -= nXDiff;
+ }
+ else if(eAlignment==LABEL_ALIGN_RIGHT
+ || eAlignment==LABEL_ALIGN_RIGHT_TOP
+ || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM )
+ {
+ aNewTextPos.X += nXDiff;
+ }
+ else if(eAlignment==LABEL_ALIGN_TOP
+ || eAlignment==LABEL_ALIGN_BOTTOM
+ || eAlignment==LABEL_ALIGN_CENTER )
+ {
+ aSymbolPosition.X -= nXDiff/2;
+ aNewTextPos.X += nXDiff/2;
+ }
+
+ xSymbol->setPosition( aSymbolPosition );
+ xTextShape->setPosition( aNewTextPos );
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ return xTextShape;
+}
+
+namespace
+{
+double lcl_getErrorBarLogicLength(
+ const uno::Sequence< double > & rData,
+ const uno::Reference< beans::XPropertySet >& xProp,
+ sal_Int32 nErrorBarStyle,
+ sal_Int32 nIndex,
+ bool bPositive,
+ bool bYError )
+{
+ double fResult = std::numeric_limits<double>::quiet_NaN();
+ try
+ {
+ switch( nErrorBarStyle )
+ {
+ case css::chart::ErrorBarStyle::NONE:
+ break;
+ case css::chart::ErrorBarStyle::VARIANCE:
+ fResult = StatisticsHelper::getVariance( rData );
+ break;
+ case css::chart::ErrorBarStyle::STANDARD_DEVIATION:
+ fResult = StatisticsHelper::getStandardDeviation( rData );
+ break;
+ case css::chart::ErrorBarStyle::RELATIVE:
+ {
+ double fPercent = 0;
+ if( xProp->getPropertyValue( bPositive
+ ? OUString("PositiveError")
+ : OUString("NegativeError") ) >>= fPercent )
+ {
+ if( nIndex >=0 && nIndex < rData.getLength() &&
+ ! std::isnan( rData[nIndex] ) &&
+ ! std::isnan( fPercent ))
+ {
+ fResult = rData[nIndex] * fPercent / 100.0;
+ }
+ }
+ }
+ break;
+ case css::chart::ErrorBarStyle::ABSOLUTE:
+ xProp->getPropertyValue( bPositive
+ ? OUString("PositiveError")
+ : OUString("NegativeError") ) >>= fResult;
+ break;
+ case css::chart::ErrorBarStyle::ERROR_MARGIN:
+ {
+ // todo: check if this is really what's called error-margin
+ double fPercent = 0;
+ if( xProp->getPropertyValue( bPositive
+ ? OUString("PositiveError")
+ : OUString("NegativeError") ) >>= fPercent )
+ {
+ double fMaxValue = -std::numeric_limits<double>::infinity();
+ for(double d : rData)
+ {
+ if(fMaxValue < d)
+ fMaxValue = d;
+ }
+ if( std::isfinite( fMaxValue ) &&
+ std::isfinite( fPercent ))
+ {
+ fResult = fMaxValue * fPercent / 100.0;
+ }
+ }
+ }
+ break;
+ case css::chart::ErrorBarStyle::STANDARD_ERROR:
+ fResult = StatisticsHelper::getStandardError( rData );
+ break;
+ case css::chart::ErrorBarStyle::FROM_DATA:
+ {
+ uno::Reference< chart2::data::XDataSource > xErrorBarData( xProp, uno::UNO_QUERY );
+ if( xErrorBarData.is())
+ fResult = StatisticsHelper::getErrorFromDataSource(
+ xErrorBarData, nIndex, bPositive, bYError);
+ }
+ break;
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ return fResult;
+}
+
+void lcl_AddErrorBottomLine( const drawing::Position3D& rPosition, ::basegfx::B2DVector aMainDirection
+ , std::vector<std::vector<css::drawing::Position3D>>& rPoly, sal_Int32 nSequenceIndex )
+{
+ double fFixedWidth = 200.0;
+
+ aMainDirection.normalize();
+ ::basegfx::B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX());
+ aOrthoDirection.normalize();
+
+ ::basegfx::B2DVector aAnchor( rPosition.PositionX, rPosition.PositionY );
+ ::basegfx::B2DVector aStart = aAnchor + aOrthoDirection*fFixedWidth/2.0;
+ ::basegfx::B2DVector aEnd = aAnchor - aOrthoDirection*fFixedWidth/2.0;
+
+ AddPointToPoly( rPoly, drawing::Position3D( aStart.getX(), aStart.getY(), rPosition.PositionZ), nSequenceIndex );
+ AddPointToPoly( rPoly, drawing::Position3D( aEnd.getX(), aEnd.getY(), rPosition.PositionZ), nSequenceIndex );
+}
+
+::basegfx::B2DVector lcl_getErrorBarMainDirection(
+ const drawing::Position3D& rStart
+ , const drawing::Position3D& rBottomEnd
+ , PlottingPositionHelper const * pPosHelper
+ , const drawing::Position3D& rUnscaledLogicPosition
+ , bool bYError )
+{
+ ::basegfx::B2DVector aMainDirection( rStart.PositionX - rBottomEnd.PositionX
+ , rStart.PositionY - rBottomEnd.PositionY );
+ if( !aMainDirection.getLength() )
+ {
+ //get logic clip values:
+ double MinX = pPosHelper->getLogicMinX();
+ double MinY = pPosHelper->getLogicMinY();
+ double MaxX = pPosHelper->getLogicMaxX();
+ double MaxY = pPosHelper->getLogicMaxY();
+ double fZ = pPosHelper->getLogicMinZ();
+
+ if( bYError )
+ {
+ //main direction has constant x value
+ MinX = rUnscaledLogicPosition.PositionX;
+ MaxX = rUnscaledLogicPosition.PositionX;
+ }
+ else
+ {
+ //main direction has constant y value
+ MinY = rUnscaledLogicPosition.PositionY;
+ MaxY = rUnscaledLogicPosition.PositionY;
+ }
+
+ drawing::Position3D aStart = pPosHelper->transformLogicToScene( MinX, MinY, fZ, false );
+ drawing::Position3D aEnd = pPosHelper->transformLogicToScene( MaxX, MaxY, fZ, false );
+
+ aMainDirection = ::basegfx::B2DVector( aStart.PositionX - aEnd.PositionX
+ , aStart.PositionY - aEnd.PositionY );
+ }
+ if( !aMainDirection.getLength() )
+ {
+ //@todo
+ }
+ return aMainDirection;
+}
+
+drawing::Position3D lcl_transformMixedToScene( PlottingPositionHelper const * pPosHelper
+ , double fX /*scaled*/, double fY /*unscaled*/, double fZ /*unscaled*/ )
+{
+ if(!pPosHelper)
+ return drawing::Position3D(0,0,0);
+ pPosHelper->doLogicScaling( nullptr,&fY,&fZ );
+ pPosHelper->clipScaledLogicValues( &fX,&fY,&fZ );
+ return pPosHelper->transformScaledLogicToScene( fX, fY, fZ, false );
+}
+
+} // anonymous namespace
+
+void VSeriesPlotter::createErrorBar(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rUnscaledLogicPosition
+ , const uno::Reference< beans::XPropertySet > & xErrorBarProperties
+ , const VDataSeries& rVDataSeries
+ , sal_Int32 nIndex
+ , bool bYError /* = true */
+ , const double* pfScaledLogicX
+ )
+{
+ if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
+ return;
+
+ if( ! xErrorBarProperties.is())
+ return;
+
+ try
+ {
+ bool bShowPositive = false;
+ bool bShowNegative = false;
+ sal_Int32 nErrorBarStyle = css::chart::ErrorBarStyle::VARIANCE;
+
+ xErrorBarProperties->getPropertyValue( "ShowPositiveError") >>= bShowPositive;
+ xErrorBarProperties->getPropertyValue( "ShowNegativeError") >>= bShowNegative;
+ xErrorBarProperties->getPropertyValue( "ErrorBarStyle") >>= nErrorBarStyle;
+
+ if(!bShowPositive && !bShowNegative)
+ return;
+
+ if(nErrorBarStyle==css::chart::ErrorBarStyle::NONE)
+ return;
+
+ if (!m_pPosHelper)
+ return;
+
+ drawing::Position3D aUnscaledLogicPosition(rUnscaledLogicPosition);
+ if(nErrorBarStyle==css::chart::ErrorBarStyle::STANDARD_DEVIATION)
+ {
+ if (bYError)
+ aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
+ else
+ aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
+ }
+
+ bool bCreateNegativeBorder = false;//make a vertical line at the negative end of the error bar
+ bool bCreatePositiveBorder = false;//make a vertical line at the positive end of the error bar
+ drawing::Position3D aMiddle(aUnscaledLogicPosition);
+ const double fX = aUnscaledLogicPosition.PositionX;
+ const double fY = aUnscaledLogicPosition.PositionY;
+ const double fZ = aUnscaledLogicPosition.PositionZ;
+ double fScaledX = fX;
+ if( pfScaledLogicX )
+ fScaledX = *pfScaledLogicX;
+ else
+ m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
+
+ aMiddle = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fY, fZ );
+
+ drawing::Position3D aNegative(aMiddle);
+ drawing::Position3D aPositive(aMiddle);
+
+ uno::Sequence< double > aData( bYError ? rVDataSeries.getAllY() : rVDataSeries.getAllX() );
+
+ if( bShowPositive )
+ {
+ double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, true, bYError );
+ if( std::isfinite( fLength ) )
+ {
+ double fLocalX = fX;
+ double fLocalY = fY;
+ if( bYError )
+ {
+ fLocalY+=fLength;
+ aPositive = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
+ }
+ else
+ {
+ fLocalX+=fLength;
+ aPositive = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
+ }
+ bCreatePositiveBorder = m_pPosHelper->isLogicVisible(fLocalX, fLocalY, fZ);
+ }
+ else
+ bShowPositive = false;
+ }
+
+ if( bShowNegative )
+ {
+ double fLength = lcl_getErrorBarLogicLength( aData, xErrorBarProperties, nErrorBarStyle, nIndex, false, bYError );
+ if( std::isfinite( fLength ) )
+ {
+ double fLocalX = fX;
+ double fLocalY = fY;
+ if( bYError )
+ {
+ fLocalY-=fLength;
+ aNegative = lcl_transformMixedToScene( m_pPosHelper, fScaledX, fLocalY, fZ );
+ }
+ else
+ {
+ fLocalX-=fLength;
+ aNegative = m_pPosHelper->transformLogicToScene( fLocalX, fLocalY, fZ, true );
+ }
+ if (std::isfinite(aNegative.PositionX) &&
+ std::isfinite(aNegative.PositionY) &&
+ std::isfinite(aNegative.PositionZ)) {
+ bCreateNegativeBorder = m_pPosHelper->isLogicVisible( fLocalX, fLocalY, fZ);
+ } else {
+ // If error bars result in a numerical problem (e.g., an
+ // error bar on a logarithmic chart that results in a point
+ // <= 0) then just turn off the error bar.
+ //
+ // TODO: This perhaps should display a warning, so the user
+ // knows why a bar is not appearing.
+ // TODO: This test could also be added to the positive case,
+ // though a numerical overflow there is less likely.
+ bShowNegative = false;
+ }
+ }
+ else
+ bShowNegative = false;
+ }
+
+ if(!bShowPositive && !bShowNegative)
+ return;
+
+ std::vector<std::vector<css::drawing::Position3D>> aPoly;
+
+ sal_Int32 nSequenceIndex=0;
+ if( bShowNegative )
+ AddPointToPoly( aPoly, aNegative, nSequenceIndex );
+ AddPointToPoly( aPoly, aMiddle, nSequenceIndex );
+ if( bShowPositive )
+ AddPointToPoly( aPoly, aPositive, nSequenceIndex );
+
+ if( bShowNegative && bCreateNegativeBorder )
+ {
+ ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aNegative, m_pPosHelper, aUnscaledLogicPosition, bYError );
+ nSequenceIndex++;
+ lcl_AddErrorBottomLine( aNegative, aMainDirection, aPoly, nSequenceIndex );
+ }
+ if( bShowPositive && bCreatePositiveBorder )
+ {
+ ::basegfx::B2DVector aMainDirection = lcl_getErrorBarMainDirection( aMiddle, aPositive, m_pPosHelper, aUnscaledLogicPosition, bYError );
+ nSequenceIndex++;
+ lcl_AddErrorBottomLine( aPositive, aMainDirection, aPoly, nSequenceIndex );
+ }
+
+ rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D( xTarget, aPoly );
+ PropertyMapper::setMappedProperties( *xShape, xErrorBarProperties, PropertyMapper::getPropertyNameMapForLineProperties() );
+ }
+ catch( const uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+}
+
+void VSeriesPlotter::addErrorBorder(
+ const drawing::Position3D& rPos0
+ ,const drawing::Position3D& rPos1
+ ,const rtl::Reference<SvxShapeGroupAnyD>& rTarget
+ ,const uno::Reference< beans::XPropertySet >& rErrorBorderProp )
+{
+ std::vector<std::vector<css::drawing::Position3D>> aPoly { { rPos0, rPos1} };
+ rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D(
+ rTarget, aPoly );
+ PropertyMapper::setMappedProperties( *xShape, rErrorBorderProp,
+ PropertyMapper::getPropertyNameMapForLineProperties() );
+}
+
+void VSeriesPlotter::createErrorRectangle(
+ const drawing::Position3D& rUnscaledLogicPosition
+ ,VDataSeries& rVDataSeries
+ ,sal_Int32 nIndex
+ ,const rtl::Reference<SvxShapeGroupAnyD>& rTarget
+ ,bool bUseXErrorData
+ ,bool bUseYErrorData )
+{
+ if ( m_nDimension != 2 )
+ return;
+
+ // error border properties
+ Reference< beans::XPropertySet > xErrorBorderPropX, xErrorBorderPropY;
+ if ( bUseXErrorData )
+ {
+ xErrorBorderPropX = rVDataSeries.getXErrorBarProperties( nIndex );
+ if ( !xErrorBorderPropX.is() )
+ return;
+ }
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBorder_ShapesX =
+ getErrorBarsGroupShape( rVDataSeries, rTarget, false );
+
+ if ( bUseYErrorData )
+ {
+ xErrorBorderPropY = rVDataSeries.getYErrorBarProperties( nIndex );
+ if ( !xErrorBorderPropY.is() )
+ return;
+ }
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBorder_ShapesY =
+ getErrorBarsGroupShape( rVDataSeries, rTarget, true );
+
+ if( !ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ) )
+ return;
+
+ try
+ {
+ bool bShowXPositive = false;
+ bool bShowXNegative = false;
+ bool bShowYPositive = false;
+ bool bShowYNegative = false;
+
+ sal_Int32 nErrorBorderStyleX = css::chart::ErrorBarStyle::VARIANCE;
+ sal_Int32 nErrorBorderStyleY = css::chart::ErrorBarStyle::VARIANCE;
+
+ if ( bUseXErrorData )
+ {
+ xErrorBorderPropX->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleX;
+ xErrorBorderPropX->getPropertyValue( "ShowPositiveError") >>= bShowXPositive;
+ xErrorBorderPropX->getPropertyValue( "ShowNegativeError") >>= bShowXNegative;
+ }
+ if ( bUseYErrorData )
+ {
+ xErrorBorderPropY->getPropertyValue( "ErrorBarStyle" ) >>= nErrorBorderStyleY;
+ xErrorBorderPropY->getPropertyValue( "ShowPositiveError") >>= bShowYPositive;
+ xErrorBorderPropY->getPropertyValue( "ShowNegativeError") >>= bShowYNegative;
+ }
+
+ if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::NONE )
+ bUseXErrorData = false;
+ if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::NONE )
+ bUseYErrorData = false;
+
+ if ( !bShowXPositive && !bShowXNegative && !bShowYPositive && !bShowYNegative )
+ return;
+
+ if ( !m_pPosHelper )
+ return;
+
+ drawing::Position3D aUnscaledLogicPosition( rUnscaledLogicPosition );
+ if ( bUseXErrorData && nErrorBorderStyleX == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
+ aUnscaledLogicPosition.PositionX = rVDataSeries.getXMeanValue();
+ if ( bUseYErrorData && nErrorBorderStyleY == css::chart::ErrorBarStyle::STANDARD_DEVIATION )
+ aUnscaledLogicPosition.PositionY = rVDataSeries.getYMeanValue();
+
+ const double fX = aUnscaledLogicPosition.PositionX;
+ const double fY = aUnscaledLogicPosition.PositionY;
+ const double fZ = aUnscaledLogicPosition.PositionZ;
+ double fScaledX = fX;
+ m_pPosHelper->doLogicScaling( &fScaledX, nullptr, nullptr );
+
+ uno::Sequence< double > aDataX( rVDataSeries.getAllX() );
+ uno::Sequence< double > aDataY( rVDataSeries.getAllY() );
+
+ double fPosX = 0.0;
+ double fPosY = 0.0;
+ double fNegX = 0.0;
+ double fNegY = 0.0;
+ if ( bUseXErrorData )
+ {
+ if ( bShowXPositive )
+ fPosX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
+ nErrorBorderStyleX, nIndex, true, false );
+ if ( bShowXNegative )
+ fNegX = lcl_getErrorBarLogicLength( aDataX, xErrorBorderPropX,
+ nErrorBorderStyleX, nIndex, false, false );
+ }
+
+ if ( bUseYErrorData )
+ {
+ if ( bShowYPositive )
+ fPosY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
+ nErrorBorderStyleY, nIndex, true, true );
+ if ( bShowYNegative )
+ fNegY = lcl_getErrorBarLogicLength( aDataY, xErrorBorderPropY,
+ nErrorBorderStyleY, nIndex, false, true );
+ }
+
+ if ( !( std::isfinite( fPosX ) &&
+ std::isfinite( fPosY ) &&
+ std::isfinite( fNegX ) &&
+ std::isfinite( fNegY ) ) )
+ return;
+
+ drawing::Position3D aBottomLeft( lcl_transformMixedToScene( m_pPosHelper,
+ fX - fNegX, fY - fNegY, fZ ) );
+ drawing::Position3D aTopLeft( lcl_transformMixedToScene( m_pPosHelper,
+ fX - fNegX, fY + fPosY, fZ ) );
+ drawing::Position3D aTopRight( lcl_transformMixedToScene( m_pPosHelper,
+ fX + fPosX, fY + fPosY, fZ ) );
+ drawing::Position3D aBottomRight( lcl_transformMixedToScene( m_pPosHelper,
+ fX + fPosX, fY - fNegY, fZ ) );
+ if ( bUseXErrorData )
+ {
+ // top border
+ addErrorBorder( aTopLeft, aTopRight, xErrorBorder_ShapesX, xErrorBorderPropX );
+
+ // bottom border
+ addErrorBorder( aBottomRight, aBottomLeft, xErrorBorder_ShapesX, xErrorBorderPropX );
+ }
+
+ if ( bUseYErrorData )
+ {
+ // left border
+ addErrorBorder( aBottomLeft, aTopLeft, xErrorBorder_ShapesY, xErrorBorderPropY );
+
+ // right border
+ addErrorBorder( aTopRight, aBottomRight, xErrorBorder_ShapesY, xErrorBorderPropY );
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2", "Exception in createErrorRectangle(). ");
+ }
+}
+
+void VSeriesPlotter::createErrorBar_X( const drawing::Position3D& rUnscaledLogicPosition
+ , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ if(m_nDimension!=2)
+ return;
+ // error bars
+ uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getXErrorBarProperties(nPointIndex));
+ if( xErrorBarProp.is())
+ {
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBarsGroup_Shapes =
+ getErrorBarsGroupShape(rVDataSeries, xTarget, false);
+
+ createErrorBar( xErrorBarsGroup_Shapes
+ , rUnscaledLogicPosition, xErrorBarProp
+ , rVDataSeries, nPointIndex
+ , false /* bYError */
+ , nullptr );
+ }
+}
+
+void VSeriesPlotter::createErrorBar_Y( const drawing::Position3D& rUnscaledLogicPosition
+ , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , double const * pfScaledLogicX )
+{
+ if(m_nDimension!=2)
+ return;
+ // error bars
+ uno::Reference< beans::XPropertySet > xErrorBarProp(rVDataSeries.getYErrorBarProperties(nPointIndex));
+ if( xErrorBarProp.is())
+ {
+ rtl::Reference<SvxShapeGroupAnyD> xErrorBarsGroup_Shapes =
+ getErrorBarsGroupShape(rVDataSeries, xTarget, true);
+
+ createErrorBar( xErrorBarsGroup_Shapes
+ , rUnscaledLogicPosition, xErrorBarProp
+ , rVDataSeries, nPointIndex
+ , true /* bYError */
+ , pfScaledLogicX );
+ }
+}
+
+void VSeriesPlotter::createRegressionCurvesShapes( VDataSeries const & rVDataSeries,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget,
+ bool bMaySkipPoints )
+{
+ if(m_nDimension!=2)
+ return;
+ rtl::Reference< DataSeries > xContainer( rVDataSeries.getModel() );
+ if(!xContainer.is())
+ return;
+
+ if (!m_pPosHelper)
+ return;
+
+ const std::vector< rtl::Reference< ::chart::RegressionCurveModel > > & aCurveList = xContainer->getRegressionCurves2();
+
+ for(std::size_t nN=0; nN<aCurveList.size(); nN++)
+ {
+ const auto & rCurve = aCurveList[nN];
+ uno::Reference< XRegressionCurveCalculator > xCalculator( rCurve->getCalculator() );
+ if( !xCalculator.is())
+ continue;
+
+ bool bAverageLine = RegressionCurveHelper::isMeanValueLine( rCurve );
+
+ sal_Int32 aDegree = 2;
+ sal_Int32 aPeriod = 2;
+ sal_Int32 aMovingAverageType = css::chart2::MovingAverageType::Prior;
+ double aExtrapolateForward = 0.0;
+ double aExtrapolateBackward = 0.0;
+ bool bForceIntercept = false;
+ double aInterceptValue = 0.0;
+
+ if ( !bAverageLine )
+ {
+ rCurve->getPropertyValue( "PolynomialDegree") >>= aDegree;
+ rCurve->getPropertyValue( "MovingAveragePeriod") >>= aPeriod;
+ rCurve->getPropertyValue( "MovingAverageType") >>= aMovingAverageType;
+ rCurve->getPropertyValue( "ExtrapolateForward") >>= aExtrapolateForward;
+ rCurve->getPropertyValue( "ExtrapolateBackward") >>= aExtrapolateBackward;
+ rCurve->getPropertyValue( "ForceIntercept") >>= bForceIntercept;
+ if (bForceIntercept)
+ rCurve->getPropertyValue( "InterceptValue") >>= aInterceptValue;
+ }
+
+ double fChartMinX = m_pPosHelper->getLogicMinX();
+ double fChartMaxX = m_pPosHelper->getLogicMaxX();
+
+ double fMinX = fChartMinX;
+ double fMaxX = fChartMaxX;
+
+ double fPointScale = 1.0;
+
+ if( !bAverageLine )
+ {
+ rVDataSeries.getMinMaxXValue(fMinX, fMaxX);
+ fMaxX += aExtrapolateForward;
+ fMinX -= aExtrapolateBackward;
+
+ fPointScale = (fMaxX - fMinX) / (fChartMaxX - fChartMinX);
+ // sanitize the value, tdf#119922
+ fPointScale = std::min(fPointScale, 1000.0);
+ }
+
+ xCalculator->setRegressionProperties(aDegree, bForceIntercept, aInterceptValue, aPeriod,
+ aMovingAverageType);
+ xCalculator->recalculateRegression(rVDataSeries.getAllX(), rVDataSeries.getAllY());
+ sal_Int32 nPointCount = 100 * fPointScale;
+
+ if ( nPointCount < 2 )
+ nPointCount = 2;
+
+ std::vector< ExplicitScaleData > aScales( m_pPosHelper->getScales());
+ uno::Reference< chart2::XScaling > xScalingX;
+ uno::Reference< chart2::XScaling > xScalingY;
+ if( aScales.size() >= 2 )
+ {
+ xScalingX.set( aScales[0].Scaling );
+ xScalingY.set( aScales[1].Scaling );
+ }
+
+ const uno::Sequence< geometry::RealPoint2D > aCalculatedPoints(
+ xCalculator->getCurveValues(
+ fMinX, fMaxX, nPointCount,
+ xScalingX, xScalingY, bMaySkipPoints ));
+
+ nPointCount = aCalculatedPoints.getLength();
+
+ drawing::PolyPolygonShape3D aRegressionPoly;
+ aRegressionPoly.SequenceX.realloc(1);
+ aRegressionPoly.SequenceY.realloc(1);
+ aRegressionPoly.SequenceZ.realloc(1);
+ auto pSequenceX = aRegressionPoly.SequenceX.getArray();
+ auto pSequenceY = aRegressionPoly.SequenceY.getArray();
+ auto pSequenceZ = aRegressionPoly.SequenceZ.getArray();
+ pSequenceX[0].realloc(nPointCount);
+ pSequenceY[0].realloc(nPointCount);
+ auto pSequenceX0 = pSequenceX[0].getArray();
+ auto pSequenceY0 = pSequenceY[0].getArray();
+
+ sal_Int32 nRealPointCount = 0;
+
+ for(geometry::RealPoint2D const & p : aCalculatedPoints)
+ {
+ double fLogicX = p.X;
+ double fLogicY = p.Y;
+ double fLogicZ = 0.0; //dummy
+
+ // fdo#51656: don't scale mean value lines
+ if(!bAverageLine)
+ m_pPosHelper->doLogicScaling( &fLogicX, &fLogicY, &fLogicZ );
+
+ if(!std::isnan(fLogicX) && !std::isinf(fLogicX) &&
+ !std::isnan(fLogicY) && !std::isinf(fLogicY) &&
+ !std::isnan(fLogicZ) && !std::isinf(fLogicZ) )
+ {
+ pSequenceX0[nRealPointCount] = fLogicX;
+ pSequenceY0[nRealPointCount] = fLogicY;
+ nRealPointCount++;
+ }
+ }
+ pSequenceX[0].realloc(nRealPointCount);
+ pSequenceY[0].realloc(nRealPointCount);
+ pSequenceZ[0].realloc(nRealPointCount);
+
+ drawing::PolyPolygonShape3D aClippedPoly;
+ Clipping::clipPolygonAtRectangle( aRegressionPoly, m_pPosHelper->getScaledLogicClipDoubleRect(), aClippedPoly );
+ aRegressionPoly = aClippedPoly;
+ m_pPosHelper->transformScaledLogicToScene( aRegressionPoly );
+
+ awt::Point aDefaultPos;
+ if( aRegressionPoly.SequenceX.hasElements() && aRegressionPoly.SequenceX[0].hasElements() )
+ {
+ VLineProperties aVLineProperties;
+ aVLineProperties.initFromPropertySet( rCurve );
+
+ //create an extra group shape for each curve for selection handling
+ rtl::Reference<SvxShapeGroupAnyD> xRegressionGroupShapes =
+ createGroupShape( xTarget, rVDataSeries.getDataCurveCID( nN, bAverageLine ) );
+ rtl::Reference<SvxShapePolyPolygon> xShape = ShapeFactory::createLine2D(
+ xRegressionGroupShapes, PolyToPointSequence( aRegressionPoly ), &aVLineProperties );
+ ShapeFactory::setShapeName( xShape, "MarkHandles" );
+ aDefaultPos = xShape->getPosition();
+ }
+
+ // curve equation and correlation coefficient
+ uno::Reference< beans::XPropertySet > xEquationProperties( rCurve->getEquationProperties());
+ if( xEquationProperties.is())
+ {
+ createRegressionCurveEquationShapes(
+ rVDataSeries.getDataCurveEquationCID( nN ),
+ xEquationProperties, xEquationTarget, xCalculator,
+ aDefaultPos );
+ }
+ }
+}
+
+static sal_Int32 lcl_getOUStringMaxLineLength ( OUStringBuffer const & aString )
+{
+ const sal_Int32 nStringLength = aString.getLength();
+ sal_Int32 nMaxLineLength = 0;
+
+ for ( sal_Int32 i=0; i<nStringLength; i++ )
+ {
+ sal_Int32 indexSep = aString.indexOf( "\n", i );
+ if ( indexSep < 0 )
+ indexSep = nStringLength;
+ sal_Int32 nLineLength = indexSep - i;
+ if ( nLineLength > nMaxLineLength )
+ nMaxLineLength = nLineLength;
+ i = indexSep;
+ }
+
+ return nMaxLineLength;
+}
+
+void VSeriesPlotter::createRegressionCurveEquationShapes(
+ const OUString & rEquationCID,
+ const uno::Reference< beans::XPropertySet > & xEquationProperties,
+ const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget,
+ const uno::Reference< chart2::XRegressionCurveCalculator > & xRegressionCurveCalculator,
+ awt::Point aDefaultPos )
+{
+ OSL_ASSERT( xEquationProperties.is());
+ if( !xEquationProperties.is())
+ return;
+
+ bool bShowEquation = false;
+ bool bShowCorrCoeff = false;
+ if(!(( xEquationProperties->getPropertyValue( "ShowEquation") >>= bShowEquation ) &&
+ ( xEquationProperties->getPropertyValue( "ShowCorrelationCoefficient") >>= bShowCorrCoeff )))
+ return;
+
+ if( ! (bShowEquation || bShowCorrCoeff))
+ return;
+
+ OUStringBuffer aFormula;
+ sal_Int32 nNumberFormatKey = 0;
+ sal_Int32 nFormulaWidth = 0;
+ xEquationProperties->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nNumberFormatKey;
+ bool bResizeEquation = true;
+ sal_Int32 nMaxIteration = 2;
+ if ( bShowEquation )
+ {
+ OUString aXName, aYName;
+ if ( !(xEquationProperties->getPropertyValue( "XName" ) >>= aXName) )
+ aXName = OUString( "x" );
+ if ( !(xEquationProperties->getPropertyValue( "YName" ) >>= aYName) )
+ aYName = OUString( "f(x)" );
+ xRegressionCurveCalculator->setXYNames( aXName, aYName );
+ }
+
+ for ( sal_Int32 nCountIteration = 0; bResizeEquation && nCountIteration < nMaxIteration ; nCountIteration++ )
+ {
+ bResizeEquation = false;
+ if( bShowEquation )
+ {
+ if (m_apNumberFormatterWrapper)
+ { // iteration 0: default representation (no wrap)
+ // iteration 1: expected width (nFormulaWidth) is calculated
+ aFormula = xRegressionCurveCalculator->getFormattedRepresentation(
+ m_apNumberFormatterWrapper->getNumberFormatsSupplier(),
+ nNumberFormatKey, nFormulaWidth );
+ nFormulaWidth = lcl_getOUStringMaxLineLength( aFormula );
+ }
+ else
+ {
+ aFormula = xRegressionCurveCalculator->getRepresentation();
+ }
+
+ if( bShowCorrCoeff )
+ {
+ aFormula.append( "\n" );
+ }
+ }
+ if( bShowCorrCoeff )
+ {
+ aFormula.append( "R" + OUStringChar( aSuperscriptFigures[2] ) + " = " );
+ double fR( xRegressionCurveCalculator->getCorrelationCoefficient());
+ if (m_apNumberFormatterWrapper)
+ {
+ Color nLabelCol;
+ bool bColChanged;
+ aFormula.append(
+ m_apNumberFormatterWrapper->getFormattedString(
+ nNumberFormatKey, fR*fR, nLabelCol, bColChanged ));
+ //@todo: change color of label if bColChanged is true
+ }
+ else
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+ const OUString& aNumDecimalSep = rLocaleDataWrapper.getNumDecimalSep();
+ sal_Unicode aDecimalSep = aNumDecimalSep[0];
+ aFormula.append( ::rtl::math::doubleToUString(
+ fR*fR, rtl_math_StringFormat_G, 4, aDecimalSep, true ));
+ }
+ }
+
+ awt::Point aScreenPosition2D;
+ chart2::RelativePosition aRelativePosition;
+ if( xEquationProperties->getPropertyValue( "RelativePosition") >>= aRelativePosition )
+ {
+ //@todo decide whether x is primary or secondary
+ double fX = aRelativePosition.Primary*m_aPageReferenceSize.Width;
+ double fY = aRelativePosition.Secondary*m_aPageReferenceSize.Height;
+ aScreenPosition2D.X = static_cast< sal_Int32 >( ::rtl::math::round( fX ));
+ aScreenPosition2D.Y = static_cast< sal_Int32 >( ::rtl::math::round( fY ));
+ }
+ else
+ aScreenPosition2D = aDefaultPos;
+
+ if( !aFormula.isEmpty())
+ {
+ // set fill and line properties on creation
+ tNameSequence aNames;
+ tAnySequence aValues;
+ PropertyMapper::getPreparedTextShapePropertyLists( xEquationProperties, aNames, aValues );
+
+ rtl::Reference<SvxShapeText> xTextShape = ShapeFactory::createText(
+ xEquationTarget, aFormula.makeStringAndClear(),
+ aNames, aValues, ShapeFactory::makeTransformation( aScreenPosition2D ));
+
+ ShapeFactory::setShapeName( xTextShape, rEquationCID );
+ awt::Size aSize( xTextShape->getSize() );
+ awt::Point aPos( RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
+ aScreenPosition2D, aSize, aRelativePosition.Anchor ) );
+ //ensure that the equation is fully placed within the page (if possible)
+ if( (aPos.X + aSize.Width) > m_aPageReferenceSize.Width )
+ aPos.X = m_aPageReferenceSize.Width - aSize.Width;
+ if( aPos.X < 0 )
+ {
+ aPos.X = 0;
+ if ( nFormulaWidth > 0 )
+ {
+ bResizeEquation = true;
+ if ( nCountIteration < nMaxIteration-1 )
+ xEquationTarget->remove( xTextShape ); // remove equation
+ nFormulaWidth *= m_aPageReferenceSize.Width / static_cast< double >(aSize.Width);
+ nFormulaWidth -= nCountIteration;
+ if ( nFormulaWidth < 0 )
+ nFormulaWidth = 0;
+ }
+ }
+ if( (aPos.Y + aSize.Height) > m_aPageReferenceSize.Height )
+ aPos.Y = m_aPageReferenceSize.Height - aSize.Height;
+ if( aPos.Y < 0 )
+ aPos.Y = 0;
+ if ( !bResizeEquation || nCountIteration == nMaxIteration-1 )
+ xTextShape->setPosition(aPos); // if equation was not removed
+ }
+ }
+}
+
+void VSeriesPlotter::setTimeResolutionOnXAxis( tools::Long TimeResolution, const Date& rNullDate )
+{
+ m_nTimeResolution = TimeResolution;
+ m_aNullDate = rNullDate;
+}
+
+// MinimumAndMaximumSupplier
+tools::Long VSeriesPlotter::calculateTimeResolutionOnXAxis()
+{
+ tools::Long nRet = css::chart::TimeUnit::YEAR;
+ if (!m_pExplicitCategoriesProvider)
+ return nRet;
+
+ const std::vector<double>& rDateCategories = m_pExplicitCategoriesProvider->getDateCategories();
+ if (rDateCategories.empty())
+ return nRet;
+
+ std::vector<double>::const_iterator aIt = rDateCategories.begin(), aEnd = rDateCategories.end();
+
+ aIt = std::find_if(aIt, aEnd, [](const double& rDateCategory) { return !std::isnan(rDateCategory); });
+ if (aIt == aEnd)
+ return nRet;
+
+ Date aNullDate(30,12,1899);
+ if (m_apNumberFormatterWrapper)
+ aNullDate = m_apNumberFormatterWrapper->getNullDate();
+
+ Date aPrevious(aNullDate); aPrevious.AddDays(rtl::math::approxFloor(*aIt));
+ ++aIt;
+ for(;aIt!=aEnd;++aIt)
+ {
+ if (std::isnan(*aIt))
+ continue;
+
+ Date aCurrent(aNullDate); aCurrent.AddDays(rtl::math::approxFloor(*aIt));
+ if( nRet == css::chart::TimeUnit::YEAR )
+ {
+ if( DateHelper::IsInSameYear( aPrevious, aCurrent ) )
+ nRet = css::chart::TimeUnit::MONTH;
+ }
+ if( nRet == css::chart::TimeUnit::MONTH )
+ {
+ if( DateHelper::IsInSameMonth( aPrevious, aCurrent ) )
+ nRet = css::chart::TimeUnit::DAY;
+ }
+ if( nRet == css::chart::TimeUnit::DAY )
+ break;
+ aPrevious=aCurrent;
+ }
+
+ return nRet;
+}
+double VSeriesPlotter::getMinimumX()
+{
+ double fMinimum, fMaximum;
+ getMinimumAndMaximumX( fMinimum, fMaximum );
+ return fMinimum;
+}
+double VSeriesPlotter::getMaximumX()
+{
+ double fMinimum, fMaximum;
+ getMinimumAndMaximumX( fMinimum, fMaximum );
+ return fMaximum;
+}
+
+double VSeriesPlotter::getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
+{
+ if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
+ {
+ double fMinY, fMaxY;
+ getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
+ return fMinY;
+ }
+
+ double fMinimum = std::numeric_limits<double>::infinity();
+ double fMaximum = -std::numeric_limits<double>::infinity();
+ for(std::vector<VDataSeriesGroup> & rXSlots : m_aZSlots)
+ {
+ for(VDataSeriesGroup & rXSlot : rXSlots)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ rXSlot.calculateYMinAndMaxForCategoryRange(
+ static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
+ , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
+ , isSeparateStackingForDifferentSigns( 1 )
+ , fLocalMinimum, fLocalMaximum, nAxisIndex );
+ if(fMaximum<fLocalMaximum)
+ fMaximum=fLocalMaximum;
+ if(fMinimum>fLocalMinimum)
+ fMinimum=fLocalMinimum;
+ }
+ }
+ if(std::isinf(fMinimum))
+ return std::numeric_limits<double>::quiet_NaN();
+ return fMinimum;
+}
+
+double VSeriesPlotter::getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex )
+{
+ if( !m_bCategoryXAxis || ( m_pExplicitCategoriesProvider && m_pExplicitCategoriesProvider->isDateAxis() ) )
+ {
+ double fMinY, fMaxY;
+ getMinimumAndMaximumYInContinuousXRange( fMinY, fMaxY, fMinimumX, fMaximumX, nAxisIndex );
+ return fMaxY;
+ }
+
+ double fMinimum = std::numeric_limits<double>::infinity();
+ double fMaximum = -std::numeric_limits<double>::infinity();
+ for( std::vector< VDataSeriesGroup > & rXSlots : m_aZSlots)
+ {
+ for(VDataSeriesGroup & rXSlot : rXSlots)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ rXSlot.calculateYMinAndMaxForCategoryRange(
+ static_cast<sal_Int32>(fMinimumX-1.0) //first category (index 0) matches with real number 1.0
+ , static_cast<sal_Int32>(fMaximumX-1.0) //first category (index 0) matches with real number 1.0
+ , isSeparateStackingForDifferentSigns( 1 )
+ , fLocalMinimum, fLocalMaximum, nAxisIndex );
+ if(fMaximum<fLocalMaximum)
+ fMaximum=fLocalMaximum;
+ if(fMinimum>fLocalMinimum)
+ fMinimum=fLocalMinimum;
+ }
+ }
+ if(std::isinf(fMaximum))
+ return std::numeric_limits<double>::quiet_NaN();
+ return fMaximum;
+}
+
+double VSeriesPlotter::getMinimumZ()
+{
+ //this is the default for all charts without a meaningful z axis
+ return 1.0;
+}
+double VSeriesPlotter::getMaximumZ()
+{
+ if( m_nDimension!=3 || m_aZSlots.empty() )
+ return getMinimumZ()+1;
+ return m_aZSlots.size();
+}
+
+namespace
+{
+ bool lcl_isValueAxis( sal_Int32 nDimensionIndex, bool bCategoryXAxis )
+ {
+ // default implementation: true for Y axes, and for value X axis
+ if( nDimensionIndex == 0 )
+ return !bCategoryXAxis;
+ return nDimensionIndex == 1;
+ }
+}
+
+bool VSeriesPlotter::isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex )
+{
+ return lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
+}
+
+bool VSeriesPlotter::isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex )
+{
+ // do not expand axes in 3D charts
+ return (m_nDimension < 3) && lcl_isValueAxis( nDimensionIndex, m_bCategoryXAxis );
+}
+
+bool VSeriesPlotter::isExpandWideValuesToZero( sal_Int32 nDimensionIndex )
+{
+ // default implementation: only for Y axis
+ return nDimensionIndex == 1;
+}
+
+bool VSeriesPlotter::isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex )
+{
+ // default implementation: only for Y axis
+ return nDimensionIndex == 1;
+}
+
+bool VSeriesPlotter::isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex )
+{
+ // default implementation: only for Y axis
+ return nDimensionIndex == 1;
+}
+
+void VSeriesPlotter::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
+{
+ rfMinimum = std::numeric_limits<double>::infinity();
+ rfMaximum = -std::numeric_limits<double>::infinity();
+
+ for (auto const& ZSlot : m_aZSlots)
+ {
+ for (auto const& XSlot : ZSlot)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ XSlot.getMinimumAndMaximumX( fLocalMinimum, fLocalMaximum );
+ if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinimum )
+ rfMinimum = fLocalMinimum;
+ if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaximum )
+ rfMaximum = fLocalMaximum;
+ }
+ }
+ if(std::isinf(rfMinimum))
+ rfMinimum = std::numeric_limits<double>::quiet_NaN();
+ if(std::isinf(rfMaximum))
+ rfMaximum = std::numeric_limits<double>::quiet_NaN();
+}
+
+void VSeriesPlotter::getMinimumAndMaximumYInContinuousXRange( double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
+{
+ rfMinY = std::numeric_limits<double>::infinity();
+ rfMaxY = -std::numeric_limits<double>::infinity();
+
+ for (auto const& ZSlot : m_aZSlots)
+ {
+ for (auto const& XSlot : ZSlot)
+ {
+ double fLocalMinimum, fLocalMaximum;
+ XSlot.getMinimumAndMaximumYInContinuousXRange( fLocalMinimum, fLocalMaximum, fMinX, fMaxX, nAxisIndex );
+ if( !std::isnan(fLocalMinimum) && fLocalMinimum< rfMinY )
+ rfMinY = fLocalMinimum;
+ if( !std::isnan(fLocalMaximum) && fLocalMaximum> rfMaxY )
+ rfMaxY = fLocalMaximum;
+ }
+ }
+ if(std::isinf(rfMinY))
+ rfMinY = std::numeric_limits<double>::quiet_NaN();
+ if(std::isinf(rfMaxY))
+ rfMaxY = std::numeric_limits<double>::quiet_NaN();
+}
+
+sal_Int32 VSeriesPlotter::getPointCount() const
+{
+ sal_Int32 nRet = 0;
+
+ for (auto const& ZSlot : m_aZSlots)
+ {
+ for (auto const& XSlot : ZSlot)
+ {
+ sal_Int32 nPointCount = XSlot.getPointCount();
+ if( nPointCount>nRet )
+ nRet = nPointCount;
+ }
+ }
+ return nRet;
+}
+
+void VSeriesPlotter::setNumberFormatsSupplier(
+ const uno::Reference< util::XNumberFormatsSupplier > & xNumFmtSupplier )
+{
+ m_apNumberFormatterWrapper.reset( new NumberFormatterWrapper( xNumFmtSupplier ));
+}
+
+void VSeriesPlotter::setColorScheme( const uno::Reference< XColorScheme >& xColorScheme )
+{
+ m_xColorScheme = xColorScheme;
+}
+
+void VSeriesPlotter::setExplicitCategoriesProvider( ExplicitCategoriesProvider* pExplicitCategoriesProvider )
+{
+ m_pExplicitCategoriesProvider = pExplicitCategoriesProvider;
+}
+
+sal_Int32 VDataSeriesGroup::getPointCount() const
+{
+ if(!m_bMaxPointCountDirty)
+ return m_nMaxPointCount;
+
+ sal_Int32 nRet = 0;
+
+ for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
+ {
+ sal_Int32 nPointCount = pSeries->getTotalPointCount();
+ if( nPointCount>nRet )
+ nRet = nPointCount;
+ }
+ m_nMaxPointCount=nRet;
+ m_aListOfCachedYValues.clear();
+ m_aListOfCachedYValues.resize(m_nMaxPointCount);
+ m_bMaxPointCountDirty=false;
+ return nRet;
+}
+
+sal_Int32 VDataSeriesGroup::getAttachedAxisIndexForFirstSeries() const
+{
+ sal_Int32 nRet = 0;
+
+ if (!m_aSeriesVector.empty())
+ nRet = m_aSeriesVector[0]->getAttachedAxisIndex();
+
+ return nRet;
+}
+
+void VDataSeriesGroup::getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const
+{
+
+ rfMinimum = std::numeric_limits<double>::infinity();
+ rfMaximum = -std::numeric_limits<double>::infinity();
+
+ for (std::unique_ptr<VDataSeries> const & pSeries : m_aSeriesVector)
+ {
+ sal_Int32 nPointCount = pSeries->getTotalPointCount();
+ for(sal_Int32 nN=0;nN<nPointCount;nN++)
+ {
+ double fX = pSeries->getXValue( nN );
+ if( std::isnan(fX) )
+ continue;
+ if(rfMaximum<fX)
+ rfMaximum=fX;
+ if(rfMinimum>fX)
+ rfMinimum=fX;
+ }
+ }
+ if(std::isinf(rfMinimum))
+ rfMinimum = std::numeric_limits<double>::quiet_NaN();
+ if(std::isinf(rfMaximum))
+ rfMaximum = std::numeric_limits<double>::quiet_NaN();
+}
+
+namespace {
+
+/**
+ * Keep track of minimum and maximum Y values for one or more data series.
+ * When multiple data series exist, that indicates that the data series are
+ * stacked.
+ *
+ * <p>For each X value, we calculate separate Y value ranges for each data
+ * series in the first pass. In the second pass, we calculate the minimum Y
+ * value by taking the absolute minimum value of all data series, whereas
+ * the maximum Y value is the sum of all the series maximum Y values.</p>
+ *
+ * <p>Once that's done for all X values, the final min / max Y values get
+ * calculated by taking the absolute min / max Y values across all the X
+ * values.</p>
+ */
+class PerXMinMaxCalculator
+{
+ typedef std::pair<double, double> MinMaxType;
+ typedef std::map<size_t, MinMaxType> SeriesMinMaxType;
+ typedef std::map<double, SeriesMinMaxType> GroupMinMaxType;
+ typedef std::unordered_map<double, MinMaxType> TotalStoreType;
+ GroupMinMaxType m_SeriesGroup;
+ size_t mnCurSeries;
+
+public:
+ PerXMinMaxCalculator() : mnCurSeries(0) {}
+
+ void nextSeries() { ++mnCurSeries; }
+
+ void setValue(double fX, double fY)
+ {
+ SeriesMinMaxType* pStore = getByXValue(fX); // get storage for given X value.
+ if (!pStore)
+ // This shouldn't happen!
+ return;
+
+ SeriesMinMaxType::iterator it = pStore->lower_bound(mnCurSeries);
+ if (it != pStore->end() && !pStore->key_comp()(mnCurSeries, it->first))
+ {
+ MinMaxType& r = it->second;
+ // A min-max pair already exists for this series. Update it.
+ if (fY < r.first)
+ r.first = fY;
+ if (r.second < fY)
+ r.second = fY;
+ }
+ else
+ {
+ // No existing pair. Insert a new one.
+ pStore->insert(
+ it, SeriesMinMaxType::value_type(
+ mnCurSeries, MinMaxType(fY,fY)));
+ }
+ }
+
+ void getTotalRange(double& rfMin, double& rfMax) const
+ {
+ TotalStoreType aStore;
+ getTotalStore(aStore);
+
+ if (aStore.empty())
+ {
+ rfMin = std::numeric_limits<double>::quiet_NaN();
+ rfMax = std::numeric_limits<double>::quiet_NaN();
+ return;
+ }
+
+ TotalStoreType::const_iterator it = aStore.begin(), itEnd = aStore.end();
+ rfMin = it->second.first;
+ rfMax = it->second.second;
+ for (++it; it != itEnd; ++it)
+ {
+ if (rfMin > it->second.first)
+ rfMin = it->second.first;
+ if (rfMax < it->second.second)
+ rfMax = it->second.second;
+ }
+ }
+
+private:
+ /**
+ * Parse all data and reduce them into a set of global Y value ranges per
+ * X value.
+ */
+ void getTotalStore(TotalStoreType& rStore) const
+ {
+ TotalStoreType aStore;
+ for (auto const& it : m_SeriesGroup)
+ {
+ double fX = it.first;
+
+ const SeriesMinMaxType& rSeries = it.second;
+ for (auto const& series : rSeries)
+ {
+ double fYMin = series.second.first, fYMax = series.second.second;
+ TotalStoreType::iterator itr = aStore.find(fX);
+ if (itr == aStore.end())
+ // New min-max pair for give X value.
+ aStore.emplace(fX, std::pair<double,double>(fYMin,fYMax));
+ else
+ {
+ MinMaxType& r = itr->second;
+ if (fYMin < r.first)
+ r.first = fYMin; // min y-value
+
+ r.second += fYMax; // accumulative max y-value.
+ }
+ }
+ }
+ rStore.swap(aStore);
+ }
+
+ SeriesMinMaxType* getByXValue(double fX)
+ {
+ GroupMinMaxType::iterator it = m_SeriesGroup.find(fX);
+ if (it == m_SeriesGroup.end())
+ {
+ std::pair<GroupMinMaxType::iterator,bool> r =
+ m_SeriesGroup.insert(std::make_pair(fX, SeriesMinMaxType{}));
+
+ if (!r.second)
+ // insertion failed.
+ return nullptr;
+
+ it = r.first;
+ }
+
+ return &it->second;
+ }
+};
+
+}
+
+void VDataSeriesGroup::getMinimumAndMaximumYInContinuousXRange(
+ double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const
+{
+ rfMinY = std::numeric_limits<double>::quiet_NaN();
+ rfMaxY = std::numeric_limits<double>::quiet_NaN();
+
+ if (m_aSeriesVector.empty())
+ // No data series. Bail out.
+ return;
+
+ PerXMinMaxCalculator aRangeCalc;
+ for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector)
+ {
+ if (!pSeries)
+ continue;
+
+ for (sal_Int32 i = 0, n = pSeries->getTotalPointCount(); i < n; ++i)
+ {
+ if (nAxisIndex != pSeries->getAttachedAxisIndex())
+ continue;
+
+ double fX = pSeries->getXValue(i);
+ if (std::isnan(fX))
+ continue;
+
+ if (fX < fMinX || fX > fMaxX)
+ // Outside specified X range. Skip it.
+ continue;
+
+ double fY = pSeries->getYValue(i);
+ if (std::isnan(fY))
+ continue;
+
+ aRangeCalc.setValue(fX, fY);
+ }
+ aRangeCalc.nextSeries();
+ }
+
+ aRangeCalc.getTotalRange(rfMinY, rfMaxY);
+}
+
+void VDataSeriesGroup::calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex
+ , bool bSeparateStackingForDifferentSigns
+ , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex ) const
+{
+ assert(nCategoryIndex >= 0);
+ assert(nCategoryIndex < getPointCount());
+
+ rfMinimumY = std::numeric_limits<double>::infinity();
+ rfMaximumY = -std::numeric_limits<double>::infinity();
+
+ if(m_aSeriesVector.empty())
+ return;
+
+ CachedYValues aCachedYValues = m_aListOfCachedYValues[nCategoryIndex][nAxisIndex];
+ if( !aCachedYValues.m_bValuesDirty )
+ {
+ //return cached values
+ rfMinimumY = aCachedYValues.m_fMinimumY;
+ rfMaximumY = aCachedYValues.m_fMaximumY;
+ return;
+ }
+
+ double fTotalSum = std::numeric_limits<double>::quiet_NaN();
+ double fPositiveSum = std::numeric_limits<double>::quiet_NaN();
+ double fNegativeSum = std::numeric_limits<double>::quiet_NaN();
+ double fFirstPositiveY = std::numeric_limits<double>::quiet_NaN();
+ double fFirstNegativeY = std::numeric_limits<double>::quiet_NaN();
+
+ if( bSeparateStackingForDifferentSigns )
+ {
+ for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
+ {
+ if( nAxisIndex != pSeries->getAttachedAxisIndex() )
+ continue;
+
+ double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
+ double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
+
+ if( fValueMaxY >= 0 )
+ {
+ if( std::isnan( fPositiveSum ) )
+ fPositiveSum = fFirstPositiveY = fValueMaxY;
+ else
+ fPositiveSum += fValueMaxY;
+ }
+ if( fValueMinY < 0 )
+ {
+ if(std::isnan( fNegativeSum ))
+ fNegativeSum = fFirstNegativeY = fValueMinY;
+ else
+ fNegativeSum += fValueMinY;
+ }
+ }
+ rfMinimumY = std::isnan( fNegativeSum ) ? fFirstPositiveY : fNegativeSum;
+ rfMaximumY = std::isnan( fPositiveSum ) ? fFirstNegativeY : fPositiveSum;
+ }
+ else
+ {
+ for (const std::unique_ptr<VDataSeries> & pSeries: m_aSeriesVector)
+ {
+ if( nAxisIndex != pSeries->getAttachedAxisIndex() )
+ continue;
+
+ double fValueMinY = pSeries->getMinimumofAllDifferentYValues( nCategoryIndex );
+ double fValueMaxY = pSeries->getMaximumofAllDifferentYValues( nCategoryIndex );
+
+ if( std::isnan( fTotalSum ) )
+ {
+ rfMinimumY = fValueMinY;
+ rfMaximumY = fTotalSum = fValueMaxY;
+ }
+ else
+ {
+ fTotalSum += fValueMaxY;
+ if( rfMinimumY > fTotalSum )
+ rfMinimumY = fTotalSum;
+ if( rfMaximumY < fTotalSum )
+ rfMaximumY = fTotalSum;
+ }
+ }
+ }
+
+ aCachedYValues.m_fMinimumY = rfMinimumY;
+ aCachedYValues.m_fMaximumY = rfMaximumY;
+ aCachedYValues.m_bValuesDirty = false;
+ m_aListOfCachedYValues[nCategoryIndex][nAxisIndex]=aCachedYValues;
+}
+
+void VDataSeriesGroup::calculateYMinAndMaxForCategoryRange(
+ sal_Int32 nStartCategoryIndex, sal_Int32 nEndCategoryIndex
+ , bool bSeparateStackingForDifferentSigns
+ , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex )
+{
+ //@todo maybe cache these values
+ rfMinimumY = std::numeric_limits<double>::infinity();
+ rfMaximumY = -std::numeric_limits<double>::infinity();
+
+ //iterate through the given categories
+ if(nStartCategoryIndex<0)
+ nStartCategoryIndex=0;
+ const sal_Int32 nPointCount = getPointCount();//necessary to create m_aListOfCachedYValues
+ if(nPointCount <= 0)
+ return;
+ if (nEndCategoryIndex >= nPointCount)
+ nEndCategoryIndex = nPointCount - 1;
+ if(nEndCategoryIndex<0)
+ nEndCategoryIndex=0;
+ for( sal_Int32 nCatIndex = nStartCategoryIndex; nCatIndex <= nEndCategoryIndex; nCatIndex++ )
+ {
+ double fMinimumY = std::numeric_limits<double>::quiet_NaN();
+ double fMaximumY = std::numeric_limits<double>::quiet_NaN();
+
+ calculateYMinAndMaxForCategory( nCatIndex
+ , bSeparateStackingForDifferentSigns, fMinimumY, fMaximumY, nAxisIndex );
+
+ if(rfMinimumY > fMinimumY)
+ rfMinimumY = fMinimumY;
+ if(rfMaximumY < fMaximumY)
+ rfMaximumY = fMaximumY;
+ }
+}
+
+double VSeriesPlotter::getTransformedDepth() const
+{
+ double MinZ = m_pMainPosHelper->getLogicMinZ();
+ double MaxZ = m_pMainPosHelper->getLogicMaxZ();
+ m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MinZ );
+ m_pMainPosHelper->doLogicScaling( nullptr, nullptr, &MaxZ );
+ return FIXED_SIZE_FOR_3D_CHART_VOLUME/(MaxZ-MinZ);
+}
+
+void VSeriesPlotter::addSecondaryValueScale( const ExplicitScaleData& rScale, sal_Int32 nAxisIndex )
+{
+ if( nAxisIndex<1 )
+ return;
+
+ m_aSecondaryValueScales[nAxisIndex]=rScale;
+}
+
+PlottingPositionHelper& VSeriesPlotter::getPlottingPositionHelper( sal_Int32 nAxisIndex ) const
+{
+ PlottingPositionHelper* pRet = nullptr;
+ if(nAxisIndex>0)
+ {
+ tSecondaryPosHelperMap::const_iterator aPosIt = m_aSecondaryPosHelperMap.find( nAxisIndex );
+ if( aPosIt != m_aSecondaryPosHelperMap.end() )
+ {
+ pRet = aPosIt->second.get();
+ }
+ else if (m_pPosHelper)
+ {
+ tSecondaryValueScales::const_iterator aScaleIt = m_aSecondaryValueScales.find( nAxisIndex );
+ if( aScaleIt != m_aSecondaryValueScales.end() )
+ {
+ m_aSecondaryPosHelperMap[nAxisIndex] = m_pPosHelper->createSecondaryPosHelper( aScaleIt->second );
+ pRet = m_aSecondaryPosHelperMap[nAxisIndex].get();
+ }
+ }
+ }
+ if( !pRet )
+ pRet = m_pMainPosHelper;
+ pRet->setTimeResolution( m_nTimeResolution, m_aNullDate );
+ return *pRet;
+}
+
+void VSeriesPlotter::rearrangeLabelToAvoidOverlapIfRequested( const awt::Size& /*rPageSize*/ )
+{
+}
+
+VDataSeries* VSeriesPlotter::getFirstSeries() const
+{
+ for (std::vector<VDataSeriesGroup> const & rGroup : m_aZSlots)
+ {
+ if (!rGroup.empty())
+ {
+ if (!rGroup[0].m_aSeriesVector.empty())
+ {
+ VDataSeries* pSeries = rGroup[0].m_aSeriesVector[0].get();
+ if (pSeries)
+ return pSeries;
+ }
+ }
+ }
+ return nullptr;
+}
+
+OUString VSeriesPlotter::getCategoryName( sal_Int32 nPointIndex ) const
+{
+ if (m_pExplicitCategoriesProvider)
+ {
+ Sequence< OUString > aCategories(m_pExplicitCategoriesProvider->getSimpleCategories());
+ if (nPointIndex >= 0 && nPointIndex < aCategories.getLength())
+ {
+ return aCategories[nPointIndex];
+ }
+ }
+ return OUString();
+}
+
+std::vector<VDataSeries const*> VSeriesPlotter::getAllSeries() const
+{
+ std::vector<VDataSeries const*> aAllSeries;
+ for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
+ {
+ for(VDataSeriesGroup const & rGroup : rXSlot)
+ {
+ for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
+ aAllSeries.push_back(p.get());
+ }
+ }
+ return aAllSeries;
+}
+
+std::vector<VDataSeries*> VSeriesPlotter::getAllSeries()
+{
+ std::vector<VDataSeries*> aAllSeries;
+ for (std::vector<VDataSeriesGroup> const & rXSlot : m_aZSlots)
+ {
+ for(VDataSeriesGroup const & rGroup : rXSlot)
+ {
+ for (std::unique_ptr<VDataSeries> const & p : rGroup.m_aSeriesVector)
+ aAllSeries.push_back(p.get());
+ }
+ }
+ return aAllSeries;
+}
+
+uno::Sequence<OUString> VSeriesPlotter::getSeriesNames() const
+{
+ std::vector<OUString> aRetVector;
+
+ OUString aRole;
+ if (m_xChartTypeModel.is())
+ aRole = m_xChartTypeModel->getRoleOfSequenceForSeriesLabel();
+
+ for (auto const& rGroup : m_aZSlots)
+ {
+ if (!rGroup.empty())
+ {
+ VDataSeriesGroup const & rSeriesGroup(rGroup[0]);
+ if (!rSeriesGroup.m_aSeriesVector.empty())
+ {
+ VDataSeries const * pSeries = rSeriesGroup.m_aSeriesVector[0].get();
+ rtl::Reference< DataSeries > xSeries( pSeries ? pSeries->getModel() : nullptr );
+ if( xSeries.is() )
+ {
+ OUString aSeriesName( DataSeriesHelper::getDataSeriesLabel( xSeries, aRole ) );
+ aRetVector.push_back( aSeriesName );
+ }
+ }
+ }
+ }
+ return comphelper::containerToSequence( aRetVector );
+}
+
+void VSeriesPlotter::setPageReferenceSize( const css::awt::Size & rPageRefSize )
+{
+ m_aPageReferenceSize = rPageRefSize;
+
+ // set reference size also at all data series
+
+ for (auto const & outer : m_aZSlots)
+ for (VDataSeriesGroup const & rGroup : outer)
+ {
+ for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
+ {
+ pSeries->setPageReferenceSize(m_aPageReferenceSize);
+ }
+ }
+}
+
+//better performance for big data
+void VSeriesPlotter::setCoordinateSystemResolution( const Sequence< sal_Int32 >& rCoordinateSystemResolution )
+{
+ m_aCoordinateSystemResolution = rCoordinateSystemResolution;
+}
+
+bool VSeriesPlotter::WantToPlotInFrontOfAxisLine()
+{
+ return ChartTypeHelper::isSeriesInFrontOfAxisLine( m_xChartTypeModel );
+}
+
+bool VSeriesPlotter::shouldSnapRectToUsedArea()
+{
+ return m_nDimension != 3;
+}
+
+std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntries(
+ const awt::Size& rEntryKeyAspectRatio
+ , LegendPosition eLegendPosition
+ , const Reference< beans::XPropertySet >& xTextProperties
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const Reference< uno::XComponentContext >& xContext
+ , ChartModel& rModel
+ )
+{
+ std::vector< ViewLegendEntry > aResult;
+
+ if( xTarget.is() )
+ {
+ rtl::Reference< Diagram > xDiagram = rModel.getFirstChartDiagram();
+ rtl::Reference< BaseCoordinateSystem > xCooSys(xDiagram->getBaseCoordinateSystems()[0]);
+ bool bSwapXAndY = false;
+
+ try
+ {
+ xCooSys->getPropertyValue( "SwapXAndYAxis" ) >>= bSwapXAndY;
+ }
+ catch( const uno::Exception& )
+ {
+ }
+
+ //iterate through all series
+ bool bBreak = false;
+ bool bFirstSeries = true;
+
+
+ for (std::vector<VDataSeriesGroup> const & rGroupVector : m_aZSlots)
+ {
+ for (VDataSeriesGroup const & rGroup : rGroupVector)
+ {
+ for (std::unique_ptr<VDataSeries> const & pSeries : rGroup.m_aSeriesVector)
+ {
+ if (!pSeries)
+ continue;
+
+ if (!pSeries->getPropertiesOfSeries()->getPropertyValue("ShowLegendEntry").get<sal_Bool>())
+ {
+ continue;
+ }
+
+ std::vector<ViewLegendEntry> aSeriesEntries(
+ createLegendEntriesForSeries(
+ rEntryKeyAspectRatio, *pSeries, xTextProperties,
+ xTarget, xContext));
+
+ //add series entries to the result now
+
+ // use only the first series if VaryColorsByPoint is set for the first series
+ if (bFirstSeries && pSeries->isVaryColorsByPoint())
+ bBreak = true;
+ bFirstSeries = false;
+
+ // add entries reverse if chart is stacked in y-direction and the legend position is right or left.
+ // If the legend is top or bottom and we have a stacked bar-chart the normal order
+ // is the correct one, unless the chart type is horizontal bar-chart.
+ bool bReverse = false;
+ if ( bSwapXAndY )
+ {
+ StackingDirection eStackingDirection( pSeries->getStackingDirection() );
+ bReverse = ( eStackingDirection != StackingDirection_Y_STACKING );
+ }
+ else if ( eLegendPosition == LegendPosition_LINE_START || eLegendPosition == LegendPosition_LINE_END )
+ {
+ StackingDirection eStackingDirection( pSeries->getStackingDirection() );
+ bReverse = ( eStackingDirection == StackingDirection_Y_STACKING );
+ }
+
+ if (bReverse)
+ aResult.insert( aResult.begin(), aSeriesEntries.begin(), aSeriesEntries.end() );
+ else
+ aResult.insert( aResult.end(), aSeriesEntries.begin(), aSeriesEntries.end() );
+ }
+ if (bBreak)
+ return aResult;
+ }
+ }
+ }
+
+ return aResult;
+}
+
+namespace
+{
+bool lcl_HasVisibleLine( const uno::Reference< beans::XPropertySet >& xProps, bool& rbHasDashedLine )
+{
+ bool bHasVisibleLine = false;
+ rbHasDashedLine = false;
+ drawing::LineStyle aLineStyle = drawing::LineStyle_NONE;
+ if( xProps.is() && ( xProps->getPropertyValue( "LineStyle") >>= aLineStyle ) )
+ {
+ if( aLineStyle != drawing::LineStyle_NONE )
+ bHasVisibleLine = true;
+ if( aLineStyle == drawing::LineStyle_DASH )
+ rbHasDashedLine = true;
+ }
+ return bHasVisibleLine;
+}
+
+bool lcl_HasRegressionCurves( const VDataSeries& rSeries, bool& rbHasDashedLine )
+{
+ bool bHasRegressionCurves = false;
+ rtl::Reference< DataSeries > xRegrCont( rSeries.getModel() );
+ for( const rtl::Reference< RegressionCurveModel > & rCurve : xRegrCont->getRegressionCurves2() )
+ {
+ bHasRegressionCurves = true;
+ lcl_HasVisibleLine( rCurve, rbHasDashedLine );
+ }
+ return bHasRegressionCurves;
+}
+}
+LegendSymbolStyle VSeriesPlotter::getLegendSymbolStyle()
+{
+ return LegendSymbolStyle::Box;
+}
+
+awt::Size VSeriesPlotter::getPreferredLegendKeyAspectRatio()
+{
+ awt::Size aRet(1000,1000);
+ if( m_nDimension==3 )
+ return aRet;
+
+ bool bSeriesAllowsLines = (getLegendSymbolStyle() == LegendSymbolStyle::Line);
+ bool bHasLines = false;
+ bool bHasDashedLines = false;
+ //iterate through all series
+ for (VDataSeries* pSeries : getAllSeries())
+ {
+ if( bSeriesAllowsLines )
+ {
+ bool bCurrentDashed = false;
+ if( lcl_HasVisibleLine( pSeries->getPropertiesOfSeries(), bCurrentDashed ) )
+ {
+ bHasLines = true;
+ if( bCurrentDashed )
+ {
+ bHasDashedLines = true;
+ break;
+ }
+ }
+ }
+ bool bRegressionHasDashedLines=false;
+ if( lcl_HasRegressionCurves( *pSeries, bRegressionHasDashedLines ) )
+ {
+ bHasLines = true;
+ if( bRegressionHasDashedLines )
+ {
+ bHasDashedLines = true;
+ break;
+ }
+ }
+ }
+ if( bHasLines )
+ {
+ if( bHasDashedLines )
+ aRet = awt::Size(1600,-1);
+ else
+ aRet = awt::Size(800,-1);
+ }
+ return aRet;
+}
+
+uno::Any VSeriesPlotter::getExplicitSymbol( const VDataSeries& /*rSeries*/, sal_Int32 /*nPointIndex*/ )
+{
+ return uno::Any();
+}
+
+rtl::Reference<SvxShapeGroup> VSeriesPlotter::createLegendSymbolForSeries(
+ const awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+
+ LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
+ uno::Any aExplicitSymbol( getExplicitSymbol( rSeries, -1 ) );
+
+ VLegendSymbolFactory::PropertyType ePropType =
+ VLegendSymbolFactory::PropertyType::FilledSeries;
+
+ // todo: maybe the property-style does not solely depend on the
+ // legend-symbol type
+ switch( eLegendSymbolStyle )
+ {
+ case LegendSymbolStyle::Line:
+ ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
+ break;
+ default:
+ break;
+ }
+ rtl::Reference<SvxShapeGroup> xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
+ xTarget, eLegendSymbolStyle,
+ rSeries.getPropertiesOfSeries(), ePropType, aExplicitSymbol );
+
+ return xShape;
+}
+
+rtl::Reference< SvxShapeGroup > VSeriesPlotter::createLegendSymbolForPoint(
+ const awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+
+ LegendSymbolStyle eLegendSymbolStyle = getLegendSymbolStyle();
+ uno::Any aExplicitSymbol( getExplicitSymbol(rSeries,nPointIndex) );
+
+ VLegendSymbolFactory::PropertyType ePropType =
+ VLegendSymbolFactory::PropertyType::FilledSeries;
+
+ // todo: maybe the property-style does not solely depend on the
+ // legend-symbol type
+ switch( eLegendSymbolStyle )
+ {
+ case LegendSymbolStyle::Line:
+ ePropType = VLegendSymbolFactory::PropertyType::LineSeries;
+ break;
+ default:
+ break;
+ }
+
+ // the default properties for the data point are the data series properties.
+ // If a data point has own attributes overwrite them
+ Reference< beans::XPropertySet > xSeriesProps( rSeries.getPropertiesOfSeries() );
+ Reference< beans::XPropertySet > xPointSet( xSeriesProps );
+ if( rSeries.isAttributedDataPoint( nPointIndex ) )
+ xPointSet.set( rSeries.getPropertiesOfPoint( nPointIndex ));
+
+ // if a data point has no own color use a color from the diagram's color scheme
+ if( ! rSeries.hasPointOwnColor( nPointIndex ))
+ {
+ Reference< util::XCloneable > xCloneable( xPointSet,uno::UNO_QUERY );
+ if( xCloneable.is() && m_xColorScheme.is() )
+ {
+ xPointSet.set( xCloneable->createClone(), uno::UNO_QUERY );
+ Reference< container::XChild > xChild( xPointSet, uno::UNO_QUERY );
+ if( xChild.is())
+ xChild->setParent( xSeriesProps );
+
+ OSL_ASSERT( xPointSet.is());
+ xPointSet->setPropertyValue(
+ "Color", uno::Any( m_xColorScheme->getColorByIndex( nPointIndex )));
+ }
+ }
+
+ rtl::Reference< SvxShapeGroup > xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
+ xTarget, eLegendSymbolStyle, xPointSet, ePropType, aExplicitSymbol );
+
+ return xShape;
+}
+
+std::vector< ViewLegendEntry > VSeriesPlotter::createLegendEntriesForSeries(
+ const awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , const Reference< beans::XPropertySet >& xTextProperties
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const Reference< uno::XComponentContext >& xContext
+ )
+{
+ std::vector< ViewLegendEntry > aResult;
+
+ if( ! ( xTarget.is() && xContext.is() ) )
+ return aResult;
+
+ try
+ {
+ ViewLegendEntry aEntry;
+ OUString aLabelText;
+ bool bVaryColorsByPoint = rSeries.isVaryColorsByPoint();
+ bool bIsPie = m_xChartTypeModel->getChartType().equalsIgnoreAsciiCase(
+ CHART2_SERVICE_NAME_CHARTTYPE_PIE);
+ try
+ {
+ if (bIsPie)
+ {
+ bool bDonut = false;
+ if ((m_xChartTypeModel->getPropertyValue("UseRings") >>= bDonut) && bDonut)
+ bIsPie = false;
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ }
+
+ if (bVaryColorsByPoint || bIsPie)
+ {
+ Sequence< OUString > aCategoryNames;
+ if( m_pExplicitCategoriesProvider )
+ aCategoryNames = m_pExplicitCategoriesProvider->getSimpleCategories();
+ Sequence<sal_Int32> deletedLegendEntries;
+ try
+ {
+ rSeries.getPropertiesOfSeries()->getPropertyValue("DeletedLegendEntries") >>= deletedLegendEntries;
+ }
+ catch (const uno::Exception&)
+ {
+ }
+ for( sal_Int32 nIdx=0; nIdx<aCategoryNames.getLength(); ++nIdx )
+ {
+ bool deletedLegendEntry = false;
+ for (const auto& deletedLegendEntryIdx : std::as_const(deletedLegendEntries))
+ {
+ if (nIdx == deletedLegendEntryIdx)
+ {
+ deletedLegendEntry = true;
+ break;
+ }
+ }
+ if (deletedLegendEntry)
+ continue;
+
+ // symbol
+ rtl::Reference< SvxShapeGroup > xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
+
+ // create the symbol
+ rtl::Reference< SvxShapeGroup > xShape = createLegendSymbolForPoint( rEntryKeyAspectRatio,
+ rSeries, nIdx, xSymbolGroup );
+
+ // set CID to symbol for selection
+ if( xShape.is() )
+ {
+ aEntry.xSymbol = xSymbolGroup;
+
+ OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_DATA_POINT, nIdx ) );
+ aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
+ OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
+ ShapeFactory::setShapeName( xShape, aCID );
+ }
+
+ // label
+ aLabelText = aCategoryNames[nIdx];
+ if( xShape.is() || !aLabelText.isEmpty() )
+ {
+ aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
+ aResult.push_back(aEntry);
+ }
+ }
+ }
+ else
+ {
+ // symbol
+ rtl::Reference< SvxShapeGroup > xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
+
+ // create the symbol
+ rtl::Reference<SvxShapeGroup> xShape = createLegendSymbolForSeries(
+ rEntryKeyAspectRatio, rSeries, xSymbolGroup );
+
+ // set CID to symbol for selection
+ if( xShape.is())
+ {
+ aEntry.xSymbol = xSymbolGroup;
+
+ OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
+ OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
+ ShapeFactory::setShapeName( xShape, aCID );
+ }
+
+ // label
+ aLabelText = DataSeriesHelper::getDataSeriesLabel( rSeries.getModel(), m_xChartTypeModel.is() ? m_xChartTypeModel->getRoleOfSequenceForSeriesLabel() : "values-y");
+ aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aLabelText, xTextProperties );
+
+ aResult.push_back(aEntry);
+ }
+
+ // don't show legend entry of regression curve & friends if this type of chart
+ // doesn't support statistics #i63016#, fdo#37197
+ if (!ChartTypeHelper::isSupportingStatisticProperties( m_xChartTypeModel, m_nDimension ))
+ return aResult;
+
+ rtl::Reference< DataSeries > xRegrCont = rSeries.getModel();
+ if( xRegrCont.is())
+ {
+ const std::vector< rtl::Reference< RegressionCurveModel > > & aCurves = xRegrCont->getRegressionCurves2();
+ sal_Int32 i = 0, nCount = aCurves.size();
+ for( i=0; i<nCount; ++i )
+ {
+ //label
+ OUString aResStr( RegressionCurveHelper::getUINameForRegressionCurve( aCurves[i] ) );
+ replaceParamterInString( aResStr, "%SERIESNAME", aLabelText );
+ aEntry.aLabel = FormattedStringHelper::createFormattedStringSequence( xContext, aResStr, xTextProperties );
+
+ // symbol
+ rtl::Reference<SvxShapeGroup> xSymbolGroup(ShapeFactory::createGroup2D( xTarget ));
+
+ // create the symbol
+ rtl::Reference<SvxShapeGroup> xShape = VLegendSymbolFactory::createSymbol( rEntryKeyAspectRatio,
+ xSymbolGroup, LegendSymbolStyle::Line,
+ aCurves[i],
+ VLegendSymbolFactory::PropertyType::Line, uno::Any() );
+
+ // set CID to symbol for selection
+ if( xShape.is())
+ {
+ aEntry.xSymbol = xSymbolGroup;
+
+ bool bAverageLine = RegressionCurveHelper::isMeanValueLine( aCurves[i] );
+ ObjectType eObjectType = bAverageLine ? OBJECTTYPE_DATA_AVERAGE_LINE : OBJECTTYPE_DATA_CURVE;
+ OUString aChildParticle( ObjectIdentifier::createChildParticleWithIndex( eObjectType, i ) );
+ aChildParticle = ObjectIdentifier::addChildParticle( aChildParticle, ObjectIdentifier::createChildParticleWithIndex( OBJECTTYPE_LEGEND_ENTRY, 0 ) );
+ OUString aCID = ObjectIdentifier::createClassifiedIdentifierForParticles( rSeries.getSeriesParticle(), aChildParticle );
+ ShapeFactory::setShapeName( xShape, aCID );
+ }
+
+ aResult.push_back(aEntry);
+ }
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2" );
+ }
+ return aResult;
+}
+
+VSeriesPlotter* VSeriesPlotter::createSeriesPlotter(
+ const rtl::Reference<ChartType>& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bExcludingPositioning )
+{
+ if (!xChartTypeModel.is())
+ return nullptr;
+
+ OUString aChartType = xChartTypeModel->getChartType();
+
+ VSeriesPlotter* pRet=nullptr;
+ if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_COLUMN ) )
+ pRet = new BarChart(xChartTypeModel,nDimensionCount);
+ else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_BAR ) )
+ pRet = new BarChart(xChartTypeModel,nDimensionCount);
+ else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_AREA ) )
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,true);
+ else if( aChartType.equalsIgnoreAsciiCase( CHART2_SERVICE_NAME_CHARTTYPE_LINE ) )
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,true,true);
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_SCATTER) )
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE) )
+ pRet = new BubbleChart(xChartTypeModel,nDimensionCount);
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE) )
+ pRet = new PieChart(xChartTypeModel,nDimensionCount, bExcludingPositioning );
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_NET) )
+ pRet = new NetChart(xChartTypeModel,nDimensionCount,true,std::make_unique<PolarPlottingPositionHelper>());
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET) )
+ pRet = new NetChart(xChartTypeModel,nDimensionCount,false,std::make_unique<PolarPlottingPositionHelper>());
+ else if( aChartType.equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK) )
+ pRet = new CandleStickChart(xChartTypeModel,nDimensionCount);
+ else
+ pRet = new AreaChart(xChartTypeModel,nDimensionCount,false,true);
+ return pRet;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/diagram/VDiagram.cxx b/chart2/source/view/diagram/VDiagram.cxx
new file mode 100644
index 000000000..2ce114676
--- /dev/null
+++ b/chart2/source/view/diagram/VDiagram.cxx
@@ -0,0 +1,703 @@
+/* -*- 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 <ShapeFactory.hxx>
+#include <VDiagram.hxx>
+#include <Diagram.hxx>
+#include <PropertyMapper.hxx>
+#include <ViewDefines.hxx>
+#include <Stripe.hxx>
+#include <ObjectIdentifier.hxx>
+#include <DiagramHelper.hxx>
+#include <ChartType.hxx>
+#include <BaseGFXHelper.hxx>
+#include <ChartTypeHelper.hxx>
+#include <ThreeDHelper.hxx>
+#include <defines.hxx>
+#include <editeng/unoprnms.hxx>
+#include <svx/scene3d.hxx>
+#include <svx/e3dsceneupdater.hxx>
+#include <tools/diagnose_ex.h>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+VDiagram::VDiagram(
+ const rtl::Reference<Diagram> & xDiagram, const drawing::Direction3D& rPreferredAspectRatio,
+ sal_Int32 nDimension )
+ : m_nDimensionCount(nDimension)
+ , m_xDiagram(xDiagram)
+ , m_aPreferredAspectRatio(rPreferredAspectRatio)
+ , m_fXAnglePi(0)
+ , m_fYAnglePi(0)
+ , m_fZAnglePi(0)
+ , m_bRightAngledAxes(false)
+{
+ if( m_nDimensionCount != 3)
+ return;
+
+ ThreeDHelper::getRotationAngleFromDiagram( xDiagram, m_fXAnglePi, m_fYAnglePi, m_fZAnglePi );
+ if( ChartTypeHelper::isSupportingRightAngledAxes(
+ DiagramHelper::getChartTypeByIndex( m_xDiagram, 0 ) ) )
+ {
+ if(xDiagram.is())
+ xDiagram->getPropertyValue("RightAngledAxes") >>= m_bRightAngledAxes;
+ if( m_bRightAngledAxes )
+ {
+ ThreeDHelper::adaptRadAnglesForRightAngledAxes( m_fXAnglePi, m_fYAnglePi );
+ m_fZAnglePi=0.0;
+ }
+ }
+}
+
+VDiagram::~VDiagram()
+{
+}
+
+void VDiagram::init( const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ m_xTarget = xTarget;
+}
+
+void VDiagram::createShapes( const awt::Point& rPos, const awt::Size& rSize )
+{
+ m_aAvailablePosIncludingAxes = rPos;
+ m_aAvailableSizeIncludingAxes = rSize;
+
+ if( m_nDimensionCount == 3 )
+ createShapes_3d();
+ else
+ createShapes_2d();
+}
+
+::basegfx::B2IRectangle VDiagram::adjustPosAndSize( const awt::Point& rPos, const awt::Size& rSize )
+{
+ ::basegfx::B2IRectangle aAllowedRect( BaseGFXHelper::makeRectangle(m_aAvailablePosIncludingAxes,m_aAvailableSizeIncludingAxes) );
+ ::basegfx::B2IRectangle aNewInnerRect( BaseGFXHelper::makeRectangle(rPos,rSize) );
+ aNewInnerRect.intersect( aAllowedRect );
+
+ if( m_nDimensionCount == 3 )
+ aNewInnerRect = adjustPosAndSize_3d( BaseGFXHelper::B2IRectangleToAWTPoint(aNewInnerRect), BaseGFXHelper::B2IRectangleToAWTSize(aNewInnerRect) );
+ else
+ aNewInnerRect = adjustPosAndSize_2d( BaseGFXHelper::B2IRectangleToAWTPoint(aNewInnerRect), BaseGFXHelper::B2IRectangleToAWTSize(aNewInnerRect) );
+
+ return aNewInnerRect;
+}
+
+::basegfx::B2IRectangle VDiagram::adjustPosAndSize_2d( const awt::Point& rPos, const awt::Size& rAvailableSize )
+{
+ m_aCurrentPosWithoutAxes = rPos;
+ m_aCurrentSizeWithoutAxes = rAvailableSize;
+ if( m_aPreferredAspectRatio.DirectionX > 0 && m_aPreferredAspectRatio.DirectionY > 0)
+ {
+ //do not change aspect ratio
+ awt::Size aAspectRatio( static_cast<sal_Int32>(m_aPreferredAspectRatio.DirectionX*FIXED_SIZE_FOR_3D_CHART_VOLUME),
+ static_cast<sal_Int32>(m_aPreferredAspectRatio.DirectionY*FIXED_SIZE_FOR_3D_CHART_VOLUME ));
+ m_aCurrentSizeWithoutAxes = ShapeFactory::calculateNewSizeRespectingAspectRatio(
+ rAvailableSize, aAspectRatio );
+ //center diagram position
+ m_aCurrentPosWithoutAxes = ShapeFactory::calculateTopLeftPositionToCenterObject(
+ rPos, rAvailableSize, m_aCurrentSizeWithoutAxes );
+
+ }
+
+ if( m_xWall2D.is() )
+ {
+ m_xWall2D->setSize( m_aCurrentSizeWithoutAxes);
+ m_xWall2D->setPosition(m_aCurrentPosWithoutAxes);
+ }
+
+ return BaseGFXHelper::makeRectangle(m_aCurrentPosWithoutAxes,m_aCurrentSizeWithoutAxes);
+}
+
+void VDiagram::createShapes_2d()
+{
+ OSL_PRECOND(m_xTarget.is(), "is not proper initialized");
+ if (!m_xTarget.is())
+ return;
+
+ //create group shape
+ rtl::Reference<SvxShapeGroupAnyD> xOuterGroup_Shapes = ShapeFactory::createGroup2D(m_xTarget);
+ m_xOuterGroupShape = xOuterGroup_Shapes;
+
+ rtl::Reference<SvxShapeGroupAnyD> xGroupForWall( ShapeFactory::createGroup2D(xOuterGroup_Shapes,"PlotAreaExcludingAxes") );
+
+ //create independent group shape as container for datapoints and such things
+ m_xCoordinateRegionShape = ShapeFactory::createGroup2D(xOuterGroup_Shapes,"testonly;CooContainer=XXX_CID");
+
+ bool bAddFloorAndWall = DiagramHelper::isSupportingFloorAndWall( m_xDiagram );
+
+ //add back wall
+ {
+ m_xWall2D = ShapeFactory::createRectangle( xGroupForWall );
+
+ try
+ {
+ OSL_ENSURE( m_xDiagram.is(), "Invalid Diagram model" );
+ if( m_xDiagram.is() )
+ {
+ uno::Reference< beans::XPropertySet > xWallProp( m_xDiagram->getWall());
+ if( xWallProp.is())
+ PropertyMapper::setMappedProperties( *m_xWall2D, xWallProp, PropertyMapper::getPropertyNameMapForFillAndLineProperties() );
+ }
+ if( !bAddFloorAndWall )
+ {
+ //we always need this object as dummy object for correct scene dimensions
+ //but it should not be visible in this case:
+ ShapeFactory::makeShapeInvisible( m_xWall2D );
+ }
+ else
+ {
+ //CID for selection handling
+ OUString aWallCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM_WALL, u"" ) );//@todo read CID from model
+ m_xWall2D->SvxShape::setPropertyValue( UNO_NAME_MISC_OBJ_NAME, uno::Any( aWallCID ) );
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }
+
+ //position and size for diagram
+ adjustPosAndSize_2d( m_aAvailablePosIncludingAxes, m_aAvailableSizeIncludingAxes );
+}
+
+static E3dScene* lcl_getE3dScene( const rtl::Reference<SvxShapeGroupAnyD>& xShape )
+{
+ return dynamic_cast< E3dScene* >(xShape->GetSdrObject());
+}
+
+static void lcl_setLightSources(
+ const uno::Reference< beans::XPropertySet > & xSource,
+ const uno::Reference< beans::XPropertySet > & xDest )
+{
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_1,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_1));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_2,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_2));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_3,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_3));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_4,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_4));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_5,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_5));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_6,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_6));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_7,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_7));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_8,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTON_8));
+
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_1,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_1));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_2,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_2));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_3,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_3));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_4,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_4));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_5,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_5));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_6,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_6));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_7,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_7));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_8,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTDIRECTION_8));
+
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_1,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_1));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_2,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_2));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_3,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_3));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_4,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_4));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_5,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_5));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_6,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_6));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_7,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_7));
+ xDest->setPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_8,
+ xSource->getPropertyValue( UNO_NAME_3D_SCENE_LIGHTCOLOR_8));
+}
+
+namespace
+{
+
+void lcl_ensureScaleValue( double& rfScale )
+{
+ OSL_ENSURE(rfScale>0, "calculation error for automatic 3D height in chart");
+ if( rfScale<0 )
+ rfScale = 1.0;
+ else if( rfScale<0.2 )
+ rfScale = 0.2;
+ else if( rfScale>5.0 )
+ rfScale = 5.0;
+}
+
+}
+
+void VDiagram::adjustAspectRatio3d( const awt::Size& rAvailableSize )
+{
+ OSL_PRECOND(m_xAspectRatio3D.is(), "created shape offers no XPropertySet");
+ if( !m_xAspectRatio3D.is())
+ return;
+
+ try
+ {
+ double fScaleX = m_aPreferredAspectRatio.DirectionX;
+ double fScaleY = m_aPreferredAspectRatio.DirectionY;
+ double fScaleZ = m_aPreferredAspectRatio.DirectionZ;
+
+ //normalize scale factors
+ {
+ double fMax = std::max( std::max( fScaleX, fScaleY) , fScaleZ );
+ fScaleX/=fMax;
+ fScaleY/=fMax;
+ fScaleZ/=fMax;
+ }
+
+ if( fScaleX<0 || fScaleY<0 || fScaleZ<0 )
+ {
+ //calculate automatic 3D aspect ratio that fits good into the given 2D area
+ double fW = rAvailableSize.Width;
+ double fH = rAvailableSize.Height;
+
+ double sx = fabs(sin(m_fXAnglePi));
+ double sy = fabs(sin(m_fYAnglePi));
+ double cz = fabs(cos(m_fZAnglePi));
+ double sz = fabs(sin(m_fZAnglePi));
+
+ if(m_bRightAngledAxes)
+ {
+ //base equations:
+ //fH*zoomfactor == sx*fScaleZ + fScaleY;
+ //fW*zoomfactor == sy*fScaleZ + fScaleX;
+
+ if( fScaleX>0 && fScaleZ>0 )
+ {
+ //calculate fScaleY:
+ if( !::basegfx::fTools::equalZero(fW) )
+ {
+ fScaleY = (fH/fW)*(sy*fScaleZ+fScaleX)-(sx*fScaleZ);
+ lcl_ensureScaleValue( fScaleY );
+ }
+ else
+ fScaleY = 1.0;//looking from top or bottom the height is irrelevant
+ }
+ else if( fScaleY>0 && fScaleZ>0 )
+ {
+ //calculate fScaleX:
+ if( !::basegfx::fTools::equalZero(fH) )
+ {
+ fScaleX = (fW/fH)*(sx*fScaleZ+fScaleY)-(sy*fScaleZ);
+ lcl_ensureScaleValue(fScaleX);
+ }
+ else
+ fScaleX = 1.0;//looking from top or bottom height is irrelevant
+ }
+ else
+ {
+ //todo
+ OSL_FAIL("not implemented yet");
+
+ if( fScaleX<0 )
+ fScaleX = 1.0;
+ if( fScaleY<0 )
+ fScaleY = 1.0;
+ if( fScaleZ<0 )
+ fScaleZ = 1.0;
+ }
+ }
+ else
+ {
+ //base equations:
+ //fH*zoomfactor == cz*fScaleY + sz*fScaleX;
+ //fW*zoomfactor == cz*fScaleX + sz*fScaleY;
+ //==> fScaleY*(fH*sz-fW*cz) == fScaleX*(fW*sz-fH*cz);
+ if( fScaleX>0 && fScaleZ>0 )
+ {
+ //calculate fScaleY:
+ double fDivide = fH*sz-fW*cz;
+ if( !::basegfx::fTools::equalZero(fDivide) )
+ {
+ fScaleY = fScaleX*(fW*sz-fH*cz) / fDivide;
+ lcl_ensureScaleValue(fScaleY);
+ }
+ else
+ fScaleY = 1.0;//looking from top or bottom the height is irrelevant
+
+ }
+ else if( fScaleY>0 && fScaleZ>0 )
+ {
+ //calculate fScaleX:
+ double fDivide = fW*sz-fH*cz;
+ if( !::basegfx::fTools::equalZero(fDivide) )
+ {
+ fScaleX = fScaleY*(fH*sz-fW*cz) / fDivide;
+ lcl_ensureScaleValue(fScaleX);
+ }
+ else
+ fScaleX = 1.0;//looking from top or bottom height is irrelevant
+ }
+ else
+ {
+ //todo
+ OSL_FAIL("not implemented yet");
+
+ if( fScaleX<0 )
+ fScaleX = 1.0;
+ if( fScaleY<0 )
+ fScaleY = 1.0;
+ if( fScaleZ<0 )
+ fScaleZ = 1.0;
+ }
+ }
+ }
+
+ //normalize scale factors
+ {
+ double fMax = std::max( std::max( fScaleX, fScaleY) , fScaleZ );
+ fScaleX/=fMax;
+ fScaleY/=fMax;
+ fScaleZ/=fMax;
+ }
+
+ // identity matrix
+ ::basegfx::B3DHomMatrix aResult;
+ aResult.translate( -FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0,
+ -FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0,
+ -FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0 );
+ aResult.scale( fScaleX, fScaleY, fScaleZ );
+ aResult.translate( FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0,
+ FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0,
+ FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0 );
+
+ // To get the 3D aspect ratio's effect on the 2D scene size, the scene's 2D size needs to be adapted to
+ // 3D content changes here. The tooling class remembers the current 3D transformation stack
+ // and in its destructor, calculates a new 2D SnapRect for the scene and it's modified 3D geometry.
+ E3DModifySceneSnapRectUpdater aUpdater(lcl_getE3dScene(m_xOuterGroupShape));
+
+ m_xAspectRatio3D->setPropertyValue( UNO_NAME_3D_TRANSFORM_MATRIX
+ , uno::Any(BaseGFXHelper::B3DHomMatrixToHomogenMatrix( aResult )) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+::basegfx::B2IRectangle VDiagram::adjustPosAndSize_3d( const awt::Point& rPos, const awt::Size& rAvailableSize )
+{
+ adjustAspectRatio3d( rAvailableSize );
+
+ //do not change aspect ratio of 3D scene with 2D bound rect
+ m_aCurrentSizeWithoutAxes = ShapeFactory::calculateNewSizeRespectingAspectRatio(
+ rAvailableSize, m_xOuterGroupShape->getSize() );
+ m_xOuterGroupShape->setSize( m_aCurrentSizeWithoutAxes );
+
+ //center diagram position
+ m_aCurrentPosWithoutAxes= ShapeFactory::calculateTopLeftPositionToCenterObject(
+ rPos, rAvailableSize, m_aCurrentSizeWithoutAxes );
+ m_xOuterGroupShape->setPosition(m_aCurrentPosWithoutAxes);
+
+ return BaseGFXHelper::makeRectangle(m_aCurrentPosWithoutAxes,m_aCurrentSizeWithoutAxes);
+}
+
+void VDiagram::createShapes_3d()
+{
+ OSL_PRECOND(m_xTarget.is(), "is not proper initialized");
+ if (!m_xTarget.is())
+ return;
+
+ //create shape
+ rtl::Reference<Svx3DSceneObject> xShapes = ShapeFactory::createGroup3D( m_xTarget, "PlotAreaExcludingAxes" );
+ m_xOuterGroupShape = xShapes;
+
+ rtl::Reference<SvxShapeGroupAnyD> xOuterGroup_Shapes = m_xOuterGroupShape;
+
+ //create additional group to manipulate the aspect ratio of the whole diagram:
+ xOuterGroup_Shapes = ShapeFactory::createGroup3D( xOuterGroup_Shapes );
+
+ m_xAspectRatio3D = xOuterGroup_Shapes;
+
+ bool bAddFloorAndWall = DiagramHelper::isSupportingFloorAndWall( m_xDiagram );
+
+ const bool bDoubleSided = false;
+
+ //add walls
+ {
+ uno::Reference< beans::XPropertySet > xWallProp;
+ if( m_xDiagram.is() )
+ xWallProp.set( m_xDiagram->getWall() );
+
+ OUString aWallCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM_WALL, u"" ) );//@todo read CID from model
+ if( !bAddFloorAndWall )
+ aWallCID.clear();
+ rtl::Reference<Svx3DSceneObject> xWallGroup_Shapes = ShapeFactory::createGroup3D( xOuterGroup_Shapes, aWallCID );
+
+ CuboidPlanePosition eLeftWallPos( ThreeDHelper::getAutomaticCuboidPlanePositionForStandardLeftWall( m_xDiagram ) );
+ CuboidPlanePosition eBackWallPos( ThreeDHelper::getAutomaticCuboidPlanePositionForStandardBackWall( m_xDiagram ) );
+
+ //add left wall
+ {
+ short nRotatedTexture = ( eBackWallPos==CuboidPlanePosition_Front ) ? 3 : 1;
+ double xPos = 0.0;
+ if( eLeftWallPos==CuboidPlanePosition_Right )
+ xPos = FIXED_SIZE_FOR_3D_CHART_VOLUME;
+ Stripe aStripe( drawing::Position3D(xPos,FIXED_SIZE_FOR_3D_CHART_VOLUME,0)
+ , drawing::Direction3D(0,0,FIXED_SIZE_FOR_3D_CHART_VOLUME)
+ , drawing::Direction3D(0,-FIXED_SIZE_FOR_3D_CHART_VOLUME,0) );
+ if( eLeftWallPos==CuboidPlanePosition_Right )
+ {
+ nRotatedTexture = ( eBackWallPos==CuboidPlanePosition_Front ) ? 2 : 0;
+ aStripe = Stripe( drawing::Position3D(xPos,FIXED_SIZE_FOR_3D_CHART_VOLUME,0)
+ , drawing::Direction3D(0,-FIXED_SIZE_FOR_3D_CHART_VOLUME,0)
+ , drawing::Direction3D(0,0,FIXED_SIZE_FOR_3D_CHART_VOLUME) );
+ }
+ aStripe.InvertNormal(true);
+
+ rtl::Reference<Svx3DPolygonObject> xShape =
+ ShapeFactory::createStripe( xWallGroup_Shapes, aStripe
+ , xWallProp, PropertyMapper::getPropertyNameMapForFillAndLineProperties(), bDoubleSided, nRotatedTexture );
+ if( !bAddFloorAndWall )
+ {
+ //we always need this object as dummy object for correct scene dimensions
+ //but it should not be visible in this case:
+ ShapeFactory::makeShapeInvisible( xShape );
+ }
+ }
+ //add back wall
+ {
+ short nRotatedTexture = 0;
+ double zPos = 0.0;
+ if( eBackWallPos==CuboidPlanePosition_Front )
+ zPos = FIXED_SIZE_FOR_3D_CHART_VOLUME;
+ Stripe aStripe( drawing::Position3D(0,FIXED_SIZE_FOR_3D_CHART_VOLUME,zPos)
+ , drawing::Direction3D(0,-FIXED_SIZE_FOR_3D_CHART_VOLUME,0)
+ , drawing::Direction3D(FIXED_SIZE_FOR_3D_CHART_VOLUME,0,0) );
+ if( eBackWallPos==CuboidPlanePosition_Front )
+ {
+ aStripe = Stripe( drawing::Position3D(0,FIXED_SIZE_FOR_3D_CHART_VOLUME,zPos)
+ , drawing::Direction3D(FIXED_SIZE_FOR_3D_CHART_VOLUME,0,0)
+ , drawing::Direction3D(0,-FIXED_SIZE_FOR_3D_CHART_VOLUME,0) );
+ nRotatedTexture = 3;
+ }
+ aStripe.InvertNormal(true);
+
+ rtl::Reference<Svx3DPolygonObject> xShape =
+ ShapeFactory::createStripe(xWallGroup_Shapes, aStripe
+ , xWallProp, PropertyMapper::getPropertyNameMapForFillAndLineProperties(), bDoubleSided, nRotatedTexture );
+ if( !bAddFloorAndWall )
+ {
+ //we always need this object as dummy object for correct scene dimensions
+ //but it should not be visible in this case:
+ ShapeFactory::makeShapeInvisible( xShape );
+ }
+ }
+ }
+
+ try
+ {
+ //perspective
+ {
+ //ignore distance and focal length from file format and model completely
+ //use vrp only to indicate the distance of the camera and thus influence the perspective
+ m_xOuterGroupShape->setPropertyValue( UNO_NAME_3D_SCENE_DISTANCE, uno::Any(
+ static_cast<sal_Int32>(ThreeDHelper::getCameraDistance( m_xDiagram ))));
+ m_xOuterGroupShape->setPropertyValue( UNO_NAME_3D_SCENE_PERSPECTIVE,
+ m_xDiagram->getPropertyValue( UNO_NAME_3D_SCENE_PERSPECTIVE));
+ }
+
+ //light
+ {
+ m_xOuterGroupShape->setPropertyValue( UNO_NAME_3D_SCENE_SHADE_MODE,
+ m_xDiagram->getPropertyValue( UNO_NAME_3D_SCENE_SHADE_MODE));
+ m_xOuterGroupShape->setPropertyValue( UNO_NAME_3D_SCENE_AMBIENTCOLOR,
+ m_xDiagram->getPropertyValue( UNO_NAME_3D_SCENE_AMBIENTCOLOR));
+ m_xOuterGroupShape->setPropertyValue( UNO_NAME_3D_SCENE_TWO_SIDED_LIGHTING,
+ m_xDiagram->getPropertyValue( UNO_NAME_3D_SCENE_TWO_SIDED_LIGHTING));
+ lcl_setLightSources( m_xDiagram, m_xOuterGroupShape );
+ }
+
+ //rotation
+ {
+ //set diagrams rotation is set exclusively via the transformation matrix
+ //don't set a camera at all!
+ //the camera's rotation is incorporated into this matrix
+
+ ::basegfx::B3DHomMatrix aEffectiveTransformation;
+ aEffectiveTransformation.translate(-FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0, -FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0, -FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0);
+
+ if(!m_bRightAngledAxes)
+ aEffectiveTransformation.rotate(m_fXAnglePi,m_fYAnglePi,m_fZAnglePi);
+ else
+ aEffectiveTransformation.shearXY(m_fYAnglePi,-m_fXAnglePi);
+
+ //#i98497# 3D charts are rendered with wrong size
+ E3DModifySceneSnapRectUpdater aUpdater(lcl_getE3dScene(m_xOuterGroupShape));
+
+ m_xOuterGroupShape->setPropertyValue( UNO_NAME_3D_TRANSFORM_MATRIX,
+ uno::Any( BaseGFXHelper::B3DHomMatrixToHomogenMatrix( aEffectiveTransformation ) ) );
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2" );
+ }
+
+ //add floor plate
+ {
+ uno::Reference< beans::XPropertySet > xFloorProp;
+ if( m_xDiagram.is() )
+ xFloorProp.set( m_xDiagram->getFloor() );
+
+ Stripe aStripe( drawing::Position3D(0,0,0)
+ , drawing::Direction3D(0,0,FIXED_SIZE_FOR_3D_CHART_VOLUME)
+ , drawing::Direction3D(FIXED_SIZE_FOR_3D_CHART_VOLUME,0,0) );
+ aStripe.InvertNormal(true);
+
+ rtl::Reference<Svx3DPolygonObject> xShape =
+ ShapeFactory::createStripe(xOuterGroup_Shapes, aStripe
+ , xFloorProp, PropertyMapper::getPropertyNameMapForFillAndLineProperties(), bDoubleSided );
+
+ CuboidPlanePosition eBottomPos( ThreeDHelper::getAutomaticCuboidPlanePositionForStandardBottom( m_xDiagram ) );
+ if( !bAddFloorAndWall || (eBottomPos!=CuboidPlanePosition_Bottom) )
+ {
+ //we always need this object as dummy object for correct scene dimensions
+ //but it should not be visible in this case:
+ ShapeFactory::makeShapeInvisible( xShape );
+ }
+ else
+ {
+ OUString aFloorCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM_FLOOR, u"" ) );//@todo read CID from model
+ ShapeFactory::setShapeName( xShape, aFloorCID );
+ }
+ }
+
+ //create an additional scene for the smaller inner coordinate region:
+ {
+ rtl::Reference<Svx3DSceneObject> xShapes2 = ShapeFactory::createGroup3D( xOuterGroup_Shapes,"testonly;CooContainer=XXX_CID" );
+ m_xCoordinateRegionShape = xShapes2;
+
+ try
+ {
+ double fXScale = (FIXED_SIZE_FOR_3D_CHART_VOLUME -GRID_TO_WALL_DISTANCE) /FIXED_SIZE_FOR_3D_CHART_VOLUME;
+ double fYScale = (FIXED_SIZE_FOR_3D_CHART_VOLUME -GRID_TO_WALL_DISTANCE) /FIXED_SIZE_FOR_3D_CHART_VOLUME;
+ double fZScale = (FIXED_SIZE_FOR_3D_CHART_VOLUME -GRID_TO_WALL_DISTANCE) /FIXED_SIZE_FOR_3D_CHART_VOLUME;
+
+ ::basegfx::B3DHomMatrix aM;
+ aM.translate(GRID_TO_WALL_DISTANCE/fXScale, GRID_TO_WALL_DISTANCE/fYScale, GRID_TO_WALL_DISTANCE/fZScale);
+ aM.scale( fXScale, fYScale, fZScale );
+ E3DModifySceneSnapRectUpdater aUpdater(lcl_getE3dScene(m_xOuterGroupShape));
+
+ xShapes2->SvxShape::setPropertyValue( UNO_NAME_3D_TRANSFORM_MATRIX
+ , uno::Any(BaseGFXHelper::B3DHomMatrixToHomogenMatrix(aM)) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }
+
+ m_aCurrentPosWithoutAxes = m_aAvailablePosIncludingAxes;
+ m_aCurrentSizeWithoutAxes = m_aAvailableSizeIncludingAxes;
+ adjustPosAndSize_3d( m_aAvailablePosIncludingAxes, m_aAvailableSizeIncludingAxes );
+}
+
+basegfx::B2IRectangle VDiagram::getCurrentRectangle() const
+{
+ return BaseGFXHelper::makeRectangle(m_aCurrentPosWithoutAxes,m_aCurrentSizeWithoutAxes);
+}
+
+void VDiagram::reduceToMinimumSize()
+{
+ if( !m_xOuterGroupShape.is() )
+ return;
+
+ awt::Size aMaxSize( m_aAvailableSizeIncludingAxes );
+ awt::Point aMaxPos( m_aAvailablePosIncludingAxes );
+
+ sal_Int32 nNewWidth = std::round(aMaxSize.Width/2.2);
+ sal_Int32 nNewHeight = std::round(aMaxSize.Height/2.2);
+ awt::Size aNewSize( nNewWidth, nNewHeight );
+ awt::Point aNewPos( aMaxPos );
+ aNewPos.X += nNewWidth;
+ aNewPos.Y += nNewHeight;
+
+ adjustPosAndSize( aNewPos, aNewSize );
+}
+
+::basegfx::B2IRectangle VDiagram::adjustInnerSize( const ::basegfx::B2IRectangle& rConsumedOuterRect )
+{
+ awt::Point aNewPos = m_aCurrentPosWithoutAxes;
+ awt::Size aNewSize = m_aCurrentSizeWithoutAxes;
+
+ basegfx::B2IRectangle aAvailableOuterRect =
+ BaseGFXHelper::makeRectangle(m_aAvailablePosIncludingAxes, m_aAvailableSizeIncludingAxes);
+
+ sal_Int32 nDeltaWidth = aAvailableOuterRect.getWidth() - rConsumedOuterRect.getWidth();
+ sal_Int32 nDeltaHeight = aAvailableOuterRect.getHeight() - rConsumedOuterRect.getHeight();
+ if( (aNewSize.Width + nDeltaWidth) < aAvailableOuterRect.getWidth()/3 )
+ nDeltaWidth = aAvailableOuterRect.getWidth()/3 - aNewSize.Width;
+ aNewSize.Width += nDeltaWidth;
+
+ if( (aNewSize.Height + nDeltaHeight) < aAvailableOuterRect.getHeight()/3 )
+ nDeltaHeight = aAvailableOuterRect.getHeight()/3 - aNewSize.Height;
+ aNewSize.Height += nDeltaHeight;
+
+ sal_Int32 nDiffLeft = rConsumedOuterRect.getMinX() - aAvailableOuterRect.getMinX();
+ sal_Int32 nDiffRight = aAvailableOuterRect.getMaxX() - rConsumedOuterRect.getMaxX();
+ if( nDiffLeft >= 0 )
+ aNewPos.X -= nDiffLeft;
+ else if( nDiffRight >= 0 )
+ {
+ if( nDiffRight > -nDiffLeft )
+ aNewPos.X += abs(nDiffLeft);
+ else if( nDiffRight > abs(nDeltaWidth) )
+ aNewPos.X += nDiffRight;
+ else
+ aNewPos.X += abs(nDeltaWidth);
+ }
+
+ sal_Int32 nDiffUp = rConsumedOuterRect.getMinY() - aAvailableOuterRect.getMinY();
+ sal_Int32 nDiffDown = aAvailableOuterRect.getMaxY() - rConsumedOuterRect.getMaxY();
+ if( nDiffUp >= 0 )
+ aNewPos.Y -= nDiffUp;
+ else if( nDiffDown >= 0 )
+ {
+ if( nDiffDown > -nDiffUp )
+ aNewPos.Y += abs(nDiffUp);
+ else if( nDiffDown > abs(nDeltaHeight) )
+ aNewPos.Y += nDiffDown;
+ else
+ aNewPos.Y += abs(nDeltaHeight);
+ }
+
+ return adjustPosAndSize( aNewPos, aNewSize );
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/Clipping.hxx b/chart2/source/view/inc/Clipping.hxx
new file mode 100644
index 000000000..e816e7aa7
--- /dev/null
+++ b/chart2/source/view/inc/Clipping.hxx
@@ -0,0 +1,61 @@
+/* -*- 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 <basegfx/range/b2drectangle.hxx>
+
+namespace com::sun::star::drawing { struct PolyPolygonShape3D; }
+namespace com::sun::star::drawing { struct Position3D; }
+
+namespace chart
+{
+
+class Clipping
+{
+ /** This class uses the Liang-Biarsky parametric line-clipping algorithm as described in:
+ Computer Graphics: principles and practice, 2nd ed.,
+ James D. Foley et al.,
+ Section 3.12.4 on page 117.
+ */
+
+public:
+ /** @descr The intersection between an open polygon and a rectangle is
+ calculated and the resulting lines are placed into the poly-polygon aResult.
+ @param rPolygon The polygon is required to be open, ie. its start and end point
+ have different coordinates and that it is continuous, ie. has no holes.
+ @param rRectangle The clipping area.
+ @param aResult The resulting lines that are the parts of the given polygon lying inside
+ the clipping area are stored into aResult whose prior content is deleted first.
+ */
+ static void clipPolygonAtRectangle(
+ const css::drawing::PolyPolygonShape3D& rPolygon
+ , const ::basegfx::B2DRectangle& rRectangle
+ , css::drawing::PolyPolygonShape3D& aResult
+ , bool bSplitPiecesToDifferentPolygons = true );
+ static void clipPolygonAtRectangle(
+ const std::vector<std::vector<css::drawing::Position3D>>& rPolygon
+ , const ::basegfx::B2DRectangle& rRectangle
+ , std::vector<std::vector<css::drawing::Position3D>>& aResult
+ , bool bSplitPiecesToDifferentPolygons = true );
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/ConfigAccess.hxx b/chart2/source/view/inc/ConfigAccess.hxx
new file mode 100644
index 000000000..df59b16a3
--- /dev/null
+++ b/chart2/source/view/inc/ConfigAccess.hxx
@@ -0,0 +1,35 @@
+/* -*- 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
+
+namespace chart::ConfigAccess
+{
+/** @descr Retrieve the setting for showing errors in charts from the registry
+ settings of the Calc application.
+
+ If this setting is not found, it is set to false (the default setting).
+
+ @return boolean UseErrorRectangle.
+ */
+bool getUseErrorRectangle();
+
+} //namespace chart::ConfigAccess
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/DateHelper.hxx b/chart2/source/view/inc/DateHelper.hxx
new file mode 100644
index 000000000..8c37851b7
--- /dev/null
+++ b/chart2/source/view/inc/DateHelper.hxx
@@ -0,0 +1,43 @@
+/* -*- 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 <tools/date.hxx>
+#include <tools/long.hxx>
+
+namespace chart
+{
+
+class DateHelper
+{
+public:
+ static bool IsInSameYear( const Date& rD1, const Date& rD2 );
+ static bool IsInSameMonth( const Date& rD1, const Date& rD2 );
+
+ static Date GetDateSomeMonthsAway( const Date& rD, sal_Int32 nMonthDistance );
+ static Date GetDateSomeYearsAway( const Date& rD, sal_Int32 nYearDistance );
+ static bool IsLessThanOneMonthAway( const Date& rD1, const Date& rD2 );
+ static bool IsLessThanOneYearAway( const Date& rD1, const Date& rD2 );
+
+ static double RasterizeDateValue( double fValue, const Date& rNullDate, tools::Long TimeResolution );
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/LabelAlignment.hxx b/chart2/source/view/inc/LabelAlignment.hxx
new file mode 100644
index 000000000..425f5c6c3
--- /dev/null
+++ b/chart2/source/view/inc/LabelAlignment.hxx
@@ -0,0 +1,38 @@
+/* -*- 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
+
+namespace chart
+{
+enum LabelAlignment
+{
+ LABEL_ALIGN_CENTER,
+ LABEL_ALIGN_LEFT,
+ LABEL_ALIGN_TOP,
+ LABEL_ALIGN_RIGHT,
+ LABEL_ALIGN_BOTTOM,
+ LABEL_ALIGN_LEFT_TOP,
+ LABEL_ALIGN_LEFT_BOTTOM,
+ LABEL_ALIGN_RIGHT_TOP,
+ LABEL_ALIGN_RIGHT_BOTTOM
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/LabelPositionHelper.hxx b/chart2/source/view/inc/LabelPositionHelper.hxx
new file mode 100644
index 000000000..63125d621
--- /dev/null
+++ b/chart2/source/view/inc/LabelPositionHelper.hxx
@@ -0,0 +1,66 @@
+/* -*- 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 "LabelAlignment.hxx"
+#include "PropertyMapper.hxx"
+#include <com/sun/star/awt/Point.hpp>
+#include <rtl/ref.hxx>
+#include <svx/unoshape.hxx>
+
+namespace com::sun::star::drawing { struct Position3D; }
+namespace com::sun::star::drawing { class XShapes; }
+namespace com::sun::star::awt { struct Size; }
+namespace com::sun::star::drawing { class XShape; }
+
+namespace chart
+{
+
+class LabelPositionHelper
+{
+public:
+ LabelPositionHelper() = delete;
+ LabelPositionHelper(
+ sal_Int32 nDimensionCount
+ , const rtl::Reference<SvxShapeGroupAnyD>& xLogicTarget );
+ virtual ~LabelPositionHelper();
+
+ css::awt::Point transformSceneToScreenPosition(
+ const css::drawing::Position3D& rScenePosition3D ) const;
+
+ static void changeTextAdjustment( tAnySequence& rPropValues, const tNameSequence& rPropNames, LabelAlignment eAlignment);
+ static void doDynamicFontResize( tAnySequence& rPropValues, const tNameSequence& rPropNames
+ , const css::uno::Reference< css::beans::XPropertySet >& xAxisModelProps
+ , const css::awt::Size& rNewReferenceSize );
+
+ static void correctPositionForRotation( const rtl::Reference<SvxShapeText>& xShape2DText
+ , LabelAlignment eLabelAlignment, const double fRotationAngle, bool bRotateAroundCenter );
+
+protected:
+ sal_Int32 m_nDimensionCount;
+
+private:
+ //these members are only necessary for transformation from 3D to 2D
+ rtl::Reference<SvxShapeGroupAnyD> m_xLogicTarget;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/LegendEntryProvider.hxx b/chart2/source/view/inc/LegendEntryProvider.hxx
new file mode 100644
index 000000000..e0133771c
--- /dev/null
+++ b/chart2/source/view/inc/LegendEntryProvider.hxx
@@ -0,0 +1,89 @@
+/* -*- 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/LegendPosition.hpp>
+#include <com/sun/star/chart2/XFormattedString2.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/uno/Sequence.h>
+#include <rtl/ref.hxx>
+#include <svx/unoshape.hxx>
+#include <vector>
+
+namespace chart { class ChartModel; }
+namespace com::sun::star::beans { class XPropertySet; }
+namespace com::sun::star::drawing { class XShape; }
+namespace com::sun::star::drawing { class XShapes; }
+namespace com::sun::star::lang { class XMultiServiceFactory; }
+namespace com::sun::star::uno { class XComponentContext; }
+
+namespace chart
+{
+
+enum class LegendSymbolStyle
+{
+ /** A square box with border.
+ */
+ Box,
+
+ /** A line like with a symbol.
+ */
+ Line,
+
+ /** A bordered circle which has the same bounding-box as the
+ <member>BOX</member>.
+ */
+ Circle
+};
+
+struct ViewLegendEntry
+{
+ /** The legend symbol that represents a data series or other
+ information contained in the legend
+ */
+ rtl::Reference< SvxShapeGroup > xSymbol;
+
+ /** The descriptive text for a legend entry.
+ */
+ css::uno::Sequence<
+ css::uno::Reference< css::chart2::XFormattedString2 > > aLabel;
+};
+
+class LegendEntryProvider
+{
+public:
+ virtual css::awt::Size getPreferredLegendKeyAspectRatio()=0;
+
+ virtual std::vector< ViewLegendEntry > createLegendEntries(
+ const css::awt::Size& rEntryKeyAspectRatio,
+ css::chart2::LegendPosition eLegendPosition,
+ const css::uno::Reference< css::beans::XPropertySet >& xTextProperties,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const css::uno::Reference< css::uno::XComponentContext >& xContext,
+ ChartModel& rModel
+ ) = 0;
+
+protected:
+ ~LegendEntryProvider() {}
+};
+
+} // namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/Linear3DTransformation.hxx b/chart2/source/view/inc/Linear3DTransformation.hxx
new file mode 100644
index 000000000..456f6e4c4
--- /dev/null
+++ b/chart2/source/view/inc/Linear3DTransformation.hxx
@@ -0,0 +1,46 @@
+/* -*- 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 "PlottingPositionHelper.hxx"
+#include <com/sun/star/drawing/HomogenMatrix.hpp>
+
+namespace chart
+{
+
+class Linear3DTransformation final : public XTransformation2
+{
+public:
+ Linear3DTransformation( const css::drawing::HomogenMatrix& rHomMatrix, bool bSwapXAndY );
+ virtual ~Linear3DTransformation() override;
+
+ // ____ XTransformation2 ____
+ virtual css::drawing::Position3D transform(
+ const css::drawing::Position3D& rSourceValues ) const override;
+ virtual css::drawing::Position3D transform(
+ const css::uno::Sequence< double >& rSourceValues ) const override;
+
+private:
+ css::drawing::HomogenMatrix m_Matrix;
+ bool m_bSwapXAndY;
+};
+
+} // namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/MinimumAndMaximumSupplier.hxx b/chart2/source/view/inc/MinimumAndMaximumSupplier.hxx
new file mode 100644
index 000000000..cbb5e55ba
--- /dev/null
+++ b/chart2/source/view/inc/MinimumAndMaximumSupplier.hxx
@@ -0,0 +1,92 @@
+/* -*- 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 <sal/types.h>
+#include <tools/date.hxx>
+#include <tools/long.hxx>
+#include <set>
+
+namespace chart
+{
+
+class MinimumAndMaximumSupplier
+{
+public:
+ virtual double getMinimumX() = 0;
+ virtual double getMaximumX() = 0;
+
+ //problem y maybe not is always the second border to ask for
+ virtual double getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) = 0;
+ virtual double getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) = 0;
+
+ //problem: z maybe not independent in future
+ virtual double getMinimumZ() = 0;
+ virtual double getMaximumZ() = 0;
+
+ virtual bool isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex ) = 0;
+ virtual bool isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex ) = 0;
+ virtual bool isExpandWideValuesToZero( sal_Int32 nDimensionIndex ) = 0;
+ virtual bool isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex ) = 0;
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) = 0;
+
+ //return a constant out of css::chart::TimeUnit that allows to display the smallest distance between occurring dates
+ virtual tools::Long calculateTimeResolutionOnXAxis() = 0;
+ virtual void setTimeResolutionOnXAxis( tools::Long nTimeResolution, const Date& rNullDate ) = 0;
+
+protected:
+ ~MinimumAndMaximumSupplier() {}
+};
+
+class MergedMinimumAndMaximumSupplier final : public MinimumAndMaximumSupplier
+{
+public:
+ MergedMinimumAndMaximumSupplier();
+ virtual ~MergedMinimumAndMaximumSupplier();
+
+ void addMinimumAndMaximumSupplier( MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier );
+ bool hasMinimumAndMaximumSupplier( MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier );
+ void clearMinimumAndMaximumSupplierList();
+
+ //--MinimumAndMaximumSupplier
+ virtual double getMinimumX() override;
+ virtual double getMaximumX() override;
+ virtual double getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) override;
+ virtual double getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) override;
+ virtual double getMinimumZ() override;
+ virtual double getMaximumZ() override;
+
+ virtual bool isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandWideValuesToZero( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex ) override;
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+ virtual tools::Long calculateTimeResolutionOnXAxis() override;
+ virtual void setTimeResolutionOnXAxis( tools::Long nTimeResolution, const Date& rNullDate ) override;
+
+private:
+ typedef std::set< MinimumAndMaximumSupplier* > MinimumAndMaximumSupplierSet;
+ MinimumAndMaximumSupplierSet m_aMinimumAndMaximumSupplierList;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/PlotterBase.hxx b/chart2/source/view/inc/PlotterBase.hxx
new file mode 100644
index 000000000..73695507f
--- /dev/null
+++ b/chart2/source/view/inc/PlotterBase.hxx
@@ -0,0 +1,79 @@
+/* -*- 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 <rtl/ustring.hxx>
+#include <svx/unoshape.hxx>
+#include <vector>
+
+namespace com::sun::star::drawing { struct HomogenMatrix; }
+namespace com::sun::star::drawing { struct Position3D; }
+namespace com::sun::star::drawing { class XShapes; }
+namespace com::sun::star::lang { class XMultiServiceFactory; }
+
+namespace chart { struct ExplicitScaleData; }
+
+namespace chart
+{
+
+class PlottingPositionHelper;
+class ShapeFactory;
+
+/** This class provides methods for setting axis scales and for performing
+ * scene to screen transformations. It is used as the base class for all
+ * plotter classes.
+ */
+class PlotterBase
+{
+public:
+ PlotterBase( sal_Int32 nDimension );
+ virtual ~PlotterBase();
+
+ /// @throws css::uno::RuntimeException
+ virtual void initPlotter(
+ const rtl::Reference<SvxShapeGroupAnyD>& xLogicTarget
+ , const rtl::Reference<SvxShapeGroupAnyD>& xFinalTarget
+ , const OUString& rCID
+ );
+
+ virtual void setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis );
+
+ virtual void setTransformationSceneToScreen( const css::drawing::HomogenMatrix& rMatrix );
+
+ virtual void createShapes() = 0;
+
+ static bool isValidPosition( const css::drawing::Position3D& rPos );
+
+protected: //methods
+ rtl::Reference< SvxShapeGroupAnyD >
+ createGroupShape( const rtl::Reference< SvxShapeGroupAnyD >& xTarget
+ , const OUString& rName=OUString() );
+
+protected: //member
+ rtl::Reference< SvxShapeGroupAnyD > m_xLogicTarget;
+ rtl::Reference< SvxShapeGroupAnyD > m_xFinalTarget;
+ OUString m_aCID;
+
+ const sal_Int32 m_nDimension;
+ // needs to be created and deleted by the derived class
+ PlottingPositionHelper* m_pPosHelper;
+};
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/PlottingPositionHelper.hxx b/chart2/source/view/inc/PlottingPositionHelper.hxx
new file mode 100644
index 000000000..c0480a4e3
--- /dev/null
+++ b/chart2/source/view/inc/PlottingPositionHelper.hxx
@@ -0,0 +1,467 @@
+/* -*- 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 <sal/config.h>
+
+#include <memory>
+
+#include <chartview/ExplicitScaleValues.hxx>
+
+#include <basegfx/range/b2drectangle.hxx>
+#include <tools/long.hxx>
+#include <com/sun/star/drawing/Direction3D.hpp>
+#include <com/sun/star/drawing/Position3D.hpp>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <com/sun/star/awt/Point.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <rtl/ref.hxx>
+#include <svx/unoshape.hxx>
+
+namespace com::sun::star::drawing { class XShapes; }
+namespace com::sun::star::drawing { struct HomogenMatrix; }
+namespace com::sun::star::drawing { struct PolyPolygonShape3D; }
+
+namespace chart
+{
+
+class ShapeFactory;
+
+/** allows the transformation of numeric values from one
+ coordinate-system into another. Values may be transformed using
+ any mapping.
+ This is a non-UNO variant of the css::chart2::XTransformation interface,
+ but using more efficient calling and returning types.
+ */
+class XTransformation2
+{
+public:
+ virtual ~XTransformation2();
+ /** transforms the given input data tuple, given in the source
+ coordinate system, according to the internal transformation
+ rules, into a tuple of transformed coordinates in the
+ destination coordinate system.
+
+ <p>Note that both coordinate systems may have different
+ dimensions, e.g., if a transformation does simply a projection
+ into a lower-dimensional space.</p>
+
+ @param aValues a source tuple of data that is to be
+ transformed. The length of this sequence must be
+ equivalent to the dimension of the source coordinate
+ system.
+
+ @return the transformed data tuple. The length of this
+ sequence is equal to the dimension of the output
+ coordinate system.
+
+ @throws ::com::sun::star::lang::IllegalArgumentException
+ if the dimension of the input vector is not equal to the
+ dimension given in getSourceDimension().
+ */
+ virtual css::drawing::Position3D transform(
+ const css::drawing::Position3D& rSourceValues ) const = 0;
+ virtual css::drawing::Position3D transform(
+ const css::uno::Sequence< double >& rSourceValues ) const = 0;
+};
+
+
+class PlottingPositionHelper
+{
+public:
+ PlottingPositionHelper();
+ PlottingPositionHelper( const PlottingPositionHelper& rSource );
+ virtual ~PlottingPositionHelper();
+
+ virtual std::unique_ptr<PlottingPositionHelper> clone() const;
+ std::unique_ptr<PlottingPositionHelper> createSecondaryPosHelper( const ExplicitScaleData& rSecondaryScale );
+
+ virtual void setTransformationSceneToScreen( const css::drawing::HomogenMatrix& rMatrix);
+
+ virtual void setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis );
+ const std::vector< ExplicitScaleData >& getScales() const { return m_aScales;}
+
+ //better performance for big data
+ inline void setCoordinateSystemResolution( const css::uno::Sequence< sal_Int32 >& rCoordinateSystemResolution );
+ inline bool isSameForGivenResolution( double fX, double fY, double fZ
+ , double fX2, double fY2, double fZ2 );
+
+ inline bool isStrongLowerRequested( sal_Int32 nDimensionIndex ) const;
+ inline bool isLogicVisible( double fX, double fY, double fZ ) const;
+ inline void doLogicScaling( double* pX, double* pY, double* pZ ) const;
+ inline void doUnshiftedLogicScaling( double* pX, double* pY, double* pZ ) const;
+ inline void clipLogicValues( double* pX, double* pY, double* pZ ) const;
+ void clipScaledLogicValues( double* pX, double* pY, double* pZ ) const;
+ inline bool clipYRange( double& rMin, double& rMax ) const;
+
+ inline void doLogicScaling( css::drawing::Position3D& rPos ) const;
+
+ virtual ::chart::XTransformation2*
+ getTransformationScaledLogicToScene() const;
+
+ virtual css::drawing::Position3D
+ transformLogicToScene( double fX, double fY, double fZ, bool bClip ) const;
+
+ virtual css::drawing::Position3D
+ transformScaledLogicToScene( double fX, double fY, double fZ, bool bClip ) const;
+
+ void transformScaledLogicToScene( css::drawing::PolyPolygonShape3D& rPoly ) const;
+ void transformScaledLogicToScene( std::vector<std::vector<css::drawing::Position3D>>& rPoly ) const;
+
+ static css::awt::Point transformSceneToScreenPosition(
+ const css::drawing::Position3D& rScenePosition3D
+ , const rtl::Reference<SvxShapeGroupAnyD>& xSceneTarget
+ , sal_Int32 nDimensionCount );
+
+ inline double getLogicMinX() const;
+ inline double getLogicMinY() const;
+ inline double getLogicMinZ() const;
+ inline double getLogicMaxX() const;
+ inline double getLogicMaxY() const;
+ inline double getLogicMaxZ() const;
+
+ inline bool isMathematicalOrientationX() const;
+ inline bool isMathematicalOrientationY() const;
+ inline bool isMathematicalOrientationZ() const;
+
+ ::basegfx::B2DRectangle getScaledLogicClipDoubleRect() const;
+ css::drawing::Direction3D getScaledLogicWidth() const;
+
+ inline bool isSwapXAndY() const;
+
+ bool isPercentY() const;
+
+ double getBaseValueY() const;
+
+ inline bool maySkipPointsInRegressionCalculation() const;
+
+ void setTimeResolution( tools::Long nTimeResolution, const Date& rNullDate );
+ virtual void setScaledCategoryWidth( double fScaledCategoryWidth );
+ void AllowShiftXAxisPos( bool bAllowShift );
+ void AllowShiftZAxisPos( bool bAllowShift );
+
+protected: //member
+ std::vector< ExplicitScaleData > m_aScales;
+ ::basegfx::B3DHomMatrix m_aMatrixScreenToScene;
+
+ //this is calculated based on m_aScales and m_aMatrixScreenToScene
+ mutable std::unique_ptr< ::chart::XTransformation2 > m_xTransformationLogicToScene;
+
+ bool m_bSwapXAndY;//e.g. true for bar chart and false for column chart
+
+ sal_Int32 m_nXResolution;
+ sal_Int32 m_nYResolution;
+ sal_Int32 m_nZResolution;
+
+ bool m_bMaySkipPointsInRegressionCalculation;
+
+ bool m_bDateAxis;
+ tools::Long m_nTimeResolution;
+ Date m_aNullDate;
+
+ double m_fScaledCategoryWidth;
+ bool m_bAllowShiftXAxisPos;
+ bool m_bAllowShiftZAxisPos;
+};
+
+class PolarPlottingPositionHelper : public PlottingPositionHelper
+{
+public:
+ PolarPlottingPositionHelper();
+ PolarPlottingPositionHelper( const PolarPlottingPositionHelper& rSource );
+ virtual ~PolarPlottingPositionHelper() override;
+
+ virtual std::unique_ptr<PlottingPositionHelper> clone() const override;
+
+ virtual void setTransformationSceneToScreen( const css::drawing::HomogenMatrix& rMatrix) override;
+ virtual void setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis ) override;
+
+ const ::basegfx::B3DHomMatrix& getUnitCartesianToScene() const { return m_aUnitCartesianToScene;}
+
+ virtual ::chart::XTransformation2*
+ getTransformationScaledLogicToScene() const override;
+
+ //the resulting values provided by the following 3 methods should be used
+ //for input to the transformation received with
+ //'getTransformationScaledLogicToScene'
+
+ /** Given a value in the radius axis scale range, it returns the normalized
+ * value.
+ */
+ double transformToRadius( double fLogicValueOnRadiusAxis, bool bDoScaling=true ) const;
+
+ /** Given a value in the angle axis scale range (e.g. [0,1] for pie charts)
+ * this method returns the related angle in degree.
+ */
+ double transformToAngleDegree( double fLogicValueOnAngleAxis, bool bDoScaling=true ) const;
+
+ /** Given 2 values in the angle axis scale range (e.g. [0,1] for pie charts)
+ * this method returns the angle between the 2 values keeping into account
+ * the correct axis orientation; (for instance, this method is used for
+ * computing the angle width of a pie slice).
+ */
+ double getWidthAngleDegree( double& fStartLogicValueOnAngleAxis, double& fEndLogicValueOnAngleAxis ) const;
+
+ virtual css::drawing::Position3D
+ transformLogicToScene( double fX, double fY, double fZ, bool bClip ) const override;
+ virtual css::drawing::Position3D
+ transformScaledLogicToScene( double fX, double fY, double fZ, bool bClip ) const override;
+ css::drawing::Position3D
+ transformAngleRadiusToScene( double fLogicValueOnAngleAxis, double fLogicValueOnRadiusAxis, double fLogicZ, bool bDoScaling=true ) const;
+
+ /** It returns the scene coordinates of the passed point: this point is
+ * described through a normalized cylindrical coordinate system.
+ * (For a pie chart the origin of the coordinate system is the pie center).
+ */
+ css::drawing::Position3D
+ transformUnitCircleToScene( double fUnitAngleDegree, double fUnitRadius, double fLogicZ ) const;
+
+ using PlottingPositionHelper::transformScaledLogicToScene;
+
+ double getOuterLogicRadius() const;
+
+ inline bool isMathematicalOrientationAngle() const;
+ inline bool isMathematicalOrientationRadius() const;
+public:
+ ///m_bSwapXAndY (inherited): by default the X axis (scale[0]) represents
+ ///the angle axis and the Y axis (scale[1]) represents the radius axis;
+ ///when this parameter is true, the opposite happens (this is the case for
+ ///pie charts).
+
+ ///Offset for radius axis in absolute logic scaled values (1.0 == 1 category)
+ ///For a donut, it represents the non-normalized inner radius (see notes for
+ ///transformToRadius)
+ double m_fRadiusOffset;
+ ///Offset for angle axis in real degree.
+ ///For a pie it represents the angle offset at which the first slice have to
+ ///start;
+ double m_fAngleDegreeOffset;
+
+private:
+ ::basegfx::B3DHomMatrix m_aUnitCartesianToScene;
+
+ ::basegfx::B3DHomMatrix impl_calculateMatrixUnitCartesianToScene( const ::basegfx::B3DHomMatrix& rMatrixScreenToScene ) const;
+};
+
+bool PolarPlottingPositionHelper::isMathematicalOrientationAngle() const
+{
+ const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[1] : m_aScales[2];
+ if( css::chart2::AxisOrientation_MATHEMATICAL==rScale.Orientation )
+ return true;
+ return false;
+}
+bool PolarPlottingPositionHelper::isMathematicalOrientationRadius() const
+{
+ const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[0] : m_aScales[1];
+ if( css::chart2::AxisOrientation_MATHEMATICAL==rScale.Orientation )
+ return true;
+ return false;
+}
+
+//better performance for big data
+void PlottingPositionHelper::setCoordinateSystemResolution( const css::uno::Sequence< sal_Int32 >& rCoordinateSystemResolution )
+{
+ m_nXResolution = 1000;
+ m_nYResolution = 1000;
+ m_nZResolution = 1000;
+ if( rCoordinateSystemResolution.getLength() > 0 )
+ m_nXResolution = rCoordinateSystemResolution[0];
+ if( rCoordinateSystemResolution.getLength() > 1 )
+ m_nYResolution = rCoordinateSystemResolution[1];
+ if( rCoordinateSystemResolution.getLength() > 2 )
+ m_nZResolution = rCoordinateSystemResolution[2];
+}
+
+bool PlottingPositionHelper::isSameForGivenResolution( double fX, double fY, double fZ
+ , double fX2, double fY2, double fZ2 /*these values are all expected tp be scaled already*/ )
+{
+ if( !std::isfinite(fX) || !std::isfinite(fY) || !std::isfinite(fZ)
+ || !std::isfinite(fX2) || !std::isfinite(fY2) || !std::isfinite(fZ2) )
+ return false;
+
+ double fScaledMinX = getLogicMinX();
+ double fScaledMinY = getLogicMinY();
+ double fScaledMinZ = getLogicMinZ();
+ double fScaledMaxX = getLogicMaxX();
+ double fScaledMaxY = getLogicMaxY();
+ double fScaledMaxZ = getLogicMaxZ();
+
+ doLogicScaling( &fScaledMinX, &fScaledMinY, &fScaledMinZ );
+ doLogicScaling( &fScaledMaxX, &fScaledMaxY, &fScaledMaxZ);
+
+ bool bSameX = ( static_cast<sal_Int32>(m_nXResolution*(fX - fScaledMinX)/(fScaledMaxX-fScaledMinX))
+ == static_cast<sal_Int32>(m_nXResolution*(fX2 - fScaledMinX)/(fScaledMaxX-fScaledMinX)) );
+
+ bool bSameY = ( static_cast<sal_Int32>(m_nYResolution*(fY - fScaledMinY)/(fScaledMaxY-fScaledMinY))
+ == static_cast<sal_Int32>(m_nYResolution*(fY2 - fScaledMinY)/(fScaledMaxY-fScaledMinY)) );
+
+ bool bSameZ = ( static_cast<sal_Int32>(m_nZResolution*(fZ - fScaledMinZ)/(fScaledMaxZ-fScaledMinZ))
+ == static_cast<sal_Int32>(m_nZResolution*(fZ2 - fScaledMinZ)/(fScaledMaxZ-fScaledMinZ)) );
+
+ return (bSameX && bSameY && bSameZ);
+}
+
+bool PlottingPositionHelper::isStrongLowerRequested( sal_Int32 nDimensionIndex ) const
+{
+ if( m_aScales.empty() )
+ return false;
+ if( 0==nDimensionIndex )
+ return m_bAllowShiftXAxisPos && m_aScales[nDimensionIndex].m_bShiftedCategoryPosition;
+ else if( 2==nDimensionIndex )
+ return m_bAllowShiftZAxisPos && m_aScales[nDimensionIndex].m_bShiftedCategoryPosition;
+ return false;
+}
+
+bool PlottingPositionHelper::isLogicVisible(
+ double fX, double fY, double fZ ) const
+{
+ return fX >= m_aScales[0].Minimum && ( isStrongLowerRequested(0) ? fX < m_aScales[0].Maximum : fX <= m_aScales[0].Maximum )
+ && fY >= m_aScales[1].Minimum && fY <= m_aScales[1].Maximum
+ && fZ >= m_aScales[2].Minimum && ( isStrongLowerRequested(2) ? fZ < m_aScales[2].Maximum : fZ <= m_aScales[2].Maximum );
+}
+
+void PlottingPositionHelper::doLogicScaling( double* pX, double* pY, double* pZ ) const
+{
+ if(pX)
+ {
+ if( m_aScales[0].Scaling.is())
+ *pX = m_aScales[0].Scaling->doScaling(*pX);
+ if( m_bAllowShiftXAxisPos && m_aScales[0].m_bShiftedCategoryPosition )
+ (*pX) += m_fScaledCategoryWidth/2.0;
+ }
+ if(pY && m_aScales[1].Scaling.is())
+ *pY = m_aScales[1].Scaling->doScaling(*pY);
+ if(pZ)
+ {
+ if( m_aScales[2].Scaling.is())
+ *pZ = m_aScales[2].Scaling->doScaling(*pZ);
+ if( m_bAllowShiftZAxisPos && m_aScales[2].m_bShiftedCategoryPosition)
+ (*pZ) += 0.5;
+ }
+}
+
+void PlottingPositionHelper::doUnshiftedLogicScaling( double* pX, double* pY, double* pZ ) const
+{
+ if(pX && m_aScales[0].Scaling.is())
+ *pX = m_aScales[0].Scaling->doScaling(*pX);
+ if(pY && m_aScales[1].Scaling.is())
+ *pY = m_aScales[1].Scaling->doScaling(*pY);
+ if(pZ && m_aScales[2].Scaling.is())
+ *pZ = m_aScales[2].Scaling->doScaling(*pZ);
+}
+
+void PlottingPositionHelper::doLogicScaling( css::drawing::Position3D& rPos ) const
+{
+ doLogicScaling( &rPos.PositionX, &rPos.PositionY, &rPos.PositionZ );
+}
+
+void PlottingPositionHelper::clipLogicValues( double* pX, double* pY, double* pZ ) const
+{
+ if(pX)
+ {
+ if( *pX < m_aScales[0].Minimum )
+ *pX = m_aScales[0].Minimum;
+ else if( *pX > m_aScales[0].Maximum )
+ *pX = m_aScales[0].Maximum;
+ }
+ if(pY)
+ {
+ if( *pY < m_aScales[1].Minimum )
+ *pY = m_aScales[1].Minimum;
+ else if( *pY > m_aScales[1].Maximum )
+ *pY = m_aScales[1].Maximum;
+ }
+ if(pZ)
+ {
+ if( *pZ < m_aScales[2].Minimum )
+ *pZ = m_aScales[2].Minimum;
+ else if( *pZ > m_aScales[2].Maximum )
+ *pZ = m_aScales[2].Maximum;
+ }
+}
+
+inline bool PlottingPositionHelper::clipYRange( double& rMin, double& rMax ) const
+{
+ //returns true if something remains
+ if( rMin > rMax )
+ {
+ double fHelp = rMin;
+ rMin = rMax;
+ rMax = fHelp;
+ }
+ if( rMin > getLogicMaxY() )
+ return false;
+ if( rMax < getLogicMinY() )
+ return false;
+ if( rMin < getLogicMinY() )
+ rMin = getLogicMinY();
+ if( rMax > getLogicMaxY() )
+ rMax = getLogicMaxY();
+ return true;
+}
+
+inline double PlottingPositionHelper::getLogicMinX() const
+{
+ return m_aScales[0].Minimum;
+}
+inline double PlottingPositionHelper::getLogicMinY() const
+{
+ return m_aScales[1].Minimum;
+}
+inline double PlottingPositionHelper::getLogicMinZ() const
+{
+ return m_aScales[2].Minimum;
+}
+
+inline double PlottingPositionHelper::getLogicMaxX() const
+{
+ return m_aScales[0].Maximum;
+}
+inline double PlottingPositionHelper::getLogicMaxY() const
+{
+ return m_aScales[1].Maximum;
+}
+inline double PlottingPositionHelper::getLogicMaxZ() const
+{
+ return m_aScales[2].Maximum;
+}
+inline bool PlottingPositionHelper::isMathematicalOrientationX() const
+{
+ return css::chart2::AxisOrientation_MATHEMATICAL == m_aScales[0].Orientation;
+}
+inline bool PlottingPositionHelper::isMathematicalOrientationY() const
+{
+ return css::chart2::AxisOrientation_MATHEMATICAL == m_aScales[1].Orientation;
+}
+inline bool PlottingPositionHelper::isMathematicalOrientationZ() const
+{
+ return css::chart2::AxisOrientation_MATHEMATICAL == m_aScales[2].Orientation;
+}
+inline bool PlottingPositionHelper::isSwapXAndY() const
+{
+ return m_bSwapXAndY;
+}
+inline bool PlottingPositionHelper::maySkipPointsInRegressionCalculation() const
+{
+ return m_bMaySkipPointsInRegressionCalculation;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/PolarLabelPositionHelper.hxx b/chart2/source/view/inc/PolarLabelPositionHelper.hxx
new file mode 100644
index 000000000..84f4ff1dc
--- /dev/null
+++ b/chart2/source/view/inc/PolarLabelPositionHelper.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include "LabelPositionHelper.hxx"
+#include <com/sun/star/awt/Point.hpp>
+
+namespace chart
+{
+
+class PolarPlottingPositionHelper;
+
+class PolarLabelPositionHelper final : public LabelPositionHelper
+{
+public:
+ PolarLabelPositionHelper(
+ PolarPlottingPositionHelper* pPosHelper
+ , sal_Int32 nDimensionCount
+ , const rtl::Reference<SvxShapeGroupAnyD>& xLogicTarget );
+ virtual ~PolarLabelPositionHelper() override;
+
+ css::awt::Point getLabelScreenPositionAndAlignmentForLogicValues(
+ LabelAlignment& rAlignment
+ , double fLogicValueOnAngleAxis
+ , double fLogicValueOnRadiusAxis
+ , double fLogicZ
+ , sal_Int32 nScreenValueOffsetInRadiusDirection ) const;
+
+ /** Calculate the anchor point position for a text label.
+ * When the requested label placement is of `INSIDE` or `OUTSIDE` type the
+ * returned anchor point for the text label is the middle point of the
+ * outer arc for the given slice; when the requested label placement is of
+ * `CENTER` type the returned anchor point for the text label is the
+ * middle point of the line segment bisecting the slice.
+ * The text alignment is always centered when the requested label
+ * placement is of `CENTER` type else it is dependent on the value of the
+ * angle defined by the horizontal axis and the ray bisecting the slice.
+ *
+ */
+ css::awt::Point getLabelScreenPositionAndAlignmentForUnitCircleValues(
+ LabelAlignment& rAlignment, sal_Int32 nLabelPlacement /*see css::chart::DataLabelPlacement*/
+ , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree
+ , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius
+ , double fLogicZ
+ , sal_Int32 nScreenValueOffsetInRadiusDirection ) const;
+
+private:
+ PolarPlottingPositionHelper* m_pPosHelper;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/PropertyMapper.hxx b/chart2/source/view/inc/PropertyMapper.hxx
new file mode 100644
index 000000000..c4d9a1fa2
--- /dev/null
+++ b/chart2/source/view/inc/PropertyMapper.hxx
@@ -0,0 +1,125 @@
+/* -*- 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 <sal/config.h>
+
+#include <unordered_map>
+
+#include <com/sun/star/uno/Sequence.h>
+#include <com/sun/star/uno/Reference.h>
+
+namespace com::sun::star::beans { class XPropertySet; }
+class SvxShape;
+
+namespace chart
+{
+
+typedef std::unordered_map<OUString, OUString> tPropertyNameMap;
+typedef std::unordered_map<OUString, css::uno::Any> tPropertyNameValueMap;
+typedef css::uno::Sequence< OUString > tNameSequence;
+typedef css::uno::Sequence< css::uno::Any > tAnySequence;
+
+/**
+ * PropertyMapper provides easy mapping of the property names of various
+ * objects in the chart model, to the property names of the destination
+ * shape objects (those whose service names begin with
+ * com.sun.star.drawing.).
+ */
+class PropertyMapper
+{
+public:
+ static void setMappedProperties(
+ const css::uno::Reference< css::beans::XPropertySet >& xTarget
+ , const css::uno::Reference< css::beans::XPropertySet >& xSource
+ , const tPropertyNameMap& rMap );
+
+ static void setMappedProperties(
+ SvxShape& xTarget
+ , const css::uno::Reference< css::beans::XPropertySet >& xSource
+ , const tPropertyNameMap& rMap );
+
+ /**
+ * Fetch property values from the source object and map it to the
+ * destination container. Only those properties that are explicitly set
+ * will be inserted into the destination container.
+ *
+ * @param rValueMap destination container
+ * @param rNameMap property name mapping rule
+ * @param xSourceProp source object from which the property values are
+ * pulled.
+ */
+ static void getValueMap(
+ tPropertyNameValueMap& rValueMap
+ , const tPropertyNameMap& rNameMap
+ , const css::uno::Reference< css::beans::XPropertySet >& xSourceProp
+ );
+
+ static void getMultiPropertyListsFromValueMap(
+ tNameSequence& rNames
+ , tAnySequence& rValues
+ , const tPropertyNameValueMap& rValueMap
+ );
+
+ static css::uno::Any*
+ getValuePointer( tAnySequence& rPropValues
+ , const tNameSequence& rPropNames
+ , std::u16string_view rPropName );
+
+ static css::uno::Any*
+ getValuePointerForLimitedSpace( tAnySequence& rPropValues
+ , const tNameSequence& rPropNames
+ , bool bLimitedHeight );
+
+ static void setMultiProperties(
+ const tNameSequence& rNames
+ , const tAnySequence& rValues
+ , SvxShape& xTarget );
+
+ static const tPropertyNameMap& getPropertyNameMapForCharacterProperties();
+ static const tPropertyNameMap& getPropertyNameMapForParagraphProperties();
+ static const tPropertyNameMap& getPropertyNameMapForFillProperties();
+ static const tPropertyNameMap& getPropertyNameMapForLineProperties();
+ static const tPropertyNameMap& getPropertyNameMapForFillAndLineProperties();
+ static const tPropertyNameMap& getPropertyNameMapForTextShapeProperties();
+
+ static const tPropertyNameMap& getPropertyNameMapForFilledSeriesProperties();
+ static const tPropertyNameMap& getPropertyNameMapForLineSeriesProperties();
+ static const tPropertyNameMap& getPropertyNameMapForTextLabelProperties();
+
+ static void getTextLabelMultiPropertyLists(
+ const css::uno::Reference< css::beans::XPropertySet >& xSourceProp
+ , tNameSequence& rPropNames, tAnySequence& rPropValues
+ , bool bName=true
+ , sal_Int32 nLimitedSpace=-1
+ , bool bLimitedHeight=false
+ , bool bSupportsLabelBorder = true);
+
+ /** adds line-, fill- and character properties and sets some suitable
+ defaults for auto-grow properties
+ */
+ static void getPreparedTextShapePropertyLists(
+ const css::uno::Reference< css::beans::XPropertySet >& xSourceProp
+ , tNameSequence& rPropNames
+ , tAnySequence& rPropValues );
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/ScaleAutomatism.hxx b/chart2/source/view/inc/ScaleAutomatism.hxx
new file mode 100644
index 000000000..1141c9e87
--- /dev/null
+++ b/chart2/source/view/inc/ScaleAutomatism.hxx
@@ -0,0 +1,144 @@
+/* -*- 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/ScaleData.hpp>
+
+#include <tools/date.hxx>
+
+namespace chart { struct ExplicitIncrementData; }
+namespace chart { struct ExplicitScaleData; }
+
+namespace chart
+{
+
+/** This class implements the calculation of automatic axis limits.
+ *
+ * This class is used for calculating axis scales and increments in the form
+ * of instances of `ExplicitScaleData` and `ExplicitIncrementData` classes.
+ * When a `ScaleAutomatism` instance is created a `ScaleData` object is passed
+ * to the constructor. Objects of `ScaleData` type are initialized by
+ * the `createCoordinateSystem` method of some chart type (e.g.
+ * the `PieChartType` class) and belong to some `Axis` object, they can be
+ * accessed through the `XAxis` interface (`XAxis::getScaleData`).
+ */
+class ScaleAutomatism
+{
+public:
+ explicit ScaleAutomatism(
+ const css::chart2::ScaleData& rSourceScale, const Date& rNullDate );
+
+ /** Expands own value range with the passed minimum and maximum.
+ *
+ * It allows to set up the `m_fValueMinimum` and the `m_fValueMaximum`
+ * parameters which are used by the `calculateExplicitScaleAndIncrement`
+ * method for initializing the `Minimum` and `Maximum` properties of the
+ * explicit scale when the same properties of the `ScaleData` object are
+ * undefined (that is empty `uno::Any` objects).
+ */
+ void expandValueRange( double fMinimum, double fMaximum );
+ void resetValueRange();
+
+ /** Sets additional auto scaling options.
+ @param bExpandBorderToIncrementRhythm If true, expands automatic
+ borders to the fixed or calculated increment rhythm.
+ @param bExpandIfValuesCloseToBorder If true, expands automatic borders
+ if values are too close (closer than 1/21 of visible area).
+ @param bExpandWideValuesToZero If true, expands automatic border to
+ zero, if source values are positive only or negative only, and if
+ the absolute values are wide spread (at least one value is less
+ than 5/6 of absolute maximum), or if all values are equal.
+ @param bExpandNarrowValuesTowardZero If true, expands automatic border
+ toward zero (50% of the visible range), if source values are
+ positive only or negative only, and if the absolute values are
+ close to the absolute maximum (no value is less than 5/6 of
+ absolute maximum). */
+ void setAutoScalingOptions(
+ bool bExpandBorderToIncrementRhythm,
+ bool bExpandIfValuesCloseToBorder,
+ bool bExpandWideValuesToZero,
+ bool bExpandNarrowValuesTowardZero );
+
+ /** Sets the maximum allowed number of automatic main increments.
+ @descr The number of main increments may be limited e.g. by the length
+ of the axis and the font size of the axis caption text. */
+ void setMaximumAutoMainIncrementCount( sal_Int32 nMaximumAutoMainIncrementCount );
+
+ /** Sets the time resolution to be used in case it is not set explicitly within the scale
+ */
+ void setAutomaticTimeResolution( sal_Int32 nTimeResolution );
+
+ /** Fills the passed scale data and increment data according to the own settings.
+ *
+ * It performs the initialization of the passed explicit scale and
+ * explicit increment parameters, mainly the initialization is achieved by
+ * using the `ScaleData` object as data source. However other parameters
+ * which affect the behavior of this method can be set through
+ * the `setAutoScalingOptions` and the `expandValueRange` methods.
+ */
+ void calculateExplicitScaleAndIncrement(
+ ExplicitScaleData& rExplicitScale,
+ ExplicitIncrementData& rExplicitIncrement ) const;
+
+ const css::chart2::ScaleData& getScale() const { return m_aSourceScale;}
+ const Date& getNullDate() const { return m_aNullDate;}
+
+private:
+ /** Fills the passed scale data and increment data for category scaling. */
+ void calculateExplicitIncrementAndScaleForCategory(
+ ExplicitScaleData& rExplicitScale,
+ ExplicitIncrementData& rExplicitIncrement,
+ bool bAutoMinimum, bool bAutoMaximum ) const;
+
+ /** Fills the passed scale data and increment data for logarithmic scaling. */
+ void calculateExplicitIncrementAndScaleForLogarithmic(
+ ExplicitScaleData& rExplicitScale,
+ ExplicitIncrementData& rExplicitIncrement,
+ bool bAutoMinimum, bool bAutoMaximum ) const;
+
+ /** Fills the passed scale data and increment data for linear scaling. */
+ void calculateExplicitIncrementAndScaleForLinear(
+ ExplicitScaleData& rExplicitScale,
+ ExplicitIncrementData& rExplicitIncrement,
+ bool bAutoMinimum, bool bAutoMaximum ) const;
+
+ /** Fills the passed scale data and increment data for date-time axis. */
+ void calculateExplicitIncrementAndScaleForDateTimeAxis(
+ ExplicitScaleData& rExplicitScale,
+ ExplicitIncrementData& rExplicitIncrement,
+ bool bAutoMinimum, bool bAutoMaximum ) const;
+
+private:
+ css::chart2::ScaleData m_aSourceScale;
+
+ double m_fValueMinimum; /// Minimum of all source values.
+ double m_fValueMaximum; /// Maximum of all source values.
+ sal_Int32 m_nMaximumAutoMainIncrementCount; /// Maximum number of automatic main increments.
+ bool m_bExpandBorderToIncrementRhythm; /// true = Expand to main increments.
+ bool m_bExpandIfValuesCloseToBorder; /// true = Expand if values are too close to the borders.
+ bool m_bExpandWideValuesToZero; /// true = Expand wide spread values to zero.
+ bool m_bExpandNarrowValuesTowardZero; /// true = Expand narrow range toward zero (add half of range).
+ sal_Int32 m_nTimeResolution;// a constant out of css::chart::TimeUnit
+
+ Date m_aNullDate;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/ShapeFactory.hxx b/chart2/source/view/inc/ShapeFactory.hxx
new file mode 100644
index 000000000..d6c05af04
--- /dev/null
+++ b/chart2/source/view/inc/ShapeFactory.hxx
@@ -0,0 +1,301 @@
+/* -*- 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 "PropertyMapper.hxx"
+#include <basegfx/range/b2irectangle.hxx>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/awt/Point.hpp>
+#include <com/sun/star/drawing/PointSequenceSequence.hpp>
+
+#include <rtl/ref.hxx>
+#include <rtl/ustring.hxx>
+#include <svx/unoshape.hxx>
+#include <svx/unodraw/SvxTableShape.hxx>
+#include <svx/unopage.hxx>
+
+namespace chart { struct VLineProperties; }
+namespace com::sun::star::beans { class XPropertySet; }
+namespace com::sun::star::chart2 { class XFormattedString; }
+namespace com::sun::star::drawing { class XShape; }
+namespace com::sun::star::drawing { class XShapes; }
+namespace com::sun::star::drawing { struct HomogenMatrix; }
+namespace com::sun::star::drawing { struct PolyPolygonShape3D; }
+namespace com::sun::star::drawing { struct Position3D; }
+namespace com::sun::star::graphic { class XGraphic; }
+namespace com::sun::star::lang { class XMultiServiceFactory; }
+namespace com::sun::star::drawing { struct Direction3D; }
+
+namespace chart
+{
+class Stripe;
+
+// Be careful here not to clash with the SYMBOL_FOO #defines in
+// <vcl/vclenum.hxx>
+enum SymbolEnum { Symbol_Square=0
+ , Symbol_Diamond
+ , Symbol_DownArrow
+ , Symbol_UpArrow
+ , Symbol_RightArrow
+ , Symbol_LeftArrow
+ , Symbol_Bowtie
+ , Symbol_Sandglass
+ , Symbol_Circle
+ , Symbol_Star
+ , Symbol_X
+ , Symbol_Plus
+ , Symbol_Asterisk
+ , Symbol_HorizontalBar
+ , Symbol_VerticalBar
+ , Symbol_COUNT
+};
+
+
+class ShapeFactory
+{
+public:
+ enum class StackPosition { Top, Bottom };
+
+ ShapeFactory() = delete;
+
+ static rtl::Reference< SvxShapeGroup >
+ createGroup2D(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const OUString& aName = OUString() );
+
+ static rtl::Reference< SvxShapeGroup >
+ createGroup2D(
+ const rtl::Reference<SvxDrawPage>& xTarget
+ , const OUString& aName = OUString() );
+
+ static rtl::Reference<Svx3DSceneObject>
+ createGroup3D(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const OUString& aName = OUString() );
+
+ static rtl::Reference<Svx3DExtrudeObject>
+ createCube( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPosition
+ , const css::drawing::Direction3D& rSize
+ , sal_Int32 nRotateZAngleHundredthDegree
+ , const css::uno::Reference< css::beans::XPropertySet >& xSourceProp
+ , const tPropertyNameMap& rPropertyNameMap
+ , bool bRounded = false);
+
+ static rtl::Reference<Svx3DLatheObject>
+ createCylinder( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPosition
+ , const css::drawing::Direction3D& rSize
+ , sal_Int32 nRotateZAngleHundredthDegree );
+
+ static rtl::Reference<Svx3DSceneObject>
+ createPyramid( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPosition
+ , const css::drawing::Direction3D& rSize
+ , double fTopHeight
+ , bool bRotateZ
+ , const css::uno::Reference< css::beans::XPropertySet >& xSourceProp
+ , const tPropertyNameMap& rPropertyNameMap);
+
+ static rtl::Reference<Svx3DLatheObject>
+ createCone( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPosition
+ , const css::drawing::Direction3D& rSize
+ , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree );
+
+ static rtl::Reference<SvxShapePolyPolygon>
+ createPieSegment2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree
+ , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius
+ , const css::drawing::Direction3D& rOffset
+ , const css::drawing::HomogenMatrix& rUnitCircleToScene );
+
+ static rtl::Reference<Svx3DExtrudeObject>
+ createPieSegment( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree
+ , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius
+ , const css::drawing::Direction3D& rOffset
+ , const css::drawing::HomogenMatrix& rUnitCircleToScene
+ , double fDepth );
+
+ static rtl::Reference<Svx3DPolygonObject>
+ createStripe( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const Stripe& rStripe
+ , const css::uno::Reference< css::beans::XPropertySet >& xSourceProp
+ , const tPropertyNameMap& rPropertyNameMap
+ , bool bDoubleSided
+ , short nRotatedTexture = 0 //0 to 7 are the different possibilities
+ , bool bFlatNormals=true );
+
+ static rtl::Reference<Svx3DExtrudeObject>
+ createArea3D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const std::vector<std::vector<css::drawing::Position3D>>& rPolyPolygon
+ , double fDepth);
+
+ static rtl::Reference<SvxShapePolyPolygon>
+ createArea2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const std::vector<std::vector<css::drawing::Position3D>>& rPolyPolygon);
+
+ static rtl::Reference<SvxShapePolyPolygon>
+ createSymbol2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPos
+ , const css::drawing::Direction3D& rSize
+ , sal_Int32 nStandardSymbol
+ , sal_Int32 nBorderColor
+ , sal_Int32 nFillColor );
+
+ static rtl::Reference<SvxGraphicObject>
+ createGraphic2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPos
+ , const css::drawing::Direction3D& rSize
+ , const css::uno::Reference< css::graphic::XGraphic >& xGraphic );
+
+ static rtl::Reference<SvxShapePolyPolygon>
+ createLine2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::PointSequenceSequence& rPoints
+ , const VLineProperties* pLineProperties = nullptr );
+ static rtl::Reference<SvxShapePolyPolygon>
+ createLine2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const std::vector<std::vector<css::drawing::Position3D>>& rPoints
+ , const VLineProperties* pLineProperties = nullptr );
+
+ static rtl::Reference<SvxShapePolyPolygon>
+ createLine ( const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const css::awt::Size& rSize, const css::awt::Point& rPosition );
+
+ static rtl::Reference<Svx3DPolygonObject>
+ createLine3D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const std::vector<std::vector<css::drawing::Position3D>>& rPoints
+ , const VLineProperties& rLineProperties );
+
+ static rtl::Reference<SvxShapeCircle>
+ createCircle2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPos
+ , const css::drawing::Direction3D& rSize );
+
+ static rtl::Reference<SvxShapeCircle>
+ createCircle( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::awt::Size& rSize
+ , const css::awt::Point& rPosition );
+
+ static rtl::Reference<SvxShapeText>
+ createText( const rtl::Reference<SvxShapeGroupAnyD>& xTarget2D
+ , const OUString& rText
+ , const tNameSequence& rPropNames
+ , const tAnySequence& rPropValues
+ , const css::uno::Any& rATransformation
+ );
+
+ static rtl::Reference<SvxShapeText>
+ createText(const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::uno::Sequence< css::uno::Reference< css::chart2::XFormattedString > >& xFormattedString
+ , const tNameSequence& rPropNames
+ , const tAnySequence& rPropValues
+ , const css::uno::Any& rATransformation);
+
+ static rtl::Reference<SvxShapeText>
+ createText( const rtl::Reference<SvxShapeGroupAnyD>& xTarget2D,
+ const css::awt::Size& rSize,
+ const css::awt::Point& rPosition,
+ css::uno::Sequence< css::uno::Reference< css::chart2::XFormattedString > >& xFormattedString,
+ const css::uno::Reference< css::beans::XPropertySet > & xTextProperties,
+ double nRotation, const OUString& aName, sal_Int32 nTextMaxWidth );
+
+ static rtl::Reference<SvxTableShape> createTable(rtl::Reference<SvxShapeGroupAnyD> const& xTarget);
+
+ static rtl::Reference<SvxShapeRect>
+ createInvisibleRectangle(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::awt::Size& rSize );
+
+ static rtl::Reference<SvxShapeRect>
+ createRectangle(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const css::awt::Size& rSize,
+ const css::awt::Point& rPosition,
+ const tNameSequence& rPropNames,
+ const tAnySequence& rPropValues,
+ StackPosition ePos = StackPosition::Top );
+
+ static rtl::Reference<SvxShapeRect>
+ createRectangle(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget );
+
+ static rtl::Reference<SvxShapeGroupAnyD>
+ getOrCreateChartRootShape( const rtl::Reference<SvxDrawPage>& xPage );
+
+ static void setPageSize(const rtl::Reference<SvxShapeGroupAnyD>& xChartShapes,
+ const css::awt::Size& rSize);
+
+ static rtl::Reference<SvxShapeGroupAnyD>
+ getChartRootShape( const rtl::Reference<SvxDrawPage>& xPage );
+
+ static void makeShapeInvisible( const rtl::Reference< SvxShape >& rShape );
+
+ static void setShapeName( const rtl::Reference< SvxShape >& xShape
+ , const OUString& rName );
+
+ static OUString getShapeName( const css::uno::Reference< css::drawing::XShape >& xShape );
+
+ static css::uno::Any makeTransformation( const css::awt::Point& rScreenPosition2D, double fRotationAnglePi=0.0 );
+
+ static OUString getStackedString( const OUString& rString, bool bStacked );
+
+ static bool hasPolygonAnyLines( const std::vector<std::vector<css::drawing::Position3D>>& rPoly );
+ static bool isPolygonEmptyOrSinglePoint( const css::drawing::PolyPolygonShape3D& rPoly );
+ static bool isPolygonEmptyOrSinglePoint( const std::vector<std::vector<css::drawing::Position3D>>& rPoly );
+ static void closePolygon( css::drawing::PolyPolygonShape3D& rPoly );
+ static void closePolygon( std::vector<std::vector<css::drawing::Position3D>>& rPoly );
+
+ static css::awt::Size calculateNewSizeRespectingAspectRatio(
+ const css::awt::Size& rTargetSize
+ , const css::awt::Size& rSourceSizeWithCorrectAspectRatio );
+
+ static css::awt::Point calculateTopLeftPositionToCenterObject(
+ const css::awt::Point& rTargetAreaPosition
+ , const css::awt::Size& rTargetAreaSize
+ , const css::awt::Size& rObjectSize );
+
+ static ::basegfx::B2IRectangle getRectangleOfShape( SvxShape& rShape );
+
+ static css::awt::Size getSizeAfterRotation(
+ SvxShape& rShape, double fRotationAngleDegree );
+
+ static void removeSubShapes( const rtl::Reference<SvxShapeGroupAnyD>& xShapes );
+
+ static sal_Int32 getSymbolCount() { return Symbol_COUNT; }
+
+private:
+ static rtl::Reference<Svx3DExtrudeObject>
+ impl_createCube( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPosition
+ , const css::drawing::Direction3D& rSize, sal_Int32 nRotateZAngleHundredthDegree
+ , bool bRounded );
+
+ static rtl::Reference<Svx3DLatheObject>
+ impl_createConeOrCylinder( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D& rPosition
+ , const css::drawing::Direction3D& rSize
+ , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree
+ , bool bCylinder);
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/Stripe.hxx b/chart2/source/view/inc/Stripe.hxx
new file mode 100644
index 000000000..0da5e0b5d
--- /dev/null
+++ b/chart2/source/view/inc/Stripe.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 <com/sun/star/drawing/Position3D.hpp>
+#include <com/sun/star/drawing/Direction3D.hpp>
+#include <com/sun/star/uno/Any.h>
+
+namespace chart
+{
+
+/** A Stripe represents a 2 dimensional foursquare plane in a 3 dimensional room.
+
+@todo could: it is not necessary to have 4 point members here; it would be sufficient to have one point and 2 directions
+*/
+
+class Stripe
+{
+public:
+ Stripe( const css::drawing::Position3D& rPoint1
+ , const css::drawing::Direction3D& rDirectionToPoint2
+ , const css::drawing::Direction3D& rDirectionToPoint4 );
+
+ Stripe( const css::drawing::Position3D& rPoint1
+ , const css::drawing::Position3D& rPoint2
+ , double fDepth );
+
+ Stripe( const css::drawing::Position3D& rPoint1
+ , const css::drawing::Position3D& rPoint2
+ , const css::drawing::Position3D& rPoint3
+ , const css::drawing::Position3D& rPoint4 );
+
+ void SetManualNormal( const css::drawing::Direction3D& rNormal );
+ css::drawing::Direction3D getNormal() const;
+
+ void InvertNormal( bool bInvertNormal );
+
+ css::uno::Any getPolyPolygonShape3D() const;
+ css::uno::Any getNormalsPolygon() const;
+ static css::uno::Any getTexturePolygon( short nRotatedTexture ); //0 to 7 are the different possibilities
+
+private:
+ css::drawing::Position3D m_aPoint1;
+ css::drawing::Position3D m_aPoint2;
+ css::drawing::Position3D m_aPoint3;
+ css::drawing::Position3D m_aPoint4;
+
+ bool m_bInvertNormal;
+ bool m_bManualNormalSet;
+ css::drawing::Direction3D m_aManualNormal;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/VCoordinateSystem.hxx b/chart2/source/view/inc/VCoordinateSystem.hxx
new file mode 100644
index 000000000..0664a5462
--- /dev/null
+++ b/chart2/source/view/inc/VCoordinateSystem.hxx
@@ -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 .
+ */
+#pragma once
+
+#include "MinimumAndMaximumSupplier.hxx"
+#include <ThreeDHelper.hxx>
+#include <chartview/ExplicitScaleValues.hxx>
+#include <com/sun/star/drawing/HomogenMatrix.hpp>
+#include <com/sun/star/uno/Sequence.h>
+#include <rtl/ref.hxx>
+#include <svx/unoshape.hxx>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace chart { class ExplicitCategoriesProvider; }
+namespace chart { class ScaleAutomatism; }
+namespace chart { class ChartModel; }
+namespace com::sun::star::awt { struct Rectangle; }
+namespace com::sun::star::awt { struct Size; }
+namespace com::sun::star::beans { class XPropertySet; }
+namespace com::sun::star::chart2 { class XAxis; }
+namespace com::sun::star::chart2 { class XChartDocument; }
+namespace com::sun::star::chart2 { class XCoordinateSystem; }
+namespace com::sun::star::drawing { class XShapes; }
+namespace com::sun::star::lang { class XMultiServiceFactory; }
+
+
+namespace chart
+{
+class Axis;
+class BaseCoordinateSystem;
+class VAxisBase;
+
+class VCoordinateSystem
+{
+public:
+ virtual ~VCoordinateSystem();
+
+ static std::unique_ptr<VCoordinateSystem> createCoordinateSystem( const rtl::Reference<
+ ::chart::BaseCoordinateSystem >& xCooSysModel );
+
+ /// @throws css::uno::RuntimeException
+ void initPlottingTargets(
+ const rtl::Reference< SvxShapeGroupAnyD >& xLogicTarget
+ , const rtl::Reference< SvxShapeGroupAnyD >& xFinalTarget
+ , rtl::Reference<SvxShapeGroupAnyD>& xLogicTargetForSeriesBehindAxis );
+
+ void setParticle( const OUString& rCooSysParticle );
+
+ void setTransformationSceneToScreen( const css::drawing::HomogenMatrix& rMatrix );
+ const css::drawing::HomogenMatrix& getTransformationSceneToScreen() const { return m_aMatrixSceneToScreen;}
+
+ //better performance for big data
+ virtual css::uno::Sequence< sal_Int32 > getCoordinateSystemResolution( const css::awt::Size& rPageSize
+ , const css::awt::Size& rPageResolution );
+
+ ExplicitScaleData getExplicitScale( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) const;
+ ExplicitIncrementData getExplicitIncrement( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) const;
+
+ void setExplicitCategoriesProvider( ExplicitCategoriesProvider* /*takes ownership*/ );
+ ExplicitCategoriesProvider* getExplicitCategoriesProvider();
+
+ // returns a complete scale set for a given dimension and index; for example if nDimensionIndex==1 and nAxisIndex==2 you get returned the secondary x axis, main y axis and main z axis
+ std::vector< ExplicitScaleData > getExplicitScales( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) const;
+ // returns a complete increment set for a given dimension and index; for example if nDimensionIndex==1 and nAxisIndex==2 you get returned the secondary x axis, main y axis and main z axis
+ std::vector< ExplicitIncrementData > getExplicitIncrements( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) const;
+
+ void addMinimumAndMaximumSupplier( MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier );
+ bool hasMinimumAndMaximumSupplier( MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier );
+ void clearMinimumAndMaximumSupplierList();
+
+ /**
+ * It sets the scaling parameters for the passed `ScaleAutomatism` object.
+ * Especially it sets the `m_fValueMinimum` and the `m_fValueMaximum`
+ * parameters (see `ScaleAutomatism::expandValueRange`).
+ * The value to be assigned to these two parameters is retrieved by
+ * invoking the `getMinimum` and `getMaximum` methods of the minimum-maximum
+ * supplier object that belongs to the given coordinate system.
+ * The minimum-maximum supplier object is set in the
+ * `SeriesPlotterContainer::initializeCooSysAndSeriesPlotter` method to the
+ * series plotter which is based on the coordinate system (see notes for
+ * the method). For instance for a pie chart the `m_fValueMinimum` and the
+ * `m_fValueMaximum` parameters are initialized by the `PieChart::getMinimum`
+ * and `PieChart::getMaximum` methods.
+ */
+ void prepareAutomaticAxisScaling( ScaleAutomatism& rScaleAutomatism, sal_Int32 nDimIndex, sal_Int32 nAxisIndex );
+
+ void setExplicitScaleAndIncrement( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex
+ , const ExplicitScaleData& rExplicitScale
+ , const ExplicitIncrementData& rExplicitIncrement );
+
+ void set3DWallPositions( CuboidPlanePosition eLeftWallPos, CuboidPlanePosition eBackWallPos, CuboidPlanePosition eBottomPos );
+
+ const rtl::Reference< ::chart::BaseCoordinateSystem >&
+ getModel() const { return m_xCooSysModel;}
+
+ /**
+ * Create "view" axis objects 'VAxis' from the coordinate system model.
+ */
+ virtual void createVAxisList(
+ const rtl::Reference<::chart::ChartModel> & xChartDoc
+ , const css::awt::Size& rFontReferenceSize
+ , const css::awt::Rectangle& rMaximumSpaceForLabels
+ , bool bLimitSpaceForLabels );
+
+ virtual void initVAxisInList();
+ virtual void updateScalesAndIncrementsOnAxes();
+
+ void createMaximumAxesLabels();
+ void createAxesLabels();
+ void updatePositions();
+ void createAxesShapes();
+
+ virtual void createGridShapes();
+
+ bool getPropertySwapXAndYAxis() const;
+
+ sal_Int32 getMaximumAxisIndexByDimension( sal_Int32 nDimensionIndex ) const;
+
+ bool needSeriesNamesForAxis() const;
+ void setSeriesNamesForAxis( const css::uno::Sequence< OUString >& rSeriesNames );
+
+protected: //methods
+ VCoordinateSystem( const rtl::Reference< ::chart::BaseCoordinateSystem >& xCooSys );
+
+ rtl::Reference< ::chart::Axis >
+ getAxisByDimension( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) const;
+ static std::vector< css::uno::Reference< css::beans::XPropertySet > >
+ getGridListFromAxis( const rtl::Reference< ::chart::Axis >& xAxis );
+
+ VAxisBase* getVAxis( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex );
+
+ OUString createCIDForAxis( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex );
+ OUString createCIDForGrid( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex );
+
+ sal_Int32 getNumberFormatKeyForAxis( const rtl::Reference< ::chart::Axis >& xAxis
+ , const rtl::Reference<::chart::ChartModel>& xChartDoc);
+
+private: //methods
+ static void impl_adjustDimension( sal_Int32& rDimensionIndex );
+ void impl_adjustDimensionAndIndex( sal_Int32& rDimensionIndex, sal_Int32& rAxisIndex ) const;
+
+protected: //member
+ rtl::Reference< ::chart::BaseCoordinateSystem > m_xCooSysModel;
+
+ OUString m_aCooSysParticle;
+
+ typedef std::pair< sal_Int32, sal_Int32 > tFullAxisIndex; //first index is the dimension, second index is the axis index that indicates whether this is a main or secondary axis
+
+ rtl::Reference<SvxShapeGroupAnyD> m_xLogicTargetForGrids;
+ rtl::Reference<SvxShapeGroupAnyD> m_xLogicTargetForAxes;
+ rtl::Reference<SvxShapeGroupAnyD> m_xFinalTarget;
+ css::drawing::HomogenMatrix m_aMatrixSceneToScreen;
+
+ CuboidPlanePosition m_eLeftWallPos;
+ CuboidPlanePosition m_eBackWallPos;
+ CuboidPlanePosition m_eBottomPos;
+
+ /**
+ * Collection of min-max suppliers which are basically different chart
+ * types present in the same coordinate system. This is used only for
+ * auto-scaling purposes.
+ */
+ MergedMinimumAndMaximumSupplier m_aMergedMinMaxSupplier;
+
+ css::uno::Sequence< OUString > m_aSeriesNamesForZAxis;
+
+ typedef std::map< tFullAxisIndex, std::shared_ptr< VAxisBase > > tVAxisMap;
+
+ tVAxisMap m_aAxisMap;
+
+private:
+ std::vector< ExplicitScaleData > m_aExplicitScales;
+ std::vector< ExplicitIncrementData > m_aExplicitIncrements;
+
+ typedef std::map< tFullAxisIndex, ExplicitScaleData > tFullExplicitScaleMap;
+ typedef std::map< tFullAxisIndex, ExplicitIncrementData > tFullExplicitIncrementMap;
+
+ tFullExplicitScaleMap m_aSecondaryExplicitScales;
+ tFullExplicitIncrementMap m_aSecondaryExplicitIncrements;
+
+ std::unique_ptr< ExplicitCategoriesProvider > m_apExplicitCategoriesProvider;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/VDataSeries.hxx b/chart2/source/view/inc/VDataSeries.hxx
new file mode 100644
index 000000000..29a71918b
--- /dev/null
+++ b/chart2/source/view/inc/VDataSeries.hxx
@@ -0,0 +1,267 @@
+/* -*- 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 "PropertyMapper.hxx"
+
+#include <com/sun/star/chart2/StackingDirection.hpp>
+#include <com/sun/star/drawing/Position3D.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/awt/Point.hpp>
+#include <rtl/ref.hxx>
+#include <svx/unoshape.hxx>
+
+#include <memory>
+#include <map>
+
+namespace com::sun::star::beans { class XPropertySet; }
+namespace com::sun::star::chart2 { class XChartType; }
+namespace com::sun::star::chart2 { class XDataSeries; }
+namespace com::sun::star::chart2::data { class XDataSequence; }
+namespace com::sun::star::chart2 { struct DataPointLabel; }
+namespace com::sun::star::chart2 { struct Symbol; }
+namespace com::sun::star::drawing { class XShapes; }
+
+namespace chart
+{
+class ChartType;
+class DataSeries;
+
+class VDataSequence
+{
+public:
+ void init( const css::uno::Reference<css::chart2::data::XDataSequence>& xModel );
+ bool is() const;
+ void clear();
+ double getValue( sal_Int32 index ) const;
+ sal_Int32 detectNumberFormatKey( sal_Int32 index ) const;
+ sal_Int32 getLength() const;
+
+ css::uno::Reference<css::chart2::data::XDataSequence> m_xModel;
+ mutable css::uno::Sequence<double> m_aValues;
+};
+
+class VDataSeries final
+{
+public:
+ VDataSeries( const rtl::Reference<::chart::DataSeries>& xDataSeries );
+
+ ~VDataSeries();
+
+ VDataSeries(const VDataSeries&) = delete;
+ const VDataSeries& operator=(const VDataSeries&) = delete;
+
+ const rtl::Reference<::chart::DataSeries>& getModel() const;
+
+ void setCategoryXAxis();
+ void setXValues( const css::uno::Reference<css::chart2::data::XDataSequence>& xValues );
+ void setXValuesIfNone( const css::uno::Reference<css::chart2::data::XDataSequence>& xValues );
+ void setParticle( const OUString& rSeriesParticle );
+ void setGlobalSeriesIndex( sal_Int32 nGlobalSeriesIndex );
+ void setPageReferenceSize( const css::awt::Size & rPageRefSize );
+
+ sal_Int32 getTotalPointCount() const { return m_nPointCount;}
+ double getXValue( sal_Int32 index ) const;
+ double getYValue( sal_Int32 index ) const;
+
+ void getMinMaxXValue( double& fMin, double& fMax ) const;
+
+ double getY_Min( sal_Int32 index ) const;
+ double getY_Max( sal_Int32 index ) const;
+ double getY_First( sal_Int32 index ) const;
+ double getY_Last( sal_Int32 index ) const;
+
+ double getBubble_Size( sal_Int32 index ) const;
+
+ double getMinimumofAllDifferentYValues( sal_Int32 index ) const;
+ double getMaximumofAllDifferentYValues( sal_Int32 index ) const;
+
+ double getValueByProperty( sal_Int32 index, const OUString& rPropName ) const;
+
+ bool hasPropertyMapping( const OUString& rPropName ) const;
+
+ css::uno::Sequence< double > const & getAllX() const;
+ css::uno::Sequence< double > const & getAllY() const;
+
+ double getXMeanValue() const;
+ double getYMeanValue() const;
+
+ bool hasExplicitNumberFormat( sal_Int32 nPointIndex, bool bForPercentage ) const;
+ sal_Int32 getExplicitNumberFormat( sal_Int32 nPointIndex, bool bForPercentage ) const;
+ sal_Int32 detectNumberFormatKey( sal_Int32 nPointIndex ) const;
+
+ sal_Int32 getLabelPlacement(
+ sal_Int32 nPointIndex, const rtl::Reference<::chart::ChartType>& xChartType,
+ bool bSwapXAndY ) const;
+
+ css::awt::Point getLabelPosition( css::awt::Point aTextShapePos, sal_Int32 nPointIndex ) const;
+ bool isLabelCustomPos( sal_Int32 nPointIndex ) const;
+
+ css::uno::Reference<css::beans::XPropertySet> getPropertiesOfPoint( sal_Int32 index ) const;
+
+ const css::uno::Reference<css::beans::XPropertySet> & getPropertiesOfSeries() const;
+
+ css::chart2::Symbol* getSymbolProperties( sal_Int32 index ) const;
+
+ css::uno::Reference<css::beans::XPropertySet> getXErrorBarProperties( sal_Int32 index ) const;
+
+ css::uno::Reference<css::beans::XPropertySet> getYErrorBarProperties( sal_Int32 index ) const;
+
+ bool hasPointOwnColor( sal_Int32 index ) const;
+
+ css::chart2::StackingDirection getStackingDirection() const;
+ sal_Int32 getAttachedAxisIndex() const;
+ void setAttachedAxisIndex( sal_Int32 nAttachedAxisIndex );
+
+ void doSortByXValues();
+
+ void setConnectBars( bool bConnectBars );
+ bool getConnectBars() const;
+
+ void setGroupBarsPerAxis( bool bGroupBarsPerAxis );
+ bool getGroupBarsPerAxis() const;
+
+ void setStartingAngle( sal_Int32 nStartingAngle );
+ sal_Int32 getStartingAngle() const;
+
+ void setRoleOfSequenceForDataLabelNumberFormatDetection( std::u16string_view rRole );
+
+ //this is only temporarily here for area chart:
+ std::vector<std::vector<css::drawing::Position3D>> m_aPolyPolygonShape3D;
+ sal_Int32 m_nPolygonIndex;
+ double m_fLogicMinX;
+ double m_fLogicMaxX;
+
+ //this is here for deep stacking:
+ double m_fLogicZPos;//from 0 to series count -1
+
+ const OUString& getCID() const { return m_aCID;}
+ const OUString& getSeriesParticle() const { return m_aSeriesParticle;}
+ const OUString& getPointCID_Stub() const { return m_aPointCID_Stub;}
+ OUString getErrorBarsCID( bool bYError ) const;
+ OUString getLabelsCID() const;
+ const OUString& getLabelCID_Stub() const { return m_aLabelCID_Stub;}
+ OUString getDataCurveCID( sal_Int32 nCurveIndex, bool bAverageLine ) const;
+
+ css::chart2::DataPointLabel* getDataPointLabelIfLabel( sal_Int32 index ) const;
+ bool getTextLabelMultiPropertyLists( sal_Int32 index, tNameSequence*& pPropNames, tAnySequence*& pPropValues ) const;
+
+ OUString getDataCurveEquationCID( sal_Int32 nCurveIndex ) const;
+ bool isAttributedDataPoint( sal_Int32 index ) const;
+
+ bool isVaryColorsByPoint() const;
+
+ void releaseShapes();
+
+ void setMissingValueTreatment( sal_Int32 nMissingValueTreatment );
+ sal_Int32 getMissingValueTreatment() const;
+
+ void setOldTimeBased( VDataSeries* pOldSeries, double nPercent );
+ VDataSeries* createCopyForTimeBased() const;
+
+private: //methods
+ css::chart2::DataPointLabel* getDataPointLabel( sal_Int32 index ) const;
+ void adaptPointCache( sal_Int32 nNewPointIndex ) const;
+
+ // for copies for time based charting
+ VDataSeries();
+
+public: //member
+ rtl::Reference<SvxShapeGroupAnyD> m_xGroupShape;
+ rtl::Reference<SvxShapeGroup> m_xLabelsGroupShape;
+ rtl::Reference<SvxShapeGroupAnyD> m_xErrorXBarsGroupShape;
+ rtl::Reference<SvxShapeGroupAnyD> m_xErrorYBarsGroupShape;
+
+ //the following group shapes will be created as children of m_xGroupShape on demand
+ //they can be used to assure that some parts of a series shape are always in front of others (e.g. symbols in front of lines)
+ rtl::Reference<SvxShapeGroupAnyD> m_xFrontSubGroupShape;
+ rtl::Reference<SvxShapeGroupAnyD> m_xBackSubGroupShape;
+
+private: //member
+ rtl::Reference<::chart::DataSeries> m_xDataSeries;
+ css::uno::Reference<css::beans::XPropertySet> m_xDataSeriesProps; // cached
+
+ //all points given by the model data (here are not only the visible points meant)
+ sal_Int32 m_nPointCount;
+
+ VDataSequence m_aValues_X;
+ VDataSequence m_aValues_Y;
+ VDataSequence m_aValues_Z;
+
+ VDataSequence m_aValues_Y_Min;
+ VDataSequence m_aValues_Y_Max;
+ VDataSequence m_aValues_Y_First;
+ VDataSequence m_aValues_Y_Last;
+
+ VDataSequence m_aValues_Bubble_Size;
+
+ VDataSequence* m_pValueSequenceForDataLabelNumberFormatDetection;
+
+ std::map<OUString, VDataSequence> m_PropertyMap;
+
+ mutable double m_fXMeanValue;
+ mutable double m_fYMeanValue;
+
+ css::uno::Sequence<sal_Int32> m_aAttributedDataPointIndexList;
+
+ css::chart2::StackingDirection m_eStackingDirection;
+
+ sal_Int32 m_nAxisIndex;//indicates whether this is attached to a main or secondary axis
+
+ bool m_bConnectBars;
+
+ bool m_bGroupBarsPerAxis;
+
+ sal_Int32 m_nStartingAngle;
+
+ OUString m_aSeriesParticle;
+ OUString m_aCID;
+ OUString m_aPointCID_Stub;
+ OUString m_aLabelCID_Stub;
+
+ sal_Int32 m_nGlobalSeriesIndex;
+
+ //some cached values for data labels as they are very expensive
+ mutable std::unique_ptr<css::chart2::DataPointLabel>
+ m_apLabel_Series;
+ mutable std::unique_ptr<tNameSequence> m_apLabelPropNames_Series;
+ mutable std::unique_ptr<tAnySequence> m_apLabelPropValues_Series;
+ mutable std::unique_ptr<css::chart2::Symbol> m_apSymbolProperties_Series;
+
+ mutable std::unique_ptr<css::chart2::DataPointLabel>
+ m_apLabel_AttributedPoint;
+ mutable std::unique_ptr<tNameSequence> m_apLabelPropNames_AttributedPoint;
+ mutable std::unique_ptr<tAnySequence> m_apLabelPropValues_AttributedPoint;
+ mutable std::unique_ptr<css::chart2::Symbol> m_apSymbolProperties_AttributedPoint;
+ mutable std::unique_ptr<css::chart2::Symbol>
+ m_apSymbolProperties_InvisibleSymbolForSelection;
+ mutable sal_Int32 m_nCurrentAttributedPoint;
+ css::awt::Size m_aReferenceSize;
+
+ sal_Int32 m_nMissingValueTreatment;
+ bool m_bAllowPercentValueInDataLabel;
+
+ // for time based charting
+ VDataSeries* mpOldSeries;
+ double mnPercent;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/VDiagram.hxx b/chart2/source/view/inc/VDiagram.hxx
new file mode 100644
index 000000000..ab391f7bc
--- /dev/null
+++ b/chart2/source/view/inc/VDiagram.hxx
@@ -0,0 +1,117 @@
+/* -*- 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 <basegfx/range/b2irectangle.hxx>
+#include <com/sun/star/drawing/Direction3D.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/awt/Point.hpp>
+#include <svx/unoshape.hxx>
+#include <rtl/ref.hxx>
+
+namespace com::sun::star::beans { class XPropertySet; }
+namespace com::sun::star::chart2 { class XDiagram; }
+namespace com::sun::star::lang { class XMultiServiceFactory; }
+namespace com::sun::star::drawing { class XShape; }
+
+
+namespace chart
+{
+class Diagram;
+class ShapeFactory;
+
+/** The VDiagram is responsible to generate the visible parts of the Diagram
+that is wall, floor, axes and data series.
+The axes and data series are subobjects which are created and managed by the
+diagram.
+*/
+
+class VDiagram final
+{
+public: //methods
+ VDiagram( const rtl::Reference<::chart::Diagram>& xDiagram,
+ const css::drawing::Direction3D& rPreferredAspectRatio,
+ sal_Int32 nDimension );
+ ~VDiagram();
+
+ void init(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget );
+
+ void createShapes( const css::awt::Point& rPos
+ , const css::awt::Size& rSize );
+
+ const rtl::Reference<SvxShapeGroupAnyD> &
+ getCoordinateRegion() const { return m_xCoordinateRegionShape; }
+
+ /**
+ * Get current bounding rectangle for the diagram without axes.
+ */
+ basegfx::B2IRectangle getCurrentRectangle() const;
+
+ void reduceToMinimumSize();
+
+ ::basegfx::B2IRectangle adjustPosAndSize( const css::awt::Point& rPos
+ , const css::awt::Size& rAvailableSize );
+
+ ::basegfx::B2IRectangle adjustInnerSize( const ::basegfx::B2IRectangle& rConsumedOuterRect );
+
+private: //methods
+ void createShapes_2d();
+ void createShapes_3d();
+
+ ::basegfx::B2IRectangle adjustPosAndSize_2d( const css::awt::Point& rPos
+ , const css::awt::Size& rAvailableSize );
+ ::basegfx::B2IRectangle adjustPosAndSize_3d( const css::awt::Point& rPos
+ , const css::awt::Size& rAvailableSize );
+
+ void adjustAspectRatio3d( const css::awt::Size& rAvailableSize );
+
+private: //members
+ VDiagram(const VDiagram& rD) = delete;
+
+ rtl::Reference<SvxShapeGroupAnyD> m_xTarget;
+
+ // this is the surrounding shape which contains floor, wall and coordinate
+ rtl::Reference<SvxShapeGroupAnyD> m_xOuterGroupShape;
+ // this is an additional inner shape that represents the coordinate region - that is - where to place data points
+ rtl::Reference<SvxShapeGroupAnyD> m_xCoordinateRegionShape;
+ rtl::Reference<SvxShapeRect> m_xWall2D;
+
+ sal_Int32 m_nDimensionCount;
+ rtl::Reference< ::chart::Diagram > m_xDiagram;
+
+ css::drawing::Direction3D m_aPreferredAspectRatio;
+ css::uno::Reference< css::beans::XPropertySet > m_xAspectRatio3D;
+
+ double m_fXAnglePi;
+ double m_fYAnglePi;
+ double m_fZAnglePi;
+
+ css::awt::Point m_aAvailablePosIncludingAxes;
+ css::awt::Size m_aAvailableSizeIncludingAxes;
+
+ css::awt::Point m_aCurrentPosWithoutAxes;
+ css::awt::Size m_aCurrentSizeWithoutAxes;
+
+ bool m_bRightAngledAxes;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/VLegendSymbolFactory.hxx b/chart2/source/view/inc/VLegendSymbolFactory.hxx
new file mode 100644
index 000000000..f637a5894
--- /dev/null
+++ b/chart2/source/view/inc/VLegendSymbolFactory.hxx
@@ -0,0 +1,53 @@
+/* -*- 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 "LegendEntryProvider.hxx"
+#include <com/sun/star/uno/Reference.h>
+#include <rtl/ref.hxx>
+#include <svx/unoshape.hxx>
+
+namespace com::sun::star::awt { struct Size; }
+namespace com::sun::star::beans { class XPropertySet; }
+namespace com::sun::star::drawing { class XShape; }
+namespace com::sun::star::drawing { class XShapes; }
+namespace com::sun::star::lang { class XMultiServiceFactory; }
+namespace com::sun::star::uno { class Any; }
+
+namespace chart::VLegendSymbolFactory
+{
+ enum class PropertyType
+ {
+ FilledSeries,
+ LineSeries,
+ Line,
+ };
+
+ rtl::Reference< SvxShapeGroup >
+ createSymbol(
+ const css::awt::Size& rEntryKeyAspectRatio,
+ const rtl::Reference<SvxShapeGroupAnyD>& rSymbolContainer,
+ LegendSymbolStyle eStyle,
+ const css::uno::Reference< css::beans::XPropertySet > & xLegendEntryProperties,
+ PropertyType ePropertyType,
+ const css::uno::Any& rExplicitSymbol /*should contain a css::chart2::Symbol without automatic symbol if the charttype does support symbols else empty*/);
+
+} // namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/VLineProperties.hxx b/chart2/source/view/inc/VLineProperties.hxx
new file mode 100644
index 000000000..aa1c88ce7
--- /dev/null
+++ b/chart2/source/view/inc/VLineProperties.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 <com/sun/star/uno/Any.h>
+#include <com/sun/star/uno/Reference.h>
+
+namespace com::sun::star::beans
+{
+class XPropertySet;
+}
+
+namespace chart
+{
+struct VLineProperties
+{
+ css::uno::Any Color; //type sal_Int32 UNO_NAME_LINECOLOR
+ css::uno::Any LineStyle; //type drawing::LineStyle for property UNO_NAME_LINESTYLE
+ css::uno::Any Transparence; //type sal_Int16 for property UNO_NAME_LINETRANSPARENCE
+ css::uno::Any Width; //type sal_Int32 for property UNO_NAME_LINEWIDTH
+ css::uno::Any DashName; //type OUString for property "LineDashName"
+ css::uno::Any LineCap; //type drawing::LineCap for property UNO_NAME_LINECAP
+
+ VLineProperties();
+ void initFromPropertySet(const css::uno::Reference<css::beans::XPropertySet>& xProp);
+
+ bool isLineVisible() const;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/VPolarTransformation.hxx b/chart2/source/view/inc/VPolarTransformation.hxx
new file mode 100644
index 000000000..23f3c3b71
--- /dev/null
+++ b/chart2/source/view/inc/VPolarTransformation.hxx
@@ -0,0 +1,45 @@
+/* -*- 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 "PlottingPositionHelper.hxx"
+
+namespace chart
+{
+
+class VPolarTransformation final : public XTransformation2
+{
+public:
+ VPolarTransformation( const PolarPlottingPositionHelper& rPositionHelper );
+ virtual ~VPolarTransformation() override;
+
+ // ____ XTransformation2 ____
+ virtual css::drawing::Position3D transform(
+ const css::uno::Sequence< double >& rSourceValues ) const override;
+ virtual css::drawing::Position3D transform(
+ const css::drawing::Position3D& rSourceValues ) const override;
+
+private:
+ PolarPlottingPositionHelper m_aPositionHelper;
+ ::basegfx::B3DHomMatrix m_aUnitCartesianToScene;
+};
+
+} // namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/VSeriesPlotter.hxx b/chart2/source/view/inc/VSeriesPlotter.hxx
new file mode 100644
index 000000000..ac911be46
--- /dev/null
+++ b/chart2/source/view/inc/VSeriesPlotter.hxx
@@ -0,0 +1,436 @@
+/* -*- 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 <memory>
+#include "PlotterBase.hxx"
+#include "VDataSeries.hxx"
+#include "LabelAlignment.hxx"
+#include "MinimumAndMaximumSupplier.hxx"
+#include "LegendEntryProvider.hxx"
+#include <basegfx/range/b2irectangle.hxx>
+#include <com/sun/star/drawing/Direction3D.hpp>
+#include <rtl/ref.hxx>
+#include <svx/unoshape.hxx>
+
+namespace com::sun::star::awt { struct Point; }
+namespace com::sun::star::chart2 { class XChartType; }
+
+
+namespace chart { class ExplicitCategoriesProvider; }
+namespace chart { struct ExplicitScaleData; }
+namespace chart { class ChartModel; }
+
+namespace com::sun::star {
+ namespace util {
+ class XNumberFormatsSupplier;
+ }
+ namespace chart2 {
+ class XColorScheme;
+ class XRegressionCurveCalculator;
+ }
+}
+
+namespace chart {
+
+class ChartType;
+class NumberFormatterWrapper;
+
+class AxesNumberFormats
+{
+public:
+ AxesNumberFormats() {};
+
+ void setFormat( sal_Int32 nFormatKey, sal_Int32 nDimIndex, sal_Int32 nAxisIndex )
+ {
+ m_aNumberFormatMap[tFullAxisIndex(nDimIndex,nAxisIndex)] = nFormatKey;
+ }
+
+private:
+ typedef std::pair< sal_Int32, sal_Int32 > tFullAxisIndex;
+ std::map< tFullAxisIndex, sal_Int32 > m_aNumberFormatMap;
+};
+
+/**
+ * A list of series that have the same CoordinateSystem. They are used to be
+ * plotted maybe in a stacked manner by a plotter.
+ */
+class VDataSeriesGroup final
+{
+public:
+ VDataSeriesGroup() = delete;
+ VDataSeriesGroup( std::unique_ptr<VDataSeries> pSeries );
+ VDataSeriesGroup(VDataSeriesGroup&&) noexcept;
+ ~VDataSeriesGroup();
+
+ void addSeries( std::unique_ptr<VDataSeries> pSeries );//takes ownership of pSeries
+ sal_Int32 getSeriesCount() const;
+ void deleteSeries();
+
+ sal_Int32 getPointCount() const;
+ sal_Int32 getAttachedAxisIndexForFirstSeries() const;
+
+ void getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const;
+ void getMinimumAndMaximumYInContinuousXRange( double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const;
+
+ void calculateYMinAndMaxForCategory( sal_Int32 nCategoryIndex
+ , bool bSeparateStackingForDifferentSigns
+ , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex ) const;
+ void calculateYMinAndMaxForCategoryRange( sal_Int32 nStartCategoryIndex, sal_Int32 nEndCategoryIndex
+ , bool bSeparateStackingForDifferentSigns
+ , double& rfMinimumY, double& rfMaximumY, sal_Int32 nAxisIndex );
+
+ std::vector< std::unique_ptr<VDataSeries> > m_aSeriesVector;
+
+private:
+ //cached values
+ struct CachedYValues
+ {
+ CachedYValues();
+
+ bool m_bValuesDirty;
+ double m_fMinimumY;
+ double m_fMaximumY;
+ };
+
+ mutable bool m_bMaxPointCountDirty;
+ mutable sal_Int32 m_nMaxPointCount;
+ typedef std::map< sal_Int32, CachedYValues > tCachedYValuesPerAxisIndexMap;
+ mutable std::vector< tCachedYValuesPerAxisIndexMap > m_aListOfCachedYValues;
+};
+
+class VSeriesPlotter : public PlotterBase, public MinimumAndMaximumSupplier, public LegendEntryProvider
+{
+public:
+ VSeriesPlotter() = delete;
+
+ virtual ~VSeriesPlotter() override;
+
+ /**
+ * A new series can be positioned relative to other series in a chart.
+ * This positioning has two dimensions. First a series can be placed
+ * next to each other on the category axis. This position is indicated by xSlot.
+ * Second a series can be stacked on top of another. This position is indicated by ySlot.
+ * The positions are counted from 0 on.
+ * xSlot < 0 : append the series to already existing x series
+ * xSlot > occupied : append the series to already existing x series
+ *
+ * If the xSlot is already occupied the given ySlot decides what should happen:
+ * ySlot < -1 : move all existing series in the xSlot to next slot
+ * ySlot == -1 : stack on top at given x position
+ * ySlot == already occupied : insert at given y and x position
+ * ySlot > occupied : stack on top at given x position
+ */
+ virtual void addSeries( std::unique_ptr<VDataSeries> pSeries, sal_Int32 zSlot, sal_Int32 xSlot, sal_Int32 ySlot );
+
+ /** a value <= 0 for a directions means that this direction can be stretched arbitrary
+ */
+ virtual css::drawing::Direction3D getPreferredDiagramAspectRatio() const;
+
+ /** this enables you to handle series on the same x axis with different y axis
+ the property AttachedAxisIndex at a dataseries indicates which value scale is to use
+ (0==AttachedAxisIndex or a not set AttachedAxisIndex property indicates that this series should be scaled at the main y-axis;
+ 1==AttachedAxisIndex indicates that the series should be scaled at the first secondary axis if there is any otherwise at the main y axis
+ and so on.
+ The parameter nAxisIndex matches this DataSeries property 'AttachedAxisIndex'.
+ nAxisIndex must be greater than 0. nAxisIndex==1 refers to the first secondary axis.
+ )
+
+ @throws css::uno::RuntimeException
+ */
+
+ void addSecondaryValueScale( const ExplicitScaleData& rScale, sal_Int32 nAxisIndex );
+
+ // MinimumAndMaximumSupplier
+
+ virtual double getMinimumX() override;
+ virtual double getMaximumX() override;
+
+ virtual double getMinimumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) override;
+ virtual double getMaximumYInRange( double fMinimumX, double fMaximumX, sal_Int32 nAxisIndex ) override;
+
+ virtual double getMinimumZ() override;
+ virtual double getMaximumZ() override;
+
+ virtual bool isExpandBorderToIncrementRhythm( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandIfValuesCloseToBorder( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandWideValuesToZero( sal_Int32 nDimensionIndex ) override;
+ virtual bool isExpandNarrowValuesTowardZero( sal_Int32 nDimensionIndex ) override;
+ virtual bool isSeparateStackingForDifferentSigns( sal_Int32 nDimensionIndex ) override;
+
+ virtual tools::Long calculateTimeResolutionOnXAxis() override;
+ virtual void setTimeResolutionOnXAxis( tools::Long nTimeResolution, const Date& rNullDate ) override;
+
+ void getMinimumAndMaximumX( double& rfMinimum, double& rfMaximum ) const;
+ void getMinimumAndMaximumYInContinuousXRange( double& rfMinY, double& rfMaxY, double fMinX, double fMaxX, sal_Int32 nAxisIndex ) const;
+
+
+ // Methods for handling legends and legend entries.
+
+ virtual std::vector< ViewLegendEntry > createLegendEntries(
+ const css::awt::Size& rEntryKeyAspectRatio,
+ css::chart2::LegendPosition eLegendPosition,
+ const css::uno::Reference< css::beans::XPropertySet >& xTextProperties,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const css::uno::Reference< css::uno::XComponentContext >& xContext,
+ ChartModel& rModel
+ ) override;
+
+ virtual LegendSymbolStyle getLegendSymbolStyle();
+ virtual css::awt::Size getPreferredLegendKeyAspectRatio() override;
+
+ virtual css::uno::Any getExplicitSymbol( const VDataSeries& rSeries, sal_Int32 nPointIndex/*-1 for series symbol*/ );
+
+ rtl::Reference<SvxShapeGroup> createLegendSymbolForSeries(
+ const css::awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget );
+
+ rtl::Reference< SvxShapeGroup > createLegendSymbolForPoint(
+ const css::awt::Size& rEntryKeyAspectRatio
+ , const VDataSeries& rSeries
+ , sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget );
+
+ std::vector< ViewLegendEntry > createLegendEntriesForSeries(
+ const css::awt::Size& rEntryKeyAspectRatio,
+ const VDataSeries& rSeries,
+ const css::uno::Reference< css::beans::XPropertySet >& xTextProperties,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const css::uno::Reference< css::uno::XComponentContext >& xContext
+ );
+
+ std::vector<VDataSeries*> getAllSeries();
+ std::vector<VDataSeries const*> getAllSeries() const;
+
+ // This method creates a series plotter of the requested type; e.g. : return new PieChart...
+ static VSeriesPlotter* createSeriesPlotter( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bExcludingPositioning /*for pie and donut charts labels and exploded segments are excluded from the given size*/);
+
+ sal_Int32 getPointCount() const;
+
+ // Methods for number formats and color schemes
+
+ void setNumberFormatsSupplier( const css::uno::Reference< css::util::XNumberFormatsSupplier > & xNumFmtSupplier );
+
+ void setColorScheme( const css::uno::Reference< css::chart2::XColorScheme >& xColorScheme );
+
+ void setExplicitCategoriesProvider( ExplicitCategoriesProvider* pExplicitCategoriesProvider );
+
+ ExplicitCategoriesProvider* getExplicitCategoriesProvider() { return m_pExplicitCategoriesProvider; }
+
+ //get series names for the z axis labels
+ css::uno::Sequence<OUString> getSeriesNames() const;
+
+ void setPageReferenceSize( const css::awt::Size & rPageRefSize );
+ //better performance for big data
+ void setCoordinateSystemResolution( const css::uno::Sequence< sal_Int32 >& rCoordinateSystemResolution );
+ bool PointsWereSkipped() const { return m_bPointsWereSkipped;}
+ void setPieLabelsAllowToMove( bool bIsPieOrDonut ) { m_bPieLabelsAllowToMove = bIsPieOrDonut; };
+ void setAvailableOuterRect( const basegfx::B2IRectangle& aAvailableOuterRect ) { m_aAvailableOuterRect = aAvailableOuterRect; };
+
+ //return the depth for a logic 1
+ double getTransformedDepth() const;
+
+ void releaseShapes();
+
+ virtual void rearrangeLabelToAvoidOverlapIfRequested( const css::awt::Size& rPageSize );
+
+ bool WantToPlotInFrontOfAxisLine();
+ virtual bool shouldSnapRectToUsedArea();
+
+protected:
+
+ VSeriesPlotter( const rtl::Reference< ::chart::ChartType >& xChartTypeModel
+ , sal_Int32 nDimensionCount
+ , bool bCategoryXAxis=true );
+
+ // Methods for group shapes.
+
+ rtl::Reference<SvxShapeGroupAnyD>
+ getSeriesGroupShape( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget );
+
+ //the following group shapes will be created as children of SeriesGroupShape on demand
+ //they can be used to assure that some parts of a series shape are always in front of others (e.g. symbols in front of lines)
+ //parameter xTarget will be used as parent for the series group shape
+ rtl::Reference<SvxShapeGroupAnyD>
+ getSeriesGroupShapeFrontChild( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget );
+ rtl::Reference<SvxShapeGroupAnyD>
+ getSeriesGroupShapeBackChild( VDataSeries* pDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget );
+
+ /// This method creates a 2D group shape for containing all text shapes
+ /// needed for this series; the group is added to the text target;
+ static rtl::Reference<SvxShapeGroup>
+ getLabelsGroupShape( VDataSeries& rDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget );
+
+ rtl::Reference<SvxShapeGroupAnyD>
+ getErrorBarsGroupShape( VDataSeries& rDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget, bool bYError );
+
+ /** This method creates a text shape for a label related to a data point
+ * and append it to the root text shape group (xTarget).
+ *
+ * @param xTarget
+ * the main root text shape group.
+ * @param rDataSeries
+ * the data series, the data point belongs to.
+ * @param nPointIndex
+ * the index of the data point the label is related to.
+ * @param fValue
+ * the value of the data point.
+ * @param fSumValue
+ * the sum of all data point values in the data series.
+ * @param rScreenPosition2D
+ * the anchor point position for the label.
+ * @param eAlignment
+ * the required alignment of the label.
+ * @param offset
+ * an optional offset depending on the label alignment.
+ * @param nTextWidth
+ * the maximum width of a text label (used for text wrapping).
+ *
+ * @return
+ * a reference to the created text shape.
+ */
+ rtl::Reference<SvxShapeText>
+ createDataLabel( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , VDataSeries& rDataSeries
+ , sal_Int32 nPointIndex
+ , double fValue
+ , double fSumValue
+ , const css::awt::Point& rScreenPosition2D
+ , LabelAlignment eAlignment
+ , sal_Int32 nOffset=0
+ , sal_Int32 nTextWidth = 0 );
+
+ /// This method returns a text string representation of the passed numeric
+ /// value by exploiting a NumberFormatterWrapper object.
+ OUString getLabelTextForValue( VDataSeries const & rDataSeries
+ , sal_Int32 nPointIndex
+ , double fValue
+ , bool bAsPercentage );
+
+ /** creates two T-shaped error bars in both directions (up/down or
+ left/right depending on the bVertical parameter)
+
+ @param rPos
+ logic coordinates
+
+ @param xErrorBarProperties
+ the XPropertySet returned by the DataPoint-property "ErrorBarX" or
+ "ErrorBarY".
+
+ @param nIndex
+ the index of the data point in rData for which the calculation is
+ done.
+
+ @param bVertical
+ for y-error bars this is true, for x-error-bars it is false.
+ */
+ void createErrorBar(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const css::drawing::Position3D & rPos
+ , const css::uno::Reference< css::beans::XPropertySet > & xErrorBarProperties
+ , const VDataSeries& rVDataSeries
+ , sal_Int32 nIndex
+ , bool bVertical
+ , const double* pfScaledLogicX
+ );
+
+ void createErrorRectangle(
+ const css::drawing::Position3D& rUnscaledLogicPosition
+ , VDataSeries& rVDataSeries
+ , sal_Int32 nIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& rTarget
+ , bool bUseXErrorData
+ , bool bUseYErrorData
+ );
+
+ static void addErrorBorder(
+ const css::drawing::Position3D& rPos0
+ , const css::drawing::Position3D& rPos1
+ , const rtl::Reference<SvxShapeGroupAnyD>& rTarget
+ , const css::uno::Reference< css::beans::XPropertySet >& rErrorBorderProp );
+
+ void createErrorBar_X( const css::drawing::Position3D& rUnscaledLogicPosition
+ , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget );
+
+ void createErrorBar_Y( const css::drawing::Position3D& rUnscaledLogicPosition
+ , VDataSeries& rVDataSeries, sal_Int32 nPointIndex
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , double const * pfScaledLogicX );
+
+ void createRegressionCurvesShapes( VDataSeries const & rVDataSeries
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget
+ , bool bMaySkipPointsInRegressionCalculation );
+
+ void createRegressionCurveEquationShapes( const OUString & rEquationCID
+ , const css::uno::Reference< css::beans::XPropertySet > & xEquationProperties
+ , const rtl::Reference<SvxShapeGroupAnyD>& xEquationTarget
+ , const css::uno::Reference< css::chart2::XRegressionCurveCalculator > & xRegressionCurveCalculator
+ , css::awt::Point aDefaultPos );
+
+ virtual PlottingPositionHelper& getPlottingPositionHelper( sal_Int32 nAxisIndex ) const;//nAxisIndex indicates whether the position belongs to the main axis ( nAxisIndex==0 ) or secondary axis ( nAxisIndex==1 )
+
+ VDataSeries* getFirstSeries() const;
+
+ OUString getCategoryName( sal_Int32 nPointIndex ) const;
+
+protected:
+ PlottingPositionHelper* m_pMainPosHelper;
+
+ rtl::Reference< ::chart::ChartType > m_xChartTypeModel;
+
+ std::vector< std::vector< VDataSeriesGroup > > m_aZSlots;
+
+ bool m_bCategoryXAxis;//true->xvalues are indices (this would not be necessary if series for category chart wouldn't have x-values)
+ tools::Long m_nTimeResolution;
+ Date m_aNullDate;
+
+ std::unique_ptr< NumberFormatterWrapper > m_apNumberFormatterWrapper;
+
+ css::uno::Reference< css::chart2::XColorScheme > m_xColorScheme;
+
+ ExplicitCategoriesProvider* m_pExplicitCategoriesProvider;
+
+ //better performance for big data
+ css::uno::Sequence< sal_Int32 > m_aCoordinateSystemResolution;
+ bool m_bPointsWereSkipped;
+ bool m_bPieLabelsAllowToMove;
+ basegfx::B2IRectangle m_aAvailableOuterRect;
+ css::awt::Size m_aPageReferenceSize;
+
+private:
+ typedef std::map< sal_Int32 , ExplicitScaleData > tSecondaryValueScales;
+ tSecondaryValueScales m_aSecondaryValueScales;
+
+ typedef std::map< sal_Int32 , std::unique_ptr<PlottingPositionHelper> > tSecondaryPosHelperMap;
+ mutable tSecondaryPosHelperMap m_aSecondaryPosHelperMap;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/inc/ViewDefines.hxx b/chart2/source/view/inc/ViewDefines.hxx
new file mode 100644
index 000000000..b8b82be95
--- /dev/null
+++ b/chart2/source/view/inc/ViewDefines.hxx
@@ -0,0 +1,35 @@
+/* -*- 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 <sal/types.h>
+
+namespace chart
+{
+#define CHART_3DOBJECT_SEGMENTCOUNT (sal_Int32(32))
+//There needs to be a little distance between grid lines and walls in 3D, otherwise the lines are partly hidden by the walls
+#define GRID_TO_WALL_DISTANCE (1.0)
+
+const double ZDIRECTION = 1.0;
+const sal_Int32 AXIS2D_TICKLENGTH = 150; //value like in old chart
+const sal_Int32 AXIS2D_TICKLABELSPACING = 100; //value like in old chart
+
+} //end namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/AxisUsage.hxx b/chart2/source/view/main/AxisUsage.hxx
new file mode 100644
index 000000000..834518707
--- /dev/null
+++ b/chart2/source/view/main/AxisUsage.hxx
@@ -0,0 +1,143 @@
+/* -*- 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 <sal/types.h>
+#include <memory>
+#include <map>
+
+#include <VCoordinateSystem.hxx>
+#include <AxisHelper.hxx>
+#include <ScaleAutomatism.hxx>
+
+namespace chart
+{
+//first index is the dimension, second index is the axis index that indicates whether this is a main or secondary axis
+typedef std::pair<sal_Int32, sal_Int32> tFullAxisIndex;
+typedef std::map<VCoordinateSystem*, tFullAxisIndex> tCoordinateSystemMap;
+
+/** This class handles a collection of coordinate systems and is used for
+ * executing some action on all coordinate systems such as
+ * "prepareAutomaticAxisScaling" and "setExplicitScaleAndIncrement".
+ * Moreover it contains the "aAutoScaling" object that is an instance of
+ * the "ScaleAutomatism" class. The initialization of "aAutoScaling" is
+ * performed in the "SeriesPlotterContainer::initAxisUsageList" method and is
+ * used in the "SeriesPlotterContainer::doAutoScaling" for calculating explicit
+ * scale and increment objects (see "SeriesPlotterContainer::doAutoScaling").
+ */
+class AxisUsage
+{
+public:
+ AxisUsage()
+ : aAutoScaling(AxisHelper::createDefaultScale(), Date(Date::SYSTEM))
+ {
+ }
+
+ void addCoordinateSystem(VCoordinateSystem* pCooSys, sal_Int32 nDimensionIndex,
+ sal_Int32 nAxisIndex)
+ {
+ if (!pCooSys)
+ return;
+
+ tFullAxisIndex aFullAxisIndex(nDimensionIndex, nAxisIndex);
+ tCoordinateSystemMap::const_iterator aFound(aCoordinateSystems.find(pCooSys));
+
+ //use one scale only once for each coordinate system
+ //main axis are preferred over secondary axis
+ //value scales are preferred
+ if (aFound != aCoordinateSystems.end())
+ {
+ sal_Int32 nFoundAxisIndex = aFound->second.second;
+ if (nFoundAxisIndex < nAxisIndex)
+ return;
+ sal_Int32 nFoundDimension = aFound->second.first;
+ if (nFoundDimension == 1)
+ return;
+ if (nFoundDimension < nDimensionIndex)
+ return;
+ }
+ aCoordinateSystems[pCooSys] = aFullAxisIndex;
+
+ //set maximum scale index
+ auto aIter = aMaxIndexPerDimension.find(nDimensionIndex);
+ if (aIter != aMaxIndexPerDimension.end())
+ {
+ sal_Int32 nCurrentMaxIndex = aIter->second;
+ if (nCurrentMaxIndex < nAxisIndex)
+ aMaxIndexPerDimension[nDimensionIndex] = nAxisIndex;
+ }
+ else
+ aMaxIndexPerDimension[nDimensionIndex] = nAxisIndex;
+ }
+
+ std::vector<VCoordinateSystem*> getCoordinateSystems(sal_Int32 nDimensionIndex,
+ sal_Int32 nAxisIndex)
+ {
+ std::vector<VCoordinateSystem*> aRet;
+
+ for (auto const& coordinateSystem : aCoordinateSystems)
+ {
+ if (coordinateSystem.second.first != nDimensionIndex)
+ continue;
+ if (coordinateSystem.second.second != nAxisIndex)
+ continue;
+ aRet.push_back(coordinateSystem.first);
+ }
+
+ return aRet;
+ }
+
+ sal_Int32 getMaxAxisIndexForDimension(sal_Int32 nDimensionIndex)
+ {
+ sal_Int32 nRet = -1;
+ auto aIter = aMaxIndexPerDimension.find(nDimensionIndex);
+ if (aIter != aMaxIndexPerDimension.end())
+ nRet = aIter->second;
+ return nRet;
+ }
+
+ void prepareAutomaticAxisScaling(ScaleAutomatism& rScaleAutomatism, sal_Int32 nDimIndex,
+ sal_Int32 nAxisIndex)
+ {
+ std::vector<VCoordinateSystem*> aVCooSysList = getCoordinateSystems(nDimIndex, nAxisIndex);
+ for (VCoordinateSystem* pVCoordinateSystem : aVCooSysList)
+ pVCoordinateSystem->prepareAutomaticAxisScaling(rScaleAutomatism, nDimIndex,
+ nAxisIndex);
+ }
+
+ void setExplicitScaleAndIncrement(sal_Int32 nDimIndex, sal_Int32 nAxisIndex,
+ const ExplicitScaleData& rScale,
+ const ExplicitIncrementData& rInc)
+ {
+ std::vector<VCoordinateSystem*> aVCooSysList = getCoordinateSystems(nDimIndex, nAxisIndex);
+ for (VCoordinateSystem* pVCoordinateSystem : aVCooSysList)
+ pVCoordinateSystem->setExplicitScaleAndIncrement(nDimIndex, nAxisIndex, rScale, rInc);
+ }
+
+ ScaleAutomatism aAutoScaling;
+
+private:
+ tCoordinateSystemMap aCoordinateSystems;
+ std::map<sal_Int32, sal_Int32> aMaxIndexPerDimension;
+};
+
+} //end chart2 namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/ChartItemPool.cxx b/chart2/source/view/main/ChartItemPool.cxx
new file mode 100644
index 000000000..3410bcdbf
--- /dev/null
+++ b/chart2/source/view/main/ChartItemPool.cxx
@@ -0,0 +1,226 @@
+/* -*- 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 "ChartItemPool.hxx"
+#include <chartview/ChartSfxItemIds.hxx>
+#include <svx/chrtitem.hxx>
+#include <svx/sdangitm.hxx>
+#include <svx/svdpool.hxx>
+#include <svx/svx3ditems.hxx>
+#include <svl/intitem.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/eeitem.hxx>
+#include <editeng/sizeitem.hxx>
+#include <svl/stritem.hxx>
+#include <svl/ilstitem.hxx>
+#include <comphelper/processfactory.hxx>
+#include <editeng/editids.hrc>
+#include <svx/svxids.hrc>
+#include <vector>
+
+#include <com/sun/star/chart2/LegendPosition.hpp>
+#include <com/sun/star/chart2/MovingAverageType.hpp>
+#include <com/sun/star/frame/XTerminateListener.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+
+namespace chart
+{
+
+ChartItemPool::ChartItemPool():
+ SfxItemPool( "ChartItemPool" , SCHATTR_START, SCHATTR_END, nullptr, nullptr ),
+ pItemInfos(new SfxItemInfo[SCHATTR_END - SCHATTR_START + 1])
+{
+ /**************************************************************************
+ * PoolDefaults
+ **************************************************************************/
+ std::vector<SfxPoolItem*>* ppPoolDefaults = new std::vector<SfxPoolItem*>(SCHATTR_END - SCHATTR_START + 1);
+ std::vector<SfxPoolItem*>& rPoolDefaults = *ppPoolDefaults;
+ rPoolDefaults[SCHATTR_DATADESCR_SHOW_NUMBER - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_SHOW_NUMBER);
+ rPoolDefaults[SCHATTR_DATADESCR_SHOW_PERCENTAGE- SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_SHOW_PERCENTAGE);
+ rPoolDefaults[SCHATTR_DATADESCR_SHOW_CATEGORY - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_SHOW_CATEGORY);
+ rPoolDefaults[SCHATTR_DATADESCR_SHOW_SYMBOL - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_SHOW_SYMBOL);
+ rPoolDefaults[SCHATTR_DATADESCR_SHOW_DATA_SERIES_NAME - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_SHOW_DATA_SERIES_NAME);
+ rPoolDefaults[SCHATTR_DATADESCR_WRAP_TEXT - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_WRAP_TEXT);
+ rPoolDefaults[SCHATTR_DATADESCR_SEPARATOR - SCHATTR_START] = new SfxStringItem(SCHATTR_DATADESCR_SEPARATOR," ");
+ rPoolDefaults[SCHATTR_DATADESCR_PLACEMENT - SCHATTR_START] = new SfxInt32Item(SCHATTR_DATADESCR_PLACEMENT,0);
+ rPoolDefaults[SCHATTR_DATADESCR_AVAILABLE_PLACEMENTS - SCHATTR_START] = new SfxIntegerListItem(SCHATTR_DATADESCR_AVAILABLE_PLACEMENTS, std::vector < sal_Int32 >() );
+ rPoolDefaults[SCHATTR_DATADESCR_NO_PERCENTVALUE - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_NO_PERCENTVALUE);
+ rPoolDefaults[SCHATTR_DATADESCR_CUSTOM_LEADER_LINES - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_CUSTOM_LEADER_LINES, true);
+ rPoolDefaults[SCHATTR_PERCENT_NUMBERFORMAT_VALUE - SCHATTR_START] = new SfxUInt32Item(SCHATTR_PERCENT_NUMBERFORMAT_VALUE, 0);
+ rPoolDefaults[SCHATTR_PERCENT_NUMBERFORMAT_SOURCE - SCHATTR_START] = new SfxBoolItem(SCHATTR_PERCENT_NUMBERFORMAT_SOURCE);
+
+ //legend
+ rPoolDefaults[SCHATTR_LEGEND_POS - SCHATTR_START] = new SfxInt32Item(SCHATTR_LEGEND_POS, sal_Int32(css::chart2::LegendPosition_LINE_END) );
+ rPoolDefaults[SCHATTR_LEGEND_SHOW - SCHATTR_START] = new SfxBoolItem(SCHATTR_LEGEND_SHOW, true);
+ rPoolDefaults[SCHATTR_LEGEND_NO_OVERLAY - SCHATTR_START] = new SfxBoolItem(SCHATTR_LEGEND_NO_OVERLAY, true);
+
+ //text
+ rPoolDefaults[SCHATTR_TEXT_DEGREES - SCHATTR_START] = new SdrAngleItem(SCHATTR_TEXT_DEGREES, 0_deg100);
+ rPoolDefaults[SCHATTR_TEXT_STACKED - SCHATTR_START] = new SfxBoolItem(SCHATTR_TEXT_STACKED,false);
+
+ //statistic
+ rPoolDefaults[SCHATTR_STAT_AVERAGE - SCHATTR_START] = new SfxBoolItem (SCHATTR_STAT_AVERAGE);
+ rPoolDefaults[SCHATTR_STAT_KIND_ERROR - SCHATTR_START] = new SvxChartKindErrorItem (SvxChartKindError::NONE, SCHATTR_STAT_KIND_ERROR);
+ rPoolDefaults[SCHATTR_STAT_PERCENT - SCHATTR_START] = new SvxDoubleItem (0.0, SCHATTR_STAT_PERCENT);
+ rPoolDefaults[SCHATTR_STAT_BIGERROR - SCHATTR_START] = new SvxDoubleItem (0.0, SCHATTR_STAT_BIGERROR);
+ rPoolDefaults[SCHATTR_STAT_CONSTPLUS - SCHATTR_START] = new SvxDoubleItem (0.0, SCHATTR_STAT_CONSTPLUS);
+ rPoolDefaults[SCHATTR_STAT_CONSTMINUS - SCHATTR_START] = new SvxDoubleItem (0.0, SCHATTR_STAT_CONSTMINUS);
+ rPoolDefaults[SCHATTR_STAT_INDICATE - SCHATTR_START] = new SvxChartIndicateItem (SvxChartIndicate::NONE, SCHATTR_STAT_INDICATE);
+ rPoolDefaults[SCHATTR_STAT_RANGE_POS - SCHATTR_START] = new SfxStringItem (SCHATTR_STAT_RANGE_POS, OUString());
+ rPoolDefaults[SCHATTR_STAT_RANGE_NEG - SCHATTR_START] = new SfxStringItem (SCHATTR_STAT_RANGE_NEG, OUString());
+ rPoolDefaults[SCHATTR_STAT_ERRORBAR_TYPE - SCHATTR_START] = new SfxBoolItem(SCHATTR_STAT_ERRORBAR_TYPE, true);
+
+ rPoolDefaults[SCHATTR_STYLE_DEEP - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_DEEP, false);
+ rPoolDefaults[SCHATTR_STYLE_3D - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_3D, false);
+ rPoolDefaults[SCHATTR_STYLE_VERTICAL - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_VERTICAL, false);
+ rPoolDefaults[SCHATTR_STYLE_BASETYPE - SCHATTR_START] = new SfxInt32Item(SCHATTR_STYLE_BASETYPE, 0);
+ rPoolDefaults[SCHATTR_STYLE_LINES - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_LINES, false);
+ rPoolDefaults[SCHATTR_STYLE_PERCENT - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_PERCENT, false);
+ rPoolDefaults[SCHATTR_STYLE_STACKED - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_STACKED, false);
+ rPoolDefaults[SCHATTR_STYLE_SPLINES - SCHATTR_START] = new SfxInt32Item (SCHATTR_STYLE_SPLINES, 0); //Bug: was Bool! test ->Fileformat (touches only 5's)
+ rPoolDefaults[SCHATTR_STYLE_SYMBOL - SCHATTR_START] = new SfxInt32Item (SCHATTR_STYLE_SYMBOL, 0);
+ rPoolDefaults[SCHATTR_STYLE_SHAPE - SCHATTR_START] = new SfxInt32Item (SCHATTR_STYLE_SHAPE, 0);
+
+ rPoolDefaults[SCHATTR_AXIS - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS,2); //2 = Y-Axis!!!
+
+ //axis scale
+ rPoolDefaults[SCHATTR_AXISTYPE - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXISTYPE, CHART_AXIS_REALNUMBER);
+ rPoolDefaults[SCHATTR_AXIS_REVERSE - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_REVERSE,false);
+ rPoolDefaults[SCHATTR_AXIS_AUTO_MIN - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_MIN);
+ rPoolDefaults[SCHATTR_AXIS_MIN - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_AXIS_MIN);
+ rPoolDefaults[SCHATTR_AXIS_AUTO_MAX - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_MAX);
+ rPoolDefaults[SCHATTR_AXIS_MAX - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_AXIS_MAX);
+ rPoolDefaults[SCHATTR_AXIS_AUTO_STEP_MAIN - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_STEP_MAIN);
+ rPoolDefaults[SCHATTR_AXIS_STEP_MAIN - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_AXIS_STEP_MAIN);
+ rPoolDefaults[SCHATTR_AXIS_MAIN_TIME_UNIT - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_MAIN_TIME_UNIT,2);
+ rPoolDefaults[SCHATTR_AXIS_AUTO_STEP_HELP - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_STEP_HELP);
+ rPoolDefaults[SCHATTR_AXIS_STEP_HELP - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_STEP_HELP,0);
+ rPoolDefaults[SCHATTR_AXIS_HELP_TIME_UNIT - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_HELP_TIME_UNIT,2);
+ rPoolDefaults[SCHATTR_AXIS_AUTO_TIME_RESOLUTION - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_TIME_RESOLUTION);
+ rPoolDefaults[SCHATTR_AXIS_TIME_RESOLUTION - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_TIME_RESOLUTION,2);
+ rPoolDefaults[SCHATTR_AXIS_LOGARITHM - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_LOGARITHM);
+ rPoolDefaults[SCHATTR_AXIS_AUTO_DATEAXIS - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_DATEAXIS);
+ rPoolDefaults[SCHATTR_AXIS_ALLOW_DATEAXIS - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_ALLOW_DATEAXIS);
+ rPoolDefaults[SCHATTR_AXIS_AUTO_ORIGIN - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_ORIGIN);
+ rPoolDefaults[SCHATTR_AXIS_ORIGIN - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_AXIS_ORIGIN);
+
+ //axis position
+ rPoolDefaults[SCHATTR_AXIS_TICKS - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_TICKS,CHAXIS_MARK_OUTER);
+ rPoolDefaults[SCHATTR_AXIS_HELPTICKS - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_HELPTICKS,0);
+ rPoolDefaults[SCHATTR_AXIS_POSITION - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_POSITION,0);
+ rPoolDefaults[SCHATTR_AXIS_POSITION_VALUE - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_AXIS_POSITION_VALUE);
+ rPoolDefaults[SCHATTR_AXIS_CROSSING_MAIN_AXIS_NUMBERFORMAT - SCHATTR_START] = new SfxUInt32Item(SCHATTR_AXIS_CROSSING_MAIN_AXIS_NUMBERFORMAT,0);
+ rPoolDefaults[SCHATTR_AXIS_SHIFTED_CATEGORY_POSITION - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_SHIFTED_CATEGORY_POSITION,false);
+ rPoolDefaults[SCHATTR_AXIS_LABEL_POSITION - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_LABEL_POSITION,0);
+ rPoolDefaults[SCHATTR_AXIS_MARK_POSITION - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_MARK_POSITION,0);
+
+ //axis label
+ rPoolDefaults[SCHATTR_AXIS_SHOWDESCR - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_SHOWDESCR,false);
+ rPoolDefaults[SCHATTR_AXIS_LABEL_ORDER - SCHATTR_START] = new SvxChartTextOrderItem(SvxChartTextOrder::SideBySide, SCHATTR_AXIS_LABEL_ORDER);
+ rPoolDefaults[SCHATTR_AXIS_LABEL_OVERLAP - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_LABEL_OVERLAP,false);
+ rPoolDefaults[SCHATTR_AXIS_LABEL_BREAK - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_LABEL_BREAK, false );
+
+ rPoolDefaults[SCHATTR_SYMBOL_BRUSH - SCHATTR_START] = new SvxBrushItem(SCHATTR_SYMBOL_BRUSH);
+ rPoolDefaults[SCHATTR_STOCK_VOLUME - SCHATTR_START] = new SfxBoolItem(SCHATTR_STOCK_VOLUME,false);
+ rPoolDefaults[SCHATTR_STOCK_UPDOWN - SCHATTR_START] = new SfxBoolItem(SCHATTR_STOCK_UPDOWN,false);
+ rPoolDefaults[SCHATTR_SYMBOL_SIZE - SCHATTR_START] = new SvxSizeItem(SCHATTR_SYMBOL_SIZE,Size(0,0));
+ rPoolDefaults[SCHATTR_HIDE_DATA_POINT_LEGEND_ENTRY - SCHATTR_START] = new SfxBoolItem(SCHATTR_HIDE_DATA_POINT_LEGEND_ENTRY, false);
+
+ // new for New Chart
+ rPoolDefaults[SCHATTR_BAR_OVERLAP - SCHATTR_START] = new SfxInt32Item(SCHATTR_BAR_OVERLAP,0);
+ rPoolDefaults[SCHATTR_BAR_GAPWIDTH - SCHATTR_START] = new SfxInt32Item(SCHATTR_BAR_GAPWIDTH,0);
+ rPoolDefaults[SCHATTR_BAR_CONNECT - SCHATTR_START] = new SfxBoolItem(SCHATTR_BAR_CONNECT, false);
+ rPoolDefaults[SCHATTR_NUM_OF_LINES_FOR_BAR - SCHATTR_START] = new SfxInt32Item( SCHATTR_NUM_OF_LINES_FOR_BAR, 0 );
+ rPoolDefaults[SCHATTR_SPLINE_ORDER - SCHATTR_START] = new SfxInt32Item( SCHATTR_SPLINE_ORDER, 3 );
+ rPoolDefaults[SCHATTR_SPLINE_RESOLUTION - SCHATTR_START] = new SfxInt32Item( SCHATTR_SPLINE_RESOLUTION, 20 );
+ rPoolDefaults[SCHATTR_GROUP_BARS_PER_AXIS - SCHATTR_START] = new SfxBoolItem(SCHATTR_GROUP_BARS_PER_AXIS, false);
+ rPoolDefaults[SCHATTR_STARTING_ANGLE - SCHATTR_START] = new SdrAngleItem( SCHATTR_STARTING_ANGLE, 9000_deg100 );
+ rPoolDefaults[SCHATTR_CLOCKWISE - SCHATTR_START] = new SfxBoolItem( SCHATTR_CLOCKWISE, false );
+
+ rPoolDefaults[SCHATTR_MISSING_VALUE_TREATMENT - SCHATTR_START] = new SfxInt32Item(SCHATTR_MISSING_VALUE_TREATMENT, 0);
+ rPoolDefaults[SCHATTR_AVAILABLE_MISSING_VALUE_TREATMENTS - SCHATTR_START] = new SfxIntegerListItem(SCHATTR_AVAILABLE_MISSING_VALUE_TREATMENTS, std::vector < sal_Int32 >() );
+ rPoolDefaults[SCHATTR_INCLUDE_HIDDEN_CELLS - SCHATTR_START] = new SfxBoolItem(SCHATTR_INCLUDE_HIDDEN_CELLS, true);
+ rPoolDefaults[SCHATTR_HIDE_LEGEND_ENTRY - SCHATTR_START] = new SfxBoolItem(SCHATTR_HIDE_LEGEND_ENTRY, false);
+
+ rPoolDefaults[SCHATTR_AXIS_FOR_ALL_SERIES - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_FOR_ALL_SERIES, 0);
+
+ rPoolDefaults[SCHATTR_REGRESSION_TYPE - SCHATTR_START] = new SvxChartRegressItem (SvxChartRegress::NONE, SCHATTR_REGRESSION_TYPE);
+ rPoolDefaults[SCHATTR_REGRESSION_SHOW_EQUATION - SCHATTR_START] = new SfxBoolItem(SCHATTR_REGRESSION_SHOW_EQUATION, false);
+ rPoolDefaults[SCHATTR_REGRESSION_SHOW_COEFF - SCHATTR_START] = new SfxBoolItem(SCHATTR_REGRESSION_SHOW_COEFF, false);
+ rPoolDefaults[SCHATTR_REGRESSION_DEGREE - SCHATTR_START] = new SfxInt32Item(SCHATTR_REGRESSION_DEGREE, 2);
+ rPoolDefaults[SCHATTR_REGRESSION_PERIOD - SCHATTR_START] = new SfxInt32Item(SCHATTR_REGRESSION_PERIOD, 2);
+ rPoolDefaults[SCHATTR_REGRESSION_EXTRAPOLATE_FORWARD - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_REGRESSION_EXTRAPOLATE_FORWARD);
+ rPoolDefaults[SCHATTR_REGRESSION_EXTRAPOLATE_BACKWARD - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_REGRESSION_EXTRAPOLATE_BACKWARD);
+ rPoolDefaults[SCHATTR_REGRESSION_SET_INTERCEPT - SCHATTR_START] = new SfxBoolItem(SCHATTR_REGRESSION_SET_INTERCEPT, false);
+ rPoolDefaults[SCHATTR_REGRESSION_INTERCEPT_VALUE - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_REGRESSION_INTERCEPT_VALUE);
+ rPoolDefaults[SCHATTR_REGRESSION_CURVE_NAME - SCHATTR_START] = new SfxStringItem(SCHATTR_REGRESSION_CURVE_NAME, OUString());
+ rPoolDefaults[SCHATTR_REGRESSION_XNAME - SCHATTR_START] = new SfxStringItem(SCHATTR_REGRESSION_XNAME, "x");
+ rPoolDefaults[SCHATTR_REGRESSION_YNAME - SCHATTR_START] = new SfxStringItem(SCHATTR_REGRESSION_YNAME, "f(x)");
+ rPoolDefaults[SCHATTR_REGRESSION_MOVING_TYPE - SCHATTR_START] = new SfxInt32Item(SCHATTR_REGRESSION_MOVING_TYPE, css::chart2::MovingAverageType::Prior);
+
+ /**************************************************************************
+ * ItemInfos
+ **************************************************************************/
+ const sal_uInt16 nMax = SCHATTR_END - SCHATTR_START + 1;
+ for( sal_uInt16 i = 0; i < nMax; i++ )
+ {
+ pItemInfos[i]._nSID = 0;
+ pItemInfos[i]._bPoolable = true;
+ }
+
+ // slot ids differing from which ids
+ pItemInfos[SCHATTR_SYMBOL_BRUSH - SCHATTR_START]._nSID = SID_ATTR_BRUSH;
+ pItemInfos[SCHATTR_STYLE_SYMBOL - SCHATTR_START]._nSID = SID_ATTR_SYMBOLTYPE;
+ pItemInfos[SCHATTR_SYMBOL_SIZE - SCHATTR_START]._nSID = SID_ATTR_SYMBOLSIZE;
+
+ SetDefaults(ppPoolDefaults);
+ SetItemInfos(pItemInfos.get());
+}
+
+ChartItemPool::ChartItemPool(const ChartItemPool& rPool):
+ SfxItemPool(rPool)
+{
+}
+
+ChartItemPool::~ChartItemPool()
+{
+ Delete();
+ // release and delete static pool default items
+ ReleaseDefaults(true);
+}
+
+rtl::Reference<SfxItemPool> ChartItemPool::Clone() const
+{
+ return new ChartItemPool(*this);
+}
+
+MapUnit ChartItemPool::GetMetric(sal_uInt16 /* nWhich */) const
+{
+ return MapUnit::Map100thMM;
+}
+
+rtl::Reference<SfxItemPool> ChartItemPool::CreateChartItemPool()
+{
+ return new ChartItemPool();
+}
+
+} // namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/ChartItemPool.hxx b/chart2/source/view/main/ChartItemPool.hxx
new file mode 100644
index 000000000..74a7ab1eb
--- /dev/null
+++ b/chart2/source/view/main/ChartItemPool.hxx
@@ -0,0 +1,48 @@
+/* -*- 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 <tools/mapunit.hxx>
+#include <svl/itempool.hxx>
+#include <memory>
+
+namespace chart
+{
+class ChartItemPool : public SfxItemPool
+{
+private:
+ std::unique_ptr<SfxItemInfo[]> pItemInfos;
+
+public:
+ ChartItemPool();
+ ChartItemPool(const ChartItemPool& rPool);
+ virtual ~ChartItemPool() override;
+
+ virtual rtl::Reference<SfxItemPool> Clone() const override;
+ MapUnit GetMetric(sal_uInt16 nWhich) const override;
+
+ /// creates a pure chart item pool
+ static rtl::Reference<SfxItemPool> CreateChartItemPool();
+};
+
+} // namespace chart
+
+// INCLUDED_CHART2_SOURCE_VIEW_MAIN_CHARTITEMPOOL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/ChartView.cxx b/chart2/source/view/main/ChartView.cxx
new file mode 100644
index 000000000..3f96a68b2
--- /dev/null
+++ b/chart2/source/view/main/ChartView.cxx
@@ -0,0 +1,2035 @@
+/* -*- 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 <config_feature_desktop.h>
+
+#include "SeriesPlotterContainer.hxx"
+
+#include <ChartView.hxx>
+#include <chartview/DrawModelWrapper.hxx>
+#include <Diagram.hxx>
+#include <ChartType.hxx>
+#include <DataSeries.hxx>
+#include <NumberFormatterWrapper.hxx>
+#include <VDiagram.hxx>
+#include "VTitle.hxx"
+#include "VButton.hxx"
+#include <ShapeFactory.hxx>
+#include <BaseCoordinateSystem.hxx>
+#include <VCoordinateSystem.hxx>
+#include <VSeriesPlotter.hxx>
+#include <CommonConverters.hxx>
+#include <TitleHelper.hxx>
+#include <Legend.hxx>
+#include <LegendHelper.hxx>
+#include "VLegend.hxx"
+#include <PropertyMapper.hxx>
+#include <ChartModel.hxx>
+#include <ChartTypeHelper.hxx>
+#include <ScaleAutomatism.hxx>
+#include <ObjectIdentifier.hxx>
+#include <DiagramHelper.hxx>
+#include <RelativePositionHelper.hxx>
+#include <servicenames.hxx>
+#include <Axis.hxx>
+#include <AxisHelper.hxx>
+#include "AxisUsage.hxx"
+#include <AxisIndexDefines.hxx>
+#include <BaseGFXHelper.hxx>
+#include <DataSeriesHelper.hxx>
+#include <DateHelper.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <defines.hxx>
+#include <unonames.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/eeitem.hxx>
+#include <tools/globname.hxx>
+#include <comphelper/fileformat.h>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <rtl/math.hxx>
+#include <unotools/streamwrap.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/unopage.hxx>
+#include <vcl/svapp.hxx>
+#include <osl/mutex.hxx>
+#include <svx/unofill.hxx>
+#include <drawinglayer/XShapeDumper.hxx>
+
+#include <time.h>
+
+#include <com/sun/star/awt/Point.hpp>
+#include <com/sun/star/chart/ChartAxisPosition.hpp>
+#include <com/sun/star/chart/TimeUnit.hpp>
+#include <com/sun/star/chart2/AxisType.hpp>
+#include <com/sun/star/chart2/StackingDirection.hpp>
+#include <com/sun/star/chart2/RelativePosition.hpp>
+#include <com/sun/star/chart2/RelativeSize.hpp>
+#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>
+#include <com/sun/star/chart2/data/PivotTableFieldEntry.hpp>
+#include <com/sun/star/drawing/GraphicExportFilter.hpp>
+#include <com/sun/star/embed/Aspects.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <com/sun/star/util/XRefreshable.hpp>
+#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
+#include <com/sun/star/text/XText.hpp>
+#include <com/sun/star/text/XTextDocument.hpp>
+#include <com/sun/star/text/WritingMode2.hpp>
+#include <com/sun/star/text/XTextEmbeddedObjectsSupplier.hpp>
+#include <com/sun/star/view/XSelectionSupplier.hpp>
+#include <svl/itempool.hxx>
+#include <svl/ctloptions.hxx>
+#include <comphelper/classids.hxx>
+#include <servicenames_charttypes.hxx>
+
+
+#include <rtl/ustring.hxx>
+
+#include <tools/diagnose_ex.h>
+#include <tools/stream.hxx>
+
+#include <memory>
+#include <libxml/xmlwriter.h>
+
+namespace com::sun::star::chart2 { class XChartDocument; }
+
+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;
+using ::com::sun::star::uno::Any;
+
+struct CreateShapeParam2D
+{
+ css::awt::Rectangle maRemainingSpace;
+
+ std::shared_ptr<SeriesPlotterContainer> mpSeriesPlotterContainer;
+
+ std::shared_ptr<VTitle> mpVTitleX;
+ std::shared_ptr<VTitle> mpVTitleY;
+ std::shared_ptr<VTitle> mpVTitleZ;
+
+ std::shared_ptr<VTitle> mpVTitleSecondX;
+ std::shared_ptr<VTitle> mpVTitleSecondY;
+
+ rtl::Reference<SvxShapeRect> mxMarkHandles;
+ rtl::Reference<SvxShapeRect> mxPlotAreaWithAxes;
+
+ rtl::Reference<SvxShapeGroup> mxDiagramWithAxesShapes;
+
+ bool mbAutoPosTitleX;
+ bool mbAutoPosTitleY;
+ bool mbAutoPosTitleZ;
+
+ bool mbAutoPosSecondTitleX;
+ bool mbAutoPosSecondTitleY;
+
+ bool mbUseFixedInnerSize;
+
+ CreateShapeParam2D() :
+ mbAutoPosTitleX(true),
+ mbAutoPosTitleY(true),
+ mbAutoPosTitleZ(true),
+ mbAutoPosSecondTitleX(true),
+ mbAutoPosSecondTitleY(true),
+ mbUseFixedInnerSize(false) {}
+};
+
+
+
+ChartView::ChartView(
+ uno::Reference<uno::XComponentContext> const & xContext,
+ ChartModel& rModel)
+ : m_xCC(xContext)
+ , mrChartModel(rModel)
+ , m_aListenerContainer( m_aMutex )
+ , m_bViewDirty(true)
+ , m_bInViewUpdate(false)
+ , m_bViewUpdatePending(false)
+ , m_bRefreshAddIn(true)
+ , m_aPageResolution(1000,1000)
+ , m_bPointsWereSkipped(false)
+ , m_nScaleXNumerator(1)
+ , m_nScaleXDenominator(1)
+ , m_nScaleYNumerator(1)
+ , m_nScaleYDenominator(1)
+ , m_bSdrViewIsInEditMode(false)
+ , m_aResultingDiagramRectangleExcludingAxes(0,0,0,0)
+{
+ init();
+}
+
+void ChartView::init()
+{
+ if( !m_pDrawModelWrapper )
+ {
+ SolarMutexGuard aSolarGuard;
+ m_pDrawModelWrapper = std::make_shared< DrawModelWrapper >();
+ m_xShapeFactory = m_pDrawModelWrapper->getShapeFactory();
+ m_xDrawPage = m_pDrawModelWrapper->getMainDrawPage();
+ StartListening( m_pDrawModelWrapper->getSdrModel() );
+ }
+}
+
+void SAL_CALL ChartView::initialize( const uno::Sequence< uno::Any >& )
+{
+ init();
+}
+
+ChartView::~ChartView()
+{
+ maTimeBased.maTimer.Stop();
+ // #i120831#. In ChartView::initialize(), m_xShapeFactory is created from SdrModel::getUnoModel() and indirectly
+ // from SfxBaseModel, it needs call dispose() to make sure SfxBaseModel object is freed correctly.
+ uno::Reference< lang::XComponent > xComp( m_xShapeFactory, uno::UNO_QUERY);
+ if ( xComp.is() )
+ xComp->dispose();
+
+ if( m_pDrawModelWrapper )
+ {
+ SolarMutexGuard aSolarGuard;
+ EndListening( m_pDrawModelWrapper->getSdrModel() );
+ m_pDrawModelWrapper.reset();
+ }
+ m_xDrawPage = nullptr;
+ impl_deleteCoordinateSystems();
+}
+
+void ChartView::impl_deleteCoordinateSystems()
+{
+ //delete all coordinate systems
+ m_aVCooSysList.clear();
+}
+
+// datatransfer::XTransferable
+namespace
+{
+constexpr OUStringLiteral lcl_aGDIMetaFileMIMEType(
+ u"application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"" );
+constexpr OUStringLiteral lcl_aGDIMetaFileMIMETypeHighContrast(
+ u"application/x-openoffice-highcontrast-gdimetafile;windows_formatname=\"GDIMetaFile\"" );
+} // anonymous namespace
+
+void ChartView::getMetaFile( const uno::Reference< io::XOutputStream >& xOutStream
+ , bool bUseHighContrast )
+{
+ if( !m_xDrawPage.is() )
+ return;
+
+ // creating the graphic exporter
+ uno::Reference< drawing::XGraphicExportFilter > xExporter = drawing::GraphicExportFilter::create( m_xCC );
+
+ uno::Sequence< beans::PropertyValue > aFilterData{
+ comphelper::makePropertyValue("ExportOnlyBackground", false),
+ comphelper::makePropertyValue("HighContrast", bUseHighContrast),
+ comphelper::makePropertyValue("Version", sal_Int32(SOFFICE_FILEFORMAT_50)),
+ comphelper::makePropertyValue("CurrentPage", uno::Reference< uno::XInterface >( static_cast<cppu::OWeakObject*>(m_xDrawPage.get()), uno::UNO_QUERY )),
+ //#i75867# poor quality of ole's alternative view with 3D scenes and zoomfactors besides 100%
+ comphelper::makePropertyValue("ScaleXNumerator", m_nScaleXNumerator),
+ comphelper::makePropertyValue("ScaleXDenominator", m_nScaleXDenominator),
+ comphelper::makePropertyValue("ScaleYNumerator", m_nScaleYNumerator),
+ comphelper::makePropertyValue("ScaleYDenominator", m_nScaleYDenominator)
+ };
+
+ uno::Sequence< beans::PropertyValue > aProps{
+ comphelper::makePropertyValue("FilterName", OUString("SVM")),
+ comphelper::makePropertyValue("OutputStream", xOutStream),
+ comphelper::makePropertyValue("FilterData", aFilterData)
+ };
+
+ xExporter->setSourceDocument( m_xDrawPage );
+ if( xExporter->filter( aProps ) )
+ {
+ xOutStream->flush();
+ xOutStream->closeOutput();
+ uno::Reference< io::XSeekable > xSeekable( xOutStream, uno::UNO_QUERY );
+ if( xSeekable.is() )
+ xSeekable->seek(0);
+ }
+}
+
+uno::Any SAL_CALL ChartView::getTransferData( const datatransfer::DataFlavor& aFlavor )
+{
+ bool bHighContrastMetaFile( aFlavor.MimeType == lcl_aGDIMetaFileMIMETypeHighContrast);
+ uno::Any aRet;
+ if( ! (bHighContrastMetaFile || aFlavor.MimeType == lcl_aGDIMetaFileMIMEType) )
+ return aRet;
+
+ update();
+
+ SvMemoryStream aStream( 1024, 1024 );
+ rtl::Reference<utl::OStreamWrapper> pStreamWrapper = new utl::OStreamWrapper( aStream );
+
+ this->getMetaFile( pStreamWrapper, bHighContrastMetaFile );
+
+ pStreamWrapper->seek(0);
+ sal_Int32 nBytesToRead = pStreamWrapper->available();
+ uno::Sequence< sal_Int8 > aSeq( nBytesToRead );
+ pStreamWrapper->readBytes( aSeq, nBytesToRead);
+ aRet <<= aSeq;
+ pStreamWrapper->closeInput();
+
+ return aRet;
+}
+uno::Sequence< datatransfer::DataFlavor > SAL_CALL ChartView::getTransferDataFlavors()
+{
+ return
+ {
+ { lcl_aGDIMetaFileMIMEType, "GDIMetaFile", cppu::UnoType<uno::Sequence< sal_Int8 >>::get() },
+ { lcl_aGDIMetaFileMIMETypeHighContrast, "GDIMetaFile", cppu::UnoType<uno::Sequence< sal_Int8 >>::get() }
+ };
+}
+sal_Bool SAL_CALL ChartView::isDataFlavorSupported( const datatransfer::DataFlavor& aFlavor )
+{
+ return ( aFlavor.MimeType == lcl_aGDIMetaFileMIMEType ||
+ aFlavor.MimeType == lcl_aGDIMetaFileMIMETypeHighContrast );
+}
+
+// ____ XUnoTunnel ___
+::sal_Int64 SAL_CALL ChartView::getSomething( const uno::Sequence< ::sal_Int8 >& aIdentifier )
+{
+ return comphelper::getSomethingImpl<ExplicitValueProvider>(aIdentifier, this);
+}
+
+// lang::XServiceInfo
+
+OUString SAL_CALL ChartView::getImplementationName()
+{
+ return CHART_VIEW_SERVICE_IMPLEMENTATION_NAME;
+}
+
+sal_Bool SAL_CALL ChartView::supportsService( const OUString& rServiceName )
+{
+ return cppu::supportsService(this, rServiceName);
+}
+
+css::uno::Sequence< OUString > SAL_CALL ChartView::getSupportedServiceNames()
+{
+ return { CHART_VIEW_SERVICE_NAME };
+}
+
+static ::basegfx::B3DHomMatrix createTransformationSceneToScreen(
+ const ::basegfx::B2IRectangle& rDiagramRectangleWithoutAxes )
+{
+ ::basegfx::B3DHomMatrix aM;
+ aM.scale(double(rDiagramRectangleWithoutAxes.getWidth())/FIXED_SIZE_FOR_3D_CHART_VOLUME
+ , -double(rDiagramRectangleWithoutAxes.getHeight())/FIXED_SIZE_FOR_3D_CHART_VOLUME, 1.0 );
+ aM.translate(double(rDiagramRectangleWithoutAxes.getMinX())
+ , double(rDiagramRectangleWithoutAxes.getMinY()+rDiagramRectangleWithoutAxes.getHeight()-1), 0);
+ return aM;
+}
+
+namespace
+{
+
+bool lcl_IsPieOrDonut( const rtl::Reference< Diagram >& xDiagram )
+{
+ //special treatment for pie charts
+ //the size is checked after complete creation to get the datalabels into the given space
+
+ //todo: this is just a workaround at the moment for pie and donut labels
+ return DiagramHelper::isPieOrDonutChart( xDiagram );
+}
+
+void lcl_setDefaultWritingMode( const std::shared_ptr< DrawModelWrapper >& pDrawModelWrapper, ChartModel& rModel)
+{
+ //get writing mode from parent document:
+ if( !SvtCTLOptions().IsCTLFontEnabled() )
+ return;
+
+ try
+ {
+ sal_Int16 nWritingMode=-1;
+ uno::Reference< beans::XPropertySet > xParentProps( rModel.getParent(), uno::UNO_QUERY );
+ uno::Reference< style::XStyleFamiliesSupplier > xStyleFamiliesSupplier( xParentProps, uno::UNO_QUERY );
+ if( xStyleFamiliesSupplier.is() )
+ {
+ uno::Reference< container::XNameAccess > xStylesFamilies( xStyleFamiliesSupplier->getStyleFamilies() );
+ if( xStylesFamilies.is() )
+ {
+ if( !xStylesFamilies->hasByName( "PageStyles" ) )
+ {
+ //draw/impress is parent document
+ uno::Reference< lang::XMultiServiceFactory > xFatcory( xParentProps, uno::UNO_QUERY );
+ if( xFatcory.is() )
+ {
+ uno::Reference< beans::XPropertySet > xDrawDefaults( xFatcory->createInstance( "com.sun.star.drawing.Defaults" ), uno::UNO_QUERY );
+ if( xDrawDefaults.is() )
+ xDrawDefaults->getPropertyValue( "WritingMode" ) >>= nWritingMode;
+ }
+ }
+ else
+ {
+ uno::Reference< container::XNameAccess > xPageStyles( xStylesFamilies->getByName( "PageStyles" ), uno::UNO_QUERY );
+ if( xPageStyles.is() )
+ {
+ OUString aPageStyle;
+
+ uno::Reference< text::XTextDocument > xTextDocument( xParentProps, uno::UNO_QUERY );
+ if( xTextDocument.is() )
+ {
+ //writer is parent document
+ //retrieve the current page style from the text cursor property PageStyleName
+
+ uno::Reference< text::XTextEmbeddedObjectsSupplier > xTextEmbeddedObjectsSupplier( xTextDocument, uno::UNO_QUERY );
+ if( xTextEmbeddedObjectsSupplier.is() )
+ {
+ uno::Reference< container::XNameAccess > xEmbeddedObjects( xTextEmbeddedObjectsSupplier->getEmbeddedObjects() );
+ if( xEmbeddedObjects.is() )
+ {
+ uno::Sequence< OUString > aNames( xEmbeddedObjects->getElementNames() );
+
+ sal_Int32 nCount = aNames.getLength();
+ for( sal_Int32 nN=0; nN<nCount; nN++ )
+ {
+ uno::Reference< beans::XPropertySet > xEmbeddedProps( xEmbeddedObjects->getByName( aNames[nN] ), uno::UNO_QUERY );
+ if( xEmbeddedProps.is() )
+ {
+ static OUString aChartCLSID = SvGlobalName( SO3_SCH_CLASSID ).GetHexName();
+ OUString aCLSID;
+ xEmbeddedProps->getPropertyValue( "CLSID" ) >>= aCLSID;
+ if( aCLSID == aChartCLSID )
+ {
+ uno::Reference< text::XTextContent > xEmbeddedObject( xEmbeddedProps, uno::UNO_QUERY );
+ if( xEmbeddedObject.is() )
+ {
+ uno::Reference< text::XTextRange > xAnchor( xEmbeddedObject->getAnchor() );
+ if( xAnchor.is() )
+ {
+ uno::Reference< beans::XPropertySet > xAnchorProps( xAnchor, uno::UNO_QUERY );
+ if( xAnchorProps.is() )
+ {
+ xAnchorProps->getPropertyValue( "WritingMode" ) >>= nWritingMode;
+ }
+ uno::Reference< text::XText > xText( xAnchor->getText() );
+ if( xText.is() )
+ {
+ uno::Reference< beans::XPropertySet > xTextCursorProps( xText->createTextCursor(), uno::UNO_QUERY );
+ if( xTextCursorProps.is() )
+ xTextCursorProps->getPropertyValue( "PageStyleName" ) >>= aPageStyle;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ if( aPageStyle.isEmpty() )
+ {
+ uno::Reference< text::XText > xText( xTextDocument->getText() );
+ if( xText.is() )
+ {
+ uno::Reference< beans::XPropertySet > xTextCursorProps( xText->createTextCursor(), uno::UNO_QUERY );
+ if( xTextCursorProps.is() )
+ xTextCursorProps->getPropertyValue( "PageStyleName" ) >>= aPageStyle;
+ }
+ }
+ if(aPageStyle.isEmpty())
+ aPageStyle = "Standard";
+ }
+ else
+ {
+ //Calc is parent document
+ Reference< com::sun::star::beans::XPropertySetInfo > xInfo = xParentProps->getPropertySetInfo();
+ if (xInfo->hasPropertyByName("PageStyle"))
+ {
+ xParentProps->getPropertyValue( "PageStyle" ) >>= aPageStyle;
+ }
+ if(aPageStyle.isEmpty())
+ aPageStyle = "Default";
+ }
+ if( nWritingMode == -1 || nWritingMode == text::WritingMode2::PAGE )
+ {
+ uno::Reference< beans::XPropertySet > xPageStyle( xPageStyles->getByName( aPageStyle ), uno::UNO_QUERY );
+ Reference< com::sun::star::beans::XPropertySetInfo > xInfo = xPageStyle->getPropertySetInfo();
+ if (xInfo->hasPropertyByName("WritingMode"))
+ {
+ if( xPageStyle.is() )
+ xPageStyle->getPropertyValue( "WritingMode" ) >>= nWritingMode;
+ }
+ }
+ }
+ }
+ }
+ }
+ if( nWritingMode != -1 && nWritingMode != text::WritingMode2::PAGE )
+ {
+ if( pDrawModelWrapper )
+ pDrawModelWrapper->GetItemPool().SetPoolDefaultItem(SvxFrameDirectionItem(static_cast<SvxFrameDirection>(nWritingMode), EE_PARA_WRITINGDIR) );
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2" );
+ }
+}
+
+sal_Int16 lcl_getDefaultWritingModeFromPool( const std::shared_ptr<DrawModelWrapper>& pDrawModelWrapper )
+{
+ sal_Int16 nWritingMode = text::WritingMode2::LR_TB;
+ if(!pDrawModelWrapper)
+ return nWritingMode;
+
+ const SfxPoolItem& rItem = pDrawModelWrapper->GetItemPool().GetDefaultItem(EE_PARA_WRITINGDIR);
+ nWritingMode
+ = static_cast<sal_Int16>(static_cast<const SvxFrameDirectionItem&>(rItem).GetValue());
+ return nWritingMode;
+}
+
+} //end anonymous namespace
+
+awt::Rectangle ChartView::impl_createDiagramAndContent( const CreateShapeParam2D& rParam, const awt::Size& rPageSize )
+{
+ //return the used rectangle
+ awt::Rectangle aUsedOuterRect(rParam.maRemainingSpace.X, rParam.maRemainingSpace.Y, 0, 0);
+
+ rtl::Reference< Diagram > xDiagram( mrChartModel.getFirstChartDiagram() );
+ if( !xDiagram.is())
+ return aUsedOuterRect;
+
+ sal_Int32 nDimensionCount = DiagramHelper::getDimension( xDiagram );
+ if(!nDimensionCount)
+ {
+ //@todo handle mixed dimension
+ nDimensionCount = 2;
+ }
+
+ basegfx::B2IRectangle aAvailableOuterRect = BaseGFXHelper::makeRectangle(rParam.maRemainingSpace);
+
+ const std::vector< std::unique_ptr<VCoordinateSystem> >& rVCooSysList( rParam.mpSeriesPlotterContainer->getCooSysList() );
+ auto& rSeriesPlotterList = rParam.mpSeriesPlotterContainer->getSeriesPlotterList();
+
+ //create VAxis, so they can give necessary information for automatic scaling
+ uno::Reference<util::XNumberFormatsSupplier> const xNumberFormatsSupplier(
+ mrChartModel.getNumberFormatsSupplier());
+
+ for (auto& rpVCooSys : rVCooSysList)
+ {
+ if (nDimensionCount == 3)
+ {
+ CuboidPlanePosition eLeftWallPos( ThreeDHelper::getAutomaticCuboidPlanePositionForStandardLeftWall( xDiagram ) );
+ CuboidPlanePosition eBackWallPos( ThreeDHelper::getAutomaticCuboidPlanePositionForStandardBackWall( xDiagram ) );
+ CuboidPlanePosition eBottomPos( ThreeDHelper::getAutomaticCuboidPlanePositionForStandardBottom( xDiagram ) );
+ rpVCooSys->set3DWallPositions( eLeftWallPos, eBackWallPos, eBottomPos );
+ }
+
+ rpVCooSys->createVAxisList(&mrChartModel, rPageSize, rParam.maRemainingSpace, rParam.mbUseFixedInnerSize);
+ }
+
+ // - prepare list of all axis and how they are used
+ Date aNullDate = NumberFormatterWrapper( xNumberFormatsSupplier ).getNullDate();
+ rParam.mpSeriesPlotterContainer->initAxisUsageList(aNullDate);
+ rParam.mpSeriesPlotterContainer->doAutoScaling( mrChartModel );
+ rParam.mpSeriesPlotterContainer->setScalesFromCooSysToPlotter();
+ rParam.mpSeriesPlotterContainer->setNumberFormatsFromAxes();
+
+ //create shapes
+
+ //aspect ratio
+ drawing::Direction3D aPreferredAspectRatio =
+ rParam.mpSeriesPlotterContainer->getPreferredAspectRatio();
+
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTargetInFrontOfAxis;
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTargetBehindAxis;
+ VDiagram aVDiagram(xDiagram, aPreferredAspectRatio, nDimensionCount);
+ {//create diagram
+ aVDiagram.init(rParam.mxDiagramWithAxesShapes);
+ aVDiagram.createShapes(
+ awt::Point(rParam.maRemainingSpace.X, rParam.maRemainingSpace.Y),
+ awt::Size(rParam.maRemainingSpace.Width, rParam.maRemainingSpace.Height));
+
+ xSeriesTargetInFrontOfAxis = aVDiagram.getCoordinateRegion();
+ // It is preferable to use full size than minimum for pie charts
+ if (!rParam.mbUseFixedInnerSize)
+ aVDiagram.reduceToMinimumSize();
+ }
+
+ rtl::Reference<SvxShapeGroup> xTextTargetShapes =
+ ShapeFactory::createGroup2D(rParam.mxDiagramWithAxesShapes);
+
+ // - create axis and grids for all coordinate systems
+
+ //init all coordinate systems
+ for (auto& rpVCooSys : rVCooSysList)
+ {
+ rpVCooSys->initPlottingTargets(xSeriesTargetInFrontOfAxis, xTextTargetShapes, xSeriesTargetBehindAxis);
+
+ rpVCooSys->setTransformationSceneToScreen( B3DHomMatrixToHomogenMatrix(
+ createTransformationSceneToScreen( aVDiagram.getCurrentRectangle() ) ));
+
+ rpVCooSys->initVAxisInList();
+ }
+
+ //calculate resulting size respecting axis label layout and fontscaling
+
+ rtl::Reference<SvxShapeGroup> xBoundingShape(rParam.mxDiagramWithAxesShapes);
+ ::basegfx::B2IRectangle aConsumedOuterRect;
+
+ //use first coosys only so far; todo: calculate for more than one coosys if we have more in future
+ //todo: this is just a workaround at the moment for pie and donut labels
+ bool bIsPieOrDonut = lcl_IsPieOrDonut(xDiagram);
+ if( !bIsPieOrDonut && (!rVCooSysList.empty()) )
+ {
+ VCoordinateSystem* pVCooSys = rVCooSysList[0].get();
+ pVCooSys->createMaximumAxesLabels();
+
+ aConsumedOuterRect = ShapeFactory::getRectangleOfShape(*xBoundingShape);
+ ::basegfx::B2IRectangle aNewInnerRect( aVDiagram.getCurrentRectangle() );
+ if (!rParam.mbUseFixedInnerSize)
+ aNewInnerRect = aVDiagram.adjustInnerSize( aConsumedOuterRect );
+
+ pVCooSys->setTransformationSceneToScreen( B3DHomMatrixToHomogenMatrix(
+ createTransformationSceneToScreen( aNewInnerRect ) ));
+
+ //redo autoscaling to get size and text dependent automatic main increment count
+ rParam.mpSeriesPlotterContainer->doAutoScaling( mrChartModel );
+ rParam.mpSeriesPlotterContainer->updateScalesAndIncrementsOnAxes();
+ rParam.mpSeriesPlotterContainer->setScalesFromCooSysToPlotter();
+
+ pVCooSys->createAxesLabels();
+
+ bool bLessSpaceConsumedThanExpected = false;
+ {
+ aConsumedOuterRect = ShapeFactory::getRectangleOfShape(*xBoundingShape);
+ if( aConsumedOuterRect.getMinX() > aAvailableOuterRect.getMinX()
+ || aConsumedOuterRect.getMaxX() < aAvailableOuterRect.getMaxX()
+ || aConsumedOuterRect.getMinY() > aAvailableOuterRect.getMinY()
+ || aConsumedOuterRect.getMinY() < aAvailableOuterRect.getMaxY() )
+ {
+ bLessSpaceConsumedThanExpected = true;
+ }
+ }
+
+ if (bLessSpaceConsumedThanExpected && !rParam.mbUseFixedInnerSize)
+ {
+ aVDiagram.adjustInnerSize( aConsumedOuterRect );
+ pVCooSys->setTransformationSceneToScreen( B3DHomMatrixToHomogenMatrix(
+ createTransformationSceneToScreen( aVDiagram.getCurrentRectangle() ) ));
+
+ // Need to re-adjust again if the labels have changed height because of
+ // text can break. Ideally this shouldn't be needed, but the chart height
+ // isn't readjusted otherwise.
+ pVCooSys->createAxesLabels();
+ aConsumedOuterRect = ShapeFactory::getRectangleOfShape(*xBoundingShape);
+ aVDiagram.adjustInnerSize(aConsumedOuterRect);
+ pVCooSys->setTransformationSceneToScreen(B3DHomMatrixToHomogenMatrix(
+ createTransformationSceneToScreen(aVDiagram.getCurrentRectangle())));
+
+ }
+ pVCooSys->updatePositions();//todo: logically this belongs to the condition above, but it seems also to be necessary to give the axes group shapes the right bounding rects for hit test - probably caused by bug i106183 -> check again if fixed
+ }
+
+ //create axes and grids for the final size
+ for (auto& rpVCooSys : rVCooSysList)
+ {
+ rpVCooSys->setTransformationSceneToScreen( B3DHomMatrixToHomogenMatrix(
+ createTransformationSceneToScreen( aVDiagram.getCurrentRectangle() ) ));
+
+ rpVCooSys->createAxesShapes();
+ rpVCooSys->createGridShapes();
+ }
+
+ // - create data series for all charttypes
+ m_bPointsWereSkipped = false;
+ for( const std::unique_ptr<VSeriesPlotter>& aPlotter : rSeriesPlotterList )
+ {
+ VSeriesPlotter* pSeriesPlotter = aPlotter.get();
+ rtl::Reference<SvxShapeGroupAnyD> xSeriesTarget;
+ if( pSeriesPlotter->WantToPlotInFrontOfAxisLine() )
+ xSeriesTarget = xSeriesTargetInFrontOfAxis;
+ else
+ {
+ xSeriesTarget = xSeriesTargetBehindAxis;
+ OSL_ENSURE( !bIsPieOrDonut, "not implemented yet! - during a complete recreation this shape is destroyed so no series can be created anymore" );
+ }
+ pSeriesPlotter->initPlotter( xSeriesTarget,xTextTargetShapes,OUString() );
+ pSeriesPlotter->setPageReferenceSize( rPageSize );
+ VCoordinateSystem* pVCooSys = SeriesPlotterContainer::getCooSysForPlotter( rVCooSysList, pSeriesPlotter );
+ if(nDimensionCount==2)
+ pSeriesPlotter->setTransformationSceneToScreen( pVCooSys->getTransformationSceneToScreen() );
+ //better performance for big data
+ {
+ //calculate resolution for coordinate system
+ Sequence<sal_Int32> aCoordinateSystemResolution = pVCooSys->getCoordinateSystemResolution( rPageSize, m_aPageResolution );
+ pSeriesPlotter->setCoordinateSystemResolution( aCoordinateSystemResolution );
+ }
+ // Do not allow to move data labels in case of pie or donut chart, yet!
+ pSeriesPlotter->setPieLabelsAllowToMove(!bIsPieOrDonut);
+ // use the pagesize as remaining space if we have a fixed inner size
+ if( rParam.mbUseFixedInnerSize )
+ aAvailableOuterRect = BaseGFXHelper::makeRectangle(awt::Rectangle(0, 0, rPageSize.Width, rPageSize.Height));
+ // set the available space for data labels to avoid moving out from chart area
+ pSeriesPlotter->setAvailableOuterRect(aAvailableOuterRect);
+ pSeriesPlotter->createShapes();
+ m_bPointsWereSkipped = m_bPointsWereSkipped || pSeriesPlotter->PointsWereSkipped();
+ }
+
+ //recreate all with corrected sizes if requested
+ if( bIsPieOrDonut )
+ {
+ m_bPointsWereSkipped = false;
+
+ aConsumedOuterRect = ShapeFactory::getRectangleOfShape(*xBoundingShape);
+ ::basegfx::B2IRectangle aNewInnerRect( aVDiagram.getCurrentRectangle() );
+ if (!rParam.mbUseFixedInnerSize)
+ aNewInnerRect = aVDiagram.adjustInnerSize( aConsumedOuterRect );
+
+ for( std::unique_ptr<VSeriesPlotter>& aPlotter : rSeriesPlotterList )
+ {
+ aPlotter->releaseShapes();
+ }
+
+ //clear and recreate
+ ShapeFactory::removeSubShapes( xSeriesTargetInFrontOfAxis ); //xSeriesTargetBehindAxis is a sub shape of xSeriesTargetInFrontOfAxis and will be removed here
+ xSeriesTargetBehindAxis.clear();
+ ShapeFactory::removeSubShapes( xTextTargetShapes );
+
+ //set new transformation
+ for (auto& rpVCooSys : rVCooSysList)
+ {
+ auto aMatrix = createTransformationSceneToScreen(aNewInnerRect);
+ rpVCooSys->setTransformationSceneToScreen(B3DHomMatrixToHomogenMatrix(aMatrix));
+ }
+
+ // - create data series for all charttypes
+ for( std::unique_ptr<VSeriesPlotter>& aPlotter : rSeriesPlotterList )
+ {
+ VCoordinateSystem* pVCooSys = SeriesPlotterContainer::getCooSysForPlotter( rVCooSysList, aPlotter.get() );
+ if(nDimensionCount==2)
+ aPlotter->setTransformationSceneToScreen( pVCooSys->getTransformationSceneToScreen() );
+ // Now we can move data labels in case of pie or donut chart!
+ aPlotter->setPieLabelsAllowToMove(bIsPieOrDonut);
+ aPlotter->createShapes();
+ m_bPointsWereSkipped = m_bPointsWereSkipped || aPlotter->PointsWereSkipped();
+ }
+
+ for( std::unique_ptr<VSeriesPlotter>& aPlotter : rSeriesPlotterList )
+ aPlotter->rearrangeLabelToAvoidOverlapIfRequested(rPageSize);
+ }
+
+ if (rParam.mbUseFixedInnerSize)
+ {
+ aUsedOuterRect = awt::Rectangle( aConsumedOuterRect.getMinX(), aConsumedOuterRect.getMinY(), aConsumedOuterRect.getWidth(), aConsumedOuterRect.getHeight() );
+ }
+ else
+ aUsedOuterRect = rParam.maRemainingSpace;
+
+ bool bSnapRectToUsedArea = false;
+ for( std::unique_ptr<VSeriesPlotter>& aPlotter : rSeriesPlotterList )
+ {
+ bSnapRectToUsedArea = aPlotter->shouldSnapRectToUsedArea();
+ if(bSnapRectToUsedArea)
+ break;
+ }
+ if(bSnapRectToUsedArea)
+ {
+ if (rParam.mbUseFixedInnerSize)
+ m_aResultingDiagramRectangleExcludingAxes = getRectangleOfObject( "PlotAreaExcludingAxes" );
+ else
+ {
+ ::basegfx::B2IRectangle aConsumedInnerRect = aVDiagram.getCurrentRectangle();
+ m_aResultingDiagramRectangleExcludingAxes = BaseGFXHelper::toAwtRectangle(aConsumedInnerRect);
+ }
+ }
+ else
+ {
+ if (rParam.mbUseFixedInnerSize)
+ m_aResultingDiagramRectangleExcludingAxes = rParam.maRemainingSpace;
+ else
+ {
+ ::basegfx::B2IRectangle aConsumedInnerRect = aVDiagram.getCurrentRectangle();
+ m_aResultingDiagramRectangleExcludingAxes = BaseGFXHelper::toAwtRectangle(aConsumedInnerRect);
+ }
+ }
+
+ if (rParam.mxMarkHandles.is())
+ {
+ awt::Point aPos(rParam.maRemainingSpace.X, rParam.maRemainingSpace.Y);
+ awt::Size aSize(rParam.maRemainingSpace.Width, rParam.maRemainingSpace.Height);
+
+ bool bPosSizeExcludeAxesProperty = true;
+ xDiagram->getPropertyValue("PosSizeExcludeAxes") >>= bPosSizeExcludeAxesProperty;
+ if (rParam.mbUseFixedInnerSize || bPosSizeExcludeAxesProperty)
+ {
+ aPos = awt::Point( m_aResultingDiagramRectangleExcludingAxes.X, m_aResultingDiagramRectangleExcludingAxes.Y );
+ aSize = awt::Size( m_aResultingDiagramRectangleExcludingAxes.Width, m_aResultingDiagramRectangleExcludingAxes.Height );
+ }
+ rParam.mxMarkHandles->setPosition(aPos);
+ rParam.mxMarkHandles->setSize(aSize);
+ }
+
+ return aUsedOuterRect;
+}
+
+bool ChartView::getExplicitValuesForAxis(
+ uno::Reference< XAxis > xAxis
+ , ExplicitScaleData& rExplicitScale
+ , ExplicitIncrementData& rExplicitIncrement )
+{
+ SolarMutexGuard aSolarGuard;
+
+ impl_updateView();
+
+ if(!xAxis.is())
+ return false;
+
+ rtl::Reference< BaseCoordinateSystem > xCooSys = AxisHelper::getCoordinateSystemOfAxis(xAxis, mrChartModel.getFirstChartDiagram() );
+ const VCoordinateSystem* pVCooSys = SeriesPlotterContainer::findInCooSysList(m_aVCooSysList, xCooSys);
+ if(!pVCooSys)
+ return false;
+
+ sal_Int32 nDimensionIndex=-1;
+ sal_Int32 nAxisIndex=-1;
+ if( !AxisHelper::getIndicesForAxis( xAxis, xCooSys, nDimensionIndex, nAxisIndex ) )
+ return false;
+
+ rExplicitScale = pVCooSys->getExplicitScale(nDimensionIndex,nAxisIndex);
+ rExplicitIncrement = pVCooSys->getExplicitIncrement(nDimensionIndex,nAxisIndex);
+ if( !rExplicitScale.m_bShiftedCategoryPosition )
+ return true;
+
+ //remove 'one' from max
+ if( rExplicitScale.AxisType == css::chart2::AxisType::DATE )
+ {
+ Date aMaxDate(rExplicitScale.NullDate); aMaxDate.AddDays(::rtl::math::approxFloor(rExplicitScale.Maximum));
+ //for explicit scales with shifted categories we need one interval more
+ switch( rExplicitScale.TimeResolution )
+ {
+ case css::chart::TimeUnit::DAY:
+ --aMaxDate;
+ break;
+ case css::chart::TimeUnit::MONTH:
+ aMaxDate = DateHelper::GetDateSomeMonthsAway(aMaxDate,-1);
+ break;
+ case css::chart::TimeUnit::YEAR:
+ aMaxDate = DateHelper::GetDateSomeYearsAway(aMaxDate,-1);
+ break;
+ }
+ rExplicitScale.Maximum = aMaxDate - rExplicitScale.NullDate;
+ }
+ else if( rExplicitScale.AxisType == css::chart2::AxisType::CATEGORY )
+ rExplicitScale.Maximum -= 1.0;
+ else if( rExplicitScale.AxisType == css::chart2::AxisType::SERIES )
+ rExplicitScale.Maximum -= 1.0;
+ return true;
+}
+
+SdrPage* ChartView::getSdrPage()
+{
+ if(m_xDrawPage)
+ return m_xDrawPage->GetSdrPage();
+
+ return nullptr;
+}
+
+rtl::Reference< SvxShape > ChartView::getShapeForCID( const OUString& rObjectCID )
+{
+ SolarMutexGuard aSolarGuard;
+ SdrObject* pObj = DrawModelWrapper::getNamedSdrObject( rObjectCID, this->getSdrPage() );
+ if( !pObj )
+ return nullptr;
+
+ uno::Reference< drawing::XShape > xShape = pObj->getUnoShape();
+ rtl::Reference<SvxShape> xShape2 = dynamic_cast<SvxShape*>(xShape.get());
+ assert(xShape2 || !xShape);
+ return xShape2;
+}
+
+awt::Rectangle ChartView::getDiagramRectangleExcludingAxes()
+{
+ impl_updateView();
+ return m_aResultingDiagramRectangleExcludingAxes;
+}
+
+awt::Rectangle ChartView::getRectangleOfObject( const OUString& rObjectCID, bool bSnapRect )
+{
+ impl_updateView();
+
+ awt::Rectangle aRet;
+ rtl::Reference< SvxShape > xShape = getShapeForCID(rObjectCID);
+ if(xShape.is())
+ {
+ //special handling for axis for old api:
+ //same special handling for diagram
+ ObjectType eObjectType( ObjectIdentifier::getObjectType( rObjectCID ) );
+ if( eObjectType == OBJECTTYPE_AXIS || eObjectType == OBJECTTYPE_DIAGRAM )
+ {
+ SolarMutexGuard aSolarGuard;
+ SdrObject* pRootSdrObject = xShape->GetSdrObject();
+ if( pRootSdrObject )
+ {
+ SdrObjList* pRootList = pRootSdrObject->GetSubList();
+ if( pRootList )
+ {
+ OUString aShapeName = "MarkHandles";
+ if( eObjectType == OBJECTTYPE_DIAGRAM )
+ aShapeName = "PlotAreaIncludingAxes";
+ SdrObject* pShape = DrawModelWrapper::getNamedSdrObject( aShapeName, pRootList );
+ if( pShape )
+ {
+ xShape = dynamic_cast<SvxShape*>(pShape->getUnoShape().get());
+ assert(xShape);
+ }
+ }
+ }
+ }
+
+ awt::Size aSize( xShape->getSize() );
+ awt::Point aPoint( xShape->getPosition() );
+ aRet = awt::Rectangle( aPoint.X, aPoint.Y, aSize.Width, aSize.Height );
+ if( bSnapRect )
+ {
+ //for rotated objects the shape size and position differs from the visible rectangle
+ SdrObject* pSdrObject = xShape->GetSdrObject();
+ if( pSdrObject )
+ {
+ tools::Rectangle aSnapRect( pSdrObject->GetSnapRect() );
+ aRet = awt::Rectangle(aSnapRect.Left(),aSnapRect.Top(),aSnapRect.GetWidth(),aSnapRect.GetHeight());
+ }
+ }
+ }
+ return aRet;
+}
+
+std::shared_ptr< DrawModelWrapper > ChartView::getDrawModelWrapper()
+{
+ return m_pDrawModelWrapper;
+}
+
+namespace
+{
+
+constexpr double constPageLayoutDistancePercentage = 0.02;
+
+bool getAvailablePosAndSizeForDiagram(
+ CreateShapeParam2D& rParam, const awt::Size & rPageSize, const uno::Reference< beans::XPropertySet >& xProp)
+{
+ rParam.mbUseFixedInnerSize = false;
+
+ //@todo: we need a size dependent on the axis labels
+ sal_Int32 nYDistance = static_cast<sal_Int32>(rPageSize.Height * constPageLayoutDistancePercentage);
+ sal_Int32 nXDistance = static_cast<sal_Int32>(rPageSize.Width * constPageLayoutDistancePercentage);
+ rParam.maRemainingSpace.X += nXDistance;
+ rParam.maRemainingSpace.Width -= 2*nXDistance;
+ rParam.maRemainingSpace.Y += nYDistance;
+ rParam.maRemainingSpace.Height -= 2*nYDistance;
+
+ bool bPosSizeExcludeAxes = false;
+ if( xProp.is() )
+ xProp->getPropertyValue( "PosSizeExcludeAxes" ) >>= bPosSizeExcludeAxes;
+
+ //size:
+ css::chart2::RelativeSize aRelativeSize;
+ if( xProp.is() && (xProp->getPropertyValue( "RelativeSize" )>>=aRelativeSize) )
+ {
+ rParam.maRemainingSpace.Height = static_cast<sal_Int32>(aRelativeSize.Secondary*rPageSize.Height);
+ rParam.maRemainingSpace.Width = static_cast<sal_Int32>(aRelativeSize.Primary*rPageSize.Width);
+ rParam.mbUseFixedInnerSize = bPosSizeExcludeAxes;
+ }
+
+ if (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0)
+ return false;
+
+ //position:
+ chart2::RelativePosition aRelativePosition;
+ if( xProp.is() && (xProp->getPropertyValue( "RelativePosition" )>>=aRelativePosition) )
+ {
+ //@todo decide whether x is primary or secondary
+
+ //the coordinates re relative to the page
+ double fX = aRelativePosition.Primary*rPageSize.Width;
+ double fY = aRelativePosition.Secondary*rPageSize.Height;
+
+ awt::Point aPos = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
+ awt::Point(static_cast<sal_Int32>(fX),static_cast<sal_Int32>(fY)),
+ awt::Size(rParam.maRemainingSpace.Width, rParam.maRemainingSpace.Height),
+ aRelativePosition.Anchor);
+
+ rParam.maRemainingSpace.X = aPos.X;
+ rParam.maRemainingSpace.Y = aPos.Y;
+
+ rParam.mbUseFixedInnerSize = bPosSizeExcludeAxes;
+ }
+
+ //ensure that the diagram does not lap out right side or out of bottom
+ if (rParam.maRemainingSpace.Y + rParam.maRemainingSpace.Height > rPageSize.Height)
+ rParam.maRemainingSpace.Height = rPageSize.Height - rParam.maRemainingSpace.Y;
+
+ if (rParam.maRemainingSpace.X + rParam.maRemainingSpace.Width > rPageSize.Width)
+ rParam.maRemainingSpace.Width = rPageSize.Width - rParam.maRemainingSpace.X;
+
+ return true;
+}
+
+enum class TitleAlignment { ALIGN_LEFT, ALIGN_TOP, ALIGN_RIGHT, ALIGN_BOTTOM, ALIGN_Z };
+
+void changePositionOfAxisTitle( VTitle* pVTitle, TitleAlignment eAlignment
+ , awt::Rectangle const & rDiagramPlusAxesRect, const awt::Size & rPageSize )
+{
+ if(!pVTitle)
+ return;
+
+ awt::Point aNewPosition(0,0);
+ awt::Size aTitleSize = pVTitle->getFinalSize();
+ sal_Int32 nYDistance = static_cast<sal_Int32>(rPageSize.Height * constPageLayoutDistancePercentage);
+ sal_Int32 nXDistance = static_cast<sal_Int32>(rPageSize.Width * constPageLayoutDistancePercentage);
+ switch (eAlignment)
+ {
+ case TitleAlignment::ALIGN_TOP:
+ aNewPosition = awt::Point( rDiagramPlusAxesRect.X + rDiagramPlusAxesRect.Width/2
+ , rDiagramPlusAxesRect.Y - aTitleSize.Height/2 - nYDistance );
+ break;
+ case TitleAlignment::ALIGN_BOTTOM:
+ aNewPosition = awt::Point( rDiagramPlusAxesRect.X + rDiagramPlusAxesRect.Width/2
+ , rDiagramPlusAxesRect.Y + rDiagramPlusAxesRect.Height + aTitleSize.Height/2 + nYDistance );
+ break;
+ case TitleAlignment::ALIGN_LEFT:
+ aNewPosition = awt::Point( rDiagramPlusAxesRect.X - aTitleSize.Width/2 - nXDistance
+ , rDiagramPlusAxesRect.Y + rDiagramPlusAxesRect.Height/2 );
+ break;
+ case TitleAlignment::ALIGN_RIGHT:
+ aNewPosition = awt::Point( rDiagramPlusAxesRect.X + rDiagramPlusAxesRect.Width + aTitleSize.Width/2 + nXDistance
+ , rDiagramPlusAxesRect.Y + rDiagramPlusAxesRect.Height/2 );
+ break;
+ case TitleAlignment::ALIGN_Z:
+ aNewPosition = awt::Point( rDiagramPlusAxesRect.X + rDiagramPlusAxesRect.Width + aTitleSize.Width/2 + nXDistance
+ , rDiagramPlusAxesRect.Y + rDiagramPlusAxesRect.Height - aTitleSize.Height/2 );
+ break;
+ }
+
+ sal_Int32 nMaxY = rPageSize.Height - aTitleSize.Height/2;
+ sal_Int32 nMaxX = rPageSize.Width - aTitleSize.Width/2;
+ sal_Int32 nMinX = aTitleSize.Width/2;
+ sal_Int32 nMinY = aTitleSize.Height/2;
+ if( aNewPosition.Y > nMaxY )
+ aNewPosition.Y = nMaxY;
+ if( aNewPosition.X > nMaxX )
+ aNewPosition.X = nMaxX;
+ if( aNewPosition.Y < nMinY )
+ aNewPosition.Y = nMinY;
+ if( aNewPosition.X < nMinX )
+ aNewPosition.X = nMinX;
+
+ pVTitle->changePosition( aNewPosition );
+}
+
+std::shared_ptr<VTitle> lcl_createTitle( TitleHelper::eTitleType eType
+ , const rtl::Reference<SvxShapeGroupAnyD>& xPageShapes
+ , ChartModel& rModel
+ , awt::Rectangle& rRemainingSpace
+ , const awt::Size & rPageSize
+ , TitleAlignment eAlignment
+ , bool& rbAutoPosition )
+{
+ std::shared_ptr<VTitle> apVTitle;
+
+ // #i109336# Improve auto positioning in chart
+ double fPercentage = constPageLayoutDistancePercentage;
+ sal_Int32 nXDistance = static_cast< sal_Int32 >( rPageSize.Width * fPercentage );
+ sal_Int32 nYDistance = static_cast< sal_Int32 >( rPageSize.Height * fPercentage );
+ if ( eType == TitleHelper::MAIN_TITLE )
+ {
+ nYDistance += 135; // 1/100 mm
+ }
+ else if ( eType == TitleHelper::TITLE_AT_STANDARD_X_AXIS_POSITION )
+ {
+ nYDistance = 420; // 1/100 mm
+ }
+ else if ( eType == TitleHelper::TITLE_AT_STANDARD_Y_AXIS_POSITION )
+ {
+ nXDistance = 450; // 1/100 mm
+ }
+
+ uno::Reference< XTitle > xTitle( TitleHelper::getTitle( eType, rModel ) );
+ OUString aCompleteString = TitleHelper::getCompleteString(xTitle);
+ if (aCompleteString.isEmpty() || !VTitle::isVisible(xTitle))
+ return apVTitle;
+
+ //create title
+ awt::Size aTextMaxWidth(rPageSize.Width, rPageSize.Height);
+ bool bYAxisTitle = false;
+ if (eType == TitleHelper::MAIN_TITLE || eType == TitleHelper::SUB_TITLE)
+ {
+ aTextMaxWidth.Width = static_cast<sal_Int32>(rPageSize.Width * 0.8);
+ aTextMaxWidth.Height = static_cast<sal_Int32>(rPageSize.Height * 0.5);
+ }
+ else if (eType == TitleHelper::X_AXIS_TITLE || eType == TitleHelper::SECONDARY_X_AXIS_TITLE
+ || eType == TitleHelper::TITLE_AT_STANDARD_X_AXIS_POSITION)
+ {
+ aTextMaxWidth.Width = static_cast<sal_Int32>(rPageSize.Width * 0.8);
+ aTextMaxWidth.Height = static_cast<sal_Int32>(rPageSize.Height * 0.2);
+ }
+ else if (eType == TitleHelper::Y_AXIS_TITLE || eType == TitleHelper::SECONDARY_Y_AXIS_TITLE
+ || eType == TitleHelper::TITLE_AT_STANDARD_Y_AXIS_POSITION)
+ {
+ aTextMaxWidth.Width = static_cast<sal_Int32>(rPageSize.Width * 0.2);
+ aTextMaxWidth.Height = static_cast<sal_Int32>(rPageSize.Height * 0.8);
+ bYAxisTitle = true;
+ }
+ apVTitle = std::make_shared<VTitle>(xTitle);
+ OUString aCID = ObjectIdentifier::createClassifiedIdentifierForObject(xTitle, &rModel);
+ apVTitle->init(xPageShapes, aCID);
+ apVTitle->createShapes(awt::Point(0, 0), rPageSize, aTextMaxWidth, bYAxisTitle);
+ awt::Size aTitleUnrotatedSize = apVTitle->getUnrotatedSize();
+ awt::Size aTitleSize = apVTitle->getFinalSize();
+
+ //position
+ rbAutoPosition = true;
+ awt::Point aNewPosition(0,0);
+ chart2::RelativePosition aRelativePosition;
+ uno::Reference<beans::XPropertySet> xProp(xTitle, uno::UNO_QUERY);
+ if (xProp.is() && (xProp->getPropertyValue("RelativePosition") >>= aRelativePosition))
+ {
+ rbAutoPosition = false;
+
+ //@todo decide whether x is primary or secondary
+ double fX = aRelativePosition.Primary*rPageSize.Width;
+ double fY = aRelativePosition.Secondary*rPageSize.Height;
+
+ double fAnglePi = apVTitle->getRotationAnglePi();
+ aNewPosition = RelativePositionHelper::getCenterOfAnchoredObject(
+ awt::Point(static_cast<sal_Int32>(fX),static_cast<sal_Int32>(fY))
+ , aTitleUnrotatedSize, aRelativePosition.Anchor, fAnglePi );
+ }
+ else //auto position
+ {
+ switch( eAlignment )
+ {
+ case TitleAlignment::ALIGN_TOP:
+ aNewPosition = awt::Point( rRemainingSpace.X + rRemainingSpace.Width/2
+ , rRemainingSpace.Y + aTitleSize.Height/2 + nYDistance );
+ break;
+ case TitleAlignment::ALIGN_BOTTOM:
+ aNewPosition = awt::Point( rRemainingSpace.X + rRemainingSpace.Width/2
+ , rRemainingSpace.Y + rRemainingSpace.Height - aTitleSize.Height/2 - nYDistance );
+ break;
+ case TitleAlignment::ALIGN_LEFT:
+ aNewPosition = awt::Point( rRemainingSpace.X + aTitleSize.Width/2 + nXDistance
+ , rRemainingSpace.Y + rRemainingSpace.Height/2 );
+ break;
+ case TitleAlignment::ALIGN_RIGHT:
+ aNewPosition = awt::Point( rRemainingSpace.X + rRemainingSpace.Width - aTitleSize.Width/2 - nXDistance
+ , rRemainingSpace.Y + rRemainingSpace.Height/2 );
+ break;
+ case TitleAlignment::ALIGN_Z:
+ break;
+
+ }
+ }
+ apVTitle->changePosition( aNewPosition );
+
+ //remaining space
+ switch( eAlignment )
+ {
+ case TitleAlignment::ALIGN_TOP:
+ // Push the remaining space down from top.
+ rRemainingSpace.Y += ( aTitleSize.Height + nYDistance );
+ rRemainingSpace.Height -= ( aTitleSize.Height + nYDistance );
+ break;
+ case TitleAlignment::ALIGN_BOTTOM:
+ // Push the remaining space up from bottom.
+ rRemainingSpace.Height -= ( aTitleSize.Height + nYDistance );
+ break;
+ case TitleAlignment::ALIGN_LEFT:
+ // Push the remaining space to the right from left edge.
+ rRemainingSpace.X += ( aTitleSize.Width + nXDistance );
+ rRemainingSpace.Width -= ( aTitleSize.Width + nXDistance );
+ break;
+ case TitleAlignment::ALIGN_RIGHT:
+ // Push the remaining space to the left from right edge.
+ rRemainingSpace.Width -= ( aTitleSize.Width + nXDistance );
+ break;
+ case TitleAlignment::ALIGN_Z:
+ break;
+ }
+
+ return apVTitle;
+}
+
+bool lcl_createLegend( const rtl::Reference< Legend > & xLegend
+ , const rtl::Reference<SvxShapeGroupAnyD>& xPageShapes
+ , const uno::Reference< uno::XComponentContext > & xContext
+ , awt::Rectangle & rRemainingSpace
+ , const awt::Size & rPageSize
+ , ChartModel& rModel
+ , std::vector< LegendEntryProvider* >&& rLegendEntryProviderList
+ , sal_Int16 nDefaultWritingMode )
+{
+ if (!VLegend::isVisible(xLegend))
+ return false;
+
+ awt::Size rDefaultLegendSize;
+ VLegend aVLegend( xLegend, xContext, std::move(rLegendEntryProviderList),
+ xPageShapes, rModel);
+ aVLegend.setDefaultWritingMode( nDefaultWritingMode );
+ aVLegend.createShapes( awt::Size( rRemainingSpace.Width, rRemainingSpace.Height ),
+ rPageSize, rDefaultLegendSize );
+ aVLegend.changePosition( rRemainingSpace, rPageSize, rDefaultLegendSize );
+ return true;
+}
+
+void lcl_createButtons(const rtl::Reference<SvxShapeGroupAnyD>& xPageShapes,
+ ChartModel& rModel,
+ awt::Rectangle& rRemainingSpace)
+{
+ uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(rModel.getDataProvider(), uno::UNO_QUERY);
+ if (!xPivotTableDataProvider.is())
+ return;
+
+ uno::Reference<beans::XPropertySet> xModelPage(rModel.getPageBackground());
+
+ awt::Size aSize(4000, 700); // size of the button
+
+ tools::Long x = 0;
+
+ if (xPivotTableDataProvider->getPageFields().hasElements())
+ {
+ x = 0;
+
+ const css::uno::Sequence<chart2::data::PivotTableFieldEntry> aPivotFieldEntries = xPivotTableDataProvider->getPageFields();
+ for (css::chart2::data::PivotTableFieldEntry const & rPageFieldEntry : aPivotFieldEntries)
+ {
+ VButton aButton;
+ aButton.init(xPageShapes);
+ awt::Point aNewPosition(rRemainingSpace.X + x + 100, rRemainingSpace.Y + 100);
+ sal_Int32 nDimensionIndex = rPageFieldEntry.DimensionIndex;
+ OUString aFieldOutputDescription = xPivotTableDataProvider->getFieldOutputDescription(nDimensionIndex);
+ aButton.setLabel(rPageFieldEntry.Name + " | " + aFieldOutputDescription);
+ aButton.setCID("FieldButton.Page." + OUString::number(nDimensionIndex));
+ aButton.setPosition(aNewPosition);
+ aButton.setSize(aSize);
+ if (rPageFieldEntry.HasHiddenMembers)
+ aButton.setArrowColor(Color(0x0000FF));
+
+ aButton.createShapes(xModelPage);
+ x += aSize.Width + 100;
+ }
+ rRemainingSpace.Y += (aSize.Height + 100 + 100);
+ rRemainingSpace.Height -= (aSize.Height + 100 + 100);
+ }
+
+ aSize = awt::Size(3000, 700); // size of the button
+
+ if (!xPivotTableDataProvider->getRowFields().hasElements())
+ return;
+
+ x = 200;
+ const css::uno::Sequence<chart2::data::PivotTableFieldEntry> aPivotFieldEntries = xPivotTableDataProvider->getRowFields();
+ for (css::chart2::data::PivotTableFieldEntry const & rRowFieldEntry : aPivotFieldEntries)
+ {
+ VButton aButton;
+ aButton.init(xPageShapes);
+ awt::Point aNewPosition(rRemainingSpace.X + x + 100,
+ rRemainingSpace.Y + rRemainingSpace.Height - aSize.Height - 100);
+ aButton.setLabel(rRowFieldEntry.Name);
+ aButton.setCID("FieldButton.Row." + OUString::number(rRowFieldEntry.DimensionIndex));
+ aButton.setPosition(aNewPosition);
+ aButton.setSize(aSize);
+ if ( rRowFieldEntry.Name == "Data" )
+ {
+ aButton.setBGColor( Color(0x00F6F6F6) );
+ aButton.showArrow( false );
+ }
+ else if (rRowFieldEntry.HasHiddenMembers)
+ aButton.setArrowColor(Color(0x0000FF));
+ aButton.createShapes(xModelPage);
+ x += aSize.Width + 100;
+ }
+ rRemainingSpace.Height -= (aSize.Height + 100 + 100);
+}
+
+void formatPage(
+ ChartModel& rChartModel
+ , const awt::Size& rPageSize
+ , const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ )
+{
+ try
+ {
+ uno::Reference< beans::XPropertySet > xModelPage( rChartModel.getPageBackground());
+ if( ! xModelPage.is())
+ return;
+
+ //format page
+ tPropertyNameValueMap aNameValueMap;
+ PropertyMapper::getValueMap( aNameValueMap, PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xModelPage );
+
+ OUString aCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_PAGE, u"" ) );
+ aNameValueMap.emplace( "Name", uno::Any( aCID ) ); //CID OUString
+
+ tNameSequence aNames;
+ tAnySequence aValues;
+ PropertyMapper::getMultiPropertyListsFromValueMap( aNames, aValues, aNameValueMap );
+
+ ShapeFactory::createRectangle(
+ xTarget, rPageSize, awt::Point(0, 0), aNames, aValues);
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2" );
+ }
+}
+
+void lcl_removeEmptyGroupShapes( const SdrObject& rParent )
+{
+ SdrObjList* pObjList = rParent.getChildrenOfSdrObject();
+ if (!pObjList || pObjList->GetObjCount() == 0)
+ return;
+
+ //iterate from back!
+ for(auto nIdx = static_cast<sal_Int32>(pObjList->GetObjCount() - 1); nIdx >= 0; --nIdx)
+ {
+ SdrObject* pChildSdrObject = pObjList->GetObj(nIdx);
+ SdrObjList* pChildObjList = pChildSdrObject->getChildrenOfSdrObject();
+ if (!pChildObjList)
+ continue;
+ if (pChildObjList->GetObjCount() == 0)
+ {
+ //remove empty group shape
+ SdrObject* pRemoved = pObjList->NbcRemoveObject(nIdx);
+ SdrObject::Free( pRemoved );
+ }
+ else
+ lcl_removeEmptyGroupShapes(*pChildSdrObject);
+ }
+}
+
+}
+
+void ChartView::impl_refreshAddIn()
+{
+ if( !m_bRefreshAddIn )
+ return;
+
+ uno::Reference< beans::XPropertySet > xProp( static_cast< ::cppu::OWeakObject* >( &mrChartModel ), uno::UNO_QUERY );
+ if( !xProp.is())
+ return;
+
+ try
+ {
+ uno::Reference< util::XRefreshable > xAddIn;
+ xProp->getPropertyValue( "AddIn" ) >>= xAddIn;
+ if( xAddIn.is() )
+ {
+ bool bRefreshAddInAllowed = true;
+ xProp->getPropertyValue( "RefreshAddInAllowed" ) >>= bRefreshAddInAllowed;
+ if( bRefreshAddInAllowed )
+ xAddIn->refresh();
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+void ChartView::createShapes()
+{
+ SolarMutexGuard aSolarGuard;
+
+ osl::MutexGuard aTimedGuard(maTimeMutex);
+ if(mrChartModel.isTimeBased())
+ {
+ maTimeBased.bTimeBased = true;
+ }
+
+ //make sure add-in is refreshed after creating the shapes
+ const ::comphelper::ScopeGuard aGuard( [this]() { this->impl_refreshAddIn(); } );
+
+ m_aResultingDiagramRectangleExcludingAxes = awt::Rectangle(0,0,0,0);
+ impl_deleteCoordinateSystems();
+ if( m_pDrawModelWrapper )
+ {
+ // #i12587# support for shapes in chart
+ m_pDrawModelWrapper->getSdrModel().EnableUndo( false );
+ m_pDrawModelWrapper->clearMainDrawPage();
+ }
+
+ lcl_setDefaultWritingMode( m_pDrawModelWrapper, mrChartModel );
+
+ awt::Size aPageSize = mrChartModel.getVisualAreaSize( embed::Aspects::MSOLE_CONTENT );
+
+ if(!mxRootShape.is())
+ mxRootShape = ShapeFactory::getOrCreateChartRootShape( m_xDrawPage );
+
+ SdrPage* pPage = ChartView::getSdrPage();
+ if(pPage) //it is necessary to use the implementation here as the uno page does not provide a propertyset
+ pPage->SetSize(Size(aPageSize.Width,aPageSize.Height));
+ else
+ {
+ OSL_FAIL("could not set page size correctly");
+ }
+ ShapeFactory::setPageSize(mxRootShape, aPageSize);
+
+ createShapes2D(aPageSize);
+
+ // #i12587# support for shapes in chart
+ if ( m_pDrawModelWrapper )
+ {
+ m_pDrawModelWrapper->getSdrModel().EnableUndo( true );
+ }
+
+ if(maTimeBased.bTimeBased)
+ {
+ maTimeBased.nFrame++;
+ }
+}
+
+// util::XEventListener (base of XCloseListener)
+void SAL_CALL ChartView::disposing( const lang::EventObject& /* rSource */ )
+{
+}
+
+void ChartView::impl_updateView( bool bCheckLockedCtrler )
+{
+ if( !m_pDrawModelWrapper )
+ return;
+
+ // #i12587# support for shapes in chart
+ if ( m_bSdrViewIsInEditMode )
+ {
+ return;
+ }
+
+ if (bCheckLockedCtrler && mrChartModel.hasControllersLocked())
+ return;
+
+ if( !m_bViewDirty || m_bInViewUpdate )
+ return;
+
+ m_bInViewUpdate = true;
+ //bool bOldRefreshAddIn = m_bRefreshAddIn;
+ //m_bRefreshAddIn = false;
+ try
+ {
+ impl_notifyModeChangeListener("invalid");
+
+ //prepare draw model
+ {
+ SolarMutexGuard aSolarGuard;
+ m_pDrawModelWrapper->lockControllers();
+ }
+
+ //create chart view
+ {
+ m_bViewDirty = false;
+ m_bViewUpdatePending = false;
+ createShapes();
+
+ if( m_bViewDirty )
+ {
+ //avoid recursions due to add-in
+ m_bRefreshAddIn = false;
+ m_bViewDirty = false;
+ m_bViewUpdatePending = false;
+ //delete old chart view
+ createShapes();
+ m_bRefreshAddIn = true;
+ }
+ }
+
+ m_bViewDirty = m_bViewUpdatePending;
+ m_bViewUpdatePending = false;
+ m_bInViewUpdate = false;
+ }
+ catch( const uno::Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2" );
+ m_bViewDirty = m_bViewUpdatePending;
+ m_bViewUpdatePending = false;
+ m_bInViewUpdate = false;
+ }
+
+ {
+ SolarMutexGuard aSolarGuard;
+ m_pDrawModelWrapper->unlockControllers();
+ }
+
+ impl_notifyModeChangeListener("valid");
+
+ //m_bRefreshAddIn = bOldRefreshAddIn;
+}
+
+// ____ XModifyListener ____
+void SAL_CALL ChartView::modified( const lang::EventObject& /* aEvent */ )
+{
+ m_bViewDirty = true;
+ if( m_bInViewUpdate )
+ m_bViewUpdatePending = true;
+
+ impl_notifyModeChangeListener("dirty");
+}
+
+//SfxListener
+void ChartView::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
+{
+ //#i77362 change notification for changes on additional shapes are missing
+ if( m_bInViewUpdate )
+ return;
+
+ // #i12587# support for shapes in chart
+ if ( m_bSdrViewIsInEditMode )
+ {
+ uno::Reference< view::XSelectionSupplier > xSelectionSupplier( mrChartModel.getCurrentController(), uno::UNO_QUERY );
+ if ( xSelectionSupplier.is() )
+ {
+ OUString aSelObjCID;
+ uno::Any aSelObj( xSelectionSupplier->getSelection() );
+ aSelObj >>= aSelObjCID;
+ if ( !aSelObjCID.isEmpty() )
+ {
+ return;
+ }
+ }
+ }
+
+ if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint)
+ return;
+ const SdrHint* pSdrHint = static_cast< const SdrHint* >(&rHint);
+
+ bool bShapeChanged = false;
+ switch( pSdrHint->GetKind() )
+ {
+ case SdrHintKind::ObjectChange:
+ bShapeChanged = true;
+ break;
+ case SdrHintKind::ObjectInserted:
+ bShapeChanged = true;
+ break;
+ case SdrHintKind::ObjectRemoved:
+ bShapeChanged = true;
+ break;
+ case SdrHintKind::ModelCleared:
+ bShapeChanged = true;
+ break;
+ case SdrHintKind::EndEdit:
+ bShapeChanged = true;
+ break;
+ default:
+ break;
+ }
+
+ if(bShapeChanged)
+ {
+ //#i76053# do not send view modified notifications for changes on the hidden page which contains e.g. the symbols for the dialogs
+ if( ChartView::getSdrPage() != pSdrHint->GetPage() )
+ bShapeChanged=false;
+ }
+
+ if(!bShapeChanged)
+ return;
+
+ mrChartModel.setModified(true);
+}
+
+void ChartView::impl_notifyModeChangeListener( const OUString& rNewMode )
+{
+ try
+ {
+ comphelper::OInterfaceContainerHelper2* pIC = m_aListenerContainer
+ .getContainer( cppu::UnoType<util::XModeChangeListener>::get());
+ if( pIC )
+ {
+ util::ModeChangeEvent aEvent( static_cast< uno::XWeak* >( this ), rNewMode );
+ comphelper::OInterfaceIteratorHelper2 aIt( *pIC );
+ while( aIt.hasMoreElements() )
+ {
+ static_cast< util::XModeChangeListener* >( aIt.next() )->modeChanged( aEvent );
+ }
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+}
+
+// ____ XModeChangeBroadcaster ____
+
+void SAL_CALL ChartView::addModeChangeListener( const uno::Reference< util::XModeChangeListener >& xListener )
+{
+ m_aListenerContainer.addInterface(
+ cppu::UnoType<util::XModeChangeListener>::get(), xListener );
+}
+void SAL_CALL ChartView::removeModeChangeListener( const uno::Reference< util::XModeChangeListener >& xListener )
+{
+ m_aListenerContainer.removeInterface(
+ cppu::UnoType<util::XModeChangeListener>::get(), xListener );
+}
+void SAL_CALL ChartView::addModeChangeApproveListener( const uno::Reference< util::XModeChangeApproveListener >& /* _rxListener */ )
+{
+
+}
+void SAL_CALL ChartView::removeModeChangeApproveListener( const uno::Reference< util::XModeChangeApproveListener >& /* _rxListener */ )
+{
+
+}
+
+// ____ XUpdatable ____
+void SAL_CALL ChartView::update()
+{
+ impl_updateView();
+
+ //#i100778# migrate all imported or old documents to a plot area sizing exclusive axes (in case the save settings allow for this):
+ //Although in general it is a bad idea to change the model from within the view this is exceptionally the best place to do this special conversion.
+ //When a view update is requested (what happens for creating the metafile or displaying
+ //the chart in edit mode or printing) it is most likely that all necessary information is available - like the underlying spreadsheet data for example.
+ //Those data are important for the correct axis label sizes which are needed during conversion.
+ if( DiagramHelper::switchDiagramPositioningToExcludingPositioning( mrChartModel, true, false ) )
+ impl_updateView();
+}
+
+void SAL_CALL ChartView::updateSoft()
+{
+ update();
+}
+
+void SAL_CALL ChartView::updateHard()
+{
+ impl_updateView(false);
+}
+
+// ____ XPropertySet ____
+Reference< beans::XPropertySetInfo > SAL_CALL ChartView::getPropertySetInfo()
+{
+ OSL_FAIL("not implemented");
+ return nullptr;
+}
+
+void SAL_CALL ChartView::setPropertyValue( const OUString& rPropertyName
+ , const Any& rValue )
+{
+ if( rPropertyName == "Resolution" )
+ {
+ awt::Size aNewResolution;
+ if( ! (rValue >>= aNewResolution) )
+ throw lang::IllegalArgumentException( "Property 'Resolution' requires value of type awt::Size", nullptr, 0 );
+
+ if( m_aPageResolution.Width!=aNewResolution.Width || m_aPageResolution.Height!=aNewResolution.Height )
+ {
+ //set modified only when the new resolution is higher and points were skipped before
+ bool bSetModified = m_bPointsWereSkipped && (m_aPageResolution.Width<aNewResolution.Width || m_aPageResolution.Height<aNewResolution.Height);
+
+ m_aPageResolution = aNewResolution;
+
+ if( bSetModified )
+ this->modified( lang::EventObject( static_cast< uno::XWeak* >( this ) ) );
+ }
+ }
+ else if( rPropertyName == "ZoomFactors" )
+ {
+ //#i75867# poor quality of ole's alternative view with 3D scenes and zoomfactors besides 100%
+ uno::Sequence< beans::PropertyValue > aZoomFactors;
+ if( ! (rValue >>= aZoomFactors) )
+ throw lang::IllegalArgumentException( "Property 'ZoomFactors' requires value of type Sequence< PropertyValue >", nullptr, 0 );
+
+ sal_Int32 nFilterArgs = aZoomFactors.getLength();
+ const beans::PropertyValue* pDataValues = aZoomFactors.getConstArray();
+ while( nFilterArgs-- )
+ {
+ if ( pDataValues->Name == "ScaleXNumerator" )
+ pDataValues->Value >>= m_nScaleXNumerator;
+ else if ( pDataValues->Name == "ScaleXDenominator" )
+ pDataValues->Value >>= m_nScaleXDenominator;
+ else if ( pDataValues->Name == "ScaleYNumerator" )
+ pDataValues->Value >>= m_nScaleYNumerator;
+ else if ( pDataValues->Name == "ScaleYDenominator" )
+ pDataValues->Value >>= m_nScaleYDenominator;
+
+ pDataValues++;
+ }
+ }
+ else if( rPropertyName == "SdrViewIsInEditMode" )
+ {
+ //#i77362 change notification for changes on additional shapes are missing
+ if( ! (rValue >>= m_bSdrViewIsInEditMode) )
+ throw lang::IllegalArgumentException( "Property 'SdrViewIsInEditMode' requires value of type sal_Bool", nullptr, 0 );
+ }
+ else
+ throw beans::UnknownPropertyException( "unknown property was tried to set to chart wizard " + rPropertyName, nullptr );
+}
+
+Any SAL_CALL ChartView::getPropertyValue( const OUString& rPropertyName )
+{
+ if( rPropertyName != "Resolution" )
+ throw beans::UnknownPropertyException( "unknown property was tried to get from chart wizard " + rPropertyName, nullptr );
+
+ return Any(m_aPageResolution);
+}
+
+void SAL_CALL ChartView::addPropertyChangeListener(
+ const OUString& /* aPropertyName */, const Reference< beans::XPropertyChangeListener >& /* xListener */ )
+{
+ OSL_FAIL("not implemented");
+}
+void SAL_CALL ChartView::removePropertyChangeListener(
+ const OUString& /* aPropertyName */, const Reference< beans::XPropertyChangeListener >& /* aListener */ )
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL ChartView::addVetoableChangeListener( const OUString& /* PropertyName */, const Reference< beans::XVetoableChangeListener >& /* aListener */ )
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL ChartView::removeVetoableChangeListener( const OUString& /* PropertyName */, const Reference< beans::XVetoableChangeListener >& /* aListener */ )
+{
+ OSL_FAIL("not implemented");
+}
+
+// ____ XMultiServiceFactory ____
+
+Reference< uno::XInterface > ChartView::createInstance( const OUString& aServiceSpecifier )
+{
+ SolarMutexGuard aSolarGuard;
+
+ SdrModel* pModel = ( m_pDrawModelWrapper ? &m_pDrawModelWrapper->getSdrModel() : nullptr );
+ if ( pModel )
+ {
+ if ( aServiceSpecifier == "com.sun.star.drawing.DashTable" )
+ {
+ if ( !m_xDashTable.is() )
+ {
+ m_xDashTable = SvxUnoDashTable_createInstance( pModel );
+ }
+ return m_xDashTable;
+ }
+ else if ( aServiceSpecifier == "com.sun.star.drawing.GradientTable" )
+ {
+ if ( !m_xGradientTable.is() )
+ {
+ m_xGradientTable = SvxUnoGradientTable_createInstance( pModel );
+ }
+ return m_xGradientTable;
+ }
+ else if ( aServiceSpecifier == "com.sun.star.drawing.HatchTable" )
+ {
+ if ( !m_xHatchTable.is() )
+ {
+ m_xHatchTable = SvxUnoHatchTable_createInstance( pModel );
+ }
+ return m_xHatchTable;
+ }
+ else if ( aServiceSpecifier == "com.sun.star.drawing.BitmapTable" )
+ {
+ if ( !m_xBitmapTable.is() )
+ {
+ m_xBitmapTable = SvxUnoBitmapTable_createInstance( pModel );
+ }
+ return m_xBitmapTable;
+ }
+ else if ( aServiceSpecifier == "com.sun.star.drawing.TransparencyGradientTable" )
+ {
+ if ( !m_xTransGradientTable.is() )
+ {
+ m_xTransGradientTable = SvxUnoTransGradientTable_createInstance( pModel );
+ }
+ return m_xTransGradientTable;
+ }
+ else if ( aServiceSpecifier == "com.sun.star.drawing.MarkerTable" )
+ {
+ if ( !m_xMarkerTable.is() )
+ {
+ m_xMarkerTable = SvxUnoMarkerTable_createInstance( pModel );
+ }
+ return m_xMarkerTable;
+ }
+ }
+
+ return nullptr;
+}
+
+Reference< uno::XInterface > ChartView::createInstanceWithArguments( const OUString& ServiceSpecifier, const uno::Sequence< uno::Any >& Arguments )
+{
+ OSL_ENSURE( Arguments.hasElements(), "ChartView::createInstanceWithArguments: arguments are ignored" );
+ return createInstance( ServiceSpecifier );
+}
+
+uno::Sequence< OUString > ChartView::getAvailableServiceNames()
+{
+ uno::Sequence< OUString > aServiceNames{ "com.sun.star.drawing.DashTable",
+ "com.sun.star.drawing.GradientTable",
+ "com.sun.star.drawing.HatchTable",
+ "com.sun.star.drawing.BitmapTable",
+ "com.sun.star.drawing.TransparencyGradientTable",
+ "com.sun.star.drawing.MarkerTable" };
+
+ return aServiceNames;
+}
+
+OUString ChartView::dump()
+{
+#if HAVE_FEATURE_DESKTOP
+ // Used for unit tests and in chartcontroller only, no need to drag in this when cross-compiling
+ // for non-desktop
+ impl_updateView();
+ sal_Int32 n = m_xDrawPage->getCount();
+ OUStringBuffer aBuffer;
+ for(sal_Int32 i = 0; i < n; ++i)
+ {
+ uno::Reference< drawing::XShapes > xShape(m_xDrawPage->getByIndex(i), uno::UNO_QUERY);
+ if(xShape.is())
+ {
+ OUString aString = XShapeDumper::dump(uno::Reference<drawing::XShapes>(mxRootShape));
+ aBuffer.append(aString);
+ }
+ else
+ {
+ uno::Reference< drawing::XShape > xSingleShape(m_xDrawPage->getByIndex(i), uno::UNO_QUERY);
+ if(!xSingleShape.is())
+ continue;
+ OUString aString = XShapeDumper::dump(xSingleShape);
+ aBuffer.append(aString);
+ }
+ aBuffer.append("\n\n");
+ }
+
+ return aBuffer.makeStringAndClear();
+#else
+ return OUString();
+#endif
+}
+
+void ChartView::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ChartView"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+
+ if (m_pDrawModelWrapper)
+ {
+ m_pDrawModelWrapper->dumpAsXml(pWriter);
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void ChartView::setViewDirty()
+{
+ osl::MutexGuard aGuard(maTimeMutex);
+ m_bViewDirty = true;
+}
+
+IMPL_LINK_NOARG(ChartView, UpdateTimeBased, Timer *, void)
+{
+ setViewDirty();
+ update();
+}
+
+void ChartView::createShapes2D( const awt::Size& rPageSize )
+{
+ // todo: it would be nicer to just pass the page m_xDrawPage and format it,
+ // but the draw page does not support XPropertySet
+ formatPage( mrChartModel, rPageSize, mxRootShape );
+
+ CreateShapeParam2D aParam;
+ aParam.maRemainingSpace.X = 0;
+ aParam.maRemainingSpace.Y = 0;
+ aParam.maRemainingSpace.Width = rPageSize.Width;
+ aParam.maRemainingSpace.Height = rPageSize.Height;
+
+ //create the group shape for diagram and axes first to have title and legends on top of it
+ rtl::Reference< Diagram > xDiagram( mrChartModel.getFirstChartDiagram() );
+ bool bHasRelativeSize = false;
+ if( xDiagram.is() && xDiagram->getPropertyValue("RelativeSize").hasValue() )
+ bHasRelativeSize = true;
+
+ OUString aDiagramCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM, OUString::number( 0 ) ) );//todo: other index if more than one diagram is possible
+ rtl::Reference<SvxShapeGroup> xDiagramPlusAxesPlusMarkHandlesGroup_Shapes =
+ ShapeFactory::createGroup2D(mxRootShape,aDiagramCID);
+
+ aParam.mxMarkHandles = ShapeFactory::createInvisibleRectangle(
+ xDiagramPlusAxesPlusMarkHandlesGroup_Shapes, awt::Size(0,0));
+ ShapeFactory::setShapeName(aParam.mxMarkHandles, "MarkHandles");
+
+ aParam.mxPlotAreaWithAxes = ShapeFactory::createInvisibleRectangle(
+ xDiagramPlusAxesPlusMarkHandlesGroup_Shapes, awt::Size(0, 0));
+ ShapeFactory::setShapeName(aParam.mxPlotAreaWithAxes, "PlotAreaIncludingAxes");
+
+ aParam.mxDiagramWithAxesShapes = ShapeFactory::createGroup2D(xDiagramPlusAxesPlusMarkHandlesGroup_Shapes);
+
+ bool bAutoPositionDummy = true;
+
+ // create buttons
+ lcl_createButtons(mxRootShape, mrChartModel, aParam.maRemainingSpace);
+
+ lcl_createTitle(
+ TitleHelper::MAIN_TITLE, mxRootShape, mrChartModel,
+ aParam.maRemainingSpace, rPageSize, TitleAlignment::ALIGN_TOP, bAutoPositionDummy);
+ if (!bHasRelativeSize && (aParam.maRemainingSpace.Width <= 0 || aParam.maRemainingSpace.Height <= 0))
+ return;
+
+ lcl_createTitle(
+ TitleHelper::SUB_TITLE, mxRootShape, mrChartModel,
+ aParam.maRemainingSpace, rPageSize, TitleAlignment::ALIGN_TOP, bAutoPositionDummy );
+ if (!bHasRelativeSize && (aParam.maRemainingSpace.Width <= 0 || aParam.maRemainingSpace.Height <= 0))
+ return;
+
+ aParam.mpSeriesPlotterContainer = std::make_shared<SeriesPlotterContainer>(m_aVCooSysList);
+ aParam.mpSeriesPlotterContainer->initializeCooSysAndSeriesPlotter( mrChartModel );
+ if(maTimeBased.bTimeBased && maTimeBased.nFrame != 0)
+ {
+ auto& rSeriesPlotter = aParam.mpSeriesPlotterContainer->getSeriesPlotterList();
+ size_t n = rSeriesPlotter.size();
+ for(size_t i = 0; i < n; ++i)
+ {
+ std::vector<VDataSeries*> aAllNewDataSeries = rSeriesPlotter[i]->getAllSeries();
+ std::vector< VDataSeries* >& rAllOldDataSeries =
+ maTimeBased.m_aDataSeriesList[i];
+ size_t m = std::min(aAllNewDataSeries.size(), rAllOldDataSeries.size());
+ for(size_t j = 0; j < m; ++j)
+ {
+ aAllNewDataSeries[j]->setOldTimeBased(
+ rAllOldDataSeries[j], (maTimeBased.nFrame % 60)/60.0);
+ }
+ }
+ }
+
+ lcl_createLegend(
+ LegendHelper::getLegend( mrChartModel ), mxRootShape, m_xCC,
+ aParam.maRemainingSpace, rPageSize, mrChartModel, aParam.mpSeriesPlotterContainer->getLegendEntryProviderList(),
+ lcl_getDefaultWritingModeFromPool( m_pDrawModelWrapper ) );
+
+ if (!bHasRelativeSize && (aParam.maRemainingSpace.Width <= 0 || aParam.maRemainingSpace.Height <= 0))
+ return;
+
+ if (!createAxisTitleShapes2D(aParam, rPageSize, bHasRelativeSize))
+ return;
+
+ bool bDummy = false;
+ bool bIsVertical = DiagramHelper::getVertical(xDiagram, bDummy, bDummy);
+
+ if (getAvailablePosAndSizeForDiagram(aParam, rPageSize, xDiagram))
+ {
+ awt::Rectangle aUsedOuterRect = impl_createDiagramAndContent(aParam, rPageSize);
+
+ if (aParam.mxPlotAreaWithAxes.is())
+ {
+ aParam.mxPlotAreaWithAxes->setPosition(awt::Point(aUsedOuterRect.X, aUsedOuterRect.Y));
+ aParam.mxPlotAreaWithAxes->setSize(awt::Size(aUsedOuterRect.Width, aUsedOuterRect.Height));
+ }
+
+ //correct axis title position
+ awt::Rectangle aDiagramPlusAxesRect( aUsedOuterRect );
+ if (aParam.mbAutoPosTitleX)
+ changePositionOfAxisTitle(aParam.mpVTitleX.get(), TitleAlignment::ALIGN_BOTTOM, aDiagramPlusAxesRect, rPageSize);
+ if (aParam.mbAutoPosTitleY)
+ changePositionOfAxisTitle(aParam.mpVTitleY.get(), TitleAlignment::ALIGN_LEFT, aDiagramPlusAxesRect, rPageSize);
+ if (aParam.mbAutoPosTitleZ)
+ changePositionOfAxisTitle(aParam.mpVTitleZ.get(), TitleAlignment::ALIGN_Z, aDiagramPlusAxesRect, rPageSize);
+ if (aParam.mbAutoPosSecondTitleX)
+ changePositionOfAxisTitle(aParam.mpVTitleSecondX.get(), bIsVertical? TitleAlignment::ALIGN_RIGHT : TitleAlignment::ALIGN_TOP, aDiagramPlusAxesRect, rPageSize);
+ if (aParam.mbAutoPosSecondTitleY)
+ changePositionOfAxisTitle(aParam.mpVTitleSecondY.get(), bIsVertical? TitleAlignment::ALIGN_TOP : TitleAlignment::ALIGN_RIGHT, aDiagramPlusAxesRect, rPageSize);
+ }
+
+ //cleanup: remove all empty group shapes to avoid grey border lines:
+ lcl_removeEmptyGroupShapes( *mxRootShape->GetSdrObject() );
+
+ if(maTimeBased.bTimeBased && maTimeBased.nFrame % 60 == 0)
+ {
+ // create copy of the data for next frame
+ auto& rSeriesPlotter = aParam.mpSeriesPlotterContainer->getSeriesPlotterList();
+ size_t n = rSeriesPlotter.size();
+ maTimeBased.m_aDataSeriesList.clear();
+ maTimeBased.m_aDataSeriesList.resize(n);
+ for(size_t i = 0; i < n; ++i)
+ {
+ std::vector<VDataSeries*> aAllNewDataSeries = rSeriesPlotter[i]->getAllSeries();
+ std::vector<VDataSeries*>& rAllOldDataSeries = maTimeBased.m_aDataSeriesList[i];
+ size_t m = aAllNewDataSeries.size();
+ for(size_t j = 0; j < m; ++j)
+ {
+ rAllOldDataSeries.push_back( aAllNewDataSeries[j]->
+ createCopyForTimeBased() );
+ }
+ }
+
+ maTimeBased.maTimer.Stop();
+ }
+
+ if(maTimeBased.bTimeBased && !maTimeBased.maTimer.IsActive())
+ {
+ maTimeBased.maTimer.SetTimeout(15);
+ maTimeBased.maTimer.SetInvokeHandler(LINK(this, ChartView, UpdateTimeBased));
+ maTimeBased.maTimer.Start();
+ }
+}
+
+bool ChartView::createAxisTitleShapes2D( CreateShapeParam2D& rParam, const css::awt::Size& rPageSize, bool bHasRelativeSize )
+{
+ rtl::Reference<Diagram> xDiagram = mrChartModel.getFirstChartDiagram();
+
+ rtl::Reference< ChartType > xChartType( DiagramHelper::getChartTypeByIndex( xDiagram, 0 ) );
+ sal_Int32 nDimension = DiagramHelper::getDimension( xDiagram );
+
+ if( ChartTypeHelper::isSupportingMainAxis( xChartType, nDimension, 0 ) )
+ rParam.mpVTitleX = lcl_createTitle( TitleHelper::TITLE_AT_STANDARD_X_AXIS_POSITION, mxRootShape, mrChartModel
+ , rParam.maRemainingSpace, rPageSize, TitleAlignment::ALIGN_BOTTOM, rParam.mbAutoPosTitleX );
+ if (!bHasRelativeSize && (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0))
+ return false;
+
+ if( ChartTypeHelper::isSupportingMainAxis( xChartType, nDimension, 1 ) )
+ rParam.mpVTitleY = lcl_createTitle( TitleHelper::TITLE_AT_STANDARD_Y_AXIS_POSITION, mxRootShape, mrChartModel
+ , rParam.maRemainingSpace, rPageSize, TitleAlignment::ALIGN_LEFT, rParam.mbAutoPosTitleY );
+ if (!bHasRelativeSize && (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0))
+ return false;
+
+ if( ChartTypeHelper::isSupportingMainAxis( xChartType, nDimension, 2 ) )
+ rParam.mpVTitleZ = lcl_createTitle( TitleHelper::Z_AXIS_TITLE, mxRootShape, mrChartModel
+ , rParam.maRemainingSpace, rPageSize, TitleAlignment::ALIGN_RIGHT, rParam.mbAutoPosTitleZ );
+ if (!bHasRelativeSize && (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0))
+ return false;
+
+ bool bDummy = false;
+ bool bIsVertical = DiagramHelper::getVertical( xDiagram, bDummy, bDummy );
+
+ if( ChartTypeHelper::isSupportingSecondaryAxis( xChartType, nDimension ) )
+ rParam.mpVTitleSecondX = lcl_createTitle( TitleHelper::SECONDARY_X_AXIS_TITLE, mxRootShape, mrChartModel
+ , rParam.maRemainingSpace, rPageSize, bIsVertical? TitleAlignment::ALIGN_RIGHT : TitleAlignment::ALIGN_TOP, rParam.mbAutoPosSecondTitleX );
+ if (!bHasRelativeSize && (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0))
+ return false;
+
+ if( ChartTypeHelper::isSupportingSecondaryAxis( xChartType, nDimension ) )
+ rParam.mpVTitleSecondY = lcl_createTitle( TitleHelper::SECONDARY_Y_AXIS_TITLE, mxRootShape, mrChartModel
+ , rParam.maRemainingSpace, rPageSize, bIsVertical? TitleAlignment::ALIGN_TOP : TitleAlignment::ALIGN_RIGHT, rParam.mbAutoPosSecondTitleY );
+ if (!bHasRelativeSize && (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0))
+ return false;
+
+ return true;
+}
+
+} //namespace chart
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_chart2_ChartView_get_implementation(css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ rtl::Reference<::chart::ChartModel> pChartModel = new ::chart::ChartModel(context);
+ return cppu::acquire(new ::chart::ChartView(context, *pChartModel));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/Clipping.cxx b/chart2/source/view/main/Clipping.cxx
new file mode 100644
index 000000000..713e88c26
--- /dev/null
+++ b/chart2/source/view/main/Clipping.cxx
@@ -0,0 +1,425 @@
+/* -*- 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 <Clipping.hxx>
+#include <CommonConverters.hxx>
+#include <BaseGFXHelper.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <com/sun/star/drawing/Position3D.hpp>
+#include <com/sun/star/drawing/DoubleSequence.hpp>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using ::basegfx::B2DRectangle;
+using ::basegfx::B2DTuple;
+
+namespace{
+/** @descr This is a supporting function for lcl_clip2d. It computes a new parametric
+ value for an entering (dTE) or leaving (dTL) intersection point with one
+ of the edges bounding the clipping area.
+ For explanation of the parameters please refer to :
+
+ Liang-Biarsky parametric line-clipping algorithm as described in:
+ Computer Graphics: principles and practice, 2nd ed.,
+ James D. Foley et al.,
+ Section 3.12.4 on page 117.
+*/
+bool lcl_CLIPt(double fDenom,double fNum, double & fTE, double & fTL)
+{
+ double fT;
+
+ if (fDenom > 0) // Intersection enters: PE
+ {
+ fT = fNum / fDenom; // Parametric value at the intersection.
+ if (fT > fTL) // fTE and fTL crossover
+ return false; // therefore reject the line.
+ else if (fT > fTE) // A new fTE has been found.
+ fTE = fT;
+ }
+ else if (fDenom < 0) // Intersection leaves: PL
+ {
+ fT = fNum / fDenom; // Parametric Value at the intersection.
+ if (fT < fTE) // fTE and fTL crossover
+ return false; // therefore reject the line.
+ else if (fT < fTL) // A new fTL has been found.
+ fTL = fT;
+ }
+ else if (fNum > 0)
+ return false; // Line lies on the outside of the edge.
+
+ return true;
+}
+
+/** @descr The line given by its two endpoints rP0 and rP1 is clipped at the rectangle
+ rRectangle. If there is at least a part of it visible then sal_True is returned and
+ the endpoints of that part are stored in rP0 and rP1. The points rP0 and rP1
+ may have the same coordinates.
+ @param rP0 Start point of the line to clip. Modified to contain a start point inside
+ the clipping area if possible.
+ @param rP1 End point of the line to clip. Modified to contain an end point inside
+ the clipping area if possible.
+ @param rRectangle Clipping area.
+ @return If the line lies completely or partly inside the clipping area then TRUE
+ is returned. If the line lies completely outside then sal_False is returned and rP0 and
+ rP1 are left unmodified.
+*/
+bool lcl_clip2d(B2DTuple& rPoint0, B2DTuple& rPoint1, const B2DRectangle& rRectangle)
+{
+ //Direction vector of the line.
+ B2DTuple aDirection = rPoint1 - rPoint0;
+
+ if( aDirection.getX()==0 && aDirection.getY()==0 && rRectangle.isInside(rPoint0) )
+ {
+ // Degenerate case of a zero length line.
+ return true;
+ }
+ else
+ {
+ // Values of the line parameter where the line enters resp. leaves the rectangle.
+ double fTE = 0,
+ fTL = 1;
+
+ // Test whether at least a part lies in the four half-planes with respect to
+ // the rectangles four edges.
+ if( lcl_CLIPt(aDirection.getX(), rRectangle.getMinX() - rPoint0.getX(), fTE, fTL) )
+ if( lcl_CLIPt(-aDirection.getX(), rPoint0.getX() - rRectangle.getMaxX(), fTE, fTL) )
+ if( lcl_CLIPt(aDirection.getY(), rRectangle.getMinY() - rPoint0.getY(), fTE, fTL) )
+ if( lcl_CLIPt(-aDirection.getY(), rPoint0.getY() - rRectangle.getMaxY(), fTE, fTL) )
+ {
+ // At least a part is visible.
+ if (fTL < 1)
+ {
+ // Compute the new end point.
+ rPoint1.setX( rPoint0.getX() + fTL * aDirection.getX() );
+ rPoint1.setY( rPoint0.getY() + fTL * aDirection.getY() );
+ }
+ if (fTE > 0)
+ {
+ // Compute the new starting point.
+ rPoint0.setX( rPoint0.getX() + fTE * aDirection.getX() );
+ rPoint0.setY( rPoint0.getY() + fTE * aDirection.getY() );
+ }
+ return true;
+ }
+
+ // Line is not visible.
+ return false;
+ }
+}
+
+bool lcl_clip2d_(drawing::Position3D& rPoint0, drawing::Position3D& rPoint1, const B2DRectangle& rRectangle)
+{
+ B2DTuple aP0(rPoint0.PositionX,rPoint0.PositionY);
+ B2DTuple aP1(rPoint1.PositionX,rPoint1.PositionY);
+ bool bRet = lcl_clip2d( aP0, aP1, rRectangle );
+
+ rPoint0.PositionX = aP0.getX();
+ rPoint0.PositionY = aP0.getY();
+ rPoint1.PositionX = aP1.getX();
+ rPoint1.PositionY = aP1.getY();
+
+ return bRet;
+}
+
+unsigned int round_up_nearest_pow2(unsigned int v)
+{
+ // compute the next highest power of 2 of 32-bit v
+ --v;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+ ++v;
+ return v;
+}
+
+void lcl_addPointToPoly( drawing::PolyPolygonShape3D& rPoly
+ , const drawing::Position3D& rPos
+ , sal_Int32 nPolygonIndex
+ , std::vector< sal_Int32 >& rResultPointCount
+ , sal_Int32 nReservePointCount )
+{
+ if(nPolygonIndex<0)
+ {
+ OSL_FAIL( "The polygon index needs to be > 0");
+ nPolygonIndex=0;
+ }
+
+ //make sure that we have enough polygons
+ if(nPolygonIndex >= rPoly.SequenceX.getLength() )
+ {
+ rPoly.SequenceX.realloc(nPolygonIndex+1);
+ rPoly.SequenceY.realloc(nPolygonIndex+1);
+ rPoly.SequenceZ.realloc(nPolygonIndex+1);
+ rResultPointCount.resize(nPolygonIndex+1,0);
+ }
+
+ drawing::DoubleSequence* pOuterSequenceX = &rPoly.SequenceX.getArray()[nPolygonIndex];
+ drawing::DoubleSequence* pOuterSequenceY = &rPoly.SequenceY.getArray()[nPolygonIndex];
+ drawing::DoubleSequence* pOuterSequenceZ = &rPoly.SequenceZ.getArray()[nPolygonIndex];
+
+ sal_Int32 nNewResultPointCount = rResultPointCount[nPolygonIndex]+1;
+ sal_Int32 nSeqLength = pOuterSequenceX->getLength();
+
+ if( nSeqLength <= nNewResultPointCount )
+ {
+ sal_Int32 nReallocLength = nReservePointCount > SAL_MAX_INT16 ? round_up_nearest_pow2(nNewResultPointCount) * 2 : nReservePointCount;
+ if( nNewResultPointCount > nReallocLength )
+ {
+ nReallocLength = nNewResultPointCount;
+ OSL_FAIL("this should not be the case to avoid performance problems");
+ }
+ pOuterSequenceX->realloc(nReallocLength);
+ pOuterSequenceY->realloc(nReallocLength);
+ pOuterSequenceZ->realloc(nReallocLength);
+ }
+
+ double* pInnerSequenceX = pOuterSequenceX->getArray();
+ double* pInnerSequenceY = pOuterSequenceY->getArray();
+ double* pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ pInnerSequenceX[nNewResultPointCount-1] = rPos.PositionX;
+ pInnerSequenceY[nNewResultPointCount-1] = rPos.PositionY;
+ pInnerSequenceZ[nNewResultPointCount-1] = rPos.PositionZ;
+ rResultPointCount[nPolygonIndex]=nNewResultPointCount;
+}
+
+void lcl_addPointToPoly( std::vector<std::vector<css::drawing::Position3D>>& rPoly
+ , const drawing::Position3D& rPos
+ , sal_Int32 nPolygonIndex
+ , std::vector< sal_Int32 >& rResultPointCount
+ , sal_Int32 nReservePointCount )
+{
+ if(nPolygonIndex<0)
+ {
+ OSL_FAIL( "The polygon index needs to be > 0");
+ nPolygonIndex=0;
+ }
+
+ //make sure that we have enough polygons
+ if(o3tl::make_unsigned(nPolygonIndex) >= rPoly.size() )
+ {
+ rPoly.resize(nPolygonIndex+1);
+ rResultPointCount.resize(nPolygonIndex+1,0);
+ }
+
+ std::vector<css::drawing::Position3D>* pOuterSequence = &rPoly[nPolygonIndex];
+
+ sal_Int32 nNewResultPointCount = rResultPointCount[nPolygonIndex]+1;
+ sal_Int32 nSeqLength = pOuterSequence->size();
+
+ if( nSeqLength <= nNewResultPointCount )
+ {
+ sal_Int32 nReallocLength = nReservePointCount > SAL_MAX_INT16 ? round_up_nearest_pow2(nNewResultPointCount) * 2 : nReservePointCount;
+ if( nNewResultPointCount > nReallocLength )
+ {
+ nReallocLength = nNewResultPointCount;
+ OSL_FAIL("this should not be the case to avoid performance problems");
+ }
+ pOuterSequence->resize(nReallocLength);
+ }
+
+ css::drawing::Position3D* pInnerSequence = pOuterSequence->data();
+
+ pInnerSequence[nNewResultPointCount-1] = rPos;
+ rResultPointCount[nPolygonIndex]=nNewResultPointCount;
+}
+
+}//end anonymous namespace
+
+void Clipping::clipPolygonAtRectangle( const drawing::PolyPolygonShape3D& rPolygon
+ , const B2DRectangle& rRectangle
+ , drawing::PolyPolygonShape3D& aResult
+ , bool bSplitPiecesToDifferentPolygons )
+{
+ aResult.SequenceX.realloc(0);
+ aResult.SequenceY.realloc(0);
+ aResult.SequenceZ.realloc(0);
+
+ if(!rPolygon.SequenceX.hasElements())
+ return;
+
+ //need clipping?:
+ {
+ ::basegfx::B3DRange a3DRange( BaseGFXHelper::getBoundVolume( rPolygon ) );
+ ::basegfx::B2DRange a2DRange( a3DRange.getMinX(), a3DRange.getMinY(), a3DRange.getMaxX(), a3DRange.getMaxY() );
+ if( rRectangle.isInside( a2DRange ) )
+ {
+ aResult = rPolygon;
+ return;
+ }
+ else
+ {
+ a2DRange.intersect( rRectangle );
+ if( a2DRange.isEmpty() )
+ return;
+ }
+ }
+
+ std::vector< sal_Int32 > aResultPointCount;//per polygon index
+
+ //apply clipping:
+ drawing::Position3D aFrom;
+ drawing::Position3D aTo;
+
+ sal_Int32 nNewPolyIndex = 0;
+ sal_Int32 nOldPolyCount = rPolygon.SequenceX.getLength();
+ for(sal_Int32 nOldPolyIndex=0; nOldPolyIndex<nOldPolyCount; nOldPolyIndex++, nNewPolyIndex++ )
+ {
+ sal_Int32 nOldPointCount = rPolygon.SequenceX[nOldPolyIndex].getLength();
+
+ // set last point to a position outside the rectangle, such that the first
+ // time lcl_clip2d returns true, the comparison to last will always yield false
+ drawing::Position3D aLast(rRectangle.getMinX()-1.0,rRectangle.getMinY()-1.0, 0.0 );
+
+ for(sal_Int32 nOldPoint=1; nOldPoint<nOldPointCount; nOldPoint++)
+ {
+ aFrom = getPointFromPoly(rPolygon,nOldPoint-1,nOldPolyIndex);
+ aTo = getPointFromPoly(rPolygon,nOldPoint,nOldPolyIndex);
+ if( lcl_clip2d_(aFrom, aTo, rRectangle) )
+ {
+ // compose a Polygon of as many consecutive points as possible
+ if(aFrom == aLast)
+ {
+ if( aTo != aFrom )
+ {
+ lcl_addPointToPoly( aResult, aTo, nNewPolyIndex, aResultPointCount, nOldPointCount );
+ }
+ }
+ else
+ {
+ if( bSplitPiecesToDifferentPolygons && nOldPoint!=1 )
+ {
+ if( nNewPolyIndex < aResult.SequenceX.getLength()
+ && aResultPointCount[nNewPolyIndex]>0 )
+ nNewPolyIndex++;
+ }
+ lcl_addPointToPoly( aResult, aFrom, nNewPolyIndex, aResultPointCount, nOldPointCount );
+ if( aTo != aFrom )
+ lcl_addPointToPoly( aResult, aTo, nNewPolyIndex, aResultPointCount, nOldPointCount );
+ }
+ aLast = aTo;
+ }
+ }
+ }
+ //free unused space
+ for( sal_Int32 nPolygonIndex = aResultPointCount.size(); nPolygonIndex--; )
+ {
+ drawing::DoubleSequence* pOuterSequenceX = &aResult.SequenceX.getArray()[nPolygonIndex];
+ drawing::DoubleSequence* pOuterSequenceY = &aResult.SequenceY.getArray()[nPolygonIndex];
+ drawing::DoubleSequence* pOuterSequenceZ = &aResult.SequenceZ.getArray()[nPolygonIndex];
+
+ sal_Int32 nUsedPointCount = aResultPointCount[nPolygonIndex];
+ pOuterSequenceX->realloc(nUsedPointCount);
+ pOuterSequenceY->realloc(nUsedPointCount);
+ pOuterSequenceZ->realloc(nUsedPointCount);
+ }
+}
+
+void Clipping::clipPolygonAtRectangle( const std::vector<std::vector<css::drawing::Position3D>>& rPolygon
+ , const B2DRectangle& rRectangle
+ , std::vector<std::vector<css::drawing::Position3D>>& aResult
+ , bool bSplitPiecesToDifferentPolygons )
+{
+ aResult.clear();
+
+ if(rPolygon.empty())
+ return;
+
+ //need clipping?:
+ {
+ ::basegfx::B3DRange a3DRange( BaseGFXHelper::getBoundVolume( rPolygon ) );
+ ::basegfx::B2DRange a2DRange( a3DRange.getMinX(), a3DRange.getMinY(), a3DRange.getMaxX(), a3DRange.getMaxY() );
+ if( rRectangle.isInside( a2DRange ) )
+ {
+ aResult = rPolygon;
+ return;
+ }
+ else
+ {
+ a2DRange.intersect( rRectangle );
+ if( a2DRange.isEmpty() )
+ return;
+ }
+ }
+
+ std::vector< sal_Int32 > aResultPointCount;//per polygon index
+
+ //apply clipping:
+ drawing::Position3D aFrom;
+ drawing::Position3D aTo;
+
+ sal_Int32 nNewPolyIndex = 0;
+ sal_Int32 nOldPolyCount = rPolygon.size();
+ for(sal_Int32 nOldPolyIndex=0; nOldPolyIndex<nOldPolyCount; nOldPolyIndex++, nNewPolyIndex++ )
+ {
+ sal_Int32 nOldPointCount = rPolygon[nOldPolyIndex].size();
+
+ // set last point to a position outside the rectangle, such that the first
+ // time lcl_clip2d returns true, the comparison to last will always yield false
+ drawing::Position3D aLast(rRectangle.getMinX()-1.0,rRectangle.getMinY()-1.0, 0.0 );
+
+ for(sal_Int32 nOldPoint=1; nOldPoint<nOldPointCount; nOldPoint++)
+ {
+ aFrom = getPointFromPoly(rPolygon,nOldPoint-1,nOldPolyIndex);
+ aTo = getPointFromPoly(rPolygon,nOldPoint,nOldPolyIndex);
+ if( lcl_clip2d_(aFrom, aTo, rRectangle) )
+ {
+ // compose a Polygon of as many consecutive points as possible
+ if(aFrom == aLast)
+ {
+ if( aTo != aFrom )
+ {
+ lcl_addPointToPoly( aResult, aTo, nNewPolyIndex, aResultPointCount, nOldPointCount );
+ }
+ }
+ else
+ {
+ if( bSplitPiecesToDifferentPolygons && nOldPoint!=1 )
+ {
+ if( nNewPolyIndex < static_cast<sal_Int32>(aResult.size())
+ && aResultPointCount[nNewPolyIndex]>0 )
+ nNewPolyIndex++;
+ }
+ lcl_addPointToPoly( aResult, aFrom, nNewPolyIndex, aResultPointCount, nOldPointCount );
+ if( aTo != aFrom )
+ lcl_addPointToPoly( aResult, aTo, nNewPolyIndex, aResultPointCount, nOldPointCount );
+ }
+ aLast = aTo;
+ }
+ }
+ }
+ //free unused space
+ for( sal_Int32 nPolygonIndex = aResultPointCount.size(); nPolygonIndex--; )
+ {
+ std::vector<css::drawing::Position3D>* pOuterSequence = &aResult[nPolygonIndex];
+
+ sal_Int32 nUsedPointCount = aResultPointCount[nPolygonIndex];
+ pOuterSequence->resize(nUsedPointCount);
+ }
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/DataPointSymbolSupplier.cxx b/chart2/source/view/main/DataPointSymbolSupplier.cxx
new file mode 100644
index 000000000..ff7f8370a
--- /dev/null
+++ b/chart2/source/view/main/DataPointSymbolSupplier.cxx
@@ -0,0 +1,44 @@
+/* -*- 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 <chartview/DataPointSymbolSupplier.hxx>
+#include <ShapeFactory.hxx>
+#include <com/sun/star/drawing/Position3D.hpp>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+
+rtl::Reference< SvxShapeGroup > DataPointSymbolSupplier::create2DSymbolList(
+ const rtl::Reference<SvxDrawPage>& xTarget
+ , const drawing::Direction3D& rSize )
+{
+ rtl::Reference< SvxShapeGroup > xGroupShapes = ShapeFactory::createGroup2D( xTarget );
+
+ drawing::Position3D aPos(0,0,0);
+ for(sal_Int32 nS=0;nS<ShapeFactory::getSymbolCount();nS++)
+ {
+ ShapeFactory::createSymbol2D( xGroupShapes, aPos, rSize, nS, 0, 0 );
+ }
+ return xGroupShapes;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/DrawModelWrapper.cxx b/chart2/source/view/main/DrawModelWrapper.cxx
new file mode 100644
index 000000000..6a6488435
--- /dev/null
+++ b/chart2/source/view/main/DrawModelWrapper.cxx
@@ -0,0 +1,335 @@
+/* -*- 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 <chartview/DrawModelWrapper.hxx>
+#include <ShapeFactory.hxx>
+#include "ChartItemPool.hxx"
+#include <ObjectIdentifier.hxx>
+#include <svx/unomodel.hxx>
+#include <svl/itempool.hxx>
+#include <svx/objfac3d.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svx3ditems.hxx>
+#include <svx/xtable.hxx>
+#include <svx/svdoutl.hxx>
+#include <editeng/unolingu.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <libxml/xmlwriter.h>
+#include <osl/diagnose.h>
+
+namespace com::sun::star::linguistic2 { class XHyphenator; }
+namespace com::sun::star::linguistic2 { class XSpellChecker1; }
+
+using namespace ::com::sun::star;
+
+
+namespace chart
+{
+
+DrawModelWrapper::DrawModelWrapper()
+: SdrModel()
+{
+ m_xChartItemPool = ChartItemPool::CreateChartItemPool();
+
+ SetScaleUnit(MapUnit::Map100thMM);
+ SetScaleFraction(Fraction(1, 1));
+ SetDefaultFontHeight(423); // 12pt
+
+ SfxItemPool* pMasterPool = &GetItemPool();
+ pMasterPool->SetDefaultMetric(MapUnit::Map100thMM);
+ pMasterPool->SetPoolDefaultItem(SfxBoolItem(EE_PARA_HYPHENATE, true) );
+ pMasterPool->SetPoolDefaultItem(makeSvx3DPercentDiagonalItem (5));
+
+ // append chart pool to end of pool chain
+ pMasterPool->GetLastPoolInChain()->SetSecondaryPool(m_xChartItemPool.get());
+ pMasterPool->FreezeIdRanges();
+ SetTextDefaults();
+
+ //this factory needs to be created before first use of 3D scenes once upon an office runtime
+ //@todo in future this should be done by drawing engine itself on demand
+ static bool b3dFactoryInitialized = false;
+ if(!b3dFactoryInitialized)
+ {
+ E3dObjFactory aObjFactory;
+ b3dFactoryInitialized = true;
+ }
+
+ //Hyphenation and spellchecking
+ SdrOutliner& rOutliner = GetDrawOutliner();
+ try
+ {
+ uno::Reference< linguistic2::XHyphenator > xHyphenator( LinguMgr::GetHyphenator() );
+ if( xHyphenator.is() )
+ rOutliner.SetHyphenator( xHyphenator );
+
+ uno::Reference< linguistic2::XSpellChecker1 > xSpellChecker( LinguMgr::GetSpellChecker() );
+ if ( xSpellChecker.is() )
+ rOutliner.SetSpeller( xSpellChecker );
+ }
+ catch(...)
+ {
+ OSL_FAIL("Can't get Hyphenator or SpellChecker for chart");
+ }
+
+ //ref device for font rendering
+ OutputDevice* pDefaultDevice = rOutliner.GetRefDevice();
+ if( !pDefaultDevice )
+ pDefaultDevice = Application::GetDefaultDevice();
+ m_pRefDevice.disposeAndClear();
+ m_pRefDevice = VclPtr<VirtualDevice>::Create(*pDefaultDevice);
+ MapMode aMapMode = m_pRefDevice->GetMapMode();
+ aMapMode.SetMapUnit(MapUnit::Map100thMM);
+ m_pRefDevice->SetMapMode(aMapMode);
+ SetRefDevice(m_pRefDevice.get());
+ rOutliner.SetRefDevice(m_pRefDevice.get());
+}
+
+DrawModelWrapper::~DrawModelWrapper()
+{
+ //remove m_pChartItemPool from pool chain
+ if(m_xChartItemPool)
+ {
+ SfxItemPool* pPool = &GetItemPool();
+ for (;;)
+ {
+ SfxItemPool* pSecondary = pPool->GetSecondaryPool();
+ if(pSecondary == m_xChartItemPool.get())
+ {
+ pPool->SetSecondaryPool (nullptr);
+ break;
+ }
+ pPool = pSecondary;
+ }
+ m_xChartItemPool.clear();
+ }
+ m_pRefDevice.disposeAndClear();
+}
+
+uno::Reference< uno::XInterface > DrawModelWrapper::createUnoModel()
+{
+ uno::Reference< lang::XComponent > xComponent = new SvxUnoDrawingModel( this ); //tell Andreas Schluens if SvxUnoDrawingModel is not needed anymore -> remove export from svx to avoid link problems in writer
+ return uno::Reference< uno::XInterface >::query( xComponent );
+}
+
+uno::Reference< frame::XModel > DrawModelWrapper::getUnoModel()
+{
+ uno::Reference< uno::XInterface > xI = SdrModel::getUnoModel();
+ return uno::Reference<frame::XModel>::query( xI );
+}
+
+SdrModel& DrawModelWrapper::getSdrModel()
+{
+ return *this;
+}
+
+uno::Reference< lang::XMultiServiceFactory > DrawModelWrapper::getShapeFactory()
+{
+ uno::Reference< lang::XMultiServiceFactory > xShapeFactory( getUnoModel(), uno::UNO_QUERY );
+ return xShapeFactory;
+}
+
+const rtl::Reference<SvxDrawPage> & DrawModelWrapper::getMainDrawPage()
+{
+ if (m_xMainDrawPage.is())
+ return m_xMainDrawPage;
+
+ // Create draw page.
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSuplier(getUnoModel(), uno::UNO_QUERY);
+ if (!xDrawPagesSuplier.is())
+ return m_xMainDrawPage;
+
+ uno::Reference<drawing::XDrawPages> xDrawPages = xDrawPagesSuplier->getDrawPages();
+ if (xDrawPages->getCount() > 1)
+ {
+ // Take the first page in case of multiple pages.
+ uno::Any aPage = xDrawPages->getByIndex(0);
+ uno::Reference<drawing::XDrawPage> xTmp;
+ aPage >>= xTmp;
+ m_xMainDrawPage = dynamic_cast<SvxDrawPage*>(xTmp.get());
+ assert(m_xMainDrawPage);
+ }
+
+ if (!m_xMainDrawPage.is())
+ {
+ m_xMainDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPages->insertNewByIndex(0).get());
+ assert(m_xMainDrawPage);
+ }
+
+ //ensure that additional shapes are in front of the chart objects so create the chart root before
+ // let us disable this call for now
+ // TODO:moggi
+ // ShapeFactory::getOrCreateShapeFactory(getShapeFactory())->getOrCreateChartRootShape( m_xMainDrawPage );
+ return m_xMainDrawPage;
+}
+
+const rtl::Reference<SvxDrawPage> & DrawModelWrapper::getHiddenDrawPage()
+{
+ if( !m_xHiddenDrawPage.is() )
+ {
+ uno::Reference< drawing::XDrawPagesSupplier > xDrawPagesSuplier( getUnoModel(), uno::UNO_QUERY );
+ if( xDrawPagesSuplier.is() )
+ {
+ uno::Reference< drawing::XDrawPages > xDrawPages( xDrawPagesSuplier->getDrawPages () );
+ if( xDrawPages->getCount()>1 )
+ {
+ uno::Any aPage = xDrawPages->getByIndex( 1 ) ;
+ uno::Reference<drawing::XDrawPage> xTmp;
+ aPage >>= xTmp;
+ m_xHiddenDrawPage = dynamic_cast<SvxDrawPage*>(xTmp.get());
+ assert(m_xHiddenDrawPage);
+ }
+
+ if(!m_xHiddenDrawPage.is())
+ {
+ if( xDrawPages->getCount()==0 )
+ {
+ m_xMainDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPages->insertNewByIndex( 0 ).get());
+ assert(m_xMainDrawPage);
+ }
+ m_xHiddenDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPages->insertNewByIndex( 1 ).get());
+ assert(m_xHiddenDrawPage);
+ }
+ }
+ }
+ return m_xHiddenDrawPage;
+}
+void DrawModelWrapper::clearMainDrawPage()
+{
+ //uno::Reference<drawing::XShapes> xChartRoot( m_xMainDrawPage, uno::UNO_QUERY );
+ rtl::Reference<SvxShapeGroupAnyD> xChartRoot( ShapeFactory::getChartRootShape( m_xMainDrawPage ) );
+ if( xChartRoot.is() )
+ {
+ sal_Int32 nSubCount = xChartRoot->getCount();
+ uno::Reference< drawing::XShape > xShape;
+ for( sal_Int32 nS = nSubCount; nS--; )
+ {
+ if( xChartRoot->getByIndex( nS ) >>= xShape )
+ xChartRoot->remove( xShape );
+ }
+ }
+}
+
+rtl::Reference<SvxShapeGroupAnyD> DrawModelWrapper::getChartRootShape( const rtl::Reference<SvxDrawPage>& xDrawPage )
+{
+ return ShapeFactory::getChartRootShape( xDrawPage );
+}
+
+void DrawModelWrapper::lockControllers()
+{
+ uno::Reference< frame::XModel > xDrawModel( getUnoModel() );
+ if( xDrawModel.is())
+ xDrawModel->lockControllers();
+}
+void DrawModelWrapper::unlockControllers()
+{
+ uno::Reference< frame::XModel > xDrawModel( getUnoModel() );
+ if( xDrawModel.is())
+ xDrawModel->unlockControllers();
+}
+
+OutputDevice* DrawModelWrapper::getReferenceDevice() const
+{
+ return SdrModel::GetRefDevice();
+}
+
+SfxItemPool& DrawModelWrapper::GetItemPool()
+{
+ return SdrModel::GetItemPool();
+}
+XColorListRef DrawModelWrapper::GetColorList() const
+{
+ return SdrModel::GetColorList();
+}
+XDashListRef DrawModelWrapper::GetDashList() const
+{
+ return SdrModel::GetDashList();
+}
+XLineEndListRef DrawModelWrapper::GetLineEndList() const
+{
+ return SdrModel::GetLineEndList();
+}
+XGradientListRef DrawModelWrapper::GetGradientList() const
+{
+ return SdrModel::GetGradientList();
+}
+XHatchListRef DrawModelWrapper::GetHatchList() const
+{
+ return SdrModel::GetHatchList();
+}
+XBitmapListRef DrawModelWrapper::GetBitmapList() const
+{
+ return SdrModel::GetBitmapList();
+}
+
+XPatternListRef DrawModelWrapper::GetPatternList() const
+{
+ return SdrModel::GetPatternList();
+}
+
+SdrObject* DrawModelWrapper::getNamedSdrObject( const OUString& rName )
+{
+ if( rName.isEmpty() )
+ return nullptr;
+ return getNamedSdrObject( rName, GetPage(0) );
+}
+
+SdrObject* DrawModelWrapper::getNamedSdrObject( const OUString& rObjectCID, SdrObjList const * pSearchList )
+{
+ if(!pSearchList || rObjectCID.isEmpty())
+ return nullptr;
+ const size_t nCount = pSearchList->GetObjCount();
+ for( size_t nN=0; nN<nCount; ++nN )
+ {
+ SdrObject* pObj = pSearchList->GetObj(nN);
+ if(!pObj)
+ continue;
+ if( ObjectIdentifier::areIdenticalObjects( rObjectCID, pObj->GetName() ) )
+ return pObj;
+ pObj = DrawModelWrapper::getNamedSdrObject( rObjectCID, pObj->GetSubList() );
+ if(pObj)
+ return pObj;
+ }
+ return nullptr;
+}
+
+bool DrawModelWrapper::removeShape( const rtl::Reference<SvxShape>& xShape )
+{
+ uno::Reference<drawing::XShapes> xShapes( xShape->getParent(), uno::UNO_QUERY );
+ if( xShapes.is() )
+ {
+ xShapes->remove(xShape);
+ return true;
+ }
+ return false;
+}
+
+void DrawModelWrapper::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("DrawModelWrapper"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+
+ SdrModel::dumpAsXml(pWriter);
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/ExplicitValueProvider.cxx b/chart2/source/view/main/ExplicitValueProvider.cxx
new file mode 100644
index 000000000..cf5a99974
--- /dev/null
+++ b/chart2/source/view/main/ExplicitValueProvider.cxx
@@ -0,0 +1,211 @@
+/* -*- 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 <chartview/ExplicitValueProvider.hxx>
+#include <AxisHelper.hxx>
+#include <ChartModel.hxx>
+#include <Diagram.hxx>
+#include <DiagramHelper.hxx>
+#include <unonames.hxx>
+#include <BaseCoordinateSystem.hxx>
+#include <TitleHelper.hxx>
+#include <ObjectIdentifier.hxx>
+
+#include <comphelper/servicehelper.hxx>
+#include <tools/diagnose_ex.h>
+
+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;
+using ::com::sun::star::uno::Any;
+
+namespace
+{
+constexpr sal_Int32 constDiagramTitleSpace = 200; //=0,2 cm spacing
+
+bool lcl_getPropertySwapXAndYAxis(const rtl::Reference<Diagram>& xDiagram)
+{
+ bool bSwapXAndY = false;
+
+ if (xDiagram.is())
+ {
+ const std::vector<rtl::Reference<BaseCoordinateSystem>>& aCooSysList(
+ xDiagram->getBaseCoordinateSystems());
+ if (!aCooSysList.empty())
+ {
+ try
+ {
+ aCooSysList[0]->getPropertyValue("SwapXAndYAxis") >>= bSwapXAndY;
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "");
+ }
+ }
+ }
+ return bSwapXAndY;
+}
+
+} // end anonymous namespace
+
+sal_Int32 ExplicitValueProvider::getExplicitNumberFormatKeyForAxis(
+ const Reference<chart2::XAxis>& xAxis,
+ const rtl::Reference<::chart::BaseCoordinateSystem>& xCorrespondingCoordinateSystem,
+ const rtl::Reference<::chart::ChartModel>& xChartDoc)
+{
+ return AxisHelper::getExplicitNumberFormatKeyForAxis(
+ xAxis, xCorrespondingCoordinateSystem, xChartDoc,
+ true /*bSearchForParallelAxisIfNothingIsFound*/);
+}
+
+const uno::Sequence<sal_Int8>& ExplicitValueProvider::getUnoTunnelId()
+{
+ static const comphelper::UnoIdInit theExplicitValueProviderUnoTunnelId;
+ return theExplicitValueProviderUnoTunnelId.getSeq();
+}
+
+sal_Int32 ExplicitValueProvider::getExplicitNumberFormatKeyForDataLabel(
+ const uno::Reference<beans::XPropertySet>& xSeriesOrPointProp)
+{
+ sal_Int32 nFormat = 0;
+ if (!xSeriesOrPointProp.is())
+ return nFormat;
+
+ try
+ {
+ xSeriesOrPointProp->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nFormat;
+ }
+ catch (const beans::UnknownPropertyException&)
+ {
+ }
+
+ if (nFormat < 0)
+ nFormat = 0;
+ return nFormat;
+}
+
+sal_Int32 ExplicitValueProvider::getExplicitPercentageNumberFormatKeyForDataLabel(
+ const uno::Reference<beans::XPropertySet>& xSeriesOrPointProp,
+ const uno::Reference<util::XNumberFormatsSupplier>& xNumberFormatsSupplier)
+{
+ sal_Int32 nFormat = 0;
+ if (!xSeriesOrPointProp.is())
+ return nFormat;
+ if (!(xSeriesOrPointProp->getPropertyValue("PercentageNumberFormat") >>= nFormat))
+ {
+ nFormat = DiagramHelper::getPercentNumberFormat(xNumberFormatsSupplier);
+ }
+ if (nFormat < 0)
+ nFormat = 0;
+ return nFormat;
+}
+
+awt::Rectangle ExplicitValueProvider::AddSubtractAxisTitleSizes(
+ ChartModel& rModel, ExplicitValueProvider* pChartView, const awt::Rectangle& rPositionAndSize,
+ bool bSubtract)
+{
+ awt::Rectangle aRet(rPositionAndSize);
+
+ //add axis title sizes to the diagram size
+ uno::Reference<chart2::XTitle> xTitle_Height(
+ TitleHelper::getTitle(TitleHelper::TITLE_AT_STANDARD_X_AXIS_POSITION, rModel));
+ uno::Reference<chart2::XTitle> xTitle_Width(
+ TitleHelper::getTitle(TitleHelper::TITLE_AT_STANDARD_Y_AXIS_POSITION, rModel));
+ uno::Reference<chart2::XTitle> xSecondTitle_Height(
+ TitleHelper::getTitle(TitleHelper::SECONDARY_X_AXIS_TITLE, rModel));
+ uno::Reference<chart2::XTitle> xSecondTitle_Width(
+ TitleHelper::getTitle(TitleHelper::SECONDARY_Y_AXIS_TITLE, rModel));
+ if (xTitle_Height.is() || xTitle_Width.is() || xSecondTitle_Height.is()
+ || xSecondTitle_Width.is())
+ {
+ ExplicitValueProvider* pExplicitValueProvider = pChartView;
+ if (pExplicitValueProvider)
+ {
+ //detect whether x axis points into x direction or not
+ if (lcl_getPropertySwapXAndYAxis(rModel.getFirstChartDiagram()))
+ {
+ std::swap(xTitle_Height, xTitle_Width);
+ std::swap(xSecondTitle_Height, xSecondTitle_Width);
+ }
+
+ sal_Int32 nTitleSpaceWidth = 0;
+ sal_Int32 nTitleSpaceHeight = 0;
+ sal_Int32 nSecondTitleSpaceWidth = 0;
+ sal_Int32 nSecondTitleSpaceHeight = 0;
+
+ if (xTitle_Height.is())
+ {
+ OUString aCID_X(
+ ObjectIdentifier::createClassifiedIdentifierForObject(xTitle_Height, &rModel));
+ nTitleSpaceHeight
+ = pExplicitValueProvider->getRectangleOfObject(aCID_X, true).Height;
+ if (nTitleSpaceHeight)
+ nTitleSpaceHeight += constDiagramTitleSpace;
+ }
+ if (xTitle_Width.is())
+ {
+ OUString aCID_Y(
+ ObjectIdentifier::createClassifiedIdentifierForObject(xTitle_Width, &rModel));
+ nTitleSpaceWidth = pExplicitValueProvider->getRectangleOfObject(aCID_Y, true).Width;
+ if (nTitleSpaceWidth)
+ nTitleSpaceWidth += constDiagramTitleSpace;
+ }
+ if (xSecondTitle_Height.is())
+ {
+ OUString aCID_X(ObjectIdentifier::createClassifiedIdentifierForObject(
+ xSecondTitle_Height, &rModel));
+ nSecondTitleSpaceHeight
+ = pExplicitValueProvider->getRectangleOfObject(aCID_X, true).Height;
+ if (nSecondTitleSpaceHeight)
+ nSecondTitleSpaceHeight += constDiagramTitleSpace;
+ }
+ if (xSecondTitle_Width.is())
+ {
+ OUString aCID_Y(ObjectIdentifier::createClassifiedIdentifierForObject(
+ xSecondTitle_Width, &rModel));
+ nSecondTitleSpaceWidth
+ += pExplicitValueProvider->getRectangleOfObject(aCID_Y, true).Width;
+ if (nSecondTitleSpaceWidth)
+ nSecondTitleSpaceWidth += constDiagramTitleSpace;
+ }
+ if (bSubtract)
+ {
+ aRet.X += nTitleSpaceWidth;
+ aRet.Y += nSecondTitleSpaceHeight;
+ aRet.Width -= (nTitleSpaceWidth + nSecondTitleSpaceWidth);
+ aRet.Height -= (nTitleSpaceHeight + nSecondTitleSpaceHeight);
+ }
+ else
+ {
+ aRet.X -= nTitleSpaceWidth;
+ aRet.Y -= nSecondTitleSpaceHeight;
+ aRet.Width += nTitleSpaceWidth + nSecondTitleSpaceWidth;
+ aRet.Height += nTitleSpaceHeight + nSecondTitleSpaceHeight;
+ }
+ }
+ }
+ return aRet;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/LabelPositionHelper.cxx b/chart2/source/view/main/LabelPositionHelper.cxx
new file mode 100644
index 000000000..e39bc0495
--- /dev/null
+++ b/chart2/source/view/main/LabelPositionHelper.cxx
@@ -0,0 +1,467 @@
+/* -*- 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 <LabelPositionHelper.hxx>
+#include <PlottingPositionHelper.hxx>
+#include <PropertyMapper.hxx>
+#include <RelativeSizeHelper.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
+#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
+
+#include <cmath>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+LabelPositionHelper::LabelPositionHelper(
+ sal_Int32 nDimensionCount
+ , const rtl::Reference<SvxShapeGroupAnyD>& xLogicTarget)
+ : m_nDimensionCount(nDimensionCount)
+ , m_xLogicTarget(xLogicTarget)
+{
+}
+
+LabelPositionHelper::~LabelPositionHelper()
+{
+}
+
+awt::Point LabelPositionHelper::transformSceneToScreenPosition( const drawing::Position3D& rScenePosition3D ) const
+{
+ return PlottingPositionHelper::transformSceneToScreenPosition(
+ rScenePosition3D, m_xLogicTarget, m_nDimensionCount );
+}
+
+void LabelPositionHelper::changeTextAdjustment( tAnySequence& rPropValues, const tNameSequence& rPropNames, LabelAlignment eAlignment)
+{
+ //HorizontalAdjustment
+ {
+ drawing::TextHorizontalAdjust eHorizontalAdjust = drawing::TextHorizontalAdjust_CENTER;
+ if( eAlignment==LABEL_ALIGN_RIGHT || eAlignment==LABEL_ALIGN_RIGHT_TOP || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM )
+ eHorizontalAdjust = drawing::TextHorizontalAdjust_LEFT;
+ else if( eAlignment==LABEL_ALIGN_LEFT || eAlignment==LABEL_ALIGN_LEFT_TOP || eAlignment==LABEL_ALIGN_LEFT_BOTTOM )
+ eHorizontalAdjust = drawing::TextHorizontalAdjust_RIGHT;
+ uno::Any* pHorizontalAdjustAny = PropertyMapper::getValuePointer(rPropValues,rPropNames,u"TextHorizontalAdjust");
+ if(pHorizontalAdjustAny)
+ *pHorizontalAdjustAny <<= eHorizontalAdjust;
+ }
+
+ //VerticalAdjustment
+ {
+ drawing::TextVerticalAdjust eVerticalAdjust = drawing::TextVerticalAdjust_CENTER;
+ if( eAlignment==LABEL_ALIGN_TOP || eAlignment==LABEL_ALIGN_RIGHT_TOP || eAlignment==LABEL_ALIGN_LEFT_TOP )
+ eVerticalAdjust = drawing::TextVerticalAdjust_BOTTOM;
+ else if( eAlignment==LABEL_ALIGN_BOTTOM || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM || eAlignment==LABEL_ALIGN_LEFT_BOTTOM )
+ eVerticalAdjust = drawing::TextVerticalAdjust_TOP;
+ uno::Any* pVerticalAdjustAny = PropertyMapper::getValuePointer(rPropValues,rPropNames,u"TextVerticalAdjust");
+ if(pVerticalAdjustAny)
+ *pVerticalAdjustAny <<= eVerticalAdjust;
+ }
+}
+
+static void lcl_doDynamicFontResize( uno::Any* pAOldAndNewFontHeightAny
+ , const awt::Size& rOldReferenceSize
+ , const awt::Size& rNewReferenceSize )
+{
+ double fOldFontHeight = 0;
+ if( pAOldAndNewFontHeightAny && ( *pAOldAndNewFontHeightAny >>= fOldFontHeight ) )
+ {
+ double fNewFontHeight = RelativeSizeHelper::calculate( fOldFontHeight, rOldReferenceSize, rNewReferenceSize );
+ *pAOldAndNewFontHeightAny <<= fNewFontHeight;
+ }
+}
+
+void LabelPositionHelper::doDynamicFontResize( tAnySequence& rPropValues
+ , const tNameSequence& rPropNames
+ , const uno::Reference< beans::XPropertySet >& xAxisModelProps
+ , const awt::Size& rNewReferenceSize
+ )
+{
+ //handle dynamic font resize:
+ awt::Size aOldReferenceSize;
+ if( xAxisModelProps->getPropertyValue( "ReferencePageSize") >>= aOldReferenceSize )
+ {
+ uno::Any* pAOldAndNewFontHeightAny = PropertyMapper::getValuePointer( rPropValues, rPropNames, u"CharHeight" );
+ lcl_doDynamicFontResize( pAOldAndNewFontHeightAny, aOldReferenceSize, rNewReferenceSize );
+ pAOldAndNewFontHeightAny = PropertyMapper::getValuePointer( rPropValues, rPropNames, u"CharHeightAsian" );
+ lcl_doDynamicFontResize( pAOldAndNewFontHeightAny, aOldReferenceSize, rNewReferenceSize );
+ pAOldAndNewFontHeightAny = PropertyMapper::getValuePointer( rPropValues, rPropNames, u"CharHeightComplex" );
+ lcl_doDynamicFontResize( pAOldAndNewFontHeightAny, aOldReferenceSize, rNewReferenceSize );
+ }
+}
+
+namespace
+{
+
+void lcl_correctRotation_Left( double& rfXCorrection, double& rfYCorrection
+ , double fAnglePositiveDegree, const awt::Size& aSize, bool bRotateAroundCenter )
+{
+ //correct label positions for labels on a left side of something with a right centered alignment
+ double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree);
+ if( fAnglePositiveDegree==0.0 )
+ {
+ }
+ else if( fAnglePositiveDegree<= 90.0 )
+ {
+ rfXCorrection = -aSize.Height*std::sin( fAnglePi )/2.0;
+ if( bRotateAroundCenter )
+ rfYCorrection = -aSize.Width*std::sin( fAnglePi )/2.0;
+ }
+ else if( fAnglePositiveDegree<= 180.0 )
+ {
+ double beta = fAnglePi-M_PI_2;
+ rfXCorrection = -aSize.Width *std::sin( beta )
+ -aSize.Height *std::cos( beta )/2.0;
+ if( bRotateAroundCenter )
+ rfYCorrection = -aSize.Width *std::cos( beta )/2.0;
+ else
+ rfYCorrection = -aSize.Width *std::cos( beta );
+ }
+ else if( fAnglePositiveDegree<= 270.0 )
+ {
+ double beta = fAnglePi - M_PI;
+ rfXCorrection = -aSize.Width *std::cos( beta )
+ -aSize.Height*std::sin( beta )/2.0;
+ if( bRotateAroundCenter )
+ rfYCorrection = aSize.Width *std::sin( beta )/2.0;
+ else
+ rfYCorrection = aSize.Width *std::sin( beta );
+ }
+ else
+ {
+ double beta = 2*M_PI - fAnglePi;
+ rfXCorrection = -aSize.Height*std::sin( beta )/2.0;
+ if( bRotateAroundCenter )
+ rfYCorrection = aSize.Width*std::sin( beta )/2.0;
+ }
+}
+
+void lcl_correctRotation_Right( double& rfXCorrection, double& rfYCorrection
+ , double fAnglePositiveDegree, const awt::Size& aSize, bool bRotateAroundCenter )
+{
+ //correct label positions for labels on a right side of something with a left centered alignment
+ double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree);
+ if( fAnglePositiveDegree== 0.0 )
+ {
+ }
+ else if( fAnglePositiveDegree<= 90.0 )
+ {
+ rfXCorrection = aSize.Height*std::sin( fAnglePi )/2.0;
+ if( bRotateAroundCenter )
+ rfYCorrection = aSize.Width*std::sin( fAnglePi )/2.0;
+ }
+ else if( fAnglePositiveDegree<= 180.0 )
+ {
+ double beta = M_PI - fAnglePi;
+ rfXCorrection = aSize.Width *std::cos( beta )
+ + aSize.Height*std::sin( beta )/2.0;
+ if( bRotateAroundCenter )
+ rfYCorrection = aSize.Width *std::sin( beta )/2.0;
+ else
+ rfYCorrection = aSize.Width *std::sin( beta );
+ }
+ else if( fAnglePositiveDegree<= 270.0 )
+ {
+ double beta = 3*M_PI_2 - fAnglePi;
+ rfXCorrection = aSize.Width *std::sin( beta )
+ +aSize.Height*std::cos( beta )/2.0;
+ if( bRotateAroundCenter )
+ rfYCorrection = -aSize.Width *std::cos( beta )/2.0;
+ else
+ rfYCorrection = -aSize.Width *std::cos( beta );
+ }
+ else
+ {
+ rfXCorrection = aSize.Height*std::sin( 2*M_PI - fAnglePi )/2.0;
+ if( bRotateAroundCenter )
+ rfYCorrection = -aSize.Width*std::sin( 2*M_PI - fAnglePi )/2.0;
+ }
+}
+
+void lcl_correctRotation_Top( double& rfXCorrection, double& rfYCorrection
+ , double fAnglePositiveDegree, const awt::Size& aSize, bool bRotateAroundCenter )
+{
+ //correct label positions for labels on top of something with a bottom centered alignment
+ double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree);
+ if( fAnglePositiveDegree== 0.0 )
+ {
+ }
+ else if( fAnglePositiveDegree<= 90.0 )
+ {
+ rfXCorrection = aSize.Height*std::sin( fAnglePi )/2.0;
+ if( !bRotateAroundCenter )
+ rfXCorrection += aSize.Width*std::cos( fAnglePi )/2.0;
+ rfYCorrection = -aSize.Width*std::sin( fAnglePi )/2.0;
+ }
+ else if( fAnglePositiveDegree<= 180.0 )
+ {
+ double beta = fAnglePi - M_PI_2;
+ rfXCorrection = aSize.Height*std::cos( beta )/2.0;
+ if( !bRotateAroundCenter )
+ rfXCorrection -= aSize.Width*std::sin( beta )/2.0;
+ rfYCorrection = -aSize.Width*std::cos( beta )/2.0
+ - aSize.Height*std::sin( beta );
+ }
+ else if( fAnglePositiveDegree<= 270.0 )
+ {
+ double beta = fAnglePi - M_PI;
+ rfXCorrection = -aSize.Height *std::sin( beta )/2.0;
+ if( !bRotateAroundCenter )
+ rfXCorrection += aSize.Width *std::cos( beta )/2.0;
+ rfYCorrection = -aSize.Width *std::sin( beta )/2.0
+ -aSize.Height *std::cos( beta );
+ }
+ else
+ {
+ rfXCorrection = aSize.Height*std::sin( fAnglePi )/2.0;
+ if( !bRotateAroundCenter )
+ rfXCorrection -= aSize.Width*std::cos( fAnglePi )/2.0;
+ rfYCorrection = aSize.Width*std::sin( fAnglePi )/2.0;
+ }
+}
+
+void lcl_correctRotation_Bottom( double& rfXCorrection, double& rfYCorrection
+ , double fAnglePositiveDegree, const awt::Size& aSize, bool bRotateAroundCenter )
+{
+ //correct label positions for labels below something with a top centered alignment
+ double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree);
+ if( fAnglePositiveDegree==0.0 )
+ {
+ }
+ else if( fAnglePositiveDegree<= 90.0 )
+ {
+ rfXCorrection = -aSize.Height*std::sin( fAnglePi )/2.0;
+ if( !bRotateAroundCenter )
+ rfXCorrection -= aSize.Width *std::cos( fAnglePi )/2.0;
+ rfYCorrection = aSize.Width*std::sin( fAnglePi )/2.0;
+ }
+ else if( fAnglePositiveDegree<= 180.0 )
+ {
+ double beta = fAnglePi-M_PI_2;
+ rfXCorrection = -aSize.Height*std::cos( beta )/2.0;
+ if( !bRotateAroundCenter )
+ rfXCorrection += aSize.Width *std::sin( beta )/2.0;
+ rfYCorrection = aSize.Width *std::cos( beta )/2.0
+ +aSize.Height*std::sin( beta );
+ }
+ else if( fAnglePositiveDegree<= 270.0 )
+ {
+ double beta = 3*M_PI_2 - fAnglePi;
+ rfXCorrection = aSize.Height*std::cos( beta )/2.0;
+ if( !bRotateAroundCenter )
+ rfXCorrection -= aSize.Width *std::sin( beta )/2.0;
+ rfYCorrection = aSize.Height*std::sin( beta )
+ +aSize.Width*std::cos( beta )/2.0;
+ }
+ else
+ {
+ double beta = 2*M_PI - fAnglePi;
+ rfXCorrection = aSize.Height*std::sin( beta )/2.0;
+ if( !bRotateAroundCenter )
+ rfXCorrection += aSize.Width*std::cos( beta )/2.0;
+ rfYCorrection = aSize.Width*std::sin( beta )/2.0;
+ }
+}
+
+void lcl_correctRotation_Left_Top( double& rfXCorrection, double& rfYCorrection
+ , double fAnglePositiveDegree, const awt::Size& aSize )
+{
+ //correct position for labels at the left top corner of something with a bottom right alignment
+ double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree);
+ if( fAnglePositiveDegree==0.0 )
+ {
+ }
+ else if( fAnglePositiveDegree<= 90.0 )
+ {
+ rfYCorrection = -aSize.Width*std::sin( fAnglePi );
+ }
+ else if( fAnglePositiveDegree<= 180.0 )
+ {
+ double beta = fAnglePi-M_PI_2;
+ rfXCorrection = -aSize.Width*std::sin( beta );
+ rfYCorrection = -aSize.Height*std::sin( beta )
+ -aSize.Width*std::cos( beta );
+ }
+ else if( fAnglePositiveDegree<= 270.0 )
+ {
+ double beta = 3*M_PI_2 - fAnglePi;
+ rfXCorrection = -aSize.Height*std::cos( beta )
+ -aSize.Width*std::sin( beta );
+ rfYCorrection = -aSize.Height*std::sin( beta );
+ }
+ else
+ {
+ rfXCorrection = aSize.Height*std::sin( fAnglePi );
+ }
+}
+
+void lcl_correctRotation_Left_Bottom( double& rfXCorrection, double& rfYCorrection
+ , double fAnglePositiveDegree, const awt::Size& aSize )
+{
+ //correct position for labels at the left bottom corner of something with a top right alignment
+ double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree);
+ if( fAnglePositiveDegree==0.0 )
+ {
+ }
+ else if( fAnglePositiveDegree<= 90.0 )
+ {
+ rfXCorrection = -aSize.Height*std::sin( fAnglePi );
+ }
+ else if( fAnglePositiveDegree<= 180.0 )
+ {
+ double beta = fAnglePi-M_PI_2;
+ rfXCorrection = -aSize.Width*std::sin( beta )
+ -aSize.Height*std::cos( beta );
+ rfYCorrection = aSize.Height*std::sin( beta );
+ }
+ else if( fAnglePositiveDegree<= 270.0 )
+ {
+ double beta = 3*M_PI_2 - fAnglePi;
+ rfXCorrection = -aSize.Width*std::sin( beta );
+ rfYCorrection = aSize.Width*std::cos( beta )
+ +aSize.Height*std::sin( beta );
+ }
+ else
+ {
+ rfYCorrection = -aSize.Width*std::sin( fAnglePi );
+ }
+}
+
+void lcl_correctRotation_Right_Top( double& rfXCorrection, double& rfYCorrection
+ , double fAnglePositiveDegree, const awt::Size& aSize )
+{
+ //correct position for labels at the right top corner of something with a bottom left alignment
+ double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree);
+ if( fAnglePositiveDegree==0.0 )
+ {
+ }
+ else if( fAnglePositiveDegree<= 90.0 )
+ {
+ rfXCorrection = aSize.Height*std::sin( fAnglePi );
+ }
+ else if( fAnglePositiveDegree<= 180.0 )
+ {
+ double beta = fAnglePi-M_PI_2;
+ rfXCorrection = aSize.Width*std::sin( beta )
+ +aSize.Height*std::cos( beta );
+ rfYCorrection = -aSize.Height*std::sin( beta );
+ }
+ else if( fAnglePositiveDegree<= 270.0 )
+ {
+ double beta = 3*M_PI_2 - fAnglePi;
+ rfXCorrection = aSize.Width*std::sin( beta );
+ rfYCorrection = -aSize.Width*std::cos( beta )
+ -aSize.Height*std::sin( beta );
+ }
+ else
+ {
+ rfYCorrection = aSize.Width*std::sin( fAnglePi );
+ }
+}
+
+void lcl_correctRotation_Right_Bottom( double& rfXCorrection, double& rfYCorrection
+ , double fAnglePositiveDegree, const awt::Size& aSize )
+{
+ //correct position for labels at the right bottom corner of something with a top left alignment
+ double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree);
+ if( fAnglePositiveDegree==0.0 )
+ {
+ }
+ else if( fAnglePositiveDegree<= 90.0 )
+ {
+ rfYCorrection = aSize.Width*std::sin( fAnglePi );
+ }
+ else if( fAnglePositiveDegree<= 180.0 )
+ {
+ double beta = fAnglePi-M_PI_2;
+ rfXCorrection = aSize.Width*std::sin( beta );
+ rfYCorrection = aSize.Height*std::sin( beta )
+ +aSize.Width*std::cos( beta );
+ }
+ else if( fAnglePositiveDegree<= 270.0 )
+ {
+ double beta = 3*M_PI_2 - fAnglePi;
+ rfXCorrection = aSize.Height*std::cos( beta )
+ +aSize.Width*std::sin( beta );
+ rfYCorrection = aSize.Height*std::sin( beta );
+ }
+ else
+ {
+ rfXCorrection = -aSize.Height*std::sin( fAnglePi );
+ }
+}
+
+}//end anonymous namespace
+
+void LabelPositionHelper::correctPositionForRotation( const rtl::Reference<SvxShapeText>& xShape2DText
+ , LabelAlignment eLabelAlignment, const double fRotationAngle, bool bRotateAroundCenter )
+{
+ if( !xShape2DText.is() )
+ return;
+
+ awt::Point aOldPos = xShape2DText->getPosition();
+ awt::Size aSize = xShape2DText->getSize();
+
+ double fYCorrection = 0.0;
+ double fXCorrection = 0.0;
+
+ double fAnglePositiveDegree = fRotationAngle;
+ while(fAnglePositiveDegree<0.0)
+ fAnglePositiveDegree+=360.0;
+
+ switch(eLabelAlignment)
+ {
+ case LABEL_ALIGN_LEFT:
+ lcl_correctRotation_Left( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize, bRotateAroundCenter );
+ break;
+ case LABEL_ALIGN_RIGHT:
+ lcl_correctRotation_Right( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize, bRotateAroundCenter );
+ break;
+ case LABEL_ALIGN_TOP:
+ lcl_correctRotation_Top( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize, bRotateAroundCenter );
+ break;
+ case LABEL_ALIGN_BOTTOM:
+ lcl_correctRotation_Bottom( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize, bRotateAroundCenter );
+ break;
+ case LABEL_ALIGN_LEFT_TOP:
+ lcl_correctRotation_Left_Top( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize );
+ break;
+ case LABEL_ALIGN_LEFT_BOTTOM:
+ lcl_correctRotation_Left_Bottom( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize );
+ break;
+ case LABEL_ALIGN_RIGHT_TOP:
+ lcl_correctRotation_Right_Top( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize );
+ break;
+ case LABEL_ALIGN_RIGHT_BOTTOM:
+ lcl_correctRotation_Right_Bottom( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize );
+ break;
+ default: //LABEL_ALIGN_CENTER
+ break;
+ }
+
+ xShape2DText->setPosition( awt::Point(
+ static_cast<sal_Int32>(aOldPos.X + fXCorrection )
+ , static_cast<sal_Int32>(aOldPos.Y + fYCorrection ) ) );
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/Linear3DTransformation.cxx b/chart2/source/view/main/Linear3DTransformation.cxx
new file mode 100644
index 000000000..0d723ef34
--- /dev/null
+++ b/chart2/source/view/main/Linear3DTransformation.cxx
@@ -0,0 +1,124 @@
+/* -*- 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 <Linear3DTransformation.hxx>
+
+using namespace ::com::sun::star;
+
+using ::com::sun::star::uno::Sequence;
+
+namespace chart
+{
+
+ Linear3DTransformation::Linear3DTransformation( const drawing::HomogenMatrix& rHomMatrix, bool bSwapXAndY )
+ : m_Matrix(rHomMatrix)
+ , m_bSwapXAndY(bSwapXAndY)
+{}
+
+Linear3DTransformation::~Linear3DTransformation()
+{}
+
+// ____ XTransformation2 ____
+css::drawing::Position3D Linear3DTransformation::transform(
+ const Sequence< double >& rSourceValues ) const
+{
+ double fX = rSourceValues[0];
+ double fY = rSourceValues[1];
+ double fZ = rSourceValues[2];
+ if(m_bSwapXAndY)
+ std::swap(fX,fY);
+ css::drawing::Position3D aNewVec;
+ double fZwi;
+
+ fZwi = m_Matrix.Line1.Column1 * fX
+ + m_Matrix.Line1.Column2 * fY
+ + m_Matrix.Line1.Column3 * fZ
+ + m_Matrix.Line1.Column4;
+ aNewVec.PositionX = fZwi;
+
+ fZwi = m_Matrix.Line2.Column1 * fX
+ + m_Matrix.Line2.Column2 * fY
+ + m_Matrix.Line2.Column3 * fZ
+ + m_Matrix.Line2.Column4;
+ aNewVec.PositionY = fZwi;
+
+ fZwi = m_Matrix.Line3.Column1 * fX
+ + m_Matrix.Line3.Column2 * fY
+ + m_Matrix.Line3.Column3 * fZ
+ + m_Matrix.Line3.Column4;
+ aNewVec.PositionZ = fZwi;
+
+ fZwi = m_Matrix.Line4.Column1 * fX
+ + m_Matrix.Line4.Column2 * fY
+ + m_Matrix.Line4.Column3 * fZ
+ + m_Matrix.Line4.Column4;
+ if(fZwi != 1.0 && fZwi != 0.0)
+ {
+ aNewVec.PositionX /= fZwi;
+ aNewVec.PositionY /= fZwi;
+ aNewVec.PositionZ /= fZwi;
+ }
+ return aNewVec;
+}
+
+css::drawing::Position3D Linear3DTransformation::transform(
+ const css::drawing::Position3D& rSourceValues ) const
+{
+ double fX = rSourceValues.PositionX;
+ double fY = rSourceValues.PositionY;
+ double fZ = rSourceValues.PositionZ;
+ if(m_bSwapXAndY)
+ std::swap(fX,fY);
+ css::drawing::Position3D aNewVec;
+ double fZwi;
+
+ fZwi = m_Matrix.Line1.Column1 * fX
+ + m_Matrix.Line1.Column2 * fY
+ + m_Matrix.Line1.Column3 * fZ
+ + m_Matrix.Line1.Column4;
+ aNewVec.PositionX = fZwi;
+
+ fZwi = m_Matrix.Line2.Column1 * fX
+ + m_Matrix.Line2.Column2 * fY
+ + m_Matrix.Line2.Column3 * fZ
+ + m_Matrix.Line2.Column4;
+ aNewVec.PositionY = fZwi;
+
+ fZwi = m_Matrix.Line3.Column1 * fX
+ + m_Matrix.Line3.Column2 * fY
+ + m_Matrix.Line3.Column3 * fZ
+ + m_Matrix.Line3.Column4;
+ aNewVec.PositionZ = fZwi;
+
+ fZwi = m_Matrix.Line4.Column1 * fX
+ + m_Matrix.Line4.Column2 * fY
+ + m_Matrix.Line4.Column3 * fZ
+ + m_Matrix.Line4.Column4;
+ if(fZwi != 1.0 && fZwi != 0.0)
+ {
+ aNewVec.PositionX /= fZwi;
+ aNewVec.PositionY /= fZwi;
+ aNewVec.PositionZ /= fZwi;
+ }
+ return aNewVec;
+}
+
+} // namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/PlotterBase.cxx b/chart2/source/view/main/PlotterBase.cxx
new file mode 100644
index 000000000..a706f1600
--- /dev/null
+++ b/chart2/source/view/main/PlotterBase.cxx
@@ -0,0 +1,106 @@
+/* -*- 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 <PlotterBase.hxx>
+#include <PlottingPositionHelper.hxx>
+#include <ShapeFactory.hxx>
+#include <osl/diagnose.h>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+PlotterBase::PlotterBase( sal_Int32 nDimensionCount )
+ : m_nDimension(nDimensionCount)
+ , m_pPosHelper(nullptr)
+{
+}
+
+void PlotterBase::initPlotter( const rtl::Reference<SvxShapeGroupAnyD>& xLogicTarget
+ , const rtl::Reference<SvxShapeGroupAnyD>& xFinalTarget
+ , const OUString& rCID )
+{
+ OSL_PRECOND(xLogicTarget.is()&&xFinalTarget.is(),"no proper initialization parameters");
+ //is only allowed to be called once
+ m_xLogicTarget = xLogicTarget;
+ m_xFinalTarget = xFinalTarget;
+ m_aCID = rCID;
+}
+
+PlotterBase::~PlotterBase()
+{
+}
+
+void PlotterBase::setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis )
+{
+ if (!m_pPosHelper)
+ return;
+
+ OSL_PRECOND(m_nDimension<=static_cast<sal_Int32>(rScales.size()),"Dimension of Plotter does not fit two dimension of given scale sequence");
+ m_pPosHelper->setScales( std::move(rScales), bSwapXAndYAxis );
+}
+
+void PlotterBase::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix)
+{
+ if (!m_pPosHelper)
+ return;
+
+ OSL_PRECOND(m_nDimension==2,"Set this transformation only in case of 2D");
+ if(m_nDimension!=2)
+ return;
+ m_pPosHelper->setTransformationSceneToScreen( rMatrix );
+}
+
+rtl::Reference<SvxShapeGroupAnyD> PlotterBase::createGroupShape(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const OUString& rName )
+{
+ if(m_nDimension==2)
+ {
+ //create and add to target
+ return ShapeFactory::createGroup2D( xTarget, rName );
+ }
+ else
+ {
+ //create and added to target
+ return ShapeFactory::createGroup3D( xTarget, rName );
+ }
+}
+
+bool PlotterBase::isValidPosition( const drawing::Position3D& rPos )
+{
+ if( std::isnan(rPos.PositionX) )
+ return false;
+ if( std::isnan(rPos.PositionY) )
+ return false;
+ if( std::isnan(rPos.PositionZ) )
+ return false;
+ if( std::isinf(rPos.PositionX) )
+ return false;
+ if( std::isinf(rPos.PositionY) )
+ return false;
+ if( std::isinf(rPos.PositionZ) )
+ return false;
+ return true;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/PlottingPositionHelper.cxx b/chart2/source/view/main/PlottingPositionHelper.cxx
new file mode 100644
index 000000000..eab2f69e7
--- /dev/null
+++ b/chart2/source/view/main/PlottingPositionHelper.cxx
@@ -0,0 +1,708 @@
+/* -*- 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 <PlottingPositionHelper.hxx>
+#include <CommonConverters.hxx>
+#include <Linear3DTransformation.hxx>
+#include <VPolarTransformation.hxx>
+#include <ShapeFactory.hxx>
+#include <PropertyMapper.hxx>
+#include <defines.hxx>
+
+#include <com/sun/star/chart/TimeUnit.hpp>
+#include <com/sun/star/chart2/AxisType.hpp>
+#include <com/sun/star/drawing/Position3D.hpp>
+
+#include <rtl/math.hxx>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+XTransformation2::~XTransformation2() {}
+
+PlottingPositionHelper::PlottingPositionHelper()
+ : m_bSwapXAndY( false )
+ , m_nXResolution( 1000 )
+ , m_nYResolution( 1000 )
+ , m_nZResolution( 1000 )
+ , m_bMaySkipPointsInRegressionCalculation( true )
+ , m_bDateAxis(false)
+ , m_nTimeResolution( css::chart::TimeUnit::DAY )
+ , m_aNullDate(30,12,1899)
+ , m_fScaledCategoryWidth(1.0)
+ , m_bAllowShiftXAxisPos(false)
+ , m_bAllowShiftZAxisPos(false)
+{
+}
+PlottingPositionHelper::PlottingPositionHelper( const PlottingPositionHelper& rSource )
+ : m_aScales( rSource.m_aScales )
+ , m_aMatrixScreenToScene( rSource.m_aMatrixScreenToScene )
+ // m_xTransformationLogicToScene( nullptr ) //should be recalculated
+ , m_bSwapXAndY( rSource.m_bSwapXAndY )
+ , m_nXResolution( rSource.m_nXResolution )
+ , m_nYResolution( rSource.m_nYResolution )
+ , m_nZResolution( rSource.m_nZResolution )
+ , m_bMaySkipPointsInRegressionCalculation( rSource.m_bMaySkipPointsInRegressionCalculation )
+ , m_bDateAxis( rSource.m_bDateAxis )
+ , m_nTimeResolution( rSource.m_nTimeResolution )
+ , m_aNullDate( rSource.m_aNullDate )
+ , m_fScaledCategoryWidth( rSource.m_fScaledCategoryWidth )
+ , m_bAllowShiftXAxisPos( rSource.m_bAllowShiftXAxisPos )
+ , m_bAllowShiftZAxisPos( rSource.m_bAllowShiftZAxisPos )
+{
+}
+
+PlottingPositionHelper::~PlottingPositionHelper()
+{
+
+}
+
+std::unique_ptr<PlottingPositionHelper> PlottingPositionHelper::clone() const
+{
+ return std::make_unique<PlottingPositionHelper>(*this);
+}
+
+std::unique_ptr<PlottingPositionHelper> PlottingPositionHelper::createSecondaryPosHelper( const ExplicitScaleData& rSecondaryScale )
+{
+ auto pRet = clone();
+ pRet->m_aScales[1]=rSecondaryScale;
+ return pRet;
+}
+
+void PlottingPositionHelper::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix)
+{
+ m_aMatrixScreenToScene = HomogenMatrixToB3DHomMatrix(rMatrix);
+ m_xTransformationLogicToScene = nullptr;
+}
+
+void PlottingPositionHelper::setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis )
+{
+ m_aScales = std::move(rScales);
+ m_bSwapXAndY = bSwapXAndYAxis;
+ m_xTransformationLogicToScene = nullptr;
+}
+
+::chart::XTransformation2* PlottingPositionHelper::getTransformationScaledLogicToScene() const
+{
+ //this is a standard transformation for a cartesian coordinate system
+
+ //transformation from 2) to 4) //@todo 2) and 4) need an ink to a document
+
+ //we need to apply this transformation to each geometric object because of a bug/problem
+ //of the old drawing layer (the UNO_NAME_3D_EXTRUDE_DEPTH is an integer value instead of a double )
+ if(!m_xTransformationLogicToScene)
+ {
+ ::basegfx::B3DHomMatrix aMatrix;
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ double MinZ = getLogicMinZ();
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ double MaxZ = getLogicMaxZ();
+
+ AxisOrientation nXAxisOrientation = m_aScales[0].Orientation;
+ AxisOrientation nYAxisOrientation = m_aScales[1].Orientation;
+ AxisOrientation nZAxisOrientation = m_aScales[2].Orientation;
+
+ //apply scaling
+ doUnshiftedLogicScaling( &MinX, &MinY, &MinZ );
+ doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ);
+
+ if(m_bSwapXAndY)
+ {
+ std::swap(MinX,MinY);
+ std::swap(MaxX,MaxY);
+ std::swap(nXAxisOrientation,nYAxisOrientation);
+ }
+
+ double fWidthX = MaxX - MinX;
+ double fWidthY = MaxY - MinY;
+ double fWidthZ = MaxZ - MinZ;
+
+ double fScaleDirectionX = nXAxisOrientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0;
+ double fScaleDirectionY = nYAxisOrientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0;
+ double fScaleDirectionZ = nZAxisOrientation==AxisOrientation_MATHEMATICAL ? -1.0 : 1.0;
+
+ double fScaleX = fScaleDirectionX*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthX;
+ double fScaleY = fScaleDirectionY*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthY;
+ double fScaleZ = fScaleDirectionZ*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthZ;
+
+ aMatrix.scale(fScaleX, fScaleY, fScaleZ);
+
+ if( nXAxisOrientation==AxisOrientation_MATHEMATICAL )
+ aMatrix.translate(-MinX*fScaleX, 0.0, 0.0);
+ else
+ aMatrix.translate(-MaxX*fScaleX, 0.0, 0.0);
+ if( nYAxisOrientation==AxisOrientation_MATHEMATICAL )
+ aMatrix.translate(0.0, -MinY*fScaleY, 0.0);
+ else
+ aMatrix.translate(0.0, -MaxY*fScaleY, 0.0);
+ if( nZAxisOrientation==AxisOrientation_MATHEMATICAL )
+ aMatrix.translate(0.0, 0.0, -MaxZ*fScaleZ);//z direction in draw is reverse mathematical direction
+ else
+ aMatrix.translate(0.0, 0.0, -MinZ*fScaleZ);
+
+ aMatrix = m_aMatrixScreenToScene*aMatrix;
+
+ m_xTransformationLogicToScene.reset(new Linear3DTransformation(B3DHomMatrixToHomogenMatrix( aMatrix ), m_bSwapXAndY));
+ }
+ return m_xTransformationLogicToScene.get();
+}
+
+drawing::Position3D PlottingPositionHelper::transformLogicToScene(
+ double fX, double fY, double fZ, bool bClip ) const
+{
+ doLogicScaling( &fX,&fY,&fZ );
+ if(bClip)
+ clipScaledLogicValues( &fX,&fY,&fZ );
+
+ return transformScaledLogicToScene( fX, fY, fZ, false );
+}
+
+drawing::Position3D PlottingPositionHelper::transformScaledLogicToScene(
+ double fX, double fY, double fZ, bool bClip ) const
+{
+ if( bClip )
+ clipScaledLogicValues( &fX,&fY,&fZ );
+
+ drawing::Position3D aPos( fX, fY, fZ);
+
+ ::chart::XTransformation2* pTransformation =
+ getTransformationScaledLogicToScene();
+ return pTransformation->transform( aPos );
+}
+
+awt::Point PlottingPositionHelper::transformSceneToScreenPosition( const drawing::Position3D& rScenePosition3D
+ , const rtl::Reference<SvxShapeGroupAnyD>& xSceneTarget
+ , sal_Int32 nDimensionCount )
+{
+ //@todo would like to have a cheaper method to do this transformation
+ awt::Point aScreenPoint( static_cast<sal_Int32>(rScenePosition3D.PositionX), static_cast<sal_Int32>(rScenePosition3D.PositionY) );
+
+ //transformation from scene to screen (only necessary for 3D):
+ if(nDimensionCount==3)
+ {
+ //create 3D anchor shape
+ tPropertyNameMap aDummyPropertyNameMap;
+ rtl::Reference<Svx3DExtrudeObject> xShape3DAnchor = ShapeFactory::createCube( xSceneTarget
+ , rScenePosition3D,drawing::Direction3D(1,1,1)
+ , 0, nullptr, aDummyPropertyNameMap);
+ //get 2D position from xShape3DAnchor
+ aScreenPoint = xShape3DAnchor->getPosition();
+ xSceneTarget->remove(xShape3DAnchor);
+ }
+ return aScreenPoint;
+}
+
+void PlottingPositionHelper::transformScaledLogicToScene( drawing::PolyPolygonShape3D& rPolygon ) const
+{
+ drawing::Position3D aScenePosition;
+ auto SequenceXRange = asNonConstRange(rPolygon.SequenceX);
+ auto SequenceYRange = asNonConstRange(rPolygon.SequenceY);
+ auto SequenceZRange = asNonConstRange(rPolygon.SequenceZ);
+ for( sal_Int32 nS = rPolygon.SequenceX.getLength(); nS--;)
+ {
+ auto xValuesRange = asNonConstRange(SequenceXRange[nS]);
+ auto yValuesRange = asNonConstRange(SequenceYRange[nS]);
+ auto zValuesRange = asNonConstRange(SequenceZRange[nS]);
+ for( sal_Int32 nP = SequenceXRange[nS].getLength(); nP--; )
+ {
+ double& fX = xValuesRange[nP];
+ double& fY = yValuesRange[nP];
+ double& fZ = zValuesRange[nP];
+ aScenePosition = transformScaledLogicToScene( fX,fY,fZ,true );
+ fX = aScenePosition.PositionX;
+ fY = aScenePosition.PositionY;
+ fZ = aScenePosition.PositionZ;
+ }
+ }
+}
+
+void PlottingPositionHelper::transformScaledLogicToScene( std::vector<std::vector<css::drawing::Position3D>>& rPolygon ) const
+{
+ drawing::Position3D aScenePosition;
+ for( sal_Int32 nS = static_cast<sal_Int32>(rPolygon.size()); nS--;)
+ {
+ auto valuesRange = rPolygon[nS].data();
+ for( sal_Int32 nP = rPolygon[nS].size(); nP--; )
+ {
+ double& fX = valuesRange[nP].PositionX;
+ double& fY = valuesRange[nP].PositionY;
+ double& fZ = valuesRange[nP].PositionZ;
+ aScenePosition = transformScaledLogicToScene( fX,fY,fZ,true );
+ fX = aScenePosition.PositionX;
+ fY = aScenePosition.PositionY;
+ fZ = aScenePosition.PositionZ;
+ }
+ }
+}
+
+void PlottingPositionHelper::clipScaledLogicValues( double* pX, double* pY, double* pZ ) const
+{
+ //get logic clip values:
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ double MinZ = getLogicMinZ();
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ double MaxZ = getLogicMaxZ();
+
+ //apply scaling
+ doUnshiftedLogicScaling( &MinX, &MinY, &MinZ );
+ doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ);
+
+ if(pX)
+ {
+ if( *pX < MinX )
+ *pX = MinX;
+ else if( *pX > MaxX )
+ *pX = MaxX;
+ }
+ if(pY)
+ {
+ if( *pY < MinY )
+ *pY = MinY;
+ else if( *pY > MaxY )
+ *pY = MaxY;
+ }
+ if(pZ)
+ {
+ if( *pZ < MinZ )
+ *pZ = MinZ;
+ else if( *pZ > MaxZ )
+ *pZ = MaxZ;
+ }
+}
+
+basegfx::B2DRectangle PlottingPositionHelper::getScaledLogicClipDoubleRect() const
+{
+ //get logic clip values:
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ double MinZ = getLogicMinZ();
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ double MaxZ = getLogicMaxZ();
+
+ //apply scaling
+ doUnshiftedLogicScaling( &MinX, &MinY, &MinZ );
+ doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ);
+
+ basegfx::B2DRectangle aRet( MinX, MaxY, MaxX, MinY );
+ return aRet;
+}
+
+drawing::Direction3D PlottingPositionHelper::getScaledLogicWidth() const
+{
+ drawing::Direction3D aRet;
+
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ double MinZ = getLogicMinZ();
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ double MaxZ = getLogicMaxZ();
+
+ doLogicScaling( &MinX, &MinY, &MinZ );
+ doLogicScaling( &MaxX, &MaxY, &MaxZ);
+
+ aRet.DirectionX = MaxX - MinX;
+ aRet.DirectionY = MaxY - MinY;
+ aRet.DirectionZ = MaxZ - MinZ;
+ return aRet;
+}
+
+PolarPlottingPositionHelper::PolarPlottingPositionHelper()
+ : m_fRadiusOffset(0.0)
+ , m_fAngleDegreeOffset(90.0)
+{
+ m_bMaySkipPointsInRegressionCalculation = false;
+}
+
+PolarPlottingPositionHelper::PolarPlottingPositionHelper( const PolarPlottingPositionHelper& rSource )
+ : PlottingPositionHelper(rSource)
+ , m_fRadiusOffset( rSource.m_fRadiusOffset )
+ , m_fAngleDegreeOffset( rSource.m_fAngleDegreeOffset )
+ , m_aUnitCartesianToScene( rSource.m_aUnitCartesianToScene )
+{
+}
+
+PolarPlottingPositionHelper::~PolarPlottingPositionHelper()
+{
+}
+
+std::unique_ptr<PlottingPositionHelper> PolarPlottingPositionHelper::clone() const
+{
+ return std::make_unique<PolarPlottingPositionHelper>(*this);
+}
+
+void PolarPlottingPositionHelper::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix)
+{
+ PlottingPositionHelper::setTransformationSceneToScreen( rMatrix);
+ m_aUnitCartesianToScene =impl_calculateMatrixUnitCartesianToScene( m_aMatrixScreenToScene );
+}
+void PolarPlottingPositionHelper::setScales( std::vector< ExplicitScaleData >&& rScales, bool bSwapXAndYAxis )
+{
+ PlottingPositionHelper::setScales( std::move(rScales), bSwapXAndYAxis );
+ m_aUnitCartesianToScene =impl_calculateMatrixUnitCartesianToScene( m_aMatrixScreenToScene );
+}
+
+::basegfx::B3DHomMatrix PolarPlottingPositionHelper::impl_calculateMatrixUnitCartesianToScene( const ::basegfx::B3DHomMatrix& rMatrixScreenToScene ) const
+{
+ ::basegfx::B3DHomMatrix aRet;
+
+ if( m_aScales.empty() )
+ return aRet;
+
+ double fTranslate =1.0;
+ double fScale =FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0;
+
+ double fTranslateLogicZ;
+ double fScaleLogicZ;
+ {
+ double fScaleDirectionZ = m_aScales[2].Orientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0;
+ double MinZ = getLogicMinZ();
+ double MaxZ = getLogicMaxZ();
+ doLogicScaling( nullptr, nullptr, &MinZ );
+ doLogicScaling( nullptr, nullptr, &MaxZ );
+ double fWidthZ = MaxZ - MinZ;
+
+ if( m_aScales[2].Orientation==AxisOrientation_MATHEMATICAL )
+ fTranslateLogicZ=MinZ;
+ else
+ fTranslateLogicZ=MaxZ;
+ fScaleLogicZ = fScaleDirectionZ*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthZ;
+ }
+
+ double fTranslateX = fTranslate;
+ double fTranslateY = fTranslate;
+ double fTranslateZ = fTranslateLogicZ;
+
+ double fScaleX = fScale;
+ double fScaleY = fScale;
+ double fScaleZ = fScaleLogicZ;
+
+ aRet.translate(fTranslateX, fTranslateY, fTranslateZ);//x first
+ aRet.scale(fScaleX, fScaleY, fScaleZ);//x first
+
+ aRet = rMatrixScreenToScene * aRet;
+ return aRet;
+}
+
+::chart::XTransformation2* PolarPlottingPositionHelper::getTransformationScaledLogicToScene() const
+{
+ if( !m_xTransformationLogicToScene )
+ m_xTransformationLogicToScene.reset(new VPolarTransformation(*this));
+ return m_xTransformationLogicToScene.get();
+}
+
+double PolarPlottingPositionHelper::getWidthAngleDegree( double& fStartLogicValueOnAngleAxis, double& fEndLogicValueOnAngleAxis ) const
+{
+ const ExplicitScaleData& rAngleScale = m_bSwapXAndY ? m_aScales[1] : m_aScales[0];
+ if( rAngleScale.Orientation != AxisOrientation_MATHEMATICAL )
+ {
+ double fHelp = fEndLogicValueOnAngleAxis;
+ fEndLogicValueOnAngleAxis = fStartLogicValueOnAngleAxis;
+ fStartLogicValueOnAngleAxis = fHelp;
+ }
+
+ double fStartAngleDegree = transformToAngleDegree( fStartLogicValueOnAngleAxis );
+ double fEndAngleDegree = transformToAngleDegree( fEndLogicValueOnAngleAxis );
+ double fWidthAngleDegree = fEndAngleDegree - fStartAngleDegree;
+
+ if( ::rtl::math::approxEqual( fStartAngleDegree, fEndAngleDegree )
+ && !::rtl::math::approxEqual( fStartLogicValueOnAngleAxis, fEndLogicValueOnAngleAxis ) )
+ fWidthAngleDegree = 360.0;
+
+ // tdf#123504: both 0 and 360 are valid and different values here!
+ while (fWidthAngleDegree < 0.0)
+ fWidthAngleDegree += 360.0;
+ while (fWidthAngleDegree > 360.0)
+ fWidthAngleDegree -= 360.0;
+
+ return fWidthAngleDegree;
+}
+
+//This method does a lot of computation for understanding which scale to
+//utilize and if reverse orientation should be used. Indeed, for a pie or donut,
+//the final result is as simple as multiplying by 360 and adding
+//`m_fAngleDegreeOffset`.
+double PolarPlottingPositionHelper::transformToAngleDegree( double fLogicValueOnAngleAxis, bool bDoScaling ) const
+{
+ double fRet=0.0;
+
+ double fAxisAngleScaleDirection = 1.0;
+ {
+ const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[1] : m_aScales[0];
+ if(rScale.Orientation != AxisOrientation_MATHEMATICAL)
+ fAxisAngleScaleDirection *= -1.0;
+ }
+
+ double MinAngleValue = 0.0;
+ double MaxAngleValue = 0.0;
+ {
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ double MinZ = getLogicMinZ();
+ double MaxZ = getLogicMaxZ();
+
+ doLogicScaling( &MinX, &MinY, &MinZ );
+ doLogicScaling( &MaxX, &MaxY, &MaxZ);
+
+ MinAngleValue = m_bSwapXAndY ? MinY : MinX;
+ MaxAngleValue = m_bSwapXAndY ? MaxY : MaxX;
+ }
+
+ double fScaledLogicAngleValue = 0.0;
+ if(bDoScaling)
+ {
+ double fX = m_bSwapXAndY ? getLogicMaxX() : fLogicValueOnAngleAxis;
+ double fY = m_bSwapXAndY ? fLogicValueOnAngleAxis : getLogicMaxY();
+ double fZ = getLogicMaxZ();
+ clipLogicValues( &fX, &fY, &fZ );
+ doLogicScaling( &fX, &fY, &fZ );
+ fScaledLogicAngleValue = m_bSwapXAndY ? fY : fX;
+ }
+ else
+ fScaledLogicAngleValue = fLogicValueOnAngleAxis;
+
+ fRet = m_fAngleDegreeOffset
+ + fAxisAngleScaleDirection*(fScaledLogicAngleValue-MinAngleValue)*360.0
+ /fabs(MaxAngleValue-MinAngleValue);
+ // tdf#123504: both 0 and 360 are valid and different values here!
+ while (fRet > 360.0)
+ fRet -= 360.0;
+ while (fRet < 0)
+ fRet += 360.0;
+ return fRet;
+}
+
+/**
+ * Given a value in the radius axis scale range, it returns, in the simplest
+ * case (that is when `m_fRadiusOffset` is zero), the normalized value; when
+ * `m_fRadiusOffset` is not zero (e.g. as in the case of a donut), the interval
+ * used for normalization is extended by `m_fRadiusOffset`: if the axis
+ * orientation is not reversed the new interval becomes
+ * [scale.Minimum - m_fRadiusOffset, scale.Maximum] else it becomes
+ * [scale.Minimum, scale.Maximum + m_fRadiusOffset].
+ * Pay attention here! For the latter case, since the axis orientation is
+ * reversed, the normalization is reversed too. Indeed, we have
+ * `transformToRadius(scale.Maximum + m_fRadiusOffset) = 0` and
+ * `transformToRadius(scale.Minimum) = 1`.
+ *
+ * For a pie chart the radius axis scale range is initialized by the
+ * `getMinimum` and `getMaximum` methods of the `PieChart` object (see notes
+ * for `VCoordinateSystem::prepareAutomaticAxisScaling`).
+ * So we have scale.Minimum = 0.5 (always constant!) and
+ * scale.Maximum = 0.5 + number_of_rings + max_offset
+ * (see notes for `PieChart::getMaxOffset`).
+ * Hence we get the following general formulas for computing normalized inner
+ * and outer radius:
+ *
+ * 1- transformToRadius(inner_radius) =
+ * (number_of_rings - (ring_index + 1) + m_fRadiusOffset)
+ * / (number_of_rings + max_offset + m_fRadiusOffset)
+ *
+ * 2- transformToRadius(outer_radius) =
+ * (1 + number_of_rings - (ring_index + 1) + m_fRadiusOffset)
+ * / (number_of_rings + max_offset + m_fRadiusOffset).
+ *
+ * Here you have to take into account that values for inner and outer radius
+ * are swapped since the radius axis is reversed (See notes for
+ * `PiePositionHelper::getInnerAndOuterRadius`). So indeed inner_radius is
+ * the outer and outer_radius is the inner. Anyway still because of the reverse
+ * orientation, the normalization performed by `transformToRadius` is reversed
+ * too, as we have seen above. Hence `transformToRadius(inner_radius)` is
+ * really the normalized inner radius and `transformToRadius(outer_radius)` is
+ * really the normalized outer radius.
+ *
+ * Some basic examples where we apply the above formulas:
+ * 1- For a non-exploded pie chart we have:
+ * `transformToRadius(inner_radius) = 0`,
+ * `transformToRadius(outer_radius) = 1`.
+ * 2- For a non-exploded donut with a single ring we have:
+ * `transformToRadius(inner_radius) =
+ * m_fRadiusOffset/(1 + m_fRadiusOffset)`,
+ * `transformToRadius(outer_radius) =
+ * (1 + m_fRadiusOffset)/(1 + m_fRadiusOffset) = 1`.
+ * 3- For an exploded pie chart we have:
+ * `transformToRadius(inner_radius) = 0/(1 + max_offset) = 0`,
+ * `transformToRadius(outer_radius) = 1/(1 + max_offset)`.
+ *
+ * The third example needs some remark. Both the logical inner and outer
+ * radius passed to `transformToRadius` are offset by `max_offset`.
+ * However the returned normalized values do not contain any (normalized)
+ * offset term at all, otherwise the returned values would be
+ * `max_offset/(1 + max_offset)` and `1`. Hence, for exploded pie/donut,
+ * `transformToRadius` returns the normalized value of radii without any
+ * offset term. These values are smaller than in the non-exploded case by an
+ * amount equals to the value of the normalized maximum offset
+ * (`max_offset/(1 + max_offset)` in the example above). That is due to the
+ * fact that the normalization keeps into account the space needed for the
+ * offset. This is the correct behavior, in fact the offset for the current
+ * slice could be different from the maximum offset.
+ * These remarks should clarify why the `PieChart::createDataPoint` and
+ * `PieChart::createTextLabelShape` methods add the normalized offset (for the
+ * current slice) to the normalized radii in order to achieve the correct
+ * placement of slice and text shapes.
+ */
+double PolarPlottingPositionHelper::transformToRadius( double fLogicValueOnRadiusAxis, bool bDoScaling ) const
+{
+ double fNormalRadius = 0.0;
+ {
+ double fScaledLogicRadiusValue = 0.0;
+ double fX = m_bSwapXAndY ? fLogicValueOnRadiusAxis: getLogicMaxX();
+ double fY = m_bSwapXAndY ? getLogicMaxY() : fLogicValueOnRadiusAxis;
+ if(bDoScaling)
+ doLogicScaling( &fX, &fY, nullptr );
+
+ fScaledLogicRadiusValue = m_bSwapXAndY ? fX : fY;
+
+ bool bMinIsInnerRadius = true;
+ const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[0] : m_aScales[1];
+ if(rScale.Orientation != AxisOrientation_MATHEMATICAL)
+ bMinIsInnerRadius = false;
+
+ double fInnerScaledLogicRadius=0.0;
+ double fOuterScaledLogicRadius=0.0;
+ {
+ double MinX = getLogicMinX();
+ double MinY = getLogicMinY();
+ doLogicScaling( &MinX, &MinY, nullptr );
+ double MaxX = getLogicMaxX();
+ double MaxY = getLogicMaxY();
+ doLogicScaling( &MaxX, &MaxY, nullptr );
+
+ double fMin = m_bSwapXAndY ? MinX : MinY;
+ double fMax = m_bSwapXAndY ? MaxX : MaxY;
+
+ fInnerScaledLogicRadius = bMinIsInnerRadius ? fMin : fMax;
+ fOuterScaledLogicRadius = bMinIsInnerRadius ? fMax : fMin;
+ }
+
+ if( bMinIsInnerRadius )
+ fInnerScaledLogicRadius -= fabs(m_fRadiusOffset);
+ else
+ fInnerScaledLogicRadius += fabs(m_fRadiusOffset);
+ fNormalRadius = (fScaledLogicRadiusValue-fInnerScaledLogicRadius)/(fOuterScaledLogicRadius-fInnerScaledLogicRadius);
+ }
+ return fNormalRadius;
+}
+
+drawing::Position3D PolarPlottingPositionHelper::transformLogicToScene( double fX, double fY, double fZ, bool bClip ) const
+{
+ if(bClip)
+ clipLogicValues( &fX,&fY,&fZ );
+ double fLogicValueOnAngleAxis = m_bSwapXAndY ? fY : fX;
+ double fLogicValueOnRadiusAxis = m_bSwapXAndY ? fX : fY;
+ return transformAngleRadiusToScene( fLogicValueOnAngleAxis, fLogicValueOnRadiusAxis, fZ );
+}
+
+drawing::Position3D PolarPlottingPositionHelper::transformScaledLogicToScene( double fX, double fY, double fZ, bool bClip ) const
+{
+ if(bClip)
+ clipScaledLogicValues( &fX,&fY,&fZ );
+ double fLogicValueOnAngleAxis = m_bSwapXAndY ? fY : fX;
+ double fLogicValueOnRadiusAxis = m_bSwapXAndY ? fX : fY;
+ return transformAngleRadiusToScene( fLogicValueOnAngleAxis, fLogicValueOnRadiusAxis, fZ, false );
+}
+drawing::Position3D PolarPlottingPositionHelper::transformUnitCircleToScene( double fUnitAngleDegree, double fUnitRadius
+ , double fLogicZ ) const
+{
+ double fAnglePi = basegfx::deg2rad(fUnitAngleDegree);
+
+ double fX=fUnitRadius*std::cos(fAnglePi);
+ double fY=fUnitRadius*std::sin(fAnglePi);
+ double fZ=fLogicZ;
+
+ //!! applying matrix to vector does ignore translation, so it is important to use a B3DPoint here instead of B3DVector
+ ::basegfx::B3DPoint aPoint(fX,fY,fZ);
+ ::basegfx::B3DPoint aRet = m_aUnitCartesianToScene * aPoint;
+ return B3DPointToPosition3D(aRet);
+}
+
+drawing::Position3D PolarPlottingPositionHelper::transformAngleRadiusToScene( double fLogicValueOnAngleAxis, double fLogicValueOnRadiusAxis, double fLogicZ, bool bDoScaling ) const
+{
+ double fUnitAngleDegree = transformToAngleDegree(fLogicValueOnAngleAxis,bDoScaling);
+ double fUnitRadius = transformToRadius(fLogicValueOnRadiusAxis,bDoScaling);
+
+ return transformUnitCircleToScene( fUnitAngleDegree, fUnitRadius, fLogicZ );
+}
+
+double PolarPlottingPositionHelper::getOuterLogicRadius() const
+{
+ const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[0] : m_aScales[1];
+ if( rScale.Orientation==AxisOrientation_MATHEMATICAL )
+ return rScale.Maximum;
+ else
+ return rScale.Minimum;
+}
+
+bool PlottingPositionHelper::isPercentY() const
+{
+ return m_aScales[1].AxisType==AxisType::PERCENT;
+}
+
+double PlottingPositionHelper::getBaseValueY() const
+{
+ return m_aScales[1].Origin;
+}
+
+void PlottingPositionHelper::setTimeResolution( tools::Long nTimeResolution, const Date& rNullDate )
+{
+ m_nTimeResolution = nTimeResolution;
+ m_aNullDate = rNullDate;
+
+ //adapt category width
+ double fCategoryWidth = 1.0;
+ if( !m_aScales.empty() )
+ {
+ if( m_aScales[0].AxisType == css::chart2::AxisType::DATE )
+ {
+ m_bDateAxis = true;
+ if( nTimeResolution == css::chart::TimeUnit::YEAR )
+ {
+ const double fMonthCount = 12.0;//todo: this depends on the DateScaling and must be adjusted in case we use more generic calendars in future
+ fCategoryWidth = fMonthCount;
+ }
+ }
+ }
+ setScaledCategoryWidth(fCategoryWidth);
+}
+
+void PlottingPositionHelper::setScaledCategoryWidth( double fScaledCategoryWidth )
+{
+ m_fScaledCategoryWidth = fScaledCategoryWidth;
+}
+void PlottingPositionHelper::AllowShiftXAxisPos( bool bAllowShift )
+{
+ m_bAllowShiftXAxisPos = bAllowShift;
+}
+void PlottingPositionHelper::AllowShiftZAxisPos( bool bAllowShift )
+{
+ m_bAllowShiftZAxisPos = bAllowShift;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/PolarLabelPositionHelper.cxx b/chart2/source/view/main/PolarLabelPositionHelper.cxx
new file mode 100644
index 000000000..d5c819954
--- /dev/null
+++ b/chart2/source/view/main/PolarLabelPositionHelper.cxx
@@ -0,0 +1,158 @@
+/* -*- 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 <PolarLabelPositionHelper.hxx>
+#include <PlottingPositionHelper.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+PolarLabelPositionHelper::PolarLabelPositionHelper(
+ PolarPlottingPositionHelper* pPosHelper
+ , sal_Int32 nDimensionCount
+ , const rtl::Reference<SvxShapeGroupAnyD>& xLogicTarget )
+ : LabelPositionHelper( nDimensionCount, xLogicTarget )
+ , m_pPosHelper(pPosHelper)
+{
+}
+
+PolarLabelPositionHelper::~PolarLabelPositionHelper()
+{
+}
+
+awt::Point PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForLogicValues(
+ LabelAlignment& rAlignment
+ , double fLogicValueOnAngleAxis
+ , double fLogicValueOnRadiusAxis
+ , double fLogicZ
+ , sal_Int32 nScreenValueOffsetInRadiusDirection ) const
+{
+ double fUnitCircleAngleDegree = m_pPosHelper->transformToAngleDegree( fLogicValueOnAngleAxis );
+ double fUnitCircleRadius = m_pPosHelper->transformToRadius( fLogicValueOnRadiusAxis );
+
+ return getLabelScreenPositionAndAlignmentForUnitCircleValues(
+ rAlignment, css::chart::DataLabelPlacement::OUTSIDE
+ , fUnitCircleAngleDegree, 0.0
+ , fUnitCircleRadius, fUnitCircleRadius, fLogicZ, nScreenValueOffsetInRadiusDirection );
+}
+
+awt::Point PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForUnitCircleValues(
+ LabelAlignment& rAlignment, sal_Int32 nLabelPlacement
+ , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree
+ , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius
+ , double fLogicZ
+ , sal_Int32 nScreenValueOffsetInRadiusDirection ) const
+{
+ bool bCenter = (nLabelPlacement != css::chart::DataLabelPlacement::OUTSIDE)
+ && (nLabelPlacement != css::chart::DataLabelPlacement::INSIDE);
+
+ double fAngleDegree = fUnitCircleStartAngleDegree + fUnitCircleWidthAngleDegree/2.0;
+ double fRadius = 0.0;
+ if( !bCenter ) //e.g. for pure pie chart(one ring only) or for angle axis of polar coordinate system
+ fRadius = fUnitCircleOuterRadius;
+ else
+ fRadius = fUnitCircleInnerRadius + (fUnitCircleOuterRadius-fUnitCircleInnerRadius)/2.0 ;
+
+ awt::Point aRet( transformSceneToScreenPosition(
+ m_pPosHelper->transformUnitCircleToScene( fAngleDegree, fRadius, fLogicZ+0.5 ) ) );
+
+ if(m_nDimensionCount==3 && nLabelPlacement == css::chart::DataLabelPlacement::OUTSIDE)
+ {
+ //check whether the upper or the downer edge is more distant from the center
+ //take the farthest point to put the label to
+
+ awt::Point aP0( transformSceneToScreenPosition(
+ m_pPosHelper->transformUnitCircleToScene( 0, 0, fLogicZ ) ) );
+ awt::Point aP1(aRet);
+ awt::Point aP2( transformSceneToScreenPosition(
+ m_pPosHelper->transformUnitCircleToScene( fAngleDegree, fRadius, fLogicZ-0.5 ) ) );
+
+ ::basegfx::B2DVector aV0( aP0.X, aP0.Y );
+ ::basegfx::B2DVector aV1( aP1.X, aP1.Y );
+ ::basegfx::B2DVector aV2( aP2.X, aP2.Y );
+
+ double fL1 = ::basegfx::B2DVector(aV1-aV0).getLength();
+ double fL2 = ::basegfx::B2DVector(aV2-aV0).getLength();
+
+ if(fL2>fL1)
+ aRet = aP2;
+
+ //calculate new angle for alignment
+ double fDX = aRet.X-aP0.X;
+ double fDY = aRet.Y-aP0.Y;
+ fDY*=-1.0;//drawing layer has inverse y values
+
+ fAngleDegree = basegfx::rad2deg(atan2(fDY,fDX));
+ }
+ //set LabelAlignment
+ if( !bCenter )
+ {
+ // tdf#123504: both 0 and 360 are valid and different values here!
+ while (fAngleDegree > 360.0)
+ fAngleDegree -= 360.0;
+ while (fAngleDegree < 0.0)
+ fAngleDegree += 360.0;
+
+ bool bOutside = nLabelPlacement == css::chart::DataLabelPlacement::OUTSIDE;
+
+ if (fAngleDegree <= 5 || fAngleDegree >= 355)
+ rAlignment = bOutside ? LABEL_ALIGN_RIGHT : LABEL_ALIGN_LEFT;
+ else if (fAngleDegree < 85)
+ rAlignment = bOutside ? LABEL_ALIGN_RIGHT_TOP : LABEL_ALIGN_LEFT_BOTTOM;
+ else if (fAngleDegree <= 95)
+ rAlignment = bOutside ? LABEL_ALIGN_TOP : LABEL_ALIGN_BOTTOM;
+ else if (fAngleDegree < 175)
+ rAlignment = bOutside ? LABEL_ALIGN_LEFT_TOP : LABEL_ALIGN_RIGHT_BOTTOM;
+ else if (fAngleDegree <= 185)
+ rAlignment = bOutside ? LABEL_ALIGN_LEFT : LABEL_ALIGN_RIGHT;
+ else if (fAngleDegree < 265)
+ rAlignment = bOutside ? LABEL_ALIGN_LEFT_BOTTOM : LABEL_ALIGN_RIGHT_TOP;
+ else if (fAngleDegree <= 275)
+ rAlignment = bOutside ? LABEL_ALIGN_BOTTOM : LABEL_ALIGN_TOP;
+ else
+ rAlignment = bOutside ? LABEL_ALIGN_RIGHT_BOTTOM : LABEL_ALIGN_LEFT_TOP;
+ }
+ else
+ {
+ rAlignment = LABEL_ALIGN_CENTER;
+ }
+
+ //add a scaling independent Offset if requested
+ if( nScreenValueOffsetInRadiusDirection != 0)
+ {
+ awt::Point aOrigin( transformSceneToScreenPosition(
+ m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, fLogicZ+0.5 ) ) );
+ basegfx::B2IVector aDirection( aRet.X- aOrigin.X, aRet.Y- aOrigin.Y );
+ aDirection.setLength(nScreenValueOffsetInRadiusDirection);
+ aRet.X += aDirection.getX();
+ aRet.Y += aDirection.getY();
+ }
+
+ return aRet;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/PropertyMapper.cxx b/chart2/source/view/main/PropertyMapper.cxx
new file mode 100644
index 000000000..0ee929bc7
--- /dev/null
+++ b/chart2/source/view/main/PropertyMapper.cxx
@@ -0,0 +1,561 @@
+/* -*- 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 <PropertyMapper.hxx>
+#include <unonames.hxx>
+
+#include <com/sun/star/beans/XMultiPropertySet.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
+#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
+#include <com/sun/star/drawing/LineJoint.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+#include <tools/diagnose_ex.h>
+#include <svx/unoshape.hxx>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+
+void PropertyMapper::setMappedProperties(
+ SvxShape& xTarget
+ , const uno::Reference< beans::XPropertySet >& xSource
+ , const tPropertyNameMap& rMap )
+{
+ if( !xSource.is() )
+ return;
+
+ sal_Int32 nPropertyCount = rMap.size();
+ tNameSequence aNames(nPropertyCount);
+ tAnySequence aValues(nPropertyCount);
+ auto pNames = aNames.getArray();
+ auto pValues = aValues.getArray();
+ sal_Int32 nN=0;
+
+ for (auto const& elem : rMap)
+ {
+ const OUString & rTarget = elem.first;
+ const OUString & rSource = elem.second;
+ try
+ {
+ uno::Any aAny( xSource->getPropertyValue(rSource) );
+ if( aAny.hasValue() )
+ {
+ //do not set empty anys because of performance (otherwise SdrAttrObj::ItemChange will take much longer)
+ pNames[nN] = rTarget;
+ pValues[nN] = std::move(aAny);
+ ++nN;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }
+ if (nN == 0)
+ return;
+ //reduce to real property count
+ aNames.realloc(nN);
+ aValues.realloc(nN);
+
+ uno::Reference< beans::XMultiPropertySet > xShapeMultiProp( xTarget, uno::UNO_QUERY_THROW );
+ try
+ {
+ xShapeMultiProp->setPropertyValues( aNames, aValues );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" ); //if this occurs more often think of removing the XMultiPropertySet completely for better performance
+ }
+}
+
+void PropertyMapper::setMappedProperties(
+ const uno::Reference< beans::XPropertySet >& xTarget
+ , const uno::Reference< beans::XPropertySet >& xSource
+ , const tPropertyNameMap& rMap )
+{
+ if( !xTarget.is() || !xSource.is() )
+ return;
+
+ tNameSequence aNames;
+ tAnySequence aValues;
+ sal_Int32 nN=0;
+ sal_Int32 nPropertyCount = rMap.size();
+ aNames.realloc(nPropertyCount);
+ auto pNames = aNames.getArray();
+ aValues.realloc(nPropertyCount);
+ auto pValues = aValues.getArray();
+
+ for (auto const& elem : rMap)
+ {
+ const OUString & rTarget = elem.first;
+ const OUString & rSource = elem.second;
+ try
+ {
+ uno::Any aAny( xSource->getPropertyValue(rSource) );
+ if( aAny.hasValue() )
+ {
+ //do not set empty anys because of performance (otherwise SdrAttrObj::ItemChange will take much longer)
+ pNames[nN] = rTarget;
+ pValues[nN] = aAny;
+ ++nN;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }
+ if (nN == 0)
+ return;
+
+ uno::Reference< beans::XMultiPropertySet > xShapeMultiProp( xTarget, uno::UNO_QUERY );
+ if (xShapeMultiProp)
+ try
+ {
+ //reduce to real property count
+ aNames.realloc(nN);
+ aValues.realloc(nN);
+ xShapeMultiProp->setPropertyValues( aNames, aValues );
+ return; // successful
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" ); //if this occurs more often think of removing the XMultiPropertySet completely for better performance
+ }
+
+ // fall back to one at a time
+ try
+ {
+ for( sal_Int32 i = 0; i < nN; i++ )
+ {
+ try
+ {
+ xTarget->setPropertyValue( aNames[i], aValues[i] );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+void PropertyMapper::getValueMap(
+ tPropertyNameValueMap& rValueMap
+ , const tPropertyNameMap& rNameMap
+ , const uno::Reference< beans::XPropertySet >& xSourceProp
+ )
+{
+ uno::Reference< beans::XMultiPropertySet > xMultiPropSet(xSourceProp, uno::UNO_QUERY);
+ if((false) && xMultiPropSet.is())
+ {
+ uno::Sequence< OUString > aPropSourceNames(rNameMap.size());
+ auto aPropSourceNamesRange = asNonConstRange(aPropSourceNames);
+ uno::Sequence< OUString > aPropTargetNames(rNameMap.size());
+ auto aPropTargetNamesRange = asNonConstRange(aPropTargetNames);
+ sal_Int32 i = 0;
+ for (auto const& elem : rNameMap)
+ {
+ aPropTargetNamesRange[i] = elem.first;
+ aPropSourceNamesRange[i] = elem.second;
+ ++i;
+ }
+
+ uno::Sequence< uno::Any > xValues = xMultiPropSet->getPropertyValues(aPropSourceNames);
+ sal_Int32 n = rNameMap.size();
+ for(i = 0;i < n; ++i)
+ {
+ if( xValues[i].hasValue() )
+ rValueMap.emplace( aPropTargetNames[i], xValues[i] );
+ }
+ }
+ else
+ {
+ for (auto const& elem : rNameMap)
+ {
+ const OUString & rTarget = elem.first;
+ const OUString & rSource = elem.second;
+ try
+ {
+ uno::Any aAny( xSourceProp->getPropertyValue(rSource) );
+ if( aAny.hasValue() )
+ rValueMap.emplace( rTarget, aAny );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }
+ }
+}
+
+void PropertyMapper::getMultiPropertyListsFromValueMap(
+ tNameSequence& rNames
+ , tAnySequence& rValues
+ , const tPropertyNameValueMap& rValueMap
+ )
+{
+ sal_Int32 nPropertyCount = rValueMap.size();
+ rNames.realloc(nPropertyCount);
+ auto pNames = rNames.getArray();
+ rValues.realloc(nPropertyCount);
+ auto pValues = rValues.getArray();
+
+ //fill sequences
+ sal_Int32 nN=0;
+ for (auto const& elem : rValueMap)
+ {
+ const uno::Any& rAny = elem.second;
+ if( rAny.hasValue() )
+ {
+ //do not set empty anys because of performance (otherwise SdrAttrObj::ItemChange will take much longer)
+ pNames[nN] = elem.first;
+ pValues[nN] = rAny;
+ ++nN;
+ }
+ }
+ //reduce to real property count
+ rNames.realloc(nN);
+ rValues.realloc(nN);
+}
+
+uno::Any* PropertyMapper::getValuePointer( tAnySequence& rPropValues
+ , const tNameSequence& rPropNames
+ , std::u16string_view rPropName )
+{
+ sal_Int32 nCount = rPropNames.getLength();
+ for( sal_Int32 nN = 0; nN < nCount; nN++ )
+ {
+ if(rPropNames[nN] == rPropName)
+ return &rPropValues.getArray()[nN];
+ }
+ return nullptr;
+}
+
+uno::Any* PropertyMapper::getValuePointerForLimitedSpace( tAnySequence& rPropValues
+ , const tNameSequence& rPropNames
+ , bool bLimitedHeight)
+{
+ return PropertyMapper::getValuePointer( rPropValues, rPropNames
+ , bLimitedHeight ? OUString("TextMaximumFrameHeight") : OUString("TextMaximumFrameWidth") );
+}
+
+const tPropertyNameMap& PropertyMapper::getPropertyNameMapForCharacterProperties()
+{
+ //shape property -- chart model object property
+ static tPropertyNameMap s_aShapePropertyMapForCharacterProperties{
+ {"CharColor", "CharColor"},
+ {"CharContoured", "CharContoured"},
+ {"CharEmphasis", "CharEmphasis"},//the service style::CharacterProperties describes a property called 'CharEmphasize' which is nowhere implemented
+
+ {"CharFontFamily", "CharFontFamily"},
+ {"CharFontFamilyAsian", "CharFontFamilyAsian"},
+ {"CharFontFamilyComplex", "CharFontFamilyComplex"},
+ {"CharFontCharSet", "CharFontCharSet"},
+ {"CharFontCharSetAsian", "CharFontCharSetAsian"},
+ {"CharFontCharSetComplex", "CharFontCharSetComplex"},
+ {"CharFontName", "CharFontName"},
+ {"CharFontNameAsian", "CharFontNameAsian"},
+ {"CharFontNameComplex", "CharFontNameComplex"},
+ {"CharFontPitch", "CharFontPitch"},
+ {"CharFontPitchAsian", "CharFontPitchAsian"},
+ {"CharFontPitchComplex", "CharFontPitchComplex"},
+ {"CharFontStyleName", "CharFontStyleName"},
+ {"CharFontStyleNameAsian", "CharFontStyleNameAsian"},
+ {"CharFontStyleNameComplex", "CharFontStyleNameComplex"},
+
+ {"CharHeight", "CharHeight"},
+ {"CharHeightAsian", "CharHeightAsian"},
+ {"CharHeightComplex", "CharHeightComplex"},
+ {"CharKerning", "CharKerning"},
+ {"CharLocale", "CharLocale"},
+ {"CharLocaleAsian", "CharLocaleAsian"},
+ {"CharLocaleComplex", "CharLocaleComplex"},
+ {"CharPosture", "CharPosture"},
+ {"CharPostureAsian", "CharPostureAsian"},
+ {"CharPostureComplex", "CharPostureComplex"},
+ {"CharRelief", "CharRelief"},
+ {"CharShadowed", "CharShadowed"},
+ {"CharStrikeout", "CharStrikeout"},
+ {"CharUnderline", "CharUnderline"},
+ {"CharUnderlineColor", "CharUnderlineColor"},
+ {"CharUnderlineHasColor", "CharUnderlineHasColor"},
+ {"CharOverline", "CharOverline"},
+ {"CharOverlineColor", "CharOverlineColor"},
+ {"CharOverlineHasColor", "CharOverlineHasColor"},
+ {"CharWeight", "CharWeight"},
+ {"CharWeightAsian", "CharWeightAsian"},
+ {"CharWeightComplex", "CharWeightComplex"},
+ {"CharWordMode", "CharWordMode"},
+
+ {"WritingMode", "WritingMode"},
+
+ {"ParaIsCharacterDistance", "ParaIsCharacterDistance"}};
+
+ return s_aShapePropertyMapForCharacterProperties;
+}
+
+const tPropertyNameMap& PropertyMapper::getPropertyNameMapForParagraphProperties()
+{
+ //shape property -- chart model object property
+ static tPropertyNameMap s_aShapePropertyMapForParagraphProperties{
+ {"ParaAdjust", "ParaAdjust"},
+ {"ParaBottomMargin", "ParaBottomMargin"},
+ {"ParaIsHyphenation", "ParaIsHyphenation"},
+ {"ParaLastLineAdjust", "ParaLastLineAdjust"},
+ {"ParaLeftMargin", "ParaLeftMargin"},
+ {"ParaRightMargin", "ParaRightMargin"},
+ {"ParaTopMargin", "ParaTopMargin"}};
+ return s_aShapePropertyMapForParagraphProperties;
+}
+
+const tPropertyNameMap& PropertyMapper::getPropertyNameMapForFillProperties()
+{
+ //shape property -- chart model object property
+ static tPropertyNameMap s_aShapePropertyMapForFillProperties{
+ {"FillBackground", "FillBackground"},
+ {"FillBitmapName", "FillBitmapName"},
+ {"FillColor", "FillColor"},
+ {"FillGradientName", "FillGradientName"},
+ {"FillGradientStepCount", "FillGradientStepCount"},
+ {"FillHatchName", "FillHatchName"},
+ {"FillStyle", "FillStyle"},
+ {"FillTransparence", "FillTransparence"},
+ {"FillTransparenceGradientName", "FillTransparenceGradientName"},
+ //bitmap properties
+ {"FillBitmapMode", "FillBitmapMode"},
+ {"FillBitmapSizeX", "FillBitmapSizeX"},
+ {"FillBitmapSizeY", "FillBitmapSizeY"},
+ {"FillBitmapLogicalSize", "FillBitmapLogicalSize"},
+ {"FillBitmapOffsetX", "FillBitmapOffsetX"},
+ {"FillBitmapOffsetY", "FillBitmapOffsetY"},
+ {"FillBitmapRectanglePoint", "FillBitmapRectanglePoint"},
+ {"FillBitmapPositionOffsetX", "FillBitmapPositionOffsetX"},
+ {"FillBitmapPositionOffsetY", "FillBitmapPositionOffsetY"}};
+ return s_aShapePropertyMapForFillProperties;
+}
+
+const tPropertyNameMap& PropertyMapper::getPropertyNameMapForLineProperties()
+{
+ //shape property -- chart model object property
+ static tPropertyNameMap s_aShapePropertyMapForLineProperties{
+ {"LineColor", "LineColor"},
+ {"LineDashName", "LineDashName"},
+ {"LineJoint", "LineJoint"},
+ {"LineStyle", "LineStyle"},
+ {"LineTransparence", "LineTransparence"},
+ {"LineWidth", "LineWidth"},
+ {"LineCap", "LineCap"}};
+ return s_aShapePropertyMapForLineProperties;
+}
+
+namespace {
+ tPropertyNameMap getPropertyNameMapForFillAndLineProperties_() {
+ auto map = PropertyMapper::getPropertyNameMapForFillProperties();
+ auto const & add
+ = PropertyMapper::getPropertyNameMapForLineProperties();
+ map.insert(add.begin(), add.end());
+ return map;
+ }
+}
+const tPropertyNameMap& PropertyMapper::getPropertyNameMapForFillAndLineProperties()
+{
+ static tPropertyNameMap s_aShapePropertyMapForFillAndLineProperties
+ = getPropertyNameMapForFillAndLineProperties_();
+ return s_aShapePropertyMapForFillAndLineProperties;
+}
+
+namespace {
+ tPropertyNameMap getPropertyNameMapForTextShapeProperties_() {
+ auto map = PropertyMapper::getPropertyNameMapForCharacterProperties();
+ auto const & add1
+ = PropertyMapper::getPropertyNameMapForFillProperties();
+ map.insert(add1.begin(), add1.end());
+ auto const & add2
+ = PropertyMapper::getPropertyNameMapForLineProperties();
+ map.insert(add2.begin(), add2.end());
+ return map;
+ }
+}
+const tPropertyNameMap& PropertyMapper::getPropertyNameMapForTextShapeProperties()
+{
+ static tPropertyNameMap s_aShapePropertyMapForTextShapeProperties
+ = getPropertyNameMapForTextShapeProperties_();
+ return s_aShapePropertyMapForTextShapeProperties;
+}
+
+const tPropertyNameMap& PropertyMapper::getPropertyNameMapForLineSeriesProperties()
+{
+ //shape property -- chart model object property
+ static tPropertyNameMap s_aShapePropertyMapForLineSeriesProperties{
+ {"LineColor", "Color"},
+ {"LineDashName", "LineDashName"},
+ {"LineStyle", "LineStyle"},
+ {"LineTransparence", "Transparency"},
+ {"LineWidth", "LineWidth"},
+ {"LineCap", "LineCap"}};
+ return s_aShapePropertyMapForLineSeriesProperties;
+}
+
+namespace {
+ tPropertyNameMap getPropertyNameMapForTextLabelProperties_() {
+ auto map = PropertyMapper::getPropertyNameMapForCharacterProperties();
+ map.insert({
+ {"LineStyle", CHART_UNONAME_LABEL_BORDER_STYLE},
+ {"LineWidth", CHART_UNONAME_LABEL_BORDER_WIDTH},
+ {"LineColor", CHART_UNONAME_LABEL_BORDER_COLOR},
+ {"LineTransparence", CHART_UNONAME_LABEL_BORDER_TRANS},
+ {"FillStyle", CHART_UNONAME_LABEL_FILL_STYLE},
+ {"FillColor", CHART_UNONAME_LABEL_FILL_COLOR},
+ {"FillBackground", CHART_UNONAME_LABEL_FILL_BACKGROUND},
+ {"FillHatchName", CHART_UNONAME_LABEL_FILL_HATCH_NAME}
+ });
+ // fix the spelling!
+ return map;
+ }
+}
+const tPropertyNameMap& PropertyMapper::getPropertyNameMapForTextLabelProperties()
+{
+ // target name (drawing layer) : source name (chart model)
+ static tPropertyNameMap aMap = getPropertyNameMapForTextLabelProperties_();
+ return aMap;
+}
+
+const tPropertyNameMap& PropertyMapper::getPropertyNameMapForFilledSeriesProperties()
+{
+ //shape property -- chart model object property
+ static tPropertyNameMap s_aShapePropertyMapForFilledSeriesProperties{
+ {"FillBackground", "FillBackground"},
+ {"FillBitmapName", "FillBitmapName"},
+ {"FillColor", "Color"},
+ {"FillGradientName", "GradientName"},
+ {"FillGradientStepCount", "GradientStepCount"},
+ {"FillHatchName", "HatchName"},
+ {"FillStyle", "FillStyle"},
+ {"FillTransparence", "Transparency"},
+ {"FillTransparenceGradientName", "TransparencyGradientName"},
+ //bitmap properties
+ {"FillBitmapMode", "FillBitmapMode"},
+ {"FillBitmapSizeX", "FillBitmapSizeX"},
+ {"FillBitmapSizeY", "FillBitmapSizeY"},
+ {"FillBitmapLogicalSize", "FillBitmapLogicalSize"},
+ {"FillBitmapOffsetX", "FillBitmapOffsetX"},
+ {"FillBitmapOffsetY", "FillBitmapOffsetY"},
+ {"FillBitmapRectanglePoint", "FillBitmapRectanglePoint"},
+ {"FillBitmapPositionOffsetX", "FillBitmapPositionOffsetX"},
+ {"FillBitmapPositionOffsetY", "FillBitmapPositionOffsetY"},
+ //line properties
+ {"LineColor", "BorderColor"},
+ {"LineDashName", "BorderDashName"},
+ {"LineStyle", "BorderStyle"},
+ {"LineTransparence", "BorderTransparency"},
+ {"LineWidth", "BorderWidth"},
+ {"LineCap", "LineCap"}};
+ return s_aShapePropertyMapForFilledSeriesProperties;
+}
+
+void PropertyMapper::setMultiProperties(
+ const tNameSequence& rNames
+ , const tAnySequence& rValues
+ , SvxShape& xTarget )
+{
+ try
+ {
+ xTarget.setPropertyValues( rNames, rValues );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" ); //if this occurs more often think of removing the XMultiPropertySet completely for better performance
+ }
+}
+
+void PropertyMapper::getTextLabelMultiPropertyLists(
+ const uno::Reference< beans::XPropertySet >& xSourceProp
+ , tNameSequence& rPropNames, tAnySequence& rPropValues
+ , bool bName
+ , sal_Int32 nLimitedSpace
+ , bool bLimitedHeight
+ , bool bSupportsLabelBorder)
+{
+ //fill character properties into the ValueMap
+ tPropertyNameValueMap aValueMap;
+ tPropertyNameMap const & aNameMap = bSupportsLabelBorder ? PropertyMapper::getPropertyNameMapForTextLabelProperties() : getPropertyNameMapForCharacterProperties();
+
+ PropertyMapper::getValueMap(aValueMap, aNameMap, xSourceProp);
+
+ //some more shape properties apart from character properties, position-matrix and label string
+ aValueMap.emplace( "TextHorizontalAdjust", uno::Any(drawing::TextHorizontalAdjust_CENTER) ); // drawing::TextHorizontalAdjust - needs to be overwritten
+ aValueMap.emplace( "TextVerticalAdjust", uno::Any(drawing::TextVerticalAdjust_CENTER) ); //drawing::TextVerticalAdjust - needs to be overwritten
+ aValueMap.emplace( "TextAutoGrowHeight", uno::Any(true) ); // sal_Bool
+ aValueMap.emplace( "TextAutoGrowWidth", uno::Any(true) ); // sal_Bool
+ aValueMap.emplace( "ParaAdjust", uno::Any(style::ParagraphAdjust_CENTER) ); // style::ParagraphAdjust_CENTER - needs to be overwritten
+ if( bName )
+ aValueMap.emplace( "Name", uno::Any( OUString() ) ); //CID OUString - needs to be overwritten for each point
+
+ if( nLimitedSpace > 0 )
+ {
+ if(bLimitedHeight)
+ aValueMap.emplace( "TextMaximumFrameHeight", uno::Any(nLimitedSpace) ); //sal_Int32
+ else
+ aValueMap.emplace( "TextMaximumFrameWidth", uno::Any(nLimitedSpace) ); //sal_Int32
+ aValueMap.emplace( "ParaIsHyphenation", uno::Any(true) );
+ }
+
+ PropertyMapper::getMultiPropertyListsFromValueMap( rPropNames, rPropValues, aValueMap );
+}
+
+void PropertyMapper::getPreparedTextShapePropertyLists(
+ const uno::Reference< beans::XPropertySet >& xSourceProp
+ , tNameSequence& rPropNames, tAnySequence& rPropValues )
+{
+ //fill character, line and fill properties into the ValueMap
+ tPropertyNameValueMap aValueMap;
+ PropertyMapper::getValueMap( aValueMap
+ , PropertyMapper::getPropertyNameMapForTextShapeProperties()
+ , xSourceProp );
+
+ // auto-grow makes sure the shape has the correct size after setting text
+ aValueMap.emplace( "TextHorizontalAdjust", uno::Any( drawing::TextHorizontalAdjust_CENTER ));
+ aValueMap.emplace( "TextVerticalAdjust", uno::Any( drawing::TextVerticalAdjust_CENTER ));
+ aValueMap.emplace( "TextAutoGrowHeight", uno::Any( true ));
+ aValueMap.emplace( "TextAutoGrowWidth", uno::Any( true ));
+
+ // set some distance to the border, in case it is shown
+ const sal_Int32 nWidthDist = 250;
+ const sal_Int32 nHeightDist = 125;
+ aValueMap.emplace( "TextLeftDistance", uno::Any( nWidthDist ));
+ aValueMap.emplace( "TextRightDistance", uno::Any( nWidthDist ));
+ aValueMap.emplace( "TextUpperDistance", uno::Any( nHeightDist ));
+ aValueMap.emplace( "TextLowerDistance", uno::Any( nHeightDist ));
+
+ // use a line-joint showing the border of thick lines like two rectangles
+ // filled in between.
+ aValueMap["LineJoint"] <<= drawing::LineJoint_ROUND;
+
+ PropertyMapper::getMultiPropertyListsFromValueMap( rPropNames, rPropValues, aValueMap );
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/SeriesPlotterContainer.cxx b/chart2/source/view/main/SeriesPlotterContainer.cxx
new file mode 100644
index 000000000..1eb6a64a4
--- /dev/null
+++ b/chart2/source/view/main/SeriesPlotterContainer.cxx
@@ -0,0 +1,739 @@
+/* -*- 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 <sal/config.h>
+
+#include <cstddef>
+
+#include "SeriesPlotterContainer.hxx"
+
+#include <ChartView.hxx>
+#include <Diagram.hxx>
+#include <ChartType.hxx>
+#include <DataSeries.hxx>
+#include <ChartModel.hxx>
+#include <ChartTypeHelper.hxx>
+#include <ObjectIdentifier.hxx>
+#include <DiagramHelper.hxx>
+#include <Axis.hxx>
+#include <AxisIndexDefines.hxx>
+#include <DataSeriesHelper.hxx>
+#include <ExplicitCategoriesProvider.hxx>
+#include <unonames.hxx>
+
+#include <com/sun/star/chart/ChartAxisPosition.hpp>
+#include <com/sun/star/chart2/AxisType.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+#include <comphelper/classids.hxx>
+#include <servicenames_charttypes.hxx>
+#include <tools/diagnose_ex.h>
+
+namespace chart
+{
+using namespace ::css;
+using namespace ::css::chart2;
+
+using ::css::uno::Reference;
+using ::css::uno::Sequence;
+using ::css::uno::Any;
+
+SeriesPlotterContainer::SeriesPlotterContainer(
+ std::vector<std::unique_ptr<VCoordinateSystem>>& rVCooSysList)
+ : m_rVCooSysList(rVCooSysList)
+ , m_nMaxAxisIndex(0)
+ , m_bChartTypeUsesShiftedCategoryPositionPerDefault(false)
+ , m_nDefaultDateNumberFormat(0)
+{
+}
+
+SeriesPlotterContainer::~SeriesPlotterContainer()
+{
+ // - remove plotter from coordinatesystems
+ for (auto& nC : m_rVCooSysList)
+ nC->clearMinimumAndMaximumSupplierList();
+}
+
+std::vector<LegendEntryProvider*> SeriesPlotterContainer::getLegendEntryProviderList()
+{
+ std::vector<LegendEntryProvider*> aRet(m_aSeriesPlotterList.size());
+ sal_Int32 nN = 0;
+ for (const std::unique_ptr<VSeriesPlotter>& aPlotter : m_aSeriesPlotterList)
+ aRet[nN++] = aPlotter.get();
+ return aRet;
+}
+
+VCoordinateSystem* SeriesPlotterContainer::findInCooSysList(
+ const std::vector<std::unique_ptr<VCoordinateSystem>>& rVCooSysList,
+ const rtl::Reference<BaseCoordinateSystem>& xCooSys)
+{
+ for (auto& pVCooSys : rVCooSysList)
+ {
+ if (pVCooSys->getModel() == xCooSys)
+ return pVCooSys.get();
+ }
+ return nullptr;
+}
+
+VCoordinateSystem* SeriesPlotterContainer::getCooSysForPlotter(
+ const std::vector<std::unique_ptr<VCoordinateSystem>>& rVCooSysList,
+ MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier)
+{
+ if (!pMinimumAndMaximumSupplier)
+ return nullptr;
+ for (auto& pVCooSys : rVCooSysList)
+ {
+ if (pVCooSys->hasMinimumAndMaximumSupplier(pMinimumAndMaximumSupplier))
+ return pVCooSys.get();
+ }
+ return nullptr;
+}
+
+VCoordinateSystem* SeriesPlotterContainer::addCooSysToList(
+ std::vector<std::unique_ptr<VCoordinateSystem>>& rVCooSysList,
+ const rtl::Reference<BaseCoordinateSystem>& xCooSys, ChartModel& rChartModel)
+{
+ VCoordinateSystem* pExistingVCooSys
+ = SeriesPlotterContainer::findInCooSysList(rVCooSysList, xCooSys);
+ if (pExistingVCooSys)
+ return pExistingVCooSys;
+
+ std::unique_ptr<VCoordinateSystem> pVCooSys
+ = VCoordinateSystem::createCoordinateSystem(xCooSys);
+ if (!pVCooSys)
+ return nullptr;
+
+ OUString aCooSysParticle(
+ ObjectIdentifier::createParticleForCoordinateSystem(xCooSys, &rChartModel));
+ pVCooSys->setParticle(aCooSysParticle);
+
+ pVCooSys->setExplicitCategoriesProvider(new ExplicitCategoriesProvider(xCooSys, rChartModel));
+ rVCooSysList.push_back(std::move(pVCooSys));
+ return rVCooSysList.back().get();
+}
+
+void SeriesPlotterContainer::initializeCooSysAndSeriesPlotter(ChartModel& rChartModel)
+{
+ rtl::Reference<Diagram> xDiagram = rChartModel.getFirstChartDiagram();
+ if (!xDiagram.is())
+ return;
+
+ uno::Reference<util::XNumberFormatsSupplier> xNumberFormatsSupplier(&rChartModel);
+ if (rChartModel.hasInternalDataProvider() && DiagramHelper::isSupportingDateAxis(xDiagram))
+ m_nDefaultDateNumberFormat = DiagramHelper::getDateNumberFormat(xNumberFormatsSupplier);
+
+ sal_Int32 nDimensionCount = DiagramHelper::getDimension(xDiagram);
+ if (!nDimensionCount)
+ {
+ //@todo handle mixed dimension
+ nDimensionCount = 2;
+ }
+
+ bool bSortByXValues = false;
+ bool bConnectBars = false;
+ bool bGroupBarsPerAxis = true;
+ bool bIncludeHiddenCells = true;
+ bool bSecondaryYaxisVisible = true;
+ sal_Int32 nStartingAngle = 90;
+ sal_Int32 n3DRelativeHeight = 100;
+ try
+ {
+ xDiagram->getPropertyValue(CHART_UNONAME_SORT_BY_XVALUES) >>= bSortByXValues;
+ xDiagram->getPropertyValue("ConnectBars") >>= bConnectBars;
+ xDiagram->getPropertyValue("GroupBarsPerAxis") >>= bGroupBarsPerAxis;
+ xDiagram->getPropertyValue("IncludeHiddenCells") >>= bIncludeHiddenCells;
+ xDiagram->getPropertyValue("StartingAngle") >>= nStartingAngle;
+
+ if (nDimensionCount == 3)
+ {
+ xDiagram->getPropertyValue("3DRelativeHeight") >>= n3DRelativeHeight;
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+
+ //prepare for autoscaling and shape creation
+ // - create plotter for charttypes (for each first scale group at each plotter, as they are independent)
+ // - add series to plotter (thus each charttype can provide minimum and maximum values for autoscaling)
+ // - add plotter to coordinate systems
+
+ //iterate through all coordinate systems
+ uno::Reference<XColorScheme> xColorScheme(xDiagram->getDefaultColorScheme());
+ auto& rCooSysList = xDiagram->getBaseCoordinateSystems();
+ sal_Int32 nGlobalSeriesIndex = 0; //for automatic symbols
+ for (std::size_t nCS = 0; nCS < rCooSysList.size(); ++nCS)
+ {
+ rtl::Reference<BaseCoordinateSystem> xCooSys(rCooSysList[nCS]);
+ VCoordinateSystem* pVCooSys
+ = SeriesPlotterContainer::addCooSysToList(m_rVCooSysList, xCooSys, rChartModel);
+ // Let's check whether the secondary Y axis is visible
+ try
+ {
+ if (xCooSys->getMaximumAxisIndexByDimension(1) > 0)
+ {
+ rtl::Reference<Axis> xAxisProp = xCooSys->getAxisByDimension2(1, 1);
+ xAxisProp->getPropertyValue("Show") >>= bSecondaryYaxisVisible;
+ }
+ }
+ catch (const lang::IndexOutOfBoundsException&)
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "");
+ }
+ //iterate through all chart types in the current coordinate system
+ std::vector<rtl::Reference<ChartType>> aChartTypeList(xCooSys->getChartTypes2());
+ for (std::size_t nT = 0; nT < aChartTypeList.size(); ++nT)
+ {
+ rtl::Reference<ChartType> xChartType(aChartTypeList[nT]);
+ if (nDimensionCount == 3
+ && xChartType->getChartType().equalsIgnoreAsciiCase(
+ CHART2_SERVICE_NAME_CHARTTYPE_PIE))
+ {
+ try
+ {
+ sal_Int32 n3DRelativeHeightOldValue(100);
+ uno::Any aAny = xChartType->getPropertyValue("3DRelativeHeight");
+ aAny >>= n3DRelativeHeightOldValue;
+ if (n3DRelativeHeightOldValue != n3DRelativeHeight)
+ xChartType->setPropertyValue("3DRelativeHeight",
+ uno::Any(n3DRelativeHeight));
+ }
+ catch (const uno::Exception&)
+ {
+ }
+ }
+
+ if (nT == 0)
+ m_bChartTypeUsesShiftedCategoryPositionPerDefault
+ = ChartTypeHelper::shiftCategoryPosAtXAxisPerDefault(xChartType);
+
+ bool bExcludingPositioning = DiagramHelper::getDiagramPositioningMode(xDiagram)
+ == DiagramPositioningMode_EXCLUDING;
+ VSeriesPlotter* pPlotter = VSeriesPlotter::createSeriesPlotter(
+ xChartType, nDimensionCount, bExcludingPositioning);
+ if (!pPlotter)
+ continue;
+
+ m_aSeriesPlotterList.push_back(std::unique_ptr<VSeriesPlotter>(pPlotter));
+ pPlotter->setNumberFormatsSupplier(xNumberFormatsSupplier);
+ pPlotter->setColorScheme(xColorScheme);
+ if (pVCooSys)
+ pPlotter->setExplicitCategoriesProvider(pVCooSys->getExplicitCategoriesProvider());
+ sal_Int32 nMissingValueTreatment
+ = DiagramHelper::getCorrectedMissingValueTreatment(xDiagram, xChartType);
+
+ if (pVCooSys)
+ pVCooSys->addMinimumAndMaximumSupplier(pPlotter);
+
+ sal_Int32 zSlot = -1;
+ sal_Int32 xSlot = -1;
+ sal_Int32 ySlot = -1;
+ const std::vector<rtl::Reference<DataSeries>>& aSeriesList
+ = xChartType->getDataSeries2();
+ for (std::size_t nS = 0; nS < aSeriesList.size(); ++nS)
+ {
+ rtl::Reference<DataSeries> const& xDataSeries = aSeriesList[nS];
+ if (!bIncludeHiddenCells && !DataSeriesHelper::hasUnhiddenData(xDataSeries))
+ continue;
+
+ std::unique_ptr<VDataSeries> pSeries(new VDataSeries(xDataSeries));
+
+ pSeries->setGlobalSeriesIndex(nGlobalSeriesIndex);
+ nGlobalSeriesIndex++;
+
+ if (bSortByXValues)
+ pSeries->doSortByXValues();
+
+ pSeries->setConnectBars(bConnectBars);
+ pSeries->setGroupBarsPerAxis(bGroupBarsPerAxis);
+ pSeries->setStartingAngle(nStartingAngle);
+
+ pSeries->setMissingValueTreatment(nMissingValueTreatment);
+
+ OUString aSeriesParticle(ObjectIdentifier::createParticleForSeries(0, nCS, nT, nS));
+ pSeries->setParticle(aSeriesParticle);
+
+ OUString aRole(ChartTypeHelper::getRoleOfSequenceForDataLabelNumberFormatDetection(
+ xChartType));
+ pSeries->setRoleOfSequenceForDataLabelNumberFormatDetection(aRole);
+
+ //ignore secondary axis for charttypes that do not support them
+ if (pSeries->getAttachedAxisIndex() != MAIN_AXIS_INDEX
+ && (!ChartTypeHelper::isSupportingSecondaryAxis(xChartType, nDimensionCount)
+ || !bSecondaryYaxisVisible))
+ {
+ pSeries->setAttachedAxisIndex(MAIN_AXIS_INDEX);
+ }
+
+ StackingDirection eDirection = pSeries->getStackingDirection();
+ switch (eDirection)
+ {
+ case StackingDirection_NO_STACKING:
+ xSlot++;
+ ySlot = -1;
+ if (zSlot < 0)
+ zSlot = 0;
+ break;
+ case StackingDirection_Y_STACKING:
+ ySlot++;
+ if (xSlot < 0)
+ xSlot = 0;
+ if (zSlot < 0)
+ zSlot = 0;
+ break;
+ case StackingDirection_Z_STACKING:
+ zSlot++;
+ xSlot = -1;
+ ySlot = -1;
+ break;
+ default:
+ // UNO enums have one additional auto-generated case
+ break;
+ }
+ pPlotter->addSeries(std::move(pSeries), zSlot, xSlot, ySlot);
+ }
+ }
+ }
+
+ //transport seriesnames to the coordinatesystems if needed
+ if (m_aSeriesPlotterList.empty())
+ return;
+
+ uno::Sequence<OUString> aSeriesNames;
+ bool bSeriesNamesInitialized = false;
+ for (auto& pVCooSys : m_rVCooSysList)
+ {
+ if (pVCooSys->needSeriesNamesForAxis())
+ {
+ if (!bSeriesNamesInitialized)
+ {
+ aSeriesNames = m_aSeriesPlotterList[0]->getSeriesNames();
+ bSeriesNamesInitialized = true;
+ }
+ pVCooSys->setSeriesNamesForAxis(aSeriesNames);
+ }
+ }
+}
+
+bool SeriesPlotterContainer::isCategoryPositionShifted(const chart2::ScaleData& rSourceScale,
+ bool bHasComplexCategories)
+{
+ if (rSourceScale.AxisType == AxisType::CATEGORY)
+ return bHasComplexCategories || rSourceScale.ShiftedCategoryPosition
+ || m_bChartTypeUsesShiftedCategoryPositionPerDefault;
+
+ if (rSourceScale.AxisType == AxisType::DATE)
+ return rSourceScale.ShiftedCategoryPosition;
+
+ return rSourceScale.AxisType == AxisType::SERIES;
+}
+
+void SeriesPlotterContainer::initAxisUsageList(const Date& rNullDate)
+{
+ m_aAxisUsageList.clear();
+
+ // Loop through coordinate systems in the diagram (though for now
+ // there should only be one coordinate system per diagram).
+ for (auto& pVCooSys : m_rVCooSysList)
+ {
+ rtl::Reference<BaseCoordinateSystem> xCooSys = pVCooSys->getModel();
+ sal_Int32 nDimCount = xCooSys->getDimension();
+ bool bComplexCategoryAllowed = ChartTypeHelper::isSupportingComplexCategory(
+ AxisHelper::getChartTypeByIndex(xCooSys, 0));
+
+ for (sal_Int32 nDimIndex = 0; nDimIndex < nDimCount; ++nDimIndex)
+ {
+ bool bDateAxisAllowed = ChartTypeHelper::isSupportingDateAxis(
+ AxisHelper::getChartTypeByIndex(xCooSys, 0), nDimIndex);
+
+ // Each dimension may have primary and secondary axes.
+ const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nDimIndex);
+ for (sal_Int32 nAxisIndex = 0; nAxisIndex <= nMaxAxisIndex; ++nAxisIndex)
+ {
+ rtl::Reference<Axis> xAxis = xCooSys->getAxisByDimension2(nDimIndex, nAxisIndex);
+
+ if (!xAxis.is())
+ continue;
+
+ if (m_aAxisUsageList.find(xAxis) == m_aAxisUsageList.end())
+ {
+ // Create axis usage object for this axis.
+
+ chart2::ScaleData aSourceScale = xAxis->getScaleData();
+ ExplicitCategoriesProvider* pCatProvider
+ = pVCooSys->getExplicitCategoriesProvider();
+ if (nDimIndex == 0)
+ AxisHelper::checkDateAxis(aSourceScale, pCatProvider, bDateAxisAllowed);
+
+ bool bHasComplexCat = pCatProvider && pCatProvider->hasComplexCategories()
+ && bComplexCategoryAllowed;
+ aSourceScale.ShiftedCategoryPosition
+ = isCategoryPositionShifted(aSourceScale, bHasComplexCat);
+
+ m_aAxisUsageList[xAxis].aAutoScaling = ScaleAutomatism(aSourceScale, rNullDate);
+ }
+
+ AxisUsage& rAxisUsage = m_aAxisUsageList[xAxis];
+ rAxisUsage.addCoordinateSystem(pVCooSys.get(), nDimIndex, nAxisIndex);
+ }
+ }
+ }
+
+ // Determine the highest axis index of all dimensions.
+ m_nMaxAxisIndex = 0;
+ for (const auto& pVCooSys : m_rVCooSysList)
+ {
+ uno::Reference<XCoordinateSystem> xCooSys = pVCooSys->getModel();
+ sal_Int32 nDimCount = xCooSys->getDimension();
+
+ for (sal_Int32 nDimIndex = 0; nDimIndex < nDimCount; ++nDimIndex)
+ {
+ for (auto& axisUsage : m_aAxisUsageList)
+ {
+ sal_Int32 nLocalMax = axisUsage.second.getMaxAxisIndexForDimension(nDimIndex);
+ if (m_nMaxAxisIndex < nLocalMax)
+ m_nMaxAxisIndex = nLocalMax;
+ }
+ }
+ }
+}
+
+void SeriesPlotterContainer::setScalesFromCooSysToPlotter()
+{
+ //set scales to plotter to enable them to provide the preferred scene AspectRatio
+ for (const std::unique_ptr<VSeriesPlotter>& aPlotter : m_aSeriesPlotterList)
+ {
+ VSeriesPlotter* pSeriesPlotter = aPlotter.get();
+ VCoordinateSystem* pVCooSys
+ = SeriesPlotterContainer::getCooSysForPlotter(m_rVCooSysList, pSeriesPlotter);
+ if (pVCooSys)
+ {
+ pSeriesPlotter->setScales(pVCooSys->getExplicitScales(0, 0),
+ pVCooSys->getPropertySwapXAndYAxis());
+ sal_Int32 nMaxAxisIndex = pVCooSys->getMaximumAxisIndexByDimension(
+ 1); //only additional value axis are relevant for series plotter
+ for (sal_Int32 nI = 1; nI <= nMaxAxisIndex; nI++)
+ pSeriesPlotter->addSecondaryValueScale(pVCooSys->getExplicitScale(1, nI), nI);
+ }
+ }
+}
+
+void SeriesPlotterContainer::setNumberFormatsFromAxes()
+{
+ //set numberformats to plotter to enable them to display the data labels in the numberformat of the axis
+ for (const std::unique_ptr<VSeriesPlotter>& aPlotter : m_aSeriesPlotterList)
+ {
+ VSeriesPlotter* pSeriesPlotter = aPlotter.get();
+ VCoordinateSystem* pVCooSys
+ = SeriesPlotterContainer::getCooSysForPlotter(m_rVCooSysList, pSeriesPlotter);
+ if (pVCooSys)
+ {
+ AxesNumberFormats aAxesNumberFormats;
+ const rtl::Reference<BaseCoordinateSystem>& xCooSys = pVCooSys->getModel();
+ sal_Int32 nDimensionCount = xCooSys->getDimension();
+ for (sal_Int32 nDimensionIndex = 0; nDimensionIndex < nDimensionCount;
+ ++nDimensionIndex)
+ {
+ const sal_Int32 nMaximumAxisIndex
+ = xCooSys->getMaximumAxisIndexByDimension(nDimensionIndex);
+ for (sal_Int32 nAxisIndex = 0; nAxisIndex <= nMaximumAxisIndex; ++nAxisIndex)
+ {
+ try
+ {
+ rtl::Reference<Axis> xAxisProp
+ = xCooSys->getAxisByDimension2(nDimensionIndex, nAxisIndex);
+ if (xAxisProp.is())
+ {
+ sal_Int32 nNumberFormatKey(0);
+ if (xAxisProp->getPropertyValue(CHART_UNONAME_NUMFMT)
+ >>= nNumberFormatKey)
+ {
+ aAxesNumberFormats.setFormat(nNumberFormatKey, nDimensionIndex,
+ nAxisIndex);
+ }
+ else if (nDimensionIndex == 0)
+ {
+ //provide a default date format for date axis with own data
+ aAxesNumberFormats.setFormat(m_nDefaultDateNumberFormat,
+ nDimensionIndex, nAxisIndex);
+ }
+ }
+ }
+ catch (const lang::IndexOutOfBoundsException&)
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "");
+ }
+ }
+ }
+ }
+ }
+}
+
+void SeriesPlotterContainer::updateScalesAndIncrementsOnAxes()
+{
+ for (auto& nC : m_rVCooSysList)
+ nC->updateScalesAndIncrementsOnAxes();
+}
+
+void SeriesPlotterContainer::doAutoScaling(ChartModel& rChartModel)
+{
+ if (m_aSeriesPlotterList.empty() || m_aAxisUsageList.empty())
+ // We need these two containers populated to do auto-scaling. Bail out.
+ return;
+
+ //iterate over the main scales first than secondary axis
+ for (sal_Int32 nAxisIndex = 0; nAxisIndex <= m_nMaxAxisIndex; ++nAxisIndex)
+ {
+ // - first do autoscale for all x and z scales (because they are treated independent)
+ for (auto & [ rAxis, rAxisUsage ] : m_aAxisUsageList)
+ {
+ (void)rAxis;
+ rAxisUsage.prepareAutomaticAxisScaling(rAxisUsage.aAutoScaling, 0, nAxisIndex);
+ rAxisUsage.prepareAutomaticAxisScaling(rAxisUsage.aAutoScaling, 2, nAxisIndex);
+
+ ExplicitScaleData aExplicitScale;
+ ExplicitIncrementData aExplicitIncrement;
+ rAxisUsage.aAutoScaling.calculateExplicitScaleAndIncrement(aExplicitScale,
+ aExplicitIncrement);
+
+ rAxisUsage.setExplicitScaleAndIncrement(0, nAxisIndex, aExplicitScale,
+ aExplicitIncrement);
+ rAxisUsage.setExplicitScaleAndIncrement(2, nAxisIndex, aExplicitScale,
+ aExplicitIncrement);
+ }
+
+ // - second do autoscale for the dependent y scales (the coordinate systems are prepared with x and z scales already )
+ for (auto & [ rAxis, rAxisUsage ] : m_aAxisUsageList)
+ {
+ (void)rAxis;
+ rAxisUsage.prepareAutomaticAxisScaling(rAxisUsage.aAutoScaling, 1, nAxisIndex);
+
+ ExplicitScaleData aExplicitScale;
+ ExplicitIncrementData aExplicitIncrement;
+ rAxisUsage.aAutoScaling.calculateExplicitScaleAndIncrement(aExplicitScale,
+ aExplicitIncrement);
+
+ rAxisUsage.setExplicitScaleAndIncrement(0, nAxisIndex, aExplicitScale,
+ aExplicitIncrement);
+ rAxisUsage.setExplicitScaleAndIncrement(1, nAxisIndex, aExplicitScale,
+ aExplicitIncrement);
+ rAxisUsage.setExplicitScaleAndIncrement(2, nAxisIndex, aExplicitScale,
+ aExplicitIncrement);
+ }
+ }
+ AdaptScaleOfYAxisWithoutAttachedSeries(rChartModel);
+}
+
+void SeriesPlotterContainer::AdaptScaleOfYAxisWithoutAttachedSeries(ChartModel& rModel)
+{
+ //issue #i80518#
+ for (sal_Int32 nAxisIndex = 0; nAxisIndex <= m_nMaxAxisIndex; nAxisIndex++)
+ {
+ for (auto & [ rAxis, rAxisUsage ] : m_aAxisUsageList)
+ {
+ (void)rAxis;
+ std::vector<VCoordinateSystem*> aVCooSysList_Y
+ = rAxisUsage.getCoordinateSystems(1, nAxisIndex);
+ if (aVCooSysList_Y.empty())
+ continue;
+
+ rtl::Reference<Diagram> xDiagram(rModel.getFirstChartDiagram());
+ if (!xDiagram.is())
+ continue;
+
+ bool bSeriesAttachedToThisAxis = false;
+ sal_Int32 nAttachedAxisIndex = -1;
+ {
+ std::vector<rtl::Reference<DataSeries>> aSeriesVector
+ = DiagramHelper::getDataSeriesFromDiagram(xDiagram);
+ for (auto const& series : aSeriesVector)
+ {
+ sal_Int32 nCurrentIndex = DataSeriesHelper::getAttachedAxisIndex(series);
+ if (nAxisIndex == nCurrentIndex)
+ {
+ bSeriesAttachedToThisAxis = true;
+ break;
+ }
+ else if (nAttachedAxisIndex < 0 || nAttachedAxisIndex > nCurrentIndex)
+ nAttachedAxisIndex = nCurrentIndex;
+ }
+ }
+
+ if (bSeriesAttachedToThisAxis || nAttachedAxisIndex < 0)
+ continue;
+
+ for (VCoordinateSystem* nC : aVCooSysList_Y)
+ {
+ nC->prepareAutomaticAxisScaling(rAxisUsage.aAutoScaling, 1, nAttachedAxisIndex);
+
+ ExplicitScaleData aExplicitScaleSource
+ = nC->getExplicitScale(1, nAttachedAxisIndex);
+ ExplicitIncrementData aExplicitIncrementSource
+ = nC->getExplicitIncrement(1, nAttachedAxisIndex);
+
+ ExplicitScaleData aExplicitScaleDest = nC->getExplicitScale(1, nAxisIndex);
+ ExplicitIncrementData aExplicitIncrementDest
+ = nC->getExplicitIncrement(1, nAxisIndex);
+
+ aExplicitScaleDest.Orientation = aExplicitScaleSource.Orientation;
+ aExplicitScaleDest.Scaling = aExplicitScaleSource.Scaling;
+ aExplicitScaleDest.AxisType = aExplicitScaleSource.AxisType;
+
+ aExplicitIncrementDest.BaseValue = aExplicitIncrementSource.BaseValue;
+
+ ScaleData aScale(rAxisUsage.aAutoScaling.getScale());
+ if (!aScale.Minimum.hasValue())
+ {
+ bool bNewMinOK = true;
+ double fMax = 0.0;
+ if (aScale.Maximum >>= fMax)
+ bNewMinOK = (aExplicitScaleSource.Minimum <= fMax);
+ if (bNewMinOK)
+ aExplicitScaleDest.Minimum = aExplicitScaleSource.Minimum;
+ }
+ else
+ aExplicitIncrementDest.BaseValue = aExplicitScaleDest.Minimum;
+
+ if (!aScale.Maximum.hasValue())
+ {
+ bool bNewMaxOK = true;
+ double fMin = 0.0;
+ if (aScale.Minimum >>= fMin)
+ bNewMaxOK = (fMin <= aExplicitScaleSource.Maximum);
+ if (bNewMaxOK)
+ aExplicitScaleDest.Maximum = aExplicitScaleSource.Maximum;
+ }
+ if (!aScale.Origin.hasValue())
+ aExplicitScaleDest.Origin = aExplicitScaleSource.Origin;
+
+ if (!aScale.IncrementData.Distance.hasValue())
+ aExplicitIncrementDest.Distance = aExplicitIncrementSource.Distance;
+
+ bool bAutoMinorInterval = true;
+ if (aScale.IncrementData.SubIncrements.hasElements())
+ bAutoMinorInterval
+ = !(aScale.IncrementData.SubIncrements[0].IntervalCount.hasValue());
+ if (bAutoMinorInterval)
+ {
+ if (!aExplicitIncrementDest.SubIncrements.empty()
+ && !aExplicitIncrementSource.SubIncrements.empty())
+ aExplicitIncrementDest.SubIncrements[0].IntervalCount
+ = aExplicitIncrementSource.SubIncrements[0].IntervalCount;
+ }
+
+ nC->setExplicitScaleAndIncrement(1, nAxisIndex, aExplicitScaleDest,
+ aExplicitIncrementDest);
+ }
+ }
+ }
+
+ if (!AxisHelper::isAxisPositioningEnabled())
+ return;
+
+ //correct origin for y main axis (the origin is where the other main axis crosses)
+ sal_Int32 nAxisIndex = 0;
+ sal_Int32 nDimensionIndex = 1;
+ for (auto & [ rAxis, rAxisUsage ] : m_aAxisUsageList)
+ {
+ (void)rAxis;
+ std::vector<VCoordinateSystem*> aVCooSysList
+ = rAxisUsage.getCoordinateSystems(nDimensionIndex, nAxisIndex);
+ size_t nC;
+ for (nC = 0; nC < aVCooSysList.size(); nC++)
+ {
+ ExplicitScaleData aExplicitScale(
+ aVCooSysList[nC]->getExplicitScale(nDimensionIndex, nAxisIndex));
+ ExplicitIncrementData aExplicitIncrement(
+ aVCooSysList[nC]->getExplicitIncrement(nDimensionIndex, nAxisIndex));
+
+ rtl::Reference<BaseCoordinateSystem> xCooSys(aVCooSysList[nC]->getModel());
+ rtl::Reference<Axis> xAxis = xCooSys->getAxisByDimension2(nDimensionIndex, nAxisIndex);
+ rtl::Reference<Axis> xCrossingMainAxis
+ = AxisHelper::getCrossingMainAxis(xAxis, xCooSys);
+
+ if (xCrossingMainAxis.is())
+ {
+ css::chart::ChartAxisPosition eCrossingMainAxisPos(
+ css::chart::ChartAxisPosition_ZERO);
+ xCrossingMainAxis->getPropertyValue("CrossoverPosition") >>= eCrossingMainAxisPos;
+ if (eCrossingMainAxisPos == css::chart::ChartAxisPosition_VALUE)
+ {
+ double fValue = 0.0;
+ xCrossingMainAxis->getPropertyValue("CrossoverValue") >>= fValue;
+ aExplicitScale.Origin = fValue;
+ }
+ else if (eCrossingMainAxisPos == css::chart::ChartAxisPosition_ZERO)
+ aExplicitScale.Origin = 0.0;
+ else if (eCrossingMainAxisPos == css::chart::ChartAxisPosition_START)
+ aExplicitScale.Origin = aExplicitScale.Minimum;
+ else if (eCrossingMainAxisPos == css::chart::ChartAxisPosition_END)
+ aExplicitScale.Origin = aExplicitScale.Maximum;
+ }
+
+ aVCooSysList[nC]->setExplicitScaleAndIncrement(nDimensionIndex, nAxisIndex,
+ aExplicitScale, aExplicitIncrement);
+ }
+ }
+}
+
+drawing::Direction3D SeriesPlotterContainer::getPreferredAspectRatio()
+{
+ drawing::Direction3D aPreferredAspectRatio(1.0, 1.0, 1.0);
+
+ //get a list of all preferred aspect ratios and combine them
+ //first with special demands wins (less or equal zero <-> arbitrary)
+ double fx, fy, fz;
+ fx = fy = fz = -1.0;
+ for (const std::unique_ptr<VSeriesPlotter>& aPlotter : m_aSeriesPlotterList)
+ {
+ drawing::Direction3D aSingleRatio(aPlotter->getPreferredDiagramAspectRatio());
+ if (fx < 0 && aSingleRatio.DirectionX > 0)
+ fx = aSingleRatio.DirectionX;
+
+ if (fy < 0 && aSingleRatio.DirectionY > 0)
+ {
+ if (fx > 0 && aSingleRatio.DirectionX > 0)
+ fy = fx * aSingleRatio.DirectionY / aSingleRatio.DirectionX;
+ else if (fz > 0 && aSingleRatio.DirectionZ > 0)
+ fy = fz * aSingleRatio.DirectionY / aSingleRatio.DirectionZ;
+ else
+ fy = aSingleRatio.DirectionY;
+ }
+
+ if (fz < 0 && aSingleRatio.DirectionZ > 0)
+ {
+ if (fx > 0 && aSingleRatio.DirectionX > 0)
+ fz = fx * aSingleRatio.DirectionZ / aSingleRatio.DirectionX;
+ else if (fy > 0 && aSingleRatio.DirectionY > 0)
+ fz = fy * aSingleRatio.DirectionZ / aSingleRatio.DirectionY;
+ else
+ fz = aSingleRatio.DirectionZ;
+ }
+
+ if (fx > 0 && fy > 0 && fz > 0)
+ break;
+ }
+ aPreferredAspectRatio = drawing::Direction3D(fx, fy, fz);
+ return aPreferredAspectRatio;
+}
+
+} //end chart2 namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/SeriesPlotterContainer.hxx b/chart2/source/view/main/SeriesPlotterContainer.hxx
new file mode 100644
index 000000000..e34d07a96
--- /dev/null
+++ b/chart2/source/view/main/SeriesPlotterContainer.hxx
@@ -0,0 +1,159 @@
+/* -*- 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 <config_feature_desktop.h>
+#include <VSeriesPlotter.hxx>
+#include <BaseCoordinateSystem.hxx>
+#include "AxisUsage.hxx"
+
+namespace chart
+{
+/** This class is a container of `SeriesPlotter` objects (such as `PieChart`
+ * instances). It is used for initializing coordinate systems, axes and scales
+ * of all series plotters which belongs to the container.
+ */
+class SeriesPlotterContainer
+{
+public:
+ explicit SeriesPlotterContainer(std::vector<std::unique_ptr<VCoordinateSystem>>& rVCooSysList);
+ ~SeriesPlotterContainer();
+
+ /** It is used to set coordinate systems (`m_rVCooSysList`), this method
+ * is invoked by `ChartView::createShapes2D` before of
+ * `ChartView::impl_createDiagramAndContent`.
+ * Coordinate systems are retrieved through the `XCoordinateSystemContainer`
+ * interface implemented by a diagram object which is provided by the
+ * `ChartModel` object passed to the method (`rChartModel.getFirstDiagram()`).
+ *
+ * It is used for creating series plotters and appending them
+ * to `m_aSeriesPlotterList`. The created series plotters are initialized
+ * through data (number formats supplier, color scheme, data series),
+ * extracted from the chart model or the diagram objects. An exception is
+ * the explicit category provider that is retrieved through the
+ * `VCoordinateSystem` object used by the series plotter.
+ *
+ * It sets the minimum-maximum supplier for a coordinate system:
+ * this supplier is the series plotter itself which utilizes the given
+ * coordinate system. In fact `VSeriesPlotter` has `MinimumMaximumSupplier`
+ * as one of its base classes.
+ * Hence, for instance, a `PieChart`, which is a series plotter, is
+ * a `MinimumMaximumSupplier`, too.
+ */
+ void initializeCooSysAndSeriesPlotter(ChartModel& rModel);
+
+ /** This method is invoked by `ChartView::impl_createDiagramAndContent`.
+ * It iterates on every axis of every coordinate systems, and if the axis
+ * is not yet present in `m_aAxisUsageList` it creates a new `AxisUsage`
+ * object and initialize its `aAutoScaling` member to the `ScaleData`
+ * object of the current axis.
+ */
+ void initAxisUsageList(const Date& rNullDate);
+
+ /**
+ * Perform automatic axis scaling and determine the amount and spacing of
+ * increments. It assumes that the caller has determined the size of the
+ * largest axis label text object prior to calling this method.
+ *
+ * The new axis scaling data will be stored in the VCoordinateSystem
+ * objects. The label alignment direction for each axis will also get
+ * determined during this process, and stored in VAxis.
+ *
+ * This method is invoked by `ChartView::impl_createDiagramAndContent`
+ * soon after `initAxisUsageList`.
+ * It initializes explicit scale and increment objects for all coordinate
+ * systems in `m_rVCooSysList`.
+ * This action is achieved by iterating on the `m_aAxisUsageList` container,
+ * and performing 3 steps:
+ * 1- call `VCoordinateSystem::prepareAutomaticAxisScaling` for setting
+ * scaling parameters of the `aAutoScaling` member (a `ScaleAutomatism`
+ * object) for the current `AxisUsage` instance
+ * (see `VCoordinateSystem::prepareAutomaticAxisScaling`);
+ * 2- calculate the explicit scale and increment objects
+ * (see ScaleAutomatism::calculateExplicitScaleAndIncrement);
+ * 3- set the explicit scale and increment objects for each coordinate
+ * system.
+ */
+ void doAutoScaling(ChartModel& rModel);
+
+ /**
+ * After auto-scaling is performed, call this method to set the explicit
+ * scaling and increment data to all relevant VAxis objects.
+ */
+ void updateScalesAndIncrementsOnAxes();
+
+ /**
+ * After auto-scaling is performed, call this method to set the explicit
+ * scaling data to all the plotters.
+ */
+ void setScalesFromCooSysToPlotter();
+
+ void setNumberFormatsFromAxes();
+ css::drawing::Direction3D getPreferredAspectRatio();
+
+ std::vector<std::unique_ptr<VSeriesPlotter>>& getSeriesPlotterList()
+ {
+ return m_aSeriesPlotterList;
+ }
+ std::vector<std::unique_ptr<VCoordinateSystem>>& getCooSysList() { return m_rVCooSysList; }
+ std::vector<LegendEntryProvider*> getLegendEntryProviderList();
+
+ void AdaptScaleOfYAxisWithoutAttachedSeries(ChartModel& rModel);
+
+ bool isCategoryPositionShifted(const css::chart2::ScaleData& rSourceScale,
+ bool bHasComplexCategories);
+
+ static VCoordinateSystem*
+ getCooSysForPlotter(const std::vector<std::unique_ptr<VCoordinateSystem>>& rVCooSysList,
+ MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier);
+ static VCoordinateSystem*
+ addCooSysToList(std::vector<std::unique_ptr<VCoordinateSystem>>& rVCooSysList,
+ const rtl::Reference<BaseCoordinateSystem>& xCooSys, ChartModel& rChartModel);
+ static VCoordinateSystem*
+ findInCooSysList(const std::vector<std::unique_ptr<VCoordinateSystem>>& rVCooSysList,
+ const rtl::Reference<BaseCoordinateSystem>& xCooSys);
+
+private:
+ /** A vector of series plotters.
+ */
+ std::vector<std::unique_ptr<VSeriesPlotter>> m_aSeriesPlotterList;
+
+ /** A vector of coordinate systems.
+ */
+ std::vector<std::unique_ptr<VCoordinateSystem>>& m_rVCooSysList;
+
+ /** A map whose key is a `XAxis` interface and the related value is
+ * an object of `AxisUsage` type.
+ */
+ std::map<rtl::Reference<Axis>, AxisUsage> m_aAxisUsageList;
+
+ /**
+ * Max axis index of all dimensions. Currently this can be either 0 or 1
+ * since we only support primary and secondary axes per dimension. The
+ * value of 0 means all dimensions have only primary axis, while 1 means
+ * at least one dimension has a secondary axis.
+ */
+ sal_Int32 m_nMaxAxisIndex;
+
+ bool m_bChartTypeUsesShiftedCategoryPositionPerDefault;
+ sal_Int32 m_nDefaultDateNumberFormat;
+};
+
+} //end chart2 namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/ShapeFactory.cxx b/chart2/source/view/main/ShapeFactory.cxx
new file mode 100644
index 000000000..7fddc5d01
--- /dev/null
+++ b/chart2/source/view/main/ShapeFactory.cxx
@@ -0,0 +1,2551 @@
+/* -*- 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 <ShapeFactory.hxx>
+#include <BaseGFXHelper.hxx>
+#include <ViewDefines.hxx>
+#include <Stripe.hxx>
+#include <CommonConverters.hxx>
+#include <RelativeSizeHelper.hxx>
+#include <PropertyMapper.hxx>
+#include <VLineProperties.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/chart2/XFormattedString.hpp>
+#include <com/sun/star/drawing/CircleKind.hpp>
+#include <com/sun/star/drawing/DoubleSequence.hpp>
+#include <com/sun/star/drawing/FlagSequence.hpp>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/drawing/NormalsKind.hpp>
+#include <com/sun/star/drawing/PointSequence.hpp>
+#include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp>
+#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
+#include <com/sun/star/drawing/TextureProjectionMode.hpp>
+#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
+#include <com/sun/star/drawing/XShapes2.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/drawing/XShapes.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/uno/Any.hxx>
+
+#include <editeng/unoprnms.hxx>
+#include <rtl/math.hxx>
+
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svdopath.hxx>
+#include <tools/diagnose_ex.h>
+#include <tools/helpers.hxx>
+#include <tools/UnitConversion.hxx>
+#include <sal/log.hxx>
+
+#include <algorithm>
+#include <cstddef>
+
+using namespace ::com::sun::star;
+using ::com::sun::star::uno::Reference;
+
+namespace chart
+{
+
+namespace
+{
+
+void lcl_addProperty(uno::Sequence<OUString> & rPropertyNames, uno::Sequence<uno::Any> & rPropertyValues,
+ OUString const & rName, uno::Any const & rAny)
+{
+ rPropertyNames.realloc(rPropertyNames.getLength() + 1);
+ rPropertyNames.getArray()[rPropertyNames.getLength() - 1] = rName;
+
+ rPropertyValues.realloc(rPropertyValues.getLength() + 1);
+ rPropertyValues.getArray()[rPropertyValues.getLength() - 1] = rAny;
+}
+
+css::drawing::PolyPolygonShape3D toPolyPolygonShape3D(const std::vector<std::vector<css::drawing::Position3D>>& rPoints)
+{
+ css::drawing::PolyPolygonShape3D aUnoPoly;
+ aUnoPoly.SequenceX.realloc(rPoints.size());
+ aUnoPoly.SequenceY.realloc(rPoints.size());
+ aUnoPoly.SequenceZ.realloc(rPoints.size());
+ for (std::size_t nPolygonIndex=0; nPolygonIndex<rPoints.size(); ++nPolygonIndex)
+ {
+ drawing::DoubleSequence* pOuterSequenceX = &aUnoPoly.SequenceX.getArray()[nPolygonIndex];
+ drawing::DoubleSequence* pOuterSequenceY = &aUnoPoly.SequenceY.getArray()[nPolygonIndex];
+ drawing::DoubleSequence* pOuterSequenceZ = &aUnoPoly.SequenceZ.getArray()[nPolygonIndex];
+ pOuterSequenceX->realloc(rPoints[nPolygonIndex].size());
+ pOuterSequenceY->realloc(rPoints[nPolygonIndex].size());
+ pOuterSequenceZ->realloc(rPoints[nPolygonIndex].size());
+ double* pInnerSequenceX = pOuterSequenceX->getArray();
+ double* pInnerSequenceY = pOuterSequenceY->getArray();
+ double* pInnerSequenceZ = pOuterSequenceZ->getArray();
+ for (std::size_t nPointIndex=0; nPointIndex<rPoints[nPolygonIndex].size(); ++nPointIndex)
+ {
+ auto& rPos = rPoints[nPolygonIndex][nPointIndex];
+ pInnerSequenceX[nPointIndex] = rPos.PositionX;
+ pInnerSequenceY[nPointIndex] = rPos.PositionY;
+ pInnerSequenceZ[nPointIndex] = rPos.PositionZ;
+ }
+ }
+ return aUnoPoly;
+}
+
+} // end anonymous namespace
+
+rtl::Reference<SvxShapeGroupAnyD> ShapeFactory::getOrCreateChartRootShape(
+ const rtl::Reference<SvxDrawPage>& xDrawPage )
+{
+ rtl::Reference<SvxShapeGroupAnyD> xRet = ShapeFactory::getChartRootShape(xDrawPage);
+ if (xRet.is())
+ return xRet;
+
+ // Create a new root shape and set it to the bottom of the page. The root
+ // shape is identified by having the name com.sun.star.chart2.shapes.
+ rtl::Reference<SvxShapeGroup> xShapeGroup = new SvxShapeGroup(nullptr, nullptr);
+ xShapeGroup->setShapeKind(SdrObjKind::Group);
+ // cast to resolve ambiguity in converting to XShape
+ xDrawPage->addBottom(static_cast<SvxShape*>(xShapeGroup.get()));
+
+ setShapeName(xShapeGroup, "com.sun.star.chart2.shapes");
+ xShapeGroup->setSize(awt::Size(0,0));
+
+ return xShapeGroup;
+}
+
+void ShapeFactory::setPageSize(const rtl::Reference<SvxShapeGroupAnyD>&, const awt::Size&) {}
+
+// diverse tools::PolyPolygon create methods
+
+static uno::Any createPolyPolygon_Cube(
+ const drawing::Direction3D& rSize, double fRoundedEdge, bool bRounded )
+{
+ OSL_PRECOND(fRoundedEdge>=0, "fRoundedEdge needs to be >= 0");
+
+ // always use extra points, so set percent diagonal to 0.4 which is 0% in the UI (old Chart comment)
+ if( fRoundedEdge == 0.0 && bRounded)
+ fRoundedEdge = 0.4 / 200.0;
+ else if(!bRounded)
+ fRoundedEdge = 0.0;
+
+ //fWidthH stands for Half Width
+ const double fWidthH = rSize.DirectionX >=0.0? rSize.DirectionX/2.0 : -rSize.DirectionX/2.0;
+ const double fHeight = rSize.DirectionY;
+
+ const double fHeightSign = fHeight >= 0.0 ? 1.0 : -1.0;
+
+ const double fOffset = (fWidthH * fRoundedEdge) * 1.05; // increase by 5% for safety
+ const bool bRoundEdges = fRoundedEdge && fOffset < fWidthH && 2.0 * fOffset < fHeightSign*fHeight;
+ const sal_Int32 nPointCount = bRoundEdges ? 13 : 5;
+
+ drawing::PolyPolygonShape3D aPP;
+
+ aPP.SequenceX.realloc(1);
+ aPP.SequenceY.realloc(1);
+ aPP.SequenceZ.realloc(1);
+
+ drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray();
+ drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray();
+ drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray();
+
+ pOuterSequenceX->realloc(nPointCount);
+ pOuterSequenceY->realloc(nPointCount);
+ pOuterSequenceZ->realloc(nPointCount);
+
+ double* pInnerSequenceX = pOuterSequenceX->getArray();
+ double* pInnerSequenceY = pOuterSequenceY->getArray();
+ double* pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ for(sal_Int32 nN = nPointCount; nN--;)
+ *pInnerSequenceZ++ = 0.0;
+
+ if(nPointCount == 5)
+ {
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceY++ = fHeight;
+ *pInnerSequenceY++ = fHeight;
+ *pInnerSequenceY++ = 0.0;
+
+ *pInnerSequenceX++ = -fWidthH;
+ *pInnerSequenceX++ = fWidthH;
+ *pInnerSequenceX++ = fWidthH;
+ *pInnerSequenceX++ = -fWidthH;
+ *pInnerSequenceX++ = -fWidthH;
+ }
+ else
+ {
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceY++ = fHeightSign*fOffset;
+ *pInnerSequenceY++ = fHeight - fHeightSign*fOffset;
+ *pInnerSequenceY++ = fHeight;
+ *pInnerSequenceY++ = fHeight;
+ *pInnerSequenceY++ = fHeight;
+ *pInnerSequenceY++ = fHeight;
+ *pInnerSequenceY++ = fHeight - fHeightSign*fOffset;
+ *pInnerSequenceY++ = fHeightSign*fOffset;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+
+ *pInnerSequenceX++ = -fWidthH + fOffset;
+ *pInnerSequenceX++ = fWidthH - fOffset;
+ *pInnerSequenceX++ = fWidthH;
+ *pInnerSequenceX++ = fWidthH;
+ *pInnerSequenceX++ = fWidthH;
+ *pInnerSequenceX++ = fWidthH;
+ *pInnerSequenceX++ = fWidthH - fOffset;
+ *pInnerSequenceX++ = -fWidthH + fOffset;
+ *pInnerSequenceX++ = -fWidthH;
+ *pInnerSequenceX++ = -fWidthH;
+ *pInnerSequenceX++ = -fWidthH;
+ *pInnerSequenceX++ = -fWidthH;
+ *pInnerSequenceX++ = -fWidthH + fOffset;
+ }
+ return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get());
+}
+
+static uno::Any createPolyPolygon_Cylinder(
+ double fHeight
+ , double fRadius
+ , sal_Int32& nVerticalSegmentCount )
+{
+ //fHeight may be negative
+ OSL_PRECOND(fRadius>0, "The radius of a cylinder needs to be > 0");
+
+ drawing::PolyPolygonShape3D aPP;
+
+ nVerticalSegmentCount=1;
+
+ aPP.SequenceX.realloc(3);
+ aPP.SequenceY.realloc(3);
+ aPP.SequenceZ.realloc(3);
+
+ drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray();
+ drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray();
+ drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray();
+
+ pOuterSequenceX->realloc(2);
+ pOuterSequenceY->realloc(2);
+ pOuterSequenceZ->realloc(2);
+
+ double* pInnerSequenceX = pOuterSequenceX->getArray();
+ double* pInnerSequenceY = pOuterSequenceY->getArray();
+ double* pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ double fY1 = 0.0;
+ double fY2 = fHeight;
+
+ if( fHeight<0.0 )
+ std::swap(fY1,fY2);
+
+ for(sal_Int32 nN = 2; nN--;)
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = fY1;
+
+ *pInnerSequenceX++ = fRadius;
+ *pInnerSequenceY++ = fY1;
+
+ pOuterSequenceX++;pOuterSequenceY++;pOuterSequenceZ++;
+ pOuterSequenceX->realloc(2);
+ pOuterSequenceY->realloc(2);
+ pOuterSequenceZ->realloc(2);
+
+ pInnerSequenceX = pOuterSequenceX->getArray();
+ pInnerSequenceY = pOuterSequenceY->getArray();
+ pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ for(sal_Int32 nN = 2; nN--;)
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = fRadius;
+ *pInnerSequenceY++ = fY1;
+
+ *pInnerSequenceX++ = fRadius;
+ *pInnerSequenceY++ = fY2;
+
+ pOuterSequenceX++;pOuterSequenceY++;pOuterSequenceZ++;
+ pOuterSequenceX->realloc(2);
+ pOuterSequenceY->realloc(2);
+ pOuterSequenceZ->realloc(2);
+
+ pInnerSequenceX = pOuterSequenceX->getArray();
+ pInnerSequenceY = pOuterSequenceY->getArray();
+ pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ for(sal_Int32 nN = 2; nN--;)
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = fRadius;
+ *pInnerSequenceY++ = fY2;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = fY2;
+
+ return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get());
+}
+
+static uno::Any createPolyPolygon_Cone( double fHeight, double fRadius, double fTopHeight
+ , sal_Int32& nVerticalSegmentCount )
+{
+ OSL_PRECOND(fRadius>0, "The radius of a cone needs to be > 0");
+
+ //for stacked charts we need cones without top -> fTopHeight != 0 resp. bTopless == true
+ //fTopHeight indicates the high of the cutted top only (not the full height)
+ bool bTopless = !::rtl::math::approxEqual( fHeight, fHeight + fTopHeight );
+
+ double r1= 0.0, r2 = fRadius;
+ if(bTopless)
+ // #i63212# fHeight may be negative, fTopHeight is always positive -> use fabs(fHeight)
+ r1 = fRadius * fTopHeight/(fabs(fHeight)+fTopHeight);
+
+ nVerticalSegmentCount=1;
+ drawing::PolyPolygonShape3D aPP;
+
+ aPP.SequenceX.realloc(2);
+ aPP.SequenceY.realloc(2);
+ aPP.SequenceZ.realloc(2);
+
+ drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray();
+ drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray();
+ drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray();
+
+ pOuterSequenceX->realloc(2);
+ pOuterSequenceY->realloc(2);
+ pOuterSequenceZ->realloc(2);
+
+ double* pInnerSequenceX = pOuterSequenceX->getArray();
+ double* pInnerSequenceY = pOuterSequenceY->getArray();
+ double* pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ double fX1 = 0.0;
+ double fX2 = r2;
+ double fX3 = r1;
+
+ double fY1 = 0.0;
+ double fY2 = 0.0;
+ double fY3 = fHeight;
+
+ if( fHeight<0.0 )
+ {
+ std::swap(fX1,fX3);
+ std::swap(fY1,fY3);
+ }
+
+ for(sal_Int32 nN = 2; nN--;)
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceY++ = fY1;
+ *pInnerSequenceX++ = fX1;
+
+ *pInnerSequenceY++ = fY2;
+ *pInnerSequenceX++ = fX2;
+
+ pOuterSequenceX++;pOuterSequenceY++;pOuterSequenceZ++;
+ pOuterSequenceX->realloc(2);
+ pOuterSequenceY->realloc(2);
+ pOuterSequenceZ->realloc(2);
+
+ pInnerSequenceX = pOuterSequenceX->getArray();
+ pInnerSequenceY = pOuterSequenceY->getArray();
+ pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ for(sal_Int32 nN = 2; nN--;)
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceY++ = fY2;
+ *pInnerSequenceX++ = fX2;
+
+ *pInnerSequenceY++ = fY3;
+ *pInnerSequenceX++ = fX3;
+
+ return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get());
+}
+
+// methods for 3D shape creation
+
+rtl::Reference<Svx3DExtrudeObject>
+ ShapeFactory::createCube(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize
+ , sal_Int32 nRotateZAngleHundredthDegree
+ , const uno::Reference< beans::XPropertySet >& xSourceProp
+ , const tPropertyNameMap& rPropertyNameMap
+ , bool bRounded )
+{
+ if( !xTarget.is() )
+ return nullptr;
+ if( bRounded )
+ {
+ try
+ {
+ if( xSourceProp.is() )
+ {
+ drawing::LineStyle aLineStyle;
+ xSourceProp->getPropertyValue( "BorderStyle" ) >>= aLineStyle;
+ if( aLineStyle == drawing::LineStyle_SOLID )
+ bRounded = false;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }
+ rtl::Reference<Svx3DExtrudeObject> xShape = impl_createCube( xTarget, rPosition, rSize, nRotateZAngleHundredthDegree, bRounded );
+ if( xSourceProp.is())
+ PropertyMapper::setMappedProperties( *xShape, xSourceProp, rPropertyNameMap );
+ return xShape;
+}
+
+rtl::Reference<Svx3DExtrudeObject>
+ ShapeFactory::impl_createCube(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize
+ , sal_Int32 nRotateZAngleHundredthDegree
+ , bool bRounded )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ //create shape
+ rtl::Reference<Svx3DExtrudeObject> xShape = new Svx3DExtrudeObject(nullptr);
+ xShape->setShapeKind(SdrObjKind::E3D_Extrusion);
+ xTarget->addShape(*xShape);
+
+ //set properties
+ try
+ {
+ //depth
+ double fDepth = rSize.DirectionZ;
+ if (fDepth<0)
+ fDepth*=-1.0;
+
+ //PercentDiagonal
+ sal_Int16 nPercentDiagonal = bRounded ? 3 : 0;
+
+ //Matrix for position
+ basegfx::B3DHomMatrix aHomMatrix;
+ if (nRotateZAngleHundredthDegree != 0)
+ aHomMatrix.rotate(0.0, 0.0, -basegfx::deg2rad<100>(nRotateZAngleHundredthDegree));
+ aHomMatrix.translate(rPosition.PositionX, rPosition.PositionY,
+ rPosition.PositionZ - (fDepth / 2.0));
+
+ uno::Sequence<OUString> aPropertyNames {
+ UNO_NAME_3D_EXTRUDE_DEPTH,
+ UNO_NAME_3D_PERCENT_DIAGONAL,
+ UNO_NAME_3D_POLYPOLYGON3D,
+ UNO_NAME_3D_TRANSFORM_MATRIX,
+ };
+
+ uno::Sequence<uno::Any> aPropertyValues {
+ uno::Any(sal_Int32(fDepth)), // Depth
+ uno::Any(nPercentDiagonal), // PercentDiagonal
+ createPolyPolygon_Cube(rSize, double(nPercentDiagonal) / 200.0, bRounded),
+ uno::Any(B3DHomMatrixToHomogenMatrix(aHomMatrix))
+ };
+
+ xShape->setPropertyValues(aPropertyNames, aPropertyValues);
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<Svx3DLatheObject>
+ ShapeFactory::createCylinder(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize
+ , sal_Int32 nRotateZAngleHundredthDegree )
+{
+ return impl_createConeOrCylinder(
+ xTarget, rPosition, rSize, 0.0, nRotateZAngleHundredthDegree, true );
+}
+
+rtl::Reference<Svx3DSceneObject>
+ ShapeFactory::createPyramid(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize
+ , double fTopHeight, bool bRotateZ
+ , const uno::Reference< beans::XPropertySet >& xSourceProp
+ , const tPropertyNameMap& rPropertyNameMap )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ rtl::Reference<Svx3DSceneObject> xGroup( ShapeFactory::createGroup3D( xTarget ) );
+
+ bool bDoubleSided = false;
+ short nRotatedTexture = 0;
+
+ const double fWidth = rSize.DirectionX;
+ const double fDepth = rSize.DirectionZ;
+ const double fHeight = rSize.DirectionY;
+
+ drawing::Position3D aBottomP1( rPosition.PositionX, rPosition.PositionY, rPosition.PositionZ - fDepth/2.0 );
+ if(bRotateZ)
+ aBottomP1.PositionY -= fWidth/2.0;
+ else
+ aBottomP1.PositionX -= fWidth/2.0;
+ drawing::Position3D aBottomP2( aBottomP1 );
+ if(bRotateZ)
+ aBottomP2.PositionY += fWidth;
+ else
+ aBottomP2.PositionX += fWidth;
+ drawing::Position3D aBottomP3( aBottomP2 );
+ drawing::Position3D aBottomP4( aBottomP1 );
+ aBottomP3.PositionZ += fDepth;
+ aBottomP4.PositionZ += fDepth;
+
+ const double fTopFactor = fTopHeight/(fabs(fHeight)+fTopHeight);
+ drawing::Position3D aTopP1( rPosition.PositionX, rPosition.PositionY, rPosition.PositionZ - fDepth*fTopFactor/2.0 );
+ if(bRotateZ)
+ {
+ aTopP1.PositionY -= fWidth*fTopFactor/2.0;
+ aTopP1.PositionX += fHeight;
+ }
+ else
+ {
+ aTopP1.PositionX -= fWidth*fTopFactor/2.0;
+ aTopP1.PositionY += fHeight;
+ }
+ drawing::Position3D aTopP2( aTopP1 );
+ if(bRotateZ)
+ aTopP2.PositionY += fWidth*fTopFactor;
+ else
+ aTopP2.PositionX += fWidth*fTopFactor;
+ drawing::Position3D aTopP3( aTopP2 );
+ drawing::Position3D aTopP4( aTopP1 );
+ aTopP3.PositionZ += fDepth*fTopFactor;
+ aTopP4.PositionZ += fDepth*fTopFactor;
+
+ Stripe aStripeBottom( aBottomP1, aBottomP4, aBottomP3, aBottomP2 );
+
+ drawing::Position3D aNormalsBottomP1( aBottomP1 );
+ drawing::Position3D aNormalsBottomP2( aBottomP2 );
+ drawing::Position3D aNormalsBottomP3( aBottomP3 );
+ drawing::Position3D aNormalsBottomP4( aBottomP4 );
+ drawing::Position3D aNormalsTopP1( aBottomP1 );
+ drawing::Position3D aNormalsTopP2( aBottomP2 );
+ drawing::Position3D aNormalsTopP3( aBottomP3 );
+ drawing::Position3D aNormalsTopP4( aBottomP4 );
+ if( bRotateZ )
+ {
+ aNormalsTopP1.PositionX += fHeight;
+ aNormalsTopP2.PositionX += fHeight;
+ aNormalsTopP3.PositionX += fHeight;
+ aNormalsTopP4.PositionX += fHeight;
+ }
+ else
+ {
+ aNormalsTopP1.PositionY += fHeight;
+ aNormalsTopP2.PositionY += fHeight;
+ aNormalsTopP3.PositionY += fHeight;
+ aNormalsTopP4.PositionY += fHeight;
+ }
+
+ bool bInvertPolygon = false;
+ bool bInvertNormals = false;
+
+ if(bRotateZ)
+ {
+ //bars
+ if(fHeight>=0.0)
+ {
+ nRotatedTexture = 2;
+ bInvertNormals = true;
+ aStripeBottom = Stripe( aBottomP1, aBottomP4, aBottomP3, aBottomP2 );
+ }
+ else
+ {
+ bInvertPolygon = true;
+ nRotatedTexture = 1;
+ aStripeBottom = Stripe( aBottomP2, aBottomP3, aBottomP4, aBottomP1 );
+ }
+ }
+ else
+ {
+ //columns
+ if(fHeight>=0.0)
+ {
+ bInvertPolygon = true;
+ nRotatedTexture = 2;
+ aStripeBottom = Stripe( aBottomP2, aBottomP3, aBottomP4, aBottomP1 );
+ }
+ else
+ {
+ nRotatedTexture = 3;
+ bInvertNormals = true;
+ aStripeBottom = Stripe( aBottomP4, aBottomP3, aBottomP2, aBottomP1 );
+ }
+ }
+ aStripeBottom.InvertNormal(true);
+
+ Stripe aStripe1( aTopP2, aTopP1, aBottomP1, aBottomP2 );
+ Stripe aStripe2( aTopP3, aTopP2, aBottomP2, aBottomP3 );
+ Stripe aStripe3( aTopP4, aTopP3, aBottomP3, aBottomP4 );
+ Stripe aStripe4( aTopP1, aTopP4, aBottomP4, aBottomP1 );
+
+ if( bInvertPolygon )
+ {
+ aStripe1 = Stripe( aBottomP1, aTopP1, aTopP2, aBottomP2 );
+ aStripe2 = Stripe( aBottomP2, aTopP2, aTopP3, aBottomP3 );
+ aStripe3 = Stripe( aBottomP3, aTopP3, aTopP4, aBottomP4 );
+ aStripe4 = Stripe( aBottomP4, aTopP4, aTopP1, aBottomP1 );
+ }
+
+ Stripe aNormalsStripe1( aNormalsTopP1, aNormalsBottomP1, aNormalsBottomP2, aNormalsTopP2 );
+ Stripe aNormalsStripe2( aNormalsTopP2, aNormalsBottomP2, aNormalsBottomP3, aNormalsTopP3 );
+ Stripe aNormalsStripe3( aNormalsTopP3, aNormalsBottomP3, aNormalsBottomP4, aNormalsTopP4 );
+ Stripe aNormalsStripe4( aNormalsTopP4, aNormalsBottomP4, aNormalsBottomP1, aNormalsTopP1 );
+
+ if( bInvertNormals )
+ {
+ aNormalsStripe1 = Stripe( aNormalsTopP2, aNormalsBottomP2, aNormalsBottomP1, aNormalsTopP1 );
+ aNormalsStripe2 = Stripe( aNormalsTopP3, aNormalsBottomP3, aNormalsBottomP2, aNormalsTopP2 );
+ aNormalsStripe3 = Stripe( aNormalsTopP4, aNormalsBottomP4, aNormalsBottomP3, aNormalsTopP3 );
+ aNormalsStripe4 = Stripe( aNormalsTopP1, aNormalsBottomP1, aNormalsBottomP4, aNormalsTopP4 );
+ }
+
+ aStripe1.SetManualNormal( aNormalsStripe1.getNormal() );
+ aStripe2.SetManualNormal( aNormalsStripe2.getNormal() );
+ aStripe3.SetManualNormal( aNormalsStripe3.getNormal() );
+ aStripe4.SetManualNormal( aNormalsStripe4.getNormal() );
+
+ const bool bFlatNormals = false;
+ ShapeFactory::createStripe( xGroup, aStripe1, xSourceProp, rPropertyNameMap, bDoubleSided, nRotatedTexture, bFlatNormals );
+ ShapeFactory::createStripe( xGroup, aStripe2, xSourceProp, rPropertyNameMap, bDoubleSided, nRotatedTexture, bFlatNormals );
+ ShapeFactory::createStripe( xGroup, aStripe3, xSourceProp, rPropertyNameMap, bDoubleSided, nRotatedTexture, bFlatNormals );
+ ShapeFactory::createStripe( xGroup, aStripe4, xSourceProp, rPropertyNameMap, bDoubleSided, nRotatedTexture, bFlatNormals );
+ ShapeFactory::createStripe( xGroup, aStripeBottom, xSourceProp, rPropertyNameMap, bDoubleSided, nRotatedTexture, bFlatNormals );
+
+ return xGroup;
+}
+
+rtl::Reference<Svx3DLatheObject>
+ ShapeFactory::createCone(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize
+ , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree )
+{
+ return impl_createConeOrCylinder( xTarget, rPosition, rSize, fTopHeight, nRotateZAngleHundredthDegree, false );
+}
+
+rtl::Reference<Svx3DLatheObject>
+ ShapeFactory::impl_createConeOrCylinder(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize
+ , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree
+ , bool bCylinder )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ //create shape
+ rtl::Reference<Svx3DLatheObject> xShape = new Svx3DLatheObject(nullptr);
+ xShape->setShapeKind(SdrObjKind::E3D_Lathe);
+ xTarget->addShape(*xShape);
+
+ double fWidth = rSize.DirectionX/2.0; //The depth will be corrected within Matrix
+ double fRadius = fWidth; //!!!!!!!! problem in drawing layer: rotation object calculates wrong needed size -> wrong camera (it's a problem with bounding boxes)
+ double fHeight = rSize.DirectionY;
+
+ //set properties
+ try
+ {
+ //Polygon
+ sal_Int32 nVerticalSegmentCount = 0;
+ uno::Any aPPolygon = bCylinder
+ ? createPolyPolygon_Cylinder(fHeight, fRadius, nVerticalSegmentCount)
+ : createPolyPolygon_Cone(fHeight, fRadius, fTopHeight, nVerticalSegmentCount);
+
+ //Matrix for position
+ basegfx::B3DHomMatrix aHomMatrix;
+ if (nRotateZAngleHundredthDegree != 0)
+ aHomMatrix.rotate(0.0,0.0,-basegfx::deg2rad<100>(nRotateZAngleHundredthDegree));
+ //stretch the symmetric objects to given depth
+ aHomMatrix.scale(1.0,1.0,rSize.DirectionZ/rSize.DirectionX);
+ aHomMatrix.translate(rPosition.PositionX, rPosition.PositionY, rPosition.PositionZ);
+
+ uno::Sequence<OUString> aPropertyNames{
+ UNO_NAME_3D_PERCENT_DIAGONAL,
+ UNO_NAME_3D_POLYPOLYGON3D,
+ UNO_NAME_3D_TRANSFORM_MATRIX,
+ UNO_NAME_3D_HORZ_SEGS,
+ UNO_NAME_3D_VERT_SEGS,
+ UNO_NAME_3D_REDUCED_LINE_GEOMETRY
+ };
+
+ uno::Sequence<uno::Any> aPropertyValues {
+ uno::Any(sal_Int16(5)), // PercentDiagonal
+ aPPolygon, // Polygon
+ uno::Any(B3DHomMatrixToHomogenMatrix(aHomMatrix)), // Matrix
+ uno::Any(CHART_3DOBJECT_SEGMENTCOUNT), // Horizontal Segments
+ uno::Any(nVerticalSegmentCount), // Vertical Segments
+ uno::Any(true) // Reduced lines
+ };
+
+ xShape->setPropertyValues(aPropertyNames, aPropertyValues);
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+static void appendAndCloseBezierCoords( drawing::PolyPolygonBezierCoords& rReturn, const drawing::PolyPolygonBezierCoords& rAdd, bool bAppendInverse )
+{
+ if(!rAdd.Coordinates.hasElements())
+ return;
+ sal_Int32 nAddCount = rAdd.Coordinates[0].getLength();
+ if(!nAddCount)
+ return;
+
+ sal_Int32 nOldCount = rReturn.Coordinates[0].getLength();
+
+ auto pCoordinates = rReturn.Coordinates.getArray();
+ pCoordinates[0].realloc(nOldCount + nAddCount + 1);
+ auto pCoordinates0 = pCoordinates[0].getArray();
+ auto pFlags = rReturn.Flags.getArray();
+ pFlags[0].realloc(nOldCount+nAddCount+1);
+ auto pFlags0 = pFlags[0].getArray();
+
+ for(sal_Int32 nN=0;nN<nAddCount; nN++ )
+ {
+ sal_Int32 nAdd = bAppendInverse ? (nAddCount-1-nN) : nN;
+ pCoordinates0[nOldCount+nN] = rAdd.Coordinates[0][nAdd];
+ pFlags0[nOldCount+nN] = rAdd.Flags[0][nAdd];
+ }
+
+ //close
+ pCoordinates0[nOldCount+nAddCount] = rReturn.Coordinates[0][0];
+ pFlags0[nOldCount+nAddCount] = rReturn.Flags[0][0];
+}
+
+static drawing::PolyPolygonBezierCoords getCircularArcBezierCoords(
+ double fStartAngleRadian, double fWidthAngleRadian, double fUnitRadius
+ , const ::basegfx::B2DHomMatrix& rTransformationFromUnitCircle
+ , const double fAngleSubdivisionRadian )
+{
+ //at least one polygon is created using two normal and two control points
+ //if the angle is larger it is separated into multiple sub angles
+
+ drawing::PolyPolygonBezierCoords aReturn;
+ sal_Int32 nSegmentCount = static_cast< sal_Int32 >( fWidthAngleRadian/fAngleSubdivisionRadian );
+ if( fWidthAngleRadian > fAngleSubdivisionRadian*nSegmentCount )
+ nSegmentCount++;
+
+ double fFirstSegmentAngle = fAngleSubdivisionRadian;
+ double fLastSegmentAngle = fAngleSubdivisionRadian;
+ if(nSegmentCount==1)
+ {
+ fFirstSegmentAngle = fWidthAngleRadian;
+ fLastSegmentAngle = 0.0;
+ }
+ else
+ {
+ double fFirstAngleOnSubDivision = (static_cast<sal_Int32>(fStartAngleRadian/fAngleSubdivisionRadian)+1)*fAngleSubdivisionRadian;
+ if( !::rtl::math::approxEqual( fStartAngleRadian, fFirstAngleOnSubDivision ) )
+ fFirstSegmentAngle = fFirstAngleOnSubDivision-fStartAngleRadian;
+
+ if(nSegmentCount>1)
+ {
+ fLastSegmentAngle = fWidthAngleRadian-fFirstSegmentAngle-fAngleSubdivisionRadian*(nSegmentCount-2);
+ if( fLastSegmentAngle<0 )
+ nSegmentCount--;
+ if( fLastSegmentAngle>fAngleSubdivisionRadian )
+ {
+ fLastSegmentAngle-=fAngleSubdivisionRadian;
+ nSegmentCount++;
+ }
+ }
+ }
+
+ sal_Int32 nPointCount = 1 + 3*nSegmentCount; //first point of next segment equals last point of former segment
+
+ drawing::PointSequence aPoints(nPointCount);
+ auto pPoints = aPoints.getArray();
+ drawing::FlagSequence aFlags(nPointCount);
+ auto pFlags = aFlags.getArray();
+
+ //!! applying matrix to vector does ignore translation, so it is important to use a B2DPoint here instead of B2DVector
+ ::basegfx::B2DPoint P0,P1,P2,P3;
+
+ sal_Int32 nPoint=0;
+ double fCurrentRotateAngle = fStartAngleRadian;
+ for(sal_Int32 nSegment=0; nSegment<nSegmentCount; nSegment++)
+ {
+ double fCurrentSegmentAngle = fAngleSubdivisionRadian;
+ if(nSegment==0)//first segment gets only a smaller peace until the next subdivision
+ fCurrentSegmentAngle = fFirstSegmentAngle;
+ else if(nSegment==(nSegmentCount-1)) //the last segment gets the rest angle that does not fit into equal pieces
+ fCurrentSegmentAngle = fLastSegmentAngle;
+
+ //first create untransformed points for a unit circle arc:
+ const double fCos = cos(fCurrentSegmentAngle/2.0);
+ const double fSin = sin(fCurrentSegmentAngle/2.0);
+ P0.setX(fCos);
+ P3.setX(fCos);
+ P0.setY(-fSin);
+ P3.setY(-P0.getY());
+
+ P1.setX((4.0-fCos)/3.0);
+ P2.setX(P1.getX());
+ P1.setY((1.0-fCos)*(fCos-3.0)/(3.0*fSin));
+ P2.setY(-P1.getY());
+ //transform thus startangle equals NULL
+ ::basegfx::B2DHomMatrix aStart;
+ aStart.rotate(fCurrentSegmentAngle/2.0 + fCurrentRotateAngle );
+ fCurrentRotateAngle+=fCurrentSegmentAngle;
+
+ aStart.scale( fUnitRadius, fUnitRadius );
+
+ //apply given transformation to get final points
+ P0 = rTransformationFromUnitCircle*(aStart*P0);
+ P1 = rTransformationFromUnitCircle*(aStart*P1);
+ P2 = rTransformationFromUnitCircle*(aStart*P2);
+ P3 = rTransformationFromUnitCircle*(aStart*P3);
+
+ pPoints[nPoint].X = static_cast< sal_Int32 >( P0.getX());
+ pPoints[nPoint].Y = static_cast< sal_Int32 >( P0.getY());
+ pFlags [nPoint++] = drawing::PolygonFlags_NORMAL;
+
+ pPoints[nPoint].X = static_cast< sal_Int32 >( P1.getX());
+ pPoints[nPoint].Y = static_cast< sal_Int32 >( P1.getY());
+ pFlags[nPoint++] = drawing::PolygonFlags_CONTROL;
+
+ pPoints[nPoint].X = static_cast< sal_Int32 >( P2.getX());
+ pPoints[nPoint].Y = static_cast< sal_Int32 >( P2.getY());
+ pFlags [nPoint++] = drawing::PolygonFlags_CONTROL;
+
+ if(nSegment==(nSegmentCount-1))
+ {
+ pPoints[nPoint].X = static_cast< sal_Int32 >( P3.getX());
+ pPoints[nPoint].Y = static_cast< sal_Int32 >( P3.getY());
+ pFlags [nPoint++] = drawing::PolygonFlags_NORMAL;
+ }
+ }
+
+ aReturn.Coordinates = { aPoints };
+ aReturn.Flags = { aFlags };
+
+ return aReturn;
+}
+
+static drawing::PolyPolygonBezierCoords getRingBezierCoords(
+ double fUnitCircleInnerRadius
+ , double fUnitCircleOuterRadius
+ , double fStartAngleRadian, double fWidthAngleRadian
+ , const ::basegfx::B2DHomMatrix& aTransformationFromUnitCircle
+ , const double fAngleSubdivisionRadian )
+{
+ drawing::PolyPolygonBezierCoords aReturn;
+
+ drawing::PolyPolygonBezierCoords aOuterArc = getCircularArcBezierCoords(
+ fStartAngleRadian, fWidthAngleRadian, fUnitCircleOuterRadius, aTransformationFromUnitCircle, fAngleSubdivisionRadian );
+ aReturn.Coordinates = { aOuterArc.Coordinates[0] };
+ aReturn.Flags = { aOuterArc.Flags[0] };
+
+ drawing::PolyPolygonBezierCoords aInnerArc = getCircularArcBezierCoords(
+ fStartAngleRadian, fWidthAngleRadian, fUnitCircleInnerRadius, aTransformationFromUnitCircle, fAngleSubdivisionRadian );
+ appendAndCloseBezierCoords( aReturn, aInnerArc, true );
+
+ return aReturn;
+}
+
+rtl::Reference<SvxShapePolyPolygon>
+ ShapeFactory::createPieSegment2D(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree
+ , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius
+ , const drawing::Direction3D& rOffset
+ , const drawing::HomogenMatrix& rUnitCircleToScene )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ // tdf#123504: both 0 and 360 are valid and different values here!
+ while (fUnitCircleWidthAngleDegree > 360)
+ fUnitCircleWidthAngleDegree -= 360.0;
+ while (fUnitCircleWidthAngleDegree < 0)
+ fUnitCircleWidthAngleDegree += 360.0;
+
+ //create shape
+ rtl::Reference<SvxShapePolyPolygon> xShape = new SvxShapePolyPolygon(nullptr);
+ xShape->setShapeKind(SdrObjKind::PathFill); // aka ClosedBezierShape
+ xTarget->addShape(*xShape); //need to add the shape before setting of properties
+
+ //set properties
+ try
+ {
+ ::basegfx::B2DHomMatrix aTransformationFromUnitCircle( IgnoreZ( HomogenMatrixToB3DHomMatrix(rUnitCircleToScene) ) );
+ aTransformationFromUnitCircle.translate(rOffset.DirectionX,rOffset.DirectionY);
+
+ const double fAngleSubdivisionRadian = M_PI/10.0;
+
+ drawing::PolyPolygonBezierCoords aCoords
+ = getRingBezierCoords(fUnitCircleInnerRadius, fUnitCircleOuterRadius,
+ basegfx::deg2rad(fUnitCircleStartAngleDegree),
+ basegfx::deg2rad(fUnitCircleWidthAngleDegree),
+ aTransformationFromUnitCircle, fAngleSubdivisionRadian);
+
+ xShape->SvxShape::setPropertyValue( "PolyPolygonBezier", uno::Any( aCoords ) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ return xShape;
+}
+
+rtl::Reference<Svx3DExtrudeObject>
+ ShapeFactory::createPieSegment(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree
+ , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius
+ , const drawing::Direction3D& rOffset
+ , const drawing::HomogenMatrix& rUnitCircleToScene
+ , double fDepth )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ // tdf#123504: both 0 and 360 are valid and different values here!
+ while (fUnitCircleWidthAngleDegree > 360)
+ fUnitCircleWidthAngleDegree -= 360.0;
+ while (fUnitCircleWidthAngleDegree < 0)
+ fUnitCircleWidthAngleDegree += 360.0;
+
+ //create shape
+ rtl::Reference<Svx3DExtrudeObject> xShape = new Svx3DExtrudeObject(nullptr);
+ xShape->setShapeKind(SdrObjKind::E3D_Extrusion);
+ xTarget->addShape(*xShape); //need to add the shape before setting of properties
+
+ //set properties
+ try
+ {
+ ::basegfx::B2DHomMatrix aTransformationFromUnitCircle( IgnoreZ( HomogenMatrixToB3DHomMatrix(rUnitCircleToScene) ) );
+ aTransformationFromUnitCircle.translate(rOffset.DirectionX,rOffset.DirectionY);
+
+ const double fAngleSubdivisionRadian = M_PI/32.0;
+
+ drawing::PolyPolygonBezierCoords aCoords
+ = getRingBezierCoords(fUnitCircleInnerRadius, fUnitCircleOuterRadius,
+ basegfx::deg2rad(fUnitCircleStartAngleDegree),
+ basegfx::deg2rad(fUnitCircleWidthAngleDegree),
+ aTransformationFromUnitCircle, fAngleSubdivisionRadian);
+
+ //depth
+ xShape->setPropertyValue( UNO_NAME_3D_EXTRUDE_DEPTH
+ , uno::Any(static_cast<sal_Int32>(fDepth)) );
+
+ //PercentDiagonal
+ xShape->setPropertyValue( UNO_NAME_3D_PERCENT_DIAGONAL
+ , uno::Any( sal_Int16(0) ) );
+
+ //Polygon
+ drawing::PolyPolygonShape3D aPoly( BezierToPoly(aCoords) );
+ ShapeFactory::closePolygon( aPoly );
+ xShape->setPropertyValue( UNO_NAME_3D_POLYPOLYGON3D
+ , uno::Any( aPoly ) );
+
+ //DoubleSided
+ xShape->setPropertyValue( UNO_NAME_3D_DOUBLE_SIDED
+ , uno::Any( true ) );
+
+ //Reduced lines
+ xShape->setPropertyValue( UNO_NAME_3D_REDUCED_LINE_GEOMETRY
+ , uno::Any( true ) );
+
+ //TextureProjectionMode
+ xShape->setPropertyValue( UNO_NAME_3D_TEXTURE_PROJ_Y
+ , uno::Any( drawing::TextureProjectionMode_OBJECTSPECIFIC ) );
+
+ //TextureProjectionMode
+ xShape->setPropertyValue( UNO_NAME_3D_TEXTURE_PROJ_X
+ , uno::Any( drawing::TextureProjectionMode_PARALLEL ) );
+ xShape->setPropertyValue( UNO_NAME_3D_TEXTURE_PROJ_Y
+ , uno::Any( drawing::TextureProjectionMode_OBJECTSPECIFIC ) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<Svx3DPolygonObject>
+ ShapeFactory::createStripe( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const Stripe& rStripe
+ , const uno::Reference< beans::XPropertySet >& xSourceProp
+ , const tPropertyNameMap& rPropertyNameMap
+ , bool bDoubleSided
+ , short nRotatedTexture
+ , bool bFlatNormals )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ //create shape
+ rtl::Reference<Svx3DPolygonObject> xShape = new Svx3DPolygonObject(nullptr);
+ xShape->setShapeKind(SdrObjKind::E3D_Polygon);
+ xTarget->addShape(*xShape);
+
+ //set properties
+ try
+ {
+ uno::Sequence<OUString> aPropertyNames{
+ UNO_NAME_3D_POLYPOLYGON3D,
+ UNO_NAME_3D_TEXTUREPOLYGON3D,
+ UNO_NAME_3D_NORMALSPOLYGON3D,
+ UNO_NAME_3D_LINEONLY,
+ UNO_NAME_3D_DOUBLE_SIDED
+ };
+
+ uno::Sequence<uno::Any> aPropertyValues {
+ rStripe.getPolyPolygonShape3D(), // Polygon
+ Stripe::getTexturePolygon(nRotatedTexture), // TexturePolygon
+ rStripe.getNormalsPolygon(), // Normals Polygon
+ uno::Any(false), // LineOnly
+ uno::Any(bDoubleSided) // DoubleSided
+ };
+
+ //NormalsKind
+ if (bFlatNormals)
+ lcl_addProperty(aPropertyNames, aPropertyValues,
+ UNO_NAME_3D_NORMALS_KIND, uno::Any(drawing::NormalsKind_FLAT));
+
+ xShape->setPropertyValues(aPropertyNames, aPropertyValues);
+
+ if (xSourceProp)
+ {
+ PropertyMapper::setMappedProperties(*xShape, xSourceProp, rPropertyNameMap);
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<Svx3DExtrudeObject>
+ ShapeFactory::createArea3D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const std::vector<std::vector<css::drawing::Position3D>>& rPolyPolygon
+ , double fDepth )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ if( rPolyPolygon.empty() )
+ return nullptr;
+
+ //create shape
+ rtl::Reference<Svx3DExtrudeObject> xShape = new Svx3DExtrudeObject(nullptr);
+ xShape->setShapeKind(SdrObjKind::E3D_Extrusion);
+ xTarget->addShape(*xShape);
+
+ css::drawing::PolyPolygonShape3D aUnoPolyPolygon = toPolyPolygonShape3D(rPolyPolygon);
+
+ //set properties
+ try
+ {
+ uno::Sequence<OUString> aPropertyNames{
+ UNO_NAME_3D_EXTRUDE_DEPTH,
+ UNO_NAME_3D_PERCENT_DIAGONAL,
+ UNO_NAME_3D_POLYPOLYGON3D,
+ UNO_NAME_3D_DOUBLE_SIDED,
+ };
+
+ uno::Sequence<uno::Any> aPropertyValues {
+ uno::Any(sal_Int32(fDepth)), // depth
+ uno::Any(sal_Int16(0)), // PercentDiagonal
+ uno::Any(aUnoPolyPolygon), // Polygon
+ uno::Any(true) // DoubleSided
+ };
+
+ //the z component of the polygon is now ignored by the drawing layer,
+ //so we need to translate the object via transformation matrix
+
+ //Matrix for position
+ if (!rPolyPolygon.empty() && !rPolyPolygon[0].empty())
+ {
+ basegfx::B3DHomMatrix aM;
+ aM.translate(0, 0, rPolyPolygon[0][0].PositionZ);
+ drawing::HomogenMatrix aHM = B3DHomMatrixToHomogenMatrix(aM);
+ lcl_addProperty(aPropertyNames, aPropertyValues, UNO_NAME_3D_TRANSFORM_MATRIX, uno::Any(aHM));
+ }
+ xShape->setPropertyValues(aPropertyNames, aPropertyValues);
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<SvxShapePolyPolygon>
+ ShapeFactory::createArea2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const std::vector<std::vector<css::drawing::Position3D>>& rPolyPolygon )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ //create shape
+ SdrPathObj* pPath = new SdrPathObj(xTarget->GetSdrObject()->getSdrModelFromSdrObject(), SdrObjKind::Polygon);
+ xTarget->GetSdrObject()->GetSubList()->InsertObject(pPath);
+
+ //set properties
+ try
+ {
+ // Polygon
+ basegfx::B2DPolyPolygon aNewPolyPolygon( PolyToB2DPolyPolygon(rPolyPolygon) );
+ // tdf#117145 metric of SdrModel is app-specific, metric of UNO API is 100thmm
+ pPath->ForceMetricToItemPoolMetric(aNewPolyPolygon);
+ pPath->SetPathPoly(aNewPolyPolygon);
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return static_cast<SvxShapePolyPolygon*>(pPath->getUnoShape().get());
+}
+
+static drawing::PointSequenceSequence createPolyPolygon_Symbol( const drawing::Position3D& rPos
+ , const drawing::Direction3D& rSize
+ , sal_Int32 nStandardSymbol )
+{
+ if(nStandardSymbol<0)
+ nStandardSymbol*=-1;
+ nStandardSymbol = nStandardSymbol%ShapeFactory::getSymbolCount();
+ SymbolEnum eSymbolType=static_cast<SymbolEnum>(nStandardSymbol);
+
+ const double& fX = rPos.PositionX;
+ const double& fY = rPos.PositionY;
+
+ const double fWidthH = rSize.DirectionX/2.0; //fWidthH stands for Half Width
+ const double fHeightH = rSize.DirectionY/2.0; //fHeightH stands for Half Height
+
+ const sal_Int32 nQuarterCount = 35; // points inside a quadrant, used in case circle
+
+ sal_Int32 nPointCount = 4; //all arrow symbols only need 4 points
+ switch( eSymbolType )
+ {
+ case Symbol_Square:
+ case Symbol_Diamond:
+ case Symbol_Bowtie:
+ case Symbol_Sandglass:
+ case Symbol_HorizontalBar:
+ case Symbol_VerticalBar:
+ nPointCount = 5;
+ break;
+ case Symbol_X:
+ nPointCount = 13;
+ break;
+ case Symbol_Plus:
+ nPointCount = 13;
+ break;
+ case Symbol_Star:
+ nPointCount = 9;
+ break;
+ case Symbol_Asterisk:
+ nPointCount = 19;
+ break;
+ case Symbol_Circle:
+ nPointCount = 5 + 4 * nQuarterCount;
+ break;
+ default:
+ break;
+ }
+
+ drawing::PointSequenceSequence aPP;
+
+ aPP.realloc(1);
+
+ uno::Sequence<awt::Point>* pOuterSequence = aPP.getArray();
+
+ pOuterSequence->realloc(nPointCount);
+
+ awt::Point* pInnerSequence = pOuterSequence->getArray();
+
+ auto toPoint = [](double x, double y) -> awt::Point
+ {
+ return { static_cast<sal_Int32>(x), static_cast<sal_Int32>(y) };
+ };
+ switch(eSymbolType)
+ {
+ case Symbol_Square:
+ {
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+ break;
+ }
+ case Symbol_UpArrow:
+ {
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+fHeightH );
+ break;
+ }
+ case Symbol_DownArrow:
+ {
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+ break;
+ }
+ case Symbol_RightArrow:
+ {
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+ break;
+ }
+ case Symbol_LeftArrow:
+ {
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY );
+ break;
+ }
+ case Symbol_Bowtie:
+ {
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+ break;
+ }
+ case Symbol_Sandglass:
+ {
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+fHeightH );
+ break;
+ }
+ case Symbol_Diamond:
+ {
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY );
+
+ *pInnerSequence++ = toPoint( fX, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY );
+
+ *pInnerSequence++ = toPoint( fX, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY );
+ break;
+ }
+ case Symbol_HorizontalBar:
+ {
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-0.2*fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY-0.2*fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY+0.2*fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+0.2*fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-0.2*fHeightH );
+ break;
+ }
+ case Symbol_VerticalBar:
+ {
+ *pInnerSequence++ = toPoint( fX-0.2*fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+0.2*fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+0.2*fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-0.2*fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-0.2*fWidthH, fY-fHeightH );
+
+ break;
+ }
+ case Symbol_Circle:
+ {
+ double fOmega = 1.5707963267948966192 / (nQuarterCount + 1.0);
+ // one point in the middle of each edge to get full size bounding rectangle
+ *pInnerSequence++ = toPoint( fX + fWidthH, fY );
+ // 0 to PI/2
+ for (sal_Int32 i = 1; i <= nQuarterCount; ++i)
+ {
+ *pInnerSequence++ = toPoint( fX + fWidthH * cos( i * fOmega ), fY - fHeightH * sin( i * fOmega ) );
+ }
+ // PI/2 to PI
+ *pInnerSequence++ = toPoint( fX, fY - fHeightH );
+ for (sal_Int32 i = 1; i <= nQuarterCount; ++i)
+ {
+ *pInnerSequence++ = toPoint( fX - fWidthH * sin( i * fOmega), fY - fHeightH * cos( i * fOmega) );
+ }
+ // PI to 3/2*PI
+ *pInnerSequence++ = toPoint( fX - fWidthH, fY );
+ for (sal_Int32 i = 1; i <= nQuarterCount; ++i)
+ {
+ *pInnerSequence++ = toPoint( fX - fWidthH * cos( i * fOmega), fY + fHeightH * sin( i * fOmega) );
+ }
+ // 3/2*PI to 2*PI
+ *pInnerSequence++ = toPoint( fX, fY + fHeightH );
+ for (sal_Int32 i = 1; i <= nQuarterCount; ++i)
+ {
+ *pInnerSequence++ = toPoint( fX + fWidthH * sin(i * fOmega), fY + fHeightH * cos(i * fOmega) );
+ }
+ // close polygon
+ *pInnerSequence++ = toPoint( fX + fWidthH, fY );
+ break;
+ }
+ case Symbol_Star:
+ {
+ *pInnerSequence++ = toPoint( fX, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+0.2*fWidthH, fY-0.2*fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY );
+
+ *pInnerSequence++ = toPoint( fX+0.2*fWidthH, fY+0.2*fHeightH );
+
+ *pInnerSequence++ = toPoint( fX, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-0.2*fWidthH, fY+0.2*fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY );
+
+ *pInnerSequence++ = toPoint( fX-0.2*fWidthH, fY-0.2*fHeightH );
+
+ *pInnerSequence++ = toPoint( fX, fY-fHeightH );
+ break;
+ }
+ case Symbol_X:
+ {
+ const double fScaleX = fWidthH / 128.0;
+ const double fScaleY = fHeightH / 128.0;
+ const double fSmall = sqrt(200.0);
+ const double fLarge = 128.0 - fSmall;
+
+ *pInnerSequence++ = toPoint( fX, fY - fScaleY * fSmall );
+
+ *pInnerSequence++ = toPoint( fX - fScaleX * fLarge, fY - fHeightH );
+
+ *pInnerSequence++ = toPoint( fX - fWidthH, fY - fScaleY * fLarge );
+
+ *pInnerSequence++ = toPoint( fX - fScaleX * fSmall, fY );
+
+ *pInnerSequence++ = toPoint( fX - fWidthH, fY + fScaleY * fLarge );
+
+ *pInnerSequence++ = toPoint( fX - fScaleX * fLarge, fY + fHeightH );
+
+ *pInnerSequence++ = toPoint( fX, fY + fScaleY * fSmall );
+
+ *pInnerSequence++ = toPoint( fX + fScaleX * fLarge, fY + fHeightH );
+
+ *pInnerSequence++ = toPoint( fX + fWidthH, fY + fScaleY * fLarge );
+
+ *pInnerSequence++ = toPoint( fX + fScaleX * fSmall, fY );
+
+ *pInnerSequence++ = toPoint( fX + fWidthH, fY - fScaleY * fLarge );
+
+ *pInnerSequence++ = toPoint( fX + fScaleX * fLarge, fY - fHeightH );
+
+ *pInnerSequence++ = toPoint( fX, fY - fScaleY * fSmall );
+ break;
+
+ }
+ case Symbol_Plus:
+ {
+ const double fScaleX = fWidthH / 128.0;
+ const double fScaleY = fHeightH / 128.0;
+ const double fHalf = 10.0; //half line width on 256 size square
+ const double fdX = fScaleX * fHalf;
+ const double fdY = fScaleY * fHalf;
+
+ *pInnerSequence++ = toPoint( fX-fdX, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fdX, fY-fdY );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fdY );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+fdY );
+
+ *pInnerSequence++ = toPoint( fX-fdX, fY+fdY );
+
+ *pInnerSequence++ = toPoint( fX-fdX, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fdX, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fdX, fY+fdY );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY+fdY );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY-fdY );
+
+ *pInnerSequence++ = toPoint( fX+fdX, fY-fdY );
+
+ *pInnerSequence++ = toPoint( fX+fdY, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fdX, fY-fHeightH );
+ break;
+
+ }
+ case Symbol_Asterisk:
+ {
+ const double fHalf = 10.0; // half line width on 256 size square
+ const double fTwoY = fHalf * sqrt(3.0);
+ const double fFourY = (128.0 - 2.0 * fHalf ) / sqrt(3.0);
+ const double fThreeX = 128.0 - fHalf;
+ const double fThreeY = fHalf * sqrt(3.0) + fFourY;
+ const double fFiveX = 2.0 * fHalf;
+
+ const double fScaleX = fWidthH / 128.0;
+ const double fScaleY = fHeightH / 128.0;
+
+ //1
+ *pInnerSequence++ = toPoint( fX-fScaleX * fHalf, fY-fHeightH );
+ //2
+ *pInnerSequence++ = toPoint( fX-fScaleX * fHalf, fY-fScaleY * fTwoY );
+ //3
+ *pInnerSequence++ = toPoint( fX-fScaleX * fThreeX, fY-fScaleY * fThreeY );
+ //4
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fScaleY * fFourY );
+ //5
+ *pInnerSequence++ = toPoint( fX-fScaleX * fFiveX, fY );
+ //6 as 4
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+fScaleY * fFourY );
+ //7 as 3
+ *pInnerSequence++ = toPoint( fX-fScaleX * fThreeX, fY+fScaleY * fThreeY );
+ //8 as 2
+ *pInnerSequence++ = toPoint( fX-fScaleX * fHalf, fY+fScaleY * fTwoY );
+ //9 as 1
+ *pInnerSequence++ = toPoint( fX-fScaleX * fHalf, fY+fHeightH );
+ //10 as 1
+ *pInnerSequence++ = toPoint( fX+fScaleX * fHalf, fY+fHeightH );
+ //11 as 2
+ *pInnerSequence++ = toPoint( fX+fScaleX * fHalf, fY+fScaleY * fTwoY );
+ //12 as 3
+ *pInnerSequence++ = toPoint( fX+fScaleX * fThreeX, fY+fScaleY * fThreeY );
+ //13 as 4
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY+fScaleY * fFourY );
+ //14 as 5
+ *pInnerSequence++ = toPoint( fX+fScaleX * fFiveX, fY );
+ //15 as 4
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY-fScaleY * fFourY );
+ //16 as 3
+ *pInnerSequence++ = toPoint( fX+fScaleX * fThreeX, fY-fScaleY * fThreeY );
+ //17 as 2
+ *pInnerSequence++ = toPoint( fX+fScaleX * fHalf, fY-fScaleY * fTwoY );
+ // 18 as 1
+ *pInnerSequence++ = toPoint( fX+fScaleX * fHalf, fY-fHeightH );
+ // 19 = 1, closing
+ *pInnerSequence++ = toPoint( fX-fScaleX * fHalf, fY-fHeightH );
+ break;
+ }
+ default: //case Symbol_Square:
+ {
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY+fHeightH );
+
+ *pInnerSequence++ = toPoint( fX+fWidthH, fY-fHeightH );
+
+ *pInnerSequence++ = toPoint( fX-fWidthH, fY-fHeightH );
+ break;
+ }
+ }
+
+ return aPP;
+}
+
+rtl::Reference<SvxShapePolyPolygon>
+ ShapeFactory::createSymbol2D(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition
+ , const drawing::Direction3D& rSize
+ , sal_Int32 nStandardSymbol
+ , sal_Int32 nBorderColor
+ , sal_Int32 nFillColor )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ //create shape
+ rtl::Reference<SvxShapePolyPolygon> xShape = new SvxShapePolyPolygon(nullptr);
+ xShape->setShapeKind(SdrObjKind::Polygon);
+ xTarget->addShape(*xShape);
+
+ //set properties
+ try
+ {
+ drawing::PointSequenceSequence aPoints =
+ createPolyPolygon_Symbol( rPosition, rSize, nStandardSymbol );
+
+ //Polygon
+ xShape->SvxShape::setPropertyValue( UNO_NAME_POLYPOLYGON
+ , uno::Any( aPoints ) );
+
+ //LineColor
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINECOLOR
+ , uno::Any( nBorderColor ) );
+
+ //FillColor
+ xShape->SvxShape::setPropertyValue( UNO_NAME_FILLCOLOR
+ , uno::Any( nFillColor ) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<SvxGraphicObject>
+ ShapeFactory::createGraphic2D(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition
+ , const drawing::Direction3D& rSize
+ , const uno::Reference< graphic::XGraphic >& xGraphic )
+{
+ if( !xTarget.is() || !xGraphic.is() )
+ return nullptr;
+
+ // @todo: change this to a rectangle shape with a fill bitmap for
+ // performance reasons (ask AW, said CL)
+
+ //create shape
+ rtl::Reference<SvxGraphicObject> xShape = new SvxGraphicObject(nullptr);
+ xShape->setShapeKind(SdrObjKind::Graphic);
+ xTarget->addShape(*xShape);
+
+ try
+ {
+ // assume position is upper left corner. Transform to center.
+ drawing::Position3D aCenterPosition(
+ rPosition.PositionX - (rSize.DirectionX / 2.0),
+ rPosition.PositionY - (rSize.DirectionY / 2.0),
+ rPosition.PositionZ );
+ xShape->setPosition( Position3DToAWTPoint( aCenterPosition ));
+ xShape->setSize( Direction3DToAWTSize( rSize ));
+ }
+ catch( const uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ try
+ {
+ xShape->SvxShape::setPropertyValue( "Graphic", uno::Any( xGraphic ));
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference< SvxShapeGroup >
+ ShapeFactory::createGroup2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const OUString& aName )
+{
+ if( !xTarget.is() )
+ return nullptr;
+ try
+ {
+ //create and add to target
+ rtl::Reference<SvxShapeGroup> xShapeGroup = new SvxShapeGroup(nullptr, nullptr);
+ xShapeGroup->setShapeKind(SdrObjKind::Group);
+ // cast to resolve ambiguity in converting to XShape
+ xTarget->addShape(*xShapeGroup);
+
+ //set name
+ if(!aName.isEmpty())
+ setShapeName( xShapeGroup, aName );
+
+ {//workaround
+ //need this null size as otherwise empty group shapes where painted with a gray border
+ xShapeGroup->setSize(awt::Size(0,0));
+ }
+
+ return xShapeGroup;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return nullptr;
+}
+
+rtl::Reference< SvxShapeGroup >
+ ShapeFactory::createGroup2D( const rtl::Reference<SvxDrawPage>& xTarget
+ , const OUString& aName )
+{
+ if( !xTarget.is() )
+ return nullptr;
+ try
+ {
+ //create and add to target
+ rtl::Reference<SvxShapeGroup> xShapeGroup = new SvxShapeGroup(nullptr, nullptr);
+ xShapeGroup->setShapeKind(SdrObjKind::Group);
+ // cast to resolve ambiguity in converting to XShape
+ xTarget->add(static_cast<SvxShape*>(xShapeGroup.get()));
+
+ //set name
+ if(!aName.isEmpty())
+ setShapeName( xShapeGroup, aName );
+
+ {//workaround
+ //need this null size as otherwise empty group shapes where painted with a gray border
+ xShapeGroup->setSize(awt::Size(0,0));
+ }
+
+ return xShapeGroup;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return nullptr;
+}
+
+rtl::Reference<Svx3DSceneObject>
+ ShapeFactory::createGroup3D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const OUString& aName )
+{
+ if( !xTarget.is() )
+ return nullptr;
+ try
+ {
+ //create shape
+ rtl::Reference<Svx3DSceneObject> xShape = new Svx3DSceneObject(nullptr, nullptr);
+ xShape->setShapeKind(SdrObjKind::E3D_Scene);
+ xTarget->addShape(*xShape);
+
+ //it is necessary to set the transform matrix to initialize the scene properly
+ //otherwise all objects which are placed into this Group will not be visible
+ //the following should be unnecessary after the bug is fixed
+ //set properties
+ try
+ {
+ ::basegfx::B3DHomMatrix aM;
+ xShape->SvxShape::setPropertyValue( UNO_NAME_3D_TRANSFORM_MATRIX
+ , uno::Any(B3DHomMatrixToHomogenMatrix(aM)) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ //set name
+ if(!aName.isEmpty())
+ setShapeName( xShape , aName );
+
+ //return
+ return xShape;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return nullptr;
+}
+
+rtl::Reference<SvxShapeCircle>
+ ShapeFactory::createCircle2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::Position3D& rPosition
+ , const drawing::Direction3D& rSize )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ //create shape
+ rtl::Reference<SvxShapeCircle> xShape = new SvxShapeCircle(nullptr);
+ xShape->setShapeKind(SdrObjKind::CircleOrEllipse);
+ xTarget->addShape(*xShape);
+
+ try
+ {
+ drawing::Position3D aCenterPosition(
+ rPosition.PositionX - (rSize.DirectionX / 2.0),
+ rPosition.PositionY - (rSize.DirectionY / 2.0),
+ rPosition.PositionZ );
+ xShape->setPosition( Position3DToAWTPoint( aCenterPosition ));
+ xShape->setSize( Direction3DToAWTSize( rSize ));
+ }
+ catch( const uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ //set properties
+ try
+ {
+ xShape->SvxShape::setPropertyValue( UNO_NAME_CIRCKIND, uno::Any( drawing::CircleKind_FULL ) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<SvxShapeCircle>
+ ShapeFactory::createCircle( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const awt::Size& rSize
+ , const awt::Point& rPosition )
+{
+ rtl::Reference<SvxShapeCircle> xShape = new SvxShapeCircle(nullptr);
+ xShape->setShapeKind(SdrObjKind::CircleOrEllipse);
+ xTarget->addShape(*xShape);
+ xShape->setSize( rSize );
+ xShape->setPosition( rPosition );
+
+ return xShape;
+}
+
+rtl::Reference<Svx3DPolygonObject>
+ ShapeFactory::createLine3D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const std::vector<std::vector<css::drawing::Position3D>>& rPoints
+ , const VLineProperties& rLineProperties )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ if(rPoints.empty())
+ return nullptr;
+
+ //create shape
+ rtl::Reference<Svx3DPolygonObject> xShape = new Svx3DPolygonObject(nullptr);
+ xShape->setShapeKind(SdrObjKind::E3D_Polygon);
+ xTarget->addShape(*xShape);
+
+ css::drawing::PolyPolygonShape3D aUnoPoly = toPolyPolygonShape3D(rPoints);
+
+ //set properties
+ try
+ {
+ uno::Sequence<OUString> aPropertyNames {
+ UNO_NAME_3D_POLYPOLYGON3D,
+ UNO_NAME_3D_LINEONLY
+ };
+
+ uno::Sequence<uno::Any> aPropertyValues {
+ uno::Any(aUnoPoly), // Polygon
+ uno::Any(true) // LineOnly
+ };
+
+ //Transparency
+ if(rLineProperties.Transparence.hasValue())
+ {
+ lcl_addProperty(aPropertyNames, aPropertyValues,
+ UNO_NAME_LINETRANSPARENCE,
+ rLineProperties.Transparence);
+ }
+
+ //LineStyle
+ if(rLineProperties.LineStyle.hasValue())
+ {
+ lcl_addProperty(aPropertyNames, aPropertyValues,
+ UNO_NAME_LINESTYLE,
+ rLineProperties.LineStyle);
+ }
+
+ //LineWidth
+ if(rLineProperties.Width.hasValue())
+ {
+ lcl_addProperty(aPropertyNames, aPropertyValues,
+ UNO_NAME_LINEWIDTH,
+ rLineProperties.Width);
+ }
+
+ //LineColor
+ if(rLineProperties.Color.hasValue())
+ {
+ lcl_addProperty(aPropertyNames, aPropertyValues,
+ UNO_NAME_LINECOLOR,
+ rLineProperties.Color);
+ }
+ xShape->setPropertyValues(aPropertyNames, aPropertyValues);
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<SvxShapePolyPolygon>
+ ShapeFactory::createLine2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const drawing::PointSequenceSequence& rPoints
+ , const VLineProperties* pLineProperties )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ if(!rPoints.hasElements())
+ return nullptr;
+
+ //create shape
+ rtl::Reference<SvxShapePolyPolygon> xShape = new SvxShapePolyPolygon(nullptr);
+ xShape->setShapeKind(SdrObjKind::PolyLine);
+ xTarget->addShape(*xShape);
+
+ //set properties
+ try
+ {
+ //Polygon
+ xShape->SvxShape::setPropertyValue( UNO_NAME_POLYPOLYGON
+ , uno::Any( rPoints ) );
+
+ if(pLineProperties)
+ {
+ //Transparency
+ if(pLineProperties->Transparence.hasValue())
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINETRANSPARENCE
+ , pLineProperties->Transparence );
+
+ //LineStyle
+ if(pLineProperties->LineStyle.hasValue())
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINESTYLE
+ , pLineProperties->LineStyle );
+
+ //LineWidth
+ if(pLineProperties->Width.hasValue())
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINEWIDTH
+ , pLineProperties->Width );
+
+ //LineColor
+ if(pLineProperties->Color.hasValue())
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINECOLOR
+ , pLineProperties->Color );
+
+ //LineDashName
+ if(pLineProperties->DashName.hasValue())
+ xShape->SvxShape::setPropertyValue( "LineDashName"
+ , pLineProperties->DashName );
+
+ //LineCap
+ if(pLineProperties->LineCap.hasValue())
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINECAP
+ , pLineProperties->LineCap );
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<SvxShapePolyPolygon>
+ ShapeFactory::createLine2D( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const std::vector<std::vector<css::drawing::Position3D>>& rPoints
+ , const VLineProperties* pLineProperties )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ if(rPoints.empty())
+ return nullptr;
+
+ //create shape
+ rtl::Reference<SvxShapePolyPolygon> xShape = new SvxShapePolyPolygon(nullptr);
+ xShape->setShapeKind(SdrObjKind::PolyLine);
+ xTarget->addShape(*xShape);
+
+ drawing::PointSequenceSequence aAnyPoints = PolyToPointSequence(rPoints);
+
+ //set properties
+ try
+ {
+ //Polygon
+ xShape->SvxShape::setPropertyValue( UNO_NAME_POLYPOLYGON
+ , uno::Any( aAnyPoints ) );
+
+ if(pLineProperties)
+ {
+ //Transparency
+ if(pLineProperties->Transparence.hasValue())
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINETRANSPARENCE
+ , pLineProperties->Transparence );
+
+ //LineStyle
+ if(pLineProperties->LineStyle.hasValue())
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINESTYLE
+ , pLineProperties->LineStyle );
+
+ //LineWidth
+ if(pLineProperties->Width.hasValue())
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINEWIDTH
+ , pLineProperties->Width );
+
+ //LineColor
+ if(pLineProperties->Color.hasValue())
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINECOLOR
+ , pLineProperties->Color );
+
+ //LineDashName
+ if(pLineProperties->DashName.hasValue())
+ xShape->SvxShape::setPropertyValue( "LineDashName"
+ , pLineProperties->DashName );
+
+ //LineCap
+ if(pLineProperties->LineCap.hasValue())
+ xShape->SvxShape::setPropertyValue( UNO_NAME_LINECAP
+ , pLineProperties->LineCap );
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<SvxShapePolyPolygon>
+ ShapeFactory::createLine ( const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const awt::Size& rSize, const awt::Point& rPosition )
+{
+ //create shape
+ rtl::Reference<SvxShapePolyPolygon> xShape = new SvxShapePolyPolygon(nullptr);
+ xShape->setShapeKind(SdrObjKind::Line);
+ xTarget->addShape(*xShape);
+ xShape->setSize( rSize );
+ xShape->setPosition( rPosition );
+
+ return xShape;
+}
+
+rtl::Reference<SvxShapeRect> ShapeFactory::createInvisibleRectangle(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const awt::Size& rSize )
+{
+ try
+ {
+ if(!xTarget.is())
+ return nullptr;
+
+ rtl::Reference<SvxShapeRect> xShape = new SvxShapeRect(nullptr);
+ xShape->setShapeKind(SdrObjKind::Rectangle);
+ xTarget->addShape( *xShape );
+ ShapeFactory::makeShapeInvisible( xShape );
+ xShape->setSize( rSize );
+ return xShape;
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+ return nullptr;
+}
+
+rtl::Reference<SvxShapeRect> ShapeFactory::createRectangle(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const awt::Size& rSize,
+ const awt::Point& rPosition,
+ const tNameSequence& rPropNames,
+ const tAnySequence& rPropValues,
+ StackPosition ePos )
+{
+ rtl::Reference<SvxShapeRect> xShape = new SvxShapeRect(nullptr);
+ xShape->setShapeKind(SdrObjKind::Rectangle);
+ if (ePos == StackPosition::Bottom)
+ {
+ uno::Reference<drawing::XShapes2> xTarget2(static_cast<cppu::OWeakObject*>(xTarget.get()), uno::UNO_QUERY);
+ if (xTarget2.is())
+ xTarget2->addBottom(xShape);
+ }
+ else
+ xTarget->addShape(*xShape);
+
+ xShape->setPosition( rPosition );
+ xShape->setSize( rSize );
+ PropertyMapper::setMultiProperties( rPropNames, rPropValues, *xShape );
+
+ return xShape;
+}
+
+rtl::Reference<SvxShapeRect>
+ ShapeFactory::createRectangle(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTarget )
+{
+ rtl::Reference<SvxShapeRect> xShape = new SvxShapeRect(nullptr);
+ xShape->setShapeKind(SdrObjKind::Rectangle);
+ xTarget->addShape( *xShape );
+
+ return xShape;
+}
+
+rtl::Reference<SvxShapeText>
+ ShapeFactory::createText( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const OUString& rText
+ , const tNameSequence& rPropNames
+ , const tAnySequence& rPropValues
+ , const uno::Any& rATransformation )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ if(rText.isEmpty())
+ return nullptr;
+
+ //create shape and add to page
+ rtl::Reference<SvxShapeText> xShape = new SvxShapeText(nullptr);
+ xShape->setShapeKind(SdrObjKind::Text);
+ xTarget->addShape(*xShape);
+
+ //set text
+ xShape->setString( rText );
+
+ //set properties
+ PropertyMapper::setMultiProperties( rPropNames, rPropValues, *xShape );
+
+ //set position matrix
+ //the matrix needs to be set at the end behind autogrow and such position influencing properties
+ try
+ {
+ if (rATransformation.hasValue())
+ xShape->SvxShape::setPropertyValue( "Transformation", rATransformation );
+ else
+ SAL_INFO("chart2", "No rATransformation value is given to ShapeFactory::createText()");
+
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<SvxShapeText>
+ ShapeFactory::createText( const rtl::Reference<SvxShapeGroupAnyD>& xTarget
+ , const uno::Sequence< uno::Reference< chart2::XFormattedString > >& xFormattedString
+ , const tNameSequence& rPropNames
+ , const tAnySequence& rPropValues
+ , const uno::Any& rATransformation )
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ if( !xFormattedString.hasElements() )
+ return nullptr;
+
+ sal_Int32 nNumberOfParagraphs = xFormattedString.getLength();
+
+ bool bNotEmpty = false;
+ for( sal_Int32 nN = 0; nN < nNumberOfParagraphs; ++nN )
+ {
+ if( !xFormattedString[nN]->getString().isEmpty() )
+ {
+ bNotEmpty = true;
+ break;
+ }
+ }
+ if( !bNotEmpty )
+ return nullptr;
+
+ //create shape and add to page
+ rtl::Reference<SvxShapeText> xShape = new SvxShapeText(nullptr);
+ xShape->setShapeKind(SdrObjKind::Text);
+ xTarget->addShape(*xShape);
+
+ //set paragraph properties
+ bNotEmpty = false;
+ // the first cursor is used for appending the next paragraph,
+ // after a new string has been inserted the cursor is moved at the end
+ // of the inserted string
+ // the second cursor is used for selecting the paragraph and apply the
+ // passed text properties
+ Reference< text::XTextCursor > xInsertCursor = xShape->createTextCursor();
+ Reference< text::XTextCursor > xSelectionCursor = xShape->createTextCursor();
+ if( xInsertCursor.is() && xSelectionCursor.is() )
+ {
+ uno::Reference< beans::XPropertySet > xSelectionProp( xSelectionCursor, uno::UNO_QUERY );
+ if( xSelectionProp.is() )
+ {
+ for( sal_Int32 nN = 0; nN < nNumberOfParagraphs; ++nN )
+ {
+ if( !xFormattedString[nN]->getString().isEmpty() )
+ {
+ xInsertCursor->gotoEnd( false );
+ xSelectionCursor->gotoEnd( false );
+ xShape->insertString( xInsertCursor, xFormattedString[nN]->getString(), false );
+ bNotEmpty = true;
+ xSelectionCursor->gotoEnd( true ); // select current paragraph
+ uno::Reference< beans::XPropertySet > xStringProperties( xFormattedString[nN], uno::UNO_QUERY );
+ PropertyMapper::setMappedProperties( xSelectionProp, xStringProperties,
+ PropertyMapper::getPropertyNameMapForTextShapeProperties() );
+ }
+ }
+ }
+ }
+
+ if( !bNotEmpty )
+ return nullptr;
+
+ //set whole text shape properties
+ PropertyMapper::setMultiProperties( rPropNames, rPropValues, *xShape );
+
+ if( rATransformation.hasValue() )
+ {
+ //set position matrix
+ //the matrix needs to be set at the end behind autogrow and such position influencing properties
+ try
+ {
+ xShape->SvxShape::setPropertyValue( "Transformation", rATransformation );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }
+ return xShape;
+}
+
+rtl::Reference<SvxShapeText>
+ ShapeFactory::createText( const rtl::Reference<SvxShapeGroupAnyD>& xTarget,
+ const awt::Size& rSize,
+ const awt::Point& rPos,
+ uno::Sequence< uno::Reference< chart2::XFormattedString > >& xFormattedString,
+ const uno::Reference<
+ beans::XPropertySet > & xTextProperties,
+ double nRotation, const OUString& aName, sal_Int32 nTextMaxWidth )
+{
+ //create shape and add to page
+ rtl::Reference<SvxShapeText> xShape = new SvxShapeText(nullptr);
+ xShape->setShapeKind(SdrObjKind::Text);
+ try
+ {
+ xTarget->addShape(*xShape);
+
+ //set text and text properties
+ uno::Reference< text::XTextCursor > xTextCursor( xShape->createTextCursor() );
+ if( !xTextCursor.is() )
+ return xShape;
+
+ tPropertyNameValueMap aValueMap;
+ //fill line-, fill- and paragraph-properties into the ValueMap
+ {
+ tPropertyNameMap aNameMap = PropertyMapper::getPropertyNameMapForParagraphProperties();
+ auto const & add = PropertyMapper::getPropertyNameMapForFillAndLineProperties();
+ aNameMap.insert(add.begin(), add.end());
+
+ PropertyMapper::getValueMap( aValueMap, aNameMap, xTextProperties );
+ }
+
+ //fill some more shape properties into the ValueMap
+ {
+ aValueMap.insert( { "TextHorizontalAdjust", uno::Any(drawing::TextHorizontalAdjust_CENTER) } ); // drawing::TextHorizontalAdjust
+ aValueMap.insert( { "TextVerticalAdjust", uno::Any(drawing::TextVerticalAdjust_CENTER) } ); //drawing::TextVerticalAdjust
+ aValueMap.insert( { "TextAutoGrowHeight", uno::Any(true) } ); // sal_Bool
+ aValueMap.insert( { "TextAutoGrowWidth", uno::Any(true) } ); // sal_Bool
+ aValueMap.insert( { "TextMaximumFrameWidth", uno::Any(nTextMaxWidth) } ); // sal_Int32
+
+ //set name/classified ObjectID (CID)
+ if( !aName.isEmpty() )
+ aValueMap.emplace( "Name", uno::Any( aName ) ); //CID OUString
+ }
+
+ //set global title properties
+ {
+ tNameSequence aPropNames;
+ tAnySequence aPropValues;
+ PropertyMapper::getMultiPropertyListsFromValueMap( aPropNames, aPropValues, aValueMap );
+ PropertyMapper::setMultiProperties( aPropNames, aPropValues, *xShape );
+ }
+
+ bool bStackCharacters(false);
+ try
+ {
+ xTextProperties->getPropertyValue( "StackCharacters" ) >>= bStackCharacters;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ if(bStackCharacters)
+ {
+ //if the characters should be stacked we use only the first character properties for code simplicity
+ if( xFormattedString.hasElements() )
+ {
+ OUString aLabel;
+ for( const auto & i : std::as_const(xFormattedString) )
+ aLabel += i->getString();
+ aLabel = ShapeFactory::getStackedString( aLabel, bStackCharacters );
+
+ xTextCursor->gotoEnd(false);
+ xShape->insertString( xTextCursor, aLabel, false );
+ xTextCursor->gotoEnd(true);
+ uno::Reference< beans::XPropertySet > xSourceProps( xFormattedString[0], uno::UNO_QUERY );
+
+ PropertyMapper::setMappedProperties( *xShape, xSourceProps
+ , PropertyMapper::getPropertyNameMapForCharacterProperties() );
+
+ // adapt font size according to page size
+ awt::Size aOldRefSize;
+ if( xTextProperties->getPropertyValue( "ReferencePageSize") >>= aOldRefSize )
+ {
+ RelativeSizeHelper::adaptFontSizes( *xShape, aOldRefSize, rSize );
+ }
+ }
+ }
+ else
+ {
+ for( const uno::Reference< chart2::XFormattedString >& rxFS : std::as_const(xFormattedString) )
+ {
+ xTextCursor->gotoEnd(false);
+ xShape->insertString( xTextCursor, rxFS->getString(), false );
+ xTextCursor->gotoEnd(true);
+ }
+ awt::Size aOldRefSize;
+ bool bHasRefPageSize =
+ ( xTextProperties->getPropertyValue( "ReferencePageSize") >>= aOldRefSize );
+
+ if( xFormattedString.hasElements() )
+ {
+ uno::Reference< beans::XPropertySet > xSourceProps( xFormattedString[0], uno::UNO_QUERY );
+ PropertyMapper::setMappedProperties( *xShape, xSourceProps, PropertyMapper::getPropertyNameMapForCharacterProperties() );
+
+ // adapt font size according to page size
+ if( bHasRefPageSize )
+ {
+ RelativeSizeHelper::adaptFontSizes( *xShape, aOldRefSize, rSize );
+ }
+ }
+ }
+
+ // #i109336# Improve auto positioning in chart
+ float fFontHeight = 0.0;
+ if ( xShape->SvxShape::getPropertyValue( "CharHeight" ) >>= fFontHeight )
+ {
+ fFontHeight = convertPointToMm100(fFontHeight);
+ sal_Int32 nXDistance = static_cast< sal_Int32 >( ::rtl::math::round( fFontHeight * 0.18f ) );
+ sal_Int32 nYDistance = static_cast< sal_Int32 >( ::rtl::math::round( fFontHeight * 0.30f ) );
+ xShape->SvxShape::setPropertyValue( "TextLeftDistance", uno::Any( nXDistance ) );
+ xShape->SvxShape::setPropertyValue( "TextRightDistance", uno::Any( nXDistance ) );
+ xShape->SvxShape::setPropertyValue( "TextUpperDistance", uno::Any( nYDistance ) );
+ xShape->SvxShape::setPropertyValue( "TextLowerDistance", uno::Any( nYDistance ) );
+ }
+ sal_Int32 nXPos = rPos.X;
+ sal_Int32 nYPos = rPos.Y;
+
+ //set position matrix
+ //the matrix needs to be set at the end behind autogrow and such position influencing properties
+ ::basegfx::B2DHomMatrix aM;
+ aM.rotate( -basegfx::deg2rad(nRotation) );//#i78696#->#i80521#
+ aM.translate( nXPos, nYPos );
+ xShape->SvxShape::setPropertyValue( "Transformation", uno::Any( B2DHomMatrixToHomogenMatrix3(aM) ) );
+
+ xShape->SvxShape::setPropertyValue( "ParaAdjust", uno::Any( style::ParagraphAdjust_CENTER ) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return xShape;
+}
+
+rtl::Reference<SvxShapeGroupAnyD> ShapeFactory::getChartRootShape(
+ const rtl::Reference<SvxDrawPage>& xDrawPage )
+{
+ rtl::Reference<SvxShapeGroupAnyD> xRet;
+ const uno::Reference< drawing::XShapes > xShapes = xDrawPage;
+ if( xShapes.is() )
+ {
+ sal_Int32 nCount = xShapes->getCount();
+ uno::Reference< drawing::XShape > xShape;
+ for( sal_Int32 nN = nCount; nN--; )
+ {
+ if( xShapes->getByIndex( nN ) >>= xShape )
+ {
+ if( ShapeFactory::getShapeName( xShape ) == "com.sun.star.chart2.shapes" )
+ {
+ xRet = dynamic_cast<SvxShapeGroupAnyD*>(xShape.get());
+ assert(xRet);
+ break;
+ }
+ }
+ }
+ }
+ return xRet;
+}
+
+void ShapeFactory::makeShapeInvisible( const rtl::Reference< SvxShape >& xShape )
+{
+ try
+ {
+ xShape->setPropertyValue( "LineStyle", uno::Any( drawing::LineStyle_NONE ));
+ xShape->setPropertyValue( "FillStyle", uno::Any( drawing::FillStyle_NONE ));
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+// set a name/CID at a shape (is used for selection handling)
+
+void ShapeFactory::setShapeName( const rtl::Reference< SvxShape >& xShape
+ , const OUString& rName )
+{
+ if(!xShape.is())
+ return;
+ try
+ {
+ xShape->setPropertyValue( UNO_NAME_MISC_OBJ_NAME
+ , uno::Any( rName ) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+OUString ShapeFactory::getShapeName( const uno::Reference< drawing::XShape >& xShape )
+{
+ OUString aRet;
+
+ uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY );
+ OSL_ENSURE(xProp.is(), "shape offers no XPropertySet");
+ if( xProp.is())
+ {
+ try
+ {
+ xProp->getPropertyValue( UNO_NAME_MISC_OBJ_NAME ) >>= aRet;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }
+
+ return aRet;
+}
+
+uno::Any ShapeFactory::makeTransformation( const awt::Point& rScreenPosition2D, double fRotationAnglePi )
+{
+ ::basegfx::B2DHomMatrix aM;
+ //As autogrow is active the rectangle is automatically expanded to that side
+ //to which the text is not adjusted.
+ // aM.scale( 1, 1 ); Oops? A scale with this parameters is neutral, line commented out
+ aM.rotate( fRotationAnglePi );
+ aM.translate( rScreenPosition2D.X, rScreenPosition2D.Y );
+ uno::Any aATransformation( B2DHomMatrixToHomogenMatrix3(aM) );
+ return aATransformation;
+}
+
+OUString ShapeFactory::getStackedString( const OUString& rString, bool bStacked )
+{
+ sal_Int32 nLen = rString.getLength();
+ if(!bStacked || !nLen)
+ return rString;
+
+ OUStringBuffer aStackStr;
+
+ //add a newline after each letter
+ //as we do not no letters here add a newline after each char
+ for( sal_Int32 nPosSrc=0; nPosSrc < nLen; nPosSrc++ )
+ {
+ if( nPosSrc )
+ aStackStr.append( '\r' );
+ aStackStr.append(rString[nPosSrc]);
+ }
+ return aStackStr.makeStringAndClear();
+}
+
+bool ShapeFactory::hasPolygonAnyLines( const std::vector<std::vector<css::drawing::Position3D>>& rPoly)
+{
+ // #i67757# check all contained polygons, if at least one polygon contains 2 or more points, return true
+ for( auto const & i : rPoly )
+ if( i.size() > 1 )
+ return true;
+ return false;
+}
+
+bool ShapeFactory::isPolygonEmptyOrSinglePoint( const drawing::PolyPolygonShape3D& rPoly)
+{
+ // true, if empty polypolygon or one polygon with one point
+ return !rPoly.SequenceX.hasElements() ||
+ ((rPoly.SequenceX.getLength() == 1) && (rPoly.SequenceX[0].getLength() <= 1));
+}
+
+bool ShapeFactory::isPolygonEmptyOrSinglePoint( const std::vector<std::vector<css::drawing::Position3D>>& rPoly)
+{
+ // true, if empty polypolygon or one polygon with one point
+ return rPoly.empty() || ((rPoly.size() == 1) && (rPoly[0].size() <= 1));
+}
+
+void ShapeFactory::closePolygon( drawing::PolyPolygonShape3D& rPoly)
+{
+ OSL_ENSURE( rPoly.SequenceX.getLength() <= 1, "ShapeFactory::closePolygon - single polygon expected" );
+ //add a last point == first point
+ if(isPolygonEmptyOrSinglePoint(rPoly))
+ return;
+ drawing::Position3D aFirst(rPoly.SequenceX[0][0],rPoly.SequenceY[0][0],rPoly.SequenceZ[0][0]);
+ AddPointToPoly( rPoly, aFirst );
+}
+
+void ShapeFactory::closePolygon( std::vector<std::vector<css::drawing::Position3D>>& rPoly)
+{
+ OSL_ENSURE( rPoly.size() <= 1, "ShapeFactory::closePolygon - single polygon expected" );
+ //add a last point == first point
+ if(isPolygonEmptyOrSinglePoint(rPoly))
+ return;
+ drawing::Position3D aFirst(rPoly[0][0]);
+ AddPointToPoly( rPoly, aFirst );
+}
+
+awt::Size ShapeFactory::calculateNewSizeRespectingAspectRatio(
+ const awt::Size& rTargetSize
+ , const awt::Size& rSourceSizeWithCorrectAspectRatio )
+{
+ awt::Size aNewSize;
+
+ double fFactorWidth = double(rTargetSize.Width)/double(rSourceSizeWithCorrectAspectRatio.Width);
+ double fFactorHeight = double(rTargetSize.Height)/double(rSourceSizeWithCorrectAspectRatio.Height);
+ double fFactor = std::min(fFactorWidth,fFactorHeight);
+ aNewSize.Width=static_cast<sal_Int32>(fFactor*rSourceSizeWithCorrectAspectRatio.Width);
+ aNewSize.Height=static_cast<sal_Int32>(fFactor*rSourceSizeWithCorrectAspectRatio.Height);
+
+ return aNewSize;
+}
+
+awt::Point ShapeFactory::calculateTopLeftPositionToCenterObject(
+ const awt::Point& rTargetAreaPosition
+ , const awt::Size& rTargetAreaSize
+ , const awt::Size& rObjectSize )
+{
+ awt::Point aNewPosition(rTargetAreaPosition);
+ aNewPosition.X += static_cast<sal_Int32>(double(rTargetAreaSize.Width-rObjectSize.Width)/2.0);
+ aNewPosition.Y += static_cast<sal_Int32>(double(rTargetAreaSize.Height-rObjectSize.Height)/2.0);
+ return aNewPosition;
+}
+
+::basegfx::B2IRectangle ShapeFactory::getRectangleOfShape( SvxShape& rShape )
+{
+ ::basegfx::B2IRectangle aRet;
+
+ awt::Point aPos = rShape.getPosition();
+ awt::Size aSize = rShape.getSize();
+ aRet = BaseGFXHelper::makeRectangle(aPos,aSize);
+
+ return aRet;
+}
+
+awt::Size ShapeFactory::getSizeAfterRotation(
+ SvxShape& rShape, double fRotationAngleDegree )
+{
+ awt::Size aRet(0,0);
+ const awt::Size aSize( rShape.getSize() );
+
+ if( fRotationAngleDegree == 0.0 )
+ aRet = aSize;
+ else
+ {
+ fRotationAngleDegree = NormAngle360(fRotationAngleDegree);
+ if(fRotationAngleDegree>270.0)
+ fRotationAngleDegree=360.0-fRotationAngleDegree;
+ else if(fRotationAngleDegree>180.0)
+ fRotationAngleDegree=fRotationAngleDegree-180.0;
+ else if(fRotationAngleDegree>90.0)
+ fRotationAngleDegree=180.0-fRotationAngleDegree;
+
+ const double fAnglePi = basegfx::deg2rad(fRotationAngleDegree);
+
+ aRet.Height = static_cast<sal_Int32>(
+ aSize.Width*std::sin( fAnglePi )
+ + aSize.Height*std::cos( fAnglePi ));
+ aRet.Width = static_cast<sal_Int32>(
+ aSize.Width*std::cos( fAnglePi )
+ + aSize.Height*std::sin( fAnglePi ));
+ }
+ return aRet;
+}
+
+void ShapeFactory::removeSubShapes( const rtl::Reference<SvxShapeGroupAnyD>& xShapes )
+{
+ if( xShapes.is() )
+ {
+ sal_Int32 nSubCount = xShapes->getCount();
+ uno::Reference< drawing::XShape > xShape;
+ for( sal_Int32 nS = nSubCount; nS--; )
+ {
+ if( xShapes->getByIndex( nS ) >>= xShape )
+ xShapes->remove( xShape );
+ }
+ }
+}
+
+rtl::Reference<SvxTableShape>
+ShapeFactory::createTable(rtl::Reference<SvxShapeGroupAnyD> const& xTarget)
+{
+ if( !xTarget.is() )
+ return nullptr;
+
+ //create table shape
+ rtl::Reference<SvxTableShape> xShape = new SvxTableShape(nullptr);
+ xShape->setShapeKind(SdrObjKind::Table);
+ xTarget->addShape(*xShape);
+
+ return xShape;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/Stripe.cxx b/chart2/source/view/main/Stripe.cxx
new file mode 100644
index 000000000..74c8ad046
--- /dev/null
+++ b/chart2/source/view/main/Stripe.cxx
@@ -0,0 +1,347 @@
+/* -*- 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 <Stripe.hxx>
+#include <CommonConverters.hxx>
+#include <com/sun/star/drawing/PolyPolygonShape3D.hpp>
+#include <com/sun/star/drawing/DoubleSequence.hpp>
+#include <basegfx/polygon/b3dpolygon.hxx>
+
+using namespace ::com::sun::star;
+
+namespace chart
+{
+
+Stripe::Stripe( const drawing::Position3D& rPoint1
+ , const drawing::Direction3D& rDirectionToPoint2
+ , const drawing::Direction3D& rDirectionToPoint4 )
+ : m_aPoint1(rPoint1)
+ , m_aPoint2(rPoint1+rDirectionToPoint2)
+ , m_aPoint3(m_aPoint2+rDirectionToPoint4)
+ , m_aPoint4(rPoint1+rDirectionToPoint4)
+ , m_bInvertNormal(false)
+ , m_bManualNormalSet(false)
+{
+}
+
+Stripe::Stripe( const drawing::Position3D& rPoint1
+ , const drawing::Position3D& rPoint2
+ , double fDepth )
+ : m_aPoint1(rPoint1)
+ , m_aPoint2(rPoint2)
+ , m_aPoint3(rPoint2)
+ , m_aPoint4(rPoint1)
+ , m_bInvertNormal(false)
+ , m_bManualNormalSet(false)
+{
+ m_aPoint3.PositionZ += fDepth;
+ m_aPoint4.PositionZ += fDepth;
+}
+
+Stripe::Stripe( const drawing::Position3D& rPoint1
+ , const drawing::Position3D& rPoint2
+ , const drawing::Position3D& rPoint3
+ , const drawing::Position3D& rPoint4 )
+ : m_aPoint1(rPoint1)
+ , m_aPoint2(rPoint2)
+ , m_aPoint3(rPoint3)
+ , m_aPoint4(rPoint4)
+ , m_bInvertNormal(false)
+ , m_bManualNormalSet(false)
+{
+}
+
+void Stripe::SetManualNormal( const drawing::Direction3D& rNormal )
+{
+ m_aManualNormal = rNormal;
+ m_bManualNormalSet = true;
+}
+
+void Stripe::InvertNormal( bool bInvertNormal )
+{
+ m_bInvertNormal = bInvertNormal;
+}
+
+uno::Any Stripe::getPolyPolygonShape3D() const
+{
+ drawing::PolyPolygonShape3D aPP;
+
+ aPP.SequenceX.realloc(1);
+ aPP.SequenceY.realloc(1);
+ aPP.SequenceZ.realloc(1);
+
+ drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray();
+ drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray();
+ drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray();
+
+ pOuterSequenceX->realloc(4);
+ pOuterSequenceY->realloc(4);
+ pOuterSequenceZ->realloc(4);
+
+ double* pInnerSequenceX = pOuterSequenceX->getArray();
+ double* pInnerSequenceY = pOuterSequenceY->getArray();
+ double* pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ *pInnerSequenceX++ = m_aPoint1.PositionX;
+ *pInnerSequenceY++ = m_aPoint1.PositionY;
+ *pInnerSequenceZ++ = m_aPoint1.PositionZ;
+
+ *pInnerSequenceX++ = m_aPoint2.PositionX;
+ *pInnerSequenceY++ = m_aPoint2.PositionY;
+ *pInnerSequenceZ++ = m_aPoint2.PositionZ;
+
+ *pInnerSequenceX++ = m_aPoint3.PositionX;
+ *pInnerSequenceY++ = m_aPoint3.PositionY;
+ *pInnerSequenceZ++ = m_aPoint3.PositionZ;
+
+ *pInnerSequenceX++ = m_aPoint4.PositionX;
+ *pInnerSequenceY++ = m_aPoint4.PositionY;
+ *pInnerSequenceZ++ = m_aPoint4.PositionZ;
+
+ return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get());
+}
+
+drawing::Direction3D Stripe::getNormal() const
+{
+ drawing::Direction3D aRet(1.0,0.0,0.0);
+
+ if( m_bManualNormalSet )
+ aRet = m_aManualNormal;
+ else
+ {
+ ::basegfx::B3DPolygon aPolygon3D;
+ aPolygon3D.append(Position3DToB3DPoint( m_aPoint1 ));
+ aPolygon3D.append(Position3DToB3DPoint( m_aPoint2 ));
+ aPolygon3D.append(Position3DToB3DPoint( m_aPoint3 ));
+ aPolygon3D.append(Position3DToB3DPoint( m_aPoint4 ));
+ ::basegfx::B3DVector aNormal(aPolygon3D.getNormal());
+ aRet = B3DVectorToDirection3D(aNormal);
+ }
+
+ if( m_bInvertNormal )
+ {
+ aRet.DirectionX *= -1.0;
+ aRet.DirectionY *= -1.0;
+ aRet.DirectionZ *= -1.0;
+ }
+ return aRet;
+}
+
+uno::Any Stripe::getNormalsPolygon() const
+{
+ drawing::PolyPolygonShape3D aPP;
+
+ aPP.SequenceX.realloc(1);
+ aPP.SequenceY.realloc(1);
+ aPP.SequenceZ.realloc(1);
+
+ drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray();
+ drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray();
+ drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray();
+
+ pOuterSequenceX->realloc(4);
+ pOuterSequenceY->realloc(4);
+ pOuterSequenceZ->realloc(4);
+
+ double* pInnerSequenceX = pOuterSequenceX->getArray();
+ double* pInnerSequenceY = pOuterSequenceY->getArray();
+ double* pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ drawing::Direction3D aNormal( getNormal() );
+
+ for(sal_Int32 nN=4; --nN; )
+ {
+ *pInnerSequenceX++ = aNormal.DirectionX;
+ *pInnerSequenceY++ = aNormal.DirectionY;
+ *pInnerSequenceZ++ = aNormal.DirectionZ;
+ }
+ return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get());
+}
+
+uno::Any Stripe::getTexturePolygon( short nRotatedTexture )
+{
+ drawing::PolyPolygonShape3D aPP;
+
+ aPP.SequenceX.realloc(1);
+ aPP.SequenceY.realloc(1);
+ aPP.SequenceZ.realloc(1);
+
+ drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray();
+ drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray();
+ drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray();
+
+ pOuterSequenceX->realloc(4);
+ pOuterSequenceY->realloc(4);
+ pOuterSequenceZ->realloc(4);
+
+ double* pInnerSequenceX = pOuterSequenceX->getArray();
+ double* pInnerSequenceY = pOuterSequenceY->getArray();
+ double* pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ if( nRotatedTexture==0 )
+ {
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+ }
+ else if( nRotatedTexture==1 )
+ {
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+ }
+ else if( nRotatedTexture==2 )
+ {
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+ }
+ else if( nRotatedTexture==3 )
+ {
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+ }
+ else if( nRotatedTexture==4 )
+ {
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+ }
+ else if( nRotatedTexture==5 )
+ {
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+ }
+ else if( nRotatedTexture==6 )
+ {
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+ }
+ else if( nRotatedTexture==7 )
+ {
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 1.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 0.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+
+ *pInnerSequenceX++ = 1.0;
+ *pInnerSequenceY++ = 0.0;
+ *pInnerSequenceZ++ = 0.0;
+ }
+
+ return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get());
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/VButton.cxx b/chart2/source/view/main/VButton.cxx
new file mode 100644
index 000000000..25a770fb0
--- /dev/null
+++ b/chart2/source/view/main/VButton.cxx
@@ -0,0 +1,136 @@
+/* -*- 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/.
+ */
+
+#include "VButton.hxx"
+
+#include <ShapeFactory.hxx>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
+#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
+
+#include <CommonConverters.hxx>
+#include <editeng/unoprnms.hxx>
+
+namespace chart
+{
+using namespace css;
+
+VButton::VButton()
+ : m_bShowArrow(true)
+ , m_nArrowColor(0x00000000)
+ , m_nBGColor(0x00E6E6E6)
+{
+}
+
+void VButton::init(const rtl::Reference<SvxShapeGroupAnyD>& xTargetPage)
+{
+ m_xTarget = xTargetPage;
+}
+
+rtl::Reference<SvxShapePolyPolygon> VButton::createTriangle(awt::Size aSize)
+{
+ rtl::Reference<SvxShapePolyPolygon> xShape = new SvxShapePolyPolygon(nullptr);
+ xShape->setShapeKind(SdrObjKind::Polygon);
+
+ drawing::PolyPolygonShape3D aPolyPolygon;
+ aPolyPolygon.SequenceX.realloc(1);
+ aPolyPolygon.SequenceY.realloc(1);
+ aPolyPolygon.SequenceZ.realloc(1);
+
+ drawing::DoubleSequence* pOuterSequenceX = aPolyPolygon.SequenceX.getArray();
+ drawing::DoubleSequence* pOuterSequenceY = aPolyPolygon.SequenceY.getArray();
+ drawing::DoubleSequence* pOuterSequenceZ = aPolyPolygon.SequenceZ.getArray();
+
+ pOuterSequenceX->realloc(3);
+ pOuterSequenceY->realloc(3);
+ pOuterSequenceZ->realloc(3);
+
+ double* pInnerSequenceX = pOuterSequenceX->getArray();
+ double* pInnerSequenceY = pOuterSequenceY->getArray();
+ double* pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ pInnerSequenceX[0] = 0.0;
+ pInnerSequenceY[0] = 0.0;
+ pInnerSequenceZ[0] = 0.0;
+
+ pInnerSequenceX[1] = aSize.Width / 2.0;
+ pInnerSequenceY[1] = aSize.Height;
+ pInnerSequenceZ[1] = 0.0;
+
+ pInnerSequenceX[2] = aSize.Width;
+ pInnerSequenceY[2] = 0.0;
+ pInnerSequenceZ[2] = 0.0;
+
+ xShape->SvxShape::setPropertyValue("Name", uno::Any(m_sCID));
+ xShape->SvxShape::setPropertyValue(UNO_NAME_POLYPOLYGON,
+ uno::Any(PolyToPointSequence(aPolyPolygon)));
+ xShape->SvxShape::setPropertyValue("LineStyle", uno::Any(drawing::LineStyle_NONE));
+ xShape->SvxShape::setPropertyValue("FillColor", uno::Any(m_nArrowColor));
+
+ return xShape;
+}
+
+void VButton::createShapes(const uno::Reference<beans::XPropertySet>& xTextProp)
+{
+ tNameSequence aPropNames;
+ tAnySequence aPropValues;
+
+ PropertyMapper::getTextLabelMultiPropertyLists(xTextProp, aPropNames, aPropValues);
+
+ m_xShape = ShapeFactory::createGroup2D(m_xTarget, m_sCID);
+ m_xShape->setPosition(m_aPosition);
+ m_xShape->setSize(m_aSize);
+
+ rtl::Reference<SvxShapeGroupAnyD> xContainer = m_xShape;
+
+ tPropertyNameValueMap aTextValueMap;
+ aTextValueMap["CharHeight"] <<= 10.0f;
+ aTextValueMap["CharHeightAsian"] <<= 10.0f;
+ aTextValueMap["CharHeightComplex"] <<= 10.0f;
+ aTextValueMap["FillColor"] <<= m_nBGColor;
+ aTextValueMap["FillStyle"] <<= drawing::FillStyle_SOLID;
+ aTextValueMap["LineColor"] <<= sal_Int32(0xcccccc);
+ aTextValueMap["LineStyle"] <<= drawing::LineStyle_SOLID;
+ aTextValueMap["ParaAdjust"] <<= style::ParagraphAdjust_CENTER;
+ aTextValueMap["TextHorizontalAdjust"] <<= drawing::TextHorizontalAdjust_LEFT;
+ aTextValueMap["TextVerticalAdjust"] <<= drawing::TextVerticalAdjust_CENTER;
+ aTextValueMap["ParaLeftMargin"] <<= sal_Int32(100);
+ aTextValueMap["ParaRightMargin"] <<= sal_Int32(600);
+
+ aTextValueMap["Name"] <<= m_sCID; //CID OUString
+
+ PropertyMapper::getMultiPropertyListsFromValueMap(aPropNames, aPropValues, aTextValueMap);
+
+ rtl::Reference<SvxShapeText> xEntry
+ = ShapeFactory::createText(xContainer, m_sLabel, aPropNames, aPropValues, uno::Any());
+
+ if (xEntry.is())
+ {
+ xEntry->setPosition(m_aPosition);
+ xEntry->setSize(m_aSize);
+ }
+
+ if (!m_bShowArrow)
+ return;
+
+ awt::Size aPolySize{ 280, 180 };
+
+ rtl::Reference<SvxShapePolyPolygon> xPoly = createTriangle(aPolySize);
+ xPoly->setSize(aPolySize);
+ xPoly->setPosition(
+ { sal_Int32(m_aPosition.X + m_aSize.Width - aPolySize.Width - 100),
+ sal_Int32(m_aPosition.Y + (m_aSize.Height / 2.0) - (aPolySize.Height / 2.0)) });
+ xContainer->add(xPoly);
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/VButton.hxx b/chart2/source/view/main/VButton.hxx
new file mode 100644
index 000000000..87017f369
--- /dev/null
+++ b/chart2/source/view/main/VButton.hxx
@@ -0,0 +1,86 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <tools/color.hxx>
+#include <com/sun/star/awt/Point.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+#include <rtl/ref.hxx>
+#include <svx/unoshape.hxx>
+
+namespace com::sun::star::beans { class XPropertySet; }
+namespace com::sun::star::drawing { class XShape; }
+namespace com::sun::star::drawing { class XShapes; }
+namespace com::sun::star::lang { class XMultiServiceFactory; }
+
+namespace chart
+{
+
+class VButton final
+{
+private:
+ rtl::Reference<SvxShapeGroupAnyD> m_xTarget;
+ rtl::Reference<SvxShapeGroup> m_xShape;
+ OUString m_sLabel;
+ OUString m_sCID;
+ css::awt::Point m_aPosition;
+ css::awt::Size m_aSize;
+ bool m_bShowArrow;
+ Color m_nArrowColor;
+ Color m_nBGColor;
+
+ rtl::Reference<SvxShapePolyPolygon>
+ createTriangle(css::awt::Size aSize);
+
+public:
+ VButton();
+
+ void init(const rtl::Reference<SvxShapeGroupAnyD>& xTargetPage);
+
+ void createShapes(const css::uno::Reference<css::beans::XPropertySet>& xTextProp);
+
+ void showArrow(bool bShowArrow)
+ {
+ m_bShowArrow = bShowArrow;
+ }
+ void setArrowColor(Color nArrowColor)
+ {
+ m_nArrowColor = nArrowColor;
+ }
+ void setBGColor(Color nBGColor)
+ {
+ m_nBGColor = nBGColor;
+ }
+ void setLabel(OUString const & rLabel)
+ {
+ m_sLabel = rLabel;
+ }
+ void setCID(OUString const & rCID)
+ {
+ m_sCID = rCID;
+ }
+ void setPosition(css::awt::Point const & rPosition)
+ {
+ m_aPosition = rPosition;
+ }
+ css::awt::Size const & getSize() const
+ {
+ return m_aSize;
+ }
+ void setSize(css::awt::Size const & rSize)
+ {
+ m_aSize = rSize;
+ }
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/VDataSeries.cxx b/chart2/source/view/main/VDataSeries.cxx
new file mode 100644
index 000000000..3fb12ebca
--- /dev/null
+++ b/chart2/source/view/main/VDataSeries.cxx
@@ -0,0 +1,1118 @@
+/* -*- 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 <limits>
+#include <memory>
+#include <VDataSeries.hxx>
+#include <DataSeries.hxx>
+#include <ObjectIdentifier.hxx>
+#include <CommonConverters.hxx>
+#include <LabelPositionHelper.hxx>
+#include <ChartType.hxx>
+#include <ChartTypeHelper.hxx>
+#include <RegressionCurveHelper.hxx>
+#include <unonames.hxx>
+
+#include <com/sun/star/chart/MissingValueTreatment.hpp>
+#include <com/sun/star/chart2/DataPointLabel.hpp>
+#include <com/sun/star/chart2/Symbol.hpp>
+#include <com/sun/star/chart2/XRegressionCurveCalculator.hpp>
+#include <com/sun/star/chart2/RelativePosition.hpp>
+
+#include <osl/diagnose.h>
+#include <tools/color.hxx>
+#include <tools/diagnose_ex.h>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XPropertyState.hpp>
+
+namespace chart {
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+using ::com::sun::star::uno::Reference;
+
+void VDataSequence::init( const uno::Reference< data::XDataSequence >& xModel )
+{
+ m_xModel = xModel;
+ m_aValues = DataSequenceToDoubleSequence( xModel );
+}
+
+bool VDataSequence::is() const
+{
+ return m_xModel.is();
+}
+void VDataSequence::clear()
+{
+ m_xModel = nullptr;
+ m_aValues.realloc(0);
+}
+
+double VDataSequence::getValue( sal_Int32 index ) const
+{
+ if( 0<=index && index<m_aValues.getLength() )
+ return m_aValues[index];
+ return std::numeric_limits<double>::quiet_NaN();
+}
+
+sal_Int32 VDataSequence::detectNumberFormatKey( sal_Int32 index ) const
+{
+ sal_Int32 nNumberFormatKey = -1;
+
+ // -1 is allowed and means a key for the whole sequence
+ if( -1<=index && index<m_aValues.getLength() && m_xModel.is())
+ {
+ nNumberFormatKey = m_xModel->getNumberFormatKeyByIndex( index );
+ }
+
+ return nNumberFormatKey;
+}
+
+sal_Int32 VDataSequence::getLength() const
+{
+ return m_aValues.getLength();
+}
+
+namespace
+{
+struct lcl_LessXOfPoint
+{
+ bool operator() ( const std::vector< double >& first,
+ const std::vector< double >& second )
+ {
+ if( !first.empty() && !second.empty() )
+ {
+ return first[0]<second[0];
+ }
+ return false;
+ }
+};
+
+void lcl_clearIfNoValuesButTextIsContained( VDataSequence& rData, const uno::Reference<data::XDataSequence>& xDataSequence )
+{
+ //#i71686#, #i101968#, #i102428#
+ sal_Int32 nCount = rData.m_aValues.getLength();
+ for( sal_Int32 i = 0; i < nCount; ++i )
+ {
+ if( !std::isnan( rData.m_aValues[i] ) )
+ return;
+ }
+ //no double value is contained
+ //is there any text?
+ uno::Sequence< OUString > aStrings( DataSequenceToStringSequence( xDataSequence ) );
+ sal_Int32 nTextCount = aStrings.getLength();
+ for( sal_Int32 j = 0; j < nTextCount; ++j )
+ {
+ if( !aStrings[j].isEmpty() )
+ {
+ rData.clear();
+ return;
+ }
+ }
+ //no content at all
+}
+
+void lcl_maybeReplaceNanWithZero( double& rfValue, sal_Int32 nMissingValueTreatment )
+{
+ if( nMissingValueTreatment == css::chart::MissingValueTreatment::USE_ZERO
+ && (std::isnan(rfValue) || std::isinf(rfValue)) )
+ rfValue = 0.0;
+}
+
+}
+
+VDataSeries::VDataSeries( const rtl::Reference< DataSeries >& xDataSeries )
+ : m_nPolygonIndex(0)
+ , m_fLogicMinX(0.0)
+ , m_fLogicMaxX(0.0)
+ , m_fLogicZPos(0.0)
+ , m_xDataSeries(xDataSeries)
+ , m_nPointCount(0)
+ , m_pValueSequenceForDataLabelNumberFormatDetection(&m_aValues_Y)
+ , m_fXMeanValue(std::numeric_limits<double>::quiet_NaN())
+ , m_fYMeanValue(std::numeric_limits<double>::quiet_NaN())
+ , m_eStackingDirection(StackingDirection_NO_STACKING)
+ , m_nAxisIndex(0)
+ , m_bConnectBars(false)
+ , m_bGroupBarsPerAxis(true)
+ , m_nStartingAngle(90)
+ , m_nGlobalSeriesIndex(0)
+ , m_nCurrentAttributedPoint(-1)
+ , m_nMissingValueTreatment(css::chart::MissingValueTreatment::LEAVE_GAP)
+ , m_bAllowPercentValueInDataLabel(false)
+ , mpOldSeries(nullptr)
+ , mnPercent(0.0)
+{
+ m_xDataSeriesProps = m_xDataSeries;
+
+ const std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > & aDataSequences =
+ m_xDataSeries->getDataSequences2();
+
+ for(sal_Int32 nN = aDataSequences.size();nN--;)
+ {
+ uno::Reference<data::XDataSequence> xDataSequence( aDataSequences[nN]->getValues());
+ uno::Reference<beans::XPropertySet> xProp(xDataSequence, uno::UNO_QUERY );
+ if( xProp.is())
+ {
+ try
+ {
+ uno::Any aARole = xProp->getPropertyValue("Role");
+ OUString aRole;
+ aARole >>= aRole;
+
+ if (aRole == "values-x")
+ {
+ m_aValues_X.init( xDataSequence );
+ lcl_clearIfNoValuesButTextIsContained( m_aValues_X, xDataSequence );
+ }
+ else if (aRole =="values-y")
+ m_aValues_Y.init( xDataSequence );
+ else if (aRole == "values-min")
+ m_aValues_Y_Min.init( xDataSequence );
+ else if (aRole == "values-max")
+ m_aValues_Y_Max.init( xDataSequence );
+ else if (aRole == "values-first")
+ m_aValues_Y_First.init( xDataSequence );
+ else if (aRole == "values-last")
+ m_aValues_Y_Last.init( xDataSequence );
+ else if (aRole == "values-size")
+ m_aValues_Bubble_Size.init( xDataSequence );
+ else
+ {
+ VDataSequence aSequence;
+ aSequence.init(xDataSequence);
+ m_PropertyMap.insert(std::make_pair(aRole, aSequence));
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ }
+ }
+
+ //determine the point count
+ m_nPointCount = m_aValues_Y.getLength();
+ {
+ if( m_nPointCount < m_aValues_Bubble_Size.getLength() )
+ m_nPointCount = m_aValues_Bubble_Size.getLength();
+ if( m_nPointCount < m_aValues_Y_Min.getLength() )
+ m_nPointCount = m_aValues_Y_Min.getLength();
+ if( m_nPointCount < m_aValues_Y_Max.getLength() )
+ m_nPointCount = m_aValues_Y_Max.getLength();
+ if( m_nPointCount < m_aValues_Y_First.getLength() )
+ m_nPointCount = m_aValues_Y_First.getLength();
+ if( m_nPointCount < m_aValues_Y_Last.getLength() )
+ m_nPointCount = m_aValues_Y_Last.getLength();
+ }
+
+ if( !xDataSeries.is())
+ return;
+
+ try
+ {
+ //get AttributedDataPoints
+ xDataSeries->getPropertyValue("AttributedDataPoints") >>= m_aAttributedDataPointIndexList;
+
+ xDataSeries->getPropertyValue("StackingDirection") >>= m_eStackingDirection;
+
+ xDataSeries->getPropertyValue("AttachedAxisIndex") >>= m_nAxisIndex;
+ if(m_nAxisIndex<0)
+ m_nAxisIndex=0;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+VDataSeries::~VDataSeries()
+{
+}
+
+void VDataSeries::doSortByXValues()
+{
+ if( !(m_aValues_X.is() && m_aValues_X.m_aValues.hasElements()) )
+ return;
+
+ //prepare a vector for sorting
+ std::vector< std::vector< double > > aTmp;//outer vector are points, inner vector are the different values of the point
+ sal_Int32 nPointIndex = 0;
+ for( nPointIndex=0; nPointIndex < m_nPointCount; nPointIndex++ )
+ {
+ aTmp.push_back(
+ { ((nPointIndex < m_aValues_X.m_aValues.getLength()) ? m_aValues_X.m_aValues[nPointIndex]
+ : std::numeric_limits<double>::quiet_NaN()),
+ ((nPointIndex < m_aValues_Y.m_aValues.getLength()) ? m_aValues_Y.m_aValues[nPointIndex]
+ : std::numeric_limits<double>::quiet_NaN())
+ }
+ );
+ }
+
+ //do sort
+ std::stable_sort( aTmp.begin(), aTmp.end(), lcl_LessXOfPoint() );
+
+ //fill the sorted points back to the members
+ m_aValues_X.m_aValues.realloc( m_nPointCount );
+ auto pDoublesX = m_aValues_X.m_aValues.getArray();
+ m_aValues_Y.m_aValues.realloc( m_nPointCount );
+ auto pDoublesY = m_aValues_Y.m_aValues.getArray();
+
+ for( nPointIndex=0; nPointIndex < m_nPointCount; nPointIndex++ )
+ {
+ pDoublesX[nPointIndex]=aTmp[nPointIndex][0];
+ pDoublesY[nPointIndex]=aTmp[nPointIndex][1];
+ }
+}
+
+void VDataSeries::releaseShapes()
+{
+ m_xGroupShape.set(nullptr);
+ m_xLabelsGroupShape.set(nullptr);
+ m_xErrorXBarsGroupShape.set(nullptr);
+ m_xErrorYBarsGroupShape.set(nullptr);
+ m_xFrontSubGroupShape.set(nullptr);
+ m_xBackSubGroupShape.set(nullptr);
+
+ m_aPolyPolygonShape3D.clear();
+ m_nPolygonIndex = 0;
+}
+
+const rtl::Reference<::chart::DataSeries>& VDataSeries::getModel() const
+{
+ return m_xDataSeries;
+}
+
+void VDataSeries::setCategoryXAxis()
+{
+ m_aValues_X.clear();
+ m_bAllowPercentValueInDataLabel = true;
+}
+
+void VDataSeries::setXValues( const Reference< chart2::data::XDataSequence >& xValues )
+{
+ m_aValues_X.clear();
+ m_aValues_X.init( xValues );
+ m_bAllowPercentValueInDataLabel = true;
+}
+
+void VDataSeries::setXValuesIfNone( const Reference< chart2::data::XDataSequence >& xValues )
+{
+ if( m_aValues_X.is() )
+ return;
+
+ m_aValues_X.init( xValues );
+ lcl_clearIfNoValuesButTextIsContained( m_aValues_X, xValues );
+}
+
+void VDataSeries::setGlobalSeriesIndex( sal_Int32 nGlobalSeriesIndex )
+{
+ m_nGlobalSeriesIndex = nGlobalSeriesIndex;
+}
+
+void VDataSeries::setParticle( const OUString& rSeriesParticle )
+{
+ m_aSeriesParticle = rSeriesParticle;
+
+ //get CID
+ m_aCID = ObjectIdentifier::createClassifiedIdentifierForParticle( m_aSeriesParticle );
+ m_aPointCID_Stub = ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT, m_aSeriesParticle );
+
+ m_aLabelCID_Stub = ObjectIdentifier::createClassifiedIdentifierWithParent(
+ OBJECTTYPE_DATA_LABEL, u"", getLabelsCID() );
+}
+OUString VDataSeries::getErrorBarsCID(bool bYError) const
+{
+ OUString aChildParticle( ObjectIdentifier::getStringForType(
+ bYError ? OBJECTTYPE_DATA_ERRORS_Y : OBJECTTYPE_DATA_ERRORS_X )
+ + "=" );
+
+ return ObjectIdentifier::createClassifiedIdentifierForParticles(
+ m_aSeriesParticle, aChildParticle );
+}
+OUString VDataSeries::getLabelsCID() const
+{
+ OUString aChildParticle( ObjectIdentifier::getStringForType( OBJECTTYPE_DATA_LABELS ) + "=" );
+
+ return ObjectIdentifier::createClassifiedIdentifierForParticles(
+ m_aSeriesParticle, aChildParticle );
+}
+OUString VDataSeries::getDataCurveCID( sal_Int32 nCurveIndex, bool bAverageLine ) const
+{
+ return ObjectIdentifier::createDataCurveCID( m_aSeriesParticle, nCurveIndex, bAverageLine );
+}
+
+OUString VDataSeries::getDataCurveEquationCID( sal_Int32 nCurveIndex ) const
+{
+ return ObjectIdentifier::createDataCurveEquationCID( m_aSeriesParticle, nCurveIndex );
+}
+void VDataSeries::setPageReferenceSize( const awt::Size & rPageRefSize )
+{
+ m_aReferenceSize = rPageRefSize;
+}
+
+void VDataSeries::setConnectBars( bool bConnectBars )
+{
+ m_bConnectBars = bConnectBars;
+}
+
+bool VDataSeries::getConnectBars() const
+{
+ return m_bConnectBars;
+}
+
+void VDataSeries::setGroupBarsPerAxis( bool bGroupBarsPerAxis )
+{
+ m_bGroupBarsPerAxis = bGroupBarsPerAxis;
+}
+
+bool VDataSeries::getGroupBarsPerAxis() const
+{
+ return m_bGroupBarsPerAxis;
+}
+
+void VDataSeries::setStartingAngle( sal_Int32 nStartingAngle )
+{
+ m_nStartingAngle = nStartingAngle;
+}
+
+sal_Int32 VDataSeries::getStartingAngle() const
+{
+ return m_nStartingAngle;
+}
+
+chart2::StackingDirection VDataSeries::getStackingDirection() const
+{
+ return m_eStackingDirection;
+}
+
+sal_Int32 VDataSeries::getAttachedAxisIndex() const
+{
+ return m_nAxisIndex;
+}
+
+void VDataSeries::setAttachedAxisIndex( sal_Int32 nAttachedAxisIndex )
+{
+ if( nAttachedAxisIndex < 0 )
+ nAttachedAxisIndex = 0;
+ m_nAxisIndex = nAttachedAxisIndex;
+}
+
+double VDataSeries::getXValue( sal_Int32 index ) const
+{
+ double fRet = std::numeric_limits<double>::quiet_NaN();
+ if(m_aValues_X.is())
+ {
+ if( 0<=index && index<m_aValues_X.getLength() )
+ {
+ fRet = m_aValues_X.m_aValues[index];
+ if(mpOldSeries && index < mpOldSeries->m_aValues_X.getLength())
+ {
+ double nOldVal = mpOldSeries->m_aValues_X.m_aValues[index];
+ fRet = nOldVal + (fRet - nOldVal) * mnPercent;
+ }
+ }
+ }
+ else
+ {
+ // #i70133# always return correct X position - needed for short data series
+ if( 0<=index /*&& index < m_nPointCount*/ )
+ fRet = index+1;//first category (index 0) matches with real number 1.0
+ }
+ lcl_maybeReplaceNanWithZero( fRet, getMissingValueTreatment() );
+ return fRet;
+}
+
+double VDataSeries::getYValue( sal_Int32 index ) const
+{
+ double fRet = std::numeric_limits<double>::quiet_NaN();
+ if(m_aValues_Y.is())
+ {
+ if( 0<=index && index<m_aValues_Y.getLength() )
+ {
+ fRet = m_aValues_Y.m_aValues[index];
+ if(mpOldSeries && index < mpOldSeries->m_aValues_Y.getLength())
+ {
+ double nOldVal = mpOldSeries->m_aValues_Y.m_aValues[index];
+ fRet = nOldVal + (fRet - nOldVal) * mnPercent;
+ }
+ }
+ }
+ else
+ {
+ // #i70133# always return correct X position - needed for short data series
+ if( 0<=index /*&& index < m_nPointCount*/ )
+ fRet = index+1;//first category (index 0) matches with real number 1.0
+ }
+ lcl_maybeReplaceNanWithZero( fRet, getMissingValueTreatment() );
+ return fRet;
+}
+
+void VDataSeries::getMinMaxXValue(double& fMin, double& fMax) const
+{
+ fMax = std::numeric_limits<double>::quiet_NaN();
+ fMin = std::numeric_limits<double>::quiet_NaN();
+
+ uno::Sequence< double > aValuesX = getAllX();
+
+ if(!aValuesX.hasElements())
+ return;
+
+ sal_Int32 i = 0;
+ while ( i < aValuesX.getLength() && std::isnan(aValuesX[i]) )
+ i++;
+ if ( i < aValuesX.getLength() )
+ fMax = fMin = aValuesX[i++];
+
+ for ( ; i < aValuesX.getLength(); i++)
+ {
+ const double aValue = aValuesX[i];
+ if ( aValue > fMax)
+ {
+ fMax = aValue;
+ }
+ else if ( aValue < fMin)
+ {
+ fMin = aValue;
+ }
+ }
+}
+double VDataSeries::getY_Min( sal_Int32 index ) const
+{
+ return m_aValues_Y_Min.getValue( index );
+}
+double VDataSeries::getY_Max( sal_Int32 index ) const
+{
+ return m_aValues_Y_Max.getValue( index );
+}
+double VDataSeries::getY_First( sal_Int32 index ) const
+{
+ return m_aValues_Y_First.getValue( index );
+}
+double VDataSeries::getY_Last( sal_Int32 index ) const
+{
+ return m_aValues_Y_Last.getValue( index );
+}
+double VDataSeries::getBubble_Size( sal_Int32 index ) const
+{
+ double nNewVal = m_aValues_Bubble_Size.getValue( index );
+ if(mpOldSeries && index < mpOldSeries->m_aValues_Bubble_Size.getLength())
+ {
+ double nOldVal = mpOldSeries->m_aValues_Bubble_Size.getValue( index );
+ nNewVal = nOldVal + (nNewVal - nOldVal) * mnPercent;
+ }
+
+ return nNewVal;
+}
+
+bool VDataSeries::hasExplicitNumberFormat( sal_Int32 nPointIndex, bool bForPercentage ) const
+{
+ OUString aPropName = bForPercentage ? OUString("PercentageNumberFormat") : OUString(CHART_UNONAME_NUMFMT);
+ bool bHasNumberFormat = false;
+ bool bLinkToSource = true;
+ uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( nPointIndex ));
+ if( xPointProp.is() && (xPointProp->getPropertyValue(CHART_UNONAME_LINK_TO_SRC_NUMFMT) >>= bLinkToSource))
+ {
+ sal_Int32 nNumberFormat = -1;
+ if( !bLinkToSource && (xPointProp->getPropertyValue(aPropName) >>= nNumberFormat))
+ bHasNumberFormat = true;
+ }
+ return bHasNumberFormat;
+}
+sal_Int32 VDataSeries::getExplicitNumberFormat( sal_Int32 nPointIndex, bool bForPercentage ) const
+{
+ OUString aPropName = bForPercentage ? OUString("PercentageNumberFormat") : OUString(CHART_UNONAME_NUMFMT);
+ sal_Int32 nNumberFormat = -1;
+ uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( nPointIndex ));
+ if( xPointProp.is() )
+ xPointProp->getPropertyValue(aPropName) >>= nNumberFormat;
+ return nNumberFormat;
+}
+void VDataSeries::setRoleOfSequenceForDataLabelNumberFormatDetection( std::u16string_view rRole )
+{
+ if (rRole == u"values-y")
+ m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y;
+ else if (rRole == u"values-size")
+ m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Bubble_Size;
+ else if (rRole == u"values-min")
+ m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_Min;
+ else if (rRole == u"values-max")
+ m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_Max;
+ else if (rRole == u"values-first")
+ m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_First;
+ else if (rRole == u"values-last")
+ m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_Last;
+ else if (rRole == u"values-x")
+ m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_X;
+}
+sal_Int32 VDataSeries::detectNumberFormatKey( sal_Int32 index ) const
+{
+ sal_Int32 nRet = 0;
+ if( m_pValueSequenceForDataLabelNumberFormatDetection )
+ nRet = m_pValueSequenceForDataLabelNumberFormatDetection->detectNumberFormatKey( index );
+ return nRet;
+}
+
+sal_Int32 VDataSeries::getLabelPlacement( sal_Int32 nPointIndex, const rtl::Reference< ChartType >& xChartType, bool bSwapXAndY ) const
+{
+ sal_Int32 nLabelPlacement=0;
+ try
+ {
+ uno::Reference< beans::XPropertySet > xPointProps( getPropertiesOfPoint( nPointIndex ) );
+ if( xPointProps.is() )
+ xPointProps->getPropertyValue("LabelPlacement") >>= nLabelPlacement;
+
+ const uno::Sequence < sal_Int32 > aAvailablePlacements( ChartTypeHelper::getSupportedLabelPlacements(
+ xChartType, bSwapXAndY, m_xDataSeries ) );
+
+ for( sal_Int32 n : aAvailablePlacements )
+ if( n == nLabelPlacement )
+ return nLabelPlacement; //ok
+
+ //otherwise use the first supported one
+ if( aAvailablePlacements.hasElements() )
+ {
+ nLabelPlacement = aAvailablePlacements[0];
+ if( xPointProps.is() )
+ xPointProps->setPropertyValue("LabelPlacement", uno::Any(nLabelPlacement));
+ return nLabelPlacement;
+ }
+
+ OSL_FAIL("no label placement supported");
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return nLabelPlacement;
+}
+
+awt::Point VDataSeries::getLabelPosition( awt::Point aTextShapePos, sal_Int32 nPointIndex ) const
+{
+ awt::Point aPos(-1, -1);
+ try
+ {
+ RelativePosition aCustomLabelPosition;
+ uno::Reference< beans::XPropertySet > xPointProps(getPropertiesOfPoint(nPointIndex));
+ if( xPointProps.is() && (xPointProps->getPropertyValue("CustomLabelPosition") >>= aCustomLabelPosition))
+ {
+ aPos.X = static_cast<sal_Int32>(aCustomLabelPosition.Primary * m_aReferenceSize.Width) + aTextShapePos.X;
+ aPos.Y = static_cast<sal_Int32>(aCustomLabelPosition.Secondary * m_aReferenceSize.Height) + aTextShapePos.Y;
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "");
+ }
+ return aPos;
+}
+
+bool VDataSeries::isLabelCustomPos(sal_Int32 nPointIndex) const
+{
+ bool bCustom = false;
+ try
+ {
+ if( isAttributedDataPoint(nPointIndex) )
+ {
+ uno::Reference< beans::XPropertySet > xPointProps(m_xDataSeries->getDataPointByIndex(nPointIndex));
+ RelativePosition aCustomLabelPosition;
+ if( xPointProps.is() && (xPointProps->getPropertyValue("CustomLabelPosition") >>= aCustomLabelPosition) )
+ bCustom = true;
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "");
+ }
+ return bCustom;
+}
+
+double VDataSeries::getMinimumofAllDifferentYValues( sal_Int32 index ) const
+{
+ double fMin = std::numeric_limits<double>::infinity();
+
+ if( !m_aValues_Y.is() &&
+ (m_aValues_Y_Min.is() || m_aValues_Y_Max.is()
+ || m_aValues_Y_First.is() || m_aValues_Y_Last.is() ) )
+ {
+ double fY_Min = getY_Min( index );
+ double fY_Max = getY_Max( index );
+ double fY_First = getY_First( index );
+ double fY_Last = getY_Last( index );
+
+ if(fMin>fY_First)
+ fMin=fY_First;
+ if(fMin>fY_Last)
+ fMin=fY_Last;
+ if(fMin>fY_Min)
+ fMin=fY_Min;
+ if(fMin>fY_Max)
+ fMin=fY_Max;
+ }
+ else
+ {
+ double fY = getYValue( index );
+ if(fMin>fY)
+ fMin=fY;
+ }
+
+ if( std::isinf(fMin) )
+ return std::numeric_limits<double>::quiet_NaN();
+
+ return fMin;
+}
+
+double VDataSeries::getMaximumofAllDifferentYValues( sal_Int32 index ) const
+{
+ double fMax = -std::numeric_limits<double>::infinity();
+
+ if( !m_aValues_Y.is() &&
+ (m_aValues_Y_Min.is() || m_aValues_Y_Max.is()
+ || m_aValues_Y_First.is() || m_aValues_Y_Last.is() ) )
+ {
+ double fY_Min = getY_Min( index );
+ double fY_Max = getY_Max( index );
+ double fY_First = getY_First( index );
+ double fY_Last = getY_Last( index );
+
+ if(fMax<fY_First)
+ fMax=fY_First;
+ if(fMax<fY_Last)
+ fMax=fY_Last;
+ if(fMax<fY_Min)
+ fMax=fY_Min;
+ if(fMax<fY_Max)
+ fMax=fY_Max;
+ }
+ else
+ {
+ double fY = getYValue( index );
+ if(fMax<fY)
+ fMax=fY;
+ }
+
+ if( std::isinf(fMax) )
+ return std::numeric_limits<double>::quiet_NaN();
+
+ return fMax;
+}
+
+uno::Sequence< double > const & VDataSeries::getAllX() const
+{
+ if(!m_aValues_X.is() && !m_aValues_X.getLength() && m_nPointCount)
+ {
+ //init x values from category indexes
+ //first category (index 0) matches with real number 1.0
+ m_aValues_X.m_aValues.realloc( m_nPointCount );
+ auto pDoubles = m_aValues_X.m_aValues.getArray();
+ for(sal_Int32 nN=m_aValues_X.getLength();nN--;)
+ pDoubles[nN] = nN+1;
+ }
+ return m_aValues_X.m_aValues;
+}
+
+uno::Sequence< double > const & VDataSeries::getAllY() const
+{
+ if(!m_aValues_Y.is() && !m_aValues_Y.getLength() && m_nPointCount)
+ {
+ //init y values from indexes
+ //first y-value (index 0) matches with real number 1.0
+ m_aValues_Y.m_aValues.realloc( m_nPointCount );
+ auto pDoubles = m_aValues_Y.m_aValues.getArray();
+ for(sal_Int32 nN=m_aValues_Y.getLength();nN--;)
+ pDoubles[nN] = nN+1;
+ }
+ return m_aValues_Y.m_aValues;
+}
+
+double VDataSeries::getXMeanValue() const
+{
+ if( std::isnan( m_fXMeanValue ) )
+ {
+ uno::Reference< XRegressionCurveCalculator > xCalculator( RegressionCurveHelper::createRegressionCurveCalculatorByServiceName( u"com.sun.star.chart2.MeanValueRegressionCurve" ) );
+ uno::Sequence< double > aXValuesDummy;
+ xCalculator->recalculateRegression( aXValuesDummy, getAllX() );
+ m_fXMeanValue = xCalculator->getCurveValue( 1.0 );
+ }
+ return m_fXMeanValue;
+}
+
+double VDataSeries::getYMeanValue() const
+{
+ if( std::isnan( m_fYMeanValue ) )
+ {
+ uno::Reference< XRegressionCurveCalculator > xCalculator(
+ RegressionCurveHelper::createRegressionCurveCalculatorByServiceName(u"com.sun.star.chart2.MeanValueRegressionCurve"));
+ uno::Sequence< double > aXValuesDummy;
+ xCalculator->recalculateRegression( aXValuesDummy, getAllY() );
+ m_fYMeanValue = xCalculator->getCurveValue( 1.0 );
+ }
+ return m_fYMeanValue;
+}
+
+static std::unique_ptr<Symbol> getSymbolPropertiesFromPropertySet( const uno::Reference< beans::XPropertySet >& xProp )
+{
+ std::unique_ptr< Symbol > apSymbolProps( new Symbol() );
+ try
+ {
+ if( xProp->getPropertyValue("Symbol") >>= *apSymbolProps )
+ {
+ //use main color to fill symbols
+ xProp->getPropertyValue("Color") >>= apSymbolProps->FillColor;
+ // border of symbols always same as fill color
+ apSymbolProps->BorderColor = apSymbolProps->FillColor;
+ }
+ else
+ apSymbolProps.reset();
+ }
+ catch(const uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return apSymbolProps;
+}
+
+Symbol* VDataSeries::getSymbolProperties( sal_Int32 index ) const
+{
+ Symbol* pRet=nullptr;
+ if( isAttributedDataPoint( index ) )
+ {
+ adaptPointCache( index );
+ if (!m_apSymbolProperties_AttributedPoint)
+ m_apSymbolProperties_AttributedPoint
+ = getSymbolPropertiesFromPropertySet(getPropertiesOfPoint(index));
+ pRet = m_apSymbolProperties_AttributedPoint.get();
+ //if a single data point does not have symbols but the dataseries itself has symbols
+ //we create an invisible symbol shape to enable selection of that point
+ if( !pRet || pRet->Style == SymbolStyle_NONE )
+ {
+ if (!m_apSymbolProperties_Series)
+ m_apSymbolProperties_Series
+ = getSymbolPropertiesFromPropertySet(getPropertiesOfSeries());
+ if( m_apSymbolProperties_Series && m_apSymbolProperties_Series->Style != SymbolStyle_NONE )
+ {
+ if (!m_apSymbolProperties_InvisibleSymbolForSelection)
+ {
+ m_apSymbolProperties_InvisibleSymbolForSelection.reset(new Symbol);
+ m_apSymbolProperties_InvisibleSymbolForSelection->Style = SymbolStyle_STANDARD;
+ m_apSymbolProperties_InvisibleSymbolForSelection->StandardSymbol = 0;//square
+ m_apSymbolProperties_InvisibleSymbolForSelection->Size = com::sun::star::awt::Size(0, 0);//tdf#126033
+ m_apSymbolProperties_InvisibleSymbolForSelection->BorderColor = 0xff000000;//invisible
+ m_apSymbolProperties_InvisibleSymbolForSelection->FillColor = 0xff000000;//invisible
+ }
+ pRet = m_apSymbolProperties_InvisibleSymbolForSelection.get();
+ }
+ }
+ }
+ else
+ {
+ if (!m_apSymbolProperties_Series)
+ m_apSymbolProperties_Series
+ = getSymbolPropertiesFromPropertySet(getPropertiesOfSeries());
+ pRet = m_apSymbolProperties_Series.get();
+ }
+
+ if( pRet && pRet->Style == SymbolStyle_AUTO )
+ {
+ pRet->Style = SymbolStyle_STANDARD;
+
+ sal_Int32 nIndex = m_nGlobalSeriesIndex;
+ if(m_aValues_X.is())
+ nIndex++;
+ pRet->StandardSymbol = nIndex;
+ }
+
+ return pRet;
+}
+
+uno::Reference< beans::XPropertySet > VDataSeries::getXErrorBarProperties( sal_Int32 index ) const
+{
+ uno::Reference< beans::XPropertySet > xErrorBarProp;
+
+ uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( index ));
+ if( xPointProp.is() )
+ xPointProp->getPropertyValue(CHART_UNONAME_ERRORBAR_X) >>= xErrorBarProp;
+ return xErrorBarProp;
+}
+
+uno::Reference< beans::XPropertySet > VDataSeries::getYErrorBarProperties( sal_Int32 index ) const
+{
+ uno::Reference< beans::XPropertySet > xErrorBarProp;
+
+ uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( index ));
+ if( xPointProp.is() )
+ xPointProp->getPropertyValue(CHART_UNONAME_ERRORBAR_Y) >>= xErrorBarProp;
+ return xErrorBarProp;
+}
+
+bool VDataSeries::hasPointOwnColor( sal_Int32 index ) const
+{
+ if( !isAttributedDataPoint(index) )
+ return false;
+
+ try
+ {
+ uno::Reference< beans::XPropertyState > xPointState( getPropertiesOfPoint(index), uno::UNO_QUERY_THROW );
+ return (xPointState->getPropertyState("Color") != beans::PropertyState_DEFAULT_VALUE );
+ }
+ catch(const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return false;
+}
+
+bool VDataSeries::isAttributedDataPoint( sal_Int32 index ) const
+{
+ //returns true if the data point assigned by the given index has set its own properties
+ if( index>=m_nPointCount || m_nPointCount==0)
+ return false;
+ for(sal_Int32 n : m_aAttributedDataPointIndexList)
+ {
+ if(index == n)
+ return true;
+ }
+ return false;
+}
+
+bool VDataSeries::isVaryColorsByPoint() const
+{
+ bool bVaryColorsByPoint = false;
+ Reference< beans::XPropertySet > xSeriesProp( getPropertiesOfSeries() );
+ if( xSeriesProp.is() )
+ xSeriesProp->getPropertyValue("VaryColorsByPoint") >>= bVaryColorsByPoint;
+ return bVaryColorsByPoint;
+}
+
+uno::Reference< beans::XPropertySet > VDataSeries::getPropertiesOfPoint( sal_Int32 index ) const
+{
+ if( isAttributedDataPoint( index ) )
+ return m_xDataSeries->getDataPointByIndex(index);
+ return getPropertiesOfSeries();
+}
+
+const uno::Reference<beans::XPropertySet> & VDataSeries::getPropertiesOfSeries() const
+{
+ return m_xDataSeriesProps;
+}
+
+static std::unique_ptr<DataPointLabel> getDataPointLabelFromPropertySet( const uno::Reference< beans::XPropertySet >& xProp )
+{
+ std::unique_ptr< DataPointLabel > apLabel( new DataPointLabel() );
+ try
+ {
+ if( !(xProp->getPropertyValue(CHART_UNONAME_LABEL) >>= *apLabel) )
+ apLabel.reset();
+ }
+ catch(const uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+ return apLabel;
+}
+
+void VDataSeries::adaptPointCache( sal_Int32 nNewPointIndex ) const
+{
+ if( m_nCurrentAttributedPoint != nNewPointIndex )
+ {
+ m_apLabel_AttributedPoint.reset();
+ m_apLabelPropNames_AttributedPoint.reset();
+ m_apLabelPropValues_AttributedPoint.reset();
+ m_apSymbolProperties_AttributedPoint.reset();
+ m_nCurrentAttributedPoint = nNewPointIndex;
+ }
+}
+
+DataPointLabel* VDataSeries::getDataPointLabel( sal_Int32 index ) const
+{
+ DataPointLabel* pRet = nullptr;
+ if( isAttributedDataPoint( index ) )
+ {
+ adaptPointCache( index );
+ if (!m_apLabel_AttributedPoint)
+ m_apLabel_AttributedPoint
+ = getDataPointLabelFromPropertySet(getPropertiesOfPoint(index));
+ pRet = m_apLabel_AttributedPoint.get();
+ }
+ else
+ {
+ if (!m_apLabel_Series)
+ m_apLabel_Series
+ = getDataPointLabelFromPropertySet(getPropertiesOfPoint(index));
+ pRet = m_apLabel_Series.get();
+ }
+ if( !m_bAllowPercentValueInDataLabel )
+ {
+ if( pRet )
+ pRet->ShowNumberInPercent = false;
+ }
+ return pRet;
+}
+
+DataPointLabel* VDataSeries::getDataPointLabelIfLabel( sal_Int32 index ) const
+{
+ DataPointLabel* pLabel = getDataPointLabel( index );
+ if( !pLabel || (!pLabel->ShowNumber && !pLabel->ShowNumberInPercent
+ && !pLabel->ShowCategoryName && !pLabel->ShowCustomLabel && !pLabel->ShowSeriesName ) )
+ return nullptr;
+ return pLabel;
+}
+
+bool VDataSeries::getTextLabelMultiPropertyLists( sal_Int32 index
+ , tNameSequence*& pPropNames
+ , tAnySequence*& pPropValues ) const
+{
+ pPropNames = nullptr; pPropValues = nullptr;
+ uno::Reference< beans::XPropertySet > xTextProp;
+ bool bDoDynamicFontResize = false;
+ if( isAttributedDataPoint( index ) )
+ {
+ adaptPointCache( index );
+ if (!m_apLabelPropValues_AttributedPoint)
+ {
+ // Cache these properties for this point.
+ m_apLabelPropNames_AttributedPoint.reset(new tNameSequence);
+ m_apLabelPropValues_AttributedPoint.reset(new tAnySequence);
+ xTextProp.set( getPropertiesOfPoint( index ));
+ PropertyMapper::getTextLabelMultiPropertyLists(
+ xTextProp, *m_apLabelPropNames_AttributedPoint, *m_apLabelPropValues_AttributedPoint);
+ bDoDynamicFontResize = true;
+ }
+ pPropNames = m_apLabelPropNames_AttributedPoint.get();
+ pPropValues = m_apLabelPropValues_AttributedPoint.get();
+ }
+ else
+ {
+ if (!m_apLabelPropValues_Series)
+ {
+ // Cache these properties for the whole series.
+ m_apLabelPropNames_Series.reset(new tNameSequence);
+ m_apLabelPropValues_Series.reset(new tAnySequence);
+ xTextProp.set( getPropertiesOfPoint( index ));
+ PropertyMapper::getTextLabelMultiPropertyLists(
+ xTextProp, *m_apLabelPropNames_Series, *m_apLabelPropValues_Series);
+ bDoDynamicFontResize = true;
+ }
+ pPropNames = m_apLabelPropNames_Series.get();
+ pPropValues = m_apLabelPropValues_Series.get();
+ }
+
+ if( bDoDynamicFontResize &&
+ pPropNames && pPropValues &&
+ xTextProp.is())
+ {
+ LabelPositionHelper::doDynamicFontResize( *pPropValues, *pPropNames, xTextProp, m_aReferenceSize );
+ }
+
+ return (pPropNames && pPropValues);
+}
+
+void VDataSeries::setMissingValueTreatment( sal_Int32 nMissingValueTreatment )
+{
+ m_nMissingValueTreatment = nMissingValueTreatment;
+}
+
+sal_Int32 VDataSeries::getMissingValueTreatment() const
+{
+ return m_nMissingValueTreatment;
+}
+
+VDataSeries::VDataSeries()
+ : m_nPolygonIndex(0)
+ , m_fLogicMinX(0)
+ , m_fLogicMaxX(0)
+ , m_fLogicZPos(0)
+ , m_nPointCount(0)
+ , m_pValueSequenceForDataLabelNumberFormatDetection(nullptr)
+ , m_fXMeanValue(0)
+ , m_fYMeanValue(0)
+ , m_eStackingDirection(chart2::StackingDirection_NO_STACKING)
+ , m_nAxisIndex(0)
+ , m_bConnectBars(false)
+ , m_bGroupBarsPerAxis(false)
+ , m_nStartingAngle(0)
+ , m_nGlobalSeriesIndex(0)
+ , m_nCurrentAttributedPoint(0)
+ , m_nMissingValueTreatment(0)
+ , m_bAllowPercentValueInDataLabel(false)
+ , mpOldSeries(nullptr)
+ , mnPercent(0)
+{
+}
+
+void VDataSeries::setOldTimeBased( VDataSeries* pOldSeries, double nPercent )
+{
+ mnPercent = nPercent;
+ mpOldSeries = pOldSeries;
+ mpOldSeries->mpOldSeries = nullptr;
+}
+
+VDataSeries* VDataSeries::createCopyForTimeBased() const
+{
+ VDataSeries* pNew = new VDataSeries();
+ pNew->m_aValues_X = m_aValues_X;
+ pNew->m_aValues_Y = m_aValues_Y;
+ pNew->m_aValues_Z = m_aValues_Z;
+ pNew->m_aValues_Y_Min = m_aValues_Y_Min;
+ pNew->m_aValues_Y_Max = m_aValues_Y_Max;
+ pNew->m_aValues_Y_First = m_aValues_Y_First;
+ pNew->m_aValues_Y_Last = m_aValues_Y_Last;
+ pNew->m_aValues_Bubble_Size = m_aValues_Bubble_Size;
+ pNew->m_PropertyMap = m_PropertyMap;
+
+ pNew->m_nPointCount = m_nPointCount;
+
+ return pNew;
+}
+
+double VDataSeries::getValueByProperty( sal_Int32 nIndex, const OUString& rPropName ) const
+{
+ auto const itr = m_PropertyMap.find(rPropName);
+ if (itr == m_PropertyMap.end())
+ return std::numeric_limits<double>::quiet_NaN();
+
+ const VDataSequence* pData = &itr->second;
+ double fValue = pData->getValue(nIndex);
+ if(mpOldSeries && mpOldSeries->hasPropertyMapping(rPropName))
+ {
+ double fOldValue = mpOldSeries->getValueByProperty( nIndex, rPropName );
+ if(rPropName.endsWith("Color"))
+ {
+ //optimized interpolation for color values
+ Color aColor(ColorTransparency, static_cast<sal_uInt32>(fValue));
+ Color aOldColor(ColorTransparency, static_cast<sal_uInt32>(fOldValue));
+ sal_uInt8 r = aOldColor.GetRed() + (aColor.GetRed() - aOldColor.GetRed()) * mnPercent;
+ sal_uInt8 g = aOldColor.GetGreen() + (aColor.GetGreen() - aOldColor.GetGreen()) * mnPercent;
+ sal_uInt8 b = aOldColor.GetBlue() + (aColor.GetBlue() - aOldColor.GetBlue()) * mnPercent;
+ sal_uInt8 a = aOldColor.GetAlpha() + (aColor.GetAlpha() - aOldColor.GetAlpha()) * mnPercent;
+ Color aRet(ColorAlpha, a, r, g, b);
+ return sal_uInt32(aRet);
+ }
+ return fOldValue + (fValue - fOldValue) * mnPercent;
+ }
+ return fValue;
+}
+
+bool VDataSeries::hasPropertyMapping(const OUString& rPropName ) const
+{
+ return m_PropertyMap.find(rPropName) != m_PropertyMap.end();
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/VLegend.cxx b/chart2/source/view/main/VLegend.cxx
new file mode 100644
index 000000000..794d8d41e
--- /dev/null
+++ b/chart2/source/view/main/VLegend.cxx
@@ -0,0 +1,1099 @@
+/* -*- 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 "VLegend.hxx"
+#include "VButton.hxx"
+#include <Legend.hxx>
+#include <PropertyMapper.hxx>
+#include <ChartModel.hxx>
+#include <ObjectIdentifier.hxx>
+#include <RelativePositionHelper.hxx>
+#include <ShapeFactory.hxx>
+#include <RelativeSizeHelper.hxx>
+#include <LegendEntryProvider.hxx>
+#include <chartview/DrawModelWrapper.hxx>
+#include <com/sun/star/text/WritingMode2.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
+#include <com/sun/star/drawing/LineJoint.hpp>
+#include <com/sun/star/chart/ChartLegendExpansion.hpp>
+#include <com/sun/star/chart2/LegendPosition.hpp>
+#include <com/sun/star/chart2/RelativePosition.hpp>
+#include <com/sun/star/chart2/RelativeSize.hpp>
+#include <com/sun/star/chart2/XFormattedString2.hpp>
+#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>
+#include <com/sun/star/chart2/data/PivotTableFieldEntry.hpp>
+#include <rtl/math.hxx>
+#include <svl/ctloptions.hxx>
+#include <tools/diagnose_ex.h>
+#include <tools/UnitConversion.hxx>
+
+#include <vector>
+#include <algorithm>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::uno::Sequence;
+
+namespace chart
+{
+
+namespace
+{
+
+typedef std::pair< ::chart::tNameSequence, ::chart::tAnySequence > tPropertyValues;
+
+double lcl_CalcViewFontSize(
+ const Reference< beans::XPropertySet > & xProp,
+ const awt::Size & rReferenceSize )
+{
+ double fResult = 10.0;
+
+ float fFontHeight( 0.0 );
+ if( xProp.is() && ( xProp->getPropertyValue( "CharHeight") >>= fFontHeight ))
+ {
+ fResult = fFontHeight;
+ try
+ {
+ awt::Size aPropRefSize;
+ if( (xProp->getPropertyValue( "ReferencePageSize") >>= aPropRefSize) &&
+ (aPropRefSize.Height > 0))
+ {
+ fResult = ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize );
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+ }
+
+ return convertPointToMm100(fResult);
+}
+
+void lcl_getProperties(
+ const Reference< beans::XPropertySet > & xLegendProp,
+ tPropertyValues & rOutLineFillProperties,
+ tPropertyValues & rOutTextProperties,
+ const awt::Size & rReferenceSize )
+{
+ // Get Line- and FillProperties from model legend
+ if( !xLegendProp.is())
+ return;
+
+ // set rOutLineFillProperties
+ ::chart::tPropertyNameValueMap aLineFillValueMap;
+ ::chart::PropertyMapper::getValueMap( aLineFillValueMap, ::chart::PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xLegendProp );
+
+ aLineFillValueMap[ "LineJoint" ] <<= drawing::LineJoint_ROUND;
+
+ ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
+ rOutLineFillProperties.first, rOutLineFillProperties.second, aLineFillValueMap );
+
+ // set rOutTextProperties
+ ::chart::tPropertyNameValueMap aTextValueMap;
+ ::chart::PropertyMapper::getValueMap( aTextValueMap, ::chart::PropertyMapper::getPropertyNameMapForCharacterProperties(), xLegendProp );
+
+ aTextValueMap[ "TextAutoGrowHeight" ] <<= true;
+ aTextValueMap[ "TextAutoGrowWidth" ] <<= true;
+ aTextValueMap[ "TextHorizontalAdjust" ] <<= drawing::TextHorizontalAdjust_LEFT;
+ aTextValueMap[ "TextMaximumFrameWidth" ] <<= rReferenceSize.Width; //needs to be overwritten by actual available space in the legend
+
+ // recalculate font size
+ awt::Size aPropRefSize;
+ float fFontHeight( 0.0 );
+ if( (xLegendProp->getPropertyValue( "ReferencePageSize") >>= aPropRefSize) &&
+ (aPropRefSize.Height > 0) &&
+ (aTextValueMap[ "CharHeight" ] >>= fFontHeight) )
+ {
+ aTextValueMap[ "CharHeight" ] <<=
+ static_cast< float >(
+ ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ));
+
+ if( aTextValueMap[ "CharHeightAsian" ] >>= fFontHeight )
+ {
+ aTextValueMap[ "CharHeightAsian" ] <<=
+ static_cast< float >(
+ ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ));
+ }
+ if( aTextValueMap[ "CharHeightComplex" ] >>= fFontHeight )
+ {
+ aTextValueMap[ "CharHeightComplex" ] <<=
+ static_cast< float >(
+ ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ));
+ }
+ }
+
+ ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
+ rOutTextProperties.first, rOutTextProperties.second, aTextValueMap );
+}
+
+awt::Size lcl_createTextShapes(
+ const std::vector<ViewLegendEntry> & rEntries,
+ const rtl::Reference<SvxShapeGroupAnyD> & xTarget,
+ std::vector< rtl::Reference<SvxShapeText> > & rOutTextShapes,
+ const tPropertyValues & rTextProperties )
+{
+ awt::Size aResult;
+
+ for (ViewLegendEntry const & rEntry : rEntries)
+ {
+ try
+ {
+ OUString aLabelString;
+ Sequence< Reference< XFormattedString2 > > aLabelSeq = rEntry.aLabel;
+ for( sal_Int32 i = 0; i < aLabelSeq.getLength(); ++i )
+ {
+ // todo: support more than one text range
+ if( i == 1 )
+ break;
+
+ aLabelString += aLabelSeq[i]->getString();
+ // workaround for Issue #i67540#
+ if( aLabelString.isEmpty())
+ aLabelString = " ";
+ }
+
+ rtl::Reference<SvxShapeText> xEntry =
+ ShapeFactory::createText( xTarget, aLabelString,
+ rTextProperties.first, rTextProperties.second, uno::Any() );
+
+ // adapt max-extent
+ awt::Size aCurrSize( xEntry->getSize());
+ aResult.Width = std::max( aResult.Width, aCurrSize.Width );
+ aResult.Height = std::max( aResult.Height, aCurrSize.Height );
+
+ rOutTextShapes.push_back( xEntry );
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+ }
+
+ return aResult;
+}
+
+void lcl_collectColumnWidths( std::vector< sal_Int32 >& rColumnWidths, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns,
+ const std::vector< rtl::Reference<SvxShapeText> >& rTextShapes, sal_Int32 nSymbolPlusDistanceWidth )
+{
+ rColumnWidths.clear();
+ sal_Int32 nNumberOfEntries = rTextShapes.size();
+ for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow )
+ {
+ for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
+ {
+ sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
+ if( nEntry < nNumberOfEntries )
+ {
+ awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() );
+ sal_Int32 nWidth = nSymbolPlusDistanceWidth + aTextSize.Width;
+ if( nRow==0 )
+ rColumnWidths.push_back( nWidth );
+ else
+ rColumnWidths[nColumn] = std::max( nWidth, rColumnWidths[nColumn] );
+ }
+ }
+ }
+}
+
+void lcl_collectRowHeighs( std::vector< sal_Int32 >& rRowHeights, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns,
+ const std::vector< rtl::Reference<SvxShapeText> >& rTextShapes )
+{
+ // calculate maximum height for each row
+ // and collect column widths
+ rRowHeights.clear();
+ sal_Int32 nNumberOfEntries = rTextShapes.size();
+ for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow)
+ {
+ sal_Int32 nCurrentRowHeight = 0;
+ for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn)
+ {
+ sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
+ if( nEntry < nNumberOfEntries )
+ {
+ awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() );
+ nCurrentRowHeight = std::max( nCurrentRowHeight, aTextSize.Height );
+ }
+ }
+ rRowHeights.push_back( nCurrentRowHeight );
+ }
+}
+
+sal_Int32 lcl_getTextLineHeight( const std::vector< sal_Int32 >& aRowHeights, const sal_Int32 nNumberOfRows, double fViewFontSize )
+{
+ const sal_Int32 nFontHeight = static_cast< sal_Int32 >( fViewFontSize );
+ if (!nFontHeight)
+ return 0;
+ sal_Int32 nTextLineHeight = nFontHeight;
+ for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow)
+ {
+ sal_Int32 nFullTextHeight = aRowHeights[nRow];
+ if( ( nFullTextHeight / nFontHeight ) <= 1 )
+ {
+ nTextLineHeight = nFullTextHeight;//found an entry with one line-> have real text height
+ break;
+ }
+ }
+ return nTextLineHeight;
+}
+
+//returns resulting legend size
+awt::Size lcl_placeLegendEntries(
+ std::vector<ViewLegendEntry> & rEntries,
+ css::chart::ChartLegendExpansion eExpansion,
+ bool bSymbolsLeftSide,
+ double fViewFontSize,
+ const awt::Size& rMaxSymbolExtent,
+ tPropertyValues & rTextProperties,
+ const rtl::Reference<SvxShapeGroupAnyD> & xTarget,
+ const awt::Size& rRemainingSpace,
+ sal_Int32 nYStartPosition,
+ const awt::Size& rPageSize,
+ bool bIsPivotChart,
+ awt::Size& rDefaultLegendSize)
+{
+ bool bIsCustomSize = (eExpansion == css::chart::ChartLegendExpansion_CUSTOM);
+ awt::Size aResultingLegendSize(0,0);
+ // For Pivot charts set the *minimum* legend size as a function of page size.
+ if ( bIsPivotChart )
+ aResultingLegendSize = awt::Size((rPageSize.Width * 13) / 80, (rPageSize.Height * 31) / 90);
+ if( bIsCustomSize )
+ aResultingLegendSize = awt::Size(rRemainingSpace.Width, rRemainingSpace.Height + nYStartPosition);
+
+ // #i109336# Improve auto positioning in chart
+ sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.33 ) );
+ sal_Int32 nXOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.66 ) );
+ sal_Int32 nYPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
+ sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
+
+ const sal_Int32 nSymbolToTextDistance = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
+ const sal_Int32 nSymbolPlusDistanceWidth = rMaxSymbolExtent.Width + nSymbolToTextDistance;
+ sal_Int32 nMaxTextWidth = rRemainingSpace.Width - nSymbolPlusDistanceWidth;
+ uno::Any* pFrameWidthAny = PropertyMapper::getValuePointer( rTextProperties.second, rTextProperties.first, u"TextMaximumFrameWidth");
+ if(pFrameWidthAny)
+ {
+ if( eExpansion == css::chart::ChartLegendExpansion_HIGH )
+ {
+ // limit the width of texts to 30% of the total available width
+ // #i109336# Improve auto positioning in chart
+ nMaxTextWidth = rRemainingSpace.Width * 3 / 10;
+ }
+ *pFrameWidthAny <<= nMaxTextWidth;
+ }
+
+ std::vector< rtl::Reference<SvxShapeText> > aTextShapes;
+ awt::Size aMaxEntryExtent = lcl_createTextShapes( rEntries, xTarget, aTextShapes, rTextProperties );
+ OSL_ASSERT( aTextShapes.size() == rEntries.size());
+
+ sal_Int32 nMaxEntryWidth = nXOffset + nSymbolPlusDistanceWidth + aMaxEntryExtent.Width;
+ sal_Int32 nMaxEntryHeight = nYOffset + aMaxEntryExtent.Height;
+ sal_Int32 nNumberOfEntries = rEntries.size();
+
+ rDefaultLegendSize.Width = nMaxEntryWidth;
+ rDefaultLegendSize.Height = nMaxEntryHeight + nYPadding;
+
+ sal_Int32 nNumberOfColumns = 0, nNumberOfRows = 0;
+ std::vector< sal_Int32 > aColumnWidths;
+ std::vector< sal_Int32 > aRowHeights;
+
+ sal_Int32 nTextLineHeight = static_cast< sal_Int32 >( fViewFontSize );
+
+ // determine layout depending on LegendExpansion
+ if( eExpansion == css::chart::ChartLegendExpansion_CUSTOM )
+ {
+ sal_Int32 nCurrentRow=0;
+ sal_Int32 nCurrentColumn=-1;
+ sal_Int32 nMaxColumnCount=-1;
+ for( sal_Int32 nN=0; nN<static_cast<sal_Int32>(aTextShapes.size()); nN++ )
+ {
+ rtl::Reference<SvxShapeText> xShape( aTextShapes[nN] );
+ if( !xShape.is() )
+ continue;
+ awt::Size aSize( xShape->getSize() );
+ sal_Int32 nNewWidth = aSize.Width + nSymbolPlusDistanceWidth;
+ sal_Int32 nCurrentColumnCount = aColumnWidths.size();
+
+ //are we allowed to add a new column?
+ if( nMaxColumnCount==-1 || (nCurrentColumn+1) < nMaxColumnCount )
+ {
+ //try add a new column
+ nCurrentColumn++;
+ if( nCurrentColumn < nCurrentColumnCount )
+ {
+ //check whether the current column width is sufficient for the new entry
+ if( aColumnWidths[nCurrentColumn]>=nNewWidth )
+ {
+ //all good proceed with next entry
+ continue;
+ }
+
+ aColumnWidths[nCurrentColumn] = std::max( nNewWidth, aColumnWidths[nCurrentColumn] );
+ } else
+ aColumnWidths.push_back(nNewWidth);
+
+ //do the columns still fit into the given size?
+ nCurrentColumnCount = aColumnWidths.size();//update count
+ sal_Int32 nSumWidth = 0;
+ for (sal_Int32 nColumn = 0; nColumn < nCurrentColumnCount; nColumn++)
+ nSumWidth += aColumnWidths[nColumn];
+
+ if( nSumWidth <= rRemainingSpace.Width || nCurrentColumnCount==1 )
+ {
+ //all good proceed with next entry
+ continue;
+ }
+ else
+ {
+ //not enough space for the current amount of columns
+ //try again with less columns
+ nMaxColumnCount = nCurrentColumnCount-1;
+ nN=-1;
+ nCurrentRow=0;
+ nCurrentColumn=-1;
+ aColumnWidths.clear();
+ }
+ }
+ else
+ {
+ //add a new row and try the same entry again
+ nCurrentRow++;
+ nCurrentColumn=-1;
+ nN--;
+ }
+ }
+ nNumberOfColumns = aColumnWidths.size();
+ nNumberOfRows = nCurrentRow+1;
+
+ //check if there is not enough space so that some entries must be removed
+ lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
+ nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
+ sal_Int32 nSumHeight = 0;
+ for (sal_Int32 nRow=0; nRow < nNumberOfRows; nRow++)
+ nSumHeight += aRowHeights[nRow];
+ sal_Int32 nRemainingSpace = rRemainingSpace.Height - nSumHeight;
+
+ if( nRemainingSpace < -100 ) // 1mm tolerance for OOXML interop tdf#90404
+ {
+ //remove entries that are too big
+ for (sal_Int32 nRow = nNumberOfRows; nRow--; )
+ {
+ for (sal_Int32 nColumn = nNumberOfColumns; nColumn--; )
+ {
+ sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
+ if( nEntry < static_cast<sal_Int32>(aTextShapes.size()) )
+ {
+ DrawModelWrapper::removeShape( aTextShapes[nEntry] );
+ aTextShapes.pop_back();
+ }
+ if( nEntry < nNumberOfEntries && ( nEntry != 0 || nNumberOfColumns != 1 ) )
+ {
+ DrawModelWrapper::removeShape( rEntries[ nEntry ].xSymbol );
+ rEntries.pop_back();
+ nNumberOfEntries--;
+ }
+ }
+ if (nRow == 0 && nNumberOfColumns == 1)
+ {
+ try
+ {
+ OUString aLabelString = rEntries[0].aLabel[0]->getString();
+ static const OUStringLiteral sDots = u"...";
+ for (sal_Int32 nNewLen = aLabelString.getLength() - sDots.getLength(); nNewLen > 0; nNewLen--)
+ {
+ OUString aNewLabel = aLabelString.subView(0, nNewLen) + sDots;
+ rtl::Reference<SvxShapeText> xEntry = ShapeFactory::createText(
+ xTarget, aNewLabel, rTextProperties.first, rTextProperties.second, uno::Any());
+ nSumHeight = xEntry->getSize().Height;
+ nRemainingSpace = rRemainingSpace.Height - nSumHeight;
+ if (nRemainingSpace >= 0)
+ {
+ sal_Int32 nWidth = xEntry->getSize().Width + nSymbolPlusDistanceWidth;
+ if (rRemainingSpace.Width - nWidth >= 0)
+ {
+ aTextShapes.push_back(xEntry);
+ rEntries[0].aLabel[0]->setString(aNewLabel);
+ aRowHeights[0] = nSumHeight;
+ aColumnWidths[0] = nWidth;
+ break;
+ }
+ }
+ DrawModelWrapper::removeShape(xEntry);
+ }
+ if (aTextShapes.size() == 0)
+ {
+ DrawModelWrapper::removeShape(rEntries[0].xSymbol);
+ rEntries.pop_back();
+ nNumberOfEntries--;
+ aRowHeights.pop_back();
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+ }
+ else
+ {
+ nSumHeight -= aRowHeights[nRow];
+ aRowHeights.pop_back();
+ nRemainingSpace = rRemainingSpace.Height - nSumHeight;
+ if (nRemainingSpace >= 0)
+ break;
+ }
+ }
+ nNumberOfRows = static_cast<sal_Int32>(aRowHeights.size());
+ }
+ if( nRemainingSpace >= -100 ) // 1mm tolerance for OOXML interop tdf#90404
+ {
+ sal_Int32 nNormalSpacingHeight = 2*nYPadding+(nNumberOfRows-1)*nYOffset;
+ if( nRemainingSpace < nNormalSpacingHeight )
+ {
+ //reduce spacing between the entries
+ nYPadding = nYOffset = nRemainingSpace/(nNumberOfRows+1);
+ }
+ else
+ {
+ //we have some space left that should be spread equally between all rows
+ sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingHeight)/(nNumberOfRows+1);
+ nYPadding += nRemainingSingleSpace;
+ nYOffset += nRemainingSingleSpace;
+ }
+ }
+
+ //check spacing between columns
+ sal_Int32 nSumWidth = 0;
+ for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; nColumn++)
+ nSumWidth += aColumnWidths[nColumn];
+ nRemainingSpace = rRemainingSpace.Width - nSumWidth;
+ if( nRemainingSpace>=0 )
+ {
+ sal_Int32 nNormalSpacingWidth = 2*nXPadding+(nNumberOfColumns-1)*nXOffset;
+ if( nRemainingSpace < nNormalSpacingWidth )
+ {
+ //reduce spacing between the entries
+ nXPadding = nXOffset = nRemainingSpace/(nNumberOfColumns+1);
+ }
+ else
+ {
+ //we have some space left that should be spread equally between all columns
+ sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingWidth)/(nNumberOfColumns+1);
+ nXPadding += nRemainingSingleSpace;
+ nXOffset += nRemainingSingleSpace;
+ }
+ }
+ }
+ else if( eExpansion == css::chart::ChartLegendExpansion_HIGH )
+ {
+ sal_Int32 nMaxNumberOfRows = nMaxEntryHeight
+ ? (rRemainingSpace.Height - 2*nYPadding ) / nMaxEntryHeight
+ : 0;
+
+ nNumberOfColumns = nMaxNumberOfRows
+ ? static_cast< sal_Int32 >(
+ ceil( static_cast< double >( nNumberOfEntries ) /
+ static_cast< double >( nMaxNumberOfRows ) ))
+ : 0;
+ nNumberOfRows = nNumberOfColumns
+ ? static_cast< sal_Int32 >(
+ ceil( static_cast< double >( nNumberOfEntries ) /
+ static_cast< double >( nNumberOfColumns ) ))
+ : 0;
+ }
+ else if( eExpansion == css::chart::ChartLegendExpansion_WIDE )
+ {
+ sal_Int32 nMaxNumberOfColumns = nMaxEntryWidth
+ ? (rRemainingSpace.Width - 2*nXPadding ) / nMaxEntryWidth
+ : 0;
+
+ nNumberOfRows = nMaxNumberOfColumns
+ ? static_cast< sal_Int32 >(
+ ceil( static_cast< double >( nNumberOfEntries ) /
+ static_cast< double >( nMaxNumberOfColumns ) ))
+ : 0;
+ nNumberOfColumns = nNumberOfRows
+ ? static_cast< sal_Int32 >(
+ ceil( static_cast< double >( nNumberOfEntries ) /
+ static_cast< double >( nNumberOfRows ) ))
+ : 0;
+ }
+ else // css::chart::ChartLegendExpansion_BALANCED
+ {
+ double fAspect = nMaxEntryHeight
+ ? static_cast< double >( nMaxEntryWidth ) / static_cast< double >( nMaxEntryHeight )
+ : 0.0;
+
+ nNumberOfRows = static_cast< sal_Int32 >(
+ ceil( sqrt( static_cast< double >( nNumberOfEntries ) * fAspect )));
+ nNumberOfColumns = nNumberOfRows
+ ? static_cast< sal_Int32 >(
+ ceil( static_cast< double >( nNumberOfEntries ) /
+ static_cast< double >( nNumberOfRows ) ))
+ : 0;
+ }
+
+ if(nNumberOfRows<=0)
+ return aResultingLegendSize;
+
+ if( eExpansion != css::chart::ChartLegendExpansion_CUSTOM )
+ {
+ lcl_collectColumnWidths( aColumnWidths, nNumberOfRows, nNumberOfColumns, aTextShapes, nSymbolPlusDistanceWidth );
+ lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
+ nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
+ }
+
+ sal_Int32 nCurrentXPos = bSymbolsLeftSide ? nXPadding : -nXPadding;
+
+ // place entries into column and rows
+ sal_Int32 nMaxYPos = 0;
+
+ for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn)
+ {
+ sal_Int32 nCurrentYPos = nYPadding + nYStartPosition;
+ for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow)
+ {
+ sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
+ if( nEntry >= nNumberOfEntries )
+ break;
+
+ // text shape
+ rtl::Reference<SvxShapeText> xTextShape( aTextShapes[nEntry] );
+ if( xTextShape.is() )
+ {
+ awt::Size aTextSize( xTextShape->getSize() );
+ sal_Int32 nTextXPos = nCurrentXPos + nSymbolPlusDistanceWidth;
+ if( !bSymbolsLeftSide )
+ nTextXPos = nCurrentXPos - nSymbolPlusDistanceWidth - aTextSize.Width;
+ xTextShape->setPosition( awt::Point( nTextXPos, nCurrentYPos ));
+ }
+
+ // symbol
+ rtl::Reference<SvxShapeGroup> & xSymbol( rEntries[ nEntry ].xSymbol );
+ if( xSymbol.is() )
+ {
+ awt::Size aSymbolSize( rMaxSymbolExtent );
+ sal_Int32 nSymbolXPos = nCurrentXPos;
+ if( !bSymbolsLeftSide )
+ nSymbolXPos = nCurrentXPos - rMaxSymbolExtent.Width;
+ sal_Int32 nSymbolYPos = nCurrentYPos + ( ( nTextLineHeight - aSymbolSize.Height ) / 2 );
+ xSymbol->setPosition( awt::Point( nSymbolXPos, nSymbolYPos ) );
+ }
+
+ nCurrentYPos += aRowHeights[ nRow ];
+ if( nRow+1 < nNumberOfRows )
+ nCurrentYPos += nYOffset;
+ nMaxYPos = std::max( nMaxYPos, nCurrentYPos );
+ }
+ if( bSymbolsLeftSide )
+ {
+ nCurrentXPos += aColumnWidths[nColumn];
+ if( nColumn+1 < nNumberOfColumns )
+ nCurrentXPos += nXOffset;
+ }
+ else
+ {
+ nCurrentXPos -= aColumnWidths[nColumn];
+ if( nColumn+1 < nNumberOfColumns )
+ nCurrentXPos -= nXOffset;
+ }
+ }
+
+ if( !bIsCustomSize )
+ {
+ if( bSymbolsLeftSide )
+ aResultingLegendSize.Width = std::max( aResultingLegendSize.Width, nCurrentXPos + nXPadding );
+ else
+ {
+ sal_Int32 nLegendWidth = -(nCurrentXPos-nXPadding);
+ aResultingLegendSize.Width = std::max( aResultingLegendSize.Width, nLegendWidth );
+ }
+ aResultingLegendSize.Height = std::max( aResultingLegendSize.Height, nMaxYPos + nYPadding );
+ }
+
+ if( !bSymbolsLeftSide )
+ {
+ sal_Int32 nLegendWidth = aResultingLegendSize.Width;
+ awt::Point aPos(0,0);
+ for( sal_Int32 nEntry=0; nEntry<nNumberOfEntries; nEntry++ )
+ {
+ rtl::Reference<SvxShapeGroup> & xSymbol( rEntries[ nEntry ].xSymbol );
+ aPos = xSymbol->getPosition();
+ aPos.X += nLegendWidth;
+ xSymbol->setPosition( aPos );
+ rtl::Reference<SvxShapeText> & xText( aTextShapes[ nEntry ] );
+ aPos = xText->getPosition();
+ aPos.X += nLegendWidth;
+ xText->setPosition( aPos );
+ }
+ }
+
+ return aResultingLegendSize;
+}
+
+// #i109336# Improve auto positioning in chart
+sal_Int32 lcl_getLegendLeftRightMargin()
+{
+ return 210; // 1/100 mm
+}
+
+// #i109336# Improve auto positioning in chart
+sal_Int32 lcl_getLegendTopBottomMargin()
+{
+ return 185; // 1/100 mm
+}
+
+chart2::RelativePosition lcl_getDefaultPosition( LegendPosition ePos, const awt::Rectangle& rOutAvailableSpace, const awt::Size & rPageSize )
+{
+ chart2::RelativePosition aResult;
+
+ switch( ePos )
+ {
+ case LegendPosition_LINE_START:
+ {
+ // #i109336# Improve auto positioning in chart
+ const double fDefaultDistance = static_cast< double >( lcl_getLegendLeftRightMargin() ) /
+ static_cast< double >( rPageSize.Width );
+ aResult = chart2::RelativePosition(
+ fDefaultDistance, 0.5, drawing::Alignment_LEFT );
+ }
+ break;
+ case LegendPosition_LINE_END:
+ {
+ // #i109336# Improve auto positioning in chart
+ const double fDefaultDistance = static_cast< double >( lcl_getLegendLeftRightMargin() ) /
+ static_cast< double >( rPageSize.Width );
+ aResult = chart2::RelativePosition(
+ 1.0 - fDefaultDistance, 0.5, drawing::Alignment_RIGHT );
+ }
+ break;
+ case LegendPosition_PAGE_START:
+ {
+ // #i109336# Improve auto positioning in chart
+ const double fDefaultDistance = static_cast< double >( lcl_getLegendTopBottomMargin() ) /
+ static_cast< double >( rPageSize.Height );
+ double fDistance = (static_cast<double>(rOutAvailableSpace.Y)/static_cast<double>(rPageSize.Height)) + fDefaultDistance;
+ aResult = chart2::RelativePosition(
+ 0.5, fDistance, drawing::Alignment_TOP );
+ }
+ break;
+ case LegendPosition_PAGE_END:
+ {
+ // #i109336# Improve auto positioning in chart
+ const double fDefaultDistance = static_cast< double >( lcl_getLegendTopBottomMargin() ) /
+ static_cast< double >( rPageSize.Height );
+
+ double fDistance = double(rPageSize.Height - (rOutAvailableSpace.Y + rOutAvailableSpace.Height));
+ fDistance += fDefaultDistance;
+ fDistance /= double(rPageSize.Height);
+
+ aResult = chart2::RelativePosition(
+ 0.5, 1.0 - fDistance, drawing::Alignment_BOTTOM );
+ }
+ break;
+ case LegendPosition::LegendPosition_MAKE_FIXED_SIZE:
+ default:
+ // nothing to be set
+ break;
+ }
+
+ return aResult;
+}
+
+/** @return
+ a point relative to the upper left corner that can be used for
+ XShape::setPosition()
+*/
+awt::Point lcl_calculatePositionAndRemainingSpace(
+ awt::Rectangle & rRemainingSpace,
+ const awt::Size & rPageSize,
+ const chart2::RelativePosition& rRelPos,
+ LegendPosition ePos,
+ const awt::Size& aLegendSize,
+ bool bOverlay )
+{
+ // calculate position
+ awt::Point aResult(
+ static_cast< sal_Int32 >( rRelPos.Primary * rPageSize.Width ),
+ static_cast< sal_Int32 >( rRelPos.Secondary * rPageSize.Height ));
+
+ aResult = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
+ aResult, aLegendSize, rRelPos.Anchor );
+
+ // adapt rRemainingSpace if LegendPosition is not CUSTOM
+ // #i109336# Improve auto positioning in chart
+ sal_Int32 nXDistance = lcl_getLegendLeftRightMargin();
+ sal_Int32 nYDistance = lcl_getLegendTopBottomMargin();
+ if (!bOverlay) switch( ePos )
+ {
+ case LegendPosition_LINE_START:
+ {
+ sal_Int32 nExtent = aLegendSize.Width;
+ rRemainingSpace.Width -= ( nExtent + nXDistance );
+ rRemainingSpace.X += ( nExtent + nXDistance );
+ }
+ break;
+ case LegendPosition_LINE_END:
+ {
+ rRemainingSpace.Width -= ( aLegendSize.Width + nXDistance );
+ }
+ break;
+ case LegendPosition_PAGE_START:
+ {
+ sal_Int32 nExtent = aLegendSize.Height;
+ rRemainingSpace.Height -= ( nExtent + nYDistance );
+ rRemainingSpace.Y += ( nExtent + nYDistance );
+ }
+ break;
+ case LegendPosition_PAGE_END:
+ {
+ rRemainingSpace.Height -= ( aLegendSize.Height + nYDistance );
+ }
+ break;
+
+ default:
+ // nothing
+ break;
+ }
+
+ // adjust the legend position. Esp. for old files that had slightly smaller legends
+ const sal_Int32 nEdgeDistance( 30 );
+ if( aResult.X + aLegendSize.Width > rPageSize.Width )
+ {
+ sal_Int32 nNewX( (rPageSize.Width - aLegendSize.Width) - nEdgeDistance );
+ if( nNewX > rPageSize.Width / 4 )
+ aResult.X = nNewX;
+ }
+ if( aResult.Y + aLegendSize.Height > rPageSize.Height )
+ {
+ sal_Int32 nNewY( (rPageSize.Height - aLegendSize.Height) - nEdgeDistance );
+ if( nNewY > rPageSize.Height / 4 )
+ aResult.Y = nNewY;
+ }
+
+ return aResult;
+}
+
+bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference< beans::XPropertySet >& xLegendProp, sal_Int16 nDefaultWritingMode )
+{
+ bool bSymbolsLeftSide = true;
+ try
+ {
+ if( SvtCTLOptions().IsCTLFontEnabled() )
+ {
+ if(xLegendProp.is())
+ {
+ sal_Int16 nWritingMode=-1;
+ if( xLegendProp->getPropertyValue( "WritingMode" ) >>= nWritingMode )
+ {
+ if( nWritingMode == text::WritingMode2::PAGE )
+ nWritingMode = nDefaultWritingMode;
+ if( nWritingMode == text::WritingMode2::RL_TB )
+ bSymbolsLeftSide=false;
+ }
+ }
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+ return bSymbolsLeftSide;
+}
+
+std::vector<std::shared_ptr<VButton>> lcl_createButtons(
+ rtl::Reference<SvxShapeGroupAnyD> const & xLegendContainer,
+ ChartModel& rModel, bool bPlaceButtonsVertically, tools::Long & nUsedHeight)
+{
+ std::vector<std::shared_ptr<VButton>> aButtons;
+
+ uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(rModel.getDataProvider(), uno::UNO_QUERY);
+ if (!xPivotTableDataProvider.is())
+ return aButtons;
+
+ if (!xPivotTableDataProvider->getColumnFields().hasElements())
+ return aButtons;
+
+ awt::Size aSize(2000, 700);
+ int x = 100;
+ int y = 100;
+
+ const css::uno::Sequence<chart2::data::PivotTableFieldEntry> aPivotFieldEntries = xPivotTableDataProvider->getColumnFields();
+ for (chart2::data::PivotTableFieldEntry const & sColumnFieldEntry : aPivotFieldEntries)
+ {
+ auto pButton = std::make_shared<VButton>();
+ aButtons.push_back(pButton);
+ pButton->init(xLegendContainer);
+ awt::Point aNewPosition(x, y);
+ pButton->setLabel(sColumnFieldEntry.Name);
+ pButton->setCID("FieldButton.Column." + OUString::number(sColumnFieldEntry.DimensionIndex));
+ pButton->setPosition(aNewPosition);
+ pButton->setSize(aSize);
+ if (sColumnFieldEntry.Name == "Data")
+ {
+ pButton->showArrow(false);
+ pButton->setBGColor(Color(0x00F6F6F6));
+ }
+ if (sColumnFieldEntry.HasHiddenMembers)
+ pButton->setArrowColor(Color(0x0000FF));
+
+ if (bPlaceButtonsVertically)
+ y += aSize.Height + 100;
+ else
+ x += aSize.Width + 100;
+ }
+ if (bPlaceButtonsVertically)
+ nUsedHeight += y + 100;
+ else
+ nUsedHeight += aSize.Height + 100;
+
+ return aButtons;
+}
+
+} // anonymous namespace
+
+VLegend::VLegend(
+ const rtl::Reference< Legend > & xLegend,
+ const Reference< uno::XComponentContext > & xContext,
+ std::vector< LegendEntryProvider* >&& rLegendEntryProviderList,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTargetPage,
+ ChartModel& rModel )
+ : m_xTarget(xTargetPage)
+ , m_xLegend(xLegend)
+ , mrModel(rModel)
+ , m_xContext(xContext)
+ , m_aLegendEntryProviderList(std::move(rLegendEntryProviderList))
+ , m_nDefaultWritingMode(text::WritingMode2::LR_TB)
+{
+}
+
+void VLegend::setDefaultWritingMode( sal_Int16 nDefaultWritingMode )
+{
+ m_nDefaultWritingMode = nDefaultWritingMode;
+}
+
+bool VLegend::isVisible( const rtl::Reference< Legend > & xLegend )
+{
+ if( ! xLegend.is())
+ return false;
+
+ bool bShow = false;
+ try
+ {
+ xLegend->getPropertyValue( "Show") >>= bShow;
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+
+ return bShow;
+}
+
+void VLegend::createShapes(
+ const awt::Size & rAvailableSpace,
+ const awt::Size & rPageSize,
+ awt::Size & rDefaultLegendSize )
+{
+ if(! (m_xLegend.is() && m_xTarget.is()))
+ return;
+
+ try
+ {
+ //create shape and add to page
+ OUString aLegendParticle( ObjectIdentifier::createParticleForLegend( &mrModel ) );
+ m_xShape = ShapeFactory::createGroup2D( m_xTarget,
+ ObjectIdentifier::createClassifiedIdentifierForParticle( aLegendParticle ) );
+
+ // create and insert sub-shapes
+ rtl::Reference<SvxShapeGroupAnyD> xLegendContainer = m_xShape;
+ if( xLegendContainer.is() )
+ {
+ // for quickly setting properties
+ tPropertyValues aLineFillProperties;
+ tPropertyValues aTextProperties;
+
+ css::chart::ChartLegendExpansion eExpansion = css::chart::ChartLegendExpansion_HIGH;
+ awt::Size aLegendSize( rAvailableSpace );
+
+ bool bCustom = false;
+ LegendPosition eLegendPosition = LegendPosition_LINE_END;
+ // get Expansion property
+ m_xLegend->getPropertyValue("Expansion") >>= eExpansion;
+ if( eExpansion == css::chart::ChartLegendExpansion_CUSTOM )
+ {
+ RelativeSize aRelativeSize;
+ if (m_xLegend->getPropertyValue("RelativeSize") >>= aRelativeSize)
+ {
+ aLegendSize.Width = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Primary * rPageSize.Width ));
+ aLegendSize.Height = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Secondary * rPageSize.Height ));
+ bCustom = true;
+ }
+ else
+ {
+ eExpansion = css::chart::ChartLegendExpansion_HIGH;
+ }
+ }
+ m_xLegend->getPropertyValue("AnchorPosition") >>= eLegendPosition;
+ lcl_getProperties( m_xLegend, aLineFillProperties, aTextProperties, rPageSize );
+
+ // create entries
+ double fViewFontSize = lcl_CalcViewFontSize( m_xLegend, rPageSize );//todo
+ // #i109336# Improve auto positioning in chart
+ sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
+ sal_Int32 nSymbolWidth = nSymbolHeight;
+
+ for (LegendEntryProvider* pLegendEntryProvider : m_aLegendEntryProviderList)
+ {
+ if (pLegendEntryProvider)
+ {
+ awt::Size aCurrentRatio = pLegendEntryProvider->getPreferredLegendKeyAspectRatio();
+ sal_Int32 nCurrentWidth = aCurrentRatio.Width;
+ if( aCurrentRatio.Height > 0 )
+ {
+ nCurrentWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
+ }
+ nSymbolWidth = std::max( nSymbolWidth, nCurrentWidth );
+ }
+ }
+ awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
+
+ std::vector<ViewLegendEntry> aViewEntries;
+ for(LegendEntryProvider* pLegendEntryProvider : m_aLegendEntryProviderList)
+ {
+ if (pLegendEntryProvider)
+ {
+ std::vector<ViewLegendEntry> aNewEntries = pLegendEntryProvider->createLegendEntries(
+ aMaxSymbolExtent, eLegendPosition, m_xLegend,
+ xLegendContainer, m_xContext, mrModel);
+ aViewEntries.insert( aViewEntries.end(), aNewEntries.begin(), aNewEntries.end() );
+ }
+ }
+
+ bool bSymbolsLeftSide = lcl_shouldSymbolsBePlacedOnTheLeftSide( m_xLegend, m_nDefaultWritingMode );
+
+ uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider( mrModel.getDataProvider(), uno::UNO_QUERY );
+ bool bIsPivotChart = xPivotTableDataProvider.is();
+
+ if ( !aViewEntries.empty() || bIsPivotChart )
+ {
+ // create buttons
+ tools::Long nUsedButtonHeight = 0;
+ bool bPlaceButtonsVertically = (eLegendPosition != LegendPosition_PAGE_START &&
+ eLegendPosition != LegendPosition_PAGE_END &&
+ eExpansion != css::chart::ChartLegendExpansion_WIDE);
+
+ std::vector<std::shared_ptr<VButton>> aButtons = lcl_createButtons(xLegendContainer, mrModel, bPlaceButtonsVertically, nUsedButtonHeight);
+
+ // A custom size includes the size we used for buttons already, so we need to
+ // subtract that from the size that is available for the legend
+ if (bCustom)
+ aLegendSize.Height -= nUsedButtonHeight;
+
+ // place the legend entries
+ aLegendSize = lcl_placeLegendEntries(aViewEntries, eExpansion, bSymbolsLeftSide, fViewFontSize,
+ aMaxSymbolExtent, aTextProperties, xLegendContainer,
+ aLegendSize, nUsedButtonHeight, rPageSize, bIsPivotChart, rDefaultLegendSize);
+
+ uno::Reference<beans::XPropertySet> xModelPage(mrModel.getPageBackground());
+
+ for (std::shared_ptr<VButton> const & pButton : aButtons)
+ {
+ // adjust the width of the buttons if we place them vertically
+ if (bPlaceButtonsVertically)
+ pButton->setSize({aLegendSize.Width - 200, pButton->getSize().Height});
+
+ // create the buttons
+ pButton->createShapes(xModelPage);
+ }
+
+ rtl::Reference<SvxShapeRect> xBorder = ShapeFactory::createRectangle(
+ xLegendContainer, aLegendSize, awt::Point(0, 0), aLineFillProperties.first,
+ aLineFillProperties.second, ShapeFactory::StackPosition::Bottom);
+
+ //because of this name this border will be used for marking the legend
+ ShapeFactory::setShapeName(xBorder, "MarkHandles");
+ }
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2" );
+ }
+}
+
+void VLegend::changePosition(
+ awt::Rectangle & rOutAvailableSpace,
+ const awt::Size & rPageSize,
+ const css::awt::Size & rDefaultLegendSize )
+{
+ if(! m_xShape.is())
+ return;
+
+ try
+ {
+ // determine position and alignment depending on default position
+ awt::Size aLegendSize = m_xShape->getSize();
+ chart2::RelativePosition aRelativePosition;
+
+ bool bDefaultLegendSize = rDefaultLegendSize.Width != 0 || rDefaultLegendSize.Height != 0;
+ bool bAutoPosition =
+ ! (m_xLegend->getPropertyValue( "RelativePosition") >>= aRelativePosition);
+
+ LegendPosition ePos = LegendPosition_LINE_END;
+ m_xLegend->getPropertyValue( "AnchorPosition") >>= ePos;
+
+ bool bOverlay = false;
+ m_xLegend->getPropertyValue("Overlay") >>= bOverlay;
+ //calculate position
+ if( bAutoPosition )
+ {
+ // auto position: relative to remaining space
+ aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
+ awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
+ rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize, bOverlay );
+ m_xShape->setPosition( aPos );
+ }
+ else
+ {
+ // manual position: relative to whole page
+ awt::Rectangle aAvailableSpace( 0, 0, rPageSize.Width, rPageSize.Height );
+ awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
+ aAvailableSpace, rPageSize, aRelativePosition, ePos, bDefaultLegendSize ? rDefaultLegendSize : aLegendSize, bOverlay );
+ m_xShape->setPosition( aPos );
+
+ if (!bOverlay)
+ {
+ // calculate remaining space as if having autoposition:
+ aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
+ lcl_calculatePositionAndRemainingSpace(
+ rOutAvailableSpace, rPageSize, aRelativePosition, ePos, bDefaultLegendSize ? rDefaultLegendSize : aLegendSize, bOverlay );
+ }
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2" );
+ }
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/VLegend.hxx b/chart2/source/view/main/VLegend.hxx
new file mode 100644
index 000000000..f47b21d26
--- /dev/null
+++ b/chart2/source/view/main/VLegend.hxx
@@ -0,0 +1,91 @@
+/* -*- 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 <Legend.hxx>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <rtl/ref.hxx>
+#include <svx/unoshape.hxx>
+#include <vector>
+
+namespace chart { class ChartModel; }
+namespace com::sun::star::awt { struct Rectangle; }
+namespace com::sun::star::awt { struct Size; }
+namespace com::sun::star::chart2 { class XLegend; }
+namespace com::sun::star::drawing { class XShape; }
+namespace com::sun::star::drawing { class XShapes; }
+namespace com::sun::star::lang { class XMultiServiceFactory; }
+namespace com::sun::star::uno { class XComponentContext; }
+
+namespace chart
+{
+
+class Legend;
+class LegendEntryProvider;
+
+class VLegend
+{
+public:
+ VLegend( const rtl::Reference< ::chart::Legend > & xLegend,
+ const css::uno::Reference< css::uno::XComponentContext > & xContext,
+ std::vector< LegendEntryProvider* >&& rLegendEntryProviderList,
+ const rtl::Reference<SvxShapeGroupAnyD>& xTargetPage,
+ ChartModel& rModel );
+
+ void setDefaultWritingMode( sal_Int16 nDefaultWritingMode );
+
+ void createShapes( const css::awt::Size & rAvailableSpace,
+ const css::awt::Size & rPageSize,
+ css::awt::Size & rDefaultLegendSize );
+
+ /** Sets the position according to its internal anchor.
+
+ @param rOutAvailableSpace
+ is modified by the method, if the legend is in a standard position,
+ such that the space allocated by the legend is removed from it.
+
+ @param rReferenceSize
+ is used to calculate the offset (default 2%) from the edge.
+ */
+ void changePosition(
+ css::awt::Rectangle & rOutAvailableSpace,
+ const css::awt::Size & rReferenceSize,
+ const css::awt::Size & rDefaultLegendSize );
+
+ static bool isVisible(
+ const rtl::Reference< ::chart::Legend > & xLegend );
+
+private:
+ rtl::Reference<SvxShapeGroupAnyD> m_xTarget;
+ rtl::Reference<::chart::Legend> m_xLegend;
+ rtl::Reference< SvxShapeGroup > m_xShape;
+
+ ChartModel& mrModel;
+
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ std::vector< LegendEntryProvider* > m_aLegendEntryProviderList;
+
+ sal_Int16 m_nDefaultWritingMode;//to be used when writing mode is set to page
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/VLegendSymbolFactory.cxx b/chart2/source/view/main/VLegendSymbolFactory.cxx
new file mode 100644
index 000000000..451e32e45
--- /dev/null
+++ b/chart2/source/view/main/VLegendSymbolFactory.cxx
@@ -0,0 +1,188 @@
+/* -*- 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 <VLegendSymbolFactory.hxx>
+#include <PropertyMapper.hxx>
+#include <ShapeFactory.hxx>
+#include <com/sun/star/drawing/Position3D.hpp>
+#include <com/sun/star/chart2/Symbol.hpp>
+#include <com/sun/star/drawing/Direction3D.hpp>
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star;
+using ::com::sun::star::uno::Reference;
+
+namespace
+{
+
+void getPropNamesAndValues( const Reference< beans::XPropertySet >& xProp,
+ ::chart::tNameSequence& rNames,
+ ::chart::tAnySequence& rValues,
+ ::chart::VLegendSymbolFactory::PropertyType ePropertyType,
+ const awt::Size& aMaxSymbolExtent)
+{
+ const ::chart::tPropertyNameMap & aFilledSeriesNameMap( ::chart::PropertyMapper::getPropertyNameMapForFilledSeriesProperties());
+ const ::chart::tPropertyNameMap & aLineSeriesNameMap( ::chart::PropertyMapper::getPropertyNameMapForLineSeriesProperties());
+ const ::chart::tPropertyNameMap & aLineNameMap( ::chart::PropertyMapper::getPropertyNameMapForLineProperties());
+
+ ::chart::tPropertyNameValueMap aValueMap;
+ switch( ePropertyType )
+ {
+ case ::chart::VLegendSymbolFactory::PropertyType::FilledSeries:
+ ::chart::PropertyMapper::getValueMap( aValueMap, aFilledSeriesNameMap, xProp );
+ break;
+ case ::chart::VLegendSymbolFactory::PropertyType::LineSeries:
+ ::chart::PropertyMapper::getValueMap( aValueMap, aLineSeriesNameMap, xProp );
+ break;
+ case ::chart::VLegendSymbolFactory::PropertyType::Line:
+ ::chart::PropertyMapper::getValueMap( aValueMap, aLineNameMap, xProp );
+ break;
+ }
+
+ ::chart::PropertyMapper::getMultiPropertyListsFromValueMap( rNames, rValues, aValueMap );
+
+ uno::Any* pLineWidthAny = ::chart::PropertyMapper::getValuePointer(rValues,rNames,u"LineWidth");
+ sal_Int32 nLineWidth = 0;
+ if( pLineWidthAny && (*pLineWidthAny>>=nLineWidth) )
+ {
+ // use legend entry height as upper limit for line width
+ sal_Int32 nMaxLineWidthForLegend = aMaxSymbolExtent.Height;
+ if( nLineWidth>nMaxLineWidthForLegend )
+ *pLineWidthAny <<= nMaxLineWidthForLegend;
+ }
+}
+
+void lcl_setPropertiesToShape(
+ const Reference< beans::XPropertySet > & xProp,
+ const rtl::Reference< SvxShape > & xShape,
+ ::chart::VLegendSymbolFactory::PropertyType ePropertyType,
+ const awt::Size& aMaxSymbolExtent)
+{
+ ::chart::tNameSequence aPropNames;
+ ::chart::tAnySequence aPropValues;
+ getPropNamesAndValues( xProp, aPropNames, aPropValues,
+ ePropertyType, aMaxSymbolExtent );
+
+ ::chart::PropertyMapper::setMultiProperties( aPropNames, aPropValues, *xShape );
+}
+
+} // anonymous namespace
+
+namespace chart
+{
+
+rtl::Reference< SvxShapeGroup > VLegendSymbolFactory::createSymbol(
+ const awt::Size& rEntryKeyAspectRatio,
+ const rtl::Reference<SvxShapeGroupAnyD>& rSymbolContainer,
+ LegendSymbolStyle eStyle,
+ const Reference< beans::XPropertySet > & xLegendEntryProperties,
+ PropertyType ePropertyType, const uno::Any& rExplicitSymbol )
+{
+ rtl::Reference< SvxShapeGroup > xResult;
+
+ if( !rSymbolContainer)
+ return xResult;
+
+ xResult = ShapeFactory::createGroup2D( rSymbolContainer );
+ if( ! xResult)
+ return xResult;
+
+ rtl::Reference<SvxShapeGroupAnyD> xResultGroup = xResult;
+
+ // add an invisible square box to maintain aspect ratio
+ ShapeFactory::createInvisibleRectangle( xResultGroup, rEntryKeyAspectRatio );
+
+ // create symbol
+ try
+ {
+ if( eStyle == LegendSymbolStyle::Line )
+ {
+ rtl::Reference<SvxShapePolyPolygon> xLine =
+ ShapeFactory::createLine( xResultGroup, awt::Size( rEntryKeyAspectRatio.Width, 0 ),
+ awt::Point( 0, rEntryKeyAspectRatio.Height/2 ));
+ lcl_setPropertiesToShape( xLegendEntryProperties, xLine, ePropertyType, rEntryKeyAspectRatio );
+
+ const sal_Int32 nSize = std::min(rEntryKeyAspectRatio.Width,rEntryKeyAspectRatio.Height);
+ chart2::Symbol aSymbol;
+ if( rExplicitSymbol >>= aSymbol )
+ {
+ drawing::Direction3D aSymbolSize( nSize, nSize, 0 );
+ drawing::Position3D aPos( rEntryKeyAspectRatio.Width/2.0, rEntryKeyAspectRatio.Height/2.0, 0 );
+ if( aSymbol.Style == chart2::SymbolStyle_STANDARD )
+ {
+ // take series color as fill color
+ xLegendEntryProperties->getPropertyValue( "Color") >>= aSymbol.FillColor;
+ // border of symbols always same as fill color
+ aSymbol.BorderColor = aSymbol.FillColor;
+
+ ShapeFactory::createSymbol2D(
+ xResultGroup,
+ aPos,
+ aSymbolSize,
+ aSymbol.StandardSymbol,
+ aSymbol.BorderColor,
+ aSymbol.FillColor );
+ }
+ else if( aSymbol.Style == chart2::SymbolStyle_GRAPHIC )
+ {
+ ShapeFactory::createGraphic2D(
+ xResultGroup,
+ aPos,
+ aSymbolSize,
+ aSymbol.Graphic );
+ }
+ else if( aSymbol.Style == chart2::SymbolStyle_AUTO )
+ {
+ SAL_WARN("chart2", "the given parameter is not allowed to contain an automatic symbol style");
+ }
+ }
+ }
+ else if( eStyle == LegendSymbolStyle::Circle )
+ {
+ sal_Int32 nSize = std::min( rEntryKeyAspectRatio.Width, rEntryKeyAspectRatio.Height );
+ rtl::Reference<SvxShapeCircle> xShape =
+ ShapeFactory::createCircle( xResultGroup, awt::Size( nSize, nSize ),
+ awt::Point( rEntryKeyAspectRatio.Width/2-nSize/2, rEntryKeyAspectRatio.Height/2-nSize/2 ));
+ lcl_setPropertiesToShape( xLegendEntryProperties, xShape, ePropertyType, awt::Size(0,0) ); // PropertyType::FilledSeries );
+ }
+ else // eStyle == LegendSymbolStyle::Box
+ {
+ tNameSequence aPropNames;
+ tAnySequence aPropValues;
+
+ getPropNamesAndValues( xLegendEntryProperties, aPropNames, aPropValues,
+ ePropertyType, awt::Size(0,0) );// PropertyType::FilledSeries
+
+ ShapeFactory::createRectangle( xResultGroup,
+ rEntryKeyAspectRatio, awt::Point( 0, 0 ),
+ aPropNames, aPropValues );
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+
+ return xResult;
+}
+
+} // namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/VLineProperties.cxx b/chart2/source/view/main/VLineProperties.cxx
new file mode 100644
index 000000000..49a3b4e77
--- /dev/null
+++ b/chart2/source/view/main/VLineProperties.cxx
@@ -0,0 +1,85 @@
+/* -*- 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 <VLineProperties.hxx>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/drawing/LineCap.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <tools/diagnose_ex.h>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+
+// get line properties from a propertyset
+
+VLineProperties::VLineProperties()
+{
+ Color <<= sal_Int32(0x000000); //type sal_Int32 UNO_NAME_LINECOLOR
+ LineStyle
+ <<= drawing::LineStyle_SOLID; //type drawing::LineStyle for property UNO_NAME_LINESTYLE
+ Transparence <<= sal_Int16(0); //type sal_Int16 for property UNO_NAME_LINETRANSPARENCE
+ Width <<= sal_Int32(0); //type sal_Int32 for property UNO_NAME_LINEWIDTH
+ LineCap <<= drawing::LineCap_BUTT; //type drawing::LineCap for property UNO_NAME_LINECAP
+}
+
+void VLineProperties::initFromPropertySet(const uno::Reference<beans::XPropertySet>& xProp)
+{
+ if (xProp.is())
+ {
+ try
+ {
+ Color = xProp->getPropertyValue("LineColor");
+ LineStyle = xProp->getPropertyValue("LineStyle");
+ Transparence = xProp->getPropertyValue("LineTransparence");
+ Width = xProp->getPropertyValue("LineWidth");
+ DashName = xProp->getPropertyValue("LineDashName");
+ LineCap = xProp->getPropertyValue("LineCap");
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "");
+ }
+ }
+ else
+ LineStyle <<= drawing::LineStyle_NONE;
+}
+
+bool VLineProperties::isLineVisible() const
+{
+ bool bRet = false;
+
+ drawing::LineStyle aLineStyle(drawing::LineStyle_SOLID);
+ LineStyle >>= aLineStyle;
+ if (aLineStyle != drawing::LineStyle_NONE)
+ {
+ sal_Int16 nLineTransparence = 0;
+ Transparence >>= nLineTransparence;
+ if (nLineTransparence != 100)
+ {
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/VPolarTransformation.cxx b/chart2/source/view/main/VPolarTransformation.cxx
new file mode 100644
index 000000000..9ec2eea3f
--- /dev/null
+++ b/chart2/source/view/main/VPolarTransformation.cxx
@@ -0,0 +1,88 @@
+/* -*- 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 <VPolarTransformation.hxx>
+
+using namespace ::com::sun::star;
+
+using ::com::sun::star::uno::Sequence;
+
+namespace chart
+{
+
+VPolarTransformation::VPolarTransformation( const PolarPlottingPositionHelper& rPositionHelper )
+ : m_aPositionHelper(rPositionHelper)
+ , m_aUnitCartesianToScene( rPositionHelper.getUnitCartesianToScene() )
+{
+}
+
+VPolarTransformation::~VPolarTransformation()
+{
+}
+
+// ____ XTransformation2 ____
+css::drawing::Position3D VPolarTransformation::transform(
+ const Sequence< double >& rSourceValues ) const
+{
+ double fScaledLogicAngle = rSourceValues[0];
+ double fScaledLogicRadius = rSourceValues[1];
+
+ if( m_aPositionHelper.isSwapXAndY() )
+ std::swap(fScaledLogicAngle,fScaledLogicRadius);
+
+ double fAngleDegree = m_aPositionHelper.transformToAngleDegree( fScaledLogicAngle, false );
+ double fAnglePi = basegfx::deg2rad(fAngleDegree);
+ double fRadius = m_aPositionHelper.transformToRadius( fScaledLogicRadius, false);
+
+ double fX=fRadius*cos(fAnglePi);
+ double fY=fRadius*sin(fAnglePi);
+ double fZ=rSourceValues[2];
+
+ //!! applying matrix to vector does ignore translation, so it is important to use a B3DPoint here instead of B3DVector
+ ::basegfx::B3DPoint aPoint(fX,fY,fZ);
+ ::basegfx::B3DPoint aRet = m_aUnitCartesianToScene * aPoint;
+ return css::drawing::Position3D(aRet.getX(), aRet.getY(), aRet.getZ());
+}
+
+css::drawing::Position3D VPolarTransformation::transform(
+ const css::drawing::Position3D& rSourceValues ) const
+{
+ double fScaledLogicAngle = rSourceValues.PositionX;
+ double fScaledLogicRadius = rSourceValues.PositionY;
+
+ if( m_aPositionHelper.isSwapXAndY() )
+ std::swap(fScaledLogicAngle,fScaledLogicRadius);
+
+ double fAngleDegree = m_aPositionHelper.transformToAngleDegree( fScaledLogicAngle, false );
+ double fAnglePi = basegfx::deg2rad(fAngleDegree);
+ double fRadius = m_aPositionHelper.transformToRadius( fScaledLogicRadius, false);
+
+ double fX=fRadius*cos(fAnglePi);
+ double fY=fRadius*sin(fAnglePi);
+ double fZ=rSourceValues.PositionZ;
+
+ //!! applying matrix to vector does ignore translation, so it is important to use a B3DPoint here instead of B3DVector
+ ::basegfx::B3DPoint aPoint(fX,fY,fZ);
+ ::basegfx::B3DPoint aRet = m_aUnitCartesianToScene * aPoint;
+ return css::drawing::Position3D(aRet.getX(), aRet.getY(), aRet.getZ());
+}
+
+} // namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/VTitle.cxx b/chart2/source/view/main/VTitle.cxx
new file mode 100644
index 000000000..74d276742
--- /dev/null
+++ b/chart2/source/view/main/VTitle.cxx
@@ -0,0 +1,158 @@
+/* -*- 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 "VTitle.hxx"
+#include <CommonConverters.hxx>
+#include <ShapeFactory.hxx>
+#include <com/sun/star/chart2/XTitle.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <tools/diagnose_ex.h>
+
+namespace chart
+{
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::chart2;
+
+VTitle::VTitle( const uno::Reference< XTitle > & xTitle )
+ : m_xTitle(xTitle)
+ , m_fRotationAngleDegree(0.0)
+ , m_nXPos(0)
+ , m_nYPos(0)
+{
+}
+
+VTitle::~VTitle()
+{
+}
+
+void VTitle::init(
+ const rtl::Reference<SvxShapeGroupAnyD>& xTargetPage
+ , const OUString& rCID )
+{
+ m_xTarget = xTargetPage;
+ m_aCID = rCID;
+}
+
+double VTitle::getRotationAnglePi() const
+{
+ return basegfx::deg2rad(m_fRotationAngleDegree);
+}
+
+awt::Size VTitle::getUnrotatedSize() const //size before rotation
+{
+ awt::Size aRet;
+ if(m_xShape.is())
+ aRet = m_xShape->getSize();
+ return aRet;
+}
+
+awt::Size VTitle::getFinalSize() const //size after rotation
+{
+ return ShapeFactory::getSizeAfterRotation(
+ *m_xShape, m_fRotationAngleDegree );
+}
+
+void VTitle::changePosition( const awt::Point& rPos )
+{
+ if(!m_xShape.is())
+ return;
+ try
+ {
+ m_nXPos = rPos.X;
+ m_nYPos = rPos.Y;
+
+ //set position matrix
+ //the matrix needs to be set at the end behind autogrow and such position influencing properties
+ ::basegfx::B2DHomMatrix aM;
+ aM.rotate( basegfx::deg2rad(-m_fRotationAngleDegree) );//#i78696#->#i80521#
+ aM.translate( m_nXPos, m_nYPos);
+ m_xShape->SvxShape::setPropertyValue( "Transformation", uno::Any( B2DHomMatrixToHomogenMatrix3(aM) ) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+}
+
+bool VTitle::isVisible(const uno::Reference< XTitle >& xTitle) {
+ if (!xTitle.is()) {
+ return false;
+ }
+ bool bShow = true;
+ try {
+ uno::Reference< beans::XPropertySet > xTitleProps(xTitle, uno::UNO_QUERY_THROW);
+ xTitleProps->getPropertyValue("Visible") >>= bShow;
+ } catch (const uno::Exception &) {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+ return bShow;
+}
+
+
+void VTitle::createShapes(
+ const awt::Point& rPos
+ , const awt::Size& rReferenceSize
+ , const awt::Size& rTextMaxWidth
+ , bool bYAxisTitle )
+{
+ if(!m_xTitle.is())
+ return;
+
+ uno::Sequence< uno::Reference< XFormattedString > > aStringList = m_xTitle->getText();
+ if(!aStringList.hasElements())
+ return;
+
+ m_nXPos = rPos.X;
+ m_nYPos = rPos.Y;
+
+ uno::Reference< beans::XPropertySet > xTitleProperties( m_xTitle, uno::UNO_QUERY );
+
+ try
+ {
+ double fAngleDegree = 0;
+ xTitleProperties->getPropertyValue( "TextRotation" ) >>= fAngleDegree;
+ m_fRotationAngleDegree += fAngleDegree;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("chart2", "" );
+ }
+
+ sal_Int32 nTextMaxWidth;
+ if (bYAxisTitle)
+ {
+ if (m_fRotationAngleDegree < 75.0 || m_fRotationAngleDegree > 285.0
+ || (m_fRotationAngleDegree > 105.0 && m_fRotationAngleDegree < 255.0))
+ nTextMaxWidth = rTextMaxWidth.Width;
+ else
+ nTextMaxWidth = rTextMaxWidth.Height;
+ }
+ else if (m_fRotationAngleDegree <= 15.0 || m_fRotationAngleDegree >= 345.0
+ || (m_fRotationAngleDegree >= 165.0 && m_fRotationAngleDegree <= 195.0))
+ nTextMaxWidth = rTextMaxWidth.Width;
+ else
+ nTextMaxWidth = rTextMaxWidth.Height;
+
+ m_xShape = ShapeFactory::createText( m_xTarget, rReferenceSize, rPos, aStringList, xTitleProperties,
+ m_fRotationAngleDegree, m_aCID, nTextMaxWidth );
+}
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/source/view/main/VTitle.hxx b/chart2/source/view/main/VTitle.hxx
new file mode 100644
index 000000000..566128b95
--- /dev/null
+++ b/chart2/source/view/main/VTitle.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 <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/uno/Reference.h>
+#include <rtl/ustring.hxx>
+#include <rtl/ref.hxx>
+#include <sal/types.h>
+#include <svx/unoshape.hxx>
+
+namespace com::sun::star::awt { struct Point; }
+namespace com::sun::star::chart2 { class XTitle; }
+namespace com::sun::star::drawing { class XShape; }
+namespace com::sun::star::drawing { class XShapes; }
+namespace com::sun::star::lang { class XMultiServiceFactory; }
+class SvxShapeText;
+
+namespace chart
+{
+
+class VTitle final
+{
+public:
+ explicit VTitle( const css::uno::Reference< css::chart2::XTitle > & xTitle );
+ ~VTitle();
+
+ void init( const rtl::Reference<SvxShapeGroupAnyD>& xTargetPage
+ , const OUString& rCID );
+
+ void createShapes( const css::awt::Point& rPos
+ , const css::awt::Size& rReferenceSize
+ , const css::awt::Size& nTextMaxWidth
+ , bool bYAxisTitle );
+
+ double getRotationAnglePi() const;
+ css::awt::Size getUnrotatedSize() const;
+ css::awt::Size getFinalSize() const;
+ void changePosition( const css::awt::Point& rPos );
+ static bool isVisible(
+ const css::uno::Reference< css::chart2::XTitle > & xTitle);
+
+private:
+ rtl::Reference<SvxShapeGroupAnyD> m_xTarget;
+ css::uno::Reference< css::chart2::XTitle > m_xTitle;
+ rtl::Reference<SvxShapeText> m_xShape;
+ OUString m_aCID;
+
+ double m_fRotationAngleDegree;
+ sal_Int32 m_nXPos;
+ sal_Int32 m_nYPos;
+};
+
+} //namespace chart
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */