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