sc/source/ui/inc/SparklineRenderer.hxx |  572 +++++++++++++++++++++++++++++++++
 sc/source/ui/view/output.cxx           |  254 +-------------
 2 files changed, 590 insertions(+), 236 deletions(-)

New commits:
commit 837606caf2f06ddbebe5045ad7e3bd844ba20003
Author:     Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk>
AuthorDate: Thu Mar 31 21:47:53 2022 +0900
Commit:     Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk>
CommitDate: Thu Mar 31 21:47:53 2022 +0900

    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

diff --git a/sc/source/ui/inc/SparklineRenderer.hxx 
b/sc/source/ui/inc/SparklineRenderer.hxx
new file mode 100644
index 000000000000..38ed9694e241
--- /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;
+        }
+    }
+
+    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);
+        }
+    }
+
+    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 1b8231974c13..14566da3d228 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>
@@ -2314,240 +2313,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;
-}
-
-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());
-    }
-}
-
-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
@@ -2581,16 +2365,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