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
