diff options
author | Dennis Francis <dennis.francis@collabora.com> | 2021-08-25 20:51:56 +0530 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2021-09-01 09:15:03 +0200 |
commit | 9d8324524bdcd1244cd6e9d93b063b981d47c9be (patch) | |
tree | 1bbd6560ef3a292b0685d4b768ea1a07c17259b8 | |
parent | b365358075d484e034eb9cd6bceeea9d639835b6 (diff) |
tdf#143942: oox: import/export labels from <c15:datalabelsRange>
When <c15:showDataLabelsRange> boolean flag is present, the imported
label texts are added as the first text field in oox data label model.
The cell-range associated is also preserved. The export part preserves
the how labels were store originally in <c15:datalabelsRange>.
However in order to make the custom labels reflect the contents of the
cells in the associated cell-range, more work needs to be done. For this
the labels present in <c15:datalabelsRange> needs to be made available
as a data-sequence with a new "role" like "point-labels" in
XInternalDataProvider implementation and and make the label renderer
read this data source rather than consulting the custom label fields
property which is static after import.
Change-Id: Ibc7045fa5ea209d463680c96efb49a06662d2500
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/121313
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
24 files changed, 588 insertions, 59 deletions
diff --git a/chart2/qa/extras/chart2export2.cxx b/chart2/qa/extras/chart2export2.cxx index adde208d620c..3a1afef91bd6 100644 --- a/chart2/qa/extras/chart2export2.cxx +++ b/chart2/qa/extras/chart2export2.cxx @@ -98,6 +98,7 @@ public: void testCustomShapeText(); void testuserShapesXLSX(); void testNameRangeXLSX(); + void testTdf143942(); CPPUNIT_TEST_SUITE(Chart2ExportTest2); CPPUNIT_TEST(testSetSeriesToSecondaryAxisXLSX); @@ -159,6 +160,7 @@ public: CPPUNIT_TEST(testCustomShapeText); CPPUNIT_TEST(testuserShapesXLSX); CPPUNIT_TEST(testNameRangeXLSX); + CPPUNIT_TEST(testTdf143942); CPPUNIT_TEST_SUITE_END(); }; @@ -1334,22 +1336,60 @@ void Chart2ExportTest2::testTdf138204() "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[1]/c:dLbls/c:dLbl/c:tx/c:rich/a:p/a:fld", "type", "CELLRANGE"); + assertXPath( + pXmlDoc, + "/c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[2]/c:dLbls/c:dLbl/c:tx/c:rich/a:p/a:fld", + "type", "CELLRANGE"); + Reference<chart2::XChartDocument> xChartDoc = getChartDocFromSheet(0, mxComponent); CPPUNIT_ASSERT(xChartDoc.is()); - uno::Reference<chart2::XDataSeries> xDataSeries(getDataSeriesFromDoc(xChartDoc, 1)); - CPPUNIT_ASSERT(xDataSeries.is()); + struct CustomLabelsTestData + { + sal_Int32 nSeriesIdx; + sal_Int32 nNumFields; + // First field attributes. + chart2::DataPointCustomLabelFieldType eFieldType; + OUString aCellRange; + OUString aString; + }; + + const CustomLabelsTestData aTestEntries[2] = { + { + // series id of c:ser[1] is 0. + 0, // nSeriesIdx + 1, // nNumFields + chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE, + "Munka1!$F$9", // aCellRange + "67,5%", // aString + }, + { + + // series id of c:ser[2] is 1. + 1, // nSeriesIdx + 1, // nNumFields + chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE, + "Munka1!$G$9", // aCellRange + "32,3%", // aString + }, + }; + + for (const auto& aTestEntry : aTestEntries) + { + uno::Reference<chart2::XDataSeries> xDataSeries( + getDataSeriesFromDoc(xChartDoc, aTestEntry.nSeriesIdx)); + CPPUNIT_ASSERT(xDataSeries.is()); - uno::Reference<beans::XPropertySet> xPropertySet; - uno::Sequence<uno::Reference<chart2::XDataPointCustomLabelField>> aFields; - xPropertySet.set(xDataSeries->getDataPointByIndex(0), uno::UNO_SET_THROW); - xPropertySet->getPropertyValue("CustomLabelFields") >>= aFields; - CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aFields.getLength()); + uno::Reference<beans::XPropertySet> xPropertySet; + uno::Sequence<uno::Reference<chart2::XDataPointCustomLabelField>> aFields; + xPropertySet.set(xDataSeries->getDataPointByIndex(0), uno::UNO_SET_THROW); + xPropertySet->getPropertyValue("CustomLabelFields") >>= aFields; + CPPUNIT_ASSERT_EQUAL(aTestEntry.nNumFields, aFields.getLength()); - CPPUNIT_ASSERT_EQUAL( - chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE, - aFields[0]->getFieldType()); - //CPPUNIT_ASSERT_EQUAL(OUString("67.5%"), aFields[0]->getString()); TODO: Not implemented yet + CPPUNIT_ASSERT_EQUAL(aTestEntry.eFieldType, aFields[0]->getFieldType()); + CPPUNIT_ASSERT_EQUAL(aTestEntry.aCellRange, aFields[0]->getCellRange()); + CPPUNIT_ASSERT_EQUAL(aTestEntry.aString, aFields[0]->getString()); + } } void Chart2ExportTest2::testTdf138181() @@ -1437,6 +1477,69 @@ void Chart2ExportTest2::testNameRangeXLSX() "[0]!series1"); } +void Chart2ExportTest2::testTdf143942() +{ + load(u"/chart2/qa/extras/data/xlsx/", "tdf143942.xlsx"); + xmlDocUniquePtr pXmlDoc = parseExport("xl/charts/chart", "Calc Office Open XML"); + CPPUNIT_ASSERT(pXmlDoc); + + constexpr size_t nLabels = 4; + OUString aCellRange = "Sheet1!$A$2:$A$5"; + OUString aLabels[nLabels] = { + "Test1", + "Test2", + "Tes3", + "Test4", + }; + + assertXPath(pXmlDoc, "/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:extLst/c:ext", + "uri", "{02D57815-91ED-43cb-92C2-25804820EDAC}"); + assertXPath(pXmlDoc, + "/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:extLst/c:ext/" + "c15:datalabelsRange/c15:dlblRangeCache/c:ptCount", + "val", "4"); + assertXPathContent(pXmlDoc, + "/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:extLst/c:ext/" + "c15:datalabelsRange/c15:f", + aCellRange); + for (size_t i = 0; i < nLabels; ++i) + { + assertXPath(pXmlDoc, + "/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:dLbls/c:dLbl[" + + OString::number(i + 1) + "]/c:tx/c:rich/a:p/a:fld", + "type", "CELLRANGE"); + assertXPath(pXmlDoc, + "/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:dLbls/c:dLbl[" + + OString::number(i + 1) + "]/c:extLst/c:ext/c15:showDataLabelsRange", + "val", "1"); + // Check if the actual label is stored under c15:datalabelsRange + assertXPathContent(pXmlDoc, + "/c:chartSpace/c:chart/c:plotArea/c:scatterChart/c:ser[1]/c:extLst/" + "c:ext/c15:datalabelsRange/c15:dlblRangeCache/c:pt[" + + OString::number(i + 1) + "]/c:v", + aLabels[i]); + } + + Reference<chart2::XChartDocument> xChartDoc = getChartDocFromSheet(0, mxComponent); + CPPUNIT_ASSERT(xChartDoc.is()); + uno::Reference<chart2::XDataSeries> xDataSeries(getDataSeriesFromDoc(xChartDoc, 0)); + CPPUNIT_ASSERT(xDataSeries.is()); + + uno::Reference<beans::XPropertySet> xPropertySet; + uno::Sequence<uno::Reference<chart2::XDataPointCustomLabelField>> aFields; + for (size_t i = 0; i < nLabels; ++i) + { + xPropertySet.set(xDataSeries->getDataPointByIndex(i), uno::UNO_SET_THROW); + xPropertySet->getPropertyValue("CustomLabelFields") >>= aFields; + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aFields.getLength()); + CPPUNIT_ASSERT_EQUAL( + chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE, + aFields[0]->getFieldType()); + CPPUNIT_ASSERT_EQUAL(aCellRange, aFields[0]->getCellRange()); + CPPUNIT_ASSERT_EQUAL(aLabels[i], aFields[0]->getString()); + } +} + CPPUNIT_TEST_SUITE_REGISTRATION(Chart2ExportTest2); CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/chart2/qa/extras/data/xlsx/tdf143942.xlsx b/chart2/qa/extras/data/xlsx/tdf143942.xlsx Binary files differnew file mode 100644 index 000000000000..33ff6696b7e6 --- /dev/null +++ b/chart2/qa/extras/data/xlsx/tdf143942.xlsx diff --git a/chart2/source/model/main/FormattedString.cxx b/chart2/source/model/main/FormattedString.cxx index 5433f3683d78..782729294789 100644 --- a/chart2/source/model/main/FormattedString.cxx +++ b/chart2/source/model/main/FormattedString.cxx @@ -98,6 +98,7 @@ namespace chart FormattedString::FormattedString() : ::property::OPropertySet( m_aMutex ), m_aType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT), + m_bDataLabelsRange(false), m_xModifyEventForwarder( ModifyListenerHelper::createModifyEventForwarder()) {} @@ -107,6 +108,7 @@ FormattedString::FormattedString( const FormattedString & rOther ) : m_aString( rOther.m_aString ), m_aType(rOther.m_aType), m_aGuid(rOther.m_aGuid), + m_bDataLabelsRange(rOther.m_bDataLabelsRange), m_xModifyEventForwarder( ModifyListenerHelper::createModifyEventForwarder()) {} @@ -172,6 +174,38 @@ void SAL_CALL FormattedString::setGuid( const OUString& guid ) } +sal_Bool SAL_CALL FormattedString::getDataLabelsRange() +{ + MutexGuard aGuard( m_aMutex); + return m_bDataLabelsRange; +} + +void SAL_CALL FormattedString::setDataLabelsRange( sal_Bool dataLabelsRange ) +{ + { + MutexGuard aGuard( m_aMutex); + m_bDataLabelsRange = dataLabelsRange; + } + //don't keep the mutex locked while calling out + fireModifyEvent(); +} + +OUString SAL_CALL FormattedString::getCellRange() +{ + MutexGuard aGuard( m_aMutex); + return m_aCellRange; +} + +void SAL_CALL FormattedString::setCellRange( const OUString& cellRange ) +{ + { + MutexGuard aGuard( m_aMutex); + m_aCellRange = cellRange; + } + //don't keep the mutex locked while calling out + fireModifyEvent(); +} + // ____ XModifyBroadcaster ____ void SAL_CALL FormattedString::addModifyListener( const uno::Reference< util::XModifyListener >& aListener ) { diff --git a/chart2/source/model/main/FormattedString.hxx b/chart2/source/model/main/FormattedString.hxx index a7f9e36bb134..bd7415fb921a 100644 --- a/chart2/source/model/main/FormattedString.hxx +++ b/chart2/source/model/main/FormattedString.hxx @@ -87,6 +87,10 @@ private: setFieldType( const css::chart2::DataPointCustomLabelFieldType FieldType ) override; virtual OUString SAL_CALL getGuid() override; void SAL_CALL setGuid( const OUString& guid ) override; + virtual sal_Bool SAL_CALL getDataLabelsRange() override; + virtual void SAL_CALL setDataLabelsRange( sal_Bool dataLabelsRange ) override; + virtual OUString SAL_CALL getCellRange() override; + virtual void SAL_CALL setCellRange( const OUString& cellRange ) override; // ____ OPropertySet ____ virtual css::uno::Any GetDefaultValue( sal_Int32 nHandle ) const override; @@ -127,6 +131,8 @@ private: // ____ XDataPointCustomLabelField ____ css::chart2::DataPointCustomLabelFieldType m_aType; OUString m_aGuid; + OUString m_aCellRange; + bool m_bDataLabelsRange; css::uno::Reference< css::util::XModifyListener > m_xModifyEventForwarder; }; diff --git a/chart2/source/view/charttypes/VSeriesPlotter.cxx b/chart2/source/view/charttypes/VSeriesPlotter.cxx index b0a591d3c065..b5bfc8d871b0 100644 --- a/chart2/source/view/charttypes/VSeriesPlotter.cxx +++ b/chart2/source/view/charttypes/VSeriesPlotter.cxx @@ -540,9 +540,16 @@ uno::Reference< drawing::XShape > VSeriesPlotter::createDataLabel( const uno::Re aTextList[i] = getLabelTextForValue(rDataSeries, nPointIndex, fValue, true); break; } - case DataPointCustomLabelFieldType_CELLREF: case DataPointCustomLabelFieldType_CELLRANGE: { + if (aCustomLabels[i]->getDataLabelsRange()) + aTextList[i] = aCustomLabels[i]->getString(); + else + aTextList[i] = OUString(); + break; + } + case DataPointCustomLabelFieldType_CELLREF: + { // TODO: for now doesn't show placeholder aTextList[i] = OUString(); break; diff --git a/include/oox/export/chartexport.hxx b/include/oox/export/chartexport.hxx index 5dbf8c20ac0b..c4440ae08419 100644 --- a/include/oox/export/chartexport.hxx +++ b/include/oox/export/chartexport.hxx @@ -91,6 +91,43 @@ struct AxisIdPair{ {} }; +/** + A helper container class to collect the chart data point labels and the address + of the cell[range] from which the labels are sourced if that is the case. This + is then used to write the label texts under the extension tag <c15:datalabelsRange>. + + @since LibreOffice 7.3.0 + */ +class DataLabelsRange +{ +public: + + /// type of the internal container that stores the indexed label text. + typedef std::map<sal_Int32, OUString> LabelsRangeMap; + + /// Returns whether the container is empty or not. + bool empty() const; + /// Returns the count of labels stored. + size_t count() const; + /// Indicates whether the container has a label with index specified by nIndex. + bool hasLabel(sal_Int32 nIndex) const; + /// Returns the address of the cell[range] from which label contents are sourced. + OUString getRange() const; + + /// Sets the address of the cell[range] from which label contents are sourced. + void setRange(const OUString& rRange); + /// Adds a new indexed label text. + void setLabel(sal_Int32 nIndex, const OUString& rText); + + LabelsRangeMap::const_iterator begin() const; + LabelsRangeMap::const_iterator end() const; + +private: + OUString maRange; + LabelsRangeMap maLabels; +}; + + class OOX_DLLPUBLIC ChartExport final : public DrawingML { public: @@ -184,7 +221,8 @@ private: void exportDataPoints( const css::uno::Reference< css::beans::XPropertySet >& xSeriesProperties, sal_Int32 nSeriesLength, sal_Int32 eChartType ); - void exportDataLabels( const css::uno::Reference<css::chart2::XDataSeries>& xSeries, sal_Int32 nSeriesLength, sal_Int32 eChartType ); + void exportDataLabels( const css::uno::Reference<css::chart2::XDataSeries>& xSeries, sal_Int32 nSeriesLength, + sal_Int32 eChartType, DataLabelsRange& rDLblsRange ); void exportGrouping( bool isBar = false ); void exportTrendlines( const css::uno::Reference< css::chart2::XDataSeries >& xSeries ); void exportMarker( const css::uno::Reference< css::beans::XPropertySet >& xPropSet ); diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx index 4458badec53d..a7a3fc17a4f6 100644 --- a/include/xmloff/xmltoken.hxx +++ b/include/xmloff/xmltoken.hxx @@ -576,10 +576,12 @@ namespace xmloff::token { XML_DATA_BAR_ENTRY, XML_DATA_CELL_RANGE_ADDRESS, XML_DATA_LABEL, + XML_DATA_LABEL_GUID, XML_DATA_LABEL_NUMBER, XML_DATA_LABEL_SYMBOL, XML_DATA_LABEL_TEXT, XML_DATA_LABEL_SERIES, + XML_DATA_LABELS_CELL_RANGE, XML_DATA_PILOT_SOURCE, XML_DATA_PILOT_FIELD, XML_DATA_PILOT_GRAND_TOTAL, diff --git a/offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl b/offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl index a6a1b0151c94..8291e9001c05 100644 --- a/offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl +++ b/offapi/com/sun/star/chart2/XDataPointCustomLabelField.idl @@ -29,6 +29,34 @@ interface XDataPointCustomLabelField : XFormattedString2 void setGuid( [in] string guid ); + /** + Indicates whether the label field's content is sourced from a cell[range] or not. + + @since LibreOffice 7.3 + */ + boolean getDataLabelsRange(); + + /** + Sets whether the label field's content is sourced from a cell[range] or not. + + @since LibreOffice 7.3 + */ + void setDataLabelsRange( [in] boolean dataLabelsRange ); + + /** + Returns the address of the cell[range] from which the content of this field is sourced. + + @since LibreOffice 7.3 + */ + string getCellRange(); + + /** + Sets the address of the cell[range] from which the content of this field is sourced. + + @since LibreOffice 7.3 + */ + void setCellRange( [in] string cellRange ); + }; diff --git a/oox/inc/drawingml/chart/datasourcecontext.hxx b/oox/inc/drawingml/chart/datasourcecontext.hxx index 8ce8469cc3cb..ec64c4768180 100644 --- a/oox/inc/drawingml/chart/datasourcecontext.hxx +++ b/oox/inc/drawingml/chart/datasourcecontext.hxx @@ -69,6 +69,7 @@ public: private: sal_Int32 mnPtIndex; /// Current data point index. + bool mbReadC15; /// Allow reading extension tags data under c15 namespace. }; diff --git a/oox/inc/drawingml/chart/seriesmodel.hxx b/oox/inc/drawingml/chart/seriesmodel.hxx index c4fb557d282d..460293a5feee 100644 --- a/oox/inc/drawingml/chart/seriesmodel.hxx +++ b/oox/inc/drawingml/chart/seriesmodel.hxx @@ -41,12 +41,18 @@ struct DataLabelModelBase OptValue< bool > mobShowPercent; /// True = show percentual value in pie/doughnut charts. OptValue< bool > mobShowSerName; /// True = show series name. OptValue< bool > mobShowVal; /// True = show data point value. + + /// True = the value from the <c15:datalabelsRange> corresponding to the + /// index of this label is used as the label text. + OptValue< bool > mobShowDataLabelsRange; bool mbDeleted; /// True = data label(s) deleted. explicit DataLabelModelBase(bool bMSO2007Doc); ~DataLabelModelBase(); }; +struct DataLabelsModel; + struct DataLabelModel : public DataLabelModelBase { typedef ModelRef< LayoutModel > LayoutRef; @@ -54,9 +60,10 @@ struct DataLabelModel : public DataLabelModelBase LayoutRef mxLayout; /// Layout/position of the data point label frame. TextRef mxText; /// Manual or linked text for this data point label. + const DataLabelsModel& mrParent; /// Reference to the labels container. sal_Int32 mnIndex; /// Data point index for this data label. - explicit DataLabelModel(bool bMSO2007Doc); + explicit DataLabelModel(const DataLabelsModel& rParent, bool bMSO2007Doc); ~DataLabelModel(); }; @@ -67,6 +74,9 @@ struct DataLabelsModel : public DataLabelModelBase DataLabelVector maPointLabels; /// Settings for individual data point labels. ShapeRef mxLeaderLines; /// Formatting of connector lines between data points and labels. + + /// Labels source (owned by SeriesModel's DataSourceMap) + const DataSourceModel* mpLabelsSource; bool mbShowLeaderLines; /// True = show connector lines between data points and labels. explicit DataLabelsModel(bool bMSO2007Doc); @@ -171,7 +181,8 @@ struct SeriesModel { CATEGORIES, /// Data point categories. VALUES, /// Data point values. - POINTS /// Data point size (e.g. bubble size in bubble charts). + POINTS, /// Data point size (e.g. bubble size in bubble charts). + DATALABELS, /// Data point labels. }; typedef ModelMap< SourceType, DataSourceModel > DataSourceMap; diff --git a/oox/source/drawingml/chart/datasourcecontext.cxx b/oox/source/drawingml/chart/datasourcecontext.cxx index eac776e107d9..1b64089d4281 100644 --- a/oox/source/drawingml/chart/datasourcecontext.cxx +++ b/oox/source/drawingml/chart/datasourcecontext.cxx @@ -166,6 +166,7 @@ SvNumberFormatter* DoubleSequenceContext::getNumberFormatter() StringSequenceContext::StringSequenceContext( ContextHandler2Helper& rParent, DataSequenceModel& rModel ) : DataSequenceContextBase( rParent, rModel ) , mnPtIndex(-1) + , mbReadC15(false) { } @@ -186,6 +187,16 @@ ContextHandlerRef StringSequenceContext::onCreateContext( sal_Int32 nElement, co } break; + case C15_TOKEN( datalabelsRange ): + mbReadC15 = true; + switch( nElement ) + { + case C15_TOKEN( f ): + case C15_TOKEN( dlblRangeCache ): + return this; + } + break; + case C_TOKEN( strRef ): switch( nElement ) { @@ -197,6 +208,10 @@ ContextHandlerRef StringSequenceContext::onCreateContext( sal_Int32 nElement, co case C_TOKEN( strCache ): case C_TOKEN( strLit ): + case C15_TOKEN( dlblRangeCache ): + if (nElement == C15_TOKEN( dlblRangeCache ) && !mbReadC15) + break; + switch( nElement ) { case C_TOKEN( ptCount ): @@ -248,6 +263,10 @@ void StringSequenceContext::onCharacters( const OUString& rChars ) case C_TOKEN( f ): mrModel.maFormula = rChars; break; + case C15_TOKEN( f ): + if (mbReadC15) + mrModel.maFormula = rChars; + break; case C_TOKEN( v ): if( mnPtIndex >= 0 ) mrModel.maData[ (mrModel.mnLevelCount-1) * mrModel.mnPointCount + mnPtIndex ] <<= rChars; @@ -270,11 +289,13 @@ ContextHandlerRef DataSourceContext::onCreateContext( sal_Int32 nElement, const { case C_TOKEN( cat ): case C_TOKEN( xVal ): + case C_TOKEN( ext ): switch( nElement ) { case C_TOKEN( multiLvlStrRef ): case C_TOKEN( strLit ): case C_TOKEN( strRef ): + case C15_TOKEN( datalabelsRange ): OSL_ENSURE( !mrModel.mxDataSeq, "DataSourceContext::onCreateContext - multiple data sequences" ); return new StringSequenceContext( *this, mrModel.mxDataSeq.create() ); diff --git a/oox/source/drawingml/chart/seriescontext.cxx b/oox/source/drawingml/chart/seriescontext.cxx index 3f92818c5544..5afc32c1497d 100644 --- a/oox/source/drawingml/chart/seriescontext.cxx +++ b/oox/source/drawingml/chart/seriescontext.cxx @@ -100,15 +100,37 @@ DataLabelContext::~DataLabelContext() ContextHandlerRef DataLabelContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) { - if( isRootElement() ) switch( nElement ) + if( isRootElement() ) { - case C_TOKEN( idx ): - mrModel.mnIndex = rAttribs.getInteger( XML_val, -1 ); - return nullptr; - case C_TOKEN( layout ): - return new LayoutContext( *this, mrModel.mxLayout.create() ); - case C_TOKEN( tx ): - return new TextContext( *this, mrModel.mxText.create() ); + switch( nElement ) + { + case C_TOKEN( idx ): + mrModel.mnIndex = rAttribs.getInteger( XML_val, -1 ); + return nullptr; + case C_TOKEN( layout ): + return new LayoutContext( *this, mrModel.mxLayout.create() ); + case C_TOKEN( tx ): + return new TextContext( *this, mrModel.mxText.create() ); + case C_TOKEN( extLst ): + return this; + } + } + else + { + switch( getCurrentElement() ) + { + case C_TOKEN( extLst ): + if ( nElement == C_TOKEN( ext ) ) + return this; + break; + case C_TOKEN( ext ): + if ( nElement == C15_TOKEN( showDataLabelsRange ) ) + { + mrModel.mobShowDataLabelsRange = rAttribs.getBool( XML_val ); + return nullptr; + } + break; + } } bool bMSO2007 = getFilter().isMSO2007Document(); return lclDataLabelSharedCreateContext( *this, nElement, rAttribs, mrModel, bMSO2007 ); @@ -135,7 +157,7 @@ ContextHandlerRef DataLabelsContext::onCreateContext( sal_Int32 nElement, const if( isRootElement() ) switch( nElement ) { case C_TOKEN( dLbl ): - return new DataLabelContext( *this, mrModel.maPointLabels.create(bMSO2007Doc) ); + return new DataLabelContext( *this, mrModel.maPointLabels.create(mrModel, bMSO2007Doc) ); case C_TOKEN( leaderLines ): return new ShapePrWrapperContext( *this, mrModel.mxLeaderLines.create() ); case C_TOKEN( showLeaderLines ): @@ -390,6 +412,8 @@ ContextHandlerRef SeriesContextBase::onCreateContext( sal_Int32 nElement, const return new ShapePropertiesContext( *this, mrModel.mxShapeProp.create() ); case C_TOKEN( tx ): return new TextContext( *this, mrModel.mxText.create() ); + case C_TOKEN( extLst ): + return this; } break; @@ -406,6 +430,19 @@ ContextHandlerRef SeriesContextBase::onCreateContext( sal_Int32 nElement, const return nullptr; } break; + + case C_TOKEN( extLst ): + switch( nElement ) + { + case C_TOKEN( ext ): + if (mrModel.maSources.has( SeriesModel::DATALABELS )) + break; + + DataSourceModel& rLabelsSource = mrModel.maSources.create( SeriesModel::DATALABELS ); + if (mrModel.mxLabels.is()) + mrModel.mxLabels->mpLabelsSource = &rLabelsSource; + return new DataSourceContext( *this, rLabelsSource ); + } } return nullptr; } diff --git a/oox/source/drawingml/chart/seriesconverter.cxx b/oox/source/drawingml/chart/seriesconverter.cxx index 2c3341a30e1a..3f03e1e5c2a6 100644 --- a/oox/source/drawingml/chart/seriesconverter.cxx +++ b/oox/source/drawingml/chart/seriesconverter.cxx @@ -319,9 +319,25 @@ void DataLabelConverter::convertFromModel( const Reference< XDataSeries >& rxDat if( nParagraphs > 1 ) nSequenceSize += nParagraphs - 1; + OptValue< OUString > oaLabelText; + OptValue< OUString > oaCellRange; + if (mrModel.mobShowDataLabelsRange.get(false)) + { + const DataSourceModel* pLabelSource = mrModel.mrParent.mpLabelsSource; + if (pLabelSource && pLabelSource->mxDataSeq.is()) + { + oaCellRange = pLabelSource->mxDataSeq->maFormula; + const auto& rLabelMap = pLabelSource->mxDataSeq->maData; + const auto& rKV = rLabelMap.find(mrModel.mnIndex); + if (rKV != rLabelMap.end()) + rKV->second >>= oaLabelText.use(); + } + } + aSequence.realloc( nSequenceSize ); int nPos = 0; + for( auto& pParagraph : rParagraphs ) { for( auto& pRun : pParagraph->getRuns() ) @@ -336,8 +352,18 @@ void DataLabelConverter::convertFromModel( const Reference< XDataSeries >& rxDat TextField* pField = nullptr; if( ( pField = dynamic_cast< TextField* >( pRun.get() ) ) ) { - xCustomLabel->setString( pField->getText() ); - xCustomLabel->setFieldType( lcl_ConvertFieldNameToFieldEnum( pField->getType() ) ); + DataPointCustomLabelFieldType eType = lcl_ConvertFieldNameToFieldEnum( pField->getType() ); + + if (eType == DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE && oaCellRange.has()) + { + xCustomLabel->setCellRange( oaCellRange.get() ); + xCustomLabel->setString( oaLabelText.get() ); + xCustomLabel->setDataLabelsRange( true ); + } + else + xCustomLabel->setString( pField->getText() ); + + xCustomLabel->setFieldType( eType ); xCustomLabel->setGuid( pField->getUuid() ); } else if( pRun ) diff --git a/oox/source/drawingml/chart/seriesmodel.cxx b/oox/source/drawingml/chart/seriesmodel.cxx index 563e0e48e3bf..4ad9b2593af4 100644 --- a/oox/source/drawingml/chart/seriesmodel.cxx +++ b/oox/source/drawingml/chart/seriesmodel.cxx @@ -31,8 +31,9 @@ DataLabelModelBase::~DataLabelModelBase() { } -DataLabelModel::DataLabelModel(bool bMSO2007Doc) : +DataLabelModel::DataLabelModel(const DataLabelsModel& rParent, bool bMSO2007Doc) : DataLabelModelBase(bMSO2007Doc), + mrParent( rParent ), mnIndex( -1 ) { } @@ -43,6 +44,7 @@ DataLabelModel::~DataLabelModel() DataLabelsModel::DataLabelsModel(bool bMSO2007Doc) : DataLabelModelBase(bMSO2007Doc), + mpLabelsSource( nullptr ), mbShowLeaderLines( !bMSO2007Doc ) { } diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx index dc122a4e97da..2e13b40288bb 100644 --- a/oox/source/export/chartexport.cxx +++ b/oox/source/export/chartexport.cxx @@ -485,6 +485,46 @@ static sal_Int32 lcl_getAlphaFromTransparenceGradient(const awt::Gradient& rGrad return (255 - nRed) * oox::drawingml::MAX_PERCENT / 255; } +bool DataLabelsRange::empty() const +{ + return maLabels.empty(); +} + +size_t DataLabelsRange::count() const +{ + return maLabels.size(); +} + +bool DataLabelsRange::hasLabel(sal_Int32 nIndex) const +{ + return maLabels.find(nIndex) != maLabels.end(); +} + +OUString DataLabelsRange::getRange() const +{ + return maRange; +} + +void DataLabelsRange::setRange(const OUString& rRange) +{ + maRange = rRange; +} + +void DataLabelsRange::setLabel(sal_Int32 nIndex, const OUString& rText) +{ + maLabels.emplace(nIndex, rText); +} + +DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::begin() const +{ + return maLabels.begin(); +} + +DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::end() const +{ + return maLabels.end(); +} + ChartExport::ChartExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, Reference< frame::XModel > const & xModel, XmlFilterBase* pFB, DocumentType eDocumentType ) : DrawingML( std::move(pFS), pFB, eDocumentType ) , mnXmlNamespace( nXmlNamespace ) @@ -2183,6 +2223,43 @@ void ChartExport::exportDoughnutChart( const Reference< chart2::XChartType >& xC pFS->endElement( FSNS( XML_c, XML_doughnutChart ) ); } +namespace { + +void writeDataLabelsRange(FSHelperPtr& pFS, XmlFilterBase* pFB, DataLabelsRange& rDLblsRange) +{ + if (rDLblsRange.empty()) + return; + + pFS->startElement(FSNS(XML_c, XML_extLst)); + pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{02D57815-91ED-43cb-92C2-25804820EDAC}", FSNS(XML_xmlns, XML_c15), pFB->getNamespaceURL(OOX_NS(c15))); + pFS->startElement(FSNS(XML_c15, XML_datalabelsRange)); + + // Write cell range. + pFS->startElement(FSNS(XML_c15, XML_f)); + pFS->writeEscaped(rDLblsRange.getRange()); + pFS->endElement(FSNS(XML_c15, XML_f)); + + // Write all labels. + pFS->startElement(FSNS(XML_c15, XML_dlblRangeCache)); + pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(rDLblsRange.count())); + for (const auto& rLabelKV: rDLblsRange) + { + pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(rLabelKV.first)); + pFS->startElement(FSNS(XML_c, XML_v)); + pFS->writeEscaped(rLabelKV.second); + pFS->endElement(FSNS( XML_c, XML_v )); + pFS->endElement(FSNS(XML_c, XML_pt)); + } + + pFS->endElement(FSNS(XML_c15, XML_dlblRangeCache)); + + pFS->endElement(FSNS(XML_c15, XML_datalabelsRange)); + pFS->endElement(FSNS(XML_c, XML_ext)); + pFS->endElement(FSNS(XML_c, XML_extLst)); +} + +} + void ChartExport::exportLineChart( const Reference< chart2::XChartType >& xChartType ) { FSHelperPtr pFS = GetFS(); @@ -2570,8 +2647,9 @@ void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType, // export data points exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType ); + DataLabelsRange aDLblsRange; // export data labels - exportDataLabels(rSeries, nSeriesLength, eChartType); + exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange); exportTrendlines( rSeries ); @@ -2644,6 +2722,9 @@ void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType, if( eChartType == chart::TYPEID_BUBBLE ) pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0"); + if (!aDLblsRange.empty()) + writeDataLabelsRange(pFS, GetFB(), aDLblsRange); + pFS->endElement( FSNS( XML_c, XML_ser ) ); } } @@ -3560,7 +3641,8 @@ void writeRunProperties( ChartExport* pChartExport, Reference<XPropertySet> cons } void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport, - const Sequence<Reference<chart2::XDataPointCustomLabelField>>& rCustomLabelFields ) + const Sequence<Reference<chart2::XDataPointCustomLabelField>>& rCustomLabelFields, + sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange ) { pFS->startElement(FSNS(XML_c, XML_tx)); pFS->startElement(FSNS(XML_c, XML_rich)); @@ -3569,6 +3651,7 @@ void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport, pFS->singleElement(FSNS(XML_a, XML_bodyPr)); OUString sFieldType; + OUString sContent; pFS->startElement(FSNS(XML_a, XML_p)); for (auto& rField : rCustomLabelFields) @@ -3576,8 +3659,25 @@ void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport, Reference<XPropertySet> xPropertySet(rField, UNO_QUERY); chart2::DataPointCustomLabelFieldType aType = rField->getFieldType(); sFieldType.clear(); + sContent.clear(); bool bNewParagraph = false; + if (aType == chart2::DataPointCustomLabelFieldType_CELLRANGE && + rField->getDataLabelsRange()) + { + if (rDLblsRange.getRange().isEmpty()) + rDLblsRange.setRange(rField->getCellRange()); + + if (!rDLblsRange.hasLabel(nLabelIndex)) + rDLblsRange.setLabel(nLabelIndex, rField->getString()); + + sContent = "[CELLRANGE]"; + } + else + { + sContent = rField->getString(); + } + if (aType == chart2::DataPointCustomLabelFieldType_NEWLINE) bNewParagraph = true; else if (aType != chart2::DataPointCustomLabelFieldType_TEXT) @@ -3597,7 +3697,7 @@ void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport, writeRunProperties(pChartExport, xPropertySet); pFS->startElement(FSNS(XML_a, XML_t)); - pFS->writeEscaped(rField->getString()); + pFS->writeEscaped(sContent); pFS->endElement(FSNS(XML_a, XML_t)); pFS->endElement(FSNS(XML_a, XML_r)); @@ -3610,7 +3710,7 @@ void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport, writeRunProperties(pChartExport, xPropertySet); pFS->startElement(FSNS(XML_a, XML_t)); - pFS->writeEscaped(rField->getString()); + pFS->writeEscaped(sContent); pFS->endElement(FSNS(XML_a, XML_t)); pFS->endElement(FSNS(XML_a, XML_fld)); @@ -3623,7 +3723,8 @@ void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport, } void writeLabelProperties( const FSHelperPtr& pFS, ChartExport* pChartExport, - const uno::Reference<beans::XPropertySet>& xPropSet, const LabelPlacementParam& rLabelParam ) + const uno::Reference<beans::XPropertySet>& xPropSet, const LabelPlacementParam& rLabelParam, + sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange ) { if (!xPropSet.is()) return; @@ -3678,7 +3779,7 @@ void writeLabelProperties( const FSHelperPtr& pFS, ChartExport* pChartExport, pChartExport->exportTextProps(xPropSet); if (aCustomLabelFields.hasElements()) - writeCustomLabel(pFS, pChartExport, aCustomLabelFields); + writeCustomLabel(pFS, pChartExport, aCustomLabelFields, nLabelIndex, rDLblsRange); if (rLabelParam.mbExport) { @@ -3707,12 +3808,26 @@ void writeLabelProperties( const FSHelperPtr& pFS, ChartExport* pChartExport, pFS->writeEscaped( nLabelSeparator ); pFS->endElement( FSNS( XML_c, XML_separator ) ); } + + if (rDLblsRange.hasLabel(nLabelIndex)) + { + pFS->startElement(FSNS(XML_c, XML_extLst)); + pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, + "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15), + pChartExport->GetFB()->getNamespaceURL(OOX_NS(c15))); + + pFS->singleElement(FSNS(XML_c15, XML_showDataLabelsRange), XML_val, "1"); + + pFS->endElement(FSNS(XML_c, XML_ext)); + pFS->endElement(FSNS(XML_c, XML_extLst)); + } } } void ChartExport::exportDataLabels( - const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 nSeriesLength, sal_Int32 eChartType ) + const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 nSeriesLength, sal_Int32 eChartType, + DataLabelsRange& rDLblsRange) { if (!xSeries.is() || nSeriesLength <= 0) return; @@ -3843,12 +3958,12 @@ void ChartExport::exportDataLabels( } // Individual label property that overwrites the baseline. - writeLabelProperties(pFS, this, xLabelPropSet, aParam); + writeLabelProperties(pFS, this, xLabelPropSet, aParam, nIdx, rDLblsRange); pFS->endElement(FSNS(XML_c, XML_dLbl)); } // Baseline label properties for all labels. - writeLabelProperties(pFS, this, xPropSet, aParam); + writeLabelProperties(pFS, this, xPropSet, aParam, -1, rDLblsRange); bool bShowLeaderLines = false; xPropSet->getPropertyValue("ShowCustomLeaderLines") >>= bShowLeaderLines; diff --git a/oox/source/token/tokens.txt b/oox/source/token/tokens.txt index 997b3e8b3b25..9a10000c1fc8 100644 --- a/oox/source/token/tokens.txt +++ b/oox/source/token/tokens.txt @@ -1657,6 +1657,7 @@ dataValidation dataValidations database databaseField +datalabelsRange datastoreItem date date1904 @@ -1872,6 +1873,7 @@ dkTurquoise dkUpDiag dkVert dkViolet +dlblRangeCache dllVersion dm dn @@ -4646,6 +4648,7 @@ showComments showDLblsOverMax showDataAs showDataDropDown +showDataLabelsRange showDataTips showDrill showDropDown diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng index 8c6d29611caa..a0f8711d092b 100644 --- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng +++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng @@ -2719,4 +2719,16 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1. </rng:element> </rng:define> + <!-- TODO no proposal --> + <rng:define name="chart-data-label-attlist" combine="interleave"> + <rng:optional> + <rng:attribute name="loext:data-label-guid"> + <rng:ref name="string"/> + </rng:attribute> + <rng:attribute name="loext:data-labels-cell-range"> + <rng:ref name="cellRangeAddress"/> + </rng:attribute> + </rng:optional> + </rng:define> + </rng:grammar> diff --git a/xmloff/source/chart/SchXMLExport.cxx b/xmloff/source/chart/SchXMLExport.cxx index 4d1596b04aa5..65510e036ac2 100644 --- a/xmloff/source/chart/SchXMLExport.cxx +++ b/xmloff/source/chart/SchXMLExport.cxx @@ -114,7 +114,27 @@ using ::std::vector; namespace { - using CustomLabelSeq = Sequence<Reference<chart2::XDataPointCustomLabelField>>; + /** + * Used to store a data point custom-label's fields and also the helper members that + * indicates whether this label's contents are sourced from a cell[range] and + * the address of the cell[range] with guid of the CELLRANGE field. + */ + struct CustomLabelData + { + CustomLabelData(): + mbDataLabelsRange( false ) + { + } + + /// Label fields. + Sequence<Reference<chart2::XDataPointCustomLabelField>> maFields; + /// Are label's contents sourced from a cell[range] ? + bool mbDataLabelsRange; + /// cell[range] from which label's contents are sourced. + OUString maRange; + /// GUID of the CELLRANGE field. + OUString maGuid; + }; struct SchXMLDataPointStruct { @@ -124,7 +144,7 @@ namespace // There is no internal equivalent for <chart:data-label>. It will be generated on the fly // on export. All about data label is hold in the data point. - CustomLabelSeq mCustomLabelText; // <text:p> child element in <chart:data-label> + CustomLabelData mCustomLabel; // <text:p> child element in <chart:data-label> OUString msDataLabelStyleName; // chart:style-name attribute in <chart:data-label> SchXMLDataPointStruct() : mnRepeat( 1 ) {} @@ -283,30 +303,43 @@ public: namespace { -CustomLabelSeq lcl_getCustomLabelField(SvXMLExport const& rExport, +CustomLabelData lcl_getCustomLabelField(SvXMLExport const& rExport, sal_Int32 nDataPointIndex, const uno::Reference< chart2::XDataSeries >& rSeries) { if (!rSeries.is()) - return CustomLabelSeq(); + return CustomLabelData(); // Custom data label text will be written to the <text:p> child element of a // <chart:data-label> element. That exists only since ODF 1.2. const SvtSaveOptions::ODFSaneDefaultVersion nCurrentODFVersion( rExport.getSaneDefaultVersion()); if (nCurrentODFVersion < SvtSaveOptions::ODFSVER_012) - return CustomLabelSeq(); + return CustomLabelData(); if(Reference<beans::XPropertySet> xLabels = rSeries->getDataPointByIndex(nDataPointIndex); xLabels.is()) { if(Any aAny = xLabels->getPropertyValue("CustomLabelFields"); aAny.hasValue()) { + CustomLabelData aData; Sequence<uno::Reference<chart2::XDataPointCustomLabelField>> aCustomLabels; aAny >>= aCustomLabels; - return aCustomLabels; + for (const auto& rField: std::as_const(aCustomLabels)) + { + if (rField->getFieldType() == chart2::DataPointCustomLabelFieldType_CELLRANGE) + { + if (rField->getDataLabelsRange()) + aData.mbDataLabelsRange = true; + aData.maRange = rField->getCellRange(); + aData.maGuid = rField->getGuid(); + } + } + + aData.maFields = aCustomLabels; + return aData; } } - return CustomLabelSeq(); + return CustomLabelData(); } css::chart2::RelativePosition lcl_getCustomLabelPosition( @@ -3463,7 +3496,7 @@ void SchXMLExportHelper_Impl::exportDataPoints( maAutoStyleNameQueue.pop(); } if(bExportNumFmt) - aPoint.mCustomLabelText = lcl_getCustomLabelField(mrExport, nElement, xSeries); + aPoint.mCustomLabel = lcl_getCustomLabelField(mrExport, nElement, xSeries); aPoint.mCustomLabelPos = lcl_getCustomLabelPosition(mrExport, nElement, xSeries); aDataPointVector.push_back( aPoint ); @@ -3542,7 +3575,7 @@ void SchXMLExportHelper_Impl::exportDataPoints( aPoint.maStyleName = maAutoStyleNameQueue.front(); maAutoStyleNameQueue.pop(); } - aPoint.mCustomLabelText = lcl_getCustomLabelField(mrExport, nCurrIndex, xSeries); + aPoint.mCustomLabel = lcl_getCustomLabelField(mrExport, nCurrIndex, xSeries); aPoint.mCustomLabelPos = lcl_getCustomLabelPosition(mrExport, nCurrIndex, xSeries); if (!aDataLabelPropertyStates.empty()) { @@ -3598,7 +3631,7 @@ void SchXMLExportHelper_Impl::exportDataPoints( aPoint = rPoint; if (aPoint.maStyleName == aLastPoint.maStyleName - && aLastPoint.mCustomLabelText.getLength() < 1 + && aLastPoint.mCustomLabel.maFields.getLength() < 1 && aLastPoint.mCustomLabelPos.Primary == 0.0 && aLastPoint.mCustomLabelPos.Secondary == 0.0 && aPoint.msDataLabelStyleName == aLastPoint.msDataLabelStyleName) @@ -3655,15 +3688,22 @@ void SchXMLExportHelper_Impl::exportDataPoints( void SchXMLExportHelper_Impl::exportCustomLabel(const SchXMLDataPointStruct& rPoint) { - if (rPoint.mCustomLabelText.getLength() < 1 && rPoint.msDataLabelStyleName.isEmpty()) + if (rPoint.mCustomLabel.maFields.getLength() < 1 && rPoint.msDataLabelStyleName.isEmpty()) return; // nothing to export if (!rPoint.msDataLabelStyleName.isEmpty()) mrExport.AddAttribute(XML_NAMESPACE_CHART, XML_STYLE_NAME, rPoint.msDataLabelStyleName); + + if (rPoint.mCustomLabel.mbDataLabelsRange) + { + mrExport.AddAttribute(XML_NAMESPACE_LO_EXT, XML_DATA_LABELS_CELL_RANGE, rPoint.mCustomLabel.maRange); + mrExport.AddAttribute(XML_NAMESPACE_LO_EXT, XML_DATA_LABEL_GUID, rPoint.mCustomLabel.maGuid); + } // TODO svg:x and svg:y for <chart:data-label> SvXMLElementExport aLabelElem( mrExport, XML_NAMESPACE_CHART, XML_DATA_LABEL, true, true); SvXMLElementExport aPara( mrExport, XML_NAMESPACE_TEXT, XML_P, true, false ); - for (const Reference<chart2::XDataPointCustomLabelField>& label : rPoint.mCustomLabelText) + + for (const Reference<chart2::XDataPointCustomLabelField>& label : rPoint.mCustomLabel.maFields) { // TODO add style SvXMLElementExport aSpan( mrExport, XML_NAMESPACE_TEXT, XML_SPAN, true, false); diff --git a/xmloff/source/chart/SchXMLPlotAreaContext.cxx b/xmloff/source/chart/SchXMLPlotAreaContext.cxx index 563798b7c599..a66b05d0661b 100644 --- a/xmloff/source/chart/SchXMLPlotAreaContext.cxx +++ b/xmloff/source/chart/SchXMLPlotAreaContext.cxx @@ -617,7 +617,7 @@ css::uno::Reference< css::xml::sax::XFastContextHandler > SchXMLDataLabelParaCon } SchXMLDataLabelContext::SchXMLDataLabelContext(SvXMLImport& rImport, - ::std::vector<OUString>& rLabels, + CustomLabelsInfo& rLabels, DataRowPointStyle& rDataLabelStyle) : SvXMLImportContext(rImport) , mrLabels(rLabels) @@ -630,7 +630,7 @@ css::uno::Reference< css::xml::sax::XFastContextHandler > SchXMLDataLabelContext const css::uno::Reference< css::xml::sax::XFastAttributeList >& ) { if ( nElement == XML_ELEMENT(TEXT, XML_P) ) - return new SchXMLDataLabelParaContext(GetImport(), mrLabels); + return new SchXMLDataLabelParaContext(GetImport(), mrLabels.mLabels); else XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement); return nullptr; @@ -663,6 +663,14 @@ void SchXMLDataLabelContext::startFastElement( case XML_ELEMENT(CHART, XML_STYLE_NAME): mrDataLabelStyle.msStyleName = aIter.toString(); break; + case XML_ELEMENT(LO_EXT, XML_DATA_LABEL_GUID): + mrLabels.msLabelGuid = aIter.toString(); + mrLabels.mbDataLabelsRange = true; + break; + case XML_ELEMENT(LO_EXT, XML_DATA_LABELS_CELL_RANGE): + mrLabels.msLabelsCellRange = aIter.toString(); + mrLabels.mbDataLabelsRange = true; + break; default: XMLOFF_WARN_UNKNOWN("xmloff", aIter); } @@ -737,7 +745,7 @@ void SchXMLDataPointContext::startFastElement (sal_Int32 /*Element*/, if (!mbHasLabelParagraph) { sCustomLabelField = aIter.toString(); - mDataPoint.mCustomLabels.push_back(sCustomLabelField); + mDataPoint.mCustomLabels.mLabels.push_back(sCustomLabelField); } break; case XML_ELEMENT(LO_EXT, XML_HIDE_LEGEND): @@ -778,7 +786,7 @@ void SchXMLDataPointContext::startFastElement (sal_Int32 /*Element*/, void SchXMLDataPointContext::endFastElement(sal_Int32 ) { - if(!mDataPoint.msStyleName.isEmpty() || mDataPoint.mCustomLabels.size() > 0) + if(!mDataPoint.msStyleName.isEmpty() || mDataPoint.mCustomLabels.mLabels.size() > 0) { mrStyleVector.push_back(mDataPoint); } diff --git a/xmloff/source/chart/SchXMLPlotAreaContext.hxx b/xmloff/source/chart/SchXMLPlotAreaContext.hxx index f0c2c2982a46..f80ee6f4840a 100644 --- a/xmloff/source/chart/SchXMLPlotAreaContext.hxx +++ b/xmloff/source/chart/SchXMLPlotAreaContext.hxx @@ -164,11 +164,11 @@ public: class SchXMLDataLabelContext: public SvXMLImportContext { private: - ::std::vector<OUString>& mrLabels; + CustomLabelsInfo& mrLabels; DataRowPointStyle& mrDataLabelStyle; public: SchXMLDataLabelContext(SvXMLImport& rImport, - ::std::vector<OUString>& rLabels, DataRowPointStyle& rDataLabel); + CustomLabelsInfo& rLabels, DataRowPointStyle& rDataLabel); virtual void SAL_CALL startFastElement( sal_Int32 nElement, diff --git a/xmloff/source/chart/SchXMLSeries2Context.cxx b/xmloff/source/chart/SchXMLSeries2Context.cxx index 544616846338..1d91af151317 100644 --- a/xmloff/source/chart/SchXMLSeries2Context.cxx +++ b/xmloff/source/chart/SchXMLSeries2Context.cxx @@ -1214,16 +1214,28 @@ void SchXMLSeries2Context::setStylesToDataPoints( SeriesDefaultsAndStyles& rSeri } // Custom labels might be passed as property - if(auto nLabelCount = seriesStyle.mCustomLabels.size(); nLabelCount > 0) + if(const size_t nLabelCount = seriesStyle.mCustomLabels.mLabels.size(); nLabelCount > 0) { + auto& rCustomLabels = seriesStyle.mCustomLabels; + Sequence< Reference<chart2::XDataPointCustomLabelField>> xLabels(nLabelCount); Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() ); - for( auto j = 0; j< xLabels.getLength(); ++j ) + for( size_t j = 0; j < nLabelCount; ++j ) { Reference< chart2::XDataPointCustomLabelField > xCustomLabel = chart2::DataPointCustomLabelField::create(xContext); xLabels[j] = xCustomLabel; - xCustomLabel->setString(seriesStyle.mCustomLabels[j]); - xCustomLabel->setFieldType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT); + xCustomLabel->setString(rCustomLabels.mLabels[j]); + if ( j == 0 && rCustomLabels.mbDataLabelsRange) + { + xCustomLabel->setFieldType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE); + xCustomLabel->setGuid(rCustomLabels.msLabelGuid); + xCustomLabel->setCellRange(rCustomLabels.msLabelsCellRange); + xCustomLabel->setDataLabelsRange(true); + } + else + { + xCustomLabel->setFieldType(chart2::DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT); + } // Restore character properties on the text span manually, till // SchXMLExportHelper_Impl::exportCustomLabel() does not write the style. @@ -1245,6 +1257,7 @@ void SchXMLSeries2Context::setStylesToDataPoints( SeriesDefaultsAndStyles& rSeri } } } + xPointProp->setPropertyValue("CustomLabelFields", uno::Any(xLabels)); xPointProp->setPropertyValue("DataCaption", uno::Any(chart::ChartDataCaption::CUSTOM)); } diff --git a/xmloff/source/chart/transporttypes.hxx b/xmloff/source/chart/transporttypes.hxx index 7393a7b34848..7053d20c84c0 100644 --- a/xmloff/source/chart/transporttypes.hxx +++ b/xmloff/source/chart/transporttypes.hxx @@ -145,6 +145,24 @@ struct RegressionStyle {} }; +/** + * Used to store text content of a data point custom-label's fields and also + * the helper members that indicates whether this label's contents are sourced + * from a cell[range] and the address of the cell[range] with GUID of + * the CELLRANGE field. + */ +struct CustomLabelsInfo +{ + /// Text content of each field. + ::std::vector<OUString> mLabels; + /// Are label's contents sourced from a cell[range] ? + bool mbDataLabelsRange = false; + /// GUID of the CELLRANGE field. + OUString msLabelGuid; + /// cell[range] from which label's contents are sourced. + OUString msLabelsCellRange; +}; + struct DataRowPointStyle { enum StyleType @@ -170,7 +188,7 @@ struct DataRowPointStyle sal_Int32 m_nPointRepeat; OUString msStyleName; OUString msStyleNameOfParent; // e.g. target of line and fill styles of data-labels - ::std::vector<OUString> mCustomLabels; + CustomLabelsInfo mCustomLabels; double mCustomLabelPos[2] = { 0.0, 0.0 }; // for svg:x and svg:y attribute (in core unit), of element <chart:data-label> std::optional<sal_Int32> mo_nLabelAbsolutePosX; diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx index d4b996a4d9dd..e3a45924091e 100644 --- a/xmloff/source/core/xmltoken.cxx +++ b/xmloff/source/core/xmltoken.cxx @@ -581,10 +581,12 @@ namespace xmloff::token { TOKEN( "data-bar-entry", XML_DATA_BAR_ENTRY ), TOKEN( "data-cell-range-address", XML_DATA_CELL_RANGE_ADDRESS ), TOKEN( "data-label", XML_DATA_LABEL ), + TOKEN( "data-label-guid", XML_DATA_LABEL_GUID ), TOKEN( "data-label-number", XML_DATA_LABEL_NUMBER ), TOKEN( "data-label-symbol", XML_DATA_LABEL_SYMBOL ), TOKEN( "data-label-text", XML_DATA_LABEL_TEXT ), TOKEN( "data-label-series", XML_DATA_LABEL_SERIES ), + TOKEN( "data-labels-cell-range", XML_DATA_LABELS_CELL_RANGE ), TOKEN( "data-pilot-source", XML_DATA_PILOT_SOURCE ), TOKEN( "data-pilot-field", XML_DATA_PILOT_FIELD ), TOKEN( "data-pilot-grand-total", XML_DATA_PILOT_GRAND_TOTAL ), diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt index 9823df1e82e7..3fde556acfee 100644 --- a/xmloff/source/token/tokens.txt +++ b/xmloff/source/token/tokens.txt @@ -493,10 +493,12 @@ data-bar data-bar-entry data-cell-range-address data-label +data-label-guid data-label-number data-label-symbol data-label-text data-label-series +data-labels-cell-range data-pilot-source data-pilot-field data-pilot-grand-total |