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