/* gSessionTimesChart Implementation * * Copyright (c) 2019-2022 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 "mainwindow.h" #include "SleepLib/profiles.h" #include "SleepLib/machine_common.h" #include "gSessionTimesChart.h" #include "gYAxis.h" extern MainWindow * mainwin; /// short SummaryCalcItem::midcalc; void gSessionTimesChart::preCalc() { midcalc = p_profile->general->prefCalcMiddle(); num_slices = 0; num_days = 0; total_length = 0; SummaryCalcItem & calc = calcitems[0]; calc.reset((idx_end - idx_start) * 6, midcalc); SummaryCalcItem & calc1 = calcitems[1]; calc1.reset(idx_end - idx_start, midcalc); SummaryCalcItem & calc2 = calcitems[2]; calc2.reset(idx_end - idx_start, midcalc); } void gSessionTimesChart::customCalc(Day *, QVector & slices) { int size = slices.size(); num_slices += size; SummaryCalcItem & calc1 = calcitems[1]; calc1.update(slices.size(), 1); EventDataType max = 0; for (auto & slice : slices) { slice.calc->update(slice.height, slice.height); max = qMax(slice.height, max); } SummaryCalcItem & calc2 = calcitems[2]; calc2.update(max, max); num_days++; } void gSessionTimesChart::afterDraw(QPainter & /*painter */, gGraph &graph, QRectF rect) { if (totaldays == nousedays) return; SummaryCalcItem & calc = calcitems[0]; // session length SummaryCalcItem & calc1 = calcitems[1]; // number of sessions SummaryCalcItem & calc2 = calcitems[2]; // number of sessions int midcalc = p_profile->general->prefCalcMiddle(); float mid = 0, mid1 = 0, midlongest = 0; switch (midcalc) { case 0: if (calc.median_data.size() > 0) { mid = median(calc.median_data.begin(), calc.median_data.end()); mid1 = median(calc1.median_data.begin(), calc1.median_data.end()); midlongest = median(calc2.median_data.begin(), calc2.median_data.end()); } break; case 1: mid = calc.wavg_sum / calc.divisor; mid1 = calc1.wavg_sum / calc1.divisor; midlongest = calc2.wavg_sum / calc2.divisor; break; case 2: mid = calc.avg_sum / calc.cnt; mid1 = calc1.avg_sum / calc1.cnt; midlongest = calc2.avg_sum / calc2.cnt; break; } // float avgsess = float(num_slices) / float(num_days); QString txt = QObject::tr("Sessions: %1 / %2 / %3 Length: %4 / %5 / %6 Longest: %7 / %8 / %9") .arg(calc1.min, 0, 'f', 2).arg(mid1, 0, 'f', 2).arg(calc1.max, 0, 'f', 2) .arg(durationInHoursToHhMmSs(calc.min)).arg(durationInHoursToHhMmSs(mid)).arg(durationInHoursToHhMmSs(calc.max)) .arg(durationInHoursToHhMmSs(calc2.min)).arg(durationInHoursToHhMmSs(midlongest)).arg(durationInHoursToHhMmSs(calc2.max)); graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); } void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion ®ion) { QRectF rect = region.boundingRect(); painter.setPen(QColor(Qt::black)); painter.drawRect(rect); m_minx = graph.min_x; m_maxx = graph.max_x; QDateTime date2 = QDateTime::fromMSecsSinceEpoch(m_minx, Qt::LocalTime); QDateTime enddate2 = QDateTime::fromMSecsSinceEpoch(m_maxx, Qt::LocalTime); QDate date = date2.date(); QDate enddate = enddate2.date(); int days = ceil(double(m_maxx - m_minx) / 86400000.0); float barw = float(rect.width()) / float(days); QDateTime splittime; auto it = dayindex.find(date); int idx=0; if (it == dayindex.end()) { it = dayindex.begin(); } else { idx = it.value(); } // Determine how many days after the first day of the chart that this data is to begin int numDaysOffset = 0; if (firstday > date) { // date = beginning date of chart; firstday = beginning date of data numDaysOffset = date.daysTo(firstday); } // Determine how many days before the last day of the chart that this data is to end int numDaysAfter = 0; if (enddate > lastday) { // enddate = last date of chart; lastday = last date of data numDaysAfter = lastday.daysTo(enddate); } if (numDaysAfter > days) // Nothing to do if this data is off the left edge of the chart return; // float lasty1 = rect.bottom(); float lastx1 = rect.left(); lastx1 += numDaysOffset * barw; auto ite = dayindex.find(enddate); int idx_end = daylist.size()-1; if (ite != dayindex.end()) { idx_end = ite.value(); } QPoint mouse = graph.graphView()->currentMousePos(); if (daylist.size() == 0) return; QVector outlines; int size = idx_end - idx; outlines.reserve(size * 5); auto it2 = it; ///////////////////////////////////////////////////////////////////// /// Calculate Graph Peaks ///////////////////////////////////////////////////////////////////// peak_value = 0; min_value = 999; auto it_end = dayindex.end(); float right_edge = (rect.left()+rect.width()+1); for (int i=idx; (i <= idx_end) && (it2 != it_end); ++i, ++it2, lastx1 += barw) { Day * day = daylist.at(i); if ((lastx1 + barw) > right_edge) break; if (!day) { continue; } auto cit = cache.find(i); if (cit == cache.end()) { day->OpenSummary(); date = it2.key(); splittime = QDateTime(date, split); QVector & slices = cache[i]; bool haveoxi = day->hasMachine(MT_OXIMETER); QColor goodcolor = haveoxi ? QColor(128,255,196) : QColor(64,128,255); QString datestr = date.toString(Qt::SystemLocaleShortDate); for (const auto & sess : day->sessions) { if (!sess->enabled() || (sess->type() != m_machtype)) continue; // Look at mask on/off slices... if (sess->m_slices.size() > 0) { // segments for (const auto & slice : sess->m_slices) { QDateTime st = QDateTime::fromMSecsSinceEpoch(slice.start, Qt::LocalTime); float s1 = float(splittime.secsTo(st)) / 3600.0; float s2 = double(slice.end - slice.start) / 3600000.0; float s2_display = double(slice.end - slice.start) / 1000.0; QColor col = (slice.status == MaskOn) ? goodcolor : Qt::black; QString txt = QObject::tr("%1\nLength: %3\nStart: %2\n").arg(datestr).arg(st.time().toString("hh:mm:ss")).arg(durationInSecondsToHhMmSs(s2_display)); txt += (slice.status == MaskOn) ? QObject::tr("Mask On") : QObject::tr("Mask Off"); slices.append(SummaryChartSlice(&calcitems[0], s1, s2, txt, col)); } } else { // otherwise just show session duration qint64 sf = sess->first(); QDateTime st = QDateTime::fromMSecsSinceEpoch(sf, Qt::LocalTime); float s1 = float(splittime.secsTo(st)) / 3600.0; float s2 = sess->hours(); QString txt = QObject::tr("%1\nLength: %3\nStart: %2").arg(datestr).arg(st.time().toString("hh:mm:ss")).arg(durationInHoursToHhMmSs(s2)); slices.append(SummaryChartSlice(&calcitems[0], s1, s2, txt, goodcolor)); } } cit = cache.find(i); } if (cit != cache.end()) { float peak = 0, base = 999; for (const auto & slice : cit.value()) { float s1 = slice.value; float s2 = slice.height; peak = qMax(peak, s1+s2); base = qMin(base, s1); } peak_value = qMax(peak_value, peak); min_value = qMin(min_value, base); } } m_miny = (min_value < 999) ? floor(min_value) : 0; m_maxy = ceil(peak_value); ///////////////////////////////////////////////////////////////////// /// Y-Axis scaling ///////////////////////////////////////////////////////////////////// EventDataType miny; EventDataType maxy; graph.roundY(miny, maxy); float ymult = float(rect.height()) / (maxy-miny); preCalc(); totaldays = 0; nousedays = 0; lastx1 = rect.left(); lastx1 += numDaysOffset * barw; ///////////////////////////////////////////////////////////////////// /// Main Loop scaling ///////////////////////////////////////////////////////////////////// do { Day * day = daylist.at(idx); if ((lastx1 + barw) > right_edge) break; totaldays++; if (!day) { // lasty1 = rect.bottom(); lastx1 += barw; nousedays++; // it++; continue; } auto cit = cache.find(idx); float x1 = lastx1 + barw; //bool hl = false; QRectF rec2(lastx1, rect.top(), barw, rect.height()); if (rec2.contains(mouse)) { QColor col2(255,0,0,64); painter.fillRect(rec2, QBrush(col2)); //hl = true; } if (cit != cache.end()) { QVector & slices = cit.value(); customCalc(day, slices); QLinearGradient gradient(lastx1, rect.bottom(), lastx1+barw, rect.bottom()); for (const auto & slice : slices) { float s1 = slice.value - miny; float s2 = slice.height; float y1 = (s1 * ymult); float y2 = (s2 * ymult); QColor col = slice.color; QRectF rec(lastx1, rect.bottom() - y1 - y2, barw, y2); rec = rec.intersected(rect); if (rec.contains(mouse)) { col = Qt::yellow; graph.ToolTip(slice.name, mouse.x() - 15,mouse.y() + 15, TT_AlignRight); } if (barw > 8) { gradient.setColorAt(0,col); gradient.setColorAt(1,brighten(col, 2.0)); painter.fillRect(rec, QBrush(gradient)); // painter.fillRect(rec, brush); outlines.append(rec); } else if (barw > 3) { painter.fillRect(rec, QBrush(brighten(col,1.25))); outlines.append(rec); } else { painter.fillRect(rec, QBrush(col)); } } } lastx1 = x1; } while (++idx <= idx_end); painter.setPen(QPen(Qt::black,1)); painter.drawRects(outlines); afterDraw(painter, graph, rect); }