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 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | CartesianChartAxis::CartesianChartAxis(QAbstractAxis *axis, QGraphicsItem *item , bool intervalAxis) |
20 | : ChartAxisElement(axis, item, intervalAxis) |
21 | { |
22 | Q_ASSERT(item); |
23 | } |
24 | |
25 | |
26 | CartesianChartAxis::~CartesianChartAxis() |
27 | { |
28 | } |
29 | |
30 | void 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 | |
104 | void 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 | |
190 | void 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 | |
206 | void 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 | |
247 | bool CartesianChartAxis::emptyAxis() const |
248 | { |
249 | return axisGeometry().isEmpty() |
250 | || gridGeometry().isEmpty() |
251 | || qFuzzyIsNull(d: max() - min()); |
252 | } |
253 | |
254 | void 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 | |
268 | QSizeF CartesianChartAxis::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const |
269 | { |
270 | Q_UNUSED(which); |
271 | Q_UNUSED(constraint); |
272 | return QSizeF(); |
273 | } |
274 | |
275 | void 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 | |
288 | void CartesianChartAxis::handleArrowPenChanged(const QPen &pen) |
289 | { |
290 | foreach (QGraphicsItem *item, arrowItems()) |
291 | static_cast<QGraphicsLineItem *>(item)->setPen(pen); |
292 | } |
293 | |
294 | void CartesianChartAxis::handleGridPenChanged(const QPen &pen) |
295 | { |
296 | foreach (QGraphicsItem *item, gridItems()) |
297 | static_cast<QGraphicsLineItem *>(item)->setPen(pen); |
298 | } |
299 | |
300 | void CartesianChartAxis::handleMinorArrowPenChanged(const QPen &pen) |
301 | { |
302 | foreach (QGraphicsItem *item, minorArrowItems()) |
303 | static_cast<QGraphicsLineItem *>(item)->setPen(pen); |
304 | } |
305 | |
306 | void CartesianChartAxis::handleMinorGridPenChanged(const QPen &pen) |
307 | { |
308 | foreach (QGraphicsItem *item, minorGridItems()) |
309 | static_cast<QGraphicsLineItem *>(item)->setPen(pen); |
310 | } |
311 | |
312 | void 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 | |
322 | void 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 | |
332 | void CartesianChartAxis::handleShadesBrushChanged(const QBrush &brush) |
333 | { |
334 | foreach (QGraphicsItem *item, shadeItems()) |
335 | static_cast<QGraphicsRectItem *>(item)->setBrush(brush); |
336 | } |
337 | |
338 | void CartesianChartAxis::handleShadesPenChanged(const QPen &pen) |
339 | { |
340 | foreach (QGraphicsItem *item, shadeItems()) |
341 | static_cast<QGraphicsRectItem *>(item)->setPen(pen); |
342 | } |
343 | |
344 | void 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 | |
373 | void 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 | |
386 | QT_END_NAMESPACE |
387 | |
388 | #include "moc_cartesianchartaxis_p.cpp" |
389 | |