tdf#165742 Step 5.12: Improve axis handling

Implement better handling of <cx:axisId> and <cx:axis> tags. In particular,
ensure we output the correct number of axisId elements, based on the chart
type, and ensure that corresponding axes (and only those) are captured in the
appropriate <cx:axis> data.

Signed-off-by: Kurt Nordback <kurt.nordback@collabora.com>
Change-Id: Ifed17afc05c70dced89e75f18dcf375fdef97f6e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5775
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 833f782bca98c891097d887a45365bc7a5322c4d)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209239
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Kurt Nordback
2026-08-18 13:08:26 +02:00
committed by Xisco Fauli
parent ad2cacedde
commit c26599741c
16 changed files with 385 additions and 71 deletions
+49 -6
View File
@@ -304,10 +304,7 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexPPTX)
assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series", 3, 0,
"layoutId", u"funnel");
// There should be only one axis, where currently there are multiple.
// However, that's a separate problem from the gapWidth output. So just
// reference the first for now.
assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:axis[1]/cx:catScaling", "gapWidth",
assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:axis/cx:catScaling", "gapWidth",
u"2.19");
}
@@ -318,7 +315,7 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexGapWidth)
xmlDocUniquePtr pXmlDoc = parseExport(u"xl/charts/chartEx1.xml"_ustr);
CPPUNIT_ASSERT(pXmlDoc);
assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:axis[1]/cx:catScaling", "gapWidth",
assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:axis/cx:catScaling", "gapWidth",
u"2.47");
}
@@ -329,7 +326,7 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexGapWidth2)
xmlDocUniquePtr pXmlDoc = parseExport(u"xl/charts/chartEx1.xml"_ustr);
CPPUNIT_ASSERT(pXmlDoc);
assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:axis[1]/cx:catScaling", "gapWidth",
assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:axis/cx:catScaling", "gapWidth",
u"2.55");
}
@@ -376,6 +373,52 @@ CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexNoSpPr)
0);
}
CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testChartexAxisIdPerSeries)
{
// Waterfall charts have two axes. Each series should reference both via
// cx:axisId, and the plot area should contain exactly two cx:axis
// elements.
loadFromFile(u"xlsx/waterfall2.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[1]/cx:axisId", 2);
assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:axis", 2);
}
CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testFunnelCharts)
{
// Funnel charts have one axis. (At least as MSO produces them -- arguably
// there are two dimensions, but we'll stick with the MSO convention for
// now.) The series should reference it via
// cx:axisId, and the plot area should contain exactly one cx:axis
// elements.
loadFromFile(u"xlsx/funnel1.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:axisId", 1);
assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:axis", 1);
}
CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testRegionMap)
{
// RegionMap charts have no axes. The series should have no cx:axisId and the plot
// area should have no cx:axis.
loadFromFile(u"xlsx/regionMap.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:axisId", 0);
assertXPath(pXmlDoc, "/cx:chartSpace/cx:chart/cx:plotArea/cx:axis", 0);
}
CPPUNIT_TEST_FIXTURE(Chart2ExportTest2, testAxisTitleRotationXLSX)
{
loadFromFile(u"xlsx/axis_title_rotation.xlsx");
+2 -1
View File
@@ -57,7 +57,8 @@ namespace chart::DataSeriesProperties
PROP_DATASERIES_GEOCACHE_BINARY,
PROP_DATASERIES_GEOCACHE_CLEAR_DATA,
PROP_DATASERIES_HAS_EXPLICIT_FILL,
PROP_DATASERIES_HAS_DATA_LABELS
PROP_DATASERIES_HAS_DATA_LABELS,
PROP_DATASERIES_CHARTEX_AXIS_IDS
};
void AddPropertiesToVector(
+17 -1
View File
@@ -79,7 +79,9 @@ enum
PROP_AXIS_TRY_STAGGERING_FIRST,
PROP_AXIS_MAJOR_ORIGIN,
PROP_AXIS_HAS_EXPLICIT_SPPR,
PROP_AXIS_HAS_EXPLICIT_TXPR
PROP_AXIS_HAS_EXPLICIT_TXPR,
PROP_AXIS_ID,
PROP_AXIS_CAT_NOT_VAL
};
void lcl_AddPropertiesToVector(
@@ -217,6 +219,20 @@ void lcl_AddPropertiesToVector(
beans::PropertyAttribute::BOUND
| beans::PropertyAttribute::MAYBEVOID );
// Chartex only: the cx:axis id attribute value from import, preserved
// so export can reuse the same id and so series-to-axis links survive
// round-trip.
rOutProperties.emplace_back( "AxisId",
PROP_AXIS_ID,
cppu::UnoType<sal_Int32>::get(),
beans::PropertyAttribute::BOUND
| beans::PropertyAttribute::MAYBEVOID );
rOutProperties.emplace_back( "CatNotVal",
PROP_AXIS_CAT_NOT_VAL,
cppu::UnoType<bool>::get(),
beans::PropertyAttribute::BOUND
| beans::PropertyAttribute::MAYBEVOID );
}
} // namespace
@@ -208,6 +208,14 @@ void DataSeriesProperties::AddPropertiesToVector(
beans::PropertyAttribute::BOUND
| beans::PropertyAttribute::MAYBEVOID );
// Chartex only: the cx:axisId values listed on the cx:series element at
// import. Empty for axis-less chartex types (regionMap, sunburst, treemap).
rOutProperties.emplace_back( "ChartexAxisIds",
PROP_DATASERIES_CHARTEX_AXIS_IDS,
cppu::UnoType<uno::Sequence<sal_Int32>>::get(),
beans::PropertyAttribute::BOUND
| beans::PropertyAttribute::MAYBEVOID );
// add properties of service DataPointProperties
DataPointProperties::AddPropertiesToVector( rOutProperties );
}
+15 -10
View File
@@ -77,21 +77,23 @@ namespace oox::drawingml {
struct LabelPlacementParam;
enum AxesType
enum AxisType
{
AXIS_PRIMARY_X = 1,
AXIS_PRIMARY_Y = 2,
AXIS_PRIMARY_Z = 3,
AXIS_SECONDARY_X = 4,
AXIS_SECONDARY_Y = 5
AXIS_PRIMARY_X,
AXIS_PRIMARY_Y,
AXIS_PRIMARY_Z,
AXIS_SECONDARY_X,
AXIS_SECONDARY_Y,
AXIS_CATEGORY, // used for chartex
AXIS_VALUE // used for chartex
};
struct AxisIdPair{
AxesType nAxisType;
AxisType nAxisType;
sal_Int32 nAxisId;
sal_Int32 nCrossAx;
AxisIdPair(AxesType nType, sal_Int32 nId, sal_Int32 nAx)
AxisIdPair(AxisType nType, sal_Int32 nId, sal_Int32 nAx)
: nAxisType(nType)
, nAxisId(nId)
, nCrossAx(nAx)
@@ -273,7 +275,8 @@ private:
void exportManualLayout(const css::chart2::RelativePosition& rPos, const css::chart2::RelativeSize& rSize, const bool bIsExcludingDiagramPositioning);
void exportAxes( bool bIsChartex );
void exportAxes_chart();
void exportAxes_chartex();
void exportAxis(const AxisIdPair& rAxisIdPair,
bool bIsChartex);
void exportOneAxis_chart(
@@ -291,7 +294,9 @@ private:
const css::uno::Reference< css::beans::XPropertySet >& xMinorGrid,
sal_Int32 nAxisType,
const AxisIdPair& rAxisIdPair);
void createAxes(bool bPrimaryAxes, bool bCheckCombinedAxes, bool bIsChartex);
void createAxes_chart(bool bPrimaryAxes, bool bCheckCombinedAxes);
void createAxes_chartex(
const css::uno::Reference<css::chart2::XDataSeries>& xSeries);
void exportView3D();
bool isDeep3dChart();
+2 -1
View File
@@ -61,7 +61,8 @@ public:
void convertFromModel(
const css::uno::Reference<css::chart2::XCoordinateSystem>& rxCoordSystem,
RefVector<TypeGroupConverter>& rTypeGroups, const AxisModel* pCrossingAxis,
sal_Int32 nAxesSetIdx, sal_Int32 nAxisIdx, bool bUseFixedInnerSize);
sal_Int32 nAxesSetIdx, sal_Int32 nAxisIdx, bool bUseFixedInnerSize,
ChartType eCT);
};
+1
View File
@@ -63,6 +63,7 @@ struct AxisModel
ShapeRef mxMajorGridLines; /// Major grid lines formatting.
ShapeRef mxMinorGridLines; /// Minor grid lines formatting.
NumberFormat maNumberFormat; /// Number format for axis tick labels.
std::optional<bool> mobCatNotVal; /// True if cat axis, false if val axis (for chartex)
std::optional< double > mofCrossesAt; /// Position on this axis where another axis crosses.
std::optional< double > mofMajorUnit; /// Unit for major tick marks on date/value axis.
std::optional< double > mofMinorUnit; /// Unit for minor tick marks on date/value axis.
+2 -1
View File
@@ -243,6 +243,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 mnTypeId; /// Chart type
sal_Int32 mnDataId; /// Reference to correct data chunk (chartex)
bool mbBubble3d; /// True = show bubbles with 3D shade.
bool mbInvertNeg; /// True = invert negative data points.
@@ -251,7 +252,7 @@ struct SeriesModel
maAxisIds; /// List of axis identifiers used
// (Only used by chartex)
explicit SeriesModel(bool bMSO2007Doc);
explicit SeriesModel(bool bMSO2007Doc, sal_Int32 nTypeId = -1);
~SeriesModel();
};
@@ -309,9 +309,11 @@ ContextHandlerRef CxAxisContext::onCreateContext( sal_Int32 nElement, const Attr
if( isRootElement() ) switch( nElement )
{
case CX_TOKEN(catScaling) :
mrModel.mobCatNotVal = true;
mrModel.mofGapWidth = rAttribs.getDouble(XML_gapWidth);
return nullptr;
case CX_TOKEN(valScaling) :
mrModel.mobCatNotVal = false;
mrModel.mofMax = rAttribs.getDouble(XML_max);
mrModel.mofMin = rAttribs.getDouble(XML_min);
mrModel.mofMajorUnit = rAttribs.getDouble(XML_majorUnit);
+11 -1
View File
@@ -146,7 +146,8 @@ AxisConverter::~AxisConverter()
void AxisConverter::convertFromModel(const Reference<XCoordinateSystem>& rxCoordSystem,
RefVector<TypeGroupConverter>& rTypeGroups,
const AxisModel* pCrossingAxis, sal_Int32 nAxesSetIdx,
sal_Int32 nAxisIdx, bool bUseFixedInnerSize)
sal_Int32 nAxisIdx, bool bUseFixedInnerSize,
ChartType eCT)
{
if (rTypeGroups.empty())
return;
@@ -226,6 +227,15 @@ void AxisConverter::convertFromModel(const Reference<XCoordinateSystem>& rxCoord
if (mrModel.mxTextProp.is())
aAxisProp.setProperty(PROP_HasExplicitTxPr, true);
// Chartex only: preserve the cx:axis id so export can re-emit it and
// so series-to-axis links can be reconstructed from the chart2 model.
if (eCT == ChartType::CX && mrModel.mnAxisId >= 0) {
aAxisProp.setProperty(PROP_AxisId, mrModel.mnAxisId);
if (mrModel.mobCatNotVal) {
aAxisProp.setProperty(PROP_CatNotVal, mrModel.mobCatNotVal.value());
}
}
// no X axis line in radar charts
if( (nAxisIdx == API_X_AXIS) && (rTypeInfo.meTypeCategory == TYPECATEGORY_RADAR) )
mrModel.mxShapeProp.getOrCreate().getLineProperties().maLineFill.moFillType = XML_noFill;
@@ -182,13 +182,6 @@ ContextHandlerRef PlotAreaContext::onCreateContext( sal_Int32 nElement, [[maybe_
if (rAttribs.hasAttribute(XML_id)) {
sal_Int32 nId = rAttribs.getInteger(XML_id, -1);
// TODO: also handle attribute "hidden"
// Add the id to the axis id list in the (fictitious)
// type group
std::shared_ptr<TypeGroupModel> aTGM =
mrModel.maTypeGroups.get(mrModel.maTypeGroups.size() - 1);
aTGM->maAxisIds.push_back(nId);
return new CxAxisContext(*this, mrModel.maAxes.create(nElement, mrModel.meCT), nId);
} else {
return nullptr;
@@ -240,7 +233,8 @@ ContextHandlerRef PlotAreaContext::onCreateContext( sal_Int32 nElement, [[maybe_
mrModel.maTypeGroups.create(nTypeId, false);
std::shared_ptr<TypeGroupModel> aTGM =
mrModel.maTypeGroups.get(mrModel.maTypeGroups.size() - 1);
return new ChartexSeriesContext(*this, aTGM->maSeries.create(false), mrModel.mnCurSeriesIdx++);
return new ChartexSeriesContext(*this,
aTGM->maSeries.create(false, nTypeId), mrModel.mnCurSeriesIdx++);
}
return nullptr;
case CX_TOKEN(plotSurface) :
@@ -228,10 +228,10 @@ void AxesSetConverter::convertFromModel( const Reference< XDiagram >& rxDiagram,
AxisConverter aXAxisConv( *this, *xXAxis );
aXAxisConv.convertFromModel(xCoordSystem, aTypeGroups, xYAxis.get(), nAxesSetIdx,
API_X_AXIS, bUseFixedInnerSize);
API_X_AXIS, bUseFixedInnerSize, eCT);
AxisConverter aYAxisConv( *this, *xYAxis );
aYAxisConv.convertFromModel(xCoordSystem, aTypeGroups, xXAxis.get(), nAxesSetIdx,
API_Y_AXIS, bUseFixedInnerSize);
API_Y_AXIS, bUseFixedInnerSize, eCT);
if( rFirstTypeGroup.isDeep3dChart() )
{
@@ -239,7 +239,7 @@ void AxesSetConverter::convertFromModel( const Reference< XDiagram >& rxDiagram,
mrModel.maAxes, API_Z_AXIS, C_TOKEN( serAx ), eCT );
AxisConverter aZAxisConv( *this, *xZAxis );
aZAxisConv.convertFromModel(xCoordSystem, aTypeGroups, nullptr, nAxesSetIdx,
API_Z_AXIS, bUseFixedInnerSize);
API_Z_AXIS, bUseFixedInnerSize, eCT);
}
}
}
@@ -412,6 +412,115 @@ void PlotAreaConverter::convertFromModel( View3DModel& rView3DModel,
}
}
// Transfer the series-level axis ids, given in chartex, to the
// type group. This doesn't work if there are multiple series
// per type group, but I don't think that should happen.
for (auto const& typeGroup : mrModel.maTypeGroups)
{
if( !typeGroup->maSeries.empty() )
{
for (auto const& elemSeries : typeGroup->maSeries) {
// First fill in any implicit axes. MS Office produces files
// where some series axis ids are implicit. Make those explicit.
switch (elemSeries->mnTypeId) {
case CX_TOKEN(boxWhisker):
case CX_TOKEN(clusteredColumn):
case CX_TOKEN(paretoLine):
case CX_TOKEN(waterfall):
{
bool bNeedCat = false, bNeedVal = false;
// Should have two axes, one category and one value
// (though they may be hidden)
if (elemSeries->maAxisIds.size() == 0) {
bNeedCat = bNeedVal = true;
} else if (elemSeries->maAxisIds.size() == 1) {
assert(aAxisMap[elemSeries->maAxisIds[0]]->mobCatNotVal.has_value());
bool bCatNotVal = aAxisMap[elemSeries->maAxisIds[0]]->mobCatNotVal.value();
if (bCatNotVal) {
// have cat, need val
bNeedVal = true;
} else {
// have val, need cat
bNeedCat = true;
}
} else if (elemSeries->maAxisIds.size() == 2) {
assert(aAxisMap[elemSeries->maAxisIds[0]]->mobCatNotVal.has_value());
assert(aAxisMap[elemSeries->maAxisIds[1]]->mobCatNotVal.has_value());
[[maybe_unused]] bool bCatNotVal0 = aAxisMap[elemSeries->maAxisIds[0]]->mobCatNotVal.value();
[[maybe_unused]] bool bCatNotVal1 = aAxisMap[elemSeries->maAxisIds[1]]->mobCatNotVal.value();
// There should be one cat axis and one val axis
assert((bCatNotVal0 && !bCatNotVal1) || (!bCatNotVal0 && bCatNotVal1));
} else {
// more than two axes: don't know what to do with this
assert(false);
}
if (bNeedCat) {
// Add the first cat axis
for (const auto& [nId, aAxisModel] : aAxisMap) {
if (aAxisModel->mobCatNotVal.has_value() &&
aAxisModel->mobCatNotVal.value()) {
elemSeries->maAxisIds.push_back(nId);
break;
}
}
}
if (bNeedVal) {
// Add the first cat axis
for (const auto& [nId, aAxisModel] : aAxisMap) {
if (aAxisModel->mobCatNotVal.has_value() &&
!aAxisModel->mobCatNotVal.value()) {
elemSeries->maAxisIds.push_back(nId);
break;
}
}
}
break;
}
case CX_TOKEN(funnel):
{
bool bNeedCat = false;
// Should have one category axis (though it may be hidden)
// Funnel never seems to have a value axis
if (elemSeries->maAxisIds.size() == 0) {
bNeedCat = true;
} else if (elemSeries->maAxisIds.size() == 1) {
assert(aAxisMap[elemSeries->maAxisIds[0]]->mobCatNotVal.has_value());
[[maybe_unused]] bool bCatNotVal = aAxisMap[elemSeries->maAxisIds[0]]->mobCatNotVal.value();
assert(bCatNotVal);
} else {
// more than one axis: don't know what to do with this
assert(false);
}
if (bNeedCat) {
// Add the first cat axis
for (const auto& [nId, aAxisModel] : aAxisMap) {
if (aAxisModel->mobCatNotVal.has_value() &&
aAxisModel->mobCatNotVal.value()) {
elemSeries->maAxisIds.push_back(nId);
break;
}
}
}
break;
}
default:
break;
}
if (typeGroup->maAxisIds.empty()) {
if (!elemSeries->maAxisIds.empty()) {
assert(typeGroup->maSeries.size() == 1);
typeGroup->maAxisIds = elemSeries->maAxisIds;
}
}
}
}
}
// group the type group models into different axes sets
typedef ModelVector< AxesSetModel > AxesSetVector;
AxesSetVector aAxesSets;
@@ -450,8 +559,9 @@ void PlotAreaConverter::convertFromModel( View3DModel& rView3DModel,
pAxesSet->maTypeGroups.push_back( typeGroup );
// collect the maximum series index for automatic series formatting
for (auto const& elemSeries : typeGroup->maSeries)
for (auto const& elemSeries : typeGroup->maSeries) {
nMaxSeriesIdx = ::std::max( nMaxSeriesIdx, elemSeries->mnIndex );
}
}
}
getFormatter().setMaxSeriesIndex( nMaxSeriesIdx );
@@ -1032,6 +1032,16 @@ Reference< XDataSeries > SeriesConverter::createDataSeries( const TypeGroupConve
convertGeography(*rLPR->mxGeography, aSeriesProp);
}
}
// Chartex only: preserve the cx:axisId list from the series so export
// can emit the matching cx:axisId children and the right number of
// cx:axis elements.
if (!mrModel.maAxisIds.empty())
{
aSeriesProp.setProperty(PROP_ChartexAxisIds,
comphelper::containerToSequence(mrModel.maAxisIds));
}
return xDataSeries;
}
+2 -1
View File
@@ -112,12 +112,13 @@ BinningModel::BinningModel() :
{
}
SeriesModel::SeriesModel(bool bMSO2007Doc) :
SeriesModel::SeriesModel(bool bMSO2007Doc, sal_Int32 nTypeId) :
mnExplosion( 0 ),
mnIndex( -1 ),
mnMarkerSize( 5 ),
mnMarkerSymbol( XML_auto ),
mnOrder( -1 ),
mnTypeId(nTypeId),
mnDataId(-1),
mbBubble3d( !bMSO2007Doc ),
mbInvertNeg( !bMSO2007Doc ),
+145 -37
View File
@@ -2876,7 +2876,7 @@ void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >&
pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, "col");
pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, "clustered");
pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0");
createAxes(true, false, false);
createAxes_chart(true, false);
pFS->endElement(FSNS(XML_c, XML_barChart));
}
@@ -3043,7 +3043,11 @@ void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >&
}
//Axis Data
exportAxes(bIsChartex);
if (bIsChartex) {
exportAxes_chartex();
} else {
exportAxes_chart();
}
if (!bIsChartex) {
// Data Table
@@ -3451,7 +3455,7 @@ void ChartExport::exportAreaChart( const Reference< chart2::XChartType >& xChart
if (splitDataSeries.hasElements())
exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes);
createAxes(bPrimaryAxes, true, false);
createAxes_chart(bPrimaryAxes, true);
//exportAxesId(bPrimaryAxes);
pFS->endElement(FSNS(XML_c, nTypeId));
@@ -3555,7 +3559,7 @@ void ChartExport::exportBarChart(const Reference< chart2::XChartType >& xChartTy
}
}
createAxes(bPrimaryAxes, true, false);
createAxes_chart(bPrimaryAxes, true);
pFS->endElement(FSNS(XML_c, nTypeId));
}
@@ -3580,7 +3584,7 @@ void ChartExport::exportBubbleChart( const Reference< chart2::XChartType >& xCha
if (splitDataSeries.hasElements())
exportSeries_chart(xChartType, splitDataSeries, bPrimaryAxes);
createAxes(bPrimaryAxes, true, false);
createAxes_chart(bPrimaryAxes, true);
pFS->endElement(FSNS(XML_c, XML_bubbleChart));
}
@@ -3598,8 +3602,6 @@ void ChartExport::exportChartex( const Reference< chart2::XChartType >& xChartTy
if (!splitDataSeries.hasElements())
continue;
createAxes(true, false, true);
//exportVaryColors(xChartType);
exportSeries_chartex(xChartType, splitDataSeries, sTypeName);
@@ -3737,7 +3739,7 @@ void ChartExport::exportLineChart( const Reference< chart2::XChartType >& xChart
pFS->singleElement(FSNS(XML_c, XML_marker), XML_val, marker);
}
createAxes(bPrimaryAxes, true, false);
createAxes_chart(bPrimaryAxes, true);
pFS->endElement( FSNS( XML_c, nTypeId ) );
}
@@ -3782,7 +3784,7 @@ void ChartExport::exportRadarChart( const Reference< chart2::XChartType >& xChar
exportVaryColors(xChartType);
bool bPrimaryAxes = true;
exportAllSeries(xChartType, bPrimaryAxes);
createAxes(bPrimaryAxes, true, false);
createAxes_chart(bPrimaryAxes, true);
pFS->endElement( FSNS( XML_c, XML_radarChart ) );
}
@@ -3812,7 +3814,7 @@ void ChartExport::exportScatterChartSeries( const Reference< chart2::XChartType
bool bPrimaryAxes = true;
if (pSeries)
exportSeries_chart(xChartType, *pSeries, bPrimaryAxes);
createAxes(bPrimaryAxes, true, false);
createAxes_chart(bPrimaryAxes, true);
//exportAxesId(bPrimaryAxes);
pFS->endElement( FSNS( XML_c, XML_scatterChart ) );
@@ -3860,7 +3862,7 @@ void ChartExport::exportStockChart( const Reference< chart2::XChartType >& xChar
exportUpDownBars(xChartType);
}
createAxes(bPrimaryAxes, true, false);
createAxes_chart(bPrimaryAxes, true);
pFS->endElement(FSNS(XML_c, XML_stockChart));
}
@@ -3935,7 +3937,7 @@ void ChartExport::exportSurfaceChart( const Reference< chart2::XChartType >& xCh
exportVaryColors(xChartType);
bool bPrimaryAxes = true;
exportAllSeries(xChartType, bPrimaryAxes);
createAxes(bPrimaryAxes, true, false);
createAxes_chart(bPrimaryAxes, true);
pFS->endElement( FSNS( XML_c, nTypeId ) );
}
@@ -4448,6 +4450,7 @@ void ChartExport::exportSeries_chartex( const Reference<chart2::XChartType>& xCh
}
// axisId
createAxes_chartex(rSeries);
// extLst
@@ -4812,7 +4815,7 @@ void ChartExport::InitPlotArea( )
}
}
void ChartExport::exportAxes( bool bIsChartex )
void ChartExport::exportAxes_chart()
{
sal_Int32 nSize = maAxes.size();
// let's export the axis types in the right order
@@ -4821,11 +4824,18 @@ void ChartExport::exportAxes( bool bIsChartex )
for ( sal_Int32 nIdx = 0; nIdx < nSize; nIdx++ )
{
if (nSortIdx == maAxes[nIdx].nAxisType)
exportAxis( maAxes[nIdx], bIsChartex );
exportAxis( maAxes[nIdx], false);
}
}
}
void ChartExport::exportAxes_chartex()
{
for (const AxisIdPair& aIdPair : maAxes) {
exportAxis(aIdPair, true);
}
}
namespace {
sal_Int32 getXAxisTypeByChartType(sal_Int32 eChartType)
@@ -4894,6 +4904,7 @@ void ChartExport::exportAxis(const AxisIdPair& rAxisIdPair, bool bIsChartex)
switch( rAxisIdPair.nAxisType )
{
case AXIS_PRIMARY_X:
case AXIS_CATEGORY:
{
Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
if( xAxisXSupp.is())
@@ -4905,16 +4916,21 @@ void ChartExport::exportAxis(const AxisIdPair& rAxisIdPair, bool bIsChartex)
if( bHasXAxisMinorGrid )
xMinorGrid = xAxisXSupp->getXHelpGrid();
nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 0);
if( nAxisType != -1 )
nAxisType = getRealXAxisType(nAxisType);
else
nAxisType = getXAxisTypeByChartType( getChartType() );
if (rAxisIdPair.nAxisType == AXIS_CATEGORY) {
nAxisType = XML_catAx;
} else {
nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 0);
if( nAxisType != -1 )
nAxisType = getRealXAxisType(nAxisType);
else
nAxisType = getXAxisTypeByChartType( getChartType() );
}
// FIXME: axPos, need to check axis direction
sAxPos = "b";
break;
}
case AXIS_PRIMARY_Y:
case AXIS_VALUE:
{
Reference< css::chart::XAxisYSupplier > xAxisYSupp( mxDiagram, uno::UNO_QUERY );
if( xAxisYSupp.is())
@@ -6214,7 +6230,7 @@ void ChartExport::exportDataPoints(
}
// Generalized axis output
void ChartExport::createAxes(bool bPrimaryAxes, bool bCheckCombinedAxes, bool bIsChartex)
void ChartExport::createAxes_chart(bool bPrimaryAxes, bool bCheckCombinedAxes)
{
sal_Int32 nAxisIdx = -1, nAxisIdy = -1;
bool bCreateAxes = true;
@@ -6222,8 +6238,8 @@ void ChartExport::createAxes(bool bPrimaryAxes, bool bCheckCombinedAxes, bool bI
// tdf#114181 keep axes of combined charts - search for existing pairs
if (bCheckCombinedAxes)
{
const AxesType eWantedX = bPrimaryAxes ? AXIS_PRIMARY_X : AXIS_SECONDARY_X;
const AxesType eWantedY = bPrimaryAxes ? AXIS_PRIMARY_Y : AXIS_SECONDARY_Y;
const AxisType eWantedX = bPrimaryAxes ? AXIS_PRIMARY_X : AXIS_SECONDARY_X;
const AxisType eWantedY = bPrimaryAxes ? AXIS_PRIMARY_Y : AXIS_SECONDARY_Y;
sal_Int32 nFoundX = -1, nFoundY = -1;
for (const auto& rAxis : maAxes)
@@ -6246,27 +6262,119 @@ void ChartExport::createAxes(bool bPrimaryAxes, bool bCheckCombinedAxes, bool bI
{
nAxisIdx = lcl_generateRandomValue();
nAxisIdy = lcl_generateRandomValue();
AxesType eXAxis = bPrimaryAxes ? AXIS_PRIMARY_X : AXIS_SECONDARY_X;
AxesType eYAxis = bPrimaryAxes ? AXIS_PRIMARY_Y : AXIS_SECONDARY_Y;
AxisType eXAxis = bPrimaryAxes ? AXIS_PRIMARY_X : AXIS_SECONDARY_X;
AxisType eYAxis = bPrimaryAxes ? AXIS_PRIMARY_Y : AXIS_SECONDARY_Y;
maAxes.emplace_back( eXAxis, nAxisIdx, nAxisIdy );
maAxes.emplace_back( eYAxis, nAxisIdy, nAxisIdx );
}
if (!bIsChartex) {
// Export IDs
FSHelperPtr pFS = GetFS();
pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdx));
pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdy));
if (mbHasZAxis)
// Export IDs
FSHelperPtr pFS = GetFS();
pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdx));
pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdy));
if (mbHasZAxis)
{
sal_Int32 nAxisIdz = 0;
if( isDeep3dChart() )
{
sal_Int32 nAxisIdz = 0;
if( isDeep3dChart() )
{
nAxisIdz = lcl_generateRandomValue();
maAxes.emplace_back( AXIS_PRIMARY_Z, nAxisIdz, nAxisIdy );
}
pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdz));
nAxisIdz = lcl_generateRandomValue();
maAxes.emplace_back( AXIS_PRIMARY_Z, nAxisIdz, nAxisIdy );
}
pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdz));
}
}
void ChartExport::createAxes_chartex(
const Reference<chart2::XDataSeries>& xSeries)
{
FSHelperPtr pFS = GetFS();
// Use the cx:axisId list preserved on the data series at import.
// For axis-less chartex types (regionMap, sunburst, treemap) the
// sequence is empty, so no <cx:axisId> and no <cx:axis> are emitted.
uno::Sequence<sal_Int32> aAxisIds;
Reference<beans::XPropertySet> xSeriesProp(xSeries, uno::UNO_QUERY);
if (xSeriesProp.is())
{
try
{
xSeriesProp->getPropertyValue(u"ChartexAxisIds"_ustr) >>= aAxisIds;
}
catch (const uno::Exception&)
{
}
}
if (!aAxisIds.hasElements())
return;
for (sal_Int32 nAxisId : aAxisIds)
{
pFS->singleElement(FSNS(XML_cx, XML_axisId), XML_val,
OString::number(nAxisId));
// Each axis appears in maAxes (and therefore in <cx:axis> output)
// exactly once across all series.
bool bKnown = std::any_of(maAxes.begin(), maAxes.end(),
[nAxisId](const AxisIdPair& rPair)
{ return rPair.nAxisId == nAxisId; });
if (bKnown)
continue;
// Find the axis type by walking through and
// matching the AxisId property we stored at import.
AxisType eAxisType = AXIS_CATEGORY;
try
{
Reference<chart2::XCoordinateSystemContainer> xCooSysCnt(
mxNewDiagram, uno::UNO_QUERY);
if (xCooSysCnt.is())
{
bool bFound = false;
const auto aCooSysSeq = xCooSysCnt->getCoordinateSystems();
for (const auto& xCooSys : aCooSysSeq)
{
if (bFound)
break;
const sal_Int32 nDim = xCooSys->getDimension();
for (sal_Int32 i = 0; i < nDim && !bFound; ++i)
{
const sal_Int32 nMaxAxIdx = xCooSys->getMaximumAxisIndexByDimension(i);
for (sal_Int32 j = 0; j <= nMaxAxIdx && !bFound; ++j)
{
Reference<chart2::XAxis> xAxis = xCooSys->getAxisByDimension(i, j);
Reference<beans::XPropertySet> xAxisProp( xAxis, uno::UNO_QUERY);
if (!xAxisProp.is())
continue;
sal_Int32 nStoredId = -1;
try
{
xAxisProp->getPropertyValue(u"AxisId"_ustr) >>= nStoredId;
}
catch (const uno::Exception&)
{
continue;
}
if (nStoredId == nAxisId)
{
bool bCatNotVal = false;
xAxisProp->getPropertyValue(u"CatNotVal"_ustr) >>= bCatNotVal;
eAxisType = bCatNotVal ? AXIS_CATEGORY : AXIS_VALUE;
bFound = true;
}
}
}
}
}
}
catch (const uno::Exception&)
{
}
// nCrossAx is unused for chartex (<cx:axis> has no crossAx field),
// so the value passed for the third parameter is irrelevant
maAxes.emplace_back(eAxisType, nAxisId, -1);
}
}
+3
View File
@@ -18,6 +18,7 @@ AttachedAxisIndex
AutoFilter
AutoShowInfo
Autocomplete
AxisId
BackColor
BackColorTransparency
BackGraphicLocation
@@ -48,6 +49,7 @@ BulletFontName
BulletRelSize
CLSID
CalcAsShown
CatNotVal
CellBackColor
CellProtection
CellStyle
@@ -57,6 +59,7 @@ Change
ChartExDimType
ChartExFormula
ChartExNFormula
ChartexAxisIds
CharBackColor
CharBackgroundComplexColor
CharCaseMap