PDA

View Full Version : Auto scaling y axis using QwtPlotPanner and QwtPlotMagnifier



masterid
10th February 2013, 16:30
Hello guys, I have been working with Qwt for the last weekends but the lack of tutorials is a little sad.
I've searched a little but I didn't find any way ( mainly an easy way ) of doing this.

Well, here is the problem:
I have a QwtPlot and its canvas is given to a QwtPlotPanner and a QwtPlotMagnifier constructors.
QwtPlotPanner is set to allow only horizontal movement( you cant go up or down), and QwtPlotMagnifier can be only scaled horizontally too.

Whenever the graphics changes I want rescale y axis to show minimum and maximum value at screen instead of keeping the old scale and showing an flat version of curve.
Here is what I mean:
869886998700

How can I do that?

Uwe
10th February 2013, 18:02
Whenever the graphics changes I want rescale y axis to show minimum and maximum value at screen instead of keeping the old scale and showing an flat version of curve.
If your question is about how to re-enable autoscaling for the y axis:


curve->setSamples( ... );
plot->setAxisAutoScale( QwtPlot::yLeft );
plot->replot();

For your understanding: a scale can be calculated by the autoscaler or is set manually by QwtPlot::setAxisScaleDiv ( or QwtPlot::setAxisScale() ). Magnifier, zoomer or panner use setAxisScale() and disable autoscaling each time they are in action. That's why you have to re-enable it, when you want to apply the autoscaler for a different series of values.

The auto scaler is implemented in the scale engine of a scale ( QwtPlot::axisScaleEngine() ). The engine offers a couple of options to configure its algo: see QwtScaleEngine::Attribute. QwtScaleEngine::Floating has to be set, if you don't want to have the scale boundaries to be aligned to the step size.

I agree, that more documentation is necessary, but when the lines above are really all you were looking for it should have been possible with reading the existing docs.

Uwe

Seamus
10th February 2013, 20:56
Whenever the graphics changes I want rescale y axis to show minimum and maximum value at screen instead of keeping the old scale and showing an flat version of curve.



// Don't use auto-scaling,
QwtPlot::setAxisAutoScale(xBottom, false);
// Get the lower-bound, upper-bound of the x axis,
QwtPlot::axisScaleDiv(xBottom).lowerBound().
QwtPlot::axisScaleDiv(xBottom).upperBound().
// Calculate your min/max y values between the date bounds.
double min;
double max;
// Set it,
QwtPlot::setAxisScale(yLeft, min, max);

masterid
12th February 2013, 21:09
Thanks for the answers guys, but unfortunately both methods don't work.
That is because the lowerBound and upperBound (and auto scale) are global relative to data.
If I know what points have been plotted to canvas, I can find their minimum and maximum, so I can resize the y scale. Is there an practical way of doing this?

I'm researching how to do that, but once again, thanks.
I will try to get xBottom scale div, walk through data and find minimum and maximum in that interval. Unfortunately there is no signal that is emitted when scale div changes, so I will have to inherit both QwtPlotPanner and QwtPlotMagnifier, which is painful, but ok...

PS: I wanted to answer earlier but I couldn't, I'm sorry for that.

Uwe
13th February 2013, 07:00
That is because the lowerBound and upperBound (and auto scale) are global relative to data.
If I know what points have been plotted to canvas, I can find their minimum and maximum, so I can resize the y scale. Is there an practical way of doing this?
Ah o.k. you want to align the y axis to the bounding interval of the points lying inside the current interval of the x axis.


Unfortunately there is no signal that is emitted when scale div changes
What about QwtScaleWidget::scaleDivChanged() ?

Another approach is to overload QwtSeriesData<T>::setRectOfInterest() ( with Qwt 6.1 you have to enable QwtPlotItem::ScaleInterest ). - or QwtPlotCurve::updateScaleDiv(). Here you could adjust the y coordinates of the bounding rectangle - what is the interval that is used by the auto scaler.

Uwe

masterid
13th February 2013, 22:04
Wonderful Uwe! Thanks a lot man.

I was searching for signal in QwtScaleDiv, that's why nothing was found.
I followed the first tip, using the signal and it worked great!

Here is the the slot which scaleDivChanged() was connected
(Study is just a QwtSeriesData<QPointF>)



void GraphicView::updateYAxis(){
QwtScaleDiv *xDiv =
plotter->axisScaleDiv(QwtPlot::xBottom);

double min=99999999999999,max=-99999999999999;

for(Study* s: studies.values()){
for(uint i = 0; i < s->size(); i++) {
double x = s->sample(i).x();
double y = s->sample(i).y();

if(xDiv->interval().contains(x)){
max=qMax(max,y);
min=qMin(min,y);
}
}
}

QwtScaleDiv div =
plotter->axisScaleEngine(QwtPlot::yLeft)->divideScale(min,max,5,25,(max-min)/5);

plotter->setAxisScaleDiv(QwtPlot::yLeft,div);

plotter->replot();

}

Seamus
14th February 2013, 03:21
I will try to get xBottom scale div, walk through data and find minimum and maximum in that interval. Unfortunately there is no signal that is emitted when scale div changes, so I will have to inherit both QwtPlotPanner and QwtPlotMagnifier, which is painful, but ok...
These connections may interest you, using only QwtScaleWidget::scaleDivChanged() didn't handle some plot movements, I don't recall why:


connect(panner, SIGNAL(panned(int,int)),
this, SLOT(updateYAxis()));
connect(axisWidget(xBottom), SIGNAL(scaleDivChanged()),
this, SLOT(updateYAxis()));


You shouldn't need two for loops, or to set the initial min max values. I've pasted in the function similar to what I currently use on a candle plot, I limit the iterations with the bounds values, If you have data from a higher time frame that could be used to avoid most iterations.


void GraphicView::updateYAxis()
{
if (! m_samples.size() || axisAutoScale(yRight))
return;

const double lowerBound = axisScaleDiv(xBottom).lowerBound();
const double upperBound = axisScaleDiv(xBottom).upperBound();

double min, max, begin, end;

if (lowerBound < upperBound)
{
begin = lowerBound;
end = upperBound;
}
else
{
begin = upperBound;
end = lowerBound;
}

if( begin < 0 )
begin = 0;
if( end < 0 )
end = 0;

if( m_samples.size() < begin )
return;

if( m_samples.size() < end )
end = m_samples.size() - 1;

min = m_samples.low().at(begin);
max = m_samples.high().at(begin);

for ( int i = begin + 1; i <= end; ++i )
{
min = qMin( min, m_samples.low().at(i) );
max = qMax( max, m_samples.high().at(i) );
}
setAxisScale(yRight, min, max);
}