diff options
Diffstat (limited to 'chart2/source/tools/DiagramHelper.cxx')
-rw-r--r-- | chart2/source/tools/DiagramHelper.cxx | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/chart2/source/tools/DiagramHelper.cxx b/chart2/source/tools/DiagramHelper.cxx new file mode 100644 index 0000000000..79a99688fd --- /dev/null +++ b/chart2/source/tools/DiagramHelper.cxx @@ -0,0 +1,552 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <DiagramHelper.hxx> +#include <Diagram.hxx> +#include <DataSeries.hxx> +#include <DataSeriesHelper.hxx> +#include <Axis.hxx> +#include <AxisHelper.hxx> +#include <ChartType.hxx> +#include <ChartTypeHelper.hxx> +#include <ChartTypeManager.hxx> +#include <ChartTypeTemplate.hxx> +#include <ChartModel.hxx> +#include <ChartModelHelper.hxx> +#include <ExplicitCategoriesProvider.hxx> +#include <servicenames_charttypes.hxx> +#include <RelativePositionHelper.hxx> +#include <ControllerLockGuard.hxx> +#include <NumberFormatterWrapper.hxx> +#include <unonames.hxx> +#include <BaseCoordinateSystem.hxx> + +#include <com/sun/star/chart/MissingValueTreatment.hpp> +#include <com/sun/star/chart/XDiagramPositioning.hpp> +#include <com/sun/star/chart2/XAnyDescriptionAccess.hpp> +#include <com/sun/star/chart2/AxisType.hpp> +#include <com/sun/star/chart2/DataPointGeometry3D.hpp> +#include <com/sun/star/chart2/RelativePosition.hpp> +#include <com/sun/star/chart2/RelativeSize.hpp> +#include <com/sun/star/chart2/StackingDirection.hpp> + +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/NumberFormat.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> + +#include <o3tl/safeint.hxx> +#include <unotools/saveopt.hxx> +#include <rtl/math.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <sal/log.hxx> + +#include <cstddef> +#include <limits> + +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; +using ::com::sun::star::chart2::XAnyDescriptionAccess; + +namespace chart +{ + +StackMode DiagramHelper::getStackModeFromChartType( + const rtl::Reference< ChartType > & xChartType, + bool& rbFound, bool& rbAmbiguous, + const rtl::Reference< BaseCoordinateSystem > & xCorrespondingCoordinateSystem ) +{ + StackMode eStackMode = StackMode::NONE; + rbFound = false; + rbAmbiguous = false; + + try + { + const std::vector< rtl::Reference< DataSeries > > & aSeries = xChartType->getDataSeries2(); + + chart2::StackingDirection eCommonDirection = chart2::StackingDirection_NO_STACKING; + bool bDirectionInitialized = false; + + // first series is irrelevant for stacking, start with second, unless + // there is only one series + const sal_Int32 nSeriesCount = aSeries.size(); + sal_Int32 i = (nSeriesCount == 1) ? 0: 1; + for( ; i<nSeriesCount; ++i ) + { + rbFound = true; + chart2::StackingDirection eCurrentDirection = eCommonDirection; + // property is not MAYBEVOID + bool bSuccess = ( aSeries[i]->getPropertyValue( "StackingDirection" ) >>= eCurrentDirection ); + OSL_ASSERT( bSuccess ); + if( ! bDirectionInitialized ) + { + eCommonDirection = eCurrentDirection; + bDirectionInitialized = true; + } + else + { + if( eCommonDirection != eCurrentDirection ) + { + rbAmbiguous = true; + break; + } + } + } + + if( rbFound ) + { + if( eCommonDirection == chart2::StackingDirection_Z_STACKING ) + eStackMode = StackMode::ZStacked; + else if( eCommonDirection == chart2::StackingDirection_Y_STACKING ) + { + eStackMode = StackMode::YStacked; + + // percent stacking + if( xCorrespondingCoordinateSystem.is() ) + { + if( 1 < xCorrespondingCoordinateSystem->getDimension() ) + { + sal_Int32 nAxisIndex = 0; + if( nSeriesCount ) + nAxisIndex = DataSeriesHelper::getAttachedAxisIndex(aSeries[0]); + + rtl::Reference< Axis > xAxis = + xCorrespondingCoordinateSystem->getAxisByDimension2( 1,nAxisIndex ); + if( xAxis.is()) + { + chart2::ScaleData aScaleData = xAxis->getScaleData(); + if( aScaleData.AxisType==chart2::AxisType::PERCENT ) + eStackMode = StackMode::YStackedPercent; + } + } + } + } + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + + return eStackMode; +} + +bool DiagramHelper::isSeriesAttachedToMainAxis( + const rtl::Reference< ::chart::DataSeries >& xDataSeries ) +{ + sal_Int32 nAxisIndex = DataSeriesHelper::getAttachedAxisIndex(xDataSeries); + return (nAxisIndex==0); +} + +static void lcl_generateAutomaticCategoriesFromChartType( + Sequence< OUString >& rRet, + const rtl::Reference< ChartType >& xChartType ) +{ + if(!xChartType.is()) + return; + OUString aMainSeq( xChartType->getRoleOfSequenceForSeriesLabel() ); + + const std::vector< rtl::Reference< DataSeries > > & aSeriesSeq = xChartType->getDataSeries2(); + for( rtl::Reference< DataSeries > const & dataSeries : aSeriesSeq ) + { + uno::Reference< data::XLabeledDataSequence > xLabeledSeq = + ::chart::DataSeriesHelper::getDataSequenceByRole( dataSeries, aMainSeq ); + if( !xLabeledSeq.is() ) + continue; + Reference< chart2::data::XDataSequence > xValueSeq( xLabeledSeq->getValues() ); + if( !xValueSeq.is() ) + continue; + rRet = xValueSeq->generateLabel( chart2::data::LabelOrigin_LONG_SIDE ); + if( rRet.hasElements() ) + return; + } +} + +Sequence< OUString > DiagramHelper::generateAutomaticCategoriesFromCooSys( const rtl::Reference< BaseCoordinateSystem > & xCooSys ) +{ + Sequence< OUString > aRet; + + if( xCooSys.is() ) + { + const std::vector< rtl::Reference< ChartType > > & aChartTypes( xCooSys->getChartTypes2() ); + for( rtl::Reference< ChartType > const & chartType : aChartTypes ) + { + lcl_generateAutomaticCategoriesFromChartType( aRet, chartType ); + if( aRet.hasElements() ) + return aRet; + } + } + return aRet; +} + +Sequence< OUString > DiagramHelper::getExplicitSimpleCategories( + ChartModel& rModel ) +{ + rtl::Reference< BaseCoordinateSystem > xCooSys( ChartModelHelper::getFirstCoordinateSystem( &rModel ) ); + ExplicitCategoriesProvider aExplicitCategoriesProvider( xCooSys, rModel ); + return aExplicitCategoriesProvider.getSimpleCategories(); +} + +namespace +{ +void lcl_switchToDateCategories( const rtl::Reference< ChartModel >& xChartDoc, const Reference< XAxis >& xAxis ) +{ + if( !xAxis.is() ) + return; + if( !xChartDoc.is() ) + return; + + ScaleData aScale( xAxis->getScaleData() ); + if( xChartDoc->hasInternalDataProvider() ) + { + //remove all content the is not of type double and remove multiple level + Reference< XAnyDescriptionAccess > xDataAccess( xChartDoc->getDataProvider(), uno::UNO_QUERY ); + if( xDataAccess.is() ) + { + Sequence< Sequence< Any > > aAnyCategories( xDataAccess->getAnyRowDescriptions() ); + auto aAnyCategoriesRange = asNonConstRange(aAnyCategories); + double fTest = 0.0; + sal_Int32 nN = aAnyCategories.getLength(); + for( ; nN--; ) + { + Sequence< Any >& rCat = aAnyCategoriesRange[nN]; + if( rCat.getLength() > 1 ) + rCat.realloc(1); + if( rCat.getLength() == 1 ) + { + Any& rAny = rCat.getArray()[0]; + if( !(rAny>>=fTest) ) + { + rAny <<= std::numeric_limits<double>::quiet_NaN(); + } + } + } + xDataAccess->setAnyRowDescriptions( aAnyCategories ); + } + //check the numberformat at the axis + Reference< beans::XPropertySet > xAxisProps( xAxis, uno::UNO_QUERY ); + if( xAxisProps.is() ) + { + sal_Int32 nNumberFormat = -1; + xAxisProps->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nNumberFormat; + + Reference< util::XNumberFormats > xNumberFormats( xChartDoc->getNumberFormats() ); + if( xNumberFormats.is() ) + { + Reference< beans::XPropertySet > xKeyProps; + try + { + xKeyProps = xNumberFormats->getByKey( nNumberFormat ); + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + sal_Int32 nType = util::NumberFormat::UNDEFINED; + if( xKeyProps.is() ) + xKeyProps->getPropertyValue( "Type" ) >>= nType; + if( !( nType & util::NumberFormat::DATE ) ) + { + //set a date format to the axis + const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper(); + Sequence<sal_Int32> aKeySeq = xNumberFormats->queryKeys( util::NumberFormat::DATE, rLocaleDataWrapper.getLanguageTag().getLocale(), true/*bCreate*/ ); + if( aKeySeq.hasElements() ) + { + xAxisProps->setPropertyValue(CHART_UNONAME_NUMFMT, uno::Any(aKeySeq[0])); + } + } + } + } + } + if( aScale.AxisType != chart2::AxisType::DATE ) + AxisHelper::removeExplicitScaling( aScale ); + aScale.AxisType = chart2::AxisType::DATE; + xAxis->setScaleData( aScale ); +} + +void lcl_switchToTextCategories( const rtl::Reference< ChartModel >& xChartDoc, const Reference< XAxis >& xAxis ) +{ + if( !xAxis.is() ) + return; + if( !xChartDoc.is() ) + return; + ScaleData aScale( xAxis->getScaleData() ); + if( aScale.AxisType != chart2::AxisType::CATEGORY ) + AxisHelper::removeExplicitScaling( aScale ); + //todo migrate dates to text? + aScale.AxisType = chart2::AxisType::CATEGORY; + aScale.AutoDateAxis = false; + xAxis->setScaleData( aScale ); +} + +} + +void DiagramHelper::switchToDateCategories( const rtl::Reference<::chart::ChartModel>& xChartDoc ) +{ + if(xChartDoc.is()) + { + ControllerLockGuardUNO aCtrlLockGuard( xChartDoc ); + + rtl::Reference< BaseCoordinateSystem > xCooSys = ChartModelHelper::getFirstCoordinateSystem( xChartDoc ); + if( xCooSys.is() ) + { + rtl::Reference< Axis > xAxis = xCooSys->getAxisByDimension2(0,0); + lcl_switchToDateCategories( xChartDoc, xAxis ); + } + } +} + +void DiagramHelper::switchToTextCategories( const rtl::Reference<::chart::ChartModel>& xChartDoc ) +{ + if(xChartDoc.is()) + { + ControllerLockGuardUNO aCtrlLockGuard( xChartDoc ); + + rtl::Reference< BaseCoordinateSystem > xCooSys = ChartModelHelper::getFirstCoordinateSystem( xChartDoc ); + if( xCooSys.is() ) + { + rtl::Reference< Axis > xAxis = xCooSys->getAxisByDimension2(0,0); + lcl_switchToTextCategories( xChartDoc, xAxis ); + } + } +} + +bool DiagramHelper::isDateNumberFormat( sal_Int32 nNumberFormat, const Reference< util::XNumberFormats >& xNumberFormats ) +{ + bool bIsDate = false; + if( !xNumberFormats.is() ) + return bIsDate; + + Reference< beans::XPropertySet > xKeyProps = xNumberFormats->getByKey( nNumberFormat ); + if( xKeyProps.is() ) + { + sal_Int32 nType = util::NumberFormat::UNDEFINED; + xKeyProps->getPropertyValue( "Type" ) >>= nType; + bIsDate = nType & util::NumberFormat::DATE; + } + return bIsDate; +} + +sal_Int32 DiagramHelper::getDateNumberFormat( const Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier ) +{ + sal_Int32 nRet=-1; + + //try to get a date format with full year display + const LanguageTag& rLanguageTag = Application::GetSettings().GetLanguageTag(); + NumberFormatterWrapper aNumberFormatterWrapper( xNumberFormatsSupplier ); + SvNumberFormatter* pNumFormatter = aNumberFormatterWrapper.getSvNumberFormatter(); + if( pNumFormatter ) + { + nRet = pNumFormatter->GetFormatIndex( NF_DATE_SYS_DDMMYYYY, rLanguageTag.getLanguageType() ); + } + else + { + Reference< util::XNumberFormats > xNumberFormats( xNumberFormatsSupplier->getNumberFormats() ); + if( xNumberFormats.is() ) + { + Sequence<sal_Int32> aKeySeq = xNumberFormats->queryKeys( util::NumberFormat::DATE, + rLanguageTag.getLocale(), true/*bCreate */); + if( aKeySeq.hasElements() ) + { + nRet = aKeySeq[0]; + } + } + } + return nRet; +} + +sal_Int32 DiagramHelper::getDateTimeInputNumberFormat( const Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier, double fNumber ) +{ + sal_Int32 nRet = 0; + + // Get the most detailed date/time format according to fNumber. + NumberFormatterWrapper aNumberFormatterWrapper( xNumberFormatsSupplier ); + SvNumberFormatter* pNumFormatter = aNumberFormatterWrapper.getSvNumberFormatter(); + if (!pNumFormatter) + SAL_WARN("chart2", "DiagramHelper::getDateTimeInputNumberFormat - no SvNumberFormatter"); + else + { + SvNumFormatType nType; + // Obtain best matching date, time or datetime format. + nRet = pNumFormatter->GuessDateTimeFormat( nType, fNumber, LANGUAGE_SYSTEM); + // Obtain the corresponding edit format. + nRet = pNumFormatter->GetEditFormat( fNumber, nRet, nType, nullptr); + } + return nRet; +} + +sal_Int32 DiagramHelper::getPercentNumberFormat( const Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier ) +{ + sal_Int32 nRet=-1; + const LanguageTag& rLanguageTag = Application::GetSettings().GetLanguageTag(); + NumberFormatterWrapper aNumberFormatterWrapper( xNumberFormatsSupplier ); + SvNumberFormatter* pNumFormatter = aNumberFormatterWrapper.getSvNumberFormatter(); + if( pNumFormatter ) + { + nRet = pNumFormatter->GetFormatIndex( NF_PERCENT_INT, rLanguageTag.getLanguageType() ); + } + else + { + Reference< util::XNumberFormats > xNumberFormats( xNumberFormatsSupplier->getNumberFormats() ); + if( xNumberFormats.is() ) + { + Sequence<sal_Int32> aKeySeq = xNumberFormats->queryKeys( util::NumberFormat::PERCENT, + rLanguageTag.getLocale(), true/*bCreate*/ ); + if( aKeySeq.hasElements() ) + { + // This *assumes* the sequence is sorted as in + // NfIndexTableOffset and the first format is the integer 0% + // format by chance... which usually is the case, but... anyway, + // we usually also have a number formatter so don't reach here. + nRet = aKeySeq[0]; + } + } + } + return nRet; +} + +bool DiagramHelper::areChartTypesCompatible( const rtl::Reference< ChartType >& xFirstType, + const rtl::Reference< ChartType >& xSecondType ) +{ + if( !xFirstType.is() || !xSecondType.is() ) + return false; + + auto aFirstRoles( comphelper::sequenceToContainer<std::vector< OUString >>( xFirstType->getSupportedMandatoryRoles() ) ); + auto aSecondRoles( comphelper::sequenceToContainer<std::vector< OUString >>( xSecondType->getSupportedMandatoryRoles() ) ); + std::sort( aFirstRoles.begin(), aFirstRoles.end() ); + std::sort( aSecondRoles.begin(), aSecondRoles.end() ); + return ( aFirstRoles == aSecondRoles ); +} + +static void lcl_ensureRange0to1( double& rValue ) +{ + if(rValue<0.0) + rValue=0.0; + if(rValue>1.0) + rValue=1.0; +} + +bool DiagramHelper::setDiagramPositioning( const rtl::Reference<::chart::ChartModel>& xChartModel, + const awt::Rectangle& rPosRect /*100th mm*/ ) +{ + ControllerLockGuardUNO aCtrlLockGuard( xChartModel ); + + bool bChanged = false; + awt::Size aPageSize( ChartModelHelper::getPageSize(xChartModel) ); + rtl::Reference< Diagram > xDiagram = xChartModel->getFirstChartDiagram(); + if( !xDiagram.is() ) + return bChanged; + + RelativePosition aOldPos; + RelativeSize aOldSize; + xDiagram->getPropertyValue("RelativePosition" ) >>= aOldPos; + xDiagram->getPropertyValue("RelativeSize" ) >>= aOldSize; + + RelativePosition aNewPos; + aNewPos.Anchor = drawing::Alignment_TOP_LEFT; + aNewPos.Primary = double(rPosRect.X)/double(aPageSize.Width); + aNewPos.Secondary = double(rPosRect.Y)/double(aPageSize.Height); + + chart2::RelativeSize aNewSize; + aNewSize.Primary = double(rPosRect.Width)/double(aPageSize.Width); + aNewSize.Secondary = double(rPosRect.Height)/double(aPageSize.Height); + + lcl_ensureRange0to1( aNewPos.Primary ); + lcl_ensureRange0to1( aNewPos.Secondary ); + lcl_ensureRange0to1( aNewSize.Primary ); + lcl_ensureRange0to1( aNewSize.Secondary ); + if( (aNewPos.Primary + aNewSize.Primary) > 1.0 ) + aNewPos.Primary = 1.0 - aNewSize.Primary; + if( (aNewPos.Secondary + aNewSize.Secondary) > 1.0 ) + aNewPos.Secondary = 1.0 - aNewSize.Secondary; + + xDiagram->setPropertyValue( "RelativePosition", uno::Any(aNewPos) ); + xDiagram->setPropertyValue( "RelativeSize", uno::Any(aNewSize) ); + + bChanged = (aOldPos.Anchor!=aNewPos.Anchor) || + (aOldPos.Primary!=aNewPos.Primary) || + (aOldPos.Secondary!=aNewPos.Secondary) || + (aOldSize.Primary!=aNewSize.Primary) || + (aOldSize.Secondary!=aNewSize.Secondary); + return bChanged; +} + +awt::Rectangle DiagramHelper::getDiagramRectangleFromModel( const rtl::Reference<::chart::ChartModel>& xChartModel ) +{ + awt::Rectangle aRet(-1,-1,-1,-1); + + rtl::Reference< Diagram > xDiagram = xChartModel->getFirstChartDiagram(); + if( !xDiagram.is() ) + return aRet; + + awt::Size aPageSize( ChartModelHelper::getPageSize(xChartModel) ); + + RelativePosition aRelPos; + RelativeSize aRelSize; + xDiagram->getPropertyValue("RelativePosition" ) >>= aRelPos; + xDiagram->getPropertyValue("RelativeSize" ) >>= aRelSize; + + awt::Size aAbsSize( + static_cast< sal_Int32 >( aRelSize.Primary * aPageSize.Width ), + static_cast< sal_Int32 >( aRelSize.Secondary * aPageSize.Height )); + + awt::Point aAbsPos( + static_cast< sal_Int32 >( aRelPos.Primary * aPageSize.Width ), + static_cast< sal_Int32 >( aRelPos.Secondary * aPageSize.Height )); + + awt::Point aAbsPosLeftTop = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject( aAbsPos, aAbsSize, aRelPos.Anchor ); + + aRet = awt::Rectangle(aAbsPosLeftTop.X, aAbsPosLeftTop.Y, aAbsSize.Width, aAbsSize.Height ); + + return aRet; +} + +bool DiagramHelper::switchDiagramPositioningToExcludingPositioning( + ChartModel& rModel, bool bResetModifiedState, bool bConvertAlsoFromAutoPositioning ) +{ + //return true if something was changed + const SvtSaveOptions::ODFSaneDefaultVersion nCurrentODFVersion(GetODFSaneDefaultVersion()); + if (SvtSaveOptions::ODFSVER_012 < nCurrentODFVersion) + { + uno::Reference< css::chart::XDiagramPositioning > xDiagramPositioning( rModel.getFirstDiagram(), uno::UNO_QUERY ); + if( xDiagramPositioning.is() && ( bConvertAlsoFromAutoPositioning || !xDiagramPositioning->isAutomaticDiagramPositioning() ) + && !xDiagramPositioning->isExcludingDiagramPositioning() ) + { + ControllerLockGuard aCtrlLockGuard( rModel ); + bool bModelWasModified = rModel.isModified(); + xDiagramPositioning->setDiagramPositionExcludingAxes( xDiagramPositioning->calculateDiagramPositionExcludingAxes() ); + if(bResetModifiedState && !bModelWasModified ) + rModel.setModified(false); + return true; + } + } + return false; +} + +} // namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |