Remove break after return
[LibreOffice.git] / oox / source / export / chartexport.cxx
blobf9c4c12d066d326accbe89357191b357b8902880
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <oox/token/namespaces.hxx>
21 #include <oox/token/properties.hxx>
22 #include <oox/token/tokens.hxx>
23 #include <oox/core/xmlfilterbase.hxx>
24 #include <oox/export/chartexport.hxx>
25 #include <oox/token/relationship.hxx>
26 #include <oox/export/utils.hxx>
27 #include <drawingml/chart/typegroupconverter.hxx>
28 #include <basegfx/utils/gradienttools.hxx>
29 #include <docmodel/uno/UnoGradientTools.hxx>
31 #include <cstdio>
32 #include <limits>
34 #include <com/sun/star/awt/Gradient2.hpp>
35 #include <com/sun/star/chart/XChartDocument.hpp>
36 #include <com/sun/star/chart/ChartLegendPosition.hpp>
37 #include <com/sun/star/chart/XTwoAxisXSupplier.hpp>
38 #include <com/sun/star/chart/XTwoAxisYSupplier.hpp>
39 #include <com/sun/star/chart/XAxisZSupplier.hpp>
40 #include <com/sun/star/chart/ChartDataRowSource.hpp>
41 #include <com/sun/star/chart/X3DDisplay.hpp>
42 #include <com/sun/star/chart/XStatisticDisplay.hpp>
43 #include <com/sun/star/chart/XSecondAxisTitleSupplier.hpp>
44 #include <com/sun/star/chart/ChartSymbolType.hpp>
45 #include <com/sun/star/chart/ChartAxisMarks.hpp>
46 #include <com/sun/star/chart/ChartAxisLabelPosition.hpp>
47 #include <com/sun/star/chart/ChartAxisPosition.hpp>
48 #include <com/sun/star/chart/ChartSolidType.hpp>
49 #include <com/sun/star/chart/DataLabelPlacement.hpp>
50 #include <com/sun/star/chart/ErrorBarStyle.hpp>
51 #include <com/sun/star/chart/MissingValueTreatment.hpp>
52 #include <com/sun/star/chart/XDiagramPositioning.hpp>
53 #include <com/sun/star/chart/TimeIncrement.hpp>
54 #include <com/sun/star/chart/TimeInterval.hpp>
55 #include <com/sun/star/chart/TimeUnit.hpp>
57 #include <com/sun/star/chart2/RelativePosition.hpp>
58 #include <com/sun/star/chart2/RelativeSize.hpp>
59 #include <com/sun/star/chart2/XChartDocument.hpp>
60 #include <com/sun/star/chart2/XDiagram.hpp>
61 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
62 #include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
63 #include <com/sun/star/chart2/XChartTypeContainer.hpp>
64 #include <com/sun/star/chart2/XDataSeriesContainer.hpp>
65 #include <com/sun/star/chart2/DataPointLabel.hpp>
66 #include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
67 #include <com/sun/star/chart2/DataPointCustomLabelFieldType.hpp>
68 #include <com/sun/star/chart2/PieChartSubType.hpp>
69 #include <com/sun/star/chart2/Symbol.hpp>
70 #include <com/sun/star/chart2/data/XDataSource.hpp>
71 #include <com/sun/star/chart2/data/XDataProvider.hpp>
72 #include <com/sun/star/chart2/data/XTextualDataSequence.hpp>
73 #include <com/sun/star/chart2/data/XNumericalDataSequence.hpp>
74 #include <com/sun/star/chart2/data/XLabeledDataSequence.hpp>
75 #include <com/sun/star/chart2/XAnyDescriptionAccess.hpp>
76 #include <com/sun/star/chart2/AxisType.hpp>
78 #include <com/sun/star/beans/XPropertySet.hpp>
79 #include <com/sun/star/container/XNameAccess.hpp>
80 #include <com/sun/star/drawing/XShape.hpp>
81 #include <com/sun/star/drawing/XShapes.hpp>
82 #include <com/sun/star/drawing/FillStyle.hpp>
83 #include <com/sun/star/drawing/LineStyle.hpp>
84 #include <com/sun/star/awt/XBitmap.hpp>
85 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
86 #include <com/sun/star/lang/XServiceName.hpp>
88 #include <com/sun/star/table/CellAddress.hpp>
89 #include <com/sun/star/sheet/XFormulaParser.hpp>
90 #include <com/sun/star/sheet/FormulaToken.hpp>
91 #include <com/sun/star/sheet/AddressConvention.hpp>
93 #include <com/sun/star/container/XNamed.hpp>
94 #include <com/sun/star/embed/XVisualObject.hpp>
95 #include <com/sun/star/embed/Aspects.hpp>
97 #include <comphelper/processfactory.hxx>
98 #include <comphelper/random.hxx>
99 #include <utility>
100 #include <xmloff/SchXMLSeriesHelper.hxx>
101 #include "ColorPropertySet.hxx"
103 #include <svl/numformat.hxx>
104 #include <svl/numuno.hxx>
105 #include <comphelper/diagnose_ex.hxx>
106 #include <sal/log.hxx>
108 #include <set>
109 #include <unordered_set>
111 #include <frozen/bits/defines.h>
112 #include <frozen/bits/elsa_std.h>
113 #include <frozen/unordered_map.h>
115 #include <o3tl/temporary.hxx>
116 #include <o3tl/sorted_vector.hxx>
118 using namespace css;
119 using namespace css::uno;
120 using namespace css::drawing;
121 using namespace ::oox::core;
122 using css::beans::PropertyValue;
123 using css::beans::XPropertySet;
124 using css::container::XNamed;
125 using css::table::CellAddress;
126 using css::sheet::XFormulaParser;
127 using ::oox::core::XmlFilterBase;
128 using ::sax_fastparser::FSHelperPtr;
130 namespace cssc = css::chart;
132 namespace oox::drawingml {
134 namespace {
136 bool isPrimaryAxes(sal_Int32 nIndex)
138 assert(nIndex == 0 || nIndex == 1);
139 return nIndex != 1;
142 class lcl_MatchesRole
144 public:
145 explicit lcl_MatchesRole( OUString aRole ) :
146 m_aRole(std::move( aRole ))
149 bool operator () ( const Reference< chart2::data::XLabeledDataSequence > & xSeq ) const
151 if( !xSeq.is() )
152 return false;
153 Reference< beans::XPropertySet > xProp( xSeq->getValues(), uno::UNO_QUERY );
154 OUString aRole;
156 return ( xProp.is() &&
157 (xProp->getPropertyValue( "Role" ) >>= aRole ) &&
158 m_aRole == aRole );
161 private:
162 OUString m_aRole;
167 static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories( const Reference< chart2::XDiagram > & xDiagram, bool& bHasDateCategories )
169 bHasDateCategories = false;
170 Reference< chart2::data::XLabeledDataSequence > xResult;
173 Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
174 xDiagram, uno::UNO_QUERY_THROW );
175 const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
176 xCooSysCnt->getCoordinateSystems());
177 for( const auto& xCooSys : aCooSysSeq )
179 OSL_ASSERT( xCooSys.is());
180 for( sal_Int32 nN = xCooSys->getDimension(); nN--; )
182 const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nN);
183 for(sal_Int32 nI=0; nI<=nMaxAxisIndex; ++nI)
185 Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension( nN, nI );
186 OSL_ASSERT( xAxis.is());
187 if( xAxis.is())
189 chart2::ScaleData aScaleData = xAxis->getScaleData();
190 if( aScaleData.Categories.is())
192 bHasDateCategories = aScaleData.AxisType == chart2::AxisType::DATE;
193 xResult.set( aScaleData.Categories );
194 break;
201 catch( const uno::Exception & )
203 DBG_UNHANDLED_EXCEPTION("oox");
206 return xResult;
209 static Reference< chart2::data::XLabeledDataSequence >
210 lcl_getDataSequenceByRole(
211 const Sequence< Reference< chart2::data::XLabeledDataSequence > > & aLabeledSeq,
212 const OUString & rRole )
214 Reference< chart2::data::XLabeledDataSequence > aNoResult;
216 const Reference< chart2::data::XLabeledDataSequence > * pBegin = aLabeledSeq.getConstArray();
217 const Reference< chart2::data::XLabeledDataSequence > * pEnd = pBegin + aLabeledSeq.getLength();
218 const Reference< chart2::data::XLabeledDataSequence > * pMatch =
219 ::std::find_if( pBegin, pEnd, lcl_MatchesRole( rRole ));
221 if( pMatch != pEnd )
222 return *pMatch;
224 return aNoResult;
227 static bool lcl_hasCategoryLabels( const Reference< chart2::XChartDocument >& xChartDoc )
229 //categories are always the first sequence
230 Reference< chart2::XDiagram > xDiagram( xChartDoc->getFirstDiagram());
231 bool bDateCategories;
232 Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( xDiagram, bDateCategories ) );
233 return xCategories.is();
236 static bool lcl_isCategoryAxisShifted( const Reference< chart2::XDiagram >& xDiagram )
238 bool bCategoryPositionShifted = false;
241 Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
242 xDiagram, uno::UNO_QUERY_THROW);
243 const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
244 xCooSysCnt->getCoordinateSystems());
245 for (const auto& xCooSys : aCooSysSeq)
247 OSL_ASSERT(xCooSys.is());
248 if( 0 < xCooSys->getDimension() && 0 <= xCooSys->getMaximumAxisIndexByDimension(0) )
250 Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, 0);
251 OSL_ASSERT(xAxis.is());
252 if (xAxis.is())
254 chart2::ScaleData aScaleData = xAxis->getScaleData();
255 bCategoryPositionShifted = aScaleData.ShiftedCategoryPosition;
256 break;
261 catch (const uno::Exception&)
263 DBG_UNHANDLED_EXCEPTION("oox");
266 return bCategoryPositionShifted;
269 static sal_Int32 lcl_getCategoryAxisType( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex )
271 sal_Int32 nAxisType = -1;
274 Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
275 xDiagram, uno::UNO_QUERY_THROW);
276 const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
277 xCooSysCnt->getCoordinateSystems());
278 for( const auto& xCooSys : aCooSysSeq )
280 OSL_ASSERT(xCooSys.is());
281 if( nDimensionIndex < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(nDimensionIndex) )
283 Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(nDimensionIndex, nAxisIndex);
284 OSL_ASSERT(xAxis.is());
285 if( xAxis.is() )
287 chart2::ScaleData aScaleData = xAxis->getScaleData();
288 nAxisType = aScaleData.AxisType;
289 break;
294 catch (const uno::Exception&)
296 DBG_UNHANDLED_EXCEPTION("oox");
299 return nAxisType;
302 static OUString lclGetTimeUnitToken( sal_Int32 nTimeUnit )
304 switch( nTimeUnit )
306 case cssc::TimeUnit::DAY: return "days";
307 case cssc::TimeUnit::MONTH: return "months";
308 case cssc::TimeUnit::YEAR: return "years";
309 default: OSL_ENSURE(false, "lclGetTimeUnitToken - unexpected time unit");
311 return "days";
314 static cssc::TimeIncrement lcl_getDateTimeIncrement( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nAxisIndex )
316 cssc::TimeIncrement aTimeIncrement;
319 Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
320 xDiagram, uno::UNO_QUERY_THROW);
321 const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
322 xCooSysCnt->getCoordinateSystems());
323 for( const auto& xCooSys : aCooSysSeq )
325 OSL_ASSERT(xCooSys.is());
326 if( 0 < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(0) )
328 Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, nAxisIndex);
329 OSL_ASSERT(xAxis.is());
330 if( xAxis.is() )
332 chart2::ScaleData aScaleData = xAxis->getScaleData();
333 aTimeIncrement = aScaleData.TimeIncrement;
334 break;
339 catch (const uno::Exception&)
341 DBG_UNHANDLED_EXCEPTION("oox");
344 return aTimeIncrement;
347 static bool lcl_isSeriesAttachedToFirstAxis(
348 const Reference< chart2::XDataSeries > & xDataSeries )
350 bool bResult=true;
354 sal_Int32 nAxisIndex = 0;
355 Reference< beans::XPropertySet > xProp( xDataSeries, uno::UNO_QUERY_THROW );
356 xProp->getPropertyValue("AttachedAxisIndex") >>= nAxisIndex;
357 bResult = (0==nAxisIndex);
359 catch( const uno::Exception & )
361 DBG_UNHANDLED_EXCEPTION("oox");
364 return bResult;
367 static OUString lcl_flattenStringSequence( const Sequence< OUString > & rSequence )
369 OUStringBuffer aResult;
370 bool bPrecedeWithSpace = false;
371 for( const auto& rString : rSequence )
373 if( !rString.isEmpty())
375 if( bPrecedeWithSpace )
376 aResult.append( ' ' );
377 aResult.append( rString );
378 bPrecedeWithSpace = true;
381 return aResult.makeStringAndClear();
384 static Sequence< OUString > lcl_getLabelSequence( const Reference< chart2::data::XDataSequence > & xLabelSeq )
386 Sequence< OUString > aLabels;
388 uno::Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xLabelSeq, uno::UNO_QUERY );
389 if( xTextualDataSequence.is())
391 aLabels = xTextualDataSequence->getTextualData();
393 else if( xLabelSeq.is())
395 const Sequence< uno::Any > aAnies( xLabelSeq->getData());
396 aLabels.realloc( aAnies.getLength());
397 auto pLabels = aLabels.getArray();
398 for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
399 aAnies[i] >>= pLabels[i];
402 return aLabels;
405 static void lcl_fillCategoriesIntoStringVector(
406 const Reference< chart2::data::XDataSequence > & xCategories,
407 ::std::vector< OUString > & rOutCategories )
409 OSL_ASSERT( xCategories.is());
410 if( !xCategories.is())
411 return;
412 Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xCategories, uno::UNO_QUERY );
413 if( xTextualDataSequence.is())
415 rOutCategories.clear();
416 const Sequence< OUString > aTextData( xTextualDataSequence->getTextualData());
417 rOutCategories.insert( rOutCategories.end(), aTextData.begin(), aTextData.end() );
419 else
421 Sequence< uno::Any > aAnies( xCategories->getData());
422 rOutCategories.resize( aAnies.getLength());
423 for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
424 aAnies[i] >>= rOutCategories[i];
428 static ::std::vector< double > lcl_getAllValuesFromSequence( const Reference< chart2::data::XDataSequence > & xSeq )
430 ::std::vector< double > aResult;
432 Reference< chart2::data::XNumericalDataSequence > xNumSeq( xSeq, uno::UNO_QUERY );
433 if( xNumSeq.is())
435 const Sequence< double > aValues( xNumSeq->getNumericalData());
436 aResult.insert( aResult.end(), aValues.begin(), aValues.end() );
438 else if( xSeq.is())
440 Sequence< uno::Any > aAnies( xSeq->getData());
441 aResult.resize( aAnies.getLength(), std::numeric_limits<double>::quiet_NaN() );
442 for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
443 aAnies[i] >>= aResult[i];
445 return aResult;
448 namespace
451 constexpr auto constChartTypeMap = frozen::make_unordered_map<std::u16string_view, chart::TypeId>(
453 { u"com.sun.star.chart.BarDiagram", chart::TYPEID_BAR },
454 { u"com.sun.star.chart2.ColumnChartType", chart::TYPEID_BAR },
455 { u"com.sun.star.chart.AreaDiagram", chart::TYPEID_AREA },
456 { u"com.sun.star.chart2.AreaChartType", chart::TYPEID_AREA },
457 { u"com.sun.star.chart.LineDiagram", chart::TYPEID_LINE },
458 { u"com.sun.star.chart2.LineChartType", chart::TYPEID_LINE },
459 { u"com.sun.star.chart.PieDiagram", chart::TYPEID_PIE },
460 { u"com.sun.star.chart2.PieChartType", chart::TYPEID_PIE },
461 { u"com.sun.star.chart.DonutDiagram", chart::TYPEID_DOUGHNUT },
462 { u"com.sun.star.chart2.DonutChartType", chart::TYPEID_DOUGHNUT },
463 { u"com.sun.star.chart.XYDiagram", chart::TYPEID_SCATTER },
464 { u"com.sun.star.chart2.ScatterChartType", chart::TYPEID_SCATTER },
465 { u"com.sun.star.chart.NetDiagram", chart::TYPEID_RADARLINE },
466 { u"com.sun.star.chart2.NetChartType", chart::TYPEID_RADARLINE },
467 { u"com.sun.star.chart.FilledNetDiagram", chart::TYPEID_RADARAREA },
468 { u"com.sun.star.chart2.FilledNetChartType", chart::TYPEID_RADARAREA },
469 { u"com.sun.star.chart.StockDiagram", chart::TYPEID_STOCK },
470 { u"com.sun.star.chart2.CandleStickChartType", chart::TYPEID_STOCK },
471 { u"com.sun.star.chart.BubbleDiagram", chart::TYPEID_BUBBLE },
472 { u"com.sun.star.chart2.BubbleChartType", chart::TYPEID_BUBBLE },
475 } // end anonymous namespace
477 static sal_Int32 lcl_getChartType(std::u16string_view sChartType)
479 auto aIterator = constChartTypeMap.find(sChartType);
480 if (aIterator == constChartTypeMap.end())
481 return chart::TYPEID_UNKNOWN;
482 return aIterator->second;
485 static sal_Int32 lcl_generateRandomValue()
487 return comphelper::rng::uniform_int_distribution(0, 100000000-1);
490 bool DataLabelsRange::empty() const
492 return maLabels.empty();
495 size_t DataLabelsRange::count() const
497 return maLabels.size();
500 bool DataLabelsRange::hasLabel(sal_Int32 nIndex) const
502 return maLabels.find(nIndex) != maLabels.end();
505 const OUString & DataLabelsRange::getRange() const
507 return maRange;
510 void DataLabelsRange::setRange(const OUString& rRange)
512 maRange = rRange;
515 void DataLabelsRange::setLabel(sal_Int32 nIndex, const OUString& rText)
517 maLabels.emplace(nIndex, rText);
520 DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::begin() const
522 return maLabels.begin();
525 DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::end() const
527 return maLabels.end();
530 ChartExport::ChartExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, Reference< frame::XModel > const & xModel, XmlFilterBase* pFB, DocumentType eDocumentType )
531 : DrawingML( std::move(pFS), pFB, eDocumentType )
532 , mnXmlNamespace( nXmlNamespace )
533 , mnSeriesCount(0)
534 , mxChartModel( xModel )
535 , mpURLTransformer(std::make_shared<URLTransformer>())
536 , mbHasCategoryLabels( false )
537 , mbHasZAxis( false )
538 , mbIs3DChart( false )
539 , mbStacked(false)
540 , mbPercent(false)
541 , mbHasDateCategories(false)
545 void ChartExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer)
547 mpURLTransformer = pTransformer;
550 sal_Int32 ChartExport::getChartType( )
552 OUString sChartType = mxDiagram->getDiagramType();
553 return lcl_getChartType( sChartType );
556 namespace {
558 uno::Sequence< beans::PropertyValue > createArguments(
559 const OUString & rRangeRepresentation, bool bUseColumns)
561 css::chart::ChartDataRowSource eRowSource = css::chart::ChartDataRowSource_ROWS;
562 if (bUseColumns)
563 eRowSource = css::chart::ChartDataRowSource_COLUMNS;
565 uno::Sequence<beans::PropertyValue> aArguments{
566 { "DataRowSource", -1, uno::Any(eRowSource), beans::PropertyState_DIRECT_VALUE },
567 { "FirstCellAsLabel", -1, uno::Any(false), beans::PropertyState_DIRECT_VALUE },
568 { "HasCategories", -1, uno::Any(false), beans::PropertyState_DIRECT_VALUE },
569 { "CellRangeRepresentation", -1, uno::Any(rRangeRepresentation),
570 beans::PropertyState_DIRECT_VALUE }
573 return aArguments;
576 Reference<chart2::XDataSeries> getPrimaryDataSeries(const Reference<chart2::XChartType>& xChartType)
578 Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY_THROW);
580 // export dataseries for current chart-type
581 const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries());
582 for (const auto& rSeries : aSeriesSeq)
584 Reference<chart2::XDataSeries> xSource(rSeries, uno::UNO_QUERY);
585 if (xSource.is())
586 return xSource;
589 return Reference<chart2::XDataSeries>();
594 Sequence< Sequence< OUString > > ChartExport::getSplitCategoriesList( const OUString& rRange )
596 Reference< chart2::XChartDocument > xChartDoc(getModel(), uno::UNO_QUERY);
597 OSL_ASSERT(xChartDoc.is());
598 if (xChartDoc.is())
600 Reference< chart2::data::XDataProvider > xDataProvider(xChartDoc->getDataProvider());
601 OSL_ENSURE(xDataProvider.is(), "No DataProvider");
602 if (xDataProvider.is())
604 //detect whether the first series is a row or a column
605 bool bSeriesUsesColumns = true;
606 Reference< chart2::XDiagram > xDiagram(xChartDoc->getFirstDiagram());
609 Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(xDiagram, uno::UNO_QUERY_THROW);
610 const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(xCooSysCnt->getCoordinateSystems());
611 for (const auto& rCooSys : aCooSysSeq)
613 const Reference< chart2::XChartTypeContainer > xCTCnt(rCooSys, uno::UNO_QUERY_THROW);
614 const Sequence< Reference< chart2::XChartType > > aChartTypeSeq(xCTCnt->getChartTypes());
615 for (const auto& rChartType : aChartTypeSeq)
617 Reference< chart2::XDataSeries > xDataSeries = getPrimaryDataSeries(rChartType);
618 if (xDataSeries.is())
620 uno::Reference< chart2::data::XDataSource > xSeriesSource(xDataSeries, uno::UNO_QUERY);
621 const uno::Sequence< beans::PropertyValue > rArguments = xDataProvider->detectArguments(xSeriesSource);
622 for (const beans::PropertyValue& rProperty : rArguments)
624 if (rProperty.Name == "DataRowSource")
626 css::chart::ChartDataRowSource eRowSource;
627 if (rProperty.Value >>= eRowSource)
629 bSeriesUsesColumns = (eRowSource == css::chart::ChartDataRowSource_COLUMNS);
630 break;
638 catch (const uno::Exception &)
640 DBG_UNHANDLED_EXCEPTION("chart2");
642 // detect we have an inner data table or not
643 if (xChartDoc->hasInternalDataProvider() && rRange == "categories")
647 css::uno::Reference< css::chart2::XAnyDescriptionAccess > xDataAccess(xChartDoc->getDataProvider(), uno::UNO_QUERY);
648 const Sequence< Sequence< uno::Any > >aAnyCategories(bSeriesUsesColumns ? xDataAccess->getAnyRowDescriptions() : xDataAccess->getAnyColumnDescriptions());
649 auto pMax = std::max_element(aAnyCategories.begin(), aAnyCategories.end(),
650 [](const Sequence<uno::Any>& a, const Sequence<uno::Any>& b) {
651 return a.getLength() < b.getLength(); });
653 //minimum is 1!
654 if (pMax != aAnyCategories.end() && pMax->getLength() > 1)
656 sal_Int32 nLevelCount = pMax->getLength();
657 //we have complex categories
658 //sort the categories name
659 Sequence<Sequence<OUString>>aFinalSplitSource(nLevelCount);
660 auto pFinalSplitSource = aFinalSplitSource.getArray();
661 for (sal_Int32 i = 0; i < nLevelCount; i++)
663 sal_Int32 nElemLabel = 0;
664 pFinalSplitSource[nLevelCount - i - 1].realloc(aAnyCategories.getLength());
665 auto pSeq = pFinalSplitSource[nLevelCount - i - 1].getArray();
666 for (auto const& elemLabel : aAnyCategories)
668 // make sure elemLabel[i] exists!
669 if (elemLabel.getLength() > i)
671 pSeq[nElemLabel] = elemLabel[i].get<OUString>();
672 nElemLabel++;
676 return aFinalSplitSource;
679 catch (const uno::Exception &)
681 DBG_UNHANDLED_EXCEPTION("oox");
684 else
688 uno::Reference< chart2::data::XDataSource > xCategoriesSource(xDataProvider->createDataSource(
689 createArguments(rRange, bSeriesUsesColumns)));
691 if (xCategoriesSource.is())
693 const Sequence< Reference< chart2::data::XLabeledDataSequence >> aCategories = xCategoriesSource->getDataSequences();
694 if (aCategories.getLength() > 1)
696 //we have complex categories
697 //sort the categories name
698 Sequence<Sequence<OUString>> aFinalSplitSource(aCategories.getLength());
699 std::transform(aCategories.begin(), aCategories.end(),
700 std::reverse_iterator(asNonConstRange(aFinalSplitSource).end()),
701 [](const Reference<chart2::data::XLabeledDataSequence>& xCat) {
702 return lcl_getLabelSequence(xCat->getValues()); });
703 return aFinalSplitSource;
707 catch (const uno::Exception &)
709 DBG_UNHANDLED_EXCEPTION("oox");
715 return Sequence< Sequence< OUString>>(0);
718 OUString ChartExport::parseFormula( const OUString& rRange )
720 OUString aResult;
721 Reference< XFormulaParser > xParser;
722 uno::Reference< lang::XMultiServiceFactory > xSF = GetFB()->getModelFactory();
723 if( xSF.is() )
727 xParser.set( xSF->createInstance("com.sun.star.sheet.FormulaParser"), UNO_QUERY );
729 catch( Exception& )
734 SAL_WARN_IF(!xParser.is(), "oox", "creating formula parser failed");
736 if( xParser.is() )
738 Reference< XPropertySet > xParserProps( xParser, uno::UNO_QUERY );
739 // rRange is the result of a
740 // css::chart2::data::XDataSequence::getSourceRangeRepresentation()
741 // call that returns the range in the document's current UI notation.
742 // Creating a FormulaParser defaults to the same notation, for
743 // parseFormula() do not attempt to override the FormulaConvention
744 // property with css::sheet::AddressConvention::OOO or some such.
745 /* TODO: it would be much better to introduce a
746 * getSourceRangeRepresentation(css::sheet::AddressConvention) to
747 * return the ranges in a specific convention than converting them with
748 * the overhead of creating an XFormulaParser for each... */
749 uno::Sequence<sheet::FormulaToken> aTokens = xParser->parseFormula( rRange, CellAddress( 0, 0, 0 ) );
750 if( xParserProps.is() )
752 xParserProps->setPropertyValue("FormulaConvention", uno::Any(css::sheet::AddressConvention::XL_OOX) );
753 // For referencing named ranges correctly with special excel chart syntax.
754 xParserProps->setPropertyValue("RefConventionChartOOXML", uno::Any(true) );
756 aResult = xParser->printFormula( aTokens, CellAddress( 0, 0, 0 ) );
758 else
760 //FIXME: currently just using simple converter, e.g $Sheet1.$A$1:$C$1 -> Sheet1!$A$1:$C$1
761 OUString aRange( rRange );
762 if( aRange.startsWith("$") )
763 aRange = aRange.copy(1);
764 aRange = aRange.replaceAll(".$", "!$" );
765 aResult = aRange;
768 return aResult;
771 void ChartExport::WriteChartObj( const Reference< XShape >& xShape, sal_Int32 nID, sal_Int32 nChartCount )
773 FSHelperPtr pFS = GetFS();
775 Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
777 pFS->startElementNS(mnXmlNamespace, XML_graphicFrame);
779 pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr);
781 // TODO: get the correct chart name chart id
782 OUString sName = "Object 1";
783 Reference< XNamed > xNamed( xShape, UNO_QUERY );
784 if (xNamed.is())
785 sName = xNamed->getName();
787 pFS->startElementNS( mnXmlNamespace, XML_cNvPr,
788 XML_id, OString::number(nID),
789 XML_name, sName);
791 OUString sURL;
792 if ( GetProperty( xShapeProps, "URL" ) )
793 mAny >>= sURL;
794 if( !sURL.isEmpty() )
796 OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
797 oox::getRelationship(Relationship::HYPERLINK),
798 mpURLTransformer->getTransformedString(sURL),
799 mpURLTransformer->isExternalURL(sURL));
801 mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
803 pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
805 pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr);
807 if( GetDocumentType() == DOCUMENT_PPTX )
808 pFS->singleElementNS(mnXmlNamespace, XML_nvPr);
809 pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr );
811 // visual chart properties
812 WriteShapeTransformation( xShape, mnXmlNamespace );
814 // writer chart object
815 pFS->startElement(FSNS(XML_a, XML_graphic));
816 pFS->startElement( FSNS( XML_a, XML_graphicData ),
817 XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart" );
818 OUString sId;
819 const char* sFullPath = nullptr;
820 const char* sRelativePath = nullptr;
821 switch( GetDocumentType() )
823 case DOCUMENT_DOCX:
825 sFullPath = "word/charts/chart";
826 sRelativePath = "charts/chart";
827 break;
829 case DOCUMENT_PPTX:
831 sFullPath = "ppt/charts/chart";
832 sRelativePath = "../charts/chart";
833 break;
835 case DOCUMENT_XLSX:
837 sFullPath = "xl/charts/chart";
838 sRelativePath = "../charts/chart";
839 break;
841 default:
843 sFullPath = "charts/chart";
844 sRelativePath = "charts/chart";
845 break;
848 OUString sFullStream = OUStringBuffer()
849 .appendAscii(sFullPath)
850 .append(OUString::number(nChartCount) + ".xml")
851 .makeStringAndClear();
852 OUString sRelativeStream = OUStringBuffer()
853 .appendAscii(sRelativePath)
854 .append(OUString::number(nChartCount) + ".xml" )
855 .makeStringAndClear();
856 FSHelperPtr pChart = CreateOutputStream(
857 sFullStream,
858 sRelativeStream,
859 pFS->getOutputStream(),
860 "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
861 oox::getRelationship(Relationship::CHART),
862 &sId );
864 XmlFilterBase* pFB = GetFB();
865 pFS->singleElement( FSNS( XML_c, XML_chart ),
866 FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)),
867 FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)),
868 FSNS(XML_r, XML_id), sId );
870 pFS->endElement( FSNS( XML_a, XML_graphicData ) );
871 pFS->endElement( FSNS( XML_a, XML_graphic ) );
872 pFS->endElementNS( mnXmlNamespace, XML_graphicFrame );
874 SetFS( pChart );
875 ExportContent();
876 SetFS( pFS );
877 pChart->endDocument();
880 void ChartExport::InitRangeSegmentationProperties( const Reference< chart2::XChartDocument > & xChartDoc )
882 if( !xChartDoc.is())
883 return;
887 Reference< chart2::data::XDataProvider > xDataProvider( xChartDoc->getDataProvider() );
888 OSL_ENSURE( xDataProvider.is(), "No DataProvider" );
889 if( xDataProvider.is())
891 mbHasCategoryLabels = lcl_hasCategoryLabels( xChartDoc );
894 catch( const uno::Exception & )
896 DBG_UNHANDLED_EXCEPTION("oox");
900 void ChartExport::ExportContent()
902 Reference< chart2::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
903 OSL_ASSERT( xChartDoc.is() );
904 if( !xChartDoc.is() )
905 return;
906 InitRangeSegmentationProperties( xChartDoc );
907 // TODO: export chart
908 ExportContent_( );
911 void ChartExport::ExportContent_()
913 Reference< css::chart::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
914 if( xChartDoc.is())
916 // determine if data comes from the outside
917 bool bIncludeTable = true;
919 Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
920 if( xNewDoc.is())
922 // check if we have own data. If so we must not export the complete
923 // range string, as this is our only indicator for having own or
924 // external data. @todo: fix this in the file format!
925 Reference< lang::XServiceInfo > xDPServiceInfo( xNewDoc->getDataProvider(), uno::UNO_QUERY );
926 if( ! (xDPServiceInfo.is() && xDPServiceInfo->getImplementationName() == "com.sun.star.comp.chart.InternalDataProvider" ))
928 bIncludeTable = false;
931 exportChartSpace( xChartDoc, bIncludeTable );
933 else
935 OSL_FAIL( "Couldn't export chart due to wrong XModel" );
939 void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument >& xChartDoc,
940 bool bIncludeTable )
942 FSHelperPtr pFS = GetFS();
943 XmlFilterBase* pFB = GetFB();
944 pFS->startElement( FSNS( XML_c, XML_chartSpace ),
945 FSNS( XML_xmlns, XML_c ), pFB->getNamespaceURL(OOX_NS(dmlChart)),
946 FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
947 FSNS( XML_xmlns, XML_r ), pFB->getNamespaceURL(OOX_NS(officeRel)));
948 // TODO: get the correct editing language
949 pFS->singleElement(FSNS(XML_c, XML_lang), XML_val, "en-US");
951 pFS->singleElement(FSNS(XML_c, XML_roundedCorners), XML_val, "0");
953 if( !bIncludeTable )
955 // TODO:external data
957 //XML_chart
958 exportChart(xChartDoc);
960 // TODO: printSettings
961 // TODO: style
962 // TODO: text properties
963 // TODO: shape properties
964 Reference< XPropertySet > xPropSet = xChartDoc->getArea();
965 if( xPropSet.is() )
966 exportShapeProps( xPropSet );
968 //XML_externalData
969 exportExternalData(xChartDoc);
971 // export additional shapes in chart
972 exportAdditionalShapes(xChartDoc);
974 pFS->endElement( FSNS( XML_c, XML_chartSpace ) );
977 void ChartExport::exportExternalData( const Reference< css::chart::XChartDocument >& xChartDoc )
979 // Embedded external data is grab bagged for docx file hence adding export part of
980 // external data for docx files only.
981 if(GetDocumentType() != DOCUMENT_DOCX)
982 return;
984 OUString externalDataPath;
985 Reference< beans::XPropertySet > xDocPropSet( xChartDoc->getDiagram(), uno::UNO_QUERY );
986 if( xDocPropSet.is())
990 Any aAny( xDocPropSet->getPropertyValue( "ExternalData" ));
991 aAny >>= externalDataPath;
993 catch( beans::UnknownPropertyException & )
995 SAL_WARN("oox", "Required property not found in ChartDocument");
998 if(externalDataPath.isEmpty())
999 return;
1001 // Here adding external data entry to relationship.
1002 OUString relationPath = externalDataPath;
1003 // Converting absolute path to relative path.
1004 if( externalDataPath[ 0 ] != '.' && externalDataPath[ 1 ] != '.')
1006 sal_Int32 nSepPos = externalDataPath.indexOf( '/', 0 );
1007 if( nSepPos > 0)
1009 relationPath = relationPath.copy( nSepPos, ::std::max< sal_Int32 >( externalDataPath.getLength(), 0 ) - nSepPos );
1010 relationPath = ".." + relationPath;
1013 FSHelperPtr pFS = GetFS();
1014 OUString type = oox::getRelationship(Relationship::PACKAGE);
1015 if (relationPath.endsWith(".bin"))
1016 type = oox::getRelationship(Relationship::OLEOBJECT);
1018 OUString sRelId = GetFB()->addRelation(pFS->getOutputStream(),
1019 type,
1020 relationPath);
1021 pFS->singleElementNS(XML_c, XML_externalData, FSNS(XML_r, XML_id), sRelId);
1024 void ChartExport::exportAdditionalShapes( const Reference< css::chart::XChartDocument >& xChartDoc )
1026 Reference< beans::XPropertySet > xDocPropSet(xChartDoc, uno::UNO_QUERY);
1027 if (!xDocPropSet.is())
1028 return;
1030 css::uno::Reference< css::drawing::XShapes > mxAdditionalShapes;
1031 // get a sequence of non-chart shapes
1034 Any aShapesAny = xDocPropSet->getPropertyValue("AdditionalShapes");
1035 if( (aShapesAny >>= mxAdditionalShapes) && mxAdditionalShapes.is() )
1037 OUString sId;
1038 const char* sFullPath = nullptr;
1039 const char* sRelativePath = nullptr;
1040 sal_Int32 nDrawing = getNewDrawingUniqueId();
1042 switch (GetDocumentType())
1044 case DOCUMENT_DOCX:
1046 sFullPath = "word/drawings/drawing";
1047 sRelativePath = "../drawings/drawing";
1048 break;
1050 case DOCUMENT_PPTX:
1052 sFullPath = "ppt/drawings/drawing";
1053 sRelativePath = "../drawings/drawing";
1054 break;
1056 case DOCUMENT_XLSX:
1058 sFullPath = "xl/drawings/drawing";
1059 sRelativePath = "../drawings/drawing";
1060 break;
1062 default:
1064 sFullPath = "drawings/drawing";
1065 sRelativePath = "drawings/drawing";
1066 break;
1069 OUString sFullStream = OUStringBuffer()
1070 .appendAscii(sFullPath)
1071 .append(OUString::number(nDrawing) + ".xml")
1072 .makeStringAndClear();
1073 OUString sRelativeStream = OUStringBuffer()
1074 .appendAscii(sRelativePath)
1075 .append(OUString::number(nDrawing) + ".xml")
1076 .makeStringAndClear();
1078 sax_fastparser::FSHelperPtr pDrawing = CreateOutputStream(
1079 sFullStream,
1080 sRelativeStream,
1081 GetFS()->getOutputStream(),
1082 "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml",
1083 oox::getRelationship(Relationship::CHARTUSERSHAPES),
1084 &sId);
1086 GetFS()->singleElementNS(XML_c, XML_userShapes, FSNS(XML_r, XML_id), sId);
1088 XmlFilterBase* pFB = GetFB();
1089 pDrawing->startElement(FSNS(XML_c, XML_userShapes),
1090 FSNS(XML_xmlns, XML_cdr), pFB->getNamespaceURL(OOX_NS(dmlChartDr)),
1091 FSNS(XML_xmlns, XML_a), pFB->getNamespaceURL(OOX_NS(dml)),
1092 FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)),
1093 FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)));
1095 const sal_Int32 nShapeCount(mxAdditionalShapes->getCount());
1096 for (sal_Int32 nShapeId = 0; nShapeId < nShapeCount; nShapeId++)
1098 Reference< drawing::XShape > xShape;
1099 mxAdditionalShapes->getByIndex(nShapeId) >>= xShape;
1100 SAL_WARN_IF(!xShape.is(), "xmloff.chart", "Shape without an XShape?");
1101 if (!xShape.is())
1102 continue;
1104 // TODO: absSizeAnchor: we import both (absSizeAnchor and relSizeAnchor), but there is no essential difference between them.
1105 pDrawing->startElement(FSNS(XML_cdr, XML_relSizeAnchor));
1106 uno::Reference< beans::XPropertySet > xShapeProperties(xShape, uno::UNO_QUERY);
1107 if( xShapeProperties.is() )
1109 Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY);
1110 awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT);
1111 WriteFromTo( xShape, aPageSize, pDrawing );
1113 ShapeExport aExport(XML_cdr, pDrawing, nullptr, GetFB(), GetDocumentType(), nullptr, true);
1114 aExport.WriteShape(xShape);
1116 pDrawing->endElement(FSNS(XML_cdr, XML_relSizeAnchor));
1118 pDrawing->endElement(FSNS(XML_c, XML_userShapes));
1119 pDrawing->endDocument();
1122 catch (const uno::Exception&)
1124 TOOLS_INFO_EXCEPTION("xmloff.chart", "AdditionalShapes not found");
1128 void ChartExport::exportChart( const Reference< css::chart::XChartDocument >& xChartDoc )
1130 Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
1131 mxDiagram.set( xChartDoc->getDiagram() );
1132 if( xNewDoc.is())
1133 mxNewDiagram.set( xNewDoc->getFirstDiagram());
1135 // get Properties of ChartDocument
1136 bool bHasMainTitle = false;
1137 OUString aSubTitle;
1138 bool bHasLegend = false;
1139 Reference< beans::XPropertySet > xDocPropSet( xChartDoc, uno::UNO_QUERY );
1140 if( xDocPropSet.is())
1144 Any aAny( xDocPropSet->getPropertyValue("HasMainTitle"));
1145 aAny >>= bHasMainTitle;
1146 aAny = xDocPropSet->getPropertyValue("HasLegend");
1147 aAny >>= bHasLegend;
1149 catch( beans::UnknownPropertyException & )
1151 SAL_WARN("oox", "Required property not found in ChartDocument");
1153 } // if( xDocPropSet.is())
1155 Reference< beans::XPropertySet > xPropSubTitle( xChartDoc->getSubTitle(), UNO_QUERY );
1156 if( xPropSubTitle.is())
1160 xPropSubTitle->getPropertyValue("String") >>= aSubTitle;
1162 catch( beans::UnknownPropertyException & )
1167 // chart element
1168 FSHelperPtr pFS = GetFS();
1169 pFS->startElement(FSNS(XML_c, XML_chart));
1171 // titles
1172 if( bHasMainTitle )
1174 exportTitle( xChartDoc->getTitle(), !aSubTitle.isEmpty() ? &aSubTitle : nullptr );
1175 pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0");
1177 else if( !aSubTitle.isEmpty() )
1179 exportTitle( xChartDoc->getSubTitle(), nullptr );
1180 pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0");
1182 else
1184 pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "1");
1187 InitPlotArea( );
1188 if( mbIs3DChart )
1190 exportView3D();
1192 // floor
1193 Reference< beans::XPropertySet > xFloor = mxNewDiagram->getFloor();
1194 if( xFloor.is() )
1196 pFS->startElement(FSNS(XML_c, XML_floor));
1197 exportShapeProps( xFloor );
1198 pFS->endElement( FSNS( XML_c, XML_floor ) );
1201 // LibreOffice doesn't distinguish between sideWall and backWall (both are using the same color).
1202 // It is controlled by the same Wall property.
1203 Reference< beans::XPropertySet > xWall = mxNewDiagram->getWall();
1204 if( xWall.is() )
1206 // sideWall
1207 pFS->startElement(FSNS(XML_c, XML_sideWall));
1208 exportShapeProps( xWall );
1209 pFS->endElement( FSNS( XML_c, XML_sideWall ) );
1211 // backWall
1212 pFS->startElement(FSNS(XML_c, XML_backWall));
1213 exportShapeProps( xWall );
1214 pFS->endElement( FSNS( XML_c, XML_backWall ) );
1218 // plot area
1219 exportPlotArea( xChartDoc );
1220 // legend
1221 if( bHasLegend )
1222 exportLegend( xChartDoc );
1224 uno::Reference<beans::XPropertySet> xDiagramPropSet(xChartDoc->getDiagram(), uno::UNO_QUERY);
1225 uno::Any aPlotVisOnly = xDiagramPropSet->getPropertyValue("IncludeHiddenCells");
1226 bool bIncludeHiddenCells = false;
1227 aPlotVisOnly >>= bIncludeHiddenCells;
1228 pFS->singleElement(FSNS(XML_c, XML_plotVisOnly), XML_val, ToPsz10(!bIncludeHiddenCells));
1230 exportMissingValueTreatment(Reference<beans::XPropertySet>(mxDiagram, uno::UNO_QUERY));
1232 pFS->endElement( FSNS( XML_c, XML_chart ) );
1235 void ChartExport::exportMissingValueTreatment(const uno::Reference<beans::XPropertySet>& xPropSet)
1237 if (!xPropSet.is())
1238 return;
1240 sal_Int32 nVal = 0;
1241 uno::Any aAny = xPropSet->getPropertyValue("MissingValueTreatment");
1242 if (!(aAny >>= nVal))
1243 return;
1245 const char* pVal = nullptr;
1246 switch (nVal)
1248 case cssc::MissingValueTreatment::LEAVE_GAP:
1249 pVal = "gap";
1250 break;
1251 case cssc::MissingValueTreatment::USE_ZERO:
1252 pVal = "zero";
1253 break;
1254 case cssc::MissingValueTreatment::CONTINUE:
1255 pVal = "span";
1256 break;
1257 default:
1258 SAL_WARN("oox", "unknown MissingValueTreatment value");
1259 break;
1262 FSHelperPtr pFS = GetFS();
1263 pFS->singleElement(FSNS(XML_c, XML_dispBlanksAs), XML_val, pVal);
1266 void ChartExport::exportLegend( const Reference< css::chart::XChartDocument >& xChartDoc )
1268 FSHelperPtr pFS = GetFS();
1269 pFS->startElement(FSNS(XML_c, XML_legend));
1271 Reference< beans::XPropertySet > xProp( xChartDoc->getLegend(), uno::UNO_QUERY );
1272 if( xProp.is() )
1274 // position
1275 css::chart::ChartLegendPosition aLegendPos = css::chart::ChartLegendPosition_NONE;
1278 Any aAny( xProp->getPropertyValue( "Alignment" ));
1279 aAny >>= aLegendPos;
1281 catch( beans::UnknownPropertyException & )
1283 SAL_WARN("oox", "Property Align not found in ChartLegend");
1286 const char* strPos = nullptr;
1287 switch( aLegendPos )
1289 case css::chart::ChartLegendPosition_LEFT:
1290 strPos = "l";
1291 break;
1292 case css::chart::ChartLegendPosition_RIGHT:
1293 strPos = "r";
1294 break;
1295 case css::chart::ChartLegendPosition_TOP:
1296 strPos = "t";
1297 break;
1298 case css::chart::ChartLegendPosition_BOTTOM:
1299 strPos = "b";
1300 break;
1301 case css::chart::ChartLegendPosition_NONE:
1302 case css::chart::ChartLegendPosition::ChartLegendPosition_MAKE_FIXED_SIZE:
1303 // nothing
1304 break;
1307 if( strPos != nullptr )
1309 pFS->singleElement(FSNS(XML_c, XML_legendPos), XML_val, strPos);
1312 // legendEntry
1313 Reference<chart2::XCoordinateSystemContainer> xCooSysContainer(mxNewDiagram, UNO_QUERY_THROW);
1314 const Sequence<Reference<chart2::XCoordinateSystem>> xCooSysSequence(xCooSysContainer->getCoordinateSystems());
1316 sal_Int32 nIndex = 0;
1317 bool bShowLegendEntry;
1318 for (const auto& rCooSys : xCooSysSequence)
1320 PropertySet aCooSysProp(rCooSys);
1321 bool bSwapXAndY = aCooSysProp.getBoolProperty(PROP_SwapXAndYAxis);
1323 Reference<chart2::XChartTypeContainer> xChartTypeContainer(rCooSys, UNO_QUERY_THROW);
1324 const Sequence<Reference<chart2::XChartType>> xChartTypeSequence(xChartTypeContainer->getChartTypes());
1325 if (!xChartTypeSequence.hasElements())
1326 continue;
1328 for (const auto& rCT : xChartTypeSequence)
1330 Reference<chart2::XDataSeriesContainer> xDSCont(rCT, UNO_QUERY);
1331 if (!xDSCont.is())
1332 continue;
1334 OUString aChartType(rCT->getChartType());
1335 bool bIsPie = lcl_getChartType(aChartType) == chart::TYPEID_PIE;
1336 if (bIsPie)
1338 PropertySet xChartTypeProp(rCT);
1339 bIsPie = !xChartTypeProp.getBoolProperty(PROP_UseRings);
1341 const Sequence<Reference<chart2::XDataSeries>> aDataSeriesSeq = xDSCont->getDataSeries();
1342 if (bSwapXAndY)
1343 nIndex += aDataSeriesSeq.getLength() - 1;
1344 for (const auto& rDataSeries : aDataSeriesSeq)
1346 PropertySet aSeriesProp(rDataSeries);
1347 bool bVaryColorsByPoint = aSeriesProp.getBoolProperty(PROP_VaryColorsByPoint);
1348 if (bVaryColorsByPoint || bIsPie)
1350 Sequence<sal_Int32> deletedLegendEntriesSeq;
1351 aSeriesProp.getProperty(deletedLegendEntriesSeq, PROP_DeletedLegendEntries);
1352 for (const auto& deletedLegendEntry : deletedLegendEntriesSeq)
1354 pFS->startElement(FSNS(XML_c, XML_legendEntry));
1355 pFS->singleElement(FSNS(XML_c, XML_idx), XML_val,
1356 OString::number(nIndex + deletedLegendEntry));
1357 pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, "1");
1358 pFS->endElement(FSNS(XML_c, XML_legendEntry));
1360 Reference<chart2::data::XDataSource> xDSrc(rDataSeries, UNO_QUERY);
1361 if (!xDSrc.is())
1362 continue;
1364 const Sequence<Reference<chart2::data::XLabeledDataSequence>> aDataSeqs = xDSrc->getDataSequences();
1365 for (const auto& rDataSeq : aDataSeqs)
1367 Reference<chart2::data::XDataSequence> xValues = rDataSeq->getValues();
1368 if (!xValues.is())
1369 continue;
1371 sal_Int32 nDataSeqSize = xValues->getData().getLength();
1372 nIndex += nDataSeqSize;
1375 else
1377 bShowLegendEntry = aSeriesProp.getBoolProperty(PROP_ShowLegendEntry);
1378 if (!bShowLegendEntry)
1380 pFS->startElement(FSNS(XML_c, XML_legendEntry));
1381 pFS->singleElement(FSNS(XML_c, XML_idx), XML_val,
1382 OString::number(nIndex));
1383 pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, "1");
1384 pFS->endElement(FSNS(XML_c, XML_legendEntry));
1386 bSwapXAndY ? nIndex-- : nIndex++;
1389 if (bSwapXAndY)
1390 nIndex += aDataSeriesSeq.getLength() + 1;
1394 uno::Any aRelativePos = xProp->getPropertyValue("RelativePosition");
1395 if (aRelativePos.hasValue())
1397 pFS->startElement(FSNS(XML_c, XML_layout));
1398 pFS->startElement(FSNS(XML_c, XML_manualLayout));
1400 pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
1401 pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
1402 chart2::RelativePosition aPos = aRelativePos.get<chart2::RelativePosition>();
1404 const double x = aPos.Primary;
1405 const double y = aPos.Secondary;
1407 pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
1408 pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
1410 uno::Any aRelativeSize = xProp->getPropertyValue("RelativeSize");
1411 if (aRelativeSize.hasValue())
1413 chart2::RelativeSize aSize = aRelativeSize.get<chart2::RelativeSize>();
1415 const double w = aSize.Primary;
1416 const double h = aSize.Secondary;
1418 pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w));
1420 pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h));
1423 SAL_WARN_IF(aPos.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position");
1425 pFS->endElement(FSNS(XML_c, XML_manualLayout));
1426 pFS->endElement(FSNS(XML_c, XML_layout));
1429 if (strPos != nullptr)
1431 uno::Any aOverlay = xProp->getPropertyValue("Overlay");
1432 if(aOverlay.get<bool>())
1433 pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "1");
1434 else
1435 pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0");
1438 // shape properties
1439 exportShapeProps( xProp );
1441 // draw-chart:txPr text properties
1442 exportTextProps( xProp );
1445 pFS->endElement( FSNS( XML_c, XML_legend ) );
1448 void ChartExport::exportTitle( const Reference< XShape >& xShape, const OUString* pSubText)
1450 OUString sText;
1451 Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
1452 if( xPropSet.is())
1454 xPropSet->getPropertyValue("String") >>= sText;
1457 // tdf#101322: add subtitle to title
1458 if( pSubText )
1459 sText = sText.isEmpty() ? *pSubText : sText + "\n" + *pSubText;
1461 if( sText.isEmpty() )
1462 return;
1464 FSHelperPtr pFS = GetFS();
1465 pFS->startElement(FSNS(XML_c, XML_title));
1467 pFS->startElement(FSNS(XML_c, XML_tx));
1468 pFS->startElement(FSNS(XML_c, XML_rich));
1470 // TODO: bodyPr
1471 const char* sWritingMode = nullptr;
1472 bool bVertical = false;
1473 xPropSet->getPropertyValue("StackedText") >>= bVertical;
1474 if( bVertical )
1475 sWritingMode = "wordArtVert";
1477 sal_Int32 nRotation = 0;
1478 xPropSet->getPropertyValue("TextRotation") >>= nRotation;
1480 pFS->singleElement( FSNS( XML_a, XML_bodyPr ),
1481 XML_vert, sWritingMode,
1482 XML_rot, oox::drawingml::calcRotationValue(nRotation) );
1483 // TODO: lstStyle
1484 pFS->singleElement(FSNS(XML_a, XML_lstStyle));
1485 // FIXME: handle multiple paragraphs to parse aText
1486 pFS->startElement(FSNS(XML_a, XML_p));
1488 pFS->startElement(FSNS(XML_a, XML_pPr));
1490 bool bDummy = false;
1491 sal_Int32 nDummy;
1492 WriteRunProperties(xPropSet, false, XML_defRPr, true, bDummy, nDummy );
1494 pFS->endElement( FSNS( XML_a, XML_pPr ) );
1496 pFS->startElement(FSNS(XML_a, XML_r));
1497 bDummy = false;
1498 WriteRunProperties( xPropSet, false, XML_rPr, true, bDummy, nDummy );
1499 pFS->startElement(FSNS(XML_a, XML_t));
1500 pFS->writeEscaped( sText );
1501 pFS->endElement( FSNS( XML_a, XML_t ) );
1502 pFS->endElement( FSNS( XML_a, XML_r ) );
1504 pFS->endElement( FSNS( XML_a, XML_p ) );
1506 pFS->endElement( FSNS( XML_c, XML_rich ) );
1507 pFS->endElement( FSNS( XML_c, XML_tx ) );
1509 uno::Any aManualLayout = xPropSet->getPropertyValue("RelativePosition");
1510 if (aManualLayout.hasValue())
1512 pFS->startElement(FSNS(XML_c, XML_layout));
1513 pFS->startElement(FSNS(XML_c, XML_manualLayout));
1514 pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
1515 pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
1517 Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY);
1518 awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT);
1520 awt::Size aSize = xShape->getSize();
1521 awt::Point aPos2 = xShape->getPosition();
1522 // rotated shapes need special handling...
1523 double fSin = fabs(sin(basegfx::deg2rad<100>(nRotation)));
1524 // remove part of height from X direction, if title is rotated down
1525 if( nRotation*0.01 > 180.0 )
1526 aPos2.X -= static_cast<sal_Int32>(fSin * aSize.Height + 0.5);
1527 // remove part of width from Y direction, if title is rotated up
1528 else if( nRotation*0.01 > 0.0 )
1529 aPos2.Y -= static_cast<sal_Int32>(fSin * aSize.Width + 0.5);
1531 double x = static_cast<double>(aPos2.X) / static_cast<double>(aPageSize.Width);
1532 double y = static_cast<double>(aPos2.Y) / static_cast<double>(aPageSize.Height);
1534 pFS->singleElement(FSNS(XML_c, XML_wMode), XML_val, "edge");
1535 pFS->singleElement(FSNS(XML_c, XML_hMode), XML_val, "edge");
1537 pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
1538 pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
1540 pFS->singleElement(FSNS(XML_c, XML_w), XML_val, "");
1541 pFS->singleElement(FSNS(XML_c, XML_h), XML_val, "");
1543 pFS->endElement(FSNS(XML_c, XML_manualLayout));
1544 pFS->endElement(FSNS(XML_c, XML_layout));
1547 pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0");
1549 // shape properties
1550 if( xPropSet.is() )
1552 exportShapeProps( xPropSet );
1555 pFS->endElement( FSNS( XML_c, XML_title ) );
1558 namespace {
1560 std::vector<Sequence<Reference<chart2::XDataSeries> > > splitDataSeriesByAxis(const Reference< chart2::XChartType >& xChartType)
1562 std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitSeries;
1563 std::map<sal_Int32, size_t> aMapAxisToIndex;
1565 Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY);
1566 if (xDSCnt.is())
1568 sal_Int32 nAxisIndexOfFirstSeries = -1;
1569 const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries());
1570 for (const uno::Reference<chart2::XDataSeries>& xSeries : aSeriesSeq)
1572 Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
1573 if (!xPropSet.is())
1574 continue;
1576 sal_Int32 nAxisIndex = -1;
1577 uno::Any aAny = xPropSet->getPropertyValue("AttachedAxisIndex");
1578 aAny >>= nAxisIndex;
1579 size_t nVectorPos = 0;
1580 if (nAxisIndexOfFirstSeries == -1)
1582 nAxisIndexOfFirstSeries = nAxisIndex;
1585 auto it = aMapAxisToIndex.find(nAxisIndex);
1586 if (it == aMapAxisToIndex.end())
1588 aSplitSeries.emplace_back();
1589 nVectorPos = aSplitSeries.size() - 1;
1590 aMapAxisToIndex.insert(std::pair<sal_Int32, size_t>(nAxisIndex, nVectorPos));
1592 else
1594 nVectorPos = it->second;
1597 uno::Sequence<Reference<chart2::XDataSeries> >& rAxisSeriesSeq = aSplitSeries[nVectorPos];
1598 sal_Int32 nLength = rAxisSeriesSeq.getLength();
1599 rAxisSeriesSeq.realloc(nLength + 1);
1600 rAxisSeriesSeq.getArray()[nLength] = xSeries;
1602 // if the first series attached to secondary axis, then export those series first, which are attached to primary axis
1603 // also the MS Office export every time in this order
1604 if (aSplitSeries.size() > 1 && nAxisIndexOfFirstSeries == 1)
1606 std::swap(aSplitSeries[0], aSplitSeries[1]);
1610 return aSplitSeries;
1615 void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >& xChartDoc)
1617 Reference< chart2::XCoordinateSystemContainer > xBCooSysCnt( mxNewDiagram, uno::UNO_QUERY );
1618 if( ! xBCooSysCnt.is())
1619 return;
1621 // plot-area element
1623 FSHelperPtr pFS = GetFS();
1624 pFS->startElement(FSNS(XML_c, XML_plotArea));
1626 Reference<beans::XPropertySet> xWall(mxNewDiagram, uno::UNO_QUERY);
1627 if( xWall.is() )
1629 uno::Any aAny = xWall->getPropertyValue("RelativePosition");
1630 if (aAny.hasValue())
1632 chart2::RelativePosition aPos = aAny.get<chart2::RelativePosition>();
1633 aAny = xWall->getPropertyValue("RelativeSize");
1634 chart2::RelativeSize aSize = aAny.get<chart2::RelativeSize>();
1635 uno::Reference< css::chart::XDiagramPositioning > xDiagramPositioning( xChartDoc->getDiagram(), uno::UNO_QUERY );
1636 exportManualLayout(aPos, aSize, xDiagramPositioning->isExcludingDiagramPositioning() );
1640 // chart type
1641 const Sequence< Reference< chart2::XCoordinateSystem > >
1642 aCooSysSeq( xBCooSysCnt->getCoordinateSystems());
1644 // tdf#123647 Save empty chart as empty bar chart.
1645 if (!aCooSysSeq.hasElements())
1647 pFS->startElement(FSNS(XML_c, XML_barChart));
1648 pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, "col");
1649 pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, "clustered");
1650 pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0");
1651 exportAxesId(true);
1652 pFS->endElement(FSNS(XML_c, XML_barChart));
1655 for( const auto& rCS : aCooSysSeq )
1657 Reference< chart2::XChartTypeContainer > xCTCnt( rCS, uno::UNO_QUERY );
1658 if( ! xCTCnt.is())
1659 continue;
1660 mnSeriesCount=0;
1661 const Sequence< Reference< chart2::XChartType > > aCTSeq( xCTCnt->getChartTypes());
1662 for( const auto& rCT : aCTSeq )
1664 Reference< chart2::XDataSeriesContainer > xDSCnt( rCT, uno::UNO_QUERY );
1665 if( ! xDSCnt.is())
1666 return;
1667 Reference< chart2::XChartType > xChartType( rCT, uno::UNO_QUERY );
1668 if( ! xChartType.is())
1669 continue;
1670 // note: if xDSCnt.is() then also aCTSeq[nCTIdx]
1671 OUString aChartType( xChartType->getChartType());
1672 sal_Int32 eChartType = lcl_getChartType( aChartType );
1673 switch( eChartType )
1675 case chart::TYPEID_BAR:
1677 exportBarChart( xChartType );
1678 break;
1680 case chart::TYPEID_AREA:
1682 exportAreaChart( xChartType );
1683 break;
1685 case chart::TYPEID_LINE:
1687 exportLineChart( xChartType );
1688 break;
1690 case chart::TYPEID_BUBBLE:
1692 exportBubbleChart( xChartType );
1693 break;
1695 case chart::TYPEID_DOUGHNUT: // doesn't currently happen
1696 case chart::TYPEID_OFPIE: // doesn't currently happen
1697 case chart::TYPEID_PIE:
1699 sal_Int32 eCT = getChartType( );
1700 if(eCT == chart::TYPEID_DOUGHNUT)
1702 exportDoughnutChart( xChartType );
1704 else
1707 PropertySet xChartTypeProp(rCT);
1708 chart2::PieChartSubType subtype(chart2::PieChartSubType_NONE);
1709 if (!xChartTypeProp.getProperty(subtype, PROP_SubPieType))
1711 subtype = chart2::PieChartSubType_NONE;
1713 if (subtype != chart2::PieChartSubType_NONE)
1715 const char* sSubType = "pie"; // default
1716 switch (subtype) {
1717 case chart2::PieChartSubType_PIE:
1718 sSubType = "pie";
1719 break;
1720 case chart2::PieChartSubType_BAR:
1721 sSubType = "bar";
1722 break;
1723 default:
1724 assert(false);
1727 exportOfPieChart(xChartType, sSubType);
1728 } else {
1729 exportPieChart( xChartType );
1732 break;
1734 case chart::TYPEID_RADARLINE:
1735 case chart::TYPEID_RADARAREA:
1737 exportRadarChart( xChartType );
1738 break;
1740 case chart::TYPEID_SCATTER:
1742 exportScatterChart( xChartType );
1743 break;
1745 case chart::TYPEID_STOCK:
1747 exportStockChart( xChartType );
1748 break;
1750 case chart::TYPEID_SURFACE:
1752 exportSurfaceChart( xChartType );
1753 break;
1755 default:
1757 SAL_WARN("oox", "ChartExport::exportPlotArea -- not support chart type");
1758 break;
1764 //Axis Data
1765 exportAxes( );
1767 // Data Table
1768 exportDataTable();
1770 // shape properties
1772 * Export the Plot area Shape Properties
1773 * eg: Fill and Outline
1775 Reference< css::chart::X3DDisplay > xWallFloorSupplier( mxDiagram, uno::UNO_QUERY );
1776 // tdf#114139 For 2D charts Plot Area equivalent is Chart Wall.
1777 // Unfortunately LibreOffice doesn't have Plot Area equivalent for 3D charts.
1778 // It means that Plot Area couldn't be displayed and changed for 3D chars in LibreOffice.
1779 // We cannot write Wall attributes into Plot Area for 3D charts, because Wall us used as background wall.
1780 if( !mbIs3DChart && xWallFloorSupplier.is() )
1782 Reference< beans::XPropertySet > xWallPropSet = xWallFloorSupplier->getWall();
1783 if( xWallPropSet.is() )
1785 uno::Any aAny = xWallPropSet->getPropertyValue("LineStyle");
1786 sal_Int32 eChartType = getChartType( );
1787 // Export LineStyle_NONE instead of default linestyle of PlotArea border, because LibreOffice
1788 // make invisible the Wall shape properties, in case of these charts. Or in the future set
1789 // the default LineStyle of these charts to LineStyle_NONE.
1790 bool noSupportWallProp = ( (eChartType == chart::TYPEID_PIE) || (eChartType == chart::TYPEID_RADARLINE) || (eChartType == chart::TYPEID_RADARAREA) );
1791 if ( noSupportWallProp && (aAny != drawing::LineStyle_NONE) )
1793 xWallPropSet->setPropertyValue( "LineStyle", uno::Any(drawing::LineStyle_NONE) );
1795 exportShapeProps( xWallPropSet );
1799 pFS->endElement( FSNS( XML_c, XML_plotArea ) );
1803 void ChartExport::exportManualLayout(const css::chart2::RelativePosition& rPos,
1804 const css::chart2::RelativeSize& rSize,
1805 const bool bIsExcludingDiagramPositioning)
1807 FSHelperPtr pFS = GetFS();
1808 pFS->startElement(FSNS(XML_c, XML_layout));
1809 pFS->startElement(FSNS(XML_c, XML_manualLayout));
1811 // By default layoutTarget is set to "outer" and we shouldn't save it in that case
1812 if ( bIsExcludingDiagramPositioning )
1814 pFS->singleElement(FSNS(XML_c, XML_layoutTarget), XML_val, "inner");
1816 pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
1817 pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
1819 double x = rPos.Primary;
1820 double y = rPos.Secondary;
1821 const double w = rSize.Primary;
1822 const double h = rSize.Secondary;
1823 switch (rPos.Anchor)
1825 case drawing::Alignment_LEFT:
1826 y -= (h/2);
1827 break;
1828 case drawing::Alignment_TOP_LEFT:
1829 break;
1830 case drawing::Alignment_BOTTOM_LEFT:
1831 y -= h;
1832 break;
1833 case drawing::Alignment_TOP:
1834 x -= (w/2);
1835 break;
1836 case drawing::Alignment_CENTER:
1837 x -= (w/2);
1838 y -= (h/2);
1839 break;
1840 case drawing::Alignment_BOTTOM:
1841 x -= (w/2);
1842 y -= h;
1843 break;
1844 case drawing::Alignment_TOP_RIGHT:
1845 x -= w;
1846 break;
1847 case drawing::Alignment_BOTTOM_RIGHT:
1848 x -= w;
1849 y -= h;
1850 break;
1851 case drawing::Alignment_RIGHT:
1852 y -= (h/2);
1853 x -= w;
1854 break;
1855 default:
1856 SAL_WARN("oox", "unhandled alignment case for manual layout export " << static_cast<sal_uInt16>(rPos.Anchor));
1859 pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
1861 pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
1863 pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w));
1865 pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h));
1867 pFS->endElement(FSNS(XML_c, XML_manualLayout));
1868 pFS->endElement(FSNS(XML_c, XML_layout));
1871 void ChartExport::exportFill( const Reference< XPropertySet >& xPropSet )
1873 // Similar to DrawingML::WriteFill, but gradient access via name
1874 if (!GetProperty( xPropSet, "FillStyle" ))
1875 return;
1876 FillStyle aFillStyle(FillStyle_NONE);
1877 mAny >>= aFillStyle;
1879 // map full transparent background to no fill
1880 if (aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparence" ))
1882 sal_Int16 nVal = 0;
1883 mAny >>= nVal;
1884 if ( nVal == 100 )
1885 aFillStyle = FillStyle_NONE;
1887 OUString sFillTransparenceGradientName;
1888 if (aFillStyle == FillStyle_SOLID
1889 && GetProperty(xPropSet, "FillTransparenceGradientName") && (mAny >>= sFillTransparenceGradientName)
1890 && !sFillTransparenceGradientName.isEmpty())
1892 awt::Gradient aTransparenceGradient;
1893 uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
1894 uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY);
1895 uno::Any rTransparenceValue = xTransparenceGradient->getByName(sFillTransparenceGradientName);
1896 rTransparenceValue >>= aTransparenceGradient;
1897 if (aTransparenceGradient.StartColor == 0xffffff && aTransparenceGradient.EndColor == 0xffffff)
1898 aFillStyle = FillStyle_NONE;
1900 switch( aFillStyle )
1902 case FillStyle_SOLID:
1903 exportSolidFill(xPropSet);
1904 break;
1905 case FillStyle_GRADIENT :
1906 exportGradientFill( xPropSet );
1907 break;
1908 case FillStyle_BITMAP :
1909 exportBitmapFill( xPropSet );
1910 break;
1911 case FillStyle_HATCH:
1912 exportHatch(xPropSet);
1913 break;
1914 case FillStyle_NONE:
1915 mpFS->singleElementNS(XML_a, XML_noFill);
1916 break;
1917 default:
1922 void ChartExport::exportSolidFill(const Reference< XPropertySet >& xPropSet)
1924 // Similar to DrawingML::WriteSolidFill, but gradient access via name
1925 // and currently no InteropGrabBag
1926 // get fill color
1927 sal_uInt32 nFillColor = 0;
1928 if (!GetProperty(xPropSet, "FillColor") || !(mAny >>= nFillColor))
1929 return;
1931 sal_Int32 nAlpha = MAX_PERCENT;
1932 if (GetProperty( xPropSet, "FillTransparence" ))
1934 sal_Int32 nTransparency = 0;
1935 mAny >>= nTransparency;
1936 // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
1937 nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
1939 // OOXML has no separate transparence gradient but uses transparency in the gradient stops.
1940 // So we merge transparency and color and use gradient fill in such case.
1941 basegfx::BGradient aTransparenceGradient;
1942 bool bNeedGradientFill(false);
1943 OUString sFillTransparenceGradientName;
1945 if (GetProperty(xPropSet, "FillTransparenceGradientName")
1946 && (mAny >>= sFillTransparenceGradientName)
1947 && !sFillTransparenceGradientName.isEmpty())
1949 uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
1950 uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY);
1951 const uno::Any rTransparenceAny = xTransparenceGradient->getByName(sFillTransparenceGradientName);
1953 aTransparenceGradient = model::gradient::getFromAny(rTransparenceAny);
1954 basegfx::BColor aSingleColor;
1955 bNeedGradientFill = !aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor);
1957 if (!bNeedGradientFill)
1959 // Our alpha is a single gray color value.
1960 const sal_uInt8 nRed(aSingleColor.getRed() * 255.0);
1962 // drawingML alpha is a percentage on a 0..100000 scale.
1963 nAlpha = (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
1966 // write XML
1967 if (bNeedGradientFill)
1969 // no longer create copy/PseudoColorGradient, use new API of
1970 // WriteGradientFill to express fix fill color
1971 mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
1972 WriteGradientFill(nullptr, nFillColor, &aTransparenceGradient);
1973 mpFS->endElementNS(XML_a, XML_gradFill);
1975 else
1976 WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
1979 void ChartExport::exportHatch( const Reference< XPropertySet >& xPropSet )
1981 if (!xPropSet.is())
1982 return;
1984 if (GetProperty(xPropSet, "FillHatchName"))
1986 OUString aHatchName;
1987 mAny >>= aHatchName;
1988 uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
1989 uno::Reference< container::XNameAccess > xHatchTable( xFact->createInstance("com.sun.star.drawing.HatchTable"), uno::UNO_QUERY );
1990 uno::Any rValue = xHatchTable->getByName(aHatchName);
1991 css::drawing::Hatch aHatch;
1992 rValue >>= aHatch;
1993 WritePattFill(xPropSet, aHatch);
1998 void ChartExport::exportBitmapFill( const Reference< XPropertySet >& xPropSet )
2000 if( !xPropSet.is() )
2001 return;
2003 OUString sFillBitmapName;
2004 xPropSet->getPropertyValue("FillBitmapName") >>= sFillBitmapName;
2006 uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
2009 uno::Reference< container::XNameAccess > xBitmapTable( xFact->createInstance("com.sun.star.drawing.BitmapTable"), uno::UNO_QUERY );
2010 uno::Any rValue = xBitmapTable->getByName( sFillBitmapName );
2011 if (rValue.has<uno::Reference<awt::XBitmap>>())
2013 uno::Reference<awt::XBitmap> xBitmap = rValue.get<uno::Reference<awt::XBitmap>>();
2014 uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY);
2015 if (xGraphic.is())
2017 WriteXGraphicBlipFill(xPropSet, xGraphic, XML_a, true, true);
2021 catch (const uno::Exception &)
2023 TOOLS_WARN_EXCEPTION("oox", "ChartExport::exportBitmapFill");
2027 void ChartExport::exportGradientFill( const Reference< XPropertySet >& xPropSet )
2029 if( !xPropSet.is() )
2030 return;
2032 OUString sFillGradientName;
2033 xPropSet->getPropertyValue("FillGradientName") >>= sFillGradientName;
2035 uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
2038 uno::Reference< container::XNameAccess > xGradient( xFact->createInstance("com.sun.star.drawing.GradientTable"), uno::UNO_QUERY );
2039 const uno::Any rGradientAny(xGradient->getByName( sFillGradientName ));
2040 const basegfx::BGradient aGradient = model::gradient::getFromAny(rGradientAny);
2041 basegfx::BColor aSingleColor;
2043 if (!aGradient.GetColorStops().isSingleColor(aSingleColor))
2045 basegfx::BGradient aTransparenceGradient;
2046 mpFS->startElementNS(XML_a, XML_gradFill);
2047 OUString sFillTransparenceGradientName;
2049 if( (xPropSet->getPropertyValue("FillTransparenceGradientName") >>= sFillTransparenceGradientName) && !sFillTransparenceGradientName.isEmpty())
2051 uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY);
2052 const uno::Any rTransparenceAny(xTransparenceGradient->getByName(sFillTransparenceGradientName));
2054 aTransparenceGradient = model::gradient::getFromAny(rTransparenceAny);
2056 WriteGradientFill(&aGradient, 0, &aTransparenceGradient);
2058 else if (GetProperty(xPropSet, "FillTransparence") )
2060 // no longer create PseudoTransparencyGradient, use new API of
2061 // WriteGradientFill to express fix transparency
2062 sal_Int32 nTransparency = 0;
2063 mAny >>= nTransparency;
2064 // nTransparency is [0..100]%
2065 WriteGradientFill(&aGradient, 0, nullptr, nTransparency * 0.01);
2067 else
2069 WriteGradientFill(&aGradient, 0, nullptr);
2072 mpFS->endElementNS(XML_a, XML_gradFill);
2075 catch (const uno::Exception &)
2077 TOOLS_INFO_EXCEPTION("oox", "ChartExport::exportGradientFill");
2081 void ChartExport::exportDataTable( )
2083 auto xDataTable = mxNewDiagram->getDataTable();
2084 if (!xDataTable.is())
2085 return;
2087 FSHelperPtr pFS = GetFS();
2088 uno::Reference<beans::XPropertySet> aPropSet(xDataTable, uno::UNO_QUERY);
2090 bool bShowVBorder = false;
2091 bool bShowHBorder = false;
2092 bool bShowOutline = false;
2093 bool bShowKeys = false;
2095 if (GetProperty(aPropSet, "HBorder"))
2096 mAny >>= bShowHBorder;
2097 if (GetProperty(aPropSet, "VBorder"))
2098 mAny >>= bShowVBorder;
2099 if (GetProperty(aPropSet, "Outline"))
2100 mAny >>= bShowOutline;
2101 if (GetProperty(aPropSet, "Keys"))
2102 mAny >>= bShowKeys;
2104 pFS->startElement(FSNS(XML_c, XML_dTable));
2106 if (bShowHBorder)
2107 pFS->singleElement(FSNS(XML_c, XML_showHorzBorder), XML_val, "1" );
2108 if (bShowVBorder)
2109 pFS->singleElement(FSNS(XML_c, XML_showVertBorder), XML_val, "1");
2110 if (bShowOutline)
2111 pFS->singleElement(FSNS(XML_c, XML_showOutline), XML_val, "1");
2112 if (bShowKeys)
2113 pFS->singleElement(FSNS(XML_c, XML_showKeys), XML_val, "1");
2115 exportShapeProps(aPropSet);
2116 exportTextProps(aPropSet);
2118 pFS->endElement(FSNS(XML_c, XML_dTable));
2121 void ChartExport::exportAreaChart( const Reference< chart2::XChartType >& xChartType )
2123 FSHelperPtr pFS = GetFS();
2124 const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
2125 for (const auto& splitDataSeries : aSplitDataSeries)
2127 if (!splitDataSeries.hasElements())
2128 continue;
2130 sal_Int32 nTypeId = XML_areaChart;
2131 if (mbIs3DChart)
2132 nTypeId = XML_area3DChart;
2133 pFS->startElement(FSNS(XML_c, nTypeId));
2135 exportGrouping();
2136 bool bPrimaryAxes = true;
2137 exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
2138 exportAxesId(bPrimaryAxes);
2140 pFS->endElement(FSNS(XML_c, nTypeId));
2144 void ChartExport::exportBarChart(const Reference< chart2::XChartType >& xChartType)
2146 sal_Int32 nTypeId = XML_barChart;
2147 if (mbIs3DChart)
2148 nTypeId = XML_bar3DChart;
2149 FSHelperPtr pFS = GetFS();
2151 const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
2152 for (const auto& splitDataSeries : aSplitDataSeries)
2154 if (!splitDataSeries.hasElements())
2155 continue;
2157 pFS->startElement(FSNS(XML_c, nTypeId));
2158 // bar direction
2159 bool bVertical = false;
2160 Reference< XPropertySet > xPropSet(mxDiagram, uno::UNO_QUERY);
2161 if (GetProperty(xPropSet, "Vertical"))
2162 mAny >>= bVertical;
2164 const char* bardir = bVertical ? "bar" : "col";
2165 pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, bardir);
2167 exportGrouping(true);
2169 exportVaryColors(xChartType);
2171 bool bPrimaryAxes = true;
2172 exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
2174 Reference< XPropertySet > xTypeProp(xChartType, uno::UNO_QUERY);
2176 if (xTypeProp.is() && GetProperty(xTypeProp, "GapwidthSequence"))
2178 uno::Sequence< sal_Int32 > aBarPositionSequence;
2179 mAny >>= aBarPositionSequence;
2180 if (aBarPositionSequence.hasElements())
2182 sal_Int32 nGapWidth = aBarPositionSequence[0];
2183 pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(nGapWidth));
2187 if (mbIs3DChart)
2189 // Shape
2190 namespace cssc = css::chart;
2191 sal_Int32 nGeom3d = cssc::ChartSolidType::RECTANGULAR_SOLID;
2192 if (xPropSet.is() && GetProperty(xPropSet, "SolidType"))
2193 mAny >>= nGeom3d;
2194 const char* sShapeType = nullptr;
2195 switch (nGeom3d)
2197 case cssc::ChartSolidType::RECTANGULAR_SOLID:
2198 sShapeType = "box";
2199 break;
2200 case cssc::ChartSolidType::CONE:
2201 sShapeType = "cone";
2202 break;
2203 case cssc::ChartSolidType::CYLINDER:
2204 sShapeType = "cylinder";
2205 break;
2206 case cssc::ChartSolidType::PYRAMID:
2207 sShapeType = "pyramid";
2208 break;
2210 pFS->singleElement(FSNS(XML_c, XML_shape), XML_val, sShapeType);
2213 //overlap
2214 if (!mbIs3DChart && xTypeProp.is() && GetProperty(xTypeProp, "OverlapSequence"))
2216 uno::Sequence< sal_Int32 > aBarPositionSequence;
2217 mAny >>= aBarPositionSequence;
2218 if (aBarPositionSequence.hasElements())
2220 sal_Int32 nOverlap = aBarPositionSequence[0];
2221 // Stacked/Percent Bar/Column chart Overlap-workaround
2222 // Export the Overlap value with 100% for stacked charts,
2223 // because the default overlap value of the Bar/Column chart is 0% and
2224 // LibreOffice do nothing with the overlap value in Stacked charts case,
2225 // unlike the MS Office, which is interpreted differently.
2226 if ((mbStacked || mbPercent) && nOverlap != 100)
2228 nOverlap = 100;
2229 pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap));
2231 else // Normal bar chart
2233 pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap));
2238 exportAxesId(bPrimaryAxes);
2240 pFS->endElement(FSNS(XML_c, nTypeId));
2244 void ChartExport::exportBubbleChart( const Reference< chart2::XChartType >& xChartType )
2246 FSHelperPtr pFS = GetFS();
2247 const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
2248 for (const auto& splitDataSeries : aSplitDataSeries)
2250 if (!splitDataSeries.hasElements())
2251 continue;
2253 pFS->startElement(FSNS(XML_c, XML_bubbleChart));
2255 exportVaryColors(xChartType);
2257 bool bPrimaryAxes = true;
2258 exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
2260 exportAxesId(bPrimaryAxes);
2262 pFS->endElement(FSNS(XML_c, XML_bubbleChart));
2266 void ChartExport::exportDoughnutChart( const Reference< chart2::XChartType >& xChartType )
2268 FSHelperPtr pFS = GetFS();
2269 pFS->startElement(FSNS(XML_c, XML_doughnutChart));
2271 exportVaryColors(xChartType);
2273 bool bPrimaryAxes = true;
2274 exportAllSeries(xChartType, bPrimaryAxes);
2275 // firstSliceAng
2276 exportFirstSliceAng( );
2277 //FIXME: holeSize
2278 pFS->singleElement(FSNS(XML_c, XML_holeSize), XML_val, OString::number(50));
2280 pFS->endElement( FSNS( XML_c, XML_doughnutChart ) );
2283 void ChartExport::exportOfPieChart(
2284 const Reference< chart2::XChartType >& xChartType,
2285 const char* sSubType )
2287 FSHelperPtr pFS = GetFS();
2288 pFS->startElement(FSNS(XML_c, XML_ofPieChart));
2290 pFS->singleElement(FSNS(XML_c, XML_ofPieType), XML_val, sSubType);
2292 exportVaryColors(xChartType);
2294 bool bPrimaryAxes = true;
2295 exportAllSeries(xChartType, bPrimaryAxes);
2297 pFS->endElement( FSNS( XML_c, XML_ofPieChart ) );
2300 namespace {
2302 void writeDataLabelsRange(const FSHelperPtr& pFS, const XmlFilterBase* pFB, DataLabelsRange& rDLblsRange)
2304 if (rDLblsRange.empty())
2305 return;
2307 pFS->startElement(FSNS(XML_c, XML_extLst));
2308 pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{02D57815-91ED-43cb-92C2-25804820EDAC}", FSNS(XML_xmlns, XML_c15), pFB->getNamespaceURL(OOX_NS(c15)));
2309 pFS->startElement(FSNS(XML_c15, XML_datalabelsRange));
2311 // Write cell range.
2312 pFS->startElement(FSNS(XML_c15, XML_f));
2313 pFS->writeEscaped(rDLblsRange.getRange());
2314 pFS->endElement(FSNS(XML_c15, XML_f));
2316 // Write all labels.
2317 pFS->startElement(FSNS(XML_c15, XML_dlblRangeCache));
2318 pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(rDLblsRange.count()));
2319 for (const auto& rLabelKV: rDLblsRange)
2321 pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(rLabelKV.first));
2322 pFS->startElement(FSNS(XML_c, XML_v));
2323 pFS->writeEscaped(rLabelKV.second);
2324 pFS->endElement(FSNS( XML_c, XML_v ));
2325 pFS->endElement(FSNS(XML_c, XML_pt));
2328 pFS->endElement(FSNS(XML_c15, XML_dlblRangeCache));
2330 pFS->endElement(FSNS(XML_c15, XML_datalabelsRange));
2331 pFS->endElement(FSNS(XML_c, XML_ext));
2332 pFS->endElement(FSNS(XML_c, XML_extLst));
2337 void ChartExport::exportLineChart( const Reference< chart2::XChartType >& xChartType )
2339 FSHelperPtr pFS = GetFS();
2340 const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
2341 for (const auto& splitDataSeries : aSplitDataSeries)
2343 if (!splitDataSeries.hasElements())
2344 continue;
2346 sal_Int32 nTypeId = XML_lineChart;
2347 if( mbIs3DChart )
2348 nTypeId = XML_line3DChart;
2349 pFS->startElement(FSNS(XML_c, nTypeId));
2351 exportGrouping( );
2353 exportVaryColors(xChartType);
2354 // TODO: show marker symbol in series?
2355 bool bPrimaryAxes = true;
2356 exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
2358 // show marker?
2359 sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
2360 Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
2361 if( GetProperty( xPropSet, "SymbolType" ) )
2362 mAny >>= nSymbolType;
2364 if( !mbIs3DChart )
2366 exportHiLowLines();
2367 exportUpDownBars(xChartType);
2368 const char* marker = nSymbolType == css::chart::ChartSymbolType::NONE? "0":"1";
2369 pFS->singleElement(FSNS(XML_c, XML_marker), XML_val, marker);
2372 exportAxesId(bPrimaryAxes, true);
2374 pFS->endElement( FSNS( XML_c, nTypeId ) );
2378 void ChartExport::exportPieChart( const Reference< chart2::XChartType >& xChartType )
2380 FSHelperPtr pFS = GetFS();
2381 sal_Int32 nTypeId = XML_pieChart;
2382 if( mbIs3DChart )
2383 nTypeId = XML_pie3DChart;
2384 pFS->startElement(FSNS(XML_c, nTypeId));
2386 exportVaryColors(xChartType);
2388 bool bPrimaryAxes = true;
2389 exportAllSeries(xChartType, bPrimaryAxes);
2391 if( !mbIs3DChart )
2393 // firstSliceAng
2394 exportFirstSliceAng( );
2397 pFS->endElement( FSNS( XML_c, nTypeId ) );
2400 void ChartExport::exportRadarChart( const Reference< chart2::XChartType >& xChartType)
2402 FSHelperPtr pFS = GetFS();
2403 pFS->startElement(FSNS(XML_c, XML_radarChart));
2405 // radarStyle
2406 sal_Int32 eChartType = getChartType( );
2407 const char* radarStyle = nullptr;
2408 if( eChartType == chart::TYPEID_RADARAREA )
2409 radarStyle = "filled";
2410 else
2411 radarStyle = "marker";
2412 pFS->singleElement(FSNS(XML_c, XML_radarStyle), XML_val, radarStyle);
2414 exportVaryColors(xChartType);
2415 bool bPrimaryAxes = true;
2416 exportAllSeries(xChartType, bPrimaryAxes);
2417 exportAxesId(bPrimaryAxes);
2419 pFS->endElement( FSNS( XML_c, XML_radarChart ) );
2422 void ChartExport::exportScatterChartSeries( const Reference< chart2::XChartType >& xChartType,
2423 const css::uno::Sequence<css::uno::Reference<chart2::XDataSeries>>* pSeries)
2425 FSHelperPtr pFS = GetFS();
2426 pFS->startElement(FSNS(XML_c, XML_scatterChart));
2427 // TODO:scatterStyle
2429 sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
2430 Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
2431 if( GetProperty( xPropSet, "SymbolType" ) )
2432 mAny >>= nSymbolType;
2434 const char* scatterStyle = "lineMarker";
2435 if (nSymbolType == css::chart::ChartSymbolType::NONE)
2437 scatterStyle = "line";
2440 pFS->singleElement(FSNS(XML_c, XML_scatterStyle), XML_val, scatterStyle);
2442 exportVaryColors(xChartType);
2443 // FIXME: should export xVal and yVal
2444 bool bPrimaryAxes = true;
2445 if (pSeries)
2446 exportSeries(xChartType, *pSeries, bPrimaryAxes);
2447 exportAxesId(bPrimaryAxes);
2449 pFS->endElement( FSNS( XML_c, XML_scatterChart ) );
2452 void ChartExport::exportScatterChart( const Reference< chart2::XChartType >& xChartType )
2454 const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
2455 bool bExported = false;
2456 for (const auto& splitDataSeries : aSplitDataSeries)
2458 if (!splitDataSeries.hasElements())
2459 continue;
2461 bExported = true;
2462 exportScatterChartSeries(xChartType, &splitDataSeries);
2464 if (!bExported)
2465 exportScatterChartSeries(xChartType, nullptr);
2468 void ChartExport::exportStockChart( const Reference< chart2::XChartType >& xChartType )
2470 FSHelperPtr pFS = GetFS();
2471 const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
2472 for (const auto& splitDataSeries : aSplitDataSeries)
2474 if (!splitDataSeries.hasElements())
2475 continue;
2477 pFS->startElement(FSNS(XML_c, XML_stockChart));
2479 bool bPrimaryAxes = true;
2480 exportCandleStickSeries(splitDataSeries, bPrimaryAxes);
2482 // export stock properties
2483 Reference< css::chart::XStatisticDisplay > xStockPropProvider(mxDiagram, uno::UNO_QUERY);
2484 if (xStockPropProvider.is())
2486 exportHiLowLines();
2487 exportUpDownBars(xChartType);
2490 exportAxesId(bPrimaryAxes);
2492 pFS->endElement(FSNS(XML_c, XML_stockChart));
2496 void ChartExport::exportHiLowLines()
2498 FSHelperPtr pFS = GetFS();
2499 // export the chart property
2500 Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY );
2502 if (!xChartPropProvider.is())
2503 return;
2505 Reference< beans::XPropertySet > xStockPropSet = xChartPropProvider->getMinMaxLine();
2506 if( !xStockPropSet.is() )
2507 return;
2509 pFS->startElement(FSNS(XML_c, XML_hiLowLines));
2510 exportShapeProps( xStockPropSet );
2511 pFS->endElement( FSNS( XML_c, XML_hiLowLines ) );
2514 void ChartExport::exportUpDownBars( const Reference< chart2::XChartType >& xChartType)
2516 if(xChartType->getChartType() != "com.sun.star.chart2.CandleStickChartType")
2517 return;
2519 FSHelperPtr pFS = GetFS();
2520 // export the chart property
2521 Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY );
2522 if(!xChartPropProvider.is())
2523 return;
2525 // updownbar
2526 pFS->startElement(FSNS(XML_c, XML_upDownBars));
2527 // TODO: gapWidth
2528 pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(150));
2530 Reference< beans::XPropertySet > xChartPropSet = xChartPropProvider->getUpBar();
2531 if( xChartPropSet.is() )
2533 pFS->startElement(FSNS(XML_c, XML_upBars));
2534 // For Linechart with UpDownBars, spPr is not getting imported
2535 // so no need to call the exportShapeProps() for LineChart
2536 if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType")
2538 exportShapeProps(xChartPropSet);
2540 pFS->endElement( FSNS( XML_c, XML_upBars ) );
2542 xChartPropSet = xChartPropProvider->getDownBar();
2543 if( xChartPropSet.is() )
2545 pFS->startElement(FSNS(XML_c, XML_downBars));
2546 if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType")
2548 exportShapeProps(xChartPropSet);
2550 pFS->endElement( FSNS( XML_c, XML_downBars ) );
2552 pFS->endElement( FSNS( XML_c, XML_upDownBars ) );
2555 void ChartExport::exportSurfaceChart( const Reference< chart2::XChartType >& xChartType )
2557 FSHelperPtr pFS = GetFS();
2558 sal_Int32 nTypeId = XML_surfaceChart;
2559 if( mbIs3DChart )
2560 nTypeId = XML_surface3DChart;
2561 pFS->startElement(FSNS(XML_c, nTypeId));
2562 exportVaryColors(xChartType);
2563 bool bPrimaryAxes = true;
2564 exportAllSeries(xChartType, bPrimaryAxes);
2565 exportAxesId(bPrimaryAxes);
2567 pFS->endElement( FSNS( XML_c, nTypeId ) );
2570 void ChartExport::exportAllSeries(const Reference<chart2::XChartType>& xChartType, bool& rPrimaryAxes)
2572 Reference< chart2::XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY );
2573 if( ! xDSCnt.is())
2574 return;
2576 // export dataseries for current chart-type
2577 Sequence< Reference< chart2::XDataSeries > > aSeriesSeq( xDSCnt->getDataSeries());
2578 exportSeries(xChartType, aSeriesSeq, rPrimaryAxes);
2581 void ChartExport::exportVaryColors(const Reference<chart2::XChartType>& xChartType)
2583 FSHelperPtr pFS = GetFS();
2586 Reference<chart2::XDataSeries> xDataSeries = getPrimaryDataSeries(xChartType);
2587 Reference<beans::XPropertySet> xDataSeriesProps(xDataSeries, uno::UNO_QUERY_THROW);
2588 Any aAnyVaryColors = xDataSeriesProps->getPropertyValue("VaryColorsByPoint");
2589 bool bVaryColors = false;
2590 aAnyVaryColors >>= bVaryColors;
2591 pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, ToPsz10(bVaryColors));
2593 catch (...)
2595 pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0");
2599 void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType,
2600 const Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq, bool& rPrimaryAxes )
2602 OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel();
2603 OUString aChartType( xChartType->getChartType());
2604 sal_Int32 eChartType = lcl_getChartType( aChartType );
2606 for( const auto& rSeries : rSeriesSeq )
2608 // export series
2609 Reference< chart2::data::XDataSource > xSource( rSeries, uno::UNO_QUERY );
2610 if( xSource.is())
2612 Reference< chart2::XDataSeries > xDataSeries( xSource, uno::UNO_QUERY );
2613 Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
2614 xSource->getDataSequences());
2615 // search for main sequence and create a series element
2617 sal_Int32 nMainSequenceIndex = -1;
2618 sal_Int32 nSeriesLength = 0;
2619 Reference< chart2::data::XDataSequence > xValuesSeq;
2620 Reference< chart2::data::XDataSequence > xLabelSeq;
2621 sal_Int32 nSeqIdx=0;
2622 for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx )
2624 Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() );
2625 if( nMainSequenceIndex==-1 )
2627 Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY );
2628 OUString aRole;
2629 if( xSeqProp.is())
2630 xSeqProp->getPropertyValue("Role") >>= aRole;
2631 // "main" sequence
2632 if( aRole == aLabelRole )
2634 xValuesSeq.set( xTempValueSeq );
2635 xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel());
2636 nMainSequenceIndex = nSeqIdx;
2639 sal_Int32 nSequenceLength = (xTempValueSeq.is()? xTempValueSeq->getData().getLength() : sal_Int32(0));
2640 if( nSeriesLength < nSequenceLength )
2641 nSeriesLength = nSequenceLength;
2644 // have found the main sequence, then xValuesSeq and
2645 // xLabelSeq contain those. Otherwise both are empty
2647 FSHelperPtr pFS = GetFS();
2649 pFS->startElement(FSNS(XML_c, XML_ser));
2651 // TODO: idx and order
2652 pFS->singleElement( FSNS( XML_c, XML_idx ),
2653 XML_val, OString::number(mnSeriesCount) );
2654 pFS->singleElement( FSNS( XML_c, XML_order ),
2655 XML_val, OString::number(mnSeriesCount++) );
2657 // export label
2658 if( xLabelSeq.is() )
2659 exportSeriesText( xLabelSeq );
2661 Reference<XPropertySet> xPropSet(xDataSeries, UNO_QUERY_THROW);
2662 if( GetProperty( xPropSet, "AttachedAxisIndex") )
2664 sal_Int32 nLocalAttachedAxis = 0;
2665 mAny >>= nLocalAttachedAxis;
2666 rPrimaryAxes = isPrimaryAxes(nLocalAttachedAxis);
2669 // export shape properties
2670 Reference< XPropertySet > xOldPropSet = SchXMLSeriesHelper::createOldAPISeriesPropertySet(
2671 rSeries, getModel() );
2672 if( xOldPropSet.is() )
2674 exportShapeProps( xOldPropSet );
2677 switch( eChartType )
2679 case chart::TYPEID_BUBBLE:
2680 case chart::TYPEID_HORBAR:
2681 case chart::TYPEID_BAR:
2683 pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0");
2685 break;
2686 case chart::TYPEID_LINE:
2688 exportMarker(xOldPropSet);
2689 break;
2691 case chart::TYPEID_PIE:
2692 case chart::TYPEID_DOUGHNUT:
2694 if( xOldPropSet.is() && GetProperty( xOldPropSet, "SegmentOffset") )
2696 sal_Int32 nOffset = 0;
2697 mAny >>= nOffset;
2698 pFS->singleElement( FSNS( XML_c, XML_explosion ),
2699 XML_val, OString::number( nOffset ) );
2701 break;
2703 case chart::TYPEID_SCATTER:
2705 exportMarker(xOldPropSet);
2706 break;
2708 case chart::TYPEID_RADARLINE:
2710 exportMarker(xOldPropSet);
2711 break;
2715 // export data points
2716 exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType );
2718 DataLabelsRange aDLblsRange;
2719 // export data labels
2720 exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange);
2722 exportTrendlines( rSeries );
2724 if( eChartType != chart::TYPEID_PIE &&
2725 eChartType != chart::TYPEID_RADARLINE )
2727 //export error bars here
2728 Reference< XPropertySet > xSeriesPropSet( xSource, uno::UNO_QUERY );
2729 Reference< XPropertySet > xErrorBarYProps;
2730 xSeriesPropSet->getPropertyValue("ErrorBarY") >>= xErrorBarYProps;
2731 if(xErrorBarYProps.is())
2732 exportErrorBar(xErrorBarYProps, true);
2733 if (eChartType != chart::TYPEID_BAR &&
2734 eChartType != chart::TYPEID_HORBAR)
2736 Reference< XPropertySet > xErrorBarXProps;
2737 xSeriesPropSet->getPropertyValue("ErrorBarX") >>= xErrorBarXProps;
2738 if(xErrorBarXProps.is())
2739 exportErrorBar(xErrorBarXProps, false);
2743 // export categories
2744 if( eChartType != chart::TYPEID_SCATTER && eChartType != chart::TYPEID_BUBBLE && mxCategoriesValues.is() )
2745 exportSeriesCategory( mxCategoriesValues );
2747 if( (eChartType == chart::TYPEID_SCATTER)
2748 || (eChartType == chart::TYPEID_BUBBLE) )
2750 // export xVal
2751 Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, "values-x" ) );
2752 if( xSequence.is() )
2754 Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() );
2755 if( xValues.is() )
2756 exportSeriesValues( xValues, XML_xVal );
2758 else if( mxCategoriesValues.is() )
2759 exportSeriesCategory( mxCategoriesValues, XML_xVal );
2762 if( eChartType == chart::TYPEID_BUBBLE )
2764 // export yVal
2765 Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, "values-y" ) );
2766 if( xSequence.is() )
2768 Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() );
2769 if( xValues.is() )
2770 exportSeriesValues( xValues, XML_yVal );
2774 // export values
2775 if( xValuesSeq.is() )
2777 sal_Int32 nYValueType = XML_val;
2778 if( eChartType == chart::TYPEID_SCATTER )
2779 nYValueType = XML_yVal;
2780 else if( eChartType == chart::TYPEID_BUBBLE )
2781 nYValueType = XML_bubbleSize;
2782 exportSeriesValues( xValuesSeq, nYValueType );
2785 if( eChartType == chart::TYPEID_SCATTER
2786 || eChartType == chart::TYPEID_LINE )
2787 exportSmooth();
2789 // tdf103988: "corrupted" files with Bubble chart opening in MSO
2790 if( eChartType == chart::TYPEID_BUBBLE )
2791 pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0");
2793 if (!aDLblsRange.empty())
2794 writeDataLabelsRange(pFS, GetFB(), aDLblsRange);
2796 pFS->endElement( FSNS( XML_c, XML_ser ) );
2803 void ChartExport::exportCandleStickSeries(
2804 const Sequence< Reference< chart2::XDataSeries > > & aSeriesSeq,
2805 bool& rPrimaryAxes)
2807 for( const Reference< chart2::XDataSeries >& xSeries : aSeriesSeq )
2809 rPrimaryAxes = lcl_isSeriesAttachedToFirstAxis(xSeries);
2811 Reference< chart2::data::XDataSource > xSource( xSeries, uno::UNO_QUERY );
2812 if( xSource.is())
2814 // export series in correct order (as we don't store roles)
2815 // with japanese candlesticks: open, low, high, close
2816 // otherwise: low, high, close
2817 Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
2818 xSource->getDataSequences());
2820 const char* sSeries[] = {"values-first","values-max","values-min","values-last",nullptr};
2822 for( sal_Int32 idx = 0; sSeries[idx] != nullptr ; idx++ )
2824 Reference< chart2::data::XLabeledDataSequence > xLabeledSeq( lcl_getDataSequenceByRole( aSeqCnt, OUString::createFromAscii(sSeries[idx]) ) );
2825 if( xLabeledSeq.is())
2827 Reference< chart2::data::XDataSequence > xLabelSeq( xLabeledSeq->getLabel());
2828 Reference< chart2::data::XDataSequence > xValueSeq( xLabeledSeq->getValues());
2830 FSHelperPtr pFS = GetFS();
2831 pFS->startElement(FSNS(XML_c, XML_ser));
2833 // TODO: idx and order
2834 // idx attribute should start from 1 and not from 0.
2835 pFS->singleElement( FSNS( XML_c, XML_idx ),
2836 XML_val, OString::number(idx+1) );
2837 pFS->singleElement( FSNS( XML_c, XML_order ),
2838 XML_val, OString::number(idx+1) );
2840 // export label
2841 if( xLabelSeq.is() )
2842 exportSeriesText( xLabelSeq );
2844 // TODO:export shape properties
2846 // export categories
2847 if( mxCategoriesValues.is() )
2848 exportSeriesCategory( mxCategoriesValues );
2850 // export values
2851 if( xValueSeq.is() )
2852 exportSeriesValues( xValueSeq );
2854 pFS->endElement( FSNS( XML_c, XML_ser ) );
2862 void ChartExport::exportSeriesText( const Reference< chart2::data::XDataSequence > & xValueSeq )
2864 FSHelperPtr pFS = GetFS();
2865 pFS->startElement(FSNS(XML_c, XML_tx));
2867 OUString aCellRange = xValueSeq->getSourceRangeRepresentation();
2868 aCellRange = parseFormula( aCellRange );
2869 pFS->startElement(FSNS(XML_c, XML_strRef));
2871 pFS->startElement(FSNS(XML_c, XML_f));
2872 pFS->writeEscaped( aCellRange );
2873 pFS->endElement( FSNS( XML_c, XML_f ) );
2875 OUString aLabelString = lcl_flattenStringSequence(lcl_getLabelSequence(xValueSeq));
2876 pFS->startElement(FSNS(XML_c, XML_strCache));
2877 pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, "1");
2878 pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, "0");
2879 pFS->startElement(FSNS(XML_c, XML_v));
2880 pFS->writeEscaped( aLabelString );
2881 pFS->endElement( FSNS( XML_c, XML_v ) );
2882 pFS->endElement( FSNS( XML_c, XML_pt ) );
2883 pFS->endElement( FSNS( XML_c, XML_strCache ) );
2884 pFS->endElement( FSNS( XML_c, XML_strRef ) );
2885 pFS->endElement( FSNS( XML_c, XML_tx ) );
2888 void ChartExport::exportSeriesCategory( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType )
2890 FSHelperPtr pFS = GetFS();
2891 pFS->startElement(FSNS(XML_c, nValueType));
2893 OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
2894 const Sequence< Sequence< OUString >> aFinalSplitSource = (nValueType == XML_cat) ? getSplitCategoriesList(aCellRange) : Sequence< Sequence< OUString>>(0);
2895 aCellRange = parseFormula( aCellRange );
2897 if(aFinalSplitSource.getLength() > 1)
2899 // export multi level category axis labels
2900 pFS->startElement(FSNS(XML_c, XML_multiLvlStrRef));
2902 pFS->startElement(FSNS(XML_c, XML_f));
2903 pFS->writeEscaped(aCellRange);
2904 pFS->endElement(FSNS(XML_c, XML_f));
2906 pFS->startElement(FSNS(XML_c, XML_multiLvlStrCache));
2907 pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(aFinalSplitSource[0].getLength()));
2908 for(const auto& rSeq : aFinalSplitSource)
2910 pFS->startElement(FSNS(XML_c, XML_lvl));
2911 for(sal_Int32 j = 0; j < rSeq.getLength(); j++)
2913 if(!rSeq[j].isEmpty())
2915 pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(j));
2916 pFS->startElement(FSNS(XML_c, XML_v));
2917 pFS->writeEscaped(rSeq[j]);
2918 pFS->endElement(FSNS(XML_c, XML_v));
2919 pFS->endElement(FSNS(XML_c, XML_pt));
2922 pFS->endElement(FSNS(XML_c, XML_lvl));
2925 pFS->endElement(FSNS(XML_c, XML_multiLvlStrCache));
2926 pFS->endElement(FSNS(XML_c, XML_multiLvlStrRef));
2928 else
2930 // export single category axis labels
2931 bool bWriteDateCategories = mbHasDateCategories && (nValueType == XML_cat);
2932 OUString aNumberFormatString;
2933 if (bWriteDateCategories)
2935 Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
2936 if( xAxisXSupp.is())
2938 Reference< XPropertySet > xAxisProp = xAxisXSupp->getXAxis();
2939 if (GetProperty(xAxisProp, "NumberFormat"))
2941 sal_Int32 nKey = 0;
2942 mAny >>= nKey;
2943 aNumberFormatString = getNumberFormatCode(nKey);
2946 if (aNumberFormatString.isEmpty())
2947 bWriteDateCategories = false;
2950 pFS->startElement(FSNS(XML_c, bWriteDateCategories ? XML_numRef : XML_strRef));
2952 pFS->startElement(FSNS(XML_c, XML_f));
2953 pFS->writeEscaped(aCellRange);
2954 pFS->endElement(FSNS(XML_c, XML_f));
2956 ::std::vector< OUString > aCategories;
2957 lcl_fillCategoriesIntoStringVector(xValueSeq, aCategories);
2958 sal_Int32 ptCount = aCategories.size();
2959 pFS->startElement(FSNS(XML_c, bWriteDateCategories ? XML_numCache : XML_strCache));
2960 if (bWriteDateCategories)
2962 pFS->startElement(FSNS(XML_c, XML_formatCode));
2963 pFS->writeEscaped(aNumberFormatString);
2964 pFS->endElement(FSNS(XML_c, XML_formatCode));
2967 pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
2968 for (sal_Int32 i = 0; i < ptCount; i++)
2970 pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
2971 pFS->startElement(FSNS(XML_c, XML_v));
2972 pFS->writeEscaped(aCategories[i]);
2973 pFS->endElement(FSNS(XML_c, XML_v));
2974 pFS->endElement(FSNS(XML_c, XML_pt));
2977 pFS->endElement(FSNS(XML_c, bWriteDateCategories ? XML_numCache : XML_strCache));
2978 pFS->endElement(FSNS(XML_c, bWriteDateCategories ? XML_numRef : XML_strRef));
2981 pFS->endElement( FSNS( XML_c, nValueType ) );
2984 void ChartExport::exportSeriesValues( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType )
2986 FSHelperPtr pFS = GetFS();
2987 pFS->startElement(FSNS(XML_c, nValueType));
2989 OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
2990 aCellRange = parseFormula( aCellRange );
2991 // TODO: need to handle XML_multiLvlStrRef according to aCellRange
2992 pFS->startElement(FSNS(XML_c, XML_numRef));
2994 pFS->startElement(FSNS(XML_c, XML_f));
2995 pFS->writeEscaped( aCellRange );
2996 pFS->endElement( FSNS( XML_c, XML_f ) );
2998 ::std::vector< double > aValues = lcl_getAllValuesFromSequence( xValueSeq );
2999 sal_Int32 ptCount = aValues.size();
3000 pFS->startElement(FSNS(XML_c, XML_numCache));
3001 pFS->startElement(FSNS(XML_c, XML_formatCode));
3002 OUString sNumberFormatString("General");
3003 const sal_Int32 nKey = xValueSeq.is() ? xValueSeq->getNumberFormatKeyByIndex(-1) : 0;
3004 if (nKey > 0)
3005 sNumberFormatString = getNumberFormatCode(nKey);
3006 pFS->writeEscaped(sNumberFormatString);
3007 pFS->endElement( FSNS( XML_c, XML_formatCode ) );
3008 pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
3010 for( sal_Int32 i = 0; i < ptCount; i++ )
3012 if (!std::isnan(aValues[i]))
3014 pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
3015 pFS->startElement(FSNS(XML_c, XML_v));
3016 pFS->write(aValues[i]);
3017 pFS->endElement(FSNS(XML_c, XML_v));
3018 pFS->endElement(FSNS(XML_c, XML_pt));
3022 pFS->endElement( FSNS( XML_c, XML_numCache ) );
3023 pFS->endElement( FSNS( XML_c, XML_numRef ) );
3024 pFS->endElement( FSNS( XML_c, nValueType ) );
3027 void ChartExport::exportShapeProps( const Reference< XPropertySet >& xPropSet )
3029 FSHelperPtr pFS = GetFS();
3030 pFS->startElement(FSNS(XML_c, XML_spPr));
3032 exportFill( xPropSet );
3033 WriteOutline( xPropSet, getModel() );
3035 pFS->endElement( FSNS( XML_c, XML_spPr ) );
3038 void ChartExport::exportTextProps(const Reference<XPropertySet>& xPropSet)
3040 FSHelperPtr pFS = GetFS();
3041 pFS->startElement(FSNS(XML_c, XML_txPr));
3043 sal_Int32 nRotation = 0;
3044 const char* textWordWrap = nullptr;
3046 if (auto xServiceInfo = uno::Reference<lang::XServiceInfo>(xPropSet, uno::UNO_QUERY))
3048 double fMultiplier = 0.0;
3049 // We have at least two possible units of returned value: degrees (e.g., for data labels),
3050 // and 100ths of degree (e.g., for axes labels). The latter is returned as an Any wrapping
3051 // a sal_Int32 value (see WrappedTextRotationProperty::convertInnerToOuterValue), while
3052 // the former is double. So we could test the contained type to decide which multiplier to
3053 // use. But testing the service info should be more robust.
3054 if (xServiceInfo->supportsService("com.sun.star.chart.ChartAxis"))
3055 fMultiplier = -600.0;
3056 else if (xServiceInfo->supportsService("com.sun.star.chart2.DataSeries") || xServiceInfo->supportsService("com.sun.star.chart2.DataPointProperties"))
3058 fMultiplier = -60000.0;
3059 bool bTextWordWrap = false;
3060 if ((xPropSet->getPropertyValue("TextWordWrap") >>= bTextWordWrap) && bTextWordWrap)
3061 textWordWrap = "square";
3062 else
3063 textWordWrap = "none";
3066 if (fMultiplier)
3068 double fTextRotation = 0.0;
3069 uno::Any aAny = xPropSet->getPropertyValue("TextRotation");
3070 if (aAny.hasValue() && (aAny >>= fTextRotation))
3072 fTextRotation *= fMultiplier;
3073 // The MS Office UI allows values only in range of [-90,90].
3074 if (fTextRotation < -5400000.0 && fTextRotation > -16200000.0)
3076 // Reflect the angle if the value is between 90° and 270°
3077 fTextRotation += 10800000.0;
3079 else if (fTextRotation <= -16200000.0)
3081 fTextRotation += 21600000.0;
3083 nRotation = std::round(fTextRotation);
3088 if (nRotation)
3089 pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_rot, OString::number(nRotation), XML_wrap, textWordWrap);
3090 else
3091 pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_wrap, textWordWrap);
3093 pFS->singleElement(FSNS(XML_a, XML_lstStyle));
3095 pFS->startElement(FSNS(XML_a, XML_p));
3096 pFS->startElement(FSNS(XML_a, XML_pPr));
3098 WriteRunProperties(xPropSet, false, XML_defRPr, true, o3tl::temporary(false),
3099 o3tl::temporary(sal_Int32()));
3101 pFS->endElement(FSNS(XML_a, XML_pPr));
3102 pFS->endElement(FSNS(XML_a, XML_p));
3103 pFS->endElement(FSNS(XML_c, XML_txPr));
3106 void ChartExport::InitPlotArea( )
3108 Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY);
3110 // Check for supported services and then the properties provided by this service.
3111 Reference<lang::XServiceInfo> xServiceInfo (mxDiagram, uno::UNO_QUERY);
3112 if (xServiceInfo.is())
3114 if (xServiceInfo->supportsService("com.sun.star.chart.ChartAxisZSupplier"))
3116 xDiagramProperties->getPropertyValue("HasZAxis") >>= mbHasZAxis;
3120 xDiagramProperties->getPropertyValue("Dim3D") >>= mbIs3DChart;
3122 if( mbHasCategoryLabels && mxNewDiagram.is())
3124 Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( mxNewDiagram, mbHasDateCategories ) );
3125 if( xCategories.is() )
3127 mxCategoriesValues.set( xCategories->getValues() );
3132 void ChartExport::exportAxes( )
3134 sal_Int32 nSize = maAxes.size();
3135 // let's export the axis types in the right order
3136 for ( sal_Int32 nSortIdx = AXIS_PRIMARY_X; nSortIdx <= AXIS_SECONDARY_Y; nSortIdx++ )
3138 for ( sal_Int32 nIdx = 0; nIdx < nSize; nIdx++ )
3140 if (nSortIdx == maAxes[nIdx].nAxisType)
3141 exportAxis( maAxes[nIdx] );
3146 namespace {
3148 sal_Int32 getXAxisTypeByChartType(sal_Int32 eChartType)
3150 if( (eChartType == chart::TYPEID_SCATTER)
3151 || (eChartType == chart::TYPEID_BUBBLE) )
3152 return XML_valAx;
3153 else if( eChartType == chart::TYPEID_STOCK )
3154 return XML_dateAx;
3156 return XML_catAx;
3159 sal_Int32 getRealXAxisType(sal_Int32 nAxisType)
3161 if( nAxisType == chart2::AxisType::CATEGORY )
3162 return XML_catAx;
3163 else if( nAxisType == chart2::AxisType::DATE )
3164 return XML_dateAx;
3165 else if( nAxisType == chart2::AxisType::SERIES )
3166 return XML_serAx;
3168 return XML_valAx;
3173 void ChartExport::exportAxis(const AxisIdPair& rAxisIdPair)
3175 // get some properties from document first
3176 bool bHasXAxisTitle = false,
3177 bHasYAxisTitle = false,
3178 bHasZAxisTitle = false,
3179 bHasSecondaryXAxisTitle = false,
3180 bHasSecondaryYAxisTitle = false;
3181 bool bHasXAxisMajorGrid = false,
3182 bHasXAxisMinorGrid = false,
3183 bHasYAxisMajorGrid = false,
3184 bHasYAxisMinorGrid = false,
3185 bHasZAxisMajorGrid = false,
3186 bHasZAxisMinorGrid = false;
3188 Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY);
3190 xDiagramProperties->getPropertyValue("HasXAxisTitle") >>= bHasXAxisTitle;
3191 xDiagramProperties->getPropertyValue("HasYAxisTitle") >>= bHasYAxisTitle;
3192 xDiagramProperties->getPropertyValue("HasZAxisTitle") >>= bHasZAxisTitle;
3193 xDiagramProperties->getPropertyValue("HasSecondaryXAxisTitle") >>= bHasSecondaryXAxisTitle;
3194 xDiagramProperties->getPropertyValue("HasSecondaryYAxisTitle") >>= bHasSecondaryYAxisTitle;
3196 xDiagramProperties->getPropertyValue("HasXAxisGrid") >>= bHasXAxisMajorGrid;
3197 xDiagramProperties->getPropertyValue("HasYAxisGrid") >>= bHasYAxisMajorGrid;
3198 xDiagramProperties->getPropertyValue("HasZAxisGrid") >>= bHasZAxisMajorGrid;
3200 xDiagramProperties->getPropertyValue("HasXAxisHelpGrid") >>= bHasXAxisMinorGrid;
3201 xDiagramProperties->getPropertyValue("HasYAxisHelpGrid") >>= bHasYAxisMinorGrid;
3202 xDiagramProperties->getPropertyValue("HasZAxisHelpGrid") >>= bHasZAxisMinorGrid;
3204 Reference< XPropertySet > xAxisProp;
3205 Reference< drawing::XShape > xAxisTitle;
3206 Reference< beans::XPropertySet > xMajorGrid;
3207 Reference< beans::XPropertySet > xMinorGrid;
3208 sal_Int32 nAxisType = XML_catAx;
3209 const char* sAxPos = nullptr;
3211 switch( rAxisIdPair.nAxisType )
3213 case AXIS_PRIMARY_X:
3215 Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
3216 if( xAxisXSupp.is())
3217 xAxisProp = xAxisXSupp->getXAxis();
3218 if( bHasXAxisTitle )
3219 xAxisTitle = xAxisXSupp->getXAxisTitle();
3220 if( bHasXAxisMajorGrid )
3221 xMajorGrid = xAxisXSupp->getXMainGrid();
3222 if( bHasXAxisMinorGrid )
3223 xMinorGrid = xAxisXSupp->getXHelpGrid();
3225 nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 0);
3226 if( nAxisType != -1 )
3227 nAxisType = getRealXAxisType(nAxisType);
3228 else
3229 nAxisType = getXAxisTypeByChartType( getChartType() );
3230 // FIXME: axPos, need to check axis direction
3231 sAxPos = "b";
3232 break;
3234 case AXIS_PRIMARY_Y:
3236 Reference< css::chart::XAxisYSupplier > xAxisYSupp( mxDiagram, uno::UNO_QUERY );
3237 if( xAxisYSupp.is())
3238 xAxisProp = xAxisYSupp->getYAxis();
3239 if( bHasYAxisTitle )
3240 xAxisTitle = xAxisYSupp->getYAxisTitle();
3241 if( bHasYAxisMajorGrid )
3242 xMajorGrid = xAxisYSupp->getYMainGrid();
3243 if( bHasYAxisMinorGrid )
3244 xMinorGrid = xAxisYSupp->getYHelpGrid();
3246 nAxisType = XML_valAx;
3247 // FIXME: axPos, need to check axis direction
3248 sAxPos = "l";
3249 break;
3251 case AXIS_PRIMARY_Z:
3253 Reference< css::chart::XAxisZSupplier > xAxisZSupp( mxDiagram, uno::UNO_QUERY );
3254 if( xAxisZSupp.is())
3255 xAxisProp = xAxisZSupp->getZAxis();
3256 if( bHasZAxisTitle )
3257 xAxisTitle = xAxisZSupp->getZAxisTitle();
3258 if( bHasZAxisMajorGrid )
3259 xMajorGrid = xAxisZSupp->getZMainGrid();
3260 if( bHasZAxisMinorGrid )
3261 xMinorGrid = xAxisZSupp->getZHelpGrid();
3263 sal_Int32 eChartType = getChartType( );
3264 if( (eChartType == chart::TYPEID_SCATTER)
3265 || (eChartType == chart::TYPEID_BUBBLE) )
3266 nAxisType = XML_valAx;
3267 else if( eChartType == chart::TYPEID_STOCK )
3268 nAxisType = XML_dateAx;
3269 else if( eChartType == chart::TYPEID_BAR || eChartType == chart::TYPEID_AREA )
3270 nAxisType = XML_serAx;
3271 // FIXME: axPos, need to check axis direction
3272 sAxPos = "b";
3273 break;
3275 case AXIS_SECONDARY_X:
3277 Reference< css::chart::XTwoAxisXSupplier > xAxisTwoXSupp( mxDiagram, uno::UNO_QUERY );
3278 if( xAxisTwoXSupp.is())
3279 xAxisProp = xAxisTwoXSupp->getSecondaryXAxis();
3280 if( bHasSecondaryXAxisTitle )
3282 Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY );
3283 xAxisTitle = xAxisSupp->getSecondXAxisTitle();
3286 nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 1);
3287 if( nAxisType != -1 )
3288 nAxisType = getRealXAxisType(nAxisType);
3289 else
3290 nAxisType = getXAxisTypeByChartType( getChartType() );
3291 // FIXME: axPos, need to check axis direction
3292 sAxPos = "t";
3293 break;
3295 case AXIS_SECONDARY_Y:
3297 Reference< css::chart::XTwoAxisYSupplier > xAxisTwoYSupp( mxDiagram, uno::UNO_QUERY );
3298 if( xAxisTwoYSupp.is())
3299 xAxisProp = xAxisTwoYSupp->getSecondaryYAxis();
3300 if( bHasSecondaryYAxisTitle )
3302 Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY );
3303 xAxisTitle = xAxisSupp->getSecondYAxisTitle();
3306 nAxisType = XML_valAx;
3307 // FIXME: axPos, need to check axis direction
3308 sAxPos = "r";
3309 break;
3313 _exportAxis(xAxisProp, xAxisTitle, xMajorGrid, xMinorGrid, nAxisType, sAxPos, rAxisIdPair);
3316 void ChartExport::_exportAxis(
3317 const Reference< XPropertySet >& xAxisProp,
3318 const Reference< drawing::XShape >& xAxisTitle,
3319 const Reference< XPropertySet >& xMajorGrid,
3320 const Reference< XPropertySet >& xMinorGrid,
3321 sal_Int32 nAxisType,
3322 const char* sAxisPos,
3323 const AxisIdPair& rAxisIdPair )
3325 FSHelperPtr pFS = GetFS();
3326 pFS->startElement(FSNS(XML_c, nAxisType));
3327 pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(rAxisIdPair.nAxisId));
3329 pFS->startElement(FSNS(XML_c, XML_scaling));
3331 // logBase, min, max
3332 if(GetProperty( xAxisProp, "Logarithmic" ) )
3334 bool bLogarithmic = false;
3335 mAny >>= bLogarithmic;
3336 if( bLogarithmic )
3338 // default value is 10?
3339 pFS->singleElement(FSNS(XML_c, XML_logBase), XML_val, OString::number(10));
3343 // orientation: minMax, maxMin
3344 bool bReverseDirection = false;
3345 if(GetProperty( xAxisProp, "ReverseDirection" ) )
3346 mAny >>= bReverseDirection;
3348 const char* orientation = bReverseDirection ? "maxMin":"minMax";
3349 pFS->singleElement(FSNS(XML_c, XML_orientation), XML_val, orientation);
3351 bool bAutoMax = false;
3352 if(GetProperty( xAxisProp, "AutoMax" ) )
3353 mAny >>= bAutoMax;
3355 if( !bAutoMax && (GetProperty( xAxisProp, "Max" ) ) )
3357 double dMax = 0;
3358 mAny >>= dMax;
3359 pFS->singleElement(FSNS(XML_c, XML_max), XML_val, OString::number(dMax));
3362 bool bAutoMin = false;
3363 if(GetProperty( xAxisProp, "AutoMin" ) )
3364 mAny >>= bAutoMin;
3366 if( !bAutoMin && (GetProperty( xAxisProp, "Min" ) ) )
3368 double dMin = 0;
3369 mAny >>= dMin;
3370 pFS->singleElement(FSNS(XML_c, XML_min), XML_val, OString::number(dMin));
3373 pFS->endElement( FSNS( XML_c, XML_scaling ) );
3375 bool bVisible = true;
3376 if( xAxisProp.is() )
3378 xAxisProp->getPropertyValue("Visible") >>= bVisible;
3381 // only export each axis only once non-deleted
3382 auto aItInsertedPair = maExportedAxis.insert(rAxisIdPair.nAxisType);
3383 bool bDeleted = !aItInsertedPair.second;
3385 pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, !bDeleted && bVisible ? "0" : "1");
3387 // FIXME: axPos, need to check the property "ReverseDirection"
3388 pFS->singleElement(FSNS(XML_c, XML_axPos), XML_val, sAxisPos);
3389 // major grid line
3390 if( xMajorGrid.is())
3392 pFS->startElement(FSNS(XML_c, XML_majorGridlines));
3393 exportShapeProps( xMajorGrid );
3394 pFS->endElement( FSNS( XML_c, XML_majorGridlines ) );
3397 // minor grid line
3398 if( xMinorGrid.is())
3400 pFS->startElement(FSNS(XML_c, XML_minorGridlines));
3401 exportShapeProps( xMinorGrid );
3402 pFS->endElement( FSNS( XML_c, XML_minorGridlines ) );
3405 // title
3406 if( xAxisTitle.is() )
3407 exportTitle( xAxisTitle );
3409 bool bLinkedNumFmt = true;
3410 if (GetProperty(xAxisProp, "LinkNumberFormatToSource"))
3411 mAny >>= bLinkedNumFmt;
3413 OUString aNumberFormatString("General");
3414 if (GetProperty(xAxisProp, "NumberFormat"))
3416 sal_Int32 nKey = 0;
3417 mAny >>= nKey;
3418 aNumberFormatString = getNumberFormatCode(nKey);
3421 pFS->singleElement(FSNS(XML_c, XML_numFmt),
3422 XML_formatCode, aNumberFormatString,
3423 XML_sourceLinked, bLinkedNumFmt ? "1" : "0");
3425 // majorTickMark
3426 sal_Int32 nValue = 0;
3427 if(GetProperty( xAxisProp, "Marks" ) )
3429 mAny >>= nValue;
3430 bool bInner = nValue & css::chart::ChartAxisMarks::INNER;
3431 bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER;
3432 const char* majorTickMark = nullptr;
3433 if( bInner && bOuter )
3434 majorTickMark = "cross";
3435 else if( bInner )
3436 majorTickMark = "in";
3437 else if( bOuter )
3438 majorTickMark = "out";
3439 else
3440 majorTickMark = "none";
3441 pFS->singleElement(FSNS(XML_c, XML_majorTickMark), XML_val, majorTickMark);
3443 // minorTickMark
3444 if(GetProperty( xAxisProp, "HelpMarks" ) )
3446 mAny >>= nValue;
3447 bool bInner = nValue & css::chart::ChartAxisMarks::INNER;
3448 bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER;
3449 const char* minorTickMark = nullptr;
3450 if( bInner && bOuter )
3451 minorTickMark = "cross";
3452 else if( bInner )
3453 minorTickMark = "in";
3454 else if( bOuter )
3455 minorTickMark = "out";
3456 else
3457 minorTickMark = "none";
3458 pFS->singleElement(FSNS(XML_c, XML_minorTickMark), XML_val, minorTickMark);
3460 // tickLblPos
3461 const char* sTickLblPos = nullptr;
3462 bool bDisplayLabel = true;
3463 if(GetProperty( xAxisProp, "DisplayLabels" ) )
3464 mAny >>= bDisplayLabel;
3465 if( bDisplayLabel && (GetProperty( xAxisProp, "LabelPosition" ) ) )
3467 css::chart::ChartAxisLabelPosition eLabelPosition = css::chart::ChartAxisLabelPosition_NEAR_AXIS;
3468 mAny >>= eLabelPosition;
3469 switch( eLabelPosition )
3471 case css::chart::ChartAxisLabelPosition_NEAR_AXIS:
3472 case css::chart::ChartAxisLabelPosition_NEAR_AXIS_OTHER_SIDE:
3473 sTickLblPos = "nextTo";
3474 break;
3475 case css::chart::ChartAxisLabelPosition_OUTSIDE_START:
3476 sTickLblPos = "low";
3477 break;
3478 case css::chart::ChartAxisLabelPosition_OUTSIDE_END:
3479 sTickLblPos = "high";
3480 break;
3481 default:
3482 sTickLblPos = "nextTo";
3483 break;
3486 else
3488 sTickLblPos = "none";
3490 pFS->singleElement(FSNS(XML_c, XML_tickLblPos), XML_val, sTickLblPos);
3492 // shape properties
3493 exportShapeProps( xAxisProp );
3495 exportTextProps(xAxisProp);
3497 pFS->singleElement(FSNS(XML_c, XML_crossAx), XML_val, OString::number(rAxisIdPair.nCrossAx));
3499 // crosses & crossesAt
3500 bool bCrossesValue = false;
3501 const char* sCrosses = nullptr;
3502 // do not export the CrossoverPosition/CrossoverValue, if the axis is deleted and not visible
3503 if( GetProperty( xAxisProp, "CrossoverPosition" ) && !bDeleted && bVisible )
3505 css::chart::ChartAxisPosition ePosition( css::chart::ChartAxisPosition_ZERO );
3506 mAny >>= ePosition;
3507 switch( ePosition )
3509 case css::chart::ChartAxisPosition_START:
3510 sCrosses = "min";
3511 break;
3512 case css::chart::ChartAxisPosition_END:
3513 sCrosses = "max";
3514 break;
3515 case css::chart::ChartAxisPosition_ZERO:
3516 sCrosses = "autoZero";
3517 break;
3518 default:
3519 bCrossesValue = true;
3520 break;
3524 if( bCrossesValue && GetProperty( xAxisProp, "CrossoverValue" ) )
3526 double dValue = 0;
3527 mAny >>= dValue;
3528 pFS->singleElement(FSNS(XML_c, XML_crossesAt), XML_val, OString::number(dValue));
3530 else
3532 if(sCrosses)
3534 pFS->singleElement(FSNS(XML_c, XML_crosses), XML_val, sCrosses);
3538 if( ( nAxisType == XML_catAx )
3539 || ( nAxisType == XML_dateAx ) )
3541 // FIXME: seems not support? use default value,
3542 const char* const isAuto = "1";
3543 pFS->singleElement(FSNS(XML_c, XML_auto), XML_val, isAuto);
3545 if( nAxisType == XML_catAx )
3547 // FIXME: seems not support? lblAlgn
3548 const char* const sLblAlgn = "ctr";
3549 pFS->singleElement(FSNS(XML_c, XML_lblAlgn), XML_val, sLblAlgn);
3552 // FIXME: seems not support? lblOffset
3553 pFS->singleElement(FSNS(XML_c, XML_lblOffset), XML_val, OString::number(100));
3555 // export baseTimeUnit, majorTimeUnit, minorTimeUnit of Date axis
3556 if( nAxisType == XML_dateAx )
3558 sal_Int32 nAxisIndex = -1;
3559 if( rAxisIdPair.nAxisType == AXIS_PRIMARY_X )
3560 nAxisIndex = 0;
3561 else if( rAxisIdPair.nAxisType == AXIS_SECONDARY_X )
3562 nAxisIndex = 1;
3564 cssc::TimeIncrement aTimeIncrement = lcl_getDateTimeIncrement( mxNewDiagram, nAxisIndex );
3565 sal_Int32 nTimeResolution = css::chart::TimeUnit::DAY;
3566 if( aTimeIncrement.TimeResolution >>= nTimeResolution )
3567 pFS->singleElement(FSNS(XML_c, XML_baseTimeUnit), XML_val, lclGetTimeUnitToken(nTimeResolution));
3569 cssc::TimeInterval aInterval;
3570 if( aTimeIncrement.MajorTimeInterval >>= aInterval )
3572 pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(aInterval.Number));
3573 pFS->singleElement(FSNS(XML_c, XML_majorTimeUnit), XML_val, lclGetTimeUnitToken(aInterval.TimeUnit));
3575 if( aTimeIncrement.MinorTimeInterval >>= aInterval )
3577 pFS->singleElement(FSNS(XML_c, XML_minorUnit), XML_val, OString::number(aInterval.Number));
3578 pFS->singleElement(FSNS(XML_c, XML_minorTimeUnit), XML_val, lclGetTimeUnitToken(aInterval.TimeUnit));
3582 // FIXME: seems not support? noMultiLvlLbl
3583 pFS->singleElement(FSNS(XML_c, XML_noMultiLvlLbl), XML_val, OString::number(0));
3586 // crossBetween
3587 if( nAxisType == XML_valAx )
3589 if( lcl_isCategoryAxisShifted( mxNewDiagram ))
3590 pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "between");
3591 else
3592 pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "midCat");
3595 // majorUnit
3596 bool bAutoStepMain = false;
3597 if(GetProperty( xAxisProp, "AutoStepMain" ) )
3598 mAny >>= bAutoStepMain;
3600 if( !bAutoStepMain && (GetProperty( xAxisProp, "StepMain" ) ) )
3602 double dMajorUnit = 0;
3603 mAny >>= dMajorUnit;
3604 pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(dMajorUnit));
3606 // minorUnit
3607 bool bAutoStepHelp = false;
3608 if(GetProperty( xAxisProp, "AutoStepHelp" ) )
3609 mAny >>= bAutoStepHelp;
3611 if( !bAutoStepHelp && (GetProperty( xAxisProp, "StepHelp" ) ) )
3613 double dMinorUnit = 0;
3614 mAny >>= dMinorUnit;
3615 if( GetProperty( xAxisProp, "StepHelpCount" ) )
3617 sal_Int32 dMinorUnitCount = 0;
3618 mAny >>= dMinorUnitCount;
3619 // tdf#114168 Don't save minor unit if number of step help count is 5 (which is default for MS Excel),
3620 // to allow proper .xlsx import. If minorUnit is set and majorUnit not, then it is impossible
3621 // to calculate StepHelpCount.
3622 if( dMinorUnitCount != 5 )
3624 pFS->singleElement( FSNS( XML_c, XML_minorUnit ),
3625 XML_val, OString::number( dMinorUnit ) );
3630 if( nAxisType == XML_valAx && GetProperty( xAxisProp, "DisplayUnits" ) )
3632 bool bDisplayUnits = false;
3633 mAny >>= bDisplayUnits;
3634 if(bDisplayUnits)
3636 if(GetProperty( xAxisProp, "BuiltInUnit" ))
3638 OUString aVal;
3639 mAny >>= aVal;
3640 if(!aVal.isEmpty())
3642 pFS->startElement(FSNS(XML_c, XML_dispUnits));
3644 pFS->singleElement(FSNS(XML_c, XML_builtInUnit), XML_val, aVal);
3646 pFS->singleElement(FSNS( XML_c, XML_dispUnitsLbl ));
3647 pFS->endElement( FSNS( XML_c, XML_dispUnits ) );
3653 pFS->endElement( FSNS( XML_c, nAxisType ) );
3656 namespace {
3658 struct LabelPlacementParam
3660 bool mbExport;
3661 sal_Int32 meDefault;
3663 std::unordered_set<sal_Int32> maAllowedValues;
3665 LabelPlacementParam(bool bExport, sal_Int32 nDefault) :
3666 mbExport(bExport),
3667 meDefault(nDefault),
3668 maAllowedValues(
3670 css::chart::DataLabelPlacement::OUTSIDE,
3671 css::chart::DataLabelPlacement::INSIDE,
3672 css::chart::DataLabelPlacement::CENTER,
3673 css::chart::DataLabelPlacement::NEAR_ORIGIN,
3674 css::chart::DataLabelPlacement::TOP,
3675 css::chart::DataLabelPlacement::BOTTOM,
3676 css::chart::DataLabelPlacement::LEFT,
3677 css::chart::DataLabelPlacement::RIGHT,
3678 css::chart::DataLabelPlacement::AVOID_OVERLAP
3684 const char* toOOXMLPlacement( sal_Int32 nPlacement )
3686 switch (nPlacement)
3688 case css::chart::DataLabelPlacement::OUTSIDE: return "outEnd";
3689 case css::chart::DataLabelPlacement::INSIDE: return "inEnd";
3690 case css::chart::DataLabelPlacement::CENTER: return "ctr";
3691 case css::chart::DataLabelPlacement::NEAR_ORIGIN: return "inBase";
3692 case css::chart::DataLabelPlacement::TOP: return "t";
3693 case css::chart::DataLabelPlacement::BOTTOM: return "b";
3694 case css::chart::DataLabelPlacement::LEFT: return "l";
3695 case css::chart::DataLabelPlacement::RIGHT: return "r";
3696 case css::chart::DataLabelPlacement::CUSTOM:
3697 case css::chart::DataLabelPlacement::AVOID_OVERLAP: return "bestFit";
3698 default:
3702 return "outEnd";
3705 OUString getFieldTypeString( const chart2::DataPointCustomLabelFieldType aType )
3707 switch (aType)
3709 case chart2::DataPointCustomLabelFieldType_CATEGORYNAME:
3710 return "CATEGORYNAME";
3712 case chart2::DataPointCustomLabelFieldType_SERIESNAME:
3713 return "SERIESNAME";
3715 case chart2::DataPointCustomLabelFieldType_VALUE:
3716 return "VALUE";
3718 case chart2::DataPointCustomLabelFieldType_CELLREF:
3719 return "CELLREF";
3721 case chart2::DataPointCustomLabelFieldType_CELLRANGE:
3722 return "CELLRANGE";
3724 default:
3725 break;
3727 return OUString();
3730 void writeRunProperties( ChartExport* pChartExport, Reference<XPropertySet> const & xPropertySet )
3732 bool bDummy = false;
3733 sal_Int32 nDummy;
3734 pChartExport->WriteRunProperties(xPropertySet, false, XML_rPr, true, bDummy, nDummy);
3737 void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport,
3738 const Sequence<Reference<chart2::XDataPointCustomLabelField>>& rCustomLabelFields,
3739 sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange )
3741 pFS->startElement(FSNS(XML_c, XML_tx));
3742 pFS->startElement(FSNS(XML_c, XML_rich));
3744 // TODO: body properties?
3745 pFS->singleElement(FSNS(XML_a, XML_bodyPr));
3747 OUString sFieldType;
3748 OUString sContent;
3749 pFS->startElement(FSNS(XML_a, XML_p));
3751 for (auto& rField : rCustomLabelFields)
3753 Reference<XPropertySet> xPropertySet(rField, UNO_QUERY);
3754 chart2::DataPointCustomLabelFieldType aType = rField->getFieldType();
3755 sFieldType.clear();
3756 sContent.clear();
3757 bool bNewParagraph = false;
3759 if (aType == chart2::DataPointCustomLabelFieldType_CELLRANGE &&
3760 rField->getDataLabelsRange())
3762 if (rDLblsRange.getRange().isEmpty())
3763 rDLblsRange.setRange(rField->getCellRange());
3765 if (!rDLblsRange.hasLabel(nLabelIndex))
3766 rDLblsRange.setLabel(nLabelIndex, rField->getString());
3768 sContent = "[CELLRANGE]";
3770 else
3772 sContent = rField->getString();
3775 if (aType == chart2::DataPointCustomLabelFieldType_NEWLINE)
3776 bNewParagraph = true;
3777 else if (aType != chart2::DataPointCustomLabelFieldType_TEXT)
3778 sFieldType = getFieldTypeString(aType);
3780 if (bNewParagraph)
3782 pFS->endElement(FSNS(XML_a, XML_p));
3783 pFS->startElement(FSNS(XML_a, XML_p));
3784 continue;
3787 if (sFieldType.isEmpty())
3789 // Normal text run
3790 pFS->startElement(FSNS(XML_a, XML_r));
3791 writeRunProperties(pChartExport, xPropertySet);
3793 pFS->startElement(FSNS(XML_a, XML_t));
3794 pFS->writeEscaped(sContent);
3795 pFS->endElement(FSNS(XML_a, XML_t));
3797 pFS->endElement(FSNS(XML_a, XML_r));
3799 else
3801 // Field
3802 pFS->startElement(FSNS(XML_a, XML_fld), XML_id, rField->getGuid(), XML_type,
3803 sFieldType);
3804 writeRunProperties(pChartExport, xPropertySet);
3806 pFS->startElement(FSNS(XML_a, XML_t));
3807 pFS->writeEscaped(sContent);
3808 pFS->endElement(FSNS(XML_a, XML_t));
3810 pFS->endElement(FSNS(XML_a, XML_fld));
3814 pFS->endElement(FSNS(XML_a, XML_p));
3815 pFS->endElement(FSNS(XML_c, XML_rich));
3816 pFS->endElement(FSNS(XML_c, XML_tx));
3819 void writeLabelProperties( const FSHelperPtr& pFS, ChartExport* pChartExport,
3820 const uno::Reference<beans::XPropertySet>& xPropSet, const LabelPlacementParam& rLabelParam,
3821 sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange )
3823 if (!xPropSet.is())
3824 return;
3826 chart2::DataPointLabel aLabel;
3827 Sequence<Reference<chart2::XDataPointCustomLabelField>> aCustomLabelFields;
3828 sal_Int32 nLabelBorderWidth = 0;
3829 sal_Int32 nLabelBorderColor = 0x00FFFFFF;
3830 sal_Int32 nLabelFillColor = -1;
3832 xPropSet->getPropertyValue("Label") >>= aLabel;
3833 xPropSet->getPropertyValue("CustomLabelFields") >>= aCustomLabelFields;
3834 xPropSet->getPropertyValue("LabelBorderWidth") >>= nLabelBorderWidth;
3835 xPropSet->getPropertyValue("LabelBorderColor") >>= nLabelBorderColor;
3836 xPropSet->getPropertyValue("LabelFillColor") >>= nLabelFillColor;
3838 if (nLabelBorderWidth > 0 || nLabelFillColor != -1)
3840 pFS->startElement(FSNS(XML_c, XML_spPr));
3842 if (nLabelFillColor != -1)
3844 pFS->startElement(FSNS(XML_a, XML_solidFill));
3846 OString aStr = OString::number(nLabelFillColor, 16).toAsciiUpperCase();
3847 pFS->singleElement(FSNS(XML_a, XML_srgbClr), XML_val, aStr);
3849 pFS->endElement(FSNS(XML_a, XML_solidFill));
3852 if (nLabelBorderWidth > 0)
3854 pFS->startElement(FSNS(XML_a, XML_ln), XML_w,
3855 OString::number(convertHmmToEmu(nLabelBorderWidth)));
3857 if (nLabelBorderColor != -1)
3859 pFS->startElement(FSNS(XML_a, XML_solidFill));
3861 OString aStr = OString::number(nLabelBorderColor, 16).toAsciiUpperCase();
3862 pFS->singleElement(FSNS(XML_a, XML_srgbClr), XML_val, aStr);
3864 pFS->endElement(FSNS(XML_a, XML_solidFill));
3867 pFS->endElement(FSNS(XML_a, XML_ln));
3870 pFS->endElement(FSNS(XML_c, XML_spPr));
3873 pChartExport->exportTextProps(xPropSet);
3875 if (aCustomLabelFields.hasElements())
3876 writeCustomLabel(pFS, pChartExport, aCustomLabelFields, nLabelIndex, rDLblsRange);
3878 if (rLabelParam.mbExport)
3880 sal_Int32 nLabelPlacement = rLabelParam.meDefault;
3881 if (xPropSet->getPropertyValue("LabelPlacement") >>= nLabelPlacement)
3883 if (!rLabelParam.maAllowedValues.count(nLabelPlacement))
3884 nLabelPlacement = rLabelParam.meDefault;
3885 pFS->singleElement(FSNS(XML_c, XML_dLblPos), XML_val, toOOXMLPlacement(nLabelPlacement));
3889 pFS->singleElement(FSNS(XML_c, XML_showLegendKey), XML_val, ToPsz10(aLabel.ShowLegendSymbol));
3890 pFS->singleElement(FSNS(XML_c, XML_showVal), XML_val, ToPsz10(aLabel.ShowNumber));
3891 pFS->singleElement(FSNS(XML_c, XML_showCatName), XML_val, ToPsz10(aLabel.ShowCategoryName));
3892 pFS->singleElement(FSNS(XML_c, XML_showSerName), XML_val, ToPsz10(aLabel.ShowSeriesName));
3893 pFS->singleElement(FSNS(XML_c, XML_showPercent), XML_val, ToPsz10(aLabel.ShowNumberInPercent));
3895 // Export the text "separator" if exists
3896 uno::Any aAny = xPropSet->getPropertyValue("LabelSeparator");
3897 if( aAny.hasValue() )
3899 OUString nLabelSeparator;
3900 aAny >>= nLabelSeparator;
3901 pFS->startElement(FSNS(XML_c, XML_separator));
3902 pFS->writeEscaped( nLabelSeparator );
3903 pFS->endElement( FSNS( XML_c, XML_separator ) );
3906 if (rDLblsRange.hasLabel(nLabelIndex))
3908 pFS->startElement(FSNS(XML_c, XML_extLst));
3909 pFS->startElement(FSNS(XML_c, XML_ext), XML_uri,
3910 "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15),
3911 pChartExport->GetFB()->getNamespaceURL(OOX_NS(c15)));
3913 pFS->singleElement(FSNS(XML_c15, XML_showDataLabelsRange), XML_val, "1");
3915 pFS->endElement(FSNS(XML_c, XML_ext));
3916 pFS->endElement(FSNS(XML_c, XML_extLst));
3922 void ChartExport::exportDataLabels(
3923 const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 nSeriesLength, sal_Int32 eChartType,
3924 DataLabelsRange& rDLblsRange)
3926 if (!xSeries.is() || nSeriesLength <= 0)
3927 return;
3929 uno::Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
3930 if (!xPropSet.is())
3931 return;
3933 FSHelperPtr pFS = GetFS();
3934 pFS->startElement(FSNS(XML_c, XML_dLbls));
3936 bool bLinkedNumFmt = true;
3937 if (GetProperty(xPropSet, "LinkNumberFormatToSource"))
3938 mAny >>= bLinkedNumFmt;
3940 chart2::DataPointLabel aLabel;
3941 bool bLabelIsNumberFormat = true;
3942 if( xPropSet->getPropertyValue("Label") >>= aLabel )
3943 bLabelIsNumberFormat = aLabel.ShowNumber;
3945 if (GetProperty(xPropSet, bLabelIsNumberFormat ? OUString("NumberFormat") : OUString("PercentageNumberFormat")))
3947 sal_Int32 nKey = 0;
3948 mAny >>= nKey;
3950 OUString aNumberFormatString = getNumberFormatCode(nKey);
3952 pFS->singleElement(FSNS(XML_c, XML_numFmt),
3953 XML_formatCode, aNumberFormatString,
3954 XML_sourceLinked, ToPsz10(bLinkedNumFmt));
3957 uno::Sequence<sal_Int32> aAttrLabelIndices;
3958 xPropSet->getPropertyValue("AttributedDataPoints") >>= aAttrLabelIndices;
3960 // We must not export label placement property when the chart type doesn't
3961 // support this option in MS Office, else MS Office would think the file
3962 // is corrupt & refuse to open it.
3964 const chart::TypeGroupInfo& rInfo = chart::GetTypeGroupInfo(static_cast<chart::TypeId>(eChartType));
3965 LabelPlacementParam aParam(!mbIs3DChart, rInfo.mnDefLabelPos);
3966 switch (eChartType) // diagram chart type
3968 case chart::TYPEID_PIE:
3969 if(getChartType() == chart::TYPEID_DOUGHNUT)
3970 aParam.mbExport = false;
3971 else
3972 // All pie charts support label placement.
3973 aParam.mbExport = true;
3974 break;
3975 case chart::TYPEID_AREA:
3976 case chart::TYPEID_RADARLINE:
3977 case chart::TYPEID_RADARAREA:
3978 // These chart types don't support label placement.
3979 aParam.mbExport = false;
3980 break;
3981 case chart::TYPEID_BAR:
3982 if (mbStacked || mbPercent)
3984 aParam.maAllowedValues.clear();
3985 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
3986 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
3987 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
3988 aParam.meDefault = css::chart::DataLabelPlacement::CENTER;
3990 else // Clustered bar chart
3992 aParam.maAllowedValues.clear();
3993 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
3994 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
3995 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::OUTSIDE);
3996 aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
3997 aParam.meDefault = css::chart::DataLabelPlacement::OUTSIDE;
3999 break;
4000 default:
4004 for (const sal_Int32 nIdx : aAttrLabelIndices)
4006 uno::Reference<beans::XPropertySet> xLabelPropSet = xSeries->getDataPointByIndex(nIdx);
4008 if (!xLabelPropSet.is())
4009 continue;
4011 pFS->startElement(FSNS(XML_c, XML_dLbl));
4012 pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nIdx));
4014 // export custom position of data label
4015 if( eChartType != chart::TYPEID_PIE )
4017 chart2::RelativePosition aCustomLabelPosition;
4018 if( xLabelPropSet->getPropertyValue("CustomLabelPosition") >>= aCustomLabelPosition )
4020 pFS->startElement(FSNS(XML_c, XML_layout));
4021 pFS->startElement(FSNS(XML_c, XML_manualLayout));
4023 pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(aCustomLabelPosition.Primary));
4024 pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(aCustomLabelPosition.Secondary));
4026 SAL_WARN_IF(aCustomLabelPosition.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position");
4028 pFS->endElement(FSNS(XML_c, XML_manualLayout));
4029 pFS->endElement(FSNS(XML_c, XML_layout));
4033 if( GetProperty(xLabelPropSet, "LinkNumberFormatToSource") )
4034 mAny >>= bLinkedNumFmt;
4036 if( xLabelPropSet->getPropertyValue("Label") >>= aLabel )
4037 bLabelIsNumberFormat = aLabel.ShowNumber;
4038 else
4039 bLabelIsNumberFormat = true;
4041 if (GetProperty(xLabelPropSet, bLabelIsNumberFormat ? OUString("NumberFormat") : OUString("PercentageNumberFormat")))
4043 sal_Int32 nKey = 0;
4044 mAny >>= nKey;
4046 OUString aNumberFormatString = getNumberFormatCode(nKey);
4048 pFS->singleElement(FSNS(XML_c, XML_numFmt), XML_formatCode, aNumberFormatString,
4049 XML_sourceLinked, ToPsz10(bLinkedNumFmt));
4052 // Individual label property that overwrites the baseline.
4053 writeLabelProperties(pFS, this, xLabelPropSet, aParam, nIdx, rDLblsRange);
4054 pFS->endElement(FSNS(XML_c, XML_dLbl));
4057 // Baseline label properties for all labels.
4058 writeLabelProperties(pFS, this, xPropSet, aParam, -1, rDLblsRange);
4060 bool bShowLeaderLines = false;
4061 xPropSet->getPropertyValue("ShowCustomLeaderLines") >>= bShowLeaderLines;
4062 pFS->singleElement(FSNS(XML_c, XML_showLeaderLines), XML_val, ToPsz10(bShowLeaderLines));
4064 // Export leader line
4065 if( eChartType != chart::TYPEID_PIE )
4067 pFS->startElement(FSNS(XML_c, XML_extLst));
4068 pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15), GetFB()->getNamespaceURL(OOX_NS(c15)));
4069 pFS->singleElement(FSNS(XML_c15, XML_showLeaderLines), XML_val, ToPsz10(bShowLeaderLines));
4070 pFS->endElement(FSNS(XML_c, XML_ext));
4071 pFS->endElement(FSNS(XML_c, XML_extLst));
4073 pFS->endElement(FSNS(XML_c, XML_dLbls));
4076 void ChartExport::exportDataPoints(
4077 const uno::Reference< beans::XPropertySet > & xSeriesProperties,
4078 sal_Int32 nSeriesLength, sal_Int32 eChartType )
4080 uno::Reference< chart2::XDataSeries > xSeries( xSeriesProperties, uno::UNO_QUERY );
4081 bool bVaryColorsByPoint = false;
4082 Sequence< sal_Int32 > aDataPointSeq;
4083 if( xSeriesProperties.is())
4085 Any aAny = xSeriesProperties->getPropertyValue( "AttributedDataPoints" );
4086 aAny >>= aDataPointSeq;
4087 xSeriesProperties->getPropertyValue( "VaryColorsByPoint" ) >>= bVaryColorsByPoint;
4090 const sal_Int32 * pPoints = aDataPointSeq.getConstArray();
4091 sal_Int32 nElement;
4092 Reference< chart2::XColorScheme > xColorScheme;
4093 if( mxNewDiagram.is())
4094 xColorScheme.set( mxNewDiagram->getDefaultColorScheme());
4096 if( bVaryColorsByPoint && xColorScheme.is() )
4098 o3tl::sorted_vector< sal_Int32 > aAttrPointSet;
4099 aAttrPointSet.reserve(aDataPointSeq.getLength());
4100 for (auto p = pPoints; p < pPoints + aDataPointSeq.getLength(); ++p)
4101 aAttrPointSet.insert(*p);
4102 const auto aEndIt = aAttrPointSet.end();
4103 for( nElement = 0; nElement < nSeriesLength; ++nElement )
4105 uno::Reference< beans::XPropertySet > xPropSet;
4106 if( aAttrPointSet.find( nElement ) != aEndIt )
4110 xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet(
4111 xSeries, nElement, getModel() );
4113 catch( const uno::Exception & )
4115 DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" );
4118 else
4120 // property set only containing the color
4121 xPropSet.set( new ColorPropertySet( ColorTransparency, xColorScheme->getColorByIndex( nElement )));
4124 if( xPropSet.is() )
4126 FSHelperPtr pFS = GetFS();
4127 pFS->startElement(FSNS(XML_c, XML_dPt));
4128 pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement));
4130 switch (eChartType)
4132 case chart::TYPEID_PIE:
4133 case chart::TYPEID_DOUGHNUT:
4135 if( xPropSet.is() && GetProperty( xPropSet, "SegmentOffset") )
4137 sal_Int32 nOffset = 0;
4138 mAny >>= nOffset;
4139 if (nOffset)
4140 pFS->singleElement( FSNS( XML_c, XML_explosion ),
4141 XML_val, OString::number( nOffset ) );
4143 break;
4145 default:
4146 break;
4148 exportShapeProps( xPropSet );
4150 pFS->endElement( FSNS( XML_c, XML_dPt ) );
4155 // Export Data Point Property in Charts even if the VaryColors is false
4156 if( bVaryColorsByPoint )
4157 return;
4159 o3tl::sorted_vector< sal_Int32 > aAttrPointSet;
4160 aAttrPointSet.reserve(aDataPointSeq.getLength());
4161 for (auto p = pPoints; p < pPoints + aDataPointSeq.getLength(); ++p)
4162 aAttrPointSet.insert(*p);
4163 const auto aEndIt = aAttrPointSet.end();
4164 for( nElement = 0; nElement < nSeriesLength; ++nElement )
4166 uno::Reference< beans::XPropertySet > xPropSet;
4167 if( aAttrPointSet.find( nElement ) != aEndIt )
4171 xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet(
4172 xSeries, nElement, getModel() );
4174 catch( const uno::Exception & )
4176 DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" );
4180 if( xPropSet.is() )
4182 FSHelperPtr pFS = GetFS();
4183 pFS->startElement(FSNS(XML_c, XML_dPt));
4184 pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement));
4186 switch( eChartType )
4188 case chart::TYPEID_BUBBLE:
4189 case chart::TYPEID_HORBAR:
4190 case chart::TYPEID_BAR:
4191 pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0");
4192 exportShapeProps(xPropSet);
4193 break;
4195 case chart::TYPEID_LINE:
4196 case chart::TYPEID_SCATTER:
4197 case chart::TYPEID_RADARLINE:
4198 exportMarker(xPropSet);
4199 break;
4201 default:
4202 exportShapeProps(xPropSet);
4203 break;
4206 pFS->endElement( FSNS( XML_c, XML_dPt ) );
4211 void ChartExport::exportAxesId(bool bPrimaryAxes, bool bCheckCombinedAxes)
4213 sal_Int32 nAxisIdx, nAxisIdy;
4214 bool bPrimaryAxisExists = false;
4215 bool bSecondaryAxisExists = false;
4216 // let's check which axis already exists and which axis is attached to the actual dataseries
4217 if (maAxes.size() >= 2)
4219 bPrimaryAxisExists = bPrimaryAxes && maAxes[1].nAxisType == AXIS_PRIMARY_Y;
4220 bSecondaryAxisExists = !bPrimaryAxes && maAxes[1].nAxisType == AXIS_SECONDARY_Y;
4222 // tdf#114181 keep axes of combined charts
4223 if ( bCheckCombinedAxes && ( bPrimaryAxisExists || bSecondaryAxisExists ) )
4225 nAxisIdx = maAxes[0].nAxisId;
4226 nAxisIdy = maAxes[1].nAxisId;
4228 else
4230 nAxisIdx = lcl_generateRandomValue();
4231 nAxisIdy = lcl_generateRandomValue();
4232 AxesType eXAxis = bPrimaryAxes ? AXIS_PRIMARY_X : AXIS_SECONDARY_X;
4233 AxesType eYAxis = bPrimaryAxes ? AXIS_PRIMARY_Y : AXIS_SECONDARY_Y;
4234 maAxes.emplace_back( eXAxis, nAxisIdx, nAxisIdy );
4235 maAxes.emplace_back( eYAxis, nAxisIdy, nAxisIdx );
4237 FSHelperPtr pFS = GetFS();
4238 pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdx));
4239 pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdy));
4240 if (mbHasZAxis)
4242 sal_Int32 nAxisIdz = 0;
4243 if( isDeep3dChart() )
4245 nAxisIdz = lcl_generateRandomValue();
4246 maAxes.emplace_back( AXIS_PRIMARY_Z, nAxisIdz, nAxisIdy );
4248 pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdz));
4252 void ChartExport::exportGrouping( bool isBar )
4254 FSHelperPtr pFS = GetFS();
4255 Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
4256 // grouping
4257 if( GetProperty( xPropSet, "Stacked" ) )
4258 mAny >>= mbStacked;
4259 if( GetProperty( xPropSet, "Percent" ) )
4260 mAny >>= mbPercent;
4262 const char* grouping = nullptr;
4263 if (mbStacked)
4264 grouping = "stacked";
4265 else if (mbPercent)
4266 grouping = "percentStacked";
4267 else
4269 if( isBar && !isDeep3dChart() )
4271 grouping = "clustered";
4273 else
4274 grouping = "standard";
4276 pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, grouping);
4279 void ChartExport::exportTrendlines( const Reference< chart2::XDataSeries >& xSeries )
4281 FSHelperPtr pFS = GetFS();
4282 Reference< chart2::XRegressionCurveContainer > xRegressionCurveContainer( xSeries, UNO_QUERY );
4283 if( !xRegressionCurveContainer.is() )
4284 return;
4286 const Sequence< Reference< chart2::XRegressionCurve > > aRegCurveSeq = xRegressionCurveContainer->getRegressionCurves();
4287 for( const Reference< chart2::XRegressionCurve >& xRegCurve : aRegCurveSeq )
4289 if (!xRegCurve.is())
4290 continue;
4292 Reference< XPropertySet > xProperties( xRegCurve , uno::UNO_QUERY );
4294 OUString aService;
4295 Reference< lang::XServiceName > xServiceName( xProperties, UNO_QUERY );
4296 if( !xServiceName.is() )
4297 continue;
4299 aService = xServiceName->getServiceName();
4301 if(aService != "com.sun.star.chart2.LinearRegressionCurve" &&
4302 aService != "com.sun.star.chart2.ExponentialRegressionCurve" &&
4303 aService != "com.sun.star.chart2.LogarithmicRegressionCurve" &&
4304 aService != "com.sun.star.chart2.PotentialRegressionCurve" &&
4305 aService != "com.sun.star.chart2.PolynomialRegressionCurve" &&
4306 aService != "com.sun.star.chart2.MovingAverageRegressionCurve")
4307 continue;
4309 pFS->startElement(FSNS(XML_c, XML_trendline));
4311 OUString aName;
4312 xProperties->getPropertyValue("CurveName") >>= aName;
4313 if(!aName.isEmpty())
4315 pFS->startElement(FSNS(XML_c, XML_name));
4316 pFS->writeEscaped(aName);
4317 pFS->endElement( FSNS( XML_c, XML_name) );
4320 exportShapeProps( xProperties );
4322 if( aService == "com.sun.star.chart2.LinearRegressionCurve" )
4324 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "linear");
4326 else if( aService == "com.sun.star.chart2.ExponentialRegressionCurve" )
4328 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "exp");
4330 else if( aService == "com.sun.star.chart2.LogarithmicRegressionCurve" )
4332 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "log");
4334 else if( aService == "com.sun.star.chart2.PotentialRegressionCurve" )
4336 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "power");
4338 else if( aService == "com.sun.star.chart2.PolynomialRegressionCurve" )
4340 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "poly");
4342 sal_Int32 aDegree = 2;
4343 xProperties->getPropertyValue( "PolynomialDegree") >>= aDegree;
4344 pFS->singleElement(FSNS(XML_c, XML_order), XML_val, OString::number(aDegree));
4346 else if( aService == "com.sun.star.chart2.MovingAverageRegressionCurve" )
4348 pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "movingAvg");
4350 sal_Int32 aPeriod = 2;
4351 xProperties->getPropertyValue( "MovingAveragePeriod") >>= aPeriod;
4353 pFS->singleElement(FSNS(XML_c, XML_period), XML_val, OString::number(aPeriod));
4355 else
4357 // should never happen
4358 // This would produce invalid OOXML files so we check earlier for the type
4359 assert(false);
4362 double fExtrapolateForward = 0.0;
4363 double fExtrapolateBackward = 0.0;
4365 xProperties->getPropertyValue("ExtrapolateForward") >>= fExtrapolateForward;
4366 xProperties->getPropertyValue("ExtrapolateBackward") >>= fExtrapolateBackward;
4368 pFS->singleElement( FSNS( XML_c, XML_forward ),
4369 XML_val, OString::number(fExtrapolateForward) );
4371 pFS->singleElement( FSNS( XML_c, XML_backward ),
4372 XML_val, OString::number(fExtrapolateBackward) );
4374 bool bForceIntercept = false;
4375 xProperties->getPropertyValue("ForceIntercept") >>= bForceIntercept;
4377 if (bForceIntercept)
4379 double fInterceptValue = 0.0;
4380 xProperties->getPropertyValue("InterceptValue") >>= fInterceptValue;
4382 pFS->singleElement( FSNS( XML_c, XML_intercept ),
4383 XML_val, OString::number(fInterceptValue) );
4386 // Equation properties
4387 Reference< XPropertySet > xEquationProperties( xRegCurve->getEquationProperties() );
4389 // Show Equation
4390 bool bShowEquation = false;
4391 xEquationProperties->getPropertyValue("ShowEquation") >>= bShowEquation;
4393 // Show R^2
4394 bool bShowCorrelationCoefficient = false;
4395 xEquationProperties->getPropertyValue("ShowCorrelationCoefficient") >>= bShowCorrelationCoefficient;
4397 pFS->singleElement( FSNS( XML_c, XML_dispRSqr ),
4398 XML_val, ToPsz10(bShowCorrelationCoefficient) );
4400 pFS->singleElement(FSNS(XML_c, XML_dispEq), XML_val, ToPsz10(bShowEquation));
4402 pFS->endElement( FSNS( XML_c, XML_trendline ) );
4406 void ChartExport::exportMarker(const Reference< XPropertySet >& xPropSet)
4408 chart2::Symbol aSymbol;
4409 if( GetProperty( xPropSet, "Symbol" ) )
4410 mAny >>= aSymbol;
4412 if(aSymbol.Style != chart2::SymbolStyle_STANDARD && aSymbol.Style != chart2::SymbolStyle_NONE)
4413 return;
4415 FSHelperPtr pFS = GetFS();
4416 pFS->startElement(FSNS(XML_c, XML_marker));
4418 sal_Int32 nSymbol = aSymbol.StandardSymbol;
4419 // TODO: more properties support for marker
4420 const char* pSymbolType; // no initialization here, to let compiler warn if we have a code path
4421 // where it stays uninitialized
4422 switch( nSymbol )
4424 case 0:
4425 pSymbolType = "square";
4426 break;
4427 case 1:
4428 pSymbolType = "diamond";
4429 break;
4430 case 2:
4431 case 3:
4432 case 4:
4433 case 5:
4434 pSymbolType = "triangle";
4435 break;
4436 case 8:
4437 pSymbolType = "circle";
4438 break;
4439 case 9:
4440 pSymbolType = "star";
4441 break;
4442 case 10:
4443 pSymbolType = "x"; // in MS office 2010 built in symbol marker 'X' is represented as 'x'
4444 break;
4445 case 11:
4446 pSymbolType = "plus";
4447 break;
4448 case 13:
4449 pSymbolType = "dash";
4450 break;
4451 default:
4452 pSymbolType = "square";
4453 break;
4456 bool bSkipFormatting = false;
4457 if (aSymbol.Style == chart2::SymbolStyle_NONE)
4459 bSkipFormatting = true;
4460 pSymbolType = "none";
4463 pFS->singleElement(FSNS(XML_c, XML_symbol), XML_val, pSymbolType);
4465 if (!bSkipFormatting)
4467 awt::Size aSymbolSize = aSymbol.Size;
4468 sal_Int32 nSize = std::max( aSymbolSize.Width, aSymbolSize.Height );
4470 nSize = nSize/250.0*7.0 + 1; // just guessed based on some test cases,
4471 //the value is always 1 less than the actual value.
4472 nSize = std::clamp( int(nSize), 2, 72 );
4473 pFS->singleElement(FSNS(XML_c, XML_size), XML_val, OString::number(nSize));
4475 pFS->startElement(FSNS(XML_c, XML_spPr));
4477 util::Color aColor = aSymbol.FillColor;
4478 if (GetProperty(xPropSet, "Color"))
4479 mAny >>= aColor;
4481 if (aColor == -1)
4483 pFS->singleElement(FSNS(XML_a, XML_noFill));
4485 else
4486 WriteSolidFill(::Color(ColorTransparency, aColor));
4488 pFS->endElement( FSNS( XML_c, XML_spPr ) );
4491 pFS->endElement( FSNS( XML_c, XML_marker ) );
4494 void ChartExport::exportSmooth()
4496 FSHelperPtr pFS = GetFS();
4497 Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY );
4498 sal_Int32 nSplineType = 0;
4499 if( GetProperty( xPropSet, "SplineType" ) )
4500 mAny >>= nSplineType;
4501 const char* pVal = nSplineType != 0 ? "1" : "0";
4502 pFS->singleElement(FSNS(XML_c, XML_smooth), XML_val, pVal);
4505 void ChartExport::exportFirstSliceAng( )
4507 FSHelperPtr pFS = GetFS();
4508 sal_Int32 nStartingAngle = 0;
4509 Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
4510 if( GetProperty( xPropSet, "StartingAngle" ) )
4511 mAny >>= nStartingAngle;
4513 // convert to ooxml angle
4514 nStartingAngle = (450 - nStartingAngle ) % 360;
4515 pFS->singleElement(FSNS(XML_c, XML_firstSliceAng), XML_val, OString::number(nStartingAngle));
4518 namespace {
4520 const char* getErrorBarStyle(sal_Int32 nErrorBarStyle)
4522 switch(nErrorBarStyle)
4524 case cssc::ErrorBarStyle::NONE:
4525 return nullptr;
4526 case cssc::ErrorBarStyle::VARIANCE:
4527 break;
4528 case cssc::ErrorBarStyle::STANDARD_DEVIATION:
4529 return "stdDev";
4530 case cssc::ErrorBarStyle::ABSOLUTE:
4531 return "fixedVal";
4532 case cssc::ErrorBarStyle::RELATIVE:
4533 return "percentage";
4534 case cssc::ErrorBarStyle::ERROR_MARGIN:
4535 break;
4536 case cssc::ErrorBarStyle::STANDARD_ERROR:
4537 return "stdErr";
4538 case cssc::ErrorBarStyle::FROM_DATA:
4539 return "cust";
4540 default:
4541 assert(false && "can't happen");
4543 return nullptr;
4546 Reference< chart2::data::XDataSequence> getLabeledSequence(
4547 const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > >& aSequences,
4548 bool bPositive )
4550 OUString aDirection;
4551 if(bPositive)
4552 aDirection = "positive";
4553 else
4554 aDirection = "negative";
4556 for( const auto& rSequence : aSequences )
4558 if( rSequence.is())
4560 uno::Reference< chart2::data::XDataSequence > xSequence( rSequence->getValues());
4561 uno::Reference< beans::XPropertySet > xSeqProp( xSequence, uno::UNO_QUERY_THROW );
4562 OUString aRole;
4563 if( ( xSeqProp->getPropertyValue( "Role" ) >>= aRole ) &&
4564 aRole.match( "error-bars" ) && aRole.indexOf(aDirection) >= 0 )
4566 return xSequence;
4571 return Reference< chart2::data::XDataSequence > ();
4576 void ChartExport::exportErrorBar(const Reference< XPropertySet>& xErrorBarProps, bool bYError)
4578 sal_Int32 nErrorBarStyle = cssc::ErrorBarStyle::NONE;
4579 xErrorBarProps->getPropertyValue("ErrorBarStyle") >>= nErrorBarStyle;
4580 const char* pErrorBarStyle = getErrorBarStyle(nErrorBarStyle);
4581 if(!pErrorBarStyle)
4582 return;
4584 FSHelperPtr pFS = GetFS();
4585 pFS->startElement(FSNS(XML_c, XML_errBars));
4586 pFS->singleElement(FSNS(XML_c, XML_errDir), XML_val, bYError ? "y" : "x");
4587 bool bPositive = false, bNegative = false;
4588 xErrorBarProps->getPropertyValue("ShowPositiveError") >>= bPositive;
4589 xErrorBarProps->getPropertyValue("ShowNegativeError") >>= bNegative;
4590 const char* pErrBarType;
4591 if(bPositive && bNegative)
4592 pErrBarType = "both";
4593 else if(bPositive)
4594 pErrBarType = "plus";
4595 else if(bNegative)
4596 pErrBarType = "minus";
4597 else
4599 // what the hell should we do now?
4600 // at least this makes the file valid
4601 pErrBarType = "both";
4603 pFS->singleElement(FSNS(XML_c, XML_errBarType), XML_val, pErrBarType);
4604 pFS->singleElement(FSNS(XML_c, XML_errValType), XML_val, pErrorBarStyle);
4605 pFS->singleElement(FSNS(XML_c, XML_noEndCap), XML_val, "0");
4606 if(nErrorBarStyle == cssc::ErrorBarStyle::FROM_DATA)
4608 uno::Reference< chart2::data::XDataSource > xDataSource(xErrorBarProps, uno::UNO_QUERY);
4609 Sequence< Reference < chart2::data::XLabeledDataSequence > > aSequences =
4610 xDataSource->getDataSequences();
4612 if(bPositive)
4614 exportSeriesValues(getLabeledSequence(aSequences, true), XML_plus);
4617 if(bNegative)
4619 exportSeriesValues(getLabeledSequence(aSequences, false), XML_minus);
4622 else
4624 double nVal = 0.0;
4625 if(nErrorBarStyle == cssc::ErrorBarStyle::STANDARD_DEVIATION)
4627 xErrorBarProps->getPropertyValue("Weight") >>= nVal;
4629 else
4631 if(bPositive)
4632 xErrorBarProps->getPropertyValue("PositiveError") >>= nVal;
4633 else
4634 xErrorBarProps->getPropertyValue("NegativeError") >>= nVal;
4637 pFS->singleElement(FSNS(XML_c, XML_val), XML_val, OString::number(nVal));
4640 exportShapeProps( xErrorBarProps );
4642 pFS->endElement( FSNS( XML_c, XML_errBars) );
4645 void ChartExport::exportView3D()
4647 Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
4648 if( !xPropSet.is() )
4649 return;
4650 FSHelperPtr pFS = GetFS();
4651 pFS->startElement(FSNS(XML_c, XML_view3D));
4652 sal_Int32 eChartType = getChartType( );
4653 // rotX
4654 if( GetProperty( xPropSet, "RotationHorizontal" ) )
4656 sal_Int32 nRotationX = 0;
4657 mAny >>= nRotationX;
4658 if( nRotationX < 0 )
4660 if(eChartType == chart::TYPEID_PIE)
4662 /* In OOXML we get value in 0..90 range for pie chart X rotation , whereas we expect it to be in -90..90 range,
4663 so we convert that during import. It is modified in View3DConverter::convertFromModel()
4664 here we convert it back to 0..90 as we received in import */
4665 nRotationX += 90; // X rotation (map Chart2 [-179,180] to OOXML [0..90])
4667 else
4668 nRotationX += 360; // X rotation (map Chart2 [-179,180] to OOXML [-90..90])
4670 pFS->singleElement(FSNS(XML_c, XML_rotX), XML_val, OString::number(nRotationX));
4672 // rotY
4673 if( GetProperty( xPropSet, "RotationVertical" ) )
4675 // Y rotation (map Chart2 [-179,180] to OOXML [0..359])
4676 if( eChartType == chart::TYPEID_PIE && GetProperty( xPropSet, "StartingAngle" ) )
4678 // Y rotation used as 'first pie slice angle' in 3D pie charts
4679 sal_Int32 nStartingAngle=0;
4680 mAny >>= nStartingAngle;
4681 // convert to ooxml angle
4682 nStartingAngle = (450 - nStartingAngle ) % 360;
4683 pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nStartingAngle));
4685 else
4687 sal_Int32 nRotationY = 0;
4688 mAny >>= nRotationY;
4689 // Y rotation (map Chart2 [-179,180] to OOXML [0..359])
4690 if( nRotationY < 0 )
4691 nRotationY += 360;
4692 pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nRotationY));
4695 // rAngAx
4696 if( GetProperty( xPropSet, "RightAngledAxes" ) )
4698 bool bRightAngled = false;
4699 mAny >>= bRightAngled;
4700 const char* sRightAngled = bRightAngled ? "1":"0";
4701 pFS->singleElement(FSNS(XML_c, XML_rAngAx), XML_val, sRightAngled);
4703 // perspective
4704 if( GetProperty( xPropSet, "Perspective" ) )
4706 sal_Int32 nPerspective = 0;
4707 mAny >>= nPerspective;
4708 // map Chart2 [0,100] to OOXML [0..200]
4709 nPerspective *= 2;
4710 pFS->singleElement(FSNS(XML_c, XML_perspective), XML_val, OString::number(nPerspective));
4712 pFS->endElement( FSNS( XML_c, XML_view3D ) );
4715 bool ChartExport::isDeep3dChart()
4717 bool isDeep = false;
4718 if( mbIs3DChart )
4720 Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
4721 if( GetProperty( xPropSet, "Deep" ) )
4722 mAny >>= isDeep;
4724 return isDeep;
4727 OUString ChartExport::getNumberFormatCode(sal_Int32 nKey) const
4729 /* XXX if this was called more than one or two times per export the two
4730 * SvNumberFormatter instances and NfKeywordTable should be member
4731 * variables and initialized only once. */
4733 OUString aCode("General"); // init with fallback
4734 uno::Reference<util::XNumberFormatsSupplier> xNumberFormatsSupplier(mxChartModel, uno::UNO_QUERY_THROW);
4735 SvNumberFormatsSupplierObj* pSupplierObj = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( xNumberFormatsSupplier);
4736 if (!pSupplierObj)
4737 return aCode;
4739 SvNumberFormatter* pNumberFormatter = pSupplierObj->GetNumberFormatter();
4740 if (!pNumberFormatter)
4741 return aCode;
4743 SvNumberFormatter aTempFormatter( comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US);
4744 NfKeywordTable aKeywords;
4745 aTempFormatter.FillKeywordTableForExcel( aKeywords);
4746 aCode = pNumberFormatter->GetFormatStringForExcel( nKey, aKeywords, aTempFormatter);
4748 return aCode;
4751 }// oox
4753 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */