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