PDA

View Full Version : QwtPlotZoomer question



TorAn
1st May 2011, 15:05
Following is the example of simple date-value curve constructed using 100 days on x axis and random y-values.

My question is about the zoomer behavior. When I do "zoom-in" the curve displays correctly and ticks/labels also looks correct. When I do "zoom-out" back to the original view, the curve dissapears and labels are also wrong.

I'll appreciate the review of the code below. What am I doing wrong here?



class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = 0);
~MainWindow();

void createDefaultCurve();
QwtScaleDiv dateScaleDiv ();

private:
Ui::MainWindowClass *ui;

QwtPlotCurve * _curve;
QVector<double> _xData;
QVector<double> _yData;
};


MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindowClass), _curve(0)
{
ui->setupUi(this);

ui->plot->setCanvasBackground(Qt::white);

// taken from friedberg example
QwtPlotZoomer* zoomer = new QwtPlotZoomer( ui->plot->canvas() );
zoomer->setRubberBandPen( QColor( Qt::black ) );
zoomer->setTrackerPen( QColor( Qt::black ) );
zoomer->setMousePattern( QwtEventPattern::MouseSelect2,
Qt::RightButton, Qt::ControlModifier );
zoomer->setMousePattern( QwtEventPattern::MouseSelect3,
Qt::RightButton );

_curve = new QwtPlotCurve();
_curve->attach(this->ui->plot);

createDateCurve();

ui->plot->replot();
}

// one hundred dates, from today forward
void MainWindow::createDateCurve()
{
unsigned int time ;
for( int x = 0; x < 100; ++x){
time = QDateTime::currentDateTime().addDays(x).toTime_t() ;
_xData.append((double)time);
_yData.append((double) (qrand()&0xFF));
}

_curve->setRawSamples(&_xData[0], &_yData[0], _xData.size());

ui->plot->setAxisScaleDiv( QwtPlot::xBottom, dateScaleDiv() );
ui->plot->setAxisScaleDraw(QwtPlot::xBottom, new DateScaleDraw("dd/MM/yyyy"));

ui->plot->setAxisScale(QwtPlot::xBottom, _xData.first() ,_xData.last());
}


// major ticks - days
// medium ticks - 12am
// minor ticks - every 2 hours
QwtScaleDiv MainWindow::dateScaleDiv ()
{
QList<double> ticks[QwtScaleDiv::NTickTypes];

QList<double> &majorTicks = ticks[QwtScaleDiv::MajorTick];
double val;
foreach (val, _xData)
majorTicks += val;

QList<double> &mediumTicks = ticks[QwtScaleDiv::MediumTick];
for (int i = 0; i < _xData.count() - 1; ++i)
mediumTicks += QDateTime::fromTime_t(_xData[i]).addSecs(3600 * 12).toTime_t();

QList<double> &minorTicks = ticks[QwtScaleDiv::MinorTick];
for (int i = 0; i < _xData.count() - 1; ++i)
for (int j = 2; j < 24; j += 2)
minorTicks += QDateTime::fromTime_t(_xData[i]).addSecs(120 * j).toTime_t();

QwtScaleDiv scaleDiv( _xData.first(), _xData.last(), ticks );
return scaleDiv;
}


class DateScaleDraw: public QwtScaleDraw
{
public:
DateScaleDraw(QString fmt) : format(fmt)
{
setTickLength( QwtScaleDiv::MinorTick, 2 );
setTickLength( QwtScaleDiv::MediumTick, 4 );
setTickLength( QwtScaleDiv::MajorTick, 8 );
}

virtual QwtText label(double v) const
{
QDate t = QDateTime::fromTime_t((int)v).date();
return t.toString(format);
}
private:
const QString format;
};

Uwe
1st May 2011, 15:52
This has been answered 100 times in this forum - see QwtPlotZoomer::setZoomBase().

Uwe

falconium
3rd May 2011, 22:02
I have the same issue. Where should I call setZoomBase()?
I put it into the modified plot class:


Zoomer *zoomer = new Zoomer(QwtPlot::xBottom, QwtPlot::yLeft, this->canvas());
zoomer->setZoomBase();

Didn't help... :(

Thx!

FelixB
4th May 2011, 07:59
you don't have to create a new zoomer. call setZoomBase() on the existing one...

falconium
4th May 2011, 20:38
Hi, that was the only existing one, but I changed the code as follows, and still the same result which is not showing round hours, but in between on scales after zooming out:

QwtPlotZoomer *zoomer = new QwtPlotZoomer(QwtPlot::xBottom, QwtPlot::yLeft, this->canvas());
zoomer->setZoomBase();

Or do you mean something else on existing one?

Basically, this is my Plot code:


ChartPlot::ChartPlot(QStandardItemModel *model, QWidget *parent) : QwtPlot(parent)
{
setAutoReplot(false);

plotLayout()->setAlignCanvasToScales(true);

QwtLegend *legend = new QwtLegend;
legend->setItemMode(QwtLegend::CheckableItem);
insertLegend(legend, QwtPlot::RightLegend);

setAxisTitle(QwtPlot::xBottom, "Report time");
setAxisLabelRotation(QwtPlot::xBottom, -45.0);
setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom);
setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw());

QwtScaleWidget *scaleWidget = axisWidget(QwtPlot::xBottom);
const int fmh = QFontMetrics(scaleWidget->font()).height();
scaleWidget->setMinBorderDist(0, fmh / 2);

setAxisTitle(QwtPlot::yLeft, "Quantity");

QwtPlotGrid *grid = new Grid;
grid->attach(this);

Background *bg = new Background();
bg->attach(this);

//QwtScaleDiv scaleDiv;

for (int row=0; row<model->rowCount(); row++)
{
QDateTime xDateTime = model->item(row, 0)->data(Qt::EditRole).toDateTime();
xData << xDateTime.toTime_t();
}

for (int column = 1; column < model->columnCount(); column++)
{
QVector<double> yData;

curve = new ChartCurve(model->horizontalHeaderItem(column)->text());
curve->setStyle(QwtPlotCurve::Lines);
QVariant decoration = model->item(0, column)->data(Qt::DecorationRole);
if (decoration.type() == QVariant::Color)
{
curve->setColor(decoration.value<QColor>());
}
curve->setCurveAttribute(QwtPlotCurve::Fitted, true);
QwtSplineCurveFitter *curveFitter = new QwtSplineCurveFitter();
curve->setCurveFitter(curveFitter);
curve->setZ(curve->z() - 1);
curve->attach(this);

for (int row=0; row<model->rowCount(); row++)
{
yData << model->item(row, column)->data(Qt::EditRole).toDouble();
}

curve->setSamples(xData, yData);
showCurve(curve, true);
}



QwtLinearScaleEngine *scaleEngineX = new QwtLinearScaleEngine;
scaleEngineX->setAttribute(QwtScaleEngine::Floating, true);

setAxisScaleEngine(QwtPlot::xBottom, scaleEngineX);

setAxisScaleDiv(QwtPlot::xBottom, scaleDiv());

QwtLinearScaleEngine *scaleEngineY = new QwtLinearScaleEngine;
scaleEngineY->setAttribute(QwtScaleEngine::IncludeReference, true);
scaleEngineY->setReference(0);
setAxisScaleEngine(QwtPlot::yLeft, scaleEngineY);

PlotPicker *picker = new PlotPicker(QwtPlot::xBottom,
QwtPlot::yLeft,
QwtPlotPicker::CrossRubberBand,
QwtPicker::ActiveOnly,
this->canvas());

picker->setStateMachine(new QwtPickerTrackerMachine());
picker->setRubberBandPen(QPen(Qt::DotLine));
picker->setRubberBand(QwtPicker::CrossRubberBand);

QwtPlotZoomer *zoomer = new QwtPlotZoomer(QwtPlot::xBottom, QwtPlot::yLeft, this->canvas());
zoomer->setZoomBase();

replot();

connect(this, SIGNAL(legendChecked(QwtPlotItem *, bool)), SLOT(showCurve(QwtPlotItem *, bool)));
}

This is the code for divisions:


QwtScaleDiv ChartPlot::scaleDiv()
{
QList<double> ticks[QwtScaleDiv::NTickTypes];

QList<double> &majorTicks = ticks[QwtScaleDiv::MajorTick];

foreach (double value, xData)
majorTicks += value;

if (xData.count()>1)
{
QList<double> &mediumTicks = ticks[QwtScaleDiv::MediumTick];
for (int i = 0; i < xData.count() - 1; i++)
for (int j = 1; j < 6; j++)
mediumTicks += xData[i] + j * (xData[i+1] - xData[i]) / 5;

QList<double> &minorTicks = ticks[QwtScaleDiv::MinorTick];
for (int i = 0; i < xData.count()*10 - 1; i++)
for (int j = 1; j < 11; j++)
minorTicks += xData[i] + j * (xData[i+1] - xData[i]) / 10;
}

return QwtScaleDiv(xData.first(), xData.last(), ticks);
}

Uwe
5th May 2011, 06:39
Where should I call setZoomBase()?
It initializes the zoom stack with the current boundaries of the scales. So call it, when you have assigned your initial scales - or in combination with autoscaling, when the complete scene ( all plot items with all data ) has been attached.

The constructor of the zoomer already initializes the zoom stack, so your code is completely pointless. setZoomBase is only for situations, where you want reininitialize to your zoom stack, f.e. when you have changed your scales later.

Uwe

falconium
10th May 2011, 05:18
It initializes the zoom stack with the current boundaries of the scales. So call it, when you have assigned your initial scales - or in combination with autoscaling, when the complete scene ( all plot items with all data ) has been attached.

The constructor of the zoomer already initializes the zoom stack, so your code is completely pointless. setZoomBase is only for situations, where you want reininitialize to your zoom stack, f.e. when you have changed your scales later.


OK, now I know when to call it, but still don't know how. So, what should I so instead of creating a new zoomer? I have added it to have the needed effect, but as you said, it is pointless.
I have initialized the scale, the curves, everything, so how should I call this setZoomBase, or do I have to call it at all, because you wrote 'setZoomBase is only for situations, where you want reininitialize to your zoom stack, f.e. when you have changed your scales later'. I only define scale at initialization, it is not changed/reinitialized (unless zooming itself means that).

So, all in all, it is still the same, after zooming out, the scale is screwed (initialized to some default). If I write QwtPlotZoomer::setZoomBase(), then it is not an instance and doesn't work... of course. Sorry for being this demanding... but I couldn't figure it out.

falconium
10th May 2011, 18:43
Bouncing up...

Uwe
10th May 2011, 21:19
If I write QwtPlotZoomer::setZoomBase(), then it is not an instance and doesn't work... of course. Sorry for being this demanding... but I couldn't figure it out.
If you want to reinitialize a zoomer you need to store a pointer to it somewhere - f.e as member of your plot. Then you have the instance of the zoomer you need to reinitialize.

But this is so very, very basic C++ that I'm surprised that you got any application running so far.

Uwe

falconium
11th May 2011, 20:26
I know it is time consuming to go through the code, but this is what I had done earlier, but didn't work. You can check the code above, but better to check code below, as it has a modified zoomer that causes to go only one stack back on right mouse click, doesn't go back to 0 immediately.

Please, point it out for me where should I put it in the code....


#include <qapplication.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qtime>
#include <qpainter.h>
#include "chartplot.h"

class PlotPicker : public QwtPlotPicker
{
public:
PlotPicker(QwtPlot::Axis xAxis,
QwtPlot::Axis yAxis,
QwtPicker::RubberBand rb,
QwtPicker::DisplayMode dm,
QwtPlotCanvas* canvas) : QwtPlotPicker( xAxis, yAxis, rb , dm , canvas)
{
setStateMachine(new QwtPickerTrackerMachine());
setRubberBandPen(QPen(Qt::DotLine));
setRubberBand(QwtPicker::CrossRubberBand);
}

private:
QwtText trackerTextF(const QPointF &pos) const
{
QwtText text("X = " + QDateTime::fromTime_t(pos.x()).toString("yyyy.MM.dd. hh:mm:ss") + "\nY = " + QString::number(pos.y()));
QColor bgColor(Qt::lightGray);
bgColor.setAlpha(100);
text.setBackgroundBrush(QBrush(bgColor));
return text;
}

};

QwtScaleDiv ChartPlot::scaleDiv()
{
QList<double> ticks[QwtScaleDiv::NTickTypes];

QList<double> &majorTicks = ticks[QwtScaleDiv::MajorTick];
QList<double> &mediumTicks = ticks[QwtScaleDiv::MediumTick];
QList<double> &minorTicks = ticks[QwtScaleDiv::MinorTick];

if (xData.count()<11)
{
foreach (double value, xData)
majorTicks += value;

if (xData.count()>1)
{
for (int i = 0; i < xData.count() - 1; i++)
for (int j = 1; j < 6; j++)
mediumTicks += xData[i] + j * (xData[i+1] - xData[i]) / 5;

for (int i = 0; i < xData.count() - 1; i++)
for (int j = 1; j < 11; j++)
minorTicks += xData[i] + j * (xData[i+1] - xData[i]) / 10;
}
}
else
{

// Quantity-based division

// QVector<double> tempXData;
// for (int i=0; i<11; i++)
// {
// int value = (i*(xData.count()-1)/10);
// tempXData.append(xData[value]);
// majorTicks += tempXData[i];
// }

// Value-based division
QVector<double> tempXData;
for (int i=0; i<11; i++)
{
tempXData.append(xData[0] + i*(xData[xData.count()-1] - xData[0])/10);
majorTicks += tempXData.last();
}
for (int i = 0; i < tempXData.count() - 1; i++)
for (int j = 1; j < 6; j++)
mediumTicks += tempXData[i] + j * (tempXData[i+1] - tempXData[i]) / 5;
for (int i = 0; i < tempXData.count() - 1; i++)
for (int j = 1; j < 11; j++)
minorTicks += tempXData[i] + j * (tempXData[i+1] - tempXData[i]) / 10;
}

return QwtScaleDiv(xData.first(), xData.last(), ticks);
}

class Zoomer: public QwtPlotZoomer
{
public:
Zoomer(int xAxis, int yAxis, QwtPlotCanvas *canvas) : QwtPlotZoomer(xAxis, yAxis, canvas)
{
setMousePattern(QwtEventPattern::MouseSelect2, Qt::RightButton, Qt::ControlModifier);
setMousePattern(QwtEventPattern::MouseSelect3, Qt::RightButton);
setZoomBase();
}
};

class Grid: public QwtPlotGrid
{
public:
Grid()
{
enableXMin(true);
setMajPen(QPen(Qt::white, 0, Qt::DotLine));
setMinPen(QPen(Qt::lightGray, 0 , Qt::DotLine));
}

virtual void updateScaleDiv(const QwtScaleDiv &xMap, const QwtScaleDiv &yMap)
{
QList<double> ticks[QwtScaleDiv::NTickTypes];

ticks[QwtScaleDiv::MajorTick] = xMap.ticks(QwtScaleDiv::MajorTick);
ticks[QwtScaleDiv::MinorTick] = xMap.ticks(QwtScaleDiv::MinorTick);

QwtPlotGrid::updateScaleDiv(QwtScaleDiv(xMap.lower Bound(), xMap.upperBound(), ticks), yMap);
}
};

class TimeScaleDraw: public QwtScaleDraw
{
public:
TimeScaleDraw()
{
setTickLength(QwtScaleDiv::MajorTick, 8);
setTickLength(QwtScaleDiv::MinorTick, 2);
setTickLength(QwtScaleDiv::MediumTick, 4);
setLabelRotation(0);
setLabelAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
setSpacing(15);
}

virtual QwtText label(double value) const
{
QDateTime time;
time = time.fromTime_t(value);
return time.toString("yyyy.MM.dd.\nhh:mm:ss");
}
};

class Background: public QwtPlotItem
{
public:
Background()
{
setZ(0.0);
}

virtual int rtti() const
{
return QwtPlotItem::Rtti_PlotUserItem;
}

virtual void draw(QPainter *painter,
const QwtScaleMap &,
const QwtScaleMap &,
const QRectF &rect) const
{
QColor color(Qt::gray);
color = color.darker(150);
painter->fillRect(rect, color);
}
};

class ChartCurve: public QwtPlotCurve
{
public:
ChartCurve(const QString &title) : QwtPlotCurve(title)
{
setRenderHint(QwtPlotItem::RenderAntialiased);
}

void setColor(const QColor &color)
{
QColor c = color;
c.setAlpha(150);

setPen(c);
setBrush(c);
}
};

ChartPlot::ChartPlot(QStandardItemModel *model, bool isSplineUsed, QWidget *parent) : QwtPlot(parent)
{
this->model = model;
splineUsed = isSplineUsed;

setAutoReplot(false);

plotLayout()->setAlignCanvasToScales(true);

signs = new QwtLegend;
signs->setItemMode(QwtLegend::CheckableItem);
insertLegend(signs, QwtPlot::RightLegend);

setAxisTitle(QwtPlot::xBottom, "Report time");
setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw());

scaleWidget = axisWidget(QwtPlot::xBottom);
const int fmh = QFontMetrics(scaleWidget->font()).height();
scaleWidget->setMinBorderDist(0, fmh / 2);

setAxisTitle(QwtPlot::yLeft, "Quantity");

grid = new Grid;
grid->attach(this);

bg = new Background();
bg->attach(this);

countPlot();

scaleEngineX = new QwtLinearScaleEngine;
scaleEngineX->setAttribute(QwtScaleEngine::Floating, true);
setAxisScaleEngine(QwtPlot::xBottom, scaleEngineX);
setAxisScaleDiv(QwtPlot::xBottom, scaleDiv());

scaleEngineY = new QwtLinearScaleEngine;
scaleEngineY->setAttribute(QwtScaleEngine::IncludeReference, true);
scaleEngineY->setReference(0);
setAxisScaleEngine(QwtPlot::yLeft, scaleEngineY);

picker = new PlotPicker(QwtPlot::xBottom,
QwtPlot::yLeft,
QwtPlotPicker::CrossRubberBand,
QwtPicker::ActiveOnly,
this->canvas());
replot();
zoomer = new Zoomer(QwtPlot::xBottom, QwtPlot::yLeft, this->canvas());
zoomer->setZoomBase(false);
connect(this, SIGNAL(legendChecked(QwtPlotItem *, bool)), SLOT(showCurve(QwtPlotItem *, bool)));
}

void ChartPlot::showCurve(QwtPlotItem *item, const bool &on, const bool &replt)
{
item->setVisible(on);
QWidget *w = legend()->find(item);
if (w && w->inherits("QwtLegendItem")) ((QwtLegendItem *)w)->setChecked(on);
if (replt) replot();
}

void ChartPlot::useSpline(bool on, bool refresh)
{
if (on)
{
for (int i=0; i<curve.count(); i++)
{
curveFitter = new QwtSplineCurveFitter;
curve.at(i)->setCurveFitter(curveFitter);
}
}
else
{
for (int i=0; i<curve.count(); i++)
{
curveFitter = new QwtWeedingCurveFitter;
curve.at(i)->setCurveFitter(curveFitter);
}
}
if (refresh) replot();
}

void ChartPlot::countPlot()
{
// Store current visibility of curves if they exist, otherwise initialize visibility to true
QList<bool> visible;
if (curve.isEmpty())
{
for (int column = 1; column < model->columnCount(); column++)
visible.append(true);
}
else
{
for (int column = 1; column < model->columnCount(); column++)
{
visible.append(curve[column - 1]->isVisible());
curve[column - 1]->detach();
}
curve.clear();
}
// Read X-axis data from table
for (int row=0; row<model->rowCount(); row++)
{
QDateTime xDateTime = model->item(row, 0)->data(Qt::EditRole).toDateTime();
xData << xDateTime.toTime_t();
}
// Read Y-axis data from table
for (int column = 1; column < model->columnCount(); column++)
{
QVector<double> yData;

curve.append(new ChartCurve(model->horizontalHeaderItem(column)->text()));
curve.last()->setStyle(QwtPlotCurve::Lines);
QVariant decoration = model->item(0, column)->data(Qt::DecorationRole);
if (decoration.type() == QVariant::Color)
{
curve.last()->setColor(decoration.value<QColor>());
}
curve.last()->setCurveAttribute(QwtPlotCurve::Fitted, true);
curve.last()->setZ(curve.last()->z() - 1);
curve.last()->attach(this);

for (int row=0; row<model->rowCount(); row++)
{
yData << model->item(row, column)->data(Qt::EditRole).toDouble();
}
curve.last()->setSamples(xData, yData);
showCurve(curve.last(), visible[column - 1], false);
}
useSpline(splineUsed, false);
}


I have tried many combinations of calling setZoomBase() and adding Zoomer to plot, no success.

Maybe it would be easier to understand if you could just show me the line where I should put it. I even checked your code, and examples (randomplot uses the same code as me), no success.

Could the solution be inside rescale() of Zoomer class? Please, help, my application is finished, running smoothly, except this one.

pkj
17th May 2011, 17:52
Here is my understanding of zoomer.
Default scales are 1000 for both the axes. So first you need to setZoomBase() after you do setSamples(). However, when you do zoom in, the autoscales are set to off. This is done implicitly. So we also need to setAutoScale(). The place to do that is in the slot for the signal zoomed(QRectF) of zoomer. Some code to explain it..


connect(d_zoomer, SIGNAL(zoomed(QRectF), this, SLOT(autoRescale(QRectF)); //d_zoomer is zoomer's pointer

function implementation...


void myPlot::autoRescale(QRectF)
{
if(d_zoomer->zoomRectIndex()==0) // autorescale only if u are at base... otherwise u defined scales by zoomer right..
{
setAxisAutoScale(yLeft);
setAxisAutoScale(xBottom);
d_zoomer->setZoomBase();
}
}

HTH

falconium
17th May 2011, 21:56
Thanks, it could be the reason! I also thought that it could rather be about that zoomer drops the original scaling after zooming. I'm going to give it a try, and let you know about the result.