tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / sc / source / ui / inc / SparklineRenderer.hxx
blob1a8adc39a828746476795a9ac2b11e84a22b1046
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 */
11 #pragma once
13 #include <document.hxx>
15 #include <basegfx/polygon/b2dpolygon.hxx>
16 #include <basegfx/polygon/b2dpolygontools.hxx>
17 #include <basegfx/matrix/b2dhommatrix.hxx>
18 #include <comphelper/scopeguard.hxx>
20 #include <Sparkline.hxx>
21 #include <SparklineGroup.hxx>
22 #include <SparklineAttributes.hxx>
24 namespace sc
26 /** Contains the marker polygon and the color of a marker */
27 struct SparklineMarker
29 basegfx::B2DPolygon maPolygon;
30 Color maColor;
33 /** Sparkline value and action that needs to me performed on the value */
34 struct SparklineValue
36 enum class Action
38 None, // No action on the value
39 Skip, // Skip the value
40 Interpolate // Interpolate the value
43 double maValue;
44 Action meAction;
46 SparklineValue(double aValue, Action eAction)
47 : maValue(aValue)
48 , meAction(eAction)
53 /** Contains and manages the values of the sparkline.
55 * It automatically keeps track of the minimums and maximums, and
56 * skips or interpolates the sparkline values if needed, depending on
57 * the input. This is done so it is easier to handle the sparkline
58 * values later on.
60 class SparklineValues
62 private:
63 double mfPreviousValue = 0.0;
64 size_t mnPreviousIndex = std::numeric_limits<size_t>::max();
66 std::vector<size_t> maToInterpolateIndex;
68 std::vector<SparklineValue> maValueList;
70 public:
71 size_t mnFirstIndex = std::numeric_limits<size_t>::max();
72 size_t mnLastIndex = 0;
74 double mfMinimum = std::numeric_limits<double>::max();
75 double mfMaximum = std::numeric_limits<double>::lowest();
77 std::vector<SparklineValue> const& getValuesList() const { return maValueList; }
79 void add(double fValue, SparklineValue::Action eAction)
81 maValueList.emplace_back(fValue, eAction);
82 size_t nCurrentIndex = maValueList.size() - 1;
84 if (eAction == SparklineValue::Action::None)
86 mnLastIndex = nCurrentIndex;
88 if (mnLastIndex < mnFirstIndex)
89 mnFirstIndex = mnLastIndex;
91 if (fValue < mfMinimum)
92 mfMinimum = fValue;
94 if (fValue > mfMaximum)
95 mfMaximum = fValue;
97 interpolatePastValues(fValue, nCurrentIndex);
99 mnPreviousIndex = nCurrentIndex;
100 mfPreviousValue = fValue;
102 else if (eAction == SparklineValue::Action::Interpolate)
104 maToInterpolateIndex.push_back(nCurrentIndex);
105 maValueList.back().meAction = SparklineValue::Action::Skip;
109 static constexpr double interpolate(double x1, double y1, double x2, double y2, double x)
111 return (y1 * (x2 - x) + y2 * (x - x1)) / (x2 - x1);
114 void interpolatePastValues(double nCurrentValue, size_t nCurrentIndex)
116 if (maToInterpolateIndex.empty())
117 return;
119 if (mnPreviousIndex == std::numeric_limits<size_t>::max())
121 for (size_t nIndex : maToInterpolateIndex)
123 auto& rValue = maValueList[nIndex];
124 rValue.meAction = SparklineValue::Action::Skip;
127 else
129 for (size_t nIndex : maToInterpolateIndex)
131 double fInterpolated = interpolate(mnPreviousIndex, mfPreviousValue, nCurrentIndex,
132 nCurrentValue, nIndex);
134 auto& rValue = maValueList[nIndex];
135 rValue.maValue = fInterpolated;
136 rValue.meAction = SparklineValue::Action::None;
139 maToInterpolateIndex.clear();
142 void convertToStacked()
144 // transform the data to 1, -1
145 for (auto& rValue : maValueList)
147 if (rValue.maValue != 0.0)
149 double fNewValue = rValue.maValue > 0.0 ? 1.0 : -1.0;
151 if (rValue.maValue == mfMinimum)
152 fNewValue -= 0.01;
154 if (rValue.maValue == mfMaximum)
155 fNewValue += 0.01;
157 rValue.maValue = fNewValue;
160 mfMinimum = -1.01;
161 mfMaximum = 1.01;
164 void reverse() { std::reverse(maValueList.begin(), maValueList.end()); }
167 /** Iterator to traverse the addresses in a range if the range is one dimensional.
169 * The direction to traverse is detected automatically or hasNext returns
170 * false if it is not possible to detect.
173 class RangeTraverser
175 enum class Direction
177 UNKNOWN,
178 ROW,
179 COLUMN
182 ScAddress m_aCurrent;
183 ScRange m_aRange;
184 Direction m_eDirection;
186 public:
187 RangeTraverser(ScRange const& rRange)
188 : m_aCurrent(ScAddress::INITIALIZE_INVALID)
189 , m_aRange(rRange)
190 , m_eDirection(Direction::UNKNOWN)
195 ScAddress const& first()
197 m_aCurrent.SetInvalid();
199 if (m_aRange.aStart.Row() == m_aRange.aEnd.Row())
201 m_eDirection = Direction::COLUMN;
202 m_aCurrent = m_aRange.aStart;
204 else if (m_aRange.aStart.Col() == m_aRange.aEnd.Col())
206 m_eDirection = Direction::ROW;
207 m_aCurrent = m_aRange.aStart;
210 return m_aCurrent;
213 bool hasNext()
215 if (m_eDirection == Direction::COLUMN)
216 return m_aCurrent.Col() <= m_aRange.aEnd.Col();
217 else if (m_eDirection == Direction::ROW)
218 return m_aCurrent.Row() <= m_aRange.aEnd.Row();
219 else
220 return false;
223 void next()
225 if (hasNext())
227 if (m_eDirection == Direction::COLUMN)
228 m_aCurrent.IncCol();
229 else if (m_eDirection == Direction::ROW)
230 m_aCurrent.IncRow();
235 /** Render a provided sparkline into the input rectangle */
236 class SparklineRenderer
238 private:
239 ScDocument& mrDocument;
240 tools::Long mnOneX;
241 tools::Long mnOneY;
243 double mfScaleX;
244 double mfScaleY;
246 void createMarker(std::vector<SparklineMarker>& rMarkers, double x, double y,
247 Color const& rColor)
249 auto& rMarker = rMarkers.emplace_back();
250 const double nHalfSizeX = double(mnOneX * 2 * mfScaleX);
251 const double nHalfSizeY = double(mnOneY * 2 * mfScaleY);
252 basegfx::B2DRectangle aRectangle(std::round(x - nHalfSizeX), std::round(y - nHalfSizeY),
253 std::round(x + nHalfSizeX), std::round(y + nHalfSizeY));
254 rMarker.maPolygon = basegfx::utils::createPolygonFromRect(aRectangle);
255 rMarker.maColor = rColor;
258 void drawLine(vcl::RenderContext& rRenderContext, tools::Rectangle const& rRectangle,
259 SparklineValues const& rSparklineValues,
260 sc::SparklineAttributes const& rAttributes)
262 double nMax = rSparklineValues.mfMaximum;
263 if (rAttributes.getMaxAxisType() == sc::AxisType::Custom && rAttributes.getManualMax())
264 nMax = *rAttributes.getManualMax();
266 double nMin = rSparklineValues.mfMinimum;
267 if (rAttributes.getMinAxisType() == sc::AxisType::Custom && rAttributes.getManualMin())
268 nMin = *rAttributes.getManualMin();
270 std::vector<SparklineValue> const& rValueList = rSparklineValues.getValuesList();
271 std::vector<basegfx::B2DPolygon> aPolygons;
272 aPolygons.emplace_back();
273 double numebrOfSteps = rValueList.size() - 1;
274 double xStep = 0;
275 double nDelta = nMax - nMin;
277 std::vector<SparklineMarker> aMarkers;
278 size_t nValueIndex = 0;
280 for (auto const& rSparklineValue : rValueList)
282 if (rSparklineValue.meAction == SparklineValue::Action::Skip)
284 aPolygons.emplace_back();
286 else
288 auto& aPolygon = aPolygons.back();
289 double nValue = rSparklineValue.maValue;
291 double nP = (nValue - nMin) / nDelta;
292 double x = rRectangle.GetWidth() * (xStep / numebrOfSteps);
293 double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nP;
295 aPolygon.append({ x, y });
297 if (rAttributes.isFirst() && nValueIndex == rSparklineValues.mnFirstIndex)
299 createMarker(aMarkers, x, y, rAttributes.getColorFirst().getFinalColor());
301 else if (rAttributes.isLast() && nValueIndex == rSparklineValues.mnLastIndex)
303 createMarker(aMarkers, x, y, rAttributes.getColorLast().getFinalColor());
305 else if (rAttributes.isHigh() && nValue == rSparklineValues.mfMaximum)
307 createMarker(aMarkers, x, y, rAttributes.getColorHigh().getFinalColor());
309 else if (rAttributes.isLow() && nValue == rSparklineValues.mfMinimum)
311 createMarker(aMarkers, x, y, rAttributes.getColorLow().getFinalColor());
313 else if (rAttributes.isNegative() && nValue < 0.0)
315 createMarker(aMarkers, x, y, rAttributes.getColorNegative().getFinalColor());
317 else if (rAttributes.isMarkers())
319 createMarker(aMarkers, x, y, rAttributes.getColorMarkers().getFinalColor());
323 xStep++;
324 nValueIndex++;
327 basegfx::B2DHomMatrix aMatrix;
328 aMatrix.translate(rRectangle.Left(), rRectangle.Top());
330 if (rAttributes.shouldDisplayXAxis())
332 double nZero = 0 - nMin / nDelta;
334 if (nZero >= 0) // if nZero < 0, the axis is not visible
336 double x1 = 0.0;
337 double x2 = double(rRectangle.GetWidth());
338 double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nZero;
340 basegfx::B2DPolygon aAxisPolygon;
341 aAxisPolygon.append({ x1, y });
342 aAxisPolygon.append({ x2, y });
344 rRenderContext.SetLineColor(rAttributes.getColorAxis().getFinalColor());
345 rRenderContext.DrawPolyLineDirect(aMatrix, aAxisPolygon, 0.2 * mfScaleX);
349 rRenderContext.SetLineColor(rAttributes.getColorSeries().getFinalColor());
351 for (auto& rPolygon : aPolygons)
353 rRenderContext.DrawPolyLineDirect(aMatrix, rPolygon,
354 rAttributes.getLineWeight() * mfScaleX, 0.0, nullptr,
355 basegfx::B2DLineJoin::Round);
358 for (auto& rMarker : aMarkers)
360 rRenderContext.SetLineColor(rMarker.maColor);
361 rRenderContext.SetFillColor(rMarker.maColor);
362 auto& rPolygon = rMarker.maPolygon;
363 rPolygon.transform(aMatrix);
364 rRenderContext.DrawPolygon(rPolygon);
368 static void setFillAndLineColor(vcl::RenderContext& rRenderContext,
369 sc::SparklineAttributes const& rAttributes, double nValue,
370 size_t nValueIndex, SparklineValues const& rSparklineValues)
372 if (rAttributes.isFirst() && nValueIndex == rSparklineValues.mnFirstIndex)
374 rRenderContext.SetLineColor(rAttributes.getColorFirst().getFinalColor());
375 rRenderContext.SetFillColor(rAttributes.getColorFirst().getFinalColor());
377 else if (rAttributes.isLast() && nValueIndex == rSparklineValues.mnLastIndex)
379 rRenderContext.SetLineColor(rAttributes.getColorLast().getFinalColor());
380 rRenderContext.SetFillColor(rAttributes.getColorLast().getFinalColor());
382 else if (rAttributes.isHigh() && nValue == rSparklineValues.mfMaximum)
384 rRenderContext.SetLineColor(rAttributes.getColorHigh().getFinalColor());
385 rRenderContext.SetFillColor(rAttributes.getColorHigh().getFinalColor());
387 else if (rAttributes.isLow() && nValue == rSparklineValues.mfMinimum)
389 rRenderContext.SetLineColor(rAttributes.getColorLow().getFinalColor());
390 rRenderContext.SetFillColor(rAttributes.getColorLow().getFinalColor());
392 else if (rAttributes.isNegative() && nValue < 0.0)
394 rRenderContext.SetLineColor(rAttributes.getColorNegative().getFinalColor());
395 rRenderContext.SetFillColor(rAttributes.getColorNegative().getFinalColor());
397 else
399 rRenderContext.SetLineColor(rAttributes.getColorSeries().getFinalColor());
400 rRenderContext.SetFillColor(rAttributes.getColorSeries().getFinalColor());
404 void drawColumn(vcl::RenderContext& rRenderContext, tools::Rectangle const& rRectangle,
405 SparklineValues const& rSparklineValues,
406 sc::SparklineAttributes const& rAttributes)
408 double nMax = rSparklineValues.mfMaximum;
409 if (rAttributes.getMaxAxisType() == sc::AxisType::Custom && rAttributes.getManualMax())
410 nMax = *rAttributes.getManualMax();
412 double nMin = rSparklineValues.mfMinimum;
413 if (rAttributes.getMinAxisType() == sc::AxisType::Custom && rAttributes.getManualMin())
414 nMin = *rAttributes.getManualMin();
416 std::vector<SparklineValue> const& rValueList = rSparklineValues.getValuesList();
418 basegfx::B2DPolygon aPolygon;
419 basegfx::B2DHomMatrix aMatrix;
420 aMatrix.translate(rRectangle.Left(), rRectangle.Top());
422 double xStep = 0;
423 double numberOfSteps = rValueList.size();
424 double nDelta = nMax - nMin;
426 double nColumnSize = rRectangle.GetWidth() / numberOfSteps;
427 nColumnSize = nColumnSize - (nColumnSize * 0.3);
429 double nZero = (0 - nMin) / nDelta;
430 double nZeroPosition = 0.0;
431 if (nZero >= 0)
433 nZeroPosition = rRectangle.GetHeight() - rRectangle.GetHeight() * nZero;
435 if (rAttributes.shouldDisplayXAxis())
437 double x1 = 0.0;
438 double x2 = double(rRectangle.GetWidth());
440 basegfx::B2DPolygon aAxisPolygon;
441 aAxisPolygon.append({ x1, nZeroPosition });
442 aAxisPolygon.append({ x2, nZeroPosition });
444 rRenderContext.SetLineColor(rAttributes.getColorAxis().getFinalColor());
445 rRenderContext.DrawPolyLineDirect(aMatrix, aAxisPolygon, 0.2 * mfScaleX);
448 else
449 nZeroPosition = rRectangle.GetHeight();
451 size_t nValueIndex = 0;
453 for (auto const& rSparklineValue : rValueList)
455 double nValue = rSparklineValue.maValue;
457 if (nValue != 0.0)
459 setFillAndLineColor(rRenderContext, rAttributes, nValue, nValueIndex,
460 rSparklineValues);
462 double nP = (nValue - nMin) / nDelta;
463 double x = rRectangle.GetWidth() * (xStep / numberOfSteps);
464 double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nP;
466 basegfx::B2DRectangle aRectangle(x, y, x + nColumnSize, nZeroPosition);
467 aPolygon = basegfx::utils::createPolygonFromRect(aRectangle);
469 aPolygon.transform(aMatrix);
470 rRenderContext.DrawPolygon(aPolygon);
472 xStep++;
473 nValueIndex++;
477 bool isCellHidden(ScAddress const& rAddress)
479 return mrDocument.RowHidden(rAddress.Row(), rAddress.Tab())
480 || mrDocument.ColHidden(rAddress.Col(), rAddress.Tab());
483 public:
484 SparklineRenderer(ScDocument& rDocument)
485 : mrDocument(rDocument)
486 , mnOneX(1)
487 , mnOneY(1)
488 , mfScaleX(1.0)
489 , mfScaleY(1.0)
493 void render(std::shared_ptr<sc::Sparkline> const& pSparkline,
494 vcl::RenderContext& rRenderContext, tools::Rectangle const& rRectangle,
495 tools::Long nOneX, tools::Long nOneY, double fScaleX, double fScaleY)
497 rRenderContext.Push();
498 comphelper::ScopeGuard aPushPopGuard([&rRenderContext]() { rRenderContext.Pop(); });
500 rRenderContext.SetAntialiasing(AntialiasingFlags::Enable);
501 rRenderContext.SetClipRegion(vcl::Region(rRectangle));
503 tools::Rectangle aOutputRectangle(rRectangle);
504 aOutputRectangle.shrink(6); // provide border
506 mnOneX = nOneX;
507 mnOneY = nOneY;
508 mfScaleX = fScaleX;
509 mfScaleY = fScaleY;
511 auto const& rRangeList = pSparkline->getInputRange();
513 if (rRangeList.empty())
515 return;
518 auto pSparklineGroup = pSparkline->getSparklineGroup();
519 auto const& rAttributes = pSparklineGroup->getAttributes();
521 ScRange aRange = rRangeList[0];
523 SparklineValues aSparklineValues;
525 RangeTraverser aTraverser(aRange);
526 for (ScAddress const& rCurrent = aTraverser.first(); aTraverser.hasNext();
527 aTraverser.next())
529 // Skip if the cell is hidden and "displayHidden" attribute is not selected
530 if (!rAttributes.shouldDisplayHidden() && isCellHidden(rCurrent))
531 continue;
533 double fCellValue = 0.0;
534 SparklineValue::Action eAction = SparklineValue::Action::None;
535 CellType eType = mrDocument.GetCellType(rCurrent);
537 if (eType == CELLTYPE_NONE) // if cell is empty
539 auto eDisplayEmpty = rAttributes.getDisplayEmptyCellsAs();
540 if (eDisplayEmpty == sc::DisplayEmptyCellsAs::Gap)
541 eAction = SparklineValue::Action::Skip;
542 else if (eDisplayEmpty == sc::DisplayEmptyCellsAs::Span)
543 eAction = SparklineValue::Action::Interpolate;
545 else
547 fCellValue = mrDocument.GetValue(rCurrent);
550 aSparklineValues.add(fCellValue, eAction);
553 if (rAttributes.isRightToLeft())
554 aSparklineValues.reverse();
556 if (rAttributes.getType() == sc::SparklineType::Column)
558 drawColumn(rRenderContext, aOutputRectangle, aSparklineValues,
559 pSparklineGroup->getAttributes());
561 else if (rAttributes.getType() == sc::SparklineType::Stacked)
563 aSparklineValues.convertToStacked();
564 drawColumn(rRenderContext, aOutputRectangle, aSparklineValues,
565 pSparklineGroup->getAttributes());
567 else if (rAttributes.getType() == sc::SparklineType::Line)
569 drawLine(rRenderContext, aOutputRectangle, aSparklineValues,
570 pSparklineGroup->getAttributes());
576 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */