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