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