Rebased ref, commits from common ancestor: commit fcdeafd4bdc483d71b42fdd0a3980213f1c755aa Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Fri Mar 4 20:40:14 2022 +0900 Commit: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> CommitDate: Fri Mar 4 20:54:34 2022 +0900
sc: Sparkline export for OOXML documents + roundtrip test Change-Id: I4ab93d7ad33867ae817aa98d13ea9bc724b7d710 diff --git a/sc/CppunitTest_sc_sparkline_test.mk b/sc/CppunitTest_sc_sparkline_test.mk index edf7a3cac7da..00db7396cdb0 100644 --- a/sc/CppunitTest_sc_sparkline_test.mk +++ b/sc/CppunitTest_sc_sparkline_test.mk @@ -32,6 +32,7 @@ $(eval $(call gb_CppunitTest_use_libraries,sc_sparkline_test, \ test \ tl \ unotest \ + utl \ vcl \ )) diff --git a/sc/Library_scfilt.mk b/sc/Library_scfilt.mk index 8e23db6662fa..84b6e5fd5edd 100644 --- a/sc/Library_scfilt.mk +++ b/sc/Library_scfilt.mk @@ -130,6 +130,7 @@ $(eval $(call gb_Library_add_exception_objects,scfilt,\ sc/source/filter/excel/xltools \ sc/source/filter/excel/xltracer \ sc/source/filter/excel/xlview \ + sc/source/filter/excel/export/SparklineExt \ sc/source/filter/ftools/fapihelper \ sc/source/filter/ftools/fprogressbar \ sc/source/filter/ftools/ftools \ diff --git a/sc/qa/extras/SparklineTest.cxx b/sc/qa/extras/SparklineTest.cxx index 122a6b23f5be..a217747899d2 100644 --- a/sc/qa/extras/SparklineTest.cxx +++ b/sc/qa/extras/SparklineTest.cxx @@ -10,19 +10,36 @@ #include <test/calc_unoapi_test.hxx> #include <com/sun/star/lang/XComponent.hpp> +#include <unotools/tempfile.hxx> +#include <comphelper/propertyvalue.hxx> #include <docsh.hxx> #include <Sparkline.hxx> using namespace css; -namespace sc_apitest -{ class SparklineTest : public CalcUnoApiTest { uno::Reference<lang::XComponent> mxComponent; public: - SparklineTest(); + SparklineTest() + : CalcUnoApiTest("sc/qa/extras/testdocuments") + { + } + + void saveAndReload(uno::Reference<lang::XComponent>& xComponent, const OUString& rFilter) + { + utl::TempFile aTempFile; + aTempFile.EnableKillingFile(); + // "calc8", "Calc Office Open XML", ... + uno::Sequence aArgs{ comphelper::makePropertyValue("FilterName", rFilter) }; + uno::Reference<frame::XStorable> xStorable(xComponent, uno::UNO_QUERY_THROW); + xStorable->storeAsURL(aTempFile.GetURL(), aArgs); + uno::Reference<util::XCloseable> xCloseable(xComponent, uno::UNO_QUERY_THROW); + xCloseable->close(true); + + xComponent = loadFromDesktop(aTempFile.GetURL(), "com.sun.star.sheet.SpreadsheetDocument"); + } void testSparklines(); @@ -31,121 +48,224 @@ public: CPPUNIT_TEST_SUITE_END(); }; -SparklineTest::SparklineTest() - : CalcUnoApiTest("sc/qa/extras/testdocuments") -{ -} - void SparklineTest::testSparklines() { OUString aFileURL; createFileURL(u"Sparklines.xlsx", aFileURL); mxComponent = loadFromDesktop(aFileURL); - SfxObjectShell* pFoundShell = SfxObjectShell::GetShellFromComponent(mxComponent); - CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pFoundShell); - ScDocShellRef xDocSh = dynamic_cast<ScDocShell*>(pFoundShell); - CPPUNIT_ASSERT(xDocSh); - - ScDocument& rDocument = xDocSh->GetDocument(); - // Sparkline at Sheet1:A2 - { - sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 1, 0)); // A2 - CPPUNIT_ASSERT(pSparkline); - auto pSparklineGroup = pSparkline->getSparklineGroup(); - CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); - - CPPUNIT_ASSERT_EQUAL(Color(0x376092), pSparklineGroup->m_aColorSeries); - CPPUNIT_ASSERT_EQUAL(Color(0x00b050), pSparklineGroup->m_aColorNegative); - CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorAxis); - CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorMarkers); - CPPUNIT_ASSERT_EQUAL(Color(0x7030a0), pSparklineGroup->m_aColorFirst); - CPPUNIT_ASSERT_EQUAL(Color(0xff0000), pSparklineGroup->m_aColorLast); - CPPUNIT_ASSERT_EQUAL(Color(0x92d050), pSparklineGroup->m_aColorHigh); - CPPUNIT_ASSERT_EQUAL(Color(0x00b0f0), pSparklineGroup->m_aColorLow); - - CPPUNIT_ASSERT_EQUAL(1.0, pSparklineGroup->m_fLineWeight); - CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDateAxis); - CPPUNIT_ASSERT_EQUAL(sc::DisplayEmptyCellAs::Gap, pSparklineGroup->m_eDisplayEmptyCellsAs); - - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bMarkers); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bHigh); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLow); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bFirst); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLast); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bNegative); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bDisplayXAxis); - CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayHidden); - CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bRightToLeft); - - CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMax)); - CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMin)); - } - // Sparkline at Sheet1:A3 - { - sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 2, 0)); // A3 - CPPUNIT_ASSERT(pSparkline); - auto pSparklineGroup = pSparkline->getSparklineGroup(); - CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Column, pSparklineGroup->m_eType); - - CPPUNIT_ASSERT_EQUAL(Color(0x376092), pSparklineGroup->m_aColorSeries); - CPPUNIT_ASSERT_EQUAL(Color(0xff0000), pSparklineGroup->m_aColorNegative); - CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorAxis); - CPPUNIT_ASSERT_EQUAL(Color(0xd00000), pSparklineGroup->m_aColorMarkers); - CPPUNIT_ASSERT_EQUAL(Color(0x92d050), pSparklineGroup->m_aColorFirst); - CPPUNIT_ASSERT_EQUAL(Color(0x00b0f0), pSparklineGroup->m_aColorLast); - CPPUNIT_ASSERT_EQUAL(Color(0x7030a0), pSparklineGroup->m_aColorHigh); - CPPUNIT_ASSERT_EQUAL(Color(0xffc000), pSparklineGroup->m_aColorLow); - - CPPUNIT_ASSERT_EQUAL(0.75, pSparklineGroup->m_fLineWeight); - CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDateAxis); - CPPUNIT_ASSERT_EQUAL(sc::DisplayEmptyCellAs::Gap, pSparklineGroup->m_eDisplayEmptyCellsAs); - - CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bMarkers); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bHigh); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLow); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bFirst); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLast); - CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bNegative); - CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayXAxis); - CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayHidden); - CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bRightToLeft); - - CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMax)); - CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMin)); - } - // Sparkline at Sheet2:B1 { - sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 0, 1)); //B1 - CPPUNIT_ASSERT(pSparkline); - auto pSparklineGroup = pSparkline->getSparklineGroup(); - CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Column, pSparklineGroup->m_eType); - } - // Sparkline at Sheet2:B2 - { - sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 1, 1)); //B2 - CPPUNIT_ASSERT(pSparkline); - auto pSparklineGroup = pSparkline->getSparklineGroup(); - CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); - } - // Sparkline at Sheet2:B2 - { - sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 1, 1)); //B2 - CPPUNIT_ASSERT(pSparkline); - auto pSparklineGroup = pSparkline->getSparklineGroup(); - CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); + SfxObjectShell* pFoundShell = SfxObjectShell::GetShellFromComponent(mxComponent); + CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pFoundShell); + ScDocShellRef xDocSh = dynamic_cast<ScDocShell*>(pFoundShell); + CPPUNIT_ASSERT(xDocSh); + + ScDocument& rDocument = xDocSh->GetDocument(); + // Sparkline at Sheet1:A2 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 1, 0)); // A2 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); + + CPPUNIT_ASSERT_EQUAL(Color(0x376092), pSparklineGroup->m_aColorSeries); + CPPUNIT_ASSERT_EQUAL(Color(0x00b050), pSparklineGroup->m_aColorNegative); + CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorAxis); + CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorMarkers); + CPPUNIT_ASSERT_EQUAL(Color(0x7030a0), pSparklineGroup->m_aColorFirst); + CPPUNIT_ASSERT_EQUAL(Color(0xff0000), pSparklineGroup->m_aColorLast); + CPPUNIT_ASSERT_EQUAL(Color(0x92d050), pSparklineGroup->m_aColorHigh); + CPPUNIT_ASSERT_EQUAL(Color(0x00b0f0), pSparklineGroup->m_aColorLow); + + CPPUNIT_ASSERT_EQUAL(1.0, pSparklineGroup->m_fLineWeight); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDateAxis); + CPPUNIT_ASSERT_EQUAL(sc::DisplayEmptyCellAs::Gap, + pSparklineGroup->m_eDisplayEmptyCellsAs); + + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bMarkers); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bHigh); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLow); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bFirst); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLast); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bNegative); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bDisplayXAxis); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayHidden); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bRightToLeft); + + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMax)); + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMin)); + } + // Sparkline at Sheet1:A3 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 2, 0)); // A3 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Column, pSparklineGroup->m_eType); + + CPPUNIT_ASSERT_EQUAL(Color(0x376092), pSparklineGroup->m_aColorSeries); + CPPUNIT_ASSERT_EQUAL(Color(0xff0000), pSparklineGroup->m_aColorNegative); + CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorAxis); + CPPUNIT_ASSERT_EQUAL(Color(0xd00000), pSparklineGroup->m_aColorMarkers); + CPPUNIT_ASSERT_EQUAL(Color(0x92d050), pSparklineGroup->m_aColorFirst); + CPPUNIT_ASSERT_EQUAL(Color(0x00b0f0), pSparklineGroup->m_aColorLast); + CPPUNIT_ASSERT_EQUAL(Color(0x7030a0), pSparklineGroup->m_aColorHigh); + CPPUNIT_ASSERT_EQUAL(Color(0xffc000), pSparklineGroup->m_aColorLow); + + CPPUNIT_ASSERT_EQUAL(0.75, pSparklineGroup->m_fLineWeight); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDateAxis); + CPPUNIT_ASSERT_EQUAL(sc::DisplayEmptyCellAs::Gap, + pSparklineGroup->m_eDisplayEmptyCellsAs); + + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bMarkers); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bHigh); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLow); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bFirst); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLast); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bNegative); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayXAxis); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayHidden); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bRightToLeft); + + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMax)); + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMin)); + } + // Sparkline at Sheet2:B1 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 0, 1)); //B1 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Column, pSparklineGroup->m_eType); + } + // Sparkline at Sheet2:B2 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 1, 1)); //B2 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); + } + // Sparkline at Sheet2:B2 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 1, 1)); //B2 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); + } + // Sparkline doesn't exists at A4 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 3, 0)); //A4 + CPPUNIT_ASSERT(!pSparkline); + } } - // Sparkline doesn't exists at A4 + + saveAndReload(mxComponent, "Calc Office Open XML"); + { - sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 3, 0)); //A4 - CPPUNIT_ASSERT(!pSparkline); + SfxObjectShell* pFoundShell = SfxObjectShell::GetShellFromComponent(mxComponent); + CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pFoundShell); + ScDocShellRef xDocSh = dynamic_cast<ScDocShell*>(pFoundShell); + CPPUNIT_ASSERT(xDocSh); + + ScDocument& rDocument = xDocSh->GetDocument(); + // Sparkline at Sheet1:A2 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 1, 0)); // A2 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); + + CPPUNIT_ASSERT_EQUAL(Color(0x376092), pSparklineGroup->m_aColorSeries); + CPPUNIT_ASSERT_EQUAL(Color(0x00b050), pSparklineGroup->m_aColorNegative); + CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorAxis); + CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorMarkers); + CPPUNIT_ASSERT_EQUAL(Color(0x7030a0), pSparklineGroup->m_aColorFirst); + CPPUNIT_ASSERT_EQUAL(Color(0xff0000), pSparklineGroup->m_aColorLast); + CPPUNIT_ASSERT_EQUAL(Color(0x92d050), pSparklineGroup->m_aColorHigh); + CPPUNIT_ASSERT_EQUAL(Color(0x00b0f0), pSparklineGroup->m_aColorLow); + + CPPUNIT_ASSERT_EQUAL(1.0, pSparklineGroup->m_fLineWeight); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDateAxis); + CPPUNIT_ASSERT_EQUAL(sc::DisplayEmptyCellAs::Gap, + pSparklineGroup->m_eDisplayEmptyCellsAs); + + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bMarkers); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bHigh); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLow); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bFirst); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLast); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bNegative); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bDisplayXAxis); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayHidden); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bRightToLeft); + + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMax)); + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMin)); + } + // Sparkline at Sheet1:A3 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 2, 0)); // A3 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Column, pSparklineGroup->m_eType); + + CPPUNIT_ASSERT_EQUAL(Color(0x376092), pSparklineGroup->m_aColorSeries); + CPPUNIT_ASSERT_EQUAL(Color(0xff0000), pSparklineGroup->m_aColorNegative); + CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorAxis); + CPPUNIT_ASSERT_EQUAL(Color(0xd00000), pSparklineGroup->m_aColorMarkers); + CPPUNIT_ASSERT_EQUAL(Color(0x92d050), pSparklineGroup->m_aColorFirst); + CPPUNIT_ASSERT_EQUAL(Color(0x00b0f0), pSparklineGroup->m_aColorLast); + CPPUNIT_ASSERT_EQUAL(Color(0x7030a0), pSparklineGroup->m_aColorHigh); + CPPUNIT_ASSERT_EQUAL(Color(0xffc000), pSparklineGroup->m_aColorLow); + + CPPUNIT_ASSERT_EQUAL(0.75, pSparklineGroup->m_fLineWeight); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDateAxis); + CPPUNIT_ASSERT_EQUAL(sc::DisplayEmptyCellAs::Gap, + pSparklineGroup->m_eDisplayEmptyCellsAs); + + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bMarkers); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bHigh); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLow); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bFirst); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLast); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bNegative); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayXAxis); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayHidden); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bRightToLeft); + + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMax)); + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMin)); + } + // Sparkline at Sheet2:B1 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 0, 1)); //B1 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Column, pSparklineGroup->m_eType); + } + // Sparkline at Sheet2:B2 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 1, 1)); //B2 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); + } + // Sparkline at Sheet2:B2 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 1, 1)); //B2 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); + } + // Sparkline doesn't exists at A4 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 3, 0)); //A4 + CPPUNIT_ASSERT(!pSparkline); + } } closeDocument(mxComponent); } CPPUNIT_TEST_SUITE_REGISTRATION(SparklineTest); -} CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sc/source/filter/excel/excdoc.cxx b/sc/source/filter/excel/excdoc.cxx index 6b02457f70f6..529e76971cbc 100644 --- a/sc/source/filter/excel/excdoc.cxx +++ b/sc/source/filter/excel/excdoc.cxx @@ -39,6 +39,7 @@ #include <xecontent.hxx> #include <xeescher.hxx> #include <xepivot.hxx> +#include <export/SparklineExt.hxx> #include <XclExpChangeTrack.hxx> #include <xepivotxml.hxx> #include <xedbdata.hxx> @@ -611,6 +612,8 @@ void ExcTable::FillAsTableXml() // conditional formats Add( new XclExpCondFormatBuffer( GetRoot(), xExtLst ) ); + Add(new xcl::exp::SparklineBuffer(GetRoot(), xExtLst)); + // data validation (DVAL and list of DV records), generated by the cell table aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_DVAL ) ); diff --git a/sc/source/filter/excel/export/SparklineExt.cxx b/sc/source/filter/excel/export/SparklineExt.cxx new file mode 100644 index 000000000000..f1e97b2c62ed --- /dev/null +++ b/sc/source/filter/excel/export/SparklineExt.cxx @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "export/SparklineExt.hxx" + +#include <oox/export/utils.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/tokens.hxx> + +using namespace oox; + +namespace xcl::exp +{ +SparklineExt::SparklineExt(const XclExpRoot& rRoot, + std::vector<std::shared_ptr<sc::Sparkline>> const& pSparklines) + : XclExpExt(rRoot) +{ + maURI = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"; + + for (auto const& pSparkline : pSparklines) + { + auto* pGroupPointer = pSparkline->getSparklineGroup().get(); + + auto aIterator = m_aSparklineGroupMap.find(pGroupPointer); + if (aIterator == m_aSparklineGroupMap.end()) + { + std::vector<std::shared_ptr<sc::Sparkline>> aSparklineVector; + aSparklineVector.push_back(pSparkline); + m_aSparklineGroupMap.emplace(pGroupPointer, aSparklineVector); + } + else + { + aIterator->second.push_back(pSparkline); + } + } +} + +void SparklineExt::SaveXml(XclExpXmlStream& rStream) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream(); + rWorksheet->startElement(XML_ext, FSNS(XML_xmlns, XML_x14), + rStream.getNamespaceURL(OOX_NS(xls14Lst)), XML_uri, maURI); + + rWorksheet->startElementNS(XML_x14, XML_sparklineGroups, FSNS(XML_xmlns, XML_xm), + rStream.getNamespaceURL(OOX_NS(xm))); + + for (auto const & [ pSparklineGroup, rSparklineVector ] : m_aSparklineGroupMap) + { + addSparklineGroup(rStream, *pSparklineGroup, rSparklineVector); + } + + rWorksheet->endElementNS(XML_x14, XML_sparklineGroups); + rWorksheet->endElement(XML_ext); +} + +void SparklineExt::addSparklineGroupAttributes( + rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, + sc::SparklineGroup& rSparklineGroup) +{ + if (rSparklineGroup.m_fLineWeight != 0.75) + pAttrList->add(XML_lineWeight, OString::number(rSparklineGroup.m_fLineWeight)); + + if (rSparklineGroup.m_eType != sc::SparklineType::Line) + { + if (rSparklineGroup.m_eType == sc::SparklineType::Column) + pAttrList->add(XML_type, "column"); + else if (rSparklineGroup.m_eType == sc::SparklineType::Stacked) + pAttrList->add(XML_type, "stacked"); + } + + if (rSparklineGroup.m_bDateAxis) + pAttrList->add(XML_dateAxis, "1"); + + if (rSparklineGroup.m_eDisplayEmptyCellsAs != sc::DisplayEmptyCellAs::Zero) + { + if (rSparklineGroup.m_eDisplayEmptyCellsAs == sc::DisplayEmptyCellAs::Gap) + pAttrList->add(XML_displayEmptyCellsAs, "gap"); + else if (rSparklineGroup.m_eDisplayEmptyCellsAs == sc::DisplayEmptyCellAs::Span) + pAttrList->add(XML_displayEmptyCellsAs, "span"); + } + + if (rSparklineGroup.m_bMarkers) + pAttrList->add(XML_markers, "1"); + if (rSparklineGroup.m_bHigh) + pAttrList->add(XML_high, "1"); + if (rSparklineGroup.m_bLow) + pAttrList->add(XML_low, "1"); + if (rSparklineGroup.m_bFirst) + pAttrList->add(XML_first, "1"); + if (rSparklineGroup.m_bLast) + pAttrList->add(XML_last, "1"); + if (rSparklineGroup.m_bNegative) + pAttrList->add(XML_negative, "1"); + if (rSparklineGroup.m_bDisplayXAxis) + pAttrList->add(XML_displayXAxis, "1"); + if (rSparklineGroup.m_bDisplayHidden) + pAttrList->add(XML_displayHidden, "1"); + + if (rSparklineGroup.m_eMinAxisType != sc::AxisType::Individual) + { + if (rSparklineGroup.m_eMinAxisType == sc::AxisType::Group) + pAttrList->add(XML_minAxisType, "group"); + else if (rSparklineGroup.m_eMinAxisType == sc::AxisType::Custom) + pAttrList->add(XML_minAxisType, "custom"); + } + + if (rSparklineGroup.m_eMaxAxisType != sc::AxisType::Individual) + { + if (rSparklineGroup.m_eMaxAxisType == sc::AxisType::Group) + pAttrList->add(XML_maxAxisType, "group"); + else if (rSparklineGroup.m_eMaxAxisType == sc::AxisType::Custom) + pAttrList->add(XML_maxAxisType, "custom"); + } + + if (rSparklineGroup.m_bRightToLeft) + pAttrList->add(XML_rightToLeft, "1"); + + if (rSparklineGroup.m_aManualMax && rSparklineGroup.m_eMaxAxisType == sc::AxisType::Custom) + pAttrList->add(XML_manualMax, OString::number(*rSparklineGroup.m_aManualMax)); + + if (rSparklineGroup.m_aManualMin && rSparklineGroup.m_eMinAxisType == sc::AxisType::Custom) + pAttrList->add(XML_manualMin, OString::number(*rSparklineGroup.m_aManualMin)); +} + +void SparklineExt::addSparklineGroupColors(XclExpXmlStream& rStream, + sc::SparklineGroup& rSparklineGroup) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream(); + + rWorksheet->singleElementNS(XML_x14, XML_colorSeries, XML_rgb, + XclXmlUtils::ToOString(rSparklineGroup.m_aColorSeries)); + + if (rSparklineGroup.m_aColorSeries != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorNegative, XML_rgb, + XclXmlUtils::ToOString(rSparklineGroup.m_aColorNegative)); + } + + if (rSparklineGroup.m_aColorAxis != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorAxis, XML_rgb, + XclXmlUtils::ToOString(rSparklineGroup.m_aColorAxis)); + } + + if (rSparklineGroup.m_aColorMarkers != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorMarkers, XML_rgb, + XclXmlUtils::ToOString(rSparklineGroup.m_aColorMarkers)); + } + + if (rSparklineGroup.m_aColorFirst != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorFirst, XML_rgb, + XclXmlUtils::ToOString(rSparklineGroup.m_aColorFirst)); + } + + if (rSparklineGroup.m_aColorLast != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorLast, XML_rgb, + XclXmlUtils::ToOString(rSparklineGroup.m_aColorLast)); + } + + if (rSparklineGroup.m_aColorHigh != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorHigh, XML_rgb, + XclXmlUtils::ToOString(rSparklineGroup.m_aColorHigh)); + } + + if (rSparklineGroup.m_aColorLow != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorLow, XML_rgb, + XclXmlUtils::ToOString(rSparklineGroup.m_aColorLow)); + } +} + +void SparklineExt::addSparklineGroup(XclExpXmlStream& rStream, sc::SparklineGroup& rSparklineGroup, + std::vector<std::shared_ptr<sc::Sparkline>> const& rSparklines) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream(); + + // Sparkline Group Attributes + auto pAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + addSparklineGroupAttributes(pAttrList, rSparklineGroup); + + rWorksheet->startElementNS(XML_x14, XML_sparklineGroup, pAttrList); + + addSparklineGroupColors(rStream, rSparklineGroup); + + // Sparklines + + rWorksheet->startElementNS(XML_x14, XML_sparklines); + for (auto const& rSparkline : rSparklines) + { + rWorksheet->startElementNS(XML_x14, XML_sparkline); + + { + rWorksheet->startElementNS(XML_xm, XML_f); + + OUString sRangeFormula; + ScRefFlags eFlags = ScRefFlags::VALID | ScRefFlags::TAB_3D; + rSparkline->getInputRange().Format(sRangeFormula, eFlags, GetDoc(), + formula::FormulaGrammar::CONV_XL_OOX, ' ', true); + + rWorksheet->writeEscaped(sRangeFormula); + rWorksheet->endElementNS(XML_xm, XML_f); + } + + { + rWorksheet->startElementNS(XML_xm, XML_sqref); + + ScAddress::Details detailsXL(formula::FormulaGrammar::CONV_XL_OOX); + ScAddress aAddress(rSparkline->getColumn(), rSparkline->getRow(), GetCurrScTab()); + OUString sLocation = aAddress.Format(ScRefFlags::VALID, &GetDoc(), detailsXL); + + rWorksheet->writeEscaped(sLocation); + rWorksheet->endElementNS(XML_xm, XML_sqref); + } + + rWorksheet->endElementNS(XML_x14, XML_sparkline); + } + rWorksheet->endElementNS(XML_x14, XML_sparklines); + rWorksheet->endElementNS(XML_x14, XML_sparklineGroup); +} + +SparklineBuffer::SparklineBuffer(const XclExpRoot& rRoot, XclExtLstRef const& xExtLst) + : XclExpRoot(rRoot) +{ + if (sc::SparklineList* pSparklineList = GetDoc().GetSparklineList(GetCurrScTab())) + { + auto pSparklines = pSparklineList->getSparklines(); + if (!pSparklines.empty()) + { + xExtLst->AddRecord(new xcl::exp::SparklineExt(GetRoot(), pSparklines)); + } + } +} + +} // end namespace xcl::exp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/export/SparklineExt.hxx b/sc/source/filter/inc/export/SparklineExt.hxx new file mode 100644 index 000000000000..116462c5f3c4 --- /dev/null +++ b/sc/source/filter/inc/export/SparklineExt.hxx @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <memory> +#include <vector> +#include <map> +#include <rangelst.hxx> +#include <Sparkline.hxx> + +#include <sax/fastattribs.hxx> + +#include "xerecord.hxx" +#include "xeroot.hxx" +#include "xeextlst.hxx" + +namespace xcl::exp +{ +class SparklineExt : public XclExpExt +{ + std::map<sc::SparklineGroup*, std::vector<std::shared_ptr<sc::Sparkline>>> m_aSparklineGroupMap; + +public: + SparklineExt(const XclExpRoot& rRoot, + std::vector<std::shared_ptr<sc::Sparkline>> const& pSparklines); + + void SaveXml(XclExpXmlStream& rStream) override; + void addSparklineGroup(XclExpXmlStream& rStream, sc::SparklineGroup& rSparklineGroup, + std::vector<std::shared_ptr<sc::Sparkline>> const& rSparklines); + void addSparklineGroupAttributes(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, + sc::SparklineGroup& rSparklineGroup); + void addSparklineGroupColors(XclExpXmlStream& rStream, sc::SparklineGroup& rSparklineGroup); + + XclExpExtType GetType() override { return XclExpExtSparklineType; } +}; + +class SparklineBuffer : public XclExpRecordBase, protected XclExpRoot +{ +public: + explicit SparklineBuffer(const XclExpRoot& rRoot, const XclExtLstRef& xExtLst); +}; + +} // end namespace xcl::exp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/xeextlst.hxx b/sc/source/filter/inc/xeextlst.hxx index c5db5fcaff99..1770f9af191d 100644 --- a/sc/source/filter/inc/xeextlst.hxx +++ b/sc/source/filter/inc/xeextlst.hxx @@ -19,7 +19,8 @@ enum XclExpExtType { XclExpExtDataBarType, - XclExpExtDataFooType + XclExpExtDataFooType, + XclExpExtSparklineType, }; struct XclExpExtCondFormatData commit e42cb7e66f5bb427421708142be9bd804bfa4960 Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Fri Mar 4 17:26:34 2022 +0900 Commit: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> CommitDate: Fri Mar 4 20:54:34 2022 +0900 sc: set colors in SparklineGroup construction to COL_TRANSPARENT Change-Id: I67ceab2ffd723511fbf0616cca661992f0a8cf69 diff --git a/sc/inc/SparklineGroup.hxx b/sc/inc/SparklineGroup.hxx index 32e4b757378f..170ccee51539 100644 --- a/sc/inc/SparklineGroup.hxx +++ b/sc/inc/SparklineGroup.hxx @@ -74,7 +74,17 @@ public: std::optional<double> m_aManualMin; OUString m_sUID; - SparklineGroup() {} + SparklineGroup() + : m_aColorSeries(COL_TRANSPARENT) + , m_aColorNegative(COL_TRANSPARENT) + , m_aColorAxis(COL_TRANSPARENT) + , m_aColorMarkers(COL_TRANSPARENT) + , m_aColorFirst(COL_TRANSPARENT) + , m_aColorLast(COL_TRANSPARENT) + , m_aColorHigh(COL_TRANSPARENT) + , m_aColorLow(COL_TRANSPARENT) + { + } SparklineGroup(const SparklineGroup&) = delete; SparklineGroup& operator=(const SparklineGroup&) = delete; commit b2160dc8db765ced65f17495734b4d5726002966 Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Wed Mar 2 17:44:08 2022 +0900 Commit: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> CommitDate: Fri Mar 4 20:54:34 2022 +0900 sc: refactor sparkline struture to store a list of sparklines We need to access a list of sparklines and sparkline groups for a sheet. To preven going through all the columns of a sheet, we need to store all the created sparklines in a list. For this it is necessary to change the model structrue a bit. A cell now has a container that stores a shared_ptr to the sparkline instead of storing the sparkline directly. With this we can store a list of weak_ptr to the sparklines in a list (vector), which can be accessed at any time and is quite fast. This is needed by the OOXML export. Change-Id: Iaca0a41e20912775f072ea6e8cab9c44367d6f30 diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk index 11449217edd9..62ab22a797c4 100644 --- a/sc/Library_sc.mk +++ b/sc/Library_sc.mk @@ -185,7 +185,6 @@ $(eval $(call gb_Library_add_exception_objects,sc,\ sc/source/core/data/sheetevents \ sc/source/core/data/simpleformulacalc \ sc/source/core/data/sortparam \ - sc/source/core/data/Sparkline \ sc/source/core/data/stlpool \ sc/source/core/data/stlsheet \ sc/source/core/data/subtotalparam \ diff --git a/sc/inc/Sparkline.hxx b/sc/inc/Sparkline.hxx index 8969fa3bfd3e..ad4688f33a5e 100644 --- a/sc/inc/Sparkline.hxx +++ b/sc/inc/Sparkline.hxx @@ -19,20 +19,67 @@ namespace sc { class SC_DLLPUBLIC Sparkline { -private: + SCCOL m_nColumn; + SCROW m_nRow; + ScRangeList m_aInputRange; std::shared_ptr<SparklineGroup> m_pSparklineGroup; public: - Sparkline(std::shared_ptr<SparklineGroup>& pSparklineGroup); + Sparkline(SCCOL nColumn, SCROW nRow, std::shared_ptr<SparklineGroup> const& pSparklineGroup) + : m_nColumn(nColumn) + , m_nRow(nRow) + , m_pSparklineGroup(pSparklineGroup) + { + } Sparkline(const Sparkline&) = delete; Sparkline& operator=(const Sparkline&) = delete; void setInputRange(ScRangeList const& rInputRange) { m_aInputRange = rInputRange; } + ScRangeList const& getInputRange() { return m_aInputRange; } std::shared_ptr<SparklineGroup> const& getSparklineGroup() { return m_pSparklineGroup; } + + SCCOL getColumn() { return m_nColumn; } + + SCROW getRow() { return m_nRow; } +}; + +class SC_DLLPUBLIC SparklineList +{ +private: + std::vector<std::weak_ptr<Sparkline>> m_pSparklines; + +public: + SparklineList() {} + + void addSparkline(std::shared_ptr<Sparkline> const& pSparkline) + { + m_pSparklines.push_back(pSparkline); + } + + std::vector<std::shared_ptr<Sparkline>> getSparklines() + { + std::vector<std::shared_ptr<Sparkline>> toReturn; + + std::vector<std::weak_ptr<Sparkline>>::iterator aIter; + for (aIter = m_pSparklines.begin(); aIter != m_pSparklines.end();) + { + if (auto aSparkline = aIter->lock()) + { + toReturn.push_back(aSparkline); + aIter++; + } + else + { + aIter = m_pSparklines.erase(aIter); + } + } + + return toReturn; + } }; } // end sc diff --git a/sc/inc/SparklineCell.hxx b/sc/inc/SparklineCell.hxx new file mode 100644 index 000000000000..0aca857170c9 --- /dev/null +++ b/sc/inc/SparklineCell.hxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#pragma once + +#include "scdllapi.h" +#include "Sparkline.hxx" +#include <memory> + +namespace sc +{ +class SC_DLLPUBLIC SparklineCell +{ +private: + std::shared_ptr<Sparkline> m_pSparkline; + +public: + SparklineCell(std::shared_ptr<Sparkline> const& pSparkline) + : m_pSparkline(pSparkline) + { + } + + SparklineCell(const SparklineCell&) = delete; + SparklineCell& operator=(const SparklineCell&) = delete; + + void setInputRange(ScRangeList const& rInputRange) { m_pSparkline->setInputRange(rInputRange); } + + ScRangeList const& getInputRange() { return m_pSparkline->getInputRange(); } + + std::shared_ptr<SparklineGroup> const& getSparklineGroup() + { + return m_pSparkline->getSparklineGroup(); + } + + std::shared_ptr<Sparkline> const& getSparkline() { return m_pSparkline; } +}; + +} // end sc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/inc/column.hxx b/sc/inc/column.hxx index f4b603c5c721..5472419e2297 100644 --- a/sc/inc/column.hxx +++ b/sc/inc/column.hxx @@ -617,8 +617,8 @@ public: void BroadcastRows( SCROW nStartRow, SCROW nEndRow, SfxHintId nHint ); // Spaklines - sc::Sparkline* GetSparkline(SCROW nRow); - void SetSparkline(SCROW nRow, std::unique_ptr<sc::Sparkline> pSparkline); + sc::SparklineCell* GetSparklineCell(SCROW nRow); + void CreateSparklineCell(SCROW nRow, std::shared_ptr<sc::Sparkline> const& pSparkline); // cell notes ScPostIt* GetCellNote( SCROW nRow ); diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx index 86bd4ebd02bf..1b86695ba94b 100644 --- a/sc/inc/document.hxx +++ b/sc/inc/document.hxx @@ -103,6 +103,7 @@ class ColumnIterator; class ExternalDataMapper; class Sparkline; class SparklineGroup; +class SparklineList; } @@ -1244,6 +1245,7 @@ public: /** Spaklines */ SC_DLLPUBLIC sc::Sparkline* GetSparkline(ScAddress const & rPosition); SC_DLLPUBLIC sc::Sparkline* CreateSparkline(ScAddress const & rPosition, std::shared_ptr<sc::SparklineGroup> & pSparklineGroup); + SC_DLLPUBLIC sc::SparklineList* GetSparklineList(SCTAB nTab); /** Notes **/ SC_DLLPUBLIC ScPostIt* GetNote(const ScAddress& rPos); diff --git a/sc/inc/mtvelements.hxx b/sc/inc/mtvelements.hxx index 6de0f3e7ec23..ee669c0a6e6b 100644 --- a/sc/inc/mtvelements.hxx +++ b/sc/inc/mtvelements.hxx @@ -15,7 +15,7 @@ #include <editeng/editobj.hxx> #include "calcmacros.hxx" #include "postit.hxx" -#include "Sparkline.hxx" +#include "SparklineCell.hxx" #include "celltextattr.hxx" #if DEBUG_COLUMN_STORAGE @@ -59,7 +59,7 @@ const mdds::mtv::element_t element_type_uint16 = mdds::mtv::element_type_uint16; /// Custom element blocks. -typedef mdds::mtv::noncopyable_managed_element_block<element_type_sparkline, sc::Sparkline> sparkline_block; +typedef mdds::mtv::noncopyable_managed_element_block<element_type_sparkline, sc::SparklineCell> sparkline_block; typedef mdds::mtv::noncopyable_managed_element_block<element_type_cellnote, ScPostIt> cellnote_block; typedef mdds::mtv::noncopyable_managed_element_block<element_type_broadcaster, SvtBroadcaster> broadcaster_block; typedef mdds::mtv::default_element_block<element_type_celltextattr, CellTextAttr> celltextattr_block; @@ -77,7 +77,7 @@ typedef mdds::mtv::uint16_element_block uint16_block; /// For example sc types like sc::CellTextAttr, ScFormulaCell in global namespace. namespace sc { MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(CellTextAttr, element_type_celltextattr, CellTextAttr(), celltextattr_block) -MDDS_MTV_DEFINE_ELEMENT_CALLBACKS_PTR(Sparkline, sc::element_type_sparkline, nullptr, sc::sparkline_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS_PTR(SparklineCell, sc::element_type_sparkline, nullptr, sc::sparkline_block) } /// These need to be in global namespace just like their respective types are. diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx index 4a8654a67344..39cc4736d12d 100644 --- a/sc/inc/table.hxx +++ b/sc/inc/table.hxx @@ -227,6 +227,7 @@ private: mutable std::unique_ptr<ScRangeName> mpRangeName; std::unique_ptr<ScConditionalFormatList> mpCondFormatList; + sc::SparklineList maSparklineList; ScAddress maLOKFreezeCell; @@ -472,6 +473,14 @@ public: void GetFirstDataPos(SCCOL& rCol, SCROW& rRow) const; void GetLastDataPos(SCCOL& rCol, SCROW& rRow) const; + // Sparklines + + sc::Sparkline* GetSparkline(SCCOL nCol, SCROW nRow); + sc::Sparkline* CreateSparkline(SCCOL nCol, SCROW nRow, std::shared_ptr<sc::SparklineGroup> & pSparklineGroup); + + sc::SparklineList& GetSparklineList(); + + // Notes / Comments std::unique_ptr<ScPostIt> ReleaseNote( SCCOL nCol, SCROW nRow ); ScPostIt* GetNote( SCCOL nCol, SCROW nRow ); void SetNote( SCCOL nCol, SCROW nRow, std::unique_ptr<ScPostIt> pNote ); diff --git a/sc/source/core/data/Sparkline.cxx b/sc/source/core/data/Sparkline.cxx deleted file mode 100644 index 301fda820ff2..000000000000 --- a/sc/source/core/data/Sparkline.cxx +++ /dev/null @@ -1,22 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is part of the LibreOffice project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - */ - -#include <memory> -#include <Sparkline.hxx> - -namespace sc -{ -Sparkline::Sparkline(std::shared_ptr<SparklineGroup>& pSparklineGroup) - : m_pSparklineGroup(pSparklineGroup) -{ -} -} - -/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/column2.cxx b/sc/source/core/data/column2.cxx index c2755f8f3cbb..cccd2af0fb34 100644 --- a/sc/source/core/data/column2.cxx +++ b/sc/source/core/data/column2.cxx @@ -1979,14 +1979,14 @@ void ScColumn::PrepareBroadcastersForDestruction() } } -sc::Sparkline* ScColumn::GetSparkline(SCROW nRow) +sc::SparklineCell* ScColumn::GetSparklineCell(SCROW nRow) { - return maSparklines.get<sc::Sparkline*>(nRow); + return maSparklines.get<sc::SparklineCell*>(nRow); } -void ScColumn::SetSparkline(SCROW nRow, std::unique_ptr<sc::Sparkline> pSparkline) +void ScColumn::CreateSparklineCell(SCROW nRow, std::shared_ptr<sc::Sparkline> const& pSparkline) { - maSparklines.set(nRow, pSparkline.release()); + maSparklines.set(nRow, new sc::SparklineCell(pSparkline)); } ScPostIt* ScColumn::GetCellNote(SCROW nRow) diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx index 92bf74e40c30..e0b1037c8d3e 100644 --- a/sc/source/core/data/document.cxx +++ b/sc/source/core/data/document.cxx @@ -6505,33 +6505,41 @@ bool ScDocument::IsInVBAMode() const return false; } +// Sparklines sc::Sparkline* ScDocument::GetSparkline(ScAddress const& rPosition) { SCTAB nTab = rPosition.Tab(); - SCCOL nCol = rPosition.Col(); - if (ValidTab(nTab) && nTab < SCTAB(maTabs.size()) && - nCol < maTabs[nTab]->GetAllocatedColumnsCount()) + if (ValidTab(nTab) && nTab < SCTAB(maTabs.size())) { - SCROW nRow = rPosition.Row(); - return maTabs[nTab]->aCol[nCol].GetSparkline(nRow); + return maTabs[nTab]->GetSparkline(rPosition.Col(), rPosition.Row()); } return nullptr; } sc::Sparkline* ScDocument::CreateSparkline(ScAddress const & rPosition, std::shared_ptr<sc::SparklineGroup> & pSparklineGroup) { - std::unique_ptr<sc::Sparkline> pSparkline(new sc::Sparkline(pSparklineGroup)); - sc::Sparkline* pCreated = pSparkline.get(); - SCTAB nTab = rPosition.Tab(); - SCCOL nCol = rPosition.Col(); - SCROW nRow = rPosition.Row(); - maTabs[nTab]->CreateColumnIfNotExists(nCol).SetSparkline(nRow, std::move(pSparkline)); - return pCreated; + if (ValidTab(nTab) && nTab < SCTAB(maTabs.size())) + { + return maTabs[nTab]->CreateSparkline(rPosition.Col(), rPosition.Row(), pSparklineGroup); + } + + return nullptr; +} + +sc::SparklineList* ScDocument::GetSparklineList(SCTAB nTab) +{ + if (ValidTab(nTab) && nTab < SCTAB(maTabs.size())) + { + return &maTabs[nTab]->GetSparklineList(); + } + return nullptr; } +// Notes + ScPostIt* ScDocument::GetNote(const ScAddress& rPos) { return GetNote(rPos.Col(), rPos.Row(), rPos.Tab()); diff --git a/sc/source/core/data/table2.cxx b/sc/source/core/data/table2.cxx index b65d1651e56d..aa218ee44cec 100644 --- a/sc/source/core/data/table2.cxx +++ b/sc/source/core/data/table2.cxx @@ -1809,6 +1809,43 @@ ScFormulaCell* ScTable::GetFormulaCell( SCCOL nCol, SCROW nRow ) return aCol[nCol].GetFormulaCell(nRow); } +// Sparklines + +sc::Sparkline* ScTable::GetSparkline(SCCOL nCol, SCROW nRow) +{ + if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount()) + return nullptr; + + sc::SparklineCell* pSparklineCell = aCol[nCol].GetSparklineCell(nRow); + if (!pSparklineCell) + return nullptr; + + std::shared_ptr<sc::Sparkline> pSparkline(pSparklineCell->getSparkline()); + assert(pSparkline); + + return pSparkline.get(); +} + +sc::Sparkline* ScTable::CreateSparkline(SCCOL nCol, SCROW nRow, std::shared_ptr<sc::SparklineGroup>& pSparklineGroup) +{ + if (!ValidCol(nCol)) + return nullptr; + + ScColumn& rColumn = CreateColumnIfNotExists(nCol); + + std::shared_ptr<sc::Sparkline> pSparkline(new sc::Sparkline(nCol, nRow, pSparklineGroup)); + maSparklineList.addSparkline(pSparkline); + rColumn.CreateSparklineCell(nRow, pSparkline); + return pSparkline.get(); +} + +sc::SparklineList& ScTable::GetSparklineList() +{ + return maSparklineList; +} + +// Notes + std::unique_ptr<ScPostIt> ScTable::ReleaseNote( SCCOL nCol, SCROW nRow ) { if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount()) commit fd7698f4c97a9aea69788d28d38bd90a96f62fac Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Mon Feb 28 15:35:48 2022 +0900 Commit: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> CommitDate: Fri Mar 4 20:54:33 2022 +0900 sc: support sparkline bar colors, add markers to sparkline lines Change-Id: I705a7f57cc4d6544ecb35a5f93c18a27056b9944 diff --git a/sc/source/ui/view/output.cxx b/sc/source/ui/view/output.cxx index 213f4c1cc69b..c504cd7f32c0 100644 --- a/sc/source/ui/view/output.cxx +++ b/sc/source/ui/view/output.cxx @@ -2316,33 +2316,121 @@ void ScOutputData::DrawChangeTrack() namespace { +struct SparklineMarker +{ + basegfx::B2DPolygon maPolygon; + Color maColor; +}; + +void createMarker(std::vector<SparklineMarker> & rMarkers, double x, double y, Color const & rColor) +{ + auto & rMarker = rMarkers.emplace_back(); + basegfx::B2DRectangle aRectangle(x - 2, y - 2, x + 2, y + 2); + rMarker.maPolygon = basegfx::utils::createPolygonFromRect(aRectangle); + rMarker.maColor = rColor; +} + void drawLine(vcl::RenderContext& rRenderContext, tools::Rectangle const & rRectangle, - std::vector<double> const & rValues, double nMin, double nMax) + std::vector<double> const & rValues, double nMin, double nMax, + std::shared_ptr<sc::SparklineGroup> const & pSparklineGroup) { basegfx::B2DPolygon aPolygon; double numebrOfSteps = rValues.size() - 1; double xStep = 0; double nDelta = nMax - nMin; - for (double aValue : rValues) + std::vector<SparklineMarker> aMarkers; + sal_Int64 nValueIndex = 0; + sal_Int64 nValuesSize = rValues.size(); + + for (double nValue : rValues) { - double nP = (aValue - nMin) / nDelta; + double nP = (nValue - nMin) / nDelta; double x = rRectangle.GetWidth() * (xStep / numebrOfSteps); double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nP; aPolygon.append({ x, y } ); + + if (pSparklineGroup->m_bFirst && nValueIndex == 0) + { + createMarker(aMarkers, x, y, pSparklineGroup->m_aColorFirst); + } + else if (pSparklineGroup->m_bLast && nValueIndex == (nValuesSize - 1)) + { + createMarker(aMarkers, x, y, pSparklineGroup->m_aColorLast); + } + else if (pSparklineGroup->m_bHigh && nValue == nMax) + { + createMarker(aMarkers, x, y, pSparklineGroup->m_aColorHigh); + } + else if (pSparklineGroup->m_bLow && nValue == nMin) + { + createMarker(aMarkers, x, y, pSparklineGroup->m_aColorLow); + } + else if (pSparklineGroup->m_bNegative && nValue < 0.0) + { + createMarker(aMarkers, x, y, pSparklineGroup->m_aColorNegative); + } + xStep++; + nValueIndex++; } basegfx::B2DHomMatrix aMatrix; aMatrix.translate(rRectangle.Left(), rRectangle.Top()); aPolygon.transform(aMatrix); + rRenderContext.SetLineColor(pSparklineGroup->m_aColorSeries); rRenderContext.DrawPolyLine(aPolygon); + + for (auto const & rMarker : aMarkers) + { + rRenderContext.SetLineColor(rMarker.maColor); + rRenderContext.SetFillColor(rMarker.maColor); + aPolygon = rMarker.maPolygon; + aPolygon.transform(aMatrix); + rRenderContext.DrawPolygon(aPolygon); + } +} + +void setFillAndLineColor(vcl::RenderContext& rRenderContext, std::shared_ptr<sc::SparklineGroup> const & pSparklineGroup, + double nValue, sal_Int64 nValueIndex, sal_Int64 nValuesSize, double nMin, double nMax) +{ + if (pSparklineGroup->m_bFirst && nValueIndex == 0) + { + rRenderContext.SetLineColor(pSparklineGroup->m_aColorFirst); + rRenderContext.SetFillColor(pSparklineGroup->m_aColorFirst); + } + else if (pSparklineGroup->m_bLast && nValueIndex == (nValuesSize - 1)) + { + rRenderContext.SetLineColor(pSparklineGroup->m_aColorLast); + rRenderContext.SetFillColor(pSparklineGroup->m_aColorLast); + } + else if (pSparklineGroup->m_bHigh && nValue == nMax) + { + rRenderContext.SetLineColor(pSparklineGroup->m_aColorHigh); + rRenderContext.SetFillColor(pSparklineGroup->m_aColorHigh); + } + else if (pSparklineGroup->m_bLow && nValue == nMin) + { + rRenderContext.SetLineColor(pSparklineGroup->m_aColorLow); + rRenderContext.SetFillColor(pSparklineGroup->m_aColorLow); + } + else if (pSparklineGroup->m_bNegative && nValue < 0.0) + { + rRenderContext.SetLineColor(pSparklineGroup->m_aColorNegative); + rRenderContext.SetFillColor(pSparklineGroup->m_aColorNegative); + } + else + { + rRenderContext.SetLineColor(pSparklineGroup->m_aColorSeries); + rRenderContext.SetFillColor(pSparklineGroup->m_aColorSeries); + } } void drawColumn(vcl::RenderContext& rRenderContext, tools::Rectangle const & rRectangle, - std::vector<double> const & rValues, double nMin, double nMax) + std::vector<double> const & rValues, double nMin, double nMax, + std::shared_ptr<sc::SparklineGroup> const & pSparklineGroup) { basegfx::B2DPolygon aPolygon; @@ -2359,11 +2447,15 @@ void drawColumn(vcl::RenderContext& rRenderContext, tools::Rectangle const & rRe else nZeroPosition = rRectangle.GetHeight(); - for (double aValue : rValues) + sal_Int64 nValueIndex = 0; + + for (double nValue : rValues) { - if (aValue != 0.0) + if (nValue != 0.0) { - double nP = (aValue - nMin) / nDelta; + setFillAndLineColor(rRenderContext, pSparklineGroup, nValue, nValueIndex, sal_Int64(rValues.size()), nMax, nMin); + + double nP = (nValue - nMin) / nDelta; double x = rRectangle.GetWidth() * (xStep / numberOfSteps); double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nP; @@ -2376,6 +2468,7 @@ void drawColumn(vcl::RenderContext& rRenderContext, tools::Rectangle const & rRe rRenderContext.DrawPolygon(aPolygon); } xStep++; + nValueIndex++; } } @@ -2391,9 +2484,6 @@ void drawSparkline(sc::Sparkline* pSparkline, vcl::RenderContext& rRenderContext rRenderContext.SetAntialiasing(AntialiasingFlags::Enable); - rRenderContext.SetLineColor(pSparklineGroup->m_aColorSeries); - rRenderContext.SetFillColor(pSparklineGroup->m_aColorSeries); - ScRange aRange = rRangeList[0]; std::vector<double> aValues; @@ -2434,7 +2524,7 @@ void drawSparkline(sc::Sparkline* pSparkline, vcl::RenderContext& rRenderContext if (pSparklineGroup->m_eType == sc::SparklineType::Column) { - drawColumn(rRenderContext, rRectangle, aValues, nMin, nMax); + drawColumn(rRenderContext, rRectangle, aValues, nMin, nMax, pSparklineGroup); } else if (pSparklineGroup->m_eType == sc::SparklineType::Stacked) { @@ -2444,11 +2534,11 @@ void drawSparkline(sc::Sparkline* pSparkline, vcl::RenderContext& rRenderContext if (rValue != 0.0) rValue = rValue > 0.0 ? 1.0 : -1.0; } - drawColumn(rRenderContext, rRectangle, aValues, -1, 1); + drawColumn(rRenderContext, rRectangle, aValues, -1, 1, pSparklineGroup); } else if (pSparklineGroup->m_eType == sc::SparklineType::Line) { - drawLine(rRenderContext, rRectangle, aValues, nMin, nMax); + drawLine(rRenderContext, rRectangle, aValues, nMin, nMax, pSparklineGroup); } } } // end anonymous namespace commit e0b8cf9dedd98165909cd5d93e490e77b1fa5497 Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Thu Feb 24 18:04:37 2022 +0900 Commit: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> CommitDate: Fri Mar 4 20:54:33 2022 +0900 sc: first simple test for Sparklines Change-Id: I513571de398be419074d54c5861374effae07709 diff --git a/sc/CppunitTest_sc_sparkline_test.mk b/sc/CppunitTest_sc_sparkline_test.mk new file mode 100644 index 000000000000..edf7a3cac7da --- /dev/null +++ b/sc/CppunitTest_sc_sparkline_test.mk @@ -0,0 +1,60 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitTest,sc_sparkline_test)) + +$(eval $(call gb_CppunitTest_use_common_precompiled_header,sc_sparkline_test)) + +$(eval $(call gb_CppunitTest_add_exception_objects,sc_sparkline_test, \ + sc/qa/extras/SparklineTest \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,sc_sparkline_test, \ + basegfx \ + comphelper \ + cppu \ + cppuhelper \ + sal \ + salhelper \ + sax \ + sc \ + scqahelper \ + sfx \ + subsequenttest \ + test \ + tl \ + unotest \ + vcl \ +)) + +$(eval $(call gb_CppunitTest_set_include,sc_sparkline_test,\ + -I$(SRCDIR)/sc/source/ui/inc \ + -I$(SRCDIR)/sc/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_CppunitTest_use_api,sc_sparkline_test,\ + offapi \ + udkapi \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,sc_sparkline_test)) + +$(eval $(call gb_CppunitTest_use_ure,sc_sparkline_test)) +$(eval $(call gb_CppunitTest_use_vcl,sc_sparkline_test)) + +$(eval $(call gb_CppunitTest_use_rdb,sc_sparkline_test,services)) + +$(eval $(call gb_CppunitTest_use_components,sc_sparkline_test)) + +$(eval $(call gb_CppunitTest_use_configuration,sc_sparkline_test)) + +# vim: set noet sw=4 ts=4: diff --git a/sc/Module_sc.mk b/sc/Module_sc.mk index 5179c5fdb292..4dcefc694ea7 100644 --- a/sc/Module_sc.mk +++ b/sc/Module_sc.mk @@ -209,6 +209,7 @@ $(eval $(call gb_Module_add_subsequentcheck_targets,sc,\ CppunitTest_sc_sheetlinkobj \ CppunitTest_sc_sheetlinksobj \ CppunitTest_sc_sortdescriptorbaseobj \ + CppunitTest_sc_sparkline_test \ CppunitTest_sc_spreadsheetsettings \ CppunitTest_sc_spreadsheetsettingsobj \ CppunitTest_sc_styleobj \ diff --git a/sc/qa/extras/SparklineTest.cxx b/sc/qa/extras/SparklineTest.cxx new file mode 100644 index 000000000000..122a6b23f5be --- /dev/null +++ b/sc/qa/extras/SparklineTest.cxx @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <test/calc_unoapi_test.hxx> + +#include <com/sun/star/lang/XComponent.hpp> +#include <docsh.hxx> +#include <Sparkline.hxx> + +using namespace css; + +namespace sc_apitest +{ +class SparklineTest : public CalcUnoApiTest +{ + uno::Reference<lang::XComponent> mxComponent; + +public: + SparklineTest(); + + void testSparklines(); + + CPPUNIT_TEST_SUITE(SparklineTest); + CPPUNIT_TEST(testSparklines); + CPPUNIT_TEST_SUITE_END(); +}; + +SparklineTest::SparklineTest() + : CalcUnoApiTest("sc/qa/extras/testdocuments") +{ +} + +void SparklineTest::testSparklines() +{ + OUString aFileURL; + createFileURL(u"Sparklines.xlsx", aFileURL); + mxComponent = loadFromDesktop(aFileURL); + + SfxObjectShell* pFoundShell = SfxObjectShell::GetShellFromComponent(mxComponent); + CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pFoundShell); + ScDocShellRef xDocSh = dynamic_cast<ScDocShell*>(pFoundShell); + CPPUNIT_ASSERT(xDocSh); + + ScDocument& rDocument = xDocSh->GetDocument(); + // Sparkline at Sheet1:A2 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 1, 0)); // A2 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); + + CPPUNIT_ASSERT_EQUAL(Color(0x376092), pSparklineGroup->m_aColorSeries); + CPPUNIT_ASSERT_EQUAL(Color(0x00b050), pSparklineGroup->m_aColorNegative); + CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorAxis); + CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorMarkers); + CPPUNIT_ASSERT_EQUAL(Color(0x7030a0), pSparklineGroup->m_aColorFirst); + CPPUNIT_ASSERT_EQUAL(Color(0xff0000), pSparklineGroup->m_aColorLast); + CPPUNIT_ASSERT_EQUAL(Color(0x92d050), pSparklineGroup->m_aColorHigh); + CPPUNIT_ASSERT_EQUAL(Color(0x00b0f0), pSparklineGroup->m_aColorLow); + + CPPUNIT_ASSERT_EQUAL(1.0, pSparklineGroup->m_fLineWeight); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDateAxis); + CPPUNIT_ASSERT_EQUAL(sc::DisplayEmptyCellAs::Gap, pSparklineGroup->m_eDisplayEmptyCellsAs); + + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bMarkers); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bHigh); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLow); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bFirst); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLast); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bNegative); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bDisplayXAxis); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayHidden); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bRightToLeft); + + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMax)); + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMin)); + } + // Sparkline at Sheet1:A3 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 2, 0)); // A3 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Column, pSparklineGroup->m_eType); + + CPPUNIT_ASSERT_EQUAL(Color(0x376092), pSparklineGroup->m_aColorSeries); + CPPUNIT_ASSERT_EQUAL(Color(0xff0000), pSparklineGroup->m_aColorNegative); + CPPUNIT_ASSERT_EQUAL(Color(0x000000), pSparklineGroup->m_aColorAxis); + CPPUNIT_ASSERT_EQUAL(Color(0xd00000), pSparklineGroup->m_aColorMarkers); + CPPUNIT_ASSERT_EQUAL(Color(0x92d050), pSparklineGroup->m_aColorFirst); + CPPUNIT_ASSERT_EQUAL(Color(0x00b0f0), pSparklineGroup->m_aColorLast); + CPPUNIT_ASSERT_EQUAL(Color(0x7030a0), pSparklineGroup->m_aColorHigh); + CPPUNIT_ASSERT_EQUAL(Color(0xffc000), pSparklineGroup->m_aColorLow); + + CPPUNIT_ASSERT_EQUAL(0.75, pSparklineGroup->m_fLineWeight); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDateAxis); + CPPUNIT_ASSERT_EQUAL(sc::DisplayEmptyCellAs::Gap, pSparklineGroup->m_eDisplayEmptyCellsAs); + + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bMarkers); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bHigh); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLow); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bFirst); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bLast); + CPPUNIT_ASSERT_EQUAL(true, pSparklineGroup->m_bNegative); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayXAxis); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bDisplayHidden); + CPPUNIT_ASSERT_EQUAL(false, pSparklineGroup->m_bRightToLeft); + + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMax)); + CPPUNIT_ASSERT_EQUAL(false, bool(pSparklineGroup->m_aManualMin)); + } + // Sparkline at Sheet2:B1 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 0, 1)); //B1 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Column, pSparklineGroup->m_eType); + } + // Sparkline at Sheet2:B2 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 1, 1)); //B2 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); + } + // Sparkline at Sheet2:B2 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(1, 1, 1)); //B2 + CPPUNIT_ASSERT(pSparkline); + auto pSparklineGroup = pSparkline->getSparklineGroup(); + CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Line, pSparklineGroup->m_eType); + } + // Sparkline doesn't exists at A4 + { + sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 3, 0)); //A4 + CPPUNIT_ASSERT(!pSparkline); + } + + closeDocument(mxComponent); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(SparklineTest); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/extras/testdocuments/Sparklines.xlsx b/sc/qa/extras/testdocuments/Sparklines.xlsx new file mode 100644 index 000000000000..3725841603be Binary files /dev/null and b/sc/qa/extras/testdocuments/Sparklines.xlsx differ commit bc536879a4d10ea3ab10b855e01586ae07d950ac Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Fri Feb 25 11:57:15 2022 +0900 Commit: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> CommitDate: Fri Mar 4 20:54:33 2022 +0900 sc: use enums instead strings for some SparklineGroup props. Change-Id: I082c0a7693c97a67bc4972398224bce4bdae85eb diff --git a/sc/inc/SparklineGroup.hxx b/sc/inc/SparklineGroup.hxx index abc1cc71333c..32e4b757378f 100644 --- a/sc/inc/SparklineGroup.hxx +++ b/sc/inc/SparklineGroup.hxx @@ -16,6 +16,27 @@ namespace sc { +enum class SparklineType +{ + Line, + Column, + Stacked +}; + +enum class AxisType +{ + Individual, + Group, + Custom +}; + +enum class DisplayEmptyCellAs +{ + Span, + Gap, + Zero +}; + class SC_DLLPUBLIC SparklineGroup { public: @@ -28,16 +49,16 @@ public: Color m_aColorHigh; Color m_aColorLow; - OUString m_sMinAxisType; // individual, group, custom - OUString m_sMaxAxisType; + AxisType m_eMinAxisType; + AxisType m_eMaxAxisType; double m_fLineWeight; // In pt - OUString m_sType; // line, column, stacked + SparklineType m_eType; bool m_bDateAxis; - OUString m_sDisplayEmptyCellsAs; // span, gap, zero + DisplayEmptyCellAs m_eDisplayEmptyCellsAs; // span, gap, zero bool m_bMarkers; bool m_bHigh; diff --git a/sc/source/filter/oox/SparklineFragment.cxx b/sc/source/filter/oox/SparklineFragment.cxx index bfc5a259a883..b4c5089dbfa1 100644 --- a/sc/source/filter/oox/SparklineFragment.cxx +++ b/sc/source/filter/oox/SparklineFragment.cxx @@ -90,6 +90,33 @@ void addColorsToSparklineGroup(sc::SparklineGroup& rSparklineGroup, sal_Int32 nE } } +sc::SparklineType parseSparklineType(OUString const& rString) +{ + if (rString == "column") + return sc::SparklineType::Column; + else if (rString == "stacked") + return sc::SparklineType::Stacked; + return sc::SparklineType::Line; +} + +sc::DisplayEmptyCellAs parseDisplayEmptyCellAs(OUString const& rString) +{ + if (rString == "span") + return sc::DisplayEmptyCellAs::Span; + else if (rString == "gap") + return sc::DisplayEmptyCellAs::Gap; + return sc::DisplayEmptyCellAs::Zero; +} + +sc::AxisType parseAxisType(OUString const& rString) +{ + if (rString == "group") + return sc::AxisType::Group; + else if (rString == "custom") + return sc::AxisType::Custom; + return sc::AxisType::Individual; +} + void addAttributesToSparklineGroup(sc::SparklineGroup& rSparklineGroup, const AttributeList& rAttribs) { @@ -98,11 +125,13 @@ void addAttributesToSparklineGroup(sc::SparklineGroup& rSparklineGroup, rSparklineGroup.m_fLineWeight = rAttribs.getDouble(XML_lineWeight, 0.75); - rSparklineGroup.m_sType = rAttribs.getString(XML_type, "line"); + OUString sType = rAttribs.getString(XML_type, "line"); + rSparklineGroup.m_eType = parseSparklineType(sType); rSparklineGroup.m_bDateAxis = rAttribs.getBool(XML_dateAxis, false); - rSparklineGroup.m_sDisplayEmptyCellsAs = rAttribs.getString(XML_displayEmptyCellsAs, "zero"); + OUString sDisplayEmptyCellsAs = rAttribs.getString(XML_displayEmptyCellsAs, "zero"); + rSparklineGroup.m_eDisplayEmptyCellsAs = parseDisplayEmptyCellAs(sDisplayEmptyCellsAs); rSparklineGroup.m_bMarkers = rAttribs.getBool(XML_markers, false); rSparklineGroup.m_bHigh = rAttribs.getBool(XML_high, false); @@ -113,16 +142,19 @@ void addAttributesToSparklineGroup(sc::SparklineGroup& rSparklineGroup, rSparklineGroup.m_bDisplayXAxis = rAttribs.getBool(XML_displayXAxis, false); rSparklineGroup.m_bDisplayHidden = rAttribs.getBool(XML_displayHidden, false); - rSparklineGroup.m_sMinAxisType = rAttribs.getString(XML_minAxisType, "individual"); - rSparklineGroup.m_sMaxAxisType = rAttribs.getString(XML_maxAxisType, "individual"); + OUString sMinAxisType = rAttribs.getString(XML_minAxisType, "individual"); + rSparklineGroup.m_eMinAxisType = parseAxisType(sMinAxisType); + + OUString sMaxAxisType = rAttribs.getString(XML_maxAxisType, "individual"); + rSparklineGroup.m_eMaxAxisType = parseAxisType(sMaxAxisType); rSparklineGroup.m_bRightToLeft = rAttribs.getBool(XML_rightToLeft, false); rSparklineGroup.m_sUID = rAttribs.getString(XML_uid, OUString()); - if (rSparklineGroup.m_sMaxAxisType == "custom") + if (rSparklineGroup.m_eMaxAxisType == sc::AxisType::Custom) rSparklineGroup.m_aManualMax = oManualMax.get(); - if (rSparklineGroup.m_sMinAxisType == "custom") + if (rSparklineGroup.m_eMinAxisType == sc::AxisType::Custom) rSparklineGroup.m_aManualMin = oManualMin.get(); } diff --git a/sc/source/ui/view/output.cxx b/sc/source/ui/view/output.cxx index 32879a8aa153..213f4c1cc69b 100644 --- a/sc/source/ui/view/output.cxx +++ b/sc/source/ui/view/output.cxx @@ -2432,12 +2432,13 @@ void drawSparkline(sc::Sparkline* pSparkline, vcl::RenderContext& rRenderContext } } - if (pSparklineGroup->m_sType == "column") + if (pSparklineGroup->m_eType == sc::SparklineType::Column) { drawColumn(rRenderContext, rRectangle, aValues, nMin, nMax); } - else if (pSparklineGroup->m_sType == "stacked") + else if (pSparklineGroup->m_eType == sc::SparklineType::Stacked) { + // transform the data to 1, -1 for (auto & rValue : aValues) { if (rValue != 0.0) @@ -2445,7 +2446,7 @@ void drawSparkline(sc::Sparkline* pSparkline, vcl::RenderContext& rRenderContext } drawColumn(rRenderContext, rRectangle, aValues, -1, 1); } - else if (pSparklineGroup->m_sType == "line") + else if (pSparklineGroup->m_eType == sc::SparklineType::Line) { drawLine(rRenderContext, rRectangle, aValues, nMin, nMax); } commit 733ed6914d5cbfe6520f2b97e07470975873f65c Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Thu Feb 24 17:43:00 2022 +0900 Commit: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> CommitDate: Fri Mar 4 20:54:33 2022 +0900 sc: initial code to draw sparkline into a cell Change-Id: I82861f4210a24f57573f0ec96489e86ab168677b diff --git a/sc/source/ui/inc/output.hxx b/sc/source/ui/inc/output.hxx index 61873156d264..d44f7052589b 100644 --- a/sc/source/ui/inc/output.hxx +++ b/sc/source/ui/inc/output.hxx @@ -382,6 +382,7 @@ public: void DrawNoteMarks(vcl::RenderContext& rRenderContext); void AddPDFNotes(); + void DrawSparklines(vcl::RenderContext& rRenderContext); }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/gridwin4.cxx b/sc/source/ui/view/gridwin4.cxx index 9b8d5c634b25..50ffcdf67709 100644 --- a/sc/source/ui/view/gridwin4.cxx +++ b/sc/source/ui/view/gridwin4.cxx @@ -903,6 +903,8 @@ void ScGridWindow::DrawContent(OutputDevice &rDevice, const ScTableInfo& rTableI aOutputData.DrawShadow(); aOutputData.DrawFrame(*pContentDev); + aOutputData.DrawSparklines(*pContentDev); + // Show Note Mark if ( rOpts.GetOption( VOPT_NOTES ) ) aOutputData.DrawNoteMarks(*pContentDev); diff --git a/sc/source/ui/view/output.cxx b/sc/source/ui/view/output.cxx index 045066e96689..32879a8aa153 100644 --- a/sc/source/ui/view/output.cxx +++ b/sc/source/ui/view/output.cxx @@ -30,7 +30,6 @@ #include <svx/framelinkarray.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <drawinglayer/processor2d/baseprocessor2d.hxx> -#include <basegfx/matrix/b2dhommatrix.hxx> #include <drawinglayer/processor2d/processorfromoutputdevice.hxx> #include <vcl/lineinfo.hxx> #include <vcl/gradient.hxx> @@ -39,6 +38,10 @@ #include <sal/log.hxx> #include <comphelper/lok.hxx> #include <o3tl/unit_conversion.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2drectangle.hxx> #include <output.hxx> #include <document.hxx> @@ -60,6 +63,7 @@ #include <detfunc.hxx> #include <colorscale.hxx> +#include <Sparkline.hxx> #include <math.h> #include <memory> @@ -2309,6 +2313,200 @@ void ScOutputData::DrawChangeTrack() } } +namespace +{ + +void drawLine(vcl::RenderContext& rRenderContext, tools::Rectangle const & rRectangle, + std::vector<double> const & rValues, double nMin, double nMax) +{ + basegfx::B2DPolygon aPolygon; + double numebrOfSteps = rValues.size() - 1; + double xStep = 0; + double nDelta = nMax - nMin; + + for (double aValue : rValues) + { + double nP = (aValue - nMin) / nDelta; + double x = rRectangle.GetWidth() * (xStep / numebrOfSteps); + double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nP; + + aPolygon.append({ x, y } ); + xStep++; + } + + basegfx::B2DHomMatrix aMatrix; + aMatrix.translate(rRectangle.Left(), rRectangle.Top()); + aPolygon.transform(aMatrix); + + rRenderContext.DrawPolyLine(aPolygon); +} + +void drawColumn(vcl::RenderContext& rRenderContext, tools::Rectangle const & rRectangle, + std::vector<double> const & rValues, double nMin, double nMax) +{ + basegfx::B2DPolygon aPolygon; + + double xStep = 0; + double numberOfSteps = rValues.size(); + double nDelta = nMax - nMin; + + double nColumnSize = rRectangle.GetWidth() / numberOfSteps; + + double nZero = (0 - nMin) / nDelta; + double nZeroPosition; + if (nZero >= 0) + nZeroPosition = rRectangle.GetHeight() - rRectangle.GetHeight() * nZero; + else + nZeroPosition = rRectangle.GetHeight(); + + for (double aValue : rValues) + { + if (aValue != 0.0) + { + double nP = (aValue - nMin) / nDelta; + double x = rRectangle.GetWidth() * (xStep / numberOfSteps); + double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nP; + + basegfx::B2DRectangle aRectangle(x, y, x + nColumnSize, nZeroPosition); + aPolygon = basegfx::utils::createPolygonFromRect(aRectangle); + + basegfx::B2DHomMatrix aMatrix; + aMatrix.translate(rRectangle.Left(), rRectangle.Top()); + aPolygon.transform(aMatrix); + rRenderContext.DrawPolygon(aPolygon); + } + xStep++; + } +} + +void drawSparkline(sc::Sparkline* pSparkline, vcl::RenderContext& rRenderContext, ScDocument* pDocument, + tools::Rectangle const & rRectangle) +{ + auto const & rRangeList = pSparkline->getInputRange(); + + if (rRangeList.empty()) + return; + + auto pSparklineGroup = pSparkline->getSparklineGroup(); + + rRenderContext.SetAntialiasing(AntialiasingFlags::Enable); + + rRenderContext.SetLineColor(pSparklineGroup->m_aColorSeries); + rRenderContext.SetFillColor(pSparklineGroup->m_aColorSeries); + + ScRange aRange = rRangeList[0]; + + std::vector<double> aValues; + + double nMin = std::numeric_limits<double>::max(); + double nMax = std::numeric_limits<double>::min(); + + if (aRange.aStart.Row() == aRange.aEnd.Row()) + { + ScAddress aAddress = aRange.aStart; + + while (aAddress.Col() <= aRange.aEnd.Col()) + { + double fCellValue = pDocument->GetValue(aAddress); + aValues.push_back(fCellValue); + if (fCellValue < nMin) + nMin = fCellValue; + if (fCellValue > nMax) + nMax = fCellValue; + aAddress.IncCol(); + } + } + else if (aRange.aStart.Col() == aRange.aEnd.Col()) + { + ScAddress aAddress = aRange.aStart; + + while (aAddress.Row() <= aRange.aEnd.Row()) + { + double fCellValue = pDocument->GetValue(aAddress); + aValues.push_back(fCellValue); + if (fCellValue < nMin) + nMin = fCellValue; + if (fCellValue > nMax) + nMax = fCellValue; + aAddress.IncRow(); + } + } + + if (pSparklineGroup->m_sType == "column") + { + drawColumn(rRenderContext, rRectangle, aValues, nMin, nMax); + } + else if (pSparklineGroup->m_sType == "stacked") + { + for (auto & rValue : aValues) + { + if (rValue != 0.0) + rValue = rValue > 0.0 ? 1.0 : -1.0; + } + drawColumn(rRenderContext, rRectangle, aValues, -1, 1); + } + else if (pSparklineGroup->m_sType == "line") + { + drawLine(rRenderContext, rRectangle, aValues, nMin, nMax); + } +} +} // end anonymous namespace + +void ScOutputData::DrawSparklines(vcl::RenderContext& rRenderContext) +{ + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + nInitPosX += nMirrorW - 1; // always in pixels + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + if ( pThisRowInfo->bChanged ) + { + tools::Long nPosX = nInitPosX; + for (SCCOL nX=nX1; nX<=nX2; nX++) + { + CellInfo* pInfo = &pThisRowInfo->cellInfo(nX); + bool bIsMerged = false; + + if ( nX==nX1 && pInfo->bHOverlapped && !pInfo->bVOverlapped ) + { + // find start of merged cell + bIsMerged = true; + SCROW nY = pRowInfo[nArrY].nRowNo; + SCCOL nMergeX = nX; + SCROW nMergeY = nY; + mpDoc->ExtendOverlapped( nMergeX, nMergeY, nX, nY, nTab ); + } + + sc::Sparkline* pSparkline = nullptr; + ScAddress aCurrentAddress(nX, pRowInfo[nArrY].nRowNo, nTab); + + if (!mpDoc->ColHidden(nX, nTab) && (pSparkline = mpDoc->GetSparkline(aCurrentAddress)) + && (bIsMerged || (!pInfo->bHOverlapped && !pInfo->bVOverlapped))) + { + constexpr tools::Long constMarginX = 6; + constexpr tools::Long constMarginY = 3; + + const tools::Long nWidth = pRowInfo[0].cellInfo(nX).nWidth; + const tools::Long nHeight = pThisRowInfo->nHeight; + + Point aPoint(nPosX + constMarginX , nPosY + constMarginY); + Size aSize(nWidth - 2 * constMarginX, nHeight - 2 * constMarginY); + + drawSparkline(pSparkline, rRenderContext, mpDoc, tools::Rectangle(aPoint, aSize)); + } + + nPosX += pRowInfo[0].cellInfo(nX).nWidth * nLayoutSign; + } + } + nPosY += pThisRowInfo->nHeight; + } + +} + //TODO: moggi Need to check if this can't be written simpler void ScOutputData::DrawNoteMarks(vcl::RenderContext& rRenderContext) { commit 73fc5112c3fbf33a40342b2c86988acf7e468a5e Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Wed Feb 23 10:06:20 2022 +0900 Commit: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> CommitDate: Fri Mar 4 20:54:32 2022 +0900 sc: write the OOXML Sparkline props. to the model Change-Id: I09ecf560f1870624cb984722bdb8ee306d839dfe diff --git a/sc/inc/SparklineGroup.hxx b/sc/inc/SparklineGroup.hxx index 250488671190..abc1cc71333c 100644 --- a/sc/inc/SparklineGroup.hxx +++ b/sc/inc/SparklineGroup.hxx @@ -11,6 +11,7 @@ #pragma once #include "scdllapi.h" +#include <tools/color.hxx> #include <optional> namespace sc @@ -18,6 +19,40 @@ namespace sc class SC_DLLPUBLIC SparklineGroup { public: + Color m_aColorSeries; + Color m_aColorNegative; + Color m_aColorAxis; + Color m_aColorMarkers; + Color m_aColorFirst; + Color m_aColorLast; + Color m_aColorHigh; + Color m_aColorLow; + + OUString m_sMinAxisType; // individual, group, custom + OUString m_sMaxAxisType; + + double m_fLineWeight; // In pt + + OUString m_sType; // line, column, stacked + + bool m_bDateAxis; + + OUString m_sDisplayEmptyCellsAs; // span, gap, zero + + bool m_bMarkers; + bool m_bHigh; + bool m_bLow; + bool m_bFirst; + bool m_bLast; + bool m_bNegative; + bool m_bDisplayXAxis; + bool m_bDisplayHidden; + bool m_bRightToLeft; + + std::optional<double> m_aManualMax; + std::optional<double> m_aManualMin; + OUString m_sUID; + SparklineGroup() {} SparklineGroup(const SparklineGroup&) = delete; diff --git a/sc/source/filter/inc/SparklineFragment.hxx b/sc/source/filter/inc/SparklineFragment.hxx index 36a7b5ca9f05..de1f9ae7ebda 100644 --- a/sc/source/filter/inc/SparklineFragment.hxx +++ b/sc/source/filter/inc/SparklineFragment.hxx @@ -11,10 +11,10 @@ #include "excelhandlers.hxx" #include <oox/core/contexthandler.hxx> +#include <SparklineGroup.hxx> #include <vector> #include <memory> -#include <optional> namespace oox { @@ -36,40 +36,15 @@ class SparklineGroup private: std::vector<Sparkline> m_aSparklines; -public: - Color m_aColorSeries; - Color m_aColorNegative; - Color m_aColorAxis; - Color m_aColorMarkers; - Color m_aColorFirst; - Color m_aColorLast; - Color m_aColorHigh; - Color m_aColorLow; - - OUString m_sMinAxisType; // individual, group, custom - OUString m_sMaxAxisType; - - double m_fLineWeight; // In pt - - OUString m_sType; // line, column, stacked + std::shared_ptr<sc::SparklineGroup> m_pSparklineGroup; - bool m_bDateAxis; - - OUString m_sDisplayEmptyCellsAs; // span, gap, zero - - bool m_bMarkers; - bool m_bHigh; - bool m_bLow; - bool m_bFirst; - bool m_bLast; - bool m_bNegative; - bool m_bDisplayXAxis; - bool m_bDisplayHidden; - bool m_bRightToLeft; +public: + SparklineGroup() + : m_pSparklineGroup(new sc::SparklineGroup()) + { + } - std::optional<double> m_aManualMax; - std::optional<double> m_aManualMin; - OUString m_sUID; + std::shared_ptr<sc::SparklineGroup> getSparklineGroup() { return m_pSparklineGroup; } std::vector<Sparkline>& getSparklines() { return m_aSparklines; } }; @@ -87,6 +62,8 @@ public: void onStartElement(const AttributeList& rAttribs) override; void onCharacters(const OUString& rCharacters) override; void onEndElement() override; + + void insertSparkline(SparklineGroup& rSparklineGroup, Sparkline& rSparkline); }; } //namespace oox::xls diff --git a/sc/source/filter/oox/SparklineFragment.cxx b/sc/source/filter/oox/SparklineFragment.cxx index a743b886b7db..bfc5a259a883 100644 --- a/sc/source/filter/oox/SparklineFragment.cxx +++ b/sc/source/filter/oox/SparklineFragment.cxx @@ -15,6 +15,8 @@ #include <oox/helper/attributelist.hxx> #include <document.hxx> #include <rangeutl.hxx> +#include <Sparkline.hxx> +#include <themebuffer.hxx> using ::oox::core::ContextHandlerRef; @@ -22,51 +24,74 @@ namespace oox::xls { namespace { -Color getColor(const AttributeList& rAttribs) +// TODO: deduplicate with importOOXColor +::Color getColor(const AttributeList& rAttribs, ThemeBuffer const& rThemeBuffer) { if (rAttribs.hasAttribute(XML_rgb)) { - return Color(ColorTransparency, - rAttribs.getIntegerHex(XML_rgb, sal_Int32(API_RGB_TRANSPARENT))); + return ::Color(ColorAlpha, rAttribs.getIntegerHex(XML_rgb, sal_Int32(API_RGB_TRANSPARENT))); } - return Color(); + else if (rAttribs.hasAttribute(XML_theme)) + { + sal_uInt32 nThemeIndex = rAttribs.getUnsigned(XML_theme, 0); + + // Excel has a bug in the mapping of index 0, 1, 2 and 3. + if (nThemeIndex == 0) + nThemeIndex = 1; + else if (nThemeIndex == 1) + nThemeIndex = 0; + else if (nThemeIndex == 2) + nThemeIndex = 3; + else if (nThemeIndex == 3) + nThemeIndex = 2; + + ::Color aColor = rThemeBuffer.getColorByIndex(nThemeIndex); + double nTint = rAttribs.getDouble(XML_tint, 0.0); + + if (nTint > 0.0) + aColor.ApplyTintOrShade(nTint * 10000); + return aColor; + } + + return ::Color(); } -void addColorsToSparklineGroup(SparklineGroup& rSparklineGroup, sal_Int32 nElement, - const AttributeList& rAttribs) +void addColorsToSparklineGroup(sc::SparklineGroup& rSparklineGroup, sal_Int32 nElement, + const AttributeList& rAttribs, ThemeBuffer& rThemeBuffer) { switch (nElement) { case XLS14_TOKEN(colorSeries): - rSparklineGroup.m_aColorSeries = getColor(rAttribs); + rSparklineGroup.m_aColorSeries = getColor(rAttribs, rThemeBuffer); break; case XLS14_TOKEN(colorNegative): - rSparklineGroup.m_aColorNegative = getColor(rAttribs); + rSparklineGroup.m_aColorNegative = getColor(rAttribs, rThemeBuffer); break; case XLS14_TOKEN(colorAxis): - rSparklineGroup.m_aColorAxis = getColor(rAttribs); + rSparklineGroup.m_aColorAxis = getColor(rAttribs, rThemeBuffer); break; case XLS14_TOKEN(colorMarkers): - rSparklineGroup.m_aColorMarkers = getColor(rAttribs); + rSparklineGroup.m_aColorMarkers = getColor(rAttribs, rThemeBuffer); break; case XLS14_TOKEN(colorFirst): - rSparklineGroup.m_aColorFirst = getColor(rAttribs); + rSparklineGroup.m_aColorFirst = getColor(rAttribs, rThemeBuffer); break; case XLS14_TOKEN(colorLast): - rSparklineGroup.m_aColorLast = getColor(rAttribs); + rSparklineGroup.m_aColorLast = getColor(rAttribs, rThemeBuffer); break; case XLS14_TOKEN(colorHigh): - rSparklineGroup.m_aColorHigh = getColor(rAttribs); + rSparklineGroup.m_aColorHigh = getColor(rAttribs, rThemeBuffer); break; case XLS14_TOKEN(colorLow): - rSparklineGroup.m_aColorLow = getColor(rAttribs); + rSparklineGroup.m_aColorLow = getColor(rAttribs, rThemeBuffer); break; default: break; } } -void addAttributesToSparklineGroup(SparklineGroup& rSparklineGroup, const AttributeList& rAttribs) +void addAttributesToSparklineGroup(sc::SparklineGroup& rSparklineGroup, + const AttributeList& rAttribs) { auto oManualMax = rAttribs.getDouble(XML_manualMax); auto oManualMin = rAttribs.getDouble(XML_manualMin); @@ -116,7 +141,7 @@ ContextHandlerRef SparklineGroupsContext::onCreateContext(sal_Int32 nElement, case XLS14_TOKEN(sparklineGroup): { auto& rLastGroup = m_aSparklineGroups.emplace_back(); - addAttributesToSparklineGroup(rLastGroup, rAttribs); + addAttributesToSparklineGroup(*rLastGroup.getSparklineGroup(), rAttribs); return this; } case XLS14_TOKEN(colorSeries): @@ -129,7 +154,8 @@ ContextHandlerRef SparklineGroupsContext::onCreateContext(sal_Int32 nElement, case XLS14_TOKEN(colorLow): { auto& rLastGroup = m_aSparklineGroups.back(); - addColorsToSparklineGroup(rLastGroup, nElement, rAttribs); + addColorsToSparklineGroup(*rLastGroup.getSparklineGroup(), nElement, rAttribs, + getTheme()); return this; } case XLS14_TOKEN(sparklines): @@ -155,7 +181,6 @@ void SparklineGroupsContext::onCharacters(const OUString& rChars) ScDocument& rDocument = getScDocument(); auto& rLastGroup = m_aSparklineGroups.back(); auto& rLastSparkline = rLastGroup.getSparklines().back(); - ScRangeList aRange; if (ScRangeStringConverter::GetRangeListFromString(aRange, rChars, rDocument, formula::FormulaGrammar::CONV_XL_OOX)) @@ -182,7 +207,32 @@ void SparklineGroupsContext::onCharacters(const OUString& rChars) } } -void SparklineGroupsContext::onEndElement() {} +void SparklineGroupsContext::onEndElement() +{ + if (getCurrentElement() == XLS14_TOKEN(sparklineGroup)) + { + auto& rLastGroup = m_aSparklineGroups.back(); + for (Sparkline& rSparkline : rLastGroup.getSparklines()) + { + insertSparkline(rLastGroup, rSparkline); + } + } +} + +void SparklineGroupsContext::insertSparkline(SparklineGroup& rSparklineGroup, Sparkline& rSparkline) +{ + auto& rDocument = getScDocument(); + if (rSparkline.m_aTargetRange.size() == 1) + { + auto& rRange = rSparkline.m_aTargetRange[0]; + if (rRange.aStart == rRange.aEnd) + { + auto pSparklineGroup = rSparklineGroup.getSparklineGroup(); + auto* pCreated = rDocument.CreateSparkline(rRange.aStart, pSparklineGroup); + pCreated->setInputRange(rSparkline.m_aInputRange); + } + } +} } //namespace oox::xls commit cdf4d1e25a8dfb84fcd53e0a73edb8dc6e42c67e Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Wed Feb 23 09:52:50 2022 +0900 Commit: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> CommitDate: Fri Mar 4 20:54:32 2022 +0900 sc: add Sparkline to the model as a column multi_type_vector Change-Id: Id309ded34bfa7a35c1be43f7c0543d88594e66ff diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk index 62ab22a797c4..11449217edd9 100644 --- a/sc/Library_sc.mk +++ b/sc/Library_sc.mk @@ -185,6 +185,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\ sc/source/core/data/sheetevents \ sc/source/core/data/simpleformulacalc \ sc/source/core/data/sortparam \ + sc/source/core/data/Sparkline \ sc/source/core/data/stlpool \ sc/source/core/data/stlsheet \ sc/source/core/data/subtotalparam \ diff --git a/sc/inc/Sparkline.hxx b/sc/inc/Sparkline.hxx new file mode 100644 index 000000000000..8969fa3bfd3e --- /dev/null +++ b/sc/inc/Sparkline.hxx @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#pragma once + +#include "scdllapi.h" +#include "SparklineGroup.hxx" +#include "rangelst.hxx" +#include <memory> + +namespace sc +{ +class SC_DLLPUBLIC Sparkline +{ +private: + ScRangeList m_aInputRange; + std::shared_ptr<SparklineGroup> m_pSparklineGroup; + +public: + Sparkline(std::shared_ptr<SparklineGroup>& pSparklineGroup); + + Sparkline(const Sparkline&) = delete; + Sparkline& operator=(const Sparkline&) = delete; + + void setInputRange(ScRangeList const& rInputRange) { m_aInputRange = rInputRange; } + ScRangeList const& getInputRange() { return m_aInputRange; } + + std::shared_ptr<SparklineGroup> const& getSparklineGroup() { return m_pSparklineGroup; } +}; + +} // end sc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/inc/SparklineGroup.hxx b/sc/inc/SparklineGroup.hxx new file mode 100644 index 000000000000..250488671190 --- /dev/null +++ b/sc/inc/SparklineGroup.hxx @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#pragma once + +#include "scdllapi.h" +#include <optional> + +namespace sc +{ +class SC_DLLPUBLIC SparklineGroup +{ +public: + SparklineGroup() {} + + SparklineGroup(const SparklineGroup&) = delete; + SparklineGroup& operator=(const SparklineGroup&) = delete; +}; + +} // end sc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/inc/column.hxx b/sc/inc/column.hxx index e23eac0fdab1..f4b603c5c721 100644 --- a/sc/inc/column.hxx +++ b/sc/inc/column.hxx @@ -65,6 +65,7 @@ class CompileFormulaContext; struct SetFormulaDirtyContext; enum class MatrixEdge; class ColumnIterator; +class Sparkline; } @@ -99,7 +100,7 @@ struct ScInterpreterContext; struct ScNeededSizeOptions { - const ScPatternAttr* pPattern; + const ScPatternAttr* pPattern; bool bFormula; bool bSkipMerged; bool bGetFont; @@ -126,6 +127,9 @@ class ScColumn // Cell values. sc::CellStoreType maCells; + // Sparklines + sc::SparklineStoreType maSparklines; + std::unique_ptr<ScAttrArray> pAttrArray; size_t mnBlkCountFormula; @@ -612,6 +616,10 @@ public: void BroadcastCells( const std::vector<SCROW>& rRows, SfxHintId nHint ); void BroadcastRows( SCROW nStartRow, SCROW nEndRow, SfxHintId nHint ); + // Spaklines + sc::Sparkline* GetSparkline(SCROW nRow); + void SetSparkline(SCROW nRow, std::unique_ptr<sc::Sparkline> pSparkline); + // cell notes ScPostIt* GetCellNote( SCROW nRow ); const ScPostIt* GetCellNote( SCROW nRow ) const; diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx index 4e8eab09ed5d..86bd4ebd02bf 100644 --- a/sc/inc/document.hxx +++ b/sc/inc/document.hxx @@ -101,6 +101,8 @@ class UpdatedRangeNames; class TableColumnBlockPositionSet; class ColumnIterator; class ExternalDataMapper; +class Sparkline; +class SparklineGroup; } @@ -1239,6 +1241,10 @@ public: */ sc::MultiDataCellState HasMultipleDataCells( const ScRange& rRange ) const; + /** Spaklines */ + SC_DLLPUBLIC sc::Sparkline* GetSparkline(ScAddress const & rPosition); + SC_DLLPUBLIC sc::Sparkline* CreateSparkline(ScAddress const & rPosition, std::shared_ptr<sc::SparklineGroup> & pSparklineGroup); + /** Notes **/ SC_DLLPUBLIC ScPostIt* GetNote(const ScAddress& rPos); SC_DLLPUBLIC ScPostIt* GetNote(SCCOL nCol, SCROW nRow, SCTAB nTab); @@ -1270,6 +1276,7 @@ public: SC_DLLPUBLIC void GetAllNoteEntries( std::vector<sc::NoteEntry>& rNotes ) const; SC_DLLPUBLIC void GetAllNoteEntries( SCTAB nTab, std::vector<sc::NoteEntry>& rNotes ) const; void GetNotesInRange( const ScRangeList& rRange, std::vector<sc::NoteEntry>& rNotes ) const; + bool ContainsNotesInRange( const ScRangeList& rRange ) const; SC_DLLPUBLIC void SetDrawPageSize(SCTAB nTab); diff --git a/sc/inc/mtvelements.hxx b/sc/inc/mtvelements.hxx index c74e1bc7d91b..6de0f3e7ec23 100644 --- a/sc/inc/mtvelements.hxx +++ b/sc/inc/mtvelements.hxx @@ -15,6 +15,7 @@ #include <editeng/editobj.hxx> #include "calcmacros.hxx" #include "postit.hxx" +#include "Sparkline.hxx" #include "celltextattr.hxx" #if DEBUG_COLUMN_STORAGE @@ -49,6 +50,7 @@ const mdds::mtv::element_t element_type_edittext = mdds::mtv::element_type_user_ const mdds::mtv::element_t element_type_formula = mdds::mtv::element_type_user_start + 4; const mdds::mtv::element_t element_type_cellnote = mdds::mtv::element_type_user_start + 5; +const mdds::mtv::element_t element_type_sparkline = mdds::mtv::element_type_user_start + 6; /// Mapped standard element types (for convenience). const mdds::mtv::element_t element_type_numeric = mdds::mtv::element_type_double; @@ -57,6 +59,7 @@ const mdds::mtv::element_t element_type_uint16 = mdds::mtv::element_type_uint16; /// Custom element blocks. +typedef mdds::mtv::noncopyable_managed_element_block<element_type_sparkline, sc::Sparkline> sparkline_block; typedef mdds::mtv::noncopyable_managed_element_block<element_type_cellnote, ScPostIt> cellnote_block; typedef mdds::mtv::noncopyable_managed_element_block<element_type_broadcaster, SvtBroadcaster> broadcaster_block; typedef mdds::mtv::default_element_block<element_type_celltextattr, CellTextAttr> celltextattr_block; @@ -68,8 +71,13 @@ typedef mdds::mtv::noncopyable_managed_element_block<element_type_formula, ScFor typedef mdds::mtv::double_element_block numeric_block; typedef mdds::mtv::uint16_element_block uint16_block; -/// This needs to be in the same namespace as CellTextAttr. +} // end sc namespace + +/// CAUTION! The following defines must be in the same namespace as the respective type. +/// For example sc types like sc::CellTextAttr, ScFormulaCell in global namespace. +namespace sc { MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(CellTextAttr, element_type_celltextattr, CellTextAttr(), celltextattr_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS_PTR(Sparkline, sc::element_type_sparkline, nullptr, sc::sparkline_block) } /// These need to be in global namespace just like their respective types are. @@ -79,9 +87,7 @@ MDDS_MTV_DEFINE_ELEMENT_CALLBACKS_PTR(ScFormulaCell, sc::element_type_formula, n MDDS_MTV_DEFINE_ELEMENT_CALLBACKS_PTR(EditTextObject, sc::element_type_edittext, nullptr, sc::edittext_block) namespace svl { - MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(SharedString, sc::element_type_string, SharedString(), sc::string_block) - } namespace sc { @@ -110,6 +116,10 @@ struct CellStoreTrait static constexpr mdds::mtv::lu_factor_t loop_unrolling = mdds::mtv::lu_factor_t::lu16; }; +/// Sparkline container +typedef mdds::mtv::custom_block_func1<sc::sparkline_block> CSparklineFunction; +typedef mdds::mtv::soa::multi_type_vector<CSparklineFunction> SparklineStoreType; + /// Cell note container typedef mdds::mtv::custom_block_func1<sc::cellnote_block> CNoteFunc; typedef mdds::mtv::soa::multi_type_vector<CNoteFunc> CellNoteStoreType; diff --git a/sc/source/core/data/Sparkline.cxx b/sc/source/core/data/Sparkline.cxx new file mode 100644 index 000000000000..301fda820ff2 --- /dev/null +++ b/sc/source/core/data/Sparkline.cxx @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include <memory> +#include <Sparkline.hxx> + +namespace sc +{ +Sparkline::Sparkline(std::shared_ptr<SparklineGroup>& pSparklineGroup) + : m_pSparklineGroup(pSparklineGroup) +{ +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/column.cxx b/sc/source/core/data/column.cxx index 5b6cb6d0d824..b66638336a11 100644 --- a/sc/source/core/data/column.cxx ... etc. - the rest is truncated