tdf#165742 Step 5.8: Add minimal support for layout props

Add framework for import/export of chartex layout properties (cx:layoutPr).
Also support the child cx:binning property, used for clusteredColumn charts
(and perhaps others). This allows the binning property to round-trip
successfully MSO -> LO -> MSO.

Change-Id: I54bd1692ce4fb59dd70c9d4ac68f108c33cbca8a
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199553
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
This commit is contained in:
Kurt Nordback
2026-03-24 06:57:32 +01:00
committed by Tomaž Vajngerl
parent 1981c314ce
commit c26dd0e1f2
11 changed files with 237 additions and 3 deletions
+13
View File
@@ -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");
+2 -1
View File
@@ -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(
@@ -81,6 +81,12 @@ void DataSeriesProperties::AddPropertiesToVector(
beans::PropertyAttribute::BOUND
| beans::PropertyAttribute::MAYBEDEFAULT );
rOutProperties.emplace_back( "IntervalClosed",
PROP_DATASERIES_INTERVAL_CLOSED,
cppu::UnoType<sal_uInt32>::get(),
beans::PropertyAttribute::BOUND
| beans::PropertyAttribute::MAYBEVOID );
// add properties of service DataPointProperties
DataPointProperties::AddPropertiesToVector( rOutProperties );
}
+30
View File
@@ -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).
+25
View File
@@ -20,6 +20,8 @@
#ifndef INCLUDED_OOX_DRAWINGML_CHART_SERIESMODEL_HXX
#define INCLUDED_OOX_DRAWINGML_CHART_SERIESMODEL_HXX
#include <variant>
#include <oox/drawingml/chart/datasourcemodel.hxx>
#include <drawingml/chart/titlemodel.hxx>
@@ -174,6 +176,27 @@ struct DataPointModel
~DataPointModel();
};
struct BinningModel
{
enum ClosedSide {
L,
R
};
std::optional<std::variant<double, sal_uInt32>> maBinSizeOrCount;
std::optional<ClosedSide> 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.
+114 -2
View File
@@ -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<double, sal_uInt32>(
static_cast<sal_uInt32>(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;
@@ -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<BinningModel> 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;
}
@@ -106,6 +106,12 @@ DataPointModel::~DataPointModel()
{
}
BinningModel::BinningModel() :
maBinSizeOrCount(),
meIntervalClosed()
{
}
SeriesModel::SeriesModel(bool bMSO2007Doc) :
mnExplosion( 0 ),
mnIndex( -1 ),
+19
View File
@@ -3932,6 +3932,25 @@ void ChartExport::exportSeries_chartex( const Reference<chart2::XChartType>& 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
+1
View File
@@ -292,6 +292,7 @@ IncludeHiddenCells
InputMessage
InputTitle
InterceptValue
IntervalClosed
InvalidUrl
InvertNegative
IsActive
+10
View File
@@ -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