diff --git a/chart2/qa/extras/chart2export2.cxx b/chart2/qa/extras/chart2export2.cxx index a1960c60f861..b3b1bdb2ff01 100644 --- a/chart2/qa/extras/chart2export2.cxx +++ b/chart2/qa/extras/chart2export2.cxx @@ -288,6 +288,19 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexAxes) assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:axis/cx:tickLabels", 2); } +CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexBinningXLSX) +{ + loadFromFile(u"xlsx/paretoLine.xlsx"); + save(TestFilter::XLSX); + xmlDocUniquePtr pXmlDoc = parseExport(u"xl/charts/chartEx1.xml"_ustr); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath( + pXmlDoc, + "/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series/cx:layoutPr/cx:binning", + "intervalClosed", u"r"); +} + CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testAxisTitleRotationXLSX) { loadFromFile(u"xlsx/axis_title_rotation.xlsx"); diff --git a/chart2/source/inc/DataSeriesProperties.hxx b/chart2/source/inc/DataSeriesProperties.hxx index d0c19d07bf23..c0aa83e317b6 100644 --- a/chart2/source/inc/DataSeriesProperties.hxx +++ b/chart2/source/inc/DataSeriesProperties.hxx @@ -36,7 +36,8 @@ namespace chart::DataSeriesProperties PROP_DATASERIES_SHOW_LEGEND_ENTRY, PROP_DATASERIES_DELETED_LEGEND_ENTRIES, PROP_DATASERIES_SHOW_CUSTOM_LEADERLINES, - PROP_DATASERIES_INVERT_NEGATIVE + PROP_DATASERIES_INVERT_NEGATIVE, + PROP_DATASERIES_INTERVAL_CLOSED }; void AddPropertiesToVector( diff --git a/chart2/source/model/main/DataSeriesProperties.cxx b/chart2/source/model/main/DataSeriesProperties.cxx index c558b3820cab..4734c22f8fda 100644 --- a/chart2/source/model/main/DataSeriesProperties.cxx +++ b/chart2/source/model/main/DataSeriesProperties.cxx @@ -81,6 +81,12 @@ void DataSeriesProperties::AddPropertiesToVector( beans::PropertyAttribute::BOUND | beans::PropertyAttribute::MAYBEDEFAULT ); + rOutProperties.emplace_back( "IntervalClosed", + PROP_DATASERIES_INTERVAL_CLOSED, + cppu::UnoType::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::MAYBEVOID ); + // add properties of service DataPointProperties DataPointProperties::AddPropertiesToVector( rOutProperties ); } diff --git a/oox/inc/drawingml/chart/seriescontext.hxx b/oox/inc/drawingml/chart/seriescontext.hxx index faaf0caa0388..b110b23114f7 100644 --- a/oox/inc/drawingml/chart/seriescontext.hxx +++ b/oox/inc/drawingml/chart/seriescontext.hxx @@ -237,6 +237,36 @@ public: virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; }; + +struct BinningModel; + +/** Handler for a chartex binning context (cx:binning element). + */ +class BinningContext final : public ContextBase< BinningModel > +{ +public: + explicit BinningContext( ::oox::core::ContextHandler2Helper& rParent, BinningModel& rModel ); + virtual ~BinningContext() override; + + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + void onCharacters( const OUString& rChars ) override; +}; + + +struct LayoutPropsModel; + +/** Handler for a chartex layout properties context (cx:layoutPr element). + */ +class LayoutPropsContext final : public ContextBase< LayoutPropsModel > +{ +public: + explicit LayoutPropsContext( ::oox::core::ContextHandler2Helper& rParent, LayoutPropsModel& rModel ); + virtual ~LayoutPropsContext() override; + + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; +}; + + /** Handler for a data series context for chartex chart types (cx:series element * for boxWhisker, clusteredColumn/histogram, funnel, paretoLine, waterfall, * sunburst, treemap, and regionMap). diff --git a/oox/inc/drawingml/chart/seriesmodel.hxx b/oox/inc/drawingml/chart/seriesmodel.hxx index 8ad07e812d54..21b1df810fb3 100644 --- a/oox/inc/drawingml/chart/seriesmodel.hxx +++ b/oox/inc/drawingml/chart/seriesmodel.hxx @@ -20,6 +20,8 @@ #ifndef INCLUDED_OOX_DRAWINGML_CHART_SERIESMODEL_HXX #define INCLUDED_OOX_DRAWINGML_CHART_SERIESMODEL_HXX +#include + #include #include @@ -174,6 +176,27 @@ struct DataPointModel ~DataPointModel(); }; +struct BinningModel +{ + enum ClosedSide { + L, + R + }; + + std::optional> maBinSizeOrCount; + std::optional meIntervalClosed; // Which side of an interval is closed + + explicit BinningModel(); +}; + +struct LayoutPropsModel +{ + typedef ModelRef< BinningModel > BinningRef; + // There's also a CT_Aggregation but no content given + + BinningRef mxBinning; // Binning settings. +}; + enum class DataSourceType: sal_Int32 { CATEGORIES, /// Data point categories. @@ -192,6 +215,7 @@ struct SeriesModel typedef ModelRef< PictureOptionsModel > PictureOptionsRef; typedef ModelRef< TextModel > TextRef; typedef ModelRef< DataLabelsModel > DataLabelsRef; + typedef ModelRef< LayoutPropsModel > LayoutPropsRef; DataSourceMap maSources; /// Series source ranges. ErrorBarVector maErrorBars; /// All error bars of this series. @@ -202,6 +226,7 @@ struct SeriesModel ShapeRef mxMarkerProp; /// Data point marker formatting. TextRef mxText; /// Series title source. DataLabelsRef mxLabels; /// Data point label settings for all points. + LayoutPropsRef mxLayoutPr; /// Chartex CT_SeriesLayoutProperties std::optional< sal_Int32 > monShape; /// 3D bar shape type. sal_Int32 mnExplosion; /// Pie slice moved from pie center. sal_Int32 mnIndex; /// Series index used for automatic formatting. diff --git a/oox/source/drawingml/chart/seriescontext.cxx b/oox/source/drawingml/chart/seriescontext.cxx index 8d163ea9af4f..946e1f47aa32 100644 --- a/oox/source/drawingml/chart/seriescontext.cxx +++ b/oox/source/drawingml/chart/seriescontext.cxx @@ -762,6 +762,119 @@ ContextHandlerRef SurfaceSeriesContext::onCreateContext( sal_Int32 nElement, con return SeriesContextBase::onCreateContext( nElement, rAttribs ); } +//====== +// BinningContext +//====== +BinningContext::BinningContext( ContextHandler2Helper& rParent, BinningModel& rModel ) : + ContextBase( rParent, rModel ) +{ +} + +BinningContext::~BinningContext() +{ +} + +void BinningContext::onCharacters( const OUString& rChars ) +{ + switch (getCurrentElement()) + { + case CX_TOKEN( binSize ) : + mrModel.maBinSizeOrCount = rChars.toDouble(); + break; + case CX_TOKEN(binCount) : + mrModel.maBinSizeOrCount = rChars.toUInt32(); + break; + default: + assert(false); + } +} + +ContextHandlerRef BinningContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case CX_TOKEN( layoutPr ): + switch( nElement ) + { + case CX_TOKEN( binSize ): + mrModel.maBinSizeOrCount = rAttribs.getDouble( XML_val, 0.0 ); + return nullptr; + case CX_TOKEN( binCount ): + mrModel.maBinSizeOrCount = std::variant( + static_cast(rAttribs.getInteger( XML_val, 0 ))); + + // TODO + return nullptr; + } + } + return ContextBase::onCreateContext( nElement, rAttribs ); +} + +//====== +// LayoutPropsContext +//====== +LayoutPropsContext::LayoutPropsContext( ContextHandler2Helper& rParent, LayoutPropsModel& rModel ) : + ContextBase( rParent, rModel ) +{ +} + +LayoutPropsContext::~LayoutPropsContext() +{ +} + +ContextHandlerRef LayoutPropsContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case CX_TOKEN( layoutPr ): + switch( nElement ) + { + case CX_TOKEN( parentLabelLayout ): + // TODO + return nullptr; + case CX_TOKEN( regionLabelLayout ): + // TODO + return nullptr; + case CX_TOKEN( visibility ): + // TODO + return nullptr; + case CX_TOKEN( aggregation ): + // The schema gives an empty definition for this type?? + // TODO + return nullptr; + case CX_TOKEN( binning ): + { + BinningModel& rB = mrModel.mxBinning.create(); + if (rAttribs.getString( XML_intervalClosed ) == "l") { + rB.meIntervalClosed = BinningModel::ClosedSide::L; + } else if (rAttribs.getString( XML_intervalClosed ) == "r") { + rB.meIntervalClosed = BinningModel::ClosedSide::R; + } else { + assert(false); // no other options + } + // TODO: Also handle attributes underflow, overflow + return new BinningContext( *this, rB ); + } + case CX_TOKEN( geography ): + // TODO + return nullptr; + case CX_TOKEN( statistics ): + // TODO + return nullptr; + case CX_TOKEN( subtotals ): + // TODO + return nullptr; + case CX_TOKEN( extLst ): + // TODO + return nullptr; + } + } + return ContextBase::onCreateContext( nElement, rAttribs ); +} + +//====== +// ChartexSeriesContext +//====== ChartexSeriesContext::ChartexSeriesContext( ContextHandler2Helper& rParent, SeriesModel& rModel, sal_Int32 nIndex ) : SeriesContextBase( rParent, rModel ) { @@ -793,8 +906,7 @@ ContextHandlerRef ChartexSeriesContext::onCreateContext( sal_Int32 nElement, con mrModel.mnDataId = rAttribs.getInteger(XML_val, -1); return nullptr; case CX_TOKEN( layoutPr ): - // This looks complicated. TODO - return nullptr; + return new LayoutPropsContext( *this, mrModel.mxLayoutPr.create()); case CX_TOKEN( axisId ): mrModel.maAxisIds.push_back( rAttribs.getInteger( XML_val, -1 ) ); return nullptr; diff --git a/oox/source/drawingml/chart/seriesconverter.cxx b/oox/source/drawingml/chart/seriesconverter.cxx index 4b8674ece646..93e90d9e882a 100644 --- a/oox/source/drawingml/chart/seriesconverter.cxx +++ b/oox/source/drawingml/chart/seriesconverter.cxx @@ -962,6 +962,17 @@ Reference< XDataSeries > SeriesConverter::createDataSeries( const TypeGroupConve aLabelsConv.convertFromModel( xDataSeries, rTypeGroup ); } + // Layout properties + if (mrModel.mxLayoutPr.is() && mrModel.mxLayoutPr->mxBinning.is()) { + ModelRef xBinning = mrModel.mxLayoutPr->mxBinning; + + if (xBinning->meIntervalClosed) { + sal_uInt32 nBinSideChar = xBinning->meIntervalClosed.value() == + BinningModel::ClosedSide::L ? 'l' : 'r'; + aSeriesProp.setProperty(PROP_IntervalClosed, nBinSideChar); + } + } + return xDataSeries; } diff --git a/oox/source/drawingml/chart/seriesmodel.cxx b/oox/source/drawingml/chart/seriesmodel.cxx index 194d4f6ecf3d..bad98728c239 100644 --- a/oox/source/drawingml/chart/seriesmodel.cxx +++ b/oox/source/drawingml/chart/seriesmodel.cxx @@ -106,6 +106,12 @@ DataPointModel::~DataPointModel() { } +BinningModel::BinningModel() : + maBinSizeOrCount(), + meIntervalClosed() +{ +} + SeriesModel::SeriesModel(bool bMSO2007Doc) : mnExplosion( 0 ), mnIndex( -1 ), diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx index 90c3a755de8d..42c849b8b89d 100644 --- a/oox/source/export/chartexport.cxx +++ b/oox/source/export/chartexport.cxx @@ -3932,6 +3932,25 @@ void ChartExport::exportSeries_chartex( const Reference& xCh OString::number(nSeriesCnt++)); // layoutPr + PropertySet aSeriesProp(rSeries); + sal_uInt32 nIntervalClosedChar = '\0'; + if (aSeriesProp.getProperty(nIntervalClosedChar, PROP_IntervalClosed)) { + pFS->startElement(FSNS(XML_cx, XML_layoutPr)); + + switch (nIntervalClosedChar) { + case 'l' : + pFS->singleElement(FSNS(XML_cx, XML_binning), XML_intervalClosed, "l"); + break; + case 'r' : + pFS->singleElement(FSNS(XML_cx, XML_binning), XML_intervalClosed, "r"); + break; + default: + assert(false); + break; + } + + pFS->endElement(FSNS(XML_cx, XML_layoutPr)); + } // axisId diff --git a/oox/source/token/properties.txt b/oox/source/token/properties.txt index 43f7cff58391..3601d90a2753 100644 --- a/oox/source/token/properties.txt +++ b/oox/source/token/properties.txt @@ -292,6 +292,7 @@ IncludeHiddenCells InputMessage InputTitle InterceptValue +IntervalClosed InvalidUrl InvertNegative IsActive diff --git a/oox/source/token/tokens.txt b/oox/source/token/tokens.txt index 3ffcbc9a3956..ead4cdf0528c 100644 --- a/oox/source/token/tokens.txt +++ b/oox/source/token/tokens.txt @@ -500,6 +500,7 @@ afterAutospacing afterEffect afterGroup afterLines +aggregation ahLst ahPolar ahXY @@ -880,6 +881,9 @@ bidi bidiVisual bilevel billions +binCount +binSize +binning birds birdsFlight bisque @@ -2516,6 +2520,7 @@ gear6 gear9 gems general +geography gfxdata ghostCol ghostRow @@ -2917,6 +2922,7 @@ interactiveSeq intercept intermediate interval +intervalClosed intraSp inv invGamma @@ -3939,6 +3945,7 @@ parameter parameterType parameters parent +parentLabelLayout parentSet paretoLine parsePre @@ -4346,6 +4353,7 @@ refreshOnLoad refreshedBy refreshedDate refreshedVersion +regionLabelLayout regionMap regroupid regrouptable @@ -5035,6 +5043,7 @@ startarrow startarrowlength startarrowwidth state +statistics status statusBar statusText @@ -5116,6 +5125,7 @@ subject subscript subsetted subtotal +subtotals subtotalCaption subtotalHiddenItems subtotalTop