1 | // Copyright (C) 2023 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtGraphs/QBarCategoryAxis> |
5 | #include <QtGraphs/QGraphsTheme> |
6 | #include <private/axisrenderer_p.h> |
7 | #include <private/qabstractaxis_p.h> |
8 | #include <private/qbarseries_p.h> |
9 | #include <private/qdatetimeaxis_p.h> |
10 | #include <private/qgraphsview_p.h> |
11 | #include <private/qvalueaxis_p.h> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | AxisRenderer::AxisRenderer(QQuickItem *parent) |
16 | : QQuickItem(parent) |
17 | { |
18 | m_graph = qobject_cast<QGraphsView *>(object: parent); |
19 | setFlag(flag: QQuickItem::ItemHasContents); |
20 | } |
21 | |
22 | AxisRenderer::~AxisRenderer() {} |
23 | |
24 | QGraphsTheme *AxisRenderer::theme() { |
25 | return m_graph->m_theme; |
26 | } |
27 | |
28 | void AxisRenderer::initialize() { |
29 | if (m_initialized) |
30 | return; |
31 | if (!window()) |
32 | return; |
33 | |
34 | if (m_axisGrid) |
35 | m_axisGrid->componentComplete(); |
36 | if (m_axisLineVertical) |
37 | m_axisLineVertical->componentComplete(); |
38 | if (m_axisTickerVertical) |
39 | m_axisTickerVertical->componentComplete(); |
40 | if (m_axisLineHorizontal) |
41 | m_axisLineHorizontal->componentComplete(); |
42 | if (m_axisTickerHorizontal) |
43 | m_axisTickerHorizontal->componentComplete(); |
44 | if (m_axisGridShadow) |
45 | m_axisGridShadow->componentComplete(); |
46 | if (m_axisLineVerticalShadow) |
47 | m_axisLineVerticalShadow->componentComplete(); |
48 | if (m_axisTickerVerticalShadow) |
49 | m_axisTickerVerticalShadow->componentComplete(); |
50 | if (m_axisLineHorizontalShadow) |
51 | m_axisLineHorizontalShadow->componentComplete(); |
52 | if (m_axisTickerHorizontalShadow) |
53 | m_axisTickerHorizontalShadow->componentComplete(); |
54 | m_initialized = true; |
55 | } |
56 | |
57 | void AxisRenderer::handlePolish() |
58 | { |
59 | if (!m_axisGrid) { |
60 | m_axisGrid = new AxisGrid(this); |
61 | m_axisGrid->setZ(-1); |
62 | m_axisGrid->setupShaders(); |
63 | m_axisGrid->setOrigo(0); |
64 | } |
65 | if (!m_axisLineVertical) { |
66 | m_axisLineVertical = new AxisLine(this); |
67 | m_axisLineVertical->setZ(-1); |
68 | m_axisLineVertical->setupShaders(); |
69 | } |
70 | if (!m_axisTickerVertical) { |
71 | m_axisTickerVertical = new AxisTicker(this); |
72 | m_axisTickerVertical->setZ(-2); |
73 | m_axisTickerVertical->setOrigo(0); |
74 | // TODO: Configurable in theme or axis? |
75 | m_axisTickerVertical->setSubTickLength(0.5); |
76 | m_axisTickerVertical->setupShaders(); |
77 | } |
78 | if (!m_axisLineHorizontal) { |
79 | m_axisLineHorizontal = new AxisLine(this); |
80 | m_axisLineHorizontal->setZ(-1); |
81 | m_axisLineHorizontal->setIsHorizontal(true); |
82 | m_axisLineHorizontal->setupShaders(); |
83 | } |
84 | if (!m_axisTickerHorizontal) { |
85 | m_axisTickerHorizontal = new AxisTicker(this); |
86 | m_axisTickerHorizontal->setZ(-2); |
87 | m_axisTickerHorizontal->setIsHorizontal(true); |
88 | m_axisTickerHorizontal->setOrigo(0); |
89 | // TODO: Configurable in theme or axis? |
90 | m_axisTickerHorizontal->setSubTickLength(0.2); |
91 | m_axisTickerHorizontal->setupShaders(); |
92 | } |
93 | |
94 | // TODO: Create shadows only when needed |
95 | if (!m_axisGridShadow) { |
96 | m_axisGridShadow = new AxisGrid(this); |
97 | m_axisGridShadow->setZ(-3); |
98 | m_axisGridShadow->setupShaders(); |
99 | m_axisGridShadow->setOrigo(0); |
100 | } |
101 | if (!m_axisLineVerticalShadow) { |
102 | m_axisLineVerticalShadow = new AxisLine(this); |
103 | m_axisLineVerticalShadow->setZ(-3); |
104 | m_axisLineVerticalShadow->setupShaders(); |
105 | } |
106 | if (!m_axisTickerVerticalShadow) { |
107 | m_axisTickerVerticalShadow = new AxisTicker(this); |
108 | m_axisTickerVerticalShadow->setZ(-3); |
109 | m_axisTickerVerticalShadow->setOrigo(0); |
110 | // TODO: Configurable in theme or axis? |
111 | m_axisTickerVerticalShadow->setSubTickLength(m_axisTickerVertical->subTickLength()); |
112 | m_axisTickerVerticalShadow->setupShaders(); |
113 | } |
114 | if (!m_axisLineHorizontalShadow) { |
115 | m_axisLineHorizontalShadow = new AxisLine(this); |
116 | m_axisLineHorizontalShadow->setZ(-3); |
117 | m_axisLineHorizontalShadow->setupShaders(); |
118 | } |
119 | if (!m_axisTickerHorizontalShadow) { |
120 | m_axisTickerHorizontalShadow = new AxisTicker(this); |
121 | m_axisTickerHorizontalShadow->setZ(-3); |
122 | m_axisTickerHorizontalShadow->setIsHorizontal(true); |
123 | m_axisTickerHorizontalShadow->setOrigo(0); |
124 | // TODO: Configurable in theme or axis? |
125 | m_axisTickerHorizontalShadow->setSubTickLength(m_axisTickerHorizontal->subTickLength()); |
126 | m_axisTickerHorizontalShadow->setupShaders(); |
127 | } |
128 | |
129 | updateAxis(); |
130 | } |
131 | |
132 | void AxisRenderer::updateAxis() |
133 | { |
134 | if (!theme()) |
135 | return; |
136 | |
137 | // Update active axis |
138 | QAbstractAxis *axisVertical = m_graph->m_axisY; |
139 | QAbstractAxis *axisHorizontal = m_graph->m_axisX; |
140 | |
141 | // See if series is horizontal, so axis should also switch places. |
142 | bool vertical = true; |
143 | if (m_graph->orientation() == Qt::Orientation::Horizontal) |
144 | vertical = false; |
145 | if (vertical) { |
146 | m_axisVertical = axisVertical; |
147 | m_axisHorizontal = axisHorizontal; |
148 | } else { |
149 | m_axisVertical = axisHorizontal; |
150 | m_axisHorizontal = axisVertical; |
151 | } |
152 | |
153 | if (vertical != m_wasVertical) { |
154 | // Orientation has changed, so clear possible custom elements |
155 | for (auto &item : m_xAxisTextItems) |
156 | item->deleteLater(); |
157 | m_xAxisTextItems.clear(); |
158 | |
159 | for (auto &item : m_yAxisTextItems) |
160 | item->deleteLater(); |
161 | m_yAxisTextItems.clear(); |
162 | |
163 | m_wasVertical = vertical; |
164 | } |
165 | |
166 | float axisWidth = m_graph->m_axisWidth; |
167 | float axisHeight = m_graph->m_axisHeight; |
168 | |
169 | const bool gridVisible = theme()->isGridVisible(); |
170 | if (m_axisVertical) { |
171 | m_gridVerticalLinesVisible = gridVisible && m_axisVertical->isGridVisible(); |
172 | m_gridVerticalSubLinesVisible = gridVisible && m_axisVertical->isSubGridVisible(); |
173 | } |
174 | if (m_axisHorizontal) { |
175 | m_gridHorizontalLinesVisible = gridVisible && m_axisHorizontal->isGridVisible(); |
176 | m_gridHorizontalSubLinesVisible = gridVisible && m_axisHorizontal->isSubGridVisible(); |
177 | } |
178 | |
179 | if (auto vaxis = qobject_cast<QValueAxis *>(object: m_axisVertical)) { |
180 | m_axisVerticalMaxValue = vaxis->max(); |
181 | m_axisVerticalMinValue = vaxis->min(); |
182 | double step = vaxis->tickInterval(); |
183 | |
184 | m_axisVerticalValueRange = m_axisVerticalMaxValue - m_axisVerticalMinValue; |
185 | // If step is not manually defined (or it is invalid), calculate autostep |
186 | if (step <= 0) |
187 | step = getValueStepsFromRange(range: m_axisVerticalValueRange); |
188 | |
189 | // Get smallest tick label value |
190 | double minLabel = vaxis->tickAnchor(); |
191 | while (minLabel < m_axisVerticalMinValue) |
192 | minLabel += step; |
193 | while (minLabel >= (m_axisVerticalMinValue + step)) |
194 | minLabel -= step; |
195 | m_axisVerticalMinLabel = minLabel; |
196 | |
197 | m_axisVerticalValueStep = step; |
198 | int axisVerticalSubTickCount = vaxis->subTickCount(); |
199 | m_axisVerticalSubGridScale = axisVerticalSubTickCount > 0 ? 1.0 / (axisVerticalSubTickCount + 1) : 1.0; |
200 | m_axisVerticalStepPx = (height() - m_graph->m_marginTop - m_graph->m_marginBottom - axisHeight) / (m_axisVerticalValueRange / m_axisVerticalValueStep); |
201 | double axisVerticalValueDiff = m_axisVerticalMinLabel - m_axisVerticalMinValue; |
202 | m_axisYDisplacement = -(axisVerticalValueDiff / m_axisVerticalValueStep) * m_axisVerticalStepPx; |
203 | |
204 | // Update value labels |
205 | updateValueYAxisLabels(axis: vaxis, rect: m_graph->m_yAxisLabelsArea); |
206 | } |
207 | |
208 | if (auto haxis = qobject_cast<QValueAxis *>(object: m_axisHorizontal)) { |
209 | m_axisHorizontalMaxValue = haxis->max(); |
210 | m_axisHorizontalMinValue = haxis->min(); |
211 | double step = haxis->tickInterval(); |
212 | |
213 | m_axisHorizontalValueRange = m_axisHorizontalMaxValue - m_axisHorizontalMinValue; |
214 | // If step is not manually defined (or it is invalid), calculate autostep |
215 | if (step <= 0) |
216 | step = getValueStepsFromRange(range: m_axisHorizontalValueRange); |
217 | |
218 | // Get smallest tick label value |
219 | double minLabel = haxis->tickAnchor(); |
220 | while (minLabel < m_axisHorizontalMinValue) |
221 | minLabel += step; |
222 | while (minLabel >= (m_axisHorizontalMinValue + step)) |
223 | minLabel -= step; |
224 | m_axisHorizontalMinLabel = minLabel; |
225 | |
226 | m_axisHorizontalValueStep = step; |
227 | int axisHorizontalSubTickCount = haxis->subTickCount(); |
228 | m_axisHorizontalSubGridScale = axisHorizontalSubTickCount > 0 ? |
229 | 1.0 / (axisHorizontalSubTickCount + 1) : 1.0; |
230 | m_axisHorizontalStepPx = (width() - m_graph->m_marginLeft - m_graph->m_marginRight - axisWidth) |
231 | / (m_axisHorizontalValueRange / m_axisHorizontalValueStep); |
232 | double axisHorizontalValueDiff = m_axisHorizontalMinLabel - m_axisHorizontalMinValue; |
233 | m_axisXDisplacement = -(axisHorizontalValueDiff / m_axisHorizontalValueStep) * m_axisHorizontalStepPx; |
234 | |
235 | // Update value labels |
236 | updateValueXAxisLabels(axis: haxis, rect: m_graph->m_xAxisLabelsArea); |
237 | } |
238 | |
239 | if (auto haxis = qobject_cast<QBarCategoryAxis *>(object: m_axisHorizontal)) { |
240 | m_axisHorizontalMaxValue = haxis->categories().size(); |
241 | m_axisHorizontalMinValue = 0; |
242 | m_axisHorizontalValueRange = m_axisHorizontalMaxValue - m_axisHorizontalMinValue; |
243 | updateBarXAxisLabels(axis: haxis, rect: m_graph->m_xAxisLabelsArea); |
244 | } |
245 | if (auto vaxis = qobject_cast<QBarCategoryAxis *>(object: m_axisVertical)) { |
246 | m_axisVerticalMaxValue = vaxis->categories().size(); |
247 | m_axisVerticalMinValue = 0; |
248 | m_axisVerticalValueRange = m_axisVerticalMaxValue - m_axisVerticalMinValue; |
249 | updateBarYAxisLabels(axis: vaxis, rect: m_graph->m_yAxisLabelsArea); |
250 | } |
251 | |
252 | if (auto vaxis = qobject_cast<QDateTimeAxis *>(object: m_axisVertical)) { |
253 | // Todo: make constant for all axis, or clamp in class? (QTBUG-124736) |
254 | const double MAX_DIVS = 100.0; |
255 | |
256 | double interval = std::clamp<double>(val: vaxis->tickInterval(), lo: 0.0, hi: MAX_DIVS); |
257 | m_axisVerticalMaxValue = vaxis->max().toMSecsSinceEpoch(); |
258 | m_axisVerticalMinValue = vaxis->min().toMSecsSinceEpoch(); |
259 | m_axisVerticalValueRange = std::abs(x: m_axisVerticalMaxValue - m_axisVerticalMinValue); |
260 | |
261 | // in ms |
262 | double segment; |
263 | if (interval <= 0) { |
264 | segment = getValueStepsFromRange(range: m_axisVerticalValueRange); |
265 | interval = m_axisVerticalValueRange / segment; |
266 | } else { |
267 | segment = m_axisVerticalValueRange / interval; |
268 | } |
269 | |
270 | m_axisVerticalMinLabel = std::clamp(val: interval, lo: 1.0, hi: MAX_DIVS); |
271 | |
272 | m_axisVerticalValueStep = segment; |
273 | int axisVerticalSubTickCount = vaxis->subTickCount(); |
274 | m_axisVerticalSubGridScale = axisVerticalSubTickCount > 0 |
275 | ? 1.0 / (axisVerticalSubTickCount + 1) |
276 | : 1.0; |
277 | m_axisVerticalStepPx = (height() - m_graph->m_marginTop - m_graph->m_marginBottom |
278 | - axisHeight) |
279 | / (qFuzzyCompare(p1: segment, p2: 0) |
280 | ? interval |
281 | : (m_axisVerticalValueRange / m_axisVerticalValueStep)); |
282 | |
283 | updateDateTimeYAxisLabels(axis: vaxis, rect: m_graph->m_yAxisLabelsArea); |
284 | } |
285 | |
286 | if (auto haxis = qobject_cast<QDateTimeAxis *>(object: m_axisHorizontal)) { |
287 | const double MAX_DIVS = 100.0; |
288 | |
289 | double interval = std::clamp<double>(val: haxis->tickInterval(), lo: 0.0, hi: MAX_DIVS); |
290 | m_axisHorizontalMaxValue = haxis->max().toMSecsSinceEpoch(); |
291 | m_axisHorizontalMinValue = haxis->min().toMSecsSinceEpoch(); |
292 | m_axisHorizontalValueRange = std::abs(x: m_axisHorizontalMaxValue - m_axisHorizontalMinValue); |
293 | |
294 | // in ms |
295 | double segment; |
296 | if (interval <= 0) { |
297 | segment = getValueStepsFromRange(range: m_axisHorizontalValueRange); |
298 | interval = m_axisHorizontalValueRange / segment; |
299 | } else { |
300 | segment = m_axisHorizontalValueRange / interval; |
301 | } |
302 | |
303 | m_axisHorizontalMinLabel = std::clamp(val: interval, lo: 1.0, hi: MAX_DIVS); |
304 | |
305 | m_axisHorizontalValueStep = segment; |
306 | int axisHorizontalSubTickCount = haxis->subTickCount(); |
307 | m_axisHorizontalSubGridScale = axisHorizontalSubTickCount > 0 |
308 | ? 1.0 / (axisHorizontalSubTickCount + 1) |
309 | : 1.0; |
310 | m_axisHorizontalStepPx = (width() - m_graph->m_marginLeft - m_graph->m_marginRight |
311 | - axisWidth) |
312 | / (qFuzzyCompare(p1: segment, p2: 0) |
313 | ? interval |
314 | : (m_axisHorizontalValueRange / m_axisHorizontalValueStep)); |
315 | updateDateTimeXAxisLabels(axis: haxis, rect: m_graph->m_xAxisLabelsArea); |
316 | } |
317 | |
318 | updateAxisTickers(); |
319 | updateAxisTickersShadow(); |
320 | updateAxisGrid(); |
321 | updateAxisGridShadow(); |
322 | updateAxisTitles(xAxisRect: m_graph->m_xAxisLabelsArea, yAxisRect: m_graph->m_yAxisLabelsArea); |
323 | } |
324 | |
325 | void AxisRenderer::updateAxisTickers() |
326 | { |
327 | if (m_axisVertical) { |
328 | // Note: Fix before enabling, see QTBUG-121207 and QTBUG-121211 |
329 | //if (theme()->themeDirty()) { |
330 | m_axisTickerVertical->setSubTickColor(theme()->axisY().subColor()); |
331 | m_axisTickerVertical->setTickColor(theme()->axisY().mainColor()); |
332 | m_axisTickerVertical->setTickLineWidth(theme()->axisY().mainWidth()); |
333 | m_axisTickerVertical->setSubTickLineWidth(theme()->axisY().subWidth()); |
334 | m_axisTickerVertical->setSmoothing(m_graph->axisYSmoothing()); |
335 | //} |
336 | float topPadding = m_axisGrid->gridLineWidth() * 0.5; |
337 | float bottomPadding = topPadding; |
338 | // TODO Only when changed |
339 | m_axisTickerVertical->setDisplacement(m_axisYDisplacement); |
340 | QRectF rect = m_graph->m_yAxisTickersArea; |
341 | m_axisTickerVertical->setX(rect.x()); |
342 | m_axisTickerVertical->setY(rect.y()); |
343 | m_axisTickerVertical->setWidth(rect.width()); |
344 | m_axisTickerVertical->setHeight(rect.height()); |
345 | m_axisTickerVertical->setFlipped(m_verticalAxisOnRight); |
346 | |
347 | m_axisTickerVertical->setSpacing((m_axisTickerVertical->height() - topPadding - bottomPadding) |
348 | / (m_axisVerticalValueRange / m_axisVerticalValueStep)); |
349 | m_axisTickerVertical->setSubTicksVisible(!qFuzzyCompare(p1: m_axisVerticalSubGridScale, p2: 1.0)); |
350 | m_axisTickerVertical->setSubTickScale(m_axisVerticalSubGridScale); |
351 | m_axisTickerVertical->setVisible(m_axisVertical->isVisible()); |
352 | // Axis line |
353 | m_axisLineVertical->setColor(theme()->axisY().mainColor()); |
354 | m_axisLineVertical->setLineWidth(theme()->axisY().mainWidth()); |
355 | m_axisLineVertical->setSmoothing(m_graph->axisYSmoothing()); |
356 | |
357 | float xMovement = 0.5 * (m_axisLineVertical->lineWidth() + m_axisLineVertical->smoothing()); |
358 | if (m_verticalAxisOnRight) |
359 | m_axisLineVertical->setX(m_axisTickerVertical->x() - xMovement); |
360 | else |
361 | m_axisLineVertical->setX(m_axisTickerVertical->x() + m_axisTickerVertical->width() - xMovement); |
362 | m_axisLineVertical->setY(m_axisTickerVertical->y()); |
363 | m_axisLineVertical->setWidth(m_axisLineVertical->lineWidth() + m_axisLineVertical->smoothing()); |
364 | m_axisLineVertical->setHeight(m_axisTickerVertical->height()); |
365 | m_axisLineVertical->setVisible(m_axisVertical->isLineVisible()); |
366 | } else { |
367 | // Hide all parts of vertical axis |
368 | m_axisTickerVertical->setVisible(false); |
369 | m_axisLineVertical->setVisible(false); |
370 | for (auto &textItem : m_yAxisTextItems) |
371 | textItem->setVisible(false); |
372 | } |
373 | |
374 | if (m_axisHorizontal) { |
375 | //if (theme()->themeDirty()) { |
376 | m_axisTickerHorizontal->setSubTickColor(theme()->axisX().subColor()); |
377 | m_axisTickerHorizontal->setTickColor(theme()->axisX().mainColor()); |
378 | m_axisTickerHorizontal->setTickLineWidth(theme()->axisX().mainWidth()); |
379 | m_axisTickerHorizontal->setSubTickLineWidth(theme()->axisX().subWidth()); |
380 | m_axisTickerHorizontal->setSmoothing(m_graph->axisXSmoothing()); |
381 | //} |
382 | float leftPadding = m_axisGrid->gridLineWidth() * 0.5; |
383 | float rightPadding = leftPadding; |
384 | // TODO Only when changed |
385 | m_axisTickerHorizontal->setDisplacement(m_axisXDisplacement); |
386 | QRectF rect = m_graph->m_xAxisTickersArea; |
387 | m_axisTickerHorizontal->setX(rect.x()); |
388 | m_axisTickerHorizontal->setY(rect.y()); |
389 | m_axisTickerHorizontal->setWidth(rect.width()); |
390 | m_axisTickerHorizontal->setHeight(rect.height()); |
391 | m_axisTickerHorizontal->setFlipped(m_horizontalAxisOnTop); |
392 | |
393 | m_axisTickerHorizontal->setSpacing((m_axisTickerHorizontal->width() - leftPadding - rightPadding) |
394 | / (m_axisHorizontalValueRange / m_axisHorizontalValueStep)); |
395 | m_axisTickerHorizontal->setSubTicksVisible(!qFuzzyCompare(p1: m_axisHorizontalSubGridScale, p2: 1.0)); |
396 | m_axisTickerHorizontal->setSubTickScale(m_axisHorizontalSubGridScale); |
397 | m_axisTickerHorizontal->setVisible(m_axisHorizontal->isVisible()); |
398 | // Axis line |
399 | m_axisLineHorizontal->setColor(theme()->axisX().mainColor()); |
400 | m_axisLineHorizontal->setLineWidth(theme()->axisX().mainWidth()); |
401 | m_axisLineHorizontal->setSmoothing(m_graph->axisXSmoothing()); |
402 | m_axisLineHorizontal->setX(m_axisTickerHorizontal->x()); |
403 | float yMovement = 0.5 * (m_axisLineHorizontal->lineWidth() + m_axisLineHorizontal->smoothing()); |
404 | if (m_horizontalAxisOnTop) |
405 | m_axisLineHorizontal->setY(m_axisTickerHorizontal->y() + m_axisTickerHorizontal->height() - yMovement); |
406 | else |
407 | m_axisLineHorizontal->setY(m_axisTickerHorizontal->y() - yMovement); |
408 | m_axisLineHorizontal->setWidth(m_axisTickerHorizontal->width()); |
409 | m_axisLineHorizontal->setHeight(m_axisLineHorizontal->lineWidth() + m_axisLineHorizontal->smoothing()); |
410 | m_axisLineHorizontal->setVisible(m_axisHorizontal->isLineVisible()); |
411 | } else { |
412 | // Hide all parts of horizontal axis |
413 | m_axisTickerHorizontal->setVisible(false); |
414 | m_axisLineHorizontal->setVisible(false); |
415 | for (auto &textItem : m_xAxisTextItems) |
416 | textItem->setVisible(false); |
417 | } |
418 | } |
419 | |
420 | void AxisRenderer::updateAxisTickersShadow() |
421 | { |
422 | if (m_axisVertical && m_graph->isShadowVisible()) { |
423 | m_axisTickerVerticalShadow->setSubTickColor(m_graph->shadowColor()); |
424 | m_axisTickerVerticalShadow->setTickColor(m_graph->shadowColor()); |
425 | m_axisTickerVerticalShadow->setSubTickLineWidth(m_axisTickerVertical->subTickLineWidth() + m_graph->shadowBarWidth()); |
426 | m_axisTickerVerticalShadow->setTickLineWidth(m_axisTickerVertical->tickLineWidth() + m_graph->shadowBarWidth()); |
427 | m_axisTickerVerticalShadow->setSmoothing(m_axisTickerVertical->smoothing() + m_graph->shadowSmoothing()); |
428 | |
429 | // TODO Only when changed |
430 | m_axisTickerVerticalShadow->setDisplacement(m_axisTickerVertical->displacement()); |
431 | m_axisTickerVerticalShadow->setX(m_axisTickerVertical->x() + m_graph->shadowXOffset()); |
432 | m_axisTickerVerticalShadow->setY(m_axisTickerVertical->y() + m_graph->shadowYOffset() + m_graph->shadowBarWidth() * 0.5); |
433 | m_axisTickerVerticalShadow->setWidth(m_axisTickerVertical->width()); |
434 | m_axisTickerVerticalShadow->setHeight(m_axisTickerVertical->height()); |
435 | m_axisTickerVerticalShadow->setFlipped(m_axisTickerVertical->isFlipped()); |
436 | m_axisTickerVerticalShadow->setSpacing(m_axisTickerVertical->spacing()); |
437 | m_axisTickerVerticalShadow->setSubTicksVisible(m_axisTickerVertical->subTicksVisible()); |
438 | m_axisTickerVerticalShadow->setSubTickScale(m_axisTickerVertical->subTickScale()); |
439 | m_axisTickerVerticalShadow->setVisible(m_axisTickerVertical->isVisible()); |
440 | // Axis line |
441 | m_axisLineVerticalShadow->setColor(m_graph->shadowColor()); |
442 | m_axisLineVerticalShadow->setLineWidth(m_axisLineVertical->lineWidth() + m_graph->shadowBarWidth()); |
443 | m_axisLineVerticalShadow->setSmoothing(m_axisLineVertical->smoothing() + m_graph->shadowSmoothing()); |
444 | m_axisLineVerticalShadow->setX(m_axisLineVertical->x() + m_graph->shadowXOffset()); |
445 | m_axisLineVerticalShadow->setY(m_axisLineVertical->y() + m_graph->shadowYOffset() + m_graph->shadowBarWidth() * 0.5); |
446 | m_axisLineVerticalShadow->setWidth(m_axisLineVertical->width()); |
447 | m_axisLineVerticalShadow->setHeight(m_axisLineVertical->height()); |
448 | m_axisLineVerticalShadow->setVisible(m_axisLineVertical->isVisible()); |
449 | } else { |
450 | // Hide all parts of vertical axis |
451 | m_axisTickerVerticalShadow->setVisible(false); |
452 | m_axisLineVerticalShadow->setVisible(false); |
453 | } |
454 | |
455 | if (m_axisHorizontal && m_graph->isShadowVisible()) { |
456 | m_axisTickerHorizontalShadow->setSubTickColor(m_graph->shadowColor()); |
457 | m_axisTickerHorizontalShadow->setTickColor(m_graph->shadowColor()); |
458 | m_axisTickerHorizontalShadow->setSubTickLineWidth(m_axisTickerHorizontal->subTickLineWidth() + m_graph->shadowBarWidth()); |
459 | m_axisTickerHorizontalShadow->setTickLineWidth(m_axisTickerHorizontal->tickLineWidth() + m_graph->shadowBarWidth()); |
460 | m_axisTickerHorizontalShadow->setSmoothing(m_axisTickerHorizontal->smoothing() + m_graph->shadowSmoothing()); |
461 | |
462 | // TODO Only when changed |
463 | m_axisTickerHorizontalShadow->setDisplacement(m_axisTickerHorizontal->displacement()); |
464 | m_axisTickerHorizontalShadow->setX(m_axisTickerHorizontal->x() + m_graph->shadowXOffset() - m_graph->shadowBarWidth() * 0.5); |
465 | m_axisTickerHorizontalShadow->setY(m_axisTickerHorizontal->y() + m_graph->shadowYOffset()); |
466 | m_axisTickerHorizontalShadow->setWidth(m_axisTickerHorizontal->width()); |
467 | m_axisTickerHorizontalShadow->setHeight(m_axisTickerHorizontal->height()); |
468 | m_axisTickerHorizontalShadow->setFlipped(m_axisTickerHorizontal->isFlipped()); |
469 | m_axisTickerHorizontalShadow->setSpacing(m_axisTickerHorizontal->spacing()); |
470 | m_axisTickerHorizontalShadow->setSubTicksVisible(m_axisTickerHorizontal->subTicksVisible()); |
471 | m_axisTickerHorizontalShadow->setSubTickScale(m_axisTickerHorizontal->subTickScale()); |
472 | m_axisTickerHorizontalShadow->setVisible(m_axisTickerHorizontal->isVisible()); |
473 | // Axis line |
474 | m_axisLineHorizontalShadow->setColor(m_graph->shadowColor()); |
475 | m_axisLineHorizontalShadow->setLineWidth(m_axisLineHorizontal->width() + m_graph->shadowBarWidth()); |
476 | m_axisLineHorizontalShadow->setSmoothing(m_axisLineHorizontal->smoothing() + m_graph->shadowSmoothing()); |
477 | m_axisLineHorizontalShadow->setX(m_axisLineHorizontal->x() + m_graph->shadowXOffset() - m_graph->shadowBarWidth() * 0.5); |
478 | m_axisLineHorizontalShadow->setY(m_axisLineHorizontal->y() + m_graph->shadowYOffset()); |
479 | m_axisLineHorizontalShadow->setWidth(m_axisLineHorizontal->width()); |
480 | m_axisLineHorizontalShadow->setHeight(m_axisLineHorizontal->height()); |
481 | m_axisLineHorizontalShadow->setVisible(m_axisLineHorizontal->isVisible()); |
482 | } else { |
483 | // Hide all parts of horizontal axis |
484 | m_axisTickerHorizontalShadow->setVisible(false); |
485 | m_axisLineHorizontalShadow->setVisible(false); |
486 | } |
487 | } |
488 | |
489 | void AxisRenderer::updateAxisGrid() |
490 | { |
491 | m_axisGrid->setGridColor(theme()->grid().mainColor()); |
492 | m_axisGrid->setSubGridColor(theme()->grid().subColor()); |
493 | m_axisGrid->setSubGridLineWidth(theme()->grid().subWidth()); |
494 | m_axisGrid->setGridLineWidth(theme()->grid().mainWidth()); |
495 | const double minimumSmoothing = 0.05; |
496 | m_axisGrid->setSmoothing(m_graph->gridSmoothing() + minimumSmoothing); |
497 | if (theme()->isPlotAreaBackgroundVisible()) |
498 | m_axisGrid->setPlotAreaBackgroundColor(theme()->plotAreaBackgroundColor()); |
499 | else |
500 | m_axisGrid->setPlotAreaBackgroundColor(QColorConstants::Transparent); |
501 | |
502 | float topPadding = m_axisGrid->gridLineWidth() * 0.5; |
503 | float bottomPadding = topPadding; |
504 | float leftPadding = topPadding; |
505 | float rightPadding = topPadding; |
506 | // TODO Only when changed |
507 | m_axisGrid->setGridMovement(QPointF(m_axisXDisplacement, m_axisYDisplacement)); |
508 | QRectF rect = m_graph->m_plotArea; |
509 | m_axisGrid->setX(rect.x()); |
510 | m_axisGrid->setY(rect.y()); |
511 | m_axisGrid->setWidth(rect.width()); |
512 | m_axisGrid->setHeight(rect.height()); |
513 | |
514 | m_axisGrid->setGridWidth((m_axisGrid->width() - leftPadding - rightPadding) |
515 | / (m_axisHorizontalValueRange / m_axisHorizontalValueStep)); |
516 | m_axisGrid->setGridHeight((m_axisGrid->height() - topPadding - bottomPadding) |
517 | / (m_axisVerticalValueRange / m_axisVerticalValueStep)); |
518 | m_axisGrid->setGridVisibility(QVector4D(m_gridHorizontalLinesVisible, |
519 | m_gridVerticalLinesVisible, |
520 | m_gridHorizontalSubLinesVisible, |
521 | m_gridVerticalSubLinesVisible)); |
522 | m_axisGrid->setVerticalSubGridScale(m_axisVerticalSubGridScale); |
523 | m_axisGrid->setHorizontalSubGridScale(m_axisHorizontalSubGridScale); |
524 | } |
525 | |
526 | void AxisRenderer::updateAxisGridShadow() |
527 | { |
528 | if (m_graph->isShadowVisible()) { |
529 | m_axisGridShadow->setGridColor(m_graph->shadowColor()); |
530 | m_axisGridShadow->setSubGridColor(m_graph->shadowColor()); |
531 | m_axisGridShadow->setSubGridLineWidth(m_axisGrid->subGridLineWidth() + m_graph->shadowBarWidth()); |
532 | m_axisGridShadow->setGridLineWidth(m_axisGrid->gridLineWidth() + m_graph->shadowBarWidth()); |
533 | m_axisGridShadow->setSmoothing(m_axisGrid->smoothing() + m_graph->shadowSmoothing()); |
534 | |
535 | // TODO Only when changed |
536 | m_axisGridShadow->setGridMovement(m_axisGrid->gridMovement()); |
537 | m_axisGridShadow->setX(m_axisGrid->x() + m_graph->shadowXOffset() - m_graph->shadowBarWidth() * 0.5); |
538 | m_axisGridShadow->setY(m_axisGrid->y() + m_graph->shadowYOffset() + m_graph->shadowBarWidth() * 0.5); |
539 | m_axisGridShadow->setWidth(m_axisGrid->width()); |
540 | m_axisGridShadow->setHeight(m_axisGrid->height()); |
541 | m_axisGridShadow->setGridWidth(m_axisGrid->gridWidth()); |
542 | m_axisGridShadow->setGridHeight(m_axisGrid->gridHeight()); |
543 | m_axisGridShadow->setGridVisibility(m_axisGrid->gridVisibility()); |
544 | m_axisGridShadow->setVerticalSubGridScale(m_axisGrid->verticalSubGridScale()); |
545 | m_axisGridShadow->setHorizontalSubGridScale(m_axisGrid->horizontalSubGridScale()); |
546 | m_axisGridShadow->setVisible(true); |
547 | } else { |
548 | m_axisGridShadow->setVisible(false); |
549 | } |
550 | } |
551 | |
552 | void AxisRenderer::updateAxisTitles(const QRectF xAxisRect, const QRectF yAxisRect) |
553 | { |
554 | if (!m_xAxisTitle) { |
555 | m_xAxisTitle = new QQuickText(this); |
556 | m_xAxisTitle->setVAlign(QQuickText::AlignBottom); |
557 | m_xAxisTitle->setHAlign(QQuickText::AlignHCenter); |
558 | } |
559 | |
560 | if (!m_yAxisTitle) { |
561 | m_yAxisTitle = new QQuickText(this); |
562 | m_yAxisTitle->setVAlign(QQuickText::AlignVCenter); |
563 | m_yAxisTitle->setHAlign(QQuickText::AlignHCenter); |
564 | } |
565 | |
566 | if (m_axisHorizontal && m_axisHorizontal->isTitleVisible()) { |
567 | m_xAxisTitle->setText(m_axisHorizontal->titleText()); |
568 | m_xAxisTitle->setX((2 * xAxisRect.x() - m_xAxisTitle->contentWidth() + xAxisRect.width()) |
569 | * 0.5); |
570 | m_xAxisTitle->setY(xAxisRect.y() + xAxisRect.height()); |
571 | if (m_axisHorizontal->titleColor().isValid()) |
572 | m_xAxisTitle->setColor(m_axisHorizontal->titleColor()); |
573 | else |
574 | m_xAxisTitle->setColor(theme()->labelTextColor()); |
575 | m_xAxisTitle->setFont(m_axisHorizontal->titleFont()); |
576 | m_xAxisTitle->setVisible(true); |
577 | } else { |
578 | m_xAxisTitle->setVisible(false); |
579 | } |
580 | |
581 | if (m_axisVertical && m_axisVertical->isTitleVisible()) { |
582 | m_yAxisTitle->setText(m_axisVertical->titleText()); |
583 | m_yAxisTitle->setX(0 + m_yAxisTitle->height() - m_yAxisTitle->contentWidth() * 0.5); |
584 | m_yAxisTitle->setY((2 * yAxisRect.y() - m_yAxisTitle->contentHeight() + yAxisRect.height()) |
585 | * 0.5); |
586 | m_yAxisTitle->setRotation(-90); |
587 | if (m_axisVertical->titleColor().isValid()) |
588 | m_yAxisTitle->setColor(m_axisVertical->titleColor()); |
589 | else |
590 | m_yAxisTitle->setColor(theme()->labelTextColor()); |
591 | m_yAxisTitle->setFont(m_axisVertical->titleFont()); |
592 | m_yAxisTitle->setVisible(true); |
593 | } else { |
594 | m_yAxisTitle->setVisible(false); |
595 | } |
596 | } |
597 | |
598 | void AxisRenderer::updateAxisLabelItems(QList<QQuickItem *> &textItems, |
599 | qsizetype neededSize, QQmlComponent *component) |
600 | { |
601 | qsizetype currentTextItemsSize = textItems.size(); |
602 | if (currentTextItemsSize < neededSize) { |
603 | for (qsizetype i = currentTextItemsSize; i <= neededSize; i++) { |
604 | QQuickItem *item = nullptr; |
605 | if (component) { |
606 | item = qobject_cast<QQuickItem *>( |
607 | o: component->create(context: component->creationContext())); |
608 | } |
609 | if (!item) |
610 | item = new QQuickText(); |
611 | item->setParent(this); |
612 | item->setParentItem(this); |
613 | textItems << item; |
614 | } |
615 | } else if (neededSize < currentTextItemsSize) { |
616 | // Hide unused text items |
617 | for (qsizetype i = neededSize; i < currentTextItemsSize; i++) { |
618 | auto textItem = textItems[i]; |
619 | textItem->setVisible(false); |
620 | } |
621 | } |
622 | } |
623 | |
624 | void AxisRenderer::setLabelTextProperties(QQuickItem *item, const QString &text, bool xAxis, |
625 | QQuickText::HAlignment hAlign, QQuickText::VAlignment vAlign) |
626 | { |
627 | if (auto textItem = qobject_cast<QQuickText *>(object: item)) { |
628 | // If the component is a Text item (default), then text |
629 | // properties can be set directly. |
630 | textItem->setText(text); |
631 | textItem->setHeight(textItem->contentHeight()); // Default height |
632 | textItem->setHAlign(hAlign); |
633 | textItem->setVAlign(vAlign); |
634 | if (xAxis) { |
635 | textItem->setFont(theme()->axisXLabelFont()); |
636 | textItem->setColor(theme()->axisX().labelTextColor()); |
637 | } else { |
638 | textItem->setFont(theme()->axisYLabelFont()); |
639 | textItem->setColor(theme()->axisY().labelTextColor()); |
640 | } |
641 | } else { |
642 | // Check for specific dynamic properties |
643 | if (item->property(name: "text" ).isValid()) |
644 | item->setProperty(name: "text" , value: text); |
645 | } |
646 | } |
647 | |
648 | void AxisRenderer::updateBarXAxisLabels(QBarCategoryAxis *axis, const QRectF rect) |
649 | { |
650 | qsizetype categoriesCount = axis->categories().size(); |
651 | // See if we need more text items |
652 | updateAxisLabelItems(textItems&: m_xAxisTextItems, neededSize: categoriesCount, component: axis->labelDelegate()); |
653 | |
654 | int textIndex = 0; |
655 | for (auto category : axis->categories()) { |
656 | auto &textItem = m_xAxisTextItems[textIndex]; |
657 | if (axis->isVisible() && axis->labelsVisible()) { |
658 | float posX = rect.x() + ((float)textIndex / categoriesCount) * rect.width(); |
659 | textItem->setX(posX); |
660 | float posY = rect.y(); |
661 | textItem->setY(posY); |
662 | textItem->setWidth(rect.width() / categoriesCount); |
663 | textItem->setRotation(axis->labelsAngle()); |
664 | if (m_horizontalAxisOnTop) { |
665 | setLabelTextProperties(item: textItem, text: category, xAxis: true, |
666 | hAlign: QQuickText::HAlignment::AlignHCenter, |
667 | vAlign: QQuickText::VAlignment::AlignBottom); |
668 | } else { |
669 | setLabelTextProperties(item: textItem, text: category, xAxis: true, |
670 | hAlign: QQuickText::HAlignment::AlignHCenter, |
671 | vAlign: QQuickText::VAlignment::AlignTop); |
672 | } |
673 | textItem->setHeight(rect.height()); |
674 | textItem->setVisible(true); |
675 | theme()->dirtyBits()->axisXDirty = false; |
676 | } else { |
677 | textItem->setVisible(false); |
678 | } |
679 | textIndex++; |
680 | } |
681 | } |
682 | |
683 | void AxisRenderer::updateBarYAxisLabels(QBarCategoryAxis *axis, const QRectF rect) |
684 | { |
685 | qsizetype categoriesCount = axis->categories().size(); |
686 | // See if we need more text items |
687 | updateAxisLabelItems(textItems&: m_yAxisTextItems, neededSize: categoriesCount, component: axis->labelDelegate()); |
688 | |
689 | int textIndex = 0; |
690 | for (auto category : axis->categories()) { |
691 | auto &textItem = m_yAxisTextItems[textIndex]; |
692 | if (axis->isVisible() && axis->labelsVisible()) { |
693 | float posX = rect.x(); |
694 | textItem->setX(posX); |
695 | float posY = rect.y() + ((float)textIndex / categoriesCount) * rect.height(); |
696 | textItem->setY(posY); |
697 | textItem->setWidth(rect.width()); |
698 | textItem->setRotation(axis->labelsAngle()); |
699 | if (m_verticalAxisOnRight) { |
700 | setLabelTextProperties(item: textItem, text: category, xAxis: false, |
701 | hAlign: QQuickText::HAlignment::AlignRight, |
702 | vAlign: QQuickText::VAlignment::AlignVCenter); |
703 | } else { |
704 | setLabelTextProperties(item: textItem, text: category, xAxis: false, |
705 | hAlign: QQuickText::HAlignment::AlignLeft, |
706 | vAlign: QQuickText::VAlignment::AlignVCenter); |
707 | } |
708 | textItem->setHeight(rect.height() / categoriesCount); |
709 | textItem->setVisible(true); |
710 | theme()->dirtyBits()->axisYDirty = false; |
711 | } else { |
712 | textItem->setVisible(false); |
713 | } |
714 | textIndex++; |
715 | } |
716 | } |
717 | |
718 | void AxisRenderer::updateValueYAxisLabels(QValueAxis *axis, const QRectF rect) |
719 | { |
720 | // Create label values in the range |
721 | QList<double> yAxisLabelValues; |
722 | const int MAX_LABELS_COUNT = 100; |
723 | for (double i = m_axisVerticalMinLabel; i <= m_axisVerticalMaxValue; i += m_axisVerticalValueStep) { |
724 | yAxisLabelValues << i; |
725 | if (yAxisLabelValues.size() >= MAX_LABELS_COUNT) |
726 | break; |
727 | } |
728 | qsizetype categoriesCount = yAxisLabelValues.size(); |
729 | |
730 | // See if we need more text items |
731 | updateAxisLabelItems(textItems&: m_yAxisTextItems, neededSize: categoriesCount, component: axis->labelDelegate()); |
732 | |
733 | for (int i = 0; i < categoriesCount; i++) { |
734 | auto &textItem = m_yAxisTextItems[i]; |
735 | if (axis->isVisible() && axis->labelsVisible()) { |
736 | float posX = rect.x(); |
737 | textItem->setX(posX); |
738 | float posY = rect.y() + rect.height() - (((float)i) * m_axisVerticalStepPx) + m_axisYDisplacement; |
739 | const double titleMargin = 0.01; |
740 | if ((posY - titleMargin) > (rect.height() + rect.y()) || (posY + titleMargin) < rect.y()) { |
741 | // Hide text item which are outside the axis area |
742 | textItem->setVisible(false); |
743 | continue; |
744 | } |
745 | textItem->setY(posY); |
746 | textItem->setWidth(rect.width()); |
747 | textItem->setRotation(axis->labelsAngle()); |
748 | double number = yAxisLabelValues.at(i); |
749 | // Format the number |
750 | int decimals = axis->labelDecimals(); |
751 | if (decimals < 0) |
752 | decimals = getValueDecimalsFromRange(range: m_axisVerticalValueRange); |
753 | const QString f = axis->labelFormat(); |
754 | QString label; |
755 | if (f.length() <= 1) { |
756 | char format = f.isEmpty() ? 'f' : f.front().toLatin1(); |
757 | label = QString::number(number, format, precision: decimals); |
758 | } else { |
759 | QByteArray array = f.toLatin1(); |
760 | label = QString::asprintf(format: array.constData(), number); |
761 | } |
762 | if (m_verticalAxisOnRight) { |
763 | setLabelTextProperties(item: textItem, text: label, xAxis: false, |
764 | hAlign: QQuickText::HAlignment::AlignLeft, |
765 | vAlign: QQuickText::VAlignment::AlignVCenter); |
766 | } else { |
767 | setLabelTextProperties(item: textItem, text: label, xAxis: false, |
768 | hAlign: QQuickText::HAlignment::AlignRight, |
769 | vAlign: QQuickText::VAlignment::AlignVCenter); |
770 | } |
771 | textItem->setHeight(0); |
772 | textItem->setVisible(true); |
773 | theme()->dirtyBits()->axisYDirty = false; |
774 | } else { |
775 | textItem->setVisible(false); |
776 | } |
777 | } |
778 | } |
779 | |
780 | void AxisRenderer::updateValueXAxisLabels(QValueAxis *axis, const QRectF rect) |
781 | { |
782 | // Create label values in the range |
783 | QList<double> axisLabelValues; |
784 | const int MAX_LABELS_COUNT = 100; |
785 | for (double i = m_axisHorizontalMinLabel; i <= m_axisHorizontalMaxValue; i += m_axisHorizontalValueStep) { |
786 | axisLabelValues << i; |
787 | if (axisLabelValues.size() >= MAX_LABELS_COUNT) |
788 | break; |
789 | } |
790 | qsizetype categoriesCount = axisLabelValues.size(); |
791 | |
792 | // See if we need more text items |
793 | updateAxisLabelItems(textItems&: m_xAxisTextItems, neededSize: categoriesCount, component: axis->labelDelegate()); |
794 | |
795 | for (int i = 0; i < categoriesCount; i++) { |
796 | auto &textItem = m_xAxisTextItems[i]; |
797 | if (axis->isVisible() && axis->labelsVisible()) { |
798 | float posY = rect.y(); |
799 | textItem->setY(posY); |
800 | float textItemWidth = 20; |
801 | float posX = rect.x() + (((float)i) * m_axisHorizontalStepPx) - m_axisXDisplacement; |
802 | const double titleMargin = 0.01; |
803 | if ((posX - titleMargin) > (rect.width() + rect.x()) || (posX + titleMargin) < rect.x()) { |
804 | // Hide text item which are outside the axis area |
805 | textItem->setVisible(false); |
806 | continue; |
807 | } |
808 | // Take text size into account only after hiding |
809 | posX -= 0.5 * textItemWidth; |
810 | textItem->setX(posX); |
811 | textItem->setWidth(textItemWidth); |
812 | textItem->setRotation(axis->labelsAngle()); |
813 | double number = axisLabelValues.at(i); |
814 | // Format the number |
815 | int decimals = axis->labelDecimals(); |
816 | if (decimals < 0) |
817 | decimals = getValueDecimalsFromRange(range: m_axisHorizontalValueRange); |
818 | const QString f = axis->labelFormat(); |
819 | QString label; |
820 | if (f.length() <= 1) { |
821 | char format = f.isEmpty() ? 'f' : f.front().toLatin1(); |
822 | label = QString::number(number, format, precision: decimals); |
823 | } else { |
824 | QByteArray array = f.toLatin1(); |
825 | label = QString::asprintf(format: array.constData(), number); |
826 | } |
827 | if (m_horizontalAxisOnTop) { |
828 | setLabelTextProperties(item: textItem, text: label, xAxis: true, |
829 | hAlign: QQuickText::HAlignment::AlignHCenter, |
830 | vAlign: QQuickText::VAlignment::AlignBottom); |
831 | } else { |
832 | setLabelTextProperties(item: textItem, text: label, xAxis: true, |
833 | hAlign: QQuickText::HAlignment::AlignHCenter, |
834 | vAlign: QQuickText::VAlignment::AlignTop); |
835 | } |
836 | textItem->setHeight(rect.height()); |
837 | textItem->setVisible(true); |
838 | theme()->dirtyBits()->axisXDirty = false; |
839 | } else { |
840 | textItem->setVisible(false); |
841 | } |
842 | } |
843 | } |
844 | |
845 | void AxisRenderer::updateDateTimeYAxisLabels(QDateTimeAxis *axis, const QRectF rect) |
846 | { |
847 | auto maxDate = axis->max(); |
848 | auto minDate = axis->min(); |
849 | int dateTimeSize = m_axisVerticalMinLabel + 1; |
850 | auto segment = (maxDate.toMSecsSinceEpoch() - minDate.toMSecsSinceEpoch()) |
851 | / m_axisVerticalMinLabel; |
852 | |
853 | // See if we need more text items |
854 | updateAxisLabelItems(textItems&: m_yAxisTextItems, neededSize: dateTimeSize, component: axis->labelDelegate()); |
855 | |
856 | for (auto i = 0; i < dateTimeSize; ++i) { |
857 | auto &textItem = m_yAxisTextItems[i]; |
858 | if (axis->isVisible() && axis->labelsVisible()) { |
859 | float posX = rect.x(); |
860 | textItem->setX(posX); |
861 | float posY = rect.y() + rect.height() - (((float) i) * m_axisVerticalStepPx); |
862 | const double titleMargin = 0.01; |
863 | if ((posY - titleMargin) > (rect.height() + rect.y()) |
864 | || (posY + titleMargin) < rect.y()) { |
865 | // Hide text item which are outside the axis area |
866 | textItem->setVisible(false); |
867 | continue; |
868 | } |
869 | textItem->setY(posY); |
870 | textItem->setWidth(rect.width()); |
871 | textItem->setRotation(axis->labelsAngle()); |
872 | QString label = minDate.addMSecs(msecs: segment * i).toString(format: axis->labelFormat()); |
873 | if (m_verticalAxisOnRight) { |
874 | setLabelTextProperties(item: textItem, text: label, xAxis: false, |
875 | hAlign: QQuickText::HAlignment::AlignLeft, |
876 | vAlign: QQuickText::VAlignment::AlignVCenter); |
877 | } else { |
878 | setLabelTextProperties(item: textItem, text: label, xAxis: false, |
879 | hAlign: QQuickText::HAlignment::AlignRight, |
880 | vAlign: QQuickText::VAlignment::AlignVCenter); |
881 | } |
882 | textItem->setHeight(0); |
883 | textItem->setVisible(true); |
884 | } else { |
885 | textItem->setVisible(false); |
886 | } |
887 | } |
888 | } |
889 | |
890 | void AxisRenderer::updateDateTimeXAxisLabels(QDateTimeAxis *axis, const QRectF rect) |
891 | { |
892 | auto maxDate = axis->max(); |
893 | auto minDate = axis->min(); |
894 | int dateTimeSize = m_axisHorizontalMinLabel + 1; |
895 | auto segment = (maxDate.toMSecsSinceEpoch() - minDate.toMSecsSinceEpoch()) |
896 | / m_axisHorizontalMinLabel; |
897 | |
898 | // See if we need more text items |
899 | updateAxisLabelItems(textItems&: m_xAxisTextItems, neededSize: dateTimeSize, component: axis->labelDelegate()); |
900 | |
901 | for (auto i = 0; i < dateTimeSize; ++i) { |
902 | auto &textItem = m_xAxisTextItems[i]; |
903 | if (axis->isVisible() && axis->labelsVisible()) { |
904 | float posY = rect.y(); |
905 | textItem->setY(posY); |
906 | float textItemWidth = 20; |
907 | float posX = rect.x() + (((float) i) * m_axisHorizontalStepPx); |
908 | const double titleMargin = 0.01; |
909 | if ((posX - titleMargin) > (rect.width() + rect.x()) |
910 | || (posX + titleMargin) < rect.x()) { |
911 | // Hide text item which are outside the axis area |
912 | textItem->setVisible(false); |
913 | continue; |
914 | } |
915 | // Take text size into account only after hiding |
916 | posX -= 0.5 * textItemWidth; |
917 | textItem->setX(posX); |
918 | textItem->setWidth(textItemWidth); |
919 | textItem->setRotation(axis->labelsAngle()); |
920 | QString label = minDate.addMSecs(msecs: segment * i).toString(format: axis->labelFormat()); |
921 | if (m_horizontalAxisOnTop) { |
922 | setLabelTextProperties(item: textItem, text: label, xAxis: true, |
923 | hAlign: QQuickText::HAlignment::AlignHCenter, |
924 | vAlign: QQuickText::VAlignment::AlignBottom); |
925 | } else { |
926 | setLabelTextProperties(item: textItem, text: label, xAxis: true, |
927 | hAlign: QQuickText::HAlignment::AlignHCenter, |
928 | vAlign: QQuickText::VAlignment::AlignTop); |
929 | } |
930 | textItem->setHeight(rect.height()); |
931 | textItem->setVisible(true); |
932 | } else { |
933 | textItem->setVisible(false); |
934 | } |
935 | } |
936 | } |
937 | |
938 | // Calculate suitable major step based on range |
939 | double AxisRenderer::getValueStepsFromRange(double range) |
940 | { |
941 | int digits = std::ceil(x: std::log10(x: range)); |
942 | double r = std::pow(x: 10.0, y: -digits); |
943 | r *= 10.0; |
944 | double v = std::ceil(x: range * r) / r; |
945 | double step = v * 0.1; |
946 | // Step must always be bigger than 0 |
947 | step = qMax(a: 0.0001, b: step); |
948 | return step; |
949 | } |
950 | |
951 | // Calculate suitable decimals amount based on range |
952 | int AxisRenderer::getValueDecimalsFromRange(double range) |
953 | { |
954 | if (range <= 0) |
955 | return 0; |
956 | int decimals = std::ceil(x: std::log10(x: 10.0 / range)); |
957 | // Decimals must always be at least 0 |
958 | decimals = qMax(a: 0, b: decimals); |
959 | return decimals; |
960 | } |
961 | |
962 | QT_END_NAMESPACE |
963 | |