Files
odfstudio/chart2/source/tools/HistogramDataSequence.cxx
T
Devansh Varshney 22b73ccfdc chart2: keep histogram axis labels in sync with bin settings
Histogram bin-property changes regenerated the category sequence stored
on the data series, while the X axis continued referencing the previous
sequence. This left the chart bars updated but displayed stale bin
labels.

Keep the category sequence produced by HistogramDataInterpreter on both
the diagram axis and data series. Update its binning parameters in place
when histogram properties change, allowing existing listeners to
recalculate and redraw the axis labels.

Signed-off-by: Devansh Varshney <varshney.devansh614@gmail.com>
Change-Id: Ibd6679bddf29de5fa1fd903af9274259b2e9c243
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9282
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 0103afa2a6a746ce54a0e3e4297e1fa60698ef03)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209218
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-01 14:00:42 +02:00

321 lines
10 KiB
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/.
*/
#include <HistogramCalculator.hxx>
#include <HistogramDataSequence.hxx>
#include <algorithm>
#include <cmath>
#include <CommonFunctors.hxx>
#include <ModifyListenerHelper.hxx>
#include <utility>
#include <cppuhelper/implbase.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/servicehelper.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <osl/mutex.hxx>
#include <com/sun/star/lang/EventObject.hpp>
#include <com/sun/star/util/XModifyBroadcaster.hpp>
#include <com/sun/star/uno/Any.hxx>
using namespace css;
namespace
{
constexpr OUString lcl_aServiceName = u"com.sun.star.chart2.data.HistogramDataSequence"_ustr;
enum
{
PROP_PROPOSED_ROLE
};
} // anonymous namespace
namespace chart
{
HistogramDataSequence::HistogramDataSequence(
const uno::Reference<chart2::data::XDataSequence>& xRawData, bool bIsCategory,
sal_Int32 nFrequencyType, double fBinWidth, sal_Int32 nBinCount, bool bUseUnderflowBin,
double fUnderflowBinValue, bool bUseOverflowBin, double fOverflowBinValue)
: ::comphelper::OPropertyContainer(GetBroadcastHelper())
, m_xModifyEventForwarder(new ModifyEventForwarder())
, m_xRawData(xRawData)
, m_bIsCategory(bIsCategory)
, m_bIsDirty(true)
, m_nFrequencyType(nFrequencyType)
, m_fBinWidth(fBinWidth)
, m_nBinCount(nBinCount)
, m_bUseUnderflowBin(bUseUnderflowBin)
, m_fUnderflowBinValue(fUnderflowBinValue)
, m_bUseOverflowBin(bUseOverflowBin)
, m_fOverflowBinValue(fOverflowBinValue)
{
registerProperty(u"Role"_ustr, PROP_PROPOSED_ROLE,
0, // PropertyAttributes
&m_sRole, cppu::UnoType<decltype(m_sRole)>::get());
if (m_xRawData.is())
{
// Listen for changes in the raw spreadsheet data
uno::Reference<util::XModifyBroadcaster> xBroadcaster(m_xRawData, uno::UNO_QUERY);
if (xBroadcaster.is())
xBroadcaster->addModifyListener(this);
}
}
HistogramDataSequence::~HistogramDataSequence()
{
if (m_xRawData.is())
{
uno::Reference<util::XModifyBroadcaster> xBroadcaster(m_xRawData, uno::UNO_QUERY);
if (xBroadcaster.is())
xBroadcaster->removeModifyListener(this);
}
}
void HistogramDataSequence::setBinningParameters(sal_Int32 nFrequencyType, double fBinWidth,
sal_Int32 nBinCount, bool bUseUnderflowBin,
double fUnderflowBinValue, bool bUseOverflowBin,
double fOverflowBinValue)
{
{
osl::MutexGuard aGuard(GetMutex());
// Recomputing the bins is expensive, so parameters that repeat the ones already held
// leave the cached bins and the modified state alone.
if (m_nFrequencyType == nFrequencyType && m_fBinWidth == fBinWidth
&& m_nBinCount == nBinCount && m_bUseUnderflowBin == bUseUnderflowBin
&& m_fUnderflowBinValue == fUnderflowBinValue && m_bUseOverflowBin == bUseOverflowBin
&& m_fOverflowBinValue == fOverflowBinValue)
{
return;
}
m_nFrequencyType = nFrequencyType;
m_fBinWidth = fBinWidth;
m_nBinCount = nBinCount;
m_bUseUnderflowBin = bUseUnderflowBin;
m_fUnderflowBinValue = fUnderflowBinValue;
m_bUseOverflowBin = bUseOverflowBin;
m_fOverflowBinValue = fOverflowBinValue;
m_bIsDirty = true;
}
m_xModifyEventForwarder->modified(lang::EventObject(static_cast<cppu::OWeakObject*>(this)));
}
IMPLEMENT_FORWARD_XINTERFACE2(HistogramDataSequence, HistogramDataSequence_Base,
::comphelper::OPropertyContainer)
IMPLEMENT_FORWARD_XTYPEPROVIDER2(HistogramDataSequence, HistogramDataSequence_Base,
::comphelper::OPropertyContainer)
uno::Reference<beans::XPropertySetInfo> SAL_CALL HistogramDataSequence::getPropertySetInfo()
{
return comphelper::OPropertyStateHelper::createPropertySetInfo(getInfoHelper());
}
::cppu::IPropertyArrayHelper& SAL_CALL HistogramDataSequence::getInfoHelper()
{
return *getArrayHelper();
}
::cppu::IPropertyArrayHelper* HistogramDataSequence::createArrayHelper() const
{
uno::Sequence<beans::Property> aProperties;
describeProperties(aProperties);
return new ::cppu::OPropertyArrayHelper(aProperties);
}
OUString SAL_CALL HistogramDataSequence::getImplementationName() { return lcl_aServiceName; }
sal_Bool SAL_CALL HistogramDataSequence::supportsService(const OUString& rServiceName)
{
return cppu::supportsService(this, rServiceName);
}
uno::Sequence<OUString> SAL_CALL HistogramDataSequence::getSupportedServiceNames()
{
return { lcl_aServiceName, u"com.sun.star.chart2.data.DataSequence"_ustr,
u"com.sun.star.chart2.data.NumericalDataSequence"_ustr,
u"com.sun.star.chart2.data.TextualDataSequence"_ustr };
}
uno::Sequence<double> SAL_CALL HistogramDataSequence::getNumericalData()
{
::osl::MutexGuard aGuard(GetMutex());
ensureCalculated();
return CommonFunctors::convertToSequence(mxValues, CommonFunctors::ToDouble());
}
uno::Sequence<OUString> SAL_CALL HistogramDataSequence::getTextualData()
{
::osl::MutexGuard aGuard(GetMutex());
ensureCalculated();
return CommonFunctors::convertToSequence(mxValues, CommonFunctors::ToString());
}
uno::Sequence<uno::Any> SAL_CALL HistogramDataSequence::getData()
{
::osl::MutexGuard aGuard(GetMutex());
ensureCalculated();
return mxValues;
}
OUString SAL_CALL HistogramDataSequence::getSourceRangeRepresentation()
{
// synthesized sequence — no underlying cell range
return OUString();
}
uno::Sequence<OUString>
SAL_CALL HistogramDataSequence::generateLabel(css::chart2::data::LabelOrigin)
{
::osl::MutexGuard aGuard(GetMutex());
return mxLabels;
}
sal_Int32 SAL_CALL HistogramDataSequence::getNumberFormatKeyByIndex(sal_Int32)
{
return 0; // TODO - NumberFormat support
}
void SAL_CALL
HistogramDataSequence::addModifyListener(const uno::Reference<util::XModifyListener>& aListener)
{
m_xModifyEventForwarder->addModifyListener(aListener);
}
void SAL_CALL
HistogramDataSequence::removeModifyListener(const uno::Reference<util::XModifyListener>& aListener)
{
m_xModifyEventForwarder->removeModifyListener(aListener);
}
void SAL_CALL HistogramDataSequence::modified(const lang::EventObject& /* aEvent */)
{
// The raw data changed! Mark ourselves dirty and tell the chart to redraw
{
::osl::MutexGuard aGuard(GetMutex());
m_bIsDirty = true;
}
m_xModifyEventForwarder->modified(lang::EventObject(static_cast<cppu::OWeakObject*>(this)));
}
void SAL_CALL HistogramDataSequence::disposing(const lang::EventObject& /* Source */)
{
m_xRawData.clear();
}
uno::Reference<util::XCloneable> SAL_CALL HistogramDataSequence::createClone()
{
rtl::Reference<HistogramDataSequence> pClone(new HistogramDataSequence(
m_xRawData, m_bIsCategory, m_nFrequencyType, m_fBinWidth, m_nBinCount, m_bUseUnderflowBin,
m_fUnderflowBinValue, m_bUseOverflowBin, m_fOverflowBinValue));
::osl::MutexGuard aGuard(GetMutex());
pClone->mxLabels = mxLabels;
pClone->mxValues = mxValues;
pClone->m_sRole = m_sRole;
pClone->m_bIsDirty = m_bIsDirty;
return pClone;
}
void HistogramDataSequence::ensureCalculated()
{
if (!m_bIsDirty || !m_xRawData.is())
return;
std::vector<double> rawData;
try
{
uno::Sequence<uno::Any> aRawAnyValues = m_xRawData->getData();
for (const auto& aAny : aRawAnyValues)
{
double fValue = 0.0;
if (aAny >>= fValue)
rawData.push_back(fValue);
}
}
catch (const uno::Exception&)
{
DBG_UNHANDLED_EXCEPTION("chart2");
}
HistogramCalculator aCalculator;
aCalculator.computeBinFrequencyHistogram(rawData, m_nFrequencyType, m_fBinWidth, m_nBinCount,
m_bUseUnderflowBin, m_fUnderflowBinValue,
m_bUseOverflowBin, m_fOverflowBinValue);
std::vector<uno::Any> aNewValues;
if (m_bIsCategory)
{
const auto& binRanges = aCalculator.getBinRanges();
const auto& binTypes = aCalculator.getBinTypes();
// keep ~3 significant figures relative to the bin width, so typical
// widths (~1) get 2 decimals and small widths (~0.002) get 5
int labelDigits = 2;
if (!binRanges.empty())
{
double firstBinWidth = binRanges.front().second - binRanges.front().first;
if (firstBinWidth > 0.0)
labelDigits
= std::max(1, 2 - static_cast<int>(std::floor(std::log10(firstBinWidth))));
}
const double roundingScale = std::pow(10.0, labelDigits);
auto formatBoundary = [roundingScale](double value) {
return OUString::number(std::round(value * roundingScale) / roundingScale);
};
for (size_t i = 0; i < binRanges.size(); ++i)
{
OUString aLabel;
if (binTypes[i] == HistogramBinType::Underflow)
{
aLabel = u"<= "_ustr + formatBoundary(binRanges[i].second);
}
else if (binTypes[i] == HistogramBinType::Overflow)
{
aLabel = u"> "_ustr + formatBoundary(binRanges[i].first);
}
else
{
const bool bPreviousIsUnderflow
= i > 0 && binTypes[i - 1] == HistogramBinType::Underflow;
const bool bUseClosedStart = i == 0 && !bPreviousIsUnderflow;
aLabel = (bUseClosedStart ? u"["_ustr : u"("_ustr)
+ formatBoundary(binRanges[i].first) + u"-"_ustr
+ formatBoundary(binRanges[i].second) + u"]"_ustr;
}
aNewValues.push_back(uno::Any(aLabel));
}
}
else
{
const auto& binFrequencies = aCalculator.getBinFrequencies();
for (sal_Int32 freq : binFrequencies)
{
aNewValues.push_back(uno::Any(static_cast<double>(freq)));
}
}
mxValues = comphelper::containerToSequence(aNewValues);
m_bIsDirty = false;
}
} // namespace chart2
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */