diff --git a/libs/graphic/graphic.cpp b/libs/graphic/graphic.cpp index 11cc548..ebe28e9 100644 --- a/libs/graphic/graphic.cpp +++ b/libs/graphic/graphic.cpp @@ -433,6 +433,7 @@ void Graphic::canvasPaintEvent() { gridborder += QPoint(font_sz.width(), font_sz.height()); if (hasLblY) gridborder += QPoint(font_sz.height(), 0); if (hasLblX) gridborder += QPoint(0, font_sz.height()); + if (axis_type_x == DateTime) gridborder += QPoint(0, font_sz.height()); } painter->setClipping(true); painter->setClipRect(QRect(gridborder.x(), 0, wid - gridborder.x(), hei - gridborder.y())); @@ -1285,27 +1286,53 @@ void Graphic::drawGrid() { } } else { int cur_scl[7] = {0,0,0,0,0,0,0}; - step = splitRangeDate(range, wid / gridx / font_sz.width() * 1.4, &df, cur_scl); - start = roundTo(canvas2realX(right), step); - px = start + step; - QDateTime cd = QDateTime::fromMSecsSinceEpoch(px * grid_numbers_x); - roundDateTime(cd, cur_scl); - addDateTime(cd, cur_scl); + const char * formats[2]; + step = splitRangeDate(range, wid / gridx / font_sz.width() * 1.4, formats[0], formats[1], cur_scl); if (step > 0.) { + QDateTime cd = QDateTime::fromMSecsSinceEpoch(canvas2realX(gbx) * grid_numbers_x), cdp, cdc; + //qDebug() << step << range << int(wid / gridx / font_sz.width() * 1.4) << cd; + roundDateTime(cd, cur_scl); + cdp = cd; + //qDebug() << cd << cur_scl[0] << cur_scl[1] << cur_scl[2] << cur_scl[3] << cur_scl[4] << cur_scl[5] << cur_scl[6]; + struct Anchor { + int pix_x[2]; + QDateTime date; + }; + QVector areas; cnt = 1000; + int area_start = gbx; while (cnt-- > 0) { - addDateTime(cd, cur_scl, -1); + addDateTime(cd, cur_scl); + cdc = cd; + roundDateTime(cdc, cur_scl); cx = real2canvasX(cd.toMSecsSinceEpoch() / grid_numbers_x); - if (cx > right) continue; - if (cx < gbx) break; + if (cx < gbx) continue; + if (cdp != cdc) { + areas << Anchor{{area_start, qMin(cx, right)}, cdp}; + area_start = areas.back().pix_x[1]; + cdp = cdc; + } + if (cx > right) break; painter->setPen(grid_pen); painter->drawLine(cx, hei + 5, cx, 0); painter->setPen(text_color); int dx = -font_sz.height() / 4.; painter->setFont(nf); - str.first = cd.toString(df); + str.first = cd.toString(formats[0]); painter->drawText(cx + dx, cy, str.first); } + if (area_start < right) + areas << Anchor{{area_start, right}, cdc}; + painter->setPen(grid_pen); + for (const auto & a: areas) { + painter->drawLine(a.pix_x[0], hei + 5, a.pix_x[0], cy - font_sz.height()); + } + painter->setPen(text_color); + painter->setFont(nf); + for (const auto & a: areas) { + QRect r(a.pix_x[0], cy - (2 * font_sz.height()), a.pix_x[1] - a.pix_x[0], font_sz.height()); + painter->drawText(r, Qt::AlignCenter | Qt::TextDontClip, a.date.toString(formats[1])); + } } } painter->setPen(text_color); @@ -1573,6 +1600,33 @@ void Graphic::drawPause() { painter->setClipping(true); } +double roundToSteps(double value, const QVector & steps) { + double ret = value, min_err = -1.; + for (double v: steps) { + double sv = qRound64(value / v) * v; + double err = qAbs(value - sv); + if (min_err < 0 || min_err > err) { + min_err = err; + ret = sv; + } + } + if (ret < steps[0]) ret = steps[0]; + return ret; +} + +double roundToNearest(double value, const QVector & values) { + double ret = value, min_err = -1.; + for (double v: values) { + double err = qAbs(value - v); + if (min_err < 0 || min_err > err) { + min_err = err; + ret = v; + } + } + if (ret < values[0]) ret = values[0]; + return ret; +} + double Graphic::splitRange(double range, int count) { double digits, step, tln; @@ -1587,45 +1641,65 @@ double Graphic::splitRange(double range, int count) { break; } } - double step5 = qRound(step / 5.) * 5., step10 = qRound(step / 10.) * 10.; - double err5 = qAbs(step - step5), err10 = qAbs(step - step10); - step = (err5 < err10 ? step5 : step10) * digits; + step = roundToSteps(step, {5., 10.}) * digits; return step; } -double Graphic::splitRangeDate(double range, int count, QString * format, int step[7]) { +double Graphic::splitRangeDate(double range, int count, const char *& up_format, const char *& lo_format, int step[7]) { + static const qint64 + to_sec = 1000LL, + to_min = 1000LL * 60, + to_hour = 1000LL * 60 * 60, + to_day = 1000LL * 60 * 60 * 24, + to_month = 1000LL * 60 * 60 * 24 * 30, + to_year = 1000LL * 60 * 60 * 24 * 30 * 12; + static const struct { + const char * upper; + const char * lower; + } formats[] = { + {"ss.zzz 's'" , "yyyy MMM dd(ddd) h:mm:ss"}, + {"ss 's'" , "yyyy MMM dd(ddd) h:mm"}, + {"mm 'm'" , "yyyy MMM dd(ddd) h 'h' "}, + {"h 'h'" , "yyyy MMM dd(ddd)"}, + {"dd(ddd)" , "yyyy MMM"}, + {"MMM" , "yyyy"}, + {"yyyy" , ""} + }; double ret = splitRange(range, count); - if (ret < 1000. * 1) {*format = "ss.zzz"; step[0] = ret;} - else if (ret < 1000. * 60) {*format = "h:m:ss"; step[1] = qRound(ret / 1000);} - else if (ret < 1000. * 60 * 60) {*format = "h:mm"; step[2] = qRound(ret / 1000 / 60);} - else if (ret < 1000. * 60 * 60 * 24) {*format = "dd(ddd) hh"; step[3] = qRound(ret / 1000 / 60 / 60);} - else if (ret < 1000. * 60 * 60 * 24 * 30) {*format = "MMM dd"; step[4] = qRound(ret / 1000 / 60 / 60 / 24);} - else if (ret < 1000. * 60 * 60 * 24 * 30 * 12) {*format = "yyyy MMM"; step[5] = qRound(ret / 1000 / 60 / 60 / 24 / 30);} - else {*format = "yyyy"; step[6] = qRound(ret / 1000 / 60 / 60 / 24 / 30 / 12);} + int format_index = 6; + if (ret < to_sec / 3) {format_index = 0; step[0] = qRound64(ret);} + else if (ret < to_min / 2) {format_index = 1; step[1] = roundToNearest(ret / to_sec , {1, 2, 5, 10, 15, 20, 30});} + else if (ret < to_hour / 2) {format_index = 2; step[2] = roundToNearest(ret / to_min , {1, 2, 5, 10, 15, 20, 30});} + else if (ret < to_day / 2) {format_index = 3; step[3] = roundToNearest(ret / to_hour , {1, 2, 3, 4, 6, 8, 12});} + else if (ret < to_month ) {format_index = 4; step[4] = roundToNearest(ret / to_day , {1, 2, 5, 10});} + else if (ret < to_year ) {format_index = 5; step[5] = roundToNearest(ret / to_month, {1, 2, 3, 4, 6});} + else {format_index = 6; step[6] = qRound64(ret);} + up_format = formats[format_index].upper; + lo_format = formats[format_index].lower; return ret; } double Graphic::roundTo(double value, double round_to) { if (round_to == 0.) return value; - return qRound(value / round_to) * round_to; + return qRound64(value / round_to) * round_to; } void Graphic::roundDateTime(QDateTime & dt, int c[7]) { QDate d(dt.date()); QTime t(dt.time()); - if (c[1] != 0) t.setHMS(t.hour(), t.minute(), t.second()); - if (c[2] != 0) t.setHMS(t.hour(), t.minute(), 0); - if (c[3] != 0) t.setHMS(t.hour(), 0, 0); - if (c[4] != 0) {t.setHMS(0, 0, 0); d.setDate(d.year(), d.month(), d.day());} - if (c[5] != 0) {t.setHMS(0, 0, 0); d.setDate(d.year(), d.month(), 1);} - if (c[6] != 0) {t.setHMS(0, 0, 0); d.setDate(d.year(), 1, 1);} + if (c[0] != 0) t.setHMS(t.hour(), t.minute(), t.second()); + if (c[1] != 0) t.setHMS(t.hour(), t.minute(), 0); + if (c[2] != 0) t.setHMS(t.hour(), 0, 0); + if (c[3] != 0) {t.setHMS(0, 0, 0); d.setDate(d.year(), d.month(), d.day());} + if (c[4] != 0) {t.setHMS(0, 0, 0); d.setDate(d.year(), d.month(), 1);} + if (c[5] != 0 || c[6] != 0) {t.setHMS(0, 0, 0); d.setDate(d.year(), 1, 1);} dt = QDateTime(d, t); } -void Graphic::addDateTime(QDateTime & dt, int c[7], int mul) { +void Graphic::addDateTime(QDateTime & dt, int c[7], qint64 mul) { if (c[0] != 0) dt = dt.addMSecs(mul * c[0]); if (c[1] != 0) dt = dt.addSecs(mul * c[1]); if (c[2] != 0) dt = dt.addSecs(mul * c[2] * 60); diff --git a/libs/graphic/graphic.h b/libs/graphic/graphic.h index 83b95e4..1e703c0 100644 --- a/libs/graphic/graphic.h +++ b/libs/graphic/graphic.h @@ -388,10 +388,10 @@ protected: void setRectToLines(); void checkLines(); double splitRange(double range, int count = 1); - double splitRangeDate(double range, int count = 1, QString * format = 0, int step[7] = 0); + double splitRangeDate(double range, int count, const char *& up_format, const char *& lo_format, int step[7]); double roundTo(double value, double round_to); void roundDateTime(QDateTime & dt, int c[7]); - void addDateTime(QDateTime & dt, int c[7], int mul = 1); + void addDateTime(QDateTime & dt, int c[7], qint64 mul = 1); QPointF absPoint(QPointF point) {return QPointF(qAbs(point.x()), qAbs(point.y()));} QString pointCoords(QPointF point, bool x = true, bool y = true); QPair gridMark(double v) const;