/src/libreoffice/sc/source/ui/inc/SparklineRenderer.hxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
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 | | |
11 | | #pragma once |
12 | | |
13 | | #include <document.hxx> |
14 | | |
15 | | #include <basegfx/polygon/b2dpolygon.hxx> |
16 | | #include <basegfx/polygon/b2dpolygontools.hxx> |
17 | | #include <basegfx/matrix/b2dhommatrix.hxx> |
18 | | #include <vcl/rendercontext/AntialiasingFlags.hxx> |
19 | | |
20 | | #include <Sparkline.hxx> |
21 | | #include <SparklineGroup.hxx> |
22 | | #include <SparklineAttributes.hxx> |
23 | | |
24 | | namespace sc |
25 | | { |
26 | | /** Contains the marker polygon and the color of a marker */ |
27 | | struct SparklineMarker |
28 | | { |
29 | | basegfx::B2DPolygon maPolygon; |
30 | | Color maColor; |
31 | | }; |
32 | | |
33 | | /** Sparkline value and action that needs to me performed on the value */ |
34 | | struct SparklineValue |
35 | | { |
36 | | enum class Action |
37 | | { |
38 | | None, // No action on the value |
39 | | Skip, // Skip the value |
40 | | Interpolate // Interpolate the value |
41 | | }; |
42 | | |
43 | | double maValue; |
44 | | Action meAction; |
45 | | |
46 | | SparklineValue(double aValue, Action eAction) |
47 | 0 | : maValue(aValue) |
48 | 0 | , meAction(eAction) |
49 | 0 | { |
50 | 0 | } |
51 | | }; |
52 | | |
53 | | /** Contains and manages the values of the sparkline. |
54 | | * |
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. |
59 | | */ |
60 | | class SparklineValues |
61 | | { |
62 | | private: |
63 | | double mfPreviousValue = 0.0; |
64 | | size_t mnPreviousIndex = std::numeric_limits<size_t>::max(); |
65 | | |
66 | | std::vector<size_t> maToInterpolateIndex; |
67 | | |
68 | | std::vector<SparklineValue> maValueList; |
69 | | |
70 | | public: |
71 | | size_t mnFirstIndex = std::numeric_limits<size_t>::max(); |
72 | | size_t mnLastIndex = 0; |
73 | | |
74 | | double mfMinimum = std::numeric_limits<double>::max(); |
75 | | double mfMaximum = std::numeric_limits<double>::lowest(); |
76 | | |
77 | 0 | std::vector<SparklineValue> const& getValuesList() const { return maValueList; } |
78 | | |
79 | | void add(double fValue, SparklineValue::Action eAction) |
80 | 0 | { |
81 | 0 | maValueList.emplace_back(fValue, eAction); |
82 | 0 | size_t nCurrentIndex = maValueList.size() - 1; |
83 | |
|
84 | 0 | if (eAction == SparklineValue::Action::None) |
85 | 0 | { |
86 | 0 | mnLastIndex = nCurrentIndex; |
87 | |
|
88 | 0 | if (mnLastIndex < mnFirstIndex) |
89 | 0 | mnFirstIndex = mnLastIndex; |
90 | |
|
91 | 0 | if (fValue < mfMinimum) |
92 | 0 | mfMinimum = fValue; |
93 | |
|
94 | 0 | if (fValue > mfMaximum) |
95 | 0 | mfMaximum = fValue; |
96 | |
|
97 | 0 | interpolatePastValues(fValue, nCurrentIndex); |
98 | |
|
99 | 0 | mnPreviousIndex = nCurrentIndex; |
100 | 0 | mfPreviousValue = fValue; |
101 | 0 | } |
102 | 0 | else if (eAction == SparklineValue::Action::Interpolate) |
103 | 0 | { |
104 | 0 | maToInterpolateIndex.push_back(nCurrentIndex); |
105 | 0 | maValueList.back().meAction = SparklineValue::Action::Skip; |
106 | 0 | } |
107 | 0 | } |
108 | | |
109 | | static constexpr double interpolate(double x1, double y1, double x2, double y2, double x) |
110 | 0 | { |
111 | 0 | return (y1 * (x2 - x) + y2 * (x - x1)) / (x2 - x1); |
112 | 0 | } |
113 | | |
114 | | void interpolatePastValues(double nCurrentValue, size_t nCurrentIndex) |
115 | 0 | { |
116 | 0 | if (maToInterpolateIndex.empty()) |
117 | 0 | return; |
118 | | |
119 | 0 | if (mnPreviousIndex == std::numeric_limits<size_t>::max()) |
120 | 0 | { |
121 | 0 | for (size_t nIndex : maToInterpolateIndex) |
122 | 0 | { |
123 | 0 | auto& rValue = maValueList[nIndex]; |
124 | 0 | rValue.meAction = SparklineValue::Action::Skip; |
125 | 0 | } |
126 | 0 | } |
127 | 0 | else |
128 | 0 | { |
129 | 0 | for (size_t nIndex : maToInterpolateIndex) |
130 | 0 | { |
131 | 0 | double fInterpolated = interpolate(mnPreviousIndex, mfPreviousValue, nCurrentIndex, |
132 | 0 | nCurrentValue, nIndex); |
133 | |
|
134 | 0 | auto& rValue = maValueList[nIndex]; |
135 | 0 | rValue.maValue = fInterpolated; |
136 | 0 | rValue.meAction = SparklineValue::Action::None; |
137 | 0 | } |
138 | 0 | } |
139 | 0 | maToInterpolateIndex.clear(); |
140 | 0 | } |
141 | | |
142 | | void convertToStacked() |
143 | 0 | { |
144 | | // transform the data to 1, -1 |
145 | 0 | for (auto& rValue : maValueList) |
146 | 0 | { |
147 | 0 | if (rValue.maValue != 0.0) |
148 | 0 | { |
149 | 0 | double fNewValue = rValue.maValue > 0.0 ? 1.0 : -1.0; |
150 | |
|
151 | 0 | if (rValue.maValue == mfMinimum) |
152 | 0 | fNewValue -= 0.01; |
153 | |
|
154 | 0 | if (rValue.maValue == mfMaximum) |
155 | 0 | fNewValue += 0.01; |
156 | |
|
157 | 0 | rValue.maValue = fNewValue; |
158 | 0 | } |
159 | 0 | } |
160 | 0 | mfMinimum = -1.01; |
161 | 0 | mfMaximum = 1.01; |
162 | 0 | } |
163 | | |
164 | 0 | void reverse() { std::reverse(maValueList.begin(), maValueList.end()); } |
165 | | }; |
166 | | |
167 | | /** Iterator to traverse the addresses in a range if the range is one dimensional. |
168 | | * |
169 | | * The direction to traverse is detected automatically or hasNext returns |
170 | | * false if it is not possible to detect. |
171 | | * |
172 | | */ |
173 | | class RangeTraverser |
174 | | { |
175 | | enum class Direction |
176 | | { |
177 | | UNKNOWN, |
178 | | ROW, |
179 | | COLUMN |
180 | | }; |
181 | | |
182 | | ScAddress m_aCurrent; |
183 | | ScRange m_aRange; |
184 | | Direction m_eDirection; |
185 | | |
186 | | public: |
187 | | RangeTraverser(ScRange const& rRange) |
188 | 0 | : m_aCurrent(ScAddress::INITIALIZE_INVALID) |
189 | 0 | , m_aRange(rRange) |
190 | 0 | , m_eDirection(Direction::UNKNOWN) |
191 | | |
192 | 0 | { |
193 | 0 | } |
194 | | |
195 | | ScAddress const& first() |
196 | 0 | { |
197 | 0 | m_aCurrent.SetInvalid(); |
198 | |
|
199 | 0 | if (m_aRange.aStart.Row() == m_aRange.aEnd.Row()) |
200 | 0 | { |
201 | 0 | m_eDirection = Direction::COLUMN; |
202 | 0 | m_aCurrent = m_aRange.aStart; |
203 | 0 | } |
204 | 0 | else if (m_aRange.aStart.Col() == m_aRange.aEnd.Col()) |
205 | 0 | { |
206 | 0 | m_eDirection = Direction::ROW; |
207 | 0 | m_aCurrent = m_aRange.aStart; |
208 | 0 | } |
209 | |
|
210 | 0 | return m_aCurrent; |
211 | 0 | } |
212 | | |
213 | | bool hasNext() |
214 | 0 | { |
215 | 0 | if (m_eDirection == Direction::COLUMN) |
216 | 0 | return m_aCurrent.Col() <= m_aRange.aEnd.Col(); |
217 | 0 | else if (m_eDirection == Direction::ROW) |
218 | 0 | return m_aCurrent.Row() <= m_aRange.aEnd.Row(); |
219 | 0 | else |
220 | 0 | return false; |
221 | 0 | } |
222 | | |
223 | | void next() |
224 | 0 | { |
225 | 0 | if (hasNext()) |
226 | 0 | { |
227 | 0 | if (m_eDirection == Direction::COLUMN) |
228 | 0 | m_aCurrent.IncCol(); |
229 | 0 | else if (m_eDirection == Direction::ROW) |
230 | 0 | m_aCurrent.IncRow(); |
231 | 0 | } |
232 | 0 | } |
233 | | }; |
234 | | |
235 | | /** Render a provided sparkline into the input rectangle */ |
236 | | class SparklineRenderer |
237 | | { |
238 | | private: |
239 | | ScDocument& mrDocument; |
240 | | tools::Long mnOneX; |
241 | | tools::Long mnOneY; |
242 | | |
243 | | double mfScaleX; |
244 | | double mfScaleY; |
245 | | |
246 | | void createMarker(std::vector<SparklineMarker>& rMarkers, double x, double y, |
247 | | Color const& rColor) |
248 | 0 | { |
249 | 0 | auto& rMarker = rMarkers.emplace_back(); |
250 | 0 | const double nHalfSizeX = mnOneX * 2 * mfScaleX; |
251 | 0 | const double nHalfSizeY = mnOneY * 2 * mfScaleY; |
252 | 0 | basegfx::B2DRectangle aRectangle(std::round(x - nHalfSizeX), std::round(y - nHalfSizeY), |
253 | 0 | std::round(x + nHalfSizeX), std::round(y + nHalfSizeY)); |
254 | 0 | rMarker.maPolygon = basegfx::utils::createPolygonFromRect(aRectangle); |
255 | 0 | rMarker.maColor = rColor; |
256 | 0 | } |
257 | | |
258 | | void drawLine(vcl::RenderContext& rRenderContext, tools::Rectangle const& rRectangle, |
259 | | SparklineValues const& rSparklineValues, |
260 | | sc::SparklineAttributes const& rAttributes) |
261 | 0 | { |
262 | 0 | double nMax = rSparklineValues.mfMaximum; |
263 | 0 | if (rAttributes.getMaxAxisType() == sc::AxisType::Custom && rAttributes.getManualMax()) |
264 | 0 | nMax = *rAttributes.getManualMax(); |
265 | |
|
266 | 0 | double nMin = rSparklineValues.mfMinimum; |
267 | 0 | if (rAttributes.getMinAxisType() == sc::AxisType::Custom && rAttributes.getManualMin()) |
268 | 0 | nMin = *rAttributes.getManualMin(); |
269 | |
|
270 | 0 | std::vector<SparklineValue> const& rValueList = rSparklineValues.getValuesList(); |
271 | 0 | std::vector<basegfx::B2DPolygon> aPolygons; |
272 | 0 | aPolygons.emplace_back(); |
273 | 0 | double numebrOfSteps = rValueList.size() - 1; |
274 | 0 | double xStep = 0; |
275 | 0 | double nDelta = nMax - nMin; |
276 | |
|
277 | 0 | std::vector<SparklineMarker> aMarkers; |
278 | 0 | size_t nValueIndex = 0; |
279 | |
|
280 | 0 | for (auto const& rSparklineValue : rValueList) |
281 | 0 | { |
282 | 0 | if (rSparklineValue.meAction == SparklineValue::Action::Skip) |
283 | 0 | { |
284 | 0 | aPolygons.emplace_back(); |
285 | 0 | } |
286 | 0 | else |
287 | 0 | { |
288 | 0 | auto& aPolygon = aPolygons.back(); |
289 | 0 | double nValue = rSparklineValue.maValue; |
290 | |
|
291 | 0 | double nP = (nValue - nMin) / nDelta; |
292 | 0 | double x = rRectangle.GetWidth() * (xStep / numebrOfSteps); |
293 | 0 | double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nP; |
294 | |
|
295 | 0 | aPolygon.append({ x, y }); |
296 | |
|
297 | 0 | if (rAttributes.isFirst() && nValueIndex == rSparklineValues.mnFirstIndex) |
298 | 0 | { |
299 | 0 | createMarker(aMarkers, x, y, rAttributes.getColorFirst().getFinalColor()); |
300 | 0 | } |
301 | 0 | else if (rAttributes.isLast() && nValueIndex == rSparklineValues.mnLastIndex) |
302 | 0 | { |
303 | 0 | createMarker(aMarkers, x, y, rAttributes.getColorLast().getFinalColor()); |
304 | 0 | } |
305 | 0 | else if (rAttributes.isHigh() && nValue == rSparklineValues.mfMaximum) |
306 | 0 | { |
307 | 0 | createMarker(aMarkers, x, y, rAttributes.getColorHigh().getFinalColor()); |
308 | 0 | } |
309 | 0 | else if (rAttributes.isLow() && nValue == rSparklineValues.mfMinimum) |
310 | 0 | { |
311 | 0 | createMarker(aMarkers, x, y, rAttributes.getColorLow().getFinalColor()); |
312 | 0 | } |
313 | 0 | else if (rAttributes.isNegative() && nValue < 0.0) |
314 | 0 | { |
315 | 0 | createMarker(aMarkers, x, y, rAttributes.getColorNegative().getFinalColor()); |
316 | 0 | } |
317 | 0 | else if (rAttributes.isMarkers()) |
318 | 0 | { |
319 | 0 | createMarker(aMarkers, x, y, rAttributes.getColorMarkers().getFinalColor()); |
320 | 0 | } |
321 | 0 | } |
322 | |
|
323 | 0 | xStep++; |
324 | 0 | nValueIndex++; |
325 | 0 | } |
326 | |
|
327 | 0 | basegfx::B2DHomMatrix aMatrix; |
328 | 0 | aMatrix.translate(rRectangle.Left(), rRectangle.Top()); |
329 | |
|
330 | 0 | if (rAttributes.shouldDisplayXAxis()) |
331 | 0 | { |
332 | 0 | double nZero = 0 - nMin / nDelta; |
333 | |
|
334 | 0 | if (nZero >= 0) // if nZero < 0, the axis is not visible |
335 | 0 | { |
336 | 0 | double x1 = 0.0; |
337 | 0 | double x2 = double(rRectangle.GetWidth()); |
338 | 0 | double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nZero; |
339 | |
|
340 | 0 | basegfx::B2DPolygon aAxisPolygon; |
341 | 0 | aAxisPolygon.append({ x1, y }); |
342 | 0 | aAxisPolygon.append({ x2, y }); |
343 | |
|
344 | 0 | rRenderContext.SetLineColor(rAttributes.getColorAxis().getFinalColor()); |
345 | 0 | rRenderContext.DrawPolyLineDirect(aMatrix, aAxisPolygon, 0.2 * mfScaleX); |
346 | 0 | } |
347 | 0 | } |
348 | |
|
349 | 0 | rRenderContext.SetLineColor(rAttributes.getColorSeries().getFinalColor()); |
350 | |
|
351 | 0 | for (auto& rPolygon : aPolygons) |
352 | 0 | { |
353 | 0 | rRenderContext.DrawPolyLineDirect(aMatrix, rPolygon, |
354 | 0 | rAttributes.getLineWeight() * mfScaleX, 0.0, nullptr, |
355 | 0 | basegfx::B2DLineJoin::Round); |
356 | 0 | } |
357 | |
|
358 | 0 | for (auto& rMarker : aMarkers) |
359 | 0 | { |
360 | 0 | rRenderContext.SetLineColor(rMarker.maColor); |
361 | 0 | rRenderContext.SetFillColor(rMarker.maColor); |
362 | 0 | auto& rPolygon = rMarker.maPolygon; |
363 | 0 | rPolygon.transform(aMatrix); |
364 | 0 | rRenderContext.DrawPolygon(rPolygon); |
365 | 0 | } |
366 | 0 | } |
367 | | |
368 | | static void setFillAndLineColor(vcl::RenderContext& rRenderContext, |
369 | | sc::SparklineAttributes const& rAttributes, double nValue, |
370 | | size_t nValueIndex, SparklineValues const& rSparklineValues) |
371 | 0 | { |
372 | 0 | if (rAttributes.isFirst() && nValueIndex == rSparklineValues.mnFirstIndex) |
373 | 0 | { |
374 | 0 | rRenderContext.SetLineColor(rAttributes.getColorFirst().getFinalColor()); |
375 | 0 | rRenderContext.SetFillColor(rAttributes.getColorFirst().getFinalColor()); |
376 | 0 | } |
377 | 0 | else if (rAttributes.isLast() && nValueIndex == rSparklineValues.mnLastIndex) |
378 | 0 | { |
379 | 0 | rRenderContext.SetLineColor(rAttributes.getColorLast().getFinalColor()); |
380 | 0 | rRenderContext.SetFillColor(rAttributes.getColorLast().getFinalColor()); |
381 | 0 | } |
382 | 0 | else if (rAttributes.isHigh() && nValue == rSparklineValues.mfMaximum) |
383 | 0 | { |
384 | 0 | rRenderContext.SetLineColor(rAttributes.getColorHigh().getFinalColor()); |
385 | 0 | rRenderContext.SetFillColor(rAttributes.getColorHigh().getFinalColor()); |
386 | 0 | } |
387 | 0 | else if (rAttributes.isLow() && nValue == rSparklineValues.mfMinimum) |
388 | 0 | { |
389 | 0 | rRenderContext.SetLineColor(rAttributes.getColorLow().getFinalColor()); |
390 | 0 | rRenderContext.SetFillColor(rAttributes.getColorLow().getFinalColor()); |
391 | 0 | } |
392 | 0 | else if (rAttributes.isNegative() && nValue < 0.0) |
393 | 0 | { |
394 | 0 | rRenderContext.SetLineColor(rAttributes.getColorNegative().getFinalColor()); |
395 | 0 | rRenderContext.SetFillColor(rAttributes.getColorNegative().getFinalColor()); |
396 | 0 | } |
397 | 0 | else |
398 | 0 | { |
399 | 0 | rRenderContext.SetLineColor(rAttributes.getColorSeries().getFinalColor()); |
400 | 0 | rRenderContext.SetFillColor(rAttributes.getColorSeries().getFinalColor()); |
401 | 0 | } |
402 | 0 | } |
403 | | |
404 | | void drawColumn(vcl::RenderContext& rRenderContext, tools::Rectangle const& rRectangle, |
405 | | SparklineValues const& rSparklineValues, |
406 | | sc::SparklineAttributes const& rAttributes) |
407 | 0 | { |
408 | 0 | double nMax = rSparklineValues.mfMaximum; |
409 | 0 | if (rAttributes.getMaxAxisType() == sc::AxisType::Custom && rAttributes.getManualMax()) |
410 | 0 | nMax = *rAttributes.getManualMax(); |
411 | |
|
412 | 0 | double nMin = rSparklineValues.mfMinimum; |
413 | 0 | if (rAttributes.getMinAxisType() == sc::AxisType::Custom && rAttributes.getManualMin()) |
414 | 0 | nMin = *rAttributes.getManualMin(); |
415 | |
|
416 | 0 | std::vector<SparklineValue> const& rValueList = rSparklineValues.getValuesList(); |
417 | |
|
418 | 0 | basegfx::B2DPolygon aPolygon; |
419 | 0 | basegfx::B2DHomMatrix aMatrix; |
420 | 0 | aMatrix.translate(rRectangle.Left(), rRectangle.Top()); |
421 | |
|
422 | 0 | double xStep = 0; |
423 | 0 | double numberOfSteps = rValueList.size(); |
424 | 0 | double nDelta = nMax - nMin; |
425 | |
|
426 | 0 | double nColumnSize = rRectangle.GetWidth() / numberOfSteps; |
427 | 0 | nColumnSize = nColumnSize - (nColumnSize * 0.3); |
428 | |
|
429 | 0 | double nZero = (0 - nMin) / nDelta; |
430 | 0 | double nZeroPosition = 0.0; |
431 | 0 | if (nZero >= 0) |
432 | 0 | { |
433 | 0 | nZeroPosition = rRectangle.GetHeight() - rRectangle.GetHeight() * nZero; |
434 | |
|
435 | 0 | if (rAttributes.shouldDisplayXAxis()) |
436 | 0 | { |
437 | 0 | double x1 = 0.0; |
438 | 0 | double x2 = double(rRectangle.GetWidth()); |
439 | |
|
440 | 0 | basegfx::B2DPolygon aAxisPolygon; |
441 | 0 | aAxisPolygon.append({ x1, nZeroPosition }); |
442 | 0 | aAxisPolygon.append({ x2, nZeroPosition }); |
443 | |
|
444 | 0 | rRenderContext.SetLineColor(rAttributes.getColorAxis().getFinalColor()); |
445 | 0 | rRenderContext.DrawPolyLineDirect(aMatrix, aAxisPolygon, 0.2 * mfScaleX); |
446 | 0 | } |
447 | 0 | } |
448 | 0 | else |
449 | 0 | nZeroPosition = rRectangle.GetHeight(); |
450 | |
|
451 | 0 | size_t nValueIndex = 0; |
452 | |
|
453 | 0 | for (auto const& rSparklineValue : rValueList) |
454 | 0 | { |
455 | 0 | double nValue = rSparklineValue.maValue; |
456 | |
|
457 | 0 | if (nValue != 0.0) |
458 | 0 | { |
459 | 0 | setFillAndLineColor(rRenderContext, rAttributes, nValue, nValueIndex, |
460 | 0 | rSparklineValues); |
461 | |
|
462 | 0 | double nP = (nValue - nMin) / nDelta; |
463 | 0 | double x = rRectangle.GetWidth() * (xStep / numberOfSteps); |
464 | 0 | double y = rRectangle.GetHeight() - rRectangle.GetHeight() * nP; |
465 | |
|
466 | 0 | basegfx::B2DRectangle aRectangle(x, y, x + nColumnSize, nZeroPosition); |
467 | 0 | aPolygon = basegfx::utils::createPolygonFromRect(aRectangle); |
468 | |
|
469 | 0 | aPolygon.transform(aMatrix); |
470 | 0 | rRenderContext.DrawPolygon(aPolygon); |
471 | 0 | } |
472 | 0 | xStep++; |
473 | 0 | nValueIndex++; |
474 | 0 | } |
475 | 0 | } |
476 | | |
477 | | bool isCellHidden(ScAddress const& rAddress) |
478 | 0 | { |
479 | 0 | return mrDocument.RowHidden(rAddress.Row(), rAddress.Tab()) |
480 | 0 | || mrDocument.ColHidden(rAddress.Col(), rAddress.Tab()); |
481 | 0 | } |
482 | | |
483 | | public: |
484 | | SparklineRenderer(ScDocument& rDocument) |
485 | 0 | : mrDocument(rDocument) |
486 | 0 | , mnOneX(1) |
487 | 0 | , mnOneY(1) |
488 | 0 | , mfScaleX(1.0) |
489 | 0 | , mfScaleY(1.0) |
490 | 0 | { |
491 | 0 | } |
492 | | |
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) |
496 | 0 | { |
497 | 0 | auto popIt = rRenderContext.ScopedPush(); |
498 | |
|
499 | 0 | rRenderContext.SetAntialiasing(AntialiasingFlags::Enable); |
500 | 0 | rRenderContext.SetClipRegion(vcl::Region(rRectangle)); |
501 | |
|
502 | 0 | tools::Rectangle aOutputRectangle(rRectangle); |
503 | 0 | aOutputRectangle.shrink(6); // provide border |
504 | |
|
505 | 0 | mnOneX = nOneX; |
506 | 0 | mnOneY = nOneY; |
507 | 0 | mfScaleX = fScaleX; |
508 | 0 | mfScaleY = fScaleY; |
509 | |
|
510 | 0 | auto const& rRangeList = pSparkline->getInputRange(); |
511 | |
|
512 | 0 | if (rRangeList.empty()) |
513 | 0 | { |
514 | 0 | return; |
515 | 0 | } |
516 | | |
517 | 0 | auto pSparklineGroup = pSparkline->getSparklineGroup(); |
518 | 0 | auto const& rAttributes = pSparklineGroup->getAttributes(); |
519 | |
|
520 | 0 | ScRange aRange = rRangeList[0]; |
521 | |
|
522 | 0 | SparklineValues aSparklineValues; |
523 | |
|
524 | 0 | RangeTraverser aTraverser(aRange); |
525 | 0 | for (ScAddress const& rCurrent = aTraverser.first(); aTraverser.hasNext(); |
526 | 0 | aTraverser.next()) |
527 | 0 | { |
528 | | // Skip if the cell is hidden and "displayHidden" attribute is not selected |
529 | 0 | if (!rAttributes.shouldDisplayHidden() && isCellHidden(rCurrent)) |
530 | 0 | continue; |
531 | | |
532 | 0 | double fCellValue = 0.0; |
533 | 0 | SparklineValue::Action eAction = SparklineValue::Action::None; |
534 | 0 | CellType eType = mrDocument.GetCellType(rCurrent); |
535 | |
|
536 | 0 | if (eType == CELLTYPE_NONE) // if cell is empty |
537 | 0 | { |
538 | 0 | auto eDisplayEmpty = rAttributes.getDisplayEmptyCellsAs(); |
539 | 0 | if (eDisplayEmpty == sc::DisplayEmptyCellsAs::Gap) |
540 | 0 | eAction = SparklineValue::Action::Skip; |
541 | 0 | else if (eDisplayEmpty == sc::DisplayEmptyCellsAs::Span) |
542 | 0 | eAction = SparklineValue::Action::Interpolate; |
543 | 0 | } |
544 | 0 | else |
545 | 0 | { |
546 | 0 | fCellValue = mrDocument.GetValue(rCurrent); |
547 | 0 | } |
548 | |
|
549 | 0 | aSparklineValues.add(fCellValue, eAction); |
550 | 0 | } |
551 | |
|
552 | 0 | if (rAttributes.isRightToLeft()) |
553 | 0 | aSparklineValues.reverse(); |
554 | |
|
555 | 0 | if (rAttributes.getType() == sc::SparklineType::Column) |
556 | 0 | { |
557 | 0 | drawColumn(rRenderContext, aOutputRectangle, aSparklineValues, |
558 | 0 | pSparklineGroup->getAttributes()); |
559 | 0 | } |
560 | 0 | else if (rAttributes.getType() == sc::SparklineType::Stacked) |
561 | 0 | { |
562 | 0 | aSparklineValues.convertToStacked(); |
563 | 0 | drawColumn(rRenderContext, aOutputRectangle, aSparklineValues, |
564 | 0 | pSparklineGroup->getAttributes()); |
565 | 0 | } |
566 | 0 | else if (rAttributes.getType() == sc::SparklineType::Line) |
567 | 0 | { |
568 | 0 | drawLine(rRenderContext, aOutputRectangle, aSparklineValues, |
569 | 0 | pSparklineGroup->getAttributes()); |
570 | 0 | } |
571 | 0 | } |
572 | | }; |
573 | | } |
574 | | |
575 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |