PDA

View Full Version : How to make some items non movable on scene



tarunrajsingh
2nd April 2013, 10:22
I have a QGraphicsscene which shows a plot with X and Y axis..When user zooms the plot...I want only curve to be zoomed and axis should be fixed at their original position.I have tried using Qt::itemIgnoresTransformation flag for X and Y axis ,,,but X and Y axis are still moving with zooming(with flag set they dont move propotional to zoom factor but they dont stay at their original position).Has anybody come across similar problem with qgraphicsscene?

d_stranz
2nd April 2013, 16:22
Has anybody come across similar problem with qgraphicsscene?

Yes. It is difficult to implement an x-y data plot using graphics / view for the whole thing because of these scaling problems.

My solution was to use graphics / view [I]only for the canvas[I] (the region containing the data curves). The axes, titles, etc. were ordinary QWidgets (a custom axis widget, and standard QLabel for titles). The composite widget that holds all of these sub-widgets handles the layout, zooming, resizing, etc.

The canvas needs to send signals when zooming occurs. The composite widget is connected to these signals and sets the axis ranges appropriately.

Search for other posts under my name and you will see detailed discussions of how to solve the problem as well as that of keeping labels on the plot the same size even when the plot is zoomed.

MarekR22
2nd April 2013, 16:55
This is simple to solve. Everything what should be scaled should have a common parent widget, and axises shouldn't have such parent.
Three of objects should look like this:

MainWidget

Lauout

AxisX
AxisY
ScaleWidget (it doesn't show own content it is a parent for object to scale, here you apply a transformation)

element 1 to scale
element 2 to scale

tarunrajsingh
3rd April 2013, 04:37
Thanks for the suggestion ...I would try that.Is this a bug in QgraphicsView because in general anyone can end up in a situation where some part of the scene needs to be zoomed and some needs to be static on some mouse action.

d_stranz
3rd April 2013, 16:27
Is this a bug in QgraphicsView

No. That is the way it is designed to work. Scientific data plots are a special case; in general, when a scene is zoomed, all elements of the scene should also zoom - think of video games, CAD drawings, and things like that.

As I said, the easiest way for scientific plots or other charts is to implement the part that should zoom separately from the parts that don't, and combine them into a composite widget. I actually use the scale widgets from Qwt for my axes - better than writing all that code from scratch.

tarunrajsingh
15th April 2013, 11:03
Hi d_stranz,

Can you please provide me some pointers using QwtscaleWidget along with Qgraphicsview.I tried getting the visible viewport and update axes(no Qwt) based on that but its not accurate every time.I need to update X and Y axis values based on zoom or pan on my plot.

d_stranz
15th April 2013, 17:02
This is the layout of my dataplot widget:

8947

The DataplotWidget is derived from QWidget, and uses a QLabel for the title, up to 4 QwtScaleWidget instances as top / bottom / left / right axes, and another custom widget derived from QGraphicsView as the "canvas" where the data is displayed.

The DataplotWidget's showEvent() and resizeEvent() each call a protected method named calculateLayout(). The purpose of this method is to eventually calculate the size and position of the canvas by subtracting out the areas occupied by the other widgets in the Dataplot:



Start with the full DataplotWidget rect
If the title is visible, set its position to the top left and set the top of the canvas rect to the bottom of the title rect
If the top axis is visible, set its top to the bottom of the title. Set the top of the canvas to the bottom of the axis
If the bottom axis is visible, set the bottom of the canvas rect to the top of the bottom axis rect
If the left axis is visible, set its top to the top of the canvas and bottom to the bottom of the canvas. Move the left side of the canvas to the right side of the left axis
If the right axis is visible, set its top and bottom to the canvas top and bottom. Move the right side of the canvas to the left side of the axis
if the top and/or bottom axes are visible, adjust their left and right sides to match the canvas left and right.



The canvas uses an overlay widget to implement zooming. When the left mouse button is clicked in the canvas, the overlay widget is displayed at the same position and size as the canvas widget, and is used to draw the rubberband. If your canvas contains a very complex scene, then it is best to take a snapshot of the contents (i.e. render the graphics view to a pixmap) and use the pixmap as the background of the overlay window. This allows you to smoothly move the rubberband around without triggering paint events in the graphics view. When the mouse is released, the overlay window is hidden and the canvas widget emits a "zoomed( const QRectF & )" signal, where the coordinates of the QRect are in scene coordinates.

The DataplotWidget is connected to this signal. When it receives it, it adjusts the axis ranges as appropriate. I have implemented zooming only for the left and bottom axes, but you could extend the behaviour by supporting zoom on any orthogonal pair of axes. If I ever need that, I will have to go back and engineer it in.

Since each of the sub-widgets is derived from QWidget, they all take care of their own painting. The DataplotWidget itself does not have a paintEvent handler. The canvas uses an external QGraphicsScene, but has its own viewport. This allows the same scene to be displayed on more than one dataplot, with different viewports or level of zoom, and more importantly, without duplicating the underlying data. If you do not need to do this, then you can simply a bit by making the scene a member of the dataplot widget.

Hope this helps a bit. The actual dataplot I have implemented is much more complex than this. The mouse interactions are implemented in a separate class based on QStateMachine, because it supports about 10 different rubberband modes (horizontal line, vertical line, rectangle, horizontal box, vertical box, lasso, etc.) for zooming, panning, range selection, object selection, and so forth. There is a zoom stack that allows the user to zoom in and then pop out to the previous zoom, etc.

tarunrajsingh
16th April 2013, 02:25
Thanks a lot d_stranz ,I really appreciate this level of detail and it really helps.My implementation is somewhat like yours except Qwtaxis and State machine for mouse interaction.Keeping a zoom stack is a good idea,I was maintaing in vars which was sort of cumbersome.Another difference is apart from rubber band zoom ,I am also zooming with mouse wheel and I am trying to scale my axis with the same zoom factor but somehow its not proportional,I will try with QwtscaleAxis today....Thanks again for help.

d_stranz
17th April 2013, 21:44
I am also zooming with mouse wheel

Yes, my state machine also supports mouse wheel zooming, as well as with +/- keys. The state machine has at least 50 states and at least as many transitions. It took a lot of work to write and debug it, but it is intended to support almost any kind of interaction. It doesn't actually depend at all on the graphics-view architecture - all it needs is a pointer to a QWidget and some rectangles that define the pixel and world coordinates of the interaction space.

The zoom stack in handled by a ZoomManager class; this manager can link several views together, so if you zoom in one view, it causes the same zoom to be set in the other views as well. This is useful if you have two different versions of the same data plotted in two views (like original and "processed" data) and you want the views to be synchronized.

The ZoomManager is implemented as a template class. It is independent of Qt (and any other windowing system, for that matter). The template argument is a ViewT type; the only requirement for this type is that it have two methods called "ZoomTo( double xMin, double xMax )" and "ZoomTo( double xMin, double xMax, double yMin, double yMax )". In the widget that uses it, you simply create an instance of ZoomManager (can be a member variable on the stack) and set the default zoom rect in world coordinates. The handler for the mouse press event captures the initial mouse position, and the handler for the mouse release event captures the final mouse position, and then calls appropriate method in the ZoomManager (ZoomX, ZoomY, ZoomXY, etc.). The manager takes care of the stack and of notifying other linked views of the new zoom coordinates.

The only dependencies are on the STL and on the boost tuples template. You could easily replace these with Qt counterparts.



#ifndef ZoomManager_H
#define ZoomManager_H

#include <list>
#include <deque>
#include <utility>
#include <algorithm>
#include <boost\tuple\tuple.hpp>

template < class ViewT >
class ZoomManager
{
private:
class ZoomRect : private boost::tuple< double, double, double, double >
{
typedef enum
{
eXMin = 0,
eXMax = 1,
eYMin = 2,
eYMax = 3
} Offsets;

public:
ZoomRect( double xMin = 0.0, double xMax = 100.0, double yMin = 0.0, double yMax = 100.0 )
{
SetRect( xMin, xMax, yMin, yMax );
}


void GetRect( double & xMin, double & xMax, double & yMin, double & yMax ) const
{
xMin = get< eXMin >();
xMax = get< eXMax >();
yMin = get< eYMin >();
yMax = get< eYMax >();
}

void GetRect( double & xMin, double & xMax ) const
{
xMin = get< eXMin >();
xMax = get< eXMax >();
}

void SetRect( double xMin, double xMax, double yMin, double yMax )
{
get< eXMin >() = xMin;
get< eXMax >() = xMax;
get< eYMin >() = yMin;
get< eYMax >() = yMax;
}

double GetXMin() const { return get< eXMin >(); }
double GetXMax() const { return get< eXMax >(); }
double GetYMin() const { return get< eYMin >(); }
double GetYMax() const { return get< eYMax >(); }
};

public:
typedef enum
{
ePanFull = 0,
ePanLine = 1,
ePanPage = 2,
} PanMode;

public:
ZoomManager( ViewT * pView = 0 )
: mZoomDepth( -1 )
{
AddLink( pView );
}

virtual ~ZoomManager() {}

// Attributes
public:
int GetZoomDepth() const { return mZoomDepth; }
void SetZoomDepth( int depth )
{
mZoomDepth = depth;
if ( mZoomDepth > 0 )
{
// Pop the zoom stack until new depth is reached
while ( mStack.size() > size_t( mZoomDepth ) )
mStack.pop_back();
}
}

void GetZoomXY( double & xMin, double & xMax, double & yMin, double & yMax ) const
{
GetZoomRect().GetRect( xMin, xMax, yMin, yMax );
}

void GetZoomX( double & xMin, double & xMax ) const
{
GetZoomRect().GetRect( xMin, xMax );
}

void GetZoomY( double & yMin, double & yMax ) const
{
double dummy;
GetZoomRect().GetRect( dummy, dummy, yMin, yMax );
}

// Get / Set the default rectangle for fully zoomed-out state
void GetDefaultZoom( double & xMin, double & xMax, double & yMin, double & yMax ) const
{
xMin = mDefaultRect.GetXMin();
xMax = mDefaultRect.GetXMax();
yMin = mDefaultRect.GetYMin();
yMax = mDefaultRect.GetYMax();
}

void SetDefaultZoom( double xMin, double xMax, double yMin = 0.0, double yMax = 100.0 )
{
mDefaultRect.SetRect( xMin, xMax, yMin, yMax );
}

// Operations
public:
// Adds a link to the plot; returns true if successful, false if already linked
bool AddLink( ViewT * pView )
{
bool bResult = false;
if ( pView )
{
Links::iterator it = std::find( mLinks.begin(), mLinks.end(), pView );
if ( it == mLinks.end() )
{
mLinks.push_back( pView );
bResult = true;
}
}
return bResult;
}

// Removes a link to the plot; returns true if found, false if not
bool RemoveLink( ViewT * pView )
{
bool bResult = false;
Links::iterator it = std::find( mLinks.begin(), mLinks.end(), pView );
if ( it != mLinks.end() )
{
mLinks.erase( it );
bResult = true;
}
return bResult;
}

// Zooms X while autoscaling Y
void ZoomX( double xMin, double xMax )
{
PushZoom( ZoomRect( xMin, xMax ) );

Links::iterator it = mLinks.begin();
Links::iterator eIt = mLinks.end();
while ( it != eIt )
{
ViewT * pView = *it++;
if ( pView )
pView->ZoomTo( xMin, xMax );
}
}

// Zooms Y leaving X unchanged
void ZoomY( double yMin, double yMax )
{
double xMin;
double xMax;
GetZoomX( xMin, xMax );
PushZoom( ZoomRect( xMin, xMax, yMin, yMax ) );

Links::iterator it = mLinks.begin();
Links::iterator eIt = mLinks.end();
while ( it != eIt )
{
ViewT * pView = *it++;
if ( pView )
pView->ZoomTo( xMin, xMax, yMin, yMax );
}
}

// Zooms both x and y axes
void ZoomXY( double xMin, double xMax, double yMin, double yMax )
{
PushZoom( ZoomRect( xMin, xMax, yMin, yMax ) );

Links::iterator it = mLinks.begin();
Links::iterator eIt = mLinks.end();
while ( it != eIt )
{
ViewT * pView = *it++;
if ( pView )
pView->ZoomTo( xMin, xMax, yMin, yMax );
}
}

// Zooms out to previous X zoom
void ZoomOutX()
{
double xMin;
double xMax;
PopZoom().GetRect( xMin, xMax );

Links::iterator it = mLinks.begin();
Links::iterator eIt = mLinks.end();
while ( it != eIt )
{
ViewT * pView = *it++;
if ( pView )
pView->ZoomTo( xMin, xMax );
}
}

// Zooms out to previous Y zoom
void ZoomOutY()
{
// Same as ZoomOutXY for now
double xMin;
double xMax;
double yMin;
double yMax;
PopZoom().GetRect( xMin, xMax, yMin, yMax );

Links::iterator it = mLinks.begin();
Links::iterator eIt = mLinks.end();
while ( it != eIt )
{
ViewT * pView = *it++;
if ( pView )
pView->ZoomTo( xMin, xMax, yMin, yMax );
}
}


// Zooms out to previous X-Y zoom
void ZoomOutXY()
{
double xMin;
double xMax;
double yMin;
double yMax;
PopZoom().GetRect( xMin, xMax, yMin, yMax );

Links::iterator it = mLinks.begin();
Links::iterator eIt = mLinks.end();
while ( it != eIt )
{
ViewT * pView = *it++;
if ( pView )
pView->ZoomTo( xMin, xMax, yMin, yMax );
}

}

// Resets the zoom rectangle to defaults (zoom x, autoscale y)
void ZoomResetX()
{
mStack.clear();

double xMin;
double xMax;
mDefaultRect.GetRect( xMin, xMax );

Links::iterator it = mLinks.begin();
Links::iterator eIt = mLinks.end();
while ( it != eIt )
{
ViewT * pView = *it++;
if ( pView )
pView->ZoomTo( xMin, xMax );
}
}

// Resets the X-Y zoom rectangle to the default
void ZoomResetXY()
{
mStack.clear();

double xMin;
double xMax;
double yMin;
double yMax;
mDefaultRect.GetRect( xMin, xMax, yMin, yMax );

Links::iterator it = mLinks.begin();
Links::iterator eIt = mLinks.end();
while ( it != eIt )
{
ViewT * pView = *it++;
if ( pView )
pView->ZoomTo( xMin, xMax, yMin, yMax );
}
}

// Pans completely to the left or by a line or page, while
// keeping the zoom level the same
void PanLeft( PanMode mode )
{
ZoomRect rect = GetZoomRect();
double xMin = rect.GetXMin();
double xMax = rect.GetXMax();
double width = xMax - xMin;

switch( mode )
{
case ePanFull:
{
xMin = mDefaultRect.GetXMin();
xMax = xMin + width;
break;
}

case ePanPage:
{
if ( xMin - width >= mDefaultRect.GetXMin() )
{
xMin -= width;
xMax -= width;
}
else
{
xMin = mDefaultRect.GetXMin();
xMax = xMin + width;
}
break;
}

case ePanLine:
{
width /= 8;
if ( xMin - width >= mDefaultRect.GetXMin() )
{
xMin -= width;
xMax -= width;
}
else
{
xMin = mDefaultRect.GetXMin();
xMax = xMin + width * 8;
}
break;
}
}

PopZoom();
ZoomX( xMin, xMax );

}

// Pans completely to the right or by a line or page, while
// keeping the zoom level the same
void PanRight( PanMode mode )
{
ZoomRect rect = GetZoomRect();
double xMin = rect.GetXMin();
double xMax = rect.GetXMax();
double width = xMax - xMin;

switch( mode )
{
case ePanFull:
{
xMax = mDefaultRect.GetXMax();
xMin = xMax - width;
break;
}

case ePanPage:
{
if ( xMax + width <= mDefaultRect.GetXMax() )
{
xMin += width;
xMax += width;
}
else
{
xMax = mDefaultRect.GetXMax();
xMin = xMax - width;
}
break;
}

case ePanLine:
{
width /= 8;
if ( xMax + width <= mDefaultRect.GetXMax() )
{
xMin += width;
xMax += width;
}
else
{
xMax = mDefaultRect.GetXMax();
xMin = xMax - width * 8;
}
break;
}
}

PopZoom();
ZoomX( xMin, xMax );
}

void PanTo( double xPct, double yPct = 0.0 )
{
// TODO: add support for y panning
if ( xPct <= -1.0 )
PanLeft( ePanFull );
else if ( xPct >= 1.0 )
PanRight( ePanFull );
else
{
ZoomRect rect = GetZoomRect();
double xMin = rect.GetXMin();
double xMax = rect.GetXMax();
double width = xMax - xMin;

double xMinDef = mDefaultRect.GetXMin();
double xMaxDef = mDefaultRect.GetXMax();

xMin = xMinDef + xPct * 0.875 * (xMaxDef - xMinDef);
xMax = xMin + width;

if ( xMax > xMaxDef )
{
xMax = xMaxDef;
xMin = xMax - width;
}

PopZoom();
ZoomX( xMin, xMax );
}
}

protected:
// Pushes the ZoomRect onto the zoom stack while managing stack depth
void PushZoom( const ZoomRect & zoomRect )
{
// If zoom depth is zero, there's no stack so nothing to save
if ( mZoomDepth == 0 )
return;

// Save it
mStack.push_front( zoomRect );

// Remove the bottom-most entry if the stack is too large
if ( mZoomDepth > 0 && mStack.size() > size_t( mZoomDepth ) )
mStack.pop_back();
}

// Pops the ZoomRect from the zoom stack
const ZoomRect & PopZoom()
{
// Remove the current level
if ( !mStack.empty() )
mStack.pop_front();
return GetZoomRect();
}

// Retrieves a reference to the current zoom rectangle
const ZoomRect & GetZoomRect() const
{
if ( mStack.empty() )
return mDefaultRect;
else
return mStack.front();
}

protected:
typedef std::list< ViewT * > Links;
Links mLinks;

typedef std::deque< ZoomRect > Stack;
Stack mStack;

int mZoomDepth;
ZoomRect mDefaultRect;
};

#endif // ZoomManager_H