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

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