| 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 |  |