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 | const auto items = m_labels->childItems(); |
242 | for (QGraphicsItem *item : items) |
243 | item->setRotation(angle); |
244 | |
245 | QGraphicsLayoutItem::updateGeometry(); |
246 | presenter()->layout()->invalidate(); |
247 | } |
248 | |
249 | void ChartAxisElement::handleLabelsBrushChanged(const QBrush &brush) |
250 | { |
251 | const auto items = m_labels->childItems(); |
252 | for (QGraphicsItem *item : items) |
253 | static_cast<QGraphicsTextItem *>(item)->setDefaultTextColor(brush.color()); |
254 | } |
255 | |
256 | void ChartAxisElement::handleLabelsFontChanged(const QFont &font) |
257 | { |
258 | const auto items = m_labels->childItems(); |
259 | for (QGraphicsItem *item : items) |
260 | static_cast<QGraphicsTextItem *>(item)->setFont(font); |
261 | QGraphicsLayoutItem::updateGeometry(); |
262 | presenter()->layout()->invalidate(); |
263 | } |
264 | |
265 | void ChartAxisElement::handleTitleTextChanged(const QString &title) |
266 | { |
267 | QGraphicsLayoutItem::updateGeometry(); |
268 | presenter()->layout()->invalidate(); |
269 | if (title.isEmpty() || !m_title->isVisible()) |
270 | m_title->setHtml(title); |
271 | } |
272 | |
273 | void ChartAxisElement::handleTitleBrushChanged(const QBrush &brush) |
274 | { |
275 | m_title->setDefaultTextColor(brush.color()); |
276 | } |
277 | |
278 | void ChartAxisElement::handleTitleFontChanged(const QFont &font) |
279 | { |
280 | if (m_title->font() != font) { |
281 | m_title->setFont(font); |
282 | QGraphicsLayoutItem::updateGeometry(); |
283 | presenter()->layout()->invalidate(); |
284 | } |
285 | } |
286 | |
287 | void ChartAxisElement::handleVisibleChanged(bool visible) |
288 | { |
289 | setVisible(visible); |
290 | if (!visible) { |
291 | m_grid->setVisible(visible); |
292 | m_arrow->setVisible(visible); |
293 | m_minorGrid->setVisible(visible); |
294 | m_minorArrow->setVisible(visible); |
295 | m_shades->setVisible(visible); |
296 | m_labels->setVisible(visible); |
297 | m_title->setVisible(visible); |
298 | if (m_colorScale) |
299 | m_colorScale->setVisible(visible); |
300 | } else { |
301 | m_grid->setVisible(axis()->isGridLineVisible()); |
302 | m_arrow->setVisible(axis()->isLineVisible()); |
303 | m_minorGrid->setVisible(axis()->isMinorGridLineVisible()); |
304 | m_minorArrow->setVisible(axis()->isLineVisible()); |
305 | m_shades->setVisible(axis()->shadesVisible()); |
306 | m_labels->setVisible(axis()->labelsVisible()); |
307 | m_title->setVisible(axis()->isTitleVisible()); |
308 | } |
309 | if (presenter()) { |
310 | if (visible) { |
311 | QSizeF before = effectiveSizeHint(which: Qt::PreferredSize); |
312 | QSizeF after = sizeHint(which: Qt::PreferredSize); |
313 | if (before != after) |
314 | QGraphicsLayoutItem::updateGeometry(); |
315 | } |
316 | presenter()->layout()->invalidate(); |
317 | } |
318 | } |
319 | |
320 | void ChartAxisElement::handleRangeChanged(qreal min, qreal max) |
321 | { |
322 | Q_UNUSED(min); |
323 | Q_UNUSED(max); |
324 | |
325 | if (!emptyAxis()) { |
326 | const QList<qreal> layout = calculateLayout(); |
327 | updateLayout(layout); |
328 | QSizeF before = effectiveSizeHint(which: Qt::PreferredSize); |
329 | QSizeF after = sizeHint(which: Qt::PreferredSize); |
330 | |
331 | if (before != after) { |
332 | QGraphicsLayoutItem::updateGeometry(); |
333 | // We don't want to call invalidate on layout, since it will change minimum size of |
334 | // component, which we would like to avoid since it causes nasty flips when scrolling |
335 | // or zooming, instead recalculate layout and use plotArea for extra space. |
336 | presenter()->layout()->setGeometry(presenter()->layout()->geometry()); |
337 | } |
338 | } |
339 | } |
340 | |
341 | void ChartAxisElement::handleReverseChanged(bool reverse) |
342 | { |
343 | Q_UNUSED(reverse); |
344 | |
345 | QGraphicsLayoutItem::updateGeometry(); |
346 | presenter()->layout()->invalidate(); |
347 | } |
348 | |
349 | bool ChartAxisElement::emptyAxis() const |
350 | { |
351 | return axisGeometry().isEmpty() |
352 | || gridGeometry().isEmpty() |
353 | || qFuzzyIsNull(d: max() - min()); |
354 | } |
355 | |
356 | qreal ChartAxisElement::min() const |
357 | { |
358 | return m_axis->d_ptr->min(); |
359 | } |
360 | |
361 | qreal ChartAxisElement::max() const |
362 | { |
363 | return m_axis->d_ptr->max(); |
364 | } |
365 | |
366 | qreal ChartAxisElement::tickInterval() const |
367 | { |
368 | QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: m_axis); |
369 | if (valueAxis) |
370 | return valueAxis->tickInterval(); |
371 | else |
372 | return 0.0; |
373 | } |
374 | |
375 | qreal ChartAxisElement::tickAnchor() const |
376 | { |
377 | QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: m_axis); |
378 | if (valueAxis) |
379 | return valueAxis->tickAnchor(); |
380 | else |
381 | return 0.0; |
382 | } |
383 | |
384 | QString ChartAxisElement::formatLabel(const QString &formatSpec, const QByteArray &array, |
385 | qreal value, int precision, const QString &preStr, |
386 | const QString &postStr) const |
387 | { |
388 | QString retVal; |
389 | if (!formatSpec.isEmpty()) { |
390 | if (formatSpec.at(i: 0) == QLatin1Char('d') |
391 | || formatSpec.at(i: 0) == QLatin1Char('i') |
392 | || formatSpec.at(i: 0) == QLatin1Char('c')) { |
393 | if (presenter()->localizeNumbers()) |
394 | retVal = preStr + presenter()->locale().toString(i: qint64(value)) + postStr; |
395 | else |
396 | retVal = QString::asprintf(format: array.constData(), qint64(value)); |
397 | } else if (formatSpec.at(i: 0) == QLatin1Char('u') |
398 | || formatSpec.at(i: 0) == QLatin1Char('o') |
399 | || formatSpec.at(i: 0) == QLatin1Char('x') |
400 | || formatSpec.at(i: 0) == QLatin1Char('X')) { |
401 | // These formats are not supported by localized numbers |
402 | retVal = QString::asprintf(format: array.constData(), quint64(value)); |
403 | } else if (formatSpec.at(i: 0) == QLatin1Char('f') |
404 | || formatSpec.at(i: 0) == QLatin1Char('F') |
405 | || formatSpec.at(i: 0) == QLatin1Char('e') |
406 | || formatSpec.at(i: 0) == QLatin1Char('E') |
407 | || formatSpec.at(i: 0) == QLatin1Char('g') |
408 | || formatSpec.at(i: 0) == QLatin1Char('G')) { |
409 | if (presenter()->localizeNumbers()) { |
410 | retVal = preStr |
411 | + presenter()->locale().toString(f: value, format: formatSpec.at(i: 0).toLatin1(), |
412 | precision) |
413 | + postStr; |
414 | } else { |
415 | retVal = QString::asprintf(format: array.constData(), value); |
416 | } |
417 | } |
418 | } |
419 | return retVal; |
420 | } |
421 | |
422 | void ChartAxisElement::prepareColorScale(const qreal width, const qreal height) |
423 | { |
424 | if (axis()->type() == QAbstractAxis::AxisTypeColor) { |
425 | QColorAxis *colorAxis = static_cast<QColorAxis *>(axis()); |
426 | |
427 | if (colorAxis->gradient() != QLinearGradient() && width != 0 && height != 0) { |
428 | m_colorScale->setVisible(true); |
429 | |
430 | QImage image(width, height, QImage::Format_ARGB32); |
431 | QPainter painter(&image); |
432 | |
433 | QLinearGradient gradient; |
434 | if (colorAxis->orientation() == Qt::Horizontal) { |
435 | gradient = QLinearGradient(QPointF(0, 0), QPointF(width, 0)); |
436 | const auto &stops = colorAxis->gradient().stops(); |
437 | for (const auto &stop : stops) |
438 | gradient.setColorAt(pos: stop.first, color: stop.second); |
439 | } else { |
440 | gradient = QLinearGradient(QPointF(0, 0), QPointF(0, height)); |
441 | for (int i = colorAxis->gradient().stops().size() - 1; i >= 0; --i) { |
442 | auto stop = colorAxis->gradient().stops()[i]; |
443 | gradient.setColorAt(pos: 1 - stop.first, color: stop.second); |
444 | } |
445 | } |
446 | |
447 | painter.fillRect(image.rect(), gradient); |
448 | |
449 | painter.setPen(axis()->linePen()); |
450 | painter.drawRect(r: image.rect()); |
451 | |
452 | const QPixmap &pixmap = QPixmap::fromImage(image); |
453 | m_colorScale->setPixmap(pixmap); |
454 | } |
455 | } |
456 | } |
457 | |
458 | static int precisionDigits(qreal min, qreal max, int ticks) |
459 | { |
460 | // How many digits of each tick value should we display after the decimal point ? |
461 | // For example tick marks 1.002 and 1.003 have a difference of 0.001 and need 3 decimals. |
462 | if (ticks > 1) { |
463 | // Number of digits after decimal that *don't* change between ticks: |
464 | const int gap = -qFloor(v: std::log10(x: (max - min) / (ticks - 1))); |
465 | if (gap > 0) |
466 | return gap + 1; |
467 | } |
468 | // We want at least one digit after the decimal point even when digits |
469 | // before are changing, or when we only have a single tick-mark: |
470 | return 1; |
471 | } |
472 | |
473 | QStringList ChartAxisElement::createValueLabels(qreal min, qreal max, int ticks, |
474 | qreal tickInterval, qreal tickAnchor, |
475 | QValueAxis::TickType tickType, |
476 | const QString &format) const |
477 | { |
478 | QStringList labels; |
479 | |
480 | if (max <= min || ticks < 1) |
481 | return labels; |
482 | |
483 | if (format.isEmpty()) { |
484 | const int n = precisionDigits(min, max, ticks); |
485 | if (tickType == QValueAxis::TicksFixed) { |
486 | for (int i = 0; i < ticks; i++) { |
487 | qreal value = min + (i * (max - min) / (ticks - 1)); |
488 | labels << presenter()->numberToString(value, f: 'f', prec: n); |
489 | } |
490 | } else { |
491 | const qreal ticksFromAnchor = (tickAnchor - min) / tickInterval; |
492 | const qreal firstMajorTick = tickAnchor - std::floor(x: ticksFromAnchor) * tickInterval; |
493 | |
494 | qreal value = firstMajorTick; |
495 | while (value <= max) { |
496 | labels << presenter()->numberToString(value, f: 'f', prec: n); |
497 | value += tickInterval; |
498 | } |
499 | } |
500 | } else { |
501 | QByteArray array = format.toLatin1(); |
502 | QString formatSpec; |
503 | QString preStr; |
504 | QString postStr; |
505 | int precision = 6; // Six is the default precision in Qt API |
506 | if (presenter()->localizeNumbers()) { |
507 | QRegularExpressionMatch rmatch; |
508 | if (format.indexOf(re: labelFormatMatcherLocalized(), from: 0, rmatch: &rmatch) != -1) { |
509 | preStr = rmatch.captured(nth: 1); |
510 | if (!rmatch.captured(nth: 2).isEmpty()) |
511 | precision = rmatch.captured(nth: 2).toInt(); |
512 | formatSpec = rmatch.captured(nth: 3); |
513 | postStr = rmatch.captured(nth: 4); |
514 | } |
515 | } else { |
516 | QRegularExpressionMatch rmatch; |
517 | if (format.indexOf(re: labelFormatMatcher(), from: 0, rmatch: &rmatch) != -1) |
518 | formatSpec = rmatch.captured(nth: 1); |
519 | } |
520 | if (tickType == QValueAxis::TicksFixed) { |
521 | for (int i = 0; i < ticks; i++) { |
522 | qreal value = min + (i * (max - min) / (ticks - 1)); |
523 | labels << formatLabel(formatSpec, array, value, precision, preStr, postStr); |
524 | } |
525 | } else { |
526 | const qreal ticksFromAnchor = (tickAnchor - min) / tickInterval; |
527 | const qreal firstMajorTick = tickAnchor - std::floor(x: ticksFromAnchor) * tickInterval; |
528 | |
529 | qreal value = firstMajorTick; |
530 | while (value <= max) { |
531 | labels << formatLabel(formatSpec, array, value, precision, preStr, postStr); |
532 | value += tickInterval; |
533 | } |
534 | } |
535 | } |
536 | |
537 | return labels; |
538 | } |
539 | |
540 | QStringList ChartAxisElement::createLogValueLabels(qreal min, qreal max, qreal base, int ticks, |
541 | const QString &format) const |
542 | { |
543 | QStringList labels; |
544 | |
545 | if (max <= min || ticks < 1) |
546 | return labels; |
547 | |
548 | const int firstTick = qCeil(v: qLn(v: base > 1 ? min : max) / qLn(v: base)); |
549 | if (format.isEmpty()) { |
550 | const int n = precisionDigits(min, max, ticks); |
551 | for (int i = firstTick; i < ticks + firstTick; i++) { |
552 | qreal value = qPow(x: base, y: i); |
553 | labels << presenter()->numberToString(value, f: 'f', prec: n); |
554 | } |
555 | } else { |
556 | QByteArray array = format.toLatin1(); |
557 | QString formatSpec; |
558 | QString preStr; |
559 | QString postStr; |
560 | int precision = 6; // Six is the default precision in Qt API |
561 | if (presenter()->localizeNumbers()) { |
562 | QRegularExpressionMatch rmatch; |
563 | if (format.indexOf(re: labelFormatMatcherLocalized(), from: 0, rmatch: &rmatch) != -1) { |
564 | preStr = rmatch.captured(nth: 1); |
565 | if (!rmatch.captured(nth: 2).isEmpty()) |
566 | precision = rmatch.captured(nth: 2).toInt(); |
567 | formatSpec = rmatch.captured(nth: 3); |
568 | postStr = rmatch.captured(nth: 4); |
569 | } |
570 | } else { |
571 | QRegularExpressionMatch rmatch; |
572 | if (format.indexOf(re: labelFormatMatcher(), from: 0, rmatch: &rmatch) != -1) |
573 | formatSpec = rmatch.captured(nth: 1); |
574 | } |
575 | for (int i = firstTick; i < ticks + firstTick; i++) { |
576 | qreal value = qPow(x: base, y: i); |
577 | labels << formatLabel(formatSpec, array, value, precision, preStr, postStr); |
578 | } |
579 | } |
580 | |
581 | return labels; |
582 | } |
583 | |
584 | QStringList ChartAxisElement::createDateTimeLabels(qreal min, qreal max,int ticks, |
585 | const QString &format) const |
586 | { |
587 | QStringList labels; |
588 | |
589 | if (max <= min || ticks < 1) |
590 | return labels; |
591 | |
592 | for (int i = 0; i < ticks; i++) { |
593 | qreal value = min + (i * (max - min) / (ticks - 1)); |
594 | labels << presenter()->locale().toString(dateTime: QDateTime::fromMSecsSinceEpoch(msecs: value), format); |
595 | } |
596 | return labels; |
597 | } |
598 | |
599 | QStringList ChartAxisElement::createColorLabels(qreal min, qreal max, int ticks) const |
600 | { |
601 | QStringList labels; |
602 | |
603 | if (max <= min || ticks < 1) |
604 | return labels; |
605 | |
606 | const int n = precisionDigits(min, max, ticks); |
607 | for (int i = 0; i < ticks; ++i) { |
608 | qreal value = min + (i * (max - min) / (ticks - 1)); |
609 | labels << presenter()->numberToString(value, f: 'f', prec: n); |
610 | } |
611 | |
612 | return labels; |
613 | } |
614 | |
615 | bool ChartAxisElement::labelsEditable() const |
616 | { |
617 | return m_labelsEditable; |
618 | } |
619 | |
620 | void ChartAxisElement::setLabelsEditable(bool labelsEditable) |
621 | { |
622 | if (axis()->type() == QAbstractAxis::AxisTypeValue |
623 | || axis()->type() == QAbstractAxis::AxisTypeDateTime) { |
624 | labelGroup()->setHandlesChildEvents(!labelsEditable); |
625 | const QList<QGraphicsItem *> childItems = labelGroup()->childItems(); |
626 | for (auto item : childItems) { |
627 | switch (axis()->type()) { |
628 | case QAbstractAxis::AxisTypeValue: |
629 | static_cast<ValueAxisLabel *>(item)->setEditable(labelsEditable); |
630 | break; |
631 | case QAbstractAxis::AxisTypeDateTime: |
632 | static_cast<DateTimeAxisLabel *>(item)->setEditable(labelsEditable); |
633 | break; |
634 | default: |
635 | break; |
636 | } |
637 | } |
638 | m_labelsEditable = labelsEditable; |
639 | } |
640 | } |
641 | |
642 | bool ChartAxisElement::labelsVisible() const |
643 | { |
644 | return m_labels->isVisible(); |
645 | } |
646 | |
647 | void ChartAxisElement::axisSelected() |
648 | { |
649 | emit clicked(); |
650 | } |
651 | |
652 | QT_END_NAMESPACE |
653 | |
654 | #include "moc_chartaxiselement_p.cpp" |
655 |
Definitions
- labelFormatMatcher
- labelFormatMatcherLocalized
- ChartAxisElement
- ~ChartAxisElement
- connectSlots
- handleArrowVisibleChanged
- handleMinorArrowVisibleChanged
- handleGridVisibleChanged
- handleMinorGridVisibleChanged
- handleLabelsPositionChanged
- handleTruncateLabelsChanged
- handleColorScaleSizeChanged
- handleColorScaleGradientChanged
- valueLabelEdited
- dateTimeLabelEdited
- handleLabelsVisibleChanged
- handleShadesVisibleChanged
- handleTitleVisibleChanged
- handleLabelsAngleChanged
- handleLabelsBrushChanged
- handleLabelsFontChanged
- handleTitleTextChanged
- handleTitleBrushChanged
- handleTitleFontChanged
- handleVisibleChanged
- handleRangeChanged
- handleReverseChanged
- emptyAxis
- min
- max
- tickInterval
- tickAnchor
- formatLabel
- prepareColorScale
- precisionDigits
- createValueLabels
- createLogValueLabels
- createDateTimeLabels
- createColorLabels
- labelsEditable
- setLabelsEditable
- labelsVisible
Learn to use CMake with our Intro Training
Find out more