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
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
55void 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
130void 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
323void 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
418void 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
487void 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
522void 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
548void 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
588void 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
614void 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
638void 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
673void 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
708void 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
764void 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
823void 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
868void 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
917double 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
930int 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
940QT_END_NAMESPACE
941

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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