1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <ExplicitCategoriesProvider.hxx>
21 #include <DiagramHelper.hxx>
22 #include <ChartType.hxx>
23 #include <ChartTypeHelper.hxx>
25 #include <AxisHelper.hxx>
26 #include <DataSourceHelper.hxx>
27 #include <ChartModel.hxx>
28 #include <ChartModelHelper.hxx>
29 #include <NumberFormatterWrapper.hxx>
30 #include <unonames.hxx>
31 #include <BaseCoordinateSystem.hxx>
32 #include <DataSeries.hxx>
34 #include <com/sun/star/chart2/AxisType.hpp>
35 #include <com/sun/star/chart/ChartDataRowSource.hpp>
36 #include <o3tl/compare.hxx>
37 #include <o3tl/safeint.hxx>
38 #include <rtl/ustrbuf.hxx>
39 #include <comphelper/diagnose_ex.hxx>
46 using namespace ::com::sun::star
;
47 using namespace ::com::sun::star::chart2
;
48 using ::com::sun::star::uno::Reference
;
49 using ::com::sun::star::uno::Sequence
;
52 ExplicitCategoriesProvider::ExplicitCategoriesProvider( const rtl::Reference
< BaseCoordinateSystem
>& xCooSysModel
53 , ChartModel
& rModel
)
55 , m_xCooSysModel( xCooSysModel
.get() )
57 , m_bIsExplicitCategoriesInited(false)
58 , m_bIsDateAxis(false)
59 , m_bIsAutoDate(false)
63 if( xCooSysModel
.is() )
65 // TODO: handle different category names on the primary and secondary category axis.
66 rtl::Reference
< Axis
> xAxis
= xCooSysModel
->getAxisByDimension2(0,0);
69 ScaleData
aScale( xAxis
->getScaleData() );
70 m_xOriginalCategories
= aScale
.Categories
;
71 m_bIsAutoDate
= (aScale
.AutoDateAxis
&& aScale
.AxisType
==chart2::AxisType::CATEGORY
);
72 m_bIsDateAxis
= (aScale
.AxisType
== chart2::AxisType::DATE
|| m_bIsAutoDate
);
76 if( m_xOriginalCategories
.is() )
78 uno::Reference
< data::XDataProvider
> xDataProvider( mrModel
.getDataProvider() );
80 OUString
aCategoriesRange( DataSourceHelper::getRangeFromValues( m_xOriginalCategories
) );
81 if( xDataProvider
.is() && !aCategoriesRange
.isEmpty() )
83 const bool bFirstCellAsLabel
= false;
84 const bool bHasCategories
= false;
85 const uno::Sequence
< sal_Int32
> aSequenceMapping
;
87 uno::Reference
< data::XDataSource
> xColumnCategoriesSource( xDataProvider
->createDataSource(
88 DataSourceHelper::createArguments( aCategoriesRange
, aSequenceMapping
, true /*bUseColumns*/
89 , bFirstCellAsLabel
, bHasCategories
) ) );
91 uno::Reference
< data::XDataSource
> xRowCategoriesSource( xDataProvider
->createDataSource(
92 DataSourceHelper::createArguments( aCategoriesRange
, aSequenceMapping
, false /*bUseColumns*/
93 , bFirstCellAsLabel
, bHasCategories
) ) );
95 if( xColumnCategoriesSource
.is() && xRowCategoriesSource
.is() )
97 Sequence
< Reference
< data::XLabeledDataSequence
> > aColumns
= xColumnCategoriesSource
->getDataSequences();
98 Sequence
< Reference
< data::XLabeledDataSequence
> > aRows
= xRowCategoriesSource
->getDataSequences();
100 sal_Int32 nColumnCount
= aColumns
.getLength();
101 sal_Int32 nRowCount
= aRows
.getLength();
102 if( nColumnCount
>1 && nRowCount
>1 )
104 //we have complex categories
105 //->split them in the direction of the first series
106 //detect whether the first series is a row or a column
107 bool bSeriesUsesColumns
= true;
108 std::vector
< rtl::Reference
< DataSeries
> > aSeries
= ChartModelHelper::getDataSeries( &mrModel
);
109 if( !aSeries
.empty() )
111 const rtl::Reference
< DataSeries
>& xSeriesSource
= aSeries
.front();
112 for(const auto& rArgument
: xDataProvider
->detectArguments( xSeriesSource
))
114 if ( rArgument
.Name
== "DataRowSource" )
116 css::chart::ChartDataRowSource eRowSource
;
117 if( rArgument
.Value
>>= eRowSource
)
119 bSeriesUsesColumns
= (eRowSource
== css::chart::ChartDataRowSource_COLUMNS
);
125 if( bSeriesUsesColumns
)
126 m_aSplitCategoriesList
= comphelper::sequenceToContainer
<std::vector
<Reference
<data::XLabeledDataSequence
>>>(aColumns
);
128 m_aSplitCategoriesList
= comphelper::sequenceToContainer
<std::vector
<Reference
<data::XLabeledDataSequence
>>>(aRows
);
132 if( m_aSplitCategoriesList
.empty() )
134 m_aSplitCategoriesList
= { m_xOriginalCategories
};
138 catch( const uno::Exception
& )
140 DBG_UNHANDLED_EXCEPTION("chart2");
144 ExplicitCategoriesProvider::~ExplicitCategoriesProvider()
148 Reference
< chart2::data::XDataSequence
> ExplicitCategoriesProvider::getOriginalCategories()
150 if( m_xOriginalCategories
.is() )
151 return m_xOriginalCategories
->getValues();
155 bool ExplicitCategoriesProvider::hasComplexCategories() const
157 return m_aSplitCategoriesList
.size() > 1;
160 sal_Int32
ExplicitCategoriesProvider::getCategoryLevelCount() const
162 sal_Int32 nCount
= m_aSplitCategoriesList
.size();
168 static std::vector
<sal_Int32
> lcl_getLimitingBorders( const std::vector
< ComplexCategory
>& rComplexCategories
)
170 std::vector
<sal_Int32
> aLimitingBorders
;
171 sal_Int32 nBorderIndex
= 0; /*border below the index*/
172 for (auto const& complexCategory
: rComplexCategories
)
174 nBorderIndex
+= complexCategory
.Count
;
175 aLimitingBorders
.push_back(nBorderIndex
);
177 return aLimitingBorders
;
180 void ExplicitCategoriesProvider::convertCategoryAnysToText( uno::Sequence
< OUString
>& rOutTexts
, const uno::Sequence
< uno::Any
>& rInAnys
, ChartModel
& rModel
)
182 sal_Int32 nCount
= rInAnys
.getLength();
185 rOutTexts
.realloc(nCount
);
186 auto pOutTexts
= rOutTexts
.getArray();
188 sal_Int32 nAxisNumberFormat
= 0;
189 rtl::Reference
< BaseCoordinateSystem
> xCooSysModel( ChartModelHelper::getFirstCoordinateSystem( &rModel
) );
190 if( xCooSysModel
.is() )
192 rtl::Reference
< Axis
> xAxis
= xCooSysModel
->getAxisByDimension2(0,0);
193 nAxisNumberFormat
= AxisHelper::getExplicitNumberFormatKeyForAxis(
194 xAxis
, xCooSysModel
, &rModel
, false );
198 bool bColorChanged
= false;
200 NumberFormatterWrapper
aNumberFormatterWrapper( rModel
.getNumberFormatsSupplier() );
202 for(sal_Int32 nN
=0;nN
<nCount
;nN
++)
205 const uno::Any
& aAny
= rInAnys
[nN
];
206 if( aAny
.hasValue() )
211 if( !std::isnan(fDouble
) )
212 aText
= aNumberFormatterWrapper
.getFormattedString(
213 nAxisNumberFormat
, fDouble
, nLabelColor
, bColorChanged
);
220 pOutTexts
[nN
] = aText
;
224 SplitCategoriesProvider::~SplitCategoriesProvider()
230 class SplitCategoriesProvider_ForLabeledDataSequences
: public SplitCategoriesProvider
234 explicit SplitCategoriesProvider_ForLabeledDataSequences(
235 const std::vector
< Reference
< data::XLabeledDataSequence
> >& rSplitCategoriesList
236 , ChartModel
& rModel
)
237 : m_rSplitCategoriesList( rSplitCategoriesList
)
241 virtual sal_Int32
getLevelCount() const override
;
242 virtual uno::Sequence
< OUString
> getStringsForLevel( sal_Int32 nIndex
) const override
;
245 const std::vector
< Reference
< data::XLabeledDataSequence
> >& m_rSplitCategoriesList
;
252 sal_Int32
SplitCategoriesProvider_ForLabeledDataSequences::getLevelCount() const
254 return m_rSplitCategoriesList
.size();
256 uno::Sequence
< OUString
> SplitCategoriesProvider_ForLabeledDataSequences::getStringsForLevel( sal_Int32 nLevel
) const
258 uno::Sequence
< OUString
> aRet
;
259 Reference
< data::XLabeledDataSequence
> xLabeledDataSequence( m_rSplitCategoriesList
[nLevel
] );
260 if( xLabeledDataSequence
.is() )
262 uno::Reference
< data::XDataSequence
> xDataSequence( xLabeledDataSequence
->getValues() );
263 if( xDataSequence
.is() )
264 ExplicitCategoriesProvider::convertCategoryAnysToText( aRet
, xDataSequence
->getData(), mrModel
);
269 static std::vector
< ComplexCategory
> lcl_DataSequenceToComplexCategoryVector(
270 const uno::Sequence
< OUString
>& rStrings
271 , const std::vector
<sal_Int32
>& rLimitingBorders
, bool bCreateSingleCategories
)
273 std::vector
< ComplexCategory
> aResult
;
275 sal_Int32 nMaxCount
= rStrings
.getLength();
277 sal_Int32 nCurrentCount
=0;
278 for( sal_Int32 nN
=0; nN
<nMaxCount
; nN
++ )
280 const OUString
& aCurrent
= rStrings
[nN
];
281 if( bCreateSingleCategories
|| std::find( rLimitingBorders
.begin(), rLimitingBorders
.end(), nN
) != rLimitingBorders
.end() )
283 aResult
.emplace_back(aPrevious
,nCurrentCount
);
285 aPrevious
= aCurrent
;
289 // Empty value is interpreted as a continuation of the previous
290 // category. Note that having the same value as the previous one
291 // does not equate to a continuation of the category.
293 if (aCurrent
.isEmpty())
297 aResult
.emplace_back(aPrevious
,nCurrentCount
);
299 aPrevious
= aCurrent
;
304 aResult
.emplace_back(aPrevious
,nCurrentCount
);
309 static sal_Int32
lcl_getCategoryCount( std::vector
< ComplexCategory
>& rComplexCategories
)
311 sal_Int32 nCount
= 0;
312 for (auto const& complexCategory
: rComplexCategories
)
313 nCount
+=complexCategory
.Count
;
317 static Sequence
< OUString
> lcl_getExplicitSimpleCategories(
318 const SplitCategoriesProvider
& rSplitCategoriesProvider
,
319 std::vector
< std::vector
< ComplexCategory
> >& rComplexCats
)
321 Sequence
< OUString
> aRet
;
323 rComplexCats
.clear();
324 sal_Int32 nLCount
= rSplitCategoriesProvider
.getLevelCount();
325 for( sal_Int32 nL
= 0; nL
< nLCount
; nL
++ )
327 std::vector
<sal_Int32
> aLimitingBorders
;
329 aLimitingBorders
= lcl_getLimitingBorders( rComplexCats
.back() );
330 rComplexCats
.push_back( lcl_DataSequenceToComplexCategoryVector(
331 rSplitCategoriesProvider
.getStringsForLevel(nL
), aLimitingBorders
, nL
==(nLCount
-1) ) );
334 //ensure that the category count is the same on each level
335 sal_Int32 nMaxCategoryCount
= 0;
337 for (auto & complexCat
: rComplexCats
)
339 sal_Int32 nCurrentCount
= lcl_getCategoryCount(complexCat
);
340 nMaxCategoryCount
= std::max( nCurrentCount
, nMaxCategoryCount
);
342 for (auto & complexCat
: rComplexCats
)
344 if ( !complexCat
.empty() )
346 sal_Int32 nCurrentCount
= lcl_getCategoryCount(complexCat
);
347 if( nCurrentCount
< nMaxCategoryCount
)
349 ComplexCategory
& rComplexCategory
= complexCat
.back();
350 rComplexCategory
.Count
+= (nMaxCategoryCount
-nCurrentCount
);
356 //create a list with an element for every index
357 std::vector
< std::vector
< ComplexCategory
> > aComplexCatsPerIndex
;
358 for (auto const& complexCat
: rComplexCats
)
360 std::vector
< ComplexCategory
> aSingleLevel
;
361 for (auto const& elem
: complexCat
)
363 sal_Int32 nCount
= elem
.Count
;
365 aSingleLevel
.push_back(elem
);
367 aComplexCatsPerIndex
.push_back( aSingleLevel
);
370 if(nMaxCategoryCount
)
372 aRet
.realloc(nMaxCategoryCount
);
373 auto pRet
= aRet
.getArray();
374 for(sal_Int32 nN
=0; nN
<nMaxCategoryCount
; nN
++)
376 OUStringBuffer aText
;
377 for (auto const& complexCatPerIndex
: aComplexCatsPerIndex
)
379 if ( o3tl::make_unsigned(nN
) < complexCatPerIndex
.size() )
381 OUString aAddText
= complexCatPerIndex
[nN
].Text
;
382 if( !aAddText
.isEmpty() )
386 aText
.append(aAddText
);
390 pRet
[nN
]=aText
.makeStringAndClear();
396 Sequence
< OUString
> ExplicitCategoriesProvider::getExplicitSimpleCategories(
397 const SplitCategoriesProvider
& rSplitCategoriesProvider
)
399 vector
< vector
< ComplexCategory
> > aComplexCats
;
400 return lcl_getExplicitSimpleCategories( rSplitCategoriesProvider
, aComplexCats
);
403 static bool lcl_fillDateCategories( const uno::Reference
< data::XDataSequence
>& xDataSequence
, std::vector
< double >& rDateCategories
, bool bIsAutoDate
, ChartModel
& rModel
)
405 bool bOnlyDatesFound
= true;
406 bool bAnyDataFound
= false;
408 if( xDataSequence
.is() )
410 uno::Sequence
< uno::Any
> aValues
= xDataSequence
->getData();
411 sal_Int32 nCount
= aValues
.getLength();
412 rDateCategories
.reserve(nCount
);
413 Reference
< util::XNumberFormats
> xNumberFormats( rModel
.getNumberFormats() );
415 bool bOwnData
= false;
416 bool bOwnDataAnddAxisHasAnyFormat
= false;
417 bool bOwnDataAnddAxisHasDateFormat
= false;
418 rtl::Reference
< BaseCoordinateSystem
> xCooSysModel( ChartModelHelper::getFirstCoordinateSystem( &rModel
) );
419 if( xCooSysModel
.is() )
421 if( rModel
.hasInternalDataProvider() )
424 rtl::Reference
< Axis
> xAxisProps
= xCooSysModel
->getAxisByDimension2(0,0);
425 sal_Int32 nAxisNumberFormat
= 0;
426 if (xAxisProps
.is() && (xAxisProps
->getPropertyValue(CHART_UNONAME_NUMFMT
) >>= nAxisNumberFormat
))
428 bOwnDataAnddAxisHasAnyFormat
= true;
429 bOwnDataAnddAxisHasDateFormat
= DiagramHelper::isDateNumberFormat( nAxisNumberFormat
, xNumberFormats
);
434 for(sal_Int32 nN
=0;nN
<nCount
;nN
++)
436 bool bIsDate
= false;
440 bIsDate
= !bOwnDataAnddAxisHasAnyFormat
|| bOwnDataAnddAxisHasDateFormat
;
442 bIsDate
= DiagramHelper::isDateNumberFormat( xDataSequence
->getNumberFormatKeyByIndex( nN
), xNumberFormats
);
447 bool bContainsEmptyString
= false;
448 const uno::Any
& aAny
= aValues
[nN
];
449 if( aAny
.hasValue() )
453 bool bContainsNan
= false;
454 if( (aAny
>>=aTest
) && aTest
.isEmpty() ) //empty String
455 bContainsEmptyString
= true;
456 else if( (aAny
>>=fTest
) && std::isnan(fTest
) )
459 if( !bContainsEmptyString
&& !bContainsNan
)
460 bAnyDataFound
= true;
463 if( bIsDate
&& (aAny
>>= aDate
) )
464 rDateCategories
.push_back( aDate
);
467 if( aAny
.hasValue() && !bContainsEmptyString
)//empty string does not count as non date value!
468 bOnlyDatesFound
=false;
469 rDateCategories
.push_back( std::numeric_limits
<double>::quiet_NaN() );
473 rDateCategories
.begin(), rDateCategories
.end(),
474 [](auto x
, auto y
) { return o3tl::strong_order(x
, y
) < 0; } );
477 return bAnyDataFound
&& bOnlyDatesFound
;
480 void ExplicitCategoriesProvider::init()
485 m_aComplexCats
.clear();//not one per index
486 m_aDateCategories
.clear();
488 if( m_xOriginalCategories
.is() )
490 if( !hasComplexCategories() )
494 if( ChartTypeHelper::isSupportingDateAxis( AxisHelper::getChartTypeByIndex( m_xCooSysModel
.get(), 0 ), 0 ) )
495 m_bIsDateAxis
= lcl_fillDateCategories( m_xOriginalCategories
->getValues(), m_aDateCategories
, m_bIsAutoDate
, mrModel
);
497 m_bIsDateAxis
= false;
502 m_bIsDateAxis
= false;
510 Sequence
< OUString
> const & ExplicitCategoriesProvider::getSimpleCategories()
512 if( !m_bIsExplicitCategoriesInited
)
515 m_aExplicitCategories
.realloc(0);
516 if( m_xOriginalCategories
.is() )
518 if( !hasComplexCategories() )
520 uno::Reference
< data::XDataSequence
> xDataSequence( m_xOriginalCategories
->getValues() );
521 if( xDataSequence
.is() )
522 ExplicitCategoriesProvider::convertCategoryAnysToText( m_aExplicitCategories
, xDataSequence
->getData(), mrModel
);
526 m_aExplicitCategories
= lcl_getExplicitSimpleCategories(
527 SplitCategoriesProvider_ForLabeledDataSequences( m_aSplitCategoriesList
, mrModel
), m_aComplexCats
);
530 if(!m_aExplicitCategories
.hasElements())
531 m_aExplicitCategories
= DiagramHelper::generateAutomaticCategoriesFromCooSys( m_xCooSysModel
.get() );
532 m_bIsExplicitCategoriesInited
= true;
534 return m_aExplicitCategories
;
537 const std::vector
<ComplexCategory
>* ExplicitCategoriesProvider::getCategoriesByLevel( sal_Int32 nLevel
)
540 sal_Int32 nMaxIndex
= m_aComplexCats
.size()-1;
541 if (nLevel
>= 0 && nLevel
<= nMaxIndex
)
542 return &m_aComplexCats
[nMaxIndex
-nLevel
];
546 OUString
ExplicitCategoriesProvider::getCategoryByIndex(
547 const rtl::Reference
< BaseCoordinateSystem
>& xCooSysModel
551 if( xCooSysModel
.is())
553 ExplicitCategoriesProvider
aExplicitCategoriesProvider( xCooSysModel
, rModel
);
554 Sequence
< OUString
> aCategories( aExplicitCategoriesProvider
.getSimpleCategories());
555 if( nIndex
< aCategories
.getLength())
556 return aCategories
[ nIndex
];
561 bool ExplicitCategoriesProvider::isDateAxis()
564 return m_bIsDateAxis
;
567 const std::vector
< double >& ExplicitCategoriesProvider::getDateCategories()
570 return m_aDateCategories
;
575 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */