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;