areachartitem.cpp
326 lines
| 12.5 KiB
| text/x-c
|
CppLexer
Miikka Heikkinen
|
r2854 | /**************************************************************************** | ||
Jani Honkonen
|
r794 | ** | ||
Miikka Heikkinen
|
r2854 | ** Copyright (C) 2016 The Qt Company Ltd. | ||
** Contact: https://www.qt.io/licensing/ | ||||
Jani Honkonen
|
r794 | ** | ||
Miikka Heikkinen
|
r2854 | ** This file is part of the Qt Charts module of the Qt Toolkit. | ||
Jani Honkonen
|
r794 | ** | ||
Miikka Heikkinen
|
r2854 | ** $QT_BEGIN_LICENSE:GPL$ | ||
Titta Heikkala
|
r2845 | ** 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 | ||||
Miikka Heikkinen
|
r2854 | ** 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. | ||||
Jani Honkonen
|
r794 | ** | ||
Titta Heikkala
|
r2845 | ** $QT_END_LICENSE$ | ||
** | ||||
Miikka Heikkinen
|
r2854 | ****************************************************************************/ | ||
Jani Honkonen
|
r794 | |||
Titta Heikkala
|
r2714 | #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 <QtGui/QPainter> | ||||
#include <QtWidgets/QGraphicsSceneMouseEvent> | ||||
#include <QtCore/QDebug> | ||||
Michal Klocek
|
r421 | |||
Titta Heikkala
|
r2712 | QT_CHARTS_BEGIN_NAMESPACE | ||
Michal Klocek
|
r421 | |||
Michal Klocek
|
r2273 | AreaChartItem::AreaChartItem(QAreaSeries *areaSeries, QGraphicsItem* item) | ||
Miikka Heikkinen
|
r2439 | : ChartItem(areaSeries->d_func(),item), | ||
Jani Honkonen
|
r2097 | m_series(areaSeries), | ||
m_upper(0), | ||||
m_lower(0), | ||||
Titta Heikkala
|
r2689 | m_pointsVisible(false), | ||
m_pointLabelsVisible(false), | ||||
Titta Heikkala
|
r2695 | m_pointLabelsFormat(areaSeries->pointLabelsFormat()), | ||
Titta Heikkala
|
r2689 | m_pointLabelsFont(areaSeries->pointLabelsFont()), | ||
Titta Heikkala
|
r2739 | m_pointLabelsColor(areaSeries->pointLabelsColor()), | ||
Titta Heikkala
|
r2815 | m_pointLabelsClipping(true), | ||
Titta Heikkala
|
r2739 | m_mousePressed(false) | ||
Michal Klocek
|
r421 | { | ||
Marek Rosa
|
r2264 | setAcceptHoverEvents(true); | ||
Titta Heikkala
|
r2739 | setFlag(QGraphicsItem::ItemIsSelectable, true); | ||
Michal Klocek
|
r564 | setZValue(ChartPresenter::LineChartZValue); | ||
Titta Heikkala
|
r2601 | if (m_series->upperSeries()) | ||
m_upper = new AreaBoundItem(this, m_series->upperSeries()); | ||||
Jani Honkonen
|
r2097 | if (m_series->lowerSeries()) | ||
Michal Klocek
|
r2273 | m_lower = new AreaBoundItem(this, m_series->lowerSeries()); | ||
Michal Klocek
|
r421 | |||
Jani Honkonen
|
r2097 | QObject::connect(m_series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated())); | ||
Tero Ahola
|
r1342 | QObject::connect(m_series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated())); | ||
Tero Ahola
|
r2067 | QObject::connect(m_series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated())); | ||
Jani Honkonen
|
r2097 | QObject::connect(this, SIGNAL(clicked(QPointF)), areaSeries, SIGNAL(clicked(QPointF))); | ||
Marek Rosa
|
r2264 | QObject::connect(this, SIGNAL(hovered(QPointF,bool)), areaSeries, SIGNAL(hovered(QPointF,bool))); | ||
Titta Heikkala
|
r2739 | 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))); | ||||
Titta Heikkala
|
r2689 | 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())); | ||||
Titta Heikkala
|
r2815 | QObject::connect(areaSeries, SIGNAL(pointLabelsClippingChanged(bool)), | ||
this, SLOT(handleUpdated())); | ||||
Michal Klocek
|
r421 | |||
handleUpdated(); | ||||
} | ||||
AreaChartItem::~AreaChartItem() | ||||
{ | ||||
delete m_upper; | ||||
delete m_lower; | ||||
Tero Ahola
|
r761 | } | ||
Michal Klocek
|
r421 | |||
Michal Klocek
|
r2273 | void AreaChartItem::setPresenter(ChartPresenter *presenter) | ||
{ | ||||
Titta Heikkala
|
r2601 | if (m_upper) | ||
m_upper->setPresenter(presenter); | ||||
Michal Klocek
|
r2273 | if (m_lower) { | ||
m_lower->setPresenter(presenter); | ||||
} | ||||
ChartItem::setPresenter(presenter); | ||||
} | ||||
Michal Klocek
|
r421 | QRectF AreaChartItem::boundingRect() const | ||
{ | ||||
return m_rect; | ||||
} | ||||
QPainterPath AreaChartItem::shape() const | ||||
{ | ||||
return m_path; | ||||
} | ||||
void AreaChartItem::updatePath() | ||||
{ | ||||
QPainterPath path; | ||||
Michal Klocek
|
r2273 | QRectF rect(QPointF(0,0),domain()->size()); | ||
Michal Klocek
|
r421 | |||
Tero Ahola
|
r1792 | path = m_upper->path(); | ||
Michal Klocek
|
r690 | |||
sauimone
|
r743 | if (m_lower) { | ||
Miikka Heikkinen
|
r2483 | // Note: Polarcharts always 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. | ||||
Tero Ahola
|
r1792 | path.connectPath(m_lower->path().toReversed()); | ||
sauimone
|
r743 | } else { | ||
Michal Klocek
|
r421 | QPointF first = path.pointAtPercent(0); | ||
QPointF last = path.pointAtPercent(1); | ||||
Miikka Heikkinen
|
r2483 | if (presenter()->chartType() == QChart::ChartTypeCartesian) { | ||
path.lineTo(last.x(), rect.bottom()); | ||||
path.lineTo(first.x(), rect.bottom()); | ||||
} else { // polar | ||||
path.lineTo(rect.center()); | ||||
} | ||||
Michal Klocek
|
r421 | } | ||
path.closeSubpath(); | ||||
Titta Heikkala
|
r2603 | |||
// 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(); | ||||
} | ||||
Michal Klocek
|
r421 | } | ||
void AreaChartItem::handleUpdated() | ||||
{ | ||||
Tero Ahola
|
r1346 | setVisible(m_series->isVisible()); | ||
Michal Klocek
|
r563 | m_pointsVisible = m_series->pointsVisible(); | ||
m_linePen = m_series->pen(); | ||||
Michal Klocek
|
r557 | m_brush = m_series->brush(); | ||
Michal Klocek
|
r563 | m_pointPen = m_series->pen(); | ||
Tero Ahola
|
r761 | m_pointPen.setWidthF(2 * m_pointPen.width()); | ||
Tero Ahola
|
r2067 | setOpacity(m_series->opacity()); | ||
Titta Heikkala
|
r2689 | m_pointLabelsFormat = m_series->pointLabelsFormat(); | ||
m_pointLabelsVisible = m_series->pointLabelsVisible(); | ||||
m_pointLabelsFont = m_series->pointLabelsFont(); | ||||
m_pointLabelsColor = m_series->pointLabelsColor(); | ||||
Titta Heikkala
|
r2815 | m_pointLabelsClipping = m_series->pointLabelsClipping(); | ||
Michal Klocek
|
r421 | update(); | ||
} | ||||
Michal Klocek
|
r1698 | void AreaChartItem::handleDomainUpdated() | ||
Michal Klocek
|
r421 | { | ||
Titta Heikkala
|
r2601 | if (m_upper) { | ||
AbstractDomain* d = m_upper->domain(); | ||||
d->setSize(domain()->size()); | ||||
d->setRange(domain()->minX(),domain()->maxX(),domain()->minY(),domain()->maxY()); | ||||
m_upper->handleDomainUpdated(); | ||||
} | ||||
Michal Klocek
|
r2273 | |||
Jani Honkonen
|
r2097 | if (m_lower) { | ||
Michal Klocek
|
r2285 | AbstractDomain* d = m_lower->domain(); | ||
Titta Heikkala
|
r2712 | d->setSize(domain()->size()); | ||
d->setRange(domain()->minX(),domain()->maxX(),domain()->minY(),domain()->maxY()); | ||||
m_lower->handleDomainUpdated(); | ||||
Michal Klocek
|
r1698 | } | ||
Michal Klocek
|
r421 | } | ||
void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) | ||||
{ | ||||
Tero Ahola
|
r611 | Q_UNUSED(widget) | ||
Q_UNUSED(option) | ||||
Tero Ahola
|
r1346 | painter->save(); | ||
painter->setPen(m_linePen); | ||||
painter->setBrush(m_brush); | ||||
Miikka Heikkinen
|
r2483 | QRectF clipRect = QRectF(QPointF(0, 0), domain()->size()); | ||
if (presenter()->chartType() == QChart::ChartTypePolar) | ||||
painter->setClipRegion(QRegion(clipRect.toRect(), QRegion::Ellipse)); | ||||
else | ||||
painter->setClipRect(clipRect); | ||||
Titta Heikkala
|
r2781 | |||
reversePainter(painter, clipRect); | ||||
Tero Ahola
|
r1346 | painter->drawPath(m_path); | ||
if (m_pointsVisible) { | ||||
Jani Honkonen
|
r2097 | painter->setPen(m_pointPen); | ||
painter->drawPoints(m_upper->geometryPoints()); | ||||
if (m_lower) | ||||
painter->drawPoints(m_lower->geometryPoints()); | ||||
Michal Klocek
|
r563 | } | ||
Titta Heikkala
|
r2689 | |||
Titta Heikkala
|
r2781 | reversePainter(painter, clipRect); | ||
Titta Heikkala
|
r2689 | // Draw series point label | ||
if (m_pointLabelsVisible) { | ||||
static const QString xPointTag(QLatin1String("@xPoint")); | ||||
static const QString yPointTag(QLatin1String("@yPoint")); | ||||
const int labelOffset = 2; | ||||
Titta Heikkala
|
r2815 | if (m_pointLabelsClipping) | ||
painter->setClipping(true); | ||||
else | ||||
painter->setClipping(false); | ||||
Titta Heikkala
|
r2689 | painter->setFont(m_pointLabelsFont); | ||
painter->setPen(QPen(m_pointLabelsColor)); | ||||
QFontMetrics fm(painter->font()); | ||||
Titta Heikkala
|
r2778 | QString pointLabel; | ||
Titta Heikkala
|
r2689 | |||
if (m_series->upperSeries()) { | ||||
for (int i(0); i < m_series->upperSeries()->count(); i++) { | ||||
Titta Heikkala
|
r2778 | pointLabel = m_pointLabelsFormat; | ||
Miikka Heikkinen
|
r2707 | pointLabel.replace(xPointTag, | ||
presenter()->numberToString(m_series->upperSeries()->at(i).x())); | ||||
pointLabel.replace(yPointTag, | ||||
presenter()->numberToString(m_series->upperSeries()->at(i).y())); | ||||
Titta Heikkala
|
r2689 | |||
// Position text in relation to the point | ||||
int pointLabelWidth = fm.width(pointLabel); | ||||
QPointF position(m_upper->geometryPoints().at(i)); | ||||
Titta Heikkala
|
r2781 | if (!seriesPrivate()->reverseXAxis()) | ||
position.setX(position.x() - pointLabelWidth / 2); | ||||
else | ||||
position.setX(domain()->size().width() - position.x() - pointLabelWidth / 2); | ||||
if (!seriesPrivate()->reverseYAxis()) { | ||||
position.setY(position.y() - m_series->upperSeries()->pen().width() / 2 | ||||
- labelOffset); | ||||
} else { | ||||
position.setY(domain()->size().height() - position.y() | ||||
- m_series->upperSeries()->pen().width() / 2 - labelOffset); | ||||
} | ||||
Titta Heikkala
|
r2689 | painter->drawText(position, pointLabel); | ||
} | ||||
} | ||||
if (m_series->lowerSeries()) { | ||||
for (int i(0); i < m_series->lowerSeries()->count(); i++) { | ||||
Titta Heikkala
|
r2778 | pointLabel = m_pointLabelsFormat; | ||
Miikka Heikkinen
|
r2707 | pointLabel.replace(xPointTag, | ||
presenter()->numberToString(m_series->lowerSeries()->at(i).x())); | ||||
pointLabel.replace(yPointTag, | ||||
presenter()->numberToString(m_series->lowerSeries()->at(i).y())); | ||||
Titta Heikkala
|
r2689 | |||
// Position text in relation to the point | ||||
int pointLabelWidth = fm.width(pointLabel); | ||||
QPointF position(m_lower->geometryPoints().at(i)); | ||||
Titta Heikkala
|
r2781 | if (!seriesPrivate()->reverseXAxis()) | ||
position.setX(position.x() - pointLabelWidth / 2); | ||||
else | ||||
position.setX(domain()->size().width() - position.x() - pointLabelWidth / 2); | ||||
if (!seriesPrivate()->reverseYAxis()) { | ||||
position.setY(position.y() - m_series->lowerSeries()->pen().width() / 2 | ||||
- labelOffset); | ||||
} else { | ||||
position.setY(domain()->size().height() - position.y() | ||||
- m_series->lowerSeries()->pen().width() / 2 - labelOffset); | ||||
} | ||||
Titta Heikkala
|
r2689 | painter->drawText(position, pointLabel); | ||
} | ||||
} | ||||
} | ||||
Tero Ahola
|
r1346 | painter->restore(); | ||
Michal Klocek
|
r421 | } | ||
sauimone
|
r743 | void AreaChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event) | ||
Michal Klocek
|
r571 | { | ||
Titta Heikkala
|
r2739 | emit pressed(m_upper->domain()->calculateDomainPoint(event->pos())); | ||
m_lastMousePos = event->pos(); | ||||
m_mousePressed = true; | ||||
Michal Klocek
|
r1747 | ChartItem::mousePressEvent(event); | ||
Michal Klocek
|
r571 | } | ||
Marek Rosa
|
r2264 | void AreaChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) | ||
{ | ||||
Michal Klocek
|
r2273 | emit hovered(domain()->calculateDomainPoint(event->pos()), true); | ||
Marek Rosa
|
r2264 | event->accept(); | ||
// QGraphicsItem::hoverEnterEvent(event); | ||||
} | ||||
void AreaChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) | ||||
{ | ||||
Michal Klocek
|
r2273 | emit hovered(domain()->calculateDomainPoint(event->pos()), false); | ||
Marek Rosa
|
r2264 | event->accept(); | ||
// QGraphicsItem::hoverEnterEvent(event); | ||||
} | ||||
Titta Heikkala
|
r2739 | void AreaChartItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) | ||
{ | ||||
Titta Heikkala
|
r2746 | emit released(m_upper->domain()->calculateDomainPoint(m_lastMousePos)); | ||
if (m_mousePressed) | ||||
emit clicked(m_upper->domain()->calculateDomainPoint(m_lastMousePos)); | ||||
Titta Heikkala
|
r2739 | m_mousePressed = false; | ||
ChartItem::mouseReleaseEvent(event); | ||||
} | ||||
void AreaChartItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) | ||||
{ | ||||
Titta Heikkala
|
r2746 | emit doubleClicked(m_upper->domain()->calculateDomainPoint(m_lastMousePos)); | ||
Titta Heikkala
|
r2739 | ChartItem::mouseDoubleClickEvent(event); | ||
} | ||||
Michal Klocek
|
r421 | #include "moc_areachartitem_p.cpp" | ||
Titta Heikkala
|
r2712 | QT_CHARTS_END_NAMESPACE | ||