summaryrefslogtreecommitdiffstats
path: root/oox/source/export/chartexport.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'oox/source/export/chartexport.cxx')
-rw-r--r--oox/source/export/chartexport.cxx4685
1 files changed, 4685 insertions, 0 deletions
diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx
new file mode 100644
index 000000000..8a6d5fa7d
--- /dev/null
+++ b/oox/source/export/chartexport.cxx
@@ -0,0 +1,4685 @@
+/* -*- 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 <oox/token/namespaces.hxx>
+#include <oox/token/properties.hxx>
+#include <oox/token/tokens.hxx>
+#include <oox/core/xmlfilterbase.hxx>
+#include <oox/export/chartexport.hxx>
+#include <oox/token/relationship.hxx>
+#include <oox/export/utils.hxx>
+#include <drawingml/chart/typegroupconverter.hxx>
+
+#include <cstdio>
+#include <limits>
+
+#include <com/sun/star/awt/Gradient.hpp>
+#include <com/sun/star/chart/XChartDocument.hpp>
+#include <com/sun/star/chart/ChartLegendPosition.hpp>
+#include <com/sun/star/chart/XTwoAxisXSupplier.hpp>
+#include <com/sun/star/chart/XTwoAxisYSupplier.hpp>
+#include <com/sun/star/chart/XAxisZSupplier.hpp>
+#include <com/sun/star/chart/ChartDataRowSource.hpp>
+#include <com/sun/star/chart/X3DDisplay.hpp>
+#include <com/sun/star/chart/XStatisticDisplay.hpp>
+#include <com/sun/star/chart/XSecondAxisTitleSupplier.hpp>
+#include <com/sun/star/chart/ChartSymbolType.hpp>
+#include <com/sun/star/chart/ChartAxisMarks.hpp>
+#include <com/sun/star/chart/ChartAxisLabelPosition.hpp>
+#include <com/sun/star/chart/ChartAxisPosition.hpp>
+#include <com/sun/star/chart/ChartSolidType.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/chart/XDiagramPositioning.hpp>
+#include <com/sun/star/chart/TimeIncrement.hpp>
+#include <com/sun/star/chart/TimeInterval.hpp>
+#include <com/sun/star/chart/TimeUnit.hpp>
+
+#include <com/sun/star/chart2/RelativePosition.hpp>
+#include <com/sun/star/chart2/RelativeSize.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/XRegressionCurveContainer.hpp>
+#include <com/sun/star/chart2/XChartTypeContainer.hpp>
+#include <com/sun/star/chart2/XDataSeriesContainer.hpp>
+#include <com/sun/star/chart2/DataPointLabel.hpp>
+#include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
+#include <com/sun/star/chart2/DataPointCustomLabelFieldType.hpp>
+#include <com/sun/star/chart2/Symbol.hpp>
+#include <com/sun/star/chart2/data/XDataSource.hpp>
+#include <com/sun/star/chart2/data/XDataProvider.hpp>
+#include <com/sun/star/chart2/data/XTextualDataSequence.hpp>
+#include <com/sun/star/chart2/data/XNumericalDataSequence.hpp>
+#include <com/sun/star/chart2/data/XLabeledDataSequence.hpp>
+#include <com/sun/star/chart2/XAnyDescriptionAccess.hpp>
+#include <com/sun/star/chart2/AxisType.hpp>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/drawing/XShapes.hpp>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/awt/XBitmap.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceName.hpp>
+
+#include <com/sun/star/table/CellAddress.hpp>
+#include <com/sun/star/sheet/XFormulaParser.hpp>
+#include <com/sun/star/sheet/FormulaToken.hpp>
+#include <com/sun/star/sheet/AddressConvention.hpp>
+
+#include <com/sun/star/container/XNamed.hpp>
+#include <com/sun/star/embed/XVisualObject.hpp>
+#include <com/sun/star/embed/Aspects.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/random.hxx>
+#include <utility>
+#include <xmloff/SchXMLSeriesHelper.hxx>
+#include "ColorPropertySet.hxx"
+
+#include <svl/numformat.hxx>
+#include <svl/numuno.hxx>
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+
+#include <set>
+#include <unordered_set>
+
+#include <o3tl/temporary.hxx>
+#include <o3tl/sorted_vector.hxx>
+
+using namespace css;
+using namespace css::uno;
+using namespace css::drawing;
+using namespace ::oox::core;
+using css::beans::PropertyValue;
+using css::beans::XPropertySet;
+using css::container::XNamed;
+using css::table::CellAddress;
+using css::sheet::XFormulaParser;
+using ::oox::core::XmlFilterBase;
+using ::sax_fastparser::FSHelperPtr;
+
+namespace cssc = css::chart;
+
+namespace oox::drawingml {
+
+namespace {
+
+bool isPrimaryAxes(sal_Int32 nIndex)
+{
+ assert(nIndex == 0 || nIndex == 1);
+ return nIndex != 1;
+}
+
+class lcl_MatchesRole
+{
+public:
+ explicit lcl_MatchesRole( const OUString & aRole ) :
+ m_aRole( aRole )
+ {}
+
+ bool operator () ( const Reference< chart2::data::XLabeledDataSequence > & xSeq ) const
+ {
+ if( !xSeq.is() )
+ return false;
+ Reference< beans::XPropertySet > xProp( xSeq->getValues(), uno::UNO_QUERY );
+ OUString aRole;
+
+ return ( xProp.is() &&
+ (xProp->getPropertyValue( "Role" ) >>= aRole ) &&
+ m_aRole == aRole );
+ }
+
+private:
+ OUString m_aRole;
+};
+
+}
+
+static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories( const Reference< chart2::XDiagram > & xDiagram, bool& bHasDateCategories )
+{
+ bHasDateCategories = false;
+ Reference< chart2::data::XLabeledDataSequence > xResult;
+ try
+ {
+ Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
+ xDiagram, uno::UNO_QUERY_THROW );
+ const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
+ xCooSysCnt->getCoordinateSystems());
+ for( const auto& xCooSys : aCooSysSeq )
+ {
+ OSL_ASSERT( xCooSys.is());
+ for( sal_Int32 nN = xCooSys->getDimension(); nN--; )
+ {
+ const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nN);
+ for(sal_Int32 nI=0; nI<=nMaxAxisIndex; ++nI)
+ {
+ Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension( nN, nI );
+ OSL_ASSERT( xAxis.is());
+ if( xAxis.is())
+ {
+ chart2::ScaleData aScaleData = xAxis->getScaleData();
+ if( aScaleData.Categories.is())
+ {
+ bHasDateCategories = aScaleData.AxisType == chart2::AxisType::DATE;
+ xResult.set( aScaleData.Categories );
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+
+ return xResult;
+}
+
+static Reference< chart2::data::XLabeledDataSequence >
+ lcl_getDataSequenceByRole(
+ const Sequence< Reference< chart2::data::XLabeledDataSequence > > & aLabeledSeq,
+ const OUString & rRole )
+{
+ Reference< chart2::data::XLabeledDataSequence > aNoResult;
+
+ const Reference< chart2::data::XLabeledDataSequence > * pBegin = aLabeledSeq.getConstArray();
+ const Reference< chart2::data::XLabeledDataSequence > * pEnd = pBegin + aLabeledSeq.getLength();
+ const Reference< chart2::data::XLabeledDataSequence > * pMatch =
+ ::std::find_if( pBegin, pEnd, lcl_MatchesRole( rRole ));
+
+ if( pMatch != pEnd )
+ return *pMatch;
+
+ return aNoResult;
+}
+
+static bool lcl_hasCategoryLabels( const Reference< chart2::XChartDocument >& xChartDoc )
+{
+ //categories are always the first sequence
+ Reference< chart2::XDiagram > xDiagram( xChartDoc->getFirstDiagram());
+ bool bDateCategories;
+ Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( xDiagram, bDateCategories ) );
+ return xCategories.is();
+}
+
+static bool lcl_isCategoryAxisShifted( const Reference< chart2::XDiagram >& xDiagram )
+{
+ bool bCategoryPositionShifted = false;
+ try
+ {
+ Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
+ xDiagram, uno::UNO_QUERY_THROW);
+ const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
+ xCooSysCnt->getCoordinateSystems());
+ for (const auto& xCooSys : aCooSysSeq)
+ {
+ OSL_ASSERT(xCooSys.is());
+ if( 0 < xCooSys->getDimension() && 0 <= xCooSys->getMaximumAxisIndexByDimension(0) )
+ {
+ Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, 0);
+ OSL_ASSERT(xAxis.is());
+ if (xAxis.is())
+ {
+ chart2::ScaleData aScaleData = xAxis->getScaleData();
+ bCategoryPositionShifted = aScaleData.ShiftedCategoryPosition;
+ break;
+ }
+ }
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+
+ return bCategoryPositionShifted;
+}
+
+static sal_Int32 lcl_getCategoryAxisType( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex )
+{
+ sal_Int32 nAxisType = -1;
+ try
+ {
+ Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
+ xDiagram, uno::UNO_QUERY_THROW);
+ const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
+ xCooSysCnt->getCoordinateSystems());
+ for( const auto& xCooSys : aCooSysSeq )
+ {
+ OSL_ASSERT(xCooSys.is());
+ if( nDimensionIndex < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(nDimensionIndex) )
+ {
+ Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(nDimensionIndex, nAxisIndex);
+ OSL_ASSERT(xAxis.is());
+ if( xAxis.is() )
+ {
+ chart2::ScaleData aScaleData = xAxis->getScaleData();
+ nAxisType = aScaleData.AxisType;
+ break;
+ }
+ }
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+
+ return nAxisType;
+}
+
+static OUString lclGetTimeUnitToken( sal_Int32 nTimeUnit )
+{
+ switch( nTimeUnit )
+ {
+ case cssc::TimeUnit::DAY: return "days";
+ case cssc::TimeUnit::MONTH: return "months";
+ case cssc::TimeUnit::YEAR: return "years";
+ default: OSL_ENSURE(false, "lclGetTimeUnitToken - unexpected time unit");
+ }
+ return "days";
+}
+
+static cssc::TimeIncrement lcl_getDateTimeIncrement( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nAxisIndex )
+{
+ cssc::TimeIncrement aTimeIncrement;
+ try
+ {
+ Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
+ xDiagram, uno::UNO_QUERY_THROW);
+ const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
+ xCooSysCnt->getCoordinateSystems());
+ for( const auto& xCooSys : aCooSysSeq )
+ {
+ OSL_ASSERT(xCooSys.is());
+ if( 0 < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(0) )
+ {
+ Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, nAxisIndex);
+ OSL_ASSERT(xAxis.is());
+ if( xAxis.is() )
+ {
+ chart2::ScaleData aScaleData = xAxis->getScaleData();
+ aTimeIncrement = aScaleData.TimeIncrement;
+ break;
+ }
+ }
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+
+ return aTimeIncrement;
+}
+
+static bool lcl_isSeriesAttachedToFirstAxis(
+ const Reference< chart2::XDataSeries > & xDataSeries )
+{
+ bool bResult=true;
+
+ try
+ {
+ sal_Int32 nAxisIndex = 0;
+ Reference< beans::XPropertySet > xProp( xDataSeries, uno::UNO_QUERY_THROW );
+ xProp->getPropertyValue("AttachedAxisIndex") >>= nAxisIndex;
+ bResult = (0==nAxisIndex);
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+
+ return bResult;
+}
+
+static OUString lcl_flattenStringSequence( const Sequence< OUString > & rSequence )
+{
+ OUStringBuffer aResult;
+ bool bPrecedeWithSpace = false;
+ for( const auto& rString : rSequence )
+ {
+ if( !rString.isEmpty())
+ {
+ if( bPrecedeWithSpace )
+ aResult.append( ' ' );
+ aResult.append( rString );
+ bPrecedeWithSpace = true;
+ }
+ }
+ return aResult.makeStringAndClear();
+}
+
+static Sequence< OUString > lcl_getLabelSequence( const Reference< chart2::data::XDataSequence > & xLabelSeq )
+{
+ Sequence< OUString > aLabels;
+
+ uno::Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xLabelSeq, uno::UNO_QUERY );
+ if( xTextualDataSequence.is())
+ {
+ aLabels = xTextualDataSequence->getTextualData();
+ }
+ else if( xLabelSeq.is())
+ {
+ const Sequence< uno::Any > aAnies( xLabelSeq->getData());
+ aLabels.realloc( aAnies.getLength());
+ auto pLabels = aLabels.getArray();
+ for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
+ aAnies[i] >>= pLabels[i];
+ }
+
+ return aLabels;
+}
+
+static void lcl_fillCategoriesIntoStringVector(
+ const Reference< chart2::data::XDataSequence > & xCategories,
+ ::std::vector< OUString > & rOutCategories )
+{
+ OSL_ASSERT( xCategories.is());
+ if( !xCategories.is())
+ return;
+ Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xCategories, uno::UNO_QUERY );
+ if( xTextualDataSequence.is())
+ {
+ rOutCategories.clear();
+ const Sequence< OUString > aTextData( xTextualDataSequence->getTextualData());
+ rOutCategories.insert( rOutCategories.end(), aTextData.begin(), aTextData.end() );
+ }
+ else
+ {
+ Sequence< uno::Any > aAnies( xCategories->getData());
+ rOutCategories.resize( aAnies.getLength());
+ for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
+ aAnies[i] >>= rOutCategories[i];
+ }
+}
+
+static ::std::vector< double > lcl_getAllValuesFromSequence( const Reference< chart2::data::XDataSequence > & xSeq )
+{
+ ::std::vector< double > aResult;
+
+ Reference< chart2::data::XNumericalDataSequence > xNumSeq( xSeq, uno::UNO_QUERY );
+ if( xNumSeq.is())
+ {
+ const Sequence< double > aValues( xNumSeq->getNumericalData());
+ aResult.insert( aResult.end(), aValues.begin(), aValues.end() );
+ }
+ else if( xSeq.is())
+ {
+ Sequence< uno::Any > aAnies( xSeq->getData());
+ aResult.resize( aAnies.getLength(), std::numeric_limits<double>::quiet_NaN() );
+ for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
+ aAnies[i] >>= aResult[i];
+ }
+ return aResult;
+}
+
+static sal_Int32 lcl_getChartType( std::u16string_view sChartType )
+{
+ chart::TypeId eChartTypeId = chart::TYPEID_UNKNOWN;
+ if( sChartType == u"com.sun.star.chart.BarDiagram"
+ || sChartType == u"com.sun.star.chart2.ColumnChartType" )
+ eChartTypeId = chart::TYPEID_BAR;
+ else if( sChartType == u"com.sun.star.chart.AreaDiagram"
+ || sChartType == u"com.sun.star.chart2.AreaChartType" )
+ eChartTypeId = chart::TYPEID_AREA;
+ else if( sChartType == u"com.sun.star.chart.LineDiagram"
+ || sChartType == u"com.sun.star.chart2.LineChartType" )
+ eChartTypeId = chart::TYPEID_LINE;
+ else if( sChartType == u"com.sun.star.chart.PieDiagram"
+ || sChartType == u"com.sun.star.chart2.PieChartType" )
+ eChartTypeId = chart::TYPEID_PIE;
+ else if( sChartType == u"com.sun.star.chart.DonutDiagram"
+ || sChartType == u"com.sun.star.chart2.DonutChartType" )
+ eChartTypeId = chart::TYPEID_DOUGHNUT;
+ else if( sChartType == u"com.sun.star.chart.XYDiagram"
+ || sChartType == u"com.sun.star.chart2.ScatterChartType" )
+ eChartTypeId = chart::TYPEID_SCATTER;
+ else if( sChartType == u"com.sun.star.chart.NetDiagram"
+ || sChartType == u"com.sun.star.chart2.NetChartType" )
+ eChartTypeId = chart::TYPEID_RADARLINE;
+ else if( sChartType == u"com.sun.star.chart.FilledNetDiagram"
+ || sChartType == u"com.sun.star.chart2.FilledNetChartType" )
+ eChartTypeId = chart::TYPEID_RADARAREA;
+ else if( sChartType == u"com.sun.star.chart.StockDiagram"
+ || sChartType == u"com.sun.star.chart2.CandleStickChartType" )
+ eChartTypeId = chart::TYPEID_STOCK;
+ else if( sChartType == u"com.sun.star.chart.BubbleDiagram"
+ || sChartType == u"com.sun.star.chart2.BubbleChartType" )
+ eChartTypeId = chart::TYPEID_BUBBLE;
+
+ return eChartTypeId;
+}
+
+static sal_Int32 lcl_generateRandomValue()
+{
+ return comphelper::rng::uniform_int_distribution(0, 100000000-1);
+}
+
+static sal_Int32 lcl_getAlphaFromTransparenceGradient(const awt::Gradient& rGradient, bool bStart)
+{
+ // Our alpha is a gray color value.
+ sal_uInt8 nRed = ::Color(ColorTransparency, bStart ? rGradient.StartColor : rGradient.EndColor).GetRed();
+ // drawingML alpha is a percentage on a 0..100000 scale.
+ return (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
+}
+
+bool DataLabelsRange::empty() const
+{
+ return maLabels.empty();
+}
+
+size_t DataLabelsRange::count() const
+{
+ return maLabels.size();
+}
+
+bool DataLabelsRange::hasLabel(sal_Int32 nIndex) const
+{
+ return maLabels.find(nIndex) != maLabels.end();
+}
+
+const OUString & DataLabelsRange::getRange() const
+{
+ return maRange;
+}
+
+void DataLabelsRange::setRange(const OUString& rRange)
+{
+ maRange = rRange;
+}
+
+void DataLabelsRange::setLabel(sal_Int32 nIndex, const OUString& rText)
+{
+ maLabels.emplace(nIndex, rText);
+}
+
+DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::begin() const
+{
+ return maLabels.begin();
+}
+
+DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::end() const
+{
+ return maLabels.end();
+}
+
+ChartExport::ChartExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, Reference< frame::XModel > const & xModel, XmlFilterBase* pFB, DocumentType eDocumentType )
+ : DrawingML( std::move(pFS), pFB, eDocumentType )
+ , mnXmlNamespace( nXmlNamespace )
+ , mnSeriesCount(0)
+ , mxChartModel( xModel )
+ , mpURLTransformer(std::make_shared<URLTransformer>())
+ , mbHasCategoryLabels( false )
+ , mbHasZAxis( false )
+ , mbIs3DChart( false )
+ , mbStacked(false)
+ , mbPercent(false)
+ , mbHasDateCategories(false)
+{
+}
+
+void ChartExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer)
+{
+ mpURLTransformer = pTransformer;
+}
+
+sal_Int32 ChartExport::getChartType( )
+{
+ OUString sChartType = mxDiagram->getDiagramType();
+ return lcl_getChartType( sChartType );
+}
+
+namespace {
+
+uno::Sequence< beans::PropertyValue > createArguments(
+ const OUString & rRangeRepresentation, bool bUseColumns)
+{
+ css::chart::ChartDataRowSource eRowSource = css::chart::ChartDataRowSource_ROWS;
+ if (bUseColumns)
+ eRowSource = css::chart::ChartDataRowSource_COLUMNS;
+
+ uno::Sequence<beans::PropertyValue> aArguments{
+ { "DataRowSource", -1, uno::Any(eRowSource), beans::PropertyState_DIRECT_VALUE },
+ { "FirstCellAsLabel", -1, uno::Any(false), beans::PropertyState_DIRECT_VALUE },
+ { "HasCategories", -1, uno::Any(false), beans::PropertyState_DIRECT_VALUE },
+ { "CellRangeRepresentation", -1, uno::Any(rRangeRepresentation),
+ beans::PropertyState_DIRECT_VALUE }
+ };
+
+ return aArguments;
+}
+
+Reference<chart2::XDataSeries> getPrimaryDataSeries(const Reference<chart2::XChartType>& xChartType)
+{
+ Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY_THROW);
+
+ // export dataseries for current chart-type
+ const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries());
+ for (const auto& rSeries : aSeriesSeq)
+ {
+ Reference<chart2::XDataSeries> xSource(rSeries, uno::UNO_QUERY);
+ if (xSource.is())
+ return xSource;
+ }
+
+ return Reference<chart2::XDataSeries>();
+}
+
+}
+
+Sequence< Sequence< OUString > > ChartExport::getSplitCategoriesList( const OUString& rRange )
+{
+ Reference< chart2::XChartDocument > xChartDoc(getModel(), uno::UNO_QUERY);
+ OSL_ASSERT(xChartDoc.is());
+ if (xChartDoc.is())
+ {
+ Reference< chart2::data::XDataProvider > xDataProvider(xChartDoc->getDataProvider());
+ OSL_ENSURE(xDataProvider.is(), "No DataProvider");
+ if (xDataProvider.is())
+ {
+ //detect whether the first series is a row or a column
+ bool bSeriesUsesColumns = true;
+ Reference< chart2::XDiagram > xDiagram(xChartDoc->getFirstDiagram());
+ try
+ {
+ Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(xDiagram, uno::UNO_QUERY_THROW);
+ const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(xCooSysCnt->getCoordinateSystems());
+ for (const auto& rCooSys : aCooSysSeq)
+ {
+ const Reference< chart2::XChartTypeContainer > xCTCnt(rCooSys, uno::UNO_QUERY_THROW);
+ const Sequence< Reference< chart2::XChartType > > aChartTypeSeq(xCTCnt->getChartTypes());
+ for (const auto& rChartType : aChartTypeSeq)
+ {
+ Reference< chart2::XDataSeries > xDataSeries = getPrimaryDataSeries(rChartType);
+ if (xDataSeries.is())
+ {
+ uno::Reference< chart2::data::XDataSource > xSeriesSource(xDataSeries, uno::UNO_QUERY);
+ const uno::Sequence< beans::PropertyValue > rArguments = xDataProvider->detectArguments(xSeriesSource);
+ for (const beans::PropertyValue& rProperty : rArguments)
+ {
+ if (rProperty.Name == "DataRowSource")
+ {
+ css::chart::ChartDataRowSource eRowSource;
+ if (rProperty.Value >>= eRowSource)
+ {
+ bSeriesUsesColumns = (eRowSource == css::chart::ChartDataRowSource_COLUMNS);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+ // detect we have an inner data table or not
+ if (xChartDoc->hasInternalDataProvider() && rRange == "categories")
+ {
+ try
+ {
+ css::uno::Reference< css::chart2::XAnyDescriptionAccess > xDataAccess(xChartDoc->getDataProvider(), uno::UNO_QUERY);
+ const Sequence< Sequence< uno::Any > >aAnyCategories(bSeriesUsesColumns ? xDataAccess->getAnyRowDescriptions() : xDataAccess->getAnyColumnDescriptions());
+ auto pMax = std::max_element(aAnyCategories.begin(), aAnyCategories.end(),
+ [](const Sequence<uno::Any>& a, const Sequence<uno::Any>& b) {
+ return a.getLength() < b.getLength(); });
+
+ //minimum is 1!
+ if (pMax != aAnyCategories.end() && pMax->getLength() > 1)
+ {
+ sal_Int32 nLevelCount = pMax->getLength();
+ //we have complex categories
+ //sort the categories name
+ Sequence<Sequence<OUString>>aFinalSplitSource(nLevelCount);
+ auto pFinalSplitSource = aFinalSplitSource.getArray();
+ for (sal_Int32 i = 0; i < nLevelCount; i++)
+ {
+ sal_Int32 nElemLabel = 0;
+ pFinalSplitSource[nLevelCount - i - 1].realloc(aAnyCategories.getLength());
+ auto pSeq = pFinalSplitSource[nLevelCount - i - 1].getArray();
+ for (auto const& elemLabel : aAnyCategories)
+ {
+ // make sure elemLabel[i] exists!
+ if (elemLabel.getLength() > i)
+ {
+ pSeq[nElemLabel] = elemLabel[i].get<OUString>();
+ nElemLabel++;
+ }
+ }
+ }
+ return aFinalSplitSource;
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+ }
+ else
+ {
+ try
+ {
+ uno::Reference< chart2::data::XDataSource > xCategoriesSource(xDataProvider->createDataSource(
+ createArguments(rRange, bSeriesUsesColumns)));
+
+ if (xCategoriesSource.is())
+ {
+ const Sequence< Reference< chart2::data::XLabeledDataSequence >> aCategories = xCategoriesSource->getDataSequences();
+ if (aCategories.getLength() > 1)
+ {
+ //we have complex categories
+ //sort the categories name
+ Sequence<Sequence<OUString>> aFinalSplitSource(aCategories.getLength());
+ std::transform(aCategories.begin(), aCategories.end(),
+ std::reverse_iterator(asNonConstRange(aFinalSplitSource).end()),
+ [](const Reference<chart2::data::XLabeledDataSequence>& xCat) {
+ return lcl_getLabelSequence(xCat->getValues()); });
+ return aFinalSplitSource;
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+ }
+ }
+ }
+
+ return Sequence< Sequence< OUString>>(0);
+}
+
+OUString ChartExport::parseFormula( const OUString& rRange )
+{
+ OUString aResult;
+ Reference< XFormulaParser > xParser;
+ uno::Reference< lang::XMultiServiceFactory > xSF = GetFB()->getModelFactory();
+ if( xSF.is() )
+ {
+ try
+ {
+ xParser.set( xSF->createInstance("com.sun.star.sheet.FormulaParser"), UNO_QUERY );
+ }
+ catch( Exception& )
+ {
+ }
+ }
+
+ SAL_WARN_IF(!xParser.is(), "oox", "creating formula parser failed");
+
+ if( xParser.is() )
+ {
+ Reference< XPropertySet > xParserProps( xParser, uno::UNO_QUERY );
+ // rRange is the result of a
+ // css::chart2::data::XDataSequence::getSourceRangeRepresentation()
+ // call that returns the range in the document's current UI notation.
+ // Creating a FormulaParser defaults to the same notation, for
+ // parseFormula() do not attempt to override the FormulaConvention
+ // property with css::sheet::AddressConvention::OOO or some such.
+ /* TODO: it would be much better to introduce a
+ * getSourceRangeRepresentation(css::sheet::AddressConvention) to
+ * return the ranges in a specific convention than converting them with
+ * the overhead of creating an XFormulaParser for each... */
+ uno::Sequence<sheet::FormulaToken> aTokens = xParser->parseFormula( rRange, CellAddress( 0, 0, 0 ) );
+ if( xParserProps.is() )
+ {
+ xParserProps->setPropertyValue("FormulaConvention", uno::Any(css::sheet::AddressConvention::XL_OOX) );
+ // For referencing named ranges correctly with special excel chart syntax.
+ xParserProps->setPropertyValue("RefConventionChartOOXML", uno::Any(true) );
+ }
+ aResult = xParser->printFormula( aTokens, CellAddress( 0, 0, 0 ) );
+ }
+ else
+ {
+ //FIXME: currently just using simple converter, e.g $Sheet1.$A$1:$C$1 -> Sheet1!$A$1:$C$1
+ OUString aRange( rRange );
+ if( aRange.startsWith("$") )
+ aRange = aRange.copy(1);
+ aRange = aRange.replaceAll(".$", "!$" );
+ aResult = aRange;
+ }
+
+ return aResult;
+}
+
+void ChartExport::WriteChartObj( const Reference< XShape >& xShape, sal_Int32 nID, sal_Int32 nChartCount )
+{
+ FSHelperPtr pFS = GetFS();
+
+ Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
+
+ pFS->startElementNS(mnXmlNamespace, XML_graphicFrame);
+
+ pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr);
+
+ // TODO: get the correct chart name chart id
+ OUString sName = "Object 1";
+ Reference< XNamed > xNamed( xShape, UNO_QUERY );
+ if (xNamed.is())
+ sName = xNamed->getName();
+
+ pFS->startElementNS( mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(nID),
+ XML_name, sName);
+
+ OUString sURL;
+ if ( GetProperty( xShapeProps, "URL" ) )
+ mAny >>= sURL;
+ if( !sURL.isEmpty() )
+ {
+ OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
+ oox::getRelationship(Relationship::HYPERLINK),
+ mpURLTransformer->getTransformedString(sURL),
+ mpURLTransformer->isExternalURL(sURL));
+
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
+ }
+ pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
+
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr);
+
+ if( GetDocumentType() == DOCUMENT_PPTX )
+ pFS->singleElementNS(mnXmlNamespace, XML_nvPr);
+ pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr );
+
+ // visual chart properties
+ WriteShapeTransformation( xShape, mnXmlNamespace );
+
+ // writer chart object
+ pFS->startElement(FSNS(XML_a, XML_graphic));
+ pFS->startElement( FSNS( XML_a, XML_graphicData ),
+ XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart" );
+ OUString sId;
+ const char* sFullPath = nullptr;
+ const char* sRelativePath = nullptr;
+ switch( GetDocumentType() )
+ {
+ case DOCUMENT_DOCX:
+ {
+ sFullPath = "word/charts/chart";
+ sRelativePath = "charts/chart";
+ break;
+ }
+ case DOCUMENT_PPTX:
+ {
+ sFullPath = "ppt/charts/chart";
+ sRelativePath = "../charts/chart";
+ break;
+ }
+ case DOCUMENT_XLSX:
+ {
+ sFullPath = "xl/charts/chart";
+ sRelativePath = "../charts/chart";
+ break;
+ }
+ default:
+ {
+ sFullPath = "charts/chart";
+ sRelativePath = "charts/chart";
+ break;
+ }
+ }
+ OUString sFullStream = OUStringBuffer()
+ .appendAscii(sFullPath)
+ .append(OUString::number(nChartCount) + ".xml")
+ .makeStringAndClear();
+ OUString sRelativeStream = OUStringBuffer()
+ .appendAscii(sRelativePath)
+ .append(OUString::number(nChartCount) + ".xml" )
+ .makeStringAndClear();
+ FSHelperPtr pChart = CreateOutputStream(
+ sFullStream,
+ sRelativeStream,
+ pFS->getOutputStream(),
+ "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
+ OUStringToOString(oox::getRelationship(Relationship::CHART), RTL_TEXTENCODING_UTF8).getStr(),
+ &sId );
+
+ XmlFilterBase* pFB = GetFB();
+ pFS->singleElement( FSNS( XML_c, XML_chart ),
+ FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)),
+ FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)),
+ FSNS(XML_r, XML_id), sId );
+
+ pFS->endElement( FSNS( XML_a, XML_graphicData ) );
+ pFS->endElement( FSNS( XML_a, XML_graphic ) );
+ pFS->endElementNS( mnXmlNamespace, XML_graphicFrame );
+
+ SetFS( pChart );
+ ExportContent();
+}
+
+void ChartExport::InitRangeSegmentationProperties( const Reference< chart2::XChartDocument > & xChartDoc )
+{
+ if( !xChartDoc.is())
+ return;
+
+ try
+ {
+ Reference< chart2::data::XDataProvider > xDataProvider( xChartDoc->getDataProvider() );
+ OSL_ENSURE( xDataProvider.is(), "No DataProvider" );
+ if( xDataProvider.is())
+ {
+ mbHasCategoryLabels = lcl_hasCategoryLabels( xChartDoc );
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+}
+
+void ChartExport::ExportContent()
+{
+ Reference< chart2::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
+ OSL_ASSERT( xChartDoc.is() );
+ if( !xChartDoc.is() )
+ return;
+ InitRangeSegmentationProperties( xChartDoc );
+ // TODO: export chart
+ ExportContent_( );
+}
+
+void ChartExport::ExportContent_()
+{
+ Reference< css::chart::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
+ if( xChartDoc.is())
+ {
+ // determine if data comes from the outside
+ bool bIncludeTable = true;
+
+ Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
+ if( xNewDoc.is())
+ {
+ // check if we have own data. If so we must not export the complete
+ // range string, as this is our only indicator for having own or
+ // external data. @todo: fix this in the file format!
+ Reference< lang::XServiceInfo > xDPServiceInfo( xNewDoc->getDataProvider(), uno::UNO_QUERY );
+ if( ! (xDPServiceInfo.is() && xDPServiceInfo->getImplementationName() == "com.sun.star.comp.chart.InternalDataProvider" ))
+ {
+ bIncludeTable = false;
+ }
+ }
+ exportChartSpace( xChartDoc, bIncludeTable );
+ }
+ else
+ {
+ OSL_FAIL( "Couldn't export chart due to wrong XModel" );
+ }
+}
+
+void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument >& xChartDoc,
+ bool bIncludeTable )
+{
+ FSHelperPtr pFS = GetFS();
+ XmlFilterBase* pFB = GetFB();
+ pFS->startElement( FSNS( XML_c, XML_chartSpace ),
+ FSNS( XML_xmlns, XML_c ), pFB->getNamespaceURL(OOX_NS(dmlChart)),
+ FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
+ FSNS( XML_xmlns, XML_r ), pFB->getNamespaceURL(OOX_NS(officeRel)));
+ // TODO: get the correct editing language
+ pFS->singleElement(FSNS(XML_c, XML_lang), XML_val, "en-US");
+
+ pFS->singleElement(FSNS(XML_c, XML_roundedCorners), XML_val, "0");
+
+ if( !bIncludeTable )
+ {
+ // TODO:external data
+ }
+ //XML_chart
+ exportChart(xChartDoc);
+
+ // TODO: printSettings
+ // TODO: style
+ // TODO: text properties
+ // TODO: shape properties
+ Reference< XPropertySet > xPropSet = xChartDoc->getArea();
+ if( xPropSet.is() )
+ exportShapeProps( xPropSet );
+
+ //XML_externalData
+ exportExternalData(xChartDoc);
+
+ // export additional shapes in chart
+ exportAdditionalShapes(xChartDoc);
+
+ pFS->endElement( FSNS( XML_c, XML_chartSpace ) );
+}
+
+void ChartExport::exportExternalData( const Reference< css::chart::XChartDocument >& xChartDoc )
+{
+ // Embedded external data is grab bagged for docx file hence adding export part of
+ // external data for docx files only.
+ if(GetDocumentType() != DOCUMENT_DOCX)
+ return;
+
+ OUString externalDataPath;
+ Reference< beans::XPropertySet > xDocPropSet( xChartDoc->getDiagram(), uno::UNO_QUERY );
+ if( xDocPropSet.is())
+ {
+ try
+ {
+ Any aAny( xDocPropSet->getPropertyValue( "ExternalData" ));
+ aAny >>= externalDataPath;
+ }
+ catch( beans::UnknownPropertyException & )
+ {
+ SAL_WARN("oox", "Required property not found in ChartDocument");
+ }
+ }
+ if(externalDataPath.isEmpty())
+ return;
+
+ // Here adding external data entry to relationship.
+ OUString relationPath = externalDataPath;
+ // Converting absolute path to relative path.
+ if( externalDataPath[ 0 ] != '.' && externalDataPath[ 1 ] != '.')
+ {
+ sal_Int32 nSepPos = externalDataPath.indexOf( '/', 0 );
+ if( nSepPos > 0)
+ {
+ relationPath = relationPath.copy( nSepPos, ::std::max< sal_Int32 >( externalDataPath.getLength(), 0 ) - nSepPos );
+ relationPath = ".." + relationPath;
+ }
+ }
+ FSHelperPtr pFS = GetFS();
+ OUString type = oox::getRelationship(Relationship::PACKAGE);
+ if (relationPath.endsWith(".bin"))
+ type = oox::getRelationship(Relationship::OLEOBJECT);
+
+ OUString sRelId = GetFB()->addRelation(pFS->getOutputStream(),
+ type,
+ relationPath);
+ pFS->singleElementNS(XML_c, XML_externalData, FSNS(XML_r, XML_id), sRelId);
+}
+
+void ChartExport::exportAdditionalShapes( const Reference< css::chart::XChartDocument >& xChartDoc )
+{
+ Reference< beans::XPropertySet > xDocPropSet(xChartDoc, uno::UNO_QUERY);
+ if (!xDocPropSet.is())
+ return;
+
+ css::uno::Reference< css::drawing::XShapes > mxAdditionalShapes;
+ // get a sequence of non-chart shapes
+ try
+ {
+ Any aShapesAny = xDocPropSet->getPropertyValue("AdditionalShapes");
+ if( (aShapesAny >>= mxAdditionalShapes) && mxAdditionalShapes.is() )
+ {
+ OUString sId;
+ const char* sFullPath = nullptr;
+ const char* sRelativePath = nullptr;
+ sal_Int32 nDrawing = getNewDrawingUniqueId();
+
+ switch (GetDocumentType())
+ {
+ case DOCUMENT_DOCX:
+ {
+ sFullPath = "word/drawings/drawing";
+ sRelativePath = "../drawings/drawing";
+ break;
+ }
+ case DOCUMENT_PPTX:
+ {
+ sFullPath = "ppt/drawings/drawing";
+ sRelativePath = "../drawings/drawing";
+ break;
+ }
+ case DOCUMENT_XLSX:
+ {
+ sFullPath = "xl/drawings/drawing";
+ sRelativePath = "../drawings/drawing";
+ break;
+ }
+ default:
+ {
+ sFullPath = "drawings/drawing";
+ sRelativePath = "drawings/drawing";
+ break;
+ }
+ }
+ OUString sFullStream = OUStringBuffer()
+ .appendAscii(sFullPath)
+ .append(OUString::number(nDrawing) + ".xml")
+ .makeStringAndClear();
+ OUString sRelativeStream = OUStringBuffer()
+ .appendAscii(sRelativePath)
+ .append(OUString::number(nDrawing) + ".xml")
+ .makeStringAndClear();
+
+ sax_fastparser::FSHelperPtr pDrawing = CreateOutputStream(
+ sFullStream,
+ sRelativeStream,
+ GetFS()->getOutputStream(),
+ "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml",
+ OUStringToOString(oox::getRelationship(Relationship::CHARTUSERSHAPES), RTL_TEXTENCODING_UTF8).getStr(),
+ &sId);
+
+ GetFS()->singleElementNS(XML_c, XML_userShapes, FSNS(XML_r, XML_id), sId);
+
+ XmlFilterBase* pFB = GetFB();
+ pDrawing->startElement(FSNS(XML_c, XML_userShapes),
+ FSNS(XML_xmlns, XML_cdr), pFB->getNamespaceURL(OOX_NS(dmlChartDr)),
+ FSNS(XML_xmlns, XML_a), pFB->getNamespaceURL(OOX_NS(dml)),
+ FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)),
+ FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)));
+
+ const sal_Int32 nShapeCount(mxAdditionalShapes->getCount());
+ for (sal_Int32 nShapeId = 0; nShapeId < nShapeCount; nShapeId++)
+ {
+ Reference< drawing::XShape > xShape;
+ mxAdditionalShapes->getByIndex(nShapeId) >>= xShape;
+ SAL_WARN_IF(!xShape.is(), "xmloff.chart", "Shape without an XShape?");
+ if (!xShape.is())
+ continue;
+
+ // TODO: absSizeAnchor: we import both (absSizeAnchor and relSizeAnchor), but there is no essential difference between them.
+ pDrawing->startElement(FSNS(XML_cdr, XML_relSizeAnchor));
+ uno::Reference< beans::XPropertySet > xShapeProperties(xShape, uno::UNO_QUERY);
+ if( xShapeProperties.is() )
+ {
+ Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY);
+ awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT);
+ WriteFromTo( xShape, aPageSize, pDrawing );
+
+ ShapeExport aExport(XML_cdr, pDrawing, nullptr, GetFB(), GetDocumentType(), nullptr, true);
+ aExport.WriteShape(xShape);
+ }
+ pDrawing->endElement(FSNS(XML_cdr, XML_relSizeAnchor));
+ }
+ pDrawing->endElement(FSNS(XML_c, XML_userShapes));
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("xmloff.chart", "AdditionalShapes not found");
+ }
+}
+
+void ChartExport::exportChart( const Reference< css::chart::XChartDocument >& xChartDoc )
+{
+ Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
+ mxDiagram.set( xChartDoc->getDiagram() );
+ if( xNewDoc.is())
+ mxNewDiagram.set( xNewDoc->getFirstDiagram());
+
+ // get Properties of ChartDocument
+ bool bHasMainTitle = false;
+ OUString aSubTitle;
+ bool bHasLegend = false;
+ Reference< beans::XPropertySet > xDocPropSet( xChartDoc, uno::UNO_QUERY );
+ if( xDocPropSet.is())
+ {
+ try
+ {
+ Any aAny( xDocPropSet->getPropertyValue("HasMainTitle"));
+ aAny >>= bHasMainTitle;
+ aAny = xDocPropSet->getPropertyValue("HasLegend");
+ aAny >>= bHasLegend;
+ }
+ catch( beans::UnknownPropertyException & )
+ {
+ SAL_WARN("oox", "Required property not found in ChartDocument");
+ }
+ } // if( xDocPropSet.is())
+
+ Reference< beans::XPropertySet > xPropSubTitle( xChartDoc->getSubTitle(), UNO_QUERY );
+ if( xPropSubTitle.is())
+ {
+ try
+ {
+ xPropSubTitle->getPropertyValue("String") >>= aSubTitle;
+ }
+ catch( beans::UnknownPropertyException & )
+ {
+ }
+ }
+
+ // chart element
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_chart));
+
+ // titles
+ if( bHasMainTitle )
+ {
+ exportTitle( xChartDoc->getTitle(), !aSubTitle.isEmpty() ? &aSubTitle : nullptr );
+ pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0");
+ }
+ else if( !aSubTitle.isEmpty() )
+ {
+ exportTitle( xChartDoc->getSubTitle(), nullptr );
+ pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0");
+ }
+ else
+ {
+ pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "1");
+ }
+
+ InitPlotArea( );
+ if( mbIs3DChart )
+ {
+ exportView3D();
+
+ // floor
+ Reference< beans::XPropertySet > xFloor = mxNewDiagram->getFloor();
+ if( xFloor.is() )
+ {
+ pFS->startElement(FSNS(XML_c, XML_floor));
+ exportShapeProps( xFloor );
+ pFS->endElement( FSNS( XML_c, XML_floor ) );
+ }
+
+ // LibreOffice doesn't distinguish between sideWall and backWall (both are using the same color).
+ // It is controlled by the same Wall property.
+ Reference< beans::XPropertySet > xWall = mxNewDiagram->getWall();
+ if( xWall.is() )
+ {
+ // sideWall
+ pFS->startElement(FSNS(XML_c, XML_sideWall));
+ exportShapeProps( xWall );
+ pFS->endElement( FSNS( XML_c, XML_sideWall ) );
+
+ // backWall
+ pFS->startElement(FSNS(XML_c, XML_backWall));
+ exportShapeProps( xWall );
+ pFS->endElement( FSNS( XML_c, XML_backWall ) );
+ }
+
+ }
+ // plot area
+ exportPlotArea( xChartDoc );
+ // legend
+ if( bHasLegend )
+ exportLegend( xChartDoc );
+
+ uno::Reference<beans::XPropertySet> xDiagramPropSet(xChartDoc->getDiagram(), uno::UNO_QUERY);
+ uno::Any aPlotVisOnly = xDiagramPropSet->getPropertyValue("IncludeHiddenCells");
+ bool bIncludeHiddenCells = false;
+ aPlotVisOnly >>= bIncludeHiddenCells;
+ pFS->singleElement(FSNS(XML_c, XML_plotVisOnly), XML_val, ToPsz10(!bIncludeHiddenCells));
+
+ exportMissingValueTreatment(Reference<beans::XPropertySet>(mxDiagram, uno::UNO_QUERY));
+
+ pFS->endElement( FSNS( XML_c, XML_chart ) );
+}
+
+void ChartExport::exportMissingValueTreatment(const uno::Reference<beans::XPropertySet>& xPropSet)
+{
+ if (!xPropSet.is())
+ return;
+
+ sal_Int32 nVal = 0;
+ uno::Any aAny = xPropSet->getPropertyValue("MissingValueTreatment");
+ if (!(aAny >>= nVal))
+ return;
+
+ const char* pVal = nullptr;
+ switch (nVal)
+ {
+ case cssc::MissingValueTreatment::LEAVE_GAP:
+ pVal = "gap";
+ break;
+ case cssc::MissingValueTreatment::USE_ZERO:
+ pVal = "zero";
+ break;
+ case cssc::MissingValueTreatment::CONTINUE:
+ pVal = "span";
+ break;
+ default:
+ SAL_WARN("oox", "unknown MissingValueTreatment value");
+ break;
+ }
+
+ FSHelperPtr pFS = GetFS();
+ pFS->singleElement(FSNS(XML_c, XML_dispBlanksAs), XML_val, pVal);
+}
+
+void ChartExport::exportLegend( const Reference< css::chart::XChartDocument >& xChartDoc )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_legend));
+
+ Reference< beans::XPropertySet > xProp( xChartDoc->getLegend(), uno::UNO_QUERY );
+ if( xProp.is() )
+ {
+ // position
+ css::chart::ChartLegendPosition aLegendPos = css::chart::ChartLegendPosition_NONE;
+ try
+ {
+ Any aAny( xProp->getPropertyValue( "Alignment" ));
+ aAny >>= aLegendPos;
+ }
+ catch( beans::UnknownPropertyException & )
+ {
+ SAL_WARN("oox", "Property Align not found in ChartLegend");
+ }
+
+ const char* strPos = nullptr;
+ switch( aLegendPos )
+ {
+ case css::chart::ChartLegendPosition_LEFT:
+ strPos = "l";
+ break;
+ case css::chart::ChartLegendPosition_RIGHT:
+ strPos = "r";
+ break;
+ case css::chart::ChartLegendPosition_TOP:
+ strPos = "t";
+ break;
+ case css::chart::ChartLegendPosition_BOTTOM:
+ strPos = "b";
+ break;
+ case css::chart::ChartLegendPosition_NONE:
+ case css::chart::ChartLegendPosition::ChartLegendPosition_MAKE_FIXED_SIZE:
+ // nothing
+ break;
+ }
+
+ if( strPos != nullptr )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_legendPos), XML_val, strPos);
+ }
+
+ // legendEntry
+ Reference<chart2::XCoordinateSystemContainer> xCooSysContainer(mxNewDiagram, UNO_QUERY_THROW);
+ const Sequence<Reference<chart2::XCoordinateSystem>> xCooSysSequence(xCooSysContainer->getCoordinateSystems());
+
+ sal_Int32 nIndex = 0;
+ bool bShowLegendEntry;
+ for (const auto& rCooSys : xCooSysSequence)
+ {
+ PropertySet aCooSysProp(rCooSys);
+ bool bSwapXAndY = aCooSysProp.getBoolProperty(PROP_SwapXAndYAxis);
+
+ Reference<chart2::XChartTypeContainer> xChartTypeContainer(rCooSys, UNO_QUERY_THROW);
+ const Sequence<Reference<chart2::XChartType>> xChartTypeSequence(xChartTypeContainer->getChartTypes());
+ if (!xChartTypeSequence.hasElements())
+ continue;
+
+ for (const auto& rCT : xChartTypeSequence)
+ {
+ Reference<chart2::XDataSeriesContainer> xDSCont(rCT, UNO_QUERY);
+ if (!xDSCont.is())
+ continue;
+
+ OUString aChartType(rCT->getChartType());
+ bool bIsPie = lcl_getChartType(aChartType) == chart::TYPEID_PIE;
+ if (bIsPie)
+ {
+ PropertySet xChartTypeProp(rCT);
+ bIsPie = !xChartTypeProp.getBoolProperty(PROP_UseRings);
+ }
+ const Sequence<Reference<chart2::XDataSeries>> aDataSeriesSeq = xDSCont->getDataSeries();
+ if (bSwapXAndY)
+ nIndex += aDataSeriesSeq.getLength() - 1;
+ for (const auto& rDataSeries : aDataSeriesSeq)
+ {
+ PropertySet aSeriesProp(rDataSeries);
+ bool bVaryColorsByPoint = aSeriesProp.getBoolProperty(PROP_VaryColorsByPoint);
+ if (bVaryColorsByPoint || bIsPie)
+ {
+ Sequence<sal_Int32> deletedLegendEntriesSeq;
+ aSeriesProp.getProperty(deletedLegendEntriesSeq, PROP_DeletedLegendEntries);
+ for (const auto& deletedLegendEntry : std::as_const(deletedLegendEntriesSeq))
+ {
+ pFS->startElement(FSNS(XML_c, XML_legendEntry));
+ pFS->singleElement(FSNS(XML_c, XML_idx), XML_val,
+ OString::number(nIndex + deletedLegendEntry));
+ pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, "1");
+ pFS->endElement(FSNS(XML_c, XML_legendEntry));
+ }
+ Reference<chart2::data::XDataSource> xDSrc(rDataSeries, UNO_QUERY);
+ if (!xDSrc.is())
+ continue;
+
+ const Sequence<Reference<chart2::data::XLabeledDataSequence>> aDataSeqs = xDSrc->getDataSequences();
+ for (const auto& rDataSeq : aDataSeqs)
+ {
+ Reference<chart2::data::XDataSequence> xValues = rDataSeq->getValues();
+ if (!xValues.is())
+ continue;
+
+ sal_Int32 nDataSeqSize = xValues->getData().getLength();
+ nIndex += nDataSeqSize;
+ }
+ }
+ else
+ {
+ bShowLegendEntry = aSeriesProp.getBoolProperty(PROP_ShowLegendEntry);
+ if (!bShowLegendEntry)
+ {
+ pFS->startElement(FSNS(XML_c, XML_legendEntry));
+ pFS->singleElement(FSNS(XML_c, XML_idx), XML_val,
+ OString::number(nIndex));
+ pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, "1");
+ pFS->endElement(FSNS(XML_c, XML_legendEntry));
+ }
+ bSwapXAndY ? nIndex-- : nIndex++;
+ }
+ }
+ if (bSwapXAndY)
+ nIndex += aDataSeriesSeq.getLength() + 1;
+ }
+ }
+
+ uno::Any aRelativePos = xProp->getPropertyValue("RelativePosition");
+ if (aRelativePos.hasValue())
+ {
+ pFS->startElement(FSNS(XML_c, XML_layout));
+ pFS->startElement(FSNS(XML_c, XML_manualLayout));
+
+ pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
+ pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
+ chart2::RelativePosition aPos = aRelativePos.get<chart2::RelativePosition>();
+
+ const double x = aPos.Primary;
+ const double y = aPos.Secondary;
+
+ pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
+ pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
+
+ uno::Any aRelativeSize = xProp->getPropertyValue("RelativeSize");
+ if (aRelativeSize.hasValue())
+ {
+ chart2::RelativeSize aSize = aRelativeSize.get<chart2::RelativeSize>();
+
+ const double w = aSize.Primary;
+ const double h = aSize.Secondary;
+
+ pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w));
+
+ pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h));
+ }
+
+ SAL_WARN_IF(aPos.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position");
+
+ pFS->endElement(FSNS(XML_c, XML_manualLayout));
+ pFS->endElement(FSNS(XML_c, XML_layout));
+ }
+
+ if (strPos != nullptr)
+ {
+ uno::Any aOverlay = xProp->getPropertyValue("Overlay");
+ if(aOverlay.get<bool>())
+ pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "1");
+ else
+ pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0");
+ }
+
+ // shape properties
+ exportShapeProps( xProp );
+
+ // draw-chart:txPr text properties
+ exportTextProps( xProp );
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_legend ) );
+}
+
+void ChartExport::exportTitle( const Reference< XShape >& xShape, const OUString* pSubText)
+{
+ OUString sText;
+ Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
+ if( xPropSet.is())
+ {
+ xPropSet->getPropertyValue("String") >>= sText;
+ }
+
+ // tdf#101322: add subtitle to title
+ if( pSubText )
+ sText = sText.isEmpty() ? *pSubText : sText + "\n" + *pSubText;
+
+ if( sText.isEmpty() )
+ return;
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_title));
+
+ pFS->startElement(FSNS(XML_c, XML_tx));
+ pFS->startElement(FSNS(XML_c, XML_rich));
+
+ // TODO: bodyPr
+ const char* sWritingMode = nullptr;
+ bool bVertical = false;
+ xPropSet->getPropertyValue("StackedText") >>= bVertical;
+ if( bVertical )
+ sWritingMode = "wordArtVert";
+
+ sal_Int32 nRotation = 0;
+ xPropSet->getPropertyValue("TextRotation") >>= nRotation;
+
+ pFS->singleElement( FSNS( XML_a, XML_bodyPr ),
+ XML_vert, sWritingMode,
+ XML_rot, oox::drawingml::calcRotationValue(nRotation) );
+ // TODO: lstStyle
+ pFS->singleElement(FSNS(XML_a, XML_lstStyle));
+ // FIXME: handle multiple paragraphs to parse aText
+ pFS->startElement(FSNS(XML_a, XML_p));
+
+ pFS->startElement(FSNS(XML_a, XML_pPr));
+
+ bool bDummy = false;
+ sal_Int32 nDummy;
+ WriteRunProperties(xPropSet, false, XML_defRPr, true, bDummy, nDummy );
+
+ pFS->endElement( FSNS( XML_a, XML_pPr ) );
+
+ pFS->startElement(FSNS(XML_a, XML_r));
+ bDummy = false;
+ WriteRunProperties( xPropSet, false, XML_rPr, true, bDummy, nDummy );
+ pFS->startElement(FSNS(XML_a, XML_t));
+ pFS->writeEscaped( sText );
+ pFS->endElement( FSNS( XML_a, XML_t ) );
+ pFS->endElement( FSNS( XML_a, XML_r ) );
+
+ pFS->endElement( FSNS( XML_a, XML_p ) );
+
+ pFS->endElement( FSNS( XML_c, XML_rich ) );
+ pFS->endElement( FSNS( XML_c, XML_tx ) );
+
+ uno::Any aManualLayout = xPropSet->getPropertyValue("RelativePosition");
+ if (aManualLayout.hasValue())
+ {
+ pFS->startElement(FSNS(XML_c, XML_layout));
+ pFS->startElement(FSNS(XML_c, XML_manualLayout));
+ pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
+ pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
+
+ Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY);
+ awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT);
+
+ awt::Size aSize = xShape->getSize();
+ awt::Point aPos2 = xShape->getPosition();
+ // rotated shapes need special handling...
+ double fSin = fabs(sin(basegfx::deg2rad<100>(nRotation)));
+ // remove part of height from X direction, if title is rotated down
+ if( nRotation*0.01 > 180.0 )
+ aPos2.X -= static_cast<sal_Int32>(fSin * aSize.Height + 0.5);
+ // remove part of width from Y direction, if title is rotated up
+ else if( nRotation*0.01 > 0.0 )
+ aPos2.Y -= static_cast<sal_Int32>(fSin * aSize.Width + 0.5);
+
+ double x = static_cast<double>(aPos2.X) / static_cast<double>(aPageSize.Width);
+ double y = static_cast<double>(aPos2.Y) / static_cast<double>(aPageSize.Height);
+ /*
+ pFS->singleElement(FSNS(XML_c, XML_wMode), XML_val, "edge");
+ pFS->singleElement(FSNS(XML_c, XML_hMode), XML_val, "edge");
+ */
+ pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
+ pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
+ /*
+ pFS->singleElement(FSNS(XML_c, XML_w), XML_val, "");
+ pFS->singleElement(FSNS(XML_c, XML_h), XML_val, "");
+ */
+ pFS->endElement(FSNS(XML_c, XML_manualLayout));
+ pFS->endElement(FSNS(XML_c, XML_layout));
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0");
+
+ // shape properties
+ if( xPropSet.is() )
+ {
+ exportShapeProps( xPropSet );
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_title ) );
+}
+
+namespace {
+
+ std::vector<Sequence<Reference<chart2::XDataSeries> > > splitDataSeriesByAxis(const Reference< chart2::XChartType >& xChartType)
+ {
+ std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitSeries;
+ std::map<sal_Int32, size_t> aMapAxisToIndex;
+
+ Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY);
+ if (xDSCnt.is())
+ {
+ sal_Int32 nAxisIndexOfFirstSeries = -1;
+ const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries());
+ for (const uno::Reference<chart2::XDataSeries>& xSeries : aSeriesSeq)
+ {
+ Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
+ if (!xPropSet.is())
+ continue;
+
+ sal_Int32 nAxisIndex = -1;
+ uno::Any aAny = xPropSet->getPropertyValue("AttachedAxisIndex");
+ aAny >>= nAxisIndex;
+ size_t nVectorPos = 0;
+ if (nAxisIndexOfFirstSeries == -1)
+ {
+ nAxisIndexOfFirstSeries = nAxisIndex;
+ }
+
+ auto it = aMapAxisToIndex.find(nAxisIndex);
+ if (it == aMapAxisToIndex.end())
+ {
+ aSplitSeries.emplace_back();
+ nVectorPos = aSplitSeries.size() - 1;
+ aMapAxisToIndex.insert(std::pair<sal_Int32, size_t>(nAxisIndex, nVectorPos));
+ }
+ else
+ {
+ nVectorPos = it->second;
+ }
+
+ uno::Sequence<Reference<chart2::XDataSeries> >& rAxisSeriesSeq = aSplitSeries[nVectorPos];
+ sal_Int32 nLength = rAxisSeriesSeq.getLength();
+ rAxisSeriesSeq.realloc(nLength + 1);
+ rAxisSeriesSeq.getArray()[nLength] = xSeries;
+ }
+ // if the first series attached to secondary axis, then export those series first, which are attached to primary axis
+ // also the MS Office export every time in this order
+ if (aSplitSeries.size() > 1 && nAxisIndexOfFirstSeries == 1)
+ {
+ std::swap(aSplitSeries[0], aSplitSeries[1]);
+ }
+ }
+
+ return aSplitSeries;
+ }
+
+}
+
+void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >& xChartDoc)
+{
+ Reference< chart2::XCoordinateSystemContainer > xBCooSysCnt( mxNewDiagram, uno::UNO_QUERY );
+ if( ! xBCooSysCnt.is())
+ return;
+
+ // plot-area element
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_plotArea));
+
+ Reference<beans::XPropertySet> xWall(mxNewDiagram, uno::UNO_QUERY);
+ if( xWall.is() )
+ {
+ uno::Any aAny = xWall->getPropertyValue("RelativePosition");
+ if (aAny.hasValue())
+ {
+ chart2::RelativePosition aPos = aAny.get<chart2::RelativePosition>();
+ aAny = xWall->getPropertyValue("RelativeSize");
+ chart2::RelativeSize aSize = aAny.get<chart2::RelativeSize>();
+ uno::Reference< css::chart::XDiagramPositioning > xDiagramPositioning( xChartDoc->getDiagram(), uno::UNO_QUERY );
+ exportManualLayout(aPos, aSize, xDiagramPositioning->isExcludingDiagramPositioning() );
+ }
+ }
+
+ // chart type
+ const Sequence< Reference< chart2::XCoordinateSystem > >
+ aCooSysSeq( xBCooSysCnt->getCoordinateSystems());
+
+ // tdf#123647 Save empty chart as empty bar chart.
+ if (!aCooSysSeq.hasElements())
+ {
+ pFS->startElement(FSNS(XML_c, XML_barChart));
+ pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, "col");
+ pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, "clustered");
+ pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0");
+ exportAxesId(true);
+ pFS->endElement(FSNS(XML_c, XML_barChart));
+ }
+
+ for( const auto& rCS : aCooSysSeq )
+ {
+ Reference< chart2::XChartTypeContainer > xCTCnt( rCS, uno::UNO_QUERY );
+ if( ! xCTCnt.is())
+ continue;
+ mnSeriesCount=0;
+ const Sequence< Reference< chart2::XChartType > > aCTSeq( xCTCnt->getChartTypes());
+ for( const auto& rCT : aCTSeq )
+ {
+ Reference< chart2::XDataSeriesContainer > xDSCnt( rCT, uno::UNO_QUERY );
+ if( ! xDSCnt.is())
+ return;
+ Reference< chart2::XChartType > xChartType( rCT, uno::UNO_QUERY );
+ if( ! xChartType.is())
+ continue;
+ // note: if xDSCnt.is() then also aCTSeq[nCTIdx]
+ OUString aChartType( xChartType->getChartType());
+ sal_Int32 eChartType = lcl_getChartType( aChartType );
+ switch( eChartType )
+ {
+ case chart::TYPEID_BAR:
+ {
+ exportBarChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_AREA:
+ {
+ exportAreaChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_LINE:
+ {
+ exportLineChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_BUBBLE:
+ {
+ exportBubbleChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_OFPIE:
+ {
+ break;
+ }
+ case chart::TYPEID_DOUGHNUT:
+ case chart::TYPEID_PIE:
+ {
+ exportPieChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_RADARLINE:
+ case chart::TYPEID_RADARAREA:
+ {
+ exportRadarChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_SCATTER:
+ {
+ exportScatterChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_STOCK:
+ {
+ exportStockChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_SURFACE:
+ {
+ exportSurfaceChart( xChartType );
+ break;
+ }
+ default:
+ {
+ SAL_WARN("oox", "ChartExport::exportPlotArea -- not support chart type");
+ break;
+ }
+ }
+
+ }
+ }
+ //Axis Data
+ exportAxes( );
+ // Data Table
+ exportDataTable();
+
+ // shape properties
+ /*
+ * Export the Plot area Shape Properties
+ * eg: Fill and Outline
+ */
+ Reference< css::chart::X3DDisplay > xWallFloorSupplier( mxDiagram, uno::UNO_QUERY );
+ // tdf#114139 For 2D charts Plot Area equivalent is Chart Wall.
+ // Unfortunately LibreOffice doesn't have Plot Area equivalent for 3D charts.
+ // It means that Plot Area couldn't be displayed and changed for 3D chars in LibreOffice.
+ // We cannot write Wall attributes into Plot Area for 3D charts, because Wall us used as background wall.
+ if( !mbIs3DChart && xWallFloorSupplier.is() )
+ {
+ Reference< beans::XPropertySet > xWallPropSet = xWallFloorSupplier->getWall();
+ if( xWallPropSet.is() )
+ {
+ uno::Any aAny = xWallPropSet->getPropertyValue("LineStyle");
+ sal_Int32 eChartType = getChartType( );
+ // Export LineStyle_NONE instead of default linestyle of PlotArea border, because LibreOffice
+ // make invisible the Wall shape properties, in case of these charts. Or in the future set
+ // the default LineStyle of these charts to LineStyle_NONE.
+ bool noSupportWallProp = ( (eChartType == chart::TYPEID_PIE) || (eChartType == chart::TYPEID_RADARLINE) || (eChartType == chart::TYPEID_RADARAREA) );
+ if ( noSupportWallProp && (aAny != drawing::LineStyle_NONE) )
+ {
+ xWallPropSet->setPropertyValue( "LineStyle", uno::Any(drawing::LineStyle_NONE) );
+ }
+ exportShapeProps( xWallPropSet );
+ }
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_plotArea ) );
+
+}
+
+void ChartExport::exportManualLayout(const css::chart2::RelativePosition& rPos,
+ const css::chart2::RelativeSize& rSize,
+ const bool bIsExcludingDiagramPositioning)
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_layout));
+ pFS->startElement(FSNS(XML_c, XML_manualLayout));
+
+ // By default layoutTarget is set to "outer" and we shouldn't save it in that case
+ if ( bIsExcludingDiagramPositioning )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_layoutTarget), XML_val, "inner");
+ }
+ pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
+ pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
+
+ double x = rPos.Primary;
+ double y = rPos.Secondary;
+ const double w = rSize.Primary;
+ const double h = rSize.Secondary;
+ switch (rPos.Anchor)
+ {
+ case drawing::Alignment_LEFT:
+ y -= (h/2);
+ break;
+ case drawing::Alignment_TOP_LEFT:
+ break;
+ case drawing::Alignment_BOTTOM_LEFT:
+ y -= h;
+ break;
+ case drawing::Alignment_TOP:
+ x -= (w/2);
+ break;
+ case drawing::Alignment_CENTER:
+ x -= (w/2);
+ y -= (h/2);
+ break;
+ case drawing::Alignment_BOTTOM:
+ x -= (w/2);
+ y -= h;
+ break;
+ case drawing::Alignment_TOP_RIGHT:
+ x -= w;
+ break;
+ case drawing::Alignment_BOTTOM_RIGHT:
+ x -= w;
+ y -= h;
+ break;
+ case drawing::Alignment_RIGHT:
+ y -= (h/2);
+ x -= w;
+ break;
+ default:
+ SAL_WARN("oox", "unhandled alignment case for manual layout export " << static_cast<sal_uInt16>(rPos.Anchor));
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
+
+ pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
+
+ pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w));
+
+ pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h));
+
+ pFS->endElement(FSNS(XML_c, XML_manualLayout));
+ pFS->endElement(FSNS(XML_c, XML_layout));
+}
+
+void ChartExport::exportFill( const Reference< XPropertySet >& xPropSet )
+{
+ // Similar to DrawingML::WriteFill, but gradient access via name
+ if (!GetProperty( xPropSet, "FillStyle" ))
+ return;
+ FillStyle aFillStyle(FillStyle_NONE);
+ mAny >>= aFillStyle;
+
+ // map full transparent background to no fill
+ if (aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparence" ))
+ {
+ sal_Int16 nVal = 0;
+ mAny >>= nVal;
+ if ( nVal == 100 )
+ aFillStyle = FillStyle_NONE;
+ }
+ OUString sFillTransparenceGradientName;
+ if (aFillStyle == FillStyle_SOLID
+ && GetProperty(xPropSet, "FillTransparenceGradientName") && (mAny >>= sFillTransparenceGradientName)
+ && !sFillTransparenceGradientName.isEmpty())
+ {
+ awt::Gradient aTransparenceGradient;
+ uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
+ uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY);
+ uno::Any rTransparenceValue = xTransparenceGradient->getByName(sFillTransparenceGradientName);
+ rTransparenceValue >>= aTransparenceGradient;
+ if (aTransparenceGradient.StartColor == 0xffffff && aTransparenceGradient.EndColor == 0xffffff)
+ aFillStyle = FillStyle_NONE;
+ }
+ switch( aFillStyle )
+ {
+ case FillStyle_SOLID:
+ exportSolidFill(xPropSet);
+ break;
+ case FillStyle_GRADIENT :
+ exportGradientFill( xPropSet );
+ break;
+ case FillStyle_BITMAP :
+ exportBitmapFill( xPropSet );
+ break;
+ case FillStyle_HATCH:
+ exportHatch(xPropSet);
+ break;
+ case FillStyle_NONE:
+ mpFS->singleElementNS(XML_a, XML_noFill);
+ break;
+ default:
+ ;
+ }
+}
+
+void ChartExport::exportSolidFill(const Reference< XPropertySet >& xPropSet)
+{
+ // Similar to DrawingML::WriteSolidFill, but gradient access via name
+ // and currently no InteropGrabBag
+ // get fill color
+ if (!GetProperty( xPropSet, "FillColor" ))
+ return;
+ sal_uInt32 nFillColor = mAny.get<sal_uInt32>();
+
+ sal_Int32 nAlpha = MAX_PERCENT;
+ if (GetProperty( xPropSet, "FillTransparence" ))
+ {
+ sal_Int32 nTransparency = 0;
+ mAny >>= nTransparency;
+ // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
+ nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
+ }
+ // OOXML has no separate transparence gradient but uses transparency in the gradient stops.
+ // So we merge transparency and color and use gradient fill in such case.
+ awt::Gradient aTransparenceGradient;
+ bool bNeedGradientFill(false);
+ OUString sFillTransparenceGradientName;
+ if (GetProperty(xPropSet, "FillTransparenceGradientName")
+ && (mAny >>= sFillTransparenceGradientName)
+ && !sFillTransparenceGradientName.isEmpty())
+ {
+ uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
+ uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY);
+ uno::Any rTransparenceValue = xTransparenceGradient->getByName(sFillTransparenceGradientName);
+ rTransparenceValue >>= aTransparenceGradient;
+ if (aTransparenceGradient.StartColor != aTransparenceGradient.EndColor)
+ bNeedGradientFill = true;
+ else if (aTransparenceGradient.StartColor != 0)
+ nAlpha = lcl_getAlphaFromTransparenceGradient(aTransparenceGradient, true);
+ }
+ // write XML
+ if (bNeedGradientFill)
+ {
+ awt::Gradient aPseudoColorGradient;
+ aPseudoColorGradient.XOffset = aTransparenceGradient.XOffset;
+ aPseudoColorGradient.YOffset = aTransparenceGradient.YOffset;
+ aPseudoColorGradient.StartIntensity = 100;
+ aPseudoColorGradient.EndIntensity = 100;
+ aPseudoColorGradient.Angle = aTransparenceGradient.Angle;
+ aPseudoColorGradient.Border = aTransparenceGradient.Border;
+ aPseudoColorGradient.Style = aTransparenceGradient.Style;
+ aPseudoColorGradient.StartColor = nFillColor;
+ aPseudoColorGradient.EndColor = nFillColor;
+ aPseudoColorGradient.StepCount = aTransparenceGradient.StepCount;
+ mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
+ WriteGradientFill(aPseudoColorGradient, aTransparenceGradient);
+ mpFS->endElementNS(XML_a, XML_gradFill);
+ }
+ else
+ WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
+}
+
+void ChartExport::exportHatch( const Reference< XPropertySet >& xPropSet )
+{
+ if (!xPropSet.is())
+ return;
+
+ if (GetProperty(xPropSet, "FillHatchName"))
+ {
+ OUString aHatchName;
+ mAny >>= aHatchName;
+ uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
+ uno::Reference< container::XNameAccess > xHatchTable( xFact->createInstance("com.sun.star.drawing.HatchTable"), uno::UNO_QUERY );
+ uno::Any rValue = xHatchTable->getByName(aHatchName);
+ css::drawing::Hatch aHatch;
+ rValue >>= aHatch;
+ WritePattFill(xPropSet, aHatch);
+ }
+
+}
+
+void ChartExport::exportBitmapFill( const Reference< XPropertySet >& xPropSet )
+{
+ if( !xPropSet.is() )
+ return;
+
+ OUString sFillBitmapName;
+ xPropSet->getPropertyValue("FillBitmapName") >>= sFillBitmapName;
+
+ uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
+ try
+ {
+ uno::Reference< container::XNameAccess > xBitmapTable( xFact->createInstance("com.sun.star.drawing.BitmapTable"), uno::UNO_QUERY );
+ uno::Any rValue = xBitmapTable->getByName( sFillBitmapName );
+ if (rValue.has<uno::Reference<awt::XBitmap>>())
+ {
+ uno::Reference<awt::XBitmap> xBitmap = rValue.get<uno::Reference<awt::XBitmap>>();
+ uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY);
+ if (xGraphic.is())
+ {
+ WriteXGraphicBlipFill(xPropSet, xGraphic, XML_a, true, true);
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION("oox", "ChartExport::exportBitmapFill");
+ }
+}
+
+void ChartExport::exportGradientFill( const Reference< XPropertySet >& xPropSet )
+{
+ if( !xPropSet.is() )
+ return;
+
+ OUString sFillGradientName;
+ xPropSet->getPropertyValue("FillGradientName") >>= sFillGradientName;
+
+ uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
+ try
+ {
+ uno::Reference< container::XNameAccess > xGradient( xFact->createInstance("com.sun.star.drawing.GradientTable"), uno::UNO_QUERY );
+ uno::Any rGradientValue = xGradient->getByName( sFillGradientName );
+ awt::Gradient aGradient;
+ if( rGradientValue >>= aGradient )
+ {
+ awt::Gradient aTransparenceGradient;
+ mpFS->startElementNS(XML_a, XML_gradFill);
+ OUString sFillTransparenceGradientName;
+ if( (xPropSet->getPropertyValue("FillTransparenceGradientName") >>= sFillTransparenceGradientName) && !sFillTransparenceGradientName.isEmpty())
+ {
+ uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY);
+ uno::Any rTransparenceValue = xTransparenceGradient->getByName(sFillTransparenceGradientName);
+ rTransparenceValue >>= aTransparenceGradient;
+ WriteGradientFill(aGradient, aTransparenceGradient);
+ }
+ else
+ {
+ WriteGradientFill(aGradient, aTransparenceGradient, xPropSet);
+ }
+ mpFS->endElementNS(XML_a, XML_gradFill);
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("oox", "ChartExport::exportGradientFill");
+ }
+}
+
+void ChartExport::exportDataTable( )
+{
+ FSHelperPtr pFS = GetFS();
+ Reference< beans::XPropertySet > aPropSet( mxDiagram, uno::UNO_QUERY );
+
+ bool bShowVBorder = false;
+ bool bShowHBorder = false;
+ bool bShowOutline = false;
+
+ if (GetProperty( aPropSet, "DataTableHBorder"))
+ mAny >>= bShowHBorder;
+ if (GetProperty( aPropSet, "DataTableVBorder"))
+ mAny >>= bShowVBorder;
+ if (GetProperty( aPropSet, "DataTableOutline"))
+ mAny >>= bShowOutline;
+
+ if (!(bShowVBorder || bShowHBorder || bShowOutline))
+ return;
+
+ pFS->startElement(FSNS(XML_c, XML_dTable));
+ if (bShowHBorder)
+ pFS->singleElement( FSNS( XML_c, XML_showHorzBorder ),
+ XML_val, "1" );
+ if (bShowVBorder)
+ pFS->singleElement(FSNS(XML_c, XML_showVertBorder), XML_val, "1");
+ if (bShowOutline)
+ pFS->singleElement(FSNS(XML_c, XML_showOutline), XML_val, "1");
+
+ pFS->endElement( FSNS( XML_c, XML_dTable));
+
+}
+void ChartExport::exportAreaChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ sal_Int32 nTypeId = XML_areaChart;
+ if (mbIs3DChart)
+ nTypeId = XML_area3DChart;
+ pFS->startElement(FSNS(XML_c, nTypeId));
+
+ exportGrouping();
+ bool bPrimaryAxes = true;
+ exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement(FSNS(XML_c, nTypeId));
+ }
+}
+
+void ChartExport::exportBarChart(const Reference< chart2::XChartType >& xChartType)
+{
+ sal_Int32 nTypeId = XML_barChart;
+ if (mbIs3DChart)
+ nTypeId = XML_bar3DChart;
+ FSHelperPtr pFS = GetFS();
+
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ pFS->startElement(FSNS(XML_c, nTypeId));
+ // bar direction
+ bool bVertical = false;
+ Reference< XPropertySet > xPropSet(mxDiagram, uno::UNO_QUERY);
+ if (GetProperty(xPropSet, "Vertical"))
+ mAny >>= bVertical;
+
+ const char* bardir = bVertical ? "bar" : "col";
+ pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, bardir);
+
+ exportGrouping(true);
+
+ exportVaryColors(xChartType);
+
+ bool bPrimaryAxes = true;
+ exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
+
+ Reference< XPropertySet > xTypeProp(xChartType, uno::UNO_QUERY);
+
+ if (xTypeProp.is() && GetProperty(xTypeProp, "GapwidthSequence"))
+ {
+ uno::Sequence< sal_Int32 > aBarPositionSequence;
+ mAny >>= aBarPositionSequence;
+ if (aBarPositionSequence.hasElements())
+ {
+ sal_Int32 nGapWidth = aBarPositionSequence[0];
+ pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(nGapWidth));
+ }
+ }
+
+ if (mbIs3DChart)
+ {
+ // Shape
+ namespace cssc = css::chart;
+ sal_Int32 nGeom3d = cssc::ChartSolidType::RECTANGULAR_SOLID;
+ if (xPropSet.is() && GetProperty(xPropSet, "SolidType"))
+ mAny >>= nGeom3d;
+ const char* sShapeType = nullptr;
+ switch (nGeom3d)
+ {
+ case cssc::ChartSolidType::RECTANGULAR_SOLID:
+ sShapeType = "box";
+ break;
+ case cssc::ChartSolidType::CONE:
+ sShapeType = "cone";
+ break;
+ case cssc::ChartSolidType::CYLINDER:
+ sShapeType = "cylinder";
+ break;
+ case cssc::ChartSolidType::PYRAMID:
+ sShapeType = "pyramid";
+ break;
+ }
+ pFS->singleElement(FSNS(XML_c, XML_shape), XML_val, sShapeType);
+ }
+
+ //overlap
+ if (!mbIs3DChart && xTypeProp.is() && GetProperty(xTypeProp, "OverlapSequence"))
+ {
+ uno::Sequence< sal_Int32 > aBarPositionSequence;
+ mAny >>= aBarPositionSequence;
+ if (aBarPositionSequence.hasElements())
+ {
+ sal_Int32 nOverlap = aBarPositionSequence[0];
+ // Stacked/Percent Bar/Column chart Overlap-workaround
+ // Export the Overlap value with 100% for stacked charts,
+ // because the default overlap value of the Bar/Column chart is 0% and
+ // LibreOffice do nothing with the overlap value in Stacked charts case,
+ // unlike the MS Office, which is interpreted differently.
+ if ((mbStacked || mbPercent) && nOverlap != 100)
+ {
+ nOverlap = 100;
+ pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap));
+ }
+ else // Normal bar chart
+ {
+ pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap));
+ }
+ }
+ }
+
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement(FSNS(XML_c, nTypeId));
+ }
+}
+
+void ChartExport::exportBubbleChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ pFS->startElement(FSNS(XML_c, XML_bubbleChart));
+
+ exportVaryColors(xChartType);
+
+ bool bPrimaryAxes = true;
+ exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
+
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement(FSNS(XML_c, XML_bubbleChart));
+ }
+}
+
+void ChartExport::exportDoughnutChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_doughnutChart));
+
+ exportVaryColors(xChartType);
+
+ bool bPrimaryAxes = true;
+ exportAllSeries(xChartType, bPrimaryAxes);
+ // firstSliceAng
+ exportFirstSliceAng( );
+ //FIXME: holeSize
+ pFS->singleElement(FSNS(XML_c, XML_holeSize), XML_val, OString::number(50));
+
+ pFS->endElement( FSNS( XML_c, XML_doughnutChart ) );
+}
+
+namespace {
+
+void writeDataLabelsRange(const FSHelperPtr& pFS, const XmlFilterBase* pFB, DataLabelsRange& rDLblsRange)
+{
+ if (rDLblsRange.empty())
+ return;
+
+ pFS->startElement(FSNS(XML_c, XML_extLst));
+ pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{02D57815-91ED-43cb-92C2-25804820EDAC}", FSNS(XML_xmlns, XML_c15), pFB->getNamespaceURL(OOX_NS(c15)));
+ pFS->startElement(FSNS(XML_c15, XML_datalabelsRange));
+
+ // Write cell range.
+ pFS->startElement(FSNS(XML_c15, XML_f));
+ pFS->writeEscaped(rDLblsRange.getRange());
+ pFS->endElement(FSNS(XML_c15, XML_f));
+
+ // Write all labels.
+ pFS->startElement(FSNS(XML_c15, XML_dlblRangeCache));
+ pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(rDLblsRange.count()));
+ for (const auto& rLabelKV: rDLblsRange)
+ {
+ pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(rLabelKV.first));
+ pFS->startElement(FSNS(XML_c, XML_v));
+ pFS->writeEscaped(rLabelKV.second);
+ pFS->endElement(FSNS( XML_c, XML_v ));
+ pFS->endElement(FSNS(XML_c, XML_pt));
+ }
+
+ pFS->endElement(FSNS(XML_c15, XML_dlblRangeCache));
+
+ pFS->endElement(FSNS(XML_c15, XML_datalabelsRange));
+ pFS->endElement(FSNS(XML_c, XML_ext));
+ pFS->endElement(FSNS(XML_c, XML_extLst));
+}
+
+}
+
+void ChartExport::exportLineChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ sal_Int32 nTypeId = XML_lineChart;
+ if( mbIs3DChart )
+ nTypeId = XML_line3DChart;
+ pFS->startElement(FSNS(XML_c, nTypeId));
+
+ exportGrouping( );
+
+ exportVaryColors(xChartType);
+ // TODO: show marker symbol in series?
+ bool bPrimaryAxes = true;
+ exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
+
+ // show marker?
+ sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ if( GetProperty( xPropSet, "SymbolType" ) )
+ mAny >>= nSymbolType;
+
+ if( !mbIs3DChart )
+ {
+ exportHiLowLines();
+ exportUpDownBars(xChartType);
+ const char* marker = nSymbolType == css::chart::ChartSymbolType::NONE? "0":"1";
+ pFS->singleElement(FSNS(XML_c, XML_marker), XML_val, marker);
+ }
+
+ exportAxesId(bPrimaryAxes, true);
+
+ pFS->endElement( FSNS( XML_c, nTypeId ) );
+ }
+}
+
+void ChartExport::exportPieChart( const Reference< chart2::XChartType >& xChartType )
+{
+ sal_Int32 eChartType = getChartType( );
+ if(eChartType == chart::TYPEID_DOUGHNUT)
+ {
+ exportDoughnutChart( xChartType );
+ return;
+ }
+ FSHelperPtr pFS = GetFS();
+ sal_Int32 nTypeId = XML_pieChart;
+ if( mbIs3DChart )
+ nTypeId = XML_pie3DChart;
+ pFS->startElement(FSNS(XML_c, nTypeId));
+
+ exportVaryColors(xChartType);
+
+ bool bPrimaryAxes = true;
+ exportAllSeries(xChartType, bPrimaryAxes);
+
+ if( !mbIs3DChart )
+ {
+ // firstSliceAng
+ exportFirstSliceAng( );
+ }
+
+ pFS->endElement( FSNS( XML_c, nTypeId ) );
+}
+
+void ChartExport::exportRadarChart( const Reference< chart2::XChartType >& xChartType)
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_radarChart));
+
+ // radarStyle
+ sal_Int32 eChartType = getChartType( );
+ const char* radarStyle = nullptr;
+ if( eChartType == chart::TYPEID_RADARAREA )
+ radarStyle = "filled";
+ else
+ radarStyle = "marker";
+ pFS->singleElement(FSNS(XML_c, XML_radarStyle), XML_val, radarStyle);
+
+ exportVaryColors(xChartType);
+ bool bPrimaryAxes = true;
+ exportAllSeries(xChartType, bPrimaryAxes);
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement( FSNS( XML_c, XML_radarChart ) );
+}
+
+void ChartExport::exportScatterChartSeries( const Reference< chart2::XChartType >& xChartType,
+ const css::uno::Sequence<css::uno::Reference<chart2::XDataSeries>>* pSeries)
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_scatterChart));
+ // TODO:scatterStyle
+
+ sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ if( GetProperty( xPropSet, "SymbolType" ) )
+ mAny >>= nSymbolType;
+
+ const char* scatterStyle = "lineMarker";
+ if (nSymbolType == css::chart::ChartSymbolType::NONE)
+ {
+ scatterStyle = "line";
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_scatterStyle), XML_val, scatterStyle);
+
+ exportVaryColors(xChartType);
+ // FIXME: should export xVal and yVal
+ bool bPrimaryAxes = true;
+ if (pSeries)
+ exportSeries(xChartType, *pSeries, bPrimaryAxes);
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement( FSNS( XML_c, XML_scatterChart ) );
+}
+
+void ChartExport::exportScatterChart( const Reference< chart2::XChartType >& xChartType )
+{
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ bool bExported = false;
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ bExported = true;
+ exportScatterChartSeries(xChartType, &splitDataSeries);
+ }
+ if (!bExported)
+ exportScatterChartSeries(xChartType, nullptr);
+}
+
+void ChartExport::exportStockChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ pFS->startElement(FSNS(XML_c, XML_stockChart));
+
+ bool bPrimaryAxes = true;
+ exportCandleStickSeries(splitDataSeries, bPrimaryAxes);
+
+ // export stock properties
+ Reference< css::chart::XStatisticDisplay > xStockPropProvider(mxDiagram, uno::UNO_QUERY);
+ if (xStockPropProvider.is())
+ {
+ exportHiLowLines();
+ exportUpDownBars(xChartType);
+ }
+
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement(FSNS(XML_c, XML_stockChart));
+ }
+}
+
+void ChartExport::exportHiLowLines()
+{
+ FSHelperPtr pFS = GetFS();
+ // export the chart property
+ Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY );
+
+ if (!xChartPropProvider.is())
+ return;
+
+ Reference< beans::XPropertySet > xStockPropSet = xChartPropProvider->getMinMaxLine();
+ if( !xStockPropSet.is() )
+ return;
+
+ pFS->startElement(FSNS(XML_c, XML_hiLowLines));
+ exportShapeProps( xStockPropSet );
+ pFS->endElement( FSNS( XML_c, XML_hiLowLines ) );
+}
+
+void ChartExport::exportUpDownBars( const Reference< chart2::XChartType >& xChartType)
+{
+ if(xChartType->getChartType() != "com.sun.star.chart2.CandleStickChartType")
+ return;
+
+ FSHelperPtr pFS = GetFS();
+ // export the chart property
+ Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY );
+ if(!xChartPropProvider.is())
+ return;
+
+ // updownbar
+ pFS->startElement(FSNS(XML_c, XML_upDownBars));
+ // TODO: gapWidth
+ pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(150));
+
+ Reference< beans::XPropertySet > xChartPropSet = xChartPropProvider->getUpBar();
+ if( xChartPropSet.is() )
+ {
+ pFS->startElement(FSNS(XML_c, XML_upBars));
+ // For Linechart with UpDownBars, spPr is not getting imported
+ // so no need to call the exportShapeProps() for LineChart
+ if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType")
+ {
+ exportShapeProps(xChartPropSet);
+ }
+ pFS->endElement( FSNS( XML_c, XML_upBars ) );
+ }
+ xChartPropSet = xChartPropProvider->getDownBar();
+ if( xChartPropSet.is() )
+ {
+ pFS->startElement(FSNS(XML_c, XML_downBars));
+ if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType")
+ {
+ exportShapeProps(xChartPropSet);
+ }
+ pFS->endElement( FSNS( XML_c, XML_downBars ) );
+ }
+ pFS->endElement( FSNS( XML_c, XML_upDownBars ) );
+}
+
+void ChartExport::exportSurfaceChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ sal_Int32 nTypeId = XML_surfaceChart;
+ if( mbIs3DChart )
+ nTypeId = XML_surface3DChart;
+ pFS->startElement(FSNS(XML_c, nTypeId));
+ exportVaryColors(xChartType);
+ bool bPrimaryAxes = true;
+ exportAllSeries(xChartType, bPrimaryAxes);
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement( FSNS( XML_c, nTypeId ) );
+}
+
+void ChartExport::exportAllSeries(const Reference<chart2::XChartType>& xChartType, bool& rPrimaryAxes)
+{
+ Reference< chart2::XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY );
+ if( ! xDSCnt.is())
+ return;
+
+ // export dataseries for current chart-type
+ Sequence< Reference< chart2::XDataSeries > > aSeriesSeq( xDSCnt->getDataSeries());
+ exportSeries(xChartType, aSeriesSeq, rPrimaryAxes);
+}
+
+void ChartExport::exportVaryColors(const Reference<chart2::XChartType>& xChartType)
+{
+ FSHelperPtr pFS = GetFS();
+ try
+ {
+ Reference<chart2::XDataSeries> xDataSeries = getPrimaryDataSeries(xChartType);
+ Reference<beans::XPropertySet> xDataSeriesProps(xDataSeries, uno::UNO_QUERY_THROW);
+ Any aAnyVaryColors = xDataSeriesProps->getPropertyValue("VaryColorsByPoint");
+ bool bVaryColors = false;
+ aAnyVaryColors >>= bVaryColors;
+ pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, ToPsz10(bVaryColors));
+ }
+ catch (...)
+ {
+ pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0");
+ }
+}
+
+void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType,
+ const Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq, bool& rPrimaryAxes )
+{
+ OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel();
+ OUString aChartType( xChartType->getChartType());
+ sal_Int32 eChartType = lcl_getChartType( aChartType );
+
+ for( const auto& rSeries : rSeriesSeq )
+ {
+ // export series
+ Reference< chart2::data::XDataSource > xSource( rSeries, uno::UNO_QUERY );
+ if( xSource.is())
+ {
+ Reference< chart2::XDataSeries > xDataSeries( xSource, uno::UNO_QUERY );
+ Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
+ xSource->getDataSequences());
+ // search for main sequence and create a series element
+ {
+ sal_Int32 nMainSequenceIndex = -1;
+ sal_Int32 nSeriesLength = 0;
+ Reference< chart2::data::XDataSequence > xValuesSeq;
+ Reference< chart2::data::XDataSequence > xLabelSeq;
+ sal_Int32 nSeqIdx=0;
+ for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx )
+ {
+ Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() );
+ if( nMainSequenceIndex==-1 )
+ {
+ Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY );
+ OUString aRole;
+ if( xSeqProp.is())
+ xSeqProp->getPropertyValue("Role") >>= aRole;
+ // "main" sequence
+ if( aRole == aLabelRole )
+ {
+ xValuesSeq.set( xTempValueSeq );
+ xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel());
+ nMainSequenceIndex = nSeqIdx;
+ }
+ }
+ sal_Int32 nSequenceLength = (xTempValueSeq.is()? xTempValueSeq->getData().getLength() : sal_Int32(0));
+ if( nSeriesLength < nSequenceLength )
+ nSeriesLength = nSequenceLength;
+ }
+
+ // have found the main sequence, then xValuesSeq and
+ // xLabelSeq contain those. Otherwise both are empty
+ {
+ FSHelperPtr pFS = GetFS();
+
+ pFS->startElement(FSNS(XML_c, XML_ser));
+
+ // TODO: idx and order
+ pFS->singleElement( FSNS( XML_c, XML_idx ),
+ XML_val, OString::number(mnSeriesCount) );
+ pFS->singleElement( FSNS( XML_c, XML_order ),
+ XML_val, OString::number(mnSeriesCount++) );
+
+ // export label
+ if( xLabelSeq.is() )
+ exportSeriesText( xLabelSeq );
+
+ Reference<XPropertySet> xPropSet(xDataSeries, UNO_QUERY_THROW);
+ if( GetProperty( xPropSet, "AttachedAxisIndex") )
+ {
+ sal_Int32 nLocalAttachedAxis = 0;
+ mAny >>= nLocalAttachedAxis;
+ rPrimaryAxes = isPrimaryAxes(nLocalAttachedAxis);
+ }
+
+ // export shape properties
+ Reference< XPropertySet > xOldPropSet = SchXMLSeriesHelper::createOldAPISeriesPropertySet(
+ rSeries, getModel() );
+ if( xOldPropSet.is() )
+ {
+ exportShapeProps( xOldPropSet );
+ }
+
+ switch( eChartType )
+ {
+ case chart::TYPEID_BUBBLE:
+ case chart::TYPEID_HORBAR:
+ case chart::TYPEID_BAR:
+ {
+ pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0");
+ }
+ break;
+ case chart::TYPEID_LINE:
+ {
+ exportMarker(xOldPropSet);
+ break;
+ }
+ case chart::TYPEID_PIE:
+ case chart::TYPEID_DOUGHNUT:
+ {
+ if( xOldPropSet.is() && GetProperty( xOldPropSet, "SegmentOffset") )
+ {
+ sal_Int32 nOffset = 0;
+ mAny >>= nOffset;
+ pFS->singleElement( FSNS( XML_c, XML_explosion ),
+ XML_val, OString::number( nOffset ) );
+ }
+ break;
+ }
+ case chart::TYPEID_SCATTER:
+ {
+ exportMarker(xOldPropSet);
+ break;
+ }
+ case chart::TYPEID_RADARLINE:
+ {
+ exportMarker(xOldPropSet);
+ break;
+ }
+ }
+
+ // export data points
+ exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType );
+
+ DataLabelsRange aDLblsRange;
+ // export data labels
+ exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange);
+
+ exportTrendlines( rSeries );
+
+ if( eChartType != chart::TYPEID_PIE &&
+ eChartType != chart::TYPEID_RADARLINE )
+ {
+ //export error bars here
+ Reference< XPropertySet > xSeriesPropSet( xSource, uno::UNO_QUERY );
+ Reference< XPropertySet > xErrorBarYProps;
+ xSeriesPropSet->getPropertyValue("ErrorBarY") >>= xErrorBarYProps;
+ if(xErrorBarYProps.is())
+ exportErrorBar(xErrorBarYProps, true);
+ if (eChartType != chart::TYPEID_BAR &&
+ eChartType != chart::TYPEID_HORBAR)
+ {
+ Reference< XPropertySet > xErrorBarXProps;
+ xSeriesPropSet->getPropertyValue("ErrorBarX") >>= xErrorBarXProps;
+ if(xErrorBarXProps.is())
+ exportErrorBar(xErrorBarXProps, false);
+ }
+ }
+
+ // export categories
+ if( eChartType != chart::TYPEID_SCATTER && eChartType != chart::TYPEID_BUBBLE && mxCategoriesValues.is() )
+ exportSeriesCategory( mxCategoriesValues );
+
+ if( (eChartType == chart::TYPEID_SCATTER)
+ || (eChartType == chart::TYPEID_BUBBLE) )
+ {
+ // export xVal
+ Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, "values-x" ) );
+ if( xSequence.is() )
+ {
+ Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() );
+ if( xValues.is() )
+ exportSeriesValues( xValues, XML_xVal );
+ }
+ else if( mxCategoriesValues.is() )
+ exportSeriesCategory( mxCategoriesValues, XML_xVal );
+ }
+
+ if( eChartType == chart::TYPEID_BUBBLE )
+ {
+ // export yVal
+ Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, "values-y" ) );
+ if( xSequence.is() )
+ {
+ Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() );
+ if( xValues.is() )
+ exportSeriesValues( xValues, XML_yVal );
+ }
+ }
+
+ // export values
+ if( xValuesSeq.is() )
+ {
+ sal_Int32 nYValueType = XML_val;
+ if( eChartType == chart::TYPEID_SCATTER )
+ nYValueType = XML_yVal;
+ else if( eChartType == chart::TYPEID_BUBBLE )
+ nYValueType = XML_bubbleSize;
+ exportSeriesValues( xValuesSeq, nYValueType );
+ }
+
+ if( eChartType == chart::TYPEID_SCATTER
+ || eChartType == chart::TYPEID_LINE )
+ exportSmooth();
+
+ // tdf103988: "corrupted" files with Bubble chart opening in MSO
+ if( eChartType == chart::TYPEID_BUBBLE )
+ pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0");
+
+ if (!aDLblsRange.empty())
+ writeDataLabelsRange(pFS, GetFB(), aDLblsRange);
+
+ pFS->endElement( FSNS( XML_c, XML_ser ) );
+ }
+ }
+ }
+ }
+}
+
+void ChartExport::exportCandleStickSeries(
+ const Sequence< Reference< chart2::XDataSeries > > & aSeriesSeq,
+ bool& rPrimaryAxes)
+{
+ for( const Reference< chart2::XDataSeries >& xSeries : aSeriesSeq )
+ {
+ rPrimaryAxes = lcl_isSeriesAttachedToFirstAxis(xSeries);
+
+ Reference< chart2::data::XDataSource > xSource( xSeries, uno::UNO_QUERY );
+ if( xSource.is())
+ {
+ // export series in correct order (as we don't store roles)
+ // with japanese candlesticks: open, low, high, close
+ // otherwise: low, high, close
+ Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
+ xSource->getDataSequences());
+
+ const char* sSeries[] = {"values-first","values-max","values-min","values-last",nullptr};
+
+ for( sal_Int32 idx = 0; sSeries[idx] != nullptr ; idx++ )
+ {
+ Reference< chart2::data::XLabeledDataSequence > xLabeledSeq( lcl_getDataSequenceByRole( aSeqCnt, OUString::createFromAscii(sSeries[idx]) ) );
+ if( xLabeledSeq.is())
+ {
+ Reference< chart2::data::XDataSequence > xLabelSeq( xLabeledSeq->getLabel());
+ Reference< chart2::data::XDataSequence > xValueSeq( xLabeledSeq->getValues());
+ {
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_ser));
+
+ // TODO: idx and order
+ // idx attribute should start from 1 and not from 0.
+ pFS->singleElement( FSNS( XML_c, XML_idx ),
+ XML_val, OString::number(idx+1) );
+ pFS->singleElement( FSNS( XML_c, XML_order ),
+ XML_val, OString::number(idx+1) );
+
+ // export label
+ if( xLabelSeq.is() )
+ exportSeriesText( xLabelSeq );
+
+ // TODO:export shape properties
+
+ // export categories
+ if( mxCategoriesValues.is() )
+ exportSeriesCategory( mxCategoriesValues );
+
+ // export values
+ if( xValueSeq.is() )
+ exportSeriesValues( xValueSeq );
+
+ pFS->endElement( FSNS( XML_c, XML_ser ) );
+ }
+ }
+ }
+ }
+ }
+}
+
+void ChartExport::exportSeriesText( const Reference< chart2::data::XDataSequence > & xValueSeq )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_tx));
+
+ OUString aCellRange = xValueSeq->getSourceRangeRepresentation();
+ aCellRange = parseFormula( aCellRange );
+ pFS->startElement(FSNS(XML_c, XML_strRef));
+
+ pFS->startElement(FSNS(XML_c, XML_f));
+ pFS->writeEscaped( aCellRange );
+ pFS->endElement( FSNS( XML_c, XML_f ) );
+
+ OUString aLabelString = lcl_flattenStringSequence(lcl_getLabelSequence(xValueSeq));
+ pFS->startElement(FSNS(XML_c, XML_strCache));
+ pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, "1");
+ pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, "0");
+ pFS->startElement(FSNS(XML_c, XML_v));
+ pFS->writeEscaped( aLabelString );
+ pFS->endElement( FSNS( XML_c, XML_v ) );
+ pFS->endElement( FSNS( XML_c, XML_pt ) );
+ pFS->endElement( FSNS( XML_c, XML_strCache ) );
+ pFS->endElement( FSNS( XML_c, XML_strRef ) );
+ pFS->endElement( FSNS( XML_c, XML_tx ) );
+}
+
+void ChartExport::exportSeriesCategory( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, nValueType));
+
+ OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
+ const Sequence< Sequence< OUString >> aFinalSplitSource = (nValueType == XML_cat) ? getSplitCategoriesList(aCellRange) : Sequence< Sequence< OUString>>(0);
+ aCellRange = parseFormula( aCellRange );
+
+ if(aFinalSplitSource.getLength() > 1)
+ {
+ // export multi level category axis labels
+ pFS->startElement(FSNS(XML_c, XML_multiLvlStrRef));
+
+ pFS->startElement(FSNS(XML_c, XML_f));
+ pFS->writeEscaped(aCellRange);
+ pFS->endElement(FSNS(XML_c, XML_f));
+
+ pFS->startElement(FSNS(XML_c, XML_multiLvlStrCache));
+ pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(aFinalSplitSource[0].getLength()));
+ for(const auto& rSeq : aFinalSplitSource)
+ {
+ pFS->startElement(FSNS(XML_c, XML_lvl));
+ for(sal_Int32 j = 0; j < rSeq.getLength(); j++)
+ {
+ if(!rSeq[j].isEmpty())
+ {
+ pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(j));
+ pFS->startElement(FSNS(XML_c, XML_v));
+ pFS->writeEscaped(rSeq[j]);
+ pFS->endElement(FSNS(XML_c, XML_v));
+ pFS->endElement(FSNS(XML_c, XML_pt));
+ }
+ }
+ pFS->endElement(FSNS(XML_c, XML_lvl));
+ }
+
+ pFS->endElement(FSNS(XML_c, XML_multiLvlStrCache));
+ pFS->endElement(FSNS(XML_c, XML_multiLvlStrRef));
+ }
+ else
+ {
+ // export single category axis labels
+ bool bWriteDateCategories = mbHasDateCategories && (nValueType == XML_cat);
+ OUString aNumberFormatString;
+ if (bWriteDateCategories)
+ {
+ Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisXSupp.is())
+ {
+ Reference< XPropertySet > xAxisProp = xAxisXSupp->getXAxis();
+ if (GetProperty(xAxisProp, "NumberFormat"))
+ {
+ sal_Int32 nKey = 0;
+ mAny >>= nKey;
+ aNumberFormatString = getNumberFormatCode(nKey);
+ }
+ }
+ if (aNumberFormatString.isEmpty())
+ bWriteDateCategories = false;
+ }
+
+ pFS->startElement(FSNS(XML_c, bWriteDateCategories ? XML_numRef : XML_strRef));
+
+ pFS->startElement(FSNS(XML_c, XML_f));
+ pFS->writeEscaped(aCellRange);
+ pFS->endElement(FSNS(XML_c, XML_f));
+
+ ::std::vector< OUString > aCategories;
+ lcl_fillCategoriesIntoStringVector(xValueSeq, aCategories);
+ sal_Int32 ptCount = aCategories.size();
+ pFS->startElement(FSNS(XML_c, bWriteDateCategories ? XML_numCache : XML_strCache));
+ if (bWriteDateCategories)
+ {
+ pFS->startElement(FSNS(XML_c, XML_formatCode));
+ pFS->writeEscaped(aNumberFormatString);
+ pFS->endElement(FSNS(XML_c, XML_formatCode));
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
+ for (sal_Int32 i = 0; i < ptCount; i++)
+ {
+ pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
+ pFS->startElement(FSNS(XML_c, XML_v));
+ pFS->writeEscaped(aCategories[i]);
+ pFS->endElement(FSNS(XML_c, XML_v));
+ pFS->endElement(FSNS(XML_c, XML_pt));
+ }
+
+ pFS->endElement(FSNS(XML_c, bWriteDateCategories ? XML_numCache : XML_strCache));
+ pFS->endElement(FSNS(XML_c, bWriteDateCategories ? XML_numRef : XML_strRef));
+ }
+
+ pFS->endElement( FSNS( XML_c, nValueType ) );
+}
+
+void ChartExport::exportSeriesValues( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, nValueType));
+
+ OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
+ aCellRange = parseFormula( aCellRange );
+ // TODO: need to handle XML_multiLvlStrRef according to aCellRange
+ pFS->startElement(FSNS(XML_c, XML_numRef));
+
+ pFS->startElement(FSNS(XML_c, XML_f));
+ pFS->writeEscaped( aCellRange );
+ pFS->endElement( FSNS( XML_c, XML_f ) );
+
+ ::std::vector< double > aValues = lcl_getAllValuesFromSequence( xValueSeq );
+ sal_Int32 ptCount = aValues.size();
+ pFS->startElement(FSNS(XML_c, XML_numCache));
+ pFS->startElement(FSNS(XML_c, XML_formatCode));
+ // TODO: what format code?
+ pFS->writeEscaped( "General" );
+ pFS->endElement( FSNS( XML_c, XML_formatCode ) );
+ pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
+
+ for( sal_Int32 i = 0; i < ptCount; i++ )
+ {
+ if (!std::isnan(aValues[i]))
+ {
+ pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
+ pFS->startElement(FSNS(XML_c, XML_v));
+ pFS->write(aValues[i]);
+ pFS->endElement(FSNS(XML_c, XML_v));
+ pFS->endElement(FSNS(XML_c, XML_pt));
+ }
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_numCache ) );
+ pFS->endElement( FSNS( XML_c, XML_numRef ) );
+ pFS->endElement( FSNS( XML_c, nValueType ) );
+}
+
+void ChartExport::exportShapeProps( const Reference< XPropertySet >& xPropSet )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_spPr));
+
+ exportFill( xPropSet );
+ WriteOutline( xPropSet, getModel() );
+
+ pFS->endElement( FSNS( XML_c, XML_spPr ) );
+}
+
+void ChartExport::exportTextProps(const Reference<XPropertySet>& xPropSet)
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_txPr));
+
+ sal_Int32 nRotation = 0;
+ const char* textWordWrap = nullptr;
+
+ if (auto xServiceInfo = uno::Reference<lang::XServiceInfo>(xPropSet, uno::UNO_QUERY))
+ {
+ double fMultiplier = 0.0;
+ // We have at least two possible units of returned value: degrees (e.g., for data labels),
+ // and 100ths of degree (e.g., for axes labels). The latter is returned as an Any wrapping
+ // a sal_Int32 value (see WrappedTextRotationProperty::convertInnerToOuterValue), while
+ // the former is double. So we could test the contained type to decide which multiplier to
+ // use. But testing the service info should be more robust.
+ if (xServiceInfo->supportsService("com.sun.star.chart.ChartAxis"))
+ fMultiplier = -600.0;
+ else if (xServiceInfo->supportsService("com.sun.star.chart2.DataSeries") || xServiceInfo->supportsService("com.sun.star.chart2.DataPointProperties"))
+ {
+ fMultiplier = -60000.0;
+ bool bTextWordWrap = false;
+ if ((xPropSet->getPropertyValue("TextWordWrap") >>= bTextWordWrap) && bTextWordWrap)
+ textWordWrap = "square";
+ else
+ textWordWrap = "none";
+ }
+
+ if (fMultiplier)
+ {
+ double fTextRotation = 0.0;
+ uno::Any aAny = xPropSet->getPropertyValue("TextRotation");
+ if (aAny.hasValue() && (aAny >>= fTextRotation))
+ {
+ fTextRotation *= fMultiplier;
+ // The MS Office UI allows values only in range of [-90,90].
+ if (fTextRotation < -5400000.0 && fTextRotation > -16200000.0)
+ {
+ // Reflect the angle if the value is between 90° and 270°
+ fTextRotation += 10800000.0;
+ }
+ else if (fTextRotation <= -16200000.0)
+ {
+ fTextRotation += 21600000.0;
+ }
+ nRotation = std::round(fTextRotation);
+ }
+ }
+ }
+
+ if (nRotation)
+ pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_rot, OString::number(nRotation), XML_wrap, textWordWrap);
+ else
+ pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_wrap, textWordWrap);
+
+ pFS->singleElement(FSNS(XML_a, XML_lstStyle));
+
+ pFS->startElement(FSNS(XML_a, XML_p));
+ pFS->startElement(FSNS(XML_a, XML_pPr));
+
+ WriteRunProperties(xPropSet, false, XML_defRPr, true, o3tl::temporary(false),
+ o3tl::temporary(sal_Int32()));
+
+ pFS->endElement(FSNS(XML_a, XML_pPr));
+ pFS->endElement(FSNS(XML_a, XML_p));
+ pFS->endElement(FSNS(XML_c, XML_txPr));
+}
+
+void ChartExport::InitPlotArea( )
+{
+ Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY);
+
+ // Check for supported services and then the properties provided by this service.
+ Reference<lang::XServiceInfo> xServiceInfo (mxDiagram, uno::UNO_QUERY);
+ if (xServiceInfo.is())
+ {
+ if (xServiceInfo->supportsService("com.sun.star.chart.ChartAxisZSupplier"))
+ {
+ xDiagramProperties->getPropertyValue("HasZAxis") >>= mbHasZAxis;
+ }
+ }
+
+ xDiagramProperties->getPropertyValue("Dim3D") >>= mbIs3DChart;
+
+ if( mbHasCategoryLabels && mxNewDiagram.is())
+ {
+ Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( mxNewDiagram, mbHasDateCategories ) );
+ if( xCategories.is() )
+ {
+ mxCategoriesValues.set( xCategories->getValues() );
+ }
+ }
+}
+
+void ChartExport::exportAxes( )
+{
+ sal_Int32 nSize = maAxes.size();
+ // let's export the axis types in the right order
+ for ( sal_Int32 nSortIdx = AXIS_PRIMARY_X; nSortIdx <= AXIS_SECONDARY_Y; nSortIdx++ )
+ {
+ for ( sal_Int32 nIdx = 0; nIdx < nSize; nIdx++ )
+ {
+ if (nSortIdx == maAxes[nIdx].nAxisType)
+ exportAxis( maAxes[nIdx] );
+ }
+ }
+}
+
+namespace {
+
+sal_Int32 getXAxisTypeByChartType(sal_Int32 eChartType)
+{
+ if( (eChartType == chart::TYPEID_SCATTER)
+ || (eChartType == chart::TYPEID_BUBBLE) )
+ return XML_valAx;
+ else if( eChartType == chart::TYPEID_STOCK )
+ return XML_dateAx;
+
+ return XML_catAx;
+}
+
+sal_Int32 getRealXAxisType(sal_Int32 nAxisType)
+{
+ if( nAxisType == chart2::AxisType::CATEGORY )
+ return XML_catAx;
+ else if( nAxisType == chart2::AxisType::DATE )
+ return XML_dateAx;
+ else if( nAxisType == chart2::AxisType::SERIES )
+ return XML_serAx;
+
+ return XML_valAx;
+}
+
+}
+
+void ChartExport::exportAxis(const AxisIdPair& rAxisIdPair)
+{
+ // get some properties from document first
+ bool bHasXAxisTitle = false,
+ bHasYAxisTitle = false,
+ bHasZAxisTitle = false,
+ bHasSecondaryXAxisTitle = false,
+ bHasSecondaryYAxisTitle = false;
+ bool bHasXAxisMajorGrid = false,
+ bHasXAxisMinorGrid = false,
+ bHasYAxisMajorGrid = false,
+ bHasYAxisMinorGrid = false,
+ bHasZAxisMajorGrid = false,
+ bHasZAxisMinorGrid = false;
+
+ Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY);
+
+ xDiagramProperties->getPropertyValue("HasXAxisTitle") >>= bHasXAxisTitle;
+ xDiagramProperties->getPropertyValue("HasYAxisTitle") >>= bHasYAxisTitle;
+ xDiagramProperties->getPropertyValue("HasZAxisTitle") >>= bHasZAxisTitle;
+ xDiagramProperties->getPropertyValue("HasSecondaryXAxisTitle") >>= bHasSecondaryXAxisTitle;
+ xDiagramProperties->getPropertyValue("HasSecondaryYAxisTitle") >>= bHasSecondaryYAxisTitle;
+
+ xDiagramProperties->getPropertyValue("HasXAxisGrid") >>= bHasXAxisMajorGrid;
+ xDiagramProperties->getPropertyValue("HasYAxisGrid") >>= bHasYAxisMajorGrid;
+ xDiagramProperties->getPropertyValue("HasZAxisGrid") >>= bHasZAxisMajorGrid;
+
+ xDiagramProperties->getPropertyValue("HasXAxisHelpGrid") >>= bHasXAxisMinorGrid;
+ xDiagramProperties->getPropertyValue("HasYAxisHelpGrid") >>= bHasYAxisMinorGrid;
+ xDiagramProperties->getPropertyValue("HasZAxisHelpGrid") >>= bHasZAxisMinorGrid;
+
+ Reference< XPropertySet > xAxisProp;
+ Reference< drawing::XShape > xAxisTitle;
+ Reference< beans::XPropertySet > xMajorGrid;
+ Reference< beans::XPropertySet > xMinorGrid;
+ sal_Int32 nAxisType = XML_catAx;
+ const char* sAxPos = nullptr;
+
+ switch( rAxisIdPair.nAxisType )
+ {
+ case AXIS_PRIMARY_X:
+ {
+ Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisXSupp.is())
+ xAxisProp = xAxisXSupp->getXAxis();
+ if( bHasXAxisTitle )
+ xAxisTitle = xAxisXSupp->getXAxisTitle();
+ if( bHasXAxisMajorGrid )
+ xMajorGrid = xAxisXSupp->getXMainGrid();
+ if( bHasXAxisMinorGrid )
+ xMinorGrid = xAxisXSupp->getXHelpGrid();
+
+ nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 0);
+ if( nAxisType != -1 )
+ nAxisType = getRealXAxisType(nAxisType);
+ else
+ nAxisType = getXAxisTypeByChartType( getChartType() );
+ // FIXME: axPos, need to check axis direction
+ sAxPos = "b";
+ break;
+ }
+ case AXIS_PRIMARY_Y:
+ {
+ Reference< css::chart::XAxisYSupplier > xAxisYSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisYSupp.is())
+ xAxisProp = xAxisYSupp->getYAxis();
+ if( bHasYAxisTitle )
+ xAxisTitle = xAxisYSupp->getYAxisTitle();
+ if( bHasYAxisMajorGrid )
+ xMajorGrid = xAxisYSupp->getYMainGrid();
+ if( bHasYAxisMinorGrid )
+ xMinorGrid = xAxisYSupp->getYHelpGrid();
+
+ nAxisType = XML_valAx;
+ // FIXME: axPos, need to check axis direction
+ sAxPos = "l";
+ break;
+ }
+ case AXIS_PRIMARY_Z:
+ {
+ Reference< css::chart::XAxisZSupplier > xAxisZSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisZSupp.is())
+ xAxisProp = xAxisZSupp->getZAxis();
+ if( bHasZAxisTitle )
+ xAxisTitle = xAxisZSupp->getZAxisTitle();
+ if( bHasZAxisMajorGrid )
+ xMajorGrid = xAxisZSupp->getZMainGrid();
+ if( bHasZAxisMinorGrid )
+ xMinorGrid = xAxisZSupp->getZHelpGrid();
+
+ sal_Int32 eChartType = getChartType( );
+ if( (eChartType == chart::TYPEID_SCATTER)
+ || (eChartType == chart::TYPEID_BUBBLE) )
+ nAxisType = XML_valAx;
+ else if( eChartType == chart::TYPEID_STOCK )
+ nAxisType = XML_dateAx;
+ else if( eChartType == chart::TYPEID_BAR || eChartType == chart::TYPEID_AREA )
+ nAxisType = XML_serAx;
+ // FIXME: axPos, need to check axis direction
+ sAxPos = "b";
+ break;
+ }
+ case AXIS_SECONDARY_X:
+ {
+ Reference< css::chart::XTwoAxisXSupplier > xAxisTwoXSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisTwoXSupp.is())
+ xAxisProp = xAxisTwoXSupp->getSecondaryXAxis();
+ if( bHasSecondaryXAxisTitle )
+ {
+ Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY );
+ xAxisTitle = xAxisSupp->getSecondXAxisTitle();
+ }
+
+ nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 1);
+ if( nAxisType != -1 )
+ nAxisType = getRealXAxisType(nAxisType);
+ else
+ nAxisType = getXAxisTypeByChartType( getChartType() );
+ // FIXME: axPos, need to check axis direction
+ sAxPos = "t";
+ break;
+ }
+ case AXIS_SECONDARY_Y:
+ {
+ Reference< css::chart::XTwoAxisYSupplier > xAxisTwoYSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisTwoYSupp.is())
+ xAxisProp = xAxisTwoYSupp->getSecondaryYAxis();
+ if( bHasSecondaryYAxisTitle )
+ {
+ Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY );
+ xAxisTitle = xAxisSupp->getSecondYAxisTitle();
+ }
+
+ nAxisType = XML_valAx;
+ // FIXME: axPos, need to check axis direction
+ sAxPos = "r";
+ break;
+ }
+ }
+
+ _exportAxis(xAxisProp, xAxisTitle, xMajorGrid, xMinorGrid, nAxisType, sAxPos, rAxisIdPair);
+}
+
+void ChartExport::_exportAxis(
+ const Reference< XPropertySet >& xAxisProp,
+ const Reference< drawing::XShape >& xAxisTitle,
+ const Reference< XPropertySet >& xMajorGrid,
+ const Reference< XPropertySet >& xMinorGrid,
+ sal_Int32 nAxisType,
+ const char* sAxisPos,
+ const AxisIdPair& rAxisIdPair )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, nAxisType));
+ pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(rAxisIdPair.nAxisId));
+
+ pFS->startElement(FSNS(XML_c, XML_scaling));
+
+ // logBase, min, max
+ if(GetProperty( xAxisProp, "Logarithmic" ) )
+ {
+ bool bLogarithmic = false;
+ mAny >>= bLogarithmic;
+ if( bLogarithmic )
+ {
+ // default value is 10?
+ pFS->singleElement(FSNS(XML_c, XML_logBase), XML_val, OString::number(10));
+ }
+ }
+
+ // orientation: minMax, maxMin
+ bool bReverseDirection = false;
+ if(GetProperty( xAxisProp, "ReverseDirection" ) )
+ mAny >>= bReverseDirection;
+
+ const char* orientation = bReverseDirection ? "maxMin":"minMax";
+ pFS->singleElement(FSNS(XML_c, XML_orientation), XML_val, orientation);
+
+ bool bAutoMax = false;
+ if(GetProperty( xAxisProp, "AutoMax" ) )
+ mAny >>= bAutoMax;
+
+ if( !bAutoMax && (GetProperty( xAxisProp, "Max" ) ) )
+ {
+ double dMax = 0;
+ mAny >>= dMax;
+ pFS->singleElement(FSNS(XML_c, XML_max), XML_val, OString::number(dMax));
+ }
+
+ bool bAutoMin = false;
+ if(GetProperty( xAxisProp, "AutoMin" ) )
+ mAny >>= bAutoMin;
+
+ if( !bAutoMin && (GetProperty( xAxisProp, "Min" ) ) )
+ {
+ double dMin = 0;
+ mAny >>= dMin;
+ pFS->singleElement(FSNS(XML_c, XML_min), XML_val, OString::number(dMin));
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_scaling ) );
+
+ bool bVisible = true;
+ if( xAxisProp.is() )
+ {
+ xAxisProp->getPropertyValue("Visible") >>= bVisible;
+ }
+
+ // only export each axis only once non-deleted
+ bool bDeleted = maExportedAxis.find(rAxisIdPair.nAxisType) != maExportedAxis.end();
+
+ if (!bDeleted)
+ maExportedAxis.insert(rAxisIdPair.nAxisType);
+
+ pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, !bDeleted && bVisible ? "0" : "1");
+
+ // FIXME: axPos, need to check the property "ReverseDirection"
+ pFS->singleElement(FSNS(XML_c, XML_axPos), XML_val, sAxisPos);
+ // major grid line
+ if( xMajorGrid.is())
+ {
+ pFS->startElement(FSNS(XML_c, XML_majorGridlines));
+ exportShapeProps( xMajorGrid );
+ pFS->endElement( FSNS( XML_c, XML_majorGridlines ) );
+ }
+
+ // minor grid line
+ if( xMinorGrid.is())
+ {
+ pFS->startElement(FSNS(XML_c, XML_minorGridlines));
+ exportShapeProps( xMinorGrid );
+ pFS->endElement( FSNS( XML_c, XML_minorGridlines ) );
+ }
+
+ // title
+ if( xAxisTitle.is() )
+ exportTitle( xAxisTitle );
+
+ bool bLinkedNumFmt = true;
+ if (GetProperty(xAxisProp, "LinkNumberFormatToSource"))
+ mAny >>= bLinkedNumFmt;
+
+ OUString aNumberFormatString("General");
+ if (GetProperty(xAxisProp, "NumberFormat"))
+ {
+ sal_Int32 nKey = 0;
+ mAny >>= nKey;
+ aNumberFormatString = getNumberFormatCode(nKey);
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_numFmt),
+ XML_formatCode, aNumberFormatString,
+ XML_sourceLinked, bLinkedNumFmt ? "1" : "0");
+
+ // majorTickMark
+ sal_Int32 nValue = 0;
+ if(GetProperty( xAxisProp, "Marks" ) )
+ {
+ mAny >>= nValue;
+ bool bInner = nValue & css::chart::ChartAxisMarks::INNER;
+ bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER;
+ const char* majorTickMark = nullptr;
+ if( bInner && bOuter )
+ majorTickMark = "cross";
+ else if( bInner )
+ majorTickMark = "in";
+ else if( bOuter )
+ majorTickMark = "out";
+ else
+ majorTickMark = "none";
+ pFS->singleElement(FSNS(XML_c, XML_majorTickMark), XML_val, majorTickMark);
+ }
+ // minorTickMark
+ if(GetProperty( xAxisProp, "HelpMarks" ) )
+ {
+ mAny >>= nValue;
+ bool bInner = nValue & css::chart::ChartAxisMarks::INNER;
+ bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER;
+ const char* minorTickMark = nullptr;
+ if( bInner && bOuter )
+ minorTickMark = "cross";
+ else if( bInner )
+ minorTickMark = "in";
+ else if( bOuter )
+ minorTickMark = "out";
+ else
+ minorTickMark = "none";
+ pFS->singleElement(FSNS(XML_c, XML_minorTickMark), XML_val, minorTickMark);
+ }
+ // tickLblPos
+ const char* sTickLblPos = nullptr;
+ bool bDisplayLabel = true;
+ if(GetProperty( xAxisProp, "DisplayLabels" ) )
+ mAny >>= bDisplayLabel;
+ if( bDisplayLabel && (GetProperty( xAxisProp, "LabelPosition" ) ) )
+ {
+ css::chart::ChartAxisLabelPosition eLabelPosition = css::chart::ChartAxisLabelPosition_NEAR_AXIS;
+ mAny >>= eLabelPosition;
+ switch( eLabelPosition )
+ {
+ case css::chart::ChartAxisLabelPosition_NEAR_AXIS:
+ case css::chart::ChartAxisLabelPosition_NEAR_AXIS_OTHER_SIDE:
+ sTickLblPos = "nextTo";
+ break;
+ case css::chart::ChartAxisLabelPosition_OUTSIDE_START:
+ sTickLblPos = "low";
+ break;
+ case css::chart::ChartAxisLabelPosition_OUTSIDE_END:
+ sTickLblPos = "high";
+ break;
+ default:
+ sTickLblPos = "nextTo";
+ break;
+ }
+ }
+ else
+ {
+ sTickLblPos = "none";
+ }
+ pFS->singleElement(FSNS(XML_c, XML_tickLblPos), XML_val, sTickLblPos);
+
+ // shape properties
+ exportShapeProps( xAxisProp );
+
+ exportTextProps(xAxisProp);
+
+ pFS->singleElement(FSNS(XML_c, XML_crossAx), XML_val, OString::number(rAxisIdPair.nCrossAx));
+
+ // crosses & crossesAt
+ bool bCrossesValue = false;
+ const char* sCrosses = nullptr;
+ // do not export the CrossoverPosition/CrossoverValue, if the axis is deleted and not visible
+ if( GetProperty( xAxisProp, "CrossoverPosition" ) && !bDeleted && bVisible )
+ {
+ css::chart::ChartAxisPosition ePosition( css::chart::ChartAxisPosition_ZERO );
+ mAny >>= ePosition;
+ switch( ePosition )
+ {
+ case css::chart::ChartAxisPosition_START:
+ sCrosses = "min";
+ break;
+ case css::chart::ChartAxisPosition_END:
+ sCrosses = "max";
+ break;
+ case css::chart::ChartAxisPosition_ZERO:
+ sCrosses = "autoZero";
+ break;
+ default:
+ bCrossesValue = true;
+ break;
+ }
+ }
+
+ if( bCrossesValue && GetProperty( xAxisProp, "CrossoverValue" ) )
+ {
+ double dValue = 0;
+ mAny >>= dValue;
+ pFS->singleElement(FSNS(XML_c, XML_crossesAt), XML_val, OString::number(dValue));
+ }
+ else
+ {
+ if(sCrosses)
+ {
+ pFS->singleElement(FSNS(XML_c, XML_crosses), XML_val, sCrosses);
+ }
+ }
+
+ if( ( nAxisType == XML_catAx )
+ || ( nAxisType == XML_dateAx ) )
+ {
+ // FIXME: seems not support? use default value,
+ const char* const isAuto = "1";
+ pFS->singleElement(FSNS(XML_c, XML_auto), XML_val, isAuto);
+
+ if( nAxisType == XML_catAx )
+ {
+ // FIXME: seems not support? lblAlgn
+ const char* const sLblAlgn = "ctr";
+ pFS->singleElement(FSNS(XML_c, XML_lblAlgn), XML_val, sLblAlgn);
+ }
+
+ // FIXME: seems not support? lblOffset
+ pFS->singleElement(FSNS(XML_c, XML_lblOffset), XML_val, OString::number(100));
+
+ // export baseTimeUnit, majorTimeUnit, minorTimeUnit of Date axis
+ if( nAxisType == XML_dateAx )
+ {
+ sal_Int32 nAxisIndex = -1;
+ if( rAxisIdPair.nAxisType == AXIS_PRIMARY_X )
+ nAxisIndex = 0;
+ else if( rAxisIdPair.nAxisType == AXIS_SECONDARY_X )
+ nAxisIndex = 1;
+
+ cssc::TimeIncrement aTimeIncrement = lcl_getDateTimeIncrement( mxNewDiagram, nAxisIndex );
+ sal_Int32 nTimeResolution = css::chart::TimeUnit::DAY;
+ if( aTimeIncrement.TimeResolution >>= nTimeResolution )
+ pFS->singleElement(FSNS(XML_c, XML_baseTimeUnit), XML_val, lclGetTimeUnitToken(nTimeResolution));
+
+ cssc::TimeInterval aInterval;
+ if( aTimeIncrement.MajorTimeInterval >>= aInterval )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(aInterval.Number));
+ pFS->singleElement(FSNS(XML_c, XML_majorTimeUnit), XML_val, lclGetTimeUnitToken(aInterval.TimeUnit));
+ }
+ if( aTimeIncrement.MinorTimeInterval >>= aInterval )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_minorUnit), XML_val, OString::number(aInterval.Number));
+ pFS->singleElement(FSNS(XML_c, XML_minorTimeUnit), XML_val, lclGetTimeUnitToken(aInterval.TimeUnit));
+ }
+ }
+
+ // FIXME: seems not support? noMultiLvlLbl
+ pFS->singleElement(FSNS(XML_c, XML_noMultiLvlLbl), XML_val, OString::number(0));
+ }
+
+ // crossBetween
+ if( nAxisType == XML_valAx )
+ {
+ if( lcl_isCategoryAxisShifted( mxNewDiagram ))
+ pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "between");
+ else
+ pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "midCat");
+ }
+
+ // majorUnit
+ bool bAutoStepMain = false;
+ if(GetProperty( xAxisProp, "AutoStepMain" ) )
+ mAny >>= bAutoStepMain;
+
+ if( !bAutoStepMain && (GetProperty( xAxisProp, "StepMain" ) ) )
+ {
+ double dMajorUnit = 0;
+ mAny >>= dMajorUnit;
+ pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(dMajorUnit));
+ }
+ // minorUnit
+ bool bAutoStepHelp = false;
+ if(GetProperty( xAxisProp, "AutoStepHelp" ) )
+ mAny >>= bAutoStepHelp;
+
+ if( !bAutoStepHelp && (GetProperty( xAxisProp, "StepHelp" ) ) )
+ {
+ double dMinorUnit = 0;
+ mAny >>= dMinorUnit;
+ if( GetProperty( xAxisProp, "StepHelpCount" ) )
+ {
+ sal_Int32 dMinorUnitCount = 0;
+ mAny >>= dMinorUnitCount;
+ // tdf#114168 Don't save minor unit if number of step help count is 5 (which is default for MS Excel),
+ // to allow proper .xlsx import. If minorUnit is set and majorUnit not, then it is impossible
+ // to calculate StepHelpCount.
+ if( dMinorUnitCount != 5 )
+ {
+ pFS->singleElement( FSNS( XML_c, XML_minorUnit ),
+ XML_val, OString::number( dMinorUnit ) );
+ }
+ }
+ }
+
+ if( nAxisType == XML_valAx && GetProperty( xAxisProp, "DisplayUnits" ) )
+ {
+ bool bDisplayUnits = false;
+ mAny >>= bDisplayUnits;
+ if(bDisplayUnits)
+ {
+ if(GetProperty( xAxisProp, "BuiltInUnit" ))
+ {
+ OUString aVal;
+ mAny >>= aVal;
+ if(!aVal.isEmpty())
+ {
+ pFS->startElement(FSNS(XML_c, XML_dispUnits));
+
+ pFS->singleElement(FSNS(XML_c, XML_builtInUnit), XML_val, aVal);
+
+ pFS->singleElement(FSNS( XML_c, XML_dispUnitsLbl ));
+ pFS->endElement( FSNS( XML_c, XML_dispUnits ) );
+ }
+ }
+ }
+ }
+
+ pFS->endElement( FSNS( XML_c, nAxisType ) );
+}
+
+namespace {
+
+struct LabelPlacementParam
+{
+ bool mbExport;
+ sal_Int32 meDefault;
+
+ std::unordered_set<sal_Int32> maAllowedValues;
+
+ LabelPlacementParam(bool bExport, sal_Int32 nDefault) :
+ mbExport(bExport),
+ meDefault(nDefault),
+ maAllowedValues(
+ {
+ css::chart::DataLabelPlacement::OUTSIDE,
+ css::chart::DataLabelPlacement::INSIDE,
+ css::chart::DataLabelPlacement::CENTER,
+ css::chart::DataLabelPlacement::NEAR_ORIGIN,
+ css::chart::DataLabelPlacement::TOP,
+ css::chart::DataLabelPlacement::BOTTOM,
+ css::chart::DataLabelPlacement::LEFT,
+ css::chart::DataLabelPlacement::RIGHT,
+ css::chart::DataLabelPlacement::AVOID_OVERLAP
+ }
+ )
+ {}
+};
+
+const char* toOOXMLPlacement( sal_Int32 nPlacement )
+{
+ switch (nPlacement)
+ {
+ case css::chart::DataLabelPlacement::OUTSIDE: return "outEnd";
+ case css::chart::DataLabelPlacement::INSIDE: return "inEnd";
+ case css::chart::DataLabelPlacement::CENTER: return "ctr";
+ case css::chart::DataLabelPlacement::NEAR_ORIGIN: return "inBase";
+ case css::chart::DataLabelPlacement::TOP: return "t";
+ case css::chart::DataLabelPlacement::BOTTOM: return "b";
+ case css::chart::DataLabelPlacement::LEFT: return "l";
+ case css::chart::DataLabelPlacement::RIGHT: return "r";
+ case css::chart::DataLabelPlacement::CUSTOM:
+ case css::chart::DataLabelPlacement::AVOID_OVERLAP: return "bestFit";
+ default:
+ ;
+ }
+
+ return "outEnd";
+}
+
+OUString getFieldTypeString( const chart2::DataPointCustomLabelFieldType aType )
+{
+ switch (aType)
+ {
+ case chart2::DataPointCustomLabelFieldType_CATEGORYNAME:
+ return "CATEGORYNAME";
+
+ case chart2::DataPointCustomLabelFieldType_SERIESNAME:
+ return "SERIESNAME";
+
+ case chart2::DataPointCustomLabelFieldType_VALUE:
+ return "VALUE";
+
+ case chart2::DataPointCustomLabelFieldType_CELLREF:
+ return "CELLREF";
+
+ case chart2::DataPointCustomLabelFieldType_CELLRANGE:
+ return "CELLRANGE";
+
+ default:
+ break;
+ }
+ return OUString();
+}
+
+void writeRunProperties( ChartExport* pChartExport, Reference<XPropertySet> const & xPropertySet )
+{
+ bool bDummy = false;
+ sal_Int32 nDummy;
+ pChartExport->WriteRunProperties(xPropertySet, false, XML_rPr, true, bDummy, nDummy);
+}
+
+void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport,
+ const Sequence<Reference<chart2::XDataPointCustomLabelField>>& rCustomLabelFields,
+ sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange )
+{
+ pFS->startElement(FSNS(XML_c, XML_tx));
+ pFS->startElement(FSNS(XML_c, XML_rich));
+
+ // TODO: body properties?
+ pFS->singleElement(FSNS(XML_a, XML_bodyPr));
+
+ OUString sFieldType;
+ OUString sContent;
+ pFS->startElement(FSNS(XML_a, XML_p));
+
+ for (auto& rField : rCustomLabelFields)
+ {
+ Reference<XPropertySet> xPropertySet(rField, UNO_QUERY);
+ chart2::DataPointCustomLabelFieldType aType = rField->getFieldType();
+ sFieldType.clear();
+ sContent.clear();
+ bool bNewParagraph = false;
+
+ if (aType == chart2::DataPointCustomLabelFieldType_CELLRANGE &&
+ rField->getDataLabelsRange())
+ {
+ if (rDLblsRange.getRange().isEmpty())
+ rDLblsRange.setRange(rField->getCellRange());
+
+ if (!rDLblsRange.hasLabel(nLabelIndex))
+ rDLblsRange.setLabel(nLabelIndex, rField->getString());
+
+ sContent = "[CELLRANGE]";
+ }
+ else
+ {
+ sContent = rField->getString();
+ }
+
+ if (aType == chart2::DataPointCustomLabelFieldType_NEWLINE)
+ bNewParagraph = true;
+ else if (aType != chart2::DataPointCustomLabelFieldType_TEXT)
+ sFieldType = getFieldTypeString(aType);
+
+ if (bNewParagraph)
+ {
+ pFS->endElement(FSNS(XML_a, XML_p));
+ pFS->startElement(FSNS(XML_a, XML_p));
+ continue;
+ }
+
+ if (sFieldType.isEmpty())
+ {
+ // Normal text run
+ pFS->startElement(FSNS(XML_a, XML_r));
+ writeRunProperties(pChartExport, xPropertySet);
+
+ pFS->startElement(FSNS(XML_a, XML_t));
+ pFS->writeEscaped(sContent);
+ pFS->endElement(FSNS(XML_a, XML_t));
+
+ pFS->endElement(FSNS(XML_a, XML_r));
+ }
+ else
+ {
+ // Field
+ pFS->startElement(FSNS(XML_a, XML_fld), XML_id, rField->getGuid(), XML_type,
+ sFieldType);
+ writeRunProperties(pChartExport, xPropertySet);
+
+ pFS->startElement(FSNS(XML_a, XML_t));
+ pFS->writeEscaped(sContent);
+ pFS->endElement(FSNS(XML_a, XML_t));
+
+ pFS->endElement(FSNS(XML_a, XML_fld));
+ }
+ }
+
+ pFS->endElement(FSNS(XML_a, XML_p));
+ pFS->endElement(FSNS(XML_c, XML_rich));
+ pFS->endElement(FSNS(XML_c, XML_tx));
+}
+
+void writeLabelProperties( const FSHelperPtr& pFS, ChartExport* pChartExport,
+ const uno::Reference<beans::XPropertySet>& xPropSet, const LabelPlacementParam& rLabelParam,
+ sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange )
+{
+ if (!xPropSet.is())
+ return;
+
+ chart2::DataPointLabel aLabel;
+ Sequence<Reference<chart2::XDataPointCustomLabelField>> aCustomLabelFields;
+ sal_Int32 nLabelBorderWidth = 0;
+ sal_Int32 nLabelBorderColor = 0x00FFFFFF;
+ sal_Int32 nLabelFillColor = -1;
+
+ xPropSet->getPropertyValue("Label") >>= aLabel;
+ xPropSet->getPropertyValue("CustomLabelFields") >>= aCustomLabelFields;
+ xPropSet->getPropertyValue("LabelBorderWidth") >>= nLabelBorderWidth;
+ xPropSet->getPropertyValue("LabelBorderColor") >>= nLabelBorderColor;
+ xPropSet->getPropertyValue("LabelFillColor") >>= nLabelFillColor;
+
+ if (nLabelBorderWidth > 0 || nLabelFillColor != -1)
+ {
+ pFS->startElement(FSNS(XML_c, XML_spPr));
+
+ if (nLabelFillColor != -1)
+ {
+ pFS->startElement(FSNS(XML_a, XML_solidFill));
+
+ OString aStr = OString::number(nLabelFillColor, 16).toAsciiUpperCase();
+ pFS->singleElement(FSNS(XML_a, XML_srgbClr), XML_val, aStr);
+
+ pFS->endElement(FSNS(XML_a, XML_solidFill));
+ }
+
+ if (nLabelBorderWidth > 0)
+ {
+ pFS->startElement(FSNS(XML_a, XML_ln), XML_w,
+ OString::number(convertHmmToEmu(nLabelBorderWidth)));
+
+ if (nLabelBorderColor != -1)
+ {
+ pFS->startElement(FSNS(XML_a, XML_solidFill));
+
+ OString aStr = OString::number(nLabelBorderColor, 16).toAsciiUpperCase();
+ pFS->singleElement(FSNS(XML_a, XML_srgbClr), XML_val, aStr);
+
+ pFS->endElement(FSNS(XML_a, XML_solidFill));
+ }
+
+ pFS->endElement(FSNS(XML_a, XML_ln));
+ }
+
+ pFS->endElement(FSNS(XML_c, XML_spPr));
+ }
+
+ pChartExport->exportTextProps(xPropSet);
+
+ if (aCustomLabelFields.hasElements())
+ writeCustomLabel(pFS, pChartExport, aCustomLabelFields, nLabelIndex, rDLblsRange);
+
+ if (rLabelParam.mbExport)
+ {
+ sal_Int32 nLabelPlacement = rLabelParam.meDefault;
+ if (xPropSet->getPropertyValue("LabelPlacement") >>= nLabelPlacement)
+ {
+ if (!rLabelParam.maAllowedValues.count(nLabelPlacement))
+ nLabelPlacement = rLabelParam.meDefault;
+ pFS->singleElement(FSNS(XML_c, XML_dLblPos), XML_val, toOOXMLPlacement(nLabelPlacement));
+ }
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_showLegendKey), XML_val, ToPsz10(aLabel.ShowLegendSymbol));
+ pFS->singleElement(FSNS(XML_c, XML_showVal), XML_val, ToPsz10(aLabel.ShowNumber));
+ pFS->singleElement(FSNS(XML_c, XML_showCatName), XML_val, ToPsz10(aLabel.ShowCategoryName));
+ pFS->singleElement(FSNS(XML_c, XML_showSerName), XML_val, ToPsz10(aLabel.ShowSeriesName));
+ pFS->singleElement(FSNS(XML_c, XML_showPercent), XML_val, ToPsz10(aLabel.ShowNumberInPercent));
+
+ // Export the text "separator" if exists
+ uno::Any aAny = xPropSet->getPropertyValue("LabelSeparator");
+ if( aAny.hasValue() )
+ {
+ OUString nLabelSeparator;
+ aAny >>= nLabelSeparator;
+ pFS->startElement(FSNS(XML_c, XML_separator));
+ pFS->writeEscaped( nLabelSeparator );
+ pFS->endElement( FSNS( XML_c, XML_separator ) );
+ }
+
+ if (rDLblsRange.hasLabel(nLabelIndex))
+ {
+ pFS->startElement(FSNS(XML_c, XML_extLst));
+ pFS->startElement(FSNS(XML_c, XML_ext), XML_uri,
+ "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15),
+ pChartExport->GetFB()->getNamespaceURL(OOX_NS(c15)));
+
+ pFS->singleElement(FSNS(XML_c15, XML_showDataLabelsRange), XML_val, "1");
+
+ pFS->endElement(FSNS(XML_c, XML_ext));
+ pFS->endElement(FSNS(XML_c, XML_extLst));
+ }
+}
+
+}
+
+void ChartExport::exportDataLabels(
+ const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 nSeriesLength, sal_Int32 eChartType,
+ DataLabelsRange& rDLblsRange)
+{
+ if (!xSeries.is() || nSeriesLength <= 0)
+ return;
+
+ uno::Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
+ if (!xPropSet.is())
+ return;
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_dLbls));
+
+ bool bLinkedNumFmt = true;
+ if (GetProperty(xPropSet, "LinkNumberFormatToSource"))
+ mAny >>= bLinkedNumFmt;
+
+ chart2::DataPointLabel aLabel;
+ bool bLabelIsNumberFormat = true;
+ if( xPropSet->getPropertyValue("Label") >>= aLabel )
+ bLabelIsNumberFormat = aLabel.ShowNumber;
+
+ if (GetProperty(xPropSet, bLabelIsNumberFormat ? OUString("NumberFormat") : OUString("PercentageNumberFormat")))
+ {
+ sal_Int32 nKey = 0;
+ mAny >>= nKey;
+
+ OUString aNumberFormatString = getNumberFormatCode(nKey);
+
+ pFS->singleElement(FSNS(XML_c, XML_numFmt),
+ XML_formatCode, aNumberFormatString,
+ XML_sourceLinked, ToPsz10(bLinkedNumFmt));
+ }
+
+ uno::Sequence<sal_Int32> aAttrLabelIndices;
+ xPropSet->getPropertyValue("AttributedDataPoints") >>= aAttrLabelIndices;
+
+ // We must not export label placement property when the chart type doesn't
+ // support this option in MS Office, else MS Office would think the file
+ // is corrupt & refuse to open it.
+
+ const chart::TypeGroupInfo& rInfo = chart::GetTypeGroupInfo(static_cast<chart::TypeId>(eChartType));
+ LabelPlacementParam aParam(!mbIs3DChart, rInfo.mnDefLabelPos);
+ switch (eChartType) // diagram chart type
+ {
+ case chart::TYPEID_PIE:
+ if(getChartType() == chart::TYPEID_DOUGHNUT)
+ aParam.mbExport = false;
+ else
+ // All pie charts support label placement.
+ aParam.mbExport = true;
+ break;
+ case chart::TYPEID_AREA:
+ case chart::TYPEID_RADARLINE:
+ case chart::TYPEID_RADARAREA:
+ // These chart types don't support label placement.
+ aParam.mbExport = false;
+ break;
+ case chart::TYPEID_BAR:
+ if (mbStacked || mbPercent)
+ {
+ aParam.maAllowedValues.clear();
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
+ aParam.meDefault = css::chart::DataLabelPlacement::CENTER;
+ }
+ else // Clustered bar chart
+ {
+ aParam.maAllowedValues.clear();
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::OUTSIDE);
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
+ aParam.meDefault = css::chart::DataLabelPlacement::OUTSIDE;
+ }
+ break;
+ default:
+ ;
+ }
+
+ for (const sal_Int32 nIdx : std::as_const(aAttrLabelIndices))
+ {
+ uno::Reference<beans::XPropertySet> xLabelPropSet = xSeries->getDataPointByIndex(nIdx);
+
+ if (!xLabelPropSet.is())
+ continue;
+
+ pFS->startElement(FSNS(XML_c, XML_dLbl));
+ pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nIdx));
+
+ // export custom position of data label
+ if( eChartType != chart::TYPEID_PIE )
+ {
+ chart2::RelativePosition aCustomLabelPosition;
+ if( xLabelPropSet->getPropertyValue("CustomLabelPosition") >>= aCustomLabelPosition )
+ {
+ pFS->startElement(FSNS(XML_c, XML_layout));
+ pFS->startElement(FSNS(XML_c, XML_manualLayout));
+
+ pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(aCustomLabelPosition.Primary));
+ pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(aCustomLabelPosition.Secondary));
+
+ SAL_WARN_IF(aCustomLabelPosition.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position");
+
+ pFS->endElement(FSNS(XML_c, XML_manualLayout));
+ pFS->endElement(FSNS(XML_c, XML_layout));
+ }
+ }
+
+ if( GetProperty(xLabelPropSet, "LinkNumberFormatToSource") )
+ mAny >>= bLinkedNumFmt;
+
+ if( xLabelPropSet->getPropertyValue("Label") >>= aLabel )
+ bLabelIsNumberFormat = aLabel.ShowNumber;
+ else
+ bLabelIsNumberFormat = true;
+
+ if (GetProperty(xLabelPropSet, bLabelIsNumberFormat ? OUString("NumberFormat") : OUString("PercentageNumberFormat")))
+ {
+ sal_Int32 nKey = 0;
+ mAny >>= nKey;
+
+ OUString aNumberFormatString = getNumberFormatCode(nKey);
+
+ pFS->singleElement(FSNS(XML_c, XML_numFmt), XML_formatCode, aNumberFormatString,
+ XML_sourceLinked, ToPsz10(bLinkedNumFmt));
+ }
+
+ // Individual label property that overwrites the baseline.
+ writeLabelProperties(pFS, this, xLabelPropSet, aParam, nIdx, rDLblsRange);
+ pFS->endElement(FSNS(XML_c, XML_dLbl));
+ }
+
+ // Baseline label properties for all labels.
+ writeLabelProperties(pFS, this, xPropSet, aParam, -1, rDLblsRange);
+
+ bool bShowLeaderLines = false;
+ xPropSet->getPropertyValue("ShowCustomLeaderLines") >>= bShowLeaderLines;
+ pFS->singleElement(FSNS(XML_c, XML_showLeaderLines), XML_val, ToPsz10(bShowLeaderLines));
+
+ // Export leader line
+ if( eChartType != chart::TYPEID_PIE )
+ {
+ pFS->startElement(FSNS(XML_c, XML_extLst));
+ pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15), GetFB()->getNamespaceURL(OOX_NS(c15)));
+ pFS->singleElement(FSNS(XML_c15, XML_showLeaderLines), XML_val, ToPsz10(bShowLeaderLines));
+ pFS->endElement(FSNS(XML_c, XML_ext));
+ pFS->endElement(FSNS(XML_c, XML_extLst));
+ }
+ pFS->endElement(FSNS(XML_c, XML_dLbls));
+}
+
+void ChartExport::exportDataPoints(
+ const uno::Reference< beans::XPropertySet > & xSeriesProperties,
+ sal_Int32 nSeriesLength, sal_Int32 eChartType )
+{
+ uno::Reference< chart2::XDataSeries > xSeries( xSeriesProperties, uno::UNO_QUERY );
+ bool bVaryColorsByPoint = false;
+ Sequence< sal_Int32 > aDataPointSeq;
+ if( xSeriesProperties.is())
+ {
+ Any aAny = xSeriesProperties->getPropertyValue( "AttributedDataPoints" );
+ aAny >>= aDataPointSeq;
+ xSeriesProperties->getPropertyValue( "VaryColorsByPoint" ) >>= bVaryColorsByPoint;
+ }
+
+ const sal_Int32 * pPoints = aDataPointSeq.getConstArray();
+ sal_Int32 nElement;
+ Reference< chart2::XColorScheme > xColorScheme;
+ if( mxNewDiagram.is())
+ xColorScheme.set( mxNewDiagram->getDefaultColorScheme());
+
+ if( bVaryColorsByPoint && xColorScheme.is() )
+ {
+ o3tl::sorted_vector< sal_Int32 > aAttrPointSet;
+ aAttrPointSet.reserve(aDataPointSeq.getLength());
+ for (auto p = pPoints; p < pPoints + aDataPointSeq.getLength(); ++p)
+ aAttrPointSet.insert(*p);
+ const auto aEndIt = aAttrPointSet.end();
+ for( nElement = 0; nElement < nSeriesLength; ++nElement )
+ {
+ uno::Reference< beans::XPropertySet > xPropSet;
+ if( aAttrPointSet.find( nElement ) != aEndIt )
+ {
+ try
+ {
+ xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet(
+ xSeries, nElement, getModel() );
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" );
+ }
+ }
+ else
+ {
+ // property set only containing the color
+ xPropSet.set( new ColorPropertySet( ColorTransparency, xColorScheme->getColorByIndex( nElement )));
+ }
+
+ if( xPropSet.is() )
+ {
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_dPt));
+ pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement));
+
+ switch (eChartType)
+ {
+ case chart::TYPEID_PIE:
+ case chart::TYPEID_DOUGHNUT:
+ {
+ if( xPropSet.is() && GetProperty( xPropSet, "SegmentOffset") )
+ {
+ sal_Int32 nOffset = 0;
+ mAny >>= nOffset;
+ if (nOffset)
+ pFS->singleElement( FSNS( XML_c, XML_explosion ),
+ XML_val, OString::number( nOffset ) );
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ exportShapeProps( xPropSet );
+
+ pFS->endElement( FSNS( XML_c, XML_dPt ) );
+ }
+ }
+ }
+
+ // Export Data Point Property in Charts even if the VaryColors is false
+ if( bVaryColorsByPoint )
+ return;
+
+ o3tl::sorted_vector< sal_Int32 > aAttrPointSet;
+ aAttrPointSet.reserve(aDataPointSeq.getLength());
+ for (auto p = pPoints; p < pPoints + aDataPointSeq.getLength(); ++p)
+ aAttrPointSet.insert(*p);
+ const auto aEndIt = aAttrPointSet.end();
+ for( nElement = 0; nElement < nSeriesLength; ++nElement )
+ {
+ uno::Reference< beans::XPropertySet > xPropSet;
+ if( aAttrPointSet.find( nElement ) != aEndIt )
+ {
+ try
+ {
+ xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet(
+ xSeries, nElement, getModel() );
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" );
+ }
+ }
+
+ if( xPropSet.is() )
+ {
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_dPt));
+ pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement));
+
+ switch( eChartType )
+ {
+ case chart::TYPEID_BUBBLE:
+ case chart::TYPEID_HORBAR:
+ case chart::TYPEID_BAR:
+ pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0");
+ exportShapeProps(xPropSet);
+ break;
+
+ case chart::TYPEID_LINE:
+ case chart::TYPEID_SCATTER:
+ case chart::TYPEID_RADARLINE:
+ exportMarker(xPropSet);
+ break;
+
+ default:
+ exportShapeProps(xPropSet);
+ break;
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_dPt ) );
+ }
+ }
+}
+
+void ChartExport::exportAxesId(bool bPrimaryAxes, bool bCheckCombinedAxes)
+{
+ sal_Int32 nAxisIdx, nAxisIdy;
+ bool bPrimaryAxisExists = false;
+ bool bSecondaryAxisExists = false;
+ // let's check which axis already exists and which axis is attached to the actual dataseries
+ if (maAxes.size() >= 2)
+ {
+ bPrimaryAxisExists = bPrimaryAxes && maAxes[1].nAxisType == AXIS_PRIMARY_Y;
+ bSecondaryAxisExists = !bPrimaryAxes && maAxes[1].nAxisType == AXIS_SECONDARY_Y;
+ }
+ // tdf#114181 keep axes of combined charts
+ if ( bCheckCombinedAxes && ( bPrimaryAxisExists || bSecondaryAxisExists ) )
+ {
+ nAxisIdx = maAxes[0].nAxisId;
+ nAxisIdy = maAxes[1].nAxisId;
+ }
+ else
+ {
+ nAxisIdx = lcl_generateRandomValue();
+ nAxisIdy = lcl_generateRandomValue();
+ AxesType eXAxis = bPrimaryAxes ? AXIS_PRIMARY_X : AXIS_SECONDARY_X;
+ AxesType eYAxis = bPrimaryAxes ? AXIS_PRIMARY_Y : AXIS_SECONDARY_Y;
+ maAxes.emplace_back( eXAxis, nAxisIdx, nAxisIdy );
+ maAxes.emplace_back( eYAxis, nAxisIdy, nAxisIdx );
+ }
+ FSHelperPtr pFS = GetFS();
+ pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdx));
+ pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdy));
+ if (mbHasZAxis)
+ {
+ sal_Int32 nAxisIdz = 0;
+ if( isDeep3dChart() )
+ {
+ nAxisIdz = lcl_generateRandomValue();
+ maAxes.emplace_back( AXIS_PRIMARY_Z, nAxisIdz, nAxisIdy );
+ }
+ pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdz));
+ }
+}
+
+void ChartExport::exportGrouping( bool isBar )
+{
+ FSHelperPtr pFS = GetFS();
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ // grouping
+ if( GetProperty( xPropSet, "Stacked" ) )
+ mAny >>= mbStacked;
+ if( GetProperty( xPropSet, "Percent" ) )
+ mAny >>= mbPercent;
+
+ const char* grouping = nullptr;
+ if (mbStacked)
+ grouping = "stacked";
+ else if (mbPercent)
+ grouping = "percentStacked";
+ else
+ {
+ if( isBar && !isDeep3dChart() )
+ {
+ grouping = "clustered";
+ }
+ else
+ grouping = "standard";
+ }
+ pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, grouping);
+}
+
+void ChartExport::exportTrendlines( const Reference< chart2::XDataSeries >& xSeries )
+{
+ FSHelperPtr pFS = GetFS();
+ Reference< chart2::XRegressionCurveContainer > xRegressionCurveContainer( xSeries, UNO_QUERY );
+ if( !xRegressionCurveContainer.is() )
+ return;
+
+ const Sequence< Reference< chart2::XRegressionCurve > > aRegCurveSeq = xRegressionCurveContainer->getRegressionCurves();
+ for( const Reference< chart2::XRegressionCurve >& xRegCurve : aRegCurveSeq )
+ {
+ if (!xRegCurve.is())
+ continue;
+
+ Reference< XPropertySet > xProperties( xRegCurve , uno::UNO_QUERY );
+
+ OUString aService;
+ Reference< lang::XServiceName > xServiceName( xProperties, UNO_QUERY );
+ if( !xServiceName.is() )
+ continue;
+
+ aService = xServiceName->getServiceName();
+
+ if(aService != "com.sun.star.chart2.LinearRegressionCurve" &&
+ aService != "com.sun.star.chart2.ExponentialRegressionCurve" &&
+ aService != "com.sun.star.chart2.LogarithmicRegressionCurve" &&
+ aService != "com.sun.star.chart2.PotentialRegressionCurve" &&
+ aService != "com.sun.star.chart2.PolynomialRegressionCurve" &&
+ aService != "com.sun.star.chart2.MovingAverageRegressionCurve")
+ continue;
+
+ pFS->startElement(FSNS(XML_c, XML_trendline));
+
+ OUString aName;
+ xProperties->getPropertyValue("CurveName") >>= aName;
+ if(!aName.isEmpty())
+ {
+ pFS->startElement(FSNS(XML_c, XML_name));
+ pFS->writeEscaped(aName);
+ pFS->endElement( FSNS( XML_c, XML_name) );
+ }
+
+ exportShapeProps( xProperties );
+
+ if( aService == "com.sun.star.chart2.LinearRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "linear");
+ }
+ else if( aService == "com.sun.star.chart2.ExponentialRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "exp");
+ }
+ else if( aService == "com.sun.star.chart2.LogarithmicRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "log");
+ }
+ else if( aService == "com.sun.star.chart2.PotentialRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "power");
+ }
+ else if( aService == "com.sun.star.chart2.PolynomialRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "poly");
+
+ sal_Int32 aDegree = 2;
+ xProperties->getPropertyValue( "PolynomialDegree") >>= aDegree;
+ pFS->singleElement(FSNS(XML_c, XML_order), XML_val, OString::number(aDegree));
+ }
+ else if( aService == "com.sun.star.chart2.MovingAverageRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "movingAvg");
+
+ sal_Int32 aPeriod = 2;
+ xProperties->getPropertyValue( "MovingAveragePeriod") >>= aPeriod;
+
+ pFS->singleElement(FSNS(XML_c, XML_period), XML_val, OString::number(aPeriod));
+ }
+ else
+ {
+ // should never happen
+ // This would produce invalid OOXML files so we check earlier for the type
+ assert(false);
+ }
+
+ double fExtrapolateForward = 0.0;
+ double fExtrapolateBackward = 0.0;
+
+ xProperties->getPropertyValue("ExtrapolateForward") >>= fExtrapolateForward;
+ xProperties->getPropertyValue("ExtrapolateBackward") >>= fExtrapolateBackward;
+
+ pFS->singleElement( FSNS( XML_c, XML_forward ),
+ XML_val, OString::number(fExtrapolateForward) );
+
+ pFS->singleElement( FSNS( XML_c, XML_backward ),
+ XML_val, OString::number(fExtrapolateBackward) );
+
+ bool bForceIntercept = false;
+ xProperties->getPropertyValue("ForceIntercept") >>= bForceIntercept;
+
+ if (bForceIntercept)
+ {
+ double fInterceptValue = 0.0;
+ xProperties->getPropertyValue("InterceptValue") >>= fInterceptValue;
+
+ pFS->singleElement( FSNS( XML_c, XML_intercept ),
+ XML_val, OString::number(fInterceptValue) );
+ }
+
+ // Equation properties
+ Reference< XPropertySet > xEquationProperties( xRegCurve->getEquationProperties() );
+
+ // Show Equation
+ bool bShowEquation = false;
+ xEquationProperties->getPropertyValue("ShowEquation") >>= bShowEquation;
+
+ // Show R^2
+ bool bShowCorrelationCoefficient = false;
+ xEquationProperties->getPropertyValue("ShowCorrelationCoefficient") >>= bShowCorrelationCoefficient;
+
+ pFS->singleElement( FSNS( XML_c, XML_dispRSqr ),
+ XML_val, ToPsz10(bShowCorrelationCoefficient) );
+
+ pFS->singleElement(FSNS(XML_c, XML_dispEq), XML_val, ToPsz10(bShowEquation));
+
+ pFS->endElement( FSNS( XML_c, XML_trendline ) );
+ }
+}
+
+void ChartExport::exportMarker(const Reference< XPropertySet >& xPropSet)
+{
+ chart2::Symbol aSymbol;
+ if( GetProperty( xPropSet, "Symbol" ) )
+ mAny >>= aSymbol;
+
+ if(aSymbol.Style != chart2::SymbolStyle_STANDARD && aSymbol.Style != chart2::SymbolStyle_NONE)
+ return;
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_marker));
+
+ sal_Int32 nSymbol = aSymbol.StandardSymbol;
+ // TODO: more properties support for marker
+ const char* pSymbolType; // no initialization here, to let compiler warn if we have a code path
+ // where it stays uninitialized
+ switch( nSymbol )
+ {
+ case 0:
+ pSymbolType = "square";
+ break;
+ case 1:
+ pSymbolType = "diamond";
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ pSymbolType = "triangle";
+ break;
+ case 8:
+ pSymbolType = "circle";
+ break;
+ case 9:
+ pSymbolType = "star";
+ break;
+ case 10:
+ pSymbolType = "x"; // in MS office 2010 built in symbol marker 'X' is represented as 'x'
+ break;
+ case 11:
+ pSymbolType = "plus";
+ break;
+ case 13:
+ pSymbolType = "dash";
+ break;
+ default:
+ pSymbolType = "square";
+ break;
+ }
+
+ bool bSkipFormatting = false;
+ if (aSymbol.Style == chart2::SymbolStyle_NONE)
+ {
+ bSkipFormatting = true;
+ pSymbolType = "none";
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_symbol), XML_val, pSymbolType);
+
+ if (!bSkipFormatting)
+ {
+ awt::Size aSymbolSize = aSymbol.Size;
+ sal_Int32 nSize = std::max( aSymbolSize.Width, aSymbolSize.Height );
+
+ nSize = nSize/250.0*7.0 + 1; // just guessed based on some test cases,
+ //the value is always 1 less than the actual value.
+ nSize = std::clamp( int(nSize), 2, 72 );
+ pFS->singleElement(FSNS(XML_c, XML_size), XML_val, OString::number(nSize));
+
+ pFS->startElement(FSNS(XML_c, XML_spPr));
+
+ util::Color aColor = aSymbol.FillColor;
+ if (GetProperty(xPropSet, "Color"))
+ mAny >>= aColor;
+
+ if (aColor == -1)
+ {
+ pFS->singleElement(FSNS(XML_a, XML_noFill));
+ }
+ else
+ WriteSolidFill(::Color(ColorTransparency, aColor));
+
+ pFS->endElement( FSNS( XML_c, XML_spPr ) );
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_marker ) );
+}
+
+void ChartExport::exportSmooth()
+{
+ FSHelperPtr pFS = GetFS();
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY );
+ sal_Int32 nSplineType = 0;
+ if( GetProperty( xPropSet, "SplineType" ) )
+ mAny >>= nSplineType;
+ const char* pVal = nSplineType != 0 ? "1" : "0";
+ pFS->singleElement(FSNS(XML_c, XML_smooth), XML_val, pVal);
+}
+
+void ChartExport::exportFirstSliceAng( )
+{
+ FSHelperPtr pFS = GetFS();
+ sal_Int32 nStartingAngle = 0;
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ if( GetProperty( xPropSet, "StartingAngle" ) )
+ mAny >>= nStartingAngle;
+
+ // convert to ooxml angle
+ nStartingAngle = (450 - nStartingAngle ) % 360;
+ pFS->singleElement(FSNS(XML_c, XML_firstSliceAng), XML_val, OString::number(nStartingAngle));
+}
+
+namespace {
+
+const char* getErrorBarStyle(sal_Int32 nErrorBarStyle)
+{
+ switch(nErrorBarStyle)
+ {
+ case cssc::ErrorBarStyle::NONE:
+ return nullptr;
+ case cssc::ErrorBarStyle::VARIANCE:
+ break;
+ case cssc::ErrorBarStyle::STANDARD_DEVIATION:
+ return "stdDev";
+ case cssc::ErrorBarStyle::ABSOLUTE:
+ return "fixedVal";
+ case cssc::ErrorBarStyle::RELATIVE:
+ return "percentage";
+ case cssc::ErrorBarStyle::ERROR_MARGIN:
+ break;
+ case cssc::ErrorBarStyle::STANDARD_ERROR:
+ return "stdErr";
+ case cssc::ErrorBarStyle::FROM_DATA:
+ return "cust";
+ default:
+ assert(false && "can't happen");
+ }
+ return nullptr;
+}
+
+Reference< chart2::data::XDataSequence> getLabeledSequence(
+ const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > >& aSequences,
+ bool bPositive )
+{
+ OUString aDirection;
+ if(bPositive)
+ aDirection = "positive";
+ else
+ aDirection = "negative";
+
+ for( const auto& rSequence : aSequences )
+ {
+ if( rSequence.is())
+ {
+ uno::Reference< chart2::data::XDataSequence > xSequence( rSequence->getValues());
+ uno::Reference< beans::XPropertySet > xSeqProp( xSequence, uno::UNO_QUERY_THROW );
+ OUString aRole;
+ if( ( xSeqProp->getPropertyValue( "Role" ) >>= aRole ) &&
+ aRole.match( "error-bars" ) && aRole.indexOf(aDirection) >= 0 )
+ {
+ return xSequence;
+ }
+ }
+ }
+
+ return Reference< chart2::data::XDataSequence > ();
+}
+
+}
+
+void ChartExport::exportErrorBar(const Reference< XPropertySet>& xErrorBarProps, bool bYError)
+{
+ sal_Int32 nErrorBarStyle = cssc::ErrorBarStyle::NONE;
+ xErrorBarProps->getPropertyValue("ErrorBarStyle") >>= nErrorBarStyle;
+ const char* pErrorBarStyle = getErrorBarStyle(nErrorBarStyle);
+ if(!pErrorBarStyle)
+ return;
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_errBars));
+ pFS->singleElement(FSNS(XML_c, XML_errDir), XML_val, bYError ? "y" : "x");
+ bool bPositive = false, bNegative = false;
+ xErrorBarProps->getPropertyValue("ShowPositiveError") >>= bPositive;
+ xErrorBarProps->getPropertyValue("ShowNegativeError") >>= bNegative;
+ const char* pErrBarType;
+ if(bPositive && bNegative)
+ pErrBarType = "both";
+ else if(bPositive)
+ pErrBarType = "plus";
+ else if(bNegative)
+ pErrBarType = "minus";
+ else
+ {
+ // what the hell should we do now?
+ // at least this makes the file valid
+ pErrBarType = "both";
+ }
+ pFS->singleElement(FSNS(XML_c, XML_errBarType), XML_val, pErrBarType);
+ pFS->singleElement(FSNS(XML_c, XML_errValType), XML_val, pErrorBarStyle);
+ pFS->singleElement(FSNS(XML_c, XML_noEndCap), XML_val, "0");
+ if(nErrorBarStyle == cssc::ErrorBarStyle::FROM_DATA)
+ {
+ uno::Reference< chart2::data::XDataSource > xDataSource(xErrorBarProps, uno::UNO_QUERY);
+ Sequence< Reference < chart2::data::XLabeledDataSequence > > aSequences =
+ xDataSource->getDataSequences();
+
+ if(bPositive)
+ {
+ exportSeriesValues(getLabeledSequence(aSequences, true), XML_plus);
+ }
+
+ if(bNegative)
+ {
+ exportSeriesValues(getLabeledSequence(aSequences, false), XML_minus);
+ }
+ }
+ else
+ {
+ double nVal = 0.0;
+ if(nErrorBarStyle == cssc::ErrorBarStyle::STANDARD_DEVIATION)
+ {
+ xErrorBarProps->getPropertyValue("Weight") >>= nVal;
+ }
+ else
+ {
+ if(bPositive)
+ xErrorBarProps->getPropertyValue("PositiveError") >>= nVal;
+ else
+ xErrorBarProps->getPropertyValue("NegativeError") >>= nVal;
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_val), XML_val, OString::number(nVal));
+ }
+
+ exportShapeProps( xErrorBarProps );
+
+ pFS->endElement( FSNS( XML_c, XML_errBars) );
+}
+
+void ChartExport::exportView3D()
+{
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ if( !xPropSet.is() )
+ return;
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_view3D));
+ sal_Int32 eChartType = getChartType( );
+ // rotX
+ if( GetProperty( xPropSet, "RotationHorizontal" ) )
+ {
+ sal_Int32 nRotationX = 0;
+ mAny >>= nRotationX;
+ if( nRotationX < 0 )
+ {
+ if(eChartType == chart::TYPEID_PIE)
+ {
+ /* In OOXML we get value in 0..90 range for pie chart X rotation , whereas we expect it to be in -90..90 range,
+ so we convert that during import. It is modified in View3DConverter::convertFromModel()
+ here we convert it back to 0..90 as we received in import */
+ nRotationX += 90; // X rotation (map Chart2 [-179,180] to OOXML [0..90])
+ }
+ else
+ nRotationX += 360; // X rotation (map Chart2 [-179,180] to OOXML [-90..90])
+ }
+ pFS->singleElement(FSNS(XML_c, XML_rotX), XML_val, OString::number(nRotationX));
+ }
+ // rotY
+ if( GetProperty( xPropSet, "RotationVertical" ) )
+ {
+ // Y rotation (map Chart2 [-179,180] to OOXML [0..359])
+ if( eChartType == chart::TYPEID_PIE && GetProperty( xPropSet, "StartingAngle" ) )
+ {
+ // Y rotation used as 'first pie slice angle' in 3D pie charts
+ sal_Int32 nStartingAngle=0;
+ mAny >>= nStartingAngle;
+ // convert to ooxml angle
+ nStartingAngle = (450 - nStartingAngle ) % 360;
+ pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nStartingAngle));
+ }
+ else
+ {
+ sal_Int32 nRotationY = 0;
+ mAny >>= nRotationY;
+ // Y rotation (map Chart2 [-179,180] to OOXML [0..359])
+ if( nRotationY < 0 )
+ nRotationY += 360;
+ pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nRotationY));
+ }
+ }
+ // rAngAx
+ if( GetProperty( xPropSet, "RightAngledAxes" ) )
+ {
+ bool bRightAngled = false;
+ mAny >>= bRightAngled;
+ const char* sRightAngled = bRightAngled ? "1":"0";
+ pFS->singleElement(FSNS(XML_c, XML_rAngAx), XML_val, sRightAngled);
+ }
+ // perspective
+ if( GetProperty( xPropSet, "Perspective" ) )
+ {
+ sal_Int32 nPerspective = 0;
+ mAny >>= nPerspective;
+ // map Chart2 [0,100] to OOXML [0..200]
+ nPerspective *= 2;
+ pFS->singleElement(FSNS(XML_c, XML_perspective), XML_val, OString::number(nPerspective));
+ }
+ pFS->endElement( FSNS( XML_c, XML_view3D ) );
+}
+
+bool ChartExport::isDeep3dChart()
+{
+ bool isDeep = false;
+ if( mbIs3DChart )
+ {
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ if( GetProperty( xPropSet, "Deep" ) )
+ mAny >>= isDeep;
+ }
+ return isDeep;
+}
+
+OUString ChartExport::getNumberFormatCode(sal_Int32 nKey) const
+{
+ /* XXX if this was called more than one or two times per export the two
+ * SvNumberFormatter instances and NfKeywordTable should be member
+ * variables and initialized only once. */
+
+ OUString aCode("General"); // init with fallback
+ uno::Reference<util::XNumberFormatsSupplier> xNumberFormatsSupplier(mxChartModel, uno::UNO_QUERY_THROW);
+ SvNumberFormatsSupplierObj* pSupplierObj = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( xNumberFormatsSupplier);
+ if (!pSupplierObj)
+ return aCode;
+
+ SvNumberFormatter* pNumberFormatter = pSupplierObj->GetNumberFormatter();
+ if (!pNumberFormatter)
+ return aCode;
+
+ SvNumberFormatter aTempFormatter( comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US);
+ NfKeywordTable aKeywords;
+ aTempFormatter.FillKeywordTableForExcel( aKeywords);
+ aCode = pNumberFormatter->GetFormatStringForExcel( nKey, aKeywords, aTempFormatter);
+
+ return aCode;
+}
+
+}// oox
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */