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/linearrowitem_p.h>
36#include <private/polarchartaxisradial_p.h>
37
38QT_CHARTS_BEGIN_NAMESPACE
39
40PolarChartAxisRadial::PolarChartAxisRadial(QAbstractAxis *axis, QGraphicsItem *item,
41 bool intervalAxis)
42 : PolarChartAxis(axis, item, intervalAxis)
43{
44}
45
46PolarChartAxisRadial::~PolarChartAxisRadial()
47{
48}
49
50void PolarChartAxisRadial::updateGeometry()
51{
52 const QVector<qreal> &layout = this->layout();
53 if (layout.isEmpty() && axis()->type() != QAbstractAxis::AxisTypeLogValue)
54 return;
55
56 createAxisLabels(layout);
57 QStringList labelList = labels();
58 QPointF center = axisGeometry().center();
59 QList<QGraphicsItem *> arrowItemList = arrowItems();
60 QList<QGraphicsItem *> gridItemList = gridItems();
61 QList<QGraphicsItem *> labelItemList = labelItems();
62 QList<QGraphicsItem *> shadeItemList = shadeItems();
63 QList<QGraphicsItem *> minorGridItemList = minorGridItems();
64 QList<QGraphicsItem *> minorArrowItemList = minorArrowItems();
65 QGraphicsTextItem* title = titleItem();
66 qreal radius = axisGeometry().height() / 2.0;
67
68 QLineF line(center, center + QPointF(0, -radius));
69 QGraphicsLineItem *axisLine = static_cast<QGraphicsLineItem *>(arrowItemList.at(i: 0));
70 axisLine->setLine(line);
71
72 QRectF previousLabelRect;
73 bool firstShade = true;
74 bool nextTickVisible = false;
75 if (layout.size())
76 nextTickVisible = !(layout.at(i: 0) < 0.0 || layout.at(i: 0) > radius);
77
78 for (int i = 0; i < layout.size(); ++i) {
79 qreal radialCoordinate = layout.at(i);
80
81 QGraphicsEllipseItem *gridItem = static_cast<QGraphicsEllipseItem *>(gridItemList.at(i));
82 QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(arrowItemList.at(i: i + 1));
83 QGraphicsTextItem *labelItem = static_cast<QGraphicsTextItem *>(labelItemList.at(i));
84 QGraphicsPathItem *shadeItem = 0;
85 if (i == 0)
86 shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(i: 0));
87 else if (i % 2)
88 shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(i: (i / 2) + 1));
89
90 // Ignore ticks outside valid range
91 bool currentTickVisible = nextTickVisible;
92 if ((i == layout.size() - 1)
93 || layout.at(i: i + 1) < 0.0
94 || layout.at(i: i + 1) > radius) {
95 nextTickVisible = false;
96 } else {
97 nextTickVisible = true;
98 }
99
100 qreal labelCoordinate = radialCoordinate;
101 bool labelVisible = currentTickVisible;
102 qreal labelPad = labelPadding() / 2.0;
103 bool centeredLabel = true; // Only used with interval axes
104 if (intervalAxis()) {
105 qreal farEdge;
106 if (i == (layout.size() - 1))
107 farEdge = radius;
108 else
109 farEdge = qMin(a: radius, b: layout.at(i: i + 1));
110
111 // Adjust the labelCoordinate to show it if next tick is visible
112 if (nextTickVisible)
113 labelCoordinate = qMax(a: qreal(0.0), b: labelCoordinate);
114
115 if (axis()->type() == QAbstractAxis::AxisTypeCategory) {
116 QCategoryAxis *categoryAxis = static_cast<QCategoryAxis *>(axis());
117 if (categoryAxis->labelsPosition() == QCategoryAxis::AxisLabelsPositionOnValue)
118 centeredLabel = false;
119 }
120 if (centeredLabel) {
121 labelCoordinate = (labelCoordinate + farEdge) / 2.0;
122 if (labelCoordinate > 0.0 && labelCoordinate < radius)
123 labelVisible = true;
124 else
125 labelVisible = false;
126 } else {
127 labelVisible = nextTickVisible;
128 labelCoordinate = farEdge;
129 }
130 }
131
132 // Radial axis label
133 if (axis()->labelsVisible() && labelVisible) {
134 QRectF boundingRect = ChartPresenter::textBoundingRect(font: axis()->labelsFont(),
135 text: labelList.at(i),
136 angle: axis()->labelsAngle());
137 labelItem->setTextWidth(boundingRect.width());
138 labelItem->setHtml(labelList.at(i));
139 QRectF labelRect = labelItem->boundingRect();
140 QPointF labelCenter = labelRect.center();
141 labelItem->setTransformOriginPoint(ax: labelCenter.x(), ay: labelCenter.y());
142 boundingRect.moveCenter(p: labelCenter);
143 QPointF positionDiff(labelRect.topLeft() - boundingRect.topLeft());
144 QPointF labelPoint = center;
145 if (intervalAxis() && centeredLabel)
146 labelPoint += QPointF(labelPad, -labelCoordinate - (boundingRect.height() / 2.0));
147 else
148 labelPoint += QPointF(labelPad, labelPad - labelCoordinate);
149 labelRect.moveTopLeft(p: labelPoint);
150 labelItem->setPos(labelRect.topLeft() + positionDiff);
151
152 // Label overlap detection
153 labelRect.setSize(boundingRect.size());
154 if ((i && previousLabelRect.intersects(r: labelRect))
155 || !axisGeometry().contains(r: labelRect)) {
156 labelVisible = false;
157 } else {
158 previousLabelRect = labelRect;
159 labelVisible = true;
160 }
161 }
162
163 labelItem->setVisible(labelVisible);
164 if (!currentTickVisible) {
165 gridItem->setVisible(false);
166 tickItem->setVisible(false);
167 if (shadeItem)
168 shadeItem->setVisible(false);
169 continue;
170 }
171
172 // Radial grid line
173 QRectF gridRect;
174 gridRect.setWidth(radialCoordinate * 2.0);
175 gridRect.setHeight(radialCoordinate * 2.0);
176 gridRect.moveCenter(p: center);
177
178 gridItem->setRect(gridRect);
179 gridItem->setVisible(true);
180
181 // Tick
182 QLineF tickLine(-tickWidth(), 0.0, tickWidth(), 0.0);
183 tickLine.translate(adx: center.rx(), ady: gridRect.top());
184 tickItem->setLine(tickLine);
185 tickItem->setVisible(true);
186
187 // Shades
188 if (i % 2 || (i == 0 && !nextTickVisible)) {
189 QPainterPath path;
190 if (i == 0) {
191 // If first tick is also the last, we need to custom fill the inner circle
192 // or it won't get filled.
193 QRectF innerCircle(0.0, 0.0, layout.at(i: 0) * 2.0, layout.at(i: 0) * 2.0);
194 innerCircle.moveCenter(p: center);
195 path.addEllipse(rect: innerCircle);
196 } else {
197 QRectF otherGridRect;
198 if (!nextTickVisible) { // Last visible tick
199 otherGridRect = axisGeometry();
200 } else {
201 qreal otherGridRectDimension = layout.at(i: i + 1) * 2.0;
202 otherGridRect.setWidth(otherGridRectDimension);
203 otherGridRect.setHeight(otherGridRectDimension);
204 otherGridRect.moveCenter(p: center);
205 }
206 path.addEllipse(rect: gridRect);
207 path.addEllipse(rect: otherGridRect);
208
209 // Add additional shading in first visible shade item if there is a partial tick
210 // to be filled at the center (log & category axes)
211 if (firstShade) {
212 QGraphicsPathItem *specialShadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(i: 0));
213 if (layout.at(i: i - 1) > 0.0) {
214 QRectF innerCircle(0.0, 0.0, layout.at(i: i - 1) * 2.0, layout.at(i: i - 1) * 2.0);
215 innerCircle.moveCenter(p: center);
216 QPainterPath specialPath;
217 specialPath.addEllipse(rect: innerCircle);
218 specialShadeItem->setPath(specialPath);
219 specialShadeItem->setVisible(true);
220 } else {
221 specialShadeItem->setVisible(false);
222 }
223 }
224 }
225 shadeItem->setPath(path);
226 shadeItem->setVisible(true);
227 firstShade = false;
228 }
229 }
230
231 updateMinorTickGeometry();
232
233 // Title, along the 0 axis
234 QString titleText = axis()->titleText();
235 if (!titleText.isEmpty() && axis()->isTitleVisible()) {
236 QRectF truncatedRect;
237 title->setHtml(ChartPresenter::truncatedText(font: axis()->titleFont(), text: titleText, angle: qreal(0.0),
238 maxWidth: radius, maxHeight: radius, boundingRect&: truncatedRect));
239 title->setTextWidth(truncatedRect.width());
240
241 QRectF titleBoundingRect = title->boundingRect();
242 QPointF titleCenter = titleBoundingRect.center();
243 QPointF arrowCenter = axisLine->boundingRect().center();
244 QPointF titleCenterDiff = arrowCenter - titleCenter;
245 title->setPos(ax: titleCenterDiff.x() - titlePadding() - (titleBoundingRect.height() / 2.0), ay: titleCenterDiff.y());
246 title->setTransformOriginPoint(titleCenter);
247 title->setRotation(270.0);
248 }
249
250 QGraphicsLayoutItem::updateGeometry();
251}
252
253Qt::Orientation PolarChartAxisRadial::orientation() const
254{
255 return Qt::Vertical;
256}
257
258void PolarChartAxisRadial::createItems(int count)
259{
260 if (arrowItems().count() == 0) {
261 // radial axis center line
262 QGraphicsLineItem *arrow = new LineArrowItem(this, presenter()->rootItem());
263 arrow->setPen(axis()->linePen());
264 arrowGroup()->addToGroup(item: arrow);
265 }
266
267 QGraphicsTextItem *title = titleItem();
268 title->setFont(axis()->titleFont());
269 title->setDefaultTextColor(axis()->titleBrush().color());
270 title->setHtml(axis()->titleText());
271
272 for (int i = 0; i < count; ++i) {
273 QGraphicsLineItem *arrow = new QGraphicsLineItem(presenter()->rootItem());
274 QGraphicsEllipseItem *grid = new QGraphicsEllipseItem(presenter()->rootItem());
275 QGraphicsTextItem *label = new QGraphicsTextItem(presenter()->rootItem());
276 label->document()->setDocumentMargin(ChartPresenter::textMargin());
277 arrow->setPen(axis()->linePen());
278 grid->setPen(axis()->gridLinePen());
279 label->setFont(axis()->labelsFont());
280 label->setDefaultTextColor(axis()->labelsBrush().color());
281 label->setRotation(axis()->labelsAngle());
282 arrowGroup()->addToGroup(item: arrow);
283 gridGroup()->addToGroup(item: grid);
284 labelGroup()->addToGroup(item: label);
285 if (gridItems().size() == 1 || (((gridItems().size() + 1) % 2) && gridItems().size() > 0)) {
286 QGraphicsPathItem *shade = new QGraphicsPathItem(presenter()->rootItem());
287 shade->setPen(axis()->shadesPen());
288 shade->setBrush(axis()->shadesBrush());
289 shadeGroup()->addToGroup(item: shade);
290 }
291 }
292}
293
294void PolarChartAxisRadial::handleArrowPenChanged(const QPen &pen)
295{
296 foreach (QGraphicsItem *item, arrowItems())
297 static_cast<QGraphicsLineItem *>(item)->setPen(pen);
298}
299
300void PolarChartAxisRadial::handleGridPenChanged(const QPen &pen)
301{
302 foreach (QGraphicsItem *item, gridItems())
303 static_cast<QGraphicsEllipseItem *>(item)->setPen(pen);
304}
305
306void PolarChartAxisRadial::handleMinorArrowPenChanged(const QPen &pen)
307{
308 foreach (QGraphicsItem *item, minorArrowItems())
309 static_cast<QGraphicsLineItem *>(item)->setPen(pen);
310}
311
312void PolarChartAxisRadial::handleMinorGridPenChanged(const QPen &pen)
313{
314 foreach (QGraphicsItem *item, minorGridItems())
315 static_cast<QGraphicsEllipseItem *>(item)->setPen(pen);
316}
317
318void PolarChartAxisRadial::handleGridLineColorChanged(const QColor &color)
319{
320 foreach (QGraphicsItem *item, gridItems()) {
321 QGraphicsEllipseItem *ellipseItem = static_cast<QGraphicsEllipseItem *>(item);
322 QPen pen = ellipseItem->pen();
323 pen.setColor(color);
324 ellipseItem->setPen(pen);
325 }
326}
327
328void PolarChartAxisRadial::handleMinorGridLineColorChanged(const QColor &color)
329{
330 foreach (QGraphicsItem *item, minorGridItems()) {
331 QGraphicsEllipseItem *ellipseItem = static_cast<QGraphicsEllipseItem *>(item);
332 QPen pen = ellipseItem->pen();
333 pen.setColor(color);
334 ellipseItem->setPen(pen);
335 }
336}
337
338QSizeF PolarChartAxisRadial::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
339{
340 Q_UNUSED(which);
341 Q_UNUSED(constraint);
342 return QSizeF(-1.0, -1.0);
343}
344
345qreal PolarChartAxisRadial::preferredAxisRadius(const QSizeF &maxSize)
346{
347 qreal radius = maxSize.height() / 2.0;
348 if (maxSize.width() < maxSize.height())
349 radius = maxSize.width() / 2.0;
350 return radius;
351}
352
353void PolarChartAxisRadial::updateMinorTickGeometry()
354{
355 if (!axis())
356 return;
357
358 QVector<qreal> layout = ChartAxisElement::layout();
359 int minorTickCount = 0;
360 qreal tickRadius = 0.0;
361 QVector<qreal> minorTickRadiuses;
362 switch (axis()->type()) {
363 case QAbstractAxis::AxisTypeValue: {
364 const QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: axis());
365
366 minorTickCount = valueAxis->minorTickCount();
367
368 if (valueAxis->tickCount() >= 2)
369 tickRadius = layout.at(i: 1) - layout.at(i: 0);
370
371 for (int i = 0; i < minorTickCount; ++i) {
372 const qreal ratio = (1.0 / qreal(minorTickCount + 1)) * qreal(i + 1);
373 minorTickRadiuses.append(t: tickRadius * ratio);
374 }
375 break;
376 }
377 case QAbstractAxis::AxisTypeLogValue: {
378 const QLogValueAxis *logValueAxis = qobject_cast<QLogValueAxis *>(object: axis());
379 const qreal base = logValueAxis->base();
380 const qreal logBase = qLn(v: base);
381
382 minorTickCount = logValueAxis->minorTickCount();
383 if (minorTickCount < 0)
384 minorTickCount = qMax(a: int(qFloor(v: base) - 2.0), b: 0);
385
386 // Two "virtual" ticks are required to make sure that all minor ticks
387 // are displayed properly (even for the partially visible segments of
388 // the chart).
389 if (layout.size() >= 2) {
390 // Calculate tickRadius as a difference between visible ticks
391 // whenever it is possible. Virtual ticks will not be correctly
392 // positioned when the layout is animating.
393 tickRadius = layout.at(i: 1) - layout.at(i: 0);
394 layout.prepend(t: layout.at(i: 0) - tickRadius);
395 layout.append(t: layout.at(i: layout.size() - 1) + tickRadius);
396 } else {
397 const qreal logMax = qLn(v: logValueAxis->max());
398 const qreal logMin = qLn(v: logValueAxis->min());
399 const qreal logExtraMaxTick = qLn(v: qPow(x: base, y: qFloor(v: logMax / logBase) + 1.0));
400 const qreal logExtraMinTick = qLn(v: qPow(x: base, y: qCeil(v: logMin / logBase) - 1.0));
401 const qreal edge = qMin(a: logMin, b: logMax);
402 const qreal delta = (axisGeometry().width() / 2.0) / qAbs(t: logMax - logMin);
403 const qreal extraMaxTick = edge + (logExtraMaxTick - edge) * delta;
404 const qreal extraMinTick = edge + (logExtraMinTick - edge) * delta;
405
406 // Calculate tickRadius using one (if layout.size() == 1) or two
407 // (if layout.size() == 0) "virtual" ticks. In both cases animation
408 // will not work as expected. This should be fixed later.
409 layout.prepend(t: extraMinTick);
410 layout.append(t: extraMaxTick);
411 tickRadius = layout.at(i: 1) - layout.at(i: 0);
412 }
413
414 const qreal minorTickStepValue = qFabs(v: base - 1.0) / qreal(minorTickCount + 1);
415 for (int i = 0; i < minorTickCount; ++i) {
416 const qreal x = minorTickStepValue * qreal(i + 1) + 1.0;
417 const qreal minorTickSpacing = tickRadius * (qLn(v: x) / logBase);
418 minorTickRadiuses.append(t: minorTickSpacing);
419 }
420 break;
421 }
422 default:
423 // minor ticks are not supported
424 break;
425 }
426
427 if (minorTickCount < 1 || tickRadius == 0.0 || minorTickRadiuses.count() != minorTickCount)
428 return;
429
430 const QPointF axisCenter = axisGeometry().center();
431 const qreal axisRadius = axisGeometry().height() / 2.0;
432
433 for (int i = 0; i < layout.size() - 1; ++i) {
434 for (int j = 0; j < minorTickCount; ++j) {
435 const int minorItemIndex = i * minorTickCount + j;
436 QGraphicsEllipseItem *minorGridLineItem =
437 static_cast<QGraphicsEllipseItem *>(minorGridItems().at(i: minorItemIndex));
438 QGraphicsLineItem *minorArrowLineItem =
439 static_cast<QGraphicsLineItem *>(minorArrowItems().at(i: minorItemIndex));
440 if (!minorGridLineItem || !minorArrowLineItem)
441 continue;
442
443 const qreal minorTickRadius = layout.at(i) + minorTickRadiuses.value(i: j, defaultValue: 0.0);
444 const qreal minorTickDiameter = minorTickRadius * 2.0;
445
446 QRectF minorGridLine(0.0, 0.0, minorTickDiameter, minorTickDiameter);
447 minorGridLine.moveCenter(p: axisCenter);
448 minorGridLineItem->setRect(minorGridLine);
449
450 QLineF minorArrowLine(-tickWidth() + 1.0, 0.0, tickWidth() - 1.0, 0.0);
451 minorArrowLine.translate(adx: axisCenter.x(), ady: minorGridLine.top());
452 minorArrowLineItem->setLine(minorArrowLine);
453
454 // check if the minor grid line and the minor axis arrow should be shown
455 bool minorGridLineVisible = (minorTickRadius >= 0.0 && minorTickRadius <= axisRadius);
456 minorGridLineItem->setVisible(minorGridLineVisible);
457 minorArrowLineItem->setVisible(minorGridLineVisible);
458 }
459 }
460}
461
462void PolarChartAxisRadial::updateMinorTickItems()
463{
464 int currentCount = minorArrowItems().size();
465 int expectedCount = 0;
466 if (axis()->type() == QAbstractAxis::AxisTypeValue) {
467 QValueAxis *valueAxis = qobject_cast<QValueAxis *>(object: axis());
468 expectedCount = valueAxis->minorTickCount() * (valueAxis->tickCount() - 1);
469 expectedCount = qMax(a: expectedCount, b: 0);
470 } else if (axis()->type() == QAbstractAxis::AxisTypeLogValue) {
471 QLogValueAxis *logValueAxis = qobject_cast<QLogValueAxis *>(object: axis());
472
473 int minorTickCount = logValueAxis->minorTickCount();
474 if (minorTickCount < 0)
475 minorTickCount = qMax(a: int(qFloor(v: logValueAxis->base()) - 2.0), b: 0);
476
477 expectedCount = minorTickCount * (logValueAxis->tickCount() + 1);
478 expectedCount = qMax(a: expectedCount, b: logValueAxis->minorTickCount());
479 } else {
480 // minor ticks are not supported
481 return;
482 }
483
484 int diff = expectedCount - currentCount;
485 if (diff > 0) {
486 for (int i = 0; i < diff; ++i) {
487 QGraphicsEllipseItem *minorGridItem = new QGraphicsEllipseItem(presenter()->rootItem());
488 minorGridItem->setPen(axis()->minorGridLinePen());
489 minorGridGroup()->addToGroup(item: minorGridItem);
490
491 QGraphicsLineItem *minorArrowItem = new QGraphicsLineItem(presenter()->rootItem());
492 minorArrowItem->setPen(axis()->linePen());
493 minorArrowGroup()->addToGroup(item: minorArrowItem);
494 }
495 } else {
496 QList<QGraphicsItem *> minorGridItemsList = minorGridItems();
497 QList<QGraphicsItem *> minorArrowItemsList = minorArrowItems();
498 for (int i = 0; i > diff; --i) {
499 if (!minorGridItemsList.isEmpty())
500 delete minorGridItemsList.takeLast();
501
502 if (!minorArrowItemsList.isEmpty())
503 delete minorArrowItemsList.takeLast();
504 }
505 }
506}
507
508QT_CHARTS_END_NAMESPACE
509
510#include "moc_polarchartaxisradial_p.cpp"
511

source code of qtcharts/src/charts/axis/polarchartaxisradial.cpp