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 | |
67 | QT_CHARTS_BEGIN_NAMESPACE |
68 | |
69 | /*! |
70 | Constructs a chart view object with the parent \a parent. |
71 | */ |
72 | |
73 | QChartView::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 | |
85 | QChartView::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 | */ |
96 | QChartView::~QChartView() |
97 | { |
98 | } |
99 | |
100 | /*! |
101 | Returns the pointer to the associated chart. |
102 | */ |
103 | QChart *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 | |
115 | void 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 | */ |
126 | void 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 | */ |
150 | QChartView::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 | */ |
163 | void 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 | */ |
186 | void 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 | */ |
216 | void 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) |
266 | void 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 | */ |
278 | void QChartView::resizeEvent(QResizeEvent *event) |
279 | { |
280 | QGraphicsView::resizeEvent(event); |
281 | d_ptr->resize(); |
282 | } |
283 | |
284 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
285 | |
286 | QChartViewPrivate::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 | |
306 | QChartViewPrivate::~QChartViewPrivate() |
307 | { |
308 | } |
309 | |
310 | void 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 | |
326 | void 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 | |
350 | QT_CHARTS_END_NAMESPACE |
351 | |
352 | #include "moc_qchartview.cpp" |
353 | |