diff options
Diffstat (limited to 'sc/source/filter/excel/xichart.cxx')
-rw-r--r-- | sc/source/filter/excel/xichart.cxx | 4415 |
1 files changed, 4415 insertions, 0 deletions
diff --git a/sc/source/filter/excel/xichart.cxx b/sc/source/filter/excel/xichart.cxx new file mode 100644 index 000000000..dc14076b7 --- /dev/null +++ b/sc/source/filter/excel/xichart.cxx @@ -0,0 +1,4415 @@ +/* -*- 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 <xichart.hxx> + +#include <algorithm> +#include <memory> +#include <utility> + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/drawing/Direction3D.hpp> +#include <com/sun/star/drawing/ProjectionMode.hpp> +#include <com/sun/star/drawing/ShadeMode.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/chart/ChartAxisArrangeOrderType.hpp> +#include <com/sun/star/chart/ChartAxisLabelPosition.hpp> +#include <com/sun/star/chart/ChartAxisMarkPosition.hpp> +#include <com/sun/star/chart/ChartAxisPosition.hpp> +#include <com/sun/star/chart/ChartLegendExpansion.hpp> +#include <com/sun/star/chart/TimeInterval.hpp> +#include <com/sun/star/chart/TimeUnit.hpp> +#include <com/sun/star/chart/XChartDocument.hpp> +#include <com/sun/star/chart/XDiagramPositioning.hpp> +#include <com/sun/star/chart/DataLabelPlacement.hpp> +#include <com/sun/star/chart/ErrorBarStyle.hpp> +#include <com/sun/star/chart/MissingValueTreatment.hpp> +#include <com/sun/star/chart2/LinearRegressionCurve.hpp> +#include <com/sun/star/chart2/ExponentialRegressionCurve.hpp> +#include <com/sun/star/chart2/LogarithmicRegressionCurve.hpp> +#include <com/sun/star/chart2/PotentialRegressionCurve.hpp> +#include <com/sun/star/chart2/PolynomialRegressionCurve.hpp> +#include <com/sun/star/chart2/MovingAverageRegressionCurve.hpp> +#include <com/sun/star/chart2/CartesianCoordinateSystem2d.hpp> +#include <com/sun/star/chart2/CartesianCoordinateSystem3d.hpp> +#include <com/sun/star/chart2/FormattedString.hpp> +#include <com/sun/star/chart2/LogarithmicScaling.hpp> +#include <com/sun/star/chart2/LinearScaling.hpp> +#include <com/sun/star/chart2/PolarCoordinateSystem2d.hpp> +#include <com/sun/star/chart2/PolarCoordinateSystem3d.hpp> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/chart2/XDiagram.hpp> +#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp> +#include <com/sun/star/chart2/XChartTypeContainer.hpp> +#include <com/sun/star/chart2/XDataSeriesContainer.hpp> +#include <com/sun/star/chart2/XRegressionCurveContainer.hpp> +#include <com/sun/star/chart2/XTitled.hpp> +#include <com/sun/star/chart2/AxisType.hpp> +#include <com/sun/star/chart2/CurveStyle.hpp> +#include <com/sun/star/chart2/DataPointGeometry3D.hpp> +#include <com/sun/star/chart2/DataPointLabel.hpp> +#include <com/sun/star/chart2/LegendPosition.hpp> +#include <com/sun/star/chart2/StackingDirection.hpp> +#include <com/sun/star/chart2/TickmarkStyle.hpp> +#include <com/sun/star/chart2/RelativePosition.hpp> +#include <com/sun/star/chart2/RelativeSize.hpp> +#include <com/sun/star/chart2/data/XDataProvider.hpp> +#include <com/sun/star/chart2/data/XDataReceiver.hpp> +#include <com/sun/star/chart2/data/XDataSink.hpp> +#include <com/sun/star/chart2/data/LabeledDataSequence.hpp> +#include <comphelper/processfactory.hxx> +#include <o3tl/numeric.hxx> +#include <o3tl/unit_conversion.hxx> +#include <sfx2/objsh.hxx> +#include <svx/svdpage.hxx> +#include <svx/unoapi.hxx> +#include <sal/log.hxx> +#include <tools/helpers.hxx> + +#include <document.hxx> +#include <drwlayer.hxx> +#include <tokenarray.hxx> +#include <compiler.hxx> +#include <reftokenhelper.hxx> +#include <chartlis.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <xltracer.hxx> +#include <xltools.hxx> +#include <xistream.hxx> +#include <xiformula.hxx> +#include <xistyle.hxx> +#include <xipage.hxx> +#include <xiview.hxx> + +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::frame::XModel; +using ::com::sun::star::util::XNumberFormatsSupplier; +using ::com::sun::star::drawing::XDrawPage; +using ::com::sun::star::drawing::XDrawPageSupplier; +using ::com::sun::star::drawing::XShape; + +using namespace ::com::sun::star::chart2; + +using ::com::sun::star::chart2::data::XDataProvider; +using ::com::sun::star::chart2::data::XDataReceiver; +using ::com::sun::star::chart2::data::XDataSequence; +using ::com::sun::star::chart2::data::XDataSink; +using ::com::sun::star::chart2::data::XLabeledDataSequence; +using ::com::sun::star::chart2::data::LabeledDataSequence; + +using ::formula::FormulaToken; +using ::formula::FormulaTokenArrayPlainIterator; +using ::std::unique_ptr; + +namespace cssc = ::com::sun::star::chart; +namespace cssc2 = ::com::sun::star::chart2; + +// Helpers ==================================================================== + +namespace { + +XclImpStream& operator>>( XclImpStream& rStrm, XclChRectangle& rRect ) +{ + rRect.mnX = rStrm.ReadInt32(); + rRect.mnY = rStrm.ReadInt32(); + rRect.mnWidth = rStrm.ReadInt32(); + rRect.mnHeight = rStrm.ReadInt32(); + return rStrm; +} + +void lclSetValueOrClearAny( Any& rAny, double fValue, bool bClear ) +{ + if( bClear ) + rAny.clear(); + else + rAny <<= fValue; +} + +void lclSetExpValueOrClearAny( Any& rAny, double fValue, bool bLogScale, bool bClear ) +{ + if( !bClear && bLogScale ) + fValue = pow( 10.0, fValue ); + lclSetValueOrClearAny( rAny, fValue, bClear ); +} + +double lclGetSerialDay( const XclImpRoot& rRoot, sal_uInt16 nValue, sal_uInt16 nTimeUnit ) +{ + switch( nTimeUnit ) + { + case EXC_CHDATERANGE_DAYS: + return nValue; + case EXC_CHDATERANGE_MONTHS: + return rRoot.GetDoubleFromDateTime( Date( 1, static_cast< sal_uInt16 >( 1 + nValue % 12 ), static_cast< sal_uInt16 >( rRoot.GetBaseYear() + nValue / 12 ) ) ); + case EXC_CHDATERANGE_YEARS: + return rRoot.GetDoubleFromDateTime( Date( 1, 1, static_cast< sal_uInt16 >( rRoot.GetBaseYear() + nValue ) ) ); + default: + OSL_ENSURE( false, "lclGetSerialDay - unexpected time unit" ); + } + return nValue; +} + +void lclConvertTimeValue( const XclImpRoot& rRoot, Any& rAny, sal_uInt16 nValue, bool bAuto, sal_uInt16 nTimeUnit ) +{ + if( bAuto ) + rAny.clear(); + else + rAny <<= lclGetSerialDay( rRoot, nValue, nTimeUnit ); +} + +sal_Int32 lclGetApiTimeUnit( sal_uInt16 nTimeUnit ) +{ + switch( nTimeUnit ) + { + case EXC_CHDATERANGE_DAYS: return cssc::TimeUnit::DAY; + case EXC_CHDATERANGE_MONTHS: return cssc::TimeUnit::MONTH; + case EXC_CHDATERANGE_YEARS: return cssc::TimeUnit::YEAR; + default: OSL_ENSURE( false, "lclGetApiTimeUnit - unexpected time unit" ); + } + return cssc::TimeUnit::DAY; +} + +void lclConvertTimeInterval( Any& rInterval, sal_uInt16 nValue, bool bAuto, sal_uInt16 nTimeUnit ) +{ + if( bAuto || (nValue == 0) ) + rInterval.clear(); + else + rInterval <<= cssc::TimeInterval( nValue, lclGetApiTimeUnit( nTimeUnit ) ); +} + +} // namespace + +// Common ===================================================================== + +/** Stores global data needed in various classes of the Chart import filter. */ +struct XclImpChRootData : public XclChRootData +{ + XclImpChChart& mrChartData; /// The chart data object. + + explicit XclImpChRootData( XclImpChChart& rChartData ) : mrChartData( rChartData ) {} +}; + +XclImpChRoot::XclImpChRoot( const XclImpRoot& rRoot, XclImpChChart& rChartData ) : + XclImpRoot( rRoot ), + mxChData( std::make_shared<XclImpChRootData>( rChartData ) ) +{ +} + +XclImpChRoot::~XclImpChRoot() +{ +} + +XclImpChChart& XclImpChRoot::GetChartData() const +{ + return mxChData->mrChartData; +} + +const XclChTypeInfo& XclImpChRoot::GetChartTypeInfo( XclChTypeId eType ) const +{ + return mxChData->mxTypeInfoProv->GetTypeInfo( eType ); +} + +const XclChTypeInfo& XclImpChRoot::GetChartTypeInfo( sal_uInt16 nRecId ) const +{ + return mxChData->mxTypeInfoProv->GetTypeInfoFromRecId( nRecId ); +} + +const XclChFormatInfo& XclImpChRoot::GetFormatInfo( XclChObjectType eObjType ) const +{ + return mxChData->mxFmtInfoProv->GetFormatInfo( eObjType ); +} + +Color XclImpChRoot::GetFontAutoColor() const +{ + return GetPalette().GetColor( EXC_COLOR_CHWINDOWTEXT ); +} + +Color XclImpChRoot::GetSeriesLineAutoColor( sal_uInt16 nFormatIdx ) const +{ + return GetPalette().GetColor( XclChartHelper::GetSeriesLineAutoColorIdx( nFormatIdx ) ); +} + +Color XclImpChRoot::GetSeriesFillAutoColor( sal_uInt16 nFormatIdx ) const +{ + const XclImpPalette& rPal = GetPalette(); + Color aColor = rPal.GetColor( XclChartHelper::GetSeriesFillAutoColorIdx( nFormatIdx ) ); + sal_uInt8 nTrans = XclChartHelper::GetSeriesFillAutoTransp( nFormatIdx ); + return ScfTools::GetMixedColor( aColor, rPal.GetColor( EXC_COLOR_CHWINDOWBACK ), nTrans ); +} + +void XclImpChRoot::InitConversion( const Reference<XChartDocument>& xChartDoc, const tools::Rectangle& rChartRect ) const +{ + // create formatting object tables + mxChData->InitConversion( GetRoot(), xChartDoc, rChartRect ); + + // lock the model to suppress any internal updates + if( xChartDoc.is() ) + xChartDoc->lockControllers(); + + SfxObjectShell* pDocShell = GetDocShell(); + Reference< XDataReceiver > xDataRec( xChartDoc, UNO_QUERY ); + if( pDocShell && xDataRec.is() ) + { + // create and register a data provider + Reference< XDataProvider > xDataProv( + ScfApiHelper::CreateInstance( pDocShell, SERVICE_CHART2_DATAPROVIDER ), UNO_QUERY ); + if( xDataProv.is() ) + xDataRec->attachDataProvider( xDataProv ); + // attach the number formatter + Reference< XNumberFormatsSupplier > xNumFmtSupp( pDocShell->GetModel(), UNO_QUERY ); + if( xNumFmtSupp.is() ) + xDataRec->attachNumberFormatsSupplier( xNumFmtSupp ); + } +} + +void XclImpChRoot::FinishConversion( XclImpDffConverter& rDffConv ) const +{ + rDffConv.Progress( EXC_CHART_PROGRESS_SIZE ); + // unlock the model + Reference< XModel > xModel = mxChData->mxChartDoc; + if( xModel.is() ) + xModel->unlockControllers(); + rDffConv.Progress( EXC_CHART_PROGRESS_SIZE ); + + mxChData->FinishConversion(); +} + +Reference< XDataProvider > XclImpChRoot::GetDataProvider() const +{ + return mxChData->mxChartDoc->getDataProvider(); +} + +Reference< XShape > XclImpChRoot::GetTitleShape( const XclChTextKey& rTitleKey ) const +{ + return mxChData->GetTitleShape( rTitleKey ); +} + +sal_Int32 XclImpChRoot::CalcHmmFromChartX( sal_Int32 nPosX ) const +{ + return static_cast< sal_Int32 >( mxChData->mfUnitSizeX * nPosX + mxChData->mnBorderGapX + 0.5 ); +} + +sal_Int32 XclImpChRoot::CalcHmmFromChartY( sal_Int32 nPosY ) const +{ + return static_cast< sal_Int32 >( mxChData->mfUnitSizeY * nPosY + mxChData->mnBorderGapY + 0.5 ); +} + +css::awt::Rectangle XclImpChRoot::CalcHmmFromChartRect( const XclChRectangle& rRect ) const +{ + return css::awt::Rectangle( + CalcHmmFromChartX( rRect.mnX ), + CalcHmmFromChartY( rRect.mnY ), + CalcHmmFromChartX( rRect.mnWidth ), + CalcHmmFromChartY( rRect.mnHeight ) ); +} + +double XclImpChRoot::CalcRelativeFromHmmX( sal_Int32 nPosX ) const +{ + const tools::Long nWidth = mxChData->maChartRect.GetWidth(); + if (!nWidth) + throw o3tl::divide_by_zero(); + return static_cast<double>(nPosX) / nWidth; +} + +double XclImpChRoot::CalcRelativeFromHmmY( sal_Int32 nPosY ) const +{ + const tools::Long nHeight = mxChData->maChartRect.GetHeight(); + if (!nHeight) + throw o3tl::divide_by_zero(); + return static_cast<double >(nPosY) / nHeight; +} + +double XclImpChRoot::CalcRelativeFromChartX( sal_Int32 nPosX ) const +{ + return CalcRelativeFromHmmX( CalcHmmFromChartX( nPosX ) ); +} + +double XclImpChRoot::CalcRelativeFromChartY( sal_Int32 nPosY ) const +{ + return CalcRelativeFromHmmY( CalcHmmFromChartY( nPosY ) ); +} + +void XclImpChRoot::ConvertLineFormat( ScfPropertySet& rPropSet, + const XclChLineFormat& rLineFmt, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().WriteLineProperties( + rPropSet, *mxChData->mxLineDashTable, rLineFmt, ePropMode ); +} + +void XclImpChRoot::ConvertAreaFormat( ScfPropertySet& rPropSet, + const XclChAreaFormat& rAreaFmt, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().WriteAreaProperties( rPropSet, rAreaFmt, ePropMode ); +} + +void XclImpChRoot::ConvertEscherFormat( ScfPropertySet& rPropSet, + const XclChEscherFormat& rEscherFmt, const XclChPicFormat* pPicFmt, + sal_uInt32 nDffFillType, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().WriteEscherProperties( rPropSet, + *mxChData->mxGradientTable, *mxChData->mxBitmapTable, + rEscherFmt, pPicFmt, nDffFillType, ePropMode ); +} + +void XclImpChRoot::ConvertFont( ScfPropertySet& rPropSet, + sal_uInt16 nFontIdx, const Color* pFontColor ) const +{ + GetFontBuffer().WriteFontProperties( rPropSet, EXC_FONTPROPSET_CHART, nFontIdx, pFontColor ); +} + +void XclImpChRoot::ConvertPieRotation( ScfPropertySet& rPropSet, sal_uInt16 nAngle ) +{ + sal_Int32 nApiRot = (450 - (nAngle % 360)) % 360; + rPropSet.SetProperty( EXC_CHPROP_STARTINGANGLE, nApiRot ); +} + +XclImpChGroupBase::~XclImpChGroupBase() +{ +} + +void XclImpChGroupBase::ReadRecordGroup( XclImpStream& rStrm ) +{ + // read contents of the header record + ReadHeaderRecord( rStrm ); + + // only read sub records, if the next record is a CHBEGIN + if( rStrm.GetNextRecId() != EXC_ID_CHBEGIN ) + return; + + // read the CHBEGIN record, may be used for special initial processing + rStrm.StartNextRecord(); + ReadSubRecord( rStrm ); + + // read the nested records + bool bLoop = true; + while( bLoop && rStrm.StartNextRecord() ) + { + sal_uInt16 nRecId = rStrm.GetRecId(); + bLoop = nRecId != EXC_ID_CHEND; + // skip unsupported nested blocks + if( nRecId == EXC_ID_CHBEGIN ) + SkipBlock( rStrm ); + else + ReadSubRecord( rStrm ); + } + /* Returns with current CHEND record or unchanged stream, if no record + group present. In every case another call to StartNextRecord() will go + to next record of interest. */ +} + +void XclImpChGroupBase::SkipBlock( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecId() == EXC_ID_CHBEGIN, "XclImpChGroupBase::SkipBlock - no CHBEGIN record" ); + // do nothing if current record is not CHBEGIN + bool bLoop = rStrm.GetRecId() == EXC_ID_CHBEGIN; + while( bLoop && rStrm.StartNextRecord() ) + { + sal_uInt16 nRecId = rStrm.GetRecId(); + bLoop = nRecId != EXC_ID_CHEND; + // skip nested record groups + if( nRecId == EXC_ID_CHBEGIN ) + SkipBlock( rStrm ); + } +} + +// Frame formatting =========================================================== + +void XclImpChFramePos::ReadChFramePos( XclImpStream& rStrm ) +{ + maData.mnTLMode = rStrm.ReaduInt16(); + maData.mnBRMode = rStrm.ReaduInt16(); + /* According to the spec, the upper 16 bits of all members in the + rectangle are unused and may contain garbage. */ + maData.maRect.mnX = rStrm.ReadInt16(); rStrm.Ignore( 2 ); + maData.maRect.mnY = rStrm.ReadInt16(); rStrm.Ignore( 2 ); + maData.maRect.mnWidth = rStrm.ReadInt16(); rStrm.Ignore( 2 ); + maData.maRect.mnHeight = rStrm.ReadInt16(); rStrm.Ignore( 2 ); +} + +void XclImpChLineFormat::ReadChLineFormat( XclImpStream& rStrm ) +{ + rStrm >> maData.maColor; + maData.mnPattern = rStrm.ReaduInt16(); + maData.mnWeight = rStrm.ReadInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + + const XclImpRoot& rRoot = rStrm.GetRoot(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + // BIFF8: index into palette used instead of RGB data + maData.maColor = rRoot.GetPalette().GetColor( rStrm.ReaduInt16() ); +} + +void XclImpChLineFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx ) const +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + if( IsAuto() ) + { + XclChLineFormat aLineFmt; + aLineFmt.maColor = (eObjType == EXC_CHOBJTYPE_LINEARSERIES) ? + rRoot.GetSeriesLineAutoColor( nFormatIdx ) : + rRoot.GetPalette().GetColor( rFmtInfo.mnAutoLineColorIdx ); + aLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + aLineFmt.mnWeight = rFmtInfo.mnAutoLineWeight; + rRoot.ConvertLineFormat( rPropSet, aLineFmt, rFmtInfo.mePropMode ); + } + else + { + rRoot.ConvertLineFormat( rPropSet, maData, rFmtInfo.mePropMode ); + } +} + +void XclImpChAreaFormat::ReadChAreaFormat( XclImpStream& rStrm ) +{ + rStrm >> maData.maPattColor >> maData.maBackColor; + maData.mnPattern = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + + const XclImpRoot& rRoot = rStrm.GetRoot(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + const XclImpPalette& rPal = rRoot.GetPalette(); + maData.maPattColor = rPal.GetColor( rStrm.ReaduInt16() ); + maData.maBackColor = rPal.GetColor( rStrm.ReaduInt16()); + } +} + +void XclImpChAreaFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx ) const +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + if( IsAuto() ) + { + XclChAreaFormat aAreaFmt; + aAreaFmt.maPattColor = (eObjType == EXC_CHOBJTYPE_FILLEDSERIES) ? + rRoot.GetSeriesFillAutoColor( nFormatIdx ) : + rRoot.GetPalette().GetColor( rFmtInfo.mnAutoPattColorIdx ); + aAreaFmt.mnPattern = EXC_PATT_SOLID; + rRoot.ConvertAreaFormat( rPropSet, aAreaFmt, rFmtInfo.mePropMode ); + } + else + { + rRoot.ConvertAreaFormat( rPropSet, maData, rFmtInfo.mePropMode ); + } +} + +XclImpChEscherFormat::XclImpChEscherFormat( const XclImpRoot& rRoot ) : + mnDffFillType( mso_fillSolid ) +{ + maData.mxItemSet = + std::make_shared<SfxItemSet>( rRoot.GetDoc().GetDrawLayer()->GetItemPool() ); +} + +void XclImpChEscherFormat::ReadHeaderRecord( XclImpStream& rStrm ) +{ + // read from stream - CHESCHERFORMAT uses own ID for record continuation + XclImpDffPropSet aPropSet( rStrm.GetRoot() ); + rStrm.ResetRecord( true, rStrm.GetRecId() ); + rStrm >> aPropSet; + // get the data + aPropSet.FillToItemSet( *maData.mxItemSet ); + // get fill type from DFF property set + mnDffFillType = aPropSet.GetPropertyValue( DFF_Prop_fillType ); +} + +void XclImpChEscherFormat::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHPICFORMAT: + maPicFmt.mnBmpMode = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + maPicFmt.mnFlags = rStrm.ReaduInt16(); + maPicFmt.mfScale = rStrm.ReadDouble(); + break; + } +} + +void XclImpChEscherFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, bool bUsePicFmt ) const +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + rRoot.ConvertEscherFormat( rPropSet, maData, bUsePicFmt ? &maPicFmt : nullptr, mnDffFillType, rFmtInfo.mePropMode ); +} + +XclImpChFrameBase::XclImpChFrameBase( const XclChFormatInfo& rFmtInfo ) +{ + if( !rFmtInfo.mbCreateDefFrame ) + return; + + switch( rFmtInfo.meDefFrameType ) + { + case EXC_CHFRAMETYPE_AUTO: + mxLineFmt = new XclImpChLineFormat(); + if( rFmtInfo.mbIsFrame ) + mxAreaFmt = std::make_shared<XclImpChAreaFormat>(); + break; + case EXC_CHFRAMETYPE_INVISIBLE: + { + XclChLineFormat aLineFmt; + ::set_flag( aLineFmt.mnFlags, EXC_CHLINEFORMAT_AUTO, false ); + aLineFmt.mnPattern = EXC_CHLINEFORMAT_NONE; + mxLineFmt = new XclImpChLineFormat( aLineFmt ); + if( rFmtInfo.mbIsFrame ) + { + XclChAreaFormat aAreaFmt; + ::set_flag( aAreaFmt.mnFlags, EXC_CHAREAFORMAT_AUTO, false ); + aAreaFmt.mnPattern = EXC_PATT_NONE; + mxAreaFmt = std::make_shared<XclImpChAreaFormat>( aAreaFmt ); + } + } + break; + default: + OSL_FAIL( "XclImpChFrameBase::XclImpChFrameBase - unknown frame type" ); + } +} + +void XclImpChFrameBase::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHLINEFORMAT: + mxLineFmt = new XclImpChLineFormat(); + mxLineFmt->ReadChLineFormat( rStrm ); + break; + case EXC_ID_CHAREAFORMAT: + mxAreaFmt = std::make_shared<XclImpChAreaFormat>(); + mxAreaFmt->ReadChAreaFormat( rStrm ); + break; + case EXC_ID_CHESCHERFORMAT: + mxEscherFmt = std::make_shared<XclImpChEscherFormat>( rStrm.GetRoot() ); + mxEscherFmt->ReadRecordGroup( rStrm ); + break; + } +} + +void XclImpChFrameBase::ConvertLineBase( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx ) const +{ + if( mxLineFmt ) + mxLineFmt->Convert( rRoot, rPropSet, eObjType, nFormatIdx ); +} + +void XclImpChFrameBase::ConvertAreaBase( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx, bool bUsePicFmt ) const +{ + if( rRoot.GetFormatInfo( eObjType ).mbIsFrame ) + { + // CHESCHERFORMAT overrides CHAREAFORMAT (even if it is auto) + if( mxEscherFmt ) + mxEscherFmt->Convert( rRoot, rPropSet, eObjType, bUsePicFmt ); + else if( mxAreaFmt ) + mxAreaFmt->Convert( rRoot, rPropSet, eObjType, nFormatIdx ); + } +} + +void XclImpChFrameBase::ConvertFrameBase( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx, bool bUsePicFmt ) const +{ + ConvertLineBase( rRoot, rPropSet, eObjType, nFormatIdx ); + ConvertAreaBase( rRoot, rPropSet, eObjType, nFormatIdx, bUsePicFmt ); +} + +XclImpChFrame::XclImpChFrame( const XclImpChRoot& rRoot, XclChObjectType eObjType ) : + XclImpChFrameBase( rRoot.GetFormatInfo( eObjType ) ), + XclImpChRoot( rRoot ), + meObjType( eObjType ) +{ +} + +void XclImpChFrame::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnFormat = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChFrame::UpdateObjFrame( const XclObjLineData& rLineData, const XclObjFillData& rFillData ) +{ + const XclImpPalette& rPal = GetPalette(); + + if( rLineData.IsVisible() && (!mxLineFmt || !mxLineFmt->HasLine()) ) + { + // line formatting + XclChLineFormat aLineFmt; + aLineFmt.maColor = rPal.GetColor( rLineData.mnColorIdx ); + switch( rLineData.mnStyle ) + { + case EXC_OBJ_LINE_SOLID: aLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; break; + case EXC_OBJ_LINE_DASH: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DASH; break; + case EXC_OBJ_LINE_DOT: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DOT; break; + case EXC_OBJ_LINE_DASHDOT: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DASHDOT; break; + case EXC_OBJ_LINE_DASHDOTDOT: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DASHDOTDOT; break; + case EXC_OBJ_LINE_MEDTRANS: aLineFmt.mnPattern = EXC_CHLINEFORMAT_MEDTRANS; break; + case EXC_OBJ_LINE_DARKTRANS: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DARKTRANS; break; + case EXC_OBJ_LINE_LIGHTTRANS: aLineFmt.mnPattern = EXC_CHLINEFORMAT_LIGHTTRANS; break; + case EXC_OBJ_LINE_NONE: aLineFmt.mnPattern = EXC_CHLINEFORMAT_NONE; break; + default: aLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + } + switch( rLineData.mnWidth ) + { + case EXC_OBJ_LINE_HAIR: aLineFmt.mnWeight = EXC_CHLINEFORMAT_HAIR; break; + case EXC_OBJ_LINE_THIN: aLineFmt.mnWeight = EXC_CHLINEFORMAT_SINGLE; break; + case EXC_OBJ_LINE_MEDIUM: aLineFmt.mnWeight = EXC_CHLINEFORMAT_DOUBLE; break; + case EXC_OBJ_LINE_THICK: aLineFmt.mnWeight = EXC_CHLINEFORMAT_TRIPLE; break; + default: aLineFmt.mnWeight = EXC_CHLINEFORMAT_HAIR; + } + ::set_flag( aLineFmt.mnFlags, EXC_CHLINEFORMAT_AUTO, rLineData.IsAuto() ); + mxLineFmt = new XclImpChLineFormat( aLineFmt ); + } + + if( rFillData.IsFilled() && (!mxAreaFmt || !mxAreaFmt->HasArea()) && !mxEscherFmt ) + { + // area formatting + XclChAreaFormat aAreaFmt; + aAreaFmt.maPattColor = rPal.GetColor( rFillData.mnPattColorIdx ); + aAreaFmt.maBackColor = rPal.GetColor( rFillData.mnBackColorIdx ); + aAreaFmt.mnPattern = rFillData.mnPattern; + ::set_flag( aAreaFmt.mnFlags, EXC_CHAREAFORMAT_AUTO, rFillData.IsAuto() ); + mxAreaFmt = std::make_shared<XclImpChAreaFormat>( aAreaFmt ); + } +} + +void XclImpChFrame::Convert( ScfPropertySet& rPropSet, bool bUsePicFmt ) const +{ + ConvertFrameBase( GetChRoot(), rPropSet, meObjType, EXC_CHDATAFORMAT_UNKNOWN, bUsePicFmt ); +} + +// Source links =============================================================== + +namespace { + +/** Creates a labeled data sequence object, adds link for series title if present. */ +Reference< XLabeledDataSequence > lclCreateLabeledDataSequence( + const XclImpChSourceLinkRef& xValueLink, const OUString& rValueRole, + const XclImpChSourceLink* pTitleLink = nullptr ) +{ + // create data sequence for values and title + Reference< XDataSequence > xValueSeq; + if( xValueLink ) + xValueSeq = xValueLink->CreateDataSequence( rValueRole ); + Reference< XDataSequence > xTitleSeq; + if( pTitleLink ) + xTitleSeq = pTitleLink->CreateDataSequence( EXC_CHPROP_ROLE_LABEL ); + + // create the labeled data sequence, if values or title are present + Reference< XLabeledDataSequence > xLabeledSeq; + if( xValueSeq.is() || xTitleSeq.is() ) + xLabeledSeq = LabeledDataSequence::create(comphelper::getProcessComponentContext()); + if( xLabeledSeq.is() ) + { + if( xValueSeq.is() ) + xLabeledSeq->setValues( xValueSeq ); + if( xTitleSeq.is() ) + xLabeledSeq->setLabel( xTitleSeq ); + } + return xLabeledSeq; +} + +} // namespace + +XclImpChSourceLink::XclImpChSourceLink( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +XclImpChSourceLink::~XclImpChSourceLink() +{ +} + +void XclImpChSourceLink::ReadChSourceLink( XclImpStream& rStrm ) +{ + maData.mnDestType = rStrm.ReaduInt8(); + maData.mnLinkType = rStrm.ReaduInt8(); + maData.mnFlags = rStrm.ReaduInt16(); + maData.mnNumFmtIdx = rStrm.ReaduInt16(); + + mxTokenArray.reset(); + if( GetLinkType() == EXC_CHSRCLINK_WORKSHEET ) + { + // read token array + XclTokenArray aXclTokArr; + rStrm >> aXclTokArr; + + // convert BIFF formula tokens to Calc token array + if( std::unique_ptr<ScTokenArray> pTokens = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CHART, aXclTokArr ) ) + mxTokenArray = std::move( pTokens ); + } + + // try to read a following CHSTRING record + if( (rStrm.GetNextRecId() == EXC_ID_CHSTRING) && rStrm.StartNextRecord() ) + { + mxString = std::make_shared<XclImpString>(); + rStrm.Ignore( 2 ); + mxString->Read( rStrm, XclStrFlags::EightBitLength | XclStrFlags::SeparateFormats ); + } +} + +void XclImpChSourceLink::SetString( const OUString& rString ) +{ + if( !mxString ) + mxString = std::make_shared<XclImpString>(); + mxString->SetText( rString ); +} + +void XclImpChSourceLink::SetTextFormats( XclFormatRunVec&& rFormats ) +{ + if( mxString ) + mxString->SetFormats( std::move(rFormats) ); +} + +sal_uInt16 XclImpChSourceLink::GetCellCount() const +{ + sal_uInt32 nCellCount = 0; + if( mxTokenArray ) + { + FormulaTokenArrayPlainIterator aIter(*mxTokenArray); + for( const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next() ) + { + switch( pToken->GetType() ) + { + case ::formula::svSingleRef: + case ::formula::svExternalSingleRef: + // single cell + ++nCellCount; + break; + case ::formula::svDoubleRef: + case ::formula::svExternalDoubleRef: + { + // cell range + const ScComplexRefData& rComplexRef = *pToken->GetDoubleRef(); + ScAddress aAbs1 = rComplexRef.Ref1.toAbs(GetRoot().GetDoc(), ScAddress()); + ScAddress aAbs2 = rComplexRef.Ref2.toAbs(GetRoot().GetDoc(), ScAddress()); + sal_uInt32 nTabs = static_cast<sal_uInt32>(aAbs2.Tab() - aAbs1.Tab() + 1); + sal_uInt32 nCols = static_cast<sal_uInt32>(aAbs2.Col() - aAbs1.Col() + 1); + sal_uInt32 nRows = static_cast<sal_uInt32>(aAbs2.Row() - aAbs1.Row() + 1); + nCellCount += nCols * nRows * nTabs; + } + break; + default: ; + } + } + } + return limit_cast< sal_uInt16 >( nCellCount ); +} + +void XclImpChSourceLink::ConvertNumFmt( ScfPropertySet& rPropSet, bool bPercent ) const +{ + bool bLinkToSource = ::get_flag( maData.mnFlags, EXC_CHSRCLINK_NUMFMT ); + sal_uInt32 nScNumFmt = bLinkToSource ? GetNumFmtBuffer().GetScFormat( maData.mnNumFmtIdx ) : NUMBERFORMAT_ENTRY_NOT_FOUND; + OUString aPropName = bPercent ? OUString( EXC_CHPROP_PERCENTAGENUMFMT ) : OUString( EXC_CHPROP_NUMBERFORMAT ); + if( nScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND ) + rPropSet.SetProperty( aPropName, static_cast< sal_Int32 >( nScNumFmt ) ); + else + // restore 'link to source' at data point (series may contain manual number format) + rPropSet.SetAnyProperty( aPropName, Any() ); +} + +Reference< XDataSequence > XclImpChSourceLink::CreateDataSequence( const OUString& rRole ) const +{ + Reference< XDataSequence > xDataSeq; + Reference< XDataProvider > xDataProv = GetDataProvider(); + if( xDataProv.is() ) + { + if ( mxTokenArray ) + { + ScCompiler aComp( GetDoc(), ScAddress(), *mxTokenArray, GetDoc().GetGrammar() ); + OUStringBuffer aRangeRep; + aComp.CreateStringFromTokenArray( aRangeRep ); + try + { + xDataSeq = xDataProv->createDataSequenceByRangeRepresentation( aRangeRep.makeStringAndClear() ); + // set sequence role + ScfPropertySet aSeqProp( xDataSeq ); + aSeqProp.SetProperty( EXC_CHPROP_ROLE, rRole ); + } + catch( Exception& ) + { + // OSL_FAIL( "XclImpChSourceLink::CreateDataSequence - cannot create data sequence" ); + } + } + else if( rRole == EXC_CHPROP_ROLE_LABEL && mxString && !mxString->GetText().isEmpty() ) + { + try + { + OUString aString("\""); + xDataSeq = xDataProv->createDataSequenceByRangeRepresentation( aString + mxString->GetText() + aString ); + // set sequence role + ScfPropertySet aSeqProp( xDataSeq ); + aSeqProp.SetProperty( EXC_CHPROP_ROLE, rRole ); + } + catch( Exception& ) { } + } + } + return xDataSeq; +} + +Sequence< Reference< XFormattedString > > XclImpChSourceLink::CreateStringSequence( + const XclImpChRoot& rRoot, sal_uInt16 nLeadFontIdx, const Color& rLeadFontColor ) const +{ + ::std::vector< Reference< XFormattedString > > aStringVec; + if( mxString ) + { + for( XclImpStringIterator aIt( *mxString ); aIt.Is(); ++aIt ) + { + Reference< css::chart2::XFormattedString2 > xFmtStr = css::chart2::FormattedString::create( comphelper::getProcessComponentContext() ); + // set text data + xFmtStr->setString( aIt.GetPortionText() ); + + // set font formatting and font color + ScfPropertySet aStringProp( xFmtStr ); + sal_uInt16 nFontIdx = aIt.GetPortionFont(); + if( (nFontIdx == EXC_FONT_NOTFOUND) && (aIt.GetPortionIndex() == 0) ) + // leading unformatted portion - use passed font settings + rRoot.ConvertFont( aStringProp, nLeadFontIdx, &rLeadFontColor ); + else + rRoot.ConvertFont( aStringProp, nFontIdx ); + + // add string to vector of strings + aStringVec.emplace_back(xFmtStr ); + } + } + return ScfApiHelper::VectorToSequence( aStringVec ); +} + +void XclImpChSourceLink::FillSourceLink( ::std::vector< ScTokenRef >& rTokens ) const +{ + if( !mxTokenArray ) + // no links to fill. + return; + + FormulaTokenArrayPlainIterator aIter(*mxTokenArray); + for (FormulaToken* p = aIter.First(); p; p = aIter.Next()) + { + ScTokenRef pToken(p->Clone()); + if (ScRefTokenHelper::isRef(pToken)) + // This is a reference token. Store it. + ScRefTokenHelper::join(&GetRoot().GetDoc(), rTokens, pToken, ScAddress()); + } +} + +// Text ======================================================================= + +XclImpChFontBase::~XclImpChFontBase() +{ +} + +void XclImpChFontBase::ConvertFontBase( const XclImpChRoot& rRoot, ScfPropertySet& rPropSet ) const +{ + Color aFontColor = GetFontColor(); + rRoot.ConvertFont( rPropSet, GetFontIndex(), &aFontColor ); +} + +void XclImpChFontBase::ConvertRotationBase( ScfPropertySet& rPropSet, bool bSupportsStacked ) const +{ + XclChPropSetHelper::WriteRotationProperties( rPropSet, GetRotation(), bSupportsStacked ); +} + +XclImpChFont::XclImpChFont() : + mnFontIdx( EXC_FONT_NOTFOUND ) +{ +} + +void XclImpChFont::ReadChFont( XclImpStream& rStrm ) +{ + mnFontIdx = rStrm.ReaduInt16(); +} + +XclImpChText::XclImpChText( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChText::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnHAlign = rStrm.ReaduInt8(); + maData.mnVAlign = rStrm.ReaduInt8(); + maData.mnBackMode = rStrm.ReaduInt16(); + rStrm >> maData.maTextColor + >> maData.maRect; + maData.mnFlags = rStrm.ReaduInt16(); + + if( GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + maData.maTextColor = GetPalette().GetColor( rStrm.ReaduInt16() ); + // placement and rotation + maData.mnFlags2 = rStrm.ReaduInt16(); + maData.mnRotation = rStrm.ReaduInt16(); + } + else + { + // BIFF2-BIFF7: get rotation from text orientation + sal_uInt8 nOrient = ::extract_value< sal_uInt8 >( maData.mnFlags, 8, 3 ); + maData.mnRotation = XclTools::GetXclRotFromOrient( nOrient ); + } +} + +void XclImpChText::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAMEPOS: + mxFramePos = std::make_shared<XclImpChFramePos>(); + mxFramePos->ReadChFramePos( rStrm ); + break; + case EXC_ID_CHFONT: + mxFont = std::make_shared<XclImpChFont>(); + mxFont->ReadChFont( rStrm ); + break; + case EXC_ID_CHFORMATRUNS: + if( GetBiff() == EXC_BIFF8 ) + XclImpString::ReadFormats( rStrm, maFormats ); + break; + case EXC_ID_CHSOURCELINK: + mxSrcLink = std::make_shared<XclImpChSourceLink>( GetChRoot() ); + mxSrcLink->ReadChSourceLink( rStrm ); + break; + case EXC_ID_CHFRAME: + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_TEXT ); + mxFrame->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHOBJECTLINK: + maObjLink.mnTarget = rStrm.ReaduInt16(); + maObjLink.maPointPos.mnSeriesIdx = rStrm.ReaduInt16(); + maObjLink.maPointPos.mnPointIdx = rStrm.ReaduInt16(); + break; + case EXC_ID_CHFRLABELPROPS: + ReadChFrLabelProps( rStrm ); + break; + case EXC_ID_CHEND: + if( mxSrcLink && !maFormats.empty() ) + mxSrcLink->SetTextFormats( std::vector(maFormats) ); + break; + } +} + +sal_uInt16 XclImpChText::GetFontIndex() const +{ + return mxFont ? mxFont->GetFontIndex() : EXC_FONT_NOTFOUND; +} + +Color XclImpChText::GetFontColor() const +{ + return ::get_flag( maData.mnFlags, EXC_CHTEXT_AUTOCOLOR ) ? GetFontAutoColor() : maData.maTextColor; +} + +sal_uInt16 XclImpChText::GetRotation() const +{ + return maData.mnRotation; +} + +void XclImpChText::SetString( const OUString& rString ) +{ + if( !mxSrcLink ) + mxSrcLink = std::make_shared<XclImpChSourceLink>( GetChRoot() ); + mxSrcLink->SetString( rString ); +} + +void XclImpChText::UpdateText( const XclImpChText* pParentText ) +{ + if( !pParentText ) + return; + + // update missing members + if( !mxFrame ) + mxFrame = pParentText->mxFrame; + if( !mxFont ) + { + mxFont = pParentText->mxFont; + // text color is taken from CHTEXT record, not from font in CHFONT + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOCOLOR, ::get_flag( pParentText->maData.mnFlags, EXC_CHTEXT_AUTOCOLOR ) ); + maData.maTextColor = pParentText->maData.maTextColor; + } +} + +void XclImpChText::UpdateDataLabel( bool bCateg, bool bValue, bool bPercent ) +{ + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEG, bCateg ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWVALUE, bValue ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWPERCENT, bPercent ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEGPERC, bCateg && bPercent ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_DELETED, !bCateg && !bValue && !bPercent ); +} + +void XclImpChText::ConvertFont( ScfPropertySet& rPropSet ) const +{ + ConvertFontBase( GetChRoot(), rPropSet ); +} + +void XclImpChText::ConvertRotation( ScfPropertySet& rPropSet, bool bSupportsStacked ) const +{ + ConvertRotationBase( rPropSet, bSupportsStacked ); +} + +void XclImpChText::ConvertFrame( ScfPropertySet& rPropSet ) const +{ + if( mxFrame ) + mxFrame->Convert( rPropSet ); +} + +void XclImpChText::ConvertNumFmt( ScfPropertySet& rPropSet, bool bPercent ) const +{ + if( mxSrcLink ) + mxSrcLink->ConvertNumFmt( rPropSet, bPercent ); +} + +void XclImpChText::ConvertDataLabel( ScfPropertySet& rPropSet, const XclChTypeInfo& rTypeInfo, const ScfPropertySet* pGlobalPropSet ) const +{ + // existing CHFRLABELPROPS record wins over flags from CHTEXT + sal_uInt16 nShowFlags = mxLabelProps ? mxLabelProps->mnFlags : maData.mnFlags; + sal_uInt16 SHOWANYCATEG = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWCATEG : (EXC_CHTEXT_SHOWCATEGPERC | EXC_CHTEXT_SHOWCATEG); + sal_uInt16 SHOWANYVALUE = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWVALUE : EXC_CHTEXT_SHOWVALUE; + sal_uInt16 SHOWANYPERCENT = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWPERCENT : (EXC_CHTEXT_SHOWPERCENT | EXC_CHTEXT_SHOWCATEGPERC); + sal_uInt16 SHOWANYBUBBLE = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWBUBBLE : EXC_CHTEXT_SHOWBUBBLE; + + // get raw flags for label values + bool bShowNone = IsDeleted(); + bool bShowCateg = !bShowNone && ::get_flag( nShowFlags, SHOWANYCATEG ); + bool bShowPercent = !bShowNone && ::get_flag( nShowFlags, SHOWANYPERCENT ); + bool bShowValue = !bShowNone && ::get_flag( nShowFlags, SHOWANYVALUE ); + bool bShowBubble = !bShowNone && ::get_flag( nShowFlags, SHOWANYBUBBLE ); + + // adjust to Chart2 behaviour + if( rTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES ) + bShowValue = bShowBubble; // Chart2 bubble charts show bubble size if 'ShowValue' is set + + // other flags + bool bShowAny = bShowValue || bShowPercent || bShowCateg; + bool bShowSymbol = bShowAny && ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWSYMBOL ); + + // create API struct for label values, set API label separator + cssc2::DataPointLabel aPointLabel( bShowValue, bShowPercent, bShowCateg, bShowSymbol, false, false ); + rPropSet.SetProperty( EXC_CHPROP_LABEL, aPointLabel ); + OUString aSep = mxLabelProps ? mxLabelProps->maSeparator : OUString('\n'); + if( aSep.isEmpty() ) + aSep = "; "; + rPropSet.SetStringProperty( EXC_CHPROP_LABELSEPARATOR, aSep ); + + // text properties of attached label + if( !bShowAny ) + return; + + ConvertFont( rPropSet ); + ConvertRotation( rPropSet, false ); + // label placement + using namespace cssc::DataLabelPlacement; + sal_Int32 nPlacement = rTypeInfo.mnDefaultLabelPos; + switch( ::extract_value< sal_uInt16 >( maData.mnFlags2, 0, 4 ) ) + { + case EXC_CHTEXT_POS_DEFAULT: nPlacement = rTypeInfo.mnDefaultLabelPos; break; + case EXC_CHTEXT_POS_OUTSIDE: nPlacement = OUTSIDE; break; + case EXC_CHTEXT_POS_INSIDE: nPlacement = INSIDE; break; + case EXC_CHTEXT_POS_CENTER: nPlacement = CENTER; break; + case EXC_CHTEXT_POS_AXIS: nPlacement = NEAR_ORIGIN; break; + case EXC_CHTEXT_POS_ABOVE: nPlacement = TOP; break; + case EXC_CHTEXT_POS_BELOW: nPlacement = BOTTOM; break; + case EXC_CHTEXT_POS_LEFT: nPlacement = LEFT; break; + case EXC_CHTEXT_POS_RIGHT: nPlacement = RIGHT; break; + case EXC_CHTEXT_POS_AUTO: nPlacement = AVOID_OVERLAP; break; + } + sal_Int32 nGlobalPlacement = 0; + if ( ( nPlacement == rTypeInfo.mnDefaultLabelPos ) && pGlobalPropSet && + pGlobalPropSet->GetProperty( nGlobalPlacement, EXC_CHPROP_LABELPLACEMENT ) ) + nPlacement = nGlobalPlacement; + + rPropSet.SetProperty( EXC_CHPROP_LABELPLACEMENT, nPlacement ); + // label number format (percentage format wins over value format) + if( bShowPercent || bShowValue ) + ConvertNumFmt( rPropSet, bShowPercent ); +} + +Reference< XTitle > XclImpChText::CreateTitle() const +{ + Reference< XTitle > xTitle; + if( mxSrcLink && mxSrcLink->HasString() ) + { + // create the formatted strings + Sequence< Reference< XFormattedString > > aStringSeq( + mxSrcLink->CreateStringSequence( GetChRoot(), GetFontIndex(), GetFontColor() ) ); + if( aStringSeq.hasElements() ) + { + // create the title object + xTitle.set( ScfApiHelper::CreateInstance( SERVICE_CHART2_TITLE ), UNO_QUERY ); + if( xTitle.is() ) + { + // set the formatted strings + xTitle->setText( aStringSeq ); + // more title formatting properties + ScfPropertySet aTitleProp( xTitle ); + ConvertFrame( aTitleProp ); + ConvertRotation( aTitleProp, true ); + } + } + } + return xTitle; +} + +void XclImpChText::ConvertTitlePosition( const XclChTextKey& rTitleKey ) const +{ + if( !mxFramePos ) return; + + const XclChFramePos& rPosData = mxFramePos->GetFramePosData(); + OSL_ENSURE( (rPosData.mnTLMode == EXC_CHFRAMEPOS_PARENT) && (rPosData.mnBRMode == EXC_CHFRAMEPOS_PARENT), + "XclImpChText::ConvertTitlePosition - unexpected frame position mode" ); + + /* Check if title is moved manually. To get the actual position of the + title, we do some kind of hack and use the values from the CHTEXT + record, effectively ignoring the contents of the CHFRAMEPOS record + which contains the position relative to the default title position + (according to the spec, the CHFRAMEPOS supersedes the CHTEXT record). + Especially when it comes to axis titles, things would become very + complicated here, because the relative title position is stored in a + measurement unit that is dependent on the size of the inner plot area, + the interpretation of the X and Y coordinate is dependent on the + direction of the axis, and in 3D charts, and the title default + positions are dependent on the 3D view settings (rotation, elevation, + and perspective). Thus, it is easier to assume that the creator has + written out the correct absolute position and size of the title in the + CHTEXT record. This is assured by checking that the shape size stored + in the CHTEXT record is non-zero. */ + if( !((rPosData.mnTLMode == EXC_CHFRAMEPOS_PARENT) && + ((rPosData.maRect.mnX != 0) || (rPosData.maRect.mnY != 0)) && + (maData.maRect.mnWidth > 0) && (maData.maRect.mnHeight > 0)) ) + return; + + try + { + Reference< XShape > xTitleShape( GetTitleShape( rTitleKey ), UNO_SET_THROW ); + // the call to XShape.getSize() may recalc the chart view + css::awt::Size aTitleSize = xTitleShape->getSize(); + // rotated titles need special handling... + Degree100 nScRot = XclTools::GetScRotation( GetRotation(), 0_deg100 ); + double fRad = toRadians(nScRot); + double fSin = fabs( sin( fRad ) ); + // calculate the title position from the values in the CHTEXT record + css::awt::Point aTitlePos( + CalcHmmFromChartX( maData.maRect.mnX ), + CalcHmmFromChartY( maData.maRect.mnY ) ); + // add part of height to X direction, if title is rotated down (clockwise) + if( nScRot > 18000_deg100 ) + aTitlePos.X += static_cast< sal_Int32 >( fSin * aTitleSize.Height + 0.5 ); + // add part of width to Y direction, if title is rotated up (counterclockwise) + else if( nScRot > 0_deg100 ) + aTitlePos.Y += static_cast< sal_Int32 >( fSin * aTitleSize.Width + 0.5 ); + // set the resulting position at the title shape + xTitleShape->setPosition( aTitlePos ); + } + catch( Exception& ) + { + } +} + +void XclImpChText::ReadChFrLabelProps( XclImpStream& rStrm ) +{ + if( GetBiff() == EXC_BIFF8 ) + { + mxLabelProps = std::make_shared<XclChFrLabelProps>(); + sal_uInt16 nSepLen; + rStrm.Ignore( 12 ); + mxLabelProps->mnFlags = rStrm.ReaduInt16(); + nSepLen = rStrm.ReaduInt16(); + if( nSepLen > 0 ) + mxLabelProps->maSeparator = rStrm.ReadUniString( nSepLen ); + } +} + +namespace { + +void lclUpdateText( XclImpChTextRef& rxText, const XclImpChText* xDefText ) +{ + if (rxText) + rxText->UpdateText( xDefText ); + else if (xDefText) + { + rxText = std::make_shared<XclImpChText>(*xDefText); + } +} + +void lclFinalizeTitle( XclImpChTextRef& rxTitle, const XclImpChText* pDefText, const OUString& rAutoTitle ) +{ + /* Do not update a title, if it is not visible (if rxTitle is null). + Existing reference indicates enabled title. */ + if( rxTitle ) + { + if( !rxTitle->HasString() ) + rxTitle->SetString( rAutoTitle ); + if( rxTitle->HasString() ) + rxTitle->UpdateText(pDefText); + else + rxTitle.reset(); + } +} + +} // namespace + +// Data series ================================================================ + +void XclImpChMarkerFormat::ReadChMarkerFormat( XclImpStream& rStrm ) +{ + rStrm >> maData.maLineColor >> maData.maFillColor; + maData.mnMarkerType = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + + const XclImpRoot& rRoot = rStrm.GetRoot(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + const XclImpPalette& rPal = rRoot.GetPalette(); + maData.maLineColor = rPal.GetColor( rStrm.ReaduInt16() ); + maData.maFillColor = rPal.GetColor( rStrm.ReaduInt16() ); + // marker size + maData.mnMarkerSize = rStrm.ReaduInt32(); + } +} + +void XclImpChMarkerFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx, sal_Int16 nLineWeight ) const +{ + if( IsAuto() ) + { + XclChMarkerFormat aMarkerFmt; + // line and fill color of the symbol are equal to series line color + //TODO: Excel sets no fill color for specific symbols (e.g. cross) + aMarkerFmt.maLineColor = aMarkerFmt.maFillColor = rRoot.GetSeriesLineAutoColor( nFormatIdx ); + switch( nLineWeight ) + { + case EXC_CHLINEFORMAT_HAIR: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_HAIRSIZE; break; + case EXC_CHLINEFORMAT_SINGLE: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_SINGLESIZE; break; + case EXC_CHLINEFORMAT_DOUBLE: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_DOUBLESIZE; break; + case EXC_CHLINEFORMAT_TRIPLE: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_TRIPLESIZE; break; + default: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_SINGLESIZE; + } + aMarkerFmt.mnMarkerType = XclChartHelper::GetAutoMarkerType( nFormatIdx ); + XclChPropSetHelper::WriteMarkerProperties( rPropSet, aMarkerFmt ); + } + else + { + XclChPropSetHelper::WriteMarkerProperties( rPropSet, maData ); + } +} + +void XclImpChMarkerFormat::ConvertColor( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx ) const +{ + Color aLineColor = IsAuto() ? rRoot.GetSeriesLineAutoColor( nFormatIdx ) : maData.maFillColor; + rPropSet.SetColorProperty( EXC_CHPROP_COLOR, aLineColor ); +} + +XclImpChPieFormat::XclImpChPieFormat() : + mnPieDist( 0 ) +{ +} + +void XclImpChPieFormat::ReadChPieFormat( XclImpStream& rStrm ) +{ + mnPieDist = rStrm.ReaduInt16(); +} + +void XclImpChPieFormat::Convert( ScfPropertySet& rPropSet ) const +{ + double fApiDist = ::std::min< double >( mnPieDist / 100.0, 1.0 ); + rPropSet.SetProperty( EXC_CHPROP_OFFSET, fApiDist ); +} + +XclImpChSeriesFormat::XclImpChSeriesFormat() : + mnFlags( 0 ) +{ +} + +void XclImpChSeriesFormat::ReadChSeriesFormat( XclImpStream& rStrm ) +{ + mnFlags = rStrm.ReaduInt16(); +} + +void XclImpCh3dDataFormat::ReadCh3dDataFormat( XclImpStream& rStrm ) +{ + maData.mnBase = rStrm.ReaduInt8(); + maData.mnTop = rStrm.ReaduInt8(); +} + +void XclImpCh3dDataFormat::Convert( ScfPropertySet& rPropSet ) const +{ + using namespace ::com::sun::star::chart2::DataPointGeometry3D; + sal_Int32 nApiType = (maData.mnBase == EXC_CH3DDATAFORMAT_RECT) ? + ((maData.mnTop == EXC_CH3DDATAFORMAT_STRAIGHT) ? CUBOID : PYRAMID) : + ((maData.mnTop == EXC_CH3DDATAFORMAT_STRAIGHT) ? CYLINDER : CONE); + rPropSet.SetProperty( EXC_CHPROP_GEOMETRY3D, nApiType ); +} + +XclImpChAttachedLabel::XclImpChAttachedLabel( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ), + mnFlags( 0 ) +{ +} + +void XclImpChAttachedLabel::ReadChAttachedLabel( XclImpStream& rStrm ) +{ + mnFlags = rStrm.ReaduInt16(); +} + +XclImpChTextRef XclImpChAttachedLabel::CreateDataLabel( const XclImpChText* pParent ) const +{ + const sal_uInt16 EXC_CHATTLABEL_SHOWANYVALUE = EXC_CHATTLABEL_SHOWVALUE; + const sal_uInt16 EXC_CHATTLABEL_SHOWANYPERCENT = EXC_CHATTLABEL_SHOWPERCENT | EXC_CHATTLABEL_SHOWCATEGPERC; + const sal_uInt16 EXC_CHATTLABEL_SHOWANYCATEG = EXC_CHATTLABEL_SHOWCATEG | EXC_CHATTLABEL_SHOWCATEGPERC; + + XclImpChTextRef xLabel; + if ( pParent ) + xLabel = std::make_shared<XclImpChText>( *pParent ); + else + xLabel = std::make_shared<XclImpChText>( GetChRoot() ); + xLabel->UpdateDataLabel( + ::get_flag( mnFlags, EXC_CHATTLABEL_SHOWANYCATEG ), + ::get_flag( mnFlags, EXC_CHATTLABEL_SHOWANYVALUE ), + ::get_flag( mnFlags, EXC_CHATTLABEL_SHOWANYPERCENT ) ); + return xLabel; +} + +XclImpChDataFormat::XclImpChDataFormat( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChDataFormat::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.maPointPos.mnPointIdx = rStrm.ReaduInt16(); + maData.maPointPos.mnSeriesIdx = rStrm.ReaduInt16(); + maData.mnFormatIdx = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChDataFormat::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHMARKERFORMAT: + mxMarkerFmt = std::make_shared<XclImpChMarkerFormat>(); + mxMarkerFmt->ReadChMarkerFormat( rStrm ); + break; + case EXC_ID_CHPIEFORMAT: + mxPieFmt = std::make_shared<XclImpChPieFormat>(); + mxPieFmt->ReadChPieFormat( rStrm ); + break; + case EXC_ID_CHSERIESFORMAT: + mxSeriesFmt = std::make_shared<XclImpChSeriesFormat>(); + mxSeriesFmt->ReadChSeriesFormat( rStrm ); + break; + case EXC_ID_CH3DDATAFORMAT: + mx3dDataFmt = std::make_shared<XclImpCh3dDataFormat>(); + mx3dDataFmt->ReadCh3dDataFormat( rStrm ); + break; + case EXC_ID_CHATTACHEDLABEL: + mxAttLabel = std::make_shared<XclImpChAttachedLabel>( GetChRoot() ); + mxAttLabel->ReadChAttachedLabel( rStrm ); + break; + default: + XclImpChFrameBase::ReadSubRecord( rStrm ); + } +} + +void XclImpChDataFormat::SetPointPos( const XclChDataPointPos& rPointPos, sal_uInt16 nFormatIdx ) +{ + maData.maPointPos = rPointPos; + maData.mnFormatIdx = nFormatIdx; +} + +void XclImpChDataFormat::UpdateGroupFormat( const XclChExtTypeInfo& rTypeInfo ) +{ + // remove formats not used for the current chart type + RemoveUnusedFormats( rTypeInfo ); +} + +void XclImpChDataFormat::UpdateSeriesFormat( const XclChExtTypeInfo& rTypeInfo, const XclImpChDataFormat* pGroupFmt ) +{ + // update missing formats from passed chart type group format + if( pGroupFmt ) + { + if( !mxLineFmt ) + mxLineFmt = pGroupFmt->mxLineFmt; + if( !mxAreaFmt && !mxEscherFmt ) + { + mxAreaFmt = pGroupFmt->mxAreaFmt; + mxEscherFmt = pGroupFmt->mxEscherFmt; + } + if( !mxMarkerFmt ) + mxMarkerFmt = pGroupFmt->mxMarkerFmt; + if( !mxPieFmt ) + mxPieFmt = pGroupFmt->mxPieFmt; + if( !mxSeriesFmt ) + mxSeriesFmt = pGroupFmt->mxSeriesFmt; + if( !mx3dDataFmt ) + mx3dDataFmt = pGroupFmt->mx3dDataFmt; + if( !mxAttLabel ) + mxAttLabel = pGroupFmt->mxAttLabel; + } + + /* Create missing but required formats. Existing line, area, and marker + format objects are needed to create automatic series formatting. */ + if( !mxLineFmt ) + mxLineFmt = new XclImpChLineFormat(); + if( !mxAreaFmt && !mxEscherFmt ) + mxAreaFmt = std::make_shared<XclImpChAreaFormat>(); + if( !mxMarkerFmt ) + mxMarkerFmt = std::make_shared<XclImpChMarkerFormat>(); + + // remove formats not used for the current chart type + RemoveUnusedFormats( rTypeInfo ); + // update data label + UpdateDataLabel( pGroupFmt ); +} + +void XclImpChDataFormat::UpdatePointFormat( const XclChExtTypeInfo& rTypeInfo, const XclImpChDataFormat* pSeriesFmt ) +{ + // remove formats if they are automatic in this and in the passed series format + if( pSeriesFmt ) + { + if( IsAutoLine() && pSeriesFmt->IsAutoLine() ) + mxLineFmt.clear(); + if( IsAutoArea() && pSeriesFmt->IsAutoArea() ) + mxAreaFmt.reset(); + if( IsAutoMarker() && pSeriesFmt->IsAutoMarker() ) + mxMarkerFmt.reset(); + mxSeriesFmt.reset(); + } + + // Excel ignores 3D bar format for single data points + mx3dDataFmt.reset(); + // remove point line formats for linear chart types, TODO: implement in OOChart + if( !rTypeInfo.IsSeriesFrameFormat() ) + mxLineFmt.clear(); + + // remove formats not used for the current chart type + RemoveUnusedFormats( rTypeInfo ); + // update data label + UpdateDataLabel( pSeriesFmt ); +} + +void XclImpChDataFormat::UpdateTrendLineFormat() +{ + if( !mxLineFmt ) + mxLineFmt = new XclImpChLineFormat(); + mxAreaFmt.reset(); + mxEscherFmt.reset(); + mxMarkerFmt.reset(); + mxPieFmt.reset(); + mxSeriesFmt.reset(); + mx3dDataFmt.reset(); + mxAttLabel.reset(); + // update data label + UpdateDataLabel( nullptr ); +} + +void XclImpChDataFormat::Convert( ScfPropertySet& rPropSet, const XclChExtTypeInfo& rTypeInfo, const ScfPropertySet* pGlobalPropSet ) const +{ + /* Line and area format. + #i71810# If the data points are filled with bitmaps, textures, or + patterns, then only bar charts will use the CHPICFORMAT record to + determine stacking/stretching mode. All other chart types ignore this + record and always use the property 'fill-type' from the DFF property + set (stretched for bitmaps, and stacked for textures and patterns). */ + bool bUsePicFmt = rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_BAR; + ConvertFrameBase( GetChRoot(), rPropSet, rTypeInfo.GetSeriesObjectType(), maData.mnFormatIdx, bUsePicFmt ); + + // #i83151# only hair lines in 3D charts with filled data points + if( rTypeInfo.mb3dChart && rTypeInfo.IsSeriesFrameFormat() && mxLineFmt && mxLineFmt->HasLine() ) + rPropSet.SetProperty< sal_Int32 >( "BorderWidth", 0 ); + + // other formatting + if( mxMarkerFmt ) + mxMarkerFmt->Convert( GetChRoot(), rPropSet, maData.mnFormatIdx, GetLineWeight() ); + if( mxPieFmt ) + mxPieFmt->Convert( rPropSet ); + if( mx3dDataFmt ) + mx3dDataFmt->Convert( rPropSet ); + if( mxLabel ) + mxLabel->ConvertDataLabel( rPropSet, rTypeInfo, pGlobalPropSet ); + + // 3D settings + rPropSet.SetProperty< sal_Int16 >( EXC_CHPROP_PERCENTDIAGONAL, 0 ); + + /* Special case: set marker color as line color, if series line is not + visible. This makes the color visible in the marker area. + TODO: remove this if OOChart supports own colors in markers. */ + if( !rTypeInfo.IsSeriesFrameFormat() && !HasLine() && mxMarkerFmt ) + mxMarkerFmt->ConvertColor( GetChRoot(), rPropSet, maData.mnFormatIdx ); +} + +void XclImpChDataFormat::ConvertLine( ScfPropertySet& rPropSet, XclChObjectType eObjType ) const +{ + ConvertLineBase( GetChRoot(), rPropSet, eObjType ); +} + +void XclImpChDataFormat::ConvertArea( ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx ) const +{ + ConvertAreaBase( GetChRoot(), rPropSet, EXC_CHOBJTYPE_FILLEDSERIES, nFormatIdx ); +} + +void XclImpChDataFormat::RemoveUnusedFormats( const XclChExtTypeInfo& rTypeInfo ) +{ + // data point marker only in linear 2D charts + if( rTypeInfo.IsSeriesFrameFormat() ) + mxMarkerFmt.reset(); + // pie format only in pie/donut charts + if( rTypeInfo.meTypeCateg != EXC_CHTYPECATEG_PIE ) + mxPieFmt.reset(); + // 3D format only in 3D bar charts + if( !rTypeInfo.mb3dChart || (rTypeInfo.meTypeCateg != EXC_CHTYPECATEG_BAR) ) + mx3dDataFmt.reset(); +} + +void XclImpChDataFormat::UpdateDataLabel( const XclImpChDataFormat* pParentFmt ) +{ + /* CHTEXT groups linked to data labels override existing CHATTACHEDLABEL + records. Only if there is a CHATTACHEDLABEL record without a CHTEXT + group, the contents of the CHATTACHEDLABEL record are used. In this + case a new CHTEXT group is created and filled with the settings from + the CHATTACHEDLABEL record. */ + const XclImpChText* pDefText = nullptr; + if (pParentFmt) + pDefText = pParentFmt->GetDataLabel(); + if (!pDefText) + pDefText = GetChartData().GetDefaultText( EXC_CHTEXTTYPE_DATALABEL ); + if (mxLabel) + mxLabel->UpdateText(pDefText); + else if (mxAttLabel) + mxLabel = mxAttLabel->CreateDataLabel( pDefText ); +} + +XclImpChSerTrendLine::XclImpChSerTrendLine( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChSerTrendLine::ReadChSerTrendLine( XclImpStream& rStrm ) +{ + maData.mnLineType = rStrm.ReaduInt8(); + maData.mnOrder = rStrm.ReaduInt8(); + maData.mfIntercept = rStrm.ReadDouble(); + maData.mnShowEquation = rStrm.ReaduInt8(); + maData.mnShowRSquared = rStrm.ReaduInt8(); + maData.mfForecastFor = rStrm.ReadDouble(); + maData.mfForecastBack = rStrm.ReadDouble(); +} + +Reference< XRegressionCurve > XclImpChSerTrendLine::CreateRegressionCurve() const +{ + // trend line type + Reference< XRegressionCurve > xRegCurve; + switch( maData.mnLineType ) + { + case EXC_CHSERTREND_POLYNOMIAL: + if( maData.mnOrder == 1 ) + { + xRegCurve = LinearRegressionCurve::create( comphelper::getProcessComponentContext() ); + } else { + xRegCurve = PolynomialRegressionCurve::create( comphelper::getProcessComponentContext() ); + } + break; + case EXC_CHSERTREND_EXPONENTIAL: + xRegCurve = ExponentialRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + case EXC_CHSERTREND_LOGARITHMIC: + xRegCurve = LogarithmicRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + case EXC_CHSERTREND_POWER: + xRegCurve = PotentialRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + case EXC_CHSERTREND_MOVING_AVG: + xRegCurve = MovingAverageRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + } + + // trend line formatting + if( xRegCurve.is() && mxDataFmt ) + { + ScfPropertySet aPropSet( xRegCurve ); + mxDataFmt->ConvertLine( aPropSet, EXC_CHOBJTYPE_TRENDLINE ); + + aPropSet.SetProperty(EXC_CHPROP_CURVENAME, maTrendLineName); + aPropSet.SetProperty(EXC_CHPROP_POLYNOMIAL_DEGREE, static_cast<sal_Int32> (maData.mnOrder) ); + aPropSet.SetProperty(EXC_CHPROP_MOVING_AVERAGE_PERIOD, static_cast<sal_Int32> (maData.mnOrder) ); + aPropSet.SetProperty(EXC_CHPROP_EXTRAPOLATE_FORWARD, maData.mfForecastFor); + aPropSet.SetProperty(EXC_CHPROP_EXTRAPOLATE_BACKWARD, maData.mfForecastBack); + + bool bForceIntercept = std::isfinite(maData.mfIntercept); + aPropSet.SetProperty(EXC_CHPROP_FORCE_INTERCEPT, bForceIntercept); + if (bForceIntercept) + { + aPropSet.SetProperty(EXC_CHPROP_INTERCEPT_VALUE, maData.mfIntercept); + } + + // #i83100# show equation and correlation coefficient + ScfPropertySet aLabelProp( xRegCurve->getEquationProperties() ); + aLabelProp.SetBoolProperty( EXC_CHPROP_SHOWEQUATION, maData.mnShowEquation != 0 ); + aLabelProp.SetBoolProperty( EXC_CHPROP_SHOWCORRELATION, maData.mnShowRSquared != 0 ); + + // #i83100# formatting of the equation text box + if (const XclImpChText* pLabel = mxDataFmt->GetDataLabel()) + { + pLabel->ConvertFont( aLabelProp ); + pLabel->ConvertFrame( aLabelProp ); + pLabel->ConvertNumFmt( aLabelProp, false ); + } + } + + return xRegCurve; +} + +XclImpChSerErrorBar::XclImpChSerErrorBar( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChSerErrorBar::ReadChSerErrorBar( XclImpStream& rStrm ) +{ + maData.mnBarType = rStrm.ReaduInt8(); + maData.mnSourceType = rStrm.ReaduInt8(); + maData.mnLineEnd = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + maData.mfValue = rStrm.ReadDouble(); + maData.mnValueCount = rStrm.ReaduInt16(); +} + +void XclImpChSerErrorBar::SetSeriesData( XclImpChSourceLinkRef const & xValueLink, XclImpChDataFormatRef const & xDataFmt ) +{ + mxValueLink = xValueLink; + mxDataFmt = xDataFmt; +} + +Reference< XLabeledDataSequence > XclImpChSerErrorBar::CreateValueSequence() const +{ + return lclCreateLabeledDataSequence( mxValueLink, XclChartHelper::GetErrorBarValuesRole( maData.mnBarType ) ); +} + +Reference< XPropertySet > XclImpChSerErrorBar::CreateErrorBar( const XclImpChSerErrorBar* pPosBar, const XclImpChSerErrorBar* pNegBar ) +{ + Reference< XPropertySet > xErrorBar; + + if( const XclImpChSerErrorBar* pPrimaryBar = pPosBar ? pPosBar : pNegBar ) + { + xErrorBar.set( ScfApiHelper::CreateInstance( SERVICE_CHART2_ERRORBAR ), UNO_QUERY ); + ScfPropertySet aBarProp( xErrorBar ); + + // plus/minus bars visible? + aBarProp.SetBoolProperty( EXC_CHPROP_SHOWPOSITIVEERROR, pPosBar != nullptr ); + aBarProp.SetBoolProperty( EXC_CHPROP_SHOWNEGATIVEERROR, pNegBar != nullptr ); + + // type of displayed error + switch( pPrimaryBar->maData.mnSourceType ) + { + case EXC_CHSERERR_PERCENT: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::RELATIVE ); + aBarProp.SetProperty( EXC_CHPROP_POSITIVEERROR, pPrimaryBar->maData.mfValue ); + aBarProp.SetProperty( EXC_CHPROP_NEGATIVEERROR, pPrimaryBar->maData.mfValue ); + break; + case EXC_CHSERERR_FIXED: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::ABSOLUTE ); + aBarProp.SetProperty( EXC_CHPROP_POSITIVEERROR, pPrimaryBar->maData.mfValue ); + aBarProp.SetProperty( EXC_CHPROP_NEGATIVEERROR, pPrimaryBar->maData.mfValue ); + break; + case EXC_CHSERERR_STDDEV: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::STANDARD_DEVIATION ); + aBarProp.SetProperty( EXC_CHPROP_WEIGHT, pPrimaryBar->maData.mfValue ); + break; + case EXC_CHSERERR_STDERR: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::STANDARD_ERROR ); + break; + case EXC_CHSERERR_CUSTOM: + { + aBarProp.SetProperty( EXC_CHPROP_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( pPosBar ) + { + Reference< XLabeledDataSequence > xValueSeq = pPosBar->CreateValueSequence(); + if( xValueSeq.is() ) + aLabeledSeqVec.push_back( xValueSeq ); + } + // add negative values + if( pNegBar ) + { + Reference< XLabeledDataSequence > xValueSeq = pNegBar->CreateValueSequence(); + if( xValueSeq.is() ) + aLabeledSeqVec.push_back( xValueSeq ); + } + // attach labeled data sequences to series + if( aLabeledSeqVec.empty() ) + xErrorBar.clear(); + else + xDataSink->setData( ScfApiHelper::VectorToSequence( aLabeledSeqVec ) ); + } + } + break; + default: + xErrorBar.clear(); + } + + // error bar formatting + if( pPrimaryBar->mxDataFmt && xErrorBar.is() ) + pPrimaryBar->mxDataFmt->ConvertLine( aBarProp, EXC_CHOBJTYPE_ERRORBAR ); + } + + return xErrorBar; +} + +XclImpChSeries::XclImpChSeries( const XclImpChRoot& rRoot, sal_uInt16 nSeriesIdx ) : + XclImpChRoot( rRoot ), + mnGroupIdx( EXC_CHSERGROUP_NONE ), + mnSeriesIdx( nSeriesIdx ), + mnParentIdx( EXC_CHSERIES_INVALID ), + mbLabelDeleted( false ) +{ +} + +void XclImpChSeries::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnCategType = rStrm.ReaduInt16(); + maData.mnValueType = rStrm.ReaduInt16(); + maData.mnCategCount = rStrm.ReaduInt16(); + maData.mnValueCount = rStrm.ReaduInt16(); + if( GetBiff() == EXC_BIFF8 ) + { + maData.mnBubbleType = rStrm.ReaduInt16(); + maData.mnBubbleCount = rStrm.ReaduInt16(); + } +} + +void XclImpChSeries::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHSOURCELINK: + ReadChSourceLink( rStrm ); + break; + case EXC_ID_CHDATAFORMAT: + ReadChDataFormat( rStrm ); + break; + case EXC_ID_CHSERGROUP: + mnGroupIdx = rStrm.ReaduInt16(); + break; + case EXC_ID_CHSERPARENT: + ReadChSerParent( rStrm ); + break; + case EXC_ID_CHSERTRENDLINE: + ReadChSerTrendLine( rStrm ); + break; + case EXC_ID_CHSERERRORBAR: + ReadChSerErrorBar( rStrm ); + break; + case EXC_ID_CHLEGENDEXCEPTION: + ReadChLegendException( rStrm ); + break; + } +} + +void XclImpChSeries::SetDataFormat( const XclImpChDataFormatRef& xDataFmt ) +{ + if (!xDataFmt) + return; + + sal_uInt16 nPointIdx = xDataFmt->GetPointPos().mnPointIdx; + if (nPointIdx == EXC_CHDATAFORMAT_ALLPOINTS) + { + if (mxSeriesFmt) + // Don't overwrite the existing format. + return; + + mxSeriesFmt = xDataFmt; + if (HasParentSeries()) + return; + + XclImpChTypeGroupRef pTypeGroup = GetChartData().GetTypeGroup(mnGroupIdx); + if (pTypeGroup) + pTypeGroup->SetUsedFormatIndex(xDataFmt->GetFormatIdx()); + + return; + } + + if (nPointIdx >= EXC_CHDATAFORMAT_MAXPOINTCOUNT) + // Above the max point count. Bail out. + return; + + XclImpChDataFormatMap::iterator itr = maPointFmts.lower_bound(nPointIdx); + if (itr == maPointFmts.end() || maPointFmts.key_comp()(nPointIdx, itr->first)) + { + // No object exists at this point index position. Insert it. + itr = maPointFmts.insert(itr, XclImpChDataFormatMap::value_type(nPointIdx, xDataFmt)); + } +} + +void XclImpChSeries::SetDataLabel( const XclImpChTextRef& xLabel ) +{ + if (!xLabel) + return; + + sal_uInt16 nPointIdx = xLabel->GetPointPos().mnPointIdx; + if ((nPointIdx != EXC_CHDATAFORMAT_ALLPOINTS) && (nPointIdx >= EXC_CHDATAFORMAT_MAXPOINTCOUNT)) + // Above the maximum allowed data points. Bail out. + return; + + XclImpChTextMap::iterator itr = maLabels.lower_bound(nPointIdx); + if (itr == maLabels.end() || maLabels.key_comp()(nPointIdx, itr->first)) + { + // No object exists at this point index position. Insert it. + itr = maLabels.insert(itr, XclImpChTextMap::value_type(nPointIdx, xLabel)); + } +} + +void XclImpChSeries::AddChildSeries( const XclImpChSeries& rSeries ) +{ + OSL_ENSURE( !HasParentSeries(), "XclImpChSeries::AddChildSeries - not allowed for child series" ); + if (&rSeries == this) + { + SAL_WARN("sc.filter", "self add attempt"); + return; + } + + /* In Excel, trend lines and error bars are stored as own series. In Calc, + these are properties of the parent series. This function adds the + settings of the passed series to this series. */ + maTrendLines.insert( maTrendLines.end(), rSeries.maTrendLines.begin(), rSeries.maTrendLines.end() ); + for (auto const& it : rSeries.m_ErrorBars) + { + m_ErrorBars.insert(std::make_pair(it.first, std::make_unique<XclImpChSerErrorBar>(*it.second))); + } +} + +void XclImpChSeries::FinalizeDataFormats() +{ + if( HasParentSeries() ) + { + // *** series is a child series, e.g. trend line or error bar *** + + // create missing series format + if( !mxSeriesFmt ) + mxSeriesFmt = CreateDataFormat( EXC_CHDATAFORMAT_ALLPOINTS, 0 ); + + if( mxSeriesFmt ) + { + // #i83100# set text label format, e.g. for trend line equations + XclImpChTextRef xLabel; + XclImpChTextMap::iterator itr = maLabels.find(EXC_CHDATAFORMAT_ALLPOINTS); + if (itr != maLabels.end()) + xLabel = itr->second; + mxSeriesFmt->SetDataLabel(xLabel); + // create missing automatic formats + mxSeriesFmt->UpdateTrendLineFormat(); + } + + // copy series formatting to child objects + for (auto const& trendLine : maTrendLines) + { + trendLine->SetDataFormat(mxSeriesFmt); + if (mxTitleLink && mxTitleLink->HasString()) + { + trendLine->SetTrendlineName(mxTitleLink->GetString()); + } + } + for (auto const& it : m_ErrorBars) + { + it.second->SetSeriesData( mxValueLink, mxSeriesFmt ); + } + } + else if( XclImpChTypeGroup* pTypeGroup = GetChartData().GetTypeGroup( mnGroupIdx ).get() ) + { + // *** series is a regular data series *** + + // create missing series format + if( !mxSeriesFmt ) + { + // #i51639# use a new unused format index to create series default format + sal_uInt16 nFormatIdx = pTypeGroup->PopUnusedFormatIndex(); + mxSeriesFmt = CreateDataFormat( EXC_CHDATAFORMAT_ALLPOINTS, nFormatIdx ); + } + + // set text labels to data formats + for (auto const& label : maLabels) + { + sal_uInt16 nPointIdx = label.first; + if (nPointIdx == EXC_CHDATAFORMAT_ALLPOINTS) + { + if (!mxSeriesFmt) + mxSeriesFmt = CreateDataFormat(nPointIdx, EXC_CHDATAFORMAT_DEFAULT); + mxSeriesFmt->SetDataLabel(label.second); + } + else if (nPointIdx < EXC_CHDATAFORMAT_MAXPOINTCOUNT) + { + XclImpChDataFormatRef p; + XclImpChDataFormatMap::iterator itr = maPointFmts.lower_bound(nPointIdx); + if (itr == maPointFmts.end() || maPointFmts.key_comp()(nPointIdx, itr->first)) + { + // No object exists at this point index position. Insert + // a new one. + p = CreateDataFormat(nPointIdx, EXC_CHDATAFORMAT_DEFAULT); + itr = maPointFmts.insert( + itr, XclImpChDataFormatMap::value_type(nPointIdx, p)); + } + else + p = itr->second; + p->SetDataLabel(label.second); + } + } + + // update series format (copy missing formatting from group default format) + if( mxSeriesFmt ) + mxSeriesFmt->UpdateSeriesFormat( pTypeGroup->GetTypeInfo(), pTypeGroup->GetGroupFormat().get() ); + + // update data point formats (removes unchanged automatic formatting) + for (auto const& pointFormat : maPointFmts) + pointFormat.second->UpdatePointFormat( pTypeGroup->GetTypeInfo(), mxSeriesFmt.get() ); + } +} + +namespace { + +/** Returns the property set of the specified data point. */ +ScfPropertySet lclGetPointPropSet( Reference< XDataSeries > const & xDataSeries, sal_uInt16 nPointIdx ) +{ + ScfPropertySet aPropSet; + try + { + aPropSet.Set( xDataSeries->getDataPointByIndex( static_cast< sal_Int32 >( nPointIdx ) ) ); + } + catch( Exception& ) + { + OSL_FAIL( "lclGetPointPropSet - no data point property set" ); + } + return aPropSet; +} + +} // namespace + +Reference< XLabeledDataSequence > XclImpChSeries::CreateValueSequence( const OUString& rValueRole ) const +{ + return lclCreateLabeledDataSequence( mxValueLink, rValueRole, mxTitleLink.get() ); +} + +Reference< XLabeledDataSequence > XclImpChSeries::CreateCategSequence( const OUString& rCategRole ) const +{ + return lclCreateLabeledDataSequence( mxCategLink, rCategRole ); +} + +Reference< XDataSeries > XclImpChSeries::CreateDataSeries() const +{ + Reference< XDataSeries > xDataSeries; + if( const XclImpChTypeGroup* pTypeGroup = GetChartData().GetTypeGroup( mnGroupIdx ).get() ) + { + const XclChExtTypeInfo& rTypeInfo = pTypeGroup->GetTypeInfo(); + + // create the data series object + xDataSeries.set( ScfApiHelper::CreateInstance( SERVICE_CHART2_DATASERIES ), UNO_QUERY ); + + // attach data and title sequences to series + 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( EXC_CHPROP_ROLE_YVALUES ); + if( xYValueSeq.is() ) + aLabeledSeqVec.push_back( xYValueSeq ); + // add X values + if( !rTypeInfo.mbCategoryAxis ) + { + Reference< XLabeledDataSequence > xXValueSeq = + CreateCategSequence( EXC_CHPROP_ROLE_XVALUES ); + if( xXValueSeq.is() ) + aLabeledSeqVec.push_back( xXValueSeq ); + // add size values of bubble charts + if( rTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES ) + { + Reference< XLabeledDataSequence > xSizeValueSeq = + lclCreateLabeledDataSequence( mxBubbleLink, EXC_CHPROP_ROLE_SIZEVALUES, mxTitleLink.get() ); + if( xSizeValueSeq.is() ) + aLabeledSeqVec.push_back( xSizeValueSeq ); + } + } + // attach labeled data sequences to series + if( !aLabeledSeqVec.empty() ) + xDataSink->setData( ScfApiHelper::VectorToSequence( aLabeledSeqVec ) ); + } + + // series formatting + ScfPropertySet aSeriesProp( xDataSeries ); + if( mxSeriesFmt ) + mxSeriesFmt->Convert( aSeriesProp, rTypeInfo ); + + if (mbLabelDeleted) + aSeriesProp.SetProperty(EXC_CHPROP_SHOWLEGENDENTRY, false); + + // trend lines + ConvertTrendLines( xDataSeries ); + + // error bars + Reference< XPropertySet > xErrorBarX = CreateErrorBar( EXC_CHSERERR_XPLUS, EXC_CHSERERR_XMINUS ); + if( xErrorBarX.is() ) + aSeriesProp.SetProperty( EXC_CHPROP_ERRORBARX, xErrorBarX ); + Reference< XPropertySet > xErrorBarY = CreateErrorBar( EXC_CHSERERR_YPLUS, EXC_CHSERERR_YMINUS ); + if( xErrorBarY.is() ) + aSeriesProp.SetProperty( EXC_CHPROP_ERRORBARY, xErrorBarY ); + + // own area formatting for every data point (TODO: varying line color not supported) + bool bVarPointFmt = pTypeGroup->HasVarPointFormat() && rTypeInfo.IsSeriesFrameFormat(); + aSeriesProp.SetBoolProperty( EXC_CHPROP_VARYCOLORSBY, rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE ); + // #i91271# always set area formatting for every point in pie/doughnut charts + if (mxSeriesFmt && mxValueLink && ((bVarPointFmt && mxSeriesFmt->IsAutoArea()) || (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE))) + { + for( sal_uInt16 nPointIdx = 0, nPointCount = mxValueLink->GetCellCount(); nPointIdx < nPointCount; ++nPointIdx ) + { + ScfPropertySet aPointProp = lclGetPointPropSet( xDataSeries, nPointIdx ); + mxSeriesFmt->ConvertArea( aPointProp, bVarPointFmt ? nPointIdx : mnSeriesIdx ); + } + } + + // data point formatting + for (auto const& pointFormat : maPointFmts) + { + ScfPropertySet aPointProp = lclGetPointPropSet( xDataSeries, pointFormat.first ); + pointFormat.second->Convert( aPointProp, rTypeInfo, &aSeriesProp ); + } + } + return xDataSeries; +} + +void XclImpChSeries::FillAllSourceLinks( ::std::vector< ScTokenRef >& rTokens ) const +{ + if( mxValueLink ) + mxValueLink->FillSourceLink( rTokens ); + if( mxCategLink ) + mxCategLink->FillSourceLink( rTokens ); + if( mxTitleLink ) + mxTitleLink->FillSourceLink( rTokens ); + if( mxBubbleLink ) + mxBubbleLink->FillSourceLink( rTokens ); +} + +void XclImpChSeries::ReadChSourceLink( XclImpStream& rStrm ) +{ + XclImpChSourceLinkRef xSrcLink = std::make_shared<XclImpChSourceLink>( GetChRoot() ); + xSrcLink->ReadChSourceLink( rStrm ); + switch( xSrcLink->GetDestType() ) + { + case EXC_CHSRCLINK_TITLE: mxTitleLink = xSrcLink; break; + case EXC_CHSRCLINK_VALUES: mxValueLink = xSrcLink; break; + case EXC_CHSRCLINK_CATEGORY: mxCategLink = xSrcLink; break; + case EXC_CHSRCLINK_BUBBLES: mxBubbleLink = xSrcLink; break; + } +} + +void XclImpChSeries::ReadChDataFormat( XclImpStream& rStrm ) +{ + // #i51639# chart stores all data formats and assigns them later to the series + GetChartData().ReadChDataFormat( rStrm ); +} + +void XclImpChSeries::ReadChSerParent( XclImpStream& rStrm ) +{ + mnParentIdx = rStrm.ReaduInt16(); + // index to parent series is 1-based, convert it to 0-based + if( mnParentIdx > 0 ) + --mnParentIdx; + else + mnParentIdx = EXC_CHSERIES_INVALID; +} + +void XclImpChSeries::ReadChSerTrendLine( XclImpStream& rStrm ) +{ + XclImpChSerTrendLineRef xTrendLine = std::make_shared<XclImpChSerTrendLine>( GetChRoot() ); + xTrendLine->ReadChSerTrendLine( rStrm ); + maTrendLines.push_back( xTrendLine ); +} + +void XclImpChSeries::ReadChSerErrorBar( XclImpStream& rStrm ) +{ + unique_ptr<XclImpChSerErrorBar> pErrorBar(new XclImpChSerErrorBar(GetChRoot())); + pErrorBar->ReadChSerErrorBar(rStrm); + sal_uInt8 nBarType = pErrorBar->GetBarType(); + m_ErrorBars.insert(std::make_pair(nBarType, std::move(pErrorBar))); +} + +XclImpChDataFormatRef XclImpChSeries::CreateDataFormat( sal_uInt16 nPointIdx, sal_uInt16 nFormatIdx ) +{ + XclImpChDataFormatRef xDataFmt = std::make_shared<XclImpChDataFormat>( GetChRoot() ); + xDataFmt->SetPointPos( XclChDataPointPos( mnSeriesIdx, nPointIdx ), nFormatIdx ); + return xDataFmt; +} + +void XclImpChSeries::ConvertTrendLines( Reference< XDataSeries > const & xDataSeries ) const +{ + Reference< XRegressionCurveContainer > xRegCurveCont( xDataSeries, UNO_QUERY ); + if( !xRegCurveCont.is() ) + return; + + for (auto const& trendLine : maTrendLines) + { + try + { + Reference< XRegressionCurve > xRegCurve = trendLine->CreateRegressionCurve(); + if( xRegCurve.is() ) + { + xRegCurveCont->addRegressionCurve( xRegCurve ); + } + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChSeries::ConvertTrendLines - cannot add regression curve" ); + } + } +} + +Reference< XPropertySet > XclImpChSeries::CreateErrorBar( sal_uInt8 nPosBarId, sal_uInt8 nNegBarId ) const +{ + XclImpChSerErrorBarMap::const_iterator itrPosBar = m_ErrorBars.find(nPosBarId); + XclImpChSerErrorBarMap::const_iterator itrNegBar = m_ErrorBars.find(nNegBarId); + XclImpChSerErrorBarMap::const_iterator itrEnd = m_ErrorBars.end(); + if (itrPosBar == itrEnd || itrNegBar == itrEnd) + return Reference<XPropertySet>(); + + return XclImpChSerErrorBar::CreateErrorBar(itrPosBar->second.get(), itrNegBar->second.get()); +} + +void XclImpChSeries::ReadChLegendException(XclImpStream& rStrm) +{ + rStrm.Ignore(2); + sal_uInt16 nFlags = rStrm.ReaduInt16(); + mbLabelDeleted = (nFlags & EXC_CHLEGENDEXCEPTION_DELETED); +} + +// Chart type groups ========================================================== + +XclImpChType::XclImpChType( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ), + mnRecId( EXC_ID_CHUNKNOWN ), + maTypeInfo( rRoot.GetChartTypeInfo( EXC_CHTYPEID_UNKNOWN ) ) +{ +} + +void XclImpChType::ReadChType( XclImpStream& rStrm ) +{ + sal_uInt16 nRecId = rStrm.GetRecId(); + bool bKnownType = true; + + switch( nRecId ) + { + case EXC_ID_CHBAR: + maData.mnOverlap = rStrm.ReadInt16(); + maData.mnGap = rStrm.ReadInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + break; + + case EXC_ID_CHLINE: + case EXC_ID_CHAREA: + case EXC_ID_CHRADARLINE: + case EXC_ID_CHRADARAREA: + maData.mnFlags = rStrm.ReaduInt16(); + break; + + case EXC_ID_CHPIE: + maData.mnRotation = rStrm.ReaduInt16(); + maData.mnPieHole = rStrm.ReaduInt16(); + if( GetBiff() == EXC_BIFF8 ) + maData.mnFlags = rStrm.ReaduInt16(); + else + maData.mnFlags = 0; + break; + + case EXC_ID_CHPIEEXT: + maData.mnRotation = 0; + maData.mnPieHole = 0; + maData.mnFlags = 0; + break; + + case EXC_ID_CHSCATTER: + if( GetBiff() == EXC_BIFF8 ) + { + maData.mnBubbleSize = rStrm.ReaduInt16(); + maData.mnBubbleType = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + } + else + maData.mnFlags = 0; + break; + + case EXC_ID_CHSURFACE: + maData.mnFlags = rStrm.ReaduInt16(); + break; + + default: + bKnownType = false; + } + + if( bKnownType ) + mnRecId = nRecId; +} + +void XclImpChType::Finalize( bool bStockChart ) +{ + switch( mnRecId ) + { + case EXC_ID_CHLINE: + maTypeInfo = GetChartTypeInfo( bStockChart ? + EXC_CHTYPEID_STOCK : EXC_CHTYPEID_LINE ); + break; + case EXC_ID_CHBAR: + maTypeInfo = GetChartTypeInfo( ::get_flagvalue( + maData.mnFlags, EXC_CHBAR_HORIZONTAL, + EXC_CHTYPEID_HORBAR, EXC_CHTYPEID_BAR ) ); + break; + case EXC_ID_CHPIE: + maTypeInfo = GetChartTypeInfo( (maData.mnPieHole > 0) ? + EXC_CHTYPEID_DONUT : EXC_CHTYPEID_PIE ); + break; + case EXC_ID_CHSCATTER: + maTypeInfo = GetChartTypeInfo( ::get_flagvalue( + maData.mnFlags, EXC_CHSCATTER_BUBBLES, + EXC_CHTYPEID_BUBBLES, EXC_CHTYPEID_SCATTER ) ); + break; + default: + maTypeInfo = GetChartTypeInfo( mnRecId ); + } + + switch( maTypeInfo.meTypeId ) + { + case EXC_CHTYPEID_PIEEXT: + case EXC_CHTYPEID_BUBBLES: + case EXC_CHTYPEID_SURFACE: + case EXC_CHTYPEID_UNKNOWN: + GetTracer().TraceChartUnKnownType(); + break; + default:; + } +} + +bool XclImpChType::IsStacked() const +{ + bool bStacked = false; + if( maTypeInfo.mbSupportsStacking ) switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_LINE: + bStacked = + ::get_flag( maData.mnFlags, EXC_CHLINE_STACKED ) && + !::get_flag( maData.mnFlags, EXC_CHLINE_PERCENT ); + break; + case EXC_CHTYPECATEG_BAR: + bStacked = + ::get_flag( maData.mnFlags, EXC_CHBAR_STACKED ) && + !::get_flag( maData.mnFlags, EXC_CHBAR_PERCENT ); + break; + default:; + } + return bStacked; +} + +bool XclImpChType::IsPercent() const +{ + bool bPercent = false; + if( maTypeInfo.mbSupportsStacking ) switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_LINE: + bPercent = + ::get_flag( maData.mnFlags, EXC_CHLINE_STACKED ) && + ::get_flag( maData.mnFlags, EXC_CHLINE_PERCENT ); + break; + case EXC_CHTYPECATEG_BAR: + bPercent = + ::get_flag( maData.mnFlags, EXC_CHBAR_STACKED ) && + ::get_flag( maData.mnFlags, EXC_CHBAR_PERCENT ); + break; + default:; + } + return bPercent; +} + +bool XclImpChType::HasCategoryLabels() const +{ + // radar charts disable category labels in chart type, not in CHTICK of X axis + return (maTypeInfo.meTypeCateg != EXC_CHTYPECATEG_RADAR) || ::get_flag( maData.mnFlags, EXC_CHRADAR_AXISLABELS ); +} + +Reference< XCoordinateSystem > XclImpChType::CreateCoordSystem( bool b3dChart ) const +{ + // create the coordinate system object + Reference< css::uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + Reference< XCoordinateSystem > xCoordSystem; + if( maTypeInfo.mbPolarCoordSystem ) + { + if( b3dChart ) + xCoordSystem = css::chart2::PolarCoordinateSystem3d::create(xContext); + else + xCoordSystem = css::chart2::PolarCoordinateSystem2d::create(xContext); + } + else + { + if( b3dChart ) + xCoordSystem = css::chart2::CartesianCoordinateSystem3d::create(xContext); + else + xCoordSystem = css::chart2::CartesianCoordinateSystem2d::create(xContext); + } + + // swap X and Y axis + if( maTypeInfo.mbSwappedAxesSet ) + { + ScfPropertySet aCoordSysProp( xCoordSystem ); + aCoordSysProp.SetBoolProperty( EXC_CHPROP_SWAPXANDYAXIS, true ); + } + + return xCoordSystem; +} + +Reference< XChartType > XclImpChType::CreateChartType( Reference< XDiagram > const & xDiagram, bool b3dChart ) const +{ + OUString aService = OUString::createFromAscii( maTypeInfo.mpcServiceName ); + Reference< XChartType > xChartType( ScfApiHelper::CreateInstance( aService ), UNO_QUERY ); + + // additional properties + switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_BAR: + { + ScfPropertySet aTypeProp( xChartType ); + Sequence< sal_Int32 > aInt32Seq{ -maData.mnOverlap, -maData.mnOverlap }; + aTypeProp.SetProperty( EXC_CHPROP_OVERLAPSEQ, aInt32Seq ); + aInt32Seq = { maData.mnGap, maData.mnGap }; + aTypeProp.SetProperty( EXC_CHPROP_GAPWIDTHSEQ, aInt32Seq ); + } + break; + case EXC_CHTYPECATEG_PIE: + { + ScfPropertySet aTypeProp( xChartType ); + aTypeProp.SetBoolProperty( EXC_CHPROP_USERINGS, maTypeInfo.meTypeId == EXC_CHTYPEID_DONUT ); + /* #i85166# starting angle of first pie slice. 3D pie charts use Y + rotation setting in view3D element. Of-pie charts do not + support pie rotation. */ + if( !b3dChart && (maTypeInfo.meTypeId != EXC_CHTYPEID_PIEEXT) ) + { + ScfPropertySet aDiaProp( xDiagram ); + XclImpChRoot::ConvertPieRotation( aDiaProp, maData.mnRotation ); + } + } + break; + default:; + } + + return xChartType; +} + +void XclImpChChart3d::ReadChChart3d( XclImpStream& rStrm ) +{ + maData.mnRotation = rStrm.ReaduInt16(); + maData.mnElevation = rStrm.ReadInt16(); + maData.mnEyeDist = rStrm.ReaduInt16(); + maData.mnRelHeight = rStrm.ReaduInt16(); + maData.mnRelDepth = rStrm.ReaduInt16(); + maData.mnDepthGap = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChChart3d::Convert( ScfPropertySet& rPropSet, bool b3dWallChart ) const +{ + namespace cssd = ::com::sun::star::drawing; + +// #i104057# do not assert this, written by broken external generators +// OSL_ENSURE( ::get_flag( maData.mnFlags, EXC_CHCHART3D_HASWALLS ) == b3dWallChart, "XclImpChChart3d::Convert - wrong wall flag" ); + + sal_Int32 nRotationY = 0; + sal_Int32 nRotationX = 0; + sal_Int32 nPerspective = 15; + bool bRightAngled = false; + cssd::ProjectionMode eProjMode = cssd::ProjectionMode_PERSPECTIVE; + Color aAmbientColor, aLightColor; + + if( b3dWallChart ) + { + // Y rotation (Excel [0..359], Chart2 [-179,180]) + nRotationY = NormAngle180<sal_Int32>(maData.mnRotation); + // X rotation a.k.a. elevation (Excel [-90..90], Chart2 [-179,180]) + nRotationX = limit_cast< sal_Int32, sal_Int32 >( maData.mnElevation, -90, 90 ); + // perspective (Excel and Chart2 [0,100]) + nPerspective = limit_cast< sal_Int32, sal_Int32 >( maData.mnEyeDist, 0, 100 ); + // right-angled axes + bRightAngled = !::get_flag( maData.mnFlags, EXC_CHCHART3D_REAL3D ); + // projection mode (parallel axes, if right-angled, #i90360# or if perspective is at 0%) + bool bParallel = bRightAngled || (nPerspective == 0); + eProjMode = bParallel ? cssd::ProjectionMode_PARALLEL : cssd::ProjectionMode_PERSPECTIVE; + // ambient color (Gray 20%) + aAmbientColor = Color( 204, 204, 204 ); + // light color (Gray 60%) + aLightColor = Color( 102, 102, 102 ); + } + else + { + // Y rotation not used in pie charts, but 'first pie slice angle' + nRotationY = 0; + XclImpChRoot::ConvertPieRotation( rPropSet, maData.mnRotation ); + // X rotation a.k.a. elevation (map Excel [10..80] to Chart2 [-80,-10]) + nRotationX = limit_cast< sal_Int32, sal_Int32 >( maData.mnElevation, 10, 80 ) - 90; + // perspective (Excel and Chart2 [0,100]) + nPerspective = limit_cast< sal_Int32, sal_Int32 >( maData.mnEyeDist, 0, 100 ); + // no right-angled axes in pie charts, but parallel projection + bRightAngled = false; + eProjMode = cssd::ProjectionMode_PARALLEL; + // ambient color (Gray 30%) + aAmbientColor = Color( 179, 179, 179 ); + // light color (Gray 70%) + aLightColor = Color( 76, 76, 76 ); + } + + // properties + rPropSet.SetProperty( EXC_CHPROP_3DRELATIVEHEIGHT, static_cast<sal_Int32>(maData.mnRelHeight / 2)); // seems to be 200%, change to 100% + rPropSet.SetProperty( EXC_CHPROP_ROTATIONVERTICAL, nRotationY ); + rPropSet.SetProperty( EXC_CHPROP_ROTATIONHORIZONTAL, nRotationX ); + rPropSet.SetProperty( EXC_CHPROP_PERSPECTIVE, nPerspective ); + rPropSet.SetBoolProperty( EXC_CHPROP_RIGHTANGLEDAXES, bRightAngled ); + rPropSet.SetProperty( EXC_CHPROP_D3DSCENEPERSPECTIVE, eProjMode ); + + // light settings + rPropSet.SetProperty( EXC_CHPROP_D3DSCENESHADEMODE, cssd::ShadeMode_FLAT ); + rPropSet.SetColorProperty( EXC_CHPROP_D3DSCENEAMBIENTCOLOR, aAmbientColor ); + rPropSet.SetBoolProperty( EXC_CHPROP_D3DSCENELIGHTON1, false ); + rPropSet.SetBoolProperty( EXC_CHPROP_D3DSCENELIGHTON2, true ); + rPropSet.SetColorProperty( EXC_CHPROP_D3DSCENELIGHTCOLOR2, aLightColor ); + rPropSet.SetProperty( EXC_CHPROP_D3DSCENELIGHTDIR2, cssd::Direction3D( 0.2, 0.4, 1.0 ) ); +} + +XclImpChLegend::XclImpChLegend( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChLegend::ReadHeaderRecord( XclImpStream& rStrm ) +{ + rStrm >> maData.maRect; + maData.mnDockMode = rStrm.ReaduInt8(); + maData.mnSpacing = rStrm.ReaduInt8(); + maData.mnFlags = rStrm.ReaduInt16(); + + // trace unsupported features + if( GetTracer().IsEnabled() ) + { + if( maData.mnDockMode == EXC_CHLEGEND_NOTDOCKED ) + GetTracer().TraceChartLegendPosition(); + if( ::get_flag( maData.mnFlags, EXC_CHLEGEND_DATATABLE ) ) + GetTracer().TraceChartDataTable(); + } +} + +void XclImpChLegend::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAMEPOS: + mxFramePos = std::make_shared<XclImpChFramePos>(); + mxFramePos->ReadChFramePos( rStrm ); + break; + case EXC_ID_CHTEXT: + mxText = std::make_shared<XclImpChText>( GetChRoot() ); + mxText->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHFRAME: + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_LEGEND ); + mxFrame->ReadRecordGroup( rStrm ); + break; + } +} + +void XclImpChLegend::Finalize() +{ + // legend default formatting differs in OOChart and Excel, missing frame means automatic + if( !mxFrame ) + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_LEGEND ); + // Update text formatting. If mxText is empty, the passed default text is used. + lclUpdateText( mxText, GetChartData().GetDefaultText( EXC_CHTEXTTYPE_LEGEND ) ); +} + +Reference< XLegend > XclImpChLegend::CreateLegend() const +{ + Reference< XLegend > xLegend( ScfApiHelper::CreateInstance( SERVICE_CHART2_LEGEND ), UNO_QUERY ); + if( xLegend.is() ) + { + ScfPropertySet aLegendProp( xLegend ); + aLegendProp.SetBoolProperty( EXC_CHPROP_SHOW, true ); + + // frame properties + if( mxFrame ) + mxFrame->Convert( aLegendProp ); + // text properties + if( mxText ) + mxText->ConvertFont( aLegendProp ); + + /* Legend position and size. Default positions are used only if the + plot area is positioned automatically (Excel sets the plot area to + manual mode, if the legend is moved or resized). With manual plot + areas, Excel ignores the value in maData.mnDockMode completely. */ + cssc2::LegendPosition eApiPos = cssc2::LegendPosition_LINE_END; + cssc::ChartLegendExpansion eApiExpand = cssc::ChartLegendExpansion_CUSTOM; + if( !GetChartData().IsManualPlotArea() ) switch( maData.mnDockMode ) + { + case EXC_CHLEGEND_LEFT: + eApiPos = cssc2::LegendPosition_LINE_START; + eApiExpand = cssc::ChartLegendExpansion_HIGH; + break; + case EXC_CHLEGEND_RIGHT: + // top-right not supported + case EXC_CHLEGEND_CORNER: + eApiPos = cssc2::LegendPosition_LINE_END; + eApiExpand = cssc::ChartLegendExpansion_HIGH; + break; + case EXC_CHLEGEND_TOP: + eApiPos = cssc2::LegendPosition_PAGE_START; + eApiExpand = cssc::ChartLegendExpansion_WIDE; + break; + case EXC_CHLEGEND_BOTTOM: + eApiPos = cssc2::LegendPosition_PAGE_END; + eApiExpand = cssc::ChartLegendExpansion_WIDE; + break; + } + + // no automatic position/size: try to find the correct position and size + if( GetChartData().IsManualPlotArea() || maData.mnDockMode == EXC_CHLEGEND_NOTDOCKED ) + { + const XclChFramePos* pFramePos = mxFramePos ? &mxFramePos->GetFramePosData() : nullptr; + + /* Legend position. Only the settings from the CHFRAMEPOS record + are used by Excel, the position in the CHLEGEND record will be + ignored. */ + if( pFramePos ) + { + RelativePosition aRelPos( + CalcRelativeFromChartX( pFramePos->maRect.mnX ), + CalcRelativeFromChartY( pFramePos->maRect.mnY ), + css::drawing::Alignment_TOP_LEFT ); + aLegendProp.SetProperty( EXC_CHPROP_RELATIVEPOSITION, aRelPos ); + } + else + { + // no manual position/size found, just go for the default + eApiPos = cssc2::LegendPosition_LINE_END; + } + + /* Legend size. The member mnBRMode specifies whether size is + automatic or changes manually. Manual size is given in points, + not in chart units. */ + if( pFramePos && (pFramePos->mnBRMode == EXC_CHFRAMEPOS_ABSSIZE_POINTS) && + (pFramePos->maRect.mnWidth > 0) && (pFramePos->maRect.mnHeight > 0) ) + { + eApiExpand = cssc::ChartLegendExpansion_CUSTOM; + sal_Int32 nWidthHmm = o3tl::convert(pFramePos->maRect.mnWidth, o3tl::Length::pt, o3tl::Length::mm100); + sal_Int32 nHeightHmm = o3tl::convert(pFramePos->maRect.mnHeight, o3tl::Length::pt, o3tl::Length::mm100); + RelativeSize aRelSize( CalcRelativeFromHmmX( nWidthHmm ), CalcRelativeFromHmmY( nHeightHmm ) ); + aLegendProp.SetProperty( EXC_CHPROP_RELATIVESIZE, aRelSize ); + } + else + { + // automatic size: determine entry direction from flags + eApiExpand = ::get_flagvalue( maData.mnFlags, EXC_CHLEGEND_STACKED, + cssc::ChartLegendExpansion_HIGH, cssc::ChartLegendExpansion_WIDE ); + } + } + aLegendProp.SetProperty( EXC_CHPROP_ANCHORPOSITION, eApiPos ); + aLegendProp.SetProperty( EXC_CHPROP_EXPANSION, eApiExpand ); + } + return xLegend; +} + +XclImpChDropBar::XclImpChDropBar( sal_uInt16 nDropBar ) : + mnDropBar( nDropBar ), + mnBarDist( 0 ) +{ +} + +void XclImpChDropBar::ReadHeaderRecord( XclImpStream& rStrm ) +{ + mnBarDist = rStrm.ReaduInt16(); +} + +void XclImpChDropBar::Convert( const XclImpChRoot& rRoot, ScfPropertySet& rPropSet ) const +{ + XclChObjectType eObjType = EXC_CHOBJTYPE_BACKGROUND; + switch( mnDropBar ) + { + case EXC_CHDROPBAR_UP: eObjType = EXC_CHOBJTYPE_WHITEDROPBAR; break; + case EXC_CHDROPBAR_DOWN: eObjType = EXC_CHOBJTYPE_BLACKDROPBAR; break; + } + ConvertFrameBase( rRoot, rPropSet, eObjType ); +} + +XclImpChTypeGroup::XclImpChTypeGroup( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ), + maType( rRoot ), + maTypeInfo( maType.GetTypeInfo() ) +{ + // Initialize unused format indexes set. At this time, all formats are unused. + for( sal_uInt16 nFormatIdx = 0; nFormatIdx <= EXC_CHSERIES_MAXSERIES; ++nFormatIdx ) + maUnusedFormats.insert( maUnusedFormats.end(), nFormatIdx ); +} + +void XclImpChTypeGroup::ReadHeaderRecord( XclImpStream& rStrm ) +{ + rStrm.Ignore( 16 ); + maData.mnFlags = rStrm.ReaduInt16(); + maData.mnGroupIdx = rStrm.ReaduInt16(); +} + +void XclImpChTypeGroup::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHCHART3D: + mxChart3d = std::make_shared<XclImpChChart3d>(); + mxChart3d->ReadChChart3d( rStrm ); + break; + case EXC_ID_CHLEGEND: + mxLegend = std::make_shared<XclImpChLegend>( GetChRoot() ); + mxLegend->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHDEFAULTTEXT: + GetChartData().ReadChDefaultText( rStrm ); + break; + case EXC_ID_CHDROPBAR: + ReadChDropBar( rStrm ); + break; + case EXC_ID_CHCHARTLINE: + ReadChChartLine( rStrm ); + break; + case EXC_ID_CHDATAFORMAT: + ReadChDataFormat( rStrm ); + break; + default: + maType.ReadChType( rStrm ); + } +} + +void XclImpChTypeGroup::Finalize() +{ + // check and set valid chart type + bool bStockChart = + (maType.GetRecId() == EXC_ID_CHLINE) && // must be a line chart + !mxChart3d && // must be a 2d chart + m_ChartLines.find(EXC_CHCHARTLINE_HILO) != m_ChartLines.end() && // must contain hi-lo lines + (maSeries.size() == static_cast<XclImpChSeriesVec::size_type>(HasDropBars() ? 4 : 3)); // correct series count + maType.Finalize( bStockChart ); + + // extended type info + maTypeInfo.Set( maType.GetTypeInfo(), static_cast< bool >(mxChart3d), false ); + + // reverse series order for some unstacked 2D chart types + if( maTypeInfo.mbReverseSeries && !Is3dChart() && !maType.IsStacked() && !maType.IsPercent() ) + ::std::reverse( maSeries.begin(), maSeries.end() ); + + // update chart type group format, may depend on chart type finalized above + if( mxGroupFmt ) + mxGroupFmt->UpdateGroupFormat( maTypeInfo ); +} + +void XclImpChTypeGroup::AddSeries( XclImpChSeriesRef const & xSeries ) +{ + if( xSeries ) + maSeries.push_back( xSeries ); + // store first inserted series separately, series order may be reversed later + if( !mxFirstSeries ) + mxFirstSeries = xSeries; +} + +void XclImpChTypeGroup::SetUsedFormatIndex( sal_uInt16 nFormatIdx ) +{ + maUnusedFormats.erase( nFormatIdx ); +} + +sal_uInt16 XclImpChTypeGroup::PopUnusedFormatIndex() +{ + OSL_ENSURE( !maUnusedFormats.empty(), "XclImpChTypeGroup::PopUnusedFormatIndex - no more format indexes available" ); + sal_uInt16 nFormatIdx = maUnusedFormats.empty() ? 0 : *maUnusedFormats.begin(); + SetUsedFormatIndex( nFormatIdx ); + return nFormatIdx; +} + +bool XclImpChTypeGroup::HasVarPointFormat() const +{ + return ::get_flag( maData.mnFlags, EXC_CHTYPEGROUP_VARIEDCOLORS ) && + ((maTypeInfo.meVarPointMode == EXC_CHVARPOINT_MULTI) || // multiple series allowed + ((maTypeInfo.meVarPointMode == EXC_CHVARPOINT_SINGLE) && // or exactly 1 series? + (maSeries.size() == 1))); +} + +bool XclImpChTypeGroup::HasConnectorLines() const +{ + // existence of connector lines (only in stacked bar charts) + if ( !(maType.IsStacked() || maType.IsPercent()) || (maTypeInfo.meTypeCateg != EXC_CHTYPECATEG_BAR) ) + return false; + XclImpChLineFormatMap::const_iterator aConLine = m_ChartLines.find(EXC_CHCHARTLINE_CONNECT); + return (aConLine != m_ChartLines.end() && aConLine->second.HasLine()); +} + +OUString XclImpChTypeGroup::GetSingleSeriesTitle() const +{ + // no automatic title for series with trendlines or error bars + // pie charts always show an automatic title, even if more series exist + return (mxFirstSeries && !mxFirstSeries->HasChildSeries() && (maTypeInfo.mbSingleSeriesVis || (maSeries.size() == 1))) ? + mxFirstSeries->GetTitle() : OUString(); +} + +void XclImpChTypeGroup::ConvertChart3d( ScfPropertySet& rPropSet ) const +{ + if( mxChart3d ) + mxChart3d->Convert( rPropSet, Is3dWallChart() ); +} + +Reference< XCoordinateSystem > XclImpChTypeGroup::CreateCoordSystem() const +{ + return maType.CreateCoordSystem( Is3dChart() ); +} + +Reference< XChartType > XclImpChTypeGroup::CreateChartType( Reference< XDiagram > const & xDiagram, sal_Int32 nApiAxesSetIdx ) const +{ + OSL_ENSURE( IsValidGroup(), "XclImpChTypeGroup::CreateChartType - type group without series" ); + + // create the chart type object + Reference< XChartType > xChartType = maType.CreateChartType( xDiagram, Is3dChart() ); + + // bar chart connector lines + if( HasConnectorLines() ) + { + ScfPropertySet aDiaProp( xDiagram ); + aDiaProp.SetBoolProperty( EXC_CHPROP_CONNECTBARS, true ); + } + + /* Stock chart needs special processing. Create one 'big' series with + data sequences of different roles. */ + if( maTypeInfo.meTypeId == EXC_CHTYPEID_STOCK ) + CreateStockSeries( xChartType, nApiAxesSetIdx ); + else + CreateDataSeries( xChartType, nApiAxesSetIdx ); + + return xChartType; +} + +Reference< XLabeledDataSequence > XclImpChTypeGroup::CreateCategSequence() const +{ + Reference< XLabeledDataSequence > xLabeledSeq; + // create category sequence from first visible series + if( mxFirstSeries ) + xLabeledSeq = mxFirstSeries->CreateCategSequence( EXC_CHPROP_ROLE_CATEG ); + return xLabeledSeq; +} + +void XclImpChTypeGroup::ReadChDropBar( XclImpStream& rStrm ) +{ + if (m_DropBars.find(EXC_CHDROPBAR_UP) == m_DropBars.end()) + { + unique_ptr<XclImpChDropBar> p(new XclImpChDropBar(EXC_CHDROPBAR_UP)); + p->ReadRecordGroup(rStrm); + m_DropBars.insert(std::make_pair(EXC_CHDROPBAR_UP, std::move(p))); + } + else if (m_DropBars.find(EXC_CHDROPBAR_DOWN) == m_DropBars.end()) + { + unique_ptr<XclImpChDropBar> p(new XclImpChDropBar(EXC_CHDROPBAR_DOWN)); + p->ReadRecordGroup(rStrm); + m_DropBars.insert(std::make_pair(EXC_CHDROPBAR_DOWN, std::move(p))); + } +} + +void XclImpChTypeGroup::ReadChChartLine( XclImpStream& rStrm ) +{ + sal_uInt16 nLineId = rStrm.ReaduInt16(); + if( (rStrm.GetNextRecId() == EXC_ID_CHLINEFORMAT) && rStrm.StartNextRecord() ) + { + XclImpChLineFormat aLineFmt; + aLineFmt.ReadChLineFormat( rStrm ); + m_ChartLines[ nLineId ] = aLineFmt; + } +} + +void XclImpChTypeGroup::ReadChDataFormat( XclImpStream& rStrm ) +{ + // global series and data point format + XclImpChDataFormatRef xDataFmt = std::make_shared<XclImpChDataFormat>( GetChRoot() ); + xDataFmt->ReadRecordGroup( rStrm ); + const XclChDataPointPos& rPos = xDataFmt->GetPointPos(); + if( (rPos.mnSeriesIdx == 0) && (rPos.mnPointIdx == 0) && + (xDataFmt->GetFormatIdx() == EXC_CHDATAFORMAT_DEFAULT) ) + mxGroupFmt = xDataFmt; +} + +void XclImpChTypeGroup::InsertDataSeries( Reference< XChartType > const & xChartType, + Reference< XDataSeries > const & xSeries, sal_Int32 nApiAxesSetIdx ) const +{ + Reference< XDataSeriesContainer > xSeriesCont( xChartType, UNO_QUERY ); + if( !(xSeriesCont.is() && xSeries.is()) ) + return; + + // series stacking mode + cssc2::StackingDirection eStacking = cssc2::StackingDirection_NO_STACKING; + // stacked overrides deep-3d + if( maType.IsStacked() || maType.IsPercent() ) + eStacking = cssc2::StackingDirection_Y_STACKING; + else if( Is3dDeepChart() ) + eStacking = cssc2::StackingDirection_Z_STACKING; + + // additional series properties + ScfPropertySet aSeriesProp( xSeries ); + aSeriesProp.SetProperty( EXC_CHPROP_STACKINGDIR, eStacking ); + aSeriesProp.SetProperty( EXC_CHPROP_ATTAXISINDEX, nApiAxesSetIdx ); + + // insert series into container + try + { + xSeriesCont->addDataSeries( xSeries ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChTypeGroup::InsertDataSeries - cannot add data series" ); + } +} + +void XclImpChTypeGroup::CreateDataSeries( Reference< XChartType > const & xChartType, sal_Int32 nApiAxesSetIdx ) const +{ + bool bSpline = false; + for (auto const& elem : maSeries) + { + Reference< XDataSeries > xDataSeries = elem->CreateDataSeries(); + InsertDataSeries( xChartType, xDataSeries, nApiAxesSetIdx ); + bSpline |= elem->HasSpline(); + } + // spline - TODO: set at single series (#i66858#) + if( bSpline && !maTypeInfo.IsSeriesFrameFormat() && (maTypeInfo.meTypeCateg != EXC_CHTYPECATEG_RADAR) ) + { + ScfPropertySet aTypeProp( xChartType ); + aTypeProp.SetProperty( EXC_CHPROP_CURVESTYLE, css::chart2::CurveStyle_CUBIC_SPLINES ); + } +} + +void XclImpChTypeGroup::CreateStockSeries( Reference< XChartType > const & xChartType, sal_Int32 nApiAxesSetIdx ) const +{ + // create the data series object + Reference< XDataSeries > xDataSeries( ScfApiHelper::CreateInstance( SERVICE_CHART2_DATASERIES ), UNO_QUERY ); + Reference< XDataSink > xDataSink( xDataSeries, UNO_QUERY ); + if( !xDataSink.is() ) + return; + + // create a list of data sequences from all series + ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec; + OSL_ENSURE( maSeries.size() >= 3, "XclImpChTypeGroup::CreateChartType - missing stock series" ); + int nRoleIdx = (maSeries.size() == 3) ? 1 : 0; + for( const auto& rxSeries : maSeries ) + { + // create a data sequence with a specific role + OUString aRole; + switch( nRoleIdx ) + { + case 0: aRole = EXC_CHPROP_ROLE_OPENVALUES; break; + case 1: aRole = EXC_CHPROP_ROLE_HIGHVALUES; break; + case 2: aRole = EXC_CHPROP_ROLE_LOWVALUES; break; + case 3: aRole = EXC_CHPROP_ROLE_CLOSEVALUES; break; + } + Reference< XLabeledDataSequence > xDataSeq = rxSeries->CreateValueSequence( aRole ); + if( xDataSeq.is() ) + aLabeledSeqVec.push_back( xDataSeq ); + ++nRoleIdx; + if (nRoleIdx >= 4) + break; + } + + // attach labeled data sequences to series and insert series into chart type + xDataSink->setData( ScfApiHelper::VectorToSequence( aLabeledSeqVec ) ); + + // formatting of special stock chart elements + ScfPropertySet aTypeProp( xChartType ); + aTypeProp.SetBoolProperty( EXC_CHPROP_JAPANESE, HasDropBars() ); + aTypeProp.SetBoolProperty( EXC_CHPROP_SHOWFIRST, HasDropBars() ); + aTypeProp.SetBoolProperty( EXC_CHPROP_SHOWHIGHLOW, true ); + // hi-lo line format + XclImpChLineFormatMap::const_iterator aHiLoLine = m_ChartLines.find( EXC_CHCHARTLINE_HILO ); + if (aHiLoLine != m_ChartLines.end()) + { + ScfPropertySet aSeriesProp( xDataSeries ); + aHiLoLine->second.Convert( GetChRoot(), aSeriesProp, EXC_CHOBJTYPE_HILOLINE ); + } + // white dropbar format + XclImpChDropBarMap::const_iterator itr = m_DropBars.find(EXC_CHDROPBAR_UP); + Reference<XPropertySet> xWhitePropSet; + if (itr != m_DropBars.end() && aTypeProp.GetProperty(xWhitePropSet, EXC_CHPROP_WHITEDAY)) + { + ScfPropertySet aBarProp( xWhitePropSet ); + itr->second->Convert(GetChRoot(), aBarProp); + } + // black dropbar format + itr = m_DropBars.find(EXC_CHDROPBAR_DOWN); + Reference<XPropertySet> xBlackPropSet; + if (itr != m_DropBars.end() && aTypeProp.GetProperty(xBlackPropSet, EXC_CHPROP_BLACKDAY)) + { + ScfPropertySet aBarProp( xBlackPropSet ); + itr->second->Convert(GetChRoot(), aBarProp); + } + + // insert the series into the chart type object + InsertDataSeries( xChartType, xDataSeries, nApiAxesSetIdx ); +} + +// Axes ======================================================================= + +XclImpChLabelRange::XclImpChLabelRange( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChLabelRange::ReadChLabelRange( XclImpStream& rStrm ) +{ + maLabelData.mnCross = rStrm.ReaduInt16(); + maLabelData.mnLabelFreq = rStrm.ReaduInt16(); + maLabelData.mnTickFreq = rStrm.ReaduInt16(); + maLabelData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChLabelRange::ReadChDateRange( XclImpStream& rStrm ) +{ + maDateData.mnMinDate = rStrm.ReaduInt16(); + maDateData.mnMaxDate = rStrm.ReaduInt16(); + maDateData.mnMajorStep = rStrm.ReaduInt16(); + maDateData.mnMajorUnit = rStrm.ReaduInt16(); + maDateData.mnMinorStep = rStrm.ReaduInt16(); + maDateData.mnMinorUnit = rStrm.ReaduInt16(); + maDateData.mnBaseUnit = rStrm.ReaduInt16(); + maDateData.mnCross = rStrm.ReaduInt16(); + maDateData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChLabelRange::Convert( ScfPropertySet& rPropSet, ScaleData& rScaleData, bool bMirrorOrient ) const +{ + // automatic axis type detection + rScaleData.AutoDateAxis = ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTODATE ); + + // the flag EXC_CHDATERANGE_DATEAXIS specifies whether this is a date axis + if( ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_DATEAXIS ) ) + { + /* Chart2 requires axis type CATEGORY for automatic category/date axis + (even if it is a date axis currently). */ + rScaleData.AxisType = rScaleData.AutoDateAxis ? cssc2::AxisType::CATEGORY : cssc2::AxisType::DATE; + rScaleData.Scaling = css::chart2::LinearScaling::create( comphelper::getProcessComponentContext() ); + /* Min/max values depend on base time unit, they specify the number of + days, months, or years starting from null date. */ + lclConvertTimeValue( GetRoot(), rScaleData.Minimum, maDateData.mnMinDate, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMIN ), maDateData.mnBaseUnit ); + lclConvertTimeValue( GetRoot(), rScaleData.Maximum, maDateData.mnMaxDate, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMAX ), maDateData.mnBaseUnit ); + // increment + cssc::TimeIncrement& rTimeIncrement = rScaleData.TimeIncrement; + lclConvertTimeInterval( rTimeIncrement.MajorTimeInterval, maDateData.mnMajorStep, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMAJOR ), maDateData.mnMajorUnit ); + lclConvertTimeInterval( rTimeIncrement.MinorTimeInterval, maDateData.mnMinorStep, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMINOR ), maDateData.mnMinorUnit ); + // base unit + if( ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOBASE ) ) + rTimeIncrement.TimeResolution.clear(); + else + rTimeIncrement.TimeResolution <<= lclGetApiTimeUnit( maDateData.mnBaseUnit ); + } + else + { + // do not overlap text unless all labels are visible + rPropSet.SetBoolProperty( EXC_CHPROP_TEXTOVERLAP, maLabelData.mnLabelFreq == 1 ); + // do not break text into several lines unless all labels are visible + rPropSet.SetBoolProperty( EXC_CHPROP_TEXTBREAK, maLabelData.mnLabelFreq == 1 ); + // do not stagger labels in two lines + rPropSet.SetProperty( EXC_CHPROP_ARRANGEORDER, cssc::ChartAxisArrangeOrderType_SIDE_BY_SIDE ); + } + + // reverse order + bool bReverse = ::get_flag( maLabelData.mnFlags, EXC_CHLABELRANGE_REVERSE ) != bMirrorOrient; + rScaleData.Orientation = bReverse ? cssc2::AxisOrientation_REVERSE : cssc2::AxisOrientation_MATHEMATICAL; + + //TODO #i58731# show n-th category +} + +void XclImpChLabelRange::ConvertAxisPosition( ScfPropertySet& rPropSet, bool b3dChart ) const +{ + /* Crossing mode (max-cross flag overrides other crossing settings). Excel + does not move the Y axis in 3D charts, regardless of actual settings. + But: the Y axis has to be moved to "end", if the X axis is mirrored, + to keep it at the left end of the chart. */ + bool bMaxCross = ::get_flag( maLabelData.mnFlags, b3dChart ? EXC_CHLABELRANGE_REVERSE : EXC_CHLABELRANGE_MAXCROSS ); + cssc::ChartAxisPosition eAxisPos = bMaxCross ? cssc::ChartAxisPosition_END : cssc::ChartAxisPosition_VALUE; + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERPOSITION, eAxisPos ); + + // crossing position (depending on axis type text/date) + if( ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_DATEAXIS ) ) + { + bool bAutoCross = ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOCROSS ); + /* Crossing position value depends on base time unit, it specifies the + number of days, months, or years from null date. Note that Excel + 2007/2010 write broken BIFF8 files, they always stores the number + of days regardless of the base time unit (and they are reading it + the same way, thus wrongly displaying files written by Excel + 97-2003). This filter sticks to the correct behaviour of Excel + 97-2003. */ + double fCrossingPos = bAutoCross ? 1.0 : lclGetSerialDay( GetRoot(), maDateData.mnCross, maDateData.mnBaseUnit ); + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERVALUE, fCrossingPos ); + } + else + { + double fCrossingPos = b3dChart ? 1.0 : maLabelData.mnCross; + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERVALUE, fCrossingPos ); + } +} + +XclImpChValueRange::XclImpChValueRange( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChValueRange::ReadChValueRange( XclImpStream& rStrm ) +{ + maData.mfMin = rStrm.ReadDouble(); + maData.mfMax = rStrm.ReadDouble(); + maData.mfMajorStep = rStrm.ReadDouble(); + maData.mfMinorStep = rStrm.ReadDouble(); + maData.mfCross = rStrm.ReadDouble(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChValueRange::Convert( ScaleData& rScaleData, bool bMirrorOrient ) const +{ + // scaling algorithm + const bool bLogScale = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_LOGSCALE ); + if( bLogScale ) + rScaleData.Scaling = css::chart2::LogarithmicScaling::create( comphelper::getProcessComponentContext() ); + else + rScaleData.Scaling = css::chart2::LinearScaling::create( comphelper::getProcessComponentContext() ); + + // min/max + lclSetExpValueOrClearAny( rScaleData.Minimum, maData.mfMin, bLogScale, ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMIN ) ); + lclSetExpValueOrClearAny( rScaleData.Maximum, maData.mfMax, bLogScale, ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMAX ) ); + + // increment + bool bAutoMajor = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMAJOR ); + bool bAutoMinor = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMINOR ); + // major increment + IncrementData& rIncrementData = rScaleData.IncrementData; + lclSetValueOrClearAny( rIncrementData.Distance, maData.mfMajorStep, bAutoMajor ); + // minor increment + Sequence< SubIncrement >& rSubIncrementSeq = rIncrementData.SubIncrements; + rSubIncrementSeq.realloc( 1 ); + Any& rIntervalCount = rSubIncrementSeq.getArray()[ 0 ].IntervalCount; + rIntervalCount.clear(); + if( bLogScale ) + { + if( !bAutoMinor ) + rIntervalCount <<= sal_Int32( 9 ); + } + else if( !bAutoMajor && !bAutoMinor && (0.0 < maData.mfMinorStep) && (maData.mfMinorStep <= maData.mfMajorStep) ) + { + double fCount = maData.mfMajorStep / maData.mfMinorStep + 0.5; + if( (1.0 <= fCount) && (fCount < 1001.0) ) + rIntervalCount <<= static_cast< sal_Int32 >( fCount ); + } + else if( bAutoMinor ) + { + // tdf#114168 If minor unit is not set then set interval to 5, as MS Excel do. + rIntervalCount <<= static_cast< sal_Int32 >( 5 ); + } + + // reverse order + bool bReverse = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_REVERSE ) != bMirrorOrient; + rScaleData.Orientation = bReverse ? cssc2::AxisOrientation_REVERSE : cssc2::AxisOrientation_MATHEMATICAL; +} + +void XclImpChValueRange::ConvertAxisPosition( ScfPropertySet& rPropSet ) const +{ + bool bMaxCross = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_MAXCROSS ); + bool bAutoCross = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS ); + bool bLogScale = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_LOGSCALE ); + + // crossing mode (max-cross flag overrides other crossing settings) + cssc::ChartAxisPosition eAxisPos = bMaxCross ? cssc::ChartAxisPosition_END : cssc::ChartAxisPosition_VALUE; + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERPOSITION, eAxisPos ); + + // crossing position + double fCrossingPos = bAutoCross ? 0.0 : maData.mfCross; + if( bLogScale ) fCrossingPos = pow( 10.0, fCrossingPos ); + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERVALUE, fCrossingPos ); +} + +namespace { + +sal_Int32 lclGetApiTickmarks( sal_uInt8 nXclTickPos ) +{ + using namespace ::com::sun::star::chart2::TickmarkStyle; + sal_Int32 nApiTickmarks = css::chart2::TickmarkStyle::NONE; + ::set_flag( nApiTickmarks, INNER, ::get_flag( nXclTickPos, EXC_CHTICK_INSIDE ) ); + ::set_flag( nApiTickmarks, OUTER, ::get_flag( nXclTickPos, EXC_CHTICK_OUTSIDE ) ); + return nApiTickmarks; +} + +cssc::ChartAxisLabelPosition lclGetApiLabelPosition( sal_Int8 nXclLabelPos ) +{ + using namespace ::com::sun::star::chart; + switch( nXclLabelPos ) + { + case EXC_CHTICK_LOW: return ChartAxisLabelPosition_OUTSIDE_START; + case EXC_CHTICK_HIGH: return ChartAxisLabelPosition_OUTSIDE_END; + case EXC_CHTICK_NEXT: return ChartAxisLabelPosition_NEAR_AXIS; + } + return ChartAxisLabelPosition_NEAR_AXIS; +} + +} // namespace + +XclImpChTick::XclImpChTick( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChTick::ReadChTick( XclImpStream& rStrm ) +{ + maData.mnMajor = rStrm.ReaduInt8(); + maData.mnMinor = rStrm.ReaduInt8(); + maData.mnLabelPos = rStrm.ReaduInt8(); + maData.mnBackMode = rStrm.ReaduInt8(); + rStrm.Ignore( 16 ); + rStrm >> maData.maTextColor; + maData.mnFlags = rStrm.ReaduInt16(); + + if( GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + maData.maTextColor = GetPalette().GetColor( rStrm.ReaduInt16() ); + // rotation + maData.mnRotation = rStrm.ReaduInt16(); + } + else + { + // BIFF2-BIFF7: get rotation from text orientation + sal_uInt8 nOrient = ::extract_value< sal_uInt8 >( maData.mnFlags, 2, 3 ); + maData.mnRotation = XclTools::GetXclRotFromOrient( nOrient ); + } +} + +Color XclImpChTick::GetFontColor() const +{ + return ::get_flag( maData.mnFlags, EXC_CHTICK_AUTOCOLOR ) ? GetFontAutoColor() : maData.maTextColor; +} + +sal_uInt16 XclImpChTick::GetRotation() const +{ + /* n#720443: Ignore auto-rotation if there is a suggested rotation. + * Better fix would be to improve our axis auto rotation algorithm. + */ + if( maData.mnRotation != EXC_ROT_NONE ) + return maData.mnRotation; + return ::get_flag( maData.mnFlags, EXC_CHTICK_AUTOROT ) ? EXC_CHART_AUTOROTATION : maData.mnRotation; +} + +void XclImpChTick::Convert( ScfPropertySet& rPropSet ) const +{ + rPropSet.SetProperty( EXC_CHPROP_MAJORTICKS, lclGetApiTickmarks( maData.mnMajor ) ); + rPropSet.SetProperty( EXC_CHPROP_MINORTICKS, lclGetApiTickmarks( maData.mnMinor ) ); + rPropSet.SetProperty( EXC_CHPROP_LABELPOSITION, lclGetApiLabelPosition( maData.mnLabelPos ) ); + rPropSet.SetProperty( EXC_CHPROP_MARKPOSITION, cssc::ChartAxisMarkPosition_AT_AXIS ); +} + +XclImpChAxis::XclImpChAxis( const XclImpChRoot& rRoot, sal_uInt16 nAxisType ) : + XclImpChRoot( rRoot ), + mnNumFmtIdx( EXC_FORMAT_NOTFOUND ) +{ + maData.mnType = nAxisType; +} + +void XclImpChAxis::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnType = rStrm.ReaduInt16(); +} + +void XclImpChAxis::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHLABELRANGE: + mxLabelRange = std::make_shared<XclImpChLabelRange>( GetChRoot() ); + mxLabelRange->ReadChLabelRange( rStrm ); + break; + case EXC_ID_CHDATERANGE: + if( !mxLabelRange ) + mxLabelRange = std::make_shared<XclImpChLabelRange>( GetChRoot() ); + mxLabelRange->ReadChDateRange( rStrm ); + break; + case EXC_ID_CHVALUERANGE: + mxValueRange = std::make_shared<XclImpChValueRange>( GetChRoot() ); + mxValueRange->ReadChValueRange( rStrm ); + break; + case EXC_ID_CHFORMAT: + mnNumFmtIdx = rStrm.ReaduInt16(); + break; + case EXC_ID_CHTICK: + mxTick = std::make_shared<XclImpChTick>( GetChRoot() ); + mxTick->ReadChTick( rStrm ); + break; + case EXC_ID_CHFONT: + mxFont = std::make_shared<XclImpChFont>(); + mxFont->ReadChFont( rStrm ); + break; + case EXC_ID_CHAXISLINE: + ReadChAxisLine( rStrm ); + break; + } +} + +void XclImpChAxis::Finalize() +{ + // add default scaling, needed e.g. to adjust rotation direction of pie and radar charts + if( !mxLabelRange ) + mxLabelRange = std::make_shared<XclImpChLabelRange>( GetChRoot() ); + if( !mxValueRange ) + mxValueRange = std::make_shared<XclImpChValueRange>( GetChRoot() ); + // remove invisible grid lines completely + if( mxMajorGrid && !mxMajorGrid->HasLine() ) + mxMajorGrid.clear(); + if( mxMinorGrid && !mxMinorGrid->HasLine() ) + mxMinorGrid.clear(); + // default tick settings different in OOChart and Excel + if( !mxTick ) + mxTick = std::make_shared<XclImpChTick>( GetChRoot() ); + // #i4140# different default axis line color + if( !mxAxisLine ) + { + XclChLineFormat aLineFmt; + // set "show axis" flag, default if line format record is missing + ::set_flag( aLineFmt.mnFlags, EXC_CHLINEFORMAT_SHOWAXIS ); + mxAxisLine = new XclImpChLineFormat( aLineFmt ); + } + // add wall/floor frame for 3d charts + if( !mxWallFrame ) + CreateWallFrame(); +} + +sal_uInt16 XclImpChAxis::GetFontIndex() const +{ + return mxFont ? mxFont->GetFontIndex() : EXC_FONT_NOTFOUND; +} + +Color XclImpChAxis::GetFontColor() const +{ + return mxTick ? mxTick->GetFontColor() : GetFontAutoColor(); +} + +sal_uInt16 XclImpChAxis::GetRotation() const +{ + return mxTick ? mxTick->GetRotation() : EXC_CHART_AUTOROTATION; +} + +Reference< XAxis > XclImpChAxis::CreateAxis( const XclImpChTypeGroup& rTypeGroup, const XclImpChAxis* pCrossingAxis ) const +{ + // create the axis object (always) + Reference< XAxis > xAxis( ScfApiHelper::CreateInstance( SERVICE_CHART2_AXIS ), UNO_QUERY ); + if( xAxis.is() ) + { + ScfPropertySet aAxisProp( xAxis ); + // #i58688# axis enabled + aAxisProp.SetBoolProperty( EXC_CHPROP_SHOW, !mxAxisLine || mxAxisLine->IsShowAxis() ); + + // axis line properties + if( mxAxisLine ) + mxAxisLine->Convert( GetChRoot(), aAxisProp, EXC_CHOBJTYPE_AXISLINE ); + // axis ticks properties + if( mxTick ) + mxTick->Convert( aAxisProp ); + + // axis caption text -------------------------------------------------- + + // radar charts disable their category labels via chart type, not via axis + bool bHasLabels = (!mxTick || mxTick->HasLabels()) && + ((GetAxisType() != EXC_CHAXIS_X) || rTypeGroup.HasCategoryLabels()); + aAxisProp.SetBoolProperty( EXC_CHPROP_DISPLAYLABELS, bHasLabels ); + if( bHasLabels ) + { + // font settings from CHFONT record or from default text + if( mxFont ) + ConvertFontBase( GetChRoot(), aAxisProp ); + else if( const XclImpChText* pDefText = GetChartData().GetDefaultText( EXC_CHTEXTTYPE_AXISLABEL ) ) + pDefText->ConvertFont( aAxisProp ); + // label text rotation + ConvertRotationBase( aAxisProp, true ); + // number format + bool bLinkNumberFmtToSource = true; + if ( mnNumFmtIdx != EXC_FORMAT_NOTFOUND ) + { + sal_uInt32 nScNumFmt = GetNumFmtBuffer().GetScFormat( mnNumFmtIdx ); + if( nScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + aAxisProp.SetProperty( EXC_CHPROP_NUMBERFORMAT, static_cast< sal_Int32 >( nScNumFmt ) ); + bLinkNumberFmtToSource = false; + } + } + + aAxisProp.SetProperty( EXC_CHPROP_NUMBERFORMAT_LINKSRC, bLinkNumberFmtToSource ); + } + + // axis scaling and increment ----------------------------------------- + + const XclChExtTypeInfo& rTypeInfo = rTypeGroup.GetTypeInfo(); + ScaleData aScaleData = xAxis->getScaleData(); + // set axis type + switch( GetAxisType() ) + { + case EXC_CHAXIS_X: + if( rTypeInfo.mbCategoryAxis ) + { + aScaleData.AxisType = cssc2::AxisType::CATEGORY; + aScaleData.Categories = rTypeGroup.CreateCategSequence(); + } + else + aScaleData.AxisType = cssc2::AxisType::REALNUMBER; + break; + case EXC_CHAXIS_Y: + aScaleData.AxisType = rTypeGroup.IsPercent() ? + cssc2::AxisType::PERCENT : cssc2::AxisType::REALNUMBER; + break; + case EXC_CHAXIS_Z: + aScaleData.AxisType = cssc2::AxisType::SERIES; + break; + } + // axis scaling settings, dependent on axis type + switch( aScaleData.AxisType ) + { + case cssc2::AxisType::CATEGORY: + case cssc2::AxisType::SERIES: + // #i71684# radar charts have reversed rotation direction + if (mxLabelRange) + mxLabelRange->Convert( aAxisProp, aScaleData, rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_RADAR ); + else + SAL_WARN("sc.filter", "missing LabelRange"); + break; + case cssc2::AxisType::REALNUMBER: + case cssc2::AxisType::PERCENT: + // #i85167# pie/donut charts have reversed rotation direction (at Y axis!) + if (mxValueRange) + mxValueRange->Convert( aScaleData, rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE ); + else + SAL_WARN("sc.filter", "missing ValueRange"); + break; + default: + OSL_FAIL( "XclImpChAxis::CreateAxis - unknown axis type" ); + } + + /* Do not set a value to the Origin member anymore (will be done via + new axis properties 'CrossoverPosition' and 'CrossoverValue'). */ + aScaleData.Origin.clear(); + + // write back + xAxis->setScaleData( aScaleData ); + + // grid --------------------------------------------------------------- + + // main grid + ScfPropertySet aGridProp( xAxis->getGridProperties() ); + aGridProp.SetBoolProperty( EXC_CHPROP_SHOW, static_cast<bool>(mxMajorGrid) ); + if( mxMajorGrid ) + mxMajorGrid->Convert( GetChRoot(), aGridProp, EXC_CHOBJTYPE_GRIDLINE ); + // sub grid + Sequence< Reference< XPropertySet > > aSubGridPropSeq = xAxis->getSubGridProperties(); + if( aSubGridPropSeq.hasElements() ) + { + ScfPropertySet aSubGridProp( aSubGridPropSeq[ 0 ] ); + aSubGridProp.SetBoolProperty( EXC_CHPROP_SHOW, static_cast<bool>(mxMinorGrid) ); + if( mxMinorGrid ) + mxMinorGrid->Convert( GetChRoot(), aSubGridProp, EXC_CHOBJTYPE_GRIDLINE ); + } + + // position of crossing axis ------------------------------------------ + + if( pCrossingAxis ) + pCrossingAxis->ConvertAxisPosition( aAxisProp, rTypeGroup ); + } + return xAxis; +} + +void XclImpChAxis::ConvertWall( ScfPropertySet& rPropSet ) const +{ + // #i71810# walls and floor in 3D charts use the CHPICFORMAT record for bitmap mode + if( mxWallFrame ) + mxWallFrame->Convert( rPropSet, true ); +} + +void XclImpChAxis::ConvertAxisPosition( ScfPropertySet& rPropSet, const XclImpChTypeGroup& rTypeGroup ) const +{ + if( ((GetAxisType() == EXC_CHAXIS_X) && rTypeGroup.GetTypeInfo().mbCategoryAxis) || (GetAxisType() == EXC_CHAXIS_Z) ) + { + if (mxLabelRange) + mxLabelRange->ConvertAxisPosition( rPropSet, rTypeGroup.Is3dChart() ); + else + SAL_WARN("sc.filter", "missing LabelRange"); + } + else + { + if (mxValueRange) + mxValueRange->ConvertAxisPosition( rPropSet ); + else + SAL_WARN("sc.filter", "missing ValueRange"); + } +} + +void XclImpChAxis::ReadChAxisLine( XclImpStream& rStrm ) +{ + XclImpChLineFormatRef* pxLineFmt = nullptr; + bool bWallFrame = false; + switch( rStrm.ReaduInt16() ) + { + case EXC_CHAXISLINE_AXISLINE: pxLineFmt = &mxAxisLine; break; + case EXC_CHAXISLINE_MAJORGRID: pxLineFmt = &mxMajorGrid; break; + case EXC_CHAXISLINE_MINORGRID: pxLineFmt = &mxMinorGrid; break; + case EXC_CHAXISLINE_WALLS: bWallFrame = true; break; + } + if( bWallFrame ) + CreateWallFrame(); + + bool bLoop = pxLineFmt || bWallFrame; + while( bLoop ) + { + sal_uInt16 nRecId = rStrm.GetNextRecId(); + bLoop = ((nRecId == EXC_ID_CHLINEFORMAT) || + (nRecId == EXC_ID_CHAREAFORMAT) || + (nRecId == EXC_ID_CHESCHERFORMAT)) + && rStrm.StartNextRecord(); + if( bLoop ) + { + if( pxLineFmt && (nRecId == EXC_ID_CHLINEFORMAT) ) + { + (*pxLineFmt) = new XclImpChLineFormat(); + (*pxLineFmt)->ReadChLineFormat( rStrm ); + } + else if( bWallFrame && mxWallFrame ) + { + mxWallFrame->ReadSubRecord( rStrm ); + } + } + } +} + +void XclImpChAxis::CreateWallFrame() +{ + switch( GetAxisType() ) + { + case EXC_CHAXIS_X: + mxWallFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_WALL3D ); + break; + case EXC_CHAXIS_Y: + mxWallFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_FLOOR3D ); + break; + default: + mxWallFrame.reset(); + } +} + +XclImpChAxesSet::XclImpChAxesSet( const XclImpChRoot& rRoot, sal_uInt16 nAxesSetId ) : + XclImpChRoot( rRoot ) +{ + maData.mnAxesSetId = nAxesSetId; +} + +void XclImpChAxesSet::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnAxesSetId = rStrm.ReaduInt16(); + rStrm >> maData.maRect; +} + +void XclImpChAxesSet::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAMEPOS: + mxFramePos = std::make_shared<XclImpChFramePos>(); + mxFramePos->ReadChFramePos( rStrm ); + break; + case EXC_ID_CHAXIS: + ReadChAxis( rStrm ); + break; + case EXC_ID_CHTEXT: + ReadChText( rStrm ); + break; + case EXC_ID_CHPLOTFRAME: + ReadChPlotFrame( rStrm ); + break; + case EXC_ID_CHTYPEGROUP: + ReadChTypeGroup( rStrm ); + break; + } +} + +void XclImpChAxesSet::Finalize() +{ + if( IsValidAxesSet() ) + { + // finalize chart type groups, erase empty groups without series + XclImpChTypeGroupMap aValidGroups; + for (auto const& typeGroup : maTypeGroups) + { + XclImpChTypeGroupRef xTypeGroup = typeGroup.second; + xTypeGroup->Finalize(); + if( xTypeGroup->IsValidGroup() ) + aValidGroups.emplace(typeGroup.first, xTypeGroup); + } + maTypeGroups.swap( aValidGroups ); + } + + // invalid chart type groups are deleted now, check again with IsValidAxesSet() + if( !IsValidAxesSet() ) + return; + + // always create missing axis objects + if( !mxXAxis ) + mxXAxis = std::make_shared<XclImpChAxis>( GetChRoot(), EXC_CHAXIS_X ); + if( !mxYAxis ) + mxYAxis = std::make_shared<XclImpChAxis>( GetChRoot(), EXC_CHAXIS_Y ); + if( !mxZAxis && GetFirstTypeGroup()->Is3dDeepChart() ) + mxZAxis = std::make_shared<XclImpChAxis>( GetChRoot(), EXC_CHAXIS_Z ); + + // finalize axes + if( mxXAxis ) mxXAxis->Finalize(); + if( mxYAxis ) mxYAxis->Finalize(); + if( mxZAxis ) mxZAxis->Finalize(); + + // finalize axis titles + const XclImpChText* pDefText = GetChartData().GetDefaultText( EXC_CHTEXTTYPE_AXISTITLE ); + OUString aAutoTitle(ScResId(STR_AXISTITLE)); + lclFinalizeTitle( mxXAxisTitle, pDefText, aAutoTitle ); + lclFinalizeTitle( mxYAxisTitle, pDefText, aAutoTitle ); + lclFinalizeTitle( mxZAxisTitle, pDefText, aAutoTitle ); + + // #i47745# missing plot frame -> invisible border and area + if( !mxPlotFrame ) + mxPlotFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_PLOTFRAME ); +} + +XclImpChTypeGroupRef XclImpChAxesSet::GetTypeGroup( sal_uInt16 nGroupIdx ) const +{ + XclImpChTypeGroupMap::const_iterator itr = maTypeGroups.find(nGroupIdx); + return itr == maTypeGroups.end() ? XclImpChTypeGroupRef() : itr->second; +} + +XclImpChTypeGroupRef XclImpChAxesSet::GetFirstTypeGroup() const +{ + XclImpChTypeGroupRef xTypeGroup; + if( !maTypeGroups.empty() ) + xTypeGroup = maTypeGroups.begin()->second; + return xTypeGroup; +} + +XclImpChLegendRef XclImpChAxesSet::GetLegend() const +{ + XclImpChLegendRef xLegend; + for( const auto& rEntry : maTypeGroups ) + { + xLegend = rEntry.second->GetLegend(); + if (xLegend) + break; + } + return xLegend; +} + +OUString XclImpChAxesSet::GetSingleSeriesTitle() const +{ + return (maTypeGroups.size() == 1) ? maTypeGroups.begin()->second->GetSingleSeriesTitle() : OUString(); +} + +void XclImpChAxesSet::Convert( Reference< XDiagram > const & xDiagram ) const +{ + if( !(IsValidAxesSet() && xDiagram.is()) ) + return; + + // diagram background formatting + if( GetAxesSetId() == EXC_CHAXESSET_PRIMARY ) + ConvertBackground( xDiagram ); + + // create the coordinate system, this inserts all chart types and series + Reference< XCoordinateSystem > xCoordSystem = CreateCoordSystem( xDiagram ); + if( !xCoordSystem.is() ) + return; + + // insert coordinate system, if not already done + try + { + Reference< XCoordinateSystemContainer > xCoordSystemCont( xDiagram, UNO_QUERY_THROW ); + Sequence< Reference< XCoordinateSystem > > aCoordSystems = xCoordSystemCont->getCoordinateSystems(); + if( !aCoordSystems.hasElements() ) + xCoordSystemCont->addCoordinateSystem( xCoordSystem ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::Convert - cannot insert coordinate system" ); + } + + // create the axes with grids and axis titles and insert them into the diagram + ConvertAxis( mxXAxis, mxXAxisTitle, xCoordSystem, mxYAxis.get() ); + ConvertAxis( mxYAxis, mxYAxisTitle, xCoordSystem, mxXAxis.get() ); + ConvertAxis( mxZAxis, mxZAxisTitle, xCoordSystem, nullptr ); +} + +void XclImpChAxesSet::ConvertTitlePositions() const +{ + if( mxXAxisTitle ) + mxXAxisTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, maData.mnAxesSetId, EXC_CHAXIS_X ) ); + if( mxYAxisTitle ) + mxYAxisTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, maData.mnAxesSetId, EXC_CHAXIS_Y ) ); + if( mxZAxisTitle ) + mxZAxisTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, maData.mnAxesSetId, EXC_CHAXIS_Z ) ); +} + +void XclImpChAxesSet::ReadChAxis( XclImpStream& rStrm ) +{ + XclImpChAxisRef xAxis = std::make_shared<XclImpChAxis>( GetChRoot() ); + xAxis->ReadRecordGroup( rStrm ); + + switch( xAxis->GetAxisType() ) + { + case EXC_CHAXIS_X: mxXAxis = xAxis; break; + case EXC_CHAXIS_Y: mxYAxis = xAxis; break; + case EXC_CHAXIS_Z: mxZAxis = xAxis; break; + } +} + +void XclImpChAxesSet::ReadChText( XclImpStream& rStrm ) +{ + XclImpChTextRef xText = std::make_shared<XclImpChText>( GetChRoot() ); + xText->ReadRecordGroup( rStrm ); + + switch( xText->GetLinkTarget() ) + { + case EXC_CHOBJLINK_XAXIS: mxXAxisTitle = xText; break; + case EXC_CHOBJLINK_YAXIS: mxYAxisTitle = xText; break; + case EXC_CHOBJLINK_ZAXIS: mxZAxisTitle = xText; break; + } +} + +void XclImpChAxesSet::ReadChPlotFrame( XclImpStream& rStrm ) +{ + if( (rStrm.GetNextRecId() == EXC_ID_CHFRAME) && rStrm.StartNextRecord() ) + { + mxPlotFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_PLOTFRAME ); + mxPlotFrame->ReadRecordGroup( rStrm ); + } +} + +void XclImpChAxesSet::ReadChTypeGroup( XclImpStream& rStrm ) +{ + XclImpChTypeGroupRef xTypeGroup = std::make_shared<XclImpChTypeGroup>( GetChRoot() ); + xTypeGroup->ReadRecordGroup( rStrm ); + sal_uInt16 nGroupIdx = xTypeGroup->GetGroupIdx(); + XclImpChTypeGroupMap::iterator itr = maTypeGroups.lower_bound(nGroupIdx); + if (itr != maTypeGroups.end() && !maTypeGroups.key_comp()(nGroupIdx, itr->first)) + // Overwrite the existing element. + itr->second = xTypeGroup; + else + maTypeGroups.insert( + itr, XclImpChTypeGroupMap::value_type(nGroupIdx, xTypeGroup)); +} + +Reference< XCoordinateSystem > XclImpChAxesSet::CreateCoordSystem( Reference< XDiagram > const & xDiagram ) const +{ + Reference< XCoordinateSystem > xCoordSystem; + + /* Try to get existing coordinate system. For now, all series from primary + and secondary axes sets are inserted into one coordinate system. Later, + this should be changed to use one coordinate system for each axes set. */ + Reference< XCoordinateSystemContainer > xCoordSystemCont( xDiagram, UNO_QUERY ); + if( xCoordSystemCont.is() ) + { + Sequence< Reference< XCoordinateSystem > > aCoordSystems = xCoordSystemCont->getCoordinateSystems(); + OSL_ENSURE( aCoordSystems.getLength() <= 1, "XclImpChAxesSet::CreateCoordSystem - too many existing coordinate systems" ); + if( aCoordSystems.hasElements() ) + xCoordSystem = aCoordSystems[ 0 ]; + } + + // create the coordinate system according to the first chart type + if( !xCoordSystem.is() ) + { + XclImpChTypeGroupRef xTypeGroup = GetFirstTypeGroup(); + if( xTypeGroup ) + { + xCoordSystem = xTypeGroup->CreateCoordSystem(); + // convert 3d chart settings + ScfPropertySet aDiaProp( xDiagram ); + xTypeGroup->ConvertChart3d( aDiaProp ); + } + } + + /* Create XChartType objects for all chart type groups. Each group will + add its series to the data provider attached to the chart document. */ + Reference< XChartTypeContainer > xChartTypeCont( xCoordSystem, UNO_QUERY ); + if( xChartTypeCont.is() ) + { + sal_Int32 nApiAxesSetIdx = GetApiAxesSetIndex(); + for( const auto& rEntry : maTypeGroups ) + { + try + { + Reference< XChartType > xChartType = rEntry.second->CreateChartType( xDiagram, nApiAxesSetIdx ); + if( xChartType.is() ) + xChartTypeCont->addChartType( xChartType ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::CreateCoordSystem - cannot add chart type" ); + } + } + } + + return xCoordSystem; +} + +void XclImpChAxesSet::ConvertAxis( + XclImpChAxisRef const & xChAxis, XclImpChTextRef const & xChAxisTitle, + Reference< XCoordinateSystem > const & xCoordSystem, const XclImpChAxis* pCrossingAxis ) const +{ + if( !xChAxis ) + return; + + // create and attach the axis object + Reference< XAxis > xAxis = CreateAxis( *xChAxis, pCrossingAxis ); + if( !xAxis.is() ) + return; + + // create and attach the axis title + if( xChAxisTitle ) try + { + Reference< XTitled > xTitled( xAxis, UNO_QUERY_THROW ); + Reference< XTitle > xTitle( xChAxisTitle->CreateTitle(), UNO_SET_THROW ); + xTitled->setTitleObject( xTitle ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::ConvertAxis - cannot set axis title" ); + } + + // insert axis into coordinate system + try + { + sal_Int32 nApiAxisDim = xChAxis->GetApiAxisDimension(); + sal_Int32 nApiAxesSetIdx = GetApiAxesSetIndex(); + xCoordSystem->setAxisByDimension( nApiAxisDim, xAxis, nApiAxesSetIdx ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::ConvertAxis - cannot set axis" ); + } +} + +Reference< XAxis > XclImpChAxesSet::CreateAxis( const XclImpChAxis& rChAxis, const XclImpChAxis* pCrossingAxis ) const +{ + Reference< XAxis > xAxis; + if( const XclImpChTypeGroup* pTypeGroup = GetFirstTypeGroup().get() ) + xAxis = rChAxis.CreateAxis( *pTypeGroup, pCrossingAxis ); + return xAxis; +} + +void XclImpChAxesSet::ConvertBackground( Reference< XDiagram > const & xDiagram ) const +{ + XclImpChTypeGroupRef xTypeGroup = GetFirstTypeGroup(); + if( xTypeGroup && xTypeGroup->Is3dWallChart() ) + { + // wall/floor formatting (3D charts) + if( mxXAxis ) + { + ScfPropertySet aWallProp( xDiagram->getWall() ); + mxXAxis->ConvertWall( aWallProp ); + } + if( mxYAxis ) + { + ScfPropertySet aFloorProp( xDiagram->getFloor() ); + mxYAxis->ConvertWall( aFloorProp ); + } + } + else if( mxPlotFrame ) + { + // diagram background formatting + ScfPropertySet aWallProp( xDiagram->getWall() ); + mxPlotFrame->Convert( aWallProp ); + } +} + +// The chart object =========================================================== + +XclImpChChart::XclImpChChart( const XclImpRoot& rRoot ) : + XclImpChRoot( rRoot, *this ) +{ + mxPrimAxesSet = std::make_shared<XclImpChAxesSet>( GetChRoot(), EXC_CHAXESSET_PRIMARY ); + mxSecnAxesSet = std::make_shared<XclImpChAxesSet>( GetChRoot(), EXC_CHAXESSET_SECONDARY ); +} + +XclImpChChart::~XclImpChChart() +{ +} + +void XclImpChChart::ReadHeaderRecord( XclImpStream& rStrm ) +{ + // coordinates are stored as 16.16 fixed point + rStrm >> maRect; +} + +void XclImpChChart::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAME: + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_BACKGROUND ); + mxFrame->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHSERIES: + ReadChSeries( rStrm ); + break; + case EXC_ID_CHPROPERTIES: + ReadChProperties( rStrm ); + break; + case EXC_ID_CHDEFAULTTEXT: + ReadChDefaultText( rStrm ); + break; + case EXC_ID_CHAXESSET: + ReadChAxesSet( rStrm ); + break; + case EXC_ID_CHTEXT: + ReadChText( rStrm ); + break; + case EXC_ID_CHEND: + Finalize(); // finalize the entire chart object + break; + } +} + +void XclImpChChart::ReadChDefaultText( XclImpStream& rStrm ) +{ + sal_uInt16 nTextId = rStrm.ReaduInt16(); + if( (rStrm.GetNextRecId() == EXC_ID_CHTEXT) && rStrm.StartNextRecord() ) + { + unique_ptr<XclImpChText> pText(new XclImpChText(GetChRoot())); + pText->ReadRecordGroup(rStrm); + m_DefTexts.insert(std::make_pair(nTextId, std::move(pText))); + } +} + +void XclImpChChart::ReadChDataFormat( XclImpStream& rStrm ) +{ + XclImpChDataFormatRef xDataFmt = std::make_shared<XclImpChDataFormat>( GetChRoot() ); + xDataFmt->ReadRecordGroup( rStrm ); + if( xDataFmt->GetPointPos().mnSeriesIdx <= EXC_CHSERIES_MAXSERIES ) + { + const XclChDataPointPos& rPos = xDataFmt->GetPointPos(); + XclImpChDataFormatMap::iterator itr = maDataFmts.lower_bound(rPos); + if (itr == maDataFmts.end() || maDataFmts.key_comp()(rPos, itr->first)) + // No element exists for this data point. Insert it. + maDataFmts.insert( + itr, XclImpChDataFormatMap::value_type(rPos, xDataFmt)); + + /* Do not overwrite existing data format group, Excel always uses the + first data format group occurring in any CHSERIES group. */ + } +} + +void XclImpChChart::UpdateObjFrame( const XclObjLineData& rLineData, const XclObjFillData& rFillData ) +{ + if( !mxFrame ) + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_BACKGROUND ); + mxFrame->UpdateObjFrame( rLineData, rFillData ); +} + +XclImpChTypeGroupRef XclImpChChart::GetTypeGroup( sal_uInt16 nGroupIdx ) const +{ + XclImpChTypeGroupRef xTypeGroup = mxPrimAxesSet->GetTypeGroup( nGroupIdx ); + if( !xTypeGroup ) xTypeGroup = mxSecnAxesSet->GetTypeGroup( nGroupIdx ); + if( !xTypeGroup ) xTypeGroup = mxPrimAxesSet->GetFirstTypeGroup(); + return xTypeGroup; +} + +const XclImpChText* XclImpChChart::GetDefaultText( XclChTextType eTextType ) const +{ + sal_uInt16 nDefTextId = EXC_CHDEFTEXT_GLOBAL; + bool bBiff8 = GetBiff() == EXC_BIFF8; + switch( eTextType ) + { + case EXC_CHTEXTTYPE_TITLE: nDefTextId = EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_LEGEND: nDefTextId = EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_AXISTITLE: nDefTextId = bBiff8 ? EXC_CHDEFTEXT_AXESSET : EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_AXISLABEL: nDefTextId = bBiff8 ? EXC_CHDEFTEXT_AXESSET : EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_DATALABEL: nDefTextId = bBiff8 ? EXC_CHDEFTEXT_AXESSET : EXC_CHDEFTEXT_GLOBAL; break; + } + + XclImpChTextMap::const_iterator const itr = m_DefTexts.find(nDefTextId); + return itr == m_DefTexts.end() ? nullptr : itr->second.get(); +} + +bool XclImpChChart::IsManualPlotArea() const +{ + // there is no real automatic mode in BIFF5 charts + return (GetBiff() <= EXC_BIFF5) || ::get_flag( maProps.mnFlags, EXC_CHPROPS_USEMANPLOTAREA ); +} + +void XclImpChChart::Convert( const Reference<XChartDocument>& xChartDoc, + XclImpDffConverter& rDffConv, const OUString& rObjName, const tools::Rectangle& rChartRect ) const +{ + // initialize conversion (locks the model to suppress any internal updates) + InitConversion( xChartDoc, rChartRect ); + + // chart frame formatting + if( mxFrame ) + { + ScfPropertySet aFrameProp( xChartDoc->getPageBackground() ); + mxFrame->Convert( aFrameProp ); + } + + // chart title + if( mxTitle ) try + { + Reference< XTitled > xTitled( xChartDoc, UNO_QUERY_THROW ); + Reference< XTitle > xTitle( mxTitle->CreateTitle(), UNO_SET_THROW ); + xTitled->setTitleObject( xTitle ); + } + catch( Exception& ) + { + } + + /* Create the diagram object and attach it to the chart document. Currently, + one diagram is used to carry all coordinate systems and data series. */ + Reference< XDiagram > xDiagram = CreateDiagram(); + xChartDoc->setFirstDiagram( xDiagram ); + + // coordinate systems and chart types, convert axis settings + mxPrimAxesSet->Convert( xDiagram ); + mxSecnAxesSet->Convert( xDiagram ); + + // legend + if( xDiagram.is() && mxLegend ) + xDiagram->setLegend( mxLegend->CreateLegend() ); + + /* Following all conversions needing the old Chart1 API that involves full + initialization of the chart view. */ + Reference< cssc::XChartDocument > xChart1Doc( xChartDoc, UNO_QUERY ); + if( xChart1Doc.is() ) + { + Reference< cssc::XDiagram > xDiagram1 = xChart1Doc->getDiagram(); + + /* Set the 'IncludeHiddenCells' property via the old API as only this + ensures that the data provider and all created sequences get this + flag correctly. */ + ScfPropertySet aDiaProp( xDiagram1 ); + bool bShowVisCells = ::get_flag( maProps.mnFlags, EXC_CHPROPS_SHOWVISIBLEONLY ); + aDiaProp.SetBoolProperty( EXC_CHPROP_INCLUDEHIDDENCELLS, !bShowVisCells ); + + // plot area position and size (there is no real automatic mode in BIFF5 charts) + XclImpChFramePosRef xPlotAreaPos = mxPrimAxesSet->GetPlotAreaFramePos(); + if( IsManualPlotArea() && xPlotAreaPos ) try + { + const XclChFramePos& rFramePos = xPlotAreaPos->GetFramePosData(); + if( (rFramePos.mnTLMode == EXC_CHFRAMEPOS_PARENT) && (rFramePos.mnBRMode == EXC_CHFRAMEPOS_PARENT) ) + { + Reference< cssc::XDiagramPositioning > xPositioning( xDiagram1, UNO_QUERY_THROW ); + css::awt::Rectangle aDiagramRect = CalcHmmFromChartRect( rFramePos.maRect ); + // for pie charts, always set inner plot area size to exclude the data labels as Excel does + const XclImpChTypeGroup* pFirstTypeGroup = mxPrimAxesSet->GetFirstTypeGroup().get(); + if( pFirstTypeGroup && (pFirstTypeGroup->GetTypeInfo().meTypeCateg == EXC_CHTYPECATEG_PIE) ) + xPositioning->setDiagramPositionExcludingAxes( aDiagramRect ); + else if( pFirstTypeGroup && pFirstTypeGroup->Is3dChart() ) + xPositioning->setDiagramPositionIncludingAxesAndAxisTitles( aDiagramRect ); + else + xPositioning->setDiagramPositionIncludingAxes( aDiagramRect ); + } + } + catch( Exception& ) + { + } + + // positions of all title objects + if( mxTitle ) + mxTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_TITLE ) ); + mxPrimAxesSet->ConvertTitlePositions(); + mxSecnAxesSet->ConvertTitlePositions(); + } + + // unlock the model + FinishConversion( rDffConv ); + + // start listening to this chart + ScDocument& rDoc = GetRoot().GetDoc(); + ScChartListenerCollection* pChartCollection = rDoc.GetChartListenerCollection(); + if(!pChartCollection) + return; + + std::vector< ScTokenRef > aRefTokens; + for( const auto& rxSeries : maSeries ) + rxSeries->FillAllSourceLinks( aRefTokens ); + if( !aRefTokens.empty() ) + { + ::std::unique_ptr< ScChartListener > xListener( new ScChartListener( rObjName, rDoc, std::move(aRefTokens) ) ); + xListener->SetUsed( true ); + xListener->StartListeningTo(); + pChartCollection->insert( xListener.release() ); + } +} + +void XclImpChChart::ReadChSeries( XclImpStream& rStrm ) +{ + sal_uInt16 nNewSeriesIdx = static_cast< sal_uInt16 >( maSeries.size() ); + XclImpChSeriesRef xSeries = std::make_shared<XclImpChSeries>( GetChRoot(), nNewSeriesIdx ); + xSeries->ReadRecordGroup( rStrm ); + maSeries.push_back( xSeries ); +} + +void XclImpChChart::ReadChProperties( XclImpStream& rStrm ) +{ + maProps.mnFlags = rStrm.ReaduInt16(); + maProps.mnEmptyMode = rStrm.ReaduInt8(); +} + +void XclImpChChart::ReadChAxesSet( XclImpStream& rStrm ) +{ + XclImpChAxesSetRef xAxesSet = std::make_shared<XclImpChAxesSet>( GetChRoot(), EXC_CHAXESSET_NONE ); + xAxesSet->ReadRecordGroup( rStrm ); + switch( xAxesSet->GetAxesSetId() ) + { + case EXC_CHAXESSET_PRIMARY: mxPrimAxesSet = xAxesSet; break; + case EXC_CHAXESSET_SECONDARY: mxSecnAxesSet = xAxesSet; break; + } +} + +void XclImpChChart::ReadChText( XclImpStream& rStrm ) +{ + XclImpChTextRef xText = std::make_shared<XclImpChText>( GetChRoot() ); + xText->ReadRecordGroup( rStrm ); + switch( xText->GetLinkTarget() ) + { + case EXC_CHOBJLINK_TITLE: + mxTitle = xText; + break; + case EXC_CHOBJLINK_DATA: + { + sal_uInt16 nSeriesIdx = xText->GetPointPos().mnSeriesIdx; + if( nSeriesIdx < maSeries.size() ) + maSeries[ nSeriesIdx ]->SetDataLabel( xText ); + } + break; + } +} + +void XclImpChChart::Finalize() +{ + // finalize series (must be done first) + FinalizeSeries(); + // #i49218# legend may be attached to primary or secondary axes set + mxLegend = mxPrimAxesSet->GetLegend(); + if( !mxLegend ) + mxLegend = mxSecnAxesSet->GetLegend(); + if( mxLegend ) + mxLegend->Finalize(); + // axes sets, updates chart type group default formats -> must be called before FinalizeDataFormats() + mxPrimAxesSet->Finalize(); + mxSecnAxesSet->Finalize(); + // formatting of all series + FinalizeDataFormats(); + // #i47745# missing frame -> invisible border and area + if( !mxFrame ) + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_BACKGROUND ); + // chart title + FinalizeTitle(); +} + +void XclImpChChart::FinalizeSeries() +{ + for( const XclImpChSeriesRef& xSeries : maSeries ) + { + if( xSeries->HasParentSeries() ) + { + /* Process child series (trend lines and error bars). Data of + child series will be set at the connected parent series. */ + if( xSeries->GetParentIdx() < maSeries.size() ) + maSeries[ xSeries->GetParentIdx() ]->AddChildSeries( *xSeries ); + } + else + { + // insert the series into the related chart type group + if( XclImpChTypeGroup* pTypeGroup = GetTypeGroup( xSeries->GetGroupIdx() ).get() ) + pTypeGroup->AddSeries( xSeries ); + } + } +} + +void XclImpChChart::FinalizeDataFormats() +{ + /* #i51639# (part 1): CHDATAFORMAT groups are part of CHSERIES groups. + Each CHDATAFORMAT group specifies the series and data point it is + assigned to. This makes it possible to have a data format that is + related to another series, e.g. a CHDATAFORMAT group for series 2 is + part of a CHSERIES group that describes series 1. Therefore the chart + itself has collected all CHDATAFORMAT groups to be able to store data + format groups for series that have not been imported at that time. This + loop finally assigns these groups to the related series. */ + for( const auto& [rPos, rDataFmt] : maDataFmts ) + { + sal_uInt16 nSeriesIdx = rPos.mnSeriesIdx; + if( nSeriesIdx < maSeries.size() ) + maSeries[ nSeriesIdx ]->SetDataFormat( rDataFmt ); + } + + /* #i51639# (part 2): Finalize data formats of all series. This adds for + example missing CHDATAFORMAT groups for entire series that are needed + for automatic colors of lines and areas. */ + for( auto& rxSeries : maSeries ) + rxSeries->FinalizeDataFormats(); +} + +void XclImpChChart::FinalizeTitle() +{ + // special handling for auto-generated title + OUString aAutoTitle; + if( !mxTitle || (!mxTitle->IsDeleted() && !mxTitle->HasString()) ) + { + // automatic title from first series name (if there are no series on secondary axes set) + if( !mxSecnAxesSet->IsValidAxesSet() ) + aAutoTitle = mxPrimAxesSet->GetSingleSeriesTitle(); + if( mxTitle || (!aAutoTitle.isEmpty()) ) + { + if( !mxTitle ) + mxTitle = std::make_shared<XclImpChText>( GetChRoot() ); + if( aAutoTitle.isEmpty() ) + aAutoTitle = ScResId(STR_CHARTTITLE); + } + } + + // will reset mxTitle, if it does not contain a string and no auto title exists + lclFinalizeTitle( mxTitle, GetDefaultText( EXC_CHTEXTTYPE_TITLE ), aAutoTitle ); +} + +Reference< XDiagram > XclImpChChart::CreateDiagram() const +{ + // create a diagram object + Reference< XDiagram > xDiagram( ScfApiHelper::CreateInstance( SERVICE_CHART2_DIAGRAM ), UNO_QUERY ); + + // convert global chart settings + ScfPropertySet aDiaProp( xDiagram ); + + // treatment of missing values + using namespace cssc::MissingValueTreatment; + sal_Int32 nMissingValues = LEAVE_GAP; + switch( maProps.mnEmptyMode ) + { + case EXC_CHPROPS_EMPTY_SKIP: nMissingValues = LEAVE_GAP; break; + case EXC_CHPROPS_EMPTY_ZERO: nMissingValues = USE_ZERO; break; + case EXC_CHPROPS_EMPTY_INTERPOLATE: nMissingValues = CONTINUE; break; + } + aDiaProp.SetProperty( EXC_CHPROP_MISSINGVALUETREATMENT, nMissingValues ); + + return xDiagram; +} + +XclImpChartDrawing::XclImpChartDrawing( const XclImpRoot& rRoot, bool bOwnTab ) : + XclImpDrawing( rRoot, bOwnTab ), // sheet charts may contain OLE objects + mnScTab( rRoot.GetCurrScTab() ), + mbOwnTab( bOwnTab ) +{ +} + +void XclImpChartDrawing::ConvertObjects( XclImpDffConverter& rDffConv, + const Reference< XModel >& rxModel, const tools::Rectangle& rChartRect ) +{ + maChartRect = rChartRect; // needed in CalcAnchorRect() callback + + SdrModel* pSdrModel = nullptr; + SdrPage* pSdrPage = nullptr; + if( mbOwnTab ) + { + // chart sheet: insert all shapes into the sheet, not into the chart object + pSdrModel = GetDoc().GetDrawLayer(); + pSdrPage = GetSdrPage( mnScTab ); + } + else + { + // embedded chart object: insert all shapes into the chart + try + { + Reference< XDrawPageSupplier > xDrawPageSupp( rxModel, UNO_QUERY_THROW ); + Reference< XDrawPage > xDrawPage( xDrawPageSupp->getDrawPage(), UNO_SET_THROW ); + pSdrPage = ::GetSdrPageFromXDrawPage( xDrawPage ); + pSdrModel = pSdrPage ? &pSdrPage->getSdrModelFromSdrPage() : nullptr; + } + catch( Exception& ) + { + } + } + + if( pSdrModel && pSdrPage ) + ImplConvertObjects( rDffConv, *pSdrModel, *pSdrPage ); +} + +tools::Rectangle XclImpChartDrawing::CalcAnchorRect( const XclObjAnchor& rAnchor, bool bDffAnchor ) const +{ + /* In objects with DFF client anchor, the position of the shape is stored + in the cell address components of the client anchor. In old BIFF3-BIFF5 + objects, the position is stored in the offset components of the anchor. */ + tools::Rectangle aRect( + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maFirst.mnCol : rAnchor.mnLX ) / EXC_CHART_TOTALUNITS * maChartRect.GetWidth() + 0.5 ), + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maFirst.mnRow : rAnchor.mnTY ) / EXC_CHART_TOTALUNITS * maChartRect.GetHeight() + 0.5 ), + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maLast.mnCol : rAnchor.mnRX ) / EXC_CHART_TOTALUNITS * maChartRect.GetWidth() + 0.5 ), + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maLast.mnRow : rAnchor.mnBY ) / EXC_CHART_TOTALUNITS * maChartRect.GetHeight() + 0.5 ) ); + aRect.Justify(); + // move shapes into chart area for sheet charts + if( mbOwnTab ) + aRect.Move( maChartRect.Left(), maChartRect.Top() ); + return aRect; +} + +void XclImpChartDrawing::OnObjectInserted( const XclImpDrawObjBase& ) +{ +} + +XclImpChart::XclImpChart( const XclImpRoot& rRoot, bool bOwnTab ) : + XclImpRoot( rRoot ), + mbOwnTab( bOwnTab ), + mbIsPivotChart( false ) +{ +} + +XclImpChart::~XclImpChart() +{ +} + +void XclImpChart::ReadChartSubStream( XclImpStream& rStrm ) +{ + XclImpPageSettings& rPageSett = GetPageSettings(); + XclImpTabViewSettings& rTabViewSett = GetTabViewSettings(); + + bool bLoop = true; + while( bLoop && rStrm.StartNextRecord() ) + { + // page settings - only for charts in entire sheet + if( mbOwnTab ) switch( rStrm.GetRecId() ) + { + case EXC_ID_HORPAGEBREAKS: + case EXC_ID_VERPAGEBREAKS: rPageSett.ReadPageBreaks( rStrm ); break; + case EXC_ID_HEADER: + case EXC_ID_FOOTER: rPageSett.ReadHeaderFooter( rStrm ); break; + case EXC_ID_LEFTMARGIN: + case EXC_ID_RIGHTMARGIN: + case EXC_ID_TOPMARGIN: + case EXC_ID_BOTTOMMARGIN: rPageSett.ReadMargin( rStrm ); break; + case EXC_ID_PRINTHEADERS: rPageSett.ReadPrintHeaders( rStrm ); break; + case EXC_ID_PRINTGRIDLINES: rPageSett.ReadPrintGridLines( rStrm ); break; + case EXC_ID_HCENTER: + case EXC_ID_VCENTER: rPageSett.ReadCenter( rStrm ); break; + case EXC_ID_SETUP: rPageSett.ReadSetup( rStrm ); break; + case EXC_ID8_IMGDATA: rPageSett.ReadImgData( rStrm ); break; + + case EXC_ID_WINDOW2: rTabViewSett.ReadWindow2( rStrm, true );break; + case EXC_ID_SCL: rTabViewSett.ReadScl( rStrm ); break; + + case EXC_ID_SHEETEXT: //0x0862 + { + // FIXME: do not need to pass palette, XclImpTabVieSettings is derived from root + XclImpPalette& rPal = GetPalette(); + rTabViewSett.ReadTabBgColor( rStrm, rPal); + } + break; + + case EXC_ID_CODENAME: ReadCodeName( rStrm, false ); break; + } + + // common records + switch( rStrm.GetRecId() ) + { + case EXC_ID_EOF: bLoop = false; break; + + // #i31882# ignore embedded chart objects + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( rStrm ); break; + + case EXC_ID_CHCHART: ReadChChart( rStrm ); break; + + case EXC_ID8_CHPIVOTREF: + GetTracer().TracePivotChartExists(); + mbIsPivotChart = true; + break; + + // BIFF specific records + default: switch( GetBiff() ) + { + case EXC_BIFF5: switch( rStrm.GetRecId() ) + { + case EXC_ID_OBJ: GetChartDrawing().ReadObj( rStrm ); break; + } + break; + case EXC_BIFF8: switch( rStrm.GetRecId() ) + { + case EXC_ID_MSODRAWING: GetChartDrawing().ReadMsoDrawing( rStrm ); break; + // #i61786# weird documents: OBJ without MSODRAWING -> read in BIFF5 format + case EXC_ID_OBJ: GetChartDrawing().ReadObj( rStrm ); break; + } + break; + default:; + } + } + } +} + +void XclImpChart::UpdateObjFrame( const XclObjLineData& rLineData, const XclObjFillData& rFillData ) +{ + if( !mxChartData ) + mxChartData = std::make_shared<XclImpChChart>( GetRoot() ); + mxChartData->UpdateObjFrame( rLineData, rFillData ); +} + +std::size_t XclImpChart::GetProgressSize() const +{ + return + (mxChartData ? XclImpChChart::GetProgressSize() : 0) + + (mxChartDrawing ? mxChartDrawing->GetProgressSize() : 0); +} + +void XclImpChart::Convert( Reference< XModel > const & xModel, XclImpDffConverter& rDffConv, const OUString& rObjName, const tools::Rectangle& rChartRect ) const +{ + Reference< XChartDocument > xChartDoc( xModel, UNO_QUERY ); + if( xChartDoc.is() ) + { + if( mxChartData ) + mxChartData->Convert( xChartDoc, rDffConv, rObjName, rChartRect ); + if( mxChartDrawing ) + mxChartDrawing->ConvertObjects( rDffConv, xModel, rChartRect ); + } +} + +XclImpChartDrawing& XclImpChart::GetChartDrawing() +{ + if( !mxChartDrawing ) + mxChartDrawing = std::make_shared<XclImpChartDrawing>( GetRoot(), mbOwnTab ); + return *mxChartDrawing; +} + +void XclImpChart::ReadChChart( XclImpStream& rStrm ) +{ + mxChartData = std::make_shared<XclImpChChart>( GetRoot() ); + mxChartData->ReadRecordGroup( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |