oox: support histogram OOXML import and export

Preserve histogram charts when importng and exporting XLSX file.
Import histogram charts from XLSX, keep their source data and
settings, and export them back that keeps them working as
histogram charts.

Change-Id: I9c3e39fd3b429b9f61c249dd952bf70d9113ed7f
(cherry picked from commit 330fa62f1ef7b7c848c2da98d18a8bb453e82201)
Signed-off-by: Devansh Varshney <varshney.devansh614@gmail.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/1882
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 2ed0256704eb3ca34ff0a217b330849a3f7952da)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/203895
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
This commit is contained in:
Devansh Varshney
2026-05-23 00:10:01 +02:00
committed by Xisco Fauli
parent 0a99dee0ba
commit 5e836dfec5
7 changed files with 387 additions and 17 deletions
+58
View File
@@ -1022,6 +1022,64 @@ CPPUNIT_TEST_FIXTURE(Chart2ImportTest2, testHistogramODSRoundtrip)
CPPUNIT_ASSERT_EQUAL(3.5, fBinWidth);
}
CPPUNIT_TEST_FIXTURE(Chart2ImportTest2, testHistogramXLSXRoundtrip)
{
// See tdf#163727
// OOXML chartex represents histograms as <cx:series layoutId="clusteredColumn">
// with a <cx:layoutPr><cx:binning/></cx:layoutPr> child.
// On import that should be detected as a HistogramChartType and on export the binning
// markup should stay preserved.
loadFromFile(u"xlsx/SimpleHistogram.xlsx");
// Round trip 1: chart type identity + binning element preserved.
saveAndReload(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",
"layoutId", u"clusteredColumn");
assertXPath(pXmlDoc,
"/cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series/cx:layoutPr/"
"cx:binning",
"intervalClosed", u"r");
uno::Reference<chart2::XChartDocument> xChartDoc = getChartDocFromSheet(0);
CPPUNIT_ASSERT(xChartDoc.is());
Reference<chart2::XChartType> xChartType = getChartTypeFromDoc(xChartDoc, 0, 0);
CPPUNIT_ASSERT(xChartType.is());
CPPUNIT_ASSERT_EQUAL(u"com.sun.star.chart2.HistogramChartType"_ustr,
xChartType->getChartType());
// Round trip 2: non-default binning parameters survive save + reload.
Reference<beans::XPropertySet> xProperties(xChartType, uno::UNO_QUERY_THROW);
xProperties->setPropertyValue(u"FrequencyType"_ustr, uno::Any(sal_Int32(1)));
xProperties->setPropertyValue(u"BinWidth"_ustr, uno::Any(2.5));
saveAndReload(TestFilter::XLSX);
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/cx:binSize",
"val", u"2.5");
xChartDoc = getChartDocFromSheet(0);
xChartType = getChartTypeFromDoc(xChartDoc, 0, 0);
Reference<beans::XPropertySet> xReloadedProperties(xChartType, uno::UNO_QUERY_THROW);
sal_Int32 nFrequencyType = -1;
CPPUNIT_ASSERT(xReloadedProperties->getPropertyValue(u"FrequencyType"_ustr) >>= nFrequencyType);
CPPUNIT_ASSERT_EQUAL(sal_Int32(1), nFrequencyType);
double fBinWidth = 0.0;
CPPUNIT_ASSERT(xReloadedProperties->getPropertyValue(u"BinWidth"_ustr) >>= fBinWidth);
CPPUNIT_ASSERT_EQUAL(2.5, fBinWidth);
}
CPPUNIT_TEST_FIXTURE(Chart2ImportTest2, testTdf60316)
{
loadFromFile(u"pptx/tdf60316.pptx");
Binary file not shown.
@@ -74,6 +74,8 @@ struct TypeGroupModel
bool mbSmooth; /// True = smooth lines in line charts.
bool mbVaryColors; /// True = different automatic colors for each point.
bool mbWireframe; /// True = wireframe surface chart, false = filled surface chart.
/// True = this type group should be imported as histogram type - TYPEID_HISTO
bool mbForceHistogram = false;
explicit TypeGroupModel( sal_Int32 nTypeId, bool bMSO2007Doc );
~TypeGroupModel();
@@ -112,11 +112,45 @@ ModelRef< AxisModel > lclGetOrCreateAxis( const AxesSetModel::AxisMap& rFromAxes
return xAxis;
}
bool lclHasTypeGroupWithLayout(const AxesSetModel::TypeGroupVector& rTypeGroups, sal_Int32 nToken)
{
for (auto const& typeGroup : rTypeGroups)
if (typeGroup && typeGroup->mnTypeId == nToken)
return true;
return false;
}
void AxesSetConverter::convertFromModel( const Reference< XDiagram >& rxDiagram,
View3DModel& rView3DModel, sal_Int32 nAxesSetIdx,
DataSourceCxModel::DataMap& raSourceMap,
bool bSupportsVaryColorsByPoint, bool bUseFixedInnerSize)
{
// chartex clusteredColumn + <cx:binning> is how a histogram is stored in OOXML.
// But the same markup also appears on the clusteredColumn sub-chart of a
// paretoLine, which must not be reclassified. The difference is visible only at
// this level, where we see all sibling type groups.
{
const bool bHasParetoSibling
= lclHasTypeGroupWithLayout(mrModel.maTypeGroups, CX_TOKEN(paretoLine));
if (!bHasParetoSibling)
{
for (auto const& typeGroup : mrModel.maTypeGroups)
{
if (!typeGroup || typeGroup->mnTypeId != CX_TOKEN(clusteredColumn))
continue;
for (auto const& rxSeries : typeGroup->maSeries)
{
if (rxSeries && rxSeries->mxLayoutPr.is()
&& rxSeries->mxLayoutPr->mxBinning.is())
{
typeGroup->mbForceHistogram = true;
break;
}
}
}
}
}
// create type group converter objects for all type groups
typedef RefVector< TypeGroupConverter > TypeGroupConvVector;
TypeGroupConvVector aTypeGroups;
@@ -172,18 +206,17 @@ 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. Must happen before convertFromModel
// so the series data is available when data series are created.
for (auto const& typeGroup : aTypeGroups)
typeGroup->moveDataToSeries(raSourceMap);
// convert all chart type groups, this converts all series data and formatting
for (auto const& typeGroup : aTypeGroups)
typeGroup->convertFromModel( rxDiagram, xCoordSystem,
nAxesSetIdx,bSupportsVaryColorsByPoint );
// 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 );
+3 -3
View File
@@ -794,7 +794,7 @@ ContextHandlerRef BinningContext::onCreateContext( sal_Int32 nElement, const Att
{
switch( getCurrentElement() )
{
case CX_TOKEN( layoutPr ):
case CX_TOKEN(binning):
switch( nElement )
{
case CX_TOKEN( binSize ):
@@ -807,6 +807,7 @@ ContextHandlerRef BinningContext::onCreateContext( sal_Int32 nElement, const Att
// TODO
return nullptr;
}
break;
}
return ContextBase::onCreateContext( nElement, rAttribs );
}
@@ -854,9 +855,8 @@ ContextHandlerRef LayoutPropsContext::onCreateContext( sal_Int32 nElement, const
rB.meIntervalClosed = BinningModel::ClosedSide::L;
} else if (rAttribs.getString( XML_intervalClosed ) == "r") {
rB.meIntervalClosed = BinningModel::ClosedSide::R;
} else {
assert(false); // no other options
}
// intervalClosed is optional in OOXML chartex
// TODO: Also handle attributes underflow, overflow
return new BinningContext( *this, rB );
}
@@ -19,6 +19,10 @@
#include <drawingml/chart/typegroupconverter.hxx>
#include <com/sun/star/chart2/XChartDocument.hpp>
#include <com/sun/star/chart2/XChartTypeManager.hpp>
#include <com/sun/star/chart2/XChartTypeTemplate.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/chart/DataLabelPlacement.hpp>
#include <com/sun/star/chart2/CartesianCoordinateSystem2d.hpp>
#include <com/sun/star/chart2/CartesianCoordinateSystem3d.hpp>
@@ -90,7 +94,7 @@ const TypeGroupInfo spTypeInfos[] =
// type-id type-category service varied-point-color default label pos polar area2d 1stvis xcateg swap stack picopt
{ TYPEID_BAR, TYPECATEGORY_BAR, SERVICE_CHART2_COLUMN, VARPOINTMODE_SINGLE, csscd::OUTSIDE, false, true, false, true, false, true, true },
{ TYPEID_HORBAR, TYPECATEGORY_BAR, SERVICE_CHART2_COLUMN, VARPOINTMODE_SINGLE, csscd::OUTSIDE, false, true, false, true, true, true, true },
{ TYPEID_HISTO, TYPECATEGORY_HISTO, SERVICE_CHART2_HISTO, VARPOINTMODE_SINGLE, csscd::OUTSIDE, false, true, false, true, true, true, true },
{ TYPEID_HISTO, TYPECATEGORY_HISTO, SERVICE_CHART2_HISTO, VARPOINTMODE_SINGLE, csscd::OUTSIDE, false, true, false, true, false, true, true },
{ TYPEID_LINE, TYPECATEGORY_LINE, SERVICE_CHART2_LINE, VARPOINTMODE_SINGLE, csscd::RIGHT, false, false, false, true, false, true, false },
{ TYPEID_AREA, TYPECATEGORY_LINE, SERVICE_CHART2_AREA, VARPOINTMODE_NONE, csscd::CENTER, false, true, false, true, false, true, false },
{ TYPEID_STOCK, TYPECATEGORY_LINE, SERVICE_CHART2_CANDLE, VARPOINTMODE_NONE, csscd::RIGHT, false, false, false, true, false, true, false },
@@ -217,6 +221,15 @@ TypeGroupConverter::TypeGroupConverter( const ConverterRoot& rParent, TypeGroupM
default:;
}
// In OOXML chartex, histogram is represented as clusteredColumn with
// <cx:layoutPr><cx:binning .../></cx:layoutPr>. The decision whether this
// is a histogram or the clusteredColumn half of a paretoLine compound chart
// is made by the plot-area converter, which has sibling type-group visibility.
if (eTypeId == TYPEID_CLUSTEREDCOLUMN && mrModel.mbForceHistogram)
{
eTypeId = TYPEID_HISTO;
}
// set the chart type info struct for the current chart type
maTypeInfo = lclGetTypeInfoFromTypeId( eTypeId );
}
@@ -367,6 +380,50 @@ void TypeGroupConverter::convertFromModel( const Reference< XDiagram >& rxDiagra
// additional properties
PropertySet aDiaProp( rxDiagram );
PropertySet aTypeProp( xChartType );
if (maTypeInfo.meTypeId == TYPEID_HISTO)
{
Reference<css::beans::XPropertySet> xTypePropSet(xChartType, UNO_QUERY);
if (xTypePropSet.is())
{
for (const auto& rxSeriesModel : mrModel.maSeries)
{
if (!rxSeriesModel || !rxSeriesModel->mxLayoutPr.is()
|| !rxSeriesModel->mxLayoutPr->mxBinning.is())
{
continue;
}
const BinningModel& rBinning = *rxSeriesModel->mxLayoutPr->mxBinning;
if (rBinning.maBinSizeOrCount)
{
if (std::holds_alternative<double>(*rBinning.maBinSizeOrCount))
{
xTypePropSet->setPropertyValue(
u"BinWidth"_ustr,
uno::Any(std::get<double>(*rBinning.maBinSizeOrCount)));
xTypePropSet->setPropertyValue(
u"FrequencyType"_ustr,
uno::Any(sal_Int32(1)));
}
else
{
xTypePropSet->setPropertyValue(
u"BinCount"_ustr,
uno::Any(static_cast<sal_Int32>(
std::get<sal_uInt32>(*rBinning.maBinSizeOrCount))));
xTypePropSet->setPropertyValue(
u"FrequencyType"_ustr,
uno::Any(sal_Int32(2)));
}
}
break;
}
}
}
switch( maTypeInfo.meTypeCategory )
{
case TYPECATEGORY_BAR:
@@ -511,6 +568,23 @@ void TypeGroupConverter::convertFromModel( const Reference< XDiagram >& rxDiagra
xChartTypeCont->addChartType(xChartType);
}
if (maTypeInfo.meTypeId == TYPEID_HISTO)
{
Reference<chart2::XChartDocument> const& xChartDoc = getChartDocument();
Reference<chart2::XChartTypeManager> xChartTypeManager(
xChartDoc.is() ? xChartDoc->getChartTypeManager() : nullptr);
Reference<lang::XMultiServiceFactory> xTemplateFactory(
xChartTypeManager, UNO_QUERY);
if (xTemplateFactory.is())
{
Reference<chart2::XChartTypeTemplate> xTemplate(
xTemplateFactory->createInstance(u"com.sun.star.chart2.template.Histogram"_ustr),
UNO_QUERY);
if (xTemplate.is())
xTemplate->changeDiagram(rxDiagram);
}
}
// set existence of bar connector lines at diagram (only in stacked 2D bar charts)
if( mrModel.mxSerLines.is() && !mb3dChart && (maTypeInfo.meTypeCategory == TYPECATEGORY_BAR) && (isStacked() || isPercent()) )
aDiaProp.setProperty( PROP_ConnectBars, true );
+209 -6
View File
@@ -31,6 +31,8 @@
#include <docmodel/uno/UnoGradientTools.hxx>
#include <docmodel/uno/UnoComplexColor.hxx>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <limits>
@@ -230,6 +232,133 @@ std::vector<Sequence<Reference<chart2::XDataSeries> > > splitDataSeriesByAxis(co
return aSplitSeries;
}
bool lclGetAutoHistogramBinning(const css::uno::Reference<css::chart2::XDataSeries>& xSeries,
double& rfBinWidth, sal_Int32& rnBinCount)
{
rfBinWidth = 0.0;
rnBinCount = 0;
css::uno::Reference<css::chart2::data::XDataSource> xSource(xSeries, css::uno::UNO_QUERY);
if (!xSource.is())
{
return false;
}
css::uno::Sequence<css::uno::Reference<css::chart2::data::XLabeledDataSequence>> aSeqs
= xSource->getDataSequences();
css::uno::Reference<css::chart2::data::XDataSequence> xValuesY;
for (const auto& xLabeledSeq : aSeqs)
{
if (!xLabeledSeq.is())
{
continue;
}
css::uno::Reference<css::chart2::data::XDataSequence> xValues = xLabeledSeq->getValues();
if (!xValues.is())
{
continue;
}
css::uno::Reference<css::beans::XPropertySet> xSeqProp(xValues, css::uno::UNO_QUERY);
if (!xSeqProp.is())
{
continue;
}
OUString aRole;
xSeqProp->getPropertyValue(u"Role"_ustr) >>= aRole;
if (aRole == "values-y")
{
xValuesY = xValues;
break;
}
}
if (!xValuesY.is())
{
return false;
}
std::vector<double> aRawData;
css::uno::Sequence<css::uno::Any> aValues = xValuesY->getData();
aRawData.reserve(aValues.getLength());
for (const auto& rAny : aValues)
{
double fValue = 0.0;
if (rAny >>= fValue)
aRawData.push_back(fValue);
}
if (aRawData.empty())
{
return false;
}
double fSum = 0.0;
double fSquareSum = 0.0;
double fMinValue = aRawData[0];
double fMaxValue = aRawData[0];
sal_Int32 nValidCount = 0;
for (double fValue : aRawData)
{
if (std::isfinite(fValue))
{
fSum += fValue;
fSquareSum += fValue * fValue;
fMinValue = std::min(fMinValue, fValue);
fMaxValue = std::max(fMaxValue, fValue);
++nValidCount;
}
}
if (nValidCount == 0)
{
return false;
}
if (nValidCount < 2 || fMinValue == fMaxValue)
{
rfBinWidth = 1.0;
rnBinCount = 1;
return true;
}
const double fMean = fSum / nValidCount;
const double fVariance = (fSquareSum - fSum * fMean) / (nValidCount - 1);
const double fStdDev = std::sqrt(fVariance);
if (!std::isfinite(fStdDev) || fStdDev <= 0.0)
{
rfBinWidth = 1.0;
rnBinCount = 1;
return true;
}
rfBinWidth = (3.5 * fStdDev) / std::cbrt(static_cast<double>(nValidCount));
if (!std::isfinite(rfBinWidth) || rfBinWidth <= 0.0)
{
rfBinWidth = 1.0;
rnBinCount = 1;
return true;
}
rfBinWidth = std::round(rfBinWidth * 100.0) / 100.0;
if (rfBinWidth <= 0.0)
{
rfBinWidth = 1.0;
rnBinCount = 1;
return true;
}
rnBinCount = static_cast<sal_Int32>(std::ceil((fMaxValue - fMinValue) / rfBinWidth));
rnBinCount = std::max<sal_Int32>(rnBinCount, 1);
return true;
}
} // unnamed namespace
static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories(
@@ -552,6 +681,8 @@ constexpr auto constChartTypeMap = frozen::make_unordered_map<std::u16string_vie
{ u"com.sun.star.chart2.BoxWhiskerChartType", chart::TYPEID_BOXWHISKER },
{ u"com.sun.star.chart2.ClusteredColumnDiagram", chart::TYPEID_CLUSTEREDCOLUMN },
{ u"com.sun.star.chart2.ClusteredColumnChartType", chart::TYPEID_CLUSTEREDCOLUMN },
{ u"com.sun.star.chart.HistogramDiagram", chart::TYPEID_HISTO },
{ u"com.sun.star.chart2.HistogramChartType", chart::TYPEID_HISTO },
{ u"com.sun.star.chart.FunnelDiagram", chart::TYPEID_FUNNEL },
{ u"com.sun.star.chart2.FunnelChartType", chart::TYPEID_FUNNEL },
{ u"com.sun.star.chart2.ParetoLineDiagram", chart::TYPEID_PARETOLINE },
@@ -1658,7 +1789,8 @@ void ChartExport::exportData_chartex( [[maybe_unused]] const Reference< css::cha
// 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) {
if (GetDocumentType() == DOCUMENT_XLSX && eChartType != chart::TYPEID_HISTO)
{
// Just hard-coding this for now
sal_Int32 nSuffixVal = nSeriesIndex;
@@ -1707,6 +1839,7 @@ void ChartExport::exportData_chartex( [[maybe_unused]] const Reference< css::cha
{
case chart::TYPEID_BOXWHISKER:
case chart::TYPEID_CLUSTEREDCOLUMN:
case chart::TYPEID_HISTO:
case chart::TYPEID_PARETOLINE:
case chart::TYPEID_SUNBURST:
case chart::TYPEID_TREEMAP:
@@ -2692,6 +2825,7 @@ void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >&
break;
}
case chart::TYPEID_CLUSTEREDCOLUMN:
case chart::TYPEID_HISTO:
{
exportChartex( xChartType, "clusteredColumn" );
break;
@@ -3996,9 +4130,12 @@ void ChartExport::exportSeries_chartex( const Reference<chart2::XChartType>& xCh
exportShapeProps( xOldPropSet, XML_cx );
}
DataLabelsRange aDLblsRange;
// export data labels
exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange, true);
if (aChartType != "com.sun.star.chart2.HistogramChartType")
{
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
@@ -4041,8 +4178,27 @@ void ChartExport::exportSeries_chartex( const Reference<chart2::XChartType>& xCh
bool bHasGeography = false;
xSeriesProp->getPropertyValue(u"HasGeography"_ustr) >>= bHasGeography;
sal_Int32 nFrequencyType = 0;
double fBinWidth = 0.0;
sal_Int32 nBinCount = 0;
bool bHasBinWidth = false;
bool bHasBinCount = false;
if (aChartType == "com.sun.star.chart2.HistogramChartType")
{
Reference<beans::XPropertySet> xChartTypePropSet(xChartType, uno::UNO_QUERY);
if (xChartTypePropSet.is())
{
xChartTypePropSet->getPropertyValue(u"FrequencyType"_ustr) >>= nFrequencyType;
bHasBinWidth =
(xChartTypePropSet->getPropertyValue(u"BinWidth"_ustr) >>= fBinWidth);
bHasBinCount =
(xChartTypePropSet->getPropertyValue(u"BinCount"_ustr) >>= nBinCount);
}
}
bool bHasAny = bHasParentLL || bHasRegionLL || bHasVisibility
|| bHasQM || bHasSubtotals || bHasIC || bHasGeography;
|| bHasQM || bHasSubtotals || bHasIC || bHasGeography
|| aChartType == "com.sun.star.chart2.HistogramChartType";
if (bHasAny)
{
pFS->startElement(FSNS(XML_cx, XML_layoutPr));
@@ -4078,8 +4234,54 @@ void ChartExport::exportSeries_chartex( const Reference<chart2::XChartType>& xCh
if (bHasGeography)
oox::drawingml::chart::exportGeography(xSeriesProp, pFS);
if (bHasIC)
if (aChartType == "com.sun.star.chart2.HistogramChartType")
{
// histogram <cx:binning> wraps bin-size/bin-count children
const char* pIntervalClosed = nullptr;
if (bHasIC)
{
sal_uInt32 nIntervalClosedChar = '\0';
aIntervalClosed >>= nIntervalClosedChar;
if (nIntervalClosedChar == 'l')
pIntervalClosed = "l";
else if (nIntervalClosedChar == 'r')
pIntervalClosed = "r";
}
if (pIntervalClosed)
pFS->startElement(FSNS(XML_cx, XML_binning),
XML_intervalClosed, pIntervalClosed);
else
pFS->startElement(FSNS(XML_cx, XML_binning));
if (nFrequencyType == 1 && bHasBinWidth)
{
pFS->singleElement(FSNS(XML_cx, XML_binSize), XML_val,
OString::number(fBinWidth));
}
else if (nFrequencyType == 2 && bHasBinCount)
{
pFS->singleElement(FSNS(XML_cx, XML_binCount), XML_val,
OString::number(nBinCount));
}
else if (nFrequencyType == 0)
{
double fAutoBinWidth = 0.0;
sal_Int32 nAutoBinCount = 0;
if (lclGetAutoHistogramBinning(rSeries, fAutoBinWidth, nAutoBinCount)
&& fAutoBinWidth > 0.0)
{
pFS->singleElement(FSNS(XML_cx, XML_binSize), XML_val,
OString::number(fAutoBinWidth));
}
}
pFS->endElement(FSNS(XML_cx, XML_binning));
}
else if (bHasIC)
{
// non-histogram (BoxWhisker): self-closing <cx:binning intervalClosed="…"/>
sal_uInt32 nIntervalClosedChar = '\0';
aIntervalClosed >>= nIntervalClosedChar;
@@ -6448,6 +6650,7 @@ bool ChartExport::isChartexNotChartNS(NamespaceAbbrev *peNS) const
break;
case chart::TYPEID_BOXWHISKER:
case chart::TYPEID_CLUSTEREDCOLUMN:
case chart::TYPEID_HISTO:
case chart::TYPEID_PARETOLINE:
case chart::TYPEID_SUNBURST:
case chart::TYPEID_TREEMAP: