1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <private/scatterchartitem_p.h> |
5 | #include <QtCharts/QScatterSeries> |
6 | #include <private/qscatterseries_p.h> |
7 | #include <private/chartpresenter_p.h> |
8 | #include <private/abstractdomain_p.h> |
9 | #include <QtCharts/QChart> |
10 | #include <QtGui/QPainter> |
11 | #include <QtWidgets/QGraphicsScene> |
12 | #include <QtCore/QDebug> |
13 | #include <QtWidgets/QGraphicsSceneMouseEvent> |
14 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | namespace { |
18 | constexpr short STAR_SPIKES = 5; |
19 | } |
20 | |
21 | ScatterChartItem::ScatterChartItem(QScatterSeries *series, QGraphicsItem *item) |
22 | : XYChart(series,item), |
23 | m_series(series), |
24 | m_items(this), |
25 | m_visible(true), |
26 | m_markerShape(QScatterSeries::MarkerShapeRectangle), |
27 | m_pointsVisible(true), |
28 | m_pointLabelsVisible(false), |
29 | m_markerSize(series->markerSize()), |
30 | m_pointLabelsFormat(series->pointLabelsFormat()), |
31 | m_pointLabelsFont(series->pointLabelsFont()), |
32 | m_pointLabelsColor(series->pointLabelsColor()), |
33 | m_pointLabelsClipping(true), |
34 | m_lastHoveredPoint(QPointF(qQNaN(), qQNaN())), |
35 | m_mousePressed(false) |
36 | { |
37 | connect(sender: series->d_func(), signal: &QXYSeriesPrivate::seriesUpdated, |
38 | context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
39 | connect(sender: series, signal: &QXYSeries::lightMarkerChanged, context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
40 | connect(sender: series, signal: &QXYSeries::selectedLightMarkerChanged, context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
41 | connect(sender: series, signal: &QXYSeries::markerSizeChanged, context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
42 | connect(sender: series, signal: &QXYSeries::visibleChanged, context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
43 | connect(sender: series, signal: &QXYSeries::opacityChanged, context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
44 | connect(sender: series, signal: &QXYSeries::pointLabelsFormatChanged, |
45 | context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
46 | connect(sender: series, signal: &QXYSeries::pointLabelsVisibilityChanged, |
47 | context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
48 | connect(sender: series, signal: &QXYSeries::pointLabelsFontChanged, |
49 | context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
50 | connect(sender: series, signal: &QXYSeries::pointLabelsColorChanged, |
51 | context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
52 | connect(sender: series, signal: &QXYSeries::pointLabelsClippingChanged, |
53 | context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
54 | connect(sender: series, signal: &QXYSeries::selectedColorChanged, |
55 | context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
56 | connect(sender: series, signal: &QXYSeries::selectedPointsChanged, |
57 | context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
58 | connect(sender: series, signal: &QScatterSeries::pointsConfigurationChanged, |
59 | context: this, slot: &ScatterChartItem::handleSeriesUpdated); |
60 | |
61 | setZValue(ChartPresenter::ScatterSeriesZValue); |
62 | setFlags(QGraphicsItem::ItemClipsChildrenToShape | QGraphicsItem::ItemIsSelectable); |
63 | |
64 | handleSeriesUpdated(); |
65 | |
66 | m_items.setHandlesChildEvents(false); |
67 | } |
68 | |
69 | QRectF ScatterChartItem::boundingRect() const |
70 | { |
71 | return m_rect; |
72 | } |
73 | |
74 | void ScatterChartItem::createPoints(int count) |
75 | { |
76 | for (int i = 0; i < count; ++i) { |
77 | |
78 | QGraphicsItem *item = 0; |
79 | |
80 | switch (m_markerShape) { |
81 | case QScatterSeries::MarkerShapeCircle: { |
82 | item = new ChartMarker<QGraphicsEllipseItem>(0, 0, m_markerSize, m_markerSize, this); |
83 | break; |
84 | } |
85 | case QScatterSeries::MarkerShapeRectangle: { |
86 | item = new ChartMarker<QGraphicsRectItem>(0, 0, m_markerSize, m_markerSize, this); |
87 | break; |
88 | } |
89 | case QScatterSeries::MarkerShapeRotatedRectangle: { |
90 | item = new RotatedRectangleMarker(0, 0, m_markerSize, m_markerSize, this); |
91 | break; |
92 | } |
93 | case QScatterSeries::MarkerShapeTriangle: { |
94 | item = new TriangleMarker(0, 0, m_markerSize, m_markerSize, this); |
95 | break; |
96 | } |
97 | case QScatterSeries::MarkerShapeStar: { |
98 | item = new StarMarker(0, 0, m_markerSize, m_markerSize, this); |
99 | break; |
100 | } |
101 | case QScatterSeries::MarkerShapePentagon: { |
102 | item = new PentagonMarker(0, 0, m_markerSize, m_markerSize, this); |
103 | break; |
104 | } |
105 | default: |
106 | qWarning() << "Unsupported marker type" ; |
107 | break; |
108 | } |
109 | |
110 | m_items.addToGroup(item); |
111 | } |
112 | } |
113 | |
114 | void ScatterChartItem::deletePoints(int count) |
115 | { |
116 | QList<QGraphicsItem *> items = m_items.childItems(); |
117 | |
118 | for (int i = 0; i < count; ++i) { |
119 | QGraphicsItem *item = items.takeLast(); |
120 | m_markerMap.remove(key: item); |
121 | delete(item); |
122 | } |
123 | } |
124 | |
125 | void ScatterChartItem::resizeMarker(QGraphicsItem *marker, const int size) |
126 | { |
127 | switch (m_markerShape) { |
128 | case QScatterSeries::MarkerShapeCircle: { |
129 | QGraphicsEllipseItem *item = static_cast<QGraphicsEllipseItem *>(marker); |
130 | item->setRect(ax: 0, ay: 0, w: size, h: size); |
131 | break; |
132 | } |
133 | case QScatterSeries::MarkerShapeRectangle: { |
134 | QGraphicsRectItem *item = static_cast<QGraphicsRectItem *>(marker); |
135 | item->setRect(ax: 0, ay: 0, w: size, h: size); |
136 | break; |
137 | } |
138 | case QScatterSeries::MarkerShapeRotatedRectangle: { |
139 | QGraphicsPolygonItem *item = static_cast<QGraphicsPolygonItem *>(marker); |
140 | item->setPolygon(RotatedRectangleMarker::polygon(x: 0, y: 0, w: size, h: size)); |
141 | break; |
142 | } |
143 | case QScatterSeries::MarkerShapeTriangle: { |
144 | QGraphicsPolygonItem *item = static_cast<QGraphicsPolygonItem *>(marker); |
145 | item->setPolygon(TriangleMarker::polygon(x: 0, y: 0, w: size, h: size)); |
146 | break; |
147 | } |
148 | case QScatterSeries::MarkerShapeStar: { |
149 | QGraphicsPolygonItem *item = static_cast<QGraphicsPolygonItem *>(marker); |
150 | item->setPolygon(StarMarker::polygon(x: 0, y: 0, w: size, h: size)); |
151 | break; |
152 | } |
153 | case QScatterSeries::MarkerShapePentagon: { |
154 | QGraphicsPolygonItem *item = static_cast<QGraphicsPolygonItem *>(marker); |
155 | item->setPolygon(PentagonMarker::polygon(x: 0, y: 0, w: size, h: size)); |
156 | break; |
157 | } |
158 | default: |
159 | qWarning() << "Unsupported marker type" ; |
160 | break; |
161 | } |
162 | } |
163 | |
164 | void ScatterChartItem::markerSelected(QGraphicsItem *marker) |
165 | { |
166 | emit XYChart::clicked(point: m_markerMap[marker]); |
167 | } |
168 | |
169 | void ScatterChartItem::markerHovered(QGraphicsItem *marker, bool state) |
170 | { |
171 | emit XYChart::hovered(point: m_markerMap[marker], state); |
172 | } |
173 | |
174 | void ScatterChartItem::markerPressed(QGraphicsItem *marker) |
175 | { |
176 | emit XYChart::pressed(point: m_markerMap[marker]); |
177 | } |
178 | |
179 | void ScatterChartItem::markerReleased(QGraphicsItem *marker) |
180 | { |
181 | emit XYChart::released(point: m_markerMap[marker]); |
182 | } |
183 | |
184 | void ScatterChartItem::markerDoubleClicked(QGraphicsItem *marker) |
185 | { |
186 | emit XYChart::doubleClicked(point: m_markerMap[marker]); |
187 | } |
188 | |
189 | void ScatterChartItem::updateGeometry() |
190 | { |
191 | if (m_series->useOpenGL()) { |
192 | if (m_items.childItems().size()) |
193 | deletePoints(count: m_items.childItems().size()); |
194 | if (!m_rect.isEmpty()) { |
195 | prepareGeometryChange(); |
196 | // Changed signal seems to trigger even with empty region |
197 | m_rect = QRectF(); |
198 | } |
199 | update(); |
200 | return; |
201 | } |
202 | |
203 | const QList<QPointF> &points = geometryPoints(); |
204 | |
205 | if (points.size() == 0) { |
206 | deletePoints(count: m_items.childItems().size()); |
207 | return; |
208 | } |
209 | |
210 | int diff = m_items.childItems().size() - points.size(); |
211 | |
212 | if (diff > 0) |
213 | deletePoints(count: diff); |
214 | else if (diff < 0) |
215 | createPoints(count: -diff); |
216 | |
217 | if (diff != 0) |
218 | handleSeriesUpdated(); |
219 | |
220 | QList<QGraphicsItem *> items = m_items.childItems(); |
221 | |
222 | QRectF clipRect(QPointF(0,0),domain()->size()); |
223 | |
224 | // Only zoom in if the clipRect fits inside int limits. QWidget::update() uses |
225 | // a region that has to be compatible with QRect. |
226 | if (clipRect.height() <= INT_MAX |
227 | && clipRect.width() <= INT_MAX) { |
228 | const QList<bool> offGridStatus = offGridStatusVector(); |
229 | const int seriesLastIndex = m_series->count() - 1; |
230 | |
231 | for (int i = 0; i < points.size(); i++) { |
232 | QAbstractGraphicsShapeItem *item = |
233 | static_cast<QAbstractGraphicsShapeItem *>(items.at(i)); |
234 | const QPointF &point = points.at(i); |
235 | |
236 | if (m_pointsConfiguration.contains(key: i)) { |
237 | const auto &conf = m_pointsConfiguration[i]; |
238 | if (conf.contains(key: QXYSeries::PointConfiguration::Size)) |
239 | resizeMarker( |
240 | marker: item, |
241 | size: m_pointsConfiguration[i][QXYSeries::PointConfiguration::Size].toReal()); |
242 | } |
243 | |
244 | const QRectF &rect = item->boundingRect(); |
245 | // During remove animation series may have different number of points, |
246 | // so ensure we don't go over the index. Animation handling itself ensures that |
247 | // if there is actually no points in the series, then it won't generate a fake point, |
248 | // so we can be assured there is always at least one point in m_series here. |
249 | // Note that marker map values can be technically incorrect during the animation, |
250 | // if it was caused by an insert, but this shouldn't be a problem as the points are |
251 | // fake anyway. After remove animation stops, geometry is updated to correct one. |
252 | m_markerMap[item] = m_series->at(index: qMin(a: seriesLastIndex, b: i)); |
253 | QPointF position; |
254 | position.setX(point.x() - rect.width() / 2); |
255 | position.setY(point.y() - rect.height() / 2); |
256 | item->setPos(position); |
257 | |
258 | if (!m_visible || offGridStatus.at(i)) { |
259 | item->setVisible(false); |
260 | } else { |
261 | bool drawPoint = m_pointsVisible; |
262 | if (m_pointsConfiguration.contains(key: i)) { |
263 | const auto &conf = m_pointsConfiguration[i]; |
264 | |
265 | if (conf.contains(key: QXYSeries::PointConfiguration::Visibility)) { |
266 | drawPoint |
267 | = m_pointsConfiguration[i][QXYSeries::PointConfiguration::Visibility] |
268 | .toBool(); |
269 | } |
270 | |
271 | if (drawPoint && conf.contains(key: QXYSeries::PointConfiguration::Color)) { |
272 | item->setBrush( |
273 | m_pointsConfiguration[i][QXYSeries::PointConfiguration::Color] |
274 | .value<QColor>()); |
275 | } |
276 | } |
277 | |
278 | if (m_series->isPointSelected(index: i)) { |
279 | drawPoint = m_series->selectedLightMarker().isNull(); |
280 | if (drawPoint && m_selectedColor.isValid()) |
281 | item->setBrush(m_selectedColor); |
282 | } |
283 | |
284 | item->setVisible(drawPoint); |
285 | } |
286 | } |
287 | |
288 | prepareGeometryChange(); |
289 | m_rect = clipRect; |
290 | } |
291 | } |
292 | |
293 | void ScatterChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event) |
294 | { |
295 | QPointF matchedP = matchForLightMarker(eventPos: event->pos()); |
296 | if (!qIsNaN(d: matchedP.x())) { |
297 | emit XYChart::pressed(point: matchedP); |
298 | m_lastMousePos = event->pos(); |
299 | m_mousePressed = true; |
300 | } else { |
301 | event->ignore(); |
302 | } |
303 | |
304 | QGraphicsItem::mousePressEvent(event); |
305 | } |
306 | |
307 | void ScatterChartItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) |
308 | { |
309 | QPointF matchedP = matchForLightMarker(eventPos: event->pos()); |
310 | if (!qIsNaN(d: matchedP.x())) { |
311 | if (matchedP != m_lastHoveredPoint) { |
312 | if (!qIsNaN(d: m_lastHoveredPoint.x())) |
313 | emit XYChart::hovered(point: m_lastHoveredPoint, state: false); |
314 | |
315 | m_lastHoveredPoint = matchedP; |
316 | emit XYChart::hovered(point: matchedP, state: true); |
317 | } |
318 | } else if (!qIsNaN(d: m_lastHoveredPoint.x())) { |
319 | emit XYChart::hovered(point: m_lastHoveredPoint, state: false); |
320 | m_lastHoveredPoint = QPointF(qQNaN(), qQNaN()); |
321 | } |
322 | |
323 | QGraphicsItem::hoverMoveEvent(event); |
324 | } |
325 | |
326 | void ScatterChartItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) |
327 | { |
328 | QPointF result; |
329 | QPointF matchedP = matchForLightMarker(eventPos: m_lastMousePos); |
330 | if (!qIsNaN(d: matchedP.x()) && m_mousePressed) { |
331 | result = matchedP; |
332 | emit XYChart::released(point: result); |
333 | emit XYChart::clicked(point: result); |
334 | } |
335 | |
336 | m_mousePressed = false; |
337 | QGraphicsItem::mouseReleaseEvent(event); |
338 | } |
339 | |
340 | void ScatterChartItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) |
341 | { |
342 | QPointF matchedP = matchForLightMarker(eventPos: event->pos()); |
343 | if (!qIsNaN(d: matchedP.x())) |
344 | emit XYChart::doubleClicked(point: matchedP); |
345 | else |
346 | emit XYChart::doubleClicked(point: domain()->calculateDomainPoint(point: m_lastMousePos)); |
347 | |
348 | QGraphicsItem::mouseDoubleClickEvent(event); |
349 | } |
350 | |
351 | void ScatterChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) |
352 | { |
353 | Q_UNUSED(option); |
354 | Q_UNUSED(widget); |
355 | |
356 | if (m_series->useOpenGL()) |
357 | return; |
358 | |
359 | QRectF clipRect = QRectF(QPointF(0, 0), domain()->size()); |
360 | |
361 | painter->save(); |
362 | painter->setClipRect(clipRect); |
363 | |
364 | // Draw markers if a marker or marker for selected points only has been |
365 | // set (set to QImage() to disable) |
366 | if (!m_series->lightMarker().isNull() || !m_series->selectedLightMarker().isNull()) { |
367 | const QImage &marker = m_series->lightMarker(); |
368 | const QImage &selectedMarker = m_series->selectedLightMarker(); |
369 | qreal markerHalfSize = m_markerSize / 2.0; |
370 | |
371 | for (int i = 0; i < m_points.size(); ++i) { |
372 | // Documentation of light markers says that points visibility and |
373 | // light markers are independent features. Therefore m_pointsVisible |
374 | // is not used here as light markers are drawn if lightMarker is not null. |
375 | // However points visibility configuration can be still used here. |
376 | bool drawPoint = !m_series->lightMarker().isNull(); |
377 | if (m_pointsConfiguration.contains(key: i)) { |
378 | const auto &conf = m_pointsConfiguration[i]; |
379 | |
380 | if (conf.contains(key: QXYSeries::PointConfiguration::Visibility)) { |
381 | drawPoint = m_pointsConfiguration[i][QXYSeries::PointConfiguration::Visibility] |
382 | .toBool(); |
383 | } |
384 | } |
385 | |
386 | bool drawSelectedPoint = false; |
387 | if (m_series->isPointSelected(index: i)) { |
388 | drawPoint = true; |
389 | drawSelectedPoint = !selectedMarker.isNull(); |
390 | } |
391 | if (drawPoint) { |
392 | const QRectF rect(m_points[i].x() - markerHalfSize, |
393 | m_points[i].y() - markerHalfSize, |
394 | m_markerSize, m_markerSize); |
395 | painter->drawImage(r: rect, image: drawSelectedPoint ? selectedMarker : marker); |
396 | } |
397 | } |
398 | } |
399 | |
400 | if (m_series->bestFitLineVisible()) |
401 | m_series->d_func()->drawBestFitLine(painter, clipRect); |
402 | |
403 | m_series->d_func()->drawPointLabels(painter, allPoints: m_points, offset: m_series->markerSize() / 2 + m_series->pen().width()); |
404 | |
405 | painter->restore(); |
406 | } |
407 | |
408 | void ScatterChartItem::setPen(const QPen &pen) |
409 | { |
410 | QPen penToUse(pen); |
411 | if (!m_series->lightMarker().isNull()) |
412 | penToUse.setColor(Qt::transparent); |
413 | |
414 | const auto &items = m_items.childItems(); |
415 | for (auto item : items) |
416 | static_cast<QAbstractGraphicsShapeItem*>(item)->setPen(penToUse); |
417 | } |
418 | |
419 | void ScatterChartItem::setBrush(const QBrush &brush) |
420 | { |
421 | const auto &items = m_items.childItems(); |
422 | for (auto item : items) { |
423 | if (m_series->lightMarker().isNull()) { |
424 | if (m_markerMap.contains(key: item)) { |
425 | auto index = m_series->points().indexOf(t: m_markerMap[item]); |
426 | if (m_selectedPoints.contains(t: index) && m_selectedColor.isValid()) { |
427 | static_cast<QAbstractGraphicsShapeItem *>(item)->setBrush(m_selectedColor); |
428 | } else { |
429 | bool useBrush = true; |
430 | if (m_pointsConfiguration.contains(key: index)) { |
431 | const auto &conf = m_pointsConfiguration[index]; |
432 | if (conf.contains(key: QXYSeries::PointConfiguration::Color)) |
433 | useBrush = false; |
434 | } |
435 | |
436 | if (useBrush) |
437 | static_cast<QAbstractGraphicsShapeItem *>(item)->setBrush(brush); |
438 | } |
439 | } else { |
440 | static_cast<QAbstractGraphicsShapeItem *>(item)->setBrush(brush); |
441 | } |
442 | } else { |
443 | QBrush brushToUse; |
444 | brushToUse.setColor(Qt::transparent); |
445 | static_cast<QAbstractGraphicsShapeItem *>(item)->setBrush(brushToUse); |
446 | } |
447 | } |
448 | } |
449 | |
450 | void ScatterChartItem::handleSeriesUpdated() |
451 | { |
452 | if (m_series->useOpenGL()) { |
453 | if ((m_series->isVisible() != m_visible)) { |
454 | m_visible = m_series->isVisible(); |
455 | refreshGlChart(); |
456 | } |
457 | return; |
458 | } |
459 | |
460 | int count = m_items.childItems().size(); |
461 | if (count == 0) |
462 | return; |
463 | |
464 | const bool pointsConfigurationDirty = |
465 | m_series->pointsConfiguration() != m_pointsConfiguration; |
466 | |
467 | bool recreate = m_visible != m_series->isVisible() |
468 | || m_pointsVisible != m_series->pointsVisible() |
469 | || m_markerSize != m_series->markerSize() |
470 | || m_markerShape != m_series->markerShape() |
471 | || m_selectedColor != m_series->selectedColor() |
472 | || m_selectedPoints != m_series->selectedPoints() |
473 | || pointsConfigurationDirty; |
474 | m_visible = m_series->isVisible(); |
475 | m_markerSize = m_series->markerSize(); |
476 | m_markerShape = m_series->markerShape(); |
477 | setVisible(m_visible); |
478 | setOpacity(m_series->opacity()); |
479 | m_pointsVisible = m_series->pointsVisible(); |
480 | m_pointLabelsFormat = m_series->pointLabelsFormat(); |
481 | m_pointLabelsVisible = m_series->pointLabelsVisible(); |
482 | m_pointLabelsFont = m_series->pointLabelsFont(); |
483 | m_pointLabelsColor = m_series->pointLabelsColor(); |
484 | m_selectedColor = m_series->selectedColor(); |
485 | m_selectedPoints = m_series->selectedPoints(); |
486 | m_pointsConfiguration = m_series->pointsConfiguration(); |
487 | bool labelClippingChanged = m_pointLabelsClipping != m_series->pointLabelsClipping(); |
488 | m_pointLabelsClipping = m_series->pointLabelsClipping(); |
489 | |
490 | if (recreate) { |
491 | deletePoints(count); |
492 | createPoints(count); |
493 | |
494 | // Updating geometry is now safe, because it won't call handleSeriesUpdated unless it |
495 | // creates/deletes points |
496 | updateGeometry(); |
497 | } |
498 | |
499 | // Only accept hover events when light/selection markers are in use so we don't unnecessarily |
500 | // eat the events in the regular case |
501 | setAcceptHoverEvents(!(m_series->lightMarker().isNull() |
502 | && (m_series->selectedLightMarker().isNull() |
503 | || m_series->selectedPoints().isEmpty()))); |
504 | |
505 | setPen(m_series->pen()); |
506 | setBrush(m_series->brush()); |
507 | // Update whole chart in case label clipping changed as labels can be outside series area |
508 | if (labelClippingChanged) |
509 | m_series->chart()->update(); |
510 | else |
511 | update(); |
512 | } |
513 | |
514 | void ScatterChartItem::handleMarkerMouseReleaseEvent(QGraphicsItem *item) |
515 | { |
516 | markerReleased(marker: item); |
517 | if (mousePressed()) |
518 | markerSelected(marker: item); |
519 | setMousePressed(false); |
520 | } |
521 | |
522 | template<class T> |
523 | ChartMarker<T>::ChartMarker(qreal x, qreal y, qreal w, qreal h, ScatterChartItem *parent) |
524 | : T(x, y, w, h, parent) |
525 | , m_parent(parent) |
526 | { |
527 | T::setAcceptHoverEvents(true); |
528 | T::setFlag(QGraphicsItem::ItemIsSelectable); |
529 | } |
530 | |
531 | template<class T> |
532 | ChartMarker<T>::ChartMarker(ScatterChartItem *parent) |
533 | : T(parent) |
534 | , m_parent(parent) |
535 | { |
536 | T::setAcceptHoverEvents(true); |
537 | T::setFlag(QGraphicsItem::ItemIsSelectable); |
538 | } |
539 | |
540 | template<class T> |
541 | void ChartMarker<T>::mousePressEvent(QGraphicsSceneMouseEvent *event) |
542 | { |
543 | T::mousePressEvent(event); |
544 | m_parent->markerPressed(marker: this); |
545 | m_parent->setMousePressed(); |
546 | } |
547 | |
548 | template<class T> |
549 | void ChartMarker<T>::hoverEnterEvent(QGraphicsSceneHoverEvent *event) |
550 | { |
551 | T::hoverEnterEvent(event); |
552 | m_parent->markerHovered(marker: this, state: true); |
553 | } |
554 | |
555 | template<class T> |
556 | void ChartMarker<T>::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) |
557 | { |
558 | T::hoverLeaveEvent(event); |
559 | m_parent->markerHovered(marker: this, state: false); |
560 | } |
561 | |
562 | template<class T> |
563 | void ChartMarker<T>::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) |
564 | { |
565 | T::mouseReleaseEvent(event); |
566 | m_parent->handleMarkerMouseReleaseEvent(item: this); |
567 | } |
568 | |
569 | template<class T> |
570 | void ChartMarker<T>::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) |
571 | { |
572 | T::mouseDoubleClickEvent(event); |
573 | m_parent->markerDoubleClicked(marker: this); |
574 | } |
575 | |
576 | RotatedRectangleMarker::RotatedRectangleMarker(qreal x, qreal y, qreal w, qreal h, |
577 | ScatterChartItem *parent) |
578 | : ChartMarker<QGraphicsPolygonItem>(parent) |
579 | { |
580 | setPolygon(RotatedRectangleMarker::polygon(x, y, w, h)); |
581 | } |
582 | |
583 | QPolygonF RotatedRectangleMarker::polygon(qreal x, qreal y, qreal w, qreal h) |
584 | { |
585 | QPolygonF rotatedRectPoly; |
586 | rotatedRectPoly << QPointF(x, y + h / 2.0); |
587 | rotatedRectPoly << QPointF(x + w / 2.0, y + h); |
588 | rotatedRectPoly << QPointF(x + w, y + h / 2.0); |
589 | rotatedRectPoly << QPointF(x + w / 2.0, y); |
590 | |
591 | return rotatedRectPoly; |
592 | } |
593 | |
594 | TriangleMarker::TriangleMarker(qreal x, qreal y, qreal w, qreal h, ScatterChartItem *parent) |
595 | : ChartMarker<QGraphicsPolygonItem>(parent) |
596 | { |
597 | setPolygon(TriangleMarker::polygon(x, y, w, h)); |
598 | } |
599 | |
600 | QPolygonF TriangleMarker::polygon(qreal x, qreal y, qreal w, qreal h) |
601 | { |
602 | QPolygonF trianglePoly; |
603 | trianglePoly << QPointF(x, y + h); |
604 | trianglePoly << QPointF(x + w, y + h); |
605 | trianglePoly << QPointF(x + w / 2.0, y); |
606 | |
607 | return trianglePoly; |
608 | } |
609 | |
610 | StarMarker::StarMarker(qreal x, qreal y, qreal w, qreal h, ScatterChartItem *parent) |
611 | : ChartMarker<QGraphicsPolygonItem>(parent) |
612 | { |
613 | setPolygon(StarMarker::polygon(x, y, w, h)); |
614 | } |
615 | |
616 | QPolygonF StarMarker::polygon(qreal x, qreal y, qreal w, qreal h) |
617 | { |
618 | QPolygonF starPoly; |
619 | |
620 | constexpr qreal step = M_PI / STAR_SPIKES; |
621 | const qreal radius = w / 2.0; |
622 | const qreal innerRadius = radius * 0.5; |
623 | const QPointF ¢er = QPointF(x + w / 2.0, y + h / 2.0); |
624 | qreal rot = M_PI / 2 * 3; |
625 | |
626 | for (int i = 0; i < STAR_SPIKES; ++i) { |
627 | starPoly << QPointF(center.x() + std::cos(x: rot) * radius, |
628 | center.y() + std::sin(x: rot) * radius); |
629 | rot += step; |
630 | |
631 | starPoly << QPointF(center.x() + std::cos(x: rot) * innerRadius, |
632 | center.y() + std::sin(x: rot) * innerRadius); |
633 | rot += step; |
634 | } |
635 | |
636 | return starPoly; |
637 | } |
638 | |
639 | PentagonMarker::PentagonMarker(qreal x, qreal y, qreal w, qreal h, ScatterChartItem *parent) |
640 | : ChartMarker<QGraphicsPolygonItem>(parent) |
641 | { |
642 | setPolygon(PentagonMarker::polygon(x, y, w, h)); |
643 | } |
644 | |
645 | QPolygonF PentagonMarker::polygon(qreal x, qreal y, qreal w, qreal h) |
646 | { |
647 | QPolygonF pentagonPoly; |
648 | |
649 | constexpr qreal step = 2 * M_PI / 5; |
650 | const qreal radius = w / 2.0; |
651 | const QPointF ¢er = QPointF(x + w / 2.0, y + h / 2.0); |
652 | qreal rot = M_PI / 2 * 3; |
653 | |
654 | for (int i = 0; i < 5; ++i) { |
655 | pentagonPoly << QPointF(center.x() + std::cos(x: rot) * radius, |
656 | center.y() + std::sin(x: rot) * radius); |
657 | rot += step; |
658 | } |
659 | |
660 | return pentagonPoly; |
661 | } |
662 | |
663 | QT_END_NAMESPACE |
664 | |
665 | #include "moc_scatterchartitem_p.cpp" |
666 | |