1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Charts module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 or (at your option) any later version |
20 | ** approved by the KDE Free Qt Foundation. The licenses are as published by |
21 | ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
22 | ** included in the packaging of this file. Please review the following |
23 | ** information to ensure the GNU General Public License requirements will |
24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
25 | ** |
26 | ** $QT_END_LICENSE$ |
27 | ** |
28 | ****************************************************************************/ |
29 | |
30 | #include <QtCharts/qcategoryaxis.h> |
31 | #include <QtCharts/qlogvalueaxis.h> |
32 | #include <QtCore/qmath.h> |
33 | #include <QtGui/qtextdocument.h> |
34 | #include <private/chartpresenter_p.h> |
35 | #include <private/polarchartaxisangular_p.h> |
36 | |
37 | QT_CHARTS_BEGIN_NAMESPACE |
38 | |
39 | PolarChartAxisAngular::PolarChartAxisAngular(QAbstractAxis *axis, QGraphicsItem *item, |
40 | bool intervalAxis) |
41 | : PolarChartAxis(axis, item, intervalAxis) |
42 | { |
43 | } |
44 | |
45 | PolarChartAxisAngular::~PolarChartAxisAngular() |
46 | { |
47 | } |
48 | |
49 | void PolarChartAxisAngular::updateGeometry() |
50 | { |
51 | QGraphicsLayoutItem::updateGeometry(); |
52 | |
53 | const QVector<qreal> &layout = this->layout(); |
54 | if (layout.isEmpty() && axis()->type() != QAbstractAxis::AxisTypeLogValue) |
55 | return; |
56 | |
57 | createAxisLabels(layout); |
58 | QStringList labelList = labels(); |
59 | QPointF center = axisGeometry().center(); |
60 | QList<QGraphicsItem *> arrowItemList = arrowItems(); |
61 | QList<QGraphicsItem *> gridItemList = gridItems(); |
62 | QList<QGraphicsItem *> labelItemList = labelItems(); |
63 | QList<QGraphicsItem *> shadeItemList = shadeItems(); |
64 | QList<QGraphicsItem *> minorGridItemList = minorGridItems(); |
65 | QList<QGraphicsItem *> minorArrowItemList = minorArrowItems(); |
66 | QGraphicsTextItem *title = titleItem(); |
67 | |
68 | QGraphicsEllipseItem *axisLine = static_cast<QGraphicsEllipseItem *>(arrowItemList.at(i: 0)); |
69 | axisLine->setRect(axisGeometry()); |
70 | |
71 | qreal radius = axisGeometry().height() / 2.0; |
72 | |
73 | QRectF previousLabelRect; |
74 | QRectF firstLabelRect; |
75 | |
76 | qreal labelHeight = 0; |
77 | |
78 | bool firstShade = true; |
79 | bool nextTickVisible = false; |
80 | if (layout.size()) |
81 | nextTickVisible = !(layout.at(i: 0) < 0.0 || layout.at(i: 0) > 360.0); |
82 | |
83 | for (int i = 0; i < layout.size(); ++i) { |
84 | qreal angularCoordinate = layout.at(i); |
85 | |
86 | QGraphicsLineItem *gridLineItem = static_cast<QGraphicsLineItem *>(gridItemList.at(i)); |
87 | QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(arrowItemList.at(i: i + 1)); |
88 | QGraphicsTextItem *labelItem = static_cast<QGraphicsTextItem *>(labelItemList.at(i)); |
89 | QGraphicsPathItem *shadeItem = 0; |
90 | if (i == 0) |
91 | shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(i: 0)); |
92 | else if (i % 2) |
93 | shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(i: (i / 2) + 1)); |
94 | |
95 | // Ignore ticks outside valid range |
96 | bool currentTickVisible = nextTickVisible; |
97 | if ((i == layout.size() - 1) |
98 | || layout.at(i: i + 1) < 0.0 |
99 | || layout.at(i: i + 1) > 360.0) { |
100 | nextTickVisible = false; |
101 | } else { |
102 | nextTickVisible = true; |
103 | } |
104 | |
105 | qreal labelCoordinate = angularCoordinate; |
106 | bool labelVisible = currentTickVisible; |
107 | if (intervalAxis()) { |
108 | qreal farEdge; |
109 | if (i == (layout.size() - 1)) |
110 | farEdge = 360.0; |
111 | else |
112 | farEdge = qMin(a: qreal(360.0), b: layout.at(i: i + 1)); |
113 | |
114 | // Adjust the labelCoordinate to show it if next tick is visible |
115 | if (nextTickVisible) |
116 | labelCoordinate = qMax(a: qreal(0.0), b: labelCoordinate); |
117 | |
118 | bool centeredLabel = true; |
119 | if (axis()->type() == QAbstractAxis::AxisTypeCategory) { |
120 | QCategoryAxis *categoryAxis = static_cast<QCategoryAxis *>(axis()); |
121 | if (categoryAxis->labelsPosition() == QCategoryAxis::AxisLabelsPositionOnValue) |
122 | centeredLabel = false; |
123 | } |
124 | if (centeredLabel) { |
125 | labelCoordinate = (labelCoordinate + farEdge) / 2.0; |
126 | // Don't display label once the category gets too small near the axis |
127 | if (labelCoordinate < 5.0 || labelCoordinate > 355.0) |
128 | labelVisible = false; |
129 | else |
130 | labelVisible = true; |
131 | } else { |
132 | labelVisible = nextTickVisible; |
133 | labelCoordinate = farEdge; |
134 | } |
135 | } |
136 | |
137 | // Need this also in label calculations, so determine it first |
138 | QLineF tickLine(QLineF::fromPolar(length: radius - tickWidth(), angle: 90.0 - angularCoordinate).p2(), |
139 | QLineF::fromPolar(length: radius + tickWidth(), angle: 90.0 - angularCoordinate).p2()); |
140 | tickLine.translate(point: center); |
141 | |
142 | // Angular axis label |
143 | if (axis()->labelsVisible() && labelVisible) { |
144 | QRectF boundingRect = ChartPresenter::textBoundingRect(font: axis()->labelsFont(), |
145 | text: labelList.at(i), |
146 | angle: axis()->labelsAngle()); |
147 | labelItem->setTextWidth(boundingRect.width()); |
148 | labelItem->setHtml(labelList.at(i)); |
149 | const QRectF &rect = labelItem->boundingRect(); |
150 | QPointF labelCenter = rect.center(); |
151 | labelItem->setTransformOriginPoint(ax: labelCenter.x(), ay: labelCenter.y()); |
152 | boundingRect.moveCenter(p: labelCenter); |
153 | QPointF positionDiff(rect.topLeft() - boundingRect.topLeft()); |
154 | |
155 | QPointF labelPoint; |
156 | if (intervalAxis()) { |
157 | QLineF labelLine = QLineF::fromPolar(length: radius + tickWidth(), angle: 90.0 - labelCoordinate); |
158 | labelLine.translate(point: center); |
159 | labelPoint = labelLine.p2(); |
160 | } else { |
161 | labelPoint = tickLine.p2(); |
162 | } |
163 | |
164 | QRectF labelRect = moveLabelToPosition(angularCoordinate: labelCoordinate, labelPoint, labelRect: boundingRect); |
165 | labelItem->setPos(labelRect.topLeft() + positionDiff); |
166 | |
167 | // Store height for title calculations |
168 | qreal labelClearance = axisGeometry().top() - labelRect.top(); |
169 | labelHeight = qMax(a: labelHeight, b: labelClearance); |
170 | |
171 | // Label overlap detection |
172 | if (i && (previousLabelRect.intersects(r: labelRect) || firstLabelRect.intersects(r: labelRect))) { |
173 | labelVisible = false; |
174 | } else { |
175 | // Store labelRect for future comparison. Some area is deducted to make things look |
176 | // little nicer, as usually intersection happens at label corner with angular labels. |
177 | labelRect.adjust(xp1: -2.0, yp1: -4.0, xp2: -2.0, yp2: -4.0); |
178 | if (firstLabelRect.isEmpty()) |
179 | firstLabelRect = labelRect; |
180 | |
181 | previousLabelRect = labelRect; |
182 | labelVisible = true; |
183 | } |
184 | } |
185 | |
186 | labelItem->setVisible(labelVisible); |
187 | if (!currentTickVisible) { |
188 | gridLineItem->setVisible(false); |
189 | tickItem->setVisible(false); |
190 | if (shadeItem) |
191 | shadeItem->setVisible(false); |
192 | continue; |
193 | } |
194 | |
195 | // Angular grid line |
196 | QLineF gridLine = QLineF::fromPolar(length: radius, angle: 90.0 - angularCoordinate); |
197 | gridLine.translate(point: center); |
198 | gridLineItem->setLine(gridLine); |
199 | gridLineItem->setVisible(true); |
200 | |
201 | // Tick |
202 | tickItem->setLine(tickLine); |
203 | tickItem->setVisible(true); |
204 | |
205 | // Shades |
206 | if (i % 2 || (i == 0 && !nextTickVisible)) { |
207 | QPainterPath path; |
208 | path.moveTo(p: center); |
209 | if (i == 0) { |
210 | // If first tick is also the last, we need to custom fill the first partial arc |
211 | // or it won't get filled. |
212 | path.arcTo(rect: axisGeometry(), startAngle: 90.0 - layout.at(i: 0), arcLength: layout.at(i: 0)); |
213 | path.closeSubpath(); |
214 | } else { |
215 | qreal nextCoordinate; |
216 | if (!nextTickVisible) // Last visible tick |
217 | nextCoordinate = 360.0; |
218 | else |
219 | nextCoordinate = layout.at(i: i + 1); |
220 | qreal arcSpan = angularCoordinate - nextCoordinate; |
221 | path.arcTo(rect: axisGeometry(), startAngle: 90.0 - angularCoordinate, arcLength: arcSpan); |
222 | path.closeSubpath(); |
223 | |
224 | // Add additional arc for first shade item if there is a partial arc to be filled |
225 | if (firstShade) { |
226 | QGraphicsPathItem *specialShadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(i: 0)); |
227 | if (layout.at(i: i - 1) > 0.0) { |
228 | QPainterPath specialPath; |
229 | specialPath.moveTo(p: center); |
230 | specialPath.arcTo(rect: axisGeometry(), startAngle: 90.0 - layout.at(i: i - 1), arcLength: layout.at(i: i - 1)); |
231 | specialPath.closeSubpath(); |
232 | specialShadeItem->setPath(specialPath); |
233 | specialShadeItem->setVisible(true); |
234 | } else { |
235 | specialShadeItem->setVisible(false); |
236 | } |
237 | } |
238 | } |
239 | shadeItem->setPath(path); |
240 | shadeItem->setVisible(true); |
241 | firstShade = false; |
242 | } |
243 | } |
244 | |
245 | updateMinorTickGeometry(); |
246 | |
247 | // Title, centered above the chart |
248 | QString titleText = axis()->titleText(); |
249 | if (!titleText.isEmpty() && axis()->isTitleVisible()) { |
250 | QRectF truncatedRect; |
251 | qreal availableTitleHeight = axisGeometry().height() - labelPadding() - titlePadding() * 2.0; |
252 | qreal minimumLabelHeight = ChartPresenter::textBoundingRect(font: axis()->labelsFont(), |
253 | QStringLiteral("..." )).height(); |
254 | availableTitleHeight -= minimumLabelHeight; |
255 | title->setHtml(ChartPresenter::truncatedText(font: axis()->titleFont(), text: titleText, angle: qreal(0.0), |
256 | maxWidth: axisGeometry().width(), maxHeight: availableTitleHeight, |
257 | boundingRect&: truncatedRect)); |
258 | title->setTextWidth(truncatedRect.width()); |
259 | |
260 | QRectF titleBoundingRect = title->boundingRect(); |
261 | QPointF titleCenter = center - titleBoundingRect.center(); |
262 | title->setPos(ax: titleCenter.x(), ay: axisGeometry().top() - titlePadding() * 2.0 - titleBoundingRect.height() - labelHeight); |
263 | } |
264 | } |
265 | |
266 | Qt::Orientation PolarChartAxisAngular::orientation() const |
267 | { |
268 | return Qt::Horizontal; |
269 | } |
270 | |
271 | void PolarChartAxisAngular::createItems(int count) |
272 | { |
273 | if (arrowItems().count() == 0) { |
274 | // angular axis line |
275 | QGraphicsEllipseItem *arrow = new QGraphicsEllipseItem(presenter()->rootItem()); |
276 | arrow->setPen(axis()->linePen()); |
277 | arrowGroup()->addToGroup(item: arrow); |
278 | } |
279 | |
280 | QGraphicsTextItem *title = titleItem(); |
281 | title->setFont(axis()->titleFont()); |
282 | title->setDefaultTextColor(axis()->titleBrush().color()); |
283 | title->setHtml(axis()->titleText()); |
284 | |
285 | for (int i = 0; i < count; ++i) { |
286 | QGraphicsLineItem *arrow = new QGraphicsLineItem(presenter()->rootItem()); |
287 | QGraphicsLineItem *grid = new QGraphicsLineItem(presenter()->rootItem()); |
288 | QGraphicsTextItem *label = new QGraphicsTextItem(presenter()->rootItem()); |
289 | label->document()->setDocumentMargin(ChartPresenter::textMargin()); |
290 | arrow->setPen(axis()->linePen()); |
291 | grid->setPen(axis()->gridLinePen()); |
292 | label->setFont(axis()->labelsFont()); |
293 | label->setDefaultTextColor(axis()->labelsBrush().color()); |
294 | label->setRotation(axis()->labelsAngle()); |
295 | arrowGroup()->addToGroup(item: arrow); |
296 | gridGroup()->addToGroup(item: grid); |
297 | labelGroup()->addToGroup(item: label); |
298 | if (gridItems().size() == 1 || (((gridItems().size() + 1) % 2) && gridItems().size() > 0)) { |
299 | QGraphicsPathItem *shade = new QGraphicsPathItem(presenter()->rootItem()); |
300 | shade->setPen(axis()->shadesPen()); |
301 | shade->setBrush(axis()->shadesBrush()); |
302 | shadeGroup()->addToGroup(item: shade); |
303 | } |
304 | } |
305 | } |
306 | |
307 | void PolarChartAxisAngular::handleArrowPenChanged(const QPen &pen) |
308 | { |
309 | bool first = true; |
310 | foreach (QGraphicsItem *item, arrowItems()) { |
311 | if (first) { |
312 | first = false; |
313 | // First arrow item is the outer circle of axis |
314 | static_cast<QGraphicsEllipseItem *>(item)->setPen(pen); |
315 | } else { |
316 | static_cast<QGraphicsLineItem *>(item)->setPen(pen); |
317 | } |
318 | } |
319 | } |
320 | |
321 | void PolarChartAxisAngular::handleGridPenChanged(const QPen &pen) |
322 | { |
323 | foreach (QGraphicsItem *item, gridItems()) |
324 | static_cast<QGraphicsLineItem *>(item)->setPen(pen); |
325 | } |
326 | |
327 | void PolarChartAxisAngular::handleMinorArrowPenChanged(const QPen &pen) |
328 | { |
329 | foreach (QGraphicsItem *item, minorArrowItems()) { |
330 | static_cast<QGraphicsLineItem *>(item)->setPen(pen); |
331 | } |
332 | } |
333 | |
334 | void PolarChartAxisAngular::handleMinorGridPenChanged(const QPen &pen) |
335 | { |
336 | foreach (QGraphicsItem *item, minorGridItems()) |
337 | static_cast<QGraphicsLineItem *>(item)->setPen(pen); |
338 | } |
339 | |
340 | void PolarChartAxisAngular::handleGridLineColorChanged(const QColor &color) |
341 | { |
342 | foreach (QGraphicsItem *item, gridItems()) { |
343 | QGraphicsLineItem *lineItem = static_cast<QGraphicsLineItem *>(item); |
344 | QPen pen = lineItem->pen(); |
345 | pen.setColor(color); |
346 | lineItem->setPen(pen); |
347 | } |
348 | } |
349 | |
350 | void PolarChartAxisAngular::handleMinorGridLineColorChanged(const QColor &color) |
351 | { |
352 | foreach (QGraphicsItem *item, minorGridItems()) { |
353 | QGraphicsLineItem *lineItem = static_cast<QGraphicsLineItem *>(item); |
354 | QPen pen = lineItem->pen(); |
355 | pen.setColor(color); |
356 | lineItem->setPen(pen); |
357 | } |
358 | } |
359 | |
360 | QSizeF PolarChartAxisAngular::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const |
361 | { |
362 | Q_UNUSED(which); |
363 | Q_UNUSED(constraint); |
364 | return QSizeF(-1, -1); |
365 | } |
366 | |
367 | qreal PolarChartAxisAngular::preferredAxisRadius(const QSizeF &maxSize) |
368 | { |
369 | qreal radius = maxSize.height() / 2.0; |
370 | if (maxSize.width() < maxSize.height()) |
371 | radius = maxSize.width() / 2.0; |
372 | |
373 | if (axis()->labelsVisible()) { |
374 | QVector<qreal> layout = calculateLayout(); |
375 | if (layout.isEmpty()) |
376 | return radius; |
377 | |
378 | createAxisLabels(layout); |
379 | QStringList labelList = labels(); |
380 | QFont font = axis()->labelsFont(); |
381 | |
382 | QRectF maxRect; |
383 | maxRect.setSize(maxSize); |
384 | maxRect.moveCenter(p: QPointF(0.0, 0.0)); |
385 | |
386 | // This is a horrible way to find out the maximum radius for angular axis and its labels. |
387 | // It just increments the radius down until everyhing fits the constraint size. |
388 | // Proper way would be to actually calculate it but this seems to work reasonably fast as it is. |
389 | bool nextTickVisible = false; |
390 | for (int i = 0; i < layout.size(); ) { |
391 | if ((i == layout.size() - 1) |
392 | || layout.at(i: i + 1) < 0.0 |
393 | || layout.at(i: i + 1) > 360.0) { |
394 | nextTickVisible = false; |
395 | } else { |
396 | nextTickVisible = true; |
397 | } |
398 | |
399 | qreal labelCoordinate = layout.at(i); |
400 | qreal labelVisible; |
401 | |
402 | if (intervalAxis()) { |
403 | qreal farEdge; |
404 | if (i == (layout.size() - 1)) |
405 | farEdge = 360.0; |
406 | else |
407 | farEdge = qMin(a: qreal(360.0), b: layout.at(i: i + 1)); |
408 | |
409 | // Adjust the labelCoordinate to show it if next tick is visible |
410 | if (nextTickVisible) |
411 | labelCoordinate = qMax(a: qreal(0.0), b: labelCoordinate); |
412 | |
413 | labelCoordinate = (labelCoordinate + farEdge) / 2.0; |
414 | } |
415 | |
416 | if (labelCoordinate < 0.0 || labelCoordinate > 360.0) |
417 | labelVisible = false; |
418 | else |
419 | labelVisible = true; |
420 | |
421 | if (!labelVisible) { |
422 | i++; |
423 | continue; |
424 | } |
425 | |
426 | QRectF boundingRect = ChartPresenter::textBoundingRect(font: axis()->labelsFont(), text: labelList.at(i), angle: axis()->labelsAngle()); |
427 | QPointF labelPoint = QLineF::fromPolar(length: radius + tickWidth(), angle: 90.0 - labelCoordinate).p2(); |
428 | |
429 | boundingRect = moveLabelToPosition(angularCoordinate: labelCoordinate, labelPoint, labelRect: boundingRect); |
430 | QRectF intersectRect = maxRect.intersected(r: boundingRect); |
431 | if (boundingRect.isEmpty() || intersectRect == boundingRect) { |
432 | i++; |
433 | } else { |
434 | qreal reduction(0.0); |
435 | // If there is no intersection, reduce by smallest dimension of label rect to be on the safe side |
436 | if (intersectRect.isEmpty()) { |
437 | reduction = qMin(a: boundingRect.height(), b: boundingRect.width()); |
438 | } else { |
439 | // Approximate needed radius reduction is the amount label rect exceeds max rect in either dimension. |
440 | // Could be further optimized by figuring out the proper math how to calculate exact needed reduction. |
441 | reduction = qMax(a: boundingRect.height() - intersectRect.height(), |
442 | b: boundingRect.width() - intersectRect.width()); |
443 | } |
444 | // Typically the approximated reduction is little low, so add one |
445 | radius -= (reduction + 1.0); |
446 | |
447 | if (radius < 1.0) // safeguard |
448 | return 1.0; |
449 | } |
450 | } |
451 | } |
452 | |
453 | if (!axis()->titleText().isEmpty() && axis()->isTitleVisible()) { |
454 | QRectF titleRect = ChartPresenter::textBoundingRect(font: axis()->titleFont(), text: axis()->titleText()); |
455 | |
456 | radius -= titlePadding() + (titleRect.height() / 2.0); |
457 | if (radius < 1.0) // safeguard |
458 | return 1.0; |
459 | } |
460 | |
461 | return radius; |
462 | } |
463 | |
464 | QRectF PolarChartAxisAngular::moveLabelToPosition(qreal angularCoordinate, QPointF labelPoint, QRectF labelRect) const |
465 | { |
466 | if (angularCoordinate == 0.0) |
467 | labelRect.moveCenter(p: labelPoint + QPointF(0, -labelRect.height() / 2.0)); |
468 | else if (angularCoordinate < 90.0) |
469 | labelRect.moveBottomLeft(p: labelPoint); |
470 | else if (angularCoordinate == 90.0) |
471 | labelRect.moveCenter(p: labelPoint + QPointF(labelRect.width() / 2.0 + 2.0, 0)); // +2 so that it does not hit the radial axis |
472 | else if (angularCoordinate < 180.0) |
473 | labelRect.moveTopLeft(p: labelPoint); |
474 | else if (angularCoordinate == 180.0) |
475 | labelRect.moveCenter(p: labelPoint + QPointF(0, labelRect.height() / 2.0)); |
476 | else if (angularCoordinate < 270.0) |
477 | labelRect.moveTopRight(p: labelPoint); |
478 | else if (angularCoordinate == 270.0) |
479 | labelRect.moveCenter(p: labelPoint + QPointF(-labelRect.width() / 2.0 - 2.0, 0)); // -2 so that it does not hit the radial axis |
480 | else if (angularCoordinate < 360.0) |
481 | labelRect.moveBottomRight(p: labelPoint); |
482 | else |
483 | labelRect.moveCenter(p: labelPoint + QPointF(0, -labelRect.height() / 2.0)); |
484 | return labelRect; |
485 | } |
486 | |
487 | void PolarChartAxisAngular::updateMinorTickGeometry() |
488 | { |
489 | if (!axis()) |
490 | return; |
491 | |
492 | QVector<qreal> layout = ChartAxisElement::layout(); |
493 | int minorTickCount = 0; |
494 | qreal tickAngle = 0.0; |
495 | QVector<qreal> minorTickAngles; |
496 | switch (axis()->type()) { |
497 | case QAbstractAxis::AxisTypeValue: { |
498 | const QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: axis()); |
499 | |
500 | minorTickCount = valueAxis->minorTickCount(); |
501 | |
502 | if (valueAxis->tickCount() >= 2) |
503 | tickAngle = layout.at(i: 1) - layout.at(i: 0); |
504 | |
505 | for (int i = 0; i < minorTickCount; ++i) { |
506 | const qreal ratio = (1.0 / qreal(minorTickCount + 1)) * qreal(i + 1); |
507 | minorTickAngles.append(t: tickAngle * ratio); |
508 | } |
509 | break; |
510 | } |
511 | case QAbstractAxis::AxisTypeLogValue: { |
512 | const QLogValueAxis *logValueAxis = qobject_cast<QLogValueAxis *>(object: axis()); |
513 | const qreal base = logValueAxis->base(); |
514 | const qreal logBase = qLn(v: base); |
515 | |
516 | minorTickCount = logValueAxis->minorTickCount(); |
517 | if (minorTickCount < 0) |
518 | minorTickCount = qMax(a: int(qFloor(v: base) - 2.0), b: 0); |
519 | |
520 | // Two "virtual" ticks are required to make sure that all minor ticks |
521 | // are displayed properly (even for the partially visible segments of |
522 | // the chart). |
523 | if (layout.size() >= 2) { |
524 | // Calculate tickAngle as a difference between visible ticks |
525 | // whenever it is possible. Virtual ticks will not be correctly |
526 | // positioned when the layout is animating. |
527 | tickAngle = layout.at(i: 1) - layout.at(i: 0); |
528 | layout.prepend(t: layout.at(i: 0) - tickAngle); |
529 | layout.append(t: layout.at(i: layout.size() - 1) + tickAngle); |
530 | } else { |
531 | const qreal logMax = qLn(v: logValueAxis->max()); |
532 | const qreal logMin = qLn(v: logValueAxis->min()); |
533 | const qreal = qLn(v: qPow(x: base, y: qFloor(v: logMax / logBase) + 1.0)); |
534 | const qreal = qLn(v: qPow(x: base, y: qCeil(v: logMin / logBase) - 1.0)); |
535 | const qreal edge = qMin(a: logMin, b: logMax); |
536 | const qreal delta = 360.0 / qAbs(t: logMax - logMin); |
537 | const qreal = edge + (logExtraMaxTick - edge) * delta; |
538 | const qreal = edge + (logExtraMinTick - edge) * delta; |
539 | |
540 | // Calculate tickAngle using one (if layout.size() == 1) or two |
541 | // (if layout.size() == 0) "virtual" ticks. In both cases animation |
542 | // will not work as expected. This should be fixed later. |
543 | layout.prepend(t: extraMinTick); |
544 | layout.append(t: extraMaxTick); |
545 | tickAngle = layout.at(i: 1) - layout.at(i: 0); |
546 | } |
547 | |
548 | const qreal minorTickStepValue = qFabs(v: base - 1.0) / qreal(minorTickCount + 1); |
549 | for (int i = 0; i < minorTickCount; ++i) { |
550 | const qreal x = minorTickStepValue * qreal(i + 1) + 1.0; |
551 | const qreal minorTickAngle = tickAngle * (qLn(v: x) / logBase); |
552 | minorTickAngles.append(t: minorTickAngle); |
553 | } |
554 | break; |
555 | } |
556 | default: |
557 | // minor ticks are not supported |
558 | break; |
559 | } |
560 | |
561 | if (minorTickCount < 1 || tickAngle == 0.0 || minorTickAngles.count() != minorTickCount) |
562 | return; |
563 | |
564 | const QPointF axisCenter = axisGeometry().center(); |
565 | const qreal axisRadius = axisGeometry().height() / 2.0; |
566 | |
567 | for (int i = 0; i < layout.size() - 1; ++i) { |
568 | for (int j = 0; j < minorTickCount; ++j) { |
569 | const int minorItemIndex = i * minorTickCount + j; |
570 | QGraphicsLineItem *minorGridLineItem = |
571 | static_cast<QGraphicsLineItem *>(minorGridItems().at(i: minorItemIndex)); |
572 | QGraphicsLineItem *minorArrowLineItem = |
573 | static_cast<QGraphicsLineItem *>(minorArrowItems().at(i: minorItemIndex)); |
574 | if (!minorGridLineItem || !minorArrowLineItem) |
575 | continue; |
576 | |
577 | const qreal minorTickAngle = 90.0 - layout.at(i) - minorTickAngles.value(i: j, defaultValue: 0.0); |
578 | |
579 | const QPointF minorArrowLinePt1 = QLineF::fromPolar(length: axisRadius - tickWidth() + 1, |
580 | angle: minorTickAngle).p2(); |
581 | const QPointF minorArrowLinePt2 = QLineF::fromPolar(length: axisRadius + tickWidth() - 1, |
582 | angle: minorTickAngle).p2(); |
583 | |
584 | QLineF minorGridLine = QLineF::fromPolar(length: axisRadius, angle: minorTickAngle); |
585 | minorGridLine.translate(point: axisCenter); |
586 | minorGridLineItem->setLine(minorGridLine); |
587 | |
588 | QLineF minorArrowLine(minorArrowLinePt1, minorArrowLinePt2); |
589 | minorArrowLine.translate(point: axisCenter); |
590 | minorArrowLineItem->setLine(minorArrowLine); |
591 | |
592 | // check if the minor grid line and the minor axis arrow should be shown |
593 | const bool minorGridLineVisible = (minorTickAngle >= -270.0 && minorTickAngle <= 90.0); |
594 | minorGridLineItem->setVisible(minorGridLineVisible); |
595 | minorArrowLineItem->setVisible(minorGridLineVisible); |
596 | } |
597 | } |
598 | } |
599 | |
600 | void PolarChartAxisAngular::updateMinorTickItems() |
601 | { |
602 | int currentCount = minorArrowItems().size(); |
603 | int expectedCount = 0; |
604 | if (axis()->type() == QAbstractAxis::AxisTypeValue) { |
605 | QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: axis()); |
606 | expectedCount = valueAxis->minorTickCount() * (valueAxis->tickCount() - 1); |
607 | expectedCount = qMax(a: expectedCount, b: 0); |
608 | } else if (axis()->type() == QAbstractAxis::AxisTypeLogValue) { |
609 | QLogValueAxis *logValueAxis = qobject_cast<QLogValueAxis *>(object: axis()); |
610 | |
611 | int minorTickCount = logValueAxis->minorTickCount(); |
612 | if (minorTickCount < 0) |
613 | minorTickCount = qMax(a: int(qFloor(v: logValueAxis->base()) - 2.0), b: 0); |
614 | |
615 | expectedCount = minorTickCount * (logValueAxis->tickCount() + 1); |
616 | expectedCount = qMax(a: expectedCount, b: logValueAxis->minorTickCount()); |
617 | } else { |
618 | // minor ticks are not supported |
619 | return; |
620 | } |
621 | |
622 | int diff = expectedCount - currentCount; |
623 | if (diff > 0) { |
624 | for (int i = 0; i < diff; ++i) { |
625 | QGraphicsLineItem *minorGridLineItem = new QGraphicsLineItem(this); |
626 | minorGridLineItem->setPen(axis()->minorGridLinePen()); |
627 | minorGridGroup()->addToGroup(item: minorGridLineItem); |
628 | |
629 | QGraphicsLineItem *minorArrowLineItem = new QGraphicsLineItem(this); |
630 | minorArrowLineItem->setPen(axis()->linePen()); |
631 | minorArrowGroup()->addToGroup(item: minorArrowLineItem); |
632 | } |
633 | } else { |
634 | QList<QGraphicsItem *> minorGridItemsList = minorGridItems(); |
635 | QList<QGraphicsItem *> minorArrowItemsList = minorArrowItems(); |
636 | for (int i = 0; i > diff; --i) { |
637 | if (!minorGridItemsList.isEmpty()) |
638 | delete minorGridItemsList.takeLast(); |
639 | |
640 | if (!minorArrowItemsList.isEmpty()) |
641 | delete minorArrowItemsList.takeLast(); |
642 | } |
643 | } |
644 | } |
645 | |
646 | QT_CHARTS_END_NAMESPACE |
647 | |
648 | #include "moc_polarchartaxisangular_p.cpp" |
649 | |