chart2/qa/extras/chart2export2.cxx | 11 chart2/qa/extras/data/pptx/funnel-pp1.pptx |binary include/oox/drawingml/chart/datasourcemodel.hxx | 18 include/oox/export/chartexport.hxx | 13 include/test/xmltesttools.hxx | 23 oox/inc/drawingml/chart/chartspacemodel.hxx | 2 oox/inc/drawingml/chart/datasourcecontext.hxx | 17 oox/inc/drawingml/chart/plotareaconverter.hxx | 5 oox/inc/drawingml/chart/seriesconverter.hxx | 3 oox/inc/drawingml/chart/seriesmodel.hxx | 19 oox/inc/drawingml/chart/typegroupconverter.hxx | 8 oox/source/drawingml/chart/chartconverter.cxx | 1 oox/source/drawingml/chart/chartspaceconverter.cxx | 3 oox/source/drawingml/chart/chartspacefragment.cxx | 4 oox/source/drawingml/chart/datasourcecontext.cxx | 110 +++ oox/source/drawingml/chart/plotareaconverter.cxx | 18 oox/source/drawingml/chart/seriescontext.cxx | 40 - oox/source/drawingml/chart/seriesconverter.cxx | 11 oox/source/drawingml/chart/seriesmodel.cxx | 1 oox/source/drawingml/chart/typegroupconverter.cxx | 30 oox/source/export/chartexport.cxx | 693 ++++++++++++++------- test/source/xmltesttools.cxx | 19 22 files changed, 781 insertions(+), 268 deletions(-)
New commits: commit c84c7b3c81308fbafa273c76a75f4a71596666b8 Author: Kurt Nordback <kurt.nordb...@collabora.com> AuthorDate: Fri Jul 4 12:43:15 2025 -0600 Commit: Tomaž Vajngerl <qui...@gmail.com> CommitDate: Fri Jul 11 18:35:29 2025 +0200 tdf#165742 Step 4.7: Add support for internal data Word and Powerpoint store data internal to the chart in .docx and .pptx formats, respectively. In chartex this uses the <cx:chartData> and <cx:data> tags, and unlike in pre-2016 charts, is at the chartSpace level rather than internal to individual series. Provide support for these tags and data structures. Note that this does not yet allow .docx and .pptx files with chartex contents to round-trip successfully (MSO -> LO -> MSO). Additional work remains needed for this. Change-Id: I1e91cd0e014787ce19303a906ff76cd6c7c4b0fa Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187426 Reviewed-by: Tomaž Vajngerl <qui...@gmail.com> Tested-by: Jenkins diff --git a/chart2/qa/extras/chart2export2.cxx b/chart2/qa/extras/chart2export2.cxx index 93492645afc4..e6cf525a2a62 100644 --- a/chart2/qa/extras/chart2export2.cxx +++ b/chart2/qa/extras/chart2export2.cxx @@ -170,6 +170,17 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexTitleXLSX) u"Funnel chart!"); } +CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexPPTX) +{ + loadFromFile(u"pptx/funnel-pp1.pptx"); + save(u"Impress Office Open XML"_ustr); + xmlDocUniquePtr pXmlDoc = parseExport(u"ppt/charts/chartEx1.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series", 3, 0, + "layoutId", u"funnel"); +} + CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testAxisTitleRotationXLSX) { loadFromFile(u"xlsx/axis_title_rotation.xlsx"); diff --git a/chart2/qa/extras/data/pptx/funnel-pp1.pptx b/chart2/qa/extras/data/pptx/funnel-pp1.pptx new file mode 100644 index 000000000000..df34aa1618d0 Binary files /dev/null and b/chart2/qa/extras/data/pptx/funnel-pp1.pptx differ diff --git a/include/oox/drawingml/chart/datasourcemodel.hxx b/include/oox/drawingml/chart/datasourcemodel.hxx index 49b8dcd90717..7cc52208e6a0 100644 --- a/include/oox/drawingml/chart/datasourcemodel.hxx +++ b/include/oox/drawingml/chart/datasourcemodel.hxx @@ -58,6 +58,24 @@ struct DataSourceModel ~DataSourceModel(); }; +enum class DataSourceType: sal_Int32; + +// Data source for chartex +struct DataSourceCxModel +{ + // Same as definition in SeriesModel + typedef ModelMap< DataSourceType, DataSourceModel > DataSourceMap; + typedef ModelMap<sal_Int32, DataSourceMap> DataMap; + + // Chartex data can have three levels of lists/maps: + // 1. Multiple data series, each with a <cx:data> tag and indexed by id. + // 2. Within a series, multiple sources ("val", "cat", etc.) + // 3. Within a source, multiple points, with corresponding index. + DataMap maSourceMap; /// Data for chartex. + + explicit DataSourceCxModel() = default; +}; + } // namespace oox::drawingml::chart diff --git a/include/oox/export/chartexport.hxx b/include/oox/export/chartexport.hxx index 2d745d295986..348af941c88a 100644 --- a/include/oox/export/chartexport.hxx +++ b/include/oox/export/chartexport.hxx @@ -174,9 +174,8 @@ private: void exportChart( const css::uno::Reference< css::chart::XChartDocument >& rChartDoc, bool bIsChartex); - void exportData( const css::uno::Reference< - css::chart::XChartDocument >& rChartDoc, - bool bIsChartex); + void exportData_chartex( const css::uno::Reference< + css::chart::XChartDocument >& rChartDoc); void exportExternalData( const css::uno::Reference< css::chart::XChartDocument >& rChartDoc, bool bIsChartex); @@ -217,10 +216,12 @@ private: void exportUpDownBars(const css::uno::Reference< css::chart2::XChartType >& xChartType ); void exportAllSeries(const css::uno::Reference<css::chart2::XChartType>& xChartType, bool& rPrimaryAxes); - void exportSeries(const css::uno::Reference< css::chart2::XChartType >& xChartType, + void exportSeries_chart(const css::uno::Reference< css::chart2::XChartType >& xChartType, const css::uno::Sequence<css::uno::Reference<css::chart2::XDataSeries> >& rSeriesSeq, - bool& rPrimaryAxes, - bool bIsChartex); + bool& rPrimaryAxes); + void exportSeries_chartex(const css::uno::Reference< css::chart2::XChartType >& xChartType, + const css::uno::Sequence<css::uno::Reference<css::chart2::XDataSeries> >& rSeriesSeq, + const char* sTypeName); void exportVaryColors(const css::uno::Reference<css::chart2::XChartType>& xChartType); void exportCandleStickSeries( diff --git a/include/test/xmltesttools.hxx b/include/test/xmltesttools.hxx index 8f5df7c77465..c6f10dd7edb6 100644 --- a/include/test/xmltesttools.hxx +++ b/include/test/xmltesttools.hxx @@ -56,6 +56,16 @@ protected: { return getXPath(pXmlDoc, sXPath.getStr(), pAttribute); } + /** + * Same as above, but where the expected number of nodes with + * the given path is nNumPaths and the desired node index is nPathIdx. + */ + OUString getXPath(const xmlDocUniquePtr& pXmlDoc, const char* pXPath, int nNumPaths, int nPathIdx, const char* pAttribute); + OUString getXPath(const xmlDocUniquePtr& pXmlDoc, const OString& sXPath, int nNumPaths, int nPathIdx, const char* pAttribute) + { + assert(nPathIdx < nNumPaths); + return getXPath(pXmlDoc, sXPath.getStr(), nNumPaths, nPathIdx, pAttribute); + } /** * Same as the assertXPathContent(), but don't assert: return the string instead. */ @@ -95,6 +105,19 @@ protected: { assertXPathAttrs(pXmlDoc, sXPath.getStr(), aPairVector); } + /** + * Assert that pXPath exists, returns nNumNodes nodes, and the attribute + * value of node number nPathIdx equals the rExpected value. + */ + void assertXPath(const xmlDocUniquePtr& pXmlDoc, const char* pXPath, + int nNumNodes, int nNodeIdx, + const char* pAttribute, std::u16string_view rExpectedValue); + void assertXPath(const xmlDocUniquePtr& pXmlDoc, const OString& sXPath, + int nNumNodes, int nNodeIdx, + const char* pAttribute, std::u16string_view rExpectedValue) + { + assertXPath(pXmlDoc, sXPath.getStr(), nNumNodes, nNodeIdx, pAttribute, rExpectedValue); + } /** * Given a double for the rExpected value, assert that pXPath exists, returns exactly one node, diff --git a/oox/inc/drawingml/chart/chartspacemodel.hxx b/oox/inc/drawingml/chart/chartspacemodel.hxx index d66abb21f9d7..dc3ab8674634 100644 --- a/oox/inc/drawingml/chart/chartspacemodel.hxx +++ b/oox/inc/drawingml/chart/chartspacemodel.hxx @@ -39,6 +39,7 @@ struct ChartSpaceModel typedef ModelRef< View3DModel > View3DRef; typedef ModelRef< TitleModel > TitleRef; typedef ModelRef< LegendModel > LegendRef; + typedef ModelRef< DataSourceCxModel > DataSourceRef; ShapeRef mxShapeProp; /// Chart frame formatting. TextBodyRef mxTextProp; /// Global chart text formatting. @@ -59,6 +60,7 @@ struct ChartSpaceModel bool mbPlotVisOnly; /// True = plot visible cells in a sheet only. bool mbShowLabelsOverMax;/// True = show labels over chart maximum. bool mbPivotChart; /// True = pivot chart. + DataSourceRef maCxData; /// Data for Chartex. explicit ChartSpaceModel(bool bMSO2007Doc); ~ChartSpaceModel(); diff --git a/oox/inc/drawingml/chart/datasourcecontext.hxx b/oox/inc/drawingml/chart/datasourcecontext.hxx index 7edb029eb336..eeeccb6d89b8 100644 --- a/oox/inc/drawingml/chart/datasourcecontext.hxx +++ b/oox/inc/drawingml/chart/datasourcecontext.hxx @@ -21,6 +21,7 @@ #define INCLUDED_OOX_DRAWINGML_CHART_DATASOURCECONTEXT_HXX #include <memory> +#include <oox/drawingml/chart/datasourcemodel.hxx> #include <drawingml/chart/chartcontextbase.hxx> class SvNumberFormatter; @@ -86,6 +87,22 @@ public: virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; }; +struct DataSourceCxModel; + +/** Handler for a chartex data source context (cx:chartData, cx:data elements). + */ +class DataSourceCxContext final : public ContextBase< DataSourceCxModel > +{ +public: + explicit DataSourceCxContext( ::oox::core::ContextHandler2Helper& rParent, + DataSourceCxModel& rModel); + virtual ~DataSourceCxContext() override; + + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + + DataSourceCxModel::DataSourceMap *paCurSource; +}; + } // namespace oox::drawingml::chart diff --git a/oox/inc/drawingml/chart/plotareaconverter.hxx b/oox/inc/drawingml/chart/plotareaconverter.hxx index 988405b3247c..750d91cb916c 100644 --- a/oox/inc/drawingml/chart/plotareaconverter.hxx +++ b/oox/inc/drawingml/chart/plotareaconverter.hxx @@ -21,6 +21,8 @@ #define INCLUDED_OOX_DRAWINGML_CHART_PLOTAREACONVERTER_HXX #include <drawingml/chart/converterbase.hxx> +#include <drawingml/chart/seriesmodel.hxx> +#include <oox/drawingml/chart/datasourcemodel.hxx> namespace com::sun::star { namespace chart2 { class XDiagram; } @@ -68,7 +70,8 @@ public: virtual ~PlotAreaConverter() override; /** Converts the OOXML plot area model to a chart2 diagram. */ - void convertFromModel( View3DModel& rView3DModel ); + void convertFromModel( View3DModel& rView3DModel, + DataSourceCxModel& raDataMap ); /** Converts the manual plot area position and size, if set. */ void convertPositionFromModel(); diff --git a/oox/inc/drawingml/chart/seriesconverter.hxx b/oox/inc/drawingml/chart/seriesconverter.hxx index c8cca31d5e3e..23a2c02cc7ef 100644 --- a/oox/inc/drawingml/chart/seriesconverter.hxx +++ b/oox/inc/drawingml/chart/seriesconverter.hxx @@ -21,6 +21,7 @@ #define INCLUDED_OOX_DRAWINGML_CHART_SERIESCONVERTER_HXX #include <drawingml/chart/converterbase.hxx> +#include <oox/drawingml/chart/datasourcemodel.hxx> #include <drawingml/chart/seriesmodel.hxx> namespace com::sun::star { @@ -134,7 +135,7 @@ public: private: css::uno::Reference< css::chart2::data::XLabeledDataSequence > createLabeledDataSequence( - SeriesModel::SourceType eSourceType, + enum DataSourceType eSourceType, const OUString& rRole, bool bUseTextLabel ); }; diff --git a/oox/inc/drawingml/chart/seriesmodel.hxx b/oox/inc/drawingml/chart/seriesmodel.hxx index 4f48115676f0..4c791d67e613 100644 --- a/oox/inc/drawingml/chart/seriesmodel.hxx +++ b/oox/inc/drawingml/chart/seriesmodel.hxx @@ -174,17 +174,17 @@ struct DataPointModel ~DataPointModel(); }; -struct SeriesModel +enum class DataSourceType: sal_Int32 { - enum SourceType - { - CATEGORIES, /// Data point categories. - VALUES, /// Data point values. - POINTS, /// Data point size (e.g. bubble size in bubble charts). - DATALABELS, /// Data point labels. - }; + CATEGORIES, /// Data point categories. + VALUES, /// Data point values. + POINTS, /// Data point size (e.g. bubble size in bubble charts). + DATALABELS, /// Data point labels. +}; - typedef ModelMap< SourceType, DataSourceModel > DataSourceMap; +struct SeriesModel +{ + typedef ModelMap< DataSourceType, DataSourceModel > DataSourceMap; typedef ModelVector< ErrorBarModel > ErrorBarVector; typedef ModelVector< TrendlineModel > TrendlineVector; typedef ModelVector< DataPointModel > DataPointVector; @@ -208,6 +208,7 @@ struct SeriesModel sal_Int32 mnMarkerSize; /// Size of the series line marker (2...72). sal_Int32 mnMarkerSymbol; /// Series line marker symbol. sal_Int32 mnOrder; /// Series order. + sal_Int32 mnDataId; /// Reference to correct data chunk (chartex) bool mbBubble3d; /// True = show bubbles with 3D shade. bool mbInvertNeg; /// True = invert negative data points. bool mbSmooth; /// True = smooth series line. diff --git a/oox/inc/drawingml/chart/typegroupconverter.hxx b/oox/inc/drawingml/chart/typegroupconverter.hxx index e7b1e16e8cec..57382f0788cd 100644 --- a/oox/inc/drawingml/chart/typegroupconverter.hxx +++ b/oox/inc/drawingml/chart/typegroupconverter.hxx @@ -21,6 +21,8 @@ #define INCLUDED_OOX_DRAWINGML_CHART_TYPEGROUPCONVERTER_HXX #include <drawingml/chart/converterbase.hxx> +#include <oox/drawingml/chart/datasourcemodel.hxx> +#include <drawingml/chart/seriesmodel.hxx> #include <com/sun/star/chart2/PieChartSubType.hpp> namespace com::sun::star { @@ -166,6 +168,12 @@ public: void convertPieExplosion( PropertySet& rPropSet, sal_Int32 nOoxExplosion ) const; /** Converts of-pie types */ css::chart2::PieChartSubType convertOfPieType(sal_Int32 nOoxOfPieType ) const; + /** Move any internal data to the appropriate series. In chartex the data + (if any is internal) is given outside the series, in a child element of + <cx:chartSpace>. Pre-2016 charts have the data inside the series, and + SeriesModel and subsequent handling reflects this. So this function gets + the data to the right place for processing. */ + void moveDataToSeries(DataSourceCxModel::DataMap& raDataMap); private: /** Inserts the passed series into the chart type. Adds additional properties to the series. */ diff --git a/oox/source/drawingml/chart/chartconverter.cxx b/oox/source/drawingml/chart/chartconverter.cxx index 3f004ffd2c9e..659132eebef2 100644 --- a/oox/source/drawingml/chart/chartconverter.cxx +++ b/oox/source/drawingml/chart/chartconverter.cxx @@ -118,6 +118,7 @@ Reference< XDataSequence > ChartConverter::createDataSequence( { // create a single-row array from constant source data // (multiple levels in the case of complex categories) + assert( rDataSeq.mnPointCount > 0); std::vector<Any> aRow(rDataSeq.mnLevelCount * rDataSeq.mnPointCount); for (auto const& elem : rDataSeq.maData) aRow.at(elem.first) = elem.second; diff --git a/oox/source/drawingml/chart/chartspaceconverter.cxx b/oox/source/drawingml/chart/chartspaceconverter.cxx index d2d51fad3c43..37111b865194 100644 --- a/oox/source/drawingml/chart/chartspaceconverter.cxx +++ b/oox/source/drawingml/chart/chartspaceconverter.cxx @@ -157,7 +157,8 @@ void ChartSpaceConverter::convertFromModel( const Reference< XShapes >& rxExtern bool bMSO2007Doc = getFilter().isMSO2007Document(); // convert plot area (container of all chart type groups) PlotAreaConverter aPlotAreaConv( *this, mrModel.mxPlotArea.getOrCreate() ); - aPlotAreaConv.convertFromModel( mrModel.mxView3D.getOrCreate(bMSO2007Doc) ); + aPlotAreaConv.convertFromModel( mrModel.mxView3D.getOrCreate(bMSO2007Doc), + mrModel.maCxData.getOrCreate()); // plot area converter has created the diagram object Reference< XDiagram > xDiagram = getChartDocument()->getFirstDiagram(); diff --git a/oox/source/drawingml/chart/chartspacefragment.cxx b/oox/source/drawingml/chart/chartspacefragment.cxx index 29e77e4b5095..f26b3cd924b4 100644 --- a/oox/source/drawingml/chart/chartspacefragment.cxx +++ b/oox/source/drawingml/chart/chartspacefragment.cxx @@ -24,6 +24,7 @@ #include <drawingml/chart/chartspacemodel.hxx> #include <drawingml/chart/plotareacontext.hxx> #include <drawingml/chart/titlecontext.hxx> +#include <drawingml/chart/datasourcecontext.hxx> #include <oox/core/xmlfilterbase.hxx> #include <oox/helper/attributelist.hxx> #include <oox/token/namespaces.hxx> @@ -142,8 +143,7 @@ ContextHandlerRef ChartSpaceFragment::onCreateContext( sal_Int32 nElement, const case CX_TOKEN(chartSpace) : switch (nElement) { case CX_TOKEN(chartData): - // TODO - return nullptr; + return new DataSourceCxContext(*this, mrModel.maCxData.create()); case CX_TOKEN(chart): return this; case CX_TOKEN(spPr): diff --git a/oox/source/drawingml/chart/datasourcecontext.cxx b/oox/source/drawingml/chart/datasourcecontext.cxx index f4660f5db708..d27b715dbbc7 100644 --- a/oox/source/drawingml/chart/datasourcecontext.cxx +++ b/oox/source/drawingml/chart/datasourcecontext.cxx @@ -17,10 +17,11 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ -#include <drawingml/chart/datasourcecontext.hxx> - #include <oox/drawingml/chart/datasourcemodel.hxx> +#include <drawingml/chart/seriesmodel.hxx> +#include <drawingml/chart/datasourcecontext.hxx> + #include <oox/core/xmlfilterbase.hxx> #include <oox/helper/attributelist.hxx> #include <oox/token/namespaces.hxx> @@ -81,6 +82,23 @@ ContextHandlerRef DoubleSequenceContext::onCreateContext( sal_Int32 nElement, co return this; } break; + case CX_TOKEN(numDim) : + switch (nElement) { + case CX_TOKEN(f): + return this; + case CX_TOKEN(lvl): + mrModel.mnPointCount = rAttribs.getInteger(XML_ptCount, -1); + mrModel.maFormatCode = rAttribs.getString(XML_formatCode, ""); + return this; + } + break; + case CX_TOKEN(lvl) : + switch(nElement) { + case CX_TOKEN(pt): + mnPtIndex = rAttribs.getInteger(XML_idx, -1); + return this; + } + break; } return nullptr; } @@ -90,12 +108,14 @@ void DoubleSequenceContext::onCharacters( const OUString& rChars ) switch( getCurrentElement() ) { case C_TOKEN( f ): + case CX_TOKEN( f ): mrModel.maFormula = rChars; break; case C_TOKEN( formatCode ): mrModel.maFormatCode = rChars; break; case C_TOKEN( v ): + case CX_TOKEN(pt): if( mnPtIndex >= 0 ) { /* Import categories as String even though it could @@ -244,9 +264,11 @@ ContextHandlerRef StringSequenceContext::onCreateContext( sal_Int32 nElement, co break; case C_TOKEN( lvl ): + case CX_TOKEN( lvl ): switch (nElement) { case C_TOKEN(pt): + case CX_TOKEN(pt): mnPtIndex = rAttribs.getInteger(XML_idx, -1); return this; } @@ -259,6 +281,14 @@ ContextHandlerRef StringSequenceContext::onCreateContext( sal_Int32 nElement, co return this; } break; + case CX_TOKEN(strDim) : + switch (nElement) { + case CX_TOKEN(f): + return this; + case CX_TOKEN(lvl): + mrModel.mnPointCount = rAttribs.getInteger(XML_ptCount, -1); + return this; + } } return nullptr; } @@ -275,8 +305,11 @@ void StringSequenceContext::onCharacters( const OUString& rChars ) mrModel.maFormula = rChars; break; case C_TOKEN( v ): - if( mnPtIndex >= 0 ) + case CX_TOKEN(pt): + if( mnPtIndex >= 0 ) { + assert(mrModel.mnPointCount > 0); mrModel.maData[ (mrModel.mnLevelCount-1) * mrModel.mnPointCount + mnPtIndex ] <<= rChars; + } break; } } @@ -290,7 +323,8 @@ DataSourceContext::~DataSourceContext() { } -ContextHandlerRef DataSourceContext::onCreateContext( sal_Int32 nElement, const AttributeList& ) +ContextHandlerRef DataSourceContext::onCreateContext( sal_Int32 nElement, const + AttributeList&) { switch( getCurrentElement() ) { @@ -330,6 +364,74 @@ ContextHandlerRef DataSourceContext::onCreateContext( sal_Int32 nElement, const return nullptr; } +// ===== +// DataSourceCxContext: handler for chartex data sources +// ===== +DataSourceCxContext::DataSourceCxContext( ContextHandler2Helper& rParent, + DataSourceCxModel& rModel ) : + ContextBase< DataSourceCxModel >( rParent, rModel ), + paCurSource(nullptr) +{ +} + +DataSourceCxContext::~DataSourceCxContext() +{ +} + +ContextHandlerRef DataSourceCxContext::onCreateContext(sal_Int32 nElement, const AttributeList& rAttribs) +{ + switch( getCurrentElement() ) + { + case CX_TOKEN(chartData) : + switch (nElement) { + case CX_TOKEN(externalData) : + return nullptr; // TODO + case CX_TOKEN(data) : + paCurSource = &mrModel.maSourceMap.create(rAttribs.getInteger(XML_id, -1)); + return this; + } + break; + case CX_TOKEN(data) : + switch (nElement) { + case CX_TOKEN(numDim) : + { + assert(paCurSource); + OUString sType = rAttribs.getString(XML_type, "val"); + if (sType == "cat") { + DataSourceModel& rDataModel = paCurSource->create(DataSourceType::CATEGORIES); + return new DoubleSequenceContext(*this, + rDataModel.mxDataSeq.create()); + } else { + // default is VALUES + DataSourceModel& rDataModel = paCurSource->create(DataSourceType::VALUES); + return new DoubleSequenceContext(*this, + rDataModel.mxDataSeq.create()); + } + break; + } + + case CX_TOKEN(strDim) : + { + assert(paCurSource); + OUString sType = rAttribs.getString(XML_type, "cat"); + if (sType == "val") { + DataSourceModel& rDataModel = paCurSource->create(DataSourceType::VALUES); + return new StringSequenceContext(*this, + rDataModel.mxDataSeq.create()); + } else { + // default is CATEGORIES + DataSourceModel& rDataModel = paCurSource->create(DataSourceType::CATEGORIES); + return new StringSequenceContext(*this, + rDataModel.mxDataSeq.create()); + } + } + } + break; + + } + return nullptr; +} + } // namespace oox::drawingml::chart /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/chart/plotareaconverter.cxx b/oox/source/drawingml/chart/plotareaconverter.cxx index 1fa8b6f9b673..95496906fdea 100644 --- a/oox/source/drawingml/chart/plotareaconverter.cxx +++ b/oox/source/drawingml/chart/plotareaconverter.cxx @@ -32,6 +32,7 @@ #include <drawingml/chart/axisconverter.hxx> #include <drawingml/chart/plotareamodel.hxx> #include <drawingml/chart/typegroupconverter.hxx> +#include <drawingml/chart/seriesmodel.hxx> #include <oox/core/xmlfilterbase.hxx> #include <oox/token/namespaces.hxx> #include <oox/token/properties.hxx> @@ -71,6 +72,7 @@ public: const Reference< XDiagram >& rxDiagram, View3DModel& rView3DModel, sal_Int32 nAxesSetIdx, + DataSourceCxModel::DataMap& raSourceMap, bool bSupportsVaryColorsByPoint, bool bUseFixedInnerSize ); @@ -112,6 +114,7 @@ ModelRef< AxisModel > lclGetOrCreateAxis( const AxesSetModel::AxisMap& rFromAxes void AxesSetConverter::convertFromModel( const Reference< XDiagram >& rxDiagram, View3DModel& rView3DModel, sal_Int32 nAxesSetIdx, + DataSourceCxModel::DataMap& raSourceMap, bool bSupportsVaryColorsByPoint, bool bUseFixedInnerSize) { // create type group converter objects for all type groups @@ -169,6 +172,13 @@ void AxesSetConverter::convertFromModel( const Reference< XDiagram >& rxDiagram, to the data provider attached to the chart document. */ if( xCoordSystem.is() ) { + // Transfer any (chartex) data, specified at the chartSpace level, + // into the appropriate series. This needs to happen before the + // calls to AxisConverter::convertFromModel() below. + for (auto const& typeGroup : aTypeGroups) { + typeGroup->moveDataToSeries(raSourceMap); + } + bool bMSO2007Doc = getFilter().isMSO2007Document(); // convert all axes (create missing axis models) ModelRef< AxisModel > xXAxis = lclGetOrCreateAxis( mrModel.maAxes, API_X_AXIS, rFirstTypeGroup.getTypeInfo().mbCategoryAxis ? C_TOKEN( catAx ) : C_TOKEN( valAx ), bMSO2007Doc ); @@ -191,7 +201,8 @@ void AxesSetConverter::convertFromModel( const Reference< XDiagram >& rxDiagram, // convert all chart type groups, this converts all series data and formatting for (auto const& typeGroup : aTypeGroups) - typeGroup->convertFromModel( rxDiagram, xCoordSystem, nAxesSetIdx, bSupportsVaryColorsByPoint ); + typeGroup->convertFromModel( rxDiagram, xCoordSystem, + nAxesSetIdx,bSupportsVaryColorsByPoint ); } } catch( Exception& ) @@ -313,7 +324,8 @@ PlotAreaConverter::~PlotAreaConverter() { } -void PlotAreaConverter::convertFromModel( View3DModel& rView3DModel ) +void PlotAreaConverter::convertFromModel( View3DModel& rView3DModel, + DataSourceCxModel& rDataCxModel ) { /* Create the diagram object and attach it to the chart document. One diagram is used to carry all coordinate systems and data series. */ @@ -426,7 +438,7 @@ void PlotAreaConverter::convertFromModel( View3DModel& rView3DModel ) { AxesSetConverter aAxesSetConv(*this, *axesSet); aAxesSetConv.convertFromModel(xDiagram, rView3DModel, nAxesSetIdx, - bSupportsVaryColorsByPoint, bUseFixedInnerSize); + rDataCxModel.maSourceMap, bSupportsVaryColorsByPoint, bUseFixedInnerSize); if(nAxesSetIdx == nStartAxesSetIdx) { maAutoTitle = aAxesSetConv.getAutomaticTitle(); diff --git a/oox/source/drawingml/chart/seriescontext.cxx b/oox/source/drawingml/chart/seriescontext.cxx index 88688642bebe..19134c7a54a7 100644 --- a/oox/source/drawingml/chart/seriescontext.cxx +++ b/oox/source/drawingml/chart/seriescontext.cxx @@ -452,10 +452,10 @@ ContextHandlerRef SeriesContextBase::onCreateContext( sal_Int32 nElement, const { case C_TOKEN( ext ): case CX_TOKEN( ext ): - if (mrModel.maSources.has( SeriesModel::DATALABELS )) + if (mrModel.maSources.has( DataSourceType::DATALABELS )) break; - DataSourceModel& rLabelsSource = mrModel.maSources.create( SeriesModel::DATALABELS ); + DataSourceModel& rLabelsSource = mrModel.maSources.create( DataSourceType::DATALABELS ); if (mrModel.mxLabels.is()) mrModel.mxLabels->mpLabelsSource = &rLabelsSource; return new DataSourceContext( *this, rLabelsSource ); @@ -483,7 +483,7 @@ ContextHandlerRef AreaSeriesContext::onCreateContext( sal_Int32 nElement, const switch( nElement ) { case C_TOKEN( cat ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::CATEGORIES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::CATEGORIES ) ); case C_TOKEN( errBars ): return new ErrorBarContext( *this, mrModel.maErrorBars.create(bMSO2007Doc) ); case C_TOKEN( dLbls ): @@ -493,7 +493,7 @@ ContextHandlerRef AreaSeriesContext::onCreateContext( sal_Int32 nElement, const case C_TOKEN( trendline ): return new TrendlineContext( *this, mrModel.maTrendlines.create(bMSO2007Doc) ); case C_TOKEN( val ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::VALUES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::VALUES ) ); } break; } @@ -518,7 +518,7 @@ ContextHandlerRef BarSeriesContext::onCreateContext( sal_Int32 nElement, const A switch( nElement ) { case C_TOKEN( cat ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::CATEGORIES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::CATEGORIES ) ); case C_TOKEN( dLbls ): return new DataLabelsContext( *this, mrModel.mxLabels.create(bMSO2007Doc) ); case C_TOKEN( dPt ): @@ -536,7 +536,7 @@ ContextHandlerRef BarSeriesContext::onCreateContext( sal_Int32 nElement, const A case C_TOKEN( trendline ): return new TrendlineContext( *this, mrModel.maTrendlines.create(bMSO2007Doc) ); case C_TOKEN( val ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::VALUES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::VALUES ) ); } break; } @@ -564,7 +564,7 @@ ContextHandlerRef BubbleSeriesContext::onCreateContext( sal_Int32 nElement, cons mrModel.mbBubble3d = rAttribs.getBool( XML_val, !bMSO2007Doc ); return nullptr; case C_TOKEN( bubbleSize ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::POINTS ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::POINTS ) ); case C_TOKEN( dLbls ): return new DataLabelsContext( *this, mrModel.mxLabels.create(bMSO2007Doc) ); case C_TOKEN( dPt ): @@ -577,9 +577,9 @@ ContextHandlerRef BubbleSeriesContext::onCreateContext( sal_Int32 nElement, cons case C_TOKEN( trendline ): return new TrendlineContext( *this, mrModel.maTrendlines.create(bMSO2007Doc) ); case C_TOKEN( xVal ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::CATEGORIES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::CATEGORIES ) ); case C_TOKEN( yVal ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::VALUES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::VALUES ) ); } break; } @@ -604,7 +604,7 @@ ContextHandlerRef LineSeriesContext::onCreateContext( sal_Int32 nElement, const switch( nElement ) { case C_TOKEN( cat ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::CATEGORIES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::CATEGORIES ) ); case C_TOKEN( dLbls ): return new DataLabelsContext( *this, mrModel.mxLabels.create(bMSO2007Doc) ); case C_TOKEN( dPt ): @@ -619,7 +619,7 @@ ContextHandlerRef LineSeriesContext::onCreateContext( sal_Int32 nElement, const case C_TOKEN( trendline ): return new TrendlineContext( *this, mrModel.maTrendlines.create(bMSO2007Doc) ); case C_TOKEN( val ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::VALUES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::VALUES ) ); } break; } @@ -644,7 +644,7 @@ ContextHandlerRef PieSeriesContext::onCreateContext( sal_Int32 nElement, const A switch( nElement ) { case C_TOKEN( cat ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::CATEGORIES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::CATEGORIES ) ); case C_TOKEN( dLbls ): return new DataLabelsContext( *this, mrModel.mxLabels.create(bMSO2007Doc) ); case C_TOKEN( dPt ): @@ -653,7 +653,7 @@ ContextHandlerRef PieSeriesContext::onCreateContext( sal_Int32 nElement, const A mrModel.mnExplosion = rAttribs.getInteger( XML_val, 0 ); return nullptr; case C_TOKEN( val ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::VALUES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::VALUES ) ); } break; } @@ -678,7 +678,7 @@ ContextHandlerRef RadarSeriesContext::onCreateContext( sal_Int32 nElement, const switch( nElement ) { case C_TOKEN( cat ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::CATEGORIES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::CATEGORIES ) ); case C_TOKEN( dLbls ): return new DataLabelsContext( *this, mrModel.mxLabels.create(bMSO2007Doc) ); case C_TOKEN( dPt ): @@ -689,7 +689,7 @@ ContextHandlerRef RadarSeriesContext::onCreateContext( sal_Int32 nElement, const mrModel.mbSmooth = rAttribs.getBool( XML_val, bMSO2007Doc ); return nullptr; case C_TOKEN( val ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::VALUES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::VALUES ) ); } break; } @@ -727,9 +727,9 @@ ContextHandlerRef ScatterSeriesContext::onCreateContext( sal_Int32 nElement, con case C_TOKEN( trendline ): return new TrendlineContext( *this, mrModel.maTrendlines.create(bMSO2007Doc) ); case C_TOKEN( xVal ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::CATEGORIES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::CATEGORIES ) ); case C_TOKEN( yVal ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::VALUES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::VALUES ) ); } break; } @@ -753,9 +753,9 @@ ContextHandlerRef SurfaceSeriesContext::onCreateContext( sal_Int32 nElement, con switch( nElement ) { case C_TOKEN( cat ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::CATEGORIES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::CATEGORIES ) ); case C_TOKEN( val ): - return new DataSourceContext( *this, mrModel.maSources.create( SeriesModel::VALUES ) ); + return new DataSourceContext( *this, mrModel.maSources.create( DataSourceType::VALUES ) ); } break; } @@ -789,7 +789,7 @@ ContextHandlerRef ChartexSeriesContext::onCreateContext( sal_Int32 nElement, con case CX_TOKEN( dataLabels ): return new DataLabelsContext( *this, mrModel.mxLabels.create(false) ); case CX_TOKEN( dataId ): - // TODO + mrModel.mnDataId = rAttribs.getInteger(XML_val, -1); return nullptr; case CX_TOKEN( layoutPr ): // This looks complicated. TODO diff --git a/oox/source/drawingml/chart/seriesconverter.cxx b/oox/source/drawingml/chart/seriesconverter.cxx index 27f25057badf..c0a15a85d922 100644 --- a/oox/source/drawingml/chart/seriesconverter.cxx +++ b/oox/source/drawingml/chart/seriesconverter.cxx @@ -799,12 +799,12 @@ SeriesConverter::~SeriesConverter() Reference< XLabeledDataSequence > SeriesConverter::createCategorySequence( const OUString& rRole ) { - return createLabeledDataSequence(SeriesModel::CATEGORIES, rRole, false); + return createLabeledDataSequence(DataSourceType::CATEGORIES, rRole, false); } Reference< XLabeledDataSequence > SeriesConverter::createValueSequence( const OUString& rRole ) { - return createLabeledDataSequence( SeriesModel::VALUES, rRole, true ); + return createLabeledDataSequence( DataSourceType::VALUES, rRole, true ); } Reference< XDataSeries > SeriesConverter::createDataSeries( const TypeGroupConverter& rTypeGroup, bool bVaryColorsByPoint ) @@ -844,7 +844,8 @@ Reference< XDataSeries > SeriesConverter::createDataSeries( const TypeGroupConve // add size values of bubble charts if( rTypeInfo.meTypeId == TYPEID_BUBBLE ) { - Reference< XLabeledDataSequence > xSizeValueSeq = createLabeledDataSequence( SeriesModel::POINTS, u"values-size"_ustr, true ); + Reference< XLabeledDataSequence > xSizeValueSeq = + createLabeledDataSequence( DataSourceType::POINTS, u"values-size"_ustr, true ); if( xSizeValueSeq.is() ) aLabeledSeqVec.push_back( xSizeValueSeq ); } @@ -936,7 +937,7 @@ Reference< XDataSeries > SeriesConverter::createDataSeries( const TypeGroupConve if( xLabels->maNumberFormat.maFormatCode.isEmpty() ) { // Use number format code from Value series - DataSourceModel* pValues = mrModel.maSources.get( SeriesModel::VALUES ).get(); + DataSourceModel* pValues = mrModel.maSources.get( DataSourceType::VALUES ).get(); if( pValues ) xLabels->maNumberFormat.maFormatCode = pValues->mxDataSeq->maFormatCode; } @@ -950,7 +951,7 @@ Reference< XDataSeries > SeriesConverter::createDataSeries( const TypeGroupConve // private -------------------------------------------------------------------- Reference< XLabeledDataSequence > SeriesConverter::createLabeledDataSequence( - SeriesModel::SourceType eSourceType, const OUString& rRole, bool bUseTextLabel ) + enum DataSourceType eSourceType, const OUString& rRole, bool bUseTextLabel ) { DataSourceModel* pValues = mrModel.maSources.get( eSourceType ).get(); TextModel* pTitle = bUseTextLabel ? mrModel.mxText.get() : nullptr; diff --git a/oox/source/drawingml/chart/seriesmodel.cxx b/oox/source/drawingml/chart/seriesmodel.cxx index 95b0deb225e7..194d4f6ecf3d 100644 --- a/oox/source/drawingml/chart/seriesmodel.cxx +++ b/oox/source/drawingml/chart/seriesmodel.cxx @@ -112,6 +112,7 @@ SeriesModel::SeriesModel(bool bMSO2007Doc) : mnMarkerSize( 5 ), mnMarkerSymbol( XML_auto ), mnOrder( -1 ), + mnDataId(-1), mbBubble3d( !bMSO2007Doc ), mbInvertNeg( !bMSO2007Doc ), mbSmooth( !bMSO2007Doc ) diff --git a/oox/source/drawingml/chart/typegroupconverter.cxx b/oox/source/drawingml/chart/typegroupconverter.cxx index 15711e156cc2..68cb7753fd62 100644 --- a/oox/source/drawingml/chart/typegroupconverter.cxx +++ b/oox/source/drawingml/chart/typegroupconverter.cxx @@ -39,6 +39,7 @@ #include <comphelper/sequence.hxx> #include <osl/diagnose.h> #include <drawingml/lineproperties.hxx> +#include <drawingml/chart/seriesmodel.hxx> #include <drawingml/chart/seriesconverter.hxx> #include <drawingml/chart/typegroupmodel.hxx> #include <oox/core/xmlfilterbase.hxx> @@ -288,16 +289,16 @@ Reference< XLabeledDataSequence > TypeGroupConverter::createCategorySequence() first series, even if it was empty. */ for (auto const& elem : mrModel.maSeries) { - if( elem->maSources.has( SeriesModel::CATEGORIES ) ) + if( elem->maSources.has( DataSourceType::CATEGORIES ) ) { SeriesConverter aSeriesConv(*this, *elem); xLabeledSeq = aSeriesConv.createCategorySequence( u"categories"_ustr ); if (xLabeledSeq.is()) break; } - else if( nMaxValues <= 0 && elem->maSources.has( SeriesModel::VALUES ) ) + else if( nMaxValues <= 0 && elem->maSources.has( DataSourceType::VALUES ) ) { - DataSourceModel *pValues = elem->maSources.get( SeriesModel::VALUES ).get(); + DataSourceModel *pValues = elem->maSources.get( DataSourceType::VALUES ).get(); if( pValues->mxDataSeq.is() ) nMaxValues = pValues->mxDataSeq->maData.size(); } @@ -308,9 +309,9 @@ Reference< XLabeledDataSequence > TypeGroupConverter::createCategorySequence() nMaxValues = 2; typedef RefVector<SeriesModel> SeriesModelVector; SeriesModelVector::value_type aModel = mrModel.maSeries.get(0); - if (!aModel->maSources.has(SeriesModel::CATEGORIES)) + if (!aModel->maSources.has(DataSourceType::CATEGORIES)) { - DataSourceModel &aSrc = aModel->maSources.create( SeriesModel::CATEGORIES ); + DataSourceModel &aSrc = aModel->maSources.create( DataSourceType::CATEGORIES ); DataSequenceModel &aSeq = aSrc.mxDataSeq.create(); aSeq.mnPointCount = nMaxValues; for( sal_Int32 i = 0; i < nMaxValues; i++ ) @@ -324,7 +325,8 @@ Reference< XLabeledDataSequence > TypeGroupConverter::createCategorySequence() void TypeGroupConverter::convertFromModel( const Reference< XDiagram >& rxDiagram, const Reference< XCoordinateSystem >& rxCoordSystem, - sal_Int32 nAxesSetIdx, bool bSupportsVaryColorsByPoint ) + sal_Int32 nAxesSetIdx, + bool bSupportsVaryColorsByPoint ) { try { @@ -612,6 +614,22 @@ PieChartSubType TypeGroupConverter::convertOfPieType(sal_Int32 nOoxOfPieType ) c } } +void TypeGroupConverter::moveDataToSeries(DataSourceCxModel::DataMap& raDataMap) +{ + // For chartex, move data from rDataMap to the appropriate series. In + // chartex the data is given outside the series, in a child element of + // <cx:chartSpace>. Pre-2016 charts have the data inside the series, and + // SeriesModel and subsequent handling reflects this. So here we get the + // data to the right place for processing. + if (!raDataMap.empty()) { + // should only happen for chartex + for (auto const& elem : mrModel.maSeries) { + // This ID must be present in the map + assert(raDataMap.find(elem->mnDataId) != raDataMap.end()); + elem->maSources = *(raDataMap[elem->mnDataId]); + } + } +} // private -------------------------------------------------------------------- diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx index 9f0b875ae6f6..6628ee02227b 100644 --- a/oox/source/export/chartexport.cxx +++ b/oox/source/export/chartexport.cxx @@ -231,12 +231,65 @@ void outputDataPointStyleEntry(FSHelperPtr pFS) pFS->endElement(FSNS(XML_cs, XML_dataPoint)); } +std::vector<Sequence<Reference<chart2::XDataSeries> > > splitDataSeriesByAxis(const Reference< chart2::XChartType >& xChartType) +{ + std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitSeries; + std::map<sal_Int32, size_t> aMapAxisToIndex; + + Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY); + if (xDSCnt.is()) + { + sal_Int32 nAxisIndexOfFirstSeries = -1; + const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries()); + for (const uno::Reference<chart2::XDataSeries>& xSeries : aSeriesSeq) + { + Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY); + if (!xPropSet.is()) + continue; + + sal_Int32 nAxisIndex = -1; + uno::Any aAny = xPropSet->getPropertyValue(u"AttachedAxisIndex"_ustr); + aAny >>= nAxisIndex; + size_t nVectorPos = 0; + if (nAxisIndexOfFirstSeries == -1) + { + nAxisIndexOfFirstSeries = nAxisIndex; + } + + auto it = aMapAxisToIndex.find(nAxisIndex); + if (it == aMapAxisToIndex.end()) + { + aSplitSeries.emplace_back(); + nVectorPos = aSplitSeries.size() - 1; + aMapAxisToIndex.insert(std::pair<sal_Int32, size_t>(nAxisIndex, nVectorPos)); + } + else + { + nVectorPos = it->second; + } + + uno::Sequence<Reference<chart2::XDataSeries> >& rAxisSeriesSeq = aSplitSeries[nVectorPos]; + sal_Int32 nLength = rAxisSeriesSeq.getLength(); + rAxisSeriesSeq.realloc(nLength + 1); + rAxisSeriesSeq.getArray()[nLength] = xSeries; + } + // if the first series attached to secondary axis, then export those series first, which are attached to primary axis + // also the MS Office export every time in this order + if (aSplitSeries.size() > 1 && nAxisIndexOfFirstSeries == 1) + { + std::swap(aSplitSeries[0], aSplitSeries[1]); + } + } + + return aSplitSeries; +} } // unnamed namespace -static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories( const Reference< chart2::XDiagram > & xDiagram, bool& bHasDateCategories ) +static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories( + const Reference< chart2::XDiagram > & xDiagram, bool *pbHasDateCategories ) { - bHasDateCategories = false; + *pbHasDateCategories = false; Reference< chart2::data::XLabeledDataSequence > xResult; try { @@ -259,7 +312,7 @@ static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories( const chart2::ScaleData aScaleData = xAxis->getScaleData(); if( aScaleData.Categories.is()) { - bHasDateCategories = aScaleData.AxisType == chart2::AxisType::DATE; + *pbHasDateCategories = aScaleData.AxisType == chart2::AxisType::DATE; xResult.set( aScaleData.Categories ); break; } @@ -299,7 +352,8 @@ static bool lcl_hasCategoryLabels( const Reference< chart2::XChartDocument >& xC //categories are always the first sequence Reference< chart2::XDiagram > xDiagram( xChartDoc->getFirstDiagram()); bool bDateCategories; - Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( xDiagram, bDateCategories ) ); + Reference< chart2::data::XLabeledDataSequence > xCategories( + lcl_getCategories( xDiagram, &bDateCategories ) ); return xCategories.is(); } @@ -1264,7 +1318,7 @@ void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument pFS->startElement(FSNS(XML_cx, XML_chartData)); exportExternalData(xChartDoc, true); - exportData(xChartDoc, true); + exportData_chartex(xChartDoc); pFS->endElement(FSNS(XML_cx, XML_chartData)); } else { @@ -1313,24 +1367,239 @@ void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument pFS->endElement( FSNS( nChartNS, XML_chartSpace ) ); } -void ChartExport::exportData( [[maybe_unused]] const Reference< css::chart::XChartDocument >& xChartDoc, - bool bIsChartex) +void ChartExport::exportData_chartex( [[maybe_unused]] const Reference< css::chart::XChartDocument >& xChartDoc) { - if (bIsChartex) { - FSHelperPtr pFS = GetFS(); - - // Not sure if the data id is always 0. However, it seems it may need to - // agree with the id in exportSeries(). See DATA_ID_COMMENT - pFS->startElement(FSNS(XML_cx, XML_data), XML_id, "0"); - // Just hard-coding this for now - pFS->startElement(FSNS(XML_cx, XML_numDim), XML_type, "val"); - pFS->startElement(FSNS(XML_cx, XML_f)); - pFS->writeEscaped("_xlchart.v2.0"); // I have no idea what this - // means or what it should be in - // general - pFS->endElement(FSNS(XML_cx, XML_f)); - pFS->endElement(FSNS(XML_cx, XML_numDim)); - pFS->endElement(FSNS(XML_cx, XML_data)); + Reference< chart2::XCoordinateSystemContainer > xBCooSysCnt( mxNewDiagram, uno::UNO_QUERY ); + if( ! xBCooSysCnt.is()) return; + const Sequence< Reference< chart2::XCoordinateSystem > > + aCooSysSeq( xBCooSysCnt->getCoordinateSystems()); + + if (!aCooSysSeq.hasElements()) return; + + for( const auto& rCS : aCooSysSeq ) { + Reference< chart2::XChartTypeContainer > xCTCnt( rCS, uno::UNO_QUERY ); + if( ! xCTCnt.is()) + continue; + const Sequence< Reference< chart2::XChartType > > aCTSeq( xCTCnt->getChartTypes()); + + for( const auto& rCT : aCTSeq ) { + Reference< chart2::XDataSeriesContainer > xDSCnt( rCT, uno::UNO_QUERY ); + if( ! xDSCnt.is()) + return; + Reference< chart2::XChartType > xChartType( rCT, uno::UNO_QUERY ); + if( ! xChartType.is()) + continue; + + OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel(); + + const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType); + + for (const auto& splitDataSeries : aSplitDataSeries) { + sal_Int32 nSeriesIndex = 0; + for( const auto& rSeries : splitDataSeries ) + { + // export series + Reference< chart2::data::XDataSource > xSource( rSeries, uno::UNO_QUERY ); + if( !xSource.is()) continue; + + Reference< chart2::XDataSeries > xDataSeries( xSource, uno::UNO_QUERY ); + Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt( + xSource->getDataSequences()); + + // search for main sequence and create a series element + sal_Int32 nMainSequenceIndex = -1; + sal_Int32 nSeriesLength = 0; + Reference< chart2::data::XDataSequence > xValueSeq; + Reference< chart2::data::XDataSequence > xLabelSeq; + sal_Int32 nSeqIdx=0; + for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx ) + { + Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() ); + if( nMainSequenceIndex==-1 ) + { + Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY ); + OUString aRole; + if( xSeqProp.is()) + xSeqProp->getPropertyValue(u"Role"_ustr) >>= aRole; + // "main" sequence + if( aRole == aLabelRole ) + { + xValueSeq.set( xTempValueSeq ); + xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel()); + nMainSequenceIndex = nSeqIdx; + } + } + sal_Int32 nSequenceLength = (xTempValueSeq.is()? xTempValueSeq->getData().getLength() : sal_Int32(0)); + if( nSeriesLength < nSequenceLength ) + nSeriesLength = nSequenceLength; + } + FSHelperPtr pFS = GetFS(); + + // The data id needs to agree with the id in exportSeries(). See DATA_ID_COMMENT + pFS->startElement(FSNS(XML_cx, XML_data), XML_id, OUString::number(nSeriesIndex++)); + + // .xlsx chartex files seem to have this magical "_xlchart.v2.0" string, + // and no explicit data, while .docx and .pptx contain the literal data, + // as well as a ../embeddings file (which LO doesn't seem to produce). + // But there's probably a smarter way to determine which pathway to take + // than based on document type. + if (GetDocumentType() == DOCUMENT_XLSX) { + // Just hard-coding this for now + pFS->startElement(FSNS(XML_cx, XML_numDim), XML_type, "val"); + pFS->startElement(FSNS(XML_cx, XML_f)); + pFS->writeEscaped("_xlchart.v2.0"); // I have no idea what this + // means or what it should be in + // general + pFS->endElement(FSNS(XML_cx, XML_f)); + pFS->endElement(FSNS(XML_cx, XML_numDim)); + } else { // PPTX, DOCX + OUString aCellRange = mxCategoriesValues.is() ? mxCategoriesValues->getSourceRangeRepresentation() : OUString(); +#undef OUTPUT_SPLIT_CATEGORIES // do we need this or not? TODO +#ifdef OUTPUT_SPLIT_CATEGORIES + const Sequence< Sequence< OUString >> aFinalSplitSource = getSplitCategoriesList(aCellRange); +#endif + aCellRange = parseFormula( aCellRange ); + +#ifdef OUTPUT_SPLIT_CATEGORIES + if (aFinalSplitSource.getLength() > 1) { + + // export multi level category axis labels + pFS->startElement(FSNS(XML_cx, XML_strDim), XML_type, "cat"); + + pFS->startElement(FSNS(XML_cx, XML_f)); + pFS->writeEscaped(aCellRange); + pFS->endElement(FSNS(XML_cx, XML_f)); + + for (const auto& rSeq : aFinalSplitSource) { + pFS->startElement(FSNS(XML_cx, XML_lvl), + XML_ptCount, OString::number(aFinalSplitSource[0].getLength())); + + for (sal_Int32 j = 0; j < rSeq.getLength(); j++) { + if(!rSeq[j].isEmpty()) { + pFS->startElement(FSNS(XML_cx, XML_pt), XML_idx, OString::number(j)); + pFS->writeEscaped(rSeq[j]); + pFS->endElement(FSNS(XML_cx, XML_pt)); + } + } + pFS->endElement(FSNS(XML_cx, XML_lvl)); + } + + pFS->endElement(FSNS(XML_cx, XML_strDim)); + } + else +#endif + { + // export single category axis labels + // TODO: seems like this should consider mbHasCategoryLabels + bool bWriteDateCategories = mbHasDateCategories; + OUString aNumberFormatString; + if (bWriteDateCategories) + { + Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY ); + if( xAxisXSupp.is()) + { + Reference< XPropertySet > xAxisProp = xAxisXSupp->getXAxis(); + if (GetProperty(xAxisProp, u"NumberFormat"_ustr)) + { + sal_Int32 nKey = 0; + mAny >>= nKey; + aNumberFormatString = getNumberFormatCode(nKey); + } + } + if (aNumberFormatString.isEmpty()) bWriteDateCategories = false; + } + + // === Output the categories + if (bWriteDateCategories) + { + std::vector<double> aDateCategories = lcl_getAllValuesFromSequence(xValueSeq); + const sal_Int32 ptCount = aDateCategories.size(); + + pFS->startElement(FSNS(XML_cx, XML_numDim), XML_type, "x"); // is "x" right? + // TODO: check this + + pFS->startElement(FSNS(XML_cx, XML_f)); + pFS->writeEscaped(aCellRange); + pFS->endElement(FSNS(XML_cx, XML_f)); + + pFS->startElement(FSNS(XML_cx, XML_lvl), + XML_ptCount, OString::number(ptCount), + XML_formatCode, aNumberFormatString); + + for (sal_Int32 i = 0; i < ptCount; i++) { + if (!std::isnan(aDateCategories[i])) { + pFS->startElement(FSNS(XML_cx, XML_pt), XML_idx, OString::number(i)); + pFS->write(OString::number(aDateCategories[i])); + pFS->endElement(FSNS(XML_cx, XML_pt)); + } + } + + pFS->endElement(FSNS(XML_cx, XML_lvl)); + pFS->endElement(FSNS(XML_cx, XML_numDim)); + } + else + { + std::vector<OUString> aCategories; + lcl_fillCategoriesIntoStringVector(xValueSeq, aCategories); + const sal_Int32 ptCount = aCategories.size(); + + // TODO: shouldn't have "cat" hard-coded here: + // other options are colorStr, entityId + pFS->startElement(FSNS(XML_cx, XML_strDim), XML_type, "cat"); + + pFS->startElement(FSNS(XML_cx, XML_f)); + pFS->writeEscaped(aCellRange); + pFS->endElement(FSNS(XML_cx, XML_f)); + + pFS->startElement(FSNS(XML_cx, XML_lvl), XML_ptCount, OString::number(ptCount)); + + for (sal_Int32 i = 0; i < ptCount; i++) { + pFS->startElement(FSNS(XML_cx, XML_pt), XML_idx, OString::number(i)); + pFS->writeEscaped(aCategories[i]); + pFS->endElement(FSNS(XML_cx, XML_pt)); + } + + pFS->endElement(FSNS(XML_cx, XML_lvl)); + pFS->endElement(FSNS(XML_cx, XML_strDim)); + } + + // === Output the values + pFS->startElement(FSNS(XML_cx, XML_numDim), XML_type, "val"); + + aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString(); + aCellRange = parseFormula( aCellRange ); + // TODO: need to handle XML_multiLvlStrRef according to aCellRange + + pFS->startElement(FSNS(XML_cx, XML_f)); + pFS->writeEscaped( aCellRange ); + pFS->endElement( FSNS( XML_cx, XML_f ) ); + + ::std::vector< double > aValues = lcl_getAllValuesFromSequence( xValueSeq ); + sal_Int32 ptCount = aValues.size(); + OUString sNumberFormatString(u"General"_ustr); + const sal_Int32 nKey = xValueSeq.is() ? xValueSeq->getNumberFormatKeyByIndex(-1) : 0; + if (nKey > 0) { + sNumberFormatString = getNumberFormatCode(nKey); + } + pFS->startElement(FSNS(XML_cx, XML_lvl), + XML_ptCount, OString::number(ptCount), + XML_formatCode, sNumberFormatString); + + for( sal_Int32 i = 0; i < ptCount; i++ ) { + + pFS->startElement(FSNS(XML_cx, XML_pt), XML_idx, OString::number(i)); + pFS->write(std::isnan(aValues[i]) ? 0 : aValues[i]); + pFS->endElement(FSNS(XML_cx, XML_pt)); + } + + pFS->endElement(FSNS(XML_cx, XML_lvl)); + pFS->endElement(FSNS(XML_cx, XML_numDim)); + } + } + pFS->endElement(FSNS(XML_cx, XML_data)); + } + } + } } } @@ -2023,63 +2292,6 @@ void ChartExport::exportTitle( const Reference< XShape >& xShape, bool bIsCharte } } -namespace { - - std::vector<Sequence<Reference<chart2::XDataSeries> > > splitDataSeriesByAxis(const Reference< chart2::XChartType >& xChartType) - { - std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitSeries; - std::map<sal_Int32, size_t> aMapAxisToIndex; - - Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY); - if (xDSCnt.is()) - { - sal_Int32 nAxisIndexOfFirstSeries = -1; - const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries()); - for (const uno::Reference<chart2::XDataSeries>& xSeries : aSeriesSeq) - { - Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY); - if (!xPropSet.is()) - continue; - - sal_Int32 nAxisIndex = -1; - uno::Any aAny = xPropSet->getPropertyValue(u"AttachedAxisIndex"_ustr); - aAny >>= nAxisIndex; - size_t nVectorPos = 0; - if (nAxisIndexOfFirstSeries == -1) - { - nAxisIndexOfFirstSeries = nAxisIndex; - } - - auto it = aMapAxisToIndex.find(nAxisIndex); - if (it == aMapAxisToIndex.end()) - { - aSplitSeries.emplace_back(); - nVectorPos = aSplitSeries.size() - 1; - aMapAxisToIndex.insert(std::pair<sal_Int32, size_t>(nAxisIndex, nVectorPos)); - } - else - { - nVectorPos = it->second; - } - - uno::Sequence<Reference<chart2::XDataSeries> >& rAxisSeriesSeq = aSplitSeries[nVectorPos]; - sal_Int32 nLength = rAxisSeriesSeq.getLength(); - rAxisSeriesSeq.realloc(nLength + 1); - rAxisSeriesSeq.getArray()[nLength] = xSeries; - } - // if the first series attached to secondary axis, then export those series first, which are attached to primary axis - // also the MS Office export every time in this order - if (aSplitSeries.size() > 1 && nAxisIndexOfFirstSeries == 1) - { - std::swap(aSplitSeries[0], aSplitSeries[1]); - } - } - - return aSplitSeries; - } - -} - void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >& xChartDoc, bool bIsChartex) { @@ -2635,8 +2847,9 @@ void ChartExport::exportAreaChart( const Reference< chart2::XChartType >& xChart exportGrouping(); bool bPrimaryAxes = true; - exportSeries(xChartType, splitDataSeries, bPrimaryAxes, false); + exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes); createAxes(bPrimaryAxes, false); + //exportAxesId(bPrimaryAxes); pFS->endElement(FSNS(XML_c, nTypeId)); } @@ -2670,7 +2883,7 @@ void ChartExport::exportBarChart(const Reference< chart2::XChartType >& xChartTy exportVaryColors(xChartType); bool bPrimaryAxes = true; - exportSeries(xChartType, splitDataSeries, bPrimaryAxes, false); + exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes); Reference< XPropertySet > xTypeProp(xChartType, uno::UNO_QUERY); @@ -2756,7 +2969,7 @@ void ChartExport::exportBubbleChart( const Reference< chart2::XChartType >& xCha exportVaryColors(xChartType); bool bPrimaryAxes = true; - exportSeries(xChartType, splitDataSeries, bPrimaryAxes, false); + exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes); createAxes(bPrimaryAxes, false); @@ -2773,14 +2986,9 @@ void ChartExport::exportFunnelChart( const Reference< chart2::XChartType >& xCha if (!splitDataSeries.hasElements()) continue; - pFS->startElement(FSNS(XML_cx, XML_series), XML_layoutId, "funnel"); - //exportVaryColors(xChartType); - bool bPrimaryAxes = false; - exportSeries(xChartType, splitDataSeries, bPrimaryAxes, true); - - pFS->endElement(FSNS(XML_cx, XML_series)); + exportSeries_chartex(xChartType, splitDataSeries, "funnel"); } } @@ -2878,7 +3086,7 @@ void ChartExport::exportLineChart( const Reference< chart2::XChartType >& xChart exportVaryColors(xChartType); // TODO: show marker symbol in series? bool bPrimaryAxes = true; - exportSeries(xChartType, splitDataSeries, bPrimaryAxes, false); + exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes); // show marker? sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE; @@ -2968,8 +3176,9 @@ void ChartExport::exportScatterChartSeries( const Reference< chart2::XChartType // FIXME: should export xVal and yVal bool bPrimaryAxes = true; if (pSeries) - exportSeries(xChartType, *pSeries, bPrimaryAxes, false); + exportSeries_chart(xChartType, *pSeries, bPrimaryAxes); createAxes(bPrimaryAxes, false); + //exportAxesId(bPrimaryAxes); pFS->endElement( FSNS( XML_c, XML_scatterChart ) ); } @@ -3100,7 +3309,7 @@ void ChartExport::exportAllSeries(const Reference<chart2::XChartType>& xChartTyp // export dataseries for current chart-type Sequence< Reference< chart2::XDataSeries > > aSeriesSeq( xDSCnt->getDataSeries()); - exportSeries(xChartType, aSeriesSeq, rPrimaryAxes, false); + exportSeries_chart(xChartType, aSeriesSeq, rPrimaryAxes); } void ChartExport::exportVaryColors(const Reference<chart2::XChartType>& xChartType) @@ -3121,10 +3330,9 @@ void ChartExport::exportVaryColors(const Reference<chart2::XChartType>& xChartTy } } -void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType, +void ChartExport::exportSeries_chart( const Reference<chart2::XChartType>& xChartType, const Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq, - bool& rPrimaryAxes, - bool bIsChartex) + bool& rPrimaryAxes) { OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel(); OUString aChartType( xChartType->getChartType()); @@ -3172,19 +3380,17 @@ void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType, // xLabelSeq contain those. Otherwise both are empty FSHelperPtr pFS = GetFS(); - if (!bIsChartex) { - pFS->startElement(FSNS(XML_c, XML_ser)); + pFS->startElement(FSNS(XML_c, XML_ser)); - // TODO: idx and order - pFS->singleElement( FSNS( XML_c, XML_idx ), - XML_val, OString::number(mnSeriesCount) ); - pFS->singleElement( FSNS( XML_c, XML_order ), - XML_val, OString::number(mnSeriesCount++) ); - } + // TODO: idx and order + pFS->singleElement( FSNS( XML_c, XML_idx ), + XML_val, OString::number(mnSeriesCount) ); + pFS->singleElement( FSNS( XML_c, XML_order ), + XML_val, OString::number(mnSeriesCount++) ); // export label if( xLabelSeq.is() ) - exportSeriesText( xLabelSeq, bIsChartex ); + exportSeriesText( xLabelSeq, false ); Reference<XPropertySet> xPropSet(xDataSeries, UNO_QUERY_THROW); if( GetProperty( xPropSet, u"AttachedAxisIndex"_ustr) ) @@ -3199,140 +3405,210 @@ void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType, rSeries, getModel() ); if( xOldPropSet.is() ) { - exportShapeProps( xOldPropSet, bIsChartex ); + exportShapeProps( xOldPropSet, false ); } - if (!bIsChartex) { - switch( eChartType ) + switch( eChartType ) + { + case chart::TYPEID_BUBBLE: + case chart::TYPEID_HORBAR: + case chart::TYPEID_BAR: { - case chart::TYPEID_BUBBLE: - case chart::TYPEID_HORBAR: - case chart::TYPEID_BAR: - { - pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0"); - } + pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0"); + } + break; + case chart::TYPEID_LINE: + { + exportMarker(xOldPropSet); break; - case chart::TYPEID_LINE: - { - exportMarker(xOldPropSet); - break; - } - case chart::TYPEID_PIE: - case chart::TYPEID_DOUGHNUT: - { - if( xOldPropSet.is() && GetProperty( xOldPropSet, u"SegmentOffset"_ustr) ) - { - sal_Int32 nOffset = 0; - mAny >>= nOffset; - pFS->singleElement( FSNS( XML_c, XML_explosion ), - XML_val, OString::number( nOffset ) ); - } - break; - } - case chart::TYPEID_SCATTER: - { - exportMarker(xOldPropSet); - break; - } - case chart::TYPEID_RADARLINE: + } + case chart::TYPEID_PIE: + case chart::TYPEID_DOUGHNUT: + { + if( xOldPropSet.is() && GetProperty( xOldPropSet, u"SegmentOffset"_ustr) ) { - exportMarker(xOldPropSet); - break; + sal_Int32 nOffset = 0; + mAny >>= nOffset; + pFS->singleElement( FSNS( XML_c, XML_explosion ), + XML_val, OString::number( nOffset ) ); } + break; + } + case chart::TYPEID_SCATTER: + { + exportMarker(xOldPropSet); + break; + } + case chart::TYPEID_RADARLINE: + { + exportMarker(xOldPropSet); + break; } - - // export data points - exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType ); } + // export data points + exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType ); + DataLabelsRange aDLblsRange; // export data labels - exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange, bIsChartex); + exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange, false); - if (!bIsChartex) { - exportTrendlines( rSeries ); + exportTrendlines( rSeries ); - if( eChartType != chart::TYPEID_PIE && - eChartType != chart::TYPEID_RADARLINE ) + if( eChartType != chart::TYPEID_PIE && + eChartType != chart::TYPEID_RADARLINE ) + { + //export error bars here + Reference< XPropertySet > xSeriesPropSet( xSource, uno::UNO_QUERY ); + Reference< XPropertySet > xErrorBarYProps; + xSeriesPropSet->getPropertyValue(u"ErrorBarY"_ustr) >>= xErrorBarYProps; + if(xErrorBarYProps.is()) + exportErrorBar(xErrorBarYProps, true); + if (eChartType != chart::TYPEID_BAR && + eChartType != chart::TYPEID_HORBAR) { - //export error bars here - Reference< XPropertySet > xSeriesPropSet( xSource, uno::UNO_QUERY ); - Reference< XPropertySet > xErrorBarYProps; - xSeriesPropSet->getPropertyValue(u"ErrorBarY"_ustr) >>= xErrorBarYProps; - if(xErrorBarYProps.is()) - exportErrorBar(xErrorBarYProps, true); - if (eChartType != chart::TYPEID_BAR && - eChartType != chart::TYPEID_HORBAR) - { - Reference< XPropertySet > xErrorBarXProps; - xSeriesPropSet->getPropertyValue(u"ErrorBarX"_ustr) >>= xErrorBarXProps; - if(xErrorBarXProps.is()) - exportErrorBar(xErrorBarXProps, false); - } + Reference< XPropertySet > xErrorBarXProps; + xSeriesPropSet->getPropertyValue(u"ErrorBarX"_ustr) >>= xErrorBarXProps; + if(xErrorBarXProps.is()) + exportErrorBar(xErrorBarXProps, false); } + } - // export categories - if( eChartType != chart::TYPEID_SCATTER && eChartType != chart::TYPEID_BUBBLE && mxCategoriesValues.is() ) - exportSeriesCategory( mxCategoriesValues ); + // export categories + if( eChartType != chart::TYPEID_SCATTER && eChartType != chart::TYPEID_BUBBLE && mxCategoriesValues.is() ) + exportSeriesCategory( mxCategoriesValues ); - if( (eChartType == chart::TYPEID_SCATTER) - || (eChartType == chart::TYPEID_BUBBLE) ) + if( (eChartType == chart::TYPEID_SCATTER) + || (eChartType == chart::TYPEID_BUBBLE) ) + { + // export xVal + Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-x"_ustr ) ); + if( xSequence.is() ) { - // export xVal - Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-x"_ustr ) ); - if( xSequence.is() ) - { - Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() ); - if( xValues.is() ) - exportSeriesValues( xValues, XML_xVal ); - } - else if( mxCategoriesValues.is() ) - exportSeriesCategory( mxCategoriesValues, XML_xVal ); + Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() ); + if( xValues.is() ) + exportSeriesValues( xValues, XML_xVal ); } + else if( mxCategoriesValues.is() ) + exportSeriesCategory( mxCategoriesValues, XML_xVal ); + } - if( eChartType == chart::TYPEID_BUBBLE ) + if( eChartType == chart::TYPEID_BUBBLE ) + { + // export yVal + Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-y"_ustr ) ); + if( xSequence.is() ) { - // export yVal - Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, u"values-y"_ustr ) ); - if( xSequence.is() ) - { - Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() ); - if( xValues.is() ) - exportSeriesValues( xValues, XML_yVal ); - } + Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() ); + if( xValues.is() ) + exportSeriesValues( xValues, XML_yVal ); } + } - // export values - if( xValuesSeq.is() ) - { - sal_Int32 nYValueType = XML_val; - if( eChartType == chart::TYPEID_SCATTER ) - nYValueType = XML_yVal; - else if( eChartType == chart::TYPEID_BUBBLE ) - nYValueType = XML_bubbleSize; - exportSeriesValues( xValuesSeq, nYValueType ); - } + // export values + if( xValuesSeq.is() ) + { + sal_Int32 nYValueType = XML_val; + if( eChartType == chart::TYPEID_SCATTER ) + nYValueType = XML_yVal; + else if( eChartType == chart::TYPEID_BUBBLE ) + nYValueType = XML_bubbleSize; + exportSeriesValues( xValuesSeq, nYValueType ); + } - if( eChartType == chart::TYPEID_SCATTER - || eChartType == chart::TYPEID_LINE ) - exportSmooth(); + if( eChartType == chart::TYPEID_SCATTER + || eChartType == chart::TYPEID_LINE ) + exportSmooth(); - // tdf103988: "corrupted" files with Bubble chart opening in MSO - if( eChartType == chart::TYPEID_BUBBLE ) - pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0"); + // tdf103988: "corrupted" files with Bubble chart opening in MSO + if( eChartType == chart::TYPEID_BUBBLE ) + pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0"); - if (!aDLblsRange.empty()) - writeDataLabelsRange(pFS, GetFB(), aDLblsRange); + if (!aDLblsRange.empty()) + writeDataLabelsRange(pFS, GetFB(), aDLblsRange); - pFS->endElement( FSNS( XML_c, XML_ser ) ); - } else { - // chartex + pFS->endElement( FSNS( XML_c, XML_ser ) ); + } + } + } +} - // Align the data id here with that in exportData(). - // See DATA_ID_COMMENT - pFS->singleElement(FSNS(XML_cx, XML_dataId), XML_val, "0"); +void ChartExport::exportSeries_chartex( const Reference<chart2::XChartType>& xChartType, + const Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq, + const char* sTypeName) +{ + OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel(); + OUString aChartType( xChartType->getChartType()); + sal_Int32 eChartType = lcl_getChartType( aChartType ); + + sal_Int32 nSeriesCnt = 0; + for( const auto& rSeries : rSeriesSeq ) + { + // export series + Reference< chart2::data::XDataSource > xSource( rSeries, uno::UNO_QUERY ); + if( xSource.is()) + { + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_cx, XML_series), XML_layoutId, sTypeName); + + Reference< chart2::XDataSeries > xDataSeries( xSource, uno::UNO_QUERY ); + Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt( + xSource->getDataSequences()); + + // search for main sequence and create a series element + sal_Int32 nMainSequenceIndex = -1; + sal_Int32 nSeriesLength = 0; + Reference< chart2::data::XDataSequence > xLabelSeq; + sal_Int32 nSeqIdx=0; + for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx ) + { + Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() ); + if( nMainSequenceIndex==-1 ) + { + Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY ); + OUString aRole; + if( xSeqProp.is()) + xSeqProp->getPropertyValue(u"Role"_ustr) >>= aRole; + // "main" sequence + if( aRole == aLabelRole ) + { + xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel()); + nMainSequenceIndex = nSeqIdx; + } } + sal_Int32 nSequenceLength = (xTempValueSeq.is()? xTempValueSeq->getData().getLength() : sal_Int32(0)); + if( nSeriesLength < nSequenceLength ) + nSeriesLength = nSequenceLength; + } + + // export label + if( xLabelSeq.is() ) + exportSeriesText( xLabelSeq, true ); + + // export shape properties + Reference< XPropertySet > xOldPropSet = SchXMLSeriesHelper::createOldAPISeriesPropertySet( + rSeries, getModel() ); + if( xOldPropSet.is() ) + { + exportShapeProps( xOldPropSet, true ); } + + DataLabelsRange aDLblsRange; + // export data labels + exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange, true); + + // dataId links to the correct data set in the <cx:chartData>. See + // DATA_ID_COMMENT + pFS->singleElement(FSNS(XML_cx, XML_dataId), XML_val, + OString::number(nSeriesCnt++)); + + // layoutPr + + // axisId + + // extLst + + pFS->endElement(FSNS(XML_cx, XML_series)); } } } @@ -3687,7 +3963,8 @@ void ChartExport::InitPlotArea( ) if( mbHasCategoryLabels && mxNewDiagram.is()) { - Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( mxNewDiagram, mbHasDateCategories ) ); + Reference< chart2::data::XLabeledDataSequence > xCategories( + lcl_getCategories( mxNewDiagram, &mbHasDateCategories ) ); if( xCategories.is() ) { mxCategoriesValues.set( xCategories->getValues() ); diff --git a/test/source/xmltesttools.cxx b/test/source/xmltesttools.cxx index 29ae85e1403e..123cb90dc5ec 100644 --- a/test/source/xmltesttools.cxx +++ b/test/source/xmltesttools.cxx @@ -88,6 +88,11 @@ void XmlTestTools::registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) } OUString XmlTestTools::getXPath(const xmlDocUniquePtr& pXmlDoc, const char* pXPath, const char* pAttribute) +{ + return getXPath(pXmlDoc, pXPath, 1, 0, pAttribute); +} + +OUString XmlTestTools::getXPath(const xmlDocUniquePtr& pXmlDoc, const char*pXPath, int nNumNodes, int nPathIdx, const char* pAttribute) { CPPUNIT_ASSERT(pXmlDoc); xmlXPathObjectPtr pXmlObj = getXPathNode(pXmlDoc, pXPath); @@ -96,10 +101,10 @@ OUString XmlTestTools::getXPath(const xmlDocUniquePtr& pXmlDoc, const char* pXPa xmlNodeSetPtr pXmlNodes = pXmlObj->nodesetval; CPPUNIT_ASSERT_MESSAGE(OString(docAndXPath + "' not found").getStr(), pXmlNodes); CPPUNIT_ASSERT_EQUAL_MESSAGE(OString(docAndXPath + "' number of nodes is incorrect").getStr(), - 1, xmlXPathNodeSetGetLength(pXmlNodes)); + nNumNodes, xmlXPathNodeSetGetLength(pXmlNodes)); CPPUNIT_ASSERT(pAttribute); CPPUNIT_ASSERT(pAttribute[0]); - xmlNodePtr pXmlNode = pXmlNodes->nodeTab[0]; + xmlNodePtr pXmlNode = pXmlNodes->nodeTab[nPathIdx]; xmlChar * prop = xmlGetProp(pXmlNode, BAD_CAST(pAttribute)); OString sAttAbsent = docAndXPath + "' no attribute '" + pAttribute + "' exist"; CPPUNIT_ASSERT_MESSAGE(sAttAbsent.getStr(), prop); @@ -169,6 +174,16 @@ void XmlTestTools::assertXPath(const xmlDocUniquePtr& pXmlDoc, const char* pXPat rExpectedValue, std::u16string_view(aValue)); } +// Verify the given path and attribute, where the expected number of nodes with +// the given path is nNumNodes and the desired node index is nPathIdx. +void XmlTestTools::assertXPath(const xmlDocUniquePtr& pXmlDoc, const char* + pXPath, int nNumNodes, int nPathIdx, const char* pAttribute, std::u16string_view rExpectedValue) +{ + OUString aValue = getXPath(pXmlDoc, pXPath, nNumNodes, nPathIdx, pAttribute); + CPPUNIT_ASSERT_EQUAL_MESSAGE(OString(OString::Concat("In <") + pXmlDoc->name + ">, attribute '" + pAttribute + "' of '" + pXPath + "' incorrect value.").getStr(), + rExpectedValue, std::u16string_view(aValue)); +} + void XmlTestTools::assertXPathDoubleValue(const xmlDocUniquePtr& pXmlDoc, const char* pXPath, const char* pAttribute, double expectedValue, double delta) { OUString aValue = getXPath(pXmlDoc, pXPath, pAttribute);