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
15QT_BEGIN_NAMESPACE
16
17namespace {
18constexpr short STAR_SPIKES = 5;
19}
20
21ScatterChartItem::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
69QRectF ScatterChartItem::boundingRect() const
70{
71 return m_rect;
72}
73
74void 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
114void 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
125void 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
164void ScatterChartItem::markerSelected(QGraphicsItem *marker)
165{
166 emit XYChart::clicked(point: m_markerMap[marker]);
167}
168
169void ScatterChartItem::markerHovered(QGraphicsItem *marker, bool state)
170{
171 emit XYChart::hovered(point: m_markerMap[marker], state);
172}
173
174void ScatterChartItem::markerPressed(QGraphicsItem *marker)
175{
176 emit XYChart::pressed(point: m_markerMap[marker]);
177}
178
179void ScatterChartItem::markerReleased(QGraphicsItem *marker)
180{
181 emit XYChart::released(point: m_markerMap[marker]);
182}
183
184void ScatterChartItem::markerDoubleClicked(QGraphicsItem *marker)
185{
186 emit XYChart::doubleClicked(point: m_markerMap[marker]);
187}
188
189void 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
293void 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
307void 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
326void 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
340void 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
351void 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
408void 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
419void 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
450void 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
514void ScatterChartItem::handleMarkerMouseReleaseEvent(QGraphicsItem *item)
515{
516 markerReleased(marker: item);
517 if (mousePressed())
518 markerSelected(marker: item);
519 setMousePressed(false);
520}
521
522template<class T>
523ChartMarker<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
531template<class T>
532ChartMarker<T>::ChartMarker(ScatterChartItem *parent)
533 : T(parent)
534 , m_parent(parent)
535{
536 T::setAcceptHoverEvents(true);
537 T::setFlag(QGraphicsItem::ItemIsSelectable);
538}
539
540template<class T>
541void ChartMarker<T>::mousePressEvent(QGraphicsSceneMouseEvent *event)
542{
543 T::mousePressEvent(event);
544 m_parent->markerPressed(marker: this);
545 m_parent->setMousePressed();
546}
547
548template<class T>
549void ChartMarker<T>::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
550{
551 T::hoverEnterEvent(event);
552 m_parent->markerHovered(marker: this, state: true);
553}
554
555template<class T>
556void ChartMarker<T>::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
557{
558 T::hoverLeaveEvent(event);
559 m_parent->markerHovered(marker: this, state: false);
560}
561
562template<class T>
563void ChartMarker<T>::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
564{
565 T::mouseReleaseEvent(event);
566 m_parent->handleMarkerMouseReleaseEvent(item: this);
567}
568
569template<class T>
570void ChartMarker<T>::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
571{
572 T::mouseDoubleClickEvent(event);
573 m_parent->markerDoubleClicked(marker: this);
574}
575
576RotatedRectangleMarker::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
583QPolygonF 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
594TriangleMarker::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
600QPolygonF 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
610StarMarker::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
616QPolygonF 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 &center = 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
639PentagonMarker::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
645QPolygonF 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 &center = 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
663QT_END_NAMESPACE
664
665#include "moc_scatterchartitem_p.cpp"
666

source code of qtcharts/src/charts/scatterchart/scatterchartitem.cpp