/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace oox::drawingml::chart { using namespace com::sun::star; using namespace ::com::sun::star::beans; using namespace ::com::sun::star::chart2; using namespace ::com::sun::star::chart2::data; using namespace ::com::sun::star::uno; namespace { Reference< XLabeledDataSequence > lclCreateLabeledDataSequence( const ConverterRoot& rParent, DataSourceModel* pValues, const OUString& rRole, TextModel* pTitle = nullptr ) { // create data sequence for values Reference< XDataSequence > xValueSeq; if( pValues ) { DataSourceConverter aSourceConv( rParent, *pValues ); xValueSeq = aSourceConv.createDataSequence( rRole ); } // create data sequence for title Reference< XDataSequence > xTitleSeq; if( pTitle ) { TextConverter aTextConv( rParent, *pTitle ); xTitleSeq = aTextConv.createDataSequence( "label" ); } // create the labeled data sequence, if values or title are present Reference< XLabeledDataSequence > xLabeledSeq; if( xValueSeq.is() || xTitleSeq.is() ) { xLabeledSeq = LabeledDataSequence::create(rParent.getComponentContext()); if( xLabeledSeq.is() ) { xLabeledSeq->setValues( xValueSeq ); xLabeledSeq->setLabel( xTitleSeq ); } } return xLabeledSeq; } void convertTextProperty(PropertySet& rPropSet, ObjectFormatter& rFormatter, DataLabelModelBase::TextBodyRef xTextProps) { rFormatter.convertTextFormatting( rPropSet, xTextProps, OBJECTTYPE_DATALABEL ); ObjectFormatter::convertTextRotation( rPropSet, xTextProps, false ); ObjectFormatter::convertTextWrap( rPropSet, xTextProps ); } void lclConvertLabelFormatting( PropertySet& rPropSet, ObjectFormatter& rFormatter, DataLabelModelBase& rDataLabel, const TypeGroupConverter& rTypeGroup, bool bDataSeriesLabel, bool bCustomLabelField, bool bHasInternalData, bool bMSO2007Doc ) { const TypeGroupInfo& rTypeInfo = rTypeGroup.getTypeInfo(); /* Excel 2007 does not change the series setting for a single data point, if none of some specific elements occur. But only one existing element in a data point will reset most other of these elements from the series (e.g.: series has , data point has , this will reset for this point, unless is repeated in the data point). The elements , , , , and are not affected at all. */ bool bHasAnyElement = true; if (bMSO2007Doc) { bHasAnyElement = rDataLabel.moaSeparator.has_value() || rDataLabel.monLabelPos.has_value() || rDataLabel.mobShowCatName.has_value() || rDataLabel.mobShowLegendKey.has_value() || rDataLabel.mobShowPercent.has_value() || rDataLabel.mobShowSerName.has_value() || rDataLabel.mobShowVal.has_value(); } bool bShowValue = !rDataLabel.mbDeleted && rDataLabel.mobShowVal.value_or( !bMSO2007Doc ); bool bShowPercent = !rDataLabel.mbDeleted && rDataLabel.mobShowPercent.value_or( !bMSO2007Doc ) && (rTypeInfo.meTypeCategory == TYPECATEGORY_PIE); bool bShowCateg = !rDataLabel.mbDeleted && rDataLabel.mobShowCatName.value_or( !bMSO2007Doc ); bool bShowSerName = !rDataLabel.mbDeleted && rDataLabel.mobShowSerName.value_or( !bMSO2007Doc ); bool bShowSymbol = !rDataLabel.mbDeleted && rDataLabel.mobShowLegendKey.value_or( !bMSO2007Doc ); // tdf#132174, tdf#136650: the inner data table has no own cell number format. if( bHasInternalData && bShowValue && !bShowPercent ) rDataLabel.maNumberFormat.mbSourceLinked = false; // type of attached label if( bHasAnyElement || rDataLabel.mbDeleted ) { DataPointLabel aPointLabel( bShowValue, bShowPercent, bShowCateg, bShowSymbol, bCustomLabelField, bShowSerName ); rPropSet.setProperty( PROP_Label, aPointLabel ); } if( rDataLabel.mbDeleted ) return; // data label number format (percentage format wins over value format) rFormatter.convertNumberFormat( rPropSet, rDataLabel.maNumberFormat, false, bShowPercent ); // data label text formatting (frame formatting not supported by Chart2) if( bDataSeriesLabel || (rDataLabel.mxTextProp.is() && !rDataLabel.mxTextProp->getParagraphs().empty()) ) convertTextProperty(rPropSet, rFormatter, rDataLabel.mxTextProp); // data label separator (do not overwrite series separator, if no explicit point separator is present) // Set the data label separator to "new line" if the value is shown as percentage with a category name, // just like in MS-Office. In any other case the default separator will be a semicolon. if( bShowPercent && !bShowValue && ( bDataSeriesLabel || rDataLabel.moaSeparator.has_value() ) ) rPropSet.setProperty( PROP_LabelSeparator, rDataLabel.moaSeparator.value_or( "\n" ) ); else if( bDataSeriesLabel || rDataLabel.moaSeparator.has_value() ) rPropSet.setProperty( PROP_LabelSeparator, rDataLabel.moaSeparator.value_or( "; " ) ); // data label placement (do not overwrite series placement, if no explicit point placement is present) if( !(bDataSeriesLabel || rDataLabel.monLabelPos.has_value()) ) return; namespace csscd = ::com::sun::star::chart::DataLabelPlacement; sal_Int32 nPlacement = -1; switch( rDataLabel.monLabelPos.value_or( XML_TOKEN_INVALID ) ) { case XML_outEnd: nPlacement = csscd::OUTSIDE; break; case XML_inEnd: nPlacement = csscd::INSIDE; break; case XML_ctr: nPlacement = csscd::CENTER; break; case XML_inBase: nPlacement = csscd::NEAR_ORIGIN; break; case XML_t: nPlacement = csscd::TOP; break; case XML_b: nPlacement = csscd::BOTTOM; break; case XML_l: nPlacement = csscd::LEFT; break; case XML_r: nPlacement = csscd::RIGHT; break; case XML_bestFit: nPlacement = csscd::AVOID_OVERLAP; break; } if( !bDataSeriesLabel && nPlacement == -1 ) return; if( nPlacement == -1 ) nPlacement = rTypeInfo.mnDefLabelPos; rPropSet.setProperty( PROP_LabelPlacement, nPlacement ); } void importBorderProperties( PropertySet& rPropSet, Shape& rShape, const GraphicHelper& rGraphicHelper ) { LineProperties& rLP = rShape.getLineProperties(); // no fill has the same effect as no border so skip it if (rLP.maLineFill.moFillType.has_value() && rLP.maLineFill.moFillType.value() == XML_noFill) return; if (rLP.moLineWidth.has_value()) { sal_Int32 nWidth = convertEmuToHmm(rLP.moLineWidth.value()); rPropSet.setProperty(PROP_LabelBorderWidth, uno::Any(nWidth)); rPropSet.setProperty(PROP_LabelBorderStyle, uno::Any(drawing::LineStyle_SOLID)); } const Color& aColor = rLP.maLineFill.maFillColor; ::Color nColor = aColor.getColor(rGraphicHelper); rPropSet.setProperty(PROP_LabelBorderColor, uno::Any(nColor)); } void importFillProperties( PropertySet& rPropSet, Shape& rShape, const GraphicHelper& rGraphicHelper, ModelObjectHelper& rModelObjHelper ) { FillProperties& rFP = rShape.getFillProperties(); if (rFP.moFillType.has_value() && rFP.moFillType.value() == XML_solidFill) { rPropSet.setProperty(PROP_LabelFillStyle, drawing::FillStyle_SOLID); const Color& aColor = rFP.maFillColor; ::Color nColor = aColor.getColor(rGraphicHelper); rPropSet.setProperty(PROP_LabelFillColor, uno::Any(nColor)); } else if(rFP.moFillType.has_value() && rFP.moFillType.value() == XML_pattFill) { rPropSet.setProperty(PROP_LabelFillStyle, drawing::FillStyle_HATCH); rPropSet.setProperty(PROP_LabelFillBackground, true); Color aHatchColor( rFP.maPatternProps.maPattFgColor ); drawing::Hatch aHatch = createHatch(rFP.maPatternProps.moPattPreset.value(), aHatchColor.getColor(rGraphicHelper, 0)); OUString sHatchName = rModelObjHelper.insertFillHatch(aHatch); rPropSet.setProperty(PROP_LabelFillHatchName, sHatchName); const Color& aColor = rFP.maPatternProps.maPattBgColor; ::Color nColor = aColor.getColor(rGraphicHelper); rPropSet.setProperty(PROP_LabelFillColor, uno::Any(nColor)); } } DataPointCustomLabelFieldType lcl_ConvertFieldNameToFieldEnum( std::u16string_view rField ) { if (rField == u"VALUE") return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_VALUE; else if (rField == u"SERIESNAME") return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_SERIESNAME; else if (rField == u"CATEGORYNAME") return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CATEGORYNAME; else if (rField == u"CELLREF") return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLREF; else if (rField == u"CELLRANGE") return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE; else if (rField == u"PERCENTAGE") return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_PERCENTAGE; else return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT; } } // namespace DataLabelConverter::DataLabelConverter( const ConverterRoot& rParent, DataLabelModel& rModel ) : ConverterBase< DataLabelModel >( rParent, rModel ) { } DataLabelConverter::~DataLabelConverter() { } void DataLabelConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries, const TypeGroupConverter& rTypeGroup ) { if (!rxDataSeries.is()) return; try { bool bMSO2007Doc = getFilter().isMSO2007Document(); bool bHasInternalData = getChartDocument()->hasInternalDataProvider(); bool bCustomLabelField = mrModel.mxText && mrModel.mxText->mxTextBody && !mrModel.mxText->mxTextBody->getParagraphs().empty(); PropertySet aPropSet( rxDataSeries->getDataPointByIndex( mrModel.mnIndex ) ); lclConvertLabelFormatting( aPropSet, getFormatter(), mrModel, rTypeGroup, false, bCustomLabelField, bHasInternalData, bMSO2007Doc ); const TypeGroupInfo& rTypeInfo = rTypeGroup.getTypeInfo(); bool bIsPie = rTypeInfo.meTypeCategory == TYPECATEGORY_PIE; if( mrModel.mxLayout && !mrModel.mxLayout->mbAutoLayout ) { RelativePosition aPos(mrModel.mxLayout->mfX, mrModel.mxLayout->mfY, css::drawing::Alignment_TOP_LEFT); aPropSet.setProperty(PROP_CustomLabelPosition, aPos); sal_Int32 nPlacement = -1; if (bIsPie && aPropSet.getProperty(nPlacement, PROP_LabelPlacement) && nPlacement == css::chart::DataLabelPlacement::AVOID_OVERLAP) aPropSet.setProperty(PROP_LabelPlacement, css::chart::DataLabelPlacement::CUSTOM); } if (mrModel.mxShapeProp) { importBorderProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper()); uno::Reference xFactory(getChartDocument(), uno::UNO_QUERY); ModelObjectHelper& rHelper = getFilter().getModelObjectHelperForModel(xFactory); importFillProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper(), rHelper); } if( bCustomLabelField ) { css::uno::Reference< XComponentContext > xContext = getComponentContext(); auto& rParagraphs = mrModel.mxText->mxTextBody->getParagraphs(); int nSequenceSize = 0; for( auto& pParagraph : rParagraphs ) nSequenceSize += pParagraph->getRuns().size(); int nParagraphs = rParagraphs.size(); if( nParagraphs > 1 ) nSequenceSize += nParagraphs - 1; std::optional< OUString > oaLabelText; std::optional< OUString > oaCellRange; if (mrModel.mobShowDataLabelsRange.value_or(false)) { const DataSourceModel* pLabelSource = mrModel.mrParent.mpLabelsSource; if (pLabelSource && pLabelSource->mxDataSeq.is()) { oaCellRange = pLabelSource->mxDataSeq->maFormula; const auto& rLabelMap = pLabelSource->mxDataSeq->maData; const auto& rKV = rLabelMap.find(mrModel.mnIndex); if (rKV != rLabelMap.end()) { oaLabelText.emplace(); rKV->second >>= *oaLabelText; } } } uno::Sequence< css::uno::Reference< XDataPointCustomLabelField > > aSequence( nSequenceSize ); auto aSequenceRange = asNonConstRange(aSequence); int nPos = 0; for( auto& pParagraph : rParagraphs ) { for( auto& pRun : pParagraph->getRuns() ) { css::uno::Reference< XDataPointCustomLabelField > xCustomLabel = DataPointCustomLabelField::create( xContext ); // Store properties oox::PropertySet aPropertySet( xCustomLabel ); convertTextProperty( aPropertySet, getFormatter(), mrModel.mxText->mxTextBody ); pRun->getTextCharacterProperties().pushToPropSet( aPropertySet, getFilter() ); if (TextField* pField = dynamic_cast(pRun.get())) { DataPointCustomLabelFieldType eType = lcl_ConvertFieldNameToFieldEnum( pField->getType() ); if (eType == DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE && oaCellRange.has_value()) { xCustomLabel->setCellRange( oaCellRange.value() ); xCustomLabel->setString( oaLabelText.value_or("") ); xCustomLabel->setDataLabelsRange( true ); } else xCustomLabel->setString( pField->getText() ); xCustomLabel->setFieldType( eType ); xCustomLabel->setGuid( pField->getUuid() ); } else { xCustomLabel->setString( pRun->getText() ); xCustomLabel->setFieldType( DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT ); } aSequenceRange[ nPos++ ] = xCustomLabel; } if( nParagraphs > 1 && nPos < nSequenceSize ) { css::uno::Reference< XDataPointCustomLabelField > xCustomLabel = DataPointCustomLabelField::create( xContext ); xCustomLabel->setFieldType( DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_NEWLINE ); xCustomLabel->setString("\n"); aSequenceRange[ nPos++ ] = xCustomLabel; } } aPropSet.setProperty( PROP_CustomLabelFields, Any( aSequence ) ); convertTextProperty(aPropSet, getFormatter(), mrModel.mxText->mxTextBody); } } catch( Exception& ) { } } DataLabelsConverter::DataLabelsConverter( const ConverterRoot& rParent, DataLabelsModel& rModel ) : ConverterBase< DataLabelsModel >( rParent, rModel ) { } DataLabelsConverter::~DataLabelsConverter() { } namespace { /// Inherit text props (if not set) from text props (if set). void InheritFromDataLabelsTextProps(const DataLabelsModel& rLabels, const DataLabelModel& rLabel) { // See if contains text properties to inherit. if (!rLabels.mxTextProp.is() || rLabels.mxTextProp->getParagraphs().empty()) { return; } const std::shared_ptr& rLabelsParagraph = rLabels.mxTextProp->getParagraphs()[0]; // See if lacks text properties. if (rLabel.mxTextProp.is()) { return; } if (!rLabel.mxText || !rLabel.mxText->mxTextBody || rLabel.mxText->mxTextBody->getParagraphs().empty()) { return; } const std::shared_ptr& rLabelParagraph = rLabel.mxText->mxTextBody->getParagraphs()[0]; // Inherit rLabel.mxText's char props from rLabels.mxTextProp's char props. TextCharacterProperties aCharProps; aCharProps.assignUsed(rLabelsParagraph->getProperties().getTextCharacterProperties()); aCharProps.assignUsed(rLabelParagraph->getProperties().getTextCharacterProperties()); rLabelParagraph->getProperties().getTextCharacterProperties().assignUsed(aCharProps); } } void DataLabelsConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries, const TypeGroupConverter& rTypeGroup ) { PropertySet aPropSet( rxDataSeries ); if( !mrModel.mbDeleted ) { bool bMSO2007Doc = getFilter().isMSO2007Document(); bool bHasInternalData = getChartDocument()->hasInternalDataProvider(); lclConvertLabelFormatting( aPropSet, getFormatter(), mrModel, rTypeGroup, true, false, bHasInternalData, bMSO2007Doc ); if (mrModel.mxShapeProp) { // Import baseline border properties for these data labels. importBorderProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper()); uno::Reference xFactory(getChartDocument(), uno::UNO_QUERY); ModelObjectHelper& rHelper = getFilter().getModelObjectHelperForModel(xFactory); importFillProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper(), rHelper); } } // import leaderline of data labels if( !mrModel.mbShowLeaderLines ) aPropSet.setProperty( PROP_ShowCustomLeaderLines, false ); // data point label settings for (auto const& pointLabel : mrModel.maPointLabels) { if (pointLabel->maNumberFormat.maFormatCode.isEmpty()) pointLabel->maNumberFormat = mrModel.maNumberFormat; InheritFromDataLabelsTextProps(mrModel, *pointLabel); DataLabelConverter aLabelConv(*this, *pointLabel); aLabelConv.convertFromModel( rxDataSeries, rTypeGroup ); } } ErrorBarConverter::ErrorBarConverter( const ConverterRoot& rParent, ErrorBarModel& rModel ) : ConverterBase< ErrorBarModel >( rParent, rModel ) { } ErrorBarConverter::~ErrorBarConverter() { } void ErrorBarConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries ) { bool bShowPos = (mrModel.mnTypeId == XML_plus) || (mrModel.mnTypeId == XML_both); bool bShowNeg = (mrModel.mnTypeId == XML_minus) || (mrModel.mnTypeId == XML_both); if( !(bShowPos || bShowNeg) ) return; try { Reference< XPropertySet > xErrorBar( createInstance( "com.sun.star.chart2.ErrorBar" ), UNO_QUERY_THROW ); PropertySet aBarProp( xErrorBar ); // plus/minus bars aBarProp.setProperty( PROP_ShowPositiveError, bShowPos ); aBarProp.setProperty( PROP_ShowNegativeError, bShowNeg ); // type of displayed error namespace cssc = ::com::sun::star::chart; switch( mrModel.mnValueType ) { case XML_cust: { // #i87806# manual error bars aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::FROM_DATA ); // attach data sequences to error bar Reference< XDataSink > xDataSink( xErrorBar, UNO_QUERY ); if( xDataSink.is() ) { // create vector of all value sequences ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec; // add positive values if( bShowPos ) { Reference< XLabeledDataSequence > xValueSeq = createLabeledDataSequence( ErrorBarModel::PLUS ); if( xValueSeq.is() ) aLabeledSeqVec.push_back( xValueSeq ); } // add negative values if( bShowNeg ) { Reference< XLabeledDataSequence > xValueSeq = createLabeledDataSequence( ErrorBarModel::MINUS ); if( xValueSeq.is() ) aLabeledSeqVec.push_back( xValueSeq ); } // attach labeled data sequences to series if( aLabeledSeqVec.empty() ) xErrorBar.clear(); else xDataSink->setData( comphelper::containerToSequence( aLabeledSeqVec ) ); } } break; case XML_fixedVal: aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::ABSOLUTE ); aBarProp.setProperty( PROP_PositiveError, mrModel.mfValue ); aBarProp.setProperty( PROP_NegativeError, mrModel.mfValue ); break; case XML_percentage: aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::RELATIVE ); aBarProp.setProperty( PROP_PositiveError, mrModel.mfValue ); aBarProp.setProperty( PROP_NegativeError, mrModel.mfValue ); break; case XML_stdDev: aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::STANDARD_DEVIATION ); aBarProp.setProperty( PROP_Weight, mrModel.mfValue ); break; case XML_stdErr: aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::STANDARD_ERROR ); break; default: OSL_FAIL( "ErrorBarConverter::convertFromModel - unknown error bar type" ); xErrorBar.clear(); } // error bar formatting getFormatter().convertFrameFormatting( aBarProp, mrModel.mxShapeProp, OBJECTTYPE_ERRORBAR ); if( xErrorBar.is() ) { PropertySet aSeriesProp( rxDataSeries ); switch( mrModel.mnDirection ) { case XML_x: aSeriesProp.setProperty( PROP_ErrorBarX, xErrorBar ); break; case XML_y: aSeriesProp.setProperty( PROP_ErrorBarY, xErrorBar ); break; default: OSL_FAIL( "ErrorBarConverter::convertFromModel - invalid error bar direction" ); } } } catch( Exception& ) { OSL_FAIL( "ErrorBarConverter::convertFromModel - error while creating error bars" ); } } Reference< XLabeledDataSequence > ErrorBarConverter::createLabeledDataSequence( ErrorBarModel::SourceType eSourceType ) { OUString aRole; switch( eSourceType ) { case ErrorBarModel::PLUS: switch( mrModel.mnDirection ) { case XML_x: aRole = "error-bars-x-positive"; break; case XML_y: aRole = "error-bars-y-positive"; break; } break; case ErrorBarModel::MINUS: switch( mrModel.mnDirection ) { case XML_x: aRole = "error-bars-x-negative"; break; case XML_y: aRole = "error-bars-y-negative"; break; } break; } OSL_ENSURE( !aRole.isEmpty(), "ErrorBarConverter::createLabeledDataSequence - invalid error bar direction" ); return lclCreateLabeledDataSequence( *this, mrModel.maSources.get( eSourceType ).get(), aRole ); } TrendlineLabelConverter::TrendlineLabelConverter( const ConverterRoot& rParent, TrendlineLabelModel& rModel ) : ConverterBase< TrendlineLabelModel >( rParent, rModel ) { } TrendlineLabelConverter::~TrendlineLabelConverter() { } void TrendlineLabelConverter::convertFromModel( PropertySet& rPropSet ) { // formatting getFormatter().convertFormatting( rPropSet, mrModel.mxShapeProp, mrModel.mxTextProp, OBJECTTYPE_TRENDLINELABEL ); } TrendlineConverter::TrendlineConverter( const ConverterRoot& rParent, TrendlineModel& rModel ) : ConverterBase< TrendlineModel >( rParent, rModel ) { } TrendlineConverter::~TrendlineConverter() { } void TrendlineConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries ) { try { // trend line type OUString aServiceName; switch( mrModel.mnTypeId ) { case XML_exp: aServiceName = "com.sun.star.chart2.ExponentialRegressionCurve"; break; case XML_linear: aServiceName = "com.sun.star.chart2.LinearRegressionCurve"; break; case XML_log: aServiceName = "com.sun.star.chart2.LogarithmicRegressionCurve"; break; case XML_movingAvg: aServiceName = "com.sun.star.chart2.MovingAverageRegressionCurve"; break; case XML_poly: aServiceName = "com.sun.star.chart2.PolynomialRegressionCurve"; break; case XML_power: aServiceName = "com.sun.star.chart2.PotentialRegressionCurve"; break; default: OSL_FAIL( "TrendlineConverter::convertFromModel - unknown trendline type" ); } if( !aServiceName.isEmpty() ) { Reference< XRegressionCurve > xRegCurve( createInstance( aServiceName ), UNO_QUERY_THROW ); PropertySet aPropSet( xRegCurve ); // Name aPropSet.setProperty( PROP_CurveName, mrModel.maName ); aPropSet.setProperty( PROP_PolynomialDegree, mrModel.mnOrder ); aPropSet.setProperty( PROP_MovingAveragePeriod, mrModel.mnPeriod ); // Intercept bool hasIntercept = mrModel.mfIntercept.has_value(); aPropSet.setProperty( PROP_ForceIntercept, hasIntercept); if (hasIntercept) aPropSet.setProperty( PROP_InterceptValue, mrModel.mfIntercept.value()); // Extrapolation if (mrModel.mfForward.has_value()) aPropSet.setProperty( PROP_ExtrapolateForward, mrModel.mfForward.value() ); if (mrModel.mfBackward.has_value()) aPropSet.setProperty( PROP_ExtrapolateBackward, mrModel.mfBackward.value() ); // trendline formatting getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, OBJECTTYPE_TRENDLINE ); // #i83100# show equation and correlation coefficient PropertySet aLabelProp( xRegCurve->getEquationProperties() ); aLabelProp.setProperty( PROP_ShowEquation, mrModel.mbDispEquation ); aLabelProp.setProperty( PROP_ShowCorrelationCoefficient, mrModel.mbDispRSquared ); // #i83100# formatting of the equation text box if( mrModel.mbDispEquation || mrModel.mbDispRSquared ) { TrendlineLabelConverter aLabelConv( *this, mrModel.mxLabel.getOrCreate() ); aLabelConv.convertFromModel( aLabelProp ); } // unsupported: #i5085# manual trendline size // unsupported: #i34093# manual crossing point Reference< XRegressionCurveContainer > xRegCurveCont( rxDataSeries, UNO_QUERY_THROW ); xRegCurveCont->addRegressionCurve( xRegCurve ); } } catch( Exception& ) { OSL_FAIL( "TrendlineConverter::convertFromModel - error while creating trendline" ); } } DataPointConverter::DataPointConverter( const ConverterRoot& rParent, DataPointModel& rModel ) : ConverterBase< DataPointModel >( rParent, rModel ) { } DataPointConverter::~DataPointConverter() { } void DataPointConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries, const TypeGroupConverter& rTypeGroup, const SeriesModel& rSeries ) { bool bMSO2007Doc = getFilter().isMSO2007Document(); try { PropertySet aPropSet( rxDataSeries->getDataPointByIndex( mrModel.mnIndex ) ); // data point marker if( ( mrModel.monMarkerSymbol.has_value() && mrModel.monMarkerSymbol.value() != rSeries.mnMarkerSymbol ) || ( mrModel.monMarkerSize.has_value() && mrModel.monMarkerSize.value() != rSeries.mnMarkerSize ) ) rTypeGroup.convertMarker( aPropSet, mrModel.monMarkerSymbol.value_or( rSeries.mnMarkerSymbol ), mrModel.monMarkerSize.value_or( rSeries.mnMarkerSize ), mrModel.mxMarkerProp ); // data point pie explosion if( mrModel.monExplosion.has_value() && mrModel.monExplosion.value() != rSeries.mnExplosion ) rTypeGroup.convertPieExplosion( aPropSet, mrModel.monExplosion.value() ); // point formatting if( mrModel.mxShapeProp.is() ) { if( rTypeGroup.getTypeInfo().mbPictureOptions ) getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, mrModel.mxPicOptions.getOrCreate(bMSO2007Doc), rTypeGroup.getSeriesObjectType(), rSeries.mnIndex ); else getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, rTypeGroup.getSeriesObjectType(), rSeries.mnIndex ); } else if (rSeries.mxShapeProp.is()) { getFormatter().convertFrameFormatting( aPropSet, rSeries.mxShapeProp, rTypeGroup.getSeriesObjectType(), rSeries.mnIndex ); } } catch( Exception& ) { } } SeriesConverter::SeriesConverter( const ConverterRoot& rParent, SeriesModel& rModel ) : ConverterBase< SeriesModel >( rParent, rModel ) { } SeriesConverter::~SeriesConverter() { } Reference< XLabeledDataSequence > SeriesConverter::createCategorySequence( const OUString& rRole ) { return createLabeledDataSequence(SeriesModel::CATEGORIES, rRole, false); } Reference< XLabeledDataSequence > SeriesConverter::createValueSequence( const OUString& rRole ) { return createLabeledDataSequence( SeriesModel::VALUES, rRole, true ); } Reference< XDataSeries > SeriesConverter::createDataSeries( const TypeGroupConverter& rTypeGroup, bool bVaryColorsByPoint ) { const TypeGroupInfo& rTypeInfo = rTypeGroup.getTypeInfo(); // create the data series object Reference< XDataSeries > xDataSeries( createInstance( "com.sun.star.chart2.DataSeries" ), UNO_QUERY ); PropertySet aSeriesProp( xDataSeries ); // attach data and title sequences to series sal_Int32 nDataPointCount = 0; Reference< XDataSink > xDataSink( xDataSeries, UNO_QUERY ); if( xDataSink.is() ) { // create vector of all value sequences ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec; // add Y values Reference< XLabeledDataSequence > xYValueSeq = createValueSequence( "values-y" ); if( xYValueSeq.is() ) { aLabeledSeqVec.push_back( xYValueSeq ); Reference< XDataSequence > xValues = xYValueSeq->getValues(); if( xValues.is() ) nDataPointCount = xValues->getData().getLength(); if (!nDataPointCount) // No values present. Don't create a data series. return Reference(); } // add X values of scatter and bubble charts if( !rTypeInfo.mbCategoryAxis ) { Reference< XLabeledDataSequence > xXValueSeq = createCategorySequence( "values-x" ); if( xXValueSeq.is() ) aLabeledSeqVec.push_back( xXValueSeq ); // add size values of bubble charts if( rTypeInfo.meTypeId == TYPEID_BUBBLE ) { Reference< XLabeledDataSequence > xSizeValueSeq = createLabeledDataSequence( SeriesModel::POINTS, "values-size", true ); if( xSizeValueSeq.is() ) aLabeledSeqVec.push_back( xSizeValueSeq ); } } // attach labeled data sequences to series if( !aLabeledSeqVec.empty() ) xDataSink->setData( comphelper::containerToSequence( aLabeledSeqVec ) ); } // error bars for (auto const& errorBar : mrModel.maErrorBars) { ErrorBarConverter aErrorBarConv(*this, *errorBar); aErrorBarConv.convertFromModel( xDataSeries ); } // trendlines for (auto const& trendLine : mrModel.maTrendlines) { TrendlineConverter aTrendlineConv(*this, *trendLine); aTrendlineConv.convertFromModel( xDataSeries ); } // data point markers rTypeGroup.convertMarker( aSeriesProp, mrModel.mnMarkerSymbol, mrModel.mnMarkerSize, mrModel.mxMarkerProp ); #if OOX_CHART_SMOOTHED_PER_SERIES // #i66858# smoothed series lines rTypeGroup.convertLineSmooth( aSeriesProp, mrModel.mbSmooth ); #endif // 3D bar style (not possible to set at chart type -> set at all series) rTypeGroup.convertBarGeometry( aSeriesProp, mrModel.monShape.value_or( rTypeGroup.getModel().mnShape ) ); // pie explosion (restricted to [0%,100%] in Chart2) rTypeGroup.convertPieExplosion( aSeriesProp, mrModel.mnExplosion ); // series formatting ObjectFormatter& rFormatter = getFormatter(); ObjectType eObjType = rTypeGroup.getSeriesObjectType(); bool bMSO2007Doc = getFilter().isMSO2007Document(); if( rTypeInfo.mbPictureOptions ) rFormatter.convertFrameFormatting( aSeriesProp, mrModel.mxShapeProp, mrModel.mxPicOptions.getOrCreate(bMSO2007Doc), eObjType, mrModel.mnIndex ); else rFormatter.convertFrameFormatting( aSeriesProp, mrModel.mxShapeProp, eObjType, mrModel.mnIndex ); // set the (unused) property default value used by the Chart2 templates (true for pie/doughnut charts) bool bIsPie = rTypeInfo.meTypeCategory == TYPECATEGORY_PIE; aSeriesProp.setProperty( PROP_VaryColorsByPoint, bVaryColorsByPoint ); // own area formatting for every data point (TODO: varying line color not supported) // #i91271# always set area formatting for every point in pie/doughnut charts to override their automatic point formatting if( bIsPie || (bVaryColorsByPoint && rTypeGroup.isSeriesFrameFormat() && ObjectFormatter::isAutomaticFill( mrModel.mxShapeProp )) ) { /* Set the series point number as color cycle size at the object formatter to get correct start-shade/end-tint. TODO: in doughnut charts, the sizes of the series may vary, need to use the maximum point count of all series. */ sal_Int32 nOldMax = rFormatter.getMaxSeriesIndex(); if( bVaryColorsByPoint ) rFormatter.setMaxSeriesIndex( nDataPointCount - 1 ); for( sal_Int32 nIndex = 0; nIndex < nDataPointCount; ++nIndex ) { try { PropertySet aPointProp( xDataSeries->getDataPointByIndex( nIndex ) ); rFormatter.convertAutomaticFill( aPointProp, eObjType, bVaryColorsByPoint ? nIndex : mrModel.mnIndex ); } catch( Exception& ) { } } rFormatter.setMaxSeriesIndex( nOldMax ); } // data point settings for (auto const& point : mrModel.maPoints) { DataPointConverter aPointConv(*this, *point); aPointConv.convertFromModel( xDataSeries, rTypeGroup, mrModel ); } /* Series data label settings. If and only if the series does not contain a c:dLbls element, then the c:dLbls element of the parent chart type is used (data label settings of the parent chart type are *not* merged into own existing data label settings). */ ModelRef< DataLabelsModel > xLabels = mrModel.mxLabels.is() ? mrModel.mxLabels : rTypeGroup.getModel().mxLabels; if( xLabels.is() ) { if( xLabels->maNumberFormat.maFormatCode.isEmpty() ) { // Use number format code from Value series DataSourceModel* pValues = mrModel.maSources.get( SeriesModel::VALUES ).get(); if( pValues ) xLabels->maNumberFormat.maFormatCode = pValues->mxDataSeq->maFormatCode; } DataLabelsConverter aLabelsConv( *this, *xLabels ); aLabelsConv.convertFromModel( xDataSeries, rTypeGroup ); } return xDataSeries; } // private -------------------------------------------------------------------- Reference< XLabeledDataSequence > SeriesConverter::createLabeledDataSequence( SeriesModel::SourceType eSourceType, const OUString& rRole, bool bUseTextLabel ) { DataSourceModel* pValues = mrModel.maSources.get( eSourceType ).get(); TextModel* pTitle = bUseTextLabel ? mrModel.mxText.get() : nullptr; return lclCreateLabeledDataSequence( *this, pValues, rRole, pTitle ); } } // namespace oox /* vim:set shiftwidth=4 softtabstop=4 expandtab: */