1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <drawingml/chart/seriesconverter.hxx>
21
22 #include <com/sun/star/chart/DataLabelPlacement.hpp>
23 #include <com/sun/star/chart2/RelativePosition.hpp>
24 #include <com/sun/star/chart/ErrorBarStyle.hpp>
25 #include <com/sun/star/chart2/DataPointLabel.hpp>
26 #include <com/sun/star/drawing/Hatch.hpp>
27 #include <com/sun/star/chart2/XChartDocument.hpp>
28 #include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
29 #include <com/sun/star/chart2/DataPointCustomLabelField.hpp>
30 #include <com/sun/star/chart2/DataPointCustomLabelFieldType.hpp>
31 #include <com/sun/star/chart2/XDataSeries.hpp>
32 #include <com/sun/star/chart2/XRegressionCurve.hpp>
33 #include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
34 #include <com/sun/star/chart2/data/XDataSink.hpp>
35 #include <com/sun/star/chart2/data/LabeledDataSequence.hpp>
36 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
37 #include <com/sun/star/drawing/FillStyle.hpp>
38 #include <osl/diagnose.h>
39 #include <drawingml/chart/datasourceconverter.hxx>
40 #include <drawingml/chart/seriesmodel.hxx>
41 #include <drawingml/chart/titleconverter.hxx>
42 #include <drawingml/chart/typegroupconverter.hxx>
43 #include <drawingml/chart/typegroupmodel.hxx>
44 #include <drawingml/fillproperties.hxx>
45 #include <oox/core/xmlfilterbase.hxx>
46 #include <oox/helper/containerhelper.hxx>
47 #include <oox/helper/modelobjecthelper.hxx>
48 #include <oox/token/properties.hxx>
49 #include <oox/token/tokens.hxx>
50 #include <drawingml/lineproperties.hxx>
51 #include <drawingml/textparagraph.hxx>
52 #include <drawingml/textrun.hxx>
53 #include <drawingml/textfield.hxx>
54 #include <drawingml/textbody.hxx>
55 #include <drawingml/hatchmap.hxx>
56
57 namespace oox::drawingml::chart {
58
59 using namespace com::sun::star;
60 using namespace ::com::sun::star::beans;
61 using namespace ::com::sun::star::chart2;
62 using namespace ::com::sun::star::chart2::data;
63 using namespace ::com::sun::star::uno;
64
65 namespace {
66
lclCreateLabeledDataSequence(const ConverterRoot & rParent,DataSourceModel * pValues,const OUString & rRole,TextModel * pTitle=nullptr)67 Reference< XLabeledDataSequence > lclCreateLabeledDataSequence(
68 const ConverterRoot& rParent,
69 DataSourceModel* pValues, const OUString& rRole,
70 TextModel* pTitle = nullptr )
71 {
72 // create data sequence for values
73 Reference< XDataSequence > xValueSeq;
74 if( pValues )
75 {
76 DataSourceConverter aSourceConv( rParent, *pValues );
77 xValueSeq = aSourceConv.createDataSequence( rRole );
78 }
79
80 // create data sequence for title
81 Reference< XDataSequence > xTitleSeq;
82 if( pTitle )
83 {
84 TextConverter aTextConv( rParent, *pTitle );
85 xTitleSeq = aTextConv.createDataSequence( "label" );
86 }
87
88 // create the labeled data sequence, if values or title are present
89 Reference< XLabeledDataSequence > xLabeledSeq;
90 if( xValueSeq.is() || xTitleSeq.is() )
91 {
92 xLabeledSeq = LabeledDataSequence::create(rParent.getComponentContext());
93 if( xLabeledSeq.is() )
94 {
95 xLabeledSeq->setValues( xValueSeq );
96 xLabeledSeq->setLabel( xTitleSeq );
97 }
98 }
99 return xLabeledSeq;
100 }
101
convertTextProperty(PropertySet & rPropSet,ObjectFormatter & rFormatter,DataLabelModelBase::TextBodyRef xTextProps)102 void convertTextProperty(PropertySet& rPropSet, ObjectFormatter& rFormatter,
103 DataLabelModelBase::TextBodyRef xTextProps)
104 {
105 rFormatter.convertTextFormatting( rPropSet, xTextProps, OBJECTTYPE_DATALABEL );
106 ObjectFormatter::convertTextRotation( rPropSet, xTextProps, false );
107 ObjectFormatter::convertTextWrap( rPropSet, xTextProps );
108 }
109
lclConvertLabelFormatting(PropertySet & rPropSet,ObjectFormatter & rFormatter,DataLabelModelBase & rDataLabel,const TypeGroupConverter & rTypeGroup,bool bDataSeriesLabel,bool bCustomLabelField,bool bHasInternalData,bool bMSO2007Doc)110 void lclConvertLabelFormatting( PropertySet& rPropSet, ObjectFormatter& rFormatter,
111 DataLabelModelBase& rDataLabel, const TypeGroupConverter& rTypeGroup,
112 bool bDataSeriesLabel, bool bCustomLabelField, bool bHasInternalData, bool bMSO2007Doc )
113 {
114 const TypeGroupInfo& rTypeInfo = rTypeGroup.getTypeInfo();
115
116 /* Excel 2007 does not change the series setting for a single data point,
117 if none of some specific elements occur. But only one existing element
118 in a data point will reset most other of these elements from the series
119 (e.g.: series has <c:showVal>, data point has <c:showCatName>, this
120 will reset <c:showVal> for this point, unless <c:showVal> is repeated
121 in the data point). The elements <c:layout>, <c:numberFormat>,
122 <c:spPr>, <c:tx>, and <c:txPr> are not affected at all. */
123 bool bHasAnyElement = true;
124 if (bMSO2007Doc)
125 {
126 bHasAnyElement = rDataLabel.moaSeparator.has() || rDataLabel.monLabelPos.has() ||
127 rDataLabel.mobShowCatName.has() || rDataLabel.mobShowLegendKey.has() ||
128 rDataLabel.mobShowPercent.has() || rDataLabel.mobShowSerName.has() ||
129 rDataLabel.mobShowVal.has();
130 }
131
132 bool bShowValue = !rDataLabel.mbDeleted && rDataLabel.mobShowVal.get( !bMSO2007Doc );
133 bool bShowPercent = !rDataLabel.mbDeleted && rDataLabel.mobShowPercent.get( !bMSO2007Doc ) && (rTypeInfo.meTypeCategory == TYPECATEGORY_PIE);
134 bool bShowCateg = !rDataLabel.mbDeleted && rDataLabel.mobShowCatName.get( !bMSO2007Doc );
135 bool bShowSerName = !rDataLabel.mbDeleted && rDataLabel.mobShowSerName.get( !bMSO2007Doc );
136 bool bShowSymbol = !rDataLabel.mbDeleted && rDataLabel.mobShowLegendKey.get( !bMSO2007Doc );
137
138 // tdf#132174, tdf#136650: the inner data table has no own cell number format.
139 if( bHasInternalData && bShowValue && !bShowPercent )
140 rDataLabel.maNumberFormat.mbSourceLinked = false;
141
142 // type of attached label
143 if( bHasAnyElement || rDataLabel.mbDeleted )
144 {
145 DataPointLabel aPointLabel( bShowValue, bShowPercent, bShowCateg, bShowSymbol, bCustomLabelField, bShowSerName );
146 rPropSet.setProperty( PROP_Label, aPointLabel );
147 }
148
149 if( rDataLabel.mbDeleted )
150 return;
151
152 // data label number format (percentage format wins over value format)
153 rFormatter.convertNumberFormat( rPropSet, rDataLabel.maNumberFormat, false, bShowPercent );
154
155 // data label text formatting (frame formatting not supported by Chart2)
156 if( bDataSeriesLabel || (rDataLabel.mxTextProp.is() && !rDataLabel.mxTextProp->getParagraphs().empty()) )
157 convertTextProperty(rPropSet, rFormatter, rDataLabel.mxTextProp);
158
159 // data label separator (do not overwrite series separator, if no explicit point separator is present)
160 // Set the data label separator to "new line" if the value is shown as percentage with a category name,
161 // just like in MS-Office. In any other case the default separator will be a semicolon.
162 if( bShowPercent && !bShowValue && ( bDataSeriesLabel || rDataLabel.moaSeparator.has() ) )
163 rPropSet.setProperty( PROP_LabelSeparator, rDataLabel.moaSeparator.get( "\n" ) );
164 else if( bDataSeriesLabel || rDataLabel.moaSeparator.has() )
165 rPropSet.setProperty( PROP_LabelSeparator, rDataLabel.moaSeparator.get( "; " ) );
166
167 // data label placement (do not overwrite series placement, if no explicit point placement is present)
168 if( !(bDataSeriesLabel || rDataLabel.monLabelPos.has()) )
169 return;
170
171 namespace csscd = ::com::sun::star::chart::DataLabelPlacement;
172 sal_Int32 nPlacement = -1;
173 switch( rDataLabel.monLabelPos.get( XML_TOKEN_INVALID ) )
174 {
175 case XML_outEnd: nPlacement = csscd::OUTSIDE; break;
176 case XML_inEnd: nPlacement = csscd::INSIDE; break;
177 case XML_ctr: nPlacement = csscd::CENTER; break;
178 case XML_inBase: nPlacement = csscd::NEAR_ORIGIN; break;
179 case XML_t: nPlacement = csscd::TOP; break;
180 case XML_b: nPlacement = csscd::BOTTOM; break;
181 case XML_l: nPlacement = csscd::LEFT; break;
182 case XML_r: nPlacement = csscd::RIGHT; break;
183 case XML_bestFit: nPlacement = csscd::AVOID_OVERLAP; break;
184 }
185
186 if( !bDataSeriesLabel && nPlacement == -1 )
187 return;
188
189 if( nPlacement == -1 )
190 nPlacement = rTypeInfo.mnDefLabelPos;
191
192 rPropSet.setProperty( PROP_LabelPlacement, nPlacement );
193 }
194
importBorderProperties(PropertySet & rPropSet,Shape & rShape,const GraphicHelper & rGraphicHelper)195 void importBorderProperties( PropertySet& rPropSet, Shape& rShape, const GraphicHelper& rGraphicHelper )
196 {
197 LineProperties& rLP = rShape.getLineProperties();
198 // no fill has the same effect as no border so skip it
199 if (rLP.maLineFill.moFillType.get() == XML_noFill)
200 return;
201
202 if (rLP.moLineWidth.has())
203 {
204 sal_Int32 nWidth = convertEmuToHmm(rLP.moLineWidth.get());
205 rPropSet.setProperty(PROP_LabelBorderWidth, uno::makeAny(nWidth));
206 rPropSet.setProperty(PROP_LabelBorderStyle, uno::makeAny(drawing::LineStyle_SOLID));
207 }
208 const Color& aColor = rLP.maLineFill.maFillColor;
209 ::Color nColor = aColor.getColor(rGraphicHelper);
210 rPropSet.setProperty(PROP_LabelBorderColor, uno::makeAny(nColor));
211 }
212
importFillProperties(PropertySet & rPropSet,Shape & rShape,const GraphicHelper & rGraphicHelper,ModelObjectHelper & rModelObjHelper)213 void importFillProperties( PropertySet& rPropSet, Shape& rShape, const GraphicHelper& rGraphicHelper, ModelObjectHelper& rModelObjHelper )
214 {
215 FillProperties& rFP = rShape.getFillProperties();
216
217 if (rFP.moFillType.has() && rFP.moFillType.get() == XML_solidFill)
218 {
219 rPropSet.setProperty(PROP_LabelFillStyle, drawing::FillStyle_SOLID);
220
221 const Color& aColor = rFP.maFillColor;
222 ::Color nColor = aColor.getColor(rGraphicHelper);
223 rPropSet.setProperty(PROP_LabelFillColor, uno::makeAny(nColor));
224 }
225 else if(rFP.moFillType.has() && rFP.moFillType.get() == XML_pattFill)
226 {
227 rPropSet.setProperty(PROP_LabelFillStyle, drawing::FillStyle_HATCH);
228 rPropSet.setProperty(PROP_LabelFillBackground, true);
229
230 Color aHatchColor( rFP.maPatternProps.maPattFgColor );
231 drawing::Hatch aHatch = createHatch(rFP.maPatternProps.moPattPreset.get(), aHatchColor.getColor(rGraphicHelper, 0));
232
233 OUString sHatchName = rModelObjHelper.insertFillHatch(aHatch);
234 rPropSet.setProperty(PROP_LabelFillHatchName, sHatchName);
235
236 const Color& aColor = rFP.maPatternProps.maPattBgColor;
237 ::Color nColor = aColor.getColor(rGraphicHelper);
238 rPropSet.setProperty(PROP_LabelFillColor, uno::makeAny(nColor));
239 }
240
241 }
242
lcl_ConvertFieldNameToFieldEnum(std::u16string_view rField)243 DataPointCustomLabelFieldType lcl_ConvertFieldNameToFieldEnum( std::u16string_view rField )
244 {
245 if (rField == u"VALUE")
246 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_VALUE;
247 else if (rField == u"SERIESNAME")
248 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_SERIESNAME;
249 else if (rField == u"CATEGORYNAME")
250 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CATEGORYNAME;
251 else if (rField == u"CELLREF")
252 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLREF;
253 else if (rField == u"CELLRANGE")
254 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE;
255 else if (rField == u"PERCENTAGE")
256 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_PERCENTAGE;
257 else
258 return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT;
259 }
260
261 } // namespace
262
DataLabelConverter(const ConverterRoot & rParent,DataLabelModel & rModel)263 DataLabelConverter::DataLabelConverter( const ConverterRoot& rParent, DataLabelModel& rModel ) :
264 ConverterBase< DataLabelModel >( rParent, rModel )
265 {
266 }
267
~DataLabelConverter()268 DataLabelConverter::~DataLabelConverter()
269 {
270 }
271
convertFromModel(const Reference<XDataSeries> & rxDataSeries,const TypeGroupConverter & rTypeGroup)272 void DataLabelConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries, const TypeGroupConverter& rTypeGroup )
273 {
274 if (!rxDataSeries.is())
275 return;
276
277 try
278 {
279 bool bMSO2007Doc = getFilter().isMSO2007Document();
280 bool bHasInternalData = getChartDocument()->hasInternalDataProvider();
281 bool bCustomLabelField = mrModel.mxText && mrModel.mxText->mxTextBody && !mrModel.mxText->mxTextBody->getParagraphs().empty();
282 PropertySet aPropSet( rxDataSeries->getDataPointByIndex( mrModel.mnIndex ) );
283
284 lclConvertLabelFormatting( aPropSet, getFormatter(), mrModel, rTypeGroup, false, bCustomLabelField, bHasInternalData, bMSO2007Doc );
285
286 const TypeGroupInfo& rTypeInfo = rTypeGroup.getTypeInfo();
287 bool bIsPie = rTypeInfo.meTypeCategory == TYPECATEGORY_PIE;
288
289 if( mrModel.mxLayout && !mrModel.mxLayout->mbAutoLayout )
290 {
291 RelativePosition aPos(mrModel.mxLayout->mfX, mrModel.mxLayout->mfY, css::drawing::Alignment_TOP_LEFT);
292 aPropSet.setProperty(PROP_CustomLabelPosition, aPos);
293 sal_Int32 nPlacement = -1;
294 if (bIsPie && aPropSet.getProperty(nPlacement, PROP_LabelPlacement)
295 && nPlacement == css::chart::DataLabelPlacement::AVOID_OVERLAP)
296 aPropSet.setProperty(PROP_LabelPlacement, css::chart::DataLabelPlacement::CUSTOM);
297 }
298
299 if (mrModel.mxShapeProp)
300 {
301 importBorderProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper());
302 uno::Reference<lang::XMultiServiceFactory> xFactory(getChartDocument(), uno::UNO_QUERY);
303 ModelObjectHelper& rHelper = getFilter().getModelObjectHelperForModel(xFactory);
304 importFillProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper(),
305 rHelper);
306 }
307 if( bCustomLabelField )
308 {
309 css::uno::Reference< XComponentContext > xContext = getComponentContext();
310 uno::Sequence< css::uno::Reference< XDataPointCustomLabelField > > aSequence;
311
312 auto& rParagraphs = mrModel.mxText->mxTextBody->getParagraphs();
313
314 int nSequenceSize = 0;
315 for( auto& pParagraph : rParagraphs )
316 nSequenceSize += pParagraph->getRuns().size();
317
318 int nParagraphs = rParagraphs.size();
319 if( nParagraphs > 1 )
320 nSequenceSize += nParagraphs - 1;
321
322 aSequence.realloc( nSequenceSize );
323
324 int nPos = 0;
325 for( auto& pParagraph : rParagraphs )
326 {
327 for( auto& pRun : pParagraph->getRuns() )
328 {
329 css::uno::Reference< XDataPointCustomLabelField > xCustomLabel = DataPointCustomLabelField::create( xContext );
330
331 // Store properties
332 oox::PropertySet aPropertySet( xCustomLabel );
333 convertTextProperty( aPropertySet, getFormatter(), mrModel.mxText->mxTextBody );
334 pRun->getTextCharacterProperties().pushToPropSet( aPropertySet, getFilter() );
335
336 TextField* pField = nullptr;
337 if( ( pField = dynamic_cast< TextField* >( pRun.get() ) ) )
338 {
339 xCustomLabel->setString( pField->getText() );
340 xCustomLabel->setFieldType( lcl_ConvertFieldNameToFieldEnum( pField->getType() ) );
341 xCustomLabel->setGuid( pField->getUuid() );
342 }
343 else if( pRun )
344 {
345 xCustomLabel->setString( pRun->getText() );
346 xCustomLabel->setFieldType( DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT );
347 }
348 aSequence[ nPos++ ] = xCustomLabel;
349 }
350
351 if( nParagraphs > 1 && nPos < nSequenceSize )
352 {
353 css::uno::Reference< XDataPointCustomLabelField > xCustomLabel = DataPointCustomLabelField::create( xContext );
354 xCustomLabel->setFieldType( DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_NEWLINE );
355 xCustomLabel->setString("\n");
356 aSequence[ nPos++ ] = xCustomLabel;
357 }
358 }
359
360 aPropSet.setProperty( PROP_CustomLabelFields, makeAny( aSequence ) );
361 convertTextProperty(aPropSet, getFormatter(), mrModel.mxText->mxTextBody);
362 }
363 }
364 catch( Exception& )
365 {
366 }
367 }
368
DataLabelsConverter(const ConverterRoot & rParent,DataLabelsModel & rModel)369 DataLabelsConverter::DataLabelsConverter( const ConverterRoot& rParent, DataLabelsModel& rModel ) :
370 ConverterBase< DataLabelsModel >( rParent, rModel )
371 {
372 }
373
~DataLabelsConverter()374 DataLabelsConverter::~DataLabelsConverter()
375 {
376 }
377
378 namespace
379 {
380 /// Inherit <c:dLbl> text props (if not set) from <c:dLbls> text props (if set).
InheritFromDataLabelsTextProps(const DataLabelsModel & rLabels,const DataLabelModel & rLabel)381 void InheritFromDataLabelsTextProps(const DataLabelsModel& rLabels, const DataLabelModel& rLabel)
382 {
383 // See if <c:dLbls> contains text properties to inherit.
384 if (!rLabels.mxTextProp.is() || rLabels.mxTextProp->getParagraphs().empty())
385 {
386 return;
387 }
388
389 const std::shared_ptr<TextParagraph>& rLabelsParagraph = rLabels.mxTextProp->getParagraphs()[0];
390
391 // See if <c:dLbl> lacks text properties.
392 if (rLabel.mxTextProp.is())
393 {
394 return;
395 }
396
397 if (!rLabel.mxText || !rLabel.mxText->mxTextBody
398 || rLabel.mxText->mxTextBody->getParagraphs().empty())
399 {
400 return;
401 }
402
403 const std::shared_ptr<TextParagraph>& rLabelParagraph
404 = rLabel.mxText->mxTextBody->getParagraphs()[0];
405
406 // Inherit rLabel.mxText's char props from rLabels.mxTextProp's char props.
407 TextCharacterProperties aCharProps;
408 aCharProps.assignUsed(rLabelsParagraph->getProperties().getTextCharacterProperties());
409 aCharProps.assignUsed(rLabelParagraph->getProperties().getTextCharacterProperties());
410 rLabelParagraph->getProperties().getTextCharacterProperties().assignUsed(aCharProps);
411 }
412 }
413
convertFromModel(const Reference<XDataSeries> & rxDataSeries,const TypeGroupConverter & rTypeGroup)414 void DataLabelsConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries, const TypeGroupConverter& rTypeGroup )
415 {
416 PropertySet aPropSet( rxDataSeries );
417 if( !mrModel.mbDeleted )
418 {
419 bool bMSO2007Doc = getFilter().isMSO2007Document();
420 bool bHasInternalData = getChartDocument()->hasInternalDataProvider();
421
422 lclConvertLabelFormatting( aPropSet, getFormatter(), mrModel, rTypeGroup, true, false, bHasInternalData, bMSO2007Doc );
423
424 if (mrModel.mxShapeProp)
425 {
426 // Import baseline border properties for these data labels.
427 importBorderProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper());
428 uno::Reference<lang::XMultiServiceFactory> xFactory(getChartDocument(), uno::UNO_QUERY);
429 ModelObjectHelper& rHelper = getFilter().getModelObjectHelperForModel(xFactory);
430 importFillProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper(),
431 rHelper);
432 }
433 }
434 // import leaderline of data labels
435 if( !mrModel.mbShowLeaderLines )
436 aPropSet.setProperty( PROP_ShowCustomLeaderLines, false );
437
438 // data point label settings
439 for (auto const& pointLabel : mrModel.maPointLabels)
440 {
441 if (pointLabel->maNumberFormat.maFormatCode.isEmpty())
442 pointLabel->maNumberFormat = mrModel.maNumberFormat;
443 InheritFromDataLabelsTextProps(mrModel, *pointLabel);
444
445 DataLabelConverter aLabelConv(*this, *pointLabel);
446 aLabelConv.convertFromModel( rxDataSeries, rTypeGroup );
447 }
448 }
449
ErrorBarConverter(const ConverterRoot & rParent,ErrorBarModel & rModel)450 ErrorBarConverter::ErrorBarConverter( const ConverterRoot& rParent, ErrorBarModel& rModel ) :
451 ConverterBase< ErrorBarModel >( rParent, rModel )
452 {
453 }
454
~ErrorBarConverter()455 ErrorBarConverter::~ErrorBarConverter()
456 {
457 }
458
convertFromModel(const Reference<XDataSeries> & rxDataSeries)459 void ErrorBarConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries )
460 {
461 bool bShowPos = (mrModel.mnTypeId == XML_plus) || (mrModel.mnTypeId == XML_both);
462 bool bShowNeg = (mrModel.mnTypeId == XML_minus) || (mrModel.mnTypeId == XML_both);
463 if( !(bShowPos || bShowNeg) )
464 return;
465
466 try
467 {
468 Reference< XPropertySet > xErrorBar( createInstance( "com.sun.star.chart2.ErrorBar" ), UNO_QUERY_THROW );
469 PropertySet aBarProp( xErrorBar );
470
471 // plus/minus bars
472 aBarProp.setProperty( PROP_ShowPositiveError, bShowPos );
473 aBarProp.setProperty( PROP_ShowNegativeError, bShowNeg );
474
475 // type of displayed error
476 namespace cssc = ::com::sun::star::chart;
477 switch( mrModel.mnValueType )
478 {
479 case XML_cust:
480 {
481 // #i87806# manual error bars
482 aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::FROM_DATA );
483 // attach data sequences to error bar
484 Reference< XDataSink > xDataSink( xErrorBar, UNO_QUERY );
485 if( xDataSink.is() )
486 {
487 // create vector of all value sequences
488 ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec;
489 // add positive values
490 if( bShowPos )
491 {
492 Reference< XLabeledDataSequence > xValueSeq = createLabeledDataSequence( ErrorBarModel::PLUS );
493 if( xValueSeq.is() )
494 aLabeledSeqVec.push_back( xValueSeq );
495 }
496 // add negative values
497 if( bShowNeg )
498 {
499 Reference< XLabeledDataSequence > xValueSeq = createLabeledDataSequence( ErrorBarModel::MINUS );
500 if( xValueSeq.is() )
501 aLabeledSeqVec.push_back( xValueSeq );
502 }
503 // attach labeled data sequences to series
504 if( aLabeledSeqVec.empty() )
505 xErrorBar.clear();
506 else
507 xDataSink->setData( ContainerHelper::vectorToSequence( aLabeledSeqVec ) );
508 }
509 }
510 break;
511 case XML_fixedVal:
512 aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::ABSOLUTE );
513 aBarProp.setProperty( PROP_PositiveError, mrModel.mfValue );
514 aBarProp.setProperty( PROP_NegativeError, mrModel.mfValue );
515 break;
516 case XML_percentage:
517 aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::RELATIVE );
518 aBarProp.setProperty( PROP_PositiveError, mrModel.mfValue );
519 aBarProp.setProperty( PROP_NegativeError, mrModel.mfValue );
520 break;
521 case XML_stdDev:
522 aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::STANDARD_DEVIATION );
523 aBarProp.setProperty( PROP_Weight, mrModel.mfValue );
524 break;
525 case XML_stdErr:
526 aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::STANDARD_ERROR );
527 break;
528 default:
529 OSL_FAIL( "ErrorBarConverter::convertFromModel - unknown error bar type" );
530 xErrorBar.clear();
531 }
532
533 // error bar formatting
534 getFormatter().convertFrameFormatting( aBarProp, mrModel.mxShapeProp, OBJECTTYPE_ERRORBAR );
535
536 if( xErrorBar.is() )
537 {
538 PropertySet aSeriesProp( rxDataSeries );
539 switch( mrModel.mnDirection )
540 {
541 case XML_x: aSeriesProp.setProperty( PROP_ErrorBarX, xErrorBar ); break;
542 case XML_y: aSeriesProp.setProperty( PROP_ErrorBarY, xErrorBar ); break;
543 default: OSL_FAIL( "ErrorBarConverter::convertFromModel - invalid error bar direction" );
544 }
545 }
546 }
547 catch( Exception& )
548 {
549 OSL_FAIL( "ErrorBarConverter::convertFromModel - error while creating error bars" );
550 }
551 }
552
createLabeledDataSequence(ErrorBarModel::SourceType eSourceType)553 Reference< XLabeledDataSequence > ErrorBarConverter::createLabeledDataSequence( ErrorBarModel::SourceType eSourceType )
554 {
555 OUString aRole;
556 switch( eSourceType )
557 {
558 case ErrorBarModel::PLUS:
559 switch( mrModel.mnDirection )
560 {
561 case XML_x: aRole = "error-bars-x-positive"; break;
562 case XML_y: aRole = "error-bars-y-positive"; break;
563 }
564 break;
565 case ErrorBarModel::MINUS:
566 switch( mrModel.mnDirection )
567 {
568 case XML_x: aRole = "error-bars-x-negative"; break;
569 case XML_y: aRole = "error-bars-y-negative"; break;
570 }
571 break;
572 }
573 OSL_ENSURE( !aRole.isEmpty(), "ErrorBarConverter::createLabeledDataSequence - invalid error bar direction" );
574 return lclCreateLabeledDataSequence( *this, mrModel.maSources.get( eSourceType ).get(), aRole );
575 }
576
TrendlineLabelConverter(const ConverterRoot & rParent,TrendlineLabelModel & rModel)577 TrendlineLabelConverter::TrendlineLabelConverter( const ConverterRoot& rParent, TrendlineLabelModel& rModel ) :
578 ConverterBase< TrendlineLabelModel >( rParent, rModel )
579 {
580 }
581
~TrendlineLabelConverter()582 TrendlineLabelConverter::~TrendlineLabelConverter()
583 {
584 }
585
convertFromModel(PropertySet & rPropSet)586 void TrendlineLabelConverter::convertFromModel( PropertySet& rPropSet )
587 {
588 // formatting
589 getFormatter().convertFormatting( rPropSet, mrModel.mxShapeProp, mrModel.mxTextProp, OBJECTTYPE_TRENDLINELABEL );
590 }
591
TrendlineConverter(const ConverterRoot & rParent,TrendlineModel & rModel)592 TrendlineConverter::TrendlineConverter( const ConverterRoot& rParent, TrendlineModel& rModel ) :
593 ConverterBase< TrendlineModel >( rParent, rModel )
594 {
595 }
596
~TrendlineConverter()597 TrendlineConverter::~TrendlineConverter()
598 {
599 }
600
convertFromModel(const Reference<XDataSeries> & rxDataSeries)601 void TrendlineConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries )
602 {
603 try
604 {
605 // trend line type
606 OUString aServiceName;
607 switch( mrModel.mnTypeId )
608 {
609 case XML_exp:
610 aServiceName = "com.sun.star.chart2.ExponentialRegressionCurve";
611 break;
612 case XML_linear:
613 aServiceName = "com.sun.star.chart2.LinearRegressionCurve";
614 break;
615 case XML_log:
616 aServiceName = "com.sun.star.chart2.LogarithmicRegressionCurve";
617 break;
618 case XML_movingAvg:
619 aServiceName = "com.sun.star.chart2.MovingAverageRegressionCurve";
620 break;
621 case XML_poly:
622 aServiceName = "com.sun.star.chart2.PolynomialRegressionCurve";
623 break;
624 case XML_power:
625 aServiceName = "com.sun.star.chart2.PotentialRegressionCurve";
626 break;
627 default:
628 OSL_FAIL( "TrendlineConverter::convertFromModel - unknown trendline type" );
629 }
630 if( !aServiceName.isEmpty() )
631 {
632 Reference< XRegressionCurve > xRegCurve( createInstance( aServiceName ), UNO_QUERY_THROW );
633 PropertySet aPropSet( xRegCurve );
634
635 // Name
636 aPropSet.setProperty( PROP_CurveName, mrModel.maName );
637 aPropSet.setProperty( PROP_PolynomialDegree, mrModel.mnOrder );
638 aPropSet.setProperty( PROP_MovingAveragePeriod, mrModel.mnPeriod );
639
640 // Intercept
641 bool hasIntercept = mrModel.mfIntercept.has();
642 aPropSet.setProperty( PROP_ForceIntercept, hasIntercept);
643 if (hasIntercept)
644 aPropSet.setProperty( PROP_InterceptValue, mrModel.mfIntercept.get());
645
646 // Extrapolation
647 if (mrModel.mfForward.has())
648 aPropSet.setProperty( PROP_ExtrapolateForward, mrModel.mfForward.get() );
649 if (mrModel.mfBackward.has())
650 aPropSet.setProperty( PROP_ExtrapolateBackward, mrModel.mfBackward.get() );
651
652 // trendline formatting
653 getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, OBJECTTYPE_TRENDLINE );
654
655 // #i83100# show equation and correlation coefficient
656 PropertySet aLabelProp( xRegCurve->getEquationProperties() );
657 aLabelProp.setProperty( PROP_ShowEquation, mrModel.mbDispEquation );
658 aLabelProp.setProperty( PROP_ShowCorrelationCoefficient, mrModel.mbDispRSquared );
659
660 // #i83100# formatting of the equation text box
661 if( mrModel.mbDispEquation || mrModel.mbDispRSquared )
662 {
663 TrendlineLabelConverter aLabelConv( *this, mrModel.mxLabel.getOrCreate() );
664 aLabelConv.convertFromModel( aLabelProp );
665 }
666
667 // unsupported: #i5085# manual trendline size
668 // unsupported: #i34093# manual crossing point
669
670 Reference< XRegressionCurveContainer > xRegCurveCont( rxDataSeries, UNO_QUERY_THROW );
671 xRegCurveCont->addRegressionCurve( xRegCurve );
672 }
673 }
674 catch( Exception& )
675 {
676 OSL_FAIL( "TrendlineConverter::convertFromModel - error while creating trendline" );
677 }
678 }
679
DataPointConverter(const ConverterRoot & rParent,DataPointModel & rModel)680 DataPointConverter::DataPointConverter( const ConverterRoot& rParent, DataPointModel& rModel ) :
681 ConverterBase< DataPointModel >( rParent, rModel )
682 {
683 }
684
~DataPointConverter()685 DataPointConverter::~DataPointConverter()
686 {
687 }
688
convertFromModel(const Reference<XDataSeries> & rxDataSeries,const TypeGroupConverter & rTypeGroup,const SeriesModel & rSeries)689 void DataPointConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries,
690 const TypeGroupConverter& rTypeGroup, const SeriesModel& rSeries )
691 {
692 bool bMSO2007Doc = getFilter().isMSO2007Document();
693 try
694 {
695 PropertySet aPropSet( rxDataSeries->getDataPointByIndex( mrModel.mnIndex ) );
696
697 // data point marker
698 if( mrModel.monMarkerSymbol.differsFrom( rSeries.mnMarkerSymbol ) || mrModel.monMarkerSize.differsFrom( rSeries.mnMarkerSize ) )
699 rTypeGroup.convertMarker( aPropSet, mrModel.monMarkerSymbol.get( rSeries.mnMarkerSymbol ),
700 mrModel.monMarkerSize.get( rSeries.mnMarkerSize ), mrModel.mxMarkerProp );
701
702 // data point pie explosion
703 if( mrModel.monExplosion.differsFrom( rSeries.mnExplosion ) )
704 rTypeGroup.convertPieExplosion( aPropSet, mrModel.monExplosion.get() );
705
706 // point formatting
707 if( mrModel.mxShapeProp.is() )
708 {
709 if( rTypeGroup.getTypeInfo().mbPictureOptions )
710 getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, mrModel.mxPicOptions.getOrCreate(bMSO2007Doc), rTypeGroup.getSeriesObjectType(), rSeries.mnIndex );
711 else
712 getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, rTypeGroup.getSeriesObjectType(), rSeries.mnIndex );
713 }
714 else if (rSeries.mxShapeProp.is())
715 {
716 getFormatter().convertFrameFormatting( aPropSet, rSeries.mxShapeProp, rTypeGroup.getSeriesObjectType(), rSeries.mnIndex );
717 }
718 }
719 catch( Exception& )
720 {
721 }
722 }
723
SeriesConverter(const ConverterRoot & rParent,SeriesModel & rModel)724 SeriesConverter::SeriesConverter( const ConverterRoot& rParent, SeriesModel& rModel ) :
725 ConverterBase< SeriesModel >( rParent, rModel )
726 {
727 }
728
~SeriesConverter()729 SeriesConverter::~SeriesConverter()
730 {
731 }
732
createCategorySequence(const OUString & rRole)733 Reference< XLabeledDataSequence > SeriesConverter::createCategorySequence( const OUString& rRole )
734 {
735 return createLabeledDataSequence(SeriesModel::CATEGORIES, rRole, false);
736 }
737
createValueSequence(const OUString & rRole)738 Reference< XLabeledDataSequence > SeriesConverter::createValueSequence( const OUString& rRole )
739 {
740 return createLabeledDataSequence( SeriesModel::VALUES, rRole, true );
741 }
742
createDataSeries(const TypeGroupConverter & rTypeGroup,bool bVaryColorsByPoint)743 Reference< XDataSeries > SeriesConverter::createDataSeries( const TypeGroupConverter& rTypeGroup, bool bVaryColorsByPoint )
744 {
745 const TypeGroupInfo& rTypeInfo = rTypeGroup.getTypeInfo();
746
747 // create the data series object
748 Reference< XDataSeries > xDataSeries( createInstance( "com.sun.star.chart2.DataSeries" ), UNO_QUERY );
749 PropertySet aSeriesProp( xDataSeries );
750
751 // attach data and title sequences to series
752 sal_Int32 nDataPointCount = 0;
753 Reference< XDataSink > xDataSink( xDataSeries, UNO_QUERY );
754 if( xDataSink.is() )
755 {
756 // create vector of all value sequences
757 ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec;
758 // add Y values
759 Reference< XLabeledDataSequence > xYValueSeq = createValueSequence( "values-y" );
760 if( xYValueSeq.is() )
761 {
762 aLabeledSeqVec.push_back( xYValueSeq );
763 Reference< XDataSequence > xValues = xYValueSeq->getValues();
764 if( xValues.is() )
765 nDataPointCount = xValues->getData().getLength();
766
767 if (!nDataPointCount)
768 // No values present. Don't create a data series.
769 return Reference<XDataSeries>();
770 }
771 // add X values of scatter and bubble charts
772 if( !rTypeInfo.mbCategoryAxis )
773 {
774 Reference< XLabeledDataSequence > xXValueSeq = createCategorySequence( "values-x" );
775 if( xXValueSeq.is() )
776 aLabeledSeqVec.push_back( xXValueSeq );
777 // add size values of bubble charts
778 if( rTypeInfo.meTypeId == TYPEID_BUBBLE )
779 {
780 Reference< XLabeledDataSequence > xSizeValueSeq = createLabeledDataSequence( SeriesModel::POINTS, "values-size", true );
781 if( xSizeValueSeq.is() )
782 aLabeledSeqVec.push_back( xSizeValueSeq );
783 }
784 }
785 // attach labeled data sequences to series
786 if( !aLabeledSeqVec.empty() )
787 xDataSink->setData( ContainerHelper::vectorToSequence( aLabeledSeqVec ) );
788 }
789
790 // error bars
791 for (auto const& errorBar : mrModel.maErrorBars)
792 {
793 ErrorBarConverter aErrorBarConv(*this, *errorBar);
794 aErrorBarConv.convertFromModel( xDataSeries );
795 }
796
797 // trendlines
798 for (auto const& trendLine : mrModel.maTrendlines)
799 {
800 TrendlineConverter aTrendlineConv(*this, *trendLine);
801 aTrendlineConv.convertFromModel( xDataSeries );
802 }
803
804 // data point markers
805 rTypeGroup.convertMarker( aSeriesProp, mrModel.mnMarkerSymbol, mrModel.mnMarkerSize, mrModel.mxMarkerProp );
806 #if OOX_CHART_SMOOTHED_PER_SERIES
807 // #i66858# smoothed series lines
808 rTypeGroup.convertLineSmooth( aSeriesProp, mrModel.mbSmooth );
809 #endif
810 // 3D bar style (not possible to set at chart type -> set at all series)
811 rTypeGroup.convertBarGeometry( aSeriesProp, mrModel.monShape.get( rTypeGroup.getModel().mnShape ) );
812 // pie explosion (restricted to [0%,100%] in Chart2)
813 rTypeGroup.convertPieExplosion( aSeriesProp, mrModel.mnExplosion );
814
815 // series formatting
816 ObjectFormatter& rFormatter = getFormatter();
817 ObjectType eObjType = rTypeGroup.getSeriesObjectType();
818 bool bMSO2007Doc = getFilter().isMSO2007Document();
819 if( rTypeInfo.mbPictureOptions )
820 rFormatter.convertFrameFormatting( aSeriesProp, mrModel.mxShapeProp, mrModel.mxPicOptions.getOrCreate(bMSO2007Doc), eObjType, mrModel.mnIndex );
821 else
822 rFormatter.convertFrameFormatting( aSeriesProp, mrModel.mxShapeProp, eObjType, mrModel.mnIndex );
823
824 // set the (unused) property default value used by the Chart2 templates (true for pie/doughnut charts)
825 bool bIsPie = rTypeInfo.meTypeCategory == TYPECATEGORY_PIE;
826 aSeriesProp.setProperty( PROP_VaryColorsByPoint, bVaryColorsByPoint );
827
828 // own area formatting for every data point (TODO: varying line color not supported)
829 // #i91271# always set area formatting for every point in pie/doughnut charts to override their automatic point formatting
830 if( bIsPie || (bVaryColorsByPoint && rTypeGroup.isSeriesFrameFormat() && ObjectFormatter::isAutomaticFill( mrModel.mxShapeProp )) )
831 {
832 /* Set the series point number as color cycle size at the object
833 formatter to get correct start-shade/end-tint. TODO: in doughnut
834 charts, the sizes of the series may vary, need to use the maximum
835 point count of all series. */
836 sal_Int32 nOldMax = rFormatter.getMaxSeriesIndex();
837 if( bVaryColorsByPoint )
838 rFormatter.setMaxSeriesIndex( nDataPointCount - 1 );
839 for( sal_Int32 nIndex = 0; nIndex < nDataPointCount; ++nIndex )
840 {
841 try
842 {
843 PropertySet aPointProp( xDataSeries->getDataPointByIndex( nIndex ) );
844 rFormatter.convertAutomaticFill( aPointProp, eObjType, bVaryColorsByPoint ? nIndex : mrModel.mnIndex );
845 }
846 catch( Exception& )
847 {
848 }
849 }
850 rFormatter.setMaxSeriesIndex( nOldMax );
851 }
852
853 // data point settings
854 for (auto const& point : mrModel.maPoints)
855 {
856 DataPointConverter aPointConv(*this, *point);
857 aPointConv.convertFromModel( xDataSeries, rTypeGroup, mrModel );
858 }
859
860 /* Series data label settings. If and only if the series does not contain
861 a c:dLbls element, then the c:dLbls element of the parent chart type is
862 used (data label settings of the parent chart type are *not* merged
863 into own existing data label settings). */
864 ModelRef< DataLabelsModel > xLabels = mrModel.mxLabels.is() ? mrModel.mxLabels : rTypeGroup.getModel().mxLabels;
865 if( xLabels.is() )
866 {
867 if( xLabels->maNumberFormat.maFormatCode.isEmpty() )
868 {
869 // Use number format code from Value series
870 DataSourceModel* pValues = mrModel.maSources.get( SeriesModel::VALUES ).get();
871 if( pValues )
872 xLabels->maNumberFormat.maFormatCode = pValues->mxDataSeq->maFormatCode;
873 }
874 DataLabelsConverter aLabelsConv( *this, *xLabels );
875 aLabelsConv.convertFromModel( xDataSeries, rTypeGroup );
876 }
877
878 return xDataSeries;
879 }
880
881 // private --------------------------------------------------------------------
882
createLabeledDataSequence(SeriesModel::SourceType eSourceType,const OUString & rRole,bool bUseTextLabel)883 Reference< XLabeledDataSequence > SeriesConverter::createLabeledDataSequence(
884 SeriesModel::SourceType eSourceType, const OUString& rRole, bool bUseTextLabel )
885 {
886 DataSourceModel* pValues = mrModel.maSources.get( eSourceType ).get();
887 TextModel* pTitle = bUseTextLabel ? mrModel.mxText.get() : nullptr;
888 return lclCreateLabeledDataSequence( *this, pValues, rRole, pTitle );
889 }
890
891 } // namespace oox
892
893 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
894