// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE PolarChartAxisRadial::PolarChartAxisRadial(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis) : PolarChartAxis(axis, item, intervalAxis) { } PolarChartAxisRadial::~PolarChartAxisRadial() { } void PolarChartAxisRadial::updateGeometry() { const QList &layout = this->layout(); if (layout.isEmpty() && axis()->type() != QAbstractAxis::AxisTypeLogValue) return; createAxisLabels(layout); QStringList labelList = labels(); QPointF center = axisGeometry().center(); QList arrowItemList = arrowItems(); QList gridItemList = gridItems(); QList labelItemList = labelItems(); QList shadeItemList = shadeItems(); QList minorGridItemList = minorGridItems(); QList minorArrowItemList = minorArrowItems(); QGraphicsTextItem* title = titleItem(); qreal radius = axisGeometry().height() / 2.0; QLineF line(center, center + QPointF(0, -radius)); QGraphicsLineItem *axisLine = static_cast(arrowItemList.at(0)); axisLine->setLine(line); QRectF previousLabelRect; bool firstShade = true; bool nextTickVisible = false; if (layout.size()) nextTickVisible = !(layout.at(0) < 0.0 || layout.at(0) > radius); for (int i = 0; i < layout.size(); ++i) { qreal radialCoordinate = layout.at(i); QGraphicsEllipseItem *gridItem = static_cast(gridItemList.at(i)); QGraphicsLineItem *tickItem = static_cast(arrowItemList.at(i + 1)); QGraphicsTextItem *labelItem = static_cast(labelItemList.at(i)); QGraphicsPathItem *shadeItem = 0; if (i == 0) shadeItem = static_cast(shadeItemList.at(0)); else if (i % 2) shadeItem = static_cast(shadeItemList.at((i / 2) + 1)); // Ignore ticks outside valid range bool currentTickVisible = nextTickVisible; if ((i == layout.size() - 1) || layout.at(i + 1) < 0.0 || layout.at(i + 1) > radius) { nextTickVisible = false; } else { nextTickVisible = true; } qreal labelCoordinate = radialCoordinate; bool labelVisible = currentTickVisible; qreal labelPad = labelPadding() / 2.0; bool centeredLabel = true; // Only used with interval axes if (intervalAxis()) { qreal farEdge; if (i == (layout.size() - 1)) farEdge = radius; else farEdge = qMin(radius, layout.at(i + 1)); // Adjust the labelCoordinate to show it if next tick is visible if (nextTickVisible) labelCoordinate = qMax(qreal(0.0), labelCoordinate); if (axis()->type() == QAbstractAxis::AxisTypeCategory) { QCategoryAxis *categoryAxis = static_cast(axis()); if (categoryAxis->labelsPosition() == QCategoryAxis::AxisLabelsPositionOnValue) centeredLabel = false; } if (centeredLabel) { labelCoordinate = (labelCoordinate + farEdge) / 2.0; if (labelCoordinate > 0.0 && labelCoordinate < radius) labelVisible = true; else labelVisible = false; } else { labelVisible = nextTickVisible; labelCoordinate = farEdge; } } // Radial axis label if (axis()->labelsVisible() && labelVisible) { QRectF boundingRect = ChartPresenter::textBoundingRect(axis()->labelsFont(), labelList.at(i), axis()->labelsAngle()); labelItem->setTextWidth(boundingRect.width()); labelItem->setHtml(labelList.at(i)); QRectF labelRect = labelItem->boundingRect(); QPointF labelCenter = labelRect.center(); labelItem->setTransformOriginPoint(labelCenter.x(), labelCenter.y()); boundingRect.moveCenter(labelCenter); QPointF positionDiff(labelRect.topLeft() - boundingRect.topLeft()); QPointF labelPoint = center; if (intervalAxis() && centeredLabel) labelPoint += QPointF(labelPad, -labelCoordinate - (boundingRect.height() / 2.0)); else labelPoint += QPointF(labelPad, labelPad - labelCoordinate); labelRect.moveTopLeft(labelPoint); labelItem->setPos(labelRect.topLeft() + positionDiff); // Label overlap detection labelRect.setSize(boundingRect.size()); if ((i && previousLabelRect.intersects(labelRect)) || !axisGeometry().contains(labelRect)) { labelVisible = false; } else { previousLabelRect = labelRect; labelVisible = true; } } labelItem->setVisible(labelVisible); if (!currentTickVisible) { gridItem->setVisible(false); tickItem->setVisible(false); if (shadeItem) shadeItem->setVisible(false); continue; } // Radial grid line QRectF gridRect; gridRect.setWidth(radialCoordinate * 2.0); gridRect.setHeight(radialCoordinate * 2.0); gridRect.moveCenter(center); gridItem->setRect(gridRect); gridItem->setVisible(true); // Tick QLineF tickLine(-tickWidth(), 0.0, tickWidth(), 0.0); tickLine.translate(center.rx(), gridRect.top()); tickItem->setLine(tickLine); tickItem->setVisible(true); // Shades if (i % 2 || (i == 0 && !nextTickVisible)) { QPainterPath path; if (i == 0) { // If first tick is also the last, we need to custom fill the inner circle // or it won't get filled. QRectF innerCircle(0.0, 0.0, layout.at(0) * 2.0, layout.at(0) * 2.0); innerCircle.moveCenter(center); path.addEllipse(innerCircle); } else { QRectF otherGridRect; if (!nextTickVisible) { // Last visible tick otherGridRect = axisGeometry(); } else { qreal otherGridRectDimension = layout.at(i + 1) * 2.0; otherGridRect.setWidth(otherGridRectDimension); otherGridRect.setHeight(otherGridRectDimension); otherGridRect.moveCenter(center); } path.addEllipse(gridRect); path.addEllipse(otherGridRect); // Add additional shading in first visible shade item if there is a partial tick // to be filled at the center (log & category axes) if (firstShade) { QGraphicsPathItem *specialShadeItem = static_cast(shadeItemList.at(0)); if (layout.at(i - 1) > 0.0) { QRectF innerCircle(0.0, 0.0, layout.at(i - 1) * 2.0, layout.at(i - 1) * 2.0); innerCircle.moveCenter(center); QPainterPath specialPath; specialPath.addEllipse(innerCircle); specialShadeItem->setPath(specialPath); specialShadeItem->setVisible(true); } else { specialShadeItem->setVisible(false); } } } shadeItem->setPath(path); shadeItem->setVisible(true); firstShade = false; } } updateMinorTickGeometry(); // Title, along the 0 axis QString titleText = axis()->titleText(); if (!titleText.isEmpty() && axis()->isTitleVisible()) { QRectF truncatedRect; title->setHtml(ChartPresenter::truncatedText(axis()->titleFont(), titleText, qreal(0.0), radius, radius, truncatedRect)); title->setTextWidth(truncatedRect.width()); QRectF titleBoundingRect = title->boundingRect(); QPointF titleCenter = titleBoundingRect.center(); QPointF arrowCenter = axisLine->boundingRect().center(); QPointF titleCenterDiff = arrowCenter - titleCenter; title->setPos(titleCenterDiff.x() - titlePadding() - (titleBoundingRect.height() / 2.0), titleCenterDiff.y()); title->setTransformOriginPoint(titleCenter); title->setRotation(270.0); } QGraphicsLayoutItem::updateGeometry(); } Qt::Orientation PolarChartAxisRadial::orientation() const { return Qt::Vertical; } void PolarChartAxisRadial::createItems(int count) { if (arrowItems().size() == 0) { // radial axis center line QGraphicsLineItem *arrow = new LineArrowItem(this, presenter()->rootItem()); arrow->setPen(axis()->linePen()); arrowGroup()->addToGroup(arrow); } QGraphicsTextItem *title = titleItem(); title->setFont(axis()->titleFont()); title->setDefaultTextColor(axis()->titleBrush().color()); title->setHtml(axis()->titleText()); for (int i = 0; i < count; ++i) { QGraphicsLineItem *arrow = new QGraphicsLineItem(presenter()->rootItem()); QGraphicsEllipseItem *grid = new QGraphicsEllipseItem(presenter()->rootItem()); QGraphicsTextItem *label = new QGraphicsTextItem(presenter()->rootItem()); label->document()->setDocumentMargin(ChartPresenter::textMargin()); arrow->setPen(axis()->linePen()); grid->setPen(axis()->gridLinePen()); label->setFont(axis()->labelsFont()); label->setDefaultTextColor(axis()->labelsBrush().color()); label->setRotation(axis()->labelsAngle()); arrowGroup()->addToGroup(arrow); gridGroup()->addToGroup(grid); labelGroup()->addToGroup(label); if (gridItems().size() == 1 || (((gridItems().size() + 1) % 2) && gridItems().size() > 0)) { QGraphicsPathItem *shade = new QGraphicsPathItem(presenter()->rootItem()); shade->setPen(axis()->shadesPen()); shade->setBrush(axis()->shadesBrush()); shadeGroup()->addToGroup(shade); } } } void PolarChartAxisRadial::handleArrowPenChanged(const QPen &pen) { const auto items = arrowItems(); for (QGraphicsItem *item : items) static_cast(item)->setPen(pen); } void PolarChartAxisRadial::handleGridPenChanged(const QPen &pen) { const auto items = gridItems(); for (QGraphicsItem *item : items) static_cast(item)->setPen(pen); } void PolarChartAxisRadial::handleMinorArrowPenChanged(const QPen &pen) { const auto items = minorArrowItems(); for (QGraphicsItem *item : items) static_cast(item)->setPen(pen); } void PolarChartAxisRadial::handleMinorGridPenChanged(const QPen &pen) { const auto items = minorGridItems(); for (QGraphicsItem *item : items) static_cast(item)->setPen(pen); } void PolarChartAxisRadial::handleGridLineColorChanged(const QColor &color) { const auto items = gridItems(); for (QGraphicsItem *item : items) { QGraphicsEllipseItem *ellipseItem = static_cast(item); QPen pen = ellipseItem->pen(); pen.setColor(color); ellipseItem->setPen(pen); } } void PolarChartAxisRadial::handleMinorGridLineColorChanged(const QColor &color) { const auto items = minorGridItems(); for (QGraphicsItem *item : items) { QGraphicsEllipseItem *ellipseItem = static_cast(item); QPen pen = ellipseItem->pen(); pen.setColor(color); ellipseItem->setPen(pen); } } QSizeF PolarChartAxisRadial::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const { Q_UNUSED(which); Q_UNUSED(constraint); return QSizeF(-1.0, -1.0); } qreal PolarChartAxisRadial::preferredAxisRadius(const QSizeF &maxSize) { qreal radius = maxSize.height() / 2.0; if (maxSize.width() < maxSize.height()) radius = maxSize.width() / 2.0; return radius; } void PolarChartAxisRadial::updateMinorTickGeometry() { if (!axis()) return; QList layout = ChartAxisElement::layout(); int minorTickCount = 0; qreal tickRadius = 0.0; QList minorTickRadiuses; switch (axis()->type()) { case QAbstractAxis::AxisTypeValue: { const QValueAxis *valueAxis = qobject_cast(axis()); minorTickCount = valueAxis->minorTickCount(); if (valueAxis->tickCount() >= 2) tickRadius = layout.at(1) - layout.at(0); for (int i = 0; i < minorTickCount; ++i) { const qreal ratio = (1.0 / qreal(minorTickCount + 1)) * qreal(i + 1); minorTickRadiuses.append(tickRadius * ratio); } break; } case QAbstractAxis::AxisTypeLogValue: { const QLogValueAxis *logValueAxis = qobject_cast(axis()); const qreal base = logValueAxis->base(); const qreal logBase = qLn(base); minorTickCount = logValueAxis->minorTickCount(); if (minorTickCount < 0) minorTickCount = qMax(qFloor(base) - 2, 0); // Two "virtual" ticks are required to make sure that all minor ticks // are displayed properly (even for the partially visible segments of // the chart). if (layout.size() >= 2) { // Calculate tickRadius as a difference between visible ticks // whenever it is possible. Virtual ticks will not be correctly // positioned when the layout is animating. tickRadius = layout.at(1) - layout.at(0); layout.prepend(layout.at(0) - tickRadius); layout.append(layout.at(layout.size() - 1) + tickRadius); } else { const qreal logMax = qLn(logValueAxis->max()); const qreal logMin = qLn(logValueAxis->min()); const qreal logExtraMaxTick = qLn(qPow(base, qFloor(logMax / logBase) + 1.0)); const qreal logExtraMinTick = qLn(qPow(base, qCeil(logMin / logBase) - 1.0)); const qreal edge = qMin(logMin, logMax); const qreal delta = (axisGeometry().width() / 2.0) / qAbs(logMax - logMin); const qreal extraMaxTick = edge + (logExtraMaxTick - edge) * delta; const qreal extraMinTick = edge + (logExtraMinTick - edge) * delta; // Calculate tickRadius using one (if layout.size() == 1) or two // (if layout.size() == 0) "virtual" ticks. In both cases animation // will not work as expected. This should be fixed later. layout.prepend(extraMinTick); layout.append(extraMaxTick); tickRadius = layout.at(1) - layout.at(0); } const qreal minorTickStepValue = qFabs(base - 1.0) / qreal(minorTickCount + 1); for (int i = 0; i < minorTickCount; ++i) { const qreal x = minorTickStepValue * qreal(i + 1) + 1.0; const qreal minorTickSpacing = tickRadius * (qLn(x) / logBase); minorTickRadiuses.append(minorTickSpacing); } break; } default: // minor ticks are not supported break; } if (minorTickCount < 1 || tickRadius == 0.0 || minorTickRadiuses.size() != minorTickCount) return; const QPointF axisCenter = axisGeometry().center(); const qreal axisRadius = axisGeometry().height() / 2.0; for (int i = 0; i < layout.size() - 1; ++i) { for (int j = 0; j < minorTickCount; ++j) { const int minorItemIndex = i * minorTickCount + j; QGraphicsEllipseItem *minorGridLineItem = static_cast(minorGridItems().at(minorItemIndex)); QGraphicsLineItem *minorArrowLineItem = static_cast(minorArrowItems().at(minorItemIndex)); if (!minorGridLineItem || !minorArrowLineItem) continue; const qreal minorTickRadius = layout.at(i) + minorTickRadiuses.value(j, 0.0); const qreal minorTickDiameter = minorTickRadius * 2.0; QRectF minorGridLine(0.0, 0.0, minorTickDiameter, minorTickDiameter); minorGridLine.moveCenter(axisCenter); minorGridLineItem->setRect(minorGridLine); QLineF minorArrowLine(-tickWidth() + 1.0, 0.0, tickWidth() - 1.0, 0.0); minorArrowLine.translate(axisCenter.x(), minorGridLine.top()); minorArrowLineItem->setLine(minorArrowLine); // check if the minor grid line and the minor axis arrow should be shown bool minorGridLineVisible = (minorTickRadius >= 0.0 && minorTickRadius <= axisRadius); minorGridLineItem->setVisible(minorGridLineVisible); minorArrowLineItem->setVisible(minorGridLineVisible); } } } void PolarChartAxisRadial::updateMinorTickItems() { int currentCount = minorArrowItems().size(); int expectedCount = 0; if (axis()->type() == QAbstractAxis::AxisTypeValue) { QValueAxis *valueAxis = qobject_cast(axis()); expectedCount = valueAxis->minorTickCount() * (valueAxis->tickCount() - 1); expectedCount = qMax(expectedCount, 0); } else if (axis()->type() == QAbstractAxis::AxisTypeLogValue) { QLogValueAxis *logValueAxis = qobject_cast(axis()); int minorTickCount = logValueAxis->minorTickCount(); if (minorTickCount < 0) minorTickCount = qMax(qFloor(logValueAxis->base()) - 2, 0); expectedCount = minorTickCount * (logValueAxis->tickCount() + 1); expectedCount = qMax(expectedCount, logValueAxis->minorTickCount()); } else { // minor ticks are not supported return; } int diff = expectedCount - currentCount; if (diff > 0) { for (int i = 0; i < diff; ++i) { QGraphicsEllipseItem *minorGridItem = new QGraphicsEllipseItem(presenter()->rootItem()); minorGridItem->setPen(axis()->minorGridLinePen()); minorGridGroup()->addToGroup(minorGridItem); QGraphicsLineItem *minorArrowItem = new QGraphicsLineItem(presenter()->rootItem()); minorArrowItem->setPen(axis()->linePen()); minorArrowGroup()->addToGroup(minorArrowItem); } } else { QList minorGridItemsList = minorGridItems(); QList minorArrowItemsList = minorArrowItems(); for (int i = 0; i > diff; --i) { if (!minorGridItemsList.isEmpty()) delete minorGridItemsList.takeLast(); if (!minorArrowItemsList.isEmpty()) delete minorArrowItemsList.takeLast(); } } } QT_END_NAMESPACE #include "moc_polarchartaxisradial_p.cpp"