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