diff options
Diffstat (limited to '')
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: */ |