How to plot a simple function with QDateTime X axis and int/double Y axis?
Hi,
It looks like all the included examples are to difficult to me (moving knobs, time-based changes in charts, etc.).
I simply cannot figure out the basic use of the library.
Can someone please include here a very-very simple example of a class that can be put in a layout as widget and shown that has only X-Y axes as described in the topic. Can someone include a simple example of Y=2X (where X is QDateTime that is converted to double for example).
My source data is in QStandardItemModel. First column is QDateTime (real dates and times of logged events), all the other columns are measured values of different objects - so basically different lines in the chart.
Thank you in advance!
I include the source where I got, but it is just unreliable. :(
HEADER:
Code:
#ifndef CHARTPLOT_H
#define CHARTPLOT_H
#include <qwt_plot.h>
#include <QStandardItemModel>
class ChartCurve;
{
Q_OBJECT
public:
const ChartCurve *getCurve() const
{
return curve;
}
{
this->model = model;
}
{
return this->model;
}
private Q_SLOTS:
private:
ChartCurve *curve;
};
#endif
SOURCE:
Code:
#include <qapplication.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qtime>
#include <qpainter.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_curve.h>
#include <qwt_scale_draw.h>
#include <qwt_scale_widget.h>
#include <qwt_legend.h>
#include <qwt_legend_item.h>
#include "chartplot.h"
#include <QDebug>
{
public:
TimeScaleDraw
(const QTime &base
) : baseTime
(base
) {
}
virtual QwtText label
(double v
) const {
QTime upTime
= baseTime.
addSecs((int)v
);
return upTime.toString();
}
private:
};
{
public:
Background()
{
setZ(0.0);
}
virtual int rtti() const
{
}
{
c=c.darker(150);
r.setBottom(yMap.transform(500));
r.setTop(yMap.transform(0));
painter->fillRect(r, c);
}
};
{
public:
{
}
void setColor
(const QColor &color
) {
c.setAlpha(150);
setPen(c);
setBrush(c);
}
};
{
setAutoReplot(false);
plotLayout()->setAlignCanvasToScales(true);
legend
->setItemMode
(QwtLegend::CheckableItem);
insertLegend
(legend,
QwtPlot::RightLegend);
setAxisTitle
(QwtPlot::xBottom,
"Report time");
//setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw(cpuStat.upTime()));
setAxisScale
(QwtPlot::xBottom,
0,
10);
setAxisLabelRotation
(QwtPlot::xBottom,
-45.0);
setAxisLabelAlignment
(QwtPlot::xBottom, Qt
::AlignLeft | Qt
::AlignBottom);
/*
In situations, when there is a label at the most right position of the
scale, additional space is needed to display the overlapping part
of the label would be taken by reducing the width of scale and canvas.
To avoid this "jumping canvas" effect, we add a permanent margin.
We don't need to do the same for the left border, because there
is enough space for the overlapping label below the left scale.
*/
scaleWidget->setMinBorderDist(0, fmh / 2);
setAxisTitle
(QwtPlot::yLeft,
"Quantity");
setAxisScale
(QwtPlot::yLeft,
0,
100);
Background *bg = new Background();
bg->attach(this);
curve = new ChartCurve("");
curve->setColor(Qt::blue);
curve->setZ(curve->z() - 1);
curve->attach(this);
double xData[model->rowCount()];
double yData[model->rowCount()];
qDebug() <<"ROWCOUNT:"<<model->rowCount();
for (int x=0; x<model->rowCount(); x++)
{
//xData[x] = model->item(x, 0)->data(Qt::EditRole).toDateTime();
xData[x] = x+1;
qDebug()<<"X:"<<xData[x];
}
for (int y=0; y<model->rowCount(); y++)
{
yData[y] = model->item(y, 1)->data(Qt::EditRole).toFloat();
qDebug()<<"Y:"<<yData[y];
}
curve->setRawSamples(xData, yData, model->rowCount());
showCurve(curve, true);
//for (int i = 0; i < 100; i++) xData[100 - 1 - i] = i;
}
{
item->setVisible(on);
if (w
&& w
->inherits
("QwtLegendItem")) ((QwtLegendItem *)w
)->setChecked
(on
);
replot();
}
I have tried to modify CPU plot to simple x/y function. No success.
Thanks!
Re: How to plot a simple function with QDateTime X axis and int/double Y axis?
I faced your problem too: if you want to learn how to use an x datetime axis, (that is not changing its values in the time like cpuplot), see friedberg example.
(I'm using qwt 6.0)
Re: How to plot a simple function with QDateTime X axis and int/double Y axis?
Scales are always based on doubles, but you can always translate date/time values into doubles by using the difference to a reference date. Additionally you need to implement how to display such a translated value into a time/date string - f.e. for the axis tick labels.
Unfortunately Qwt doesn't offer a scale engine, that calculates useful scale ticks for date/time intervals. So if you want to have ticks according to something like 60/60/24 or 7/52/365 you need to set the ticks manually - otherwise you get ticks based on decades.
This is what the friedberg example does - and what doesn't work very well when you zoom in.
Uwe
Re: How to plot a simple function with QDateTime X axis and int/double Y axis?
I found this example hidden in the code as commented, but it cannot be compiled. This looks to be a simple application of Qwt, and it is almost what I need to start with and to learn some basics.
Code:
#include <cmath>
#include <qwt_series_data.h>
#include <qwt_plot_curve.h>
#include <qwt_plot.h>
#include <qapplication.h>
class SinusData: public QwtSyntheticPointData
{
public:
SinusData():
QwtSyntheticPointData(100)
{
}
virtual double y(double x) const
{
return qSin(x);
}
};
int main(int argc, char **argv)
{
plot.
setAxisScale(QwtPlot::xBottom,
0.0,
10.0);
plot.
setAxisScale(QwtPlot::yLeft,
-1.0,
1.0);
curve->setData(SinusData());
curve->attach(&plot);
plot.show();
return a.exec();
}
This is what I get as error:
..\sinus\main.cpp: In function 'int qMain(int, char**)':
..\sinus\main.cpp:29: error: no matching function for call to 'QwtPlotCurve::setData(SinusData)'
..\sinus\libraries\qwt/qwt_plot_seriesitem.h:146: note: candidates are: void QwtPlotSeriesItem<T>::setData(QwtSeriesData<T>*) [with T = QPointF]
mingw32-make[1]: *** [release/main.o] Error 1
If you can fix this code for me, I think I will be able to extend it with more. Thanx in advance!
Re: How to plot a simple function with QDateTime X axis and int/double Y axis?
This is basic C++ and the compiler already told you what to do: "curve->setData( new SinusData() );"
In most applications you want to pass samples from some sort array instead of a data object, that calculates values on the fly. So check all classes derived from QwtSeriesData and the convenience methods QwtPlotCurve::setSamples().
Uwe
Re: How to plot a simple function with QDateTime X axis and int/double Y axis?
Yes, it was the problem. I thought that it could be some mismatch in arguments, but I also noticed finally that not the instance is given. Thx!
Re: How to plot a simple function with QDateTime X axis and int/double Y axis?
Alright, this is where I got:
Code:
#include <qapplication.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qtime>
#include <qpainter.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_scale_draw.h>
#include <qwt_scale_widget.h>
#include <qwt_legend.h>
#include <qwt_legend_item.h>
#include <qwt_curve_fitter.h>
#include "chartplot.h"
#include <QDebug>
{
public:
Grid()
{
enableXMin(true);
setMajPen
(QPen(Qt
::white,
0, Qt
::DotLine));
setMinPen
(QPen(Qt
::lightGray,
0 , Qt
::DotLine));
}
{
QwtScaleDiv(xMap.
lowerBound(), xMap.
upperBound(), ticks
),
yMap );
}
};
{
public:
TimeScaleDraw(/*const QTime &base*/)/* : baseTime(base)*/
{
setLabelRotation(0);
setLabelAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
setSpacing(20);
}
virtual QwtText label
(double v
) const {
// QTime upTime = baseTime.addSecs((int)v);
time = time.fromTime_t(v);
return time.toString("yyyy.MM.dd.\nhh:mm:ss");
}
//private:
// QTime baseTime;
};
{
public:
Background()
{
setZ(0.0);
}
virtual int rtti() const
{
}
{
c=c.darker(150);
// QRectF r = rect;
// r.setBottom(yMap.transform(500));
// r.setTop(yMap.transform(0));
painter->fillRect(rect, c);
}
};
{
public:
{
}
void setColor
(const QColor &color
) {
c.setAlpha(150);
setPen(c);
setBrush(c);
}
};
{
setAutoReplot(false);
plotLayout()->setAlignCanvasToScales(true);
legend
->setItemMode
(QwtLegend::CheckableItem);
insertLegend
(legend,
QwtPlot::RightLegend);
setAxisTitle
(QwtPlot::xBottom,
"Report time");
setAxisAutoScale
(QwtPlot::xBottom);
setAxisLabelRotation
(QwtPlot::xBottom,
-45.0);
setAxisLabelAlignment
(QwtPlot::xBottom, Qt
::AlignLeft | Qt
::AlignBottom);
setAxisScaleDraw
(QwtPlot::xBottom,
new TimeScaleDraw
());
scaleWidget->setMinBorderDist(0, fmh / 2);
setAxisTitle
(QwtPlot::yLeft,
"Quantity");
//setAxisScale(QwtPlot::yLeft, 0, 50);
grid->attach(this);
Background *bg = new Background();
bg->attach(this);
for (int column = 1; column < model->columnCount(); column++)
{
curve = new ChartCurve(model->horizontalHeaderItem(column)->text());
QVariant decoration
= model
->item
(0, column
)->data
(Qt
::DecorationRole);
if (decoration.
type() == QVariant::Color) {
curve->setColor(decoration.value<QColor>());
}
curve->setCurveFitter(curveFitter);
curve->setZ(curve->z() - 1);
curve->attach(this);
QVector<double> xData;
QVector<double> yData;
for (int row=0; row<model->rowCount(); row++)
{
QDateTime xDateTime
= model
->item
(row,
0)->data
(Qt
::EditRole).
toDateTime();
xData << xDateTime.toTime_t();
yData << model->item(row, column)->data(Qt::EditRole).toDouble();
}
curve->setSamples(xData, yData);
showCurve(curve, true);
}
}
{
item->setVisible(on);
if (w
&& w
->inherits
("QwtLegendItem")) ((QwtLegendItem *)w
)->setChecked
(on
);
replot();
}
How can I change the values of (major) ticks on axis X to match the date of source values or to match some nice value to be easily read by humans?
I also have problems with spine, as it doesn't always match precisely. The plot can grow higher than the actual value. I'll send you picture tomorrow. Here it is: wrong spline
Pictures:
Source data: link
Chart: link
Thanks for any idea!
Re: How to plot a simple function with QDateTime X axis and int/double Y axis?
OK, I have managed to fill the gaps in the chart by using Floating attribute:
Code:
setAxisScaleEngine
(QwtPlot::xBottom, scaleEngineX
);
Now, the question is still, how to set major ticks to exact values of the original, source data, not some intermediate, calculated ones. (And of course, if there are too many discrete values on axis, to show only part of them based on an algorithm.)
I have tried to play around with:
setAxisMaxMinor(QwtPlot::xBottom, <some value>);
setAxisMaxMajor(QwtPlot::xBottom, <model->rowCount() or other values>);
No success. They always put ticks to wrong positions.
Thank you for the help in advance! This would be the only critical thing to me.
Re: How to plot a simple function with QDateTime X axis and int/double Y axis?
Now I know i would have had to use QwtScaleDiv class for this purpose:
Code:
{
QList<double>
&majorTicks
= ticks
[QwtScaleDiv::MajorTick];
foreach (double 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(900).
toTime_t();
QList<double>
&minorTicks
= ticks
[QwtScaleDiv::MinorTick];
for (int i = 0; i < xData.count() - 1; ++i)
minorTicks
+= QDateTime::fromTime_t(xData
[i
]).
addSecs(300).
toTime_t();
return QwtScaleDiv(xData.
first(), xData.
last(), ticks
);
}
Simply call this function to set axis scale divisions in your QwtPlot instance as following:
Code:
setAxisScaleDiv
(QwtPlot::xBottom, scaleDiv
());
The only problem now I have is to set one minor tick to 1/10 of major tick and one medium tick to 1/5 of major tick. Thanks for suggestions in advance!
Re: How to plot a simple function with QDateTime X axis and int/double Y axis?
I solved it, please, find it here for future reference:
Code:
{
QList<double>
&majorTicks
= ticks
[QwtScaleDiv::MajorTick];
foreach (double val, xData)
majorTicks += val;
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
);
}