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