| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Charts module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 or (at your option) any later version |
| ** approved by the KDE Free Qt Foundation. The licenses are as published by |
| ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <private/areachartitem_p.h> |
| #include <QtCharts/QAreaSeries> |
| #include <private/qareaseries_p.h> |
| #include <QtCharts/QLineSeries> |
| #include <private/chartpresenter_p.h> |
| #include <private/abstractdomain_p.h> |
| #include <private/chartdataset_p.h> |
| #include <QtGui/QPainter> |
| #include <QtWidgets/QGraphicsSceneMouseEvent> |
| #include <QtCore/QDebug> |
| |
| |
| QT_CHARTS_BEGIN_NAMESPACE |
| |
| AreaChartItem::AreaChartItem(QAreaSeries *areaSeries, QGraphicsItem* item) |
| : ChartItem(areaSeries->d_func(),item), |
| m_series(areaSeries), |
| m_upper(0), |
| m_lower(0), |
| m_pointsVisible(false), |
| m_pointLabelsVisible(false), |
| m_pointLabelsFormat(areaSeries->pointLabelsFormat()), |
| m_pointLabelsFont(areaSeries->pointLabelsFont()), |
| m_pointLabelsColor(areaSeries->pointLabelsColor()), |
| m_pointLabelsClipping(true), |
| m_mousePressed(false) |
| { |
| setAcceptHoverEvents(true); |
| setFlag(QGraphicsItem::ItemIsSelectable, true); |
| setZValue(ChartPresenter::LineChartZValue); |
| if (m_series->upperSeries()) |
| m_upper = new AreaBoundItem(this, m_series->upperSeries()); |
| if (m_series->lowerSeries()) |
| m_lower = new AreaBoundItem(this, m_series->lowerSeries()); |
| |
| QObject::connect(m_series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated())); |
| QObject::connect(m_series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated())); |
| QObject::connect(m_series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated())); |
| QObject::connect(this, SIGNAL(clicked(QPointF)), areaSeries, SIGNAL(clicked(QPointF))); |
| QObject::connect(this, SIGNAL(hovered(QPointF,bool)), areaSeries, SIGNAL(hovered(QPointF,bool))); |
| QObject::connect(this, SIGNAL(pressed(QPointF)), areaSeries, SIGNAL(pressed(QPointF))); |
| QObject::connect(this, SIGNAL(released(QPointF)), areaSeries, SIGNAL(released(QPointF))); |
| QObject::connect(this, SIGNAL(doubleClicked(QPointF)), |
| areaSeries, SIGNAL(doubleClicked(QPointF))); |
| QObject::connect(areaSeries, SIGNAL(pointLabelsFormatChanged(QString)), |
| this, SLOT(handleUpdated())); |
| QObject::connect(areaSeries, SIGNAL(pointLabelsVisibilityChanged(bool)), |
| this, SLOT(handleUpdated())); |
| QObject::connect(areaSeries, SIGNAL(pointLabelsFontChanged(QFont)), |
| this, SLOT(handleUpdated())); |
| QObject::connect(areaSeries, SIGNAL(pointLabelsColorChanged(QColor)), |
| this, SLOT(handleUpdated())); |
| QObject::connect(areaSeries, SIGNAL(pointLabelsClippingChanged(bool)), |
| this, SLOT(handleUpdated())); |
| |
| handleUpdated(); |
| } |
| |
| AreaChartItem::~AreaChartItem() |
| { |
| delete m_upper; |
| delete m_lower; |
| } |
| |
| void AreaChartItem::setPresenter(ChartPresenter *presenter) |
| { |
| if (m_upper) |
| m_upper->setPresenter(presenter); |
| if (m_lower) |
| m_lower->setPresenter(presenter); |
| ChartItem::setPresenter(presenter); |
| } |
| |
| void AreaChartItem::setUpperSeries(QLineSeries *series) |
| { |
| delete m_upper; |
| if (series) |
| m_upper = new AreaBoundItem(this, series); |
| else |
| m_upper = 0; |
| if (m_upper) { |
| m_upper->setPresenter(presenter()); |
| fixEdgeSeriesDomain(m_upper); |
| } else { |
| updatePath(); |
| } |
| } |
| |
| void AreaChartItem::setLowerSeries(QLineSeries *series) |
| { |
| delete m_lower; |
| if (series) |
| m_lower = new AreaBoundItem(this, series); |
| else |
| m_lower = 0; |
| if (m_lower) { |
| m_lower->setPresenter(presenter()); |
| fixEdgeSeriesDomain(m_lower); |
| } else { |
| updatePath(); |
| } |
| } |
| |
| QRectF AreaChartItem::boundingRect() const |
| { |
| return m_rect; |
| } |
| |
| QPainterPath AreaChartItem::shape() const |
| { |
| return m_path; |
| } |
| |
| void AreaChartItem::updatePath() |
| { |
| QPainterPath path; |
| QRectF rect(QPointF(0,0),domain()->size()); |
| |
| if (m_upper) { |
| path = m_upper->path(); |
| |
| if (m_lower) { |
| // Note: Polarcharts draw area correctly only when both series have equal width or are |
| // fully displayed. If one series is partally off-chart, the connecting line between |
| // the series does not attach to the end of the partially hidden series but to the point |
| // where it intersects the axis line. The problem is especially noticeable when one of |
| // the series is entirely off-chart, in which case the connecting line connects two |
| // ends of the visible series. |
| // This happens because we get the paths from linechart, which omits off-chart segments. |
| // To properly fix, linechart would need to provide true full path, in right, left, |
| // and the rest portions to enable proper clipping. However, combining those to single |
| // visually unified area would be a nightmare, since they would have to be painted |
| // separately. |
| path.connectPath(m_lower->path().toReversed()); |
| } else { |
| QPointF first = path.pointAtPercent(0); |
| QPointF last = path.pointAtPercent(1); |
| if (presenter()->chartType() == QChart::ChartTypeCartesian) { |
| path.lineTo(last.x(), rect.bottom()); |
| path.lineTo(first.x(), rect.bottom()); |
| } else { // polar |
| path.lineTo(rect.center()); |
| } |
| } |
| path.closeSubpath(); |
| } |
| |
| // Only zoom in if the bounding rect of the path fits inside int limits. QWidget::update() uses |
| // a region that has to be compatible with QRect. |
| if (path.boundingRect().height() <= INT_MAX |
| && path.boundingRect().width() <= INT_MAX) { |
| prepareGeometryChange(); |
| m_path = path; |
| m_rect = path.boundingRect(); |
| update(); |
| } |
| } |
| |
| void AreaChartItem::handleUpdated() |
| { |
| setVisible(m_series->isVisible()); |
| m_pointsVisible = m_series->pointsVisible(); |
| m_linePen = m_series->pen(); |
| m_brush = m_series->brush(); |
| m_pointPen = m_series->pen(); |
| m_pointPen.setWidthF(2 * m_pointPen.width()); |
| setOpacity(m_series->opacity()); |
| m_pointLabelsFormat = m_series->pointLabelsFormat(); |
| m_pointLabelsVisible = m_series->pointLabelsVisible(); |
| m_pointLabelsFont = m_series->pointLabelsFont(); |
| m_pointLabelsColor = m_series->pointLabelsColor(); |
| bool labelClippingChanged = m_pointLabelsClipping != m_series->pointLabelsClipping(); |
| m_pointLabelsClipping = m_series->pointLabelsClipping(); |
| // Update whole chart in case label clipping changed as labels can be outside series area |
| if (labelClippingChanged) |
| m_series->chart()->update(); |
| else |
| update(); |
| } |
| |
| void AreaChartItem::handleDomainUpdated() |
| { |
| fixEdgeSeriesDomain(m_upper); |
| fixEdgeSeriesDomain(m_lower); |
| } |
| |
| void AreaChartItem::fixEdgeSeriesDomain(LineChartItem *edgeSeries) |
| { |
| if (edgeSeries) { |
| AbstractDomain* mainDomain = domain(); |
| AbstractDomain* edgeDomain = edgeSeries->domain(); |
| |
| if (edgeDomain->type() != mainDomain->type()) { |
| // Change the domain of edge series to the same type as the area series |
| edgeDomain = dataSet()->createDomain(mainDomain->type()); |
| edgeSeries->seriesPrivate()->setDomain(edgeDomain); |
| } |
| edgeDomain->setSize(mainDomain->size()); |
| edgeDomain->setRange(mainDomain->minX(), mainDomain->maxX(), mainDomain->minY(), mainDomain->maxY()); |
| edgeDomain->setReverseX(mainDomain->isReverseX()); |
| edgeDomain->setReverseY(mainDomain->isReverseY()); |
| edgeSeries->handleDomainUpdated(); |
| } |
| } |
| |
| void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) |
| { |
| Q_UNUSED(widget) |
| Q_UNUSED(option) |
| |
| painter->save(); |
| painter->setPen(m_linePen); |
| painter->setBrush(m_brush); |
| QRectF clipRect = QRectF(QPointF(0, 0), domain()->size()); |
| if (presenter()->chartType() == QChart::ChartTypePolar) |
| painter->setClipRegion(QRegion(clipRect.toRect(), QRegion::Ellipse)); |
| else |
| painter->setClipRect(clipRect); |
| |
| painter->drawPath(m_path); |
| if (m_pointsVisible) { |
| painter->setPen(m_pointPen); |
| if (m_upper) |
| painter->drawPoints(m_upper->geometryPoints()); |
| if (m_lower) |
| painter->drawPoints(m_lower->geometryPoints()); |
| } |
| |
| // Draw series point label |
| if (m_pointLabelsVisible) { |
| static const QString xPointTag(QLatin1String("@xPoint")); |
| static const QString yPointTag(QLatin1String("@yPoint")); |
| const int labelOffset = 2; |
| |
| if (m_pointLabelsClipping) |
| painter->setClipping(true); |
| else |
| painter->setClipping(false); |
| |
| QFont f(m_pointLabelsFont); |
| f.setPixelSize(QFontInfo(m_pointLabelsFont).pixelSize()); |
| painter->setFont(f); |
| painter->setPen(QPen(m_pointLabelsColor)); |
| QFontMetrics fm(painter->font()); |
| |
| QString pointLabel; |
| |
| if (m_series->upperSeries()) { |
| for (int i(0); i < m_series->upperSeries()->count(); i++) { |
| pointLabel = m_pointLabelsFormat; |
| pointLabel.replace(xPointTag, |
| presenter()->numberToString(m_series->upperSeries()->at(i).x())); |
| pointLabel.replace(yPointTag, |
| presenter()->numberToString(m_series->upperSeries()->at(i).y())); |
| |
| // Position text in relation to the point |
| int pointLabelWidth = fm.horizontalAdvance(pointLabel); |
| QPointF position(m_upper->geometryPoints().at(i)); |
| position.setX(position.x() - pointLabelWidth / 2); |
| position.setY(position.y() - m_series->upperSeries()->pen().width() / 2 |
| - labelOffset); |
| painter->drawText(position, pointLabel); |
| } |
| } |
| |
| if (m_series->lowerSeries()) { |
| for (int i(0); i < m_series->lowerSeries()->count(); i++) { |
| pointLabel = m_pointLabelsFormat; |
| pointLabel.replace(xPointTag, |
| presenter()->numberToString(m_series->lowerSeries()->at(i).x())); |
| pointLabel.replace(yPointTag, |
| presenter()->numberToString(m_series->lowerSeries()->at(i).y())); |
| |
| // Position text in relation to the point |
| int pointLabelWidth = fm.horizontalAdvance(pointLabel); |
| QPointF position(m_lower->geometryPoints().at(i)); |
| position.setX(position.x() - pointLabelWidth / 2); |
| position.setY(position.y() - m_series->lowerSeries()->pen().width() / 2 |
| - labelOffset); |
| painter->drawText(position, pointLabel); |
| } |
| } |
| } |
| |
| painter->restore(); |
| } |
| |
| void AreaChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event) |
| { |
| emit pressed(domain()->calculateDomainPoint(event->pos())); |
| m_lastMousePos = event->pos(); |
| m_mousePressed = true; |
| ChartItem::mousePressEvent(event); |
| } |
| |
| void AreaChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) |
| { |
| emit hovered(domain()->calculateDomainPoint(event->pos()), true); |
| event->accept(); |
| // QGraphicsItem::hoverEnterEvent(event); |
| } |
| |
| void AreaChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) |
| { |
| emit hovered(domain()->calculateDomainPoint(event->pos()), false); |
| event->accept(); |
| // QGraphicsItem::hoverEnterEvent(event); |
| } |
| |
| void AreaChartItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) |
| { |
| emit released(domain()->calculateDomainPoint(m_lastMousePos)); |
| if (m_mousePressed) |
| emit clicked(domain()->calculateDomainPoint(m_lastMousePos)); |
| m_mousePressed = false; |
| ChartItem::mouseReleaseEvent(event); |
| } |
| |
| void AreaChartItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) |
| { |
| emit doubleClicked(domain()->calculateDomainPoint(m_lastMousePos)); |
| ChartItem::mouseDoubleClickEvent(event); |
| } |
| |
| QT_CHARTS_END_NAMESPACE |
| |
| #include "moc_areachartitem_p.cpp" |