1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtCharts/qabstractaxis.h>
5#include <QtCharts/qlogvalueaxis.h>
6#include <QtCharts/qvalueaxis.h>
7#include <QtCore/qmath.h>
8#include <QtGui/qtextdocument.h>
9#include <QtWidgets/qgraphicslayout.h>
10#include <private/abstractchartlayout_p.h>
11#include <private/abstractdomain_p.h>
12#include <private/cartesianchartaxis_p.h>
13#include <private/chartpresenter_p.h>
14#include <private/linearrowitem_p.h>
15#include <private/qabstractaxis_p.h>
16
17QT_BEGIN_NAMESPACE
18
19CartesianChartAxis::CartesianChartAxis(QAbstractAxis *axis, QGraphicsItem *item , bool intervalAxis)
20 : ChartAxisElement(axis, item, intervalAxis)
21{
22 Q_ASSERT(item);
23}
24
25
26CartesianChartAxis::~CartesianChartAxis()
27{
28}
29
30void CartesianChartAxis::createItems(int count)
31{
32 if (arrowItems().size() == 0) {
33 QGraphicsLineItem *arrow = new LineArrowItem(this, this);
34 arrow->setAcceptedMouseButtons({});
35 arrow->setPen(axis()->linePen());
36 arrowGroup()->addToGroup(item: arrow);
37 }
38
39 if (intervalAxis() && gridItems().size() == 0) {
40 for (int i = 0 ; i < 2 ; i ++){
41 QGraphicsLineItem *item = new QGraphicsLineItem(this);
42 item->setAcceptedMouseButtons({});
43 item->setPen(axis()->gridLinePen());
44 gridGroup()->addToGroup(item);
45 QGraphicsRectItem *shades = new QGraphicsRectItem(this);
46 shades->setAcceptedMouseButtons({});
47 shades->setPen(axis()->shadesPen());
48 shades->setBrush(axis()->shadesBrush());
49 shadeGroup()->addToGroup(item: shades);
50 }
51 }
52
53 QGraphicsTextItem *title = titleItem();
54 title->setFont(axis()->titleFont());
55 title->setDefaultTextColor(axis()->titleBrush().color());
56 title->setHtml(axis()->titleText());
57
58 for (int i = 0; i < count; ++i) {
59 QGraphicsLineItem *arrow = new QGraphicsLineItem(this);
60 arrow->setAcceptedMouseButtons({});
61 QGraphicsLineItem *grid = new QGraphicsLineItem(this);
62 grid->setAcceptedMouseButtons({});
63 QGraphicsTextItem *label;
64 if (axis()->type() == QAbstractAxis::AxisTypeValue) {
65 label = new ValueAxisLabel(this);
66 connect(sender: static_cast<ValueAxisLabel *>(label), signal: &ValueAxisLabel::valueChanged,
67 context: this, slot: &ChartAxisElement::valueLabelEdited);
68 if (labelsEditable())
69 static_cast<ValueAxisLabel *>(label)->setEditable(true);
70#if QT_CONFIG(charts_datetime_axis)
71 } else if (axis()->type() == QAbstractAxis::AxisTypeDateTime) {
72 DateTimeAxisLabel *dateTimeLabel = new DateTimeAxisLabel(this);
73 label = dateTimeLabel;
74 connect(sender: dateTimeLabel, signal: &DateTimeAxisLabel::dateTimeChanged,
75 context: this, slot: &ChartAxisElement::dateTimeLabelEdited);
76 if (labelsEditable())
77 dateTimeLabel->setEditable(true);
78 dateTimeLabel->setFormat(static_cast<QDateTimeAxis*>(axis())->format());
79#endif
80 } else {
81 label = new QGraphicsTextItem(this);
82 }
83
84 label->setAcceptedMouseButtons({});
85 label->document()->setDocumentMargin(ChartPresenter::textMargin());
86 arrow->setPen(axis()->linePen());
87 grid->setPen(axis()->gridLinePen());
88 label->setFont(axis()->labelsFont());
89 label->setDefaultTextColor(axis()->labelsBrush().color());
90 label->setRotation(axis()->labelsAngle());
91 arrowGroup()->addToGroup(item: arrow);
92 gridGroup()->addToGroup(item: grid);
93 labelGroup()->addToGroup(item: label);
94
95 if (gridItems().size() == 1 || (((gridItems().size() + 1) % 2) && gridItems().size() > 0)) {
96 QGraphicsRectItem *shades = new QGraphicsRectItem(this);
97 shades->setPen(axis()->shadesPen());
98 shades->setBrush(axis()->shadesBrush());
99 shadeGroup()->addToGroup(item: shades);
100 }
101 }
102}
103
104void CartesianChartAxis::updateMinorTickItems()
105{
106 int currentCount = minorArrowItems().size();
107 int expectedCount = 0;
108 if (axis()->type() == QAbstractAxis::AxisTypeValue) {
109 QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: axis());
110 if (valueAxis->tickType() == QValueAxis::TicksFixed) {
111 expectedCount = valueAxis->minorTickCount() * (valueAxis->tickCount() - 1);
112 expectedCount = qMax(a: expectedCount, b: 0);
113 } else {
114 const qreal interval = valueAxis->tickInterval();
115 const qreal anchor = valueAxis->tickAnchor();
116 const qreal max = valueAxis->max();
117 const qreal min = valueAxis->min();
118 const int _minorTickCount = valueAxis->minorTickCount();
119
120 // Find the closest major tick <= the min of the range, even if it's not drawn!
121 // This is where we'll start counting minor ticks from, because minor ticks
122 // might need to be drawn even before the first major tick.
123 const qreal ticksFromAnchor = (anchor - min) / interval;
124 const qreal firstMajorTick = anchor - std::ceil(x: ticksFromAnchor) * interval;
125
126 const qreal deltaMinor = interval / qreal(_minorTickCount + 1);
127 qreal minorTick = firstMajorTick + deltaMinor;
128 int minorCounter = 0;
129
130 while (minorTick < min) {
131 minorTick += deltaMinor;
132 minorCounter++;
133 }
134
135 QList<qreal> points;
136
137 // Calculate the points on axis value space. Conversion to graphical points
138 // will be done on axis specific geometry update function
139 while (minorTick <= max) {
140 if (minorCounter < _minorTickCount) {
141 expectedCount++;
142 minorCounter++;
143 points << (minorTick - min);
144 } else {
145 minorCounter = 0;
146 }
147 minorTick += deltaMinor;
148 }
149
150 setDynamicMinorTickLayout(points);
151 }
152 } else if (axis()->type() == QAbstractAxis::AxisTypeLogValue) {
153 QLogValueAxis *logValueAxis = qobject_cast<QLogValueAxis *>(object: axis());
154
155 int minorTickCount = logValueAxis->minorTickCount();
156 if (minorTickCount < 0)
157 minorTickCount = qMax(a: qFloor(v: logValueAxis->base()) - 2, b: 0);
158
159 expectedCount = minorTickCount * (logValueAxis->tickCount() + 1);
160 expectedCount = qMax(a: expectedCount, b: logValueAxis->minorTickCount());
161 } else {
162 // minor ticks are not supported
163 return;
164 }
165
166 int diff = expectedCount - currentCount;
167 if (diff > 0) {
168 for (int i = 0; i < diff; ++i) {
169 QGraphicsLineItem *minorGridLineItem = new QGraphicsLineItem(this);
170 minorGridLineItem->setPen(axis()->minorGridLinePen());
171 minorGridGroup()->addToGroup(item: minorGridLineItem);
172
173 QGraphicsLineItem *minorArrowLineItem = new QGraphicsLineItem(this);
174 minorArrowLineItem->setPen(axis()->linePen());
175 minorArrowGroup()->addToGroup(item: minorArrowLineItem);
176 }
177 } else {
178 QList<QGraphicsItem *> minorGridItemsList = minorGridItems();
179 QList<QGraphicsItem *> minorArrowItemsList = minorArrowItems();
180 for (int i = 0; i > diff; --i) {
181 if (!minorGridItemsList.isEmpty())
182 delete minorGridItemsList.takeLast();
183
184 if (!minorArrowItemsList.isEmpty())
185 delete minorArrowItemsList.takeLast();
186 }
187 }
188}
189
190void CartesianChartAxis::deleteItems(int count)
191{
192 QList<QGraphicsItem *> lines = gridItems();
193 QList<QGraphicsItem *> labels = labelItems();
194 QList<QGraphicsItem *> shades = shadeItems();
195 QList<QGraphicsItem *> axis = arrowItems();
196
197 for (int i = 0; i < count; ++i) {
198 if (lines.size() == 1 || (((lines.size() + 1) % 2) && lines.size() > 0))
199 delete(shades.takeLast());
200 delete(lines.takeLast());
201 delete(labels.takeLast());
202 delete(axis.takeLast());
203 }
204}
205
206void CartesianChartAxis::updateLayout(const QList<qreal> &layout)
207{
208 int diff = ChartAxisElement::layout().size() - layout.size();
209
210 if (diff > 0)
211 deleteItems(count: diff);
212 else if (diff <= 0)
213 createItems(count: -diff);
214
215 updateMinorTickItems();
216
217 if (animation()) {
218 switch (presenter()->state()) {
219 case ChartPresenter::ZoomInState:
220 animation()->setAnimationType(AxisAnimation::ZoomInAnimation);
221 animation()->setAnimationPoint(presenter()->statePoint());
222 break;
223 case ChartPresenter::ZoomOutState:
224 animation()->setAnimationType(AxisAnimation::ZoomOutAnimation);
225 animation()->setAnimationPoint(presenter()->statePoint());
226 break;
227 case ChartPresenter::ScrollUpState:
228 case ChartPresenter::ScrollLeftState:
229 animation()->setAnimationType(AxisAnimation::MoveBackwordAnimation);
230 break;
231 case ChartPresenter::ScrollDownState:
232 case ChartPresenter::ScrollRightState:
233 animation()->setAnimationType(AxisAnimation::MoveForwardAnimation);
234 break;
235 case ChartPresenter::ShowState:
236 animation()->setAnimationType(AxisAnimation::DefaultAnimation);
237 break;
238 }
239 animation()->setValues(oldLayout&: ChartAxisElement::layout(), newLayout: layout);
240 presenter()->startAnimation(animation: animation());
241 } else {
242 setLayout(layout);
243 updateGeometry();
244 }
245}
246
247bool CartesianChartAxis::emptyAxis() const
248{
249 return axisGeometry().isEmpty()
250 || gridGeometry().isEmpty()
251 || qFuzzyIsNull(d: max() - min());
252}
253
254void CartesianChartAxis::setGeometry(const QRectF &axis, const QRectF &grid)
255{
256 m_gridRect = grid;
257 setAxisGeometry(axis);
258
259 if (emptyAxis()) {
260 prepareGeometryChange();
261 return;
262 }
263
264 const QList<qreal> layout = calculateLayout();
265 updateLayout(layout);
266}
267
268QSizeF CartesianChartAxis::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
269{
270 Q_UNUSED(which);
271 Q_UNUSED(constraint);
272 return QSizeF();
273}
274
275void CartesianChartAxis::setDateTimeLabelsFormat(const QString &format)
276{
277 if (max() <= min()
278 || layout().size() < 1
279 || axis()->type() != QAbstractAxis::AxisTypeDateTime) {
280 return;
281 }
282#if QT_CONFIG(charts_datetime_axis)
283 for (int i = 0; i < layout().size(); i++)
284 static_cast<DateTimeAxisLabel *>(labelItems().at(i))->setFormat(format);
285#endif
286}
287
288void CartesianChartAxis::handleArrowPenChanged(const QPen &pen)
289{
290 foreach (QGraphicsItem *item, arrowItems())
291 static_cast<QGraphicsLineItem *>(item)->setPen(pen);
292}
293
294void CartesianChartAxis::handleGridPenChanged(const QPen &pen)
295{
296 foreach (QGraphicsItem *item, gridItems())
297 static_cast<QGraphicsLineItem *>(item)->setPen(pen);
298}
299
300void CartesianChartAxis::handleMinorArrowPenChanged(const QPen &pen)
301{
302 foreach (QGraphicsItem *item, minorArrowItems())
303 static_cast<QGraphicsLineItem *>(item)->setPen(pen);
304}
305
306void CartesianChartAxis::handleMinorGridPenChanged(const QPen &pen)
307{
308 foreach (QGraphicsItem *item, minorGridItems())
309 static_cast<QGraphicsLineItem *>(item)->setPen(pen);
310}
311
312void CartesianChartAxis::handleGridLineColorChanged(const QColor &color)
313{
314 foreach (QGraphicsItem *item, gridItems()) {
315 QGraphicsLineItem *lineItem = static_cast<QGraphicsLineItem *>(item);
316 QPen pen = lineItem->pen();
317 pen.setColor(color);
318 lineItem->setPen(pen);
319 }
320}
321
322void CartesianChartAxis::handleMinorGridLineColorChanged(const QColor &color)
323{
324 foreach (QGraphicsItem *item, minorGridItems()) {
325 QGraphicsLineItem *lineItem = static_cast<QGraphicsLineItem *>(item);
326 QPen pen = lineItem->pen();
327 pen.setColor(color);
328 lineItem->setPen(pen);
329 }
330}
331
332void CartesianChartAxis::handleShadesBrushChanged(const QBrush &brush)
333{
334 foreach (QGraphicsItem *item, shadeItems())
335 static_cast<QGraphicsRectItem *>(item)->setBrush(brush);
336}
337
338void CartesianChartAxis::handleShadesPenChanged(const QPen &pen)
339{
340 foreach (QGraphicsItem *item, shadeItems())
341 static_cast<QGraphicsRectItem *>(item)->setPen(pen);
342}
343
344void CartesianChartAxis::updateLabelsValues(QValueAxis *axis)
345{
346 const QList<qreal> &layout = ChartAxisElement::layout();
347 if (layout.isEmpty())
348 return;
349
350 if (axis->tickType() == QValueAxis::TicksFixed) {
351 for (int i = 0; i < layout.size(); ++i) {
352 qreal value = axis->isReverse()
353 ? min() + ((layout.size() - 1 - i) * (max() - min()) / (layout.size() - 1))
354 : min() + (i * (max() - min()) / (layout.size() - 1));
355 static_cast<ValueAxisLabel *>(labelItems().at(i))->setValue(value);
356 }
357 } else {
358 const qreal anchor = axis->tickAnchor();
359 const qreal interval = axis->tickInterval();
360 const qreal ticksFromAnchor = (anchor - min()) / interval;
361 const qreal firstMajorTick = anchor - std::floor(x: ticksFromAnchor) * interval;
362
363 int i = axis->isReverse() ? labelItems().size()-1 : 0;
364 qreal value = firstMajorTick;
365 while (value <= max()) {
366 static_cast<ValueAxisLabel *>(labelItems().at(i))->setValue(value);
367 value += interval;
368 i += axis->isReverse() ? -1 : 1;
369 }
370 }
371}
372
373void CartesianChartAxis::updateLabelsDateTimes()
374{
375 if (max() <= min() || layout().size() < 1)
376 return;
377#if QT_CONFIG(charts_datetime_axis)
378 for (int i = 0; i < layout().size(); i++) {
379 qreal value = min() + (i * (max() - min()) / (layout().size() - 1));
380 static_cast<DateTimeAxisLabel *>(labelItems().at(i))->setValue(
381 QDateTime::fromMSecsSinceEpoch(msecs: value));
382 }
383#endif
384}
385
386QT_END_NAMESPACE
387
388#include "moc_cartesianchartaxis_p.cpp"
389

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