diff options
Diffstat (limited to 'chart2/source/tools/ExplicitCategoriesProvider.cxx')
-rw-r--r-- | chart2/source/tools/ExplicitCategoriesProvider.cxx | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/chart2/source/tools/ExplicitCategoriesProvider.cxx b/chart2/source/tools/ExplicitCategoriesProvider.cxx new file mode 100644 index 000000000..bf65f58ec --- /dev/null +++ b/chart2/source/tools/ExplicitCategoriesProvider.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 <ExplicitCategoriesProvider.hxx> +#include <DiagramHelper.hxx> +#include <ChartTypeHelper.hxx> +#include <AxisHelper.hxx> +#include <DataSourceHelper.hxx> +#include <ChartModel.hxx> +#include <ChartModelHelper.hxx> +#include <NumberFormatterWrapper.hxx> +#include <unonames.hxx> + +#include <com/sun/star/chart2/AxisType.hpp> +#include <o3tl/safeint.hxx> +#include <rtl/math.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 std::vector; + +ExplicitCategoriesProvider::ExplicitCategoriesProvider( const Reference< chart2::XCoordinateSystem >& xCooSysModel + , ChartModel& rModel ) + : m_bDirty(true) + , m_xCooSysModel( xCooSysModel ) + , mrModel(rModel) + , m_xOriginalCategories() + , m_bIsExplicitCategoriesInited(false) + , m_bIsDateAxis(false) + , m_bIsAutoDate(false) +{ + try + { + if( xCooSysModel.is() ) + { + // TODO: handle different category names on the primary and secondary category axis. + uno::Reference< XAxis > xAxis( xCooSysModel->getAxisByDimension(0,0) ); + if( xAxis.is() ) + { + ScaleData aScale( xAxis->getScaleData() ); + m_xOriginalCategories = aScale.Categories; + m_bIsAutoDate = (aScale.AutoDateAxis && aScale.AxisType==chart2::AxisType::CATEGORY); + m_bIsDateAxis = (aScale.AxisType == chart2::AxisType::DATE || m_bIsAutoDate); + } + } + + if( m_xOriginalCategories.is() ) + { + uno::Reference< data::XDataProvider > xDataProvider( mrModel.getDataProvider() ); + + OUString aCategoriesRange( DataSourceHelper::getRangeFromValues( m_xOriginalCategories ) ); + if( xDataProvider.is() && !aCategoriesRange.isEmpty() ) + { + const bool bFirstCellAsLabel = false; + const bool bHasCategories = false; + const uno::Sequence< sal_Int32 > aSequenceMapping; + + uno::Reference< data::XDataSource > xColumnCategoriesSource( xDataProvider->createDataSource( + DataSourceHelper::createArguments( aCategoriesRange, aSequenceMapping, true /*bUseColumns*/ + , bFirstCellAsLabel, bHasCategories ) ) ); + + uno::Reference< data::XDataSource > xRowCategoriesSource( xDataProvider->createDataSource( + DataSourceHelper::createArguments( aCategoriesRange, aSequenceMapping, false /*bUseColumns*/ + , bFirstCellAsLabel, bHasCategories ) ) ); + + if( xColumnCategoriesSource.is() && xRowCategoriesSource.is() ) + { + Sequence< Reference< data::XLabeledDataSequence> > aColumns = xColumnCategoriesSource->getDataSequences(); + Sequence< Reference< data::XLabeledDataSequence> > aRows = xRowCategoriesSource->getDataSequences(); + + sal_Int32 nColumnCount = aColumns.getLength(); + sal_Int32 nRowCount = aRows.getLength(); + if( nColumnCount>1 && nRowCount>1 ) + { + //we have complex categories + //->split them in the direction of the first series + //detect whether the first series is a row or a column + bool bSeriesUsesColumns = true; + std::vector< Reference< XDataSeries > > aSeries( ChartModelHelper::getDataSeries( mrModel ) ); + if( !aSeries.empty() ) + { + uno::Reference< data::XDataSource > xSeriesSource( aSeries.front(), uno::UNO_QUERY ); + OUString aStringDummy; + bool bDummy; + uno::Sequence< sal_Int32 > aSeqDummy; + DataSourceHelper::readArguments( xDataProvider->detectArguments( xSeriesSource), + aStringDummy, aSeqDummy, bSeriesUsesColumns, bDummy, bDummy ); + } + if( bSeriesUsesColumns ) + m_aSplitCategoriesList=aColumns; + else + m_aSplitCategoriesList=aRows; + } + } + } + if( !m_aSplitCategoriesList.hasElements() ) + { + m_aSplitCategoriesList.realloc(1); + m_aSplitCategoriesList[0]=m_xOriginalCategories; + } + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } +} + +ExplicitCategoriesProvider::~ExplicitCategoriesProvider() +{ +} + +Reference< chart2::data::XDataSequence > ExplicitCategoriesProvider::getOriginalCategories() +{ + if( m_xOriginalCategories.is() ) + return m_xOriginalCategories->getValues(); + return nullptr; +} + +bool ExplicitCategoriesProvider::hasComplexCategories() const +{ + return m_aSplitCategoriesList.getLength() > 1; +} + +sal_Int32 ExplicitCategoriesProvider::getCategoryLevelCount() const +{ + sal_Int32 nCount = m_aSplitCategoriesList.getLength(); + if(!nCount) + nCount = 1; + return nCount; +} + +static std::vector<sal_Int32> lcl_getLimitingBorders( const std::vector< ComplexCategory >& rComplexCategories ) +{ + std::vector<sal_Int32> aLimitingBorders; + sal_Int32 nBorderIndex = 0; /*border below the index*/ + for (auto const& complexCategory : rComplexCategories) + { + nBorderIndex += complexCategory.Count; + aLimitingBorders.push_back(nBorderIndex); + } + return aLimitingBorders; +} + +void ExplicitCategoriesProvider::convertCategoryAnysToText( uno::Sequence< OUString >& rOutTexts, const uno::Sequence< uno::Any >& rInAnys, ChartModel& rModel ) +{ + sal_Int32 nCount = rInAnys.getLength(); + if(!nCount) + return; + rOutTexts.realloc(nCount); + + sal_Int32 nAxisNumberFormat = 0; + Reference< XCoordinateSystem > xCooSysModel( ChartModelHelper::getFirstCoordinateSystem( rModel ) ); + if( xCooSysModel.is() ) + { + Reference< chart2::XAxis > xAxis( xCooSysModel->getAxisByDimension(0,0) ); + nAxisNumberFormat = AxisHelper::getExplicitNumberFormatKeyForAxis( + xAxis, xCooSysModel, uno::Reference<chart2::XChartDocument>(static_cast< ::cppu::OWeakObject* >(&rModel), uno::UNO_QUERY), false ); + } + + Color nLabelColor; + bool bColorChanged = false; + + NumberFormatterWrapper aNumberFormatterWrapper( rModel.getNumberFormatsSupplier() ); + + for(sal_Int32 nN=0;nN<nCount;nN++) + { + OUString aText; + uno::Any aAny = rInAnys[nN]; + if( aAny.hasValue() ) + { + double fDouble = 0; + if( aAny>>=fDouble ) + { + if( !std::isnan(fDouble) ) + aText = aNumberFormatterWrapper.getFormattedString( + nAxisNumberFormat, fDouble, nLabelColor, bColorChanged ); + } + else + { + aAny>>=aText; + } + } + rOutTexts[nN] = aText; + } +} + +SplitCategoriesProvider::~SplitCategoriesProvider() +{ +} + +namespace { + +class SplitCategoriesProvider_ForLabeledDataSequences : public SplitCategoriesProvider +{ +public: + + explicit SplitCategoriesProvider_ForLabeledDataSequences( + const css::uno::Sequence< + css::uno::Reference< css::chart2::data::XLabeledDataSequence> >& rSplitCategoriesList + , ChartModel& rModel ) + : m_rSplitCategoriesList( rSplitCategoriesList ) + , mrModel( rModel ) + {} + + virtual sal_Int32 getLevelCount() const override; + virtual uno::Sequence< OUString > getStringsForLevel( sal_Int32 nIndex ) const override; + +private: + const css::uno::Sequence< css::uno::Reference< + css::chart2::data::XLabeledDataSequence> >& m_rSplitCategoriesList; + + ChartModel& mrModel; +}; + +} + +sal_Int32 SplitCategoriesProvider_ForLabeledDataSequences::getLevelCount() const +{ + return m_rSplitCategoriesList.getLength(); +} +uno::Sequence< OUString > SplitCategoriesProvider_ForLabeledDataSequences::getStringsForLevel( sal_Int32 nLevel ) const +{ + uno::Sequence< OUString > aRet; + Reference< data::XLabeledDataSequence > xLabeledDataSequence( m_rSplitCategoriesList[nLevel] ); + if( xLabeledDataSequence.is() ) + { + uno::Reference< data::XDataSequence > xDataSequence( xLabeledDataSequence->getValues() ); + if( xDataSequence.is() ) + ExplicitCategoriesProvider::convertCategoryAnysToText( aRet, xDataSequence->getData(), mrModel ); + } + return aRet; +} + +static std::vector< ComplexCategory > lcl_DataSequenceToComplexCategoryVector( + const uno::Sequence< OUString >& rStrings + , const std::vector<sal_Int32>& rLimitingBorders, bool bCreateSingleCategories ) +{ + std::vector< ComplexCategory > aResult; + + sal_Int32 nMaxCount = rStrings.getLength(); + OUString aPrevious; + sal_Int32 nCurrentCount=0; + for( sal_Int32 nN=0; nN<nMaxCount; nN++ ) + { + const OUString& aCurrent = rStrings[nN]; + if( bCreateSingleCategories || std::find( rLimitingBorders.begin(), rLimitingBorders.end(), nN ) != rLimitingBorders.end() ) + { + aResult.emplace_back(aPrevious,nCurrentCount ); + nCurrentCount=1; + aPrevious = aCurrent; + } + else + { + // Empty value is interpreted as a continuation of the previous + // category. Note that having the same value as the previous one + // does not equate to a continuation of the category. + + if (aCurrent.isEmpty()) + ++nCurrentCount; + else + { + aResult.emplace_back(aPrevious,nCurrentCount ); + nCurrentCount=1; + aPrevious = aCurrent; + } + } + } + if( nCurrentCount ) + aResult.emplace_back(aPrevious,nCurrentCount ); + + return aResult; +} + +static sal_Int32 lcl_getCategoryCount( std::vector< ComplexCategory >& rComplexCategories ) +{ + sal_Int32 nCount = 0; + for (auto const& complexCategory : rComplexCategories) + nCount+=complexCategory.Count; + return nCount; +} + +static Sequence< OUString > lcl_getExplicitSimpleCategories( + const SplitCategoriesProvider& rSplitCategoriesProvider, + std::vector< std::vector< ComplexCategory > >& rComplexCats ) +{ + Sequence< OUString > aRet; + + rComplexCats.clear(); + sal_Int32 nLCount = rSplitCategoriesProvider.getLevelCount(); + for( sal_Int32 nL = 0; nL < nLCount; nL++ ) + { + std::vector<sal_Int32> aLimitingBorders; + if(nL>0) + aLimitingBorders = lcl_getLimitingBorders( rComplexCats.back() ); + rComplexCats.push_back( lcl_DataSequenceToComplexCategoryVector( + rSplitCategoriesProvider.getStringsForLevel(nL), aLimitingBorders, nL==(nLCount-1) ) ); + } + + //ensure that the category count is the same on each level + sal_Int32 nMaxCategoryCount = 0; + { + for (auto & complexCat : rComplexCats) + { + sal_Int32 nCurrentCount = lcl_getCategoryCount(complexCat); + nMaxCategoryCount = std::max( nCurrentCount, nMaxCategoryCount ); + } + for (auto & complexCat : rComplexCats) + { + if ( !complexCat.empty() ) + { + sal_Int32 nCurrentCount = lcl_getCategoryCount(complexCat); + if( nCurrentCount< nMaxCategoryCount ) + { + ComplexCategory& rComplexCategory = complexCat.back(); + rComplexCategory.Count += (nMaxCategoryCount-nCurrentCount); + } + } + } + } + + //create a list with an element for every index + std::vector< std::vector< ComplexCategory > > aComplexCatsPerIndex; + for (auto const& complexCat : rComplexCats) + { + std::vector< ComplexCategory > aSingleLevel; + for (auto const& elem : complexCat) + { + sal_Int32 nCount = elem.Count; + while( nCount-- ) + aSingleLevel.push_back(elem); + } + aComplexCatsPerIndex.push_back( aSingleLevel ); + } + + if(nMaxCategoryCount) + { + aRet.realloc(nMaxCategoryCount); + for(sal_Int32 nN=0; nN<nMaxCategoryCount; nN++) + { + OUStringBuffer aText; + for (auto const& complexCatPerIndex : aComplexCatsPerIndex) + { + if ( o3tl::make_unsigned(nN) < complexCatPerIndex.size() ) + { + OUString aAddText = complexCatPerIndex[nN].Text; + if( !aAddText.isEmpty() ) + { + if(!aText.isEmpty()) + aText.append(" "); + aText.append(aAddText); + } + } + } + aRet[nN]=aText.makeStringAndClear(); + } + } + return aRet; +} + +Sequence< OUString > ExplicitCategoriesProvider::getExplicitSimpleCategories( + const SplitCategoriesProvider& rSplitCategoriesProvider ) +{ + vector< vector< ComplexCategory > > aComplexCats; + return lcl_getExplicitSimpleCategories( rSplitCategoriesProvider, aComplexCats ); +} + +static bool lcl_fillDateCategories( const uno::Reference< data::XDataSequence >& xDataSequence, std::vector< double >& rDateCategories, bool bIsAutoDate, ChartModel& rModel ) +{ + bool bOnlyDatesFound = true; + bool bAnyDataFound = false; + + if( xDataSequence.is() ) + { + uno::Sequence< uno::Any > aValues = xDataSequence->getData(); + sal_Int32 nCount = aValues.getLength(); + rDateCategories.reserve(nCount); + Reference< util::XNumberFormats > xNumberFormats( rModel.getNumberFormats() ); + + bool bOwnData = false; + bool bOwnDataAnddAxisHasAnyFormat = false; + bool bOwnDataAnddAxisHasDateFormat = false; + Reference< XCoordinateSystem > xCooSysModel( ChartModelHelper::getFirstCoordinateSystem( rModel ) ); + if( xCooSysModel.is() ) + { + if( rModel.hasInternalDataProvider() ) + { + bOwnData = true; + Reference< beans::XPropertySet > xAxisProps( xCooSysModel->getAxisByDimension(0,0), uno::UNO_QUERY ); + sal_Int32 nAxisNumberFormat = 0; + if (xAxisProps.is() && (xAxisProps->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nAxisNumberFormat)) + { + bOwnDataAnddAxisHasAnyFormat = true; + bOwnDataAnddAxisHasDateFormat = DiagramHelper::isDateNumberFormat( nAxisNumberFormat, xNumberFormats ); + } + } + } + + for(sal_Int32 nN=0;nN<nCount;nN++) + { + bool bIsDate = false; + if( bIsAutoDate ) + { + if( bOwnData ) + bIsDate = !bOwnDataAnddAxisHasAnyFormat || bOwnDataAnddAxisHasDateFormat; + else + bIsDate = DiagramHelper::isDateNumberFormat( xDataSequence->getNumberFormatKeyByIndex( nN ), xNumberFormats ); + } + else + bIsDate = true; + + bool bContainsEmptyString = false; + uno::Any aAny = aValues[nN]; + if( aAny.hasValue() ) + { + OUString aTest; + double fTest = 0; + bool bContainsNan = false; + if( (aAny>>=aTest) && aTest.isEmpty() ) //empty String + bContainsEmptyString = true; + else if( (aAny>>=fTest) && std::isnan(fTest) ) + bContainsNan = true; + + if( !bContainsEmptyString && !bContainsNan ) + bAnyDataFound = true; + } + double aDate( 1.0 ); + if( bIsDate && (aAny >>= aDate) ) + rDateCategories.push_back( aDate ); + else + { + if( aAny.hasValue() && !bContainsEmptyString )//empty string does not count as non date value! + bOnlyDatesFound=false; + ::rtl::math::setNan( &aDate ); + rDateCategories.push_back( aDate ); + } + } + std::sort( rDateCategories.begin(), rDateCategories.end() ); + } + + return bAnyDataFound && bOnlyDatesFound; +} + +void ExplicitCategoriesProvider::init() +{ + if( !m_bDirty ) + return; + + m_aComplexCats.clear();//not one per index + m_aDateCategories.clear(); + + if( m_xOriginalCategories.is() ) + { + if( !hasComplexCategories() ) + { + if(m_bIsDateAxis) + { + if( ChartTypeHelper::isSupportingDateAxis( AxisHelper::getChartTypeByIndex( m_xCooSysModel, 0 ), 0 ) ) + m_bIsDateAxis = lcl_fillDateCategories( m_xOriginalCategories->getValues(), m_aDateCategories, m_bIsAutoDate, mrModel ); + else + m_bIsDateAxis = false; + } + } + else + { + m_bIsDateAxis = false; + } + } + else + m_bIsDateAxis=false; + m_bDirty = false; +} + +Sequence< OUString > const & ExplicitCategoriesProvider::getSimpleCategories() +{ + if( !m_bIsExplicitCategoriesInited ) + { + init(); + m_aExplicitCategories.realloc(0); + if( m_xOriginalCategories.is() ) + { + if( !hasComplexCategories() ) + { + uno::Reference< data::XDataSequence > xDataSequence( m_xOriginalCategories->getValues() ); + if( xDataSequence.is() ) + ExplicitCategoriesProvider::convertCategoryAnysToText( m_aExplicitCategories, xDataSequence->getData(), mrModel ); + } + else + { + m_aExplicitCategories = lcl_getExplicitSimpleCategories( + SplitCategoriesProvider_ForLabeledDataSequences( m_aSplitCategoriesList, mrModel ), m_aComplexCats ); + } + } + if(!m_aExplicitCategories.hasElements()) + m_aExplicitCategories = DiagramHelper::generateAutomaticCategoriesFromCooSys( m_xCooSysModel ); + m_bIsExplicitCategoriesInited = true; + } + return m_aExplicitCategories; +} + +const std::vector<ComplexCategory>* ExplicitCategoriesProvider::getCategoriesByLevel( sal_Int32 nLevel ) +{ + init(); + sal_Int32 nMaxIndex = m_aComplexCats.size()-1; + if (nLevel >= 0 && nLevel <= nMaxIndex) + return &m_aComplexCats[nMaxIndex-nLevel]; + return nullptr; +} + +OUString ExplicitCategoriesProvider::getCategoryByIndex( + const Reference< XCoordinateSystem >& xCooSysModel + , ChartModel& rModel + , sal_Int32 nIndex ) +{ + if( xCooSysModel.is()) + { + ExplicitCategoriesProvider aExplicitCategoriesProvider( xCooSysModel, rModel ); + Sequence< OUString > aCategories( aExplicitCategoriesProvider.getSimpleCategories()); + if( nIndex < aCategories.getLength()) + return aCategories[ nIndex ]; + } + return OUString(); +} + +bool ExplicitCategoriesProvider::isDateAxis() +{ + init(); + return m_bIsDateAxis; +} + +const std::vector< double >& ExplicitCategoriesProvider::getDateCategories() +{ + init(); + return m_aDateCategories; +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |