| 1 | // Copyright (C) 2023 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include <QtGraphs/QBarCategoryAxis> | 
| 5 | #include <QtGraphs/QGraphsTheme> | 
| 6 | #include <private/axisrenderer_p.h> | 
| 7 | #include <private/qabstractaxis_p.h> | 
| 8 | #include <private/qbarseries_p.h> | 
| 9 | #include <private/qdatetimeaxis_p.h> | 
| 10 | #include <private/qgraphsview_p.h> | 
| 11 | #include <private/qvalueaxis_p.h> | 
| 12 |  | 
| 13 | QT_BEGIN_NAMESPACE | 
| 14 |  | 
| 15 | AxisRenderer::AxisRenderer(QQuickItem *parent) | 
| 16 |     : QQuickItem(parent) | 
| 17 | { | 
| 18 |     m_graph = qobject_cast<QGraphsView *>(object: parent); | 
| 19 |     setFlag(flag: QQuickItem::ItemHasContents); | 
| 20 | } | 
| 21 |  | 
| 22 | AxisRenderer::~AxisRenderer() {} | 
| 23 |  | 
| 24 | QGraphsTheme *AxisRenderer::theme() { | 
| 25 |     return m_graph->m_theme; | 
| 26 | } | 
| 27 |  | 
| 28 | void AxisRenderer::initialize() { | 
| 29 |     if (m_initialized) | 
| 30 |         return; | 
| 31 |     if (!window()) | 
| 32 |         return; | 
| 33 |  | 
| 34 |     if (m_axisGrid) | 
| 35 |         m_axisGrid->componentComplete(); | 
| 36 |     if (m_axisLineVertical) | 
| 37 |         m_axisLineVertical->componentComplete(); | 
| 38 |     if (m_axisTickerVertical) | 
| 39 |         m_axisTickerVertical->componentComplete(); | 
| 40 |     if (m_axisLineHorizontal) | 
| 41 |         m_axisLineHorizontal->componentComplete(); | 
| 42 |     if (m_axisTickerHorizontal) | 
| 43 |         m_axisTickerHorizontal->componentComplete(); | 
| 44 |     if (m_axisGridShadow) | 
| 45 |         m_axisGridShadow->componentComplete(); | 
| 46 |     if (m_axisLineVerticalShadow) | 
| 47 |         m_axisLineVerticalShadow->componentComplete(); | 
| 48 |     if (m_axisTickerVerticalShadow) | 
| 49 |         m_axisTickerVerticalShadow->componentComplete(); | 
| 50 |     if (m_axisLineHorizontalShadow) | 
| 51 |         m_axisLineHorizontalShadow->componentComplete(); | 
| 52 |     if (m_axisTickerHorizontalShadow) | 
| 53 |         m_axisTickerHorizontalShadow->componentComplete(); | 
| 54 |     m_initialized = true; | 
| 55 | } | 
| 56 |  | 
| 57 | void AxisRenderer::handlePolish() | 
| 58 | { | 
| 59 |     if (!m_axisGrid) { | 
| 60 |         m_axisGrid = new AxisGrid(this); | 
| 61 |         m_axisGrid->setZ(-1); | 
| 62 |         m_axisGrid->setupShaders(); | 
| 63 |         m_axisGrid->setOrigo(0); | 
| 64 |     } | 
| 65 |     if (!m_axisLineVertical) { | 
| 66 |         m_axisLineVertical = new AxisLine(this); | 
| 67 |         m_axisLineVertical->setZ(-1); | 
| 68 |         m_axisLineVertical->setupShaders(); | 
| 69 |     } | 
| 70 |     if (!m_axisTickerVertical) { | 
| 71 |         m_axisTickerVertical = new AxisTicker(this); | 
| 72 |         m_axisTickerVertical->setZ(-2); | 
| 73 |         m_axisTickerVertical->setOrigo(0); | 
| 74 |         // TODO: Configurable in theme or axis? | 
| 75 |         m_axisTickerVertical->setSubTickLength(0.5); | 
| 76 |         m_axisTickerVertical->setupShaders(); | 
| 77 |     } | 
| 78 |     if (!m_axisLineHorizontal) { | 
| 79 |         m_axisLineHorizontal = new AxisLine(this); | 
| 80 |         m_axisLineHorizontal->setZ(-1); | 
| 81 |         m_axisLineHorizontal->setIsHorizontal(true); | 
| 82 |         m_axisLineHorizontal->setupShaders(); | 
| 83 |     } | 
| 84 |     if (!m_axisTickerHorizontal) { | 
| 85 |         m_axisTickerHorizontal = new AxisTicker(this); | 
| 86 |         m_axisTickerHorizontal->setZ(-2); | 
| 87 |         m_axisTickerHorizontal->setIsHorizontal(true); | 
| 88 |         m_axisTickerHorizontal->setOrigo(0); | 
| 89 |         // TODO: Configurable in theme or axis? | 
| 90 |         m_axisTickerHorizontal->setSubTickLength(0.2); | 
| 91 |         m_axisTickerHorizontal->setupShaders(); | 
| 92 |     } | 
| 93 |  | 
| 94 |     // TODO: Create shadows only when needed | 
| 95 |     if (!m_axisGridShadow) { | 
| 96 |         m_axisGridShadow = new AxisGrid(this); | 
| 97 |         m_axisGridShadow->setZ(-3); | 
| 98 |         m_axisGridShadow->setupShaders(); | 
| 99 |         m_axisGridShadow->setOrigo(0); | 
| 100 |     } | 
| 101 |     if (!m_axisLineVerticalShadow) { | 
| 102 |         m_axisLineVerticalShadow = new AxisLine(this); | 
| 103 |         m_axisLineVerticalShadow->setZ(-3); | 
| 104 |         m_axisLineVerticalShadow->setupShaders(); | 
| 105 |     } | 
| 106 |     if (!m_axisTickerVerticalShadow) { | 
| 107 |         m_axisTickerVerticalShadow = new AxisTicker(this); | 
| 108 |         m_axisTickerVerticalShadow->setZ(-3); | 
| 109 |         m_axisTickerVerticalShadow->setOrigo(0); | 
| 110 |         // TODO: Configurable in theme or axis? | 
| 111 |         m_axisTickerVerticalShadow->setSubTickLength(m_axisTickerVertical->subTickLength()); | 
| 112 |         m_axisTickerVerticalShadow->setupShaders(); | 
| 113 |     } | 
| 114 |     if (!m_axisLineHorizontalShadow) { | 
| 115 |         m_axisLineHorizontalShadow = new AxisLine(this); | 
| 116 |         m_axisLineHorizontalShadow->setZ(-3); | 
| 117 |         m_axisLineHorizontalShadow->setupShaders(); | 
| 118 |     } | 
| 119 |     if (!m_axisTickerHorizontalShadow) { | 
| 120 |         m_axisTickerHorizontalShadow = new AxisTicker(this); | 
| 121 |         m_axisTickerHorizontalShadow->setZ(-3); | 
| 122 |         m_axisTickerHorizontalShadow->setIsHorizontal(true); | 
| 123 |         m_axisTickerHorizontalShadow->setOrigo(0); | 
| 124 |         // TODO: Configurable in theme or axis? | 
| 125 |         m_axisTickerHorizontalShadow->setSubTickLength(m_axisTickerHorizontal->subTickLength()); | 
| 126 |         m_axisTickerHorizontalShadow->setupShaders(); | 
| 127 |     } | 
| 128 |  | 
| 129 |     updateAxis(); | 
| 130 | } | 
| 131 |  | 
| 132 | void AxisRenderer::updateAxis() | 
| 133 | { | 
| 134 |     if (!theme()) | 
| 135 |         return; | 
| 136 |  | 
| 137 |     // Update active axis | 
| 138 |     QAbstractAxis *axisVertical = m_graph->m_axisY; | 
| 139 |     QAbstractAxis *axisHorizontal = m_graph->m_axisX; | 
| 140 |  | 
| 141 |     // See if series is horizontal, so axis should also switch places. | 
| 142 |     bool vertical = true; | 
| 143 |     if (m_graph->orientation() == Qt::Orientation::Horizontal) | 
| 144 |         vertical = false; | 
| 145 |     if (vertical) { | 
| 146 |         m_axisVertical = axisVertical; | 
| 147 |         m_axisHorizontal = axisHorizontal; | 
| 148 |     } else { | 
| 149 |         m_axisVertical = axisHorizontal; | 
| 150 |         m_axisHorizontal = axisVertical; | 
| 151 |     } | 
| 152 |  | 
| 153 |     if (vertical != m_wasVertical) { | 
| 154 |         // Orientation has changed, so clear possible custom elements | 
| 155 |         for (auto &item : m_xAxisTextItems) | 
| 156 |             item->deleteLater(); | 
| 157 |         m_xAxisTextItems.clear(); | 
| 158 |  | 
| 159 |         for (auto &item : m_yAxisTextItems) | 
| 160 |             item->deleteLater(); | 
| 161 |         m_yAxisTextItems.clear(); | 
| 162 |  | 
| 163 |         m_wasVertical = vertical; | 
| 164 |     } | 
| 165 |  | 
| 166 |     float axisWidth = m_graph->m_axisWidth; | 
| 167 |     float axisHeight = m_graph->m_axisHeight; | 
| 168 |  | 
| 169 |     const bool gridVisible = theme()->isGridVisible(); | 
| 170 |     if (m_axisVertical) { | 
| 171 |         m_gridVerticalLinesVisible = gridVisible && m_axisVertical->isGridVisible(); | 
| 172 |         m_gridVerticalSubLinesVisible = gridVisible && m_axisVertical->isSubGridVisible(); | 
| 173 |     } | 
| 174 |     if (m_axisHorizontal) { | 
| 175 |         m_gridHorizontalLinesVisible = gridVisible && m_axisHorizontal->isGridVisible(); | 
| 176 |         m_gridHorizontalSubLinesVisible = gridVisible && m_axisHorizontal->isSubGridVisible(); | 
| 177 |     } | 
| 178 |  | 
| 179 |     if (auto vaxis = qobject_cast<QValueAxis *>(object: m_axisVertical)) { | 
| 180 |         m_axisVerticalMaxValue = vaxis->max(); | 
| 181 |         m_axisVerticalMinValue = vaxis->min(); | 
| 182 |         double step = vaxis->tickInterval(); | 
| 183 |  | 
| 184 |         m_axisVerticalValueRange = m_axisVerticalMaxValue - m_axisVerticalMinValue; | 
| 185 |         // If step is not manually defined (or it is invalid), calculate autostep | 
| 186 |         if (step <= 0) | 
| 187 |             step = getValueStepsFromRange(range: m_axisVerticalValueRange); | 
| 188 |  | 
| 189 |         // Get smallest tick label value | 
| 190 |         double minLabel = vaxis->tickAnchor(); | 
| 191 |         while (minLabel < m_axisVerticalMinValue) | 
| 192 |             minLabel += step; | 
| 193 |         while (minLabel >= (m_axisVerticalMinValue + step)) | 
| 194 |             minLabel -= step; | 
| 195 |         m_axisVerticalMinLabel = minLabel; | 
| 196 |  | 
| 197 |         m_axisVerticalValueStep = step; | 
| 198 |         int axisVerticalSubTickCount = vaxis->subTickCount(); | 
| 199 |         m_axisVerticalSubGridScale = axisVerticalSubTickCount > 0 ? 1.0 / (axisVerticalSubTickCount + 1) : 1.0; | 
| 200 |         m_axisVerticalStepPx = (height() - m_graph->m_marginTop - m_graph->m_marginBottom - axisHeight) / (m_axisVerticalValueRange / m_axisVerticalValueStep); | 
| 201 |         double axisVerticalValueDiff = m_axisVerticalMinLabel - m_axisVerticalMinValue; | 
| 202 |         m_axisYDisplacement = -(axisVerticalValueDiff / m_axisVerticalValueStep) * m_axisVerticalStepPx; | 
| 203 |  | 
| 204 |         // Update value labels | 
| 205 |         updateValueYAxisLabels(axis: vaxis, rect: m_graph->m_yAxisLabelsArea); | 
| 206 |     } | 
| 207 |  | 
| 208 |     if (auto haxis = qobject_cast<QValueAxis *>(object: m_axisHorizontal)) { | 
| 209 |         m_axisHorizontalMaxValue = haxis->max(); | 
| 210 |         m_axisHorizontalMinValue = haxis->min(); | 
| 211 |         double step = haxis->tickInterval(); | 
| 212 |  | 
| 213 |         m_axisHorizontalValueRange = m_axisHorizontalMaxValue - m_axisHorizontalMinValue; | 
| 214 |         // If step is not manually defined (or it is invalid), calculate autostep | 
| 215 |         if (step <= 0) | 
| 216 |             step = getValueStepsFromRange(range: m_axisHorizontalValueRange); | 
| 217 |  | 
| 218 |         // Get smallest tick label value | 
| 219 |         double minLabel = haxis->tickAnchor(); | 
| 220 |         while (minLabel < m_axisHorizontalMinValue) | 
| 221 |             minLabel += step; | 
| 222 |         while (minLabel >= (m_axisHorizontalMinValue + step)) | 
| 223 |             minLabel -= step; | 
| 224 |         m_axisHorizontalMinLabel = minLabel; | 
| 225 |  | 
| 226 |         m_axisHorizontalValueStep = step; | 
| 227 |         int axisHorizontalSubTickCount = haxis->subTickCount(); | 
| 228 |         m_axisHorizontalSubGridScale = axisHorizontalSubTickCount > 0 ? | 
| 229 |                 1.0 / (axisHorizontalSubTickCount + 1) : 1.0; | 
| 230 |         m_axisHorizontalStepPx = (width() - m_graph->m_marginLeft - m_graph->m_marginRight - axisWidth) | 
| 231 |                 / (m_axisHorizontalValueRange / m_axisHorizontalValueStep); | 
| 232 |         double axisHorizontalValueDiff = m_axisHorizontalMinLabel - m_axisHorizontalMinValue; | 
| 233 |         m_axisXDisplacement = -(axisHorizontalValueDiff / m_axisHorizontalValueStep) * m_axisHorizontalStepPx; | 
| 234 |  | 
| 235 |         // Update value labels | 
| 236 |         updateValueXAxisLabels(axis: haxis, rect: m_graph->m_xAxisLabelsArea); | 
| 237 |     } | 
| 238 |  | 
| 239 |     if (auto haxis = qobject_cast<QBarCategoryAxis *>(object: m_axisHorizontal)) { | 
| 240 |         m_axisHorizontalMaxValue = haxis->categories().size(); | 
| 241 |         m_axisHorizontalMinValue = 0; | 
| 242 |         m_axisHorizontalValueRange = m_axisHorizontalMaxValue - m_axisHorizontalMinValue; | 
| 243 |         updateBarXAxisLabels(axis: haxis, rect: m_graph->m_xAxisLabelsArea); | 
| 244 |     } | 
| 245 |     if (auto vaxis = qobject_cast<QBarCategoryAxis *>(object: m_axisVertical)) { | 
| 246 |         m_axisVerticalMaxValue = vaxis->categories().size(); | 
| 247 |         m_axisVerticalMinValue = 0; | 
| 248 |         m_axisVerticalValueRange = m_axisVerticalMaxValue - m_axisVerticalMinValue; | 
| 249 |         updateBarYAxisLabels(axis: vaxis, rect: m_graph->m_yAxisLabelsArea); | 
| 250 |     } | 
| 251 |  | 
| 252 |     if (auto vaxis = qobject_cast<QDateTimeAxis *>(object: m_axisVertical)) { | 
| 253 |         // Todo: make constant for all axis, or clamp in class? (QTBUG-124736) | 
| 254 |         const double MAX_DIVS = 100.0; | 
| 255 |  | 
| 256 |         double interval = std::clamp<double>(val: vaxis->tickInterval(), lo: 0.0, hi: MAX_DIVS); | 
| 257 |         m_axisVerticalMaxValue = vaxis->max().toMSecsSinceEpoch(); | 
| 258 |         m_axisVerticalMinValue = vaxis->min().toMSecsSinceEpoch(); | 
| 259 |         m_axisVerticalValueRange = std::abs(x: m_axisVerticalMaxValue - m_axisVerticalMinValue); | 
| 260 |  | 
| 261 |         // in ms | 
| 262 |         double segment; | 
| 263 |         if (interval <= 0) { | 
| 264 |             segment = getValueStepsFromRange(range: m_axisVerticalValueRange); | 
| 265 |             interval = m_axisVerticalValueRange / segment; | 
| 266 |         } else { | 
| 267 |             segment = m_axisVerticalValueRange / interval; | 
| 268 |         } | 
| 269 |  | 
| 270 |         m_axisVerticalMinLabel = std::clamp(val: interval, lo: 1.0, hi: MAX_DIVS); | 
| 271 |  | 
| 272 |         m_axisVerticalValueStep = segment; | 
| 273 |         int axisVerticalSubTickCount = vaxis->subTickCount(); | 
| 274 |         m_axisVerticalSubGridScale = axisVerticalSubTickCount > 0 | 
| 275 |                                            ? 1.0 / (axisVerticalSubTickCount + 1) | 
| 276 |                                            : 1.0; | 
| 277 |         m_axisVerticalStepPx = (height() - m_graph->m_marginTop - m_graph->m_marginBottom | 
| 278 |                                 - axisHeight) | 
| 279 |                                / (qFuzzyCompare(p1: segment, p2: 0) | 
| 280 |                                       ? interval | 
| 281 |                                       : (m_axisVerticalValueRange / m_axisVerticalValueStep)); | 
| 282 |  | 
| 283 |         updateDateTimeYAxisLabels(axis: vaxis, rect: m_graph->m_yAxisLabelsArea); | 
| 284 |     } | 
| 285 |  | 
| 286 |     if (auto haxis = qobject_cast<QDateTimeAxis *>(object: m_axisHorizontal)) { | 
| 287 |         const double MAX_DIVS = 100.0; | 
| 288 |  | 
| 289 |         double interval = std::clamp<double>(val: haxis->tickInterval(), lo: 0.0, hi: MAX_DIVS); | 
| 290 |         m_axisHorizontalMaxValue = haxis->max().toMSecsSinceEpoch(); | 
| 291 |         m_axisHorizontalMinValue = haxis->min().toMSecsSinceEpoch(); | 
| 292 |         m_axisHorizontalValueRange = std::abs(x: m_axisHorizontalMaxValue - m_axisHorizontalMinValue); | 
| 293 |  | 
| 294 |         // in ms | 
| 295 |         double segment; | 
| 296 |         if (interval <= 0) { | 
| 297 |             segment = getValueStepsFromRange(range: m_axisHorizontalValueRange); | 
| 298 |             interval = m_axisHorizontalValueRange / segment; | 
| 299 |         } else { | 
| 300 |             segment = m_axisHorizontalValueRange / interval; | 
| 301 |         } | 
| 302 |  | 
| 303 |         m_axisHorizontalMinLabel = std::clamp(val: interval, lo: 1.0, hi: MAX_DIVS); | 
| 304 |  | 
| 305 |         m_axisHorizontalValueStep = segment; | 
| 306 |         int axisHorizontalSubTickCount = haxis->subTickCount(); | 
| 307 |         m_axisHorizontalSubGridScale = axisHorizontalSubTickCount > 0 | 
| 308 |                                              ? 1.0 / (axisHorizontalSubTickCount + 1) | 
| 309 |                                              : 1.0; | 
| 310 |         m_axisHorizontalStepPx = (width() - m_graph->m_marginLeft - m_graph->m_marginRight | 
| 311 |                                   - axisWidth) | 
| 312 |                                  / (qFuzzyCompare(p1: segment, p2: 0) | 
| 313 |                                         ? interval | 
| 314 |                                         : (m_axisHorizontalValueRange / m_axisHorizontalValueStep)); | 
| 315 |         updateDateTimeXAxisLabels(axis: haxis, rect: m_graph->m_xAxisLabelsArea); | 
| 316 |     } | 
| 317 |  | 
| 318 |     updateAxisTickers(); | 
| 319 |     updateAxisTickersShadow(); | 
| 320 |     updateAxisGrid(); | 
| 321 |     updateAxisGridShadow(); | 
| 322 |     updateAxisTitles(xAxisRect: m_graph->m_xAxisLabelsArea, yAxisRect: m_graph->m_yAxisLabelsArea); | 
| 323 | } | 
| 324 |  | 
| 325 | void AxisRenderer::updateAxisTickers() | 
| 326 | { | 
| 327 |     if (m_axisVertical) { | 
| 328 |         // Note: Fix before enabling, see QTBUG-121207 and QTBUG-121211 | 
| 329 |         //if (theme()->themeDirty()) { | 
| 330 |             m_axisTickerVertical->setSubTickColor(theme()->axisY().subColor()); | 
| 331 |             m_axisTickerVertical->setTickColor(theme()->axisY().mainColor()); | 
| 332 |             m_axisTickerVertical->setTickLineWidth(theme()->axisY().mainWidth()); | 
| 333 |             m_axisTickerVertical->setSubTickLineWidth(theme()->axisY().subWidth()); | 
| 334 |             m_axisTickerVertical->setSmoothing(m_graph->axisYSmoothing()); | 
| 335 |         //} | 
| 336 |         float topPadding = m_axisGrid->gridLineWidth() * 0.5; | 
| 337 |         float bottomPadding = topPadding; | 
| 338 |         // TODO Only when changed | 
| 339 |         m_axisTickerVertical->setDisplacement(m_axisYDisplacement); | 
| 340 |         QRectF rect = m_graph->m_yAxisTickersArea; | 
| 341 |         m_axisTickerVertical->setX(rect.x()); | 
| 342 |         m_axisTickerVertical->setY(rect.y()); | 
| 343 |         m_axisTickerVertical->setWidth(rect.width()); | 
| 344 |         m_axisTickerVertical->setHeight(rect.height()); | 
| 345 |         m_axisTickerVertical->setFlipped(m_verticalAxisOnRight); | 
| 346 |  | 
| 347 |         m_axisTickerVertical->setSpacing((m_axisTickerVertical->height() - topPadding - bottomPadding) | 
| 348 |                                          / (m_axisVerticalValueRange / m_axisVerticalValueStep)); | 
| 349 |         m_axisTickerVertical->setSubTicksVisible(!qFuzzyCompare(p1: m_axisVerticalSubGridScale, p2: 1.0)); | 
| 350 |         m_axisTickerVertical->setSubTickScale(m_axisVerticalSubGridScale); | 
| 351 |         m_axisTickerVertical->setVisible(m_axisVertical->isVisible()); | 
| 352 |         // Axis line | 
| 353 |         m_axisLineVertical->setColor(theme()->axisY().mainColor()); | 
| 354 |         m_axisLineVertical->setLineWidth(theme()->axisY().mainWidth()); | 
| 355 |         m_axisLineVertical->setSmoothing(m_graph->axisYSmoothing()); | 
| 356 |  | 
| 357 |         float xMovement = 0.5 * (m_axisLineVertical->lineWidth() + m_axisLineVertical->smoothing()); | 
| 358 |         if (m_verticalAxisOnRight) | 
| 359 |             m_axisLineVertical->setX(m_axisTickerVertical->x() - xMovement); | 
| 360 |         else | 
| 361 |             m_axisLineVertical->setX(m_axisTickerVertical->x() + m_axisTickerVertical->width() - xMovement); | 
| 362 |         m_axisLineVertical->setY(m_axisTickerVertical->y()); | 
| 363 |         m_axisLineVertical->setWidth(m_axisLineVertical->lineWidth() + m_axisLineVertical->smoothing()); | 
| 364 |         m_axisLineVertical->setHeight(m_axisTickerVertical->height()); | 
| 365 |         m_axisLineVertical->setVisible(m_axisVertical->isLineVisible()); | 
| 366 |     } else { | 
| 367 |         // Hide all parts of vertical axis | 
| 368 |         m_axisTickerVertical->setVisible(false); | 
| 369 |         m_axisLineVertical->setVisible(false); | 
| 370 |         for (auto &textItem : m_yAxisTextItems) | 
| 371 |             textItem->setVisible(false); | 
| 372 |     } | 
| 373 |  | 
| 374 |     if (m_axisHorizontal) { | 
| 375 |         //if (theme()->themeDirty()) { | 
| 376 |             m_axisTickerHorizontal->setSubTickColor(theme()->axisX().subColor()); | 
| 377 |             m_axisTickerHorizontal->setTickColor(theme()->axisX().mainColor()); | 
| 378 |             m_axisTickerHorizontal->setTickLineWidth(theme()->axisX().mainWidth()); | 
| 379 |             m_axisTickerHorizontal->setSubTickLineWidth(theme()->axisX().subWidth()); | 
| 380 |             m_axisTickerHorizontal->setSmoothing(m_graph->axisXSmoothing()); | 
| 381 |         //} | 
| 382 |         float leftPadding = m_axisGrid->gridLineWidth() * 0.5; | 
| 383 |         float rightPadding = leftPadding; | 
| 384 |         // TODO Only when changed | 
| 385 |         m_axisTickerHorizontal->setDisplacement(m_axisXDisplacement); | 
| 386 |         QRectF rect = m_graph->m_xAxisTickersArea; | 
| 387 |         m_axisTickerHorizontal->setX(rect.x()); | 
| 388 |         m_axisTickerHorizontal->setY(rect.y()); | 
| 389 |         m_axisTickerHorizontal->setWidth(rect.width()); | 
| 390 |         m_axisTickerHorizontal->setHeight(rect.height()); | 
| 391 |         m_axisTickerHorizontal->setFlipped(m_horizontalAxisOnTop); | 
| 392 |  | 
| 393 |         m_axisTickerHorizontal->setSpacing((m_axisTickerHorizontal->width() - leftPadding - rightPadding) | 
| 394 |                                          / (m_axisHorizontalValueRange / m_axisHorizontalValueStep)); | 
| 395 |         m_axisTickerHorizontal->setSubTicksVisible(!qFuzzyCompare(p1: m_axisHorizontalSubGridScale, p2: 1.0)); | 
| 396 |         m_axisTickerHorizontal->setSubTickScale(m_axisHorizontalSubGridScale); | 
| 397 |         m_axisTickerHorizontal->setVisible(m_axisHorizontal->isVisible()); | 
| 398 |         // Axis line | 
| 399 |         m_axisLineHorizontal->setColor(theme()->axisX().mainColor()); | 
| 400 |         m_axisLineHorizontal->setLineWidth(theme()->axisX().mainWidth()); | 
| 401 |         m_axisLineHorizontal->setSmoothing(m_graph->axisXSmoothing()); | 
| 402 |         m_axisLineHorizontal->setX(m_axisTickerHorizontal->x()); | 
| 403 |         float yMovement = 0.5 * (m_axisLineHorizontal->lineWidth() + m_axisLineHorizontal->smoothing()); | 
| 404 |         if (m_horizontalAxisOnTop) | 
| 405 |             m_axisLineHorizontal->setY(m_axisTickerHorizontal->y() + m_axisTickerHorizontal->height() - yMovement); | 
| 406 |         else | 
| 407 |             m_axisLineHorizontal->setY(m_axisTickerHorizontal->y() - yMovement); | 
| 408 |         m_axisLineHorizontal->setWidth(m_axisTickerHorizontal->width()); | 
| 409 |         m_axisLineHorizontal->setHeight(m_axisLineHorizontal->lineWidth() + m_axisLineHorizontal->smoothing()); | 
| 410 |         m_axisLineHorizontal->setVisible(m_axisHorizontal->isLineVisible()); | 
| 411 |     } else { | 
| 412 |         // Hide all parts of horizontal axis | 
| 413 |         m_axisTickerHorizontal->setVisible(false); | 
| 414 |         m_axisLineHorizontal->setVisible(false); | 
| 415 |         for (auto &textItem : m_xAxisTextItems) | 
| 416 |             textItem->setVisible(false); | 
| 417 |     } | 
| 418 | } | 
| 419 |  | 
| 420 | void AxisRenderer::updateAxisTickersShadow() | 
| 421 | { | 
| 422 |     if (m_axisVertical && m_graph->isShadowVisible()) { | 
| 423 |         m_axisTickerVerticalShadow->setSubTickColor(m_graph->shadowColor()); | 
| 424 |         m_axisTickerVerticalShadow->setTickColor(m_graph->shadowColor()); | 
| 425 |         m_axisTickerVerticalShadow->setSubTickLineWidth(m_axisTickerVertical->subTickLineWidth() + m_graph->shadowBarWidth()); | 
| 426 |         m_axisTickerVerticalShadow->setTickLineWidth(m_axisTickerVertical->tickLineWidth() + m_graph->shadowBarWidth()); | 
| 427 |         m_axisTickerVerticalShadow->setSmoothing(m_axisTickerVertical->smoothing() + m_graph->shadowSmoothing()); | 
| 428 |  | 
| 429 |         // TODO Only when changed | 
| 430 |         m_axisTickerVerticalShadow->setDisplacement(m_axisTickerVertical->displacement()); | 
| 431 |         m_axisTickerVerticalShadow->setX(m_axisTickerVertical->x() + m_graph->shadowXOffset()); | 
| 432 |         m_axisTickerVerticalShadow->setY(m_axisTickerVertical->y() + m_graph->shadowYOffset() + m_graph->shadowBarWidth() * 0.5); | 
| 433 |         m_axisTickerVerticalShadow->setWidth(m_axisTickerVertical->width()); | 
| 434 |         m_axisTickerVerticalShadow->setHeight(m_axisTickerVertical->height()); | 
| 435 |         m_axisTickerVerticalShadow->setFlipped(m_axisTickerVertical->isFlipped()); | 
| 436 |         m_axisTickerVerticalShadow->setSpacing(m_axisTickerVertical->spacing()); | 
| 437 |         m_axisTickerVerticalShadow->setSubTicksVisible(m_axisTickerVertical->subTicksVisible()); | 
| 438 |         m_axisTickerVerticalShadow->setSubTickScale(m_axisTickerVertical->subTickScale()); | 
| 439 |         m_axisTickerVerticalShadow->setVisible(m_axisTickerVertical->isVisible()); | 
| 440 |         // Axis line | 
| 441 |         m_axisLineVerticalShadow->setColor(m_graph->shadowColor()); | 
| 442 |         m_axisLineVerticalShadow->setLineWidth(m_axisLineVertical->lineWidth() + m_graph->shadowBarWidth()); | 
| 443 |         m_axisLineVerticalShadow->setSmoothing(m_axisLineVertical->smoothing() + m_graph->shadowSmoothing()); | 
| 444 |         m_axisLineVerticalShadow->setX(m_axisLineVertical->x() + m_graph->shadowXOffset()); | 
| 445 |         m_axisLineVerticalShadow->setY(m_axisLineVertical->y() + m_graph->shadowYOffset() + m_graph->shadowBarWidth() * 0.5); | 
| 446 |         m_axisLineVerticalShadow->setWidth(m_axisLineVertical->width()); | 
| 447 |         m_axisLineVerticalShadow->setHeight(m_axisLineVertical->height()); | 
| 448 |         m_axisLineVerticalShadow->setVisible(m_axisLineVertical->isVisible()); | 
| 449 |     } else { | 
| 450 |         // Hide all parts of vertical axis | 
| 451 |         m_axisTickerVerticalShadow->setVisible(false); | 
| 452 |         m_axisLineVerticalShadow->setVisible(false); | 
| 453 |     } | 
| 454 |  | 
| 455 |     if (m_axisHorizontal && m_graph->isShadowVisible()) { | 
| 456 |         m_axisTickerHorizontalShadow->setSubTickColor(m_graph->shadowColor()); | 
| 457 |         m_axisTickerHorizontalShadow->setTickColor(m_graph->shadowColor()); | 
| 458 |         m_axisTickerHorizontalShadow->setSubTickLineWidth(m_axisTickerHorizontal->subTickLineWidth() + m_graph->shadowBarWidth()); | 
| 459 |         m_axisTickerHorizontalShadow->setTickLineWidth(m_axisTickerHorizontal->tickLineWidth() + m_graph->shadowBarWidth()); | 
| 460 |         m_axisTickerHorizontalShadow->setSmoothing(m_axisTickerHorizontal->smoothing() + m_graph->shadowSmoothing()); | 
| 461 |  | 
| 462 |         // TODO Only when changed | 
| 463 |         m_axisTickerHorizontalShadow->setDisplacement(m_axisTickerHorizontal->displacement()); | 
| 464 |         m_axisTickerHorizontalShadow->setX(m_axisTickerHorizontal->x() + m_graph->shadowXOffset() - m_graph->shadowBarWidth() * 0.5); | 
| 465 |         m_axisTickerHorizontalShadow->setY(m_axisTickerHorizontal->y() + m_graph->shadowYOffset()); | 
| 466 |         m_axisTickerHorizontalShadow->setWidth(m_axisTickerHorizontal->width()); | 
| 467 |         m_axisTickerHorizontalShadow->setHeight(m_axisTickerHorizontal->height()); | 
| 468 |         m_axisTickerHorizontalShadow->setFlipped(m_axisTickerHorizontal->isFlipped()); | 
| 469 |         m_axisTickerHorizontalShadow->setSpacing(m_axisTickerHorizontal->spacing()); | 
| 470 |         m_axisTickerHorizontalShadow->setSubTicksVisible(m_axisTickerHorizontal->subTicksVisible()); | 
| 471 |         m_axisTickerHorizontalShadow->setSubTickScale(m_axisTickerHorizontal->subTickScale()); | 
| 472 |         m_axisTickerHorizontalShadow->setVisible(m_axisTickerHorizontal->isVisible()); | 
| 473 |         // Axis line | 
| 474 |         m_axisLineHorizontalShadow->setColor(m_graph->shadowColor()); | 
| 475 |         m_axisLineHorizontalShadow->setLineWidth(m_axisLineHorizontal->width() + m_graph->shadowBarWidth()); | 
| 476 |         m_axisLineHorizontalShadow->setSmoothing(m_axisLineHorizontal->smoothing() + m_graph->shadowSmoothing()); | 
| 477 |         m_axisLineHorizontalShadow->setX(m_axisLineHorizontal->x() + m_graph->shadowXOffset() - m_graph->shadowBarWidth() * 0.5); | 
| 478 |         m_axisLineHorizontalShadow->setY(m_axisLineHorizontal->y() + m_graph->shadowYOffset()); | 
| 479 |         m_axisLineHorizontalShadow->setWidth(m_axisLineHorizontal->width()); | 
| 480 |         m_axisLineHorizontalShadow->setHeight(m_axisLineHorizontal->height()); | 
| 481 |         m_axisLineHorizontalShadow->setVisible(m_axisLineHorizontal->isVisible()); | 
| 482 |     } else { | 
| 483 |         // Hide all parts of horizontal axis | 
| 484 |         m_axisTickerHorizontalShadow->setVisible(false); | 
| 485 |         m_axisLineHorizontalShadow->setVisible(false); | 
| 486 |     } | 
| 487 | } | 
| 488 |  | 
| 489 | void AxisRenderer::updateAxisGrid() | 
| 490 | { | 
| 491 |     m_axisGrid->setGridColor(theme()->grid().mainColor()); | 
| 492 |     m_axisGrid->setSubGridColor(theme()->grid().subColor()); | 
| 493 |     m_axisGrid->setSubGridLineWidth(theme()->grid().subWidth()); | 
| 494 |     m_axisGrid->setGridLineWidth(theme()->grid().mainWidth()); | 
| 495 |     const double minimumSmoothing = 0.05; | 
| 496 |     m_axisGrid->setSmoothing(m_graph->gridSmoothing() + minimumSmoothing); | 
| 497 |     if (theme()->isPlotAreaBackgroundVisible()) | 
| 498 |         m_axisGrid->setPlotAreaBackgroundColor(theme()->plotAreaBackgroundColor()); | 
| 499 |     else | 
| 500 |         m_axisGrid->setPlotAreaBackgroundColor(QColorConstants::Transparent); | 
| 501 |  | 
| 502 |     float topPadding = m_axisGrid->gridLineWidth() * 0.5; | 
| 503 |     float bottomPadding = topPadding; | 
| 504 |     float leftPadding = topPadding; | 
| 505 |     float rightPadding = topPadding; | 
| 506 |     // TODO Only when changed | 
| 507 |     m_axisGrid->setGridMovement(QPointF(m_axisXDisplacement, m_axisYDisplacement)); | 
| 508 |     QRectF rect = m_graph->m_plotArea; | 
| 509 |     m_axisGrid->setX(rect.x()); | 
| 510 |     m_axisGrid->setY(rect.y()); | 
| 511 |     m_axisGrid->setWidth(rect.width()); | 
| 512 |     m_axisGrid->setHeight(rect.height()); | 
| 513 |  | 
| 514 |     m_axisGrid->setGridWidth((m_axisGrid->width() - leftPadding - rightPadding) | 
| 515 |                              / (m_axisHorizontalValueRange / m_axisHorizontalValueStep)); | 
| 516 |     m_axisGrid->setGridHeight((m_axisGrid->height() - topPadding - bottomPadding) | 
| 517 |                               / (m_axisVerticalValueRange / m_axisVerticalValueStep)); | 
| 518 |     m_axisGrid->setGridVisibility(QVector4D(m_gridHorizontalLinesVisible, | 
| 519 |                                             m_gridVerticalLinesVisible, | 
| 520 |                                             m_gridHorizontalSubLinesVisible, | 
| 521 |                                             m_gridVerticalSubLinesVisible)); | 
| 522 |     m_axisGrid->setVerticalSubGridScale(m_axisVerticalSubGridScale); | 
| 523 |     m_axisGrid->setHorizontalSubGridScale(m_axisHorizontalSubGridScale); | 
| 524 | } | 
| 525 |  | 
| 526 | void AxisRenderer::updateAxisGridShadow() | 
| 527 | { | 
| 528 |     if (m_graph->isShadowVisible()) { | 
| 529 |         m_axisGridShadow->setGridColor(m_graph->shadowColor()); | 
| 530 |         m_axisGridShadow->setSubGridColor(m_graph->shadowColor()); | 
| 531 |         m_axisGridShadow->setSubGridLineWidth(m_axisGrid->subGridLineWidth() + m_graph->shadowBarWidth()); | 
| 532 |         m_axisGridShadow->setGridLineWidth(m_axisGrid->gridLineWidth() + m_graph->shadowBarWidth()); | 
| 533 |         m_axisGridShadow->setSmoothing(m_axisGrid->smoothing() + m_graph->shadowSmoothing()); | 
| 534 |  | 
| 535 |         // TODO Only when changed | 
| 536 |         m_axisGridShadow->setGridMovement(m_axisGrid->gridMovement()); | 
| 537 |         m_axisGridShadow->setX(m_axisGrid->x() + m_graph->shadowXOffset() - m_graph->shadowBarWidth() * 0.5); | 
| 538 |         m_axisGridShadow->setY(m_axisGrid->y() + m_graph->shadowYOffset() + m_graph->shadowBarWidth() * 0.5); | 
| 539 |         m_axisGridShadow->setWidth(m_axisGrid->width()); | 
| 540 |         m_axisGridShadow->setHeight(m_axisGrid->height()); | 
| 541 |         m_axisGridShadow->setGridWidth(m_axisGrid->gridWidth()); | 
| 542 |         m_axisGridShadow->setGridHeight(m_axisGrid->gridHeight()); | 
| 543 |         m_axisGridShadow->setGridVisibility(m_axisGrid->gridVisibility()); | 
| 544 |         m_axisGridShadow->setVerticalSubGridScale(m_axisGrid->verticalSubGridScale()); | 
| 545 |         m_axisGridShadow->setHorizontalSubGridScale(m_axisGrid->horizontalSubGridScale()); | 
| 546 |         m_axisGridShadow->setVisible(true); | 
| 547 |     } else { | 
| 548 |         m_axisGridShadow->setVisible(false); | 
| 549 |     } | 
| 550 | } | 
| 551 |  | 
| 552 | void AxisRenderer::updateAxisTitles(const QRectF xAxisRect, const QRectF yAxisRect) | 
| 553 | { | 
| 554 |     if (!m_xAxisTitle) { | 
| 555 |         m_xAxisTitle = new QQuickText(this); | 
| 556 |         m_xAxisTitle->setVAlign(QQuickText::AlignBottom); | 
| 557 |         m_xAxisTitle->setHAlign(QQuickText::AlignHCenter); | 
| 558 |     } | 
| 559 |  | 
| 560 |     if (!m_yAxisTitle) { | 
| 561 |         m_yAxisTitle = new QQuickText(this); | 
| 562 |         m_yAxisTitle->setVAlign(QQuickText::AlignVCenter); | 
| 563 |         m_yAxisTitle->setHAlign(QQuickText::AlignHCenter); | 
| 564 |     } | 
| 565 |  | 
| 566 |     if (m_axisHorizontal && m_axisHorizontal->isTitleVisible()) { | 
| 567 |         m_xAxisTitle->setText(m_axisHorizontal->titleText()); | 
| 568 |         m_xAxisTitle->setX((2 * xAxisRect.x() - m_xAxisTitle->contentWidth() + xAxisRect.width()) | 
| 569 |                            * 0.5); | 
| 570 |         m_xAxisTitle->setY(xAxisRect.y() + xAxisRect.height()); | 
| 571 |         if (m_axisHorizontal->titleColor().isValid()) | 
| 572 |             m_xAxisTitle->setColor(m_axisHorizontal->titleColor()); | 
| 573 |         else | 
| 574 |             m_xAxisTitle->setColor(theme()->labelTextColor()); | 
| 575 |         m_xAxisTitle->setFont(m_axisHorizontal->titleFont()); | 
| 576 |         m_xAxisTitle->setVisible(true); | 
| 577 |     } else { | 
| 578 |         m_xAxisTitle->setVisible(false); | 
| 579 |     } | 
| 580 |  | 
| 581 |     if (m_axisVertical && m_axisVertical->isTitleVisible()) { | 
| 582 |         m_yAxisTitle->setText(m_axisVertical->titleText()); | 
| 583 |         m_yAxisTitle->setX(0 + m_yAxisTitle->height() - m_yAxisTitle->contentWidth() * 0.5); | 
| 584 |         m_yAxisTitle->setY((2 * yAxisRect.y() - m_yAxisTitle->contentHeight() + yAxisRect.height()) | 
| 585 |                            * 0.5); | 
| 586 |         m_yAxisTitle->setRotation(-90); | 
| 587 |         if (m_axisVertical->titleColor().isValid()) | 
| 588 |             m_yAxisTitle->setColor(m_axisVertical->titleColor()); | 
| 589 |         else | 
| 590 |             m_yAxisTitle->setColor(theme()->labelTextColor()); | 
| 591 |         m_yAxisTitle->setFont(m_axisVertical->titleFont()); | 
| 592 |         m_yAxisTitle->setVisible(true); | 
| 593 |     } else { | 
| 594 |         m_yAxisTitle->setVisible(false); | 
| 595 |     } | 
| 596 | } | 
| 597 |  | 
| 598 | void AxisRenderer::updateAxisLabelItems(QList<QQuickItem *> &textItems, | 
| 599 |                                         qsizetype neededSize, QQmlComponent *component) | 
| 600 | { | 
| 601 |     qsizetype currentTextItemsSize = textItems.size(); | 
| 602 |     if (currentTextItemsSize < neededSize) { | 
| 603 |         for (qsizetype i = currentTextItemsSize; i <= neededSize; i++) { | 
| 604 |             QQuickItem *item = nullptr; | 
| 605 |             if (component) { | 
| 606 |                 item = qobject_cast<QQuickItem *>( | 
| 607 |                     o: component->create(context: component->creationContext())); | 
| 608 |             } | 
| 609 |             if (!item) | 
| 610 |                 item = new QQuickText(); | 
| 611 |             item->setParent(this); | 
| 612 |             item->setParentItem(this); | 
| 613 |             textItems << item; | 
| 614 |         } | 
| 615 |     } else if (neededSize < currentTextItemsSize) { | 
| 616 |         // Hide unused text items | 
| 617 |         for (qsizetype i = neededSize;  i < currentTextItemsSize; i++) { | 
| 618 |             auto textItem = textItems[i]; | 
| 619 |             textItem->setVisible(false); | 
| 620 |         } | 
| 621 |     } | 
| 622 | } | 
| 623 |  | 
| 624 | void AxisRenderer::setLabelTextProperties(QQuickItem *item, const QString &text, bool xAxis, | 
| 625 |                                           QQuickText::HAlignment hAlign, QQuickText::VAlignment vAlign) | 
| 626 | { | 
| 627 |     if (auto textItem = qobject_cast<QQuickText *>(object: item)) { | 
| 628 |         // If the component is a Text item (default), then text | 
| 629 |         // properties can be set directly. | 
| 630 |         textItem->setText(text); | 
| 631 |         textItem->setHeight(textItem->contentHeight()); // Default height | 
| 632 |         textItem->setHAlign(hAlign); | 
| 633 |         textItem->setVAlign(vAlign); | 
| 634 |         if (xAxis) { | 
| 635 |             textItem->setFont(theme()->axisXLabelFont()); | 
| 636 |             textItem->setColor(theme()->axisX().labelTextColor()); | 
| 637 |         } else { | 
| 638 |             textItem->setFont(theme()->axisYLabelFont()); | 
| 639 |             textItem->setColor(theme()->axisY().labelTextColor()); | 
| 640 |         } | 
| 641 |     } else { | 
| 642 |         // Check for specific dynamic properties | 
| 643 |         if (item->property(name: "text" ).isValid()) | 
| 644 |             item->setProperty(name: "text" , value: text); | 
| 645 |     } | 
| 646 | } | 
| 647 |  | 
| 648 | void AxisRenderer::updateBarXAxisLabels(QBarCategoryAxis *axis, const QRectF rect) | 
| 649 | { | 
| 650 |     qsizetype categoriesCount = axis->categories().size(); | 
| 651 |     // See if we need more text items | 
| 652 |     updateAxisLabelItems(textItems&: m_xAxisTextItems, neededSize: categoriesCount, component: axis->labelDelegate()); | 
| 653 |  | 
| 654 |     int textIndex = 0; | 
| 655 |     for (auto category : axis->categories()) { | 
| 656 |         auto &textItem = m_xAxisTextItems[textIndex]; | 
| 657 |         if (axis->isVisible() && axis->labelsVisible()) { | 
| 658 |             float posX = rect.x() + ((float)textIndex / categoriesCount) *  rect.width(); | 
| 659 |             textItem->setX(posX); | 
| 660 |             float posY = rect.y(); | 
| 661 |             textItem->setY(posY); | 
| 662 |             textItem->setWidth(rect.width() / categoriesCount); | 
| 663 |             textItem->setRotation(axis->labelsAngle()); | 
| 664 |             if (m_horizontalAxisOnTop) { | 
| 665 |                 setLabelTextProperties(item: textItem, text: category, xAxis: true, | 
| 666 |                                        hAlign: QQuickText::HAlignment::AlignHCenter, | 
| 667 |                                        vAlign: QQuickText::VAlignment::AlignBottom); | 
| 668 |             } else { | 
| 669 |                 setLabelTextProperties(item: textItem, text: category, xAxis: true, | 
| 670 |                                        hAlign: QQuickText::HAlignment::AlignHCenter, | 
| 671 |                                        vAlign: QQuickText::VAlignment::AlignTop); | 
| 672 |             } | 
| 673 |             textItem->setHeight(rect.height()); | 
| 674 |             textItem->setVisible(true); | 
| 675 |             theme()->dirtyBits()->axisXDirty = false; | 
| 676 |         } else { | 
| 677 |             textItem->setVisible(false); | 
| 678 |         } | 
| 679 |         textIndex++; | 
| 680 |     } | 
| 681 | } | 
| 682 |  | 
| 683 | void AxisRenderer::updateBarYAxisLabels(QBarCategoryAxis *axis, const QRectF rect) | 
| 684 | { | 
| 685 |     qsizetype categoriesCount = axis->categories().size(); | 
| 686 |     // See if we need more text items | 
| 687 |     updateAxisLabelItems(textItems&: m_yAxisTextItems, neededSize: categoriesCount, component: axis->labelDelegate()); | 
| 688 |  | 
| 689 |     int textIndex = 0; | 
| 690 |     for (auto category : axis->categories()) { | 
| 691 |         auto &textItem = m_yAxisTextItems[textIndex]; | 
| 692 |         if (axis->isVisible() && axis->labelsVisible()) { | 
| 693 |             float posX = rect.x(); | 
| 694 |             textItem->setX(posX); | 
| 695 |             float posY = rect.y() + ((float)textIndex / categoriesCount) *  rect.height(); | 
| 696 |             textItem->setY(posY); | 
| 697 |             textItem->setWidth(rect.width()); | 
| 698 |             textItem->setRotation(axis->labelsAngle()); | 
| 699 |             if (m_verticalAxisOnRight) { | 
| 700 |                 setLabelTextProperties(item: textItem, text: category, xAxis: false, | 
| 701 |                                        hAlign: QQuickText::HAlignment::AlignRight, | 
| 702 |                                        vAlign: QQuickText::VAlignment::AlignVCenter); | 
| 703 |             } else { | 
| 704 |                 setLabelTextProperties(item: textItem, text: category, xAxis: false, | 
| 705 |                                        hAlign: QQuickText::HAlignment::AlignLeft, | 
| 706 |                                        vAlign: QQuickText::VAlignment::AlignVCenter); | 
| 707 |             } | 
| 708 |             textItem->setHeight(rect.height() / categoriesCount); | 
| 709 |             textItem->setVisible(true); | 
| 710 |             theme()->dirtyBits()->axisYDirty = false; | 
| 711 |         } else { | 
| 712 |             textItem->setVisible(false); | 
| 713 |         } | 
| 714 |         textIndex++; | 
| 715 |     } | 
| 716 | } | 
| 717 |  | 
| 718 | void AxisRenderer::updateValueYAxisLabels(QValueAxis *axis, const QRectF rect) | 
| 719 | { | 
| 720 |     // Create label values in the range | 
| 721 |     QList<double> yAxisLabelValues; | 
| 722 |     const int MAX_LABELS_COUNT = 100; | 
| 723 |     for (double i = m_axisVerticalMinLabel; i <= m_axisVerticalMaxValue; i += m_axisVerticalValueStep) { | 
| 724 |         yAxisLabelValues << i; | 
| 725 |         if (yAxisLabelValues.size() >= MAX_LABELS_COUNT) | 
| 726 |             break; | 
| 727 |     } | 
| 728 |     qsizetype categoriesCount = yAxisLabelValues.size(); | 
| 729 |  | 
| 730 |     // See if we need more text items | 
| 731 |     updateAxisLabelItems(textItems&: m_yAxisTextItems, neededSize: categoriesCount, component: axis->labelDelegate()); | 
| 732 |  | 
| 733 |     for (int i = 0;  i < categoriesCount; i++) { | 
| 734 |         auto &textItem = m_yAxisTextItems[i]; | 
| 735 |         if (axis->isVisible() && axis->labelsVisible()) { | 
| 736 |             float posX = rect.x(); | 
| 737 |             textItem->setX(posX); | 
| 738 |             float posY = rect.y() + rect.height() - (((float)i) * m_axisVerticalStepPx) + m_axisYDisplacement; | 
| 739 |             const double titleMargin = 0.01; | 
| 740 |             if ((posY - titleMargin) > (rect.height() + rect.y()) || (posY + titleMargin) < rect.y()) { | 
| 741 |                 // Hide text item which are outside the axis area | 
| 742 |                 textItem->setVisible(false); | 
| 743 |                 continue; | 
| 744 |             } | 
| 745 |             textItem->setY(posY); | 
| 746 |             textItem->setWidth(rect.width()); | 
| 747 |             textItem->setRotation(axis->labelsAngle()); | 
| 748 |             double number = yAxisLabelValues.at(i); | 
| 749 |             // Format the number | 
| 750 |             int decimals = axis->labelDecimals(); | 
| 751 |             if (decimals < 0) | 
| 752 |                 decimals = getValueDecimalsFromRange(range: m_axisVerticalValueRange); | 
| 753 |             const QString f = axis->labelFormat(); | 
| 754 |             QString label; | 
| 755 |             if (f.length() <= 1) { | 
| 756 |               char format = f.isEmpty() ? 'f' : f.front().toLatin1(); | 
| 757 |               label = QString::number(number, format, precision: decimals); | 
| 758 |             } else { | 
| 759 |               QByteArray array = f.toLatin1(); | 
| 760 |               label = QString::asprintf(format: array.constData(), number); | 
| 761 |             } | 
| 762 |             if (m_verticalAxisOnRight) { | 
| 763 |                 setLabelTextProperties(item: textItem, text: label, xAxis: false, | 
| 764 |                                        hAlign: QQuickText::HAlignment::AlignLeft, | 
| 765 |                                        vAlign: QQuickText::VAlignment::AlignVCenter); | 
| 766 |             } else { | 
| 767 |                 setLabelTextProperties(item: textItem, text: label, xAxis: false, | 
| 768 |                                        hAlign: QQuickText::HAlignment::AlignRight, | 
| 769 |                                        vAlign: QQuickText::VAlignment::AlignVCenter); | 
| 770 |             } | 
| 771 |             textItem->setHeight(0); | 
| 772 |             textItem->setVisible(true); | 
| 773 |             theme()->dirtyBits()->axisYDirty = false; | 
| 774 |         } else { | 
| 775 |             textItem->setVisible(false); | 
| 776 |         } | 
| 777 |     } | 
| 778 | } | 
| 779 |  | 
| 780 | void AxisRenderer::updateValueXAxisLabels(QValueAxis *axis, const QRectF rect) | 
| 781 | { | 
| 782 |     // Create label values in the range | 
| 783 |     QList<double> axisLabelValues; | 
| 784 |     const int MAX_LABELS_COUNT = 100; | 
| 785 |     for (double i = m_axisHorizontalMinLabel; i <= m_axisHorizontalMaxValue; i += m_axisHorizontalValueStep) { | 
| 786 |         axisLabelValues << i; | 
| 787 |         if (axisLabelValues.size() >= MAX_LABELS_COUNT) | 
| 788 |             break; | 
| 789 |     } | 
| 790 |     qsizetype categoriesCount = axisLabelValues.size(); | 
| 791 |  | 
| 792 |     // See if we need more text items | 
| 793 |     updateAxisLabelItems(textItems&: m_xAxisTextItems, neededSize: categoriesCount, component: axis->labelDelegate()); | 
| 794 |  | 
| 795 |     for (int i = 0;  i < categoriesCount; i++) { | 
| 796 |         auto &textItem = m_xAxisTextItems[i]; | 
| 797 |         if (axis->isVisible() && axis->labelsVisible()) { | 
| 798 |             float posY = rect.y(); | 
| 799 |             textItem->setY(posY); | 
| 800 |             float textItemWidth = 20; | 
| 801 |             float posX = rect.x() + (((float)i) * m_axisHorizontalStepPx) - m_axisXDisplacement; | 
| 802 |             const double titleMargin = 0.01; | 
| 803 |             if ((posX - titleMargin) > (rect.width() + rect.x()) || (posX + titleMargin) < rect.x()) { | 
| 804 |                 // Hide text item which are outside the axis area | 
| 805 |                 textItem->setVisible(false); | 
| 806 |                 continue; | 
| 807 |             } | 
| 808 |             // Take text size into account only after hiding | 
| 809 |             posX -= 0.5 * textItemWidth; | 
| 810 |             textItem->setX(posX); | 
| 811 |             textItem->setWidth(textItemWidth); | 
| 812 |             textItem->setRotation(axis->labelsAngle()); | 
| 813 |             double number = axisLabelValues.at(i); | 
| 814 |             // Format the number | 
| 815 |             int decimals = axis->labelDecimals(); | 
| 816 |             if (decimals < 0) | 
| 817 |                 decimals = getValueDecimalsFromRange(range: m_axisHorizontalValueRange); | 
| 818 |             const QString f = axis->labelFormat(); | 
| 819 |             QString label; | 
| 820 |             if (f.length() <= 1) { | 
| 821 |               char format = f.isEmpty() ? 'f' : f.front().toLatin1(); | 
| 822 |               label = QString::number(number, format, precision: decimals); | 
| 823 |             } else { | 
| 824 |               QByteArray array = f.toLatin1(); | 
| 825 |               label = QString::asprintf(format: array.constData(), number); | 
| 826 |             } | 
| 827 |             if (m_horizontalAxisOnTop) { | 
| 828 |                 setLabelTextProperties(item: textItem, text: label, xAxis: true, | 
| 829 |                                        hAlign: QQuickText::HAlignment::AlignHCenter, | 
| 830 |                                        vAlign: QQuickText::VAlignment::AlignBottom); | 
| 831 |             } else { | 
| 832 |                 setLabelTextProperties(item: textItem, text: label, xAxis: true, | 
| 833 |                                        hAlign: QQuickText::HAlignment::AlignHCenter, | 
| 834 |                                        vAlign: QQuickText::VAlignment::AlignTop); | 
| 835 |             } | 
| 836 |             textItem->setHeight(rect.height()); | 
| 837 |             textItem->setVisible(true); | 
| 838 |             theme()->dirtyBits()->axisXDirty = false; | 
| 839 |         } else { | 
| 840 |             textItem->setVisible(false); | 
| 841 |         } | 
| 842 |     } | 
| 843 | } | 
| 844 |  | 
| 845 | void AxisRenderer::updateDateTimeYAxisLabels(QDateTimeAxis *axis, const QRectF rect) | 
| 846 | { | 
| 847 |     auto maxDate = axis->max(); | 
| 848 |     auto minDate = axis->min(); | 
| 849 |     int dateTimeSize = m_axisVerticalMinLabel + 1; | 
| 850 |     auto segment = (maxDate.toMSecsSinceEpoch() - minDate.toMSecsSinceEpoch()) | 
| 851 |                    / m_axisVerticalMinLabel; | 
| 852 |  | 
| 853 |     // See if we need more text items | 
| 854 |     updateAxisLabelItems(textItems&: m_yAxisTextItems, neededSize: dateTimeSize, component: axis->labelDelegate()); | 
| 855 |  | 
| 856 |     for (auto i = 0; i < dateTimeSize; ++i) { | 
| 857 |         auto &textItem = m_yAxisTextItems[i]; | 
| 858 |         if (axis->isVisible() && axis->labelsVisible()) { | 
| 859 |             float posX = rect.x(); | 
| 860 |             textItem->setX(posX); | 
| 861 |             float posY = rect.y() + rect.height() - (((float) i) * m_axisVerticalStepPx); | 
| 862 |             const double titleMargin = 0.01; | 
| 863 |             if ((posY - titleMargin) > (rect.height() + rect.y()) | 
| 864 |                 || (posY + titleMargin) < rect.y()) { | 
| 865 |                 // Hide text item which are outside the axis area | 
| 866 |                 textItem->setVisible(false); | 
| 867 |                 continue; | 
| 868 |             } | 
| 869 |             textItem->setY(posY); | 
| 870 |             textItem->setWidth(rect.width()); | 
| 871 |             textItem->setRotation(axis->labelsAngle()); | 
| 872 |             QString label = minDate.addMSecs(msecs: segment * i).toString(format: axis->labelFormat()); | 
| 873 |             if (m_verticalAxisOnRight) { | 
| 874 |                 setLabelTextProperties(item: textItem, text: label, xAxis: false, | 
| 875 |                                        hAlign: QQuickText::HAlignment::AlignLeft, | 
| 876 |                                        vAlign: QQuickText::VAlignment::AlignVCenter); | 
| 877 |             } else { | 
| 878 |                 setLabelTextProperties(item: textItem, text: label, xAxis: false, | 
| 879 |                                        hAlign: QQuickText::HAlignment::AlignRight, | 
| 880 |                                        vAlign: QQuickText::VAlignment::AlignVCenter); | 
| 881 |             } | 
| 882 |             textItem->setHeight(0); | 
| 883 |             textItem->setVisible(true); | 
| 884 |         } else { | 
| 885 |             textItem->setVisible(false); | 
| 886 |         } | 
| 887 |     } | 
| 888 | } | 
| 889 |  | 
| 890 | void AxisRenderer::updateDateTimeXAxisLabels(QDateTimeAxis *axis, const QRectF rect) | 
| 891 | { | 
| 892 |     auto maxDate = axis->max(); | 
| 893 |     auto minDate = axis->min(); | 
| 894 |     int dateTimeSize = m_axisHorizontalMinLabel + 1; | 
| 895 |     auto segment = (maxDate.toMSecsSinceEpoch() - minDate.toMSecsSinceEpoch()) | 
| 896 |                    / m_axisHorizontalMinLabel; | 
| 897 |  | 
| 898 |     // See if we need more text items | 
| 899 |     updateAxisLabelItems(textItems&: m_xAxisTextItems, neededSize: dateTimeSize, component: axis->labelDelegate()); | 
| 900 |  | 
| 901 |     for (auto i = 0; i < dateTimeSize; ++i) { | 
| 902 |         auto &textItem = m_xAxisTextItems[i]; | 
| 903 |         if (axis->isVisible() && axis->labelsVisible()) { | 
| 904 |             float posY = rect.y(); | 
| 905 |             textItem->setY(posY); | 
| 906 |             float textItemWidth = 20; | 
| 907 |             float posX = rect.x() + (((float) i) * m_axisHorizontalStepPx); | 
| 908 |             const double titleMargin = 0.01; | 
| 909 |             if ((posX - titleMargin) > (rect.width() + rect.x()) | 
| 910 |                 || (posX + titleMargin) < rect.x()) { | 
| 911 |                 // Hide text item which are outside the axis area | 
| 912 |                 textItem->setVisible(false); | 
| 913 |                 continue; | 
| 914 |             } | 
| 915 |             // Take text size into account only after hiding | 
| 916 |             posX -= 0.5 * textItemWidth; | 
| 917 |             textItem->setX(posX); | 
| 918 |             textItem->setWidth(textItemWidth); | 
| 919 |             textItem->setRotation(axis->labelsAngle()); | 
| 920 |             QString label = minDate.addMSecs(msecs: segment * i).toString(format: axis->labelFormat()); | 
| 921 |             if (m_horizontalAxisOnTop) { | 
| 922 |                 setLabelTextProperties(item: textItem, text: label, xAxis: true, | 
| 923 |                                        hAlign: QQuickText::HAlignment::AlignHCenter, | 
| 924 |                                        vAlign: QQuickText::VAlignment::AlignBottom); | 
| 925 |             } else { | 
| 926 |                 setLabelTextProperties(item: textItem, text: label, xAxis: true, | 
| 927 |                                        hAlign: QQuickText::HAlignment::AlignHCenter, | 
| 928 |                                        vAlign: QQuickText::VAlignment::AlignTop); | 
| 929 |             } | 
| 930 |             textItem->setHeight(rect.height()); | 
| 931 |             textItem->setVisible(true); | 
| 932 |         } else { | 
| 933 |             textItem->setVisible(false); | 
| 934 |         } | 
| 935 |     } | 
| 936 | } | 
| 937 |  | 
| 938 | // Calculate suitable major step based on range | 
| 939 | double AxisRenderer::getValueStepsFromRange(double range) | 
| 940 | { | 
| 941 |     int digits = std::ceil(x: std::log10(x: range)); | 
| 942 |     double r = std::pow(x: 10.0, y: -digits); | 
| 943 |     r *= 10.0; | 
| 944 |     double v = std::ceil(x: range * r) / r; | 
| 945 |     double step = v * 0.1; | 
| 946 |     // Step must always be bigger than 0 | 
| 947 |     step = qMax(a: 0.0001, b: step); | 
| 948 |     return step; | 
| 949 | } | 
| 950 |  | 
| 951 | // Calculate suitable decimals amount based on range | 
| 952 | int AxisRenderer::getValueDecimalsFromRange(double range) | 
| 953 | { | 
| 954 |     if (range <= 0) | 
| 955 |         return 0; | 
| 956 |     int decimals = std::ceil(x: std::log10(x: 10.0 / range)); | 
| 957 |     // Decimals must always be at least 0 | 
| 958 |     decimals = qMax(a: 0, b: decimals); | 
| 959 |     return decimals; | 
| 960 | } | 
| 961 |  | 
| 962 | QT_END_NAMESPACE | 
| 963 |  |