From 9882bdf1956719c4d0cbf349ad7e98dc14922247 2012-02-29 10:23:19 From: Tero Ahola Date: 2012-02-29 10:23:19 Subject: [PATCH] Clicked, remove and clear to QScatterSeries --- diff --git a/example/example.pro b/example/example.pro index 68f488f..b7e00c7 100644 --- a/example/example.pro +++ b/example/example.pro @@ -12,4 +12,5 @@ SUBDIRS += linechart \ multichart \ gdpbarchart \ presenterchart \ - chartview + chartview \ + scatterinteractions diff --git a/example/scatter/main.cpp b/example/scatter/main.cpp index bd612f4..6483000 100644 --- a/example/scatter/main.cpp +++ b/example/scatter/main.cpp @@ -14,15 +14,9 @@ int main(int argc, char *argv[]) // Create chart view QChartView *chartView = new QChartView(); chartView->setChartTheme(QChart::ChartThemeIcy); - // Add scatter series with simple test data QScatterSeries *scatter = new QScatterSeries(); - *scatter << QPointF(0.5, 5.0) - << QPointF(1.0, 4.5) - << QPointF(1.0, 5.5) - << QPointF(1.5, 5.0) - << QPointF(2.0, 4.5) - << QPointF(2.5, 5.0); + *scatter << QPointF(0.5, 5.0) << QPointF(1.0, 4.5) << QPointF(1.0, 5.5) << QPointF(1.5, 5.0); // Chart takes ownership chartView->addSeries(scatter); //! [1] @@ -54,8 +48,9 @@ int main(int argc, char *argv[]) // Use the chart widget as the central widget QMainWindow w; - w.resize(640, 480); + w.resize(400, 300); w.setCentralWidget(chartView); + w.setWindowFlags(Qt::FramelessWindowHint); w.show(); return a.exec(); diff --git a/example/scatter/scatter.pro b/example/scatter/scatter.pro index b677f26..a91bbcb 100644 --- a/example/scatter/scatter.pro +++ b/example/scatter/scatter.pro @@ -1,8 +1,5 @@ !include( ../example.pri ) { - error( "Couldn't find the example.pri file!" ) + error( "Couldn't find the example.pri file!" ) } TARGET = scatter SOURCES += main.cpp - - - diff --git a/example/scatterinteractions/main.cpp b/example/scatterinteractions/main.cpp new file mode 100644 index 0000000..9ae175b --- /dev/null +++ b/example/scatterinteractions/main.cpp @@ -0,0 +1,11 @@ +#include +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/example/scatterinteractions/mainwindow.cpp b/example/scatterinteractions/mainwindow.cpp new file mode 100644 index 0000000..f7b3862 --- /dev/null +++ b/example/scatterinteractions/mainwindow.cpp @@ -0,0 +1,51 @@ +#include "mainwindow.h" +#include +#include +#include + +QTCOMMERCIALCHART_USE_NAMESPACE + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) +{ + resize(400, 300); + setWindowFlags(Qt::FramelessWindowHint); + + QChartView *chartView = new QChartView(this); + chartView->setChartTitle("Click to play with points"); + chartView->setRenderHint(QPainter::Antialiasing); + setCentralWidget(chartView); + + m_scatter = new QScatterSeries(); + for(qreal x(0.5); x <= 5.0; x += 0.5) { + for(qreal y(0.5); y <= 5.0; y += 0.5) { + *m_scatter << QPointF(x, y); + } + } + chartView->addSeries(m_scatter); + + // Add two more series + m_scatter2 = new QScatterSeries(); + chartView->addSeries(m_scatter2); + m_scatter3 = new QScatterSeries(); + chartView->addSeries(m_scatter3); + + connect(m_scatter, SIGNAL(clicked(QPointF)), this, SLOT(clickPoint(QPointF))); +} + +MainWindow::~MainWindow() +{ +} + +void MainWindow::clickPoint(QPointF coordinate) +{ + // Remove the clicked point from the series and add points to the two other series we have + int index = m_scatter->closestPoint(coordinate); + QPointF point = m_scatter->data().at(index); + Q_ASSERT(m_scatter->removeAt(index)); + point.rx() += 0.25; + point.ry() += 0.25; + *m_scatter2 << point; + point.ry() -= 0.25; + *m_scatter3 << point; +} diff --git a/example/scatterinteractions/mainwindow.h b/example/scatterinteractions/mainwindow.h new file mode 100644 index 0000000..fb71277 --- /dev/null +++ b/example/scatterinteractions/mainwindow.h @@ -0,0 +1,27 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +QTCOMMERCIALCHART_USE_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private Q_SLOTS: + void clickPoint(QPointF coordinate); + +private: + QScatterSeries *m_scatter; + QScatterSeries *m_scatter2; + QScatterSeries *m_scatter3; +}; + +#endif // MAINWINDOW_H diff --git a/example/scatterinteractions/scatterinteractions.pro b/example/scatterinteractions/scatterinteractions.pro new file mode 100644 index 0000000..2514c4b --- /dev/null +++ b/example/scatterinteractions/scatterinteractions.pro @@ -0,0 +1,12 @@ +!include( ../example.pri ) { + error( "Couldn't find the example.pri file!" ) +} +QT += core gui + +TARGET = scatterinteractions +TEMPLATE = app + +SOURCES += main.cpp\ + mainwindow.cpp + +HEADERS += mainwindow.h diff --git a/src/chartpresenter.cpp b/src/chartpresenter.cpp index c97c9ab..29b2746 100644 --- a/src/chartpresenter.cpp +++ b/src/chartpresenter.cpp @@ -166,7 +166,8 @@ void ChartPresenter::handleSeriesAdded(QSeries* series) case QSeries::SeriesTypeScatter: { QScatterSeries *scatterSeries = qobject_cast(series); ScatterPresenter *scatterPresenter = new ScatterPresenter(scatterSeries, m_chart); - QObject::connect(scatterPresenter, SIGNAL(clicked()), scatterSeries, SIGNAL(clicked())); + QObject::connect(scatterPresenter, SIGNAL(clicked(QPointF)), + scatterSeries, SIGNAL(clicked(QPointF))); QObject::connect(this, SIGNAL(geometryChanged(const QRectF&)), scatterPresenter, SLOT(handleGeometryChanged(const QRectF&))); m_chartTheme->decorate(scatterPresenter, scatterSeries, m_chartItems.count()); diff --git a/src/scatterseries/qscatterseries.cpp b/src/scatterseries/qscatterseries.cpp index e99b4fa..1487be4 100644 --- a/src/scatterseries/qscatterseries.cpp +++ b/src/scatterseries/qscatterseries.cpp @@ -36,8 +36,10 @@ */ /*! - \fn void QScatterSeries::clicked() - \brief TODO + \fn void QScatterSeries::clicked(QPointF coordinate) + User clicked the scatter series. Note that the \a coordinate is the chart coordinate that the + click occurred on; not necessarily a data point coordinate. To find the corresponding (closest) + data point you can use closestPoint(). */ /*! @@ -142,6 +144,59 @@ QList QScatterSeries::data() } /*! + Remove the data point at \a pointIndex. Returns true if a point was removed, false if the point + at \a pointIndex does not exist on the series. +*/ +bool QScatterSeries::removeAt(int pointIndex) +{ + if (pointIndex >=0 && pointIndex < d->m_data.count()) { + d->m_data.removeAt(pointIndex); + emit changed(); + return true; + } + return false; +} + +/*! + Remove all occurrences of \a point from the series and returns the number of points removed. +*/ +int QScatterSeries::removeAll(QPointF point) +{ + int count = d->m_data.removeAll(point); + emit changed(); + return count; +} + +/*! + Remove all data points from the series. +*/ +void QScatterSeries::clear() +{ + d->m_data.clear(); + emit changed(); +} + +/*! + Returns the index of the data point that is closest to \a coordinate. If several data points + are at the same distance from the \a coordinate, returns the last one. If no points exist, + returns -1. +*/ +int QScatterSeries::closestPoint(QPointF coordinate) +{ + qreal distance(-1); + int pointIndex(-1); + for (int i(0); i < d->m_data.count(); i++) { + QPointF dataPoint = d->m_data.at(i); + QPointF difference = dataPoint - coordinate; + if (i == 0 || difference.manhattanLength() <= distance) { + distance = difference.manhattanLength(); + pointIndex = i; + } + } + return pointIndex; +} + +/*! Overrides the default pen used for drawing a marker item with a user defined \a pen. The default pen is defined by chart theme setting. diff --git a/src/scatterseries/qscatterseries.h b/src/scatterseries/qscatterseries.h index 0063390..e3dd7b8 100644 --- a/src/scatterseries/qscatterseries.h +++ b/src/scatterseries/qscatterseries.h @@ -40,7 +40,11 @@ public: QScatterSeries& operator << (const QPointF &value); QScatterSeries& operator << (QList points); QList data(); - //TODO: insert, replace, remove, clear...? + bool removeAt(int pointIndex); + int removeAll(QPointF point); + void clear(); + int closestPoint(QPointF coordinate); + //TODO: insert, replace...? QPen pen(); void setPen(QPen pen); @@ -51,7 +55,7 @@ public: // TODO: marker size? Q_SIGNALS: - void clicked(/* TODO: parameters? */); + void clicked(QPointF coordinate); // TODO: move to PIMPL for simplicity or does the user ever need changed signals? // TODO: more finegrained signaling for performance reasons // (check QPieSeries implementation with change sets) diff --git a/src/scatterseries/scatterpresenter.cpp b/src/scatterseries/scatterpresenter.cpp index a8c93f8..1470c85 100644 --- a/src/scatterseries/scatterpresenter.cpp +++ b/src/scatterseries/scatterpresenter.cpp @@ -41,8 +41,9 @@ void ScatterPresenter::handleDomainChanged(const Domain& domain) void ScatterPresenter::handleGeometryChanged(const QRectF& rect) { - m_boundingRect = rect; + m_boundingRect = rect.translated(-rect.topLeft()); changeGeometry(); + setPos(rect.topLeft()); } void ScatterPresenter::handleModelChanged() @@ -90,11 +91,16 @@ void ScatterPresenter::paint(QPainter *painter, const QStyleOptionGraphicsItem * void ScatterPresenter::mousePressEvent(QGraphicsSceneMouseEvent *event) { - qDebug() << "ScatterPresenter::mousePressEvent" << event << " cont: " - << m_path.contains(event->lastPos()); + // Empty implementation to grab mouse release events for this item + Q_UNUSED(event) +} - if (m_path.contains(event->lastPos())) - emit clicked(); +void ScatterPresenter::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + QPointF clickedPoint( + m_visibleChartArea.m_minX + (event->lastPos().x() / m_boundingRect.width()) * m_visibleChartArea.spanX(), + m_visibleChartArea.m_maxY - (event->lastPos().y() / m_boundingRect.height()) * m_visibleChartArea.spanY()); + emit clicked(clickedPoint); } void ScatterPresenter::changeGeometry() @@ -107,28 +113,29 @@ void ScatterPresenter::changeGeometry() int shape = m_series->shape(); m_path = QPainterPath(); m_path.setFillRule(Qt::WindingFill); + const qreal size(9); // TODO: user defined size? foreach (QPointF point, m_series->data()) { // Convert relative coordinates to absolute pixel coordinates that can be used for drawing - qreal x = m_boundingRect.left() + point.x() * scalex - m_visibleChartArea.m_minX * scalex; - qreal y = m_boundingRect.bottom() - point.y() * scaley + m_visibleChartArea.m_minY * scaley; + qreal x = point.x() * scalex - m_visibleChartArea.m_minX * scalex - size / 2; + qreal y = m_boundingRect.height() - point.y() * scaley + m_visibleChartArea.m_minY * scaley - size / 2; - if (scene()->width() > x && scene()->height() > y) { + if (x < scene()->width() && y < scene()->height()) { switch (shape) { case QScatterSeries::MarkerShapeDefault: // Fallthrough, defaults to circle case QScatterSeries::MarkerShapeCircle: - m_path.addEllipse(x, y, 9, 9); + m_path.addEllipse(x, y, size, size); break; case QScatterSeries::MarkerShapePoint: m_path.addEllipse(x, y, 2, 2); break; case QScatterSeries::MarkerShapeRectangle: - m_path.addRect(x, y, 9, 9); + m_path.addRect(x, y, size, size); break; case QScatterSeries::MarkerShapeTiltedRectangle: { // TODO: tilt the rectangle - m_path.addRect(x, y, 9, 9); + m_path.addRect(x, y, size, size); break; } default: diff --git a/src/scatterseries/scatterpresenter_p.h b/src/scatterseries/scatterpresenter_p.h index 06608ce..5cd9943 100644 --- a/src/scatterseries/scatterpresenter_p.h +++ b/src/scatterseries/scatterpresenter_p.h @@ -10,10 +10,6 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class QScatterSeries; -/*! - * The "business logic" of scatter series. This is a QObject that does not have a parent QObject. - * The QGraphicsItem parent owns the object instead. - */ class ScatterPresenter : public QObject, public ChartItem { Q_OBJECT @@ -21,13 +17,14 @@ public: explicit ScatterPresenter(QScatterSeries *series, QGraphicsObject *parent = 0); public: // from ChartItem - QRectF boundingRect() const { return m_path.boundingRect(); } + QRectF boundingRect() const { return m_path.controlPointRect(); } QPainterPath shape() const { return m_path; } void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); void mousePressEvent (QGraphicsSceneMouseEvent * event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); Q_SIGNALS: - void clicked(); + void clicked(QPointF coordinates); public Q_SLOTS: void handleDomainChanged(const Domain& domain);