chart2/source/view/charttypes/AreaChart.cxx | 2 chart2/source/view/charttypes/VSeriesPlotter.cxx | 125 +++++++---------------- 2 files changed, 42 insertions(+), 85 deletions(-)
New commits: commit 51ea035a6f1f05260381bf98373774cde7ce5dfa Author: Noel Grandin <noelgran...@gmail.com> AuthorDate: Sat Apr 5 19:41:37 2025 +0200 Commit: Noel Grandin <noelgran...@gmail.com> CommitDate: Sun Apr 6 07:12:52 2025 +0200 tdf#151876 speedup editing large chart reduces hang time when switching chart to edit mode by 50%. PerXMinMaxCalculator does not need to build a summary data structure, since we already walking over the data in X order. Also switch some the intermediate data structures to more cache friendly ones. In this case, std::map is better because we walk the data in X order. Change-Id: I3d291fd56becc1dfdd7d2df87d04b11f363bfd52 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/183747 Reviewed-by: Noel Grandin <noel.gran...@collabora.co.uk> Tested-by: Jenkins diff --git a/chart2/source/view/charttypes/VSeriesPlotter.cxx b/chart2/source/view/charttypes/VSeriesPlotter.cxx index ecaff62a93ee..bb85d35afffd 100644 --- a/chart2/source/view/charttypes/VSeriesPlotter.cxx +++ b/chart2/source/view/charttypes/VSeriesPlotter.cxx @@ -1944,117 +1944,65 @@ namespace { class PerXMinMaxCalculator { typedef std::pair<double, double> MinMaxType; - typedef std::map<size_t, MinMaxType> SeriesMinMaxType; + typedef std::vector<MinMaxType> SeriesMinMaxType; typedef std::map<double, SeriesMinMaxType> GroupMinMaxType; - typedef std::unordered_map<double, MinMaxType> TotalStoreType; GroupMinMaxType m_SeriesGroup; + size_t mnNumSeries; size_t mnCurSeries; public: - PerXMinMaxCalculator() : mnCurSeries(0) {} + PerXMinMaxCalculator(size_t numSeries) : mnNumSeries(numSeries), mnCurSeries(0) {} void nextSeries() { ++mnCurSeries; } void setValue(double fX, double fY) { - SeriesMinMaxType* pStore = getByXValue(fX); // get storage for given X value. - if (!pStore) - // This shouldn't happen! - return; + SeriesMinMaxType& rStore = m_SeriesGroup[fX]; // get storage for given X value. + if (rStore.empty()) + rStore.resize(mnNumSeries, { std::numeric_limits<double>::max(), std::numeric_limits<double>::min() }); - SeriesMinMaxType::iterator it = pStore->lower_bound(mnCurSeries); - if (it != pStore->end() && !pStore->key_comp()(mnCurSeries, it->first)) - { - MinMaxType& r = it->second; - // A min-max pair already exists for this series. Update it. - if (fY < r.first) - r.first = fY; - if (r.second < fY) - r.second = fY; - } - else - { - // No existing pair. Insert a new one. - pStore->insert( - it, SeriesMinMaxType::value_type( - mnCurSeries, MinMaxType(fY,fY))); - } + MinMaxType& r = rStore[mnCurSeries]; + if (fY < r.first) + r.first = fY; + if (r.second < fY) + r.second = fY; } void getTotalRange(double& rfMin, double& rfMax) const { - TotalStoreType aStore; - getTotalStore(aStore); - - if (aStore.empty()) + if (m_SeriesGroup.empty()) { rfMin = std::numeric_limits<double>::quiet_NaN(); rfMax = std::numeric_limits<double>::quiet_NaN(); return; } - TotalStoreType::const_iterator it = aStore.begin(), itEnd = aStore.end(); - rfMin = it->second.first; - rfMax = it->second.second; - for (++it; it != itEnd; ++it) - { - if (rfMin > it->second.first) - rfMin = it->second.first; - if (rfMax < it->second.second) - rfMax = it->second.second; - } - } + rfMin = std::numeric_limits<double>::max(); + rfMax = std::numeric_limits<double>::min(); -private: - /** - * Parse all data and reduce them into a set of global Y value ranges per - * X value. - */ - void getTotalStore(TotalStoreType& rStore) const - { - TotalStoreType aStore; + /** + * For each, X value, calculate Y value range + */ for (auto const& it : m_SeriesGroup) { - double fX = it.first; - const SeriesMinMaxType& rSeries = it.second; - for (auto const& series : rSeries) + MinMaxType aPerXMinMax { std::numeric_limits<double>::max(), 0.0 }; + for (MinMaxType const& series : rSeries) { - double fYMin = series.second.first, fYMax = series.second.second; - TotalStoreType::iterator itr = aStore.find(fX); - if (itr == aStore.end()) - // New min-max pair for give X value. - aStore.emplace(fX, std::pair<double,double>(fYMin,fYMax)); - else - { - MinMaxType& r = itr->second; - if (fYMin < r.first) - r.first = fYMin; // min y-value + double fYMin = series.first, fYMax = series.second; + if (fYMin < aPerXMinMax.first) + aPerXMinMax.first = fYMin; // min y-value - r.second += fYMax; // accumulative max y-value. - } + aPerXMinMax.second += fYMax; // accumulative max y-value. } - } - rStore.swap(aStore); - } - - SeriesMinMaxType* getByXValue(double fX) - { - GroupMinMaxType::iterator it = m_SeriesGroup.find(fX); - if (it == m_SeriesGroup.end()) - { - std::pair<GroupMinMaxType::iterator,bool> r = - m_SeriesGroup.insert(std::make_pair(fX, SeriesMinMaxType{})); - if (!r.second) - // insertion failed. - return nullptr; - - it = r.first; + if (rfMin > aPerXMinMax.first) + rfMin = aPerXMinMax.first; + if (rfMax < aPerXMinMax.second) + rfMax = aPerXMinMax.second; } - - return &it->second; } + }; } @@ -2069,7 +2017,17 @@ void VDataSeriesGroup::getMinimumAndMaximumYInContinuousXRange( // No data series. Bail out. return; - PerXMinMaxCalculator aRangeCalc; + size_t nNumSeries = 0; + for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector) + { + if (!pSeries) + continue; + if (nAxisIndex != pSeries->getAttachedAxisIndex()) + continue; + nNumSeries++; + } + + PerXMinMaxCalculator aRangeCalc(nNumSeries); for (const std::unique_ptr<VDataSeries> & pSeries : m_aSeriesVector) { if (!pSeries) commit a35e809943ec9a06a94cff3b11019fb766e8c3a4 Author: Noel Grandin <noelgran...@gmail.com> AuthorDate: Sat Apr 5 18:30:37 2025 +0200 Commit: Noel Grandin <noelgran...@gmail.com> CommitDate: Sun Apr 6 07:12:40 2025 +0200 hoist calls outside loop in chart2 no need to call these inside the loop Change-Id: Ica84ca353e3c7b14542c405d8866386210babc3e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/183746 Reviewed-by: Noel Grandin <noel.gran...@collabora.co.uk> Tested-by: Jenkins diff --git a/chart2/source/view/charttypes/AreaChart.cxx b/chart2/source/view/charttypes/AreaChart.cxx index f5a73b966c4a..027fd936ee52 100644 --- a/chart2/source/view/charttypes/AreaChart.cxx +++ b/chart2/source/view/charttypes/AreaChart.cxx @@ -618,10 +618,10 @@ void AreaChart::createShapes() if (bDateCategory) pSeries->doSortByXValues(); + sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex(); for( sal_Int32 nIndex = nStartIndex; nIndex < nEndIndex; nIndex++ ) { std::map< sal_Int32, double >& rLogicYSumMap = aLogicYSumMapByX[nIndex]; - sal_Int32 nAttachedAxisIndex = pSeries->getAttachedAxisIndex(); rLogicYSumMap.insert({nAttachedAxisIndex, 0.0}); m_pPosHelper = &getPlottingPositionHelper(nAttachedAxisIndex); diff --git a/chart2/source/view/charttypes/VSeriesPlotter.cxx b/chart2/source/view/charttypes/VSeriesPlotter.cxx index 952dd9c7f050..ecaff62a93ee 100644 --- a/chart2/source/view/charttypes/VSeriesPlotter.cxx +++ b/chart2/source/view/charttypes/VSeriesPlotter.cxx @@ -2074,12 +2074,11 @@ void VDataSeriesGroup::getMinimumAndMaximumYInContinuousXRange( { if (!pSeries) continue; + if (nAxisIndex != pSeries->getAttachedAxisIndex()) + continue; for (sal_Int32 i = 0, n = pSeries->getTotalPointCount(); i < n; ++i) { - if (nAxisIndex != pSeries->getAttachedAxisIndex()) - continue; - double fX = pSeries->getXValue(i); if (std::isnan(fX)) continue;