1/****************************************************************************
2**
3** Copyright (C) 2017 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/QChartView>
31#include <private/qchartview_p.h>
32#include <private/qchart_p.h>
33#include <QtWidgets/QGraphicsScene>
34#include <QtWidgets/QRubberBand>
35
36/*!
37 \enum QChartView::RubberBand
38
39 This enum describes the different types of rubber band effects that can
40 be applied to the rectangular zooming area.
41
42 \value NoRubberBand
43 No zooming area is specified, and therefore zooming is not enabled.
44 \value VerticalRubberBand
45 The rubber band is locked to the size of the chart horizontally
46 and can be pulled vertically to specify the zooming area.
47 \value HorizontalRubberBand
48 The rubber band is locked to the size of the chart vertically
49 and can be pulled horizontally to specify the zooming area.
50 \value RectangleRubberBand
51 The rubber band is fixed to the point that was clicked and can be
52 pulled both vertically and horizontally.
53*/
54
55/*!
56 \class QChartView
57 \inmodule QtCharts
58 \brief The QChartView is a standalone widget that can display charts.
59
60 A chart view does not require a QGraphicsScene object to work. To display
61 a chart in an existing QGraphicsScene, the QChart or QPolarChart class should
62 be used instead.
63
64 \sa QChart, QPolarChart
65*/
66
67QT_CHARTS_BEGIN_NAMESPACE
68
69/*!
70 Constructs a chart view object with the parent \a parent.
71*/
72
73QChartView::QChartView(QWidget *parent)
74 : QGraphicsView(parent),
75 d_ptr(new QChartViewPrivate(this))
76{
77
78}
79
80/*!
81 Constructs a chart view object with the parent \a parent to display the
82 chart \a chart. The ownership of the chart is passed to the chart view.
83*/
84
85QChartView::QChartView(QChart *chart, QWidget *parent)
86 : QGraphicsView(parent),
87 d_ptr(new QChartViewPrivate(this, chart))
88{
89
90}
91
92
93/*!
94 Deletes the chart view object and the associated chart.
95*/
96QChartView::~QChartView()
97{
98}
99
100/*!
101 Returns the pointer to the associated chart.
102*/
103QChart *QChartView::chart() const
104{
105 return d_ptr->m_chart;
106}
107
108/*!
109 Sets the current chart to \a chart. The ownership of the new chart is passed to
110 the chart view and the ownership of the previous chart is released.
111
112 To avoid memory leaks, the previous chart must be deleted.
113*/
114
115void QChartView::setChart(QChart *chart)
116{
117 d_ptr->setChart(chart);
118}
119
120/*!
121 Sets the rubber band flags to \a rubberBand.
122 The selected flags determine the way zooming is performed.
123
124 \note Rubber band zooming is not supported for polar charts.
125*/
126void QChartView::setRubberBand(const RubberBands &rubberBand)
127{
128#ifndef QT_NO_RUBBERBAND
129 d_ptr->m_rubberBandFlags = rubberBand;
130
131 if (!d_ptr->m_rubberBandFlags) {
132 delete d_ptr->m_rubberBand;
133 d_ptr->m_rubberBand = nullptr;
134 return;
135 }
136
137 if (!d_ptr->m_rubberBand) {
138 d_ptr->m_rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
139 d_ptr->m_rubberBand->setEnabled(true);
140 }
141#else
142 Q_UNUSED(rubberBand);
143 qWarning("Unable to set rubber band because Qt is configured without it.");
144#endif
145}
146
147/*!
148 Returns the rubber band flags that are currently being used by the chart view.
149*/
150QChartView::RubberBands QChartView::rubberBand() const
151{
152 return d_ptr->m_rubberBandFlags;
153}
154
155/*!
156 If the left mouse button is pressed and the rubber band is enabled, the event
157 \a event is accepted and the rubber band is displayed on the screen. This
158 enables the user to select the zoom area.
159
160 If some other mouse button is pressed or the rubber band is disabled, the event
161 is passed to QGraphicsView::mousePressEvent().
162*/
163void QChartView::mousePressEvent(QMouseEvent *event)
164{
165#ifndef QT_NO_RUBBERBAND
166 QRectF plotArea = d_ptr->m_chart->plotArea();
167 if (d_ptr->m_rubberBand && d_ptr->m_rubberBand->isEnabled()
168 && event->button() == Qt::LeftButton && plotArea.contains(p: event->pos())) {
169 d_ptr->m_rubberBandOrigin = event->pos();
170 d_ptr->m_rubberBand->setGeometry(QRect(d_ptr->m_rubberBandOrigin, QSize()));
171 d_ptr->m_rubberBand->show();
172 event->accept();
173 } else {
174#endif
175 QGraphicsView::mousePressEvent(event);
176#ifndef QT_NO_RUBBERBAND
177 }
178#endif
179}
180
181/*!
182 If the rubber band rectangle is displayed in the press event specified by
183 \a event, the event data is used to update the rubber band geometry.
184 Otherwise, the default QGraphicsView::mouseMoveEvent() implementation is called.
185*/
186void QChartView::mouseMoveEvent(QMouseEvent *event)
187{
188#ifndef QT_NO_RUBBERBAND
189 if (d_ptr->m_rubberBand && d_ptr->m_rubberBand->isVisible()) {
190 QRect rect = d_ptr->m_chart->plotArea().toRect();
191 int width = event->pos().x() - d_ptr->m_rubberBandOrigin.x();
192 int height = event->pos().y() - d_ptr->m_rubberBandOrigin.y();
193 if (!d_ptr->m_rubberBandFlags.testFlag(flag: VerticalRubberBand)) {
194 d_ptr->m_rubberBandOrigin.setY(rect.top());
195 height = rect.height();
196 }
197 if (!d_ptr->m_rubberBandFlags.testFlag(flag: HorizontalRubberBand)) {
198 d_ptr->m_rubberBandOrigin.setX(rect.left());
199 width = rect.width();
200 }
201 d_ptr->m_rubberBand->setGeometry(QRect(d_ptr->m_rubberBandOrigin.x(), d_ptr->m_rubberBandOrigin.y(), width, height).normalized());
202 } else {
203#endif
204 QGraphicsView::mouseMoveEvent(event);
205#ifndef QT_NO_RUBBERBAND
206 }
207#endif
208}
209
210/*!
211 If the left mouse button is released and the rubber band is enabled, the
212 event \a event is accepted and the view is zoomed into the rectangle
213 specified by the rubber band. If releasing the right mouse button triggered
214 the event, the view is zoomed out.
215*/
216void QChartView::mouseReleaseEvent(QMouseEvent *event)
217{
218#ifndef QT_NO_RUBBERBAND
219 if (d_ptr->m_rubberBand && d_ptr->m_rubberBand->isVisible()) {
220 if (event->button() == Qt::LeftButton) {
221 d_ptr->m_rubberBand->hide();
222 QRectF rect = d_ptr->m_rubberBand->geometry();
223 // Since plotArea uses QRectF and rubberband uses QRect, we can't just blindly use
224 // rubberband's dimensions for vertical and horizontal rubberbands, where one
225 // dimension must match the corresponding plotArea dimension exactly.
226 if (d_ptr->m_rubberBandFlags == VerticalRubberBand) {
227 rect.setX(d_ptr->m_chart->plotArea().x());
228 rect.setWidth(d_ptr->m_chart->plotArea().width());
229 } else if (d_ptr->m_rubberBandFlags == HorizontalRubberBand) {
230 rect.setY(d_ptr->m_chart->plotArea().y());
231 rect.setHeight(d_ptr->m_chart->plotArea().height());
232 }
233 d_ptr->m_chart->zoomIn(rect);
234 event->accept();
235 }
236
237 } else if (d_ptr->m_rubberBand && event->button() == Qt::RightButton) {
238 // If vertical or horizontal rubberband mode, restrict zoom out to specified axis.
239 // Since there is no suitable API for that, use zoomIn with rect bigger than the
240 // plot area.
241 if (d_ptr->m_rubberBandFlags == VerticalRubberBand
242 || d_ptr->m_rubberBandFlags == HorizontalRubberBand) {
243 QRectF rect = d_ptr->m_chart->plotArea();
244 if (d_ptr->m_rubberBandFlags == VerticalRubberBand) {
245 qreal adjustment = rect.height() / 2;
246 rect.adjust(xp1: 0, yp1: -adjustment, xp2: 0, yp2: adjustment);
247 } else if (d_ptr->m_rubberBandFlags == HorizontalRubberBand) {
248 qreal adjustment = rect.width() / 2;
249 rect.adjust(xp1: -adjustment, yp1: 0, xp2: adjustment, yp2: 0);
250 }
251 d_ptr->m_chart->zoomIn(rect);
252 } else {
253 d_ptr->m_chart->zoomOut();
254 }
255 event->accept();
256 } else {
257#endif
258 QGraphicsView::mouseReleaseEvent(event);
259#ifndef QT_NO_RUBBERBAND
260 }
261#endif
262}
263
264#ifdef Q_OS_MACOS
265#if QT_CONFIG(wheelevent)
266void QChartView::wheelEvent(QWheelEvent *event)
267{
268 Q_UNUSED(event)
269 // We just need to override wheelEvent, or scrolling won't work correctly on macOS trackpad
270 // (QTBUG-77403)
271}
272#endif
273#endif
274
275/*!
276 Resizes and updates the chart area using the data specified by \a event.
277*/
278void QChartView::resizeEvent(QResizeEvent *event)
279{
280 QGraphicsView::resizeEvent(event);
281 d_ptr->resize();
282}
283
284///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
285
286QChartViewPrivate::QChartViewPrivate(QChartView *q, QChart *chart)
287 : q_ptr(q),
288 m_scene(new QGraphicsScene(q)),
289 m_chart(chart),
290#ifndef QT_NO_RUBBERBAND
291 m_rubberBand(nullptr),
292#endif
293 m_rubberBandFlags(QChartView::NoRubberBand)
294{
295 q_ptr->setFrameShape(QFrame::NoFrame);
296 q_ptr->setBackgroundRole(QPalette::Window);
297 q_ptr->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
298 q_ptr->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
299 q_ptr->setScene(m_scene);
300 q_ptr->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Expanding);
301 if (!m_chart)
302 m_chart = new QChart();
303 m_scene->addItem(item: m_chart);
304}
305
306QChartViewPrivate::~QChartViewPrivate()
307{
308}
309
310void QChartViewPrivate::setChart(QChart *chart)
311{
312 Q_ASSERT(chart);
313
314 if (m_chart == chart)
315 return;
316
317 if (m_chart)
318 m_scene->removeItem(item: m_chart);
319
320 m_chart = chart;
321 m_scene->addItem(item: m_chart);
322
323 resize();
324}
325
326void QChartViewPrivate::resize()
327{
328 // Fit the chart into view if it has been rotated
329 qreal sinA = qAbs(t: q_ptr->transform().m21());
330 qreal cosA = qAbs(t: q_ptr->transform().m11());
331 QSize chartSize = q_ptr->size();
332
333 if (sinA == 1.0) {
334 chartSize.setHeight(q_ptr->size().width());
335 chartSize.setWidth(q_ptr->size().height());
336 } else if (sinA != 0.0) {
337 // Non-90 degree rotation, find largest square chart that can fit into the view.
338 qreal minDimension = qMin(a: q_ptr->size().width(), b: q_ptr->size().height());
339 qreal h = (minDimension - (minDimension / ((sinA / cosA) + 1.0))) / sinA;
340 chartSize.setHeight(h);
341 chartSize.setWidth(h);
342 }
343
344 m_chart->resize(size: chartSize);
345 q_ptr->setMinimumSize(m_chart->minimumSize().toSize().expandedTo(otherSize: q_ptr->minimumSize()));
346 q_ptr->setMaximumSize(q_ptr->maximumSize().boundedTo(otherSize: m_chart->maximumSize().toSize()));
347 q_ptr->setSceneRect(m_chart->geometry());
348}
349
350QT_CHARTS_END_NAMESPACE
351
352#include "moc_qchartview.cpp"
353

source code of qtcharts/src/charts/qchartview.cpp