1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Charts module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include <QtCharts/qcategoryaxis.h>
31#include <QtCharts/qlogvalueaxis.h>
32#include <QtCore/qmath.h>
33#include <private/chartpresenter_p.h>
34#include <private/verticalaxis_p.h>
35
36QT_CHARTS_BEGIN_NAMESPACE
37
38VerticalAxis::VerticalAxis(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis)
39 : CartesianChartAxis(axis, item, intervalAxis)
40{
41}
42
43VerticalAxis::~VerticalAxis()
44{
45}
46
47QSizeF VerticalAxis::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
48{
49 Q_UNUSED(constraint);
50 QSizeF sh(0, 0);
51
52 if (axis()->titleText().isEmpty() || !titleItem()->isVisible())
53 return sh;
54
55 switch (which) {
56 case Qt::MinimumSize: {
57 QRectF titleRect = ChartPresenter::textBoundingRect(font: axis()->titleFont(),
58 QStringLiteral("..."));
59 sh = QSizeF(titleRect.height() + (titlePadding() * 2.0), titleRect.width());
60 break;
61 }
62 case Qt::MaximumSize:
63 case Qt::PreferredSize: {
64 QRectF titleRect = ChartPresenter::textBoundingRect(font: axis()->titleFont(), text: axis()->titleText());
65 sh = QSizeF(titleRect.height() + (titlePadding() * 2.0), titleRect.width());
66 break;
67 }
68 default:
69 break;
70 }
71
72 return sh;
73}
74
75void VerticalAxis::updateGeometry()
76{
77 const QVector<qreal> &layout = ChartAxisElement::layout();
78 const QVector<qreal> &dynamicMinorTicklayout = ChartAxisElement::dynamicMinorTicklayout();
79
80 if (layout.isEmpty() && dynamicMinorTicklayout.isEmpty()
81 && axis()->type() != QAbstractAxis::AxisTypeLogValue) {
82 return;
83 }
84
85 QStringList labelList = labels();
86
87 QList<QGraphicsItem *> labels = labelItems();
88 QList<QGraphicsItem *> arrow = arrowItems();
89 QGraphicsTextItem *title = titleItem();
90
91 Q_ASSERT(labels.size() == labelList.size());
92 Q_ASSERT(layout.size() == labelList.size());
93
94 const QRectF &axisRect = axisGeometry();
95 const QRectF &gridRect = gridGeometry();
96
97 qreal height = axisRect.bottom();
98
99 //arrow
100 QGraphicsLineItem *arrowItem = static_cast<QGraphicsLineItem*>(arrow.at(i: 0));
101
102 //arrow position
103 if (axis()->alignment() == Qt::AlignLeft)
104 arrowItem->setLine(x1: axisRect.right(), y1: gridRect.top(), x2: axisRect.right(), y2: gridRect.bottom());
105 else if (axis()->alignment() == Qt::AlignRight)
106 arrowItem->setLine(x1: axisRect.left(), y1: gridRect.top(), x2: axisRect.left(), y2: gridRect.bottom());
107
108 //title
109 QRectF titleBoundingRect;
110 QString titleText = axis()->titleText();
111 qreal labelAvailableSpace = axisRect.width();
112 if (!titleText.isEmpty() && titleItem()->isVisible()) {
113 const qreal titleAvailableSpace =
114 axisRect.height() - labelPadding() - (titlePadding() * 2.0);
115 qreal minimumLabelWidth = ChartPresenter::textBoundingRect(font: axis()->labelsFont(),
116 QStringLiteral("...")).width();
117 qreal titleSpace = titleAvailableSpace - minimumLabelWidth;
118 title->setHtml(ChartPresenter::truncatedText(font: axis()->titleFont(), text: titleText, angle: qreal(90.0),
119 maxWidth: titleSpace, maxHeight: gridRect.height(),
120 boundingRect&: titleBoundingRect));
121 title->setTextWidth(titleBoundingRect.height());
122
123 titleBoundingRect = title->boundingRect();
124
125 QPointF center = gridRect.center() - titleBoundingRect.center();
126 if (axis()->alignment() == Qt::AlignLeft) {
127 title->setPos(ax: axisRect.left() - titleBoundingRect.width() / 2.0
128 + titleBoundingRect.height() / 2.0 + titlePadding(), ay: center.y());
129 } else if (axis()->alignment() == Qt::AlignRight) {
130 title->setPos(ax: axisRect.right() - titleBoundingRect.width() / 2.0
131 - titleBoundingRect.height() / 2.0 - titlePadding(), ay: center.y());
132 }
133
134 title->setTransformOriginPoint(titleBoundingRect.center());
135 title->setRotation(270);
136 labelAvailableSpace -= titleBoundingRect.height();
137 }
138
139 QList<QGraphicsItem *> lines = gridItems();
140 QList<QGraphicsItem *> shades = shadeItems();
141
142 for (int i = 0; i < layout.size(); ++i) {
143 //items
144 QGraphicsLineItem *gridItem = static_cast<QGraphicsLineItem *>(lines.at(i));
145 QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(arrow.at(i: i + 1));
146 QGraphicsTextItem *labelItem = static_cast<QGraphicsTextItem *>(labels.at(i));
147
148 //grid line
149 if (axis()->isReverse()) {
150 gridItem->setLine(x1: gridRect.left(), y1: gridRect.top() + gridRect.bottom() - layout[i],
151 x2: gridRect.right(), y2: gridRect.top() + gridRect.bottom() - layout[i]);
152 } else {
153 gridItem->setLine(x1: gridRect.left(), y1: layout[i], x2: gridRect.right(), y2: layout[i]);
154 }
155
156 //label text wrapping
157 QString text;
158 if (axis()->isReverse() && axis()->type() != QAbstractAxis::AxisTypeCategory)
159 text = labelList.at(i: labelList.count() - i - 1);
160 else
161 text = labelList.at(i);
162
163 QRectF boundingRect;
164 // don't truncate empty labels
165 if (text.isEmpty()) {
166 labelItem->setHtml(text);
167 } else {
168 qreal labelHeight = (axisRect.height() / layout.count()) - (2 * labelPadding());
169 QString truncatedText =
170 ChartPresenter::truncatedText(font: axis()->labelsFont(), text, angle: axis()->labelsAngle(),
171 maxWidth: labelAvailableSpace, maxHeight: labelHeight, boundingRect);
172 labelItem->setTextWidth(ChartPresenter::textBoundingRect(font: axis()->labelsFont(),
173 text: truncatedText).width());
174 labelItem->setHtml(truncatedText);
175 }
176
177 //label transformation origin point
178 const QRectF &rect = labelItem->boundingRect();
179 QPointF center = rect.center();
180 labelItem->setTransformOriginPoint(ax: center.x(), ay: center.y());
181 qreal widthDiff = rect.width() - boundingRect.width();
182 qreal heightDiff = rect.height() - boundingRect.height();
183
184 //ticks and label position
185 QPointF labelPos;
186 if (axis()->alignment() == Qt::AlignLeft) {
187 if (axis()->isReverse()) {
188 labelPos = QPointF(axisRect.right() - rect.width() + (widthDiff / 2.0)
189 - labelPadding(),
190 gridRect.top() + gridRect.bottom()
191 - layout[layout.size() - i - 1] - center.y());
192 tickItem->setLine(x1: axisRect.right() - labelPadding(),
193 y1: gridRect.top() + gridRect.bottom() - layout[i],
194 x2: axisRect.right(),
195 y2: gridRect.top() + gridRect.bottom() - layout[i]);
196 } else {
197 labelPos = QPointF(axisRect.right() - rect.width() + (widthDiff / 2.0)
198 - labelPadding(),
199 layout[i] - center.y());
200 tickItem->setLine(x1: axisRect.right() - labelPadding(), y1: layout[i],
201 x2: axisRect.right(), y2: layout[i]);
202 }
203 } else if (axis()->alignment() == Qt::AlignRight) {
204 if (axis()->isReverse()) {
205 tickItem->setLine(x1: axisRect.left(),
206 y1: gridRect.top() + gridRect.bottom() - layout[i],
207 x2: axisRect.left() + labelPadding(),
208 y2: gridRect.top() + gridRect.bottom() - layout[i]);
209 labelPos = QPointF(axisRect.left() + labelPadding() - (widthDiff / 2.0),
210 gridRect.top() + gridRect.bottom()
211 - layout[layout.size() - i - 1] - center.y());
212 } else {
213 labelPos = QPointF(axisRect.left() + labelPadding() - (widthDiff / 2.0),
214 layout[i] - center.y());
215 tickItem->setLine(x1: axisRect.left(), y1: layout[i],
216 x2: axisRect.left() + labelPadding(), y2: layout[i]);
217 }
218 }
219
220 //label in between
221 bool forceHide = false;
222 bool labelOnValue = false;
223 if (intervalAxis() && (i + 1) != layout.size()) {
224 qreal lowerBound;
225 qreal upperBound;
226 if (axis()->isReverse()) {
227 lowerBound = qMax(a: gridRect.top() + gridRect.bottom() - layout[i + 1],
228 b: gridRect.top());
229 upperBound = qMin(a: gridRect.top() + gridRect.bottom() - layout[i],
230 b: gridRect.bottom());
231 } else {
232 lowerBound = qMin(a: layout[i], b: gridRect.bottom());
233 upperBound = qMax(a: layout[i + 1], b: gridRect.top());
234 }
235 const qreal delta = lowerBound - upperBound;
236 if (axis()->type() != QAbstractAxis::AxisTypeCategory) {
237 // Hide label in case visible part of the category at the grid edge is too narrow
238 if (delta < boundingRect.height()
239 && (lowerBound == gridRect.bottom() || upperBound == gridRect.top())) {
240 forceHide = true;
241 } else {
242 labelPos.setY(lowerBound - (delta / 2.0) - center.y());
243 }
244 } else {
245 QCategoryAxis *categoryAxis = static_cast<QCategoryAxis *>(axis());
246 if (categoryAxis->labelsPosition() == QCategoryAxis::AxisLabelsPositionCenter) {
247 if (delta < boundingRect.height()
248 && (lowerBound == gridRect.bottom() || upperBound == gridRect.top())) {
249 forceHide = true;
250 } else {
251 labelPos.setY(lowerBound - (delta / 2.0) - center.y());
252 }
253 } else if (categoryAxis->labelsPosition()
254 == QCategoryAxis::AxisLabelsPositionOnValue) {
255 labelOnValue = true;
256 if (axis()->isReverse()) {
257 labelPos.setY(gridRect.top() + gridRect.bottom()
258 - layout[i + 1] - center.y());
259 } else {
260 labelPos.setY(upperBound - center.y());
261 }
262 }
263 }
264 }
265
266 // Round to full pixel via QPoint to avoid one pixel clipping on the edge in some cases
267 labelItem->setPos(labelPos.toPoint());
268
269 //label overlap detection - compensate one pixel for rounding errors
270 if (axis()->isReverse()) {
271 if (forceHide)
272 labelItem->setVisible(false);
273 } else if (labelItem->pos().y() + boundingRect.height() > height || forceHide ||
274 ((labelItem->pos().y() + (heightDiff / 2.0) - 1.0) > axisRect.bottom()
275 && !labelOnValue) ||
276 (labelItem->pos().y() + (heightDiff / 2.0) < (axisRect.top() - 1.0) && !labelOnValue)) {
277 labelItem->setVisible(false);
278 }
279 else {
280 labelItem->setVisible(true);
281 height=labelItem->pos().y();
282 }
283
284 //shades
285 QGraphicsRectItem *shadeItem = 0;
286 if (i == 0)
287 shadeItem = static_cast<QGraphicsRectItem *>(shades.at(i: 0));
288 else if (i % 2)
289 shadeItem = static_cast<QGraphicsRectItem *>(shades.at(i: (i / 2) + 1));
290 if (shadeItem) {
291 qreal lowerBound;
292 qreal upperBound;
293 if (i == 0) {
294 if (axis()->isReverse()) {
295 upperBound = gridRect.top();
296 lowerBound = gridRect.top() + gridRect.bottom() - layout[i];
297 } else {
298 lowerBound = gridRect.bottom();
299 upperBound = layout[0];
300 }
301 } else {
302 if (axis()->isReverse()) {
303 upperBound = gridRect.top() + gridRect.bottom() - layout[i];
304 if (i == layout.size() - 1) {
305 lowerBound = gridRect.bottom();
306 } else {
307 lowerBound = qMax(a: gridRect.top() + gridRect.bottom() - layout[i + 1],
308 b: gridRect.top());
309 }
310 } else {
311 lowerBound = layout[i];
312 if (i == layout.size() - 1)
313 upperBound = gridRect.top();
314 else
315 upperBound = qMax(a: layout[i + 1], b: gridRect.top());
316 }
317
318 }
319 if (lowerBound > gridRect.bottom())
320 lowerBound = gridRect.bottom();
321 if (upperBound < gridRect.top())
322 upperBound = gridRect.top();
323 shadeItem->setRect(ax: gridRect.left(), ay: upperBound, w: gridRect.width(),
324 h: lowerBound - upperBound);
325 if (shadeItem->rect().height() <= 0.0)
326 shadeItem->setVisible(false);
327 else
328 shadeItem->setVisible(true);
329 }
330
331 // check if the grid line and the axis tick should be shown
332 const bool gridLineVisible = (gridItem->line().p1().y() >= gridRect.top()
333 && gridItem->line().p1().y() <= gridRect.bottom());
334 gridItem->setVisible(gridLineVisible);
335 tickItem->setVisible(gridLineVisible);
336 }
337
338 updateMinorTickGeometry();
339
340 // begin/end grid line in case labels between
341 if (intervalAxis()) {
342 QGraphicsLineItem *gridLine;
343 gridLine = static_cast<QGraphicsLineItem *>(lines.at(i: layout.size()));
344 gridLine->setLine(x1: gridRect.left(), y1: gridRect.top(), x2: gridRect.right(), y2: gridRect.top());
345 gridLine->setVisible(true);
346 gridLine = static_cast<QGraphicsLineItem*>(lines.at(i: layout.size() + 1));
347 gridLine->setLine(x1: gridRect.left(), y1: gridRect.bottom(), x2: gridRect.right(), y2: gridRect.bottom());
348 gridLine->setVisible(true);
349 }
350}
351
352void VerticalAxis::updateMinorTickGeometry()
353{
354 if (!axis())
355 return;
356
357 QVector<qreal> layout = ChartAxisElement::layout();
358 int minorTickCount = 0;
359 qreal tickSpacing = 0.0;
360 QVector<qreal> minorTickSpacings;
361 switch (axis()->type()) {
362 case QAbstractAxis::AxisTypeValue: {
363 const QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: axis());
364
365 minorTickCount = valueAxis->minorTickCount();
366
367 if (valueAxis->tickType() == QValueAxis::TicksFixed) {
368 if (valueAxis->tickCount() >= 2)
369 tickSpacing = layout.at(i: 0) - layout.at(i: 1);
370
371 for (int i = 0; i < minorTickCount; ++i) {
372 const qreal ratio = (1.0 / qreal(minorTickCount + 1)) * qreal(i + 1);
373 minorTickSpacings.append(t: tickSpacing * ratio);
374 }
375 }
376 break;
377 }
378 case QAbstractAxis::AxisTypeLogValue: {
379 const QLogValueAxis *logValueAxis = qobject_cast<QLogValueAxis *>(object: axis());
380 const qreal base = logValueAxis->base();
381 const qreal logBase = qLn(v: base);
382
383 minorTickCount = logValueAxis->minorTickCount();
384 if (minorTickCount < 0)
385 minorTickCount = qMax(a: int(qFloor(v: base) - 2.0), b: 0);
386
387 // Two "virtual" ticks are required to make sure that all minor ticks
388 // are displayed properly (even for the partially visible segments of
389 // the chart).
390 if (layout.size() >= 2) {
391 // Calculate tickSpacing as a difference between visible ticks
392 // whenever it is possible. Virtual ticks will not be correctly
393 // positioned when the layout is animating.
394 tickSpacing = layout.at(i: 0) - layout.at(i: 1);
395 layout.prepend(t: layout.at(i: 0) + tickSpacing);
396 layout.append(t: layout.at(i: layout.size() - 1) - tickSpacing);
397 } else {
398 const qreal logMax = qLn(v: logValueAxis->max());
399 const qreal logMin = qLn(v: logValueAxis->min());
400 const qreal logExtraMaxTick = qLn(v: qPow(x: base, y: qFloor(v: logMax / logBase) + 1.0));
401 const qreal logExtraMinTick = qLn(v: qPow(x: base, y: qCeil(v: logMin / logBase) - 1.0));
402 const qreal edge = gridGeometry().bottom();
403 const qreal delta = gridGeometry().height() / qAbs(t: logMax - logMin);
404 const qreal extraMaxTick = edge - (logExtraMaxTick - qMin(a: logMin, b: logMax)) * delta;
405 const qreal extraMinTick = edge - (logExtraMinTick - qMin(a: logMin, b: logMax)) * delta;
406
407 // Calculate tickSpacing using one (if layout.size() == 1) or two
408 // (if layout.size() == 0) "virtual" ticks. In both cases animation
409 // will not work as expected. This should be fixed later.
410 layout.prepend(t: extraMinTick);
411 layout.append(t: extraMaxTick);
412 tickSpacing = layout.at(i: 0) - layout.at(i: 1);
413 }
414
415 const qreal minorTickStepValue = qFabs(v: base - 1.0) / qreal(minorTickCount + 1);
416 for (int i = 0; i < minorTickCount; ++i) {
417 const qreal x = minorTickStepValue * qreal(i + 1) + 1.0;
418 const qreal minorTickSpacing = tickSpacing * (qLn(v: x) / logBase);
419 minorTickSpacings.append(t: minorTickSpacing);
420 }
421 break;
422 }
423 default:
424 // minor ticks are not supported
425 break;
426 }
427
428 const QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: axis());
429 if (valueAxis && valueAxis->tickType() == QValueAxis::TicksDynamic) {
430 const QVector<qreal> dynamicMinorTicklayout = ChartAxisElement::dynamicMinorTicklayout();
431 const QRectF &gridRect = gridGeometry();
432 const qreal deltaY = gridRect.height() / (valueAxis->max() - valueAxis->min());
433 const qreal bottomPos = gridRect.bottom();
434 const qreal topPos = gridRect.top();
435
436 for (int i = 0; i < dynamicMinorTicklayout.size(); i++) {
437 QGraphicsLineItem *minorGridLineItem =
438 static_cast<QGraphicsLineItem *>(minorGridItems().value(i));
439 QGraphicsLineItem *minorArrowLineItem =
440 static_cast<QGraphicsLineItem *>(minorArrowItems().value(i));
441 if (!minorGridLineItem || !minorArrowLineItem)
442 continue;
443
444 qreal minorGridLineItemY = 0.0;
445 if (axis()->isReverse())
446 minorGridLineItemY = topPos + dynamicMinorTicklayout.at(i) * deltaY;
447 else
448 minorGridLineItemY = bottomPos - dynamicMinorTicklayout.at(i) * deltaY;
449
450 qreal minorArrowLineItemX1;
451 qreal minorArrowLineItemX2;
452 switch (axis()->alignment()) {
453 case Qt::AlignLeft:
454 minorArrowLineItemX1 = gridGeometry().left() - labelPadding() / 2.0;
455 minorArrowLineItemX2 = gridGeometry().left();
456 break;
457 case Qt::AlignRight:
458 minorArrowLineItemX1 = gridGeometry().right();
459 minorArrowLineItemX2 = gridGeometry().right() + labelPadding() / 2.0;
460 break;
461 default:
462 minorArrowLineItemX1 = 0.0;
463 minorArrowLineItemX2 = 0.0;
464 break;
465 }
466
467 minorGridLineItem->setLine(x1: gridGeometry().left(), y1: minorGridLineItemY,
468 x2: gridGeometry().right(), y2: minorGridLineItemY);
469 minorArrowLineItem->setLine(x1: minorArrowLineItemX1, y1: minorGridLineItemY,
470 x2: minorArrowLineItemX2, y2: minorGridLineItemY);
471
472 // check if the minor grid line and the minor axis arrow should be shown
473 bool minorGridLineVisible = (minorGridLineItemY >= gridGeometry().top()
474 && minorGridLineItemY <= gridGeometry().bottom());
475 minorGridLineItem->setVisible(minorGridLineVisible);
476 minorArrowLineItem->setVisible(minorGridLineVisible);
477 }
478 } else {
479 if (minorTickCount < 1 || tickSpacing == 0.0 || minorTickSpacings.count() != minorTickCount)
480 return;
481
482 for (int i = 0; i < layout.size() - 1; ++i) {
483 for (int j = 0; j < minorTickCount; ++j) {
484 const int minorItemIndex = i * minorTickCount + j;
485 QGraphicsLineItem *minorGridLineItem =
486 static_cast<QGraphicsLineItem *>(minorGridItems().value(i: minorItemIndex));
487 QGraphicsLineItem *minorArrowLineItem =
488 static_cast<QGraphicsLineItem *>(minorArrowItems().value(i: minorItemIndex));
489 if (!minorGridLineItem || !minorArrowLineItem)
490 continue;
491
492 const qreal minorTickSpacing = minorTickSpacings.value(i: j, defaultValue: 0.0);
493
494 qreal minorGridLineItemY = 0.0;
495 if (axis()->isReverse()) {
496 minorGridLineItemY = qFloor(v: gridGeometry().top() + gridGeometry().bottom()
497 - layout.at(i) + minorTickSpacing);
498 } else {
499 minorGridLineItemY = qCeil(v: layout.at(i) - minorTickSpacing);
500 }
501
502 qreal minorArrowLineItemX1;
503 qreal minorArrowLineItemX2;
504 switch (axis()->alignment()) {
505 case Qt::AlignLeft:
506 minorArrowLineItemX1 = gridGeometry().left() - labelPadding() / 2.0;
507 minorArrowLineItemX2 = gridGeometry().left();
508 break;
509 case Qt::AlignRight:
510 minorArrowLineItemX1 = gridGeometry().right();
511 minorArrowLineItemX2 = gridGeometry().right() + labelPadding() / 2.0;
512 break;
513 default:
514 minorArrowLineItemX1 = 0.0;
515 minorArrowLineItemX2 = 0.0;
516 break;
517 }
518
519 minorGridLineItem->setLine(x1: gridGeometry().left(), y1: minorGridLineItemY,
520 x2: gridGeometry().right(), y2: minorGridLineItemY);
521 minorArrowLineItem->setLine(x1: minorArrowLineItemX1, y1: minorGridLineItemY,
522 x2: minorArrowLineItemX2, y2: minorGridLineItemY);
523
524 // check if the minor grid line and the minor axis arrow should be shown
525 bool minorGridLineVisible = (minorGridLineItemY >= gridGeometry().top()
526 && minorGridLineItemY <= gridGeometry().bottom());
527 minorGridLineItem->setVisible(minorGridLineVisible);
528 minorArrowLineItem->setVisible(minorGridLineVisible);
529 }
530 }
531 }
532}
533
534QT_CHARTS_END_NAMESPACE
535

source code of qtcharts/src/charts/axis/verticalaxis.cpp