Coverage Report

Created: 2026-07-10 11:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/chart2/source/model/template/HistogramCalculator.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 */
9
10
#include <HistogramCalculator.hxx>
11
12
#include <algorithm>
13
#include <cmath>
14
15
namespace chart
16
{
17
0
HistogramCalculator::HistogramCalculator() = default;
18
19
void HistogramCalculator::computeBinFrequencyHistogram(
20
    const std::vector<double>& rDataPoints, sal_Int32 nFrequencyType, double fFixedBinWidth,
21
    sal_Int32 nFixedBinCount, bool bUseUnderflowBin, double fUnderflowBinValue,
22
    bool bUseOverflowBin, double fOverflowBinValue)
23
0
{
24
0
    if (rDataPoints.empty())
25
0
        return;
26
27
0
    mnBins = 1;
28
0
    mfBinWidth = 1.0;
29
0
    maBinRanges.clear();
30
0
    maBinFrequencies.clear();
31
0
    maBinTypes.clear();
32
33
    // Calculate statistics
34
0
    double fSum = 0.0;
35
0
    double fSquareSum = 0.0;
36
0
    double fMinValue = rDataPoints[0];
37
0
    double fMaxValue = rDataPoints[0];
38
0
    sal_Int32 nValidCount = 0;
39
40
    // Compute min and max values, ignoring non-finite values
41
0
    for (const auto& rValue : rDataPoints)
42
0
    {
43
0
        if (std::isfinite(rValue))
44
0
        {
45
0
            if (nValidCount == 0)
46
0
            {
47
0
                fMinValue = rValue;
48
0
                fMaxValue = rValue;
49
0
            }
50
0
            else
51
0
            {
52
0
                fMinValue = std::min(fMinValue, rValue);
53
0
                fMaxValue = std::max(fMaxValue, rValue);
54
0
            }
55
56
0
            fSum += rValue;
57
0
            fSquareSum += rValue * rValue;
58
0
            ++nValidCount;
59
0
        }
60
0
    }
61
62
0
    if (nValidCount == 0)
63
0
        return;
64
65
0
    const bool bHasUnderflow = bUseUnderflowBin && std::isfinite(fUnderflowBinValue);
66
0
    const bool bHasOverflow = bUseOverflowBin && std::isfinite(fOverflowBinValue);
67
68
    // Ignore both special bins if their boundaries cross; otherwise normal bins
69
    // would have an invalid range.
70
0
    const bool bUseSpecialBins
71
0
        = !(bHasUnderflow && bHasOverflow && fUnderflowBinValue >= fOverflowBinValue);
72
73
0
    const bool bEffectiveUnderflow = bHasUnderflow && bUseSpecialBins;
74
0
    const bool bEffectiveOverflow = bHasOverflow && bUseSpecialBins;
75
76
0
    double fEffectiveMin = bEffectiveUnderflow ? fUnderflowBinValue : fMinValue;
77
0
    double fEffectiveMax = bEffectiveOverflow ? fOverflowBinValue : fMaxValue;
78
79
0
    if (fEffectiveMin >= fEffectiveMax)
80
0
    {
81
0
        fEffectiveMin = fMinValue;
82
0
        fEffectiveMax = fMaxValue;
83
0
    }
84
85
0
    if (nValidCount < 2 || fEffectiveMin == fEffectiveMax) // Need at least two points for variance
86
0
    {
87
0
        mnBins = 1;
88
0
        mfBinWidth = 1.0;
89
90
0
        if (bEffectiveUnderflow)
91
0
        {
92
0
            maBinTypes.push_back(HistogramBinType::Underflow);
93
0
            maBinRanges.emplace_back(fUnderflowBinValue, fUnderflowBinValue);
94
0
        }
95
96
0
        maBinTypes.push_back(HistogramBinType::Normal);
97
0
        maBinRanges.emplace_back(std::floor(fEffectiveMin), std::ceil(fEffectiveMin + 1.0));
98
99
0
        if (bEffectiveOverflow)
100
0
        {
101
0
            maBinTypes.push_back(HistogramBinType::Overflow);
102
0
            maBinRanges.emplace_back(fOverflowBinValue, fOverflowBinValue);
103
0
        }
104
105
0
        maBinFrequencies.assign(maBinRanges.size(), 0);
106
0
    }
107
0
    else
108
0
    {
109
        // Pick bin width / count based on frequency-type mode. Invalid fixed values
110
        // fall back to auto so stale or unset properties cannot produce a degenerate histogram.
111
0
        bool bResolved = false;
112
0
        if (nFrequencyType == 1 && fFixedBinWidth > 0.0)
113
0
        {
114
0
            mfBinWidth = fFixedBinWidth;
115
0
            mnBins
116
0
                = static_cast<sal_Int32>(std::ceil((fEffectiveMax - fEffectiveMin) / mfBinWidth));
117
0
            bResolved = true;
118
0
        }
119
0
        else if (nFrequencyType == 2 && nFixedBinCount > 0)
120
0
        {
121
0
            mnBins = nFixedBinCount;
122
0
            mfBinWidth = (fEffectiveMax - fEffectiveMin) / mnBins;
123
0
            bResolved = true;
124
0
        }
125
126
0
        if (!bResolved)
127
0
        {
128
            // Auto: Scott's rule.
129
0
            double fMean = fSum / nValidCount;
130
0
            double fVariance = (fSquareSum - fSum * fMean) / (nValidCount - 1);
131
0
            double fStdDev = std::sqrt(fVariance);
132
133
0
            mfBinWidth = (3.5 * fStdDev) / std::cbrt(nValidCount);
134
0
            mnBins
135
0
                = static_cast<sal_Int32>(std::ceil((fEffectiveMax - fEffectiveMin) / mfBinWidth));
136
0
        }
137
138
0
        mnBins = std::max<sal_Int32>(mnBins, 1); // Ensure at least one bin
139
140
0
        maBinRanges.reserve(mnBins + (bEffectiveUnderflow ? 1 : 0) + (bEffectiveOverflow ? 1 : 0));
141
0
        maBinTypes.reserve(mnBins + (bEffectiveUnderflow ? 1 : 0) + (bEffectiveOverflow ? 1 : 0));
142
143
0
        if (bEffectiveUnderflow)
144
0
        {
145
0
            maBinTypes.push_back(HistogramBinType::Underflow);
146
0
            maBinRanges.emplace_back(fUnderflowBinValue, fUnderflowBinValue);
147
0
        }
148
149
0
        double fBinStart = fEffectiveMin;
150
0
        for (sal_Int32 i = 0; i < mnBins; ++i)
151
0
        {
152
0
            double fBinEnd = fBinStart + mfBinWidth;
153
154
0
            maBinTypes.push_back(HistogramBinType::Normal);
155
0
            maBinRanges.emplace_back(fBinStart, fBinEnd);
156
157
0
            fBinStart = fBinEnd;
158
0
        }
159
160
        // With an overflow bin values above the overflow boundary belong to the overflow bin
161
0
        if (!maBinRanges.empty())
162
0
        {
163
0
            size_t nLastNormal = maBinRanges.size() - 1;
164
0
            if (bEffectiveOverflow)
165
0
                maBinRanges[nLastNormal].second = fEffectiveMax;
166
0
            else
167
0
                maBinRanges[nLastNormal].second
168
0
                    = std::max(maBinRanges[nLastNormal].second, fEffectiveMax);
169
0
        }
170
171
0
        if (bEffectiveOverflow)
172
0
        {
173
0
            maBinTypes.push_back(HistogramBinType::Overflow);
174
0
            maBinRanges.emplace_back(fOverflowBinValue, fOverflowBinValue);
175
0
        }
176
177
0
        maBinFrequencies.assign(maBinRanges.size(), 0);
178
0
    }
179
180
    // Calculate frequencies. Underflow is <= boundary, overflow is > boundary.
181
    // Normal bins are [start,end] for the first bin unless an underflow bin exists;
182
    // otherwise they are (start,end].
183
0
    for (double fValue : rDataPoints)
184
0
    {
185
0
        if (!std::isfinite(fValue))
186
0
            continue;
187
188
0
        for (size_t i = 0; i < maBinRanges.size(); ++i)
189
0
        {
190
0
            if (maBinTypes[i] == HistogramBinType::Underflow)
191
0
            {
192
0
                if (fValue <= maBinRanges[i].second)
193
0
                {
194
0
                    maBinFrequencies[i]++;
195
0
                    break;
196
0
                }
197
0
                continue;
198
0
            }
199
200
0
            if (maBinTypes[i] == HistogramBinType::Overflow)
201
0
            {
202
0
                if (fValue > maBinRanges[i].first)
203
0
                {
204
0
                    maBinFrequencies[i]++;
205
0
                    break;
206
0
                }
207
0
                continue;
208
0
            }
209
210
0
            const bool bPreviousIsUnderflow
211
0
                = i > 0 && maBinTypes[i - 1] == HistogramBinType::Underflow;
212
0
            const bool bFirstNormalBin = i == 0 || bPreviousIsUnderflow;
213
214
0
            const bool bInBin
215
0
                = bFirstNormalBin && !bPreviousIsUnderflow
216
0
                      ? fValue >= maBinRanges[i].first && fValue <= maBinRanges[i].second
217
0
                      : fValue > maBinRanges[i].first && fValue <= maBinRanges[i].second;
218
219
0
            if (bInBin)
220
0
            {
221
0
                maBinFrequencies[i]++;
222
0
                break;
223
0
            }
224
0
        }
225
0
    }
226
0
}
227
228
} // namespace chart
229
230
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */