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 <private/chartaxiselement_p.h> |
31 | #include <private/qabstractaxis_p.h> |
32 | #include <private/chartpresenter_p.h> |
33 | #include <private/abstractchartlayout_p.h> |
34 | #include <QtCharts/QCategoryAxis> |
35 | #include <QtCore/QtMath> |
36 | #include <QtCore/QDateTime> |
37 | #include <QtCore/QRegularExpression> |
38 | #include <QtGui/QTextDocument> |
39 | #include <cmath> |
40 | |
41 | QT_CHARTS_BEGIN_NAMESPACE |
42 | |
43 | static const char *labelFormatMatchString = "%[\\-\\+#\\s\\d\\.\\'lhjztL]*([dicuoxfegXFEG])" ; |
44 | static const char *labelFormatMatchLocalizedString = "^([^%]*)%\\.(\\d+)([defgiEG])(.*)$" ; |
45 | static QRegularExpression *labelFormatMatcher = 0; |
46 | static QRegularExpression *labelFormatMatcherLocalized = 0; |
47 | class StaticLabelFormatMatcherDeleter |
48 | { |
49 | public: |
50 | StaticLabelFormatMatcherDeleter() {} |
51 | ~StaticLabelFormatMatcherDeleter() { |
52 | delete labelFormatMatcher; |
53 | delete labelFormatMatcherLocalized; |
54 | } |
55 | }; |
56 | static StaticLabelFormatMatcherDeleter staticLabelFormatMatcherDeleter; |
57 | |
58 | ChartAxisElement::ChartAxisElement(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis) |
59 | : ChartElement(item), |
60 | m_axis(axis), |
61 | m_animation(0), |
62 | m_grid(new QGraphicsItemGroup(item)), |
63 | m_arrow(new QGraphicsItemGroup(item)), |
64 | m_minorGrid(new QGraphicsItemGroup(item)), |
65 | m_minorArrow(new QGraphicsItemGroup(item)), |
66 | m_shades(new QGraphicsItemGroup(item)), |
67 | m_labels(new QGraphicsItemGroup(item)), |
68 | m_title(new QGraphicsTextItem(item)), |
69 | m_intervalAxis(intervalAxis) |
70 | |
71 | { |
72 | //initial initialization |
73 | m_arrow->setHandlesChildEvents(false); |
74 | m_arrow->setZValue(ChartPresenter::AxisZValue); |
75 | m_minorArrow->setHandlesChildEvents(false); |
76 | m_minorArrow->setZValue(ChartPresenter::AxisZValue); |
77 | m_labels->setZValue(ChartPresenter::AxisZValue); |
78 | m_shades->setZValue(ChartPresenter::ShadesZValue); |
79 | m_grid->setZValue(ChartPresenter::GridZValue); |
80 | m_minorGrid->setZValue(ChartPresenter::GridZValue); |
81 | m_title->setZValue(ChartPresenter::GridZValue); |
82 | m_title->document()->setDocumentMargin(ChartPresenter::textMargin()); |
83 | handleVisibleChanged(visible: axis->isVisible()); |
84 | connectSlots(); |
85 | |
86 | setFlag(flag: QGraphicsItem::ItemHasNoContents, enabled: true); |
87 | } |
88 | |
89 | ChartAxisElement::~ChartAxisElement() |
90 | { |
91 | } |
92 | |
93 | void ChartAxisElement::connectSlots() |
94 | { |
95 | QObject::connect(sender: axis(), SIGNAL(visibleChanged(bool)), receiver: this, SLOT(handleVisibleChanged(bool))); |
96 | QObject::connect(sender: axis(), SIGNAL(lineVisibleChanged(bool)), receiver: this, SLOT(handleArrowVisibleChanged(bool))); |
97 | QObject::connect(sender: axis(), SIGNAL(gridVisibleChanged(bool)), receiver: this, SLOT(handleGridVisibleChanged(bool))); |
98 | QObject::connect(sender: axis(), SIGNAL(labelsVisibleChanged(bool)), receiver: this, SLOT(handleLabelsVisibleChanged(bool))); |
99 | QObject::connect(sender: axis(), SIGNAL(shadesVisibleChanged(bool)), receiver: this, SLOT(handleShadesVisibleChanged(bool))); |
100 | QObject::connect(sender: axis(), SIGNAL(labelsAngleChanged(int)), receiver: this, SLOT(handleLabelsAngleChanged(int))); |
101 | QObject::connect(sender: axis(), SIGNAL(linePenChanged(const QPen&)), receiver: this, SLOT(handleArrowPenChanged(const QPen&))); |
102 | QObject::connect(sender: axis(), SIGNAL(labelsBrushChanged(const QBrush&)), receiver: this, SLOT(handleLabelsBrushChanged(const QBrush&))); |
103 | QObject::connect(sender: axis(), SIGNAL(labelsFontChanged(const QFont&)), receiver: this, SLOT(handleLabelsFontChanged(const QFont&))); |
104 | QObject::connect(sender: axis(), SIGNAL(gridLinePenChanged(const QPen&)), receiver: this, SLOT(handleGridPenChanged(const QPen&))); |
105 | QObject::connect(sender: axis(), SIGNAL(shadesPenChanged(const QPen&)), receiver: this, SLOT(handleShadesPenChanged(const QPen&))); |
106 | QObject::connect(sender: axis(), SIGNAL(shadesBrushChanged(const QBrush&)), receiver: this, SLOT(handleShadesBrushChanged(const QBrush&))); |
107 | QObject::connect(sender: axis(), SIGNAL(titleTextChanged(const QString&)), receiver: this, SLOT(handleTitleTextChanged(const QString&))); |
108 | QObject::connect(sender: axis(), SIGNAL(titleFontChanged(const QFont&)), receiver: this, SLOT(handleTitleFontChanged(const QFont&))); |
109 | QObject::connect(sender: axis(), SIGNAL(titleBrushChanged(const QBrush&)), receiver: this, SLOT(handleTitleBrushChanged(const QBrush&))); |
110 | QObject::connect(sender: axis(), SIGNAL(titleVisibleChanged(bool)), receiver: this, SLOT(handleTitleVisibleChanged(bool))); |
111 | QObject::connect(sender: axis()->d_ptr.data(), SIGNAL(rangeChanged(qreal, qreal)), receiver: this, SLOT(handleRangeChanged(qreal, qreal))); |
112 | QObject::connect(sender: axis(), SIGNAL(reverseChanged(bool)), receiver: this, SLOT(handleReverseChanged(bool))); |
113 | QObject::connect(sender: axis(), SIGNAL(lineVisibleChanged(bool)), |
114 | receiver: this, SLOT(handleMinorArrowVisibleChanged(bool))); |
115 | QObject::connect(sender: axis(), SIGNAL(linePenChanged(const QPen&)), receiver: this, |
116 | SLOT(handleMinorArrowPenChanged(const QPen&))); |
117 | QObject::connect(sender: axis(), SIGNAL(minorGridVisibleChanged(bool)), |
118 | receiver: this, SLOT(handleMinorGridVisibleChanged(bool))); |
119 | QObject::connect(sender: axis(), SIGNAL(minorGridLinePenChanged(const QPen&)), |
120 | receiver: this, SLOT(handleMinorGridPenChanged(const QPen&))); |
121 | QObject::connect(sender: axis(), SIGNAL(gridLineColorChanged(const QColor&)), |
122 | receiver: this, SLOT(handleGridLineColorChanged(const QColor&))); |
123 | QObject::connect(sender: axis(), SIGNAL(minorGridLineColorChanged(const QColor&)), |
124 | receiver: this, SLOT(handleMinorGridLineColorChanged(const QColor&))); |
125 | |
126 | if (axis()->type() == QAbstractAxis::AxisTypeCategory) { |
127 | QCategoryAxis *categoryAxis = static_cast<QCategoryAxis *>(axis()); |
128 | QObject::connect(sender: categoryAxis, |
129 | SIGNAL(labelsPositionChanged(QCategoryAxis::AxisLabelsPosition)), |
130 | receiver: this, SLOT(handleLabelsPositionChanged())); |
131 | } |
132 | } |
133 | |
134 | void ChartAxisElement::handleArrowVisibleChanged(bool visible) |
135 | { |
136 | m_arrow->setVisible(visible); |
137 | } |
138 | |
139 | void ChartAxisElement::handleMinorArrowVisibleChanged(bool visible) |
140 | { |
141 | m_minorArrow->setVisible(visible); |
142 | } |
143 | |
144 | void ChartAxisElement::handleGridVisibleChanged(bool visible) |
145 | { |
146 | m_grid->setVisible(visible); |
147 | } |
148 | |
149 | void ChartAxisElement::handleMinorGridVisibleChanged(bool visible) |
150 | { |
151 | m_minorGrid->setVisible(visible); |
152 | } |
153 | |
154 | void ChartAxisElement::handleLabelsPositionChanged() |
155 | { |
156 | QGraphicsLayoutItem::updateGeometry(); |
157 | presenter()->layout()->invalidate(); |
158 | } |
159 | |
160 | void ChartAxisElement::valueLabelEdited(qreal oldValue, qreal newValue) |
161 | { |
162 | qreal range = max() - min(); |
163 | qreal center = ((max() - min()) / 2.0) + min(); |
164 | qreal newRange = 0.0; |
165 | auto label = static_cast<ValueAxisLabel *>(this->sender()); |
166 | if ((oldValue >= center && newValue >= min()) |
167 | || (oldValue < center && newValue >= max() && oldValue != min())) { |
168 | newRange = range * ((newValue - min()) / (oldValue - min())); |
169 | if (newRange > 0) { |
170 | m_axis->setRange(min: min(), max: min() + newRange); |
171 | return; |
172 | } |
173 | } else if ((oldValue >= center && newValue <= min() && max() != oldValue) |
174 | || (oldValue < center && newValue < max())) { |
175 | newRange = range * ((max() - newValue) / (max() - oldValue)); |
176 | if (newRange > 0) { |
177 | m_axis->setRange(min: max() - newRange, max: max()); |
178 | return; |
179 | } |
180 | } |
181 | label->reloadBeforeEditContent(); |
182 | } |
183 | |
184 | void ChartAxisElement::dateTimeLabelEdited(const QDateTime &oldTime, const QDateTime &newTime) |
185 | { |
186 | qreal range = max() - min(); |
187 | qreal center = ((max() - min()) / 2.0) + min(); |
188 | qreal newRange = 0.0; |
189 | qint64 oldValue = oldTime.toMSecsSinceEpoch(); |
190 | qint64 newValue = newTime.toMSecsSinceEpoch(); |
191 | if ((oldValue >= center && newValue >= min()) |
192 | || (oldValue < center && newValue >= max() && oldValue != min())) { |
193 | newRange = range * ((newValue - min()) / (oldValue - min())); |
194 | if (newRange > 0) { |
195 | m_axis->setRange( |
196 | min: QDateTime::fromMSecsSinceEpoch(msecs: min()), |
197 | max: QDateTime::fromMSecsSinceEpoch(msecs: min() + newRange)); |
198 | return; |
199 | } |
200 | } else if ((oldValue >= center && newValue <= min() && max() != oldValue) |
201 | || (oldValue < center && newValue < max())) { |
202 | newRange = range * ((max() - newValue) / (max() - oldValue)); |
203 | if (newRange > 0) { |
204 | m_axis->setRange(min: max() - newRange, max: max()); |
205 | m_axis->setRange( |
206 | min: QDateTime::fromMSecsSinceEpoch(msecs: max() - newRange), |
207 | max: QDateTime::fromMSecsSinceEpoch(msecs: max())); |
208 | return; |
209 | } |
210 | } |
211 | static_cast<DateTimeAxisLabel *>(this->sender())->reloadBeforeEditContent(); |
212 | } |
213 | |
214 | void ChartAxisElement::handleLabelsVisibleChanged(bool visible) |
215 | { |
216 | QGraphicsLayoutItem::updateGeometry(); |
217 | presenter()->layout()->invalidate(); |
218 | m_labels->setVisible(visible); |
219 | } |
220 | |
221 | void ChartAxisElement::handleShadesVisibleChanged(bool visible) |
222 | { |
223 | m_shades->setVisible(visible); |
224 | } |
225 | |
226 | void ChartAxisElement::handleTitleVisibleChanged(bool visible) |
227 | { |
228 | QGraphicsLayoutItem::updateGeometry(); |
229 | presenter()->layout()->invalidate(); |
230 | m_title->setVisible(visible); |
231 | } |
232 | |
233 | void ChartAxisElement::handleLabelsAngleChanged(int angle) |
234 | { |
235 | foreach (QGraphicsItem *item, m_labels->childItems()) |
236 | item->setRotation(angle); |
237 | |
238 | QGraphicsLayoutItem::updateGeometry(); |
239 | presenter()->layout()->invalidate(); |
240 | } |
241 | |
242 | void ChartAxisElement::handleLabelsBrushChanged(const QBrush &brush) |
243 | { |
244 | foreach (QGraphicsItem *item, m_labels->childItems()) |
245 | static_cast<QGraphicsTextItem *>(item)->setDefaultTextColor(brush.color()); |
246 | } |
247 | |
248 | void ChartAxisElement::handleLabelsFontChanged(const QFont &font) |
249 | { |
250 | foreach (QGraphicsItem *item, m_labels->childItems()) |
251 | static_cast<QGraphicsTextItem *>(item)->setFont(font); |
252 | QGraphicsLayoutItem::updateGeometry(); |
253 | presenter()->layout()->invalidate(); |
254 | } |
255 | |
256 | void ChartAxisElement::handleTitleTextChanged(const QString &title) |
257 | { |
258 | QGraphicsLayoutItem::updateGeometry(); |
259 | presenter()->layout()->invalidate(); |
260 | if (title.isEmpty() || !m_title->isVisible()) |
261 | m_title->setHtml(title); |
262 | } |
263 | |
264 | void ChartAxisElement::handleTitleBrushChanged(const QBrush &brush) |
265 | { |
266 | m_title->setDefaultTextColor(brush.color()); |
267 | } |
268 | |
269 | void ChartAxisElement::handleTitleFontChanged(const QFont &font) |
270 | { |
271 | if (m_title->font() != font) { |
272 | m_title->setFont(font); |
273 | QGraphicsLayoutItem::updateGeometry(); |
274 | presenter()->layout()->invalidate(); |
275 | } |
276 | } |
277 | |
278 | void ChartAxisElement::handleVisibleChanged(bool visible) |
279 | { |
280 | setVisible(visible); |
281 | if (!visible) { |
282 | m_grid->setVisible(visible); |
283 | m_arrow->setVisible(visible); |
284 | m_minorGrid->setVisible(visible); |
285 | m_minorArrow->setVisible(visible); |
286 | m_shades->setVisible(visible); |
287 | m_labels->setVisible(visible); |
288 | m_title->setVisible(visible); |
289 | } else { |
290 | m_grid->setVisible(axis()->isGridLineVisible()); |
291 | m_arrow->setVisible(axis()->isLineVisible()); |
292 | m_minorGrid->setVisible(axis()->isMinorGridLineVisible()); |
293 | m_minorArrow->setVisible(axis()->isLineVisible()); |
294 | m_shades->setVisible(axis()->shadesVisible()); |
295 | m_labels->setVisible(axis()->labelsVisible()); |
296 | m_title->setVisible(axis()->isTitleVisible()); |
297 | } |
298 | if (presenter()) { |
299 | if (visible) { |
300 | QSizeF before = effectiveSizeHint(which: Qt::PreferredSize); |
301 | QSizeF after = sizeHint(which: Qt::PreferredSize); |
302 | if (before != after) |
303 | QGraphicsLayoutItem::updateGeometry(); |
304 | } |
305 | presenter()->layout()->invalidate(); |
306 | } |
307 | } |
308 | |
309 | void ChartAxisElement::handleRangeChanged(qreal min, qreal max) |
310 | { |
311 | Q_UNUSED(min); |
312 | Q_UNUSED(max); |
313 | |
314 | if (!isEmpty()) { |
315 | QVector<qreal> layout = calculateLayout(); |
316 | updateLayout(layout); |
317 | QSizeF before = effectiveSizeHint(which: Qt::PreferredSize); |
318 | QSizeF after = sizeHint(which: Qt::PreferredSize); |
319 | |
320 | if (before != after) { |
321 | QGraphicsLayoutItem::updateGeometry(); |
322 | // We don't want to call invalidate on layout, since it will change minimum size of |
323 | // component, which we would like to avoid since it causes nasty flips when scrolling |
324 | // or zooming, instead recalculate layout and use plotArea for extra space. |
325 | presenter()->layout()->setGeometry(presenter()->layout()->geometry()); |
326 | } |
327 | } |
328 | } |
329 | |
330 | void ChartAxisElement::handleReverseChanged(bool reverse) |
331 | { |
332 | Q_UNUSED(reverse); |
333 | |
334 | QGraphicsLayoutItem::updateGeometry(); |
335 | presenter()->layout()->invalidate(); |
336 | } |
337 | |
338 | bool ChartAxisElement::isEmpty() |
339 | { |
340 | return axisGeometry().isEmpty() |
341 | || gridGeometry().isEmpty() |
342 | || qFuzzyCompare(p1: min(), p2: max()); |
343 | } |
344 | |
345 | qreal ChartAxisElement::min() const |
346 | { |
347 | return m_axis->d_ptr->min(); |
348 | } |
349 | |
350 | qreal ChartAxisElement::max() const |
351 | { |
352 | return m_axis->d_ptr->max(); |
353 | } |
354 | |
355 | qreal ChartAxisElement::tickInterval() const |
356 | { |
357 | QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: m_axis); |
358 | if (valueAxis) |
359 | return valueAxis->tickInterval(); |
360 | else |
361 | return 0.0; |
362 | } |
363 | |
364 | qreal ChartAxisElement::tickAnchor() const |
365 | { |
366 | QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: m_axis); |
367 | if (valueAxis) |
368 | return valueAxis->tickAnchor(); |
369 | else |
370 | return 0.0; |
371 | } |
372 | |
373 | QString ChartAxisElement::formatLabel(const QString &formatSpec, const QByteArray &array, |
374 | qreal value, int precision, const QString &preStr, |
375 | const QString &postStr) const |
376 | { |
377 | QString retVal; |
378 | if (!formatSpec.isEmpty()) { |
379 | if (formatSpec.at(i: 0) == QLatin1Char('d') |
380 | || formatSpec.at(i: 0) == QLatin1Char('i') |
381 | || formatSpec.at(i: 0) == QLatin1Char('c')) { |
382 | if (presenter()->localizeNumbers()) |
383 | retVal = preStr + presenter()->locale().toString(i: qint64(value)) + postStr; |
384 | else |
385 | retVal = QString::asprintf(format: array.constData(), qint64(value)); |
386 | } else if (formatSpec.at(i: 0) == QLatin1Char('u') |
387 | || formatSpec.at(i: 0) == QLatin1Char('o') |
388 | || formatSpec.at(i: 0) == QLatin1Char('x') |
389 | || formatSpec.at(i: 0) == QLatin1Char('X')) { |
390 | // These formats are not supported by localized numbers |
391 | retVal = QString::asprintf(format: array.constData(), quint64(value)); |
392 | } else if (formatSpec.at(i: 0) == QLatin1Char('f') |
393 | || formatSpec.at(i: 0) == QLatin1Char('F') |
394 | || formatSpec.at(i: 0) == QLatin1Char('e') |
395 | || formatSpec.at(i: 0) == QLatin1Char('E') |
396 | || formatSpec.at(i: 0) == QLatin1Char('g') |
397 | || formatSpec.at(i: 0) == QLatin1Char('G')) { |
398 | if (presenter()->localizeNumbers()) { |
399 | retVal = preStr |
400 | + presenter()->locale().toString(i: value, f: formatSpec.at(i: 0).toLatin1(), |
401 | prec: precision) |
402 | + postStr; |
403 | } else { |
404 | retVal = QString::asprintf(format: array.constData(), value); |
405 | } |
406 | } |
407 | } |
408 | return retVal; |
409 | } |
410 | |
411 | QStringList ChartAxisElement::createValueLabels(qreal min, qreal max, int ticks, |
412 | qreal tickInterval, qreal tickAnchor, |
413 | QValueAxis::TickType tickType, |
414 | const QString &format) const |
415 | { |
416 | QStringList labels; |
417 | |
418 | if (max <= min || ticks < 1) |
419 | return labels; |
420 | |
421 | if (format.isEmpty()) { |
422 | // Calculate how many decimal digits are needed to show difference between ticks, |
423 | // for example tick marks 1.002 and 1.003 have a difference of 0.001 and need 3 decimals. |
424 | // For differences >= 1 (positive log10) use always 1 decimal. |
425 | double l10 = std::log10(x: (max - min) / (ticks - 1)); |
426 | int n = qMax(a: int(-qFloor(v: l10)), b: 0) + 1; |
427 | if (tickType == QValueAxis::TicksFixed) { |
428 | for (int i = 0; i < ticks; i++) { |
429 | qreal value = min + (i * (max - min) / (ticks - 1)); |
430 | labels << presenter()->numberToString(value, f: 'f', prec: n); |
431 | } |
432 | } else { |
433 | qreal value = tickAnchor; |
434 | if (value > min) |
435 | value = value - int((value - min) / tickInterval) * tickInterval; |
436 | else |
437 | value = value + qCeil(v: (min - value) / tickInterval) * tickInterval; |
438 | |
439 | while (value <= max || qFuzzyCompare(p1: value, p2: max)) { |
440 | labels << presenter()->numberToString(value, f: 'f', prec: n); |
441 | value += tickInterval; |
442 | } |
443 | } |
444 | } else { |
445 | QByteArray array = format.toLatin1(); |
446 | QString formatSpec; |
447 | QString preStr; |
448 | QString postStr; |
449 | int precision = 6; // Six is the default precision in Qt API |
450 | if (presenter()->localizeNumbers()) { |
451 | if (!labelFormatMatcherLocalized) |
452 | labelFormatMatcherLocalized |
453 | = new QRegularExpression(QString::fromLatin1(str: labelFormatMatchLocalizedString)); |
454 | QRegularExpressionMatch rmatch; |
455 | if (format.indexOf(re: *labelFormatMatcherLocalized, from: 0, rmatch: &rmatch) != -1) { |
456 | preStr = rmatch.captured(nth: 1); |
457 | if (!rmatch.captured(nth: 2).isEmpty()) |
458 | precision = rmatch.captured(nth: 2).toInt(); |
459 | formatSpec = rmatch.captured(nth: 3); |
460 | postStr = rmatch.captured(nth: 4); |
461 | } |
462 | } else { |
463 | if (!labelFormatMatcher) |
464 | labelFormatMatcher = new QRegularExpression(QString::fromLatin1(str: labelFormatMatchString)); |
465 | QRegularExpressionMatch rmatch; |
466 | if (format.indexOf(re: *labelFormatMatcher, from: 0, rmatch: &rmatch) != -1) |
467 | formatSpec = rmatch.captured(nth: 1); |
468 | } |
469 | if (tickType == QValueAxis::TicksFixed) { |
470 | for (int i = 0; i < ticks; i++) { |
471 | qreal value = min + (i * (max - min) / (ticks - 1)); |
472 | labels << formatLabel(formatSpec, array, value, precision, preStr, postStr); |
473 | } |
474 | } else { |
475 | qreal value = tickAnchor; |
476 | if (value > min) |
477 | value = value - int((value - min) / tickInterval) * tickInterval; |
478 | else |
479 | value = value + qCeil(v: (min - value) / tickInterval) * tickInterval; |
480 | |
481 | while (value <= max || qFuzzyCompare(p1: value, p2: max)) { |
482 | labels << formatLabel(formatSpec, array, value, precision, preStr, postStr); |
483 | value += tickInterval; |
484 | } |
485 | } |
486 | } |
487 | |
488 | return labels; |
489 | } |
490 | |
491 | QStringList ChartAxisElement::createLogValueLabels(qreal min, qreal max, qreal base, int ticks, |
492 | const QString &format) const |
493 | { |
494 | QStringList labels; |
495 | |
496 | if (max <= min || ticks < 1) |
497 | return labels; |
498 | |
499 | int firstTick; |
500 | if (base > 1) |
501 | firstTick = qCeil(v: std::log10(x: min) / std::log10(x: base)); |
502 | else |
503 | firstTick = qCeil(v: std::log10(x: max) / std::log10(x: base)); |
504 | |
505 | if (format.isEmpty()) { |
506 | int n = 0; |
507 | if (ticks > 1) |
508 | n = qMax(a: int(-qFloor(v: std::log10(x: (max - min) / (ticks - 1)))), b: 0); |
509 | n++; |
510 | for (int i = firstTick; i < ticks + firstTick; i++) { |
511 | qreal value = qPow(x: base, y: i); |
512 | labels << presenter()->numberToString(value, f: 'f', prec: n); |
513 | } |
514 | } else { |
515 | QByteArray array = format.toLatin1(); |
516 | QString formatSpec; |
517 | QString preStr; |
518 | QString postStr; |
519 | int precision = 6; // Six is the default precision in Qt API |
520 | if (presenter()->localizeNumbers()) { |
521 | if (!labelFormatMatcherLocalized) |
522 | labelFormatMatcherLocalized = |
523 | new QRegularExpression(QString::fromLatin1(str: labelFormatMatchLocalizedString)); |
524 | QRegularExpressionMatch rmatch; |
525 | if (format.indexOf(re: *labelFormatMatcherLocalized, from: 0, rmatch: &rmatch) != -1) { |
526 | preStr = rmatch.captured(nth: 1); |
527 | if (!rmatch.captured(nth: 2).isEmpty()) |
528 | precision = rmatch.captured(nth: 2).toInt(); |
529 | formatSpec = rmatch.captured(nth: 3); |
530 | postStr = rmatch.captured(nth: 4); |
531 | } |
532 | } else { |
533 | if (!labelFormatMatcher) |
534 | labelFormatMatcher = new QRegularExpression(QString::fromLatin1(str: labelFormatMatchString)); |
535 | QRegularExpressionMatch rmatch; |
536 | if (format.indexOf(re: *labelFormatMatcher, from: 0, rmatch: &rmatch) != -1) |
537 | formatSpec = rmatch.captured(nth: 1); |
538 | } |
539 | for (int i = firstTick; i < ticks + firstTick; i++) { |
540 | qreal value = qPow(x: base, y: i); |
541 | labels << formatLabel(formatSpec, array, value, precision, preStr, postStr); |
542 | } |
543 | } |
544 | |
545 | return labels; |
546 | } |
547 | |
548 | QStringList ChartAxisElement::createDateTimeLabels(qreal min, qreal max,int ticks, |
549 | const QString &format) const |
550 | { |
551 | QStringList labels; |
552 | |
553 | if (max <= min || ticks < 1) |
554 | return labels; |
555 | |
556 | for (int i = 0; i < ticks; i++) { |
557 | qreal value = min + (i * (max - min) / (ticks - 1)); |
558 | labels << presenter()->locale().toString(dateTime: QDateTime::fromMSecsSinceEpoch(msecs: value), format); |
559 | } |
560 | return labels; |
561 | } |
562 | |
563 | |
564 | bool ChartAxisElement::labelsEditable() const |
565 | { |
566 | return m_labelsEditable; |
567 | } |
568 | |
569 | void ChartAxisElement::setLabelsEditable(bool labelsEditable) |
570 | { |
571 | if (axis()->type() == QAbstractAxis::AxisTypeValue |
572 | || axis()->type() == QAbstractAxis::AxisTypeDateTime) { |
573 | labelGroup()->setHandlesChildEvents(!labelsEditable); |
574 | const QList<QGraphicsItem *> childItems = labelGroup()->childItems(); |
575 | for (auto item : childItems) { |
576 | switch (axis()->type()) { |
577 | case QtCharts::QAbstractAxis::AxisTypeValue: |
578 | static_cast<ValueAxisLabel *>(item)->setEditable(labelsEditable); |
579 | break; |
580 | case QtCharts::QAbstractAxis::AxisTypeDateTime: |
581 | static_cast<DateTimeAxisLabel *>(item)->setEditable(labelsEditable); |
582 | break; |
583 | default: |
584 | break; |
585 | } |
586 | } |
587 | m_labelsEditable = labelsEditable; |
588 | } |
589 | } |
590 | |
591 | void ChartAxisElement::axisSelected() |
592 | { |
593 | emit clicked(); |
594 | } |
595 | |
596 | QT_CHARTS_END_NAMESPACE |
597 | |
598 | #include "moc_chartaxiselement_p.cpp" |
599 | |