/************************************************************** * * 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 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ #include "oox/drawingml/chart/seriesconverter.hxx" #include #include #include #include #include #include #include #include "oox/drawingml/chart/datasourceconverter.hxx" #include "oox/drawingml/chart/seriesmodel.hxx" #include "oox/drawingml/chart/titleconverter.hxx" #include "oox/drawingml/chart/typegroupconverter.hxx" #include "oox/drawingml/chart/typegroupmodel.hxx" #include "oox/helper/containerhelper.hxx" namespace oox { namespace drawingml { namespace chart { // ============================================================================ 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; using ::rtl::OUString; // ============================================================================ namespace { Reference< XLabeledDataSequence > lclCreateLabeledDataSequence( const ConverterRoot& rParent, DataSourceModel* pValues, const OUString& rRole, TextModel* pTitle = 0 ) { // 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( CREATE_OUSTRING( "label" ) ); } // create the labeled data sequence, if values or title are present Reference< XLabeledDataSequence > xLabeledSeq; if( xValueSeq.is() || xTitleSeq.is() ) { xLabeledSeq.set( rParent.createInstance( CREATE_OUSTRING( "com.sun.star.chart2.data.LabeledDataSequence" ) ), UNO_QUERY ); if( xLabeledSeq.is() ) { xLabeledSeq->setValues( xValueSeq ); xLabeledSeq->setLabel( xTitleSeq ); } } return xLabeledSeq; } void lclConvertLabelFormatting( PropertySet& rPropSet, ObjectFormatter& rFormatter, const DataLabelModelBase& rDataLabel, const TypeGroupConverter& rTypeGroup, bool bDataSeriesLabel ) { 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 = rDataLabel.moaSeparator.has() || rDataLabel.monLabelPos.has() || rDataLabel.mobShowCatName.has() || rDataLabel.mobShowLegendKey.has() || rDataLabel.mobShowPercent.has() || rDataLabel.mobShowSerName.has() || rDataLabel.mobShowVal.has(); bool bShowValue = !rDataLabel.mbDeleted && rDataLabel.mobShowVal.get( false ); bool bShowPercent = !rDataLabel.mbDeleted && rDataLabel.mobShowPercent.get( false ) && (rTypeInfo.meTypeCategory == TYPECATEGORY_PIE); bool bShowCateg = !rDataLabel.mbDeleted && rDataLabel.mobShowCatName.get( false ); bool bShowSymbol = !rDataLabel.mbDeleted && rDataLabel.mobShowLegendKey.get( false ); // type of attached label if( bHasAnyElement || rDataLabel.mbDeleted ) { DataPointLabel aPointLabel( bShowValue, bShowPercent, bShowCateg, bShowSymbol ); rPropSet.setProperty( PROP_Label, aPointLabel ); } if( !rDataLabel.mbDeleted ) { // data label number format (percentage format wins over value format) rFormatter.convertNumberFormat( rPropSet, rDataLabel.maNumberFormat, bShowPercent ); // data label text formatting (frame formatting not supported by Chart2) rFormatter.convertTextFormatting( rPropSet, rDataLabel.mxTextProp, OBJECTTYPE_DATALABEL ); rFormatter.convertTextRotation( rPropSet, rDataLabel.mxTextProp, false ); // data label separator (do not overwrite series separator, if no explicit point separator is present) if( bDataSeriesLabel || rDataLabel.moaSeparator.has() ) rPropSet.setProperty( PROP_LabelSeparator, rDataLabel.moaSeparator.get( CREATE_OUSTRING( "; " ) ) ); // data label placement (do not overwrite series placement, if no explicit point placement is present) if( bDataSeriesLabel || rDataLabel.monLabelPos.has() ) { namespace csscd = ::com::sun::star::chart::DataLabelPlacement; sal_Int32 nPlacement = rTypeInfo.mnDefLabelPos; switch( rDataLabel.monLabelPos.get( 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; } rPropSet.setProperty( PROP_LabelPlacement, nPlacement ); } } } } // 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() ) try { PropertySet aPropSet( rxDataSeries->getDataPointByIndex( mrModel.mnIndex ) ); lclConvertLabelFormatting( aPropSet, getFormatter(), mrModel, rTypeGroup, false ); } catch( Exception& ) { } } // ============================================================================ DataLabelsConverter::DataLabelsConverter( const ConverterRoot& rParent, DataLabelsModel& rModel ) : ConverterBase< DataLabelsModel >( rParent, rModel ) { } DataLabelsConverter::~DataLabelsConverter() { } void DataLabelsConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries, const TypeGroupConverter& rTypeGroup ) { if( !mrModel.mbDeleted ) { PropertySet aPropSet( rxDataSeries ); lclConvertLabelFormatting( aPropSet, getFormatter(), mrModel, rTypeGroup, true ); } // data point label settings for( DataLabelsModel::DataLabelVector::iterator aIt = mrModel.maPointLabels.begin(), aEnd = mrModel.maPointLabels.end(); aIt != aEnd; ++aIt ) { DataLabelConverter aLabelConv( *this, **aIt ); 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 ) try { Reference< XPropertySet > xErrorBar( createInstance( CREATE_OUSTRING( "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 erorr 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( ContainerHelper::vectorToSequence( 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_ENSURE( false, "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_ENSURE( false, "ErrorBarConverter::convertFromModel - invalid error bar direction" ); } } } catch( Exception& ) { OSL_ENSURE( false, "ErrorBarConverter::convertFromModel - error while creating error bars" ); } } // private -------------------------------------------------------------------- Reference< XLabeledDataSequence > ErrorBarConverter::createLabeledDataSequence( ErrorBarModel::SourceType eSourceType ) { OUString aRole; switch( eSourceType ) { case ErrorBarModel::PLUS: switch( mrModel.mnDirection ) { case XML_x: aRole = CREATE_OUSTRING( "error-bars-x-positive" ); break; case XML_y: aRole = CREATE_OUSTRING( "error-bars-y-positive" ); break; } break; case ErrorBarModel::MINUS: switch( mrModel.mnDirection ) { case XML_x: aRole = CREATE_OUSTRING( "error-bars-x-negative" ); break; case XML_y: aRole = CREATE_OUSTRING( "error-bars-y-negative" ); break; } break; } OSL_ENSURE( aRole.getLength() > 0, "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 = CREATE_OUSTRING( "com.sun.star.chart2.ExponentialRegressionCurve" ); break; case XML_linear: aServiceName = CREATE_OUSTRING( "com.sun.star.chart2.LinearRegressionCurve" ); break; case XML_log: aServiceName = CREATE_OUSTRING( "com.sun.star.chart2.LogarithmicRegressionCurve" ); break; case XML_movingAvg: /* #i66819# moving average trendlines not supported */ break; case XML_poly: /* #i20819# polynomial trendlines not supported */ break; case XML_power: aServiceName = CREATE_OUSTRING( "com.sun.star.chart2.PotentialRegressionCurve" ); break; default: OSL_ENSURE( false, "TrendlineConverter::convertFromModel - unknown trendline type" ); } if( aServiceName.getLength() > 0 ) { Reference< XRegressionCurve > xRegCurve( createInstance( aServiceName ), UNO_QUERY_THROW ); PropertySet aPropSet( xRegCurve ); // 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_ENSURE( false, "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 ) { try { PropertySet aPropSet( rxDataSeries->getDataPointByIndex( mrModel.mnIndex ) ); // data point marker if( mrModel.monMarkerSymbol.differsFrom( rSeries.mnMarkerSymbol ) || mrModel.monMarkerSize.differsFrom( rSeries.mnMarkerSize ) ) rTypeGroup.convertMarker( aPropSet, mrModel.monMarkerSymbol.get( rSeries.mnMarkerSymbol ), mrModel.monMarkerSize.get( rSeries.mnMarkerSize ) ); // data point pie explosion if( mrModel.monExplosion.differsFrom( rSeries.mnExplosion ) ) rTypeGroup.convertPieExplosion( aPropSet, mrModel.monExplosion.get() ); // point formatting if( mrModel.mxShapeProp.is() ) { if( rTypeGroup.getTypeInfo().mbPictureOptions ) getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, mrModel.mxPicOptions.getOrCreate(), rTypeGroup.getSeriesObjectType(), rSeries.mnIndex ); else getFormatter().convertFrameFormatting( aPropSet, mrModel.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( CREATE_OUSTRING( "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( CREATE_OUSTRING( "values-y" ) ); if( xYValueSeq.is() ) { aLabeledSeqVec.push_back( xYValueSeq ); Reference< XDataSequence > xValues = xYValueSeq->getValues(); if( xValues.is() ) nDataPointCount = xValues->getData().getLength(); } // add X values of scatter and bubble charts if( !rTypeInfo.mbCategoryAxis ) { Reference< XLabeledDataSequence > xXValueSeq = createCategorySequence( CREATE_OUSTRING( "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, CREATE_OUSTRING( "values-size" ), true ); if( xSizeValueSeq.is() ) aLabeledSeqVec.push_back( xSizeValueSeq ); } } // attach labeled data sequences to series if( !aLabeledSeqVec.empty() ) xDataSink->setData( ContainerHelper::vectorToSequence( aLabeledSeqVec ) ); } // error bars for( SeriesModel::ErrorBarVector::iterator aIt = mrModel.maErrorBars.begin(), aEnd = mrModel.maErrorBars.end(); aIt != aEnd; ++aIt ) { ErrorBarConverter aErrorBarConv( *this, **aIt ); aErrorBarConv.convertFromModel( xDataSeries ); } // trendlines for( SeriesModel::TrendlineVector::iterator aIt = mrModel.maTrendlines.begin(), aEnd = mrModel.maTrendlines.end(); aIt != aEnd; ++aIt ) { TrendlineConverter aTrendlineConv( *this, **aIt ); aTrendlineConv.convertFromModel( xDataSeries ); } // data point markers rTypeGroup.convertMarker( aSeriesProp, mrModel.mnMarkerSymbol, mrModel.mnMarkerSize ); #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.get( 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(); if( rTypeInfo.mbPictureOptions ) rFormatter.convertFrameFormatting( aSeriesProp, mrModel.mxShapeProp, mrModel.mxPicOptions.getOrCreate(), 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, bIsPie ); // 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( SeriesModel::DataPointVector::iterator aIt = mrModel.maPoints.begin(), aEnd = mrModel.maPoints.end(); aIt != aEnd; ++aIt ) { DataPointConverter aPointConv( *this, **aIt ); 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() ) { 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() : 0; return lclCreateLabeledDataSequence( *this, pValues, rRole, pTitle ); } // ============================================================================ } // namespace chart } // namespace drawingml } // namespace oox