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 | |
47 | QT_BEGIN_NAMESPACE |
48 | |
49 | /*! |
50 | Constructs a chart view object with the parent \a parent. |
51 | */ |
52 | |
53 | QChartView::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 | |
65 | QChartView::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 | */ |
76 | QChartView::~QChartView() |
77 | { |
78 | } |
79 | |
80 | /*! |
81 | Returns the pointer to the associated chart. |
82 | */ |
83 | QChart *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 | |
95 | void 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 | */ |
106 | void 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 | */ |
130 | QChartView::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 | */ |
143 | void 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 | */ |
171 | void 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 | */ |
201 | void 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) |
251 | void 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 | */ |
263 | void QChartView::resizeEvent(QResizeEvent *event) |
264 | { |
265 | QGraphicsView::resizeEvent(event); |
266 | d_ptr->resize(); |
267 | } |
268 | |
269 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
270 | |
271 | QChartViewPrivate::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 | |
291 | QChartViewPrivate::~QChartViewPrivate() |
292 | { |
293 | } |
294 | |
295 | void 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 | |
311 | void 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 | |
335 | QT_END_NAMESPACE |
336 | |
337 | #include "moc_qchartview.cpp" |
338 | |