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
13QT_BEGIN_NAMESPACE
14
15AxisRenderer::AxisRenderer(QQuickItem *parent)
16 : QQuickItem(parent)
17{
18 m_graph = qobject_cast<QGraphsView *>(object: parent);
19 setFlag(flag: QQuickItem::ItemHasContents);
20}
21
22AxisRenderer::~AxisRenderer() {}
23
24QGraphsTheme *AxisRenderer::theme() {
25 return m_graph->m_theme;
26}
27
28void 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
57void 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
132void 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
325void 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
420void 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
489void 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
526void 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
552void 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
598void 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
624void 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
648void 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
683void 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
718void 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
780void 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
845void 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
890void 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
939double 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
952int 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
962QT_END_NAMESPACE
963

source code of qtgraphs/src/graphs2d/qsgrenderer/axisrenderer.cpp