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 <drawingml/chart/seriesconverter.hxx>
22 #include <com/sun/star/chart/DataLabelPlacement.hpp>
23 #include <com/sun/star/chart2/RelativePosition.hpp>
24 #include <com/sun/star/chart2/RelativeSize.hpp>
25 #include <com/sun/star/chart/ErrorBarStyle.hpp>
26 #include <com/sun/star/chart2/DataPointLabel.hpp>
27 #include <com/sun/star/drawing/Hatch.hpp>
28 #include <com/sun/star/chart2/XChartDocument.hpp>
29 #include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
30 #include <com/sun/star/chart2/DataPointCustomLabelField.hpp>
31 #include <com/sun/star/chart2/DataPointCustomLabelFieldType.hpp>
32 #include <com/sun/star/chart2/XDataSeries.hpp>
33 #include <com/sun/star/chart2/XRegressionCurve.hpp>
34 #include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
35 #include <com/sun/star/chart2/data/XDataSink.hpp>
36 #include <com/sun/star/chart2/data/LabeledDataSequence.hpp>
37 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
38 #include <com/sun/star/drawing/FillStyle.hpp>
40 #include <comphelper/sequence.hxx>
41 #include <osl/diagnose.h>
42 #include <drawingml/chart/datasourceconverter.hxx>
43 #include <drawingml/chart/seriesmodel.hxx>
44 #include <drawingml/chart/titleconverter.hxx>
45 #include <drawingml/chart/typegroupconverter.hxx>
46 #include <drawingml/chart/typegroupmodel.hxx>
47 #include <drawingml/fillproperties.hxx>
48 #include <oox/core/xmlfilterbase.hxx>
49 #include <oox/helper/modelobjecthelper.hxx>
50 #include <oox/token/properties.hxx>
51 #include <oox/token/tokens.hxx>
52 #include <drawingml/lineproperties.hxx>
53 #include <drawingml/textparagraph.hxx>
54 #include <drawingml/textrun.hxx>
55 #include <drawingml/textfield.hxx>
56 #include <drawingml/textbody.hxx>
57 #include <drawingml/hatchmap.hxx>
59 namespace oox::drawingml::chart
{
61 using namespace com::sun::star
;
62 using namespace ::com::sun::star::beans
;
63 using namespace ::com::sun::star::chart2
;
64 using namespace ::com::sun::star::chart2::data
;
65 using namespace ::com::sun::star::uno
;
69 Reference
< XLabeledDataSequence
> lclCreateLabeledDataSequence(
70 const ConverterRoot
& rParent
,
71 DataSourceModel
* pValues
, const OUString
& rRole
,
72 TextModel
* pTitle
= nullptr )
74 // create data sequence for values
75 Reference
< XDataSequence
> xValueSeq
;
78 DataSourceConverter
aSourceConv( rParent
, *pValues
);
79 xValueSeq
= aSourceConv
.createDataSequence( rRole
);
82 // create data sequence for title
83 Reference
< XDataSequence
> xTitleSeq
;
86 TextConverter
aTextConv( rParent
, *pTitle
);
87 xTitleSeq
= aTextConv
.createDataSequence( u
"label"_ustr
);
90 // create the labeled data sequence, if values or title are present
91 Reference
< XLabeledDataSequence
> xLabeledSeq
;
92 if( xValueSeq
.is() || xTitleSeq
.is() )
94 xLabeledSeq
= LabeledDataSequence::create(rParent
.getComponentContext());
95 if( xLabeledSeq
.is() )
97 xLabeledSeq
->setValues( xValueSeq
);
98 xLabeledSeq
->setLabel( xTitleSeq
);
104 void convertTextProperty(PropertySet
& rPropSet
, ObjectFormatter
& rFormatter
,
105 DataLabelModelBase::TextBodyRef xTextProps
)
107 rFormatter
.convertTextFormatting( rPropSet
, xTextProps
, OBJECTTYPE_DATALABEL
);
108 ObjectFormatter::convertTextRotation( rPropSet
, xTextProps
, false );
109 ObjectFormatter::convertTextWrap( rPropSet
, xTextProps
);
112 void lclConvertLabelFormatting( PropertySet
& rPropSet
, ObjectFormatter
& rFormatter
,
113 DataLabelModelBase
& rDataLabel
, const TypeGroupConverter
& rTypeGroup
,
114 bool bDataSeriesLabel
, bool bCustomLabelField
, bool bHasInternalData
, bool bMSO2007Doc
)
116 const TypeGroupInfo
& rTypeInfo
= rTypeGroup
.getTypeInfo();
118 /* Excel 2007 does not change the series setting for a single data point,
119 if none of some specific elements occur. But only one existing element
120 in a data point will reset most other of these elements from the series
121 (e.g.: series has <c:showVal>, data point has <c:showCatName>, this
122 will reset <c:showVal> for this point, unless <c:showVal> is repeated
123 in the data point). The elements <c:layout>, <c:numberFormat>,
124 <c:spPr>, <c:tx>, and <c:txPr> are not affected at all. */
125 bool bHasAnyElement
= true;
128 bHasAnyElement
= rDataLabel
.moaSeparator
.has_value() || rDataLabel
.monLabelPos
.has_value() ||
129 rDataLabel
.mobShowCatName
.has_value() || rDataLabel
.mobShowLegendKey
.has_value() ||
130 rDataLabel
.mobShowPercent
.has_value() || rDataLabel
.mobShowSerName
.has_value() ||
131 rDataLabel
.mobShowVal
.has_value();
134 bool bShowValue
= !rDataLabel
.mbDeleted
&& rDataLabel
.mobShowVal
.value_or( !bMSO2007Doc
);
135 bool bShowPercent
= !rDataLabel
.mbDeleted
&& rDataLabel
.mobShowPercent
.value_or( !bMSO2007Doc
) && (rTypeInfo
.meTypeCategory
== TYPECATEGORY_PIE
);
136 bool bShowCateg
= !rDataLabel
.mbDeleted
&& rDataLabel
.mobShowCatName
.value_or( !bMSO2007Doc
);
137 bool bShowSerName
= !rDataLabel
.mbDeleted
&& rDataLabel
.mobShowSerName
.value_or( !bMSO2007Doc
);
138 bool bShowSymbol
= !rDataLabel
.mbDeleted
&& rDataLabel
.mobShowLegendKey
.value_or( !bMSO2007Doc
);
140 // tdf#132174, tdf#136650: the inner data table has no own cell number format.
141 if( bHasInternalData
&& bShowValue
&& !bShowPercent
)
142 rDataLabel
.maNumberFormat
.mbSourceLinked
= false;
144 // type of attached label
145 if( bHasAnyElement
|| rDataLabel
.mbDeleted
)
147 DataPointLabel
aPointLabel( bShowValue
, bShowPercent
, bShowCateg
, bShowSymbol
, bCustomLabelField
, bShowSerName
);
148 rPropSet
.setProperty( PROP_Label
, aPointLabel
);
151 if( rDataLabel
.mbDeleted
)
154 // data label number format (percentage format wins over value format)
155 rFormatter
.convertNumberFormat( rPropSet
, rDataLabel
.maNumberFormat
, false, bShowPercent
);
157 // data label text formatting (frame formatting not supported by Chart2)
158 if( bDataSeriesLabel
|| (rDataLabel
.mxTextProp
.is() && !rDataLabel
.mxTextProp
->getParagraphs().empty()) )
159 convertTextProperty(rPropSet
, rFormatter
, rDataLabel
.mxTextProp
);
161 // data label separator (do not overwrite series separator, if no explicit point separator is present)
162 // Set the data label separator to "new line" if the value is shown as percentage with a category name,
163 // just like in MS-Office. In any other case the default separator will be a semicolon.
164 if( bShowPercent
&& !bShowValue
&& ( bDataSeriesLabel
|| rDataLabel
.moaSeparator
.has_value() ) )
165 rPropSet
.setProperty( PROP_LabelSeparator
, rDataLabel
.moaSeparator
.value_or( "\n" ) );
166 else if( bDataSeriesLabel
|| rDataLabel
.moaSeparator
.has_value() )
167 rPropSet
.setProperty( PROP_LabelSeparator
, rDataLabel
.moaSeparator
.value_or( "; " ) );
169 // data label placement (do not overwrite series placement, if no explicit point placement is present)
170 if( !(bDataSeriesLabel
|| rDataLabel
.monLabelPos
.has_value()) )
173 namespace csscd
= css::chart::DataLabelPlacement
;
174 sal_Int32 nPlacement
= -1;
175 switch( rDataLabel
.monLabelPos
.value_or( XML_TOKEN_INVALID
) )
177 case XML_outEnd
: nPlacement
= csscd::OUTSIDE
; break;
178 case XML_inEnd
: nPlacement
= csscd::INSIDE
; break;
179 case XML_ctr
: nPlacement
= csscd::CENTER
; break;
180 case XML_inBase
: nPlacement
= csscd::NEAR_ORIGIN
; break;
181 case XML_t
: nPlacement
= csscd::TOP
; break;
182 case XML_b
: nPlacement
= csscd::BOTTOM
; break;
183 case XML_l
: nPlacement
= csscd::LEFT
; break;
184 case XML_r
: nPlacement
= csscd::RIGHT
; break;
185 case XML_bestFit
: nPlacement
= csscd::AVOID_OVERLAP
; break;
188 if( !bDataSeriesLabel
&& nPlacement
== -1 )
191 if( nPlacement
== -1 )
192 nPlacement
= rTypeInfo
.mnDefLabelPos
;
194 rPropSet
.setProperty( PROP_LabelPlacement
, nPlacement
);
197 void importBorderProperties( PropertySet
& rPropSet
, Shape
& rShape
, const GraphicHelper
& rGraphicHelper
)
199 LineProperties
& rLP
= rShape
.getLineProperties();
200 // no fill has the same effect as no border so skip it
201 if (rLP
.maLineFill
.moFillType
.has_value() && rLP
.maLineFill
.moFillType
.value() == XML_noFill
)
204 if (rLP
.moLineWidth
.has_value())
206 sal_Int32 nWidth
= convertEmuToHmm(rLP
.moLineWidth
.value());
207 rPropSet
.setProperty(PROP_LabelBorderWidth
, uno::Any(nWidth
));
208 rPropSet
.setProperty(PROP_LabelBorderStyle
, uno::Any(drawing::LineStyle_SOLID
));
210 const Color
& aColor
= rLP
.maLineFill
.maFillColor
;
211 ::Color nColor
= aColor
.getColor(rGraphicHelper
);
212 rPropSet
.setProperty(PROP_LabelBorderColor
, uno::Any(nColor
));
215 void lcl_ImportLeaderLineProperties(PropertySet
& rPropSet
, Shape
& rShape
,
216 const GraphicHelper
& rGraphicHelper
)
218 LineProperties
& rLP
= rShape
.getLineProperties();
219 // no fill has the same effect as no line so skip it
220 if (rLP
.maLineFill
.moFillType
.has_value() && rLP
.maLineFill
.moFillType
.value() == XML_noFill
)
223 if (rLP
.moLineWidth
.has_value())
225 sal_Int32 nWidth
= convertEmuToHmm(rLP
.moLineWidth
.value());
226 rPropSet
.setProperty(PROP_LineWidth
, uno::Any(nWidth
));
229 if (rLP
.maLineFill
.moFillType
.has_value() && rLP
.maLineFill
.moFillType
.value() == XML_solidFill
)
231 const Color
& aColor
= rLP
.maLineFill
.maFillColor
;
232 ::Color nColor
= aColor
.getColor(rGraphicHelper
);
233 rPropSet
.setProperty(PROP_LineColor
, uno::Any(nColor
));
237 void importFillProperties( PropertySet
& rPropSet
, Shape
& rShape
, const GraphicHelper
& rGraphicHelper
, ModelObjectHelper
& rModelObjHelper
)
239 FillProperties
& rFP
= rShape
.getFillProperties();
241 if (rFP
.moFillType
.has_value() && rFP
.moFillType
.value() == XML_solidFill
)
243 rPropSet
.setProperty(PROP_LabelFillStyle
, drawing::FillStyle_SOLID
);
245 const Color
& aColor
= rFP
.maFillColor
;
246 ::Color nColor
= aColor
.getColor(rGraphicHelper
);
247 rPropSet
.setProperty(PROP_LabelFillColor
, uno::Any(nColor
));
249 else if(rFP
.moFillType
.has_value() && rFP
.moFillType
.value() == XML_pattFill
)
251 rPropSet
.setProperty(PROP_LabelFillStyle
, drawing::FillStyle_HATCH
);
252 rPropSet
.setProperty(PROP_LabelFillBackground
, true);
254 Color
aHatchColor( rFP
.maPatternProps
.maPattFgColor
);
255 drawing::Hatch aHatch
= createHatch(rFP
.maPatternProps
.moPattPreset
.value(), aHatchColor
.getColor(rGraphicHelper
, 0));
257 OUString sHatchName
= rModelObjHelper
.insertFillHatch(aHatch
);
258 rPropSet
.setProperty(PROP_LabelFillHatchName
, sHatchName
);
260 const Color
& aColor
= rFP
.maPatternProps
.maPattBgColor
;
261 ::Color nColor
= aColor
.getColor(rGraphicHelper
);
262 rPropSet
.setProperty(PROP_LabelFillColor
, uno::Any(nColor
));
267 DataPointCustomLabelFieldType
lcl_ConvertFieldNameToFieldEnum( std::u16string_view rField
)
269 if (rField
== u
"VALUE")
270 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_VALUE
;
271 else if (rField
== u
"SERIESNAME")
272 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_SERIESNAME
;
273 else if (rField
== u
"CATEGORYNAME")
274 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CATEGORYNAME
;
275 else if (rField
== u
"CELLREF")
276 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLREF
;
277 else if (rField
== u
"CELLRANGE")
278 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE
;
279 else if (rField
== u
"PERCENTAGE")
280 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_PERCENTAGE
;
282 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT
;
287 DataLabelConverter::DataLabelConverter( const ConverterRoot
& rParent
, DataLabelModel
& rModel
) :
288 ConverterBase
< DataLabelModel
>( rParent
, rModel
)
292 DataLabelConverter::~DataLabelConverter()
296 void DataLabelConverter::convertFromModel( const Reference
< XDataSeries
>& rxDataSeries
, const TypeGroupConverter
& rTypeGroup
)
298 if (!rxDataSeries
.is())
303 bool bMSO2007Doc
= getFilter().isMSO2007Document();
304 bool bHasInternalData
= getChartDocument()->hasInternalDataProvider();
305 bool bCustomLabelField
= mrModel
.mxText
&& mrModel
.mxText
->mxTextBody
&& !mrModel
.mxText
->mxTextBody
->getParagraphs().empty();
306 PropertySet
aPropSet( rxDataSeries
->getDataPointByIndex( mrModel
.mnIndex
) );
308 lclConvertLabelFormatting( aPropSet
, getFormatter(), mrModel
, rTypeGroup
, false, bCustomLabelField
, bHasInternalData
, bMSO2007Doc
);
310 const TypeGroupInfo
& rTypeInfo
= rTypeGroup
.getTypeInfo();
311 bool bIsPie
= rTypeInfo
.meTypeCategory
== TYPECATEGORY_PIE
;
313 if( mrModel
.mxLayout
&& !mrModel
.mxLayout
->mbAutoLayout
)
315 RelativePosition
aPos(mrModel
.mxLayout
->mfX
, mrModel
.mxLayout
->mfY
, css::drawing::Alignment_TOP_LEFT
);
316 aPropSet
.setProperty(PROP_CustomLabelPosition
, aPos
);
317 const RelativeSize
aSize(mrModel
.mxLayout
->mfW
, mrModel
.mxLayout
->mfH
);
318 aPropSet
.setProperty(PROP_CustomLabelSize
, aSize
);
319 sal_Int32 nPlacement
= -1;
320 if (bIsPie
&& aPropSet
.getProperty(nPlacement
, PROP_LabelPlacement
)
321 && nPlacement
== css::chart::DataLabelPlacement::AVOID_OVERLAP
)
322 aPropSet
.setProperty(PROP_LabelPlacement
, css::chart::DataLabelPlacement::CUSTOM
);
325 if (mrModel
.mxShapeProp
)
327 importBorderProperties(aPropSet
, *mrModel
.mxShapeProp
, getFilter().getGraphicHelper());
328 uno::Reference
<lang::XMultiServiceFactory
> xFactory(getChartDocument(), uno::UNO_QUERY
);
329 ModelObjectHelper
& rHelper
= getFilter().getModelObjectHelperForModel(xFactory
);
330 importFillProperties(aPropSet
, *mrModel
.mxShapeProp
, getFilter().getGraphicHelper(),
333 if( bCustomLabelField
)
335 css::uno::Reference
< XComponentContext
> xContext
= getComponentContext();
337 auto& rParagraphs
= mrModel
.mxText
->mxTextBody
->getParagraphs();
339 int nSequenceSize
= 0;
340 for( auto& pParagraph
: rParagraphs
)
341 nSequenceSize
+= pParagraph
->getRuns().size();
343 int nParagraphs
= rParagraphs
.size();
344 if( nParagraphs
> 1 )
345 nSequenceSize
+= nParagraphs
- 1;
347 std::optional
< OUString
> oaLabelText
;
348 std::optional
< OUString
> oaCellRange
;
349 if (mrModel
.mobShowDataLabelsRange
.value_or(false))
351 const DataSourceModel
* pLabelSource
= mrModel
.mrParent
.mpLabelsSource
;
352 if (pLabelSource
&& pLabelSource
->mxDataSeq
.is())
354 oaCellRange
= pLabelSource
->mxDataSeq
->maFormula
;
355 const auto& rLabelMap
= pLabelSource
->mxDataSeq
->maData
;
356 const auto aKV
= rLabelMap
.find(mrModel
.mnIndex
);
357 if (aKV
!= rLabelMap
.end())
359 oaLabelText
.emplace();
360 aKV
->second
>>= *oaLabelText
;
365 uno::Sequence
< css::uno::Reference
< XDataPointCustomLabelField
> > aSequence( nSequenceSize
);
366 auto aSequenceRange
= asNonConstRange(aSequence
);
370 for( auto& pParagraph
: rParagraphs
)
372 for( auto& pRun
: pParagraph
->getRuns() )
374 css::uno::Reference
< XDataPointCustomLabelField
> xCustomLabel
= DataPointCustomLabelField::create( xContext
);
377 oox::PropertySet
aPropertySet( xCustomLabel
);
378 convertTextProperty( aPropertySet
, getFormatter(), mrModel
.mxText
->mxTextBody
);
379 pRun
->getTextCharacterProperties().pushToPropSet( aPropertySet
, getFilter() );
381 if (TextField
* pField
= dynamic_cast<TextField
*>(pRun
.get()))
383 DataPointCustomLabelFieldType eType
= lcl_ConvertFieldNameToFieldEnum( pField
->getType() );
385 if (eType
== DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE
&& oaCellRange
.has_value())
387 xCustomLabel
->setCellRange( oaCellRange
.value() );
388 xCustomLabel
->setString( oaLabelText
.value_or("") );
389 xCustomLabel
->setDataLabelsRange( true );
392 xCustomLabel
->setString( pField
->getText() );
394 xCustomLabel
->setFieldType( eType
);
395 xCustomLabel
->setGuid( pField
->getUuid() );
399 xCustomLabel
->setString( pRun
->getText() );
400 xCustomLabel
->setFieldType( DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT
);
402 aSequenceRange
[ nPos
++ ] = std::move(xCustomLabel
);
405 if( nParagraphs
> 1 && nPos
< nSequenceSize
)
407 css::uno::Reference
< XDataPointCustomLabelField
> xCustomLabel
= DataPointCustomLabelField::create( xContext
);
408 xCustomLabel
->setFieldType( DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_NEWLINE
);
409 xCustomLabel
->setString(u
"\n"_ustr
);
410 aSequenceRange
[ nPos
++ ] = std::move(xCustomLabel
);
414 aPropSet
.setProperty( PROP_CustomLabelFields
, Any( aSequence
) );
415 convertTextProperty(aPropSet
, getFormatter(), mrModel
.mxText
->mxTextBody
);
423 DataLabelsConverter::DataLabelsConverter( const ConverterRoot
& rParent
, DataLabelsModel
& rModel
) :
424 ConverterBase
< DataLabelsModel
>( rParent
, rModel
)
428 DataLabelsConverter::~DataLabelsConverter()
434 /// Inherit <c:dLbl> text props (if not set) from <c:dLbls> text props (if set).
435 void InheritFromDataLabelsTextProps(const DataLabelsModel
& rLabels
, const DataLabelModel
& rLabel
)
437 // See if <c:dLbls> contains text properties to inherit.
438 if (!rLabels
.mxTextProp
.is() || rLabels
.mxTextProp
->getParagraphs().empty())
443 const std::shared_ptr
<TextParagraph
>& rLabelsParagraph
= rLabels
.mxTextProp
->getParagraphs()[0];
445 // See if <c:dLbl> lacks text properties.
446 if (rLabel
.mxTextProp
.is())
451 if (!rLabel
.mxText
|| !rLabel
.mxText
->mxTextBody
452 || rLabel
.mxText
->mxTextBody
->getParagraphs().empty())
457 const std::shared_ptr
<TextParagraph
>& rLabelParagraph
458 = rLabel
.mxText
->mxTextBody
->getParagraphs()[0];
460 // Inherit rLabel.mxText's char props from rLabels.mxTextProp's char props.
461 TextCharacterProperties aCharProps
;
462 aCharProps
.assignUsed(rLabelsParagraph
->getProperties().getTextCharacterProperties());
463 aCharProps
.assignUsed(rLabelParagraph
->getProperties().getTextCharacterProperties());
464 rLabelParagraph
->getProperties().getTextCharacterProperties().assignUsed(aCharProps
);
468 void DataLabelsConverter::convertFromModel( const Reference
< XDataSeries
>& rxDataSeries
, const TypeGroupConverter
& rTypeGroup
)
470 PropertySet
aPropSet( rxDataSeries
);
471 if( !mrModel
.mbDeleted
)
473 bool bMSO2007Doc
= getFilter().isMSO2007Document();
474 bool bHasInternalData
= getChartDocument()->hasInternalDataProvider();
476 lclConvertLabelFormatting( aPropSet
, getFormatter(), mrModel
, rTypeGroup
, true, false, bHasInternalData
, bMSO2007Doc
);
478 if (mrModel
.mxShapeProp
)
480 // Import baseline border properties for these data labels.
481 importBorderProperties(aPropSet
, *mrModel
.mxShapeProp
, getFilter().getGraphicHelper());
482 uno::Reference
<lang::XMultiServiceFactory
> xFactory(getChartDocument(), uno::UNO_QUERY
);
483 ModelObjectHelper
& rHelper
= getFilter().getModelObjectHelperForModel(xFactory
);
484 importFillProperties(aPropSet
, *mrModel
.mxShapeProp
, getFilter().getGraphicHelper(),
488 // import leaderline of data labels
489 if( !mrModel
.mbShowLeaderLines
)
490 aPropSet
.setProperty( PROP_ShowCustomLeaderLines
, false );
492 if (mrModel
.mxLeaderLines
)
494 // Import leaderline properties (SolidFill color, and width)
495 lcl_ImportLeaderLineProperties(aPropSet
, *mrModel
.mxLeaderLines
,
496 getFilter().getGraphicHelper());
499 // data point label settings
500 for (auto const& pointLabel
: mrModel
.maPointLabels
)
502 if (pointLabel
->maNumberFormat
.maFormatCode
.isEmpty())
503 pointLabel
->maNumberFormat
= mrModel
.maNumberFormat
;
504 InheritFromDataLabelsTextProps(mrModel
, *pointLabel
);
506 DataLabelConverter
aLabelConv(*this, *pointLabel
);
507 aLabelConv
.convertFromModel( rxDataSeries
, rTypeGroup
);
511 ErrorBarConverter::ErrorBarConverter( const ConverterRoot
& rParent
, ErrorBarModel
& rModel
) :
512 ConverterBase
< ErrorBarModel
>( rParent
, rModel
)
516 ErrorBarConverter::~ErrorBarConverter()
520 void ErrorBarConverter::convertFromModel( const Reference
< XDataSeries
>& rxDataSeries
)
522 bool bShowPos
= (mrModel
.mnTypeId
== XML_plus
) || (mrModel
.mnTypeId
== XML_both
);
523 bool bShowNeg
= (mrModel
.mnTypeId
== XML_minus
) || (mrModel
.mnTypeId
== XML_both
);
524 if( !(bShowPos
|| bShowNeg
) )
529 Reference
< XPropertySet
> xErrorBar( createInstance( u
"com.sun.star.chart2.ErrorBar"_ustr
), UNO_QUERY_THROW
);
530 PropertySet
aBarProp( xErrorBar
);
533 aBarProp
.setProperty( PROP_ShowPositiveError
, bShowPos
);
534 aBarProp
.setProperty( PROP_ShowNegativeError
, bShowNeg
);
536 // type of displayed error
537 namespace cssc
= css::chart
;
538 switch( mrModel
.mnValueType
)
542 // #i87806# manual error bars
543 aBarProp
.setProperty( PROP_ErrorBarStyle
, cssc::ErrorBarStyle::FROM_DATA
);
544 // attach data sequences to error bar
545 Reference
< XDataSink
> xDataSink( xErrorBar
, UNO_QUERY
);
548 // create vector of all value sequences
549 ::std::vector
< Reference
< XLabeledDataSequence
> > aLabeledSeqVec
;
550 // add positive values
553 Reference
< XLabeledDataSequence
> xValueSeq
= createLabeledDataSequence( ErrorBarModel::PLUS
);
555 aLabeledSeqVec
.push_back( xValueSeq
);
557 // add negative values
560 Reference
< XLabeledDataSequence
> xValueSeq
= createLabeledDataSequence( ErrorBarModel::MINUS
);
562 aLabeledSeqVec
.push_back( xValueSeq
);
564 // attach labeled data sequences to series
565 if( aLabeledSeqVec
.empty() )
568 xDataSink
->setData( comphelper::containerToSequence( aLabeledSeqVec
) );
573 aBarProp
.setProperty( PROP_ErrorBarStyle
, cssc::ErrorBarStyle::ABSOLUTE
);
574 aBarProp
.setProperty( PROP_PositiveError
, mrModel
.mfValue
);
575 aBarProp
.setProperty( PROP_NegativeError
, mrModel
.mfValue
);
578 aBarProp
.setProperty( PROP_ErrorBarStyle
, cssc::ErrorBarStyle::RELATIVE
);
579 aBarProp
.setProperty( PROP_PositiveError
, mrModel
.mfValue
);
580 aBarProp
.setProperty( PROP_NegativeError
, mrModel
.mfValue
);
583 aBarProp
.setProperty( PROP_ErrorBarStyle
, cssc::ErrorBarStyle::STANDARD_DEVIATION
);
584 aBarProp
.setProperty( PROP_Weight
, mrModel
.mfValue
);
587 aBarProp
.setProperty( PROP_ErrorBarStyle
, cssc::ErrorBarStyle::STANDARD_ERROR
);
590 OSL_FAIL( "ErrorBarConverter::convertFromModel - unknown error bar type" );
594 // error bar formatting
595 getFormatter().convertFrameFormatting( aBarProp
, mrModel
.mxShapeProp
, OBJECTTYPE_ERRORBAR
);
599 PropertySet
aSeriesProp( rxDataSeries
);
600 switch( mrModel
.mnDirection
)
602 case XML_x
: aSeriesProp
.setProperty( PROP_ErrorBarX
, xErrorBar
); break;
603 case XML_y
: aSeriesProp
.setProperty( PROP_ErrorBarY
, xErrorBar
); break;
604 default: OSL_FAIL( "ErrorBarConverter::convertFromModel - invalid error bar direction" );
610 OSL_FAIL( "ErrorBarConverter::convertFromModel - error while creating error bars" );
614 Reference
< XLabeledDataSequence
> ErrorBarConverter::createLabeledDataSequence( ErrorBarModel::SourceType eSourceType
)
617 switch( eSourceType
)
619 case ErrorBarModel::PLUS
:
620 switch( mrModel
.mnDirection
)
622 case XML_x
: aRole
= "error-bars-x-positive"; break;
623 case XML_y
: aRole
= "error-bars-y-positive"; break;
626 case ErrorBarModel::MINUS
:
627 switch( mrModel
.mnDirection
)
629 case XML_x
: aRole
= "error-bars-x-negative"; break;
630 case XML_y
: aRole
= "error-bars-y-negative"; break;
634 OSL_ENSURE( !aRole
.isEmpty(), "ErrorBarConverter::createLabeledDataSequence - invalid error bar direction" );
635 return lclCreateLabeledDataSequence( *this, mrModel
.maSources
.get( eSourceType
).get(), aRole
);
638 TrendlineLabelConverter::TrendlineLabelConverter( const ConverterRoot
& rParent
, TrendlineLabelModel
& rModel
) :
639 ConverterBase
< TrendlineLabelModel
>( rParent
, rModel
)
643 TrendlineLabelConverter::~TrendlineLabelConverter()
647 void TrendlineLabelConverter::convertFromModel( PropertySet
& rPropSet
)
650 getFormatter().convertFormatting( rPropSet
, mrModel
.mxShapeProp
, mrModel
.mxTextProp
, OBJECTTYPE_TRENDLINELABEL
);
653 TrendlineConverter::TrendlineConverter( const ConverterRoot
& rParent
, TrendlineModel
& rModel
) :
654 ConverterBase
< TrendlineModel
>( rParent
, rModel
)
658 TrendlineConverter::~TrendlineConverter()
662 void TrendlineConverter::convertFromModel( const Reference
< XDataSeries
>& rxDataSeries
)
667 OUString aServiceName
;
668 switch( mrModel
.mnTypeId
)
671 aServiceName
= "com.sun.star.chart2.ExponentialRegressionCurve";
674 aServiceName
= "com.sun.star.chart2.LinearRegressionCurve";
677 aServiceName
= "com.sun.star.chart2.LogarithmicRegressionCurve";
680 aServiceName
= "com.sun.star.chart2.MovingAverageRegressionCurve";
683 aServiceName
= "com.sun.star.chart2.PolynomialRegressionCurve";
686 aServiceName
= "com.sun.star.chart2.PotentialRegressionCurve";
689 OSL_FAIL( "TrendlineConverter::convertFromModel - unknown trendline type" );
691 if( !aServiceName
.isEmpty() )
693 Reference
< XRegressionCurve
> xRegCurve( createInstance( aServiceName
), UNO_QUERY_THROW
);
694 PropertySet
aPropSet( xRegCurve
);
697 aPropSet
.setProperty( PROP_CurveName
, mrModel
.maName
);
698 aPropSet
.setProperty( PROP_PolynomialDegree
, mrModel
.mnOrder
);
699 aPropSet
.setProperty( PROP_MovingAveragePeriod
, mrModel
.mnPeriod
);
702 bool hasIntercept
= mrModel
.mfIntercept
.has_value();
703 aPropSet
.setProperty( PROP_ForceIntercept
, hasIntercept
);
705 aPropSet
.setProperty( PROP_InterceptValue
, mrModel
.mfIntercept
.value());
708 if (mrModel
.mfForward
.has_value())
709 aPropSet
.setProperty( PROP_ExtrapolateForward
, mrModel
.mfForward
.value() );
710 if (mrModel
.mfBackward
.has_value())
711 aPropSet
.setProperty( PROP_ExtrapolateBackward
, mrModel
.mfBackward
.value() );
713 // trendline formatting
714 getFormatter().convertFrameFormatting( aPropSet
, mrModel
.mxShapeProp
, OBJECTTYPE_TRENDLINE
);
716 // #i83100# show equation and correlation coefficient
717 PropertySet
aLabelProp( xRegCurve
->getEquationProperties() );
718 aLabelProp
.setProperty( PROP_ShowEquation
, mrModel
.mbDispEquation
);
719 aLabelProp
.setProperty( PROP_ShowCorrelationCoefficient
, mrModel
.mbDispRSquared
);
721 // #i83100# formatting of the equation text box
722 if( mrModel
.mbDispEquation
|| mrModel
.mbDispRSquared
)
724 TrendlineLabelConverter
aLabelConv( *this, mrModel
.mxLabel
.getOrCreate() );
725 aLabelConv
.convertFromModel( aLabelProp
);
728 // unsupported: #i5085# manual trendline size
729 // unsupported: #i34093# manual crossing point
731 Reference
< XRegressionCurveContainer
> xRegCurveCont( rxDataSeries
, UNO_QUERY_THROW
);
732 xRegCurveCont
->addRegressionCurve( xRegCurve
);
737 OSL_FAIL( "TrendlineConverter::convertFromModel - error while creating trendline" );
741 DataPointConverter::DataPointConverter( const ConverterRoot
& rParent
, DataPointModel
& rModel
) :
742 ConverterBase
< DataPointModel
>( rParent
, rModel
)
746 DataPointConverter::~DataPointConverter()
750 void DataPointConverter::convertFromModel( const Reference
< XDataSeries
>& rxDataSeries
,
751 const TypeGroupConverter
& rTypeGroup
, const SeriesModel
& rSeries
)
753 bool bMSO2007Doc
= getFilter().isMSO2007Document();
756 PropertySet
aPropSet( rxDataSeries
->getDataPointByIndex( mrModel
.mnIndex
) );
759 if( ( mrModel
.monMarkerSymbol
.has_value() && mrModel
.monMarkerSymbol
.value() != rSeries
.mnMarkerSymbol
) ||
760 ( mrModel
.monMarkerSize
.has_value() && mrModel
.monMarkerSize
.value() != rSeries
.mnMarkerSize
) )
761 rTypeGroup
.convertMarker( aPropSet
, mrModel
.monMarkerSymbol
.value_or( rSeries
.mnMarkerSymbol
),
762 mrModel
.monMarkerSize
.value_or( rSeries
.mnMarkerSize
), mrModel
.mxMarkerProp
);
764 // data point pie explosion
765 if( mrModel
.monExplosion
.has_value() && mrModel
.monExplosion
.value() != rSeries
.mnExplosion
)
766 rTypeGroup
.convertPieExplosion( aPropSet
, mrModel
.monExplosion
.value() );
768 // data point invert negative
769 if( mrModel
.mbInvertNeg
!= rSeries
.mbInvertNeg
) {
770 aPropSet
.setProperty( PROP_InvertNegative
, mrModel
.mbInvertNeg
);
774 if( mrModel
.mxShapeProp
.is() )
776 if( rTypeGroup
.getTypeInfo().mbPictureOptions
)
777 getFormatter().convertFrameFormatting( aPropSet
, mrModel
.mxShapeProp
, mrModel
.mxPicOptions
.getOrCreate(bMSO2007Doc
), rTypeGroup
.getSeriesObjectType(), rSeries
.mnIndex
);
779 getFormatter().convertFrameFormatting( aPropSet
, mrModel
.mxShapeProp
, rTypeGroup
.getSeriesObjectType(), rSeries
.mnIndex
);
781 else if (rSeries
.mxShapeProp
.is())
783 getFormatter().convertFrameFormatting( aPropSet
, rSeries
.mxShapeProp
, rTypeGroup
.getSeriesObjectType(), rSeries
.mnIndex
);
791 SeriesConverter::SeriesConverter( const ConverterRoot
& rParent
, SeriesModel
& rModel
) :
792 ConverterBase
< SeriesModel
>( rParent
, rModel
)
796 SeriesConverter::~SeriesConverter()
800 Reference
< XLabeledDataSequence
> SeriesConverter::createCategorySequence( const OUString
& rRole
)
802 return createLabeledDataSequence(SeriesModel::CATEGORIES
, rRole
, false);
805 Reference
< XLabeledDataSequence
> SeriesConverter::createValueSequence( const OUString
& rRole
)
807 return createLabeledDataSequence( SeriesModel::VALUES
, rRole
, true );
810 Reference
< XDataSeries
> SeriesConverter::createDataSeries( const TypeGroupConverter
& rTypeGroup
, bool bVaryColorsByPoint
)
812 const TypeGroupInfo
& rTypeInfo
= rTypeGroup
.getTypeInfo();
814 // create the data series object
815 Reference
< XDataSeries
> xDataSeries( createInstance( u
"com.sun.star.chart2.DataSeries"_ustr
), UNO_QUERY
);
816 PropertySet
aSeriesProp( xDataSeries
);
818 // attach data and title sequences to series
819 sal_Int32 nDataPointCount
= 0;
820 Reference
< XDataSink
> xDataSink( xDataSeries
, UNO_QUERY
);
823 // create vector of all value sequences
824 ::std::vector
< Reference
< XLabeledDataSequence
> > aLabeledSeqVec
;
826 Reference
< XLabeledDataSequence
> xYValueSeq
= createValueSequence( u
"values-y"_ustr
);
827 if( xYValueSeq
.is() )
829 aLabeledSeqVec
.push_back( xYValueSeq
);
830 Reference
< XDataSequence
> xValues
= xYValueSeq
->getValues();
832 nDataPointCount
= xValues
->getData().getLength();
834 if (!nDataPointCount
)
835 // No values present. Don't create a data series.
836 return Reference
<XDataSeries
>();
838 // add X values of scatter and bubble charts
839 if( !rTypeInfo
.mbCategoryAxis
)
841 Reference
< XLabeledDataSequence
> xXValueSeq
= createCategorySequence( u
"values-x"_ustr
);
842 if( xXValueSeq
.is() )
843 aLabeledSeqVec
.push_back( xXValueSeq
);
844 // add size values of bubble charts
845 if( rTypeInfo
.meTypeId
== TYPEID_BUBBLE
)
847 Reference
< XLabeledDataSequence
> xSizeValueSeq
= createLabeledDataSequence( SeriesModel::POINTS
, u
"values-size"_ustr
, true );
848 if( xSizeValueSeq
.is() )
849 aLabeledSeqVec
.push_back( xSizeValueSeq
);
852 // attach labeled data sequences to series
853 if( !aLabeledSeqVec
.empty() )
854 xDataSink
->setData( comphelper::containerToSequence( aLabeledSeqVec
) );
858 for (auto const& errorBar
: mrModel
.maErrorBars
)
860 ErrorBarConverter
aErrorBarConv(*this, *errorBar
);
861 aErrorBarConv
.convertFromModel( xDataSeries
);
865 for (auto const& trendLine
: mrModel
.maTrendlines
)
867 TrendlineConverter
aTrendlineConv(*this, *trendLine
);
868 aTrendlineConv
.convertFromModel( xDataSeries
);
871 // data point markers
872 rTypeGroup
.convertMarker( aSeriesProp
, mrModel
.mnMarkerSymbol
, mrModel
.mnMarkerSize
, mrModel
.mxMarkerProp
);
873 #if OOX_CHART_SMOOTHED_PER_SERIES
874 // #i66858# smoothed series lines
875 rTypeGroup
.convertLineSmooth( aSeriesProp
, mrModel
.mbSmooth
);
877 // 3D bar style (not possible to set at chart type -> set at all series)
878 rTypeGroup
.convertBarGeometry( aSeriesProp
, mrModel
.monShape
.value_or( rTypeGroup
.getModel().mnShape
) );
879 // pie explosion (restricted to [0%,100%] in Chart2)
880 rTypeGroup
.convertPieExplosion( aSeriesProp
, mrModel
.mnExplosion
);
881 // invert if negative
882 aSeriesProp
.setProperty(PROP_InvertNegative
, mrModel
.mbInvertNeg
);
885 ObjectFormatter
& rFormatter
= getFormatter();
886 ObjectType eObjType
= rTypeGroup
.getSeriesObjectType();
887 bool bMSO2007Doc
= getFilter().isMSO2007Document();
888 if( rTypeInfo
.mbPictureOptions
)
889 rFormatter
.convertFrameFormatting( aSeriesProp
, mrModel
.mxShapeProp
, mrModel
.mxPicOptions
.getOrCreate(bMSO2007Doc
), eObjType
, mrModel
.mnIndex
);
891 rFormatter
.convertFrameFormatting( aSeriesProp
, mrModel
.mxShapeProp
, eObjType
, mrModel
.mnIndex
);
893 // set the (unused) property default value used by the Chart2 templates (true for pie/doughnut charts)
894 bool bIsPie
= rTypeInfo
.meTypeCategory
== TYPECATEGORY_PIE
;
895 aSeriesProp
.setProperty( PROP_VaryColorsByPoint
, bVaryColorsByPoint
);
897 // own area formatting for every data point (TODO: varying line color not supported)
898 // #i91271# always set area formatting for every point in pie/doughnut charts to override their automatic point formatting
899 if( bIsPie
|| (bVaryColorsByPoint
&& rTypeGroup
.isSeriesFrameFormat() && ObjectFormatter::isAutomaticFill( mrModel
.mxShapeProp
)) )
901 /* Set the series point number as color cycle size at the object
902 formatter to get correct start-shade/end-tint. TODO: in doughnut
903 charts, the sizes of the series may vary, need to use the maximum
904 point count of all series. */
905 sal_Int32 nOldMax
= rFormatter
.getMaxSeriesIndex();
906 if( bVaryColorsByPoint
)
907 rFormatter
.setMaxSeriesIndex( nDataPointCount
- 1 );
908 for( sal_Int32 nIndex
= 0; nIndex
< nDataPointCount
; ++nIndex
)
912 PropertySet
aPointProp( xDataSeries
->getDataPointByIndex( nIndex
) );
913 rFormatter
.convertAutomaticFill( aPointProp
, eObjType
, bVaryColorsByPoint
? nIndex
: mrModel
.mnIndex
);
919 rFormatter
.setMaxSeriesIndex( nOldMax
);
922 // data point settings
923 for (auto const& point
: mrModel
.maPoints
)
925 DataPointConverter
aPointConv(*this, *point
);
926 aPointConv
.convertFromModel( xDataSeries
, rTypeGroup
, mrModel
);
929 /* Series data label settings. If and only if the series does not contain
930 a c:dLbls element, then the c:dLbls element of the parent chart type is
931 used (data label settings of the parent chart type are *not* merged
932 into own existing data label settings). */
933 ModelRef
< DataLabelsModel
> xLabels
= mrModel
.mxLabels
.is() ? mrModel
.mxLabels
: rTypeGroup
.getModel().mxLabels
;
936 if( xLabels
->maNumberFormat
.maFormatCode
.isEmpty() )
938 // Use number format code from Value series
939 DataSourceModel
* pValues
= mrModel
.maSources
.get( SeriesModel::VALUES
).get();
941 xLabels
->maNumberFormat
.maFormatCode
= pValues
->mxDataSeq
->maFormatCode
;
943 DataLabelsConverter
aLabelsConv( *this, *xLabels
);
944 aLabelsConv
.convertFromModel( xDataSeries
, rTypeGroup
);
950 // private --------------------------------------------------------------------
952 Reference
< XLabeledDataSequence
> SeriesConverter::createLabeledDataSequence(
953 SeriesModel::SourceType eSourceType
, const OUString
& rRole
, bool bUseTextLabel
)
955 DataSourceModel
* pValues
= mrModel
.maSources
.get( eSourceType
).get();
956 TextModel
* pTitle
= bUseTextLabel
? mrModel
.mxText
.get() : nullptr;
957 return lclCreateLabeledDataSequence( *this, pValues
, rRole
, pTitle
);
962 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */