#include "seaplot.h" #include #include #include #include #include #include #include #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= from) { break; } } for (; 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(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(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(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(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(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(c); if (crv) { // omit Marker crv->data->bold = crv == selectedCrv; crv->changeBold(); crv->leg->update(); } } autoZoom(true); set->replotAnyway(); set->updateCurveList(); }