sc/Library_sc.mk                                 |    1 
 sc/inc/SparklineGroup.hxx                        |    3 
 sc/inc/globstr.hrc                               |    1 
 sc/qa/unit/SparklineTest.cxx                     |   69 ++
 sc/source/ui/dialogs/SparklineDialog.cxx         |  146 ++---
 sc/source/ui/docshell/docfunc.cxx                |   12 
 sc/source/ui/inc/SparklineDialog.hxx             |    6 
 sc/source/ui/inc/SparklineRenderer.hxx           |  572 +++++++++++++++++++++++
 sc/source/ui/inc/docfunc.hxx                     |    4 
 sc/source/ui/inc/undo/UndoEditSparklineGroup.hxx |   44 +
 sc/source/ui/sparklines/SparklineGroup.cxx       |    6 
 sc/source/ui/undo/UndoEditSparklineGroup.cxx     |   65 ++
 sc/source/ui/view/output.cxx                     |  256 ----------
 13 files changed, 869 insertions(+), 316 deletions(-)

New commits:
commit 87d30e1ddfb7c138836e8dafbe061ba849327a78
Author:     Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk>
AuthorDate: Thu Mar 31 23:07:44 2022 +0900
Commit:     Tomaž Vajngerl <qui...@gmail.com>
CommitDate: Tue Apr 12 17:06:12 2022 +0200

    sc: add SparklineGroup Undo/Redo
    
    As SparklineAttributes are COW, we can just exchange them around
    in the SparklineGroup when undoing and redoing.
    
    This also changes SparklineDialog to work with a local copy of
    SparklineAttributes when editing, or an empty initial copy when
    inserting a new Sparkline into the sheet.
    
    Change-Id: I36e9c887ca640f40266f381e98e57f027a5ca07f
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132542
    Tested-by: Jenkins
    Reviewed-by: Tomaž Vajngerl <qui...@gmail.com>
    (cherry picked from commit 12bb0d897d97c9231e86d6b0071f8a0d29c7e660)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132877
    Tested-by: Tomaž Vajngerl <qui...@gmail.com>

diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk
index 2d65401e1c6d..82894aa72a94 100644
--- a/sc/Library_sc.mk
+++ b/sc/Library_sc.mk
@@ -553,6 +553,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\
     sc/source/ui/undo/undotab \
     sc/source/ui/undo/undoutil \
     sc/source/ui/undo/UndoInsertSparkline \
+    sc/source/ui/undo/UndoEditSparklineGroup \
     sc/source/ui/undo/UndoDeleteSparkline \
     sc/source/ui/unoobj/ChartRangeSelectionListener \
     sc/source/ui/unoobj/addruno \
diff --git a/sc/inc/SparklineGroup.hxx b/sc/inc/SparklineGroup.hxx
index c5e917f059f3..7d2e75e6e9e3 100644
--- a/sc/inc/SparklineGroup.hxx
+++ b/sc/inc/SparklineGroup.hxx
@@ -29,12 +29,15 @@ public:
     SparklineAttributes& getAttributes() { return m_aAttributes; }
     SparklineAttributes const& getAttributes() const { return m_aAttributes; }
 
+    void setAttributes(SparklineAttributes const& rAttributes) { m_aAttributes 
= rAttributes; };
+
     tools::Guid& getID() { return m_aGUID; }
 
     void setID(tools::Guid const& rGuid) { m_aGUID = rGuid; }
 
     SparklineGroup();
     SparklineGroup(SparklineGroup const& pOtherSparkline);
+    SparklineGroup(SparklineAttributes const& rSparklineAttributes);
 
     SparklineGroup& operator=(const SparklineGroup&) = delete;
 };
diff --git a/sc/inc/globstr.hrc b/sc/inc/globstr.hrc
index abb896a33bcf..e6cecc716c55 100644
--- a/sc/inc/globstr.hrc
+++ b/sc/inc/globstr.hrc
@@ -541,6 +541,7 @@
 #define STR_INDENTCELL                          NC_("STR_INDENTCELL", "Indent: 
")
 #define STR_UNDO_INSERT_SPARKLINE_GROUP         
NC_("STR_UNDO_INSERT_SPARKLINE", "Insert Sparkline Group")
 #define STR_UNDO_DELETE_SPARKLINE               
NC_("STR_UNDO_DELETE_SPARKLINE", "Delete Sparkline")
+#define STR_UNDO_EDIT_SPARKLINE_GROUP           
NC_("STR_UNDO_EDIT_SPARKLINE_GROUP", "Edit Sparkline Group")
 
 #endif
 
diff --git a/sc/qa/unit/SparklineTest.cxx b/sc/qa/unit/SparklineTest.cxx
index 6e203131e9c5..42ac88467b6d 100644
--- a/sc/qa/unit/SparklineTest.cxx
+++ b/sc/qa/unit/SparklineTest.cxx
@@ -53,6 +53,7 @@ public:
     void testUndoRedoInsertSparkline();
     void testUndoRedoDeleteSparkline();
     void testUndoRedoClearContentForSparkline();
+    void testUndoRedoEditSparklineGroup();
 
     CPPUNIT_TEST_SUITE(SparklineTest);
     CPPUNIT_TEST(testAddSparkline);
@@ -62,6 +63,7 @@ public:
     CPPUNIT_TEST(testUndoRedoInsertSparkline);
     CPPUNIT_TEST(testUndoRedoDeleteSparkline);
     CPPUNIT_TEST(testUndoRedoClearContentForSparkline);
+    CPPUNIT_TEST(testUndoRedoEditSparklineGroup);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -427,6 +429,73 @@ void SparklineTest::testUndoRedoClearContentForSparkline()
     xDocSh->DoClose();
 }
 
+void SparklineTest::testUndoRedoEditSparklineGroup()
+{
+    ScDocShellRef xDocSh = loadEmptyDocument();
+    CPPUNIT_ASSERT(xDocSh);
+
+    ScDocument& rDocument = xDocSh->GetDocument();
+    ScTabViewShell* pViewShell = xDocSh->GetBestViewShell(false);
+    CPPUNIT_ASSERT(pViewShell);
+
+    auto& rDocFunc = xDocSh->GetDocFunc();
+
+    auto pSparklineGroup = std::make_shared<sc::SparklineGroup>();
+    {
+        sc::SparklineAttributes& rAttibutes = pSparklineGroup->getAttributes();
+        rAttibutes.setType(sc::SparklineType::Column);
+        rAttibutes.setColorSeries(COL_YELLOW);
+        rAttibutes.setColorAxis(COL_GREEN);
+    }
+
+    rDocument.CreateSparkline(ScAddress(0, 6, 0), pSparklineGroup);
+
+    sc::SparklineAttributes aNewAttributes;
+    aNewAttributes.setType(sc::SparklineType::Stacked);
+    aNewAttributes.setColorSeries(COL_BLACK);
+    aNewAttributes.setColorAxis(COL_BLUE);
+
+    sc::SparklineAttributes 
aInitialAttibutes(pSparklineGroup->getAttributes());
+
+    CPPUNIT_ASSERT(aNewAttributes != aInitialAttibutes);
+
+    CPPUNIT_ASSERT_EQUAL(true, aInitialAttibutes == 
pSparklineGroup->getAttributes());
+    CPPUNIT_ASSERT_EQUAL(false, aNewAttributes == 
pSparklineGroup->getAttributes());
+
+    CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Column, 
pSparklineGroup->getAttributes().getType());
+    CPPUNIT_ASSERT_EQUAL(COL_YELLOW, 
pSparklineGroup->getAttributes().getColorSeries());
+    CPPUNIT_ASSERT_EQUAL(COL_GREEN, 
pSparklineGroup->getAttributes().getColorAxis());
+
+    rDocFunc.ChangeSparklineGroupAttributes(pSparklineGroup, aNewAttributes);
+
+    CPPUNIT_ASSERT_EQUAL(false, aInitialAttibutes == 
pSparklineGroup->getAttributes());
+    CPPUNIT_ASSERT_EQUAL(true, aNewAttributes == 
pSparklineGroup->getAttributes());
+
+    CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Stacked, 
pSparklineGroup->getAttributes().getType());
+    CPPUNIT_ASSERT_EQUAL(COL_BLACK, 
pSparklineGroup->getAttributes().getColorSeries());
+    CPPUNIT_ASSERT_EQUAL(COL_BLUE, 
pSparklineGroup->getAttributes().getColorAxis());
+
+    rDocument.GetUndoManager()->Undo();
+
+    CPPUNIT_ASSERT_EQUAL(true, aInitialAttibutes == 
pSparklineGroup->getAttributes());
+    CPPUNIT_ASSERT_EQUAL(false, aNewAttributes == 
pSparklineGroup->getAttributes());
+
+    CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Column, 
pSparklineGroup->getAttributes().getType());
+    CPPUNIT_ASSERT_EQUAL(COL_YELLOW, 
pSparklineGroup->getAttributes().getColorSeries());
+    CPPUNIT_ASSERT_EQUAL(COL_GREEN, 
pSparklineGroup->getAttributes().getColorAxis());
+
+    rDocument.GetUndoManager()->Redo();
+
+    CPPUNIT_ASSERT_EQUAL(false, aInitialAttibutes == 
pSparklineGroup->getAttributes());
+    CPPUNIT_ASSERT_EQUAL(true, aNewAttributes == 
pSparklineGroup->getAttributes());
+
+    CPPUNIT_ASSERT_EQUAL(sc::SparklineType::Stacked, 
pSparklineGroup->getAttributes().getType());
+    CPPUNIT_ASSERT_EQUAL(COL_BLACK, 
pSparklineGroup->getAttributes().getColorSeries());
+    CPPUNIT_ASSERT_EQUAL(COL_BLUE, 
pSparklineGroup->getAttributes().getColorAxis());
+
+    xDocSh->DoClose();
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(SparklineTest);
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/dialogs/SparklineDialog.cxx 
b/sc/source/ui/dialogs/SparklineDialog.cxx
index 59068077b969..4a7fa320a38e 100644
--- a/sc/source/ui/dialogs/SparklineDialog.cxx
+++ b/sc/source/ui/dialogs/SparklineDialog.cxx
@@ -163,7 +163,8 @@ void SparklineDialog::setupValues()
     {
         if (auto pSparkline = mrDocument.GetSparkline(aSelectionRange.aStart))
         {
-            mpLocalSparklineGroup = pSparkline->getSparklineGroup();
+            mpSparklineGroup = pSparkline->getSparklineGroup();
+            maAttributes = mpSparklineGroup->getAttributes();
             mxFrameData->set_visible(false);
             mbEditMode = true;
         }
@@ -173,16 +174,9 @@ void SparklineDialog::setupValues()
         maInputRange = aSelectionRange;
     }
 
-    if (!mpLocalSparklineGroup)
-    {
-        mpLocalSparklineGroup = std::make_shared<sc::SparklineGroup>();
-    }
-
     setInputSelection();
 
-    auto const& rAttribute = mpLocalSparklineGroup->getAttributes();
-
-    switch (rAttribute.getType())
+    switch (maAttributes.getType())
     {
         case sc::SparklineType::Line:
             mxRadioLine->set_active(true);
@@ -195,7 +189,7 @@ void SparklineDialog::setupValues()
             break;
     }
 
-    switch (rAttribute.getDisplayEmptyCellsAs())
+    switch (maAttributes.getDisplayEmptyCellsAs())
     {
         case sc::DisplayEmptyCellsAs::Gap:
             mxRadioDisplayEmptyGap->set_active(true);
@@ -208,28 +202,28 @@ void SparklineDialog::setupValues()
             break;
     }
 
-    mxColorSeries->SelectEntry(rAttribute.getColorSeries());
-    mxColorNegative->SelectEntry(rAttribute.getColorNegative());
-    mxColorMarker->SelectEntry(rAttribute.getColorMarkers());
-    mxColorHigh->SelectEntry(rAttribute.getColorHigh());
-    mxColorLow->SelectEntry(rAttribute.getColorLow());
-    mxColorFirst->SelectEntry(rAttribute.getColorFirst());
-    mxColorLast->SelectEntry(rAttribute.getColorLast());
+    mxColorSeries->SelectEntry(maAttributes.getColorSeries());
+    mxColorNegative->SelectEntry(maAttributes.getColorNegative());
+    mxColorMarker->SelectEntry(maAttributes.getColorMarkers());
+    mxColorHigh->SelectEntry(maAttributes.getColorHigh());
+    mxColorLow->SelectEntry(maAttributes.getColorLow());
+    mxColorFirst->SelectEntry(maAttributes.getColorFirst());
+    mxColorLast->SelectEntry(maAttributes.getColorLast());
 
-    mxCheckButtonNegative->set_active(rAttribute.isNegative());
-    mxCheckButtonMarker->set_active(rAttribute.isMarkers());
-    mxCheckButtonHigh->set_active(rAttribute.isHigh());
-    mxCheckButtonLow->set_active(rAttribute.isLow());
-    mxCheckButtonFirst->set_active(rAttribute.isFirst());
-    mxCheckButtonLast->set_active(rAttribute.isLast());
+    mxCheckButtonNegative->set_active(maAttributes.isNegative());
+    mxCheckButtonMarker->set_active(maAttributes.isMarkers());
+    mxCheckButtonHigh->set_active(maAttributes.isHigh());
+    mxCheckButtonLow->set_active(maAttributes.isLow());
+    mxCheckButtonFirst->set_active(maAttributes.isFirst());
+    mxCheckButtonLast->set_active(maAttributes.isLast());
 
-    mxSpinLineWidth->set_value(sal_Int64(rAttribute.getLineWeight() * 100.0));
+    mxSpinLineWidth->set_value(sal_Int64(maAttributes.getLineWeight() * 
100.0));
 
-    mxCheckDisplayXAxis->set_active(rAttribute.shouldDisplayXAxis());
-    mxCheckDisplayHidden->set_active(rAttribute.shouldDisplayHidden());
-    mxCheckRightToLeft->set_active(rAttribute.isRightToLeft());
+    mxCheckDisplayXAxis->set_active(maAttributes.shouldDisplayXAxis());
+    mxCheckDisplayHidden->set_active(maAttributes.shouldDisplayHidden());
+    mxCheckRightToLeft->set_active(maAttributes.isRightToLeft());
 
-    switch (rAttribute.getMinAxisType())
+    switch (maAttributes.getMinAxisType())
     {
         case sc::AxisType::Individual:
             mxComboMinAxisType->set_active(0);
@@ -241,13 +235,13 @@ void SparklineDialog::setupValues()
             break;
         case sc::AxisType::Custom:
             mxComboMinAxisType->set_active(2);
-            if (rAttribute.getManualMin())
-                
mxSpinCustomMin->GetFormatter().SetValue(*rAttribute.getManualMin());
+            if (maAttributes.getManualMin())
+                
mxSpinCustomMin->GetFormatter().SetValue(*maAttributes.getManualMin());
             break;
     }
     ComboValueChanged(*mxComboMinAxisType);
 
-    switch (rAttribute.getMaxAxisType())
+    switch (maAttributes.getMaxAxisType())
     {
         case sc::AxisType::Individual:
             mxComboMaxAxisType->set_active(0);
@@ -259,8 +253,8 @@ void SparklineDialog::setupValues()
             break;
         case sc::AxisType::Custom:
             mxComboMaxAxisType->set_active(2);
-            if (rAttribute.getManualMin())
-                
mxSpinCustomMax->GetFormatter().SetValue(*rAttribute.getManualMax());
+            if (maAttributes.getManualMin())
+                
mxSpinCustomMax->GetFormatter().SetValue(*maAttributes.getManualMax());
             break;
     }
     ComboValueChanged(*mxComboMaxAxisType);
@@ -405,72 +399,63 @@ IMPL_LINK(SparklineDialog, ButtonClicked, weld::Button&, 
rButton, void)
 
 IMPL_LINK(SparklineDialog, ToggleHandler, weld::Toggleable&, rToggle, void)
 {
-    auto& rAttribute = mpLocalSparklineGroup->getAttributes();
-
     if (mxCheckButtonNegative.get() == &rToggle)
-        rAttribute.setNegative(mxCheckButtonNegative->get_active());
+        maAttributes.setNegative(mxCheckButtonNegative->get_active());
     if (mxCheckButtonMarker.get() == &rToggle)
-        rAttribute.setMarkers(mxCheckButtonMarker->get_active());
+        maAttributes.setMarkers(mxCheckButtonMarker->get_active());
     if (mxCheckButtonHigh.get() == &rToggle)
-        rAttribute.setHigh(mxCheckButtonHigh->get_active());
+        maAttributes.setHigh(mxCheckButtonHigh->get_active());
     if (mxCheckButtonLow.get() == &rToggle)
-        rAttribute.setLow(mxCheckButtonLow->get_active());
+        maAttributes.setLow(mxCheckButtonLow->get_active());
     if (mxCheckButtonFirst.get() == &rToggle)
-        rAttribute.setFirst(mxCheckButtonFirst->get_active());
+        maAttributes.setFirst(mxCheckButtonFirst->get_active());
     if (mxCheckButtonLast.get() == &rToggle)
-        rAttribute.setLast(mxCheckButtonLast->get_active());
+        maAttributes.setLast(mxCheckButtonLast->get_active());
     if (mxCheckDisplayXAxis.get() == &rToggle)
-        rAttribute.setDisplayXAxis(mxCheckDisplayXAxis->get_active());
+        maAttributes.setDisplayXAxis(mxCheckDisplayXAxis->get_active());
     if (mxCheckDisplayHidden.get() == &rToggle)
-        rAttribute.setDisplayHidden(mxCheckDisplayHidden->get_active());
+        maAttributes.setDisplayHidden(mxCheckDisplayHidden->get_active());
     if (mxCheckRightToLeft.get() == &rToggle)
-        rAttribute.setRightToLeft(mxCheckRightToLeft->get_active());
+        maAttributes.setRightToLeft(mxCheckRightToLeft->get_active());
 }
 
 IMPL_LINK_NOARG(SparklineDialog, SelectSparklineType, weld::Toggleable&, void)
 {
-    auto& rAttribute = mpLocalSparklineGroup->getAttributes();
-
     if (mxRadioLine->get_active())
-        rAttribute.setType(sc::SparklineType::Line);
+        maAttributes.setType(sc::SparklineType::Line);
     else if (mxRadioColumn->get_active())
-        rAttribute.setType(sc::SparklineType::Column);
+        maAttributes.setType(sc::SparklineType::Column);
     else if (mxRadioStacked->get_active())
-        rAttribute.setType(sc::SparklineType::Stacked);
+        maAttributes.setType(sc::SparklineType::Stacked);
 
     if (mxRadioDisplayEmptyGap->get_active())
-        rAttribute.setDisplayEmptyCellsAs(sc::DisplayEmptyCellsAs::Gap);
+        maAttributes.setDisplayEmptyCellsAs(sc::DisplayEmptyCellsAs::Gap);
     else if (mxRadioDisplayEmptyZero->get_active())
-        rAttribute.setDisplayEmptyCellsAs(sc::DisplayEmptyCellsAs::Zero);
+        maAttributes.setDisplayEmptyCellsAs(sc::DisplayEmptyCellsAs::Zero);
     else if (mxRadioDisplayEmptySpan->get_active())
-        rAttribute.setDisplayEmptyCellsAs(sc::DisplayEmptyCellsAs::Span);
+        maAttributes.setDisplayEmptyCellsAs(sc::DisplayEmptyCellsAs::Span);
 }
 
 IMPL_LINK_NOARG(SparklineDialog, SpinLineWidthChanged, weld::SpinButton&, void)
 {
-    auto& rAttribute = mpLocalSparklineGroup->getAttributes();
-
     double value = mxSpinLineWidth->get_value() / 100.0;
-    rAttribute.setLineWeight(value);
+    maAttributes.setLineWeight(value);
 }
 
 IMPL_LINK(SparklineDialog, SpinCustomChanged, weld::FormattedSpinButton&, 
rFormatted, void)
 {
-    auto& rAttribute = mpLocalSparklineGroup->getAttributes();
-
     if (mxSpinCustomMin.get() == &rFormatted)
     {
-        rAttribute.setManualMin(rFormatted.GetFormatter().GetValue());
+        maAttributes.setManualMin(rFormatted.GetFormatter().GetValue());
     }
     else if (mxSpinCustomMax.get() == &rFormatted)
     {
-        rAttribute.setManualMax(rFormatted.GetFormatter().GetValue());
+        maAttributes.setManualMax(rFormatted.GetFormatter().GetValue());
     }
 }
 
 IMPL_LINK(SparklineDialog, ComboValueChanged, weld::ComboBox&, rComboBox, void)
 {
-    auto& rAttribute = mpLocalSparklineGroup->getAttributes();
     int nActive = rComboBox.get_active();
 
     if (mxComboMinAxisType.get() == &rComboBox)
@@ -478,15 +463,15 @@ IMPL_LINK(SparklineDialog, ComboValueChanged, 
weld::ComboBox&, rComboBox, void)
         switch (nActive)
         {
             case 0:
-                rAttribute.setMinAxisType(sc::AxisType::Individual);
+                maAttributes.setMinAxisType(sc::AxisType::Individual);
                 mxSpinCustomMin->set_sensitive(false);
                 break;
             case 1:
-                rAttribute.setMinAxisType(sc::AxisType::Group);
+                maAttributes.setMinAxisType(sc::AxisType::Group);
                 mxSpinCustomMin->set_sensitive(false);
                 break;
             case 2:
-                rAttribute.setMinAxisType(sc::AxisType::Custom);
+                maAttributes.setMinAxisType(sc::AxisType::Custom);
                 mxSpinCustomMin->set_sensitive(true);
                 break;
             default:
@@ -498,15 +483,15 @@ IMPL_LINK(SparklineDialog, ComboValueChanged, 
weld::ComboBox&, rComboBox, void)
         switch (nActive)
         {
             case 0:
-                rAttribute.setMaxAxisType(sc::AxisType::Individual);
+                maAttributes.setMaxAxisType(sc::AxisType::Individual);
                 mxSpinCustomMax->set_sensitive(false);
                 break;
             case 1:
-                rAttribute.setMaxAxisType(sc::AxisType::Group);
+                maAttributes.setMaxAxisType(sc::AxisType::Group);
                 mxSpinCustomMax->set_sensitive(false);
                 break;
             case 2:
-                rAttribute.setMaxAxisType(sc::AxisType::Custom);
+                maAttributes.setMaxAxisType(sc::AxisType::Custom);
                 mxSpinCustomMax->set_sensitive(true);
                 break;
             default:
@@ -540,20 +525,27 @@ bool SparklineDialog::checkValidInputOutput()
 
 void SparklineDialog::perform()
 {
-    auto& rAttribute = mpLocalSparklineGroup->getAttributes();
-
-    rAttribute.setColorSeries(mxColorSeries->GetSelectEntryColor());
-    rAttribute.setColorNegative(mxColorNegative->GetSelectEntryColor());
-    rAttribute.setColorMarkers(mxColorMarker->GetSelectEntryColor());
-    rAttribute.setColorHigh(mxColorHigh->GetSelectEntryColor());
-    rAttribute.setColorLow(mxColorLow->GetSelectEntryColor());
-    rAttribute.setColorFirst(mxColorFirst->GetSelectEntryColor());
-    rAttribute.setColorLast(mxColorLast->GetSelectEntryColor());
+    maAttributes.setColorSeries(mxColorSeries->GetSelectEntryColor());
+    maAttributes.setColorNegative(mxColorNegative->GetSelectEntryColor());
+    maAttributes.setColorMarkers(mxColorMarker->GetSelectEntryColor());
+    maAttributes.setColorHigh(mxColorHigh->GetSelectEntryColor());
+    maAttributes.setColorLow(mxColorLow->GetSelectEntryColor());
+    maAttributes.setColorFirst(mxColorFirst->GetSelectEntryColor());
+    maAttributes.setColorLast(mxColorLast->GetSelectEntryColor());
 
     auto& rDocFunc = mrViewData.GetDocShell()->GetDocFunc();
 
-    rDocFunc.InsertSparklines(maInputRange, maOutputRange, 
mpLocalSparklineGroup);
-}
+    if (mpSparklineGroup)
+    {
+        rDocFunc.ChangeSparklineGroupAttributes(mpSparklineGroup, 
maAttributes);
+    }
+    else
+    {
+        auto pNewSparklineGroup = 
std::make_shared<sc::SparklineGroup>(maAttributes);
+        rDocFunc.InsertSparklines(maInputRange, maOutputRange, 
pNewSparklineGroup);
+    }
 }
 
+} // end sc
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/docshell/docfunc.cxx 
b/sc/source/ui/docshell/docfunc.cxx
index 51f79e99ce81..58e39325bd28 100644
--- a/sc/source/ui/docshell/docfunc.cxx
+++ b/sc/source/ui/docshell/docfunc.cxx
@@ -95,9 +95,11 @@
 #include <columnspanset.hxx>
 #include <validat.hxx>
 #include <SparklineGroup.hxx>
+#include <SparklineAttributes.hxx>
 #include <SparklineData.hxx>
 #include <undo/UndoInsertSparkline.hxx>
 #include <undo/UndoDeleteSparkline.hxx>
+#include <undo/UndoEditSparklineGroup.hxx>
 #include <config_features.h>
 
 #include <memory>
@@ -5858,4 +5860,14 @@ bool ScDocFunc::DeleteSparkline(ScAddress const& 
rAddress)
     return true;
 }
 
+bool 
ScDocFunc::ChangeSparklineGroupAttributes(std::shared_ptr<sc::SparklineGroup> 
const& pExistingSparklineGroup,
+                                               sc::SparklineAttributes const& 
rNewAttributes)
+{
+    auto pUndo = std::make_unique<sc::UndoEditSparklneGroup>(rDocShell, 
pExistingSparklineGroup, rNewAttributes);
+    // change sparkline group attributes by "redoing"
+    pUndo->Redo();
+    rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo));
+    return true;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/inc/SparklineDialog.hxx 
b/sc/source/ui/inc/SparklineDialog.hxx
index ce8b7cf36118..20e279d5bf93 100644
--- a/sc/source/ui/inc/SparklineDialog.hxx
+++ b/sc/source/ui/inc/SparklineDialog.hxx
@@ -13,6 +13,9 @@
 #include "anyrefdg.hxx"
 #include "viewdata.hxx"
 
+#include <SparklineGroup.hxx>
+#include <SparklineAttributes.hxx>
+
 class ColorListBox;
 
 namespace sc
@@ -88,7 +91,8 @@ private:
     DECL_LINK(SpinLineWidthChanged, weld::SpinButton&, void);
     DECL_LINK(SpinCustomChanged, weld::FormattedSpinButton&, void);
 
-    std::shared_ptr<sc::SparklineGroup> mpLocalSparklineGroup;
+    std::shared_ptr<sc::SparklineGroup> mpSparklineGroup;
+    sc::SparklineAttributes maAttributes;
 
     bool mbEditMode;
 
diff --git a/sc/source/ui/inc/docfunc.hxx b/sc/source/ui/inc/docfunc.hxx
index c701595f8eba..1429657c68ae 100644
--- a/sc/source/ui/inc/docfunc.hxx
+++ b/sc/source/ui/inc/docfunc.hxx
@@ -54,6 +54,7 @@ enum class CreateNameFlags;
 namespace sc
 {
     struct ColRowSpan;
+    class SparklineAttributes;
     class SparklineGroup;
 }
 
@@ -241,6 +242,9 @@ public:
 
     SC_DLLPUBLIC bool DeleteSparkline(ScAddress const& rAddress);
 
+    SC_DLLPUBLIC bool 
ChangeSparklineGroupAttributes(std::shared_ptr<sc::SparklineGroup> const& 
pExistingSparklineGroup,
+                                                     sc::SparklineAttributes 
const& rNewAttributes);
+
 private:
     void ProtectDocument(const ScDocProtection& rProtect);
 };
diff --git a/sc/source/ui/inc/undo/UndoEditSparklineGroup.hxx 
b/sc/source/ui/inc/undo/UndoEditSparklineGroup.hxx
new file mode 100644
index 000000000000..4ab3e6a4e48f
--- /dev/null
+++ b/sc/source/ui/inc/undo/UndoEditSparklineGroup.hxx
@@ -0,0 +1,44 @@
+/* -*- 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 <undobase.hxx>
+#include <memory>
+
+#include <SparklineAttributes.hxx>
+#include <SparklineGroup.hxx>
+
+namespace sc
+{
+/** Undo action for editing a Sparkline */
+class UndoEditSparklneGroup : public ScSimpleUndo
+{
+private:
+    std::shared_ptr<sc::SparklineGroup> m_pSparklineGroup;
+    sc::SparklineAttributes m_aNewAttributes;
+    sc::SparklineAttributes m_aOriginalAttributes;
+
+public:
+    UndoEditSparklneGroup(ScDocShell& rDocShell,
+                          std::shared_ptr<sc::SparklineGroup> const& 
rSparklineGroup,
+                          sc::SparklineAttributes const& rAttributes);
+    virtual ~UndoEditSparklneGroup() override;
+
+    void Undo() override;
+    void Redo() override;
+    bool CanRepeat(SfxRepeatTarget& rTarget) const override;
+    void Repeat(SfxRepeatTarget& rTarget) override;
+    OUString GetComment() const override;
+};
+
+} // namespace sc
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/sparklines/SparklineGroup.cxx 
b/sc/source/ui/sparklines/SparklineGroup.cxx
index 9ef2c7044ab0..1ba235e75014 100644
--- a/sc/source/ui/sparklines/SparklineGroup.cxx
+++ b/sc/source/ui/sparklines/SparklineGroup.cxx
@@ -12,6 +12,12 @@
 
 namespace sc
 {
+SparklineGroup::SparklineGroup(SparklineAttributes const& rSparklineAttributes)
+    : m_aAttributes(rSparklineAttributes)
+    , m_aGUID(tools::Guid::Generate)
+{
+}
+
 SparklineGroup::SparklineGroup()
     : m_aGUID(tools::Guid::Generate)
 {
diff --git a/sc/source/ui/undo/UndoEditSparklineGroup.cxx 
b/sc/source/ui/undo/UndoEditSparklineGroup.cxx
new file mode 100644
index 000000000000..8136003d6b20
--- /dev/null
+++ b/sc/source/ui/undo/UndoEditSparklineGroup.cxx
@@ -0,0 +1,65 @@
+/* -*- 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 <undo/UndoEditSparklineGroup.hxx>
+
+#include <globstr.hrc>
+#include <scresid.hxx>
+
+#include <Sparkline.hxx>
+#include <SparklineGroup.hxx>
+#include <SparklineAttributes.hxx>
+
+namespace sc
+{
+UndoEditSparklneGroup::UndoEditSparklneGroup(
+    ScDocShell& rDocShell, std::shared_ptr<sc::SparklineGroup> const& 
pSparklineGroup,
+    sc::SparklineAttributes const& rAttributes)
+    : ScSimpleUndo(&rDocShell)
+    , m_pSparklineGroup(pSparklineGroup)
+    , m_aNewAttributes(rAttributes)
+    , m_aOriginalAttributes(pSparklineGroup->getAttributes())
+{
+}
+
+UndoEditSparklneGroup::~UndoEditSparklneGroup() = default;
+
+void UndoEditSparklneGroup::Undo()
+{
+    BeginUndo();
+
+    m_pSparklineGroup->setAttributes(m_aOriginalAttributes);
+    pDocShell->PostPaintGridAll();
+
+    EndUndo();
+}
+
+void UndoEditSparklneGroup::Redo()
+{
+    BeginRedo();
+
+    m_pSparklineGroup->setAttributes(m_aNewAttributes);
+    pDocShell->PostPaintGridAll();
+
+    EndRedo();
+}
+
+void UndoEditSparklneGroup::Repeat(SfxRepeatTarget& /*rTarget*/) {}
+
+bool UndoEditSparklneGroup::CanRepeat(SfxRepeatTarget& /*rTarget*/) const { 
return false; }
+
+OUString UndoEditSparklneGroup::GetComment() const
+{
+    return ScResId(STR_UNDO_EDIT_SPARKLINE_GROUP);
+}
+
+} // end sc namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit c85c2abfa3093d9965a6c5c917bae8d7d729b2fc
Author:     Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk>
AuthorDate: Thu Mar 31 21:47:53 2022 +0900
Commit:     Tomaž Vajngerl <qui...@gmail.com>
CommitDate: Tue Apr 12 17:05:57 2022 +0200

    sc: Sparkline rendering improvement, take all attrs. into account
    
    This change moves Sparkline rendering into a new class and into
    a separate file - SparklineRenderer, and improve the rendering by
    taking all the sparkline attributes into account.
    Improvements:
    - render correct line width for lines
    - take hidden cells into account
    - draw the X axis
    - allow to override the min, max values (custom X axis limits)
    - handle empty cells (interpret as zero, as a gap or interpolate)
    - correctly handle first and last value
    - take marker attribute and color into account (missing before)
    - simplify range iteration (with RangeTraverser class)
    - show min, max also for stacked sparkline type
    - ...
    
    Change-Id: Id855f775677aa309b42174e086ad4f5d7de079c0
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132541
    Tested-by: Jenkins
    Reviewed-by: Tomaž Vajngerl <qui...@gmail.com>
    (cherry picked from commit 574384e90fdb24aed40e0dcffd17adae3443aa04)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132876
    Tested-by: Tomaž Vajngerl <qui...@gmail.com>

diff --git a/sc/source/ui/inc/SparklineRenderer.hxx 
b/sc/source/ui/inc/SparklineRenderer.hxx
new file mode 100644
index 000000000000..9f697a6002b2
--- /dev/null
+++ b/sc/source/ui/inc/SparklineRenderer.hxx
@@ -0,0 +1,572 @@
+/* -*- 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 <document.hxx>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <Sparkline.hxx>
+#include <SparklineGroup.hxx>
+#include <SparklineAttributes.hxx>
+
+namespace sc
+{
+/** Contains the marker polygon and the color of a marker */
+struct SparklineMarker
+{
+    basegfx::B2DPolygon maPolygon;
+    Color maColor;
+};
+
+/** Sparkline value and action that needs to me performed on the value */
+struct SparklineValue
+{
+    enum class Action
+    {
+        None, // No action on the value
+        Skip, // Skip the value
+        Interpolate // Intrpolate the value
+    };
+
+    double maValue;
+    Action meAction;
+
+    SparklineValue(double aValue, Action eAction)
+        : maValue(aValue)
+        , meAction(eAction)
+    {
+    }
+};
+
+/** Contains and manages the values of the sparkline.
+ *
+ * It automatically keeps track of the minimums and maximums, and
+ * skips or interpolates the sparkline values if needed, depending on
+ * the input. This is done so it is easier to handle the sparkline
+ * values later on.
+ */
+class SparklineValues
+{
+private:
+    double mfPreviousValue = 0.0;
+    size_t mnPreviousIndex = std::numeric_limits<size_t>::max();
+
+    std::vector<size_t> maToInterpolateIndex;
+
+    std::vector<SparklineValue> maValueList;
+
+public:
+    size_t mnFirstIndex = std::numeric_limits<size_t>::max();
+    size_t mnLastIndex = 0;
+
+    double mfMinimum = std::numeric_limits<double>::max();
+    double mfMaximum = std::numeric_limits<double>::min();
+
+    std::vector<SparklineValue> const& getValuesList() const { return 
maValueList; }
+
+    void add(double fValue, SparklineValue::Action eAction)
+    {
+        maValueList.emplace_back(fValue, eAction);
+        size_t nCurrentIndex = maValueList.size() - 1;
+
+        if (eAction == SparklineValue::Action::None)
+        {
+            mnLastIndex = nCurrentIndex;
+
+            if (mnLastIndex < mnFirstIndex)
+                mnFirstIndex = mnLastIndex;
+
+            if (fValue < mfMinimum)
+                mfMinimum = fValue;
+
+            if (fValue > mfMaximum)
+                mfMaximum = fValue;
+
+            interpolatePastValues(fValue, nCurrentIndex);
+
+            mnPreviousIndex = nCurrentIndex;
+            mfPreviousValue = fValue;
+        }
+        else if (eAction == SparklineValue::Action::Interpolate)
+        {
+            maToInterpolateIndex.push_back(nCurrentIndex);
+            maValueList.back().meAction = SparklineValue::Action::Skip;
+        }
+    }
+
+    static constexpr double interpolate(double x1, double y1, double x2, 
double y2, double x)
+    {
+        return (y1 * (x2 - x) + y2 * (x - x1)) / (x2 - x1);
+    }
+
+    void interpolatePastValues(double nCurrentValue, size_t nCurrentIndex)
+    {
+        if (maToInterpolateIndex.empty())
+            return;
+
+        if (mnPreviousIndex == std::numeric_limits<size_t>::max())
+        {
+            for (size_t nIndex : maToInterpolateIndex)
+            {
+                auto& rValue = maValueList[nIndex];
+                rValue.meAction = SparklineValue::Action::Skip;
+            }
+        }
+        else
+        {
+            for (size_t nIndex : maToInterpolateIndex)
+            {
+                double fInterpolated = interpolate(mnPreviousIndex, 
mfPreviousValue, nCurrentIndex,
+                                                   nCurrentValue, nIndex);
+
+                auto& rValue = maValueList[nIndex];
+                rValue.maValue = fInterpolated;
+                rValue.meAction = SparklineValue::Action::None;
+            }
+        }
+        maToInterpolateIndex.clear();
+    }
+
+    void convertToStacked()
+    {
+        // transform the data to 1, -1
+        for (auto& rValue : maValueList)
+        {
+            if (rValue.maValue != 0.0)
+            {
+                double fNewValue = rValue.maValue > 0.0 ? 1.0 : -1.0;
+
+                if (rValue.maValue == mfMinimum)
+                    fNewValue -= 0.01;
+
+                if (rValue.maValue == mfMaximum)
+                    fNewValue += 0.01;
+
+                rValue.maValue = fNewValue;
+            }
+        }
+        mfMinimum = -1.01;
+        mfMaximum = 1.01;
+    }
+
+    void reverse() { std::reverse(maValueList.begin(), maValueList.end()); }
+};
+
+/** Iterator to traverse the addresses in a range if the range is one 
dimesional.
+ *
+ * The direction to traverse is detcted automatically or hasNext returns
+ * false if it is not possible to detect.
+ *
+ */
+class RangeTraverser
+{
+    enum class Direction
+    {
+        UNKNOWN,
+        ROW,
+        COLUMN
+    };
+
+    ScAddress m_aCurrent;
+    ScRange m_aRange;
+    Direction m_eDirection;
+
+public:
+    RangeTraverser(ScRange const& rRange)
+        : m_aCurrent(ScAddress::INITIALIZE_INVALID)
+        , m_aRange(rRange)
+        , m_eDirection(Direction::UNKNOWN)
+
+    {
+    }
+
+    ScAddress const& first()
+    {
+        m_aCurrent.SetInvalid();
+
+        if (m_aRange.aStart.Row() == m_aRange.aEnd.Row())
+        {
+            m_eDirection = Direction::COLUMN;
+            m_aCurrent = m_aRange.aStart;
+        }
+        else if (m_aRange.aStart.Col() == m_aRange.aEnd.Col())
+        {
+            m_eDirection = Direction::ROW;
+            m_aCurrent = m_aRange.aStart;
+        }
+
+        return m_aCurrent;
+    }
+
+    bool hasNext()
+    {
+        if (m_eDirection == Direction::COLUMN)
+            return m_aCurrent.Col() <= m_aRange.aEnd.Col();
+        else if (m_eDirection == Direction::ROW)
+            return m_aCurrent.Row() <= m_aRange.aEnd.Row();
+        else
+            return false;
+    }
+
+    void next()
+    {
+        if (hasNext())
+        {
+            if (m_eDirection == Direction::COLUMN)
+                m_aCurrent.IncCol();
+            else if (m_eDirection == Direction::ROW)
+                m_aCurrent.IncRow();
+        }
+    }
+};
+
+/** Render a provided sparkline into the input rectangle */
+class SparklineRenderer
+{
+private:
+    ScDocument& mrDocument;
+    tools::Long mnOneX;
+    tools::Long mnOneY;
+
+    double mfScaleX;
+    double mfScaleY;
+
+    void createMarker(std::vector<SparklineMarker>& rMarkers, double x, double 
y,
+                      Color const& rColor)
+    {
+        auto& rMarker = rMarkers.emplace_back();
+        const double nHalfSizeX = double(mnOneX * 2 * mfScaleX);
+        const double nHalfSizeY = double(mnOneY * 2 * mfScaleY);
+        basegfx::B2DRectangle aRectangle(std::round(x - nHalfSizeX), 
std::round(y - nHalfSizeY),
+                                         std::round(x + nHalfSizeX), 
std::round(y + nHalfSizeY));
+        rMarker.maPolygon = basegfx::utils::createPolygonFromRect(aRectangle);
+        rMarker.maColor = rColor;
+    }
+
+    void drawLine(vcl::RenderContext& rRenderContext, tools::Rectangle const& 
rRectangle,
+                  SparklineValues const& rSparklineValues,
+                  sc::SparklineAttributes const& rAttributes)
+    {
+        double nMax = rSparklineValues.mfMaximum;
+        if (rAttributes.getMaxAxisType() == sc::AxisType::Custom && 
rAttributes.getManualMax())
+            nMax = *rAttributes.getManualMax();
+
+        double nMin = rSparklineValues.mfMinimum;
+        if (rAttributes.getMinAxisType() == sc::AxisType::Custom && 
rAttributes.getManualMin())
+            nMin = *rAttributes.getManualMin();
+
+        std::vector<SparklineValue> const& rValueList = 
rSparklineValues.getValuesList();
+        std::vector<basegfx::B2DPolygon> aPolygons;
+        aPolygons.emplace_back();
+        double numebrOfSteps = rValueList.size() - 1;
+        double xStep = 0;
+        double nDelta = nMax - nMin;
+
+        std::vector<SparklineMarker> aMarkers;
+        size_t nValueIndex = 0;
+
+        for (auto const& rSparklineValue : rValueList)
+        {
+            if (rSparklineValue.meAction == SparklineValue::Action::Skip)
+            {
+                aPolygons.emplace_back();
+            }
+            else
+            {
+                auto& aPolygon = aPolygons.back();
+                double nValue = rSparklineValue.maValue;
+
+                double nP = (nValue - nMin) / nDelta;
+                double x = rRectangle.GetWidth() * (xStep / numebrOfSteps);
+                double y = rRectangle.GetHeight() - rRectangle.GetHeight() * 
nP;
+
+                aPolygon.append({ x, y });
+
+                if (rAttributes.isFirst() && nValueIndex == 
rSparklineValues.mnFirstIndex)
+                {
+                    createMarker(aMarkers, x, y, rAttributes.getColorFirst());
+                }
+                else if (rAttributes.isLast() && nValueIndex == 
rSparklineValues.mnLastIndex)
+                {
+                    createMarker(aMarkers, x, y, rAttributes.getColorLast());
+                }
+                else if (rAttributes.isHigh() && nValue == 
rSparklineValues.mfMaximum)
+                {
+                    createMarker(aMarkers, x, y, rAttributes.getColorHigh());
+                }
+                else if (rAttributes.isLow() && nValue == 
rSparklineValues.mfMinimum)
+                {
+                    createMarker(aMarkers, x, y, rAttributes.getColorLow());
+                }
+                else if (rAttributes.isNegative() && nValue < 0.0)
+                {
+                    createMarker(aMarkers, x, y, 
rAttributes.getColorNegative());
+                }
+                else if (rAttributes.isMarkers())
+                {
+                    createMarker(aMarkers, x, y, 
rAttributes.getColorMarkers());
+                }
+            }
+
+            xStep++;
+            nValueIndex++;
+        }
+
+        basegfx::B2DHomMatrix aMatrix;
+        aMatrix.translate(rRectangle.Left(), rRectangle.Top());
+
+        if (rAttributes.shouldDisplayXAxis())
+        {
+            double nZero = 0 - nMin / nDelta;
+
+            if (nZero >= 0) // if nZero < 0, the axis is not visible
+            {
+                double x1 = 0.0;
+                double x2 = double(rRectangle.GetWidth());
+                double y = rRectangle.GetHeight() - rRectangle.GetHeight() * 
nZero;
+
+                basegfx::B2DPolygon aAxisPolygon;
+                aAxisPolygon.append({ x1, y });
+                aAxisPolygon.append({ x2, y });
+
+                rRenderContext.SetLineColor(rAttributes.getColorAxis());
+                rRenderContext.DrawPolyLineDirect(aMatrix, aAxisPolygon, 0.2 * 
mfScaleX);
+            }
+        }
+
+        rRenderContext.SetLineColor(rAttributes.getColorSeries());
+
+        for (auto& rPolygon : aPolygons)
+        {
+            rRenderContext.DrawPolyLineDirect(aMatrix, rPolygon,
+                                              rAttributes.getLineWeight() * 
mfScaleX, 0.0, nullptr,
+                                              basegfx::B2DLineJoin::Round);
+        }
+
+        for (auto& rMarker : aMarkers)
+        {
+            rRenderContext.SetLineColor(rMarker.maColor);
+            rRenderContext.SetFillColor(rMarker.maColor);
+            auto& rPolygon = rMarker.maPolygon;
+            rPolygon.transform(aMatrix);
+            rRenderContext.DrawPolygon(rPolygon);
+        }
+    }
+
+    static void setFillAndLineColor(vcl::RenderContext& rRenderContext,
+                                    sc::SparklineAttributes const& 
rAttributes, double nValue,
+                                    size_t nValueIndex, SparklineValues const& 
rSparklineValues)
+    {
+        if (rAttributes.isFirst() && nValueIndex == 
rSparklineValues.mnFirstIndex)
+        {
+            rRenderContext.SetLineColor(rAttributes.getColorFirst());
+            rRenderContext.SetFillColor(rAttributes.getColorFirst());
+        }
+        else if (rAttributes.isLast() && nValueIndex == 
rSparklineValues.mnLastIndex)
+        {
+            rRenderContext.SetLineColor(rAttributes.getColorLast());
+            rRenderContext.SetFillColor(rAttributes.getColorLast());
+        }
+        else if (rAttributes.isHigh() && nValue == rSparklineValues.mfMaximum)
+        {
+            rRenderContext.SetLineColor(rAttributes.getColorHigh());
+            rRenderContext.SetFillColor(rAttributes.getColorHigh());
+        }
+        else if (rAttributes.isLow() && nValue == rSparklineValues.mfMinimum)
+        {
+            rRenderContext.SetLineColor(rAttributes.getColorLow());
+            rRenderContext.SetFillColor(rAttributes.getColorLow());
+        }
+        else if (rAttributes.isNegative() && nValue < 0.0)
+        {
+            rRenderContext.SetLineColor(rAttributes.getColorNegative());
+            rRenderContext.SetFillColor(rAttributes.getColorNegative());
+        }
+        else
+        {
+            rRenderContext.SetLineColor(rAttributes.getColorSeries());
+            rRenderContext.SetFillColor(rAttributes.getColorSeries());
+        }
+    }
+
+    void drawColumn(vcl::RenderContext& rRenderContext, tools::Rectangle 
const& rRectangle,
+                    SparklineValues const& rSparklineValues,
+                    sc::SparklineAttributes const& rAttributes)
+    {
+        double nMax = rSparklineValues.mfMaximum;
+        if (rAttributes.getMaxAxisType() == sc::AxisType::Custom && 
rAttributes.getManualMax())
+            nMax = *rAttributes.getManualMax();
+
+        double nMin = rSparklineValues.mfMinimum;
+        if (rAttributes.getMinAxisType() == sc::AxisType::Custom && 
rAttributes.getManualMin())
+            nMin = *rAttributes.getManualMin();
+
+        std::vector<SparklineValue> const& rValueList = 
rSparklineValues.getValuesList();
+
+        basegfx::B2DPolygon aPolygon;
+        basegfx::B2DHomMatrix aMatrix;
+        aMatrix.translate(rRectangle.Left(), rRectangle.Top());
+
+        double xStep = 0;
+        double numberOfSteps = rValueList.size();
+        double nDelta = nMax - nMin;
+
+        double nColumnSize = rRectangle.GetWidth() / numberOfSteps;
+        nColumnSize = nColumnSize - (nColumnSize * 0.3);
+
+        double nZero = (0 - nMin) / nDelta;
+        double nZeroPosition = 0.0;
+        if (nZero >= 0)
+        {
+            nZeroPosition = rRectangle.GetHeight() - rRectangle.GetHeight() * 
nZero;
+
+            if (rAttributes.shouldDisplayXAxis())
+            {
+                double x1 = 0.0;
+                double x2 = double(rRectangle.GetWidth());
+
+                basegfx::B2DPolygon aAxisPolygon;
+                aAxisPolygon.append({ x1, nZeroPosition });
+                aAxisPolygon.append({ x2, nZeroPosition });
+
+                rRenderContext.SetLineColor(rAttributes.getColorAxis());
+                rRenderContext.DrawPolyLineDirect(aMatrix, aAxisPolygon, 0.2 * 
mfScaleX);
+            }
+        }
+        else
+            nZeroPosition = rRectangle.GetHeight();
+
+        size_t nValueIndex = 0;
+
+        for (auto const& rSparklineValue : rValueList)
+        {
+            double nValue = rSparklineValue.maValue;
+
+            if (nValue != 0.0)
+            {
+                setFillAndLineColor(rRenderContext, rAttributes, nValue, 
nValueIndex,
+                                    rSparklineValues);
+
+                double nP = (nValue - 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);
+
+                aPolygon.transform(aMatrix);
+                rRenderContext.DrawPolygon(aPolygon);
+            }
+            xStep++;
+            nValueIndex++;
+        }
+    }
+
+    bool isCellHidden(ScAddress const& rAddress)
+    {
+        return mrDocument.RowHidden(rAddress.Row(), rAddress.Tab())
+               || mrDocument.ColHidden(rAddress.Col(), rAddress.Tab());
+    }
+
+public:
+    SparklineRenderer(ScDocument& rDocument)
+        : mrDocument(rDocument)
+        , mnOneX(1)
+        , mnOneY(1)
+        , mfScaleX(1.0)
+        , mfScaleY(1.0)
+    {
+    }
+
+    void render(std::shared_ptr<sc::Sparkline> const& pSparkline,
+                vcl::RenderContext& rRenderContext, tools::Rectangle const& 
rRectangle,
+                tools::Long nOneX, tools::Long nOneY, double fScaleX, double 
fScaleY)
+    {
+        rRenderContext.Push();
+        rRenderContext.SetAntialiasing(AntialiasingFlags::Enable);
+        rRenderContext.SetClipRegion(vcl::Region(rRectangle));
+
+        tools::Rectangle aOutputRectangle(rRectangle);
+        aOutputRectangle.shrink(6); // provide border
+
+        mnOneX = nOneX;
+        mnOneY = nOneY;
+        mfScaleX = fScaleX;
+        mfScaleY = fScaleY;
+
+        auto const& rRangeList = pSparkline->getInputRange();
+
+        if (rRangeList.empty())
+            return;
+
+        auto pSparklineGroup = pSparkline->getSparklineGroup();
+        auto const& rAttributes = pSparklineGroup->getAttributes();
+
+        ScRange aRange = rRangeList[0];
+
+        SparklineValues aSparklineValues;
+
+        RangeTraverser aTraverser(aRange);
+        for (ScAddress const& rCurrent = aTraverser.first(); 
aTraverser.hasNext();
+             aTraverser.next())
+        {
+            // Skip if the cell is hidden and "displayHidden" attribute is not 
selected
+            if (!rAttributes.shouldDisplayHidden() && isCellHidden(rCurrent))
+                continue;
+
+            double fCellValue = 0.0;
+            SparklineValue::Action eAction = SparklineValue::Action::None;
+            CellType eType = mrDocument.GetCellType(rCurrent);
+
+            if (eType == CELLTYPE_NONE) // if cell is empty
+            {
+                auto eDisplayEmpty = rAttributes.getDisplayEmptyCellsAs();
+                if (eDisplayEmpty == sc::DisplayEmptyCellsAs::Gap)
+                    eAction = SparklineValue::Action::Skip;
+                else if (eDisplayEmpty == sc::DisplayEmptyCellsAs::Span)
+                    eAction = SparklineValue::Action::Interpolate;
+            }
+            else
+            {
+                fCellValue = mrDocument.GetValue(rCurrent);
+            }
+
+            aSparklineValues.add(fCellValue, eAction);
+        }
+
+        if (rAttributes.isRightToLeft())
+            aSparklineValues.reverse();
+
+        if (rAttributes.getType() == sc::SparklineType::Column)
+        {
+            drawColumn(rRenderContext, aOutputRectangle, aSparklineValues,
+                       pSparklineGroup->getAttributes());
+        }
+        else if (rAttributes.getType() == sc::SparklineType::Stacked)
+        {
+            aSparklineValues.convertToStacked();
+            drawColumn(rRenderContext, aOutputRectangle, aSparklineValues,
+                       pSparklineGroup->getAttributes());
+        }
+        else if (rAttributes.getType() == sc::SparklineType::Line)
+        {
+            drawLine(rRenderContext, aOutputRectangle, aSparklineValues,
+                     pSparklineGroup->getAttributes());
+        }
+        rRenderContext.Pop();
+    }
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/view/output.cxx b/sc/source/ui/view/output.cxx
index 0cd3d91cda7f..279225079d79 100644
--- a/sc/source/ui/view/output.cxx
+++ b/sc/source/ui/view/output.cxx
@@ -62,9 +62,8 @@
 #include <validat.hxx>
 #include <detfunc.hxx>
 
+#include <SparklineRenderer.hxx>
 #include <colorscale.hxx>
-#include <Sparkline.hxx>
-#include <SparklineGroup.hxx>
 
 #include <math.h>
 #include <memory>
@@ -2304,242 +2303,25 @@ 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;
-}
-
-/** Draw a line chart into the rectangle bounds */
-void drawLine(vcl::RenderContext& rRenderContext, tools::Rectangle const & 
rRectangle,
-                std::vector<double> const& rValues, double nMin, double nMax,
-                sc::SparklineAttributes const& rAttributes)
-{
-    basegfx::B2DPolygon aPolygon;
-    double numebrOfSteps = rValues.size() - 1;
-    double xStep = 0;
-    double nDelta = nMax - nMin;
-
-    std::vector<SparklineMarker> aMarkers;
-    sal_Int64 nValueIndex = 0;
-    sal_Int64 nValuesSize = rValues.size();
-
-    for (double nValue : rValues)
-    {
-        double nP = (nValue - nMin) / nDelta;
-        double x = rRectangle.GetWidth() * (xStep / numebrOfSteps);
-        double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nP;
-
-        aPolygon.append({ x, y } );
-
-        if (rAttributes.isFirst() && nValueIndex == 0)
-        {
-            createMarker(aMarkers, x, y, rAttributes.getColorFirst());
-        }
-        else if (rAttributes.isLast() && nValueIndex == (nValuesSize - 1))
-        {
-            createMarker(aMarkers, x, y, rAttributes.getColorLast());
-        }
-        else if (rAttributes.isHigh() && nValue == nMax)
-        {
-            createMarker(aMarkers, x, y, rAttributes.getColorHigh());
-        }
-        else if (rAttributes.isLow() && nValue == nMin)
-        {
-            createMarker(aMarkers, x, y, rAttributes.getColorLow());
-        }
-        else if (rAttributes.isNegative() && nValue < 0.0)
-        {
-            createMarker(aMarkers, x, y, rAttributes.getColorNegative());
-        }
-
-        xStep++;
-        nValueIndex++;
-    }
-
-    basegfx::B2DHomMatrix aMatrix;
-    aMatrix.translate(rRectangle.Left(), rRectangle.Top());
-    aPolygon.transform(aMatrix);
-
-    rRenderContext.SetLineColor(rAttributes.getColorSeries());
-    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, 
sc::SparklineAttributes const& rAttributes,
-                         double nValue, sal_Int64 nValueIndex, sal_Int64 
nValuesSize, double nMin, double nMax)
-{
-    if (rAttributes.isFirst() && nValueIndex == 0)
-    {
-        rRenderContext.SetLineColor(rAttributes.getColorFirst());
-        rRenderContext.SetFillColor(rAttributes.getColorFirst());
-    }
-    else if (rAttributes.isLast() && nValueIndex == (nValuesSize - 1))
-    {
-        rRenderContext.SetLineColor(rAttributes.getColorLast());
-        rRenderContext.SetFillColor(rAttributes.getColorLast());
-    }
-    else if (rAttributes.isHigh() && nValue == nMax)
-    {
-        rRenderContext.SetLineColor(rAttributes.getColorHigh());
-        rRenderContext.SetFillColor(rAttributes.getColorHigh());
-    }
-    else if (rAttributes.isLow() && nValue == nMin)
-    {
-        rRenderContext.SetLineColor(rAttributes.getColorLow());
-        rRenderContext.SetFillColor(rAttributes.getColorLow());
-    }
-    else if (rAttributes.isNegative() && nValue < 0.0)
-    {
-        rRenderContext.SetLineColor(rAttributes.getColorNegative());
-        rRenderContext.SetFillColor(rAttributes.getColorNegative());
-    }
-    else
-    {
-        rRenderContext.SetLineColor(rAttributes.getColorSeries());
-        rRenderContext.SetFillColor(rAttributes.getColorSeries());
-    }
-}
-
-/** Draw a column chart into the rectangle bounds */
-void drawColumn(vcl::RenderContext& rRenderContext, tools::Rectangle const & 
rRectangle,
-                std::vector<double> const & rValues, double nMin, double nMax,
-                sc::SparklineAttributes const & rAttributes)
-{
-    basegfx::B2DPolygon aPolygon;
-
-    double xStep = 0;
-    double numberOfSteps = rValues.size();
-    double nDelta = nMax - nMin;
-
-    double nColumnSize = rRectangle.GetWidth() / numberOfSteps;
-    nColumnSize = nColumnSize - (nColumnSize * 0.3);
-
-    double nZero = (0 - nMin) / nDelta;
-    double nZeroPosition;
-    if (nZero >= 0)
-        nZeroPosition = rRectangle.GetHeight() - rRectangle.GetHeight() * 
nZero;
-    else
-        nZeroPosition = rRectangle.GetHeight();
-
-    sal_Int64 nValueIndex = 0;
-
-    for (double nValue : rValues)
-    {
-        if (nValue != 0.0)
-        {
-            setFillAndLineColor(rRenderContext, rAttributes, 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;
-
-            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++;
-        nValueIndex++;
-    }
-}
-
-void drawSparkline(std::shared_ptr<sc::Sparkline> const& pSparkline, 
vcl::RenderContext& rRenderContext, ScDocument* pDocument,
-                                 tools::Rectangle const & rRectangle)
+void ScOutputData::DrawSparklines(vcl::RenderContext& rRenderContext)
 {
-    auto const & rRangeList = pSparkline->getInputRange();
-
-    if (rRangeList.empty())
-        return;
-
-    auto pSparklineGroup = pSparkline->getSparklineGroup();
-    auto const& rAttributes = pSparklineGroup->getAttributes();
-
-    rRenderContext.SetAntialiasing(AntialiasingFlags::Enable);
-
-    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;
+    Size aOnePixel = rRenderContext.PixelToLogic(Size(1,1));
+    tools::Long nOneXLogic = aOnePixel.Width();
+    tools::Long nOneYLogic = aOnePixel.Height();
 
-        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();
-        }
-    }
+    // See more about bWorksInPixels in ScOutputData::DrawGrid
+    bool bWorksInPixels = false;
+    if (eType == OUTTYPE_WINDOW)
+        bWorksInPixels = true;
 
-    if (rAttributes.getType() == sc::SparklineType::Column)
-    {
-        drawColumn(rRenderContext, rRectangle, aValues, nMin, nMax, 
pSparklineGroup->getAttributes());
-    }
-    else if (rAttributes.getType() == sc::SparklineType::Stacked)
-    {
-        // transform the data to 1, -1
-        for (auto & rValue : aValues)
-        {
-            if (rValue != 0.0)
-                rValue = rValue > 0.0 ? 1.0 : -1.0;
-        }
-        drawColumn(rRenderContext, rRectangle, aValues, -1, 1, 
pSparklineGroup->getAttributes());
-    }
-    else if (rAttributes.getType() == sc::SparklineType::Line)
+    tools::Long nOneX = 1;
+    tools::Long nOneY = 1;
+    if (!bWorksInPixels)
     {
-        drawLine(rRenderContext, rRectangle, aValues, nMin, nMax, 
pSparklineGroup->getAttributes());
+        nOneX = nOneXLogic;
+        nOneY = nOneYLogic;
     }
-}
-} // end anonymous namespace
 
-void ScOutputData::DrawSparklines(vcl::RenderContext& rRenderContext)
-{
     tools::Long nInitPosX = nScrX;
     if ( bLayoutRTL )
         nInitPosX += nMirrorW - 1;              // always in pixels
@@ -2573,16 +2355,14 @@ void ScOutputData::DrawSparklines(vcl::RenderContext& 
rRenderContext)
                 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].basicCellInfo(nX).nWidth;
                     const tools::Long nHeight = pThisRowInfo->nHeight;
 
-                    Point aPoint(nPosX + constMarginX , nPosY + constMarginY);
-                    Size aSize(nWidth - 2 * constMarginX, nHeight - 2 * 
constMarginY);
+                    Point aPoint(nPosX, nPosY);
+                    Size aSize(nWidth, nHeight);
 
-                    drawSparkline(pSparkline, rRenderContext, mpDoc, 
tools::Rectangle(aPoint, aSize));
+                    sc::SparklineRenderer renderer(*mpDoc);
+                    renderer.render(pSparkline, rRenderContext, 
tools::Rectangle(aPoint, aSize), nOneX, nOneY, double(aZoomX), double(aZoomY));
                 }
 
                 nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign;

Reply via email to