Session times replacement chart (work in progress)

This commit is contained in:
Mark Watkins 2014-09-04 12:17:59 +10:00
parent 5601be1b91
commit 23ce39efad
10 changed files with 252 additions and 74 deletions

View File

@ -32,6 +32,7 @@
#include "Graphs/glcommon.h"
#include "Graphs/gLineChart.h"
#include "Graphs/gSummaryChart.h"
#include "Graphs/gSessionTimesChart.h"
#include "Graphs/gYAxis.h"
#include "Graphs/gFlagsLine.h"
#include "SleepLib/profiles.h"
@ -1269,6 +1270,9 @@ void gGraphView::paintGL()
if (!graphs_drawn) { // No graphs drawn? show something useful :)
QString txt = QObject::tr("SleepyHead is proudly brought to you by JediMark.");
if (emptyText() == STR_Empty_Brick) {
txt += "\nI'm very sorry your machine doesn't record useful data to graph in Daily View :(";
}
// int x2, y2;
// GetTextExtent(m_emptytext, x2, y2, bigfont);
@ -1868,9 +1872,11 @@ void gGraphView::populateMenu(gGraph * graph)
gLineChart * lc = dynamic_cast<gLineChart *>(findLayer(graph,LT_LineChart));
SummaryChart * sc = dynamic_cast<SummaryChart *>(findLayer(graph,LT_SummaryChart));
gSessionTimesChart * stg = dynamic_cast<gSessionTimesChart *>(findLayer(graph,LT_SessionTimes));
limits_menu->clear();
if (lc || sc) {
if (lc || sc || stg) {
QWidgetAction * widget = new QWidgetAction(this);
MinMaxWidget * minmax = new MinMaxWidget(graph, this);

View File

@ -18,6 +18,7 @@
gSessionTimesChart::gSessionTimesChart(QString label, MachineType machtype)
:Layer(NoChannel), m_label(label), m_machtype(machtype)
{
m_layertype = LT_SessionTimes;
QDateTime d1 = QDateTime::currentDateTime();
QDateTime d2 = d1;
d1.setTimeSpec(Qt::UTC); // CHECK: Does this deal with DST?
@ -34,106 +35,213 @@ void gSessionTimesChart::SetDay(Day *unused_day)
Q_UNUSED(unused_day)
Layer::SetDay(nullptr);
QDate firstday = p_profile->FirstDay(m_machtype);
QDate lastday = p_profile->LastDay(m_machtype);
firstday = p_profile->FirstDay(m_machtype);
lastday = p_profile->LastDay(m_machtype);
QDate date = firstday;
do {
QMap<QDate, Day *>::iterator di = p_profile->daylist.find(date);
Day * day = di.value();
if (di == p_profile->daylist.end()) {
}
} while ((date = date.addDays(1)) < lastday);
m_minx = QDateTime(firstday, QTime(0,0,0)).toMSecsSinceEpoch();
m_maxx = QDateTime(lastday, QTime(23,59,59)).toMSecsSinceEpoch();
m_miny = 0;
m_maxy = 30;
m_empty = false;
// Get list of valid day records in supplied date range
QList<Day *> daylist = p_profile->getDays(m_machtype, firstday, lastday);
if (daylist.size() == 0) {
m_miny = m_maxy = 0;
return;
}
int cnt = 0;
bool first = true;
QList<Day *>::iterator end = daylist.end();
quint32 dn; // day number
// For each day
for (QList<Day *>::iterator it = daylist.begin(); it != end; ++it) {
Day * day = (*it);
if (day->size() == 0) continue;
dn = day->first() / 86400000L;
// For each session
for (int i=0; i < day->size(); i++) {
Session * session = (*day)[i];
if (!session->enabled()) continue;
// calculate start and end hours
float start = ((session->first() / 1000L) % 86400) / 3600.0;
float end = ((session->last() / 1000L) % 86400) / 3600.0;
// apply tzoffset??
// update min & max Y values
if (first) {
first = false;
m_miny = start;
m_maxy = end;
} else {
if (start < m_miny) m_miny = start;
if (end > m_maxy) m_maxy = end;
}
sessiontimes[cnt].push_back(TimeSpan(start,end));
}
}
}
void gSessionTimesChart::paint(QPainter &painter, gGraph &w, const QRegion &region)
QColor brighten(QColor color, float mult = 2.0);
void gSessionTimesChart::paint(QPainter &painter, gGraph &graph, const QRegion &region)
{
Q_UNUSED(painter)
Q_UNUSED(w)
Q_UNUSED(region)
QRect rect = region.boundingRect();
QMap<quint32, QList<TimeSpan> >::iterator st_end = sessiontimes.end();
QMap<quint32, QList<TimeSpan> >::iterator it;
painter.setPen(QColor(Qt::black));
painter.drawRect(rect);
m_minx = graph.min_x;
m_maxx = graph.max_x;
EventDataType miny;
EventDataType maxy;
graph.roundY(miny, maxy);
QDateTime date2 = QDateTime::fromMSecsSinceEpoch(m_minx);
QDateTime enddate2 = QDateTime::fromMSecsSinceEpoch(m_maxx);
QDate date = date2.date();
QDate enddate = enddate2.date();
QString text = QString("Work in progress, I know about the bugs :P There is a very good and urgent reason for redoing this graph... "); //.arg(date2.toString("yyyyMMdd hh:mm:ss")).arg(enddate2.toString("yyyyMMdd hh:mm:ss"));
painter.setFont(*defaultfont);
painter.drawText(rect.left(), rect.top()-4, text);
int days = ceil(double(m_maxx - m_minx) / 86400000.0);
float barw = float(rect.width()) / float(days);
QTime split = p_profile->session->daySplitTime();
QDateTime splittime;
for (it = sessiontimes.begin(); it != st_end; ++it) {
// int dn = it.key();
QList<TimeSpan> & st = it.value();
int stsize = st.size();
//float maxy = m_maxy;
float ymult = float(rect.height()) / (maxy-miny);
// Skip if empty
if (stsize == 0) continue;
int dn = 0;
float lasty1 = rect.bottom();
float lastx1 = rect.left();
do {
QMap<QDate, Day *>::iterator di = p_profile->daylist.find(date);
if (di == p_profile->daylist.end()) {
dn++;
lasty1 = rect.bottom();
lastx1 += barw;
continue;
}
Day * day = di.value();
// if (day->first() > m_maxx) { //|| (day->last() < m_minx)) {
// continue;
// }
splittime = QDateTime(date, split);
float x1 = lastx1 + barw;
QList<Session *>::iterator si;
}
if ((lastx1 + barw) > (rect.left()+rect.width()+1))
break;
bool hl = false;
QPoint mouse = graph.graphView()->currentMousePos();
QRect rec2(lastx1, rect.top(), barw, rect.height());
if (rec2.contains(mouse)) {
QColor col2(255,0,0,64);
painter.fillRect(rec2, QBrush(col2));
hl = true;
}
bool haveoxi = day->hasMachine(MT_OXIMETER);
QColor goodcolor = haveoxi ? QColor(128,196,255) : Qt::blue;
for (si = day->begin(); si != day->end(); ++si) {
Session *sess = (*si);
if (!sess->enabled() || (sess->machine()->type() != m_machtype)) continue;
int slize = sess->m_slices.size();
if (slize > 0) {
// segments
for (int i=0; i<slize; ++i) {
const SessionSlice & slice = sess->m_slices.at(i);
float s1 = float(splittime.secsTo(QDateTime::fromMSecsSinceEpoch(slice.start))) / 3600.0;
float s2 = double(slice.end - slice.start) / 3600000.0;
float y1 = (s1 * ymult);
float y2 = (s2 * ymult);
QColor col = (slice.status == EquipmentOn) ? goodcolor : Qt::black;
QColor col2 = brighten(col,2.5);
QRect rec(lastx1, rect.bottom() - y1 - y2, barw, y2);
QLinearGradient gradient(lastx1, rect.bottom(), lastx1+barw, rect.bottom());
if (rec.contains(mouse)) {
// if (hl) {
col = Qt::yellow;
}
gradient.setColorAt(0,col);
gradient.setColorAt(1,col2);
painter.fillRect(rec, QBrush(gradient));
painter.setPen(QPen(Qt::black,1));
painter.drawRect(rec);
}
} else {
qint64 sf = sess->first();
QDateTime st = QDateTime::fromMSecsSinceEpoch(sf);
float s1 = float(splittime.secsTo(st)) / 3600.0;
float s2 = sess->hours();
float y1 = (s1 * ymult);
float y2 = (s2 * ymult);
QColor col = goodcolor;
QLinearGradient gradient(lastx1, rect.bottom(), lastx1+barw, rect.bottom());
QRect rec(lastx1, rect.bottom() - y1 - y2, barw, y2);
if (rec.contains(mouse)) {
QString text = QObject::tr("%1\nBedtime:%2\nLength:%3").arg(st.date().toString(Qt::SystemLocaleDate)).arg(st.time().toString("hh:mm:ss")).arg(s2,0,'f',2);
graph.ToolTip(text,mouse.x() - 15,mouse.y() + 15, TT_AlignRight);
col = QColor("gold");
}
QColor col2 = brighten(col,2.5);
gradient.setColorAt(0,col);
gradient.setColorAt(1,col2);
painter.fillRect(rec, QBrush(gradient));
painter.setPen(QPen(Qt::black,1));
painter.drawRect(rec);
// no segments
}
}
// float y = double(day->total_time(m_machtype)) / 3600000.0;
// float y1 = rect.bottom() - (y * ymult);
// float x1 = lastx1 + barw;
// painter.drawLine(lastx1, lasty1, lastx1,y1);
// painter.drawLine(lastx1, y1, x1, y1);
dn++;
// lasty1 = y1;
lastx1 = x1;
} while ((date = date.addDays(1)) <= enddate);
}
bool gSessionTimesChart::keyPressEvent(QKeyEvent *event, gGraph *graph)
{
Q_UNUSED(event)
Q_UNUSED(graph)
return true;
return false;
}
bool gSessionTimesChart::mouseMoveEvent(QMouseEvent *event, gGraph *graph)
{
Q_UNUSED(event)
Q_UNUSED(graph)
return true;
return false;
}
bool gSessionTimesChart::mousePressEvent(QMouseEvent *event, gGraph *graph)
{
Q_UNUSED(event)
Q_UNUSED(graph)
return true;
return false;
}
bool gSessionTimesChart::mouseReleaseEvent(QMouseEvent *event, gGraph *graph)
{
Q_UNUSED(event)
Q_UNUSED(graph)
return true;
return false;
}

View File

@ -71,6 +71,8 @@ protected:
int hl_day;
int tz_offset;
float tz_hours;
QDate firstday;
QDate lastday;
QMap<quint32, QList<TimeSpan> > sessiontimes;
};

View File

@ -320,11 +320,11 @@ const QString gYAxisTime::Format(EventDataType v, int dp)
int m = int(v * 60) % 60;
int s = int(v * 3600) % 60;
char pm[3] = {"am"};
char pm[3] = {"pm"};
if (show_12hr) {
h >= 12 ? pm[0] = 'p' : pm[0] = 'a';
h >= 12 ? pm[0] = 'a' : pm[0] = 'p';
h %= 12;
if (h == 0) { h = 12; }

View File

@ -26,7 +26,7 @@ enum LayerPosition { LayerLeft, LayerRight, LayerTop, LayerBottom, LayerCenter,
enum ToolTipAlignment { TT_AlignCenter, TT_AlignLeft, TT_AlignRight };
enum LayerType { LT_Other = 0, LT_LineChart, LT_SummaryChart, LT_EventFlags, LT_Spacer };
enum LayerType { LT_Other = 0, LT_LineChart, LT_SummaryChart, LT_EventFlags, LT_Spacer, LT_SessionTimes };
/*! \class Layer
\brief The base component for all individual Graph layers

View File

@ -91,6 +91,8 @@ class Day
//! \brief Returns if the cache contains SummaryType information about the requested code
bool hasData(ChannelID code, SummaryType type);
inline bool hasMachine(MachineType mt) const { return machines.contains(mt); }
//! \brief Returns the Average of all Sessions setting 'code' for this day
EventDataType settings_avg(ChannelID code);
@ -278,7 +280,9 @@ class Day
int useCounter() { return d_useCounter; }
protected:
//! \brief A Vector containing all sessions for this day
QHash<ChannelID, QHash<EventDataType, EventDataType> > perc_cache;
//qint64 d_first,d_last;
private:

View File

@ -1173,13 +1173,39 @@ bool PRS1Import::ParseCompliance()
session->settings[PRS1_HumidStatus] = (bool)(data[0x0A] & 0x80); // Humidifier Connected
session->settings[PRS1_HumidLevel] = (int)(data[0x0A] & 7); // Humidifier Value
// need to parse a repeating structure here containing lengths of mask on/off..
// 0x03 = mask on
// 0x01 = mask off
// This is probably wrong
summary_duration = data[0x12] | data[0x13] << 8;
qint64 start = qint64(compliance->timestamp) * 1000L;
qint64 tt = start;
session->set_first(qint64(compliance->timestamp) * 1000L);
session->set_last(qint64(compliance->timestamp + (summary_duration * 2)) * 1000L);
int len = compliance->size()-3;
int pos = 0x11;
do {
quint8 c = data[pos++];
quint64 duration = data[pos] | data[pos+1] << 8;
pos+=2;
duration *= 1000L;
SliceStatus status;
if (c == 0x03) {
status = EquipmentOn;
} else if (c == 0x02) {
status = EquipmentLeaking;
} else if (c == 0x01) {
status = EquipmentOff;
} else {
qDebug() << compliance->sessionid << "Wasn't expecting" << c;
break;
}
session->m_slices.append(SessionSlice(tt, tt + duration, status));
qDebug() << compliance->sessionid << "Added Slice" << tt << (tt+duration) << status;
tt += duration;
} while (pos < len);
session->set_first(start);
session->set_last(tt);
// Bleh!! There is probably 10 different formats for these useless piece of junk machines
return true;

View File

@ -23,6 +23,29 @@
//class EventList;
class Machine;
enum SliceStatus {
UnknownStatus=0, EquipmentOff, EquipmentLeaking, EquipmentOn
};
class SessionSlice
{
public:
SessionSlice() {
start = end = 0;
status = UnknownStatus;
}
SessionSlice(const SessionSlice & copy) {
start = copy.start;
end = copy.end;
status = copy.status;
}
SessionSlice(qint64 start, qint64 end, SliceStatus status):start(start), end(end), status(status) {}
qint64 start;
qint64 end;
SliceStatus status;
};
/*! \class Session
\brief Contains a single Sessions worth of machine event/waveform information.
@ -179,6 +202,8 @@ class Session
QList<ChannelID> m_availableChannels;
QList<SessionSlice> m_slices;
const QList<ChannelID> & availableChannels() { return m_availableChannels; }
//! \brief Generates sum and time data for each distinct value in 'code' events..

View File

@ -124,6 +124,10 @@ Overview::Overview(QWidget *parent, gGraphView *shared) :
}
STG = createGraph("New Session", tr("Session Times2"), tr("Session Times"), YT_Time);
stg = new gSessionTimesChart("STG", MT_CPAP);
STG->AddLayer(stg);
UC = createGraph(STR_GRAPH_Usage, tr("Usage"), tr("Usage\n(hours)"));
FL = createGraph(schema::channel[CPAP_FlowLimit].code(), schema::channel[CPAP_FlowLimit].label(), STR_TR_FlowLimit);

View File

@ -16,6 +16,7 @@
#include "SleepLib/profiles.h"
#include "Graphs/gGraphView.h"
#include "Graphs/gSummaryChart.h"
#include "Graphs/gSessionTimesChart.h"
namespace Ui {
class Overview;
@ -62,11 +63,13 @@ class Overview : public QWidget
gGraph *createGraph(QString code, QString name, QString units = "", YTickerType yttype = YT_Number);
gGraph *AHI, *AHIHR, *UC, *FL, *SA, *US, *PR, *LK, *NPB, *SET, *SES, *RR, *MV, *TV, *PTB, *PULSE, *SPO2, *NLL,
// gGraph *AHI, *AHIHR, *UC, *FL, *US, *PR, *LK, *NPB, *SET, *SES, *RR, *MV, *TV, *PTB, *PULSE, *SPO2,
*WEIGHT, *ZOMBIE, *BMI, *TGMV, *TOTLK;
*WEIGHT, *ZOMBIE, *BMI, *TGMV, *TOTLK, *STG;
SummaryChart *bc, *uc, *fl, *sa, *us, *pr, *lk, *npb, *set, *ses, *rr, *mv, *tv, *ptb, *pulse, *spo2,
// SummaryChart *bc, *uc, *fl, *us, *pr, *lk, *npb, *set, *ses, *rr, *mv, *tv, *ptb, *pulse, *spo2,
*weight, *zombie, *bmi, *ahihr, *tgmv, *totlk, *nll;
gSessionTimesChart * stg;
//! \breif List of SummaryCharts shown on the overview page
QVector<SummaryChart *> OverviewCharts;