Implement selection on QChartView - qt

I want to make an implementation of chart selection based on QChart and QChartView.
The family of the classes have a big advantage - easy use of openGL and animations, for example:
QLineSeries *series = new QLineSeries();
series->setUseOpenGL(true); // <==
QChart *chart = new QChart();
chart->addSeries(series);
chart->setAnimationOptions(QChart::AllAnimations); // <==
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
The QChartView class provides the useful zoom feature - QChartView::setRubberBand():
chartView->setRubberBand(QChartView::RectangleRubberBand);
The main problem is that the rubber band can be used only for zoom, but I need to implement it for horizontal selection without zoom, as the feature usually implemented in audio editors:
Now, when I have inherit QChartView, I can disable zoom after selection:
class ChartView : public QChartView
...
bool m_drawRubberBand;
QRubberBand m_rubberBand;
...
ChartView::ChartView(QChart *chart, QWidget *parent)
: QChartView(chart, parent)
{
setRubberBand(QChartView::HorizontalRubberBand);
}
...
// Just copy-paste from the Qt 5 sources - file \Src\qtcharts\src\charts\qchartview.cpp:
/*!
If the rubber band rectangle is displayed in the press event specified by
\a event, the event data is used to update the rubber band geometry.
Otherwise, the default QGraphicsView::mouseMoveEvent() implementation is called.
*/
void ChartView::mouseMoveEvent(QMouseEvent *event)
{
if (m_drawRubberBand && m_rubberBand.isVisible())
{
QRect rect = chart()->plotArea().toRect();
int width = event->pos().x() - m_rubberBandOrigin.x();
int height = event->pos().y() - m_rubberBandOrigin.y();
if (!rubberBand().testFlag(VerticalRubberBand))
{
m_rubberBandOrigin.setY(rect.top());
height = rect.height();
}
if (!rubberBand().testFlag(HorizontalRubberBand))
{
m_rubberBandOrigin.setX(rect.left());
width = rect.width();
}
m_rubberBand.setGeometry(QRect(m_rubberBandOrigin.x(), m_rubberBandOrigin.y(), width, height).normalized());
}
else
{
QGraphicsView::mouseMoveEvent(event);
}
}
Then I can just don't implement the zoom action on the mouse key release event:
void ChartView::mouseReleaseEvent(QMouseEvent *event)
{
if (m_rubberBand.isVisible())
{
if (event->button() == Qt::LeftButton)
{
m_drawRubberBand = false;
do_nothing(); // <==
}
}
}
So, my questions now:
How borders of the the visual rubber band can be mapped to real chart's coordinates. I.e., how can the selection be mapped into a line series on the chart? Now I receive same wrong coordinates:
void MyView::resizeEvent(QResizeEvent *event)
{
QChartView::resizeEvent(event);
QRect rct(QPoint(10, 10), QPoint(20, 20));
qDebug() << mapToScene(rct); <==
}
Output:
QPolygonF(QPointF(10,10)QPointF(21,10)QPointF(21,21)QPointF(10,21))
QPolygonF(QPointF(10,10)QPointF(21,10)QPointF(21,21)QPointF(10,21))
QPolygonF(QPointF(10,10)QPointF(21,10)QPointF(21,21)QPointF(10,21))
QPolygonF(QPointF(10,10)QPointF(21,10)QPointF(21,21)QPointF(10,21))
...
How can an existing rubber selection be proportionally resized together with the view?
Edit: May be it is a useful keyword - QGraphicsScene::setSelectionArea().
The Qt 5 chip example which provides nice rubber band selection, but the example based on QGraphicsView, not on QChartView.

The question is resolved thanks to the reply to this answer: Get mouse coordinates in QChartView's axis system
The key moment: it was necessary to invoke QChart::mapToValue() for a correct coordinates transform:
QPointF ChartView::point_to_chart(const QPoint &pnt)
{
QPointF scene_point = mapToScene(pnt);
QPointF chart_point = chart()->mapToValue(scene_point);
return chart_point;
}
And the inverse transformation:
QPoint ChartView::chart_to_view_point(QPointF char_coord)
{
QPointF scene_point = chart()->mapToPosition(char_coord);
QPoint view_point = mapFromScene(scene_point);
return view_point;
}
That's how I have implemented resize of the rubber band on the resizeEvent.
Firstly, I save the current rubber band on mouse release event:
void ChartView::mouseReleaseEvent(QMouseEvent *event)
{
if (m_rubberBand.isVisible())
{
update_rubber_band(event);
m_drawRubberBand = false;
save_current_rubber_band(); <==
}
}
Where:
void ChartView::update_rubber_band(QMouseEvent * event)
{
QRect rect = chart()->plotArea().toRect();
int width = event->pos().x() - m_rubberBandOrigin.x();
int height = event->pos().y() - m_rubberBandOrigin.y();
if (!rubberBand().testFlag(VerticalRubberBand))
{
m_rubberBandOrigin.setY(rect.top());
height = rect.height();
}
if (!rubberBand().testFlag(HorizontalRubberBand))
{
m_rubberBandOrigin.setX(rect.left());
width = rect.width();
}
m_rubberBand.setGeometry(QRect(m_rubberBandOrigin.x(), m_rubberBandOrigin.y(), width, height).normalized());
}
And:
void ChartView::save_current_rubber_band()
{
QRect rect = m_rubberBand.geometry();
QPointF chart_top_left = point_to_chart(rect.topLeft());
m_chartRectF.setTopLeft(chart_top_left);
QPointF chart_bottom_right = point_to_chart(rect.bottomRight());
m_chartRectF.setBottomRight(chart_bottom_right);
}
And how I repaint the rubber on the resize event:
void ChartView::resizeEvent(QResizeEvent *event)
{
QChartView::resizeEvent(event);
if (m_rubberBand.isVisible())
{
restore_rubber_band();
}
apply_nice_numbers();
}
Where:
void ChartView::restore_rubber_band()
{
QPoint view_top_left = chart_to_view_point(m_chartRectF.topLeft());
QPoint view_bottom_right = chart_to_view_point(m_chartRectF.bottomRight());
m_rubberBandOrigin = view_top_left;
m_rubberBand.setGeometry(QRect(view_top_left, view_bottom_right));
}
And don't forget about the "nice numbers":
void ChartView::apply_nice_numbers()
{
QList<QAbstractAxis*> axes_list = chart()->axes();
for each(QAbstractAxis* abstract_axis in axes_list)
{
QValueAxis* value_axis = qobject_cast<QValueAxis*>(abstract_axis);
if (value_axis)
{
value_axis->applyNiceNumbers();
}
}
}
This logic in action.
Before resize:
After resize:

Related

How can we make a QRubberBand semi-transparent

I have already used
setOpacity();
setAttribute(Qt:WA_TranseculentBackground:)
even i have tied all the available solution nothing has effect.
this is my code
void Physician::mouseMoveEvent(QMouseEvent *e)
{
rubberBand->hide();
bottomRight = e->pos();
QRect rect = QRect(topLeft, bottomRight);
rubberBand->setGeometry(rect);//Area Bounding
QToolTip::showText(e->globalPos(), QString("%1,%2")
.arg(rubberBand->size().width())
.arg(rubberBand->size().height()), this);
}
void Physician::mousePressEvent(QMouseEvent *e)
{
rubberBand->hide();
if(e->x()<ui->videoShowLabel->x()||e->y()<ui->videoShowLabel->y())
{
selectWithInLabel.critical(0,"Error", "Select within the LABEL !");
selectWithInLabel.setFixedSize(500, 200);
}
else{
topLeft = e->pos();
myPoint = ui->videoShowLabel->mapFromGlobal(this->mapToGlobal(e->pos()));
}
}
void Physician::mouseReleaseEvent(QMouseEvent *e){
rubberBand->setWindowOpacity(0.5);
rubberBand->show();
}
void Physician::on_manualROIRadioButton_clicked()
{
rubberBand = new RubberBand(RubberBand::Rectangle, this);
}
What should i do to make rubberBand semiTransparent
I assume you sub classed QRubberBand (RubberBand).
After calling the setWindowopacity the paint event is generated (http://doc.qt.io/qt-5/qwidget.html#windowOpacity-prop)
So redefine the paint event in RubberBand class.
Inside the paint event call "initStyleOption" (given below)
http://doc.qt.io/qt-5/qrubberband.html#initStyleOption
By calling "initStyleOption" you can set the rubber band parameters for drawing.
The real issue with making the QRubberband semi-transparent is that mplayer is painting on a window without Qt having any knowledge of it. Hence Qt itself cannot act as a compositor to generate the required effect.
One possibility would be to make the QRubberBand a top level window. That way the compositing is the responsibility of the underlying graphics system rather than Qt.
With that in mind try the following. Firstly a utility base class to manage the geometry...
class abstract_rubber_band {
public:
virtual QRect get_geometry () const
{
return(QRect(m_parent->mapFromGlobal(widget().geometry().topLeft()), widget().size()));
}
virtual void set_geometry (const QRect &rect)
{
widget().setGeometry(map_rect(rect));
}
protected:
explicit abstract_rubber_band (QWidget *parent)
: m_parent(parent)
{}
/*
* #param point Coords relative to effective parent.
*/
QPoint map_point (QPoint point) const
{
if (point.x() < 0)
point.rx() = 0;
else if (point.x() >= m_parent->width())
point.rx() = m_parent->width() - 1;
if (point.y() < 0)
point.ry() = 0;
else if (point.y() >= m_parent->height())
point.ry() = m_parent->height() - 1;
point = m_parent->mapToGlobal(point);
return(point);
}
QRect map_rect (QRect rect) const
{
return(QRect(map_point(rect.topLeft()), map_point(rect.bottomRight())));
}
private:
QWidget &widget ()
{
return(dynamic_cast<QWidget &>(*this));
}
const QWidget &widget () const
{
return(dynamic_cast<const QWidget &>(*this));
}
QWidget *m_parent;
};
Now use the above as a base of the required rubber band class...
class rubber_band: public abstract_rubber_band,
public QRubberBand {
using super = QRubberBand;
public:
/*
* #param parent Note that this isn't actually used as the
* parent widget but rather the widget to which
* this rubber_band should be confined.
*/
explicit rubber_band (QWidget *parent)
: abstract_rubber_band(parent)
, super(QRubberBand::Rectangle)
{
setAttribute(Qt::WA_TranslucentBackground, true);
}
protected:
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
painter.fillRect(rect(), QColor::fromRgbF(0.5, 0.5, 1.0, 0.25));
QPen pen(Qt::green);
pen.setWidth(5);
painter.setPen(pen);
painter.setBrush(Qt::NoBrush);
painter.drawRect(rect().adjusted(0, 0, -1, -1));
/*
* Display the current geometry in the top left corner.
*/
QRect geom(get_geometry());
painter.drawText(rect().adjusted(5, 5, 0, 0),
Qt::AlignLeft | Qt::AlignTop,
QString("%1x%2+%3+%4").arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
}
};
The above rubber_band class should almost be a drop in replacement for QRubberBand. The main difference is that rather than reading/writing its geometry with geometry/setGeometry you must use get_geometry/set_geometry -- those will perform the mapping to/from global coordinates.
In your particular case create the rubber_band with...
rubberBand = new rubber_band(ui->videoShowLabel);

Bounding rect to the complex form?

The subjects of our project is making a program who simulate a Fusion.
We have some problem with the colliding with our classe Fusion. We want to make a shape complex for our colliding.
printScreenFusionProgramm
Our shape is two circle near each other and we dont want to have a bounding rect but shape "complex"...
this is our Fusion class
Fusion::Fusion(int x, int y)
{
this->setPos(x, y);
}
void Fusion::shape(){
//...
}
void Fusion::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
//set the color
QBrush brushColor(QColor(Qt::blue));
painter->setBrush(brushColor);
painter->setPen(QColor(Qt::blue));
painter->drawEllipse(0,0,40,40);
painter->drawEllipse(-20,-20,40,40);
}
void Fusion::doCollision()
{
// get a new position
// change the angle with randomness
if(qrand() %1)
{
setRotation(rotation() + (180 + (qrand() % 10)));
}
else
{
setRotation(rotation() + (180 + (qrand() % -10)));
}
// check if the new position is in bounds
QPointF newPoint = mapToParent(-(boundingRect().width()), -(boundingRect().width() + 2));
if(!scene()->sceneRect().contains((newPoint)))
{
// move back in bounds
newPoint = mapToParent(0,0);
}
else
{
// set the new position
setPos(newPoint);
}
}
void Fusion::advance(int step)
{
if(!step) return;
if(!scene()->collidingItems(this).isEmpty())
{
doCollision();
}
setPos(mapToParent(0, -1));
}
You need to reimplement the "shape" method for your graphics items to return the actual shape of your object. You can return any shape you want in a QPainterPath, and Qt will use that for collision detection.

Move QGraphicsItem only when pressing inside the shape. Otherwise, drag scene

I have a QGraphicsView with a bigger QGraphicsScene that can be dragged.
In the QGraphicsScene I have a subclassed QGraphicsItem (TestItem) that displays a QGraphicsPixmapItem, which can have random shapes.
(I don't use QGraphicsPixmapItem directly because of extra functionality to be implemented in the future)
I want this item to be movable, but only if the user presses within the shape of the item. If outside the shape, but still inside the boundingRectangle, I want the scene behind it to be dragged. This because the boundingRectangle can be much bigger than the shape and the user doesn't see it, so it would be weird trying to drag the scene near the Pixmap and it not working.
This is my subclassed item:
TestItem::TestItem(QPointF position, QPixmap testImage, double width,
double length, QGraphicsItem * parent):
QGraphicsItem(parent),
m_boundingRect(QRectF(0,0,5, 5)),
m_dragValid(false),
m_path(QPainterPath()),
mp_image(new QGraphicsPixmapItem(this))
{
setBoundingRect(QRectF(0,0,width,length));
setPos(position - boundingRect().center());
setFlag(QGraphicsItem::ItemIsMovable);
mp_image->setPixmap(testImage.scaled(width, length));
m_path = mp_image->shape();
}
QPainterPath TestItem::shape()
{
return m_path;
}
QRectF TestItem::boundingRect() const
{
return m_boundingRect;
}
void TestItem::setBoundingRect(QRectF newRect)
{
prepareGeometryChange();
m_boundingRect = newRect;
}
I've tried overriding the mouse events like this, but all it brings me is no functionality at all when outside the shape but inside the bounding rectangle
void TestItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(shape().contains(event->pos()))
{
QGraphicsItem::mousePressEvent(event);
m_dragValid = true;
}
}
void TestItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(m_dragValid)
QGraphicsItem::mouseMoveEvent(event);
}
void TestItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(m_dragValid)
QGraphicsItem::mouseReleaseEvent(event);
m_dragValid = false;
}
which of course makes sense, but I wouldn't know how to implement the dragging of the scene, since it's the scene itself that sends the mouse events to the graphics item.
(My QGraphicsView is setup to DragMode QGraphicsView::ScrollHandDrag)
Anyone have ideas?
I figured it out. I only needed to add a event->ignore(); to my mouse events.
void TestItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(shape().contains(event->pos()))
{
QGraphicsItem::mousePressEvent(event);
m_dragValid = true;
}
else
event->ignore();
}
void TestItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(m_dragValid)
QGraphicsItem::mouseMoveEvent(event);
else
event->ignore();
}
void TestItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(m_dragValid)
QGraphicsItem::mouseReleaseEvent(event);
else
event->ignore();
m_dragValid = false;
}
You just need to enable QGraphicsItem::ItemClipsToShape flag:
The item clips to its own shape. The item cannot draw or receive mouse, tablet, drag and drop or hover events outside its shape. It is disabled by default.

QgraphicsView rubberbanddrag effects zooming behavior

I am stuck with this weird behavior where, if I enable rubberband drag, the wheel event doesn't zoom under mouse anymore. It does zooming but irrespective of mouse position. And if I disable other events, then wheelEvent works properly.
I have a custom class inheriting QGraphicsView as :
class MyGraphics : public QGraphicsView{
public :
MyGraphics(QWidget *parent = NULL);
public slots:
void zoomIn() { scale(1.2, 1.2); }
void zoomOut() { scale(1 / 1.2, 1 / 1.2); }
protected :
QRubberBand *rubberBand;
QPoint origin;
QPointF InitialCenterPoint;
QPointF CurrentCenterPoint;
QPoint rubberBandOrigin;
bool rubberBandActive;
QPoint LastPanPoint;
int _numScheduledScalings;
//if I uncomment these three event handlers, the wheelevent doesnt zoom properly!
// virtual void mousePressEvent(QMouseEvent *event);
// virtual void mouseMoveEvent(QMouseEvent *event);
// virtual void mouseReleaseEvent(QMouseEvent *event);
virtual void wheelEvent(QWheelEvent *);
virtual void resizeEvent(QResizeEvent *event);
};
The constructor :
MyGraphics::MyGraphics(QWidget *parent) : QGraphicsView(parent){
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
this->installEventFilter(this);
this->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
this->setResizeAnchor( QGraphicsView::AnchorUnderMouse );
QGraphicsScene *graphScene = new QGraphicsScene(this);
this->setScene(graphScene);
QGraphicsItem* pEllipse = graphScene->addEllipse(0,100,50,50);
QGraphicsItem* pRect = graphScene->addRect(200,100,50,50);
pEllipse->setFlag(QGraphicsItem::ItemIsSelectable, true);
pRect->setFlag(QGraphicsItem::ItemIsSelectable, true);
setSceneRect(0, 0, 1000, 1000);
rubberBandOrigin = QPoint(0,0);
}
Event handlers :
void MyGraphics::wheelEvent(QWheelEvent *event){
if(event->delta() > 0){
//Zoom in
this->zoomIn();
} else {
this->zoomOut();
}
}
/*
void MyGraphics::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::MiddleButton) {
rubberBandOrigin = event->pos();
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(event->x(),event->y(),0, 0);
rubberBand->show();
rubberBandActive = true;
}
if(event->button() == Qt::LeftButton){
LastPanPoint = event->pos();
}
}
void MyGraphics::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() == Qt::MiddleButton && rubberBandActive == true){
rubberBand->resize( event->x()-rubberBandOrigin.x(), event->y()-rubberBandOrigin.y() );
}
else{
if(!LastPanPoint.isNull()) {
//Get how much we panned
QGraphicsView * view = static_cast<QGraphicsView *>(this);
QPointF delta = view->mapToScene(LastPanPoint) - view->mapToScene(event->pos());
LastPanPoint = event->pos();
}
}
}
void MyGraphics::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::MiddleButton){
QGraphicsView * view = static_cast<QGraphicsView *>(this);
QPoint rubberBandEnd = event->pos();
QRectF zoomRectInScene = QRectF(view->mapToScene(rubberBandOrigin), view->mapToScene(rubberBandEnd));
QPointF center = zoomRectInScene.center();
view->fitInView(zoomRectInScene, Qt::KeepAspectRatio);
rubberBandActive = false;
delete rubberBand;
}
else{
LastPanPoint = QPoint();
}
}
*/
Any idea where I am doing wrong or how do I fix it ?
QGraphicsView::scale function's behaviour depends on the mouse position. It's performing automatically and internally by QGraphicsView. Since you don't pass mouse position to the scale function, I think QGraphicsView tracks the mouse and remembers the last position on its own.
By reimplementing mouse event handlers you have taken this ability from it. The view can't determine the mouse position anymore because its original handlers aren't called.
Luckily this issue can be easily fixed. You need to call base class implementation before your own:
void MyGraphics::mousePressEvent(QMouseEvent *event) {
QGraphicsView::mousePressEvent(event);
// your implementation goes here
}
It's an example for mousePressEvent but you should add similar statements to all your event handlers unless you need to disable some part of default behavior.

QGraphicsView rubber band selection rectangle not visible

I have a QGraphicsView widget with lots of items on scene. I am panning the view on ctr+left mouse click and zooming it to rectangle of rubber band created with left mouse button drag. I am not able to see rubber band selection rectangle (dotted lines) whereas rubberband selection functionality works fine. Can anybody help me understand this?.I use these flags in my view:
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
setRenderHints(QPainter::HighQualityAntialiasing | QPainter::SmoothPixmapTransform);
setOptimizationFlag(QGraphicsView::DontSavePainterState,true);
setCacheMode(QGraphicsView::CacheBackground);
setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing);
setViewport(new QGLWidget);
below are my event handlers.
Mouse press event handler:
void MyView::mousePressEvent(QMouseEvent* event)
{
if(event->button()==Qt::LeftButton)
{
if(event->modifiers()==Qt::ControlModifier)
{
setDragMode(QGraphicsView::NoDrag);
m_rubberBandActive = false;
mousepressed=true;
m_lastDragPos = event->pos();
return;
}
else
{
setDragMode(QGraphicsView::RubberBandDrag);
m_rubberBandOrigin = event->pos();
m_rubberBandActive = true;
}
}
event->accept();
}
else
{
QWidget::mousePressEvent(event);
}
}
Mouse move event:
void MyView::mouseMoveEvent(QMouseEvent* event)
{
if(mousepressed)
{
QPointF delta = mapToScene(event->pos()) - mapToScene(m_lastDragPos);
this->panView(delta);
m_lastDragPos = event->pos();
return;
}
event->accept();
}
Mouse release event Handler:
void MyView::mouseReleaseEvent(QMouseEvent *event)
{
if (m_rubberBandActive)
{
QPoint rubberBandEnd = event->pos();
QRectF zoomRectInScene = QRectF(mapToScene(m_rubberBandOrigin),mapToScene(rubberBandEnd));
fitInView(zoomRectInScene, Qt::KeepAspectRatio);
m_rubberBandActive = false;
}
mousepressed=false;
event->accept();
}
Pan view:
void MyView::panView(QPointF delta)
{
QPoint viewCenter(viewport()->width() / 2 + delta.x(), viewport()->height() / 2 + delta.y());
QPointF newCenter = mapToScene(viewCenter);
centerOn(newCenter);
}

Resources