Files
seaclient/seaplot.cpp

723 lines
17 KiB
C++

#include "seaplot.h"
#include <stdlib.h>
#include <math.h>
#include <qfont.h>
#include <qgrid.h>
#include <qtooltip.h>
#include <qwt_plot_layout.h>
#include <qwt_scale.h>
#include "utils.h"
#define SEA_RESOLUTION 0.00005 /* chagned from 0.0005 for Amy, July 2020 */
LegendButton::LegendButton(QWidget *parent, SeaPlot *plot, SeaCurve *crv) : QwtLegendButton(parent) {
this->plot = plot;
this->crv = crv;
connect(this, SIGNAL(clicked()), this, SLOT(legendClicked()));
connect(&clickTimer, SIGNAL(timeout()), this, SLOT(handleClickTimer()));
label = new FlatButton(crv->title(), parent);
setFixedHeight(label->sizeHint().height());
setFixedWidth(16);
connect(label, SIGNAL(clicked()), this, SLOT(legendClicked()));
connect(label, SIGNAL(mousePressed()), this, SLOT(mousePressed()));
};
void LegendButton::init(SeaPlot *plot, SeaCurve *crv) {
this->plot = plot;
this->crv = crv;
};
void LegendButton::mousePressed() {
clickDone = false;
clickTimer.stop();
clickTimer.start(300);
}
void LegendButton::handleClickTimer() {
if (!clickDone) {
clickTimer.stop();
clickDone = true;
longClick();
}
}
void LegendButton::legendClicked() {
if (!clickDone) {
clickDone = true;
clickTimer.stop();
plot->switchBold(crv);
}
};
void LegendButton::longClick() {
plot->setOneBold(crv);
};
void LegendButton::mousePressEvent(QMouseEvent *e) {
mousePressed();
QwtLegendButton::mousePressEvent(e);
}
void LegendButton::setTitle(const QString &title) {
label->setText(title);
}
class TimeScaleDraw: public QwtScaleDraw {
public:
TimeScaleDraw(SeaSet *set) : QwtScaleDraw() {
this->set = set;
}
virtual QString label(double v) const {
QTime time;
QwtScaleDiv div = scaleDiv();
double step = div.majStep();
int itic0 = lround(div.lBound() / step + 0.5001);
int itic1 = lround(div.hBound() / step - 0.5001);
int itic = lround(v / step);;
int m;
if (step < 23*3600) {
m = 2;
if (step > 59 && step < 121) {
m = 5;
} else if (lround(step) == 600) {
m = 3;
}
} else {
m = (itic1 - itic0) / 5 + 1;
}
time.setHMS(0,0,0);
itic0 = lround(div.lBound() / step / m + 0.5001);
itic1 = lround(div.hBound() / step / m - 0.5001);
if (itic1 - itic0 > 1 && v > div.hBound() - step) {
return ""; //do not show last label
} else if (itic % m == 0) {
if (step < 3599) {
return time.addSecs(lround(v)).toString("hh:mm");
} else if (lround(v/3600) % 24 == 0) {
return set->dateLabel(lround(v/(24*3600)));
} else {
return time.addSecs(lround(v)).toString("hh").append("h");
}
} else {
return "";
}
}
SeaSet *set;
};
class YScaleDraw: public QwtScaleDraw {
public:
YScaleDraw(SeaSet *set) : QwtScaleDraw() {
this->set = set;
}
virtual int maxLabelWidth(const QFontMetrics &fm) const {
return set->leftLabelWidth(QwtScaleDraw::maxLabelWidth(fm));
}
SeaSet *set;
};
class SeaZoomer: public QwtPlotZoomer {
friend class SeaPlot;
public:
SeaZoomer(SeaPlot *p) :
QwtPlotZoomer(QwtPlot::xBottom, QwtPlot::yLeft,
QwtPicker::DragSelection, QwtPicker::AlwaysOff,
p->canvas()) {
yAuto = true;
displayMarker = false;
pl = p;
doZoom = false;
};
SeaPlot *pl;
bool yAuto;
bool setMark;
bool displayMarker;
bool track;
bool doZoom;
int xp, yp;
QTime t;
protected:
QwtDoubleSize minZoomSize() const {
const QwtScaleDiv *scy = pl->axisScale(QwtPlot::yLeft);
double hei = fabs(scy->hBound() + scy->lBound()) * SEA_RESOLUTION;
return QwtDoubleSize(60., hei);
};
void rescale() {
QwtDoubleRect r = zoomRect();
if (doZoom && r != scaleRect()) {
pl->zoomTo(scaleRect(), r);
}
}
void enableMarker() {
if (!displayMarker) {
QWidget *parent = parentWidget();
track = parent->hasMouseTracking();
parent->setMouseTracking(true);
displayMarker = true;
}
}
void disableMarker() {
if (displayMarker) {
QWidget *parent = parentWidget();
displayMarker = false;
parent->setMouseTracking(track);
}
}
void widgetMousePressEvent(QMouseEvent *e) {
//printf("*** mousePress %d %d\n", e->x(), e->y());
xp = e->x();
yp = e->y();
if (pl->set->markerFix) {
setMark = true;
} else {
setMark = !pl->set->markerWasOn;
}
pl->set->markerFix = false;
t.start();
QwtPlotZoomer::widgetMousePressEvent(e);
}
void widgetMouseReleaseEvent(QMouseEvent *e) {
//printf("*** mouseRelease %d %d %d\n", e->x(), e->y(), t.elapsed());
// mechanism for avoiding unintended zooms:
// do not allow if the rectangle is too small
// or if the mouse was pressed for less than 200 ms
doZoom = abs(e->x() - xp) + abs(e->y() - yp) > 2 && t.elapsed() > 200;
QwtPlotZoomer::widgetMouseReleaseEvent(e);
if (!doZoom && !displayMarker && setMark) {
QwtDoublePoint p = invTransform(e->pos());
pl->set->setMarker(p.x());
}
}
void widgetMouseDoubleClickEvent(QMouseEvent *e) {
//printf("*** mouseDoubleClick %d %d %d\n", e->x(), e->y(), t.elapsed());
setMark = true;
pl->set->markerFix = true;
QwtDoublePoint at =invTransform(e->pos());
//pl->set->gotoTime(at.x(), FALSE);
}
void widgetMouseMoveEvent(QMouseEvent *e) {
if (displayMarker) {
QwtDoublePoint p = invTransform(e->pos());
if (!pl->set->markerFix) {
pl->set->setMarker(p.x());
pl->set->gotoTime(p.x(), TRUE);
}
} else {
QwtPlotZoomer::widgetMouseMoveEvent(e);
}
}
};
SeaCurve::SeaCurve(QwtPlot *parent, const QString &title) : QwtPlotCurve(parent, title) {
leg = NULL;
iColor = -1;
};
void SeaCurve::draw(QPainter *p, const QwtDiMap &xMap, const QwtDiMap &yMap,
int from, int to) {
int i, start;
double ylowlim;
if (leg->plot->logarithmic) {
ylowlim = 1e-30;
} else {
ylowlim = DATA_UNDEF;
}
start = -1;
if (to < 0) {
to = dataSize() - 1;
if (to < 0) return;
}
for (i=from; i<=to; i++) {
if (i > 0 && y(i) <= ylowlim) { /* undefined value */
if (start >= 0) {
QwtPlotCurve::draw(p, xMap, yMap, start, i-1);
start = -1;
}
} else {
if (start < 0) start = i;
}
}
if (start >= 0) {
QwtPlotCurve::draw(p, xMap, yMap, start, to);
}
};
void SeaCurve::setPen(const QPen &p) {
QwtCurve::setPen(p);
if (leg) leg->setCurvePen(p);
};
void SeaCurve::setTitle(const QString &t) {
QwtCurve::setTitle(t);
if (leg) leg->setTitle(t);
};
void SeaCurve::yBounds(double from, double to, double &ymin, double &ymax) {
int i, n = dataSize();
double y1, y2;
double ya, yb, yc, yd, yi;
int k, n1, n2, nn;
bool done;
double ylowlim;
// implementing a mechanism to ignore spikes:
// after the first pass, the whole range is divided into 3 ranges.
// in the next pass, only the points in the middle range are taken into account
// for minimum and maximum, the others are counted only. When the number of points
// in the upper or in the lower part are less than 2 %, the range is reduced by
// the upper and/or the lower part and a next pass is done
if (leg->plot->logarithmic) {
ylowlim = 1e-30;
} else {
ylowlim = DATA_UNDEF;
}
y1 = 1e30;
y2 = -1e30;
ya = -1e30;
yb = -1e30;
yc = 1e30;
yd = 1e30;
k = 0;
yi = 1e30;
while (1) {
n1 = 0;
n2 = 0;
nn = 0;
for (i=0; i<n; i++) {
if (x(i)>= from) {
break;
}
}
for (; i<n; i++) {
if (x(i) > to) {
break;
}
yi = y(i);
if (yi > ylowlim) { // and therefore yi != DATA_UNDEF
if (yi < yb) {
n1++;
} else if (yi > yc) {
n2++;
} else {
nn++;
if (yi > y2) {
y2 = yi;
}
if (yi < y1) {
y1 = yi;
}
}
}
}
if (i == n && yi > ylowlim) {
if (yi < y1) y1 = yi;
if (yi > y2) y2 = yi;
}
if (k == 0) {
ya = y1;
yd = y2;
} else {
done = true;
if (n1 * 49 < nn + n2 && y1 >= yb) {
ya = y1;
done = false;
}
if (n2 * 49 < nn + n1 && y2 <= yc) {
yd = y2;
done = false;
}
if (done || k > 20 || ya == yd) {
break;
}
//printf("%s %d %d %d %d %f %f\n", data->label.latin1(), k, n1, n2, nn, ya, yd);
}
k++;
yb = ya + (yd - ya) / 3;
yc = yd - (yd - ya) / 3;
y1 = yc;
y2 = yb;
}
if (yi < 0.9e30) {
ymin = QMIN(ymin, ya);
ymax = QMAX(ymax, yd);
}
return;
}
void SeaCurve::changeBold() {
int i;
if (iColor == -1) {
i = Convert2ColorIndex("black");
} else {
i = iColor;
}
leg->plot->set->saveBoldState(data);
if (data->bold) {
setPen(thisPen(i, 0));
QToolTip::add(leg, "hide curve (long click: show this curve only)");
QToolTip::add(leg->label, "hide curve (long click: show this curve only)");
} else {
setPen(thisPen(i, 1));
QToolTip::add(leg, "show curve (long click: this curve only)");
QToolTip::add(leg->label, "show curve (long click: this curve only)");
}
}
SeaPlot::SeaPlot(QWidget *p, SeaSet *set, const QString &name) : QwtPlot(p, name) {
QTime now(QTime::currentTime());
QTime midnight(0,0,0);
double dnow;
//setAutoLegend( TRUE );
//setLegendPosition( QwtPlot::Left , 0.5);
this->set = set;
dnow = midnight.secsTo(now);
setAxisScale(QwtPlot::xBottom, dnow-1800, dnow+600, 300.0);
setAxisScale(QwtPlot::yLeft, 0.0, 0.9999);
setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw(set));
setAxisScaleDraw(QwtPlot::yLeft, new YScaleDraw(set));
setAxisLabelFormat(QwtPlot::yLeft, 'g', 5);
zoom = new SeaZoomer(this);
zoom->setRubberBand(QwtPlotZoomer::RectRubberBand);
zoom->setRubberBandPen(QPen(Qt::red));
plotLayout()->setCanvasMargin(0, SeaPlot::yLeft);
plotLayout()->setCanvasMargin(0, SeaPlot::yRight);
canvas()->setPaletteBackgroundColor(QColor(238,238,238));
setGridPen(QPen(QColor(221, 221, 221), 1));
logarithmic = false;
removeAll();
};
void SeaPlot::removeAll() {
tag = "";
removeCurves();
marker = insertCurve("Marker");
// setCurvePen(marker, QPen(Qt::white, 3));
setCurvePen(marker, QPen(Qt::black, 1));
}
void SeaPlot::setMarker(double x) {
const QwtScaleDiv *s = axisScale(yLeft);
double xval[2], yval[2], h;
SeaCurve *crv;
QwtPlotCurveIterator itc = curveIterator();
if (x == DATA_UNDEF) {
zoom->disableMarker();
xval[0] = DATA_UNDEF;
xval[1] = DATA_UNDEF;
} else {
zoom->enableMarker();
xval[0] = x;
xval[1] = x;
}
h = s->hBound() - s->lBound();
if (logarithmic) {
yval[0] = s->lBound() * 0.5;
} else {
yval[0] = s->lBound() - h;
}
yval[1] = s->hBound() + h;
setCurveData(marker, xval, yval, 2);
for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) {
crv = dynamic_cast<SeaCurve *>(c);
if (crv) {
crv->data->showValueAt(x);
}
}
}
void SeaPlot::refresh() {
const QwtScaleDiv *s = axisScale(xBottom);
replotRange(s->lBound(), s->hBound(), s->majStep());
};
void SeaPlot::autoColors() {
SeaCurve *crv;
QwtPlotCurveIterator itc = curveIterator();
int i;
usedColors.fill(false, N_SEACOLORS);
usedColors.setBit(0); // do not use white
for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) {
crv = dynamic_cast<SeaCurve *>(c);
if (crv) {
if (crv->iColor >= 0 && crv->iColor < N_SEACOLORS) {
usedColors.setBit(crv->iColor);
}
}
}
i=0;
for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) {
crv = dynamic_cast<SeaCurve *>(c);
if (crv) {
if (crv->iColor < 0) {
while (i < N_SEACOLORS && usedColors.at(i)) {
i++;
}
if (i >= N_SEACOLORS) {
crv->iColor = Convert2ColorIndex("black");
} else {
crv->iColor = i;
usedColors.setBit(i);
crv->changeBold();
}
}
}
}
}
LegendButton *SeaPlot::putCurve(SeaData *d, QWidget *parent, LegendButton *legButton) {
SeaCurve *crv = new SeaCurve(this, d->label);
d->plot = this;
d->key = insertCurve(crv);
if (legButton) {
legButton->init(this, crv);
} else {
legButton = new LegendButton(parent, this, crv);
}
legButton->setTitle(d->label);
//QFont myfont(legButton->font());
//myfont.setPointSize(12);
//legButton->setFont(myfont);
//legButton->setFixedHeight(legButton->fontMetrics().height()+2);
crv->leg = legButton;
//crv->leg->setTitle(crv->title());
//crv->leg->setTitle("");
//if (d->style >= 0 && d->style <= N_SEACOLORS) {
if (d->style >= 0) {
crv->iColor = d->style;
crv->autoColor = false;
} else {
crv->iColor = -1;
crv->autoColor = true;
}
crv->data = d;
crv->changeBold();
crv->setStyle(QwtCurve::Lines);
d->modified = true;
d->update();
return legButton;
}
void SeaPlot::showAgain() {
SeaCurve *crv;
QwtPlotCurveIterator itc = curveIterator();
for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) {
crv = dynamic_cast<SeaCurve *>(c);
if (crv) {
crv->data->bold = true;
crv->changeBold();
}
}
autoColors();
//refresh();
set->replotAnyway();
}
bool SeaPlot::toggleLinLog() {
logarithmic = ! logarithmic;
if (logarithmic) {
setAxisOptions(QwtPlot::yLeft, QwtAutoScale::Logarithmic);
} else {
setAxisOptions(QwtPlot::yLeft, QwtAutoScale::None);
}
refresh();
return logarithmic;
}
void SeaPlot::zoomMax() {
autoZoom(true);
refresh();
}
void SeaPlot::autoZoom(bool on) {
zoom->yAuto = on;
setAutoOff(!on);
}
void SeaPlot::undoZoom(QwtDoubleRect &r) {
setAxisScale(QwtPlot::yLeft, r.y1(), r.y2());
zoom->setZoomBase();
}
void SeaPlot::zoomTo(QwtDoubleRect old, QwtDoubleRect &r) {
set->saveRange(this, old, zoom->yAuto);
autoZoom(false);
setAxisScale(QwtPlot::yLeft, r.y1(), r.y2());
set->rescale(r.x1(), r.x2());
zoom->setZoomBase();
}
void SeaPlot::zoomOut() {
QwtDoubleRect r = zoom->zoomRect();
QwtDoubleRect old= zoom->scaleRect();
set->saveRange(this, old, zoom->yAuto);
if (logarithmic) {
double h = log(r.y2()) - log(r.y1());
r.setY2(exp(log(r.y2()) + h / 2));
r.setY1(exp(log(r.y1()) - h / 2));
} else {
double h = r.y2() - r.y1();
r.setY2(r.y2() + h / 2);
r.setY1(r.y1() - h / 2);
}
zoom->setZoomBase(r);
zoom->zoom(0);
autoZoom(false);
refresh();
}
void SeaPlot::shiftUp() {
QwtDoubleRect r = zoom->zoomRect();
double h = (r.y2() - r.y1()) * 0.5;
QwtDoubleRect old= zoom->scaleRect();
set->saveRange(this, old, zoom->yAuto);
r.setY2(r.y2() + h);
r.setY1(r.y1() + h);
zoom->setZoomBase(r);
zoom->zoom(r);
zoom->setZoomBase();
zoom->zoom(0);
autoZoom(false);
refresh();
}
void SeaPlot::shiftDown() {
QwtDoubleRect r = zoom->zoomRect();
double h = (r.y2() - r.y1()) * 0.5;
QwtDoubleRect old= zoom->scaleRect();
set->saveRange(this, old, zoom->yAuto);
r.setY2(r.y2() - h);
r.setY1(r.y1() - h);
zoom->setZoomBase(r);
zoom->zoom(r);
zoom->setZoomBase();
zoom->zoom(0);
autoZoom(false);
refresh();
}
int SeaPlot::replotRange(double from, double to, double tick) {
SeaCurve *crv;
QwtDoubleRect r;
double ymin, ymax, yminA, ymaxA, y0, res;
int result;
bool nobold;
result = 1;
ymin = 1e30;
ymax = -1e30;
yminA = ymin;
ymaxA = ymax;
nobold = true;
QwtPlotCurveIterator itc = curveIterator();
for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) {
crv = dynamic_cast<SeaCurve *>(c);
if (crv) {
crv->yBounds(from, to, yminA, ymaxA);
if (crv->data->bold) {
crv->yBounds(from, to, ymin, ymax);
}
}
}
if (ymin > ymax) {
ymin = yminA;
ymax = ymaxA;
if (ymin > ymax) {
ymin = 0;
ymax = 1;
result = 0;
}
}
r = zoom->zoomRect();
if (zoom->yAuto) {
res = 0.1;
} else {
ymin = r.y1();
ymax = r.y2();
res = SEA_RESOLUTION;
}
if (logarithmic) {
if (ymax <= 0) {
ymax = ymaxA;
ymin = yminA;
} else if (ymin <= 0) {
ymin = QMIN(ymax, yminA);
}
y0 = (log(ymin) + log(ymax)) / 2;
if ((log(ymax) - log(ymin)) < res) {
ymin = exp(y0 - res * 0.4999);
ymax = exp(y0 + res * 0.5);
}
} else {
y0 = (ymin + ymax) / 2;
if ((ymax - ymin) < y0 * res) {
ymin = y0 * (1 - res * 0.4999);
ymax = y0 * (1 + res * 0.5);
}
}
setAxisMaxMinor(QwtPlot::xBottom, 0);
setAxisScale(QwtPlot::xBottom, from, to, tick);
setAxisScale(QwtPlot::yLeft, ymin, ymax);
replot();
if (zoom->zoomRectIndex() == 0) {
zoom->setZoomBase();
}
return result;
};
int SeaPlot::replotRange() {
const QwtScaleDiv *scx = axisScale(QwtPlot::xBottom);
return replotRange(scx->lBound(), scx->hBound(), scx->majStep());
};
void SeaPlot::switchBold(SeaCurve *crv) {
crv->data->bold = !crv->data->bold;
crv->changeBold();
crv->leg->update();
set->replotAnyway();
set->updateCurveList();
}
void SeaPlot::setOneBold(SeaCurve *selectedCrv) {
QwtPlotCurveIterator itc = curveIterator();
SeaCurve *crv;
for (QwtPlotCurve *c = itc.toFirst(); c != 0; c = ++itc ) {
crv = dynamic_cast<SeaCurve *>(c);
if (crv) { // omit Marker
crv->data->bold = crv == selectedCrv;
crv->changeBold();
crv->leg->update();
}
}
autoZoom(true);
set->replotAnyway();
set->updateCurveList();
}