1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#ifdef USE_BARGRAPH
5#include <QtGraphs/QBarCategoryAxis>
6#endif
7#include <QtGraphs/QGraphsTheme>
8#include <private/axisrenderer_p.h>
9#include <private/qabstractaxis_p.h>
10#include <private/qbarseries_p.h>
11#include <private/qdatetimeaxis_p.h>
12#include <private/qgraphsview_p.h>
13#include <private/qvalueaxis_p.h>
14#include <QtQuick/private/qquickdraghandler_p.h>
15
16QT_BEGIN_NAMESPACE
17
18AxisRenderer::AxisRenderer(QQuickItem *parent)
19 : QQuickItem(parent)
20{
21 m_graph = qobject_cast<QGraphsView *>(object: parent);
22 setFlag(flag: QQuickItem::ItemHasContents);
23
24 m_dragHandler = new QQuickDragHandler(this);
25 m_dragHandler->setDragThreshold(10);
26 m_dragHandler->setTarget(nullptr);
27 connect(sender: m_dragHandler, signal: &QQuickDragHandler::translationChanged,
28 context: this, slot: &AxisRenderer::onTranslationChanged);
29 connect(sender: m_dragHandler, signal: &QQuickDragHandler::grabChanged,
30 context: this, slot: &AxisRenderer::onGrabChanged);
31}
32
33AxisRenderer::~AxisRenderer() {}
34
35QGraphsTheme *AxisRenderer::theme() {
36 return m_graph->m_theme;
37}
38
39void AxisRenderer::initialize() {
40 if (m_initialized) {
41 return;
42 }
43 if (!window()) {
44 qCCritical(lcCritical2D, "window doesn't exist.");
45 return;
46 }
47
48 if (m_axisGrid)
49 m_axisGrid->componentComplete();
50 if (m_axisGridShadow)
51 m_axisGridShadow->componentComplete();
52
53 for (auto&& ax : *m_horzAxes) {
54 if (ax.line)
55 ax.line->componentComplete();
56 if (ax.ticker)
57 ax.ticker->componentComplete();
58 if (ax.lineShadow)
59 ax.lineShadow->componentComplete();
60 if (ax.tickerShadow)
61 ax.tickerShadow->componentComplete();
62 }
63
64 for (auto&& ax : *m_vertAxes) {
65 if (ax.line)
66 ax.line->componentComplete();
67 if (ax.ticker)
68 ax.ticker->componentComplete();
69 if (ax.lineShadow)
70 ax.lineShadow->componentComplete();
71 if (ax.tickerShadow)
72 ax.tickerShadow->componentComplete();
73 }
74
75 m_initialized = true;
76}
77
78QVector2D AxisRenderer::windowToAxisCoords(QVector2D coords)
79{
80 float x = coords.x();
81 float y = coords.y();
82 x /= width() - m_graph->m_marginLeft - m_graph->m_marginRight - m_graph->m_axisWidth;
83 y /= height() - m_graph->m_marginTop - m_graph->m_marginBottom - m_graph->m_axisHeight;
84 x *= (*m_horzAxes)[0].valueRange;
85 y *= (*m_vertAxes)[0].valueRange;
86 return QVector2D(x, y);
87}
88
89bool AxisRenderer::zoom(qreal delta)
90{
91 if (m_graph->zoomStyle() != QGraphsView::ZoomStyle::Center)
92 return false;
93
94 auto haxis = qobject_cast<QValueAxis *>(object: (*m_horzAxes)[0].axis);
95 auto vaxis = qobject_cast<QValueAxis *>(object: (*m_vertAxes)[0].axis);
96
97 if (!haxis && !vaxis)
98 return false;
99
100 QVector2D zoom(1.0, 1.0);
101 if (haxis)
102 zoom.setX(haxis->zoom());
103
104 if (vaxis)
105 zoom.setY(vaxis->zoom());
106
107 QVector2D change;
108 if (delta > 0)
109 change = zoom * m_graph->m_zoomSensitivity;
110 else if (delta < 0)
111 change = -zoom * m_graph->m_zoomSensitivity;
112
113 zoom += change;
114
115 if (zoom.x() < 0.01f)
116 zoom.setX(0.01f);
117 if (zoom.y() < 0.01f)
118 zoom.setY(0.01f);
119
120 if (haxis)
121 haxis->setZoom(zoom.x());
122
123 if (vaxis)
124 vaxis->setZoom(zoom.y());
125
126 return true;
127}
128
129const AxisRenderer::AxisProperties &AxisRenderer::getAxisX(QAbstractSeries *series) const
130{
131 for (auto &&ax : *m_horzAxes) {
132 if (ax.axis && (ax.axis == series->axisX() || ax.axis == series->axisY()))
133 return ax;
134 }
135 return (*m_horzAxes)[0];
136}
137
138const AxisRenderer::AxisProperties &AxisRenderer::getAxisY(QAbstractSeries *series) const
139{
140 for (auto &&ax : *m_vertAxes) {
141 if (ax.axis && (ax.axis == series->axisX() || ax.axis == series->axisY()))
142 return ax;
143 }
144 return (*m_vertAxes)[0];
145}
146
147bool AxisRenderer::handleWheel(QWheelEvent *event)
148{
149 return zoom(delta: -event->angleDelta().y());
150}
151
152void AxisRenderer::handlePinchScale(qreal delta)
153{
154 zoom(delta: delta - 1.0);
155}
156
157void AxisRenderer::handlePinchGrab(QPointingDevice::GrabTransition transition, QEventPoint point)
158{
159 Q_UNUSED(transition)
160 Q_UNUSED(point)
161}
162
163void AxisRenderer::onTranslationChanged(QVector2D delta)
164{
165 if (!m_dragState.dragging)
166 return;
167
168 m_dragState.delta += delta;
169
170 if (m_graph->zoomAreaEnabled() && m_graph->m_zoomAreaItem) {
171 m_graph->m_zoomAreaItem->setVisible(true);
172
173 qreal x = m_dragState.touchPositionAtPress.x();
174 if (m_dragState.delta.x() < 0)
175 x += m_dragState.delta.x();
176
177 qreal y = m_dragState.touchPositionAtPress.y();
178 if (m_dragState.delta.y() < 0)
179 y += m_dragState.delta.y();
180
181 qreal width = qAbs(t: m_dragState.delta.x());
182 qreal height = qAbs(t: m_dragState.delta.y());
183
184 m_graph->m_zoomAreaItem->setX(x);
185 m_graph->m_zoomAreaItem->setY(y);
186 m_graph->m_zoomAreaItem->setWidth(width);
187 m_graph->m_zoomAreaItem->setHeight(height);
188 }
189
190 if (m_graph->panStyle() != QGraphsView::PanStyle::Drag)
191 return;
192
193 auto haxis = qobject_cast<QValueAxis *>(object: (*m_horzAxes)[0].axis);
194 auto vaxis = qobject_cast<QValueAxis *>(object: (*m_vertAxes)[0].axis);
195
196 if (!haxis && !vaxis)
197 return;
198
199 QVector2D change(m_dragState.delta);
200 change = windowToAxisCoords(coords: change);
201 change.setX(-change.x());
202
203 if (haxis)
204 haxis->setPan(m_dragState.panAtPress.x() + change.x());
205
206 if (vaxis)
207 vaxis->setPan(m_dragState.panAtPress.y() + change.y());
208}
209
210void AxisRenderer::onGrabChanged(QPointingDevice::GrabTransition transition, QEventPoint point)
211{
212 const QPointF position = point.position();
213
214 auto &hax = (*m_horzAxes)[0];
215 auto &vax = (*m_vertAxes)[0];
216
217 if (transition == QPointingDevice::GrabPassive
218 && point.pressPosition() == point.position()) {
219 auto haxis = qobject_cast<QValueAxis *>(object: hax.axis);
220 auto vaxis = qobject_cast<QValueAxis *>(object: vax.axis);
221
222 if (!haxis && !vaxis)
223 return;
224
225 m_dragState.dragging = true;
226 m_dragState.touchPositionAtPress = QVector2D(position);
227 m_dragState.delta = QVector2D(0, 0);
228
229 if (haxis)
230 m_dragState.panAtPress.setX(haxis->pan());
231
232 if (vaxis)
233 m_dragState.panAtPress.setY(vaxis->pan());
234 } else if (m_dragState.dragging && transition == QPointingDevice::UngrabExclusive) {
235 m_dragState.dragging = false;
236
237 if (!m_graph->zoomAreaEnabled())
238 return;
239
240 if (m_graph->m_zoomAreaItem)
241 m_graph->m_zoomAreaItem->setVisible(false);
242
243 auto haxis = qobject_cast<QValueAxis *>(object: hax.axis);
244 auto vaxis = qobject_cast<QValueAxis *>(object: vax.axis);
245
246 if (!haxis && !vaxis)
247 return;
248
249 QVector2D zoomBoxEnd(position);
250 auto center = (m_dragState.touchPositionAtPress + zoomBoxEnd) / 2;
251 auto size = (m_dragState.touchPositionAtPress - zoomBoxEnd);
252 size.setX(qAbs(t: size.x()));
253 size.setY(qAbs(t: size.y()));
254
255 if (int(size.x()) == 0 || int(size.y()) == 0)
256 return;
257
258 size = windowToAxisCoords(coords: size);
259
260 if (haxis)
261 haxis->setZoom(hax.valueRangeZoomless / size.x());
262
263 if (vaxis)
264 vaxis->setZoom(vax.valueRangeZoomless / size.y());
265
266 center -= QVector2D(m_graph->m_marginLeft + m_graph->m_axisWidth, m_graph->m_marginTop);
267
268 center = windowToAxisCoords(coords: center);
269
270 center -= QVector2D(hax.valueRange / 2.0f, vax.valueRange / 2.0f);
271
272 if (haxis)
273 haxis->setPan(haxis->pan() + center.x());
274
275 if (vaxis)
276 vaxis->setPan(vaxis->pan() - center.y());
277 }
278}
279
280void AxisRenderer::handlePolish()
281{
282 // See if series is horizontal, so axis should also switch places.
283 bool vertical = true;
284 if (m_graph->orientation() == Qt::Orientation::Horizontal)
285 vertical = false;
286
287 if (vertical) {
288 m_horzAxes = &m_axes1;
289 m_vertAxes = &m_axes2;
290 } else {
291 m_horzAxes = &m_axes2;
292 m_vertAxes = &m_axes1;
293 }
294
295 if (vertical != m_wasVertical) {
296 // Orientation has changed, so clear possible custom elements
297 for (auto&& ax : m_axes1) {
298 if (ax.title)
299 ax.title->deleteLater();
300 if (ax.line)
301 ax.line->deleteLater();
302 if (ax.ticker)
303 ax.ticker->deleteLater();
304 if (ax.lineShadow)
305 ax.lineShadow->deleteLater();
306 if (ax.tickerShadow)
307 ax.tickerShadow->deleteLater();
308 for (auto&& item : ax.textItems)
309 item->deleteLater();
310 }
311
312 for (auto&& ax : m_axes2) {
313 if (ax.title)
314 ax.title->deleteLater();
315 if (ax.line)
316 ax.line->deleteLater();
317 if (ax.ticker)
318 ax.ticker->deleteLater();
319 if (ax.lineShadow)
320 ax.lineShadow->deleteLater();
321 if (ax.tickerShadow)
322 ax.tickerShadow->deleteLater();
323 for (auto&& item : ax.textItems)
324 item->deleteLater();
325 }
326
327 m_axes1.clear();
328 m_axes2.clear();
329
330 m_wasVertical = vertical;
331 }
332
333 if (m_axes1.empty())
334 m_axes1.emplace_back();
335
336 if (m_axes2.empty())
337 m_axes2.emplace_back();
338
339 m_axes1[0].axis = m_graph->axisX();
340 m_axes2[0].axis = m_graph->axisY();
341
342 for (auto&& s : m_graph->m_seriesList) {
343 if (auto series = qobject_cast<QAbstractSeries *>(object: s)) {
344 if (series->axisX() && series->axisX() != m_graph->axisX()) {
345 bool contains = false;
346 for (auto&& ax : m_axes1) {
347 if (ax.axis == series->axisX()) {
348 contains = true;
349 break;
350 }
351 }
352
353 if (!contains) {
354 auto &ax = m_axes1.emplace_back();
355 ax.axis = series->axisX();
356 }
357 }
358
359 if (series->axisY() && series->axisY() != m_graph->axisY()) {
360 bool contains = false;
361 for (auto&& ax : m_axes2) {
362 if (ax.axis == series->axisY()) {
363 contains = true;
364 break;
365 }
366 }
367
368 if (!contains) {
369 auto &ax = m_axes2.emplace_back();
370 ax.axis = series->axisY();
371 }
372 }
373 }
374 }
375
376 if (!m_axisGrid) {
377 m_axisGrid = new AxisGrid(this);
378 m_axisGrid->setZ(-1);
379 m_axisGrid->setupShaders();
380 m_axisGrid->setOrigo(0);
381 }
382 if (!m_axisGridShadow) {
383 m_axisGridShadow = new AxisGrid(this);
384 m_axisGridShadow->setZ(-3);
385 m_axisGridShadow->setupShaders();
386 m_axisGridShadow->setOrigo(0);
387 }
388
389 for (int i = 1; i < m_axes1.size(); i++) {
390 auto& ax = m_axes1[i];
391
392 bool used = false;
393
394 for (auto&& s : m_graph->m_seriesList) {
395 if (auto series = qobject_cast<QAbstractSeries *>(object: s)) {
396 if (series->axisX() && series->axisX() == ax.axis) {
397 used = true;
398 break;
399 }
400 }
401 }
402
403 if (!used) {
404 if (ax.title)
405 ax.title->deleteLater();
406 if (ax.line)
407 ax.line->deleteLater();
408 if (ax.ticker)
409 ax.ticker->deleteLater();
410 if (ax.lineShadow)
411 ax.lineShadow->deleteLater();
412 if (ax.tickerShadow)
413 ax.tickerShadow->deleteLater();
414 for (auto&& item : ax.textItems)
415 item->deleteLater();
416 m_axes1.removeAt(i);
417 i--;
418 continue;
419 }
420 }
421
422 for (int i = 1; i < m_axes2.size(); i++) {
423 auto& ax = m_axes2[i];
424
425 bool used = false;
426
427 for (auto&& s : m_graph->m_seriesList) {
428 if (auto series = qobject_cast<QAbstractSeries *>(object: s)) {
429 if (series->axisY() && series->axisY() == ax.axis) {
430 used = true;
431 break;
432 }
433 }
434 }
435
436 if (!used) {
437 if (ax.title)
438 ax.title->deleteLater();
439 if (ax.line)
440 ax.line->deleteLater();
441 if (ax.ticker)
442 ax.ticker->deleteLater();
443 if (ax.lineShadow)
444 ax.lineShadow->deleteLater();
445 if (ax.tickerShadow)
446 ax.tickerShadow->deleteLater();
447 for (auto&& item : ax.textItems)
448 item->deleteLater();
449 m_axes2.removeAt(i);
450 i--;
451 continue;
452 }
453 }
454
455 for (int i = 0; i < m_vertAxes->size(); i++) {
456 auto& ax = (*m_vertAxes)[i];
457
458 if (!ax.line) {
459 ax.line = new AxisLine(this);
460 ax.line->setZ(-1);
461 ax.line->setupShaders();
462 }
463 if (!ax.ticker) {
464 ax.ticker = new AxisTicker(this);
465 ax.ticker->setZ(-2);
466 ax.ticker->setOrigo(0);
467 // TODO: Configurable in theme or axis?
468 ax.ticker->setSubTickLength(0.5);
469 ax.ticker->setupShaders();
470 }
471 if (!ax.lineShadow) {
472 ax.lineShadow = new AxisLine(this);
473 ax.lineShadow->setZ(-3);
474 ax.lineShadow->setupShaders();
475 }
476 if (!ax.tickerShadow) {
477 ax.tickerShadow = new AxisTicker(this);
478 ax.tickerShadow->setZ(-3);
479 ax.tickerShadow->setOrigo(0);
480 // TODO: Configurable in theme or axis?
481 ax.tickerShadow->setSubTickLength(ax.ticker->subTickLength());
482 ax.tickerShadow->setupShaders();
483 }
484 }
485
486 for (int i = 0; i < m_horzAxes->size(); i++) {
487 auto& ax = (*m_horzAxes)[i];
488
489 if (!ax.line) {
490 ax.line = new AxisLine(this);
491 ax.line->setZ(-1);
492 ax.line->setIsHorizontal(true);
493 ax.line->setupShaders();
494 }
495 if (!ax.ticker) {
496 ax.ticker = new AxisTicker(this);
497 ax.ticker->setZ(-2);
498 ax.ticker->setIsHorizontal(true);
499 ax.ticker->setOrigo(0);
500 // TODO: Configurable in theme or axis?
501 ax.ticker->setSubTickLength(0.2);
502 ax.ticker->setupShaders();
503 }
504 if (!ax.lineShadow) {
505 ax.lineShadow = new AxisLine(this);
506 ax.lineShadow->setZ(-3);
507 ax.lineShadow->setupShaders();
508 }
509 if (!ax.tickerShadow) {
510 ax.tickerShadow = new AxisTicker(this);
511 ax.tickerShadow->setZ(-3);
512 ax.tickerShadow->setIsHorizontal(true);
513 ax.tickerShadow->setOrigo(0);
514 // TODO: Configurable in theme or axis?
515 ax.tickerShadow->setSubTickLength(ax.ticker->subTickLength());
516 ax.tickerShadow->setupShaders();
517 }
518 }
519
520 updateAxis();
521}
522
523void AxisRenderer::updateAxis()
524{
525 if (!theme())
526 return;
527
528 float axisWidth = m_graph->m_axisWidth;
529 float axisHeight = m_graph->m_axisHeight;
530
531 const bool gridVisible = theme()->isGridVisible();
532 if ((*m_vertAxes)[0].axis) {
533 m_gridVerticalLinesVisible = gridVisible && (*m_vertAxes)[0].axis->isGridVisible();
534 m_gridVerticalSubLinesVisible = gridVisible && (*m_vertAxes)[0].axis->isSubGridVisible();
535 }
536 if ((*m_horzAxes)[0].axis) {
537 m_gridHorizontalLinesVisible = gridVisible && (*m_horzAxes)[0].axis->isGridVisible();
538 m_gridHorizontalSubLinesVisible = gridVisible && (*m_horzAxes)[0].axis->isSubGridVisible();
539 }
540
541 int topCount = 0;
542 int leftCount = 0;
543 int xCount = 0;
544 int yCount = 0;
545
546 for (auto &&ax : *m_horzAxes) {
547 if (ax.axis && (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft))
548 topCount++;
549 if (ax.axis)
550 xCount++;
551 }
552
553 for (auto&& ax : *m_vertAxes) {
554 if (ax.axis && (ax.axis->alignment() == Qt::AlignLeft || ax.axis->alignment() == Qt::AlignTop))
555 leftCount++;
556 if (ax.axis)
557 yCount++;
558 }
559
560 int top = 0;
561 int bottom = 0;
562 for (auto&& ax : *m_horzAxes) {
563 if (!ax.axis)
564 continue;
565
566 ax.x = leftCount * m_graph->m_axisWidth;
567
568 if (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft) {
569 ax.y = (topCount - top - 1) * m_graph->m_axisHeight;
570 top++;
571 } else if (ax.axis->alignment() == Qt::AlignBottom || ax.axis->alignment() == Qt::AlignRight) {
572 ax.y = bottom * m_graph->m_axisHeight;
573 bottom++;
574 }
575 }
576
577 int left = 0;
578 int right = 0;
579 for (auto&& ax : *m_vertAxes) {
580 if (!ax.axis)
581 continue;
582
583 ax.y = topCount * m_graph->m_axisHeight;
584
585 if (ax.axis->alignment() == Qt::AlignLeft || ax.axis->alignment() == Qt::AlignTop) {
586 ax.x = (leftCount - left - 1) * m_graph->m_axisWidth;
587 left++;
588 } else if (ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom) {
589 ax.x = right * m_graph->m_axisWidth;
590 right++;
591 }
592 }
593
594 for (auto&& ax : *m_vertAxes) {
595 if (auto vaxis = qobject_cast<QValueAxis *>(object: ax.axis)) {
596 double step = vaxis->tickInterval();
597
598 qreal diff = vaxis->max() - vaxis->min();
599 qreal center = diff / 2.0f + vaxis->min() + vaxis->pan();
600
601 diff /= vaxis->zoom();
602
603 ax.maxValue = center + diff / 2.0f;
604 ax.minValue = center - diff / 2.0f;
605
606 ax.valueRange = ax.maxValue - ax.minValue;
607 ax.valueRangeZoomless = vaxis->max() - vaxis->min();
608
609 // If step is not manually defined (or it is invalid), calculate autostep
610 if (step <= 0)
611 step = getValueStepsFromRange(range: vaxis->max() - vaxis->min());
612
613 // Get smallest tick label value
614 double minLabel = vaxis->tickAnchor();
615 while (minLabel < ax.minValue)
616 minLabel += step;
617 while (minLabel >= (ax.minValue + step))
618 minLabel -= step;
619 ax.minLabel = minLabel;
620
621 ax.valueStep = step;
622 int axisVerticalSubTickCount = vaxis->subTickCount();
623 ax.subGridScale = axisVerticalSubTickCount > 0 ? 1.0 / (axisVerticalSubTickCount + 1)
624 : 1.0;
625 ax.stepPx = (height() - m_graph->m_marginTop - m_graph->m_marginBottom
626 - axisHeight * xCount)
627 / (ax.valueRange / ax.valueStep);
628 double axisVerticalValueDiff = ax.minLabel - ax.minValue;
629 ax.displacement = -(axisVerticalValueDiff / ax.valueStep) * ax.stepPx;
630
631 // Update value labels
632 if (ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom)
633 updateValueYAxisLabels(ax, rect: m_graph->m_y2AxisLabelsArea);
634 else
635 updateValueYAxisLabels(ax, rect: m_graph->m_y1AxisLabelsArea);
636 }
637 }
638 for (auto&& ax : *m_horzAxes) {
639 if (auto haxis = qobject_cast<QValueAxis *>(object: ax.axis)) {
640 double step = haxis->tickInterval();
641
642 qreal diff = haxis->max() - haxis->min();
643 qreal center = diff / 2.0f + haxis->min() + haxis->pan();
644
645 diff /= haxis->zoom();
646
647 ax.maxValue = center + diff / 2.0f;
648 ax.minValue = center - diff / 2.0f;
649
650 ax.valueRange = ax.maxValue - ax.minValue;
651 ax.valueRangeZoomless = haxis->max() - haxis->min();
652
653 // If step is not manually defined (or it is invalid), calculate autostep
654 if (step <= 0)
655 step = getValueStepsFromRange(range: haxis->max() - haxis->min());
656
657 // Get smallest tick label value
658 double minLabel = haxis->tickAnchor();
659 while (minLabel < ax.minValue)
660 minLabel += step;
661 while (minLabel >= (ax.minValue + step))
662 minLabel -= step;
663 ax.minLabel = minLabel;
664
665 ax.valueStep = step;
666 int axisHorizontalSubTickCount = haxis->subTickCount();
667 ax.subGridScale = axisHorizontalSubTickCount > 0 ?
668 1.0 / (axisHorizontalSubTickCount + 1) : 1.0;
669 ax.stepPx = (width() - m_graph->m_marginLeft - m_graph->m_marginRight
670 - axisWidth * yCount)
671 / (ax.valueRange / ax.valueStep);
672 double axisHorizontalValueDiff = ax.minLabel - ax.minValue;
673 ax.displacement = -(axisHorizontalValueDiff / ax.valueStep) * ax.stepPx;
674
675 // Update value labels
676 if (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft)
677 updateValueXAxisLabels(ax, rect: m_graph->m_x2AxisLabelsArea);
678 else
679 updateValueXAxisLabels(ax, rect: m_graph->m_x1AxisLabelsArea);
680 }
681 }
682
683#ifdef USE_BARGRAPH
684 for (auto&& ax : *m_vertAxes) {
685 if (auto vaxis = qobject_cast<QBarCategoryAxis *>(object: ax.axis)) {
686 ax.maxValue = vaxis->categories().size();
687 ax.minValue = 0;
688 ax.valueRange = ax.maxValue - ax.minValue;
689 if (ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom)
690 updateBarYAxisLabels(ax, rect: m_graph->m_y2AxisLabelsArea);
691 else
692 updateBarYAxisLabels(ax, rect: m_graph->m_y1AxisLabelsArea);
693 }
694 }
695
696 for (auto&& ax : *m_horzAxes) {
697 if (auto haxis = qobject_cast<QBarCategoryAxis *>(object: ax.axis)) {
698 ax.maxValue = haxis->categories().size();
699 ax.minValue = 0;
700 ax.valueRange = ax.maxValue - ax.minValue;
701 if (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft)
702 updateBarXAxisLabels(ax, rect: m_graph->m_x2AxisLabelsArea);
703 else
704 updateBarXAxisLabels(ax, rect: m_graph->m_x1AxisLabelsArea);
705 }
706 }
707#endif
708
709 for (auto&& ax : *m_vertAxes) {
710 if (auto vaxis = qobject_cast<QDateTimeAxis *>(object: ax.axis)) {
711 // Todo: make constant for all axis, or clamp in class? (QTBUG-124736)
712 const double MAX_DIVS = 100.0;
713
714 double interval = std::clamp<double>(val: vaxis->tickInterval(), lo: 0.0, hi: MAX_DIVS);
715 ax.maxValue = vaxis->max().toMSecsSinceEpoch();
716 ax.minValue = vaxis->min().toMSecsSinceEpoch();
717 ax.valueRange = std::abs(x: ax.maxValue - ax.minValue);
718
719 // in ms
720 double segment;
721 if (interval <= 0) {
722 segment = getValueStepsFromRange(range: ax.valueRange);
723 interval = ax.valueRange / segment;
724 } else {
725 segment = ax.valueRange / interval;
726 }
727
728 ax.minLabel = std::clamp(val: interval, lo: 1.0, hi: MAX_DIVS);
729
730 ax.valueStep = segment;
731 int axisVerticalSubTickCount = vaxis->subTickCount();
732 ax.subGridScale = axisVerticalSubTickCount > 0
733 ? 1.0 / (axisVerticalSubTickCount + 1)
734 : 1.0;
735 ax.stepPx = (height() - m_graph->m_marginTop - m_graph->m_marginBottom
736 - axisHeight)
737 / (qFuzzyCompare(p1: segment, p2: 0)
738 ? interval
739 : (ax.valueRange / ax.valueStep));
740
741 if (ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom)
742 updateDateTimeYAxisLabels(ax, rect: m_graph->m_y2AxisLabelsArea);
743 else
744 updateDateTimeYAxisLabels(ax, rect: m_graph->m_y1AxisLabelsArea);
745 }
746 }
747
748 for (auto&& ax : *m_horzAxes) {
749 if (auto haxis = qobject_cast<QDateTimeAxis *>(object: ax.axis)) {
750 const double MAX_DIVS = 100.0;
751
752 double interval = std::clamp<double>(val: haxis->tickInterval(), lo: 0.0, hi: MAX_DIVS);
753 ax.maxValue = haxis->max().toMSecsSinceEpoch();
754 ax.minValue = haxis->min().toMSecsSinceEpoch();
755 ax.valueRange = std::abs(x: ax.maxValue - ax.minValue);
756
757 // in ms
758 double segment;
759 if (interval <= 0) {
760 segment = getValueStepsFromRange(range: ax.valueRange);
761 interval = ax.valueRange / segment;
762 } else {
763 segment = ax.valueRange / interval;
764 }
765
766 ax.minLabel = std::clamp(val: interval, lo: 1.0, hi: MAX_DIVS);
767
768 ax.valueStep = segment;
769 int axisHorizontalSubTickCount = haxis->subTickCount();
770 ax.subGridScale = axisHorizontalSubTickCount > 0
771 ? 1.0 / (axisHorizontalSubTickCount + 1)
772 : 1.0;
773 ax.stepPx = (width() - m_graph->m_marginLeft - m_graph->m_marginRight
774 - axisWidth)
775 / (qFuzzyCompare(p1: segment, p2: 0)
776 ? interval
777 : (ax.valueRange / ax.valueStep));
778
779 if (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft)
780 updateDateTimeXAxisLabels(ax, rect: m_graph->m_x2AxisLabelsArea);
781 else
782 updateDateTimeXAxisLabels(ax, rect: m_graph->m_x1AxisLabelsArea);
783 }
784 }
785
786 updateAxisTickers();
787 updateAxisTickersShadow();
788 updateAxisGrid();
789 updateAxisGridShadow();
790 updateAxisTitles();
791}
792
793void AxisRenderer::updateAxisTickers()
794{
795 for (auto&& ax : *m_vertAxes) {
796 if (ax.axis) {
797 QRectF yAxisRect;
798 if (ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom)
799 yAxisRect = m_graph->m_y2AxisTickersArea;
800 else
801 yAxisRect = m_graph->m_y1AxisTickersArea;
802
803 // Note: Fix before enabling, see QTBUG-121207 and QTBUG-121211
804 //if (theme()->themeDirty()) {
805 ax.ticker->setSubTickColor(theme()->axisY().subColor());
806 ax.ticker->setTickColor(theme()->axisY().mainColor());
807 ax.ticker->setTickLineWidth(theme()->axisY().mainWidth());
808 ax.ticker->setSubTickLineWidth(theme()->axisY().subWidth());
809 ax.ticker->setSmoothing(m_graph->axisYSmoothing());
810 //}
811
812 float topPadding = m_axisGrid->gridLineWidth() * 0.5;
813 float bottomPadding = topPadding;
814 // TODO Only when changed
815 ax.ticker->setDisplacement(ax.displacement);
816 QRectF &rect = yAxisRect;
817 ax.ticker->setX(rect.x() + ax.x);
818 ax.ticker->setY(rect.y() + ax.y);
819 ax.ticker->setWidth(rect.width());
820 ax.ticker->setHeight(rect.height());
821 ax.ticker->setFlipped(ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom);
822
823 ax.ticker->setSpacing((ax.ticker->height() - topPadding - bottomPadding)
824 / (ax.valueRange / ax.valueStep));
825 ax.ticker->setSubTicksVisible(!qFuzzyCompare(p1: ax.subGridScale, p2: 1.0));
826 ax.ticker->setSubTickScale(ax.subGridScale);
827 ax.ticker->setVisible(ax.axis->isVisible());
828 // Axis line
829 ax.line->setColor(theme()->axisY().mainColor());
830 ax.line->setLineWidth(theme()->axisY().mainWidth());
831 ax.line->setSmoothing(m_graph->axisYSmoothing());
832
833 float xMovement = 0.5 * (ax.line->lineWidth() + ax.line->smoothing());
834 if (ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom)
835 ax.line->setX(ax.ticker->x() - xMovement);
836 else
837 ax.line->setX(ax.ticker->x() + ax.ticker->width() - xMovement);
838 ax.line->setY(ax.ticker->y());
839 ax.line->setWidth(ax.line->lineWidth() + ax.line->smoothing());
840 ax.line->setHeight(ax.ticker->height());
841 ax.line->setVisible(ax.axis->isLineVisible());
842 } else {
843 // Hide all parts of vertical axis
844 ax.ticker->setVisible(false);
845 ax.line->setVisible(false);
846 for (auto &textItem : ax.textItems)
847 textItem->setVisible(false);
848 }
849 }
850
851 for (auto&& ax : *m_horzAxes) {
852 if (ax.axis) {
853 QRectF xAxisRect;
854
855 if (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft)
856 xAxisRect = m_graph->m_x2AxisTickersArea;
857 else
858 xAxisRect = m_graph->m_x1AxisTickersArea;
859
860 //if (theme()->themeDirty()) {
861 ax.ticker->setSubTickColor(theme()->axisX().subColor());
862 ax.ticker->setTickColor(theme()->axisX().mainColor());
863 ax.ticker->setTickLineWidth(theme()->axisX().mainWidth());
864 ax.ticker->setSubTickLineWidth(theme()->axisX().subWidth());
865 ax.ticker->setSmoothing(m_graph->axisXSmoothing());
866 //}
867
868 float leftPadding = m_axisGrid->gridLineWidth() * 0.5;
869 float rightPadding = leftPadding;
870 // TODO Only when changed
871 ax.ticker->setDisplacement(ax.displacement);
872 QRectF &rect = xAxisRect;
873 ax.ticker->setX(rect.x() + ax.x);
874 ax.ticker->setY(rect.y() + ax.y);
875 ax.ticker->setWidth(rect.width());
876 ax.ticker->setHeight(rect.height());
877 ax.ticker->setFlipped(ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft);
878
879 ax.ticker->setSpacing((ax.ticker->width() - leftPadding - rightPadding)
880 / (ax.valueRange / ax.valueStep));
881 ax.ticker->setSubTicksVisible(!qFuzzyCompare(p1: ax.subGridScale, p2: 1.0));
882 ax.ticker->setSubTickScale(ax.subGridScale);
883 ax.ticker->setVisible(ax.axis->isVisible());
884 // Axis line
885 ax.line->setColor(theme()->axisX().mainColor());
886 ax.line->setLineWidth(theme()->axisX().mainWidth());
887 ax.line->setSmoothing(m_graph->axisXSmoothing());
888 ax.line->setX(ax.ticker->x());
889 float yMovement = 0.5 * (ax.line->lineWidth() + ax.line->smoothing());
890 if (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft)
891 ax.line->setY(ax.ticker->y() + ax.ticker->height() - yMovement);
892 else
893 ax.line->setY(ax.ticker->y() - yMovement);
894 ax.line->setWidth(ax.ticker->width());
895 ax.line->setHeight(ax.line->lineWidth() + ax.line->smoothing());
896 ax.line->setVisible(ax.axis->isLineVisible());
897 } else {
898 // Hide all parts of horizontal axis
899 ax.ticker->setVisible(false);
900 ax.line->setVisible(false);
901 for (auto &textItem : ax.textItems)
902 textItem->setVisible(false);
903 }
904 }
905}
906
907void AxisRenderer::updateAxisTickersShadow()
908{
909 for (auto&& ax : *m_vertAxes) {
910 if (ax.axis && m_graph->isShadowVisible()) {
911 ax.tickerShadow->setSubTickColor(m_graph->shadowColor());
912 ax.tickerShadow->setTickColor(m_graph->shadowColor());
913 ax.tickerShadow->setSubTickLineWidth(ax.ticker->subTickLineWidth()
914 + m_graph->shadowBarWidth());
915 ax.tickerShadow->setTickLineWidth(ax.ticker->tickLineWidth()
916 + m_graph->shadowBarWidth());
917 ax.tickerShadow->setSmoothing(ax.ticker->smoothing() + m_graph->shadowSmoothing());
918
919 // TODO Only when changed
920 ax.tickerShadow->setDisplacement(ax.ticker->displacement());
921 ax.tickerShadow->setX(ax.ticker->x() + m_graph->shadowXOffset());
922 ax.tickerShadow->setY(ax.ticker->y() + m_graph->shadowYOffset()
923 + m_graph->shadowBarWidth() * 0.5);
924 ax.tickerShadow->setWidth(ax.ticker->width());
925 ax.tickerShadow->setHeight(ax.ticker->height());
926 ax.tickerShadow->setFlipped(ax.ticker->isFlipped());
927 ax.tickerShadow->setSpacing(ax.ticker->spacing());
928 ax.tickerShadow->setSubTicksVisible(ax.ticker->subTicksVisible());
929 ax.tickerShadow->setSubTickScale(ax.ticker->subTickScale());
930 ax.tickerShadow->setVisible(ax.ticker->isVisible());
931 // Axis line
932 ax.lineShadow->setColor(m_graph->shadowColor());
933 ax.lineShadow->setLineWidth(ax.line->lineWidth() + m_graph->shadowBarWidth());
934 ax.lineShadow->setSmoothing(ax.line->smoothing() + m_graph->shadowSmoothing());
935 ax.lineShadow->setX(ax.line->x() + m_graph->shadowXOffset());
936 ax.lineShadow->setY(ax.line->y() + m_graph->shadowYOffset()
937 + m_graph->shadowBarWidth() * 0.5);
938 ax.lineShadow->setWidth(ax.line->width());
939 ax.lineShadow->setHeight(ax.line->height());
940 ax.lineShadow->setVisible(ax.line->isVisible());
941 } else {
942 // Hide all parts of vertical axis
943 ax.tickerShadow->setVisible(false);
944 ax.lineShadow->setVisible(false);
945 }
946 }
947
948 for (auto&& ax : *m_horzAxes) {
949 if (ax.axis && m_graph->isShadowVisible()) {
950 ax.tickerShadow->setSubTickColor(m_graph->shadowColor());
951 ax.tickerShadow->setTickColor(m_graph->shadowColor());
952 ax.tickerShadow->setSubTickLineWidth(ax.ticker->subTickLineWidth()
953 + m_graph->shadowBarWidth());
954 ax.tickerShadow->setTickLineWidth(ax.ticker->tickLineWidth()
955 + m_graph->shadowBarWidth());
956 ax.tickerShadow->setSmoothing(ax.ticker->smoothing() + m_graph->shadowSmoothing());
957
958 // TODO Only when changed
959 ax.tickerShadow->setDisplacement(ax.ticker->displacement());
960 ax.tickerShadow->setX(ax.ticker->x() + m_graph->shadowXOffset()
961 - m_graph->shadowBarWidth() * 0.5);
962 ax.tickerShadow->setY(ax.ticker->y() + m_graph->shadowYOffset());
963 ax.tickerShadow->setWidth(ax.ticker->width());
964 ax.tickerShadow->setHeight(ax.ticker->height());
965 ax.tickerShadow->setFlipped(ax.ticker->isFlipped());
966 ax.tickerShadow->setSpacing(ax.ticker->spacing());
967 ax.tickerShadow->setSubTicksVisible(ax.ticker->subTicksVisible());
968 ax.tickerShadow->setSubTickScale(ax.ticker->subTickScale());
969 ax.tickerShadow->setVisible(ax.ticker->isVisible());
970 // Axis line
971 ax.lineShadow->setColor(m_graph->shadowColor());
972 ax.lineShadow->setLineWidth(ax.line->width() + m_graph->shadowBarWidth());
973 ax.lineShadow->setSmoothing(ax.line->smoothing() + m_graph->shadowSmoothing());
974 ax.lineShadow->setX(ax.line->x() + m_graph->shadowXOffset()
975 - m_graph->shadowBarWidth() * 0.5);
976 ax.lineShadow->setY(ax.line->y() + m_graph->shadowYOffset());
977 ax.lineShadow->setWidth(ax.line->width());
978 ax.lineShadow->setHeight(ax.line->height());
979 ax.lineShadow->setVisible(ax.line->isVisible());
980 } else {
981 // Hide all parts of horizontal axis
982 ax.tickerShadow->setVisible(false);
983 ax.lineShadow->setVisible(false);
984 }
985 }
986}
987
988void AxisRenderer::updateAxisGrid()
989{
990 auto &hax = (*m_horzAxes)[0];
991 auto &vax = (*m_vertAxes)[0];
992
993 m_axisGrid->setGridColor(theme()->grid().mainColor());
994 m_axisGrid->setSubGridColor(theme()->grid().subColor());
995 m_axisGrid->setSubGridLineWidth(theme()->grid().subWidth());
996 m_axisGrid->setGridLineWidth(theme()->grid().mainWidth());
997 const double minimumSmoothing = 0.05;
998 m_axisGrid->setSmoothing(m_graph->gridSmoothing() + minimumSmoothing);
999 if (theme()->isPlotAreaBackgroundVisible())
1000 m_axisGrid->setPlotAreaBackgroundColor(theme()->plotAreaBackgroundColor());
1001 else
1002 m_axisGrid->setPlotAreaBackgroundColor(QColorConstants::Transparent);
1003
1004 float topPadding = m_axisGrid->gridLineWidth() * 0.5;
1005 float bottomPadding = topPadding;
1006 float leftPadding = topPadding;
1007 float rightPadding = topPadding;
1008 // TODO Only when changed
1009 m_axisGrid->setGridMovement(QPointF(hax.displacement, vax.displacement));
1010 QRectF rect = m_graph->m_plotArea;
1011 m_axisGrid->setX(rect.x());
1012 m_axisGrid->setY(rect.y());
1013 m_axisGrid->setWidth(rect.width());
1014 m_axisGrid->setHeight(rect.height());
1015
1016 m_axisGrid->setGridWidth((m_axisGrid->width() - leftPadding - rightPadding)
1017 / (hax.valueRange / hax.valueStep));
1018 m_axisGrid->setGridHeight((m_axisGrid->height() - topPadding - bottomPadding)
1019 / (vax.valueRange / vax.valueStep));
1020 m_axisGrid->setGridVisibility(QVector4D(m_gridHorizontalLinesVisible,
1021 m_gridVerticalLinesVisible,
1022 m_gridHorizontalSubLinesVisible,
1023 m_gridVerticalSubLinesVisible));
1024 m_axisGrid->setVerticalSubGridScale(vax.subGridScale);
1025 m_axisGrid->setHorizontalSubGridScale(hax.subGridScale);
1026}
1027
1028void AxisRenderer::updateAxisGridShadow()
1029{
1030 if (m_graph->isShadowVisible()) {
1031 m_axisGridShadow->setGridColor(m_graph->shadowColor());
1032 m_axisGridShadow->setSubGridColor(m_graph->shadowColor());
1033 m_axisGridShadow->setSubGridLineWidth(m_axisGrid->subGridLineWidth() + m_graph->shadowBarWidth());
1034 m_axisGridShadow->setGridLineWidth(m_axisGrid->gridLineWidth() + m_graph->shadowBarWidth());
1035 m_axisGridShadow->setSmoothing(m_axisGrid->smoothing() + m_graph->shadowSmoothing());
1036
1037 // TODO Only when changed
1038 m_axisGridShadow->setGridMovement(m_axisGrid->gridMovement());
1039 m_axisGridShadow->setX(m_axisGrid->x() + m_graph->shadowXOffset() - m_graph->shadowBarWidth() * 0.5);
1040 m_axisGridShadow->setY(m_axisGrid->y() + m_graph->shadowYOffset() + m_graph->shadowBarWidth() * 0.5);
1041 m_axisGridShadow->setWidth(m_axisGrid->width());
1042 m_axisGridShadow->setHeight(m_axisGrid->height());
1043 m_axisGridShadow->setGridWidth(m_axisGrid->gridWidth());
1044 m_axisGridShadow->setGridHeight(m_axisGrid->gridHeight());
1045 m_axisGridShadow->setGridVisibility(m_axisGrid->gridVisibility());
1046 m_axisGridShadow->setVerticalSubGridScale(m_axisGrid->verticalSubGridScale());
1047 m_axisGridShadow->setHorizontalSubGridScale(m_axisGrid->horizontalSubGridScale());
1048 m_axisGridShadow->setVisible(true);
1049 } else {
1050 m_axisGridShadow->setVisible(false);
1051 }
1052}
1053
1054void AxisRenderer::updateAxisTitles()
1055{
1056 QRectF xAxisRect;
1057 QRectF yAxisRect;
1058
1059 for (auto &&ax : *m_horzAxes) {
1060 if (!ax.title) {
1061 ax.title = new QQuickText(this);
1062 ax.title->setVAlign(QQuickText::AlignBottom);
1063 ax.title->setHAlign(QQuickText::AlignHCenter);
1064 }
1065
1066 if (ax.axis && ax.axis->isTitleVisible()) {
1067 if (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft) {
1068 xAxisRect = m_graph->m_x2AxisLabelsArea;
1069 ax.title->setY(xAxisRect.y() - ax.title->contentHeight() * 0.5 + ax.y);
1070 } else {
1071 xAxisRect = m_graph->m_x1AxisLabelsArea;
1072 ax.title->setY(xAxisRect.y() + xAxisRect.height() + ax.y);
1073 }
1074
1075 ax.title->setText(ax.axis->titleText());
1076 ax.title->setX(
1077 (2 * xAxisRect.x() - ax.title->contentWidth() + xAxisRect.width()) * 0.5 + ax.x);
1078 if (ax.axis->titleColor().isValid())
1079 ax.title->setColor(ax.axis->titleColor());
1080 else
1081 ax.title->setColor(theme()->labelTextColor());
1082 ax.title->setFont(ax.axis->titleFont());
1083 ax.title->setVisible(true);
1084 } else {
1085 ax.title->setVisible(false);
1086 }
1087 }
1088
1089 for (auto &&ax : *m_vertAxes) {
1090 if (!ax.title) {
1091 ax.title = new QQuickText(this);
1092 ax.title->setVAlign(QQuickText::AlignVCenter);
1093 ax.title->setHAlign(QQuickText::AlignHCenter);
1094 }
1095
1096 if (ax.axis && ax.axis->isTitleVisible()) {
1097 if (ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom) {
1098 yAxisRect = m_graph->m_y2AxisLabelsArea;
1099 ax.title->setX(yAxisRect.x() + ax.title->height() + ax.x);
1100 } else {
1101 yAxisRect = m_graph->m_y1AxisLabelsArea;
1102 ax.title->setX(yAxisRect.x() + ax.title->height() - ax.title->contentWidth() * 0.5 + ax.x);
1103 }
1104
1105 ax.title->setText(ax.axis->titleText());
1106 ax.title->setY(
1107 (2 * yAxisRect.y() - ax.title->contentHeight() + yAxisRect.height()) * 0.5 + ax.y);
1108 ax.title->setRotation(-90);
1109 if (ax.axis->titleColor().isValid())
1110 ax.title->setColor(ax.axis->titleColor());
1111 else
1112 ax.title->setColor(theme()->labelTextColor());
1113 ax.title->setFont(ax.axis->titleFont());
1114 ax.title->setVisible(true);
1115 } else {
1116 ax.title->setVisible(false);
1117 }
1118 }
1119}
1120
1121void AxisRenderer::updateAxisLabelItems(QList<QQuickItem *> &textItems,
1122 qsizetype neededSize, QQmlComponent *component)
1123{
1124 qsizetype currentTextItemsSize = textItems.size();
1125 if (currentTextItemsSize < neededSize) {
1126 for (qsizetype i = currentTextItemsSize; i <= neededSize; i++) {
1127 QQuickItem *item = nullptr;
1128 if (component) {
1129 item = qobject_cast<QQuickItem *>(
1130 o: component->create(context: component->creationContext()));
1131 }
1132 if (!item)
1133 item = new QQuickText();
1134 item->setParent(this);
1135 item->setParentItem(this);
1136 textItems << item;
1137 }
1138 } else if (neededSize < currentTextItemsSize) {
1139 // Hide unused text items
1140 for (qsizetype i = neededSize; i < currentTextItemsSize; i++) {
1141 auto textItem = textItems[i];
1142 textItem->setVisible(false);
1143 }
1144 }
1145}
1146
1147void AxisRenderer::setLabelTextProperties(QQuickItem *item, const QString &text, bool xAxis,
1148 QQuickText::HAlignment hAlign, QQuickText::VAlignment vAlign,
1149 Qt::TextElideMode elide)
1150{
1151 if (auto textItem = qobject_cast<QQuickText *>(object: item)) {
1152 // If the component is a Text item (default), then text
1153 // properties can be set directly.
1154 textItem->setText(text);
1155 textItem->setHeight(textItem->contentHeight()); // Default height
1156 textItem->setHAlign(hAlign);
1157 textItem->setVAlign(vAlign);
1158 if (xAxis) {
1159 textItem->setFont(theme()->axisXLabelFont());
1160 textItem->setColor(theme()->axisX().labelTextColor());
1161 } else {
1162 textItem->setFont(theme()->axisYLabelFont());
1163 textItem->setColor(theme()->axisY().labelTextColor());
1164 }
1165
1166 QQuickText::TextElideMode e;
1167 switch (elide) {
1168 case Qt::ElideLeft:
1169 e = QQuickText::ElideLeft;
1170 break;
1171 case Qt::ElideRight:
1172 e = QQuickText::ElideRight;
1173 break;
1174 case Qt::ElideMiddle:
1175 e = QQuickText::ElideMiddle;
1176 break;
1177 default:
1178 e = QQuickText::ElideNone;
1179 }
1180 textItem->setElideMode(e);
1181
1182 } else {
1183 // Check for specific dynamic properties
1184 if (item->property(name: "text").isValid())
1185 item->setProperty(name: "text", value: text);
1186 }
1187}
1188
1189#ifdef USE_BARGRAPH
1190void AxisRenderer::updateBarXAxisLabels(AxisProperties &ax, const QRectF rect)
1191{
1192 auto axis = qobject_cast<QBarCategoryAxis *>(object: ax.axis);
1193 if (!axis)
1194 return;
1195
1196 qsizetype categoriesCount = axis->categories().size();
1197 // See if we need more text items
1198 updateAxisLabelItems(textItems&: ax.textItems, neededSize: categoriesCount, component: axis->labelDelegate());
1199
1200 int textIndex = 0;
1201 auto categories = axis->categories();
1202 for (const auto &category : std::as_const(t&: categories)) {
1203 auto &textItem = ax.textItems[textIndex];
1204 if (axis->isVisible() && axis->labelsVisible()) {
1205 float posX = rect.x() + ((float)textIndex / categoriesCount) * rect.width() + ax.x;
1206 textItem->setX(posX);
1207 float posY = rect.y() + ax.y;
1208 textItem->setY(posY);
1209 textItem->setWidth(rect.width() / categoriesCount);
1210 textItem->setRotation(axis->labelsAngle());
1211 if (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft) {
1212 setLabelTextProperties(item: textItem, text: category, xAxis: true,
1213 hAlign: QQuickText::HAlignment::AlignHCenter,
1214 vAlign: QQuickText::VAlignment::AlignBottom,
1215 elide: axis->textElideMode());
1216 } else {
1217 setLabelTextProperties(item: textItem, text: category, xAxis: true,
1218 hAlign: QQuickText::HAlignment::AlignHCenter,
1219 vAlign: QQuickText::VAlignment::AlignTop,
1220 elide: axis->textElideMode());
1221 }
1222 textItem->setHeight(rect.height());
1223 textItem->setVisible(true);
1224 theme()->dirtyBits()->axisXDirty = false;
1225 } else {
1226 textItem->setVisible(false);
1227 }
1228 textIndex++;
1229 }
1230}
1231
1232void AxisRenderer::updateBarYAxisLabels(AxisProperties &ax, const QRectF rect)
1233{
1234 auto axis = qobject_cast<QBarCategoryAxis *>(object: ax.axis);
1235 if (!axis)
1236 return;
1237
1238 qsizetype categoriesCount = axis->categories().size();
1239 // See if we need more text items
1240 updateAxisLabelItems(textItems&: ax.textItems, neededSize: categoriesCount, component: axis->labelDelegate());
1241
1242 int textIndex = 0;
1243 auto categories = axis->categories();
1244 for (const auto &category : std::as_const(t&: categories)) {
1245 auto &textItem = ax.textItems[textIndex];
1246 if (axis->isVisible() && axis->labelsVisible()) {
1247 float posX = rect.x() + ax.x;
1248 textItem->setX(posX);
1249 float posY = rect.y() + ((float)textIndex / categoriesCount) * rect.height() + ax.y;
1250 textItem->setY(posY);
1251 textItem->setWidth(rect.width());
1252 textItem->setRotation(axis->labelsAngle());
1253 if (ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom) {
1254 setLabelTextProperties(item: textItem, text: category, xAxis: false,
1255 hAlign: QQuickText::HAlignment::AlignLeft,
1256 vAlign: QQuickText::VAlignment::AlignVCenter,
1257 elide: axis->textElideMode());
1258 } else {
1259 setLabelTextProperties(item: textItem, text: category, xAxis: false,
1260 hAlign: QQuickText::HAlignment::AlignRight,
1261 vAlign: QQuickText::VAlignment::AlignVCenter,
1262 elide: axis->textElideMode());
1263 }
1264 textItem->setHeight(rect.height() / categoriesCount);
1265 textItem->setVisible(true);
1266 theme()->dirtyBits()->axisYDirty = false;
1267 } else {
1268 textItem->setVisible(false);
1269 }
1270 textIndex++;
1271 }
1272}
1273#endif
1274
1275void AxisRenderer::updateValueYAxisLabels(AxisProperties &ax, const QRectF rect)
1276{
1277 auto axis = qobject_cast<QValueAxis *>(object: ax.axis);
1278 if (!axis)
1279 return;
1280
1281 // Create label values in the range
1282 QList<double> yAxisLabelValues;
1283 const int MAX_LABELS_COUNT = 100;
1284 if (m_graph->orientation() == Qt::Vertical) {
1285 for (double i = ax.minLabel; i <= ax.maxValue; i += ax.valueStep) {
1286 yAxisLabelValues << i;
1287 if (yAxisLabelValues.size() >= MAX_LABELS_COUNT)
1288 break;
1289 }
1290 } else {
1291 for (double i = ax.maxValue; i >= ax.minLabel; i -= ax.valueStep) {
1292 yAxisLabelValues << i;
1293 if (yAxisLabelValues.size() >= MAX_LABELS_COUNT)
1294 break;
1295 }
1296 }
1297 qsizetype categoriesCount = yAxisLabelValues.size();
1298
1299 // See if we need more text items
1300 updateAxisLabelItems(textItems&: ax.textItems, neededSize: categoriesCount, component: axis->labelDelegate());
1301
1302 for (int i = 0; i < categoriesCount; i++) {
1303 auto &textItem = ax.textItems[i];
1304 if (axis->isVisible() && axis->labelsVisible()) {
1305 float posX = rect.x() + ax.x;
1306 textItem->setX(posX);
1307 float posY = rect.y() + rect.height() - (((float)i) * ax.stepPx) + ax.displacement;
1308 const double titleMargin = 0.01;
1309 if ((posY - titleMargin) > (rect.height() + rect.y()) || (posY + titleMargin) < rect.y()) {
1310 // Hide text item which are outside the axis area
1311 textItem->setVisible(false);
1312 continue;
1313 }
1314 posY += ax.y;
1315 textItem->setY(posY);
1316 textItem->setWidth(rect.width());
1317 textItem->setRotation(axis->labelsAngle());
1318 double number = yAxisLabelValues.at(i);
1319 // Format the number
1320 int decimals = axis->labelDecimals();
1321 if (decimals < 0)
1322 decimals = getValueDecimalsFromRange(range: ax.valueRange);
1323 const QString f = axis->labelFormat();
1324 QString label;
1325 if (f.length() <= 1) {
1326 char format = f.isEmpty() ? 'f' : f.front().toLatin1();
1327 label = QString::number(number, format, precision: decimals);
1328 } else {
1329 QByteArray array = f.toLatin1();
1330 label = QString::asprintf(format: array.constData(), number);
1331 }
1332 if (ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom) {
1333 setLabelTextProperties(item: textItem, text: label, xAxis: false,
1334 hAlign: QQuickText::HAlignment::AlignLeft,
1335 vAlign: QQuickText::VAlignment::AlignVCenter,
1336 elide: axis->textElideMode());
1337 } else {
1338 setLabelTextProperties(item: textItem, text: label, xAxis: false,
1339 hAlign: QQuickText::HAlignment::AlignRight,
1340 vAlign: QQuickText::VAlignment::AlignVCenter,
1341 elide: axis->textElideMode());
1342 }
1343 textItem->setHeight(0);
1344 textItem->setVisible(true);
1345 theme()->dirtyBits()->axisYDirty = false;
1346 } else {
1347 textItem->setVisible(false);
1348 }
1349 }
1350}
1351
1352void AxisRenderer::updateValueXAxisLabels(AxisProperties &ax, const QRectF rect)
1353{
1354 auto axis = qobject_cast<QValueAxis *>(object: ax.axis);
1355 if (!axis)
1356 return;
1357
1358 // Create label values in the range
1359 QList<double> axisLabelValues;
1360 const int MAX_LABELS_COUNT = 100;
1361 for (double i = ax.minLabel; i <= ax.maxValue; i += ax.valueStep) {
1362 axisLabelValues << i;
1363 if (axisLabelValues.size() >= MAX_LABELS_COUNT)
1364 break;
1365 }
1366 qsizetype categoriesCount = axisLabelValues.size();
1367
1368 // See if we need more text items
1369 updateAxisLabelItems(textItems&: ax.textItems, neededSize: categoriesCount, component: axis->labelDelegate());
1370
1371 for (int i = 0; i < categoriesCount; i++) {
1372 auto &textItem = ax.textItems[i];
1373 if (axis->isVisible() && axis->labelsVisible()) {
1374 float posY = rect.y() + ax.y;
1375 textItem->setY(posY);
1376 float textItemWidth = 20;
1377 float posX = rect.x() + (((float)i) * ax.stepPx) - ax.displacement;
1378 const double titleMargin = 0.01;
1379 if ((posX - titleMargin) > (rect.width() + rect.x()) || (posX + titleMargin) < rect.x()) {
1380 // Hide text item which are outside the axis area
1381 textItem->setVisible(false);
1382 continue;
1383 }
1384 // Take text size into account only after hiding
1385 posX -= 0.5 * textItemWidth;
1386 posX += ax.x;
1387 textItem->setX(posX);
1388 textItem->setWidth(textItemWidth);
1389 textItem->setRotation(axis->labelsAngle());
1390 double number = axisLabelValues.at(i);
1391 // Format the number
1392 int decimals = axis->labelDecimals();
1393 if (decimals < 0)
1394 decimals = getValueDecimalsFromRange(range: ax.valueRange);
1395 const QString f = axis->labelFormat();
1396 QString label;
1397 if (f.length() <= 1) {
1398 char format = f.isEmpty() ? 'f' : f.front().toLatin1();
1399 label = QString::number(number, format, precision: decimals);
1400 } else {
1401 QByteArray array = f.toLatin1();
1402 label = QString::asprintf(format: array.constData(), number);
1403 }
1404 if (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft) {
1405 setLabelTextProperties(item: textItem, text: label, xAxis: true,
1406 hAlign: QQuickText::HAlignment::AlignHCenter,
1407 vAlign: QQuickText::VAlignment::AlignBottom,
1408 elide: axis->textElideMode());
1409 } else {
1410 setLabelTextProperties(item: textItem, text: label, xAxis: true,
1411 hAlign: QQuickText::HAlignment::AlignHCenter,
1412 vAlign: QQuickText::VAlignment::AlignTop,
1413 elide: axis->textElideMode());
1414 }
1415 textItem->setHeight(rect.height());
1416 textItem->setVisible(true);
1417 theme()->dirtyBits()->axisXDirty = false;
1418 } else {
1419 textItem->setVisible(false);
1420 }
1421 }
1422}
1423
1424void AxisRenderer::updateDateTimeYAxisLabels(AxisProperties &ax, const QRectF rect)
1425{
1426 auto axis = qobject_cast<QDateTimeAxis *>(object: ax.axis);
1427 if (!axis)
1428 return;
1429
1430 auto maxDate = axis->max();
1431 auto minDate = axis->min();
1432 int dateTimeSize = ax.minLabel + 1;
1433 auto segment = (maxDate.toMSecsSinceEpoch() - minDate.toMSecsSinceEpoch())
1434 / ax.minLabel;
1435
1436 // See if we need more text items
1437 updateAxisLabelItems(textItems&: ax.textItems, neededSize: dateTimeSize, component: axis->labelDelegate());
1438
1439 for (auto i = 0; i < dateTimeSize; ++i) {
1440 auto &textItem = ax.textItems[i];
1441 if (axis->isVisible() && axis->labelsVisible()) {
1442 float posX = rect.x() + ax.x;
1443 textItem->setX(posX);
1444 float posY = rect.y() + rect.height() - (((float) i) * ax.stepPx);
1445 const double titleMargin = 0.01;
1446 if ((posY - titleMargin) > (rect.height() + rect.y())
1447 || (posY + titleMargin) < rect.y()) {
1448 // Hide text item which are outside the axis area
1449 textItem->setVisible(false);
1450 continue;
1451 }
1452 posY += ax.y;
1453 textItem->setY(posY);
1454 textItem->setWidth(rect.width());
1455 textItem->setRotation(axis->labelsAngle());
1456 QString label = minDate.addMSecs(msecs: segment * i).toString(format: axis->labelFormat());
1457 if (ax.axis->alignment() == Qt::AlignRight || ax.axis->alignment() == Qt::AlignBottom) {
1458 setLabelTextProperties(item: textItem, text: label, xAxis: false,
1459 hAlign: QQuickText::HAlignment::AlignLeft,
1460 vAlign: QQuickText::VAlignment::AlignVCenter,
1461 elide: axis->textElideMode());
1462 } else {
1463 setLabelTextProperties(item: textItem, text: label, xAxis: false,
1464 hAlign: QQuickText::HAlignment::AlignRight,
1465 vAlign: QQuickText::VAlignment::AlignVCenter,
1466 elide: axis->textElideMode());
1467 }
1468 textItem->setHeight(0);
1469 textItem->setVisible(true);
1470 } else {
1471 textItem->setVisible(false);
1472 }
1473 }
1474}
1475
1476void AxisRenderer::updateDateTimeXAxisLabels(AxisProperties &ax, const QRectF rect)
1477{
1478 auto axis = qobject_cast<QDateTimeAxis *>(object: ax.axis);
1479 if (!axis)
1480 return;
1481
1482 auto maxDate = axis->max();
1483 auto minDate = axis->min();
1484 int dateTimeSize = ax.minLabel + 1;
1485 auto segment = (maxDate.toMSecsSinceEpoch() - minDate.toMSecsSinceEpoch())
1486 / ax.minLabel;
1487
1488 // See if we need more text items
1489 updateAxisLabelItems(textItems&: ax.textItems, neededSize: dateTimeSize, component: axis->labelDelegate());
1490
1491 for (auto i = 0; i < dateTimeSize; ++i) {
1492 auto &textItem = ax.textItems[i];
1493 if (axis->isVisible() && axis->labelsVisible()) {
1494 float posY = rect.y() + ax.y;
1495 textItem->setY(posY);
1496 float textItemWidth = 20;
1497 float posX = rect.x() + (((float) i) * ax.stepPx);
1498 const double titleMargin = 0.01;
1499 if ((posX - titleMargin) > (rect.width() + rect.x())
1500 || (posX + titleMargin) < rect.x()) {
1501 // Hide text item which are outside the axis area
1502 textItem->setVisible(false);
1503 continue;
1504 }
1505 // Take text size into account only after hiding
1506 posX += ax.x - 0.5 * textItemWidth;
1507 textItem->setX(posX);
1508 textItem->setWidth(textItemWidth);
1509 textItem->setRotation(axis->labelsAngle());
1510 QString label = minDate.addMSecs(msecs: segment * i).toString(format: axis->labelFormat());
1511 if (ax.axis->alignment() == Qt::AlignTop || ax.axis->alignment() == Qt::AlignLeft) {
1512 setLabelTextProperties(item: textItem, text: label, xAxis: true,
1513 hAlign: QQuickText::HAlignment::AlignHCenter,
1514 vAlign: QQuickText::VAlignment::AlignBottom,
1515 elide: axis->textElideMode());
1516 } else {
1517 setLabelTextProperties(item: textItem, text: label, xAxis: true,
1518 hAlign: QQuickText::HAlignment::AlignHCenter,
1519 vAlign: QQuickText::VAlignment::AlignTop,
1520 elide: axis->textElideMode());
1521 }
1522 textItem->setHeight(rect.height());
1523 textItem->setVisible(true);
1524 } else {
1525 textItem->setVisible(false);
1526 }
1527 }
1528}
1529
1530// Calculate suitable major step based on range
1531double AxisRenderer::getValueStepsFromRange(double range)
1532{
1533 int digits = std::ceil(x: std::log10(x: range));
1534 double r = std::pow(x: 10.0, y: -digits);
1535 r *= 10.0;
1536 double v = std::ceil(x: range * r) / r;
1537 double step = v * 0.1;
1538 // Step must always be bigger than 0
1539 step = qMax(a: 0.0001, b: step);
1540 return step;
1541}
1542
1543// Calculate suitable decimals amount based on range
1544int AxisRenderer::getValueDecimalsFromRange(double range)
1545{
1546 if (range <= 0)
1547 return 0;
1548 int decimals = std::ceil(x: std::log10(x: 10.0 / range));
1549 // Decimals must always be at least 0
1550 decimals = qMax(a: 0, b: decimals);
1551 return decimals;
1552}
1553
1554QT_END_NAMESPACE
1555

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