/* gOverviewGraph Implementation * * Copyright (c) 2019-2024 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * 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 #include #include #include "gYAxis.h" #include "gOverviewGraph.h" #ifndef REMOVE_FITNESS /* To enable this module change the REMOTE_FITNESS define in appsettings.h */ gOverviewGraph::gOverviewGraph(QString label, GraphType type) : Layer(NoChannel), m_label(label), m_graphtype(type) { m_empty = true; hl_day = -1; m_machinetype = MT_CPAP; QDateTime d1 = QDateTime::currentDateTime(); QDateTime d2 = d1; d1.setTimeSpec(Qt::UTC); tz_offset = d2.secsTo(d1); tz_hours = tz_offset / 3600.0; m_layertype = LT_SummaryChart; } gOverviewGraph::~gOverviewGraph() { } void gOverviewGraph::SetDay(Day * nullday) { Q_UNUSED(nullday) Day *day = nullptr; Layer::SetDay(day); m_values.clear(); m_times.clear(); m_days.clear(); m_hours.clear(); m_goodcodes.clear(); m_miny = 999999999.0F; m_maxy = -999999999.0F; m_physmaxy = 0; m_physminy = 0; m_minx = 0; m_maxx = 0; int dn; EventDataType tmp, total; ChannelID code; CPAPMode cpapmode = (CPAPMode)(int)p_profile->calcSettingsMax(CPAP_Mode, MT_CPAP, p_profile->FirstDay(MT_CPAP), p_profile->LastDay(MT_CPAP)); ////////////////////////////////////////////////////////// // Setup for dealing with different CPAP Pressure types ////////////////////////////////////////////////////////// if (m_label == STR_TR_Pressure) { m_codes.clear(); m_colors.clear(); m_type.clear(); m_typeval.clear(); float perc = p_profile->general->prefCalcPercentile() / 100.0; int mididx = p_profile->general->prefCalcMiddle(); SummaryType mid; if (mididx == 0) { mid = ST_PERC; } else if (mididx == 1) { mid = ST_WAVG; } else mid = ST_AVG; if (cpapmode >= MODE_ASV) { addSlice(CPAP_EPAP, QColor("green"), ST_SETMIN); addSlice(CPAP_IPAPLo, QColor("light blue"), ST_SETMIN); addSlice(CPAP_IPAP, QColor("cyan"), mid, 0.5); addSlice(CPAP_IPAP, QColor("dark cyan"), ST_PERC, perc); //addSlice(CPAP_IPAP,QColor("light blue"),ST_PERC,0.95); addSlice(CPAP_IPAPHi, QColor("blue"), ST_SETMAX); } else if (cpapmode >= MODE_BILEVEL_AUTO_FIXED_PS) { addSlice(CPAP_EPAP, QColor("green"), ST_SETMIN); addSlice(CPAP_IPAP, QColor("light cyan"), mid, 0.5); addSlice(CPAP_IPAP, QColor("light blue"), ST_PERC, perc); addSlice(CPAP_PSMin, QColor("blue"), ST_SETMIN, perc); addSlice(CPAP_PSMax, QColor("red"), ST_SETMAX, perc); } else if (cpapmode >= MODE_BILEVEL_FIXED) { addSlice(CPAP_EPAP, QColor("green"), ST_SETMIN); addSlice(CPAP_EPAP, QColor("light green"), ST_PERC, perc); addSlice(CPAP_IPAP, QColor("light cyan"), mid, 0.5); addSlice(CPAP_IPAP, QColor("light blue"), ST_PERC, perc); addSlice(CPAP_IPAPHi, QColor("blue"), ST_SETMAX); } else if (cpapmode >= MODE_APAP) { addSlice(CPAP_PressureMin, QColor("orange"), ST_SETMIN); addSlice(CPAP_Pressure, QColor("dark green"), mid, 0.5f); addSlice(CPAP_Pressure, QColor("grey"), ST_PERC, perc); addSlice(CPAP_PressureMax, QColor("red"), ST_SETMAX); } else { addSlice(CPAP_Pressure, QColor("dark green"), ST_SETWAVG); } } // Initialize goodcodes (which identified which legends are drawn) to all off m_goodcodes.resize(m_codes.size()); for (int i = 0; i < m_codes.size(); i++) { m_goodcodes[i] = false; } m_fday = 0; qint64 tt; m_empty = true; if (m_graphtype == GT_SESSIONS) { // No point drawing anything if no real data on record if (p_profile->countDays(MT_CPAP, p_profile->FirstDay(MT_CPAP), p_profile->LastDay(MT_CPAP)) == 0) { return; } } bool first = true; int suboffset; SummaryType type; // For each day in the main profile daylist for (auto d=p_profile->daylist.begin(), dend=p_profile->daylist.end(); d!=dend; ++d) { Day * day = d.value(); // get the timestamp of this day. tt = QDateTime(d.key(), QTime(0, 0, 0), Qt::UTC).toTime_t(); // calculate day number dn = tt / 86400; // to ms since epoch. tt *= 1000L; // update min and max for this timestamp if (!m_minx || tt < m_minx) { m_minx = tt; } if (!m_maxx || tt > m_maxx) { m_maxx = tt; } total = 0; bool fnd = false; ////////////////////////////////////////////////////////// // Setup for Sessions Time display chart ////////////////////////////////////////////////////////// if (m_graphtype == GT_SESSIONS) { qint64 zt; EventDataType tmp2; // Turn all legends on for (int i = 0; i < m_codes.size(); i++) { m_goodcodes[i] = true; } // for each day object on record for this date // skip any empty or irrelevant day records if (!day || (day->machine(m_machinetype) == nullptr)) { continue; } //int ft = qint64(day->first()) / 1000L; //ft += tz_offset; // convert to local time //int dz2 = ft / 86400; //dz2 *= 86400; // ft = first sessions time, rounded back to midnight.. // For each session in this day record for (int s=0, size=day->size(); s < size; s++) { Session *sess = (*day)[s]; if (!sess->enabled()) { continue; } // Get session duration tmp = sess->hours(); m_values[dn][s] = tmp; total += tmp; // Get session start timestamp zt = qint64(sess->first()) / 1000L; zt += tz_offset; // Calculate the starting hour tmp2 = zt - dn * 86400; tmp2 /= 3600.0; m_times[dn][s] = tmp2; // Update min & max Y values if (first) { m_miny = tmp2; m_maxy = tmp2 + tmp; first = false; } else { if (tmp2 < m_miny) { m_miny = tmp2; } if (tmp2 + tmp > m_maxy) { m_maxy = tmp2 + tmp; } } } // for each session // if total hours for all sessions more than 0, register the day as valid if (total > 0) { m_days[dn] = day; m_hours[dn] = total; m_empty = false; } } else { ////////////////////////////////////////////////////////////////////////////// // Data Channel summary charts ////////////////////////////////////////////////////////////////////////////// // For each Channel for (int j = 0; j < m_codes.size(); j++) { code = m_codes[j]; suboffset = 0; type = m_type[j]; EventDataType typeval = m_typeval[j]; day = d.value(); CPAPMode mode = (CPAPMode)(int)day->settings_max(CPAP_Mode); // ignore irrelevent day objects if (day->machine(m_machinetype) == nullptr) { continue; } bool hascode = //day->channelHasData(code) || (type == ST_HOURS) || (type == ST_SESSIONS) || day->settingExists(code) || day->hasData(code, type); if (code == CPAP_Pressure) { if ((cpapmode > MODE_CPAP) && (mode == MODE_CPAP)) { hascode = false; if ((type == ST_WAVG) || (type == ST_AVG) || ((type == ST_PERC) && (typeval == 0.5))) { type = ST_SETWAVG; hascode = true; } } else { type = m_type[j]; } } //if (code==CPAP_Hypopnea) { // Make sure at least one of the CPAP data gets through with 0 // hascode=true; //} if (hascode) { m_days[dn] = day; switch (type) { case ST_AVG: tmp = day->avg(code); break; case ST_SUM: tmp = day->sum(code); break; case ST_WAVG: tmp = day->wavg(code); break; case ST_90P: tmp = day->p90(code); break; case ST_PERC: tmp = day->percentile(code, typeval); break; case ST_MIN: tmp = day->Min(code); break; case ST_MAX: tmp = day->Max(code); break; case ST_CNT: tmp = day->count(code); break; case ST_CPH: tmp = day->count(code) / day->hours(m_machinetype); break; case ST_SPH: tmp = day->sph(code); break; case ST_HOURS: tmp = day->hours(m_machinetype); break; case ST_SESSIONS: tmp = day->size(); break; case ST_SETMIN: tmp = day->settings_min(code); break; case ST_SETMAX: tmp = day->settings_max(code); break; case ST_SETAVG: tmp = day->settings_avg(code); break; case ST_SETWAVG: tmp = day->settings_wavg(code); break; case ST_SETSUM: tmp = day->settings_sum(code); break; default: tmp = 0; break; } if (suboffset > 0) { tmp -= suboffset; if (tmp < 0) { tmp = 0; } } total += tmp; m_values[dn][j + 1] = tmp; if (tmp < m_miny) { m_miny = tmp; } if (tmp > m_maxy) { m_maxy = tmp; } m_goodcodes[j] = true; fnd = true; } } if (fnd) { if (!m_fday) { m_fday = dn; } m_values[dn][0] = total; m_hours[dn] = day->hours(m_machinetype); if (m_graphtype == GT_BAR) { if (total < m_miny) { m_miny = total; } if (total > m_maxy) { m_maxy = total; } } } } } m_empty = true; for (const auto & goodcode : m_goodcodes) { if (goodcode) { m_empty = false; break; } } if (m_graphtype == GT_BAR) { m_miny = 0; } // m_minx=qint64(QDateTime(p_profile->FirstDay(),QTime(0,0,0),Qt::UTC).toTime_t())*1000L; m_maxx = qint64(QDateTime(p_profile->LastDay(), QTime(23, 59, 0), Qt::UTC).toTime_t()) * 1000L; m_physmaxy = m_maxy; m_physminy = m_miny; } void gOverviewGraph::paint(QPainter &painter, gGraph &w, const QRegion ®ion) { int left = region.boundingRect().left(); int top = region.boundingRect().top(); int width = region.boundingRect().width(); int height = region.boundingRect().height(); if (!m_visible) { return; } GraphType graphtype = m_graphtype; if (graphtype == GT_LINE || graphtype == GT_POINTS) { bool pts = AppSetting->overviewLinechartMode() == OLC_Lines; graphtype = pts ? GT_POINTS : GT_LINE; } rtop = top; painter.setPen(QColor(Qt::black)); painter.drawLine(left, top, left, top+height); painter.drawLine(left, top+height, left+width, top+height); painter.drawLine(left+width, top+height, left+width, top); painter.drawLine( left+width, top, left, top); qint64 minx = w.min_x, maxx = w.max_x; int days = ceil(double(maxx-minx) / 86400000.0); bool buttuglydaysteps = false ; //!p_profile->appearance->animations(); double lcursor = w.graphView()->currentTime(); if (days >= 1) { double b = w.max_x - w.min_x; double a = lcursor - w.min_x; double c = a / b; if (buttuglydaysteps) { // this kills the beautiful smooth scrolling and makes days stop on day boundaries :( minx = floor(double(minx)/86400000.0); minx *= 86400000L; maxx = minx + 86400000L * qint64(days)-1; } b = maxx - minx; double d = c * b; lcursor = d + minx; } qint64 xx = maxx - minx; EventDataType miny = m_physminy; EventDataType maxy = m_physmaxy; w.roundY(miny, maxy); EventDataType yy = maxy - miny; EventDataType ymult = float(height - 2) / yy; barw = (float(width) / float(days)); // graph = &w; float px;// = left; l_left = w.marginLeft() + gYAxis::Margin; l_top = w.marginTop(); l_width = width; l_height = height; float py; EventDataType total; int daynum = 0; EventDataType h, tmp; l_offset = (minx) % 86400000L; offset = float(l_offset) / 86400000.0; offset *= barw; px = left - offset; l_minx = minx; l_maxx = maxx + 86400000L; int total_days = 0; double total_val = 0; double total_hours = 0; bool lastdaygood = false; QVector totalcounts; QVector totalvalues; QVector lastX; QVector lastY; int numcodes = m_codes.size(); totalcounts.resize(numcodes); totalvalues.resize(numcodes); lastX.resize(numcodes); lastY.resize(numcodes); int zd = minx / 86400000L; zd--; auto d = m_values.find(zd); QVector goodcodes; goodcodes.resize(m_goodcodes.size()); lastdaygood = true; // Display Line Cursor if (AppSetting->lineCursorMode()) { qint64 time = lcursor; double xmult = double(width) / xx; if ((time > minx) && (time < maxx)) { double xpos = (time - minx) * xmult; painter.setPen(QPen(QBrush(QColor(0,255,0,255)),1)); painter.drawLine(left+xpos, top-w.marginTop()-3, left+xpos, top+height+w.bottom-1); } // QDateTime dt=QDateTime::fromMSecsSinceEpoch(time,Qt::UTC); // QString text = dt.date().toString(Qt::SystemLocaleLongDate); // int wid, h; // GetTextExtent(text, wid, h); // w.renderText(text, left + width/2 - wid/2, top-h+5); } for (int i = 0; i < numcodes; i++) { totalcounts[i] = 0; // Set min and max to the opposite largest starting value if ((m_type[i] == ST_MIN) || (m_type[i] == ST_SETMIN)) { totalvalues[i] = maxy; } else if ((m_type[i] == ST_MAX) || (m_type[i] == ST_SETMAX)) { totalvalues[i] = miny; } else { totalvalues[i] = 0; } // Turn off legend display.. It will only display if it's turned back on during draw. goodcodes[i] = false; if (!m_goodcodes[i]) { continue; } lastX[i] = px; if (d != m_values.end() && d.value().contains(i + 1)) { tmp = d.value()[i + 1]; h = tmp * ymult; } else { lastdaygood = false; h = 0; } lastY[i] = top + height - 1 - h; } float compliance_hours = 0; int incompliant = 0; Day *day; EventDataType hours; //quint32 * tptr; //EventStoreType * dptr; short px2, py2; const qint64 ms_per_day = 86400000L; painter.setClipRect(left, top, width, height); painter.setClipping(true); QColor summaryColor = QColor("dark gray"); float lineThickness = AppSetting->lineThickness(); for (qint64 Q = minx; Q <= maxx + ms_per_day; Q += ms_per_day) { zd = Q / ms_per_day; d = m_values.find(zd); if (Q < minx) { goto jumpnext; } if (d != m_values.end()) { day = m_days[zd]; bool summary_only = day && day->summaryOnly(); if (!m_hours.contains(zd)) { goto jumpnext; } hours = m_hours[zd]; int x1 = px; //x1-=(barw/2.0); int x2 = px + barw; //if (x1 < left) { x1 = left; } if (x2 > left + width) { x2 = left + width; } // if (x2add(x1 - 1, top, x1 - 1, top + height, x2, top + height, x2, top, col.rgba()); } else { painter.fillRect((x1+barw/2)-5, top, barw, height, QBrush(col)); // quads->add((x1 + barw / 2) - 5, top, (x1 + barw / 2) - 5, top + height, (x2 - barw / 2) + 5, // top + height, (x2 - barw / 2) + 5, top, col.rgba()); } } if (graphtype == GT_SESSIONS) { int j; auto times = m_times.find(zd); QColor col = m_colors[0]; //if (hourssetColor(Qt::black); int np = d.value().size(); if (np > 0) { for (auto & goodcode : goodcodes) { goodcode = true; } } for (j = 0; j < np; j++) { EventDataType tmp2 = times.value()[j] - miny; py = top + height - (tmp2 * ymult); tmp = d.value()[j]; // length //tmp-=miny; h = tmp * ymult; QLinearGradient gradient(x1, py-h, x1+barw, py-h); gradient.setColorAt(0,col1); gradient.setColorAt(1,col2); painter.fillRect(x1, py-h, barw, h, QBrush(gradient)); // quads->add(x1, py, x1, py - h, x2, py - h, x2, py, col1, col2); if ((h > 0) && (barw > 2)) { painter.setPen(QColor(Qt::black)); painter.drawLine(x1, py, x1, py - h); painter.drawLine(x1, py - h, x2, py - h); painter.drawLine(x1, py, x2, py); painter.drawLine(x2, py, x2, py - h); } totalvalues[0] += hours * tmp; } totalcounts[0] += hours; totalvalues[1] += j; totalcounts[1]++; total_val += hours; total_hours += hours; total_days++; } else { if (!d.value().contains(0)) { goto jumpnext; } total = d.value()[0]; //if (total>0) { if (day) { EventDataType hours = m_hours[zd]; total_val += total * hours; total_hours += hours; total_days++; } py = top + height; //} bool good; SummaryType type; for (auto g=d.value().begin(), dend=d.value().end(); g != dend; g++) { short j = g.key(); if (!j) { continue; } j--; good = m_goodcodes[j]; if (!good) { continue; } type = m_type[j]; // code was actually used (to signal the display of the legend summary) goodcodes[j] = good; tmp = g.value(); QColor col = m_colors[j]; if (type == ST_HOURS) { if (tmp < compliance_hours) { col = QColor("#f04040"); incompliant++; } else if (summary_only) { col = summaryColor; } } if (zd == hl_day) { col = COLOR_Gold; } //if (!tmp) continue; if ((type == ST_MAX) || (type == ST_SETMAX)) { if (totalvalues[j] < tmp) { totalvalues[j] = tmp; } } else if ((type == ST_MIN) || (type == ST_SETMIN)) { if (totalvalues[j] > tmp) { totalvalues[j] = tmp; } } else { totalvalues[j] += tmp * hours; } //if (tmp) { totalcounts[j] += hours; //} tmp -= miny; h = tmp * ymult; // height in pixels if (graphtype == GT_BAR) { QColor col1 = col; QColor col2 = brighten(col,2.5); QLinearGradient gradient(x1, py-h, x1+barw, py-h); gradient.setColorAt(0,col1); gradient.setColorAt(1,col2); painter.fillRect(x1, py-h, barw, h, QBrush(gradient)); // quads->add(x1, py, x1, py - h, col1); // quads->add(x2, py - h, x2, py, col2); if (h > 0 && barw > 2) { painter.setPen(QColor(Qt::black)); painter.drawLine(x1, py, x1, py - h); painter.drawLine(x1, py - h, x2, py - h); painter.drawLine(x1, py, x2, py); painter.drawLine(x2, py, x2, py - h); } // if (bar py -= h; } else if (graphtype == GT_LINE) { // if (m_graphtype==GT_BAR QColor col1 = col; QColor col2 = m_colors[j]; px2 = px + barw; py2 = (top + height - 1) - h; // If more than 1 day between records, skip the vertical crud. if ((px2 - lastX[j]) > barw + 1) { lastdaygood = false; } if (lastdaygood) { if (lastY[j] != py2) { // vertical line painter.setPen(QPen(col2, lineThickness)); painter.drawLine(lastX[j], lastY[j], px, py2); } painter.setPen(QPen(col1, lineThickness)); painter.drawLine(px, py2, px2, py2); } else { painter.setPen(QPen(col1, lineThickness)); painter.drawLine(x1, py2, x2, py2); } lastX[j] = px2; lastY[j] = py2; } else if (graphtype == GT_POINTS) { QColor col1 = col; QColor col2 = m_colors[j]; px2 = px + barw; py2 = (top + height - 2) - h; // If more than 1 day between records, skip the vertical crud. if ((px2 - lastX[j]) > barw + 1) { lastdaygood = false; } if (zd == hl_day) { painter.setPen(QPen(brighten(col2),10)); painter.drawPoint(px2 - barw / 2, py2); } if (lastdaygood) { painter.setPen(QPen(col2, lineThickness)); painter.drawLine(lastX[j] - barw / 2, lastY[j], px2 - barw / 2, py2); } else { painter.setPen(QPen(col1, lineThickness)); painter.drawLine(px + barw / 2 - 1, py2, px + barw / 2 + 1, py2); } lastX[j] = px2; lastY[j] = py2; } } // for(QHashmaxx+extra) break; } else { if (Q < maxx) { incompliant++; } lastdaygood = false; } jumpnext: if (px >= left + width + barw) { break; } px += barw; daynum++; //lastQ=Q; } painter.setClipping(false); // Draw Ledgend px = left + width - 3; py = top - 5; int legendx = px; QString a, b; int x, y; QSize size = QFontMetrics(*defaultfont).size(Qt::TextSingleLine ,"X"); int bw = size.width(); int bh = size.height() / 1.8; //QFontMetrics fm(*defaultfont); //int bw = fm.width('X'); //int bh = fm.height() / 1.8; // bool ishours = false; int good = 0; for (int j = 0; j < m_codes.size(); j++) { if (!goodcodes[j]) { continue; } good++; SummaryType type = m_type[j]; ChannelID code = m_codes[j]; EventDataType tval = m_typeval[j]; switch (type) { case ST_WAVG: b = "Avg"; break; case ST_AVG: b = "Avg"; break; case ST_90P: b = "90%"; break; case ST_PERC: if (tval >= 0.99) { b = STR_TR_Max; } else if (tval == 0.5) { b = STR_TR_Med; } else { b = QString("%1%").arg(tval * 100.0, 0, 'f', 0); } break; //b=QString("%1%").arg(tval*100.0,0,'f',0); break; case ST_MIN: b = STR_TR_Min; break; case ST_MAX: b = STR_TR_Max; break; case ST_SETMIN: b = STR_TR_Min; break; case ST_SETMAX: b = STR_TR_Max; break; case ST_CPH: b = ""; break; case ST_SPH: b = "%"; break; case ST_HOURS: b = STR_UNIT_Hours; break; case ST_SESSIONS: b = STR_TR_Sessions; break; default: b = ""; break; } a = schema::channel[code].label(); if (a == w.title() && !b.isEmpty()) { a = b; } else { a += " " + b; } QString val; float f = 0; if (totalcounts[j] > 0) { if ((type == ST_MIN) || (type == ST_MAX) || (type == ST_SETMIN) || (type == ST_SETMAX)) { f = totalvalues[j]; } else { f = totalvalues[j] / totalcounts[j]; } } if (type == ST_HOURS) { int h = f; int m = int(f * 60) % 60; val = QString::asprintf("%02i:%02i", h, m); // ishours = true; } else { val = QString::number(f, 'f', 2); } a += ": " + val; //GetTextExtent(a,x,y); //float wt=20*w.printScaleX(); //px-=wt+x; //w.renderText(a,px+wt,py+1); //quads->add(px+wt-y/4-y,py-y,px+wt-y/4,py-y,px+wt-y/4,py+1,px+wt-y/4-y,py+1,m_colors[j].rgba()); //QString text=schema::channel[code].label(); int wid, hi; GetTextExtent(a, wid, hi); legendx -= wid; w.renderText(a, legendx, top - 4); // legendx-=bw/2; painter.fillRect(legendx - bw-4, top-w.marginTop()-1, bh, w.marginTop(), QBrush(m_colors[j])); legendx -= bw * 2; //lines->add(px,py,px+20,py,m_colors[j]); //lines->add(px,py+1,px+20,py+1,m_colors[j]); } if ((m_graphtype == GT_BAR) && (good > 0)) { if (m_type.size() > 1) { float val = total_val / float(total_hours); a = m_label + ": " + QString::number(val, 'f', 2) + " "; GetTextExtent(a, x, y); legendx -= x; w.renderText(a, legendx, py + 1); } } a = ""; /*if (m_graphtype==GT_BAR) { if (m_type.size()>1) { float val=total_val/float(total_days); a+=m_label+": "+QString::number(val,'f',2)+" "; // } }*/ a += QString(QObject::tr("Days: %1")).arg(total_days, 0); //GetTextExtent(a,x,y); //legendx-=30+x; //w.renderText(a,px+24,py+5); w.renderText(a, left, py + 1); } QString formatTime(EventDataType v, bool show_seconds = false, bool duration = false, bool show_12hr = false) { int h = int(v); if (!duration) { h %= 24; } else { show_12hr = false; } int m = int(v * 60) % 60; int s = int(v * 3600) % 60; char pm[3] = {"am"}; if (show_12hr) { h >= 12 ? pm[0] = 'p' : pm[0] = 'a'; h %= 12; if (h == 0) { h = 12; } } else { pm[0] = 0; } if (show_seconds) { return QString::asprintf("%i:%02i:%02i%s", h, m, s, pm); } else { return QString::asprintf("%i:%02i%s", h, m, pm); } } bool gOverviewGraph::mouseMoveEvent(QMouseEvent *event, gGraph *graph) { graph->timedRedraw(0); int xposLeft = event->x(); int yPosTop = event->y(); if (!m_rect.contains(xposLeft, yPosTop)) { // if ((x<0 || y<0 || x>l_width || y>l_height)) { hl_day = -1; //graph->timedRedraw(2000); return false; } xposLeft -= m_rect.left(); yPosTop -= m_rect.top(); Q_UNUSED(yPosTop) double xx = l_maxx - l_minx; double xmult = xx / double(l_width + barw); qint64 mx = ceil(xmult * double(xposLeft - offset)); mx += l_minx; mx = mx + l_offset; //-86400000L; int zd = mx / 86400000L; Day *day; //if (hl_day!=zd) // This line is an optimization { hl_day = zd; graph->Trigger(2000); auto d = m_values.find(hl_day); QMap &valhash = d.value(); xposLeft += m_rect.left(); //gYAxis::Margin+gGraphView::titleWidth; //graph->m_marginleft+ int y = event->y() - m_rect.top() + rtop - 15; //QDateTime dt1=QDateTime::fromTime_t(hl_day*86400).toLocalTime(); QDateTime dt2 = QDateTime::fromTime_t(hl_day * 86400).toUTC(); // QDateTime dt2 = QDateTime::fromTime_t(hl_day * 86400).toLocalTime(); //QTime t1=dt1.time(); //QTime t2=dt2.time(); QDate dt = dt2.date(); day = m_days[zd]; if ((d != m_values.end()) && (day != nullptr)) { bool summary_only = day->summaryOnly(); QString strTooltip = dt.toString(Qt::SystemLocaleShortDate); // Day * day=m_days[hl_day]; //EventDataType val; QString val; if (m_graphtype == GT_SESSIONS) { if (m_type[0] == ST_HOURS) { int t = m_hours[zd] * 3600.0; int h = t / 3600; int m = (t / 60) % 60; //int s=t % 60; val = QString::asprintf("%02i:%02i", h, m); } else { val = QString::number(d.value()[0], 'f', 2); } strTooltip += "\r\n" + m_label + ": " + val; if (m_type[1] == ST_SESSIONS) { strTooltip += " "+QString(QObject::tr("(Sess: %1)")).arg(day->size(), 0); } EventDataType v = m_times[zd][0]; int lastt = m_times[zd].size() - 1; if (lastt < 0) { lastt = 0; } strTooltip += "\r\n"+QString(QObject::tr("Bedtime: %1")).arg(formatTime(v, false, false, true)); v = m_times[zd][lastt] + m_values[zd][lastt]; strTooltip += "\r\n"+QString(QObject::tr("Waketime: %1")).arg(formatTime(v, false, false, true)); } else if (m_graphtype == GT_BAR) { if (m_type[0] == ST_HOURS) { int t = d.value()[0] * 3600.0; int h = t / 3600; int m = (t / 60) % 60; //int s=t % 60; val = QString::asprintf("%02i:%02i", h, m); } else { val = QString::number(d.value()[0], 'f', 2); } strTooltip += "\r\n" + m_label + ": " + val; //z+="\r\nMode="+QString::number(day->settings_min("FlexSet"),'f',0); } else { QString strDataType; for (int i = 0; i < m_type.size(); i++) { if (!m_goodcodes[i]) { continue; } if (!valhash.contains(i + 1)) { continue; } EventDataType tval = m_typeval[i]; switch (m_type[i]) { case ST_WAVG: strDataType = STR_TR_WAvg; break; case ST_AVG: strDataType = STR_TR_Avg; break; case ST_90P: strDataType = QString("90%"); break; case ST_PERC: if (tval >= 0.99) { strDataType = STR_TR_Max; } else if (tval == 0.5) { strDataType = STR_TR_Med; } else { strDataType = QString("%1%").arg(tval * 100.0, 0, 'f', 0); } break; case ST_MIN: strDataType = STR_TR_Min; break; case ST_MAX: strDataType = STR_TR_Max; break; case ST_CPH: strDataType = ""; break; case ST_SPH: strDataType = "%"; break; case ST_HOURS: strDataType = STR_UNIT_Hours; break; case ST_SESSIONS: strDataType = STR_TR_Sessions; break; case ST_SETMIN: strDataType = STR_TR_Min; break; case ST_SETMAX: strDataType = STR_TR_Max; break; default: strDataType = ""; break; } if (m_type[i] == ST_SESSIONS) { val = QString::number(d.value()[i + 1], 'f', 0); strTooltip += "\r\n" + strDataType + ": " + val; } else { //if (day && (day->channelExists(m_codes[i]) || day->settingExists(m_codes[i]))) { schema::Channel &chan = schema::channel[m_codes[i]]; EventDataType v; if (valhash.contains(i + 1)) { v = valhash[i + 1]; } else { v = 0; } if (m_codes[i] == Journal_Weight) { val = weightString(v, p_profile->general->unitSystem()); } else { val = QString::number(v, 'f', 2); } strTooltip += "\r\n" + chan.label() + " " + strDataType + ": " + val; //} } } } if (summary_only) { strTooltip += "\r\n"+QObject::tr("(Summary Only)"); } graph->ToolTip(strTooltip, xposLeft, y - 15); return false; } else { QString z = dt.toString(Qt::SystemLocaleShortDate) + "\r\n"+QObject::tr("No Data"); graph->ToolTip(z, xposLeft, y - 15); return false; } } return false; } bool gOverviewGraph::mousePressEvent(QMouseEvent *event, gGraph *graph) { if (event->modifiers() & Qt::ShiftModifier) { //qDebug() << "Jump to daily view?"; return true; } Q_UNUSED(graph) return false; } bool gOverviewGraph::keyPressEvent(QKeyEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) //qDebug() << "Summarychart Keypress"; return false; } #include "mainwindow.h" extern MainWindow *mainwin; bool gOverviewGraph::mouseReleaseEvent(QMouseEvent *event, gGraph *graph) { if (event->modifiers() & Qt::ShiftModifier) { if (hl_day < 0) { mouseMoveEvent(event, graph); } if (hl_day > 0) { QDateTime d = QDateTime::fromTime_t(hl_day * 86400).toUTC(); // QDateTime d = QDateTime::fromTime_t(hl_day * 86400).toLocalTime(); mainwin->getDaily()->LoadDate(d.date()); mainwin->JumpDaily(); //qDebug() << "Jump to daily view?" << d; return true; } } Q_UNUSED(event) hl_day = -1; graph->timedRedraw(2000); return false; } #endif