Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sc/source/ui/inc/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 19 kB image not shown  

Quelle  SparklineRenderer.hxx   Sprache: C

 
/* -*- 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 <comphelper/scopeguard.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 // Interpolate 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>::lowest();

    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 dimensional.
 *
 * The direction to traverse is detected 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().getFinalColor());
                }
                else if (rAttributes.isLast() && nValueIndex == rSparklineValues.mnLastIndex)
                {
                    createMarker(aMarkers, x, y, rAttributes.getColorLast().getFinalColor());
                }
                else if (rAttributes.isHigh() && nValue == rSparklineValues.mfMaximum)
                {
                    createMarker(aMarkers, x, y, rAttributes.getColorHigh().getFinalColor());
                }
                else if (rAttributes.isLow() && nValue == rSparklineValues.mfMinimum)
                {
                    createMarker(aMarkers, x, y, rAttributes.getColorLow().getFinalColor());
                }
                else if (rAttributes.isNegative() && nValue < 0.0)
                {
                    createMarker(aMarkers, x, y, rAttributes.getColorNegative().getFinalColor());
                }
                else if (rAttributes.isMarkers())
                {
                    createMarker(aMarkers, x, y, rAttributes.getColorMarkers().getFinalColor());
                }
            }

            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().getFinalColor());
                rRenderContext.DrawPolyLineDirect(aMatrix, aAxisPolygon, 0.2 * mfScaleX);
            }
        }

        rRenderContext.SetLineColor(rAttributes.getColorSeries().getFinalColor());

        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().getFinalColor());
            rRenderContext.SetFillColor(rAttributes.getColorFirst().getFinalColor());
        }
        else if (rAttributes.isLast() && nValueIndex == rSparklineValues.mnLastIndex)
        {
            rRenderContext.SetLineColor(rAttributes.getColorLast().getFinalColor());
            rRenderContext.SetFillColor(rAttributes.getColorLast().getFinalColor());
        }
        else if (rAttributes.isHigh() && nValue == rSparklineValues.mfMaximum)
        {
            rRenderContext.SetLineColor(rAttributes.getColorHigh().getFinalColor());
            rRenderContext.SetFillColor(rAttributes.getColorHigh().getFinalColor());
        }
        else if (rAttributes.isLow() && nValue == rSparklineValues.mfMinimum)
        {
            rRenderContext.SetLineColor(rAttributes.getColorLow().getFinalColor());
            rRenderContext.SetFillColor(rAttributes.getColorLow().getFinalColor());
        }
        else if (rAttributes.isNegative() && nValue < 0.0)
        {
            rRenderContext.SetLineColor(rAttributes.getColorNegative().getFinalColor());
            rRenderContext.SetFillColor(rAttributes.getColorNegative().getFinalColor());
        }
        else
        {
            rRenderContext.SetLineColor(rAttributes.getColorSeries().getFinalColor());
            rRenderContext.SetFillColor(rAttributes.getColorSeries().getFinalColor());
        }
    }

    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().getFinalColor());
                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();
        comphelper::ScopeGuard aPushPopGuard([&rRenderContext]() { rRenderContext.Pop(); });

        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());
        }
    }
};
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Messung V0.5
C=91 H=98 G=94

¤ Dauer der Verarbeitung: 0.1 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.