mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-04 18:20:42 +00:00
513 lines
15 KiB
C++
513 lines
15 KiB
C++
/* gXAxis Implementation
|
|
*
|
|
* Copyright (c) 2019-2024 The OSCAR Team
|
|
* Copyright (c) 2011-2018 Mark Watkins <mark@jedimark.net>
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file COPYING in the main directory of the source code
|
|
* for more details. */
|
|
|
|
#define TEST_MACROS_ENABLEDoff
|
|
#include <test_macros.h>
|
|
|
|
#include <QDebug>
|
|
#include <QFontMetrics>
|
|
|
|
#include <math.h>
|
|
|
|
#include "Graphs/gXAxis.h"
|
|
#include "SleepLib/profiles.h"
|
|
#include "SleepLib/common.h"
|
|
#include "Graphs/glcommon.h"
|
|
#include "Graphs/gGraph.h"
|
|
#include "Graphs/gGraphView.h"
|
|
|
|
#define FIX_FOR_DATE_MISMATCH
|
|
|
|
// These divisors are used to round xaxis timestamps to reasonable increments
|
|
const quint64 divisors[] = {
|
|
15552000000ULL, 7776000000ULL, 5184000000ULL, 2419200000ULL, 1814400000ULL, 1209600000L, 604800000L, 259200000L,
|
|
172800000L, 86400000, 2880000, 14400000, 7200000, 3600000, 2700000,
|
|
1800000, 1200000, 900000, 600000, 300000, 120000, 60000, 45000, 30000,
|
|
20000, 15000, 10000, 5000, 2000, 1000, 500, 100, 50, 10, 1
|
|
};
|
|
const int divcnt = sizeof(divisors) / sizeof(quint64);
|
|
|
|
gXAxis::gXAxis(QColor col, bool fadeout)
|
|
: Layer(NoChannel)
|
|
{
|
|
m_line_color = col;
|
|
m_text_color = col;
|
|
m_major_color = Qt::darkGray;
|
|
m_minor_color = Qt::lightGray;
|
|
m_show_major_lines = false;
|
|
m_show_minor_lines = false;
|
|
m_show_minor_ticks = true;
|
|
m_show_major_ticks = true;
|
|
m_utcfix = false;
|
|
m_fadeout = fadeout;
|
|
|
|
#if !defined (FIX_FOR_DATE_MISMATCH) // between labels on graphs and system data,
|
|
tz_offset = timezoneOffset();
|
|
tz_hours = tz_offset / 3600000.0;
|
|
#endif
|
|
|
|
m_roundDays = false;
|
|
}
|
|
gXAxis::~gXAxis()
|
|
{
|
|
}
|
|
|
|
int gXAxis::minimumHeight()
|
|
{
|
|
QFontMetrics fm(*defaultfont);
|
|
int h = fm.height();
|
|
#if defined(Q_OS_MAC)
|
|
return 9+h;
|
|
#else
|
|
return 11+h;
|
|
#endif
|
|
}
|
|
|
|
const QString months[] = {
|
|
QObject::tr("Jan"), QObject::tr("Feb"), QObject::tr("Mar"), QObject::tr("Apr"), QObject::tr("May"), QObject::tr("Jun"),
|
|
QObject::tr("Jul"), QObject::tr("Aug"), QObject::tr("Sep"), QObject::tr("Oct"), QObject::tr("Nov"), QObject::tr("Dec")
|
|
};
|
|
//static QString dow[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
|
|
|
|
|
|
void gXAxis::SetDay(Day* day) {
|
|
if (!day) return;
|
|
|
|
#if defined (FIX_FOR_DATE_MISMATCH) // between labels on graphs and system data,
|
|
#if QT_VERSION < QT_VERSION_CHECK(5,14,0)
|
|
startOfDay = QDateTime(QDateTime::fromMSecsSinceEpoch(day->first()).date()).toMSecsSinceEpoch();
|
|
#else
|
|
startOfDay = QDateTime::fromMSecsSinceEpoch(day->first()).date().startOfDay().toMSecsSinceEpoch();
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
void gXAxis::paint(QPainter &painter, gGraph &w, const QRegion ®ion)
|
|
{
|
|
float left = region.boundingRect().left();
|
|
float top = region.boundingRect().top()-0.999f;
|
|
float width = region.boundingRect().width();
|
|
float height = region.boundingRect().height();
|
|
|
|
|
|
QVector<QLineF> ticks;
|
|
|
|
|
|
QPainter painter2; // Only need this for pixmap caching
|
|
|
|
// pixmap caching screws font size when printing
|
|
|
|
|
|
QFontMetrics fm(*defaultfont);
|
|
|
|
bool usepixmap = AppSetting->usePixmapCaching(); // Whether or not to use pixmap caching
|
|
|
|
if (!usepixmap || (usepixmap && w.invalidate_xAxisImage)) {
|
|
// Redraw graph xaxis labels and ticks either to pixmap or directly to screen
|
|
|
|
if (usepixmap) {
|
|
// Initialize a new cache image
|
|
m_image = QImage(width + 22, height + 4, QImage::Format_ARGB32_Premultiplied);
|
|
m_image.fill(Qt::transparent);
|
|
painter2.begin(&m_image);
|
|
painter2.setPen(Qt::black);
|
|
painter2.setFont(*defaultfont);
|
|
}
|
|
|
|
double px, py;
|
|
|
|
int start_px = left;
|
|
//int start_py=top;
|
|
//int width=scrx-(w.GetLeftMargin()+w.GetRightMargin());
|
|
// float height=scry-(w.GetTopMargin()+w.GetBottomMargin());
|
|
|
|
if (width < 40) {
|
|
return;
|
|
}
|
|
|
|
qint64 minx;
|
|
qint64 maxx;
|
|
|
|
if (w.blockZoom()) {
|
|
// Lock zoom to entire data range
|
|
minx = w.rmin_x;
|
|
maxx = w.rmax_x;
|
|
} else {
|
|
// Allow zoom
|
|
minx = w.min_x;
|
|
maxx = w.max_x;
|
|
|
|
}
|
|
|
|
int days = ceil(double(maxx-minx) / 86400000.0);
|
|
|
|
if (m_roundDays) {
|
|
minx = floor(double(minx)/86400000.0);
|
|
minx *= 86400000L;
|
|
|
|
maxx = minx + 86400000L * qint64(days);
|
|
}
|
|
|
|
// duration of graph display window in milliseconds.
|
|
qint64 xx = maxx - minx;
|
|
|
|
// shouldn't really be negative, but this is safer than an assert
|
|
if (xx <= 0) {
|
|
return;
|
|
}
|
|
|
|
//Most of this could be precalculated when min/max is set..
|
|
QString fd, tmpstr;
|
|
int divmax, dividx;
|
|
int fitmode;
|
|
|
|
// Have a quick look at the scale and prep the autoscaler a little faster
|
|
if (xx >= 86400000L) { // Day
|
|
fd = "Mjj 00";
|
|
dividx = 0;
|
|
divmax = 10;
|
|
fitmode = 0;
|
|
} else if (xx > 1800000) { // Minutes
|
|
fd = " j0:00";
|
|
dividx = 10;
|
|
divmax = 21;
|
|
fitmode = 1;
|
|
} else if (xx > 5000) { // Seconds
|
|
fd = " j0:00:00";
|
|
dividx = 16;
|
|
divmax = 29;
|
|
fitmode = 2;
|
|
} else { // Microseconds
|
|
fd = "j0:00:00:000";
|
|
dividx = 28;
|
|
divmax = divcnt;
|
|
fitmode = 3;
|
|
}
|
|
|
|
//if (divmax>divcnt) divmax=divcnt;
|
|
|
|
int x, y;
|
|
// grab the text extent of the dummy text fields above to know how much space is needed
|
|
QRect r2 = fm.boundingRect(fd);
|
|
x = r2.width();
|
|
y = r2.height();
|
|
|
|
// Not sure when this was a problem...
|
|
if (x<=0) {
|
|
qWarning() << "gXAxis::Paint called with x<=0";
|
|
return;
|
|
}
|
|
|
|
// Max number of ticks that will fit, with a bit of room for a buffer
|
|
int max_ticks = width / (x + 15);
|
|
|
|
int fit_ticks = 0;
|
|
int div = -1;
|
|
qint64 closest = 0, tmp, tmpft;
|
|
|
|
// Scan through divisor list with the index range given above, to find which
|
|
// gives the closest number of ticks to the maximum that will physically fit
|
|
|
|
for (int i = dividx; i < divmax; i++) {
|
|
tmpft = xx / divisors[i];
|
|
tmp = max_ticks - tmpft;
|
|
|
|
if (tmp < 0) { continue; }
|
|
|
|
if (tmpft > closest) { // Find the closest scale to the number
|
|
closest = tmpft; // that will fit
|
|
div = i;
|
|
fit_ticks = tmpft;
|
|
}
|
|
}
|
|
|
|
if (fit_ticks == 0) {
|
|
qDebug() << "gXAxis::Plot() Couldn't fit ticks.. Too short?" << minx << maxx << xx;
|
|
return;
|
|
}
|
|
|
|
if ((div < 0) || (div > divcnt)) {
|
|
qDebug() << "gXAxis::Plot() div out of bounds";
|
|
return;
|
|
}
|
|
|
|
qint64 step = divisors[div];
|
|
|
|
//Align left minimum to divisor by losing precision
|
|
qint64 aligned_start = minx / step;
|
|
aligned_start *= step;
|
|
|
|
while (aligned_start < minx) {
|
|
aligned_start += step;
|
|
}
|
|
|
|
painter.setPen(QColor(Qt::black));
|
|
|
|
int num_minor_ticks;
|
|
|
|
if (step >= 86400000) {
|
|
qint64 i = step / 86400000L; // number of days
|
|
|
|
if (i > 14) { i /= 2; }
|
|
|
|
if (i < 0) { i = 1; }
|
|
|
|
num_minor_ticks = i;
|
|
} else { num_minor_ticks = 10; }
|
|
|
|
float xmult = double(width) / double(xx);
|
|
float step_pixels = double(step / float(num_minor_ticks)) * xmult;
|
|
|
|
py = left + float(aligned_start - minx) * xmult;
|
|
|
|
//py+=usepixmap ? 20 : left;
|
|
|
|
int mintop = top + 3.0 * (float(y) / 10.0);
|
|
int majtop = top + 6.0 * (float(y) / 10.0);
|
|
int texttop = majtop + y; // 18*w.printScaleY();
|
|
#if defined (Q_OS_MAC)
|
|
texttop += 2;
|
|
#endif
|
|
|
|
// Fill in the minor tick marks up to the first major alignment tick
|
|
|
|
for (int i = 0; i < num_minor_ticks; i++) {
|
|
py -= step_pixels;
|
|
|
|
if (py < start_px) { continue; }
|
|
|
|
if (usepixmap) {
|
|
ticks.append(QLineF(py - left + 20, 0, py - left + 20, mintop - top));
|
|
} else {
|
|
ticks.append(QLineF(py, top+2, py, mintop+2));
|
|
}
|
|
}
|
|
|
|
int ms, m, h, s, d;
|
|
qint64 j;
|
|
|
|
for (qint64 i = aligned_start; i < maxx; i += step) {
|
|
px = (i - minx) * xmult;
|
|
px += left;
|
|
|
|
if (usepixmap) {
|
|
ticks.append(QLineF(px - left + 20, 0, px - left + 20, majtop - top));
|
|
} else {
|
|
ticks.append(QLineF(px, top+2, px, majtop+2));
|
|
}
|
|
|
|
j = i;
|
|
|
|
#if defined (FIX_FOR_DATE_MISMATCH) // between labels on graphs and system data,
|
|
qint64 msInDay = j - startOfDay;
|
|
|
|
ms = msInDay % 1000;
|
|
s = (msInDay / 1000L) % 60L;
|
|
m = (msInDay / 60000L) % 60L;
|
|
h = (msInDay / 3600000L) % 24L;
|
|
#else
|
|
if (!m_utcfix) { j += tz_offset; }
|
|
|
|
ms = j % 1000;
|
|
s = (j / 1000L) % 60L;
|
|
m = (j / 60000L) % 60L;
|
|
h = (j / 3600000L) % 24L;
|
|
//int d=(j/86400000) % 7;
|
|
#endif
|
|
|
|
if (fitmode == 0) {
|
|
d = (j / 1000);
|
|
QDateTime dt = QDateTime::fromTime_t(d).toLocalTime();
|
|
QDate date = dt.date();
|
|
// SLOW SLOW SLOW!!! On Mac especially, this function is pathetically slow.
|
|
//dt.toString("MMM dd");
|
|
|
|
// Doing it this way instead because it's MUUUUUUCH faster
|
|
tmpstr = QString(dayFirst?"%1 %2":"%2 %1").arg(date.day()).arg(months[date.month() - 1]);
|
|
|
|
//} else if (fitmode==0) {
|
|
// tmpstr=QString("%1 %2:%3").arg(dow[d]).arg(h,2,10,QChar('0')).arg(m,2,10,QChar('0'));
|
|
} else if (fitmode == 1) { // minute
|
|
tmpstr = QString("%1:%2").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0'));
|
|
} else if (fitmode == 2) { // second
|
|
tmpstr = QString("%1:%2:%3").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0')).arg(s, 2, 10, QChar('0'));
|
|
} else if (fitmode == 3) { // milli
|
|
tmpstr = QString("%1:%2:%3:%4").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0')).arg(s, 2, 10, QChar('0')).arg(ms, 3, 10, QChar('0'));
|
|
}
|
|
|
|
int tx = px - x / 2.0;
|
|
|
|
if (m_utcfix) {
|
|
tx += step_pixels / 2.0;
|
|
}
|
|
|
|
if ((tx + x) < (left + width)) {
|
|
if (!usepixmap) { w.renderText(tmpstr, tx, texttop, 0, Qt::black, defaultfont); }
|
|
else { painter2.drawText(tx - left + 20, texttop - top, tmpstr); }
|
|
}
|
|
|
|
py = px;
|
|
|
|
for (int j = 1; j < num_minor_ticks; j++) {
|
|
py += step_pixels;
|
|
|
|
if (py >= left + width) { break; }
|
|
|
|
if (usepixmap) {
|
|
ticks.append(QLineF(py - left + 20, 0, py - left + 20, mintop - top));
|
|
} else {
|
|
ticks.append(QLineF(py, top+2, py, mintop+2));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (usepixmap) {
|
|
painter2.drawLines(ticks);
|
|
painter2.end();
|
|
} else {
|
|
painter.drawLines(ticks);
|
|
}
|
|
w.graphView()->lines_drawn_this_frame += ticks.size();
|
|
|
|
w.invalidate_xAxisImage = false;
|
|
}
|
|
|
|
if (usepixmap && !m_image.isNull()) {
|
|
painter.drawImage(QPoint(left - 20, top + height - m_image.height() + 5), m_image);
|
|
}
|
|
}
|
|
|
|
|
|
gXAxisDay::gXAxisDay(QColor col)
|
|
:Layer(NoChannel)
|
|
{
|
|
m_line_color = col;
|
|
m_text_color = col;
|
|
m_major_color = Qt::darkGray;
|
|
m_minor_color = Qt::lightGray;
|
|
m_show_major_lines = false;
|
|
m_show_minor_lines = false;
|
|
m_show_minor_ticks = true;
|
|
m_show_major_ticks = true;
|
|
}
|
|
gXAxisDay::~gXAxisDay()
|
|
{
|
|
}
|
|
|
|
int gXAxisDay::minimumHeight()
|
|
{
|
|
QFontMetrics fm(*defaultfont);
|
|
int h = fm.height();
|
|
#if defined(Q_OS_MAC)
|
|
return 9+h;
|
|
#else
|
|
return 11+h;
|
|
#endif
|
|
}
|
|
|
|
void gXAxisDay::paint(QPainter &painter, gGraph &graph, const QRegion ®ion)
|
|
{
|
|
float left = region.boundingRect().left();
|
|
float top = region.boundingRect().top();
|
|
float width = region.boundingRect().width();
|
|
//float height = region.boundingRect().height();
|
|
|
|
QString months[] = {
|
|
QObject::tr("Jan"), QObject::tr("Feb"), QObject::tr("Mar"), QObject::tr("Apr"), QObject::tr("May"), QObject::tr("Jun"),
|
|
QObject::tr("Jul"), QObject::tr("Aug"), QObject::tr("Sep"), QObject::tr("Oct"), QObject::tr("Nov"), QObject::tr("Dec")
|
|
};
|
|
qint64 minx;
|
|
qint64 maxx;
|
|
|
|
minx = graph.min_x;
|
|
maxx = graph.max_x;
|
|
|
|
QDateTime date2 = QDateTime::fromMSecsSinceEpoch(minx, Qt::LocalTime);
|
|
// QDateTime enddate2 = QDateTime::fromMSecsSinceEpoch(maxx, Qt::UTC);
|
|
|
|
//qInfo() << "Drawing date axis from " << date2 << " to " << enddate2;
|
|
|
|
QDate date = date2.date();
|
|
// QDate enddate = enddate2.date();
|
|
|
|
int days = ceil(double(maxx - minx) / 86400000.0);
|
|
|
|
float barw = width / float(days);
|
|
|
|
qint64 xx = maxx - minx;
|
|
|
|
// shouldn't really be negative, but this is safer than an assert
|
|
if (xx <= 0) {
|
|
return;
|
|
}
|
|
|
|
|
|
float lastx = left;
|
|
float y1 = top;
|
|
|
|
QString fd = "Mjj 00";
|
|
int x,y;
|
|
GetTextExtent(fd, x, y);
|
|
float xpos = (barw / 2.0) - (float(x) / 2.0);
|
|
|
|
float lastxpos = 0;
|
|
QVector<QLineF> lines;
|
|
for (int i=0; i < days; i++) {
|
|
if ((lastx + barw) > (left + width + 1))
|
|
break;
|
|
|
|
QString tmpstr = QString(dayFirst?"%2 %1":"%1 %2").arg(months[date.month() - 1]).arg(date.day(), 2, 10, QChar('0'));
|
|
|
|
float x1 = lastx + xpos;
|
|
//lines.append(QLine(lastx, top, lastx, top+6));
|
|
if (x1 > (lastxpos + x + 8*graph.printScaleX())) {
|
|
graph.renderText(tmpstr, x1, y1 + y + 8);
|
|
lastxpos = x1;
|
|
lines.append(QLineF(lastx+barw/2, top, lastx+barw/2, top+6));
|
|
}
|
|
lastx = lastx + barw;
|
|
date = date.addDays(1);
|
|
}
|
|
painter.setPen(QPen(Qt::black,1));
|
|
painter.drawLines(lines);
|
|
|
|
}
|
|
|
|
|
|
gXAxisPressure::gXAxisPressure(QColor col)
|
|
:Layer(NoChannel)
|
|
{
|
|
m_line_color = col;
|
|
m_text_color = col;
|
|
m_major_color = Qt::darkGray;
|
|
m_minor_color = Qt::lightGray;
|
|
m_show_major_lines = false;
|
|
m_show_minor_lines = false;
|
|
m_show_minor_ticks = true;
|
|
m_show_major_ticks = true;
|
|
}
|
|
gXAxisPressure::~gXAxisPressure()
|
|
{
|
|
}
|
|
|
|
int gXAxisPressure::minimumHeight()
|
|
{
|
|
QFontMetrics fm(*defaultfont);
|
|
int h = fm.height();
|
|
#if defined(Q_OS_MAC)
|
|
return 9+h;
|
|
#else
|
|
return 11+h;
|
|
#endif
|
|
}
|
|
|
|
void gXAxisPressure::paint(QPainter & /*painter*/, gGraph &/*graph*/, const QRegion &/*region*/)
|
|
{
|
|
}
|