2018-06-14 07:25:54 +00:00
/* MinutesAtPressure Graph Implementation
2014-08-11 18:29:44 +00:00
*
2021-11-02 20:34:12 +00:00
* Copyright ( c ) 2019 - 2022 The OSCAR Team
2018-03-28 07:10:52 +00:00
* Copyright ( c ) 2011 - 2018 Mark Watkins < mark @ jedimark . net >
2014-08-11 18:29:44 +00:00
*
* This file is subject to the terms and conditions of the GNU General Public
2018-06-04 20:48:38 +00:00
* License . See the file COPYING in the main directory of the source code
* for more details . */
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
/*
MinutesAtPressure Graph
The MinutesAtPressure ( TimeAtPressure ) Graph is the Pressure Graph transposed - with similar look and feel as other graphs .
The Y - Axis ( pressure ) becomes the X - Axis ( MinutesAtPressure ) .
The X - Axis ( Time ) becomes the Y - Axis ( duration in minutes ) .
The MinutesAtPressure Graph uses the configurable Plot and Overlay Settings from the Pressure Graph ,
EPAP and IPAP ( CPAP_Pressure ) will both be conditionally displayed
Events ( H , CA , OA , UA ) are displayed as tick marks similar to the Pressure Graph .
Events ( LL , CSR ) are also displayed like the pressure Graph with lightgrap or lightgreen background coloring .
This gives the MinutesAtPressure a similar look and feel as other graphs .
The MetaData ( top label Bar ) now contains the total duration for each pressure pressure bucket ( range of pressures ) as well as the events that occured .
The MinutesAtPressure tool - tips now contains just the names of the Event Ticks ( similar to the pressure Graph )
The tooltip information is minimal and only contains the name of the event and the number of occurrences for that pressure range .
On Mouse Over
1. Each data point will be highlight with a small dot .
2. Tooltips will be displayed for the appropriate Pressure Range and the respective data point will be displayed with a square box - similar to the current current implementation .
When there is no data available ( the time selected is between sessions in a day ) then " No Data " will be displayed .
When no graphs are selected then " Plots Disabled " will be displayed - just like the Pressure Graph .
The X - Axis start and end pressure now use the Machine limits . If plot data has a data outside the range is appopaitely updated .
Refactoring was done to
1. Reduce duplicate / similar instances of code
2. Shorten long methods
3. Enhance readability ,
5. Add Dynamic Meta data for Pressure times and Events .
6. insure data accuracy .
7. Conditional compilation for features
Total Duration displayed by Minutes AT Pressure graphs is based on the actual waveform form files .
In some cases this duration is different that the session time indicated in the daily session information - due to reasons below .
Issues found while testing based on 1.2 .0 base .
* Event Flags do not display short SPANS .
* Pressure Graph does not display short SPANS .
* sessions times ( as displayed in daily session information ) are not always the same as the first and last time in the waveforms .
for example for resmed the the session start time is about 40 seconds before the first sample in the waveform ( eventlist ) .
also the session end time is not always the same as the last sample in the waveform . typically 1 second different ( either before or after waveform ) .
* multiple eventlists in a session . time betwwen eventlists is small - under 1 - 2 minutes .
This is not the same as multiple sessions per day .
Naming convention
pixel = = at point on the display area .
pixels = = distance between to pixels
Pressure = = a value in cmH20
Bucket = = A range of pressures to collect the ammount of time in that pressure range
some messages from Apnea Board .
0000042 : Graphs Daily " Time at Pressure " graph x - axis goes to - 1 and plot trace showing behind other graphs
Graphs Daily " Time at Pressure " graph x - axis goes to - 1 and plot trace showing behind other graphs On the " Daily " tab ,
the Time at Pressure plot ' s x - axis minimum is a value of - 1. i
Also , the plot traces on this graph jump to unreasonably high values ( e . g . , 508 )
which are shown behind the other graphs above it .
Picture available on GitLab All 2 - high All Issue 000004 9 from SH 1.1 .0 GitLab Issue List
000003 8 : Graphs any " Time at Pressure graph does not show the right pressure
Graphs any " Time at Pressure graph does not show the right pressure, i
e . g . CPAP with constant pressure at 7.5 cmH2O , but the Time at Pressure graph only has a peak around 6.2 cmH2O
" This is especially true when looking at data at constant pressure.
It was found with DV64 data , but is presumably true for other machine types as well as the graphs do not differentiate on machine types - to be confirmed All 2 - high
All Issue 0000054 from SH 1.1 .0 GitLab Issue List
*/
2014-08-11 18:29:44 +00:00
# include <cmath>
# include <QApplication>
# include <QThreadPool>
# include <QMutexLocker>
# include "MinutesAtPressure.h"
# include "Graphs/gGraph.h"
# include "Graphs/gGraphView.h"
# include "SleepLib/profiles.h"
# include "Graphs/gXAxis.h"
2014-08-12 08:04:53 +00:00
# include "Graphs/gYAxis.h"
2021-04-18 01:54:56 +00:00
# include "common_gui.h"
# include "Graphs/gLineChart.h"
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// Compile time Features
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# define ENABLE_DISPLAY_SPAN_EVENTS_AS_BACKGROUND // Enables SPAN event to be displayed as a background (like pressure graph)
# define ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS // Enables FLAG events to be displayed as tick (like pressure graph)
# define ENABLE_BUCKET_PRESSURE_AVERAGE // Average method
// Bucket pressure is in the middle of the pressure range.
// New definition of bucket Pressure-0.1 - Pressure0.1 with INTERVALS_PER_CCMH2O=5
// Original bucket definition. Pressure - Pressure+0.2 wiyh INTERVALS_PER_CCMH2O=5
# define EXTRA_SPACE_ON_X_AXIS // adds a small space (Pressure/(INTERVALS_PER_CCMH2O*2) to each end.
# define ENABLE_SMOOTH_CURVES // decreases performance.
//#define ALIGN_X_AXIS_ON_INTEGER_BOUNDS
//#define ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH // enable graphing of flag events. Overlays plots on top on pressure plots.
// ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS is enabled instead.
// ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS and ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH can both be enabled
// Compile time Constants
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# define HIGHEST_POSSIBLE_PRESSURE 60 // should be the lowest possible pressure that a CPAP machine accepts - Plus spare.
# define INTERVALS_PER_CCMH2O 5 // must be a positive integer > 0. Five (5) produces good graphs. Other values will work.
// 10 also loogs good. larger number have smaller intervals and the starting pressure interval will be huge
// relation to the rest of the pressure intervals - making the graph unusable.
# define NUMBER_OF_CATMULLROMSPLINE_POINTS 5 // Higher the number decreases performance. 5 produces smooth curves. must be >= 1. 1 connects points with straight line.
// ENABLE_SMOOTH_CURVES must also be enabled.
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
//#define ENABLE_MAP_DRAWING_DEBUG // ENABLE DEBUG / TESTING definitions
# ifdef ENABLE_MAP_DRAWING_DEBUG
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// Define Enable common debug / test features
//#define ENABLE_HOURS_TIME_DISPLAY
//#define ENABLE_MOUSE_DEBUG_INFO
//#define ENABLE_MAP_DRAWING_RECT_DEBUG
//#define TEST_DURATION
//#define MAP_LOG_EVENTS
//#define ENABLE_UNEVEN_MACHINE_MIN_MAX_TEST
2022-04-26 23:40:40 +00:00
/ < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < <
2021-04-18 01:54:56 +00:00
// Define Display macros to enhance displays
# endif
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# if defined(ENABLE_MAP_DRAWING_DEBUG) && ( defined(ENABLE_TEST_CPAP) || defined(ENABLE_TEST_SAWTOOTH) || defined(ENABLE_TEST_SINGLE) || defined(ENABLE_TEST_NODATA) )
# define test_data(A,B,C,D,E,F,G,H) if (!testdata( A , B , C , D , E , F , G, H)) continue ;
# else
# define test_data(A,B,C,D,E,F,G,H)
# endif
2022-04-26 23:40:40 +00:00
# define TEST_MACROS_ENABLEDoff
# include "test_macros.h"
2021-04-18 01:54:56 +00:00
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# ifdef ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS
static const int drawTickLength = 12 ;
# else
static const int drawTickLength = 0 ;
# endif
static const int tableSize = 1 + ( HIGHEST_POSSIBLE_PRESSURE * INTERVALS_PER_CCMH2O ) ;
static constexpr EventDataType sampleIntervalDiv2 = 1.0 / EventDataType ( INTERVALS_PER_CCMH2O * 2 ) ; // interval pressure is Value-sampleInterval to value+sampleInterval
# ifdef ENABLE_BUCKET_PRESSURE_AVERAGE // New definition of bucket Pressure-0.1 - Pressure0.1 //Bucket pressure is in the middle of the low high range
static constexpr EventDataType sampleIntervalStart = sampleIntervalDiv2 ;
static constexpr EventDataType sampleIntervalEnd = sampleIntervalDiv2 ;
# else // Original bucket definition. Pressure - Pressure+0.2 // Bucket Presure is at the low end of the range.
static constexpr EventDataType sampleIntervalStart = 0.0 ;
static constexpr EventDataType sampleIntervalEnd = sampleIntervalDiv2 * 2 ; // interval pressure is Value-sampleInterval to value+sampleInterval
# endif
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Module <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
int calculatePrecision ( EventDataType value ) {
if ( value > 5 ) return 1 ;
if ( value < 0.5 ) return 3 ;
return 2 ;
}
2014-08-12 08:04:53 +00:00
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
EventDataType pressureToBucket ( EventDataType pressure , int bucketsPerPressure ) {
return pressure * bucketsPerPressure ;
2014-08-11 18:29:44 +00:00
}
2021-04-18 01:54:56 +00:00
EventDataType convertBucketToPressure ( EventDataType bucket , int bucketsPerPressure ) {
return bucket / bucketsPerPressure ;
2014-08-11 18:29:44 +00:00
}
2021-04-18 01:54:56 +00:00
EventDataType pressureToXaxis ( EventDataType pressure , EventDataType pixelsPerPressure , EventDataType minpressure , QRectF & drawingRect ) {
return ( ( pressure - minpressure ) * pixelsPerPressure ) + drawingRect . left ( ) ;
2014-08-11 18:29:44 +00:00
}
2021-04-18 01:54:56 +00:00
EventDataType convertXaxisToPressure ( EventDataType mouseXaxis , EventDataType pixelsPerPressure , EventDataType minpressure , QRectF & drawingRect ) {
return minpressure + ( EventDataType ( mouseXaxis - drawingRect . left ( ) ) / pixelsPerPressure ) ;
2014-08-11 18:29:44 +00:00
}
2021-04-18 01:54:56 +00:00
EventDataType msecToMinutes ( EventDataType value ) {
return value / 60000.0 ; // convert milli-seconds to minutes.
}
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
EventDataType getSetting ( Session * sess , ChannelID code ) {
2021-12-20 18:27:48 +00:00
if ( ! sess - > settings . contains ( code ) ) {
qWarning ( ) < < " MinutesAtPressure could not find channel " < < code ;
return - 1 ;
}
auto setting = sess - > settings . value ( code ) ; /*[code]; */
2021-04-18 01:54:56 +00:00
enum schema : : DataType datatype = schema : : channel [ code ] . datatype ( ) ;
if ( ! ( datatype = = schema : : DEFAULT | | datatype = = schema : : DOUBLE ) ) return - 1 ;
return setting . toDouble ( ) ;
}
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
QString timeString ( EventDataType milliSeconds ) {
# if 1
EventDataType h , m , s = milliSeconds ;
if ( s < 0 ) return QString ( ) ;
s = 60 * modf ( s / 60000 , & m ) ;
m = 60 * modf ( m / 60 , & h ) ;
// These string are existing translations
static const char * TR_TIME_FMT_S = " (%3 sec) " ;
static const char * TR_TIME_FMT_MS = " (%2 min, %3 sec) " ;
static const char * TR_TIME_FMT_HMS = " %1 hours, %2 minutes and %3 seconds " ;
if ( m > 0 ) {
if ( h < = 0 ) {
return QObject : : tr ( TR_TIME_FMT_MS ) . arg ( m , 0 , ' f ' , 0 ) . arg ( s , 0 , ' f ' , 0 ) ;
} else {
return QString ( " (%1) " ).arg(QObject::tr(TR_TIME_FMT_HMS).arg(h,0,'f',0).arg(m,0,'f',0).arg(s,2,'f',0)) ;
2014-08-12 05:59:41 +00:00
}
2021-04-18 01:54:56 +00:00
} else if ( s < 3 & & s > 0.01 ) {
return QObject : : tr ( TR_TIME_FMT_S ) . arg ( s , 1 , ' f ' , 1 ) ;
2014-08-12 05:59:41 +00:00
}
2021-04-18 01:54:56 +00:00
return QObject : : tr ( TR_TIME_FMT_S ) . arg ( s , 0 , ' f ' , 0 ) ;
# else
EventDataType time = milliSeconds ;
QString unit ; // these are already translated.
if ( time > 60 * 1000 ) {
if ( time < = 60 * 60 * 1000 ) {
time / = ( 60 * 1000 ) ;
unit = STR_UNIT_Minutes ;
} else {
time / = ( 60 * 60 * 1000 ) ;
unit = STR_UNIT_Hours ;
}
} else {
// have seconds.
unit = STR_UNIT_Seconds ;
time / = ( 1000 ) ;
}
//DEBUG <<O(milliSeconds) <<O(time) <<O(unit);
return QString ( " (%1 %2) " ) .arg(time,0,'f',milliSeconds<10?0:1) .arg(unit) ;
# endif
2014-08-11 18:29:44 +00:00
}
2021-04-18 01:54:56 +00:00
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< PressureInfo methods <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
PressureInfo : : PressureInfo ( )
2014-08-17 12:56:05 +00:00
{
2021-04-18 01:54:56 +00:00
code = - 1 ;
minTime = maxTime = 0 ;
2014-08-17 12:56:05 +00:00
}
2021-04-18 01:54:56 +00:00
PressureInfo : : PressureInfo ( ChannelID code , qint64 minTime , qint64 maxTime ) : code ( code ) , minTime ( minTime ) , maxTime ( maxTime )
2014-08-11 18:29:44 +00:00
{
2021-04-18 01:54:56 +00:00
init ( ) ;
times . resize ( tableSize ) ;
//times = QVector<int>(tableSize,-1);
2014-08-11 18:29:44 +00:00
}
2021-04-18 01:54:56 +00:00
void PressureInfo : : finishCalcs ( )
2014-08-11 18:29:44 +00:00
{
2021-04-18 01:54:56 +00:00
peaktime = 0 ;
peakevents = 0 ;
firstPlotBucket = 0 ;
lastPlotBucket = 0 ;
2014-08-17 12:56:05 +00:00
2021-04-18 01:54:56 +00:00
int val ;
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
for ( int i = 0 , end = times . size ( ) ; i < end ; + + i ) {
val = times [ i ] ;
if ( val < = 0 ) continue ;
peaktime = qMax ( peaktime , val ) ;
if ( firstPlotBucket = = 0 ) firstPlotBucket = i ;
lastPlotBucket = i ;
2014-08-11 18:29:44 +00:00
}
2014-08-29 05:32:49 +00:00
2021-04-18 01:54:56 +00:00
minpressure = convertBucketToPressure ( firstPlotBucket , bucketsPerPressure ) ;
maxpressure = convertBucketToPressure ( lastPlotBucket , bucketsPerPressure ) ;
2014-09-11 14:23:08 +00:00
2021-04-18 01:54:56 +00:00
for ( const auto & cod : chans ) {
numEvents [ cod ] = 0 ;
if ( schema : : channel [ cod ] . type ( ) ! = schema : : FLAG ) continue ;
int size = events [ cod ] . size ( ) ;
for ( int i = 0 ; i < size ; i + + ) {
val = events [ cod ] [ i ] ;
if ( val > 0 ) numEvents [ cod ] + = val ;
peakevents = qMax ( val , peakevents ) ;
}
2014-08-29 05:32:49 +00:00
}
2021-04-18 01:54:56 +00:00
}
2014-08-29 05:32:49 +00:00
2021-04-18 01:54:56 +00:00
void PressureInfo : : init ( ) {
chan = schema : : channel [ code ] ;
peaktime = peakevents = 0 ;
firstPlotBucket = 0 ;
lastPlotBucket = 0 ;
} ;
2014-08-29 05:32:49 +00:00
2021-04-18 01:54:56 +00:00
void PressureInfo : : AddChannel ( ChannelID c )
{
chans . append ( c ) ;
events [ c ] . resize ( tableSize ) ;
}
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
void PressureInfo : : AddChannels ( QList < ChannelID > & chans )
{
for ( int i = 0 ; i < chans . size ( ) ; + + i ) {
AddChannel ( chans . at ( i ) ) ;
2016-03-08 03:21:26 +00:00
}
2021-04-18 01:54:56 +00:00
}
2016-03-08 03:21:26 +00:00
2021-04-18 01:54:56 +00:00
void PressureInfo : : updateBucketsPerPressure ( Session * sess ) {
bucketsPerPressure = ( sess - > machine ( ) - > loaderName ( ) = = " PRS1 " ) ? 2 : INTERVALS_PER_CCMH2O ;
numberXaxisDivisions = qMin ( 2 * bucketsPerPressure , 10 ) ;
}
2016-03-07 13:54:14 +00:00
2021-04-18 01:54:56 +00:00
EventDataType PressureInfo : : rawToPressure ( EventStoreType raw , EventDataType gain ) {
return EventDataType ( raw ) * gain ;
}
2016-03-07 13:54:14 +00:00
2021-04-18 01:54:56 +00:00
EventStoreType PressureInfo : : rawToBucketId ( EventStoreType raw , EventDataType gain ) {
EventDataType pressure = rawToPressure ( raw , gain ) + sampleIntervalStart ;
EventStoreType ret = floor ( pressure * bucketsPerPressure ) ;
//DEBUG <<O(raw) <<O(gain) <<O(pressure) <<O(ret) ;
return ret ;
}
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
void PressureInfo : : setMachineTimes ( EventDataType min , EventDataType max ) {
machinePressureMin = min ;
machinePressureMax = max ;
}
2016-03-07 09:10:58 +00:00
2016-03-08 04:26:48 +00:00
2021-04-18 01:54:56 +00:00
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< RecalcMAP class <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
2016-03-08 04:26:48 +00:00
2021-04-18 01:54:56 +00:00
RecalcMAP : : ~ RecalcMAP ( )
{
}
void RecalcMAP : : quit ( ) {
map - > mutex . lock ( ) ;
m_quit = true ;
map - > mutex . unlock ( ) ;
}
2016-03-08 04:26:48 +00:00
2021-04-18 01:54:56 +00:00
// find pressure given the time of the event.
void RecalcMAP : : updateFlagData ( int & currentLoc , int & currentEL , int & currentData , qint64 eventTime , QVector < int > & dataArray , PressureInfo & info )
{
for ( ; currentEL < info . eventLists . size ( ) ; currentEL + + ) {
auto EL = info . eventLists [ currentEL ] ;
//DEBUG << NAME(chanId) <<OO(EL,currentEL) <<OO(LOC,currentLoc) <<O(currentData) <<OO(C,info.eventLists.size()) <<OO(D,EL->count());
for ( ; currentLoc < ( int ) EL - > count ( ) ; currentLoc + + ) {
if ( m_quit ) return ;
qint64 sampleTime = EL - > time ( currentLoc ) ;
int raw = EL - > raw ( currentLoc ) ;
EventDataType gain = EL - > gain ( ) ;
EventStoreType data = info . rawToBucketId ( raw , gain ) ;
if ( data > = tableSize ) {
data = tableSize - 1 ;
2016-03-08 04:26:48 +00:00
}
2021-04-18 01:54:56 +00:00
if ( sampleTime < = eventTime ) {
currentData = data ;
if ( sampleTime < eventTime ) continue ;
// Note: The equal part of the comparision allows the pressure graph and the TimeAtPressure to reference the same pressure.
2016-03-08 04:26:48 +00:00
}
2021-04-18 01:54:56 +00:00
if ( currentData < 0 ) currentData = data ;
if ( currentData > = 0 ) {
# if defined(MAP_LOG_EVENTS)
DEBUG < < NAME ( chanId )
< < OO ( EL , currentEL )
< < OO ( LOC , currentLoc )
< < TIME ( sampleTime )
< < TIMEO ( E , eventTime )
< < OO ( same , bool ( sampleTime = = eventTime ) )
< < O ( raw )
< < O ( gain )
< < OO ( bucket , currentData )
< < OO ( P , info . rawToPressure ( raw , gain ) )
;
# endif
dataArray [ currentData ] + + ;
2016-03-08 04:26:48 +00:00
}
2021-04-18 01:54:56 +00:00
return ;
2016-03-08 01:30:47 +00:00
}
2021-04-18 01:54:56 +00:00
currentLoc = 0 ;
}
return ;
}
2016-03-08 01:30:47 +00:00
2021-04-18 01:54:56 +00:00
void RecalcMAP : : updateSpanData ( int & currentLoc , int & currentEL , int & currentData , qint64 startSpan , qint64 eventTime , QVector < int > & dataArray , PressureInfo & info ) {
EventStoreType useddata = ~ 0 ;
for ( ; currentEL < info . eventLists . size ( ) ; currentEL + + ) {
auto EL = info . eventLists [ currentEL ] ;
//DEBUG << NAME(chanId) <<OO(EL,currentEL) <<OO(LOC,currentLoc) <<TIME(startSpan)<< TIME(eventTime) <<OO(D,currentData) <<OO(S,EL->count() );
for ( ; currentLoc < ( int ) EL - > count ( ) ; currentLoc + + ) {
if ( m_quit ) return ;
qint64 sampleTime = EL - > time ( currentLoc ) ;
int raw = EL - > raw ( currentLoc ) ;
EventDataType gain = EL - > gain ( ) ;
EventStoreType data = info . rawToBucketId ( raw , gain ) ;
if ( data > = tableSize ) {
data = tableSize - 1 ;
2016-03-08 03:21:26 +00:00
}
2021-04-18 01:54:56 +00:00
if ( sampleTime < startSpan ) {
currentData = data ;
continue ;
2016-03-08 02:17:00 +00:00
}
2021-04-18 01:54:56 +00:00
if ( currentData < 0 ) {
// have first sample
currentData = data ;
2016-03-08 01:30:47 +00:00
}
2021-04-18 01:54:56 +00:00
if ( useddata ! = currentData & & currentData > 0 ) {
dataArray [ currentData ] + + ;
# if defined(MAP_LOG_EVENTS)
DEBUG
< < NAME ( chanId )
< < OO ( EL , currentEL )
< < OO ( LOC , currentLoc )
< < TIME ( startSpan )
< < TIMEO ( S , sampleTime )
< < TIMEO ( E , eventTime )
< < OO ( same , bool ( sampleTime = = eventTime ) )
< < OO ( P , info . rawToPressure ( raw , gain ) )
< < OO ( lastBucket , currentData )
< < OO ( bucket , data ) ;
# endif
useddata = currentData ;
2018-05-05 07:14:44 +00:00
}
2021-04-18 01:54:56 +00:00
if ( data < = 0 ) {
break ;
2016-03-07 09:10:58 +00:00
}
2021-04-18 01:54:56 +00:00
if ( sampleTime > = eventTime ) return ;
currentData = data ;
2016-03-07 09:10:58 +00:00
}
2021-04-18 01:54:56 +00:00
currentLoc = 0 ;
2016-03-07 09:10:58 +00:00
}
2021-04-18 01:54:56 +00:00
return ;
}
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
void RecalcMAP : : updateEventsChannel ( Session * sess , ChannelID chanId , QVector < int > & dataArray , PressureInfo & info )
{
this - > chanId = chanId ;
int qtyEvents = 0 ;
EventDataType duration = 0 , gain ;
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
qint64 t , start ;
EventStoreType * dptr ;
EventStoreType * eptr ;
quint32 * tptr ;
int cnt = 0 ;
2014-08-17 12:56:05 +00:00
2021-04-18 01:54:56 +00:00
schema : : ChanType chanType = schema : : channel [ chanId ] . type ( ) ;
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
auto channelEvents = sess - > eventlist . value ( chanId ) ;
// Loop through event lists
for ( int index = 0 ; index < channelEvents . size ( ) ; index + + ) {
auto EL = channelEvents [ index ] ;
qint64 minx = info . minTime ;
qint64 maxx = info . maxTime ;
start = EL - > first ( ) ;
tptr = EL - > rawTime ( ) ;
dptr = EL - > rawData ( ) ;
cnt = EL - > count ( ) ;
eptr = dptr + cnt ;
gain = EL - > gain ( ) ;
2014-08-11 19:25:27 +00:00
2021-04-18 01:54:56 +00:00
int currentLoc = 0 ;
int currentEL = 0 ;
int currentData = - 1 ;
for ( ; dptr < eptr ; dptr + + ) {
if ( m_quit ) return ;
t = start + * tptr + + ;
if ( t < minx ) continue ;
duration = EventDataType ( * dptr ) * gain ;
if ( chanType = = schema : : SPAN ) {
qint64 ts = t - qint64 ( duration * 1000 ) ;
// ts is the start of the span sequence. t is the end of span sequence - when span goes away.
if ( ts > maxx ) continue ;
if ( ts < minx ) ts = minx ;
if ( t > maxx ) t = maxx ;
qtyEvents + + ;
updateSpanData ( currentLoc , currentEL , currentData , ts , t , dataArray , info ) ;
2014-08-11 19:25:27 +00:00
} else {
2021-04-18 01:54:56 +00:00
if ( t > maxx ) continue ;
if ( schema : : channel [ chanId ] . type ( ) = = schema : : FLAG ) {
updateFlagData ( currentLoc , currentEL , currentData , t , dataArray , info ) ;
}
2014-08-11 18:29:44 +00:00
}
}
}
2021-04-18 01:54:56 +00:00
return ;
}
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
void RecalcMAP : : updateEvents ( Session * sess , PressureInfo & info ) {
QHash < ChannelID , QVector < EventList * > > : : iterator ei = sess - > eventlist . find ( info . code ) ;
if ( ei = = sess - > eventlist . end ( ) ) return ;
for ( const auto & cod : info . chans ) {
updateEventsChannel ( sess , cod , info . events [ cod ] , info ) ;
if ( m_quit ) return ;
2014-08-19 10:26:44 +00:00
}
2014-08-11 18:29:44 +00:00
}
2021-04-18 01:54:56 +00:00
void RecalcMAP : : updateTimesValues ( qint64 d1 , qint64 d2 , int key , PressureInfo & info ) {
qint64 duration = ( d2 - d1 ) ;
info . times [ key ] + = duration ;
info . totalDuration + = duration ;
}
2014-08-11 18:29:44 +00:00
2016-03-07 09:10:58 +00:00
//! \brief Updates Time At Pressure from session *sess
2021-04-18 01:54:56 +00:00
void RecalcMAP : : updateTimes ( PressureInfo & info ) {
2016-03-07 09:10:58 +00:00
qint64 d1 , d2 ;
2021-04-18 01:54:56 +00:00
qint64 minx = 0 , maxx = 0 ;
//qint64 prevSessDuration=info.totalDuration;
2016-03-07 09:10:58 +00:00
EventDataType gain ;
EventStoreType data , lastdata ;
qint64 time , lasttime ;
2021-04-18 01:54:56 +00:00
int ELsize ;
2016-03-07 09:10:58 +00:00
bool first ;
2021-04-18 01:54:56 +00:00
if ( info . eventLists . size ( ) = = 0 ) return ;
2016-03-07 09:10:58 +00:00
// Find pressure channel
// Loop through event lists
2021-04-18 01:54:56 +00:00
EventList * EL = info . eventLists [ 0 ] ;
int idx = 0 ;
for ( ; idx < info . eventLists . size ( ) ; idx + + ) {
EL = info . eventLists [ idx ] ;
2016-03-07 09:10:58 +00:00
ELsize = EL - > count ( ) ;
if ( ELsize < 1 ) continue ;
2021-04-18 01:54:56 +00:00
gain = EL - > gain ( ) ;
# if 1
// Workaround for the popout function. when the MAP popout graph is created the time selction mixx and miny are both zero.
// this indicates that there is no data to be displayed. WHY ??
// This workaround uses the session min/max times when the selection min/max times are zero.
if ( map - > numCloned > 0 ) {
bool cloneWorkAround = false ;
if ( info . minTime = = 0 ) {
cloneWorkAround = true ;
info . minTime = EL - > first ( ) ;
}
if ( cloneWorkAround ) {
if ( info . maxTime < EL - > last ( ) ) info . maxTime = EL - > last ( ) ;
}
}
# endif
2016-03-07 09:10:58 +00:00
// Skip if outside of range
2021-04-18 01:54:56 +00:00
if ( ( EL - > first ( ) > info . maxTime ) | | ( EL - > last ( ) < info . minTime ) ) {
2016-03-07 09:10:58 +00:00
continue ;
}
2021-04-18 01:54:56 +00:00
// adjust for multiple sessions.
// EL->first and last are for the current session while minTime and MaxTime are for a set of seesion for the day.
minx = qMax ( info . minTime , EL - > first ( ) ) ;
maxx = qMin ( info . maxTime , EL - > last ( ) ) ;
lasttime = 0 ;
lastdata = 0 ;
data = 0 ;
first = true ;
2016-03-07 09:10:58 +00:00
// Scan through pressure samples
for ( int e = 0 ; e < ELsize ; + + e ) {
2021-04-18 01:54:56 +00:00
if ( m_quit ) return ;
2016-03-07 09:10:58 +00:00
time = EL - > time ( e ) ;
2021-04-18 01:54:56 +00:00
EventStoreType raw = EL - > raw ( e ) ;
test_data ( e , ELsize , raw , time , info . minTime , info . maxTime , gain , EL ) ;
data = ipap_info - > rawToBucketId ( raw , gain ) ;
//DEBUG << OO(e=,e) << TIME(time) <<O(raw) << O(data) ; // <<TIMEO(EL,EL->first()) <<TIMEO(el,EL->last()) <<TIMEO(I,minx) <<TIMEO(i,maxx);
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
if ( data > = tableSize ) {
data = tableSize - 1 ;
2018-04-25 10:34:23 +00:00
}
2016-03-07 09:10:58 +00:00
if ( ( time < minx ) | | first ) {
2021-04-18 01:54:56 +00:00
lasttime = time ;
lastdata = data ;
first = false ;
continue ;
}
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
if ( lastdata ! = data ) {
d1 = qMax ( minx , lasttime ) ;
d2 = qMin ( maxx , time ) ;
updateTimesValues ( d1 , d2 , lastdata , info ) ;
lasttime = time ;
lastdata = data ;
}
if ( time > maxx ) {
break ;
}
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
}
if ( ( lasttime > 0 ) & & ( ( lasttime < = maxx ) | | ( lastdata = = data ) ) ) {
d1 = qMax ( minx , lasttime ) ;
d2 = qMin ( maxx , EL - > last ( ) ) ;
updateTimesValues ( d1 , d2 , lastdata , info ) ;
2016-03-07 09:10:58 +00:00
}
}
}
2021-04-18 01:54:56 +00:00
void RecalcMAP : : setSelectionRange ( gGraph * graph ) {
graph - > graphView ( ) - > GetXBounds ( minTime , maxTime ) ;
}
2016-03-07 09:10:58 +00:00
2014-08-11 18:29:44 +00:00
void RecalcMAP : : run ( )
{
QMutexLocker locker ( & map - > mutex ) ;
map - > m_recalculating = true ;
Day * day = map - > m_day ;
if ( ! day ) return ;
2016-03-08 02:31:55 +00:00
2021-04-18 01:54:56 +00:00
// Get the channels for specified Channel types
QList < ChannelID > chans ;
# if defined(ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH) || defined(ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS)
chans = day - > getSortedMachineChannels ( schema : : FLAG ) ;
2016-03-08 02:31:55 +00:00
chans . removeAll ( CPAP_VSnore ) ;
chans . removeAll ( CPAP_VSnore2 ) ;
chans . removeAll ( CPAP_FlowLimit ) ;
chans . removeAll ( CPAP_RERA ) ;
2021-04-18 01:54:56 +00:00
# endif
// Get the channels for specified Channel types
QList < ChannelID > chansSpan ;
# ifdef ENABLE_DISPLAY_SPAN_EVENTS_AS_BACKGROUND
chansSpan = day - > getSortedMachineChannels ( schema : : SPAN ) ;
chansSpan . removeAll ( CPAP_Ramp ) ;
# endif
2014-08-11 18:29:44 +00:00
2020-04-10 20:49:23 +00:00
ChannelID ipapcode = CPAP_Pressure ; // default
2021-04-18 01:54:56 +00:00
for ( auto & ch : { CPAP_IPAPSet , CPAP_IPAP , CPAP_PressureSet } ) {
2020-04-10 20:49:23 +00:00
if ( day - > channelExists ( ch ) ) {
ipapcode = ch ;
break ;
}
}
2021-04-18 01:54:56 +00:00
2020-04-10 20:49:23 +00:00
ChannelID epapcode = NoChannel ; // default
2021-04-18 01:54:56 +00:00
for ( auto & ch : { CPAP_EPAPSet , CPAP_EPAP } ) {
2020-04-10 20:49:23 +00:00
if ( day - > channelExists ( ch ) ) {
epapcode = ch ;
break ;
}
}
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
PressureInfo IPAP ( ipapcode , minTime , maxTime ) , EPAP ( epapcode , minTime , maxTime ) ;
ipap_info = & IPAP ;
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
chans + = chansSpan ;
2016-03-07 09:10:58 +00:00
IPAP . AddChannels ( chans ) ;
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
EventDataType minP = HIGHEST_POSSIBLE_PRESSURE ;
EventDataType maxP = 0 ;
auto & sessions = day - > sessions ;
# if defined(TEST_DURATION)
if ( sessions . size ( ) = = 1 )
{
auto & eventLists = sess - > eventlist . value ( ipapcode ) ;
if ( eventLists . size ( ) = = 1 ) {
if ( sess - > first ( ) ! = minTime ) {
DEBUG < < " Session " < < DATETIME ( sess - > first ( ) ) < < " sessFirst " < < TIME ( sess - > first ( ) ) < < " minTime.. " < < TIME ( minTime ) < < OO ( diffMs , sess - > first ( ) - minTime ) < < NAME ( info . code ) ;
}
if ( sess - > last ( ) ! = maxTime ) {
DEBUG < < " Session " < < DATETIME ( sess - > first ( ) ) < < " SessEnd.. " < < TIME ( sess - > last ( ) ) < < " MaxTime,, " < < TIME ( maxTime ) < < OO ( diffMs , sess - > last ( ) - maxTime ) < < NAME ( info . code ) ;
}
}
}
# endif
for ( int idx = 0 ; idx < sessions . size ( ) ; idx + + ) {
auto & sess = sessions [ idx ] ;
2021-12-20 18:27:48 +00:00
if ( sess - > type ( ) = = MT_CPAP ) {
IPAP . updateBucketsPerPressure ( sess ) ;
EPAP . updateBucketsPerPressure ( sess ) ;
IPAP . eventLists = sess - > eventlist . value ( ipapcode ) ;
EPAP . eventLists = sess - > eventlist . value ( epapcode ) ;
2014-08-11 18:29:44 +00:00
2021-12-20 18:27:48 +00:00
updateTimes ( IPAP ) ;
updateTimes ( EPAP ) ;
2014-08-11 18:29:44 +00:00
2021-12-20 18:27:48 +00:00
EventDataType value = getSetting ( sess , CPAP_PressureMin ) ;
if ( value > = 0.1 & & minP > value ) minP = value ;
value = getSetting ( sess , CPAP_PressureMax ) ;
if ( value > = 0.1 & & maxP < value ) maxP = value ;
2014-08-11 18:29:44 +00:00
2021-12-20 18:27:48 +00:00
updateEvents ( sess , IPAP ) ;
2014-08-11 18:29:44 +00:00
2021-12-20 18:27:48 +00:00
if ( m_quit ) break ;
}
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
}
if ( minP > maxP ) minP = maxP ;
IPAP . setMachineTimes ( minP , maxP ) ;
# ifdef ENABLE_UNEVEN_MACHINE_MIN_MAX_TEST
int dayInMonth = day - > date ( ) . day ( ) ;
if ( ( dayInMonth & 1 ) ! = 0 )
{
machinePressureMin - = 0.05 ;
machinePressureMax + = 0.05 ;
}
# endif
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
if ( m_quit ) {
m_done = true ;
return ;
}
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
IPAP . finishCalcs ( ) ;
EPAP . finishCalcs ( ) ;
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
map - > timelock . lock ( ) ;
map - > epap = EPAP ;
map - > ipap = IPAP ;
map - > timelock . unlock ( ) ;
map - > recalcFinished ( ) ;
m_done = true ;
}
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< MinutesAtPressure class <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
MinutesAtPressure : : MinutesAtPressure ( ) : Layer ( NoChannel )
{
m_remap = nullptr ;
m_minimum_height = 0 ;
}
MinutesAtPressure : : ~ MinutesAtPressure ( )
{
while ( recalculating ( ) ) { } ;
}
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
void MinutesAtPressure : : SetDay ( Day * day )
{
Layer : : SetDay ( day ) ;
//if (m_day) DEBUGTF << day->date().toString("dd MMM yyyy hh:mm:ss.zzz");
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
m_empty = false ;
m_recalculating = false ;
m_lastminx = 0 ;
m_lastmaxx = 0 ;
m_empty = ! m_day | | ! ( m_day - > channelExists ( CPAP_Pressure ) | | m_day - > channelExists ( CPAP_EPAP ) | | m_day - > channelExists ( CPAP_PressureSet ) | | m_day - > channelExists ( CPAP_EPAPSet ) ) ;
}
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
int MinutesAtPressure : : minimumHeight ( )
{
return m_minimum_height ;
}
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
bool MinutesAtPressure : : isEmpty ( )
{
2014-08-19 10:26:44 +00:00
2021-04-18 01:54:56 +00:00
return m_empty ;
}
2014-08-19 10:26:44 +00:00
2021-04-18 01:54:56 +00:00
void MinutesAtPressure : : paint ( QPainter & painter , gGraph & graph , const QRegion & region )
{
2014-08-19 10:26:44 +00:00
2021-04-18 01:54:56 +00:00
QRectF boundingRect = region . boundingRect ( ) ;
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
m_minx = graph . min_x ;
m_maxx = graph . max_x ;
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
if ( graph . printing ( ) | | ( ( m_lastminx ! = m_minx ) | | ( m_lastmaxx ! = m_maxx ) ) ) {
// note: this doesn't run on popout graphs that aren't linked with scrolling...
// it's a pretty useless graph to popout, probably should just block it's popout instead.
recalculate ( & graph ) ;
}
if ( ! initialized ) return ;
// conditional display of TimeAtPressure Plots based on Plots displayed by the Pressure Graph.
// if no Plots are displayed on the Pressure Graph then Displays "Plots Disabled"
// if Pressure Graph is not displayed then "time at Pressure" displays both Plots
// the max Y Axis value is updated for the displayed plot.
setEnabled ( graph ) ;
bool display_pressure = isEnabled ( ipap . code ) ;
bool display_epap = isEnabled ( epap . code ) ;
if ( ! ( display_epap | | display_pressure ) ) {
// No Data
QString msg = QObject : : tr ( " Plots Disabled " ) ;
int x , y ;
GetTextExtent ( msg , x , y , bigfont ) ;
graph . renderText ( msg , boundingRect , Qt : : AlignCenter , 0 , Qt : : gray , bigfont ) ;
return ;
}
// check for empty data.
if ( ipap . lastPlotBucket = = 0 ) display_pressure = false ;
if ( epap . lastPlotBucket = = 0 ) display_epap = false ;
if ( ! ( display_epap | | display_pressure ) ) {
QString msg = QObject : : tr ( " No Data " ) ;
int x , y ;
GetTextExtent ( msg , x , y , bigfont ) ;
graph . renderText ( msg , boundingRect , Qt : : AlignCenter , 0 , Qt : : gray , bigfont ) ;
return ;
}
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
// need to Check if windows has changed.
m_lastminx = m_minx ;
m_lastmaxx = m_maxx ;
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
if ( graph . printing ( ) ) {
// Could just lock the mutex QMutex instead
mutex . lock ( ) ;
// do nothing between, it should hang until complete.
mutex . unlock ( ) ;
//while (recalculating()) { QThread::yieldCurrentThread(); } // yield or whatever
2014-08-11 18:29:44 +00:00
}
2014-08-11 19:25:27 +00:00
2021-04-18 01:54:56 +00:00
if ( ! painter . isActive ( ) ) return ;
// Recalculating in the background... So we just draw an old copy until then the new data is ready
// (it will refresh itself when complete)
// The only time we can't draw when at the end of the recalc when the map variables are being updated
// So use a mutex to lock
QMutexLocker TimeLock ( & timelock ) ;
////////////////////////////////////////////////////////////////////
// calculate pressure Ranges
////////////////////////////////////////////////////////////////////
int peaktime = 0 ;
2014-08-19 10:26:44 +00:00
2021-04-18 01:54:56 +00:00
EventDataType minpressure = ipap . machinePressureMin ;
EventDataType maxpressure = ipap . machinePressureMax ;
//DEBUG <<O(minpressure) <<O(maxpressure) ;
2016-03-07 09:10:58 +00:00
2021-04-18 01:54:56 +00:00
if ( maxpressure < 0.5 | | minpressure > maxpressure ) {
minpressure = HIGHEST_POSSIBLE_PRESSURE ;
maxpressure = 0 ;
2014-08-11 19:25:27 +00:00
}
2021-04-18 01:54:56 +00:00
//DEBUG <<O(minpressure) <<O(maxpressure) ;
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
// Calculate pressure range for current display.
if ( display_pressure ) {
maxpressure = qMax ( ipap . maxpressure , maxpressure ) ;
minpressure = qMin ( ipap . minpressure , minpressure ) ;
peaktime = qMax ( ipap . peaktime , peaktime ) ;
2014-08-19 10:26:44 +00:00
}
2021-04-18 01:54:56 +00:00
//DEBUG <<O(minpressure) <<O(maxpressure) ;
2014-08-12 05:59:41 +00:00
2021-04-18 01:54:56 +00:00
if ( display_epap ) {
maxpressure = qMax ( epap . maxpressure , maxpressure ) ;
minpressure = qMin ( epap . minpressure , minpressure ) ;
peaktime = qMax ( epap . peaktime , peaktime ) ;
}
//DEBUG <<O(minpressure) <<O(maxpressure) ;
// insure pressure range is above minumum. - especially for constant pressures.
// software needs a range of pressures.
static const EventDataType minPressureRange = 3 ; // 3 is same as pressure graph
if ( ( maxpressure - minpressure ) < = minPressureRange ) {
minpressure - = ( minPressureRange ) ;
maxpressure + = ( minPressureRange ) ;
}
//DEBUG <<O(minpressure) <<O(maxpressure) << O(minPressureRange);
2014-08-12 05:59:41 +00:00
2021-04-18 01:54:56 +00:00
# ifdef ALIGN_X_AXIS_ON_INTEGER_BOUNDS
// align min and max on integers pressures.
minpressure = floor ( minpressure ) ;
maxpressure = ceil ( maxpressure ) ;
# endif
//DEBUG <<O(minpressure) <<O(maxpressure) ;
2014-08-12 05:59:41 +00:00
2021-04-18 01:54:56 +00:00
EventDataType startGraphBucket = pressureToBucket ( minpressure , ipap . bucketsPerPressure ) ;
EventDataType endGraphBucket = pressureToBucket ( maxpressure , ipap . bucketsPerPressure ) ;
EventDataType numGraphBucket = endGraphBucket - startGraphBucket ;
//DEBUG << O(minpressure) <<O(maxpressure) << O(startGraphBucket) << O(endGraphBucket) << OO(min2,convertBucketToPressure(endGraphBucket,ipap.bucketsPerPressure));
2018-05-05 07:14:44 +00:00
2021-04-18 01:54:56 +00:00
////////////////////////////////////////////////////////////////////
// Generate drawing Info
////////////////////////////////////////////////////////////////////
2014-08-11 18:29:44 +00:00
2021-04-18 01:54:56 +00:00
if ( ipap . minpressure < = 0.01 ) return ;
// draw rectangle for graph boundary.
painter . setFont ( * defaultfont ) ;
painter . setPen ( Qt : : black ) ;
painter . drawRect ( boundingRect ) ;
////////////////////////////////////////////////////////////////////
// Calculate drawing bounds
////////////////////////////////////////////////////////////////////
EventDataType rectOffset = 0.0 ;
# if defined( EXTRA_SPACE_ON_X_AXIS )
rectOffset = ( ( boundingRect . width ( ) / numGraphBucket ) * ipap . bucketsPerPressure ) / ipap . numberXaxisDivisions ;
# endif
//DEBUG << O(rectOffset);
# ifdef ENABLE_MAP_DRAWING_RECT_DEBUG
painter . setPen ( QPen ( QColor ( 70 , 70 , 70 , 60 ) , 1.5 , Qt : : SolidLine ) ) ;
int leftAdjustment = 90 ;
int topAdjustment = 30 ;
int heightAdjustment = topAdjustment + 20 ;
int widthAdjustment = 50 ;
rectOffset + = leftAdjustment ;
QRectF drawingRect = QRectF ( boundingRect . left ( ) + rectOffset , boundingRect . top ( ) + topAdjustment , boundingRect . width ( ) - ( 2 * rectOffset ) - widthAdjustment , boundingRect . height ( ) - heightAdjustment ) ;
painter . drawRect ( drawingRect ) ;
painter . setPen ( Qt : : black ) ;
# else
QRectF drawingRect = QRectF ( boundingRect . left ( ) + rectOffset , boundingRect . top ( ) , boundingRect . width ( ) - ( 2 * rectOffset ) , boundingRect . height ( ) - 2 ) ;
# endif
EventDataType pixelsPerBucket = EventDataType ( drawingRect . width ( ) ) / EventDataType ( numGraphBucket ) ;
MapPainter mapPainter ( painter , graph , drawingRect , boundingRect ) ;
mapPainter . setHorizontal ( minpressure , maxpressure , pixelsPerBucket , ipap . bucketsPerPressure , NUMBER_OF_CATMULLROMSPLINE_POINTS ) ;
////////////////////////////////////////////////////////////////////
// Draw X and Y axis's and Top Bar (mouse)
////////////////////////////////////////////////////////////////////
int widest_YAxis = mapPainter . drawYaxis ( peaktime ) ;
mapPainter . drawXaxis ( ipap . numberXaxisDivisions , startGraphBucket , endGraphBucket ) ;
mapPainter . drawEventYaxis ( ipap . peakevents , widest_YAxis ) ;
mapPainter . drawMetaData ( last_mouse , topBarLabel , ipap , epap , m_enabled , minpressure , maxpressure ) ;
////////////////////////////////////////////////////////////////////
// Draw Events and waveforms.
////////////////////////////////////////////////////////////////////
# if defined(ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH) || defined(ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS) || defined(ENABLE_DISPLAY_SPAN_EVENTS_AS_BACKGROUND)
for ( const auto ch : ipap . chans ) {
// display Event H, CA, OA, UA, H as tick marks or CSR or LargeLeaks as background - based on Presssure graph configuration.
if ( ! isEnabled ( ch ) ) continue ; // skip if not enabled
if ( ( schema : : channel [ ch ] . type ( ) = = schema : : FLAG ) & & ( ipap . numEvents [ ch ] = = 0 ) ) continue ;
mapPainter . setChannelInfo ( ch , ipap . events [ ch ] , mapPainter . yPixelsPerEvent , startGraphBucket , endGraphBucket ) ;
mapPainter . drawEvent ( ) ;
}
# endif
if ( display_pressure ) {
mapPainter . setChannelInfo ( ipap . code , ipap . times , mapPainter . yPixelsPerMsec , ipap . firstPlotBucket , ipap . lastPlotBucket ) ;
mapPainter . drawPlot ( ) ;
}
if ( display_epap ) {
mapPainter . setChannelInfo ( epap . code , epap . times , mapPainter . yPixelsPerMsec , epap . firstPlotBucket , epap . lastPlotBucket ) ;
mapPainter . drawPlot ( ) ;
}
2014-08-11 18:29:44 +00:00
}
void MinutesAtPressure : : recalculate ( gGraph * graph )
{
while ( recalculating ( ) )
m_remap - > quit ( ) ;
m_remap = new RecalcMAP ( this ) ;
m_remap - > setAutoDelete ( true ) ;
2021-04-18 01:54:56 +00:00
m_remap - > setSelectionRange ( graph ) ;
m_graph = graph ;
2014-08-11 18:29:44 +00:00
QThreadPool * tp = QThreadPool : : globalInstance ( ) ;
2014-08-17 12:56:05 +00:00
if ( graph - > printing ( ) ) {
m_remap - > run ( ) ;
} else {
while ( ! tp - > tryStart ( m_remap ) ) ;
2018-05-05 07:14:44 +00:00
m_lastmaxx = m_maxx ;
m_lastminx = m_minx ;
2014-08-17 12:56:05 +00:00
}
2014-08-11 18:29:44 +00:00
}
void MinutesAtPressure : : recalcFinished ( )
{
2018-05-05 07:14:44 +00:00
if ( m_graph & & ! m_graph - > printing ( ) ) {
// Can't call this using standard timedRedraw function, we are in another thread, so have to use a throwaway timer
QTimer : : singleShot ( 0 , m_graph - > graphView ( ) , SLOT ( refreshTimeout ( ) ) ) ;
2021-04-18 01:54:56 +00:00
// this causes MinutesAtPressure:: paint to be called.
2014-08-11 18:29:44 +00:00
}
m_remap = nullptr ;
2018-05-05 07:14:44 +00:00
m_recalculating = false ;
2021-04-18 01:54:56 +00:00
initialized = true ;
2014-08-11 18:29:44 +00:00
}
2014-10-03 02:31:51 +00:00
bool MinutesAtPressure : : mouseMoveEvent ( QMouseEvent * , gGraph * graph )
2014-08-11 18:29:44 +00:00
{
2018-05-05 07:14:44 +00:00
if ( graph ) graph - > timedRedraw ( 0 ) ;
2014-08-12 10:44:05 +00:00
return false ;
2014-08-11 18:29:44 +00:00
}
bool MinutesAtPressure : : mousePressEvent ( QMouseEvent * event , gGraph * graph )
{
Q_UNUSED ( event ) ;
Q_UNUSED ( graph ) ;
2014-08-12 10:44:05 +00:00
return false ;
2014-08-11 18:29:44 +00:00
}
bool MinutesAtPressure : : mouseReleaseEvent ( QMouseEvent * event , gGraph * graph )
{
Q_UNUSED ( event ) ;
Q_UNUSED ( graph ) ;
2014-08-12 10:44:05 +00:00
return false ;
2014-08-11 18:29:44 +00:00
}
2021-04-18 01:54:56 +00:00
bool MinutesAtPressure : : isEnabled ( ChannelID id ) {
return m_enabled [ id ] ;
} ;
void MinutesAtPressure : : setEnabled ( gGraph & graph ) {
QList < ChannelID > channels ;
channels + = ipap . code ;
channels + = epap . code ;
channels + = ipap . chans ;
gGraphView * graphView = graph . graphView ( ) ;
gGraph * pressureGraph = graphView - > findGraph ( STR_GRAPH_Pressure ) ;
gLineChart * pressureGraphLC = NULL ;
if ( ! pressureGraph ) return ;
pressureGraphLC = dynamic_cast < gLineChart * > ( pressureGraph - > getLineChart ( ) ) ;
if ( ! pressureGraph - > visible ( ) ) return ;
if ( ! pressureGraphLC ) return ;
m_enabled . clear ( ) ;
for ( QList < ChannelID > : : iterator it = channels . begin ( ) ; it ! = channels . end ( ) ; + + it ) {
ChannelID ch = * it ;
bool value ;
schema : : Channel & chan = schema : : channel [ ch ] ;
value = chan . enabled ( ) ;
if ( chan . type ( ) = = schema : : WAVEFORM ) {
value = pressureGraphLC - > plotEnabled ( ch ) ;
} else {
value & = pressureGraphLC - > m_flags_enabled [ ch ] ;
}
m_enabled [ ch ] = value ;
}
} ;
EventDataType getStep ( int & stepi , EventDataType & stepmult ) {
static const QList < EventDataType > stepArray { 1.0 , 2.0 , 5.0 } ;
return stepmult * stepArray [ stepi ] ;
}
void decStep ( int & stepi , EventDataType & stepmult ) {
stepmult = stepmult / 10 ;
Q_UNUSED ( stepi ) ;
}
void MapPainter : : calculatePeakY ( int peaktime ) {
GetTextExtent ( " W " , singleCharWidth , textHeight ) ;
peakMinutes = msecToMinutes ( peaktime + 1 ) ; // peakMinutes must not be zero.
static const QList < EventDataType > stepArray { 1.0 , 2.0 , 5.0 } ;
//static const QList<EventDataType> stepArray {1.0, 2.5,5.0, 7.5};
int stepArraySize = stepArray . size ( ) ;
int height = drawingRect . height ( ) ;
height - = qMin ( int ( drawingRect . height ( ) / 10 ) , qMax ( textHeight , drawTickLength ) ) ;
int maxsteps = ceil ( height / textHeight ) ;
# define MINSTEPS 1
# define MAXSTEPS 15
maxsteps = qMax ( MINSTEPS , qMin ( maxsteps , MAXSTEPS ) ) ;
EventDataType minStep = peakMinutes / maxsteps ;
int stepi = 0 ; // o - ArraySize-1
EventDataType stepmult = 1 ; //10**n
int numberSteps = 1 ;
EventDataType step = 1 ;
yPixelsPerStep = 0 ;
EventDataType totalMinutes = 0 ;
EventDataType pixelsPerMinute = 0 ;
bool up = false ; // find smallest step
// find smallest step that where step label do not overlap
for ( ; ; ) {
step = stepmult * stepArray [ stepi ] ;
if ( step > minStep ) {
if ( ! up ) {
// very low levels.
stepmult = stepmult / 10 ;
continue ;
}
numberSteps = ceil ( peakMinutes / step ) ;
if ( numberSteps = = 0 ) numberSteps = 1 ;
totalMinutes = step * numberSteps ;
if ( totalMinutes > = peakMinutes ) {
// this works.
break ;
}
}
up = true ;
stepi = ( stepi + 1 ) % stepArraySize ; // next step module array size.
if ( stepi = = 0 ) stepmult * = 10 ;
}
// determine Y-axis scale
pixelsPerMinute = height / totalMinutes ;
totalMinutes = step * numberSteps ;
pixelsPerMinute = height / totalMinutes ;
yPixelsPerStep = height / numberSteps ;
// update parameters required for the Y-axis
yPixelsPerMsec = pixelsPerMinute / 60000 ;
yMinutesPerStep = step ;
peakMinutes = totalMinutes ;
//DEBUG << O(drawingRect.height() ) << O(textHeight) << O(peaktime) << O(peakmult) << O(yPixelsPerMsec) << O(yMinutesPerStep) << O(peakMinutes) ;
}
int MapPainter : : drawYaxis ( int peaktime ) {
MapPainter : : calculatePeakY ( peaktime ) ;
////////////////////////////////////////////////////////////////////
// Draw Y Axis labels
////////////////////////////////////////////////////////////////////
QString label ;
int labelWidth , labelHeight ;
EventDataType bot = drawingRect . bottom ( ) ;
int left = boundingRect . left ( ) ;
int width = boundingRect . width ( ) ;
int widest_YAxis = 2 ;
EventDataType limit = peakMinutes + ( yMinutesPerStep / 2 ) ;
for ( EventDataType f = 0.0 ; f < limit ; f + = yMinutesPerStep , bot - = yPixelsPerStep ) {
painter . setPen ( Qt : : black ) ;
painter . drawLine ( left , bot , left - 4 , bot ) ;
painter . setPen ( QColor ( 128 , 128 , 128 , 30 ) ) ;
painter . drawLine ( left , bot , left + width , bot ) ;
label = QString ( " %1 " ) . arg ( f ) ;
GetTextExtent ( label , labelWidth , labelHeight ) ;
widest_YAxis = qMax ( widest_YAxis , labelWidth + 8 ) ;
graph . renderText ( label , left - ( labelWidth + 8 ) , bot + labelHeight / 2 - 2 ) ;
//DEBUG << O(label) << O(bot) <<O(yPixelsPerStep) <<OO(bot,drawingRect.bottom()) ;
}
return widest_YAxis ;
}
void MapPainter : : drawEventYaxis ( EventDataType peakEvents , int widest_YAxis ) {
# ifdef ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH
EventDataType eventHeight = drawingRect . height ( ) ;
EventDataType yEventPerStep = 1.0 ;
QString label ;
int reserveSpace = textHeight + = 4 ;
////////////////////////////////////////////////////////////////////
// Calculate vertical step for ploting events
////////////////////////////////////////////////////////////////////
EventDataType yPixelsPerEventStep = ( eventHeight * yEventPerStep ) / peakEvents ;
if ( yPixelsPerEventStep < reserveSpace ) {
yEventPerStep = 2.0 ;
yPixelsPerEventStep = ( eventHeight * yEventPerStep ) / peakEvents ;
if ( yPixelsPerEventStep < reserveSpace ) {
yEventPerStep = 5.0 ;
yPixelsPerEventStep = ( eventHeight * yEventPerStep ) / peakEvents ;
if ( yPixelsPerEventStep < reserveSpace ) {
yEventPerStep = 20.0 ;
yPixelsPerEventStep = ( eventHeight * yEventPerStep ) / peakEvents ;
if ( yPixelsPerEventStep < reserveSpace ) {
yEventPerStep = 50.0 ;
yPixelsPerEventStep = ( eventHeight * yEventPerStep ) / peakEvents ;
}
}
}
}
yPixelsPerEvent = EventDataType ( eventHeight / peakEvents ) ;
//DEBUG <<O(yPixelsPerEventStep) <<O(reserveSpace) << O(eventHeight) <<O(yEventPerStep) <<O(yPixelsPerEvent) <<O(peakEvents);
////////////////////////////////////////////////////////////////////
// Draw event y axis
////////////////////////////////////////////////////////////////////
EventDataType bot = drawingRect . bottom ( ) ;
EventDataType left = boundingRect . left ( ) - widest_YAxis - 4 ;
EventDataType limit = peakEvents + ( yEventPerStep / 2 ) ;
int labelWidth , labelHeight ;
for ( EventDataType f = 0.0 ; f < = limit ; f + = yEventPerStep , bot - = yPixelsPerEventStep ) {
label = QString ( " %1- " ) . arg ( f ) ;
GetTextExtent ( label , labelWidth , labelHeight ) ;
graph . renderText ( label , left - labelWidth , bot + labelHeight / 2 - 2 ) ;
}
# else
Q_UNUSED ( peakEvents ) ;
Q_UNUSED ( widest_YAxis ) ;
# endif
}
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< MapPainter class <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
void MapPainter : : drawXaxis ( int numberXaxisDivisions , int startGraphBucket , int endGraphBucket )
{
////////////////////////////////////////////////////////////////////
// Draw X Axis labels
////////////////////////////////////////////////////////////////////
QString label ;
EventDataType xp , yp ;
pixelsPerPressure = ( pixelsPerBucket * bucketsPerPressure ) ;
EventDataType pixelsPerTick = pixelsPerPressure / numberXaxisDivisions ;
EventDataType tickPressure = minpressure ;
xp = drawingRect . left ( ) ;
int jlabel = 0 ;
int accentTick = numberXaxisDivisions / 2 ;
# if defined(ALIGN_X_AXIS_ON_INTEGER_BOUNDS)
EventDataType pressurePerTick = 1.0 / numberXaxisDivisions ;
Q_UNUSED ( pixelsPerPressure ) ;
# else
EventDataType deltaSteps = ( ceil ( minpressure ) - minpressure ) * pixelsPerPressure ;
EventDataType pixelsFirstTick = fmod ( deltaSteps , pixelsPerTick ) ;
jlabel = floor ( deltaSteps / pixelsPerTick ) ;
EventDataType presFirstTick = ( pixelsFirstTick / pixelsPerPressure ) ;
xp + = pixelsFirstTick ;
tickPressure + = presFirstTick ;
EventDataType pressurePerTick = pixelsPerTick / pixelsPerPressure ;
accentTick = ( jlabel + accentTick ) % numberXaxisDivisions ;
# endif
yp = boundingRect . bottom ( ) + 1 ;
EventDataType ypEnd = yp + 6 ;
EventDataType ypAccentEnd = yp + 12 ;
int j = 0 , labelWidth , labelHeight ;
int right = drawingRect . right ( ) + 1 ;
int end = ( endGraphBucket - startGraphBucket ) ;
painter . setPen ( Qt : : black ) ;
for ( int i = 0 ; i < = end & & xp < = right ; + + i ) {
for ( j = 0 ; j < numberXaxisDivisions & & xp < = right ; + + j , xp + = pixelsPerTick , tickPressure + = pressurePerTick ) {
if ( j = = jlabel ) {
label = QString ( " %1 " ) . arg ( tickPressure , 0 , ' f ' , 0 ) ;
GetTextExtent ( label , labelWidth , labelHeight ) ;
graph . renderText ( label , xp - labelWidth / 2 , yp + labelHeight + 4 ) ;
}
painter . drawLine ( xp , yp , xp , ( j = = accentTick ? ypAccentEnd : ypEnd ) ) ;
}
}
}
QString displayMetaData ( QString label , EventDataType mop , EventDataType min , EventDataType max , QString timeString , QString label2 , QString timeString2 ) {
return QString ( " %1:%2[%3-%4]%5 %6%7 " )
. arg ( label )
. arg ( mop , 3 , ' f ' , 1 )
. arg ( min , 3 , ' f ' , 1 )
. arg ( max , 3 , ' f ' , 1 )
. arg ( timeString )
. arg ( label2 )
. arg ( timeString2 )
;
}
void MapPainter : : drawMetaData ( QPoint & last_mouse , QString & topBarLabel , PressureInfo & ipap , PressureInfo & epap , QHash < ChannelID , bool > & enabled , EventDataType minpressure , EventDataType maxpressure )
{
int top = boundingRect . top ( ) ;
int bottom = boundingRect . bottom ( ) ;
////////////////////////////////////////////////////////////////////
// Draw mouse over events
////////////////////////////////////////////////////////////////////
mouseOverKey = - 1 ;
QPoint mouse = graph . graphView ( ) - > currentMousePos ( ) ;
bool toolTipOff = false ;
if ( mouse . x ( ) = = 0 & & mouse . y ( ) = = 0 ) {
toolTipOff = true ;
mouse = last_mouse ;
} else {
last_mouse = mouse ;
}
graphSelected = ( mouse . y ( ) < = boundingRect . bottom ( ) & & mouse . y ( ) > = boundingRect . top ( ) ) ;
bool eventOccured = false ;
if ( ( mouse . x ( ) < boundingRect . left ( ) | | mouse . x ( ) > boundingRect . right ( ) ) ) {
graphSelected = false ;
// note until Session start times are synced with waveforms start time. there will be a difference in the total time displayed.
// so don't display the total waveform time, because the user can see the difference between sessions times and the total duration
// calculated. both the first and last times can be different for resmed machines. This can be confusing so don't display questionable data.
topBarLabel = displayMetaData ( ipap . chan . label ( ) , minpressure , minpressure , maxpressure , timeString ( ipap . totalDuration ) , " " , " " ) ;
//topBarLabel = displayMetaData(ipap.chan.label(),minpressure, minpressure, maxpressure, "" ,"","");
//So just display original Label instead of total Duration.
// topBarLabel = QObject::tr("Peak %1").arg(msecToMinutes(qMax(ipap.peaktime, epap.peaktime)),1,'f',1);
Q_UNUSED ( maxpressure ) ;
} else {
// Mouse is in the horizantile ploting area of all graphs.
EventDataType pMousePressure = minpressure + ( ( mouse . x ( ) - drawingRect . left ( ) ) / pixelsPerPressure ) ;
mouseOverKey = floor ( ( pMousePressure + sampleIntervalStart ) * bucketsPerPressure ) ;
EventDataType mouseOverPressure = ( EventDataType ) mouseOverKey / bucketsPerPressure ;
int bucketX = ( ( mouseOverPressure - minpressure ) * pixelsPerPressure ) + drawingRect . left ( ) ;
// Draw veritical line for mouse cursor. jump to closest pressure bucket.
painter . setPen ( QPen ( QColor ( 128 , 128 , 128 , 30 ) , 1.5 * AppSetting - > lineThickness ( ) ) ) ;
painter . drawLine ( bucketX , top , bucketX , bottom ) ;
bool epapEnabled = enabled [ epap . code ] ;
topBarLabel = displayMetaData (
ipap . chan . label ( ) ,
mouseOverPressure ,
mouseOverPressure - sampleIntervalStart ,
mouseOverPressure + sampleIntervalEnd ,
timeString ( ipap . times [ mouseOverKey ] ) ,
epapEnabled ? epap . chan . label ( ) : " " ,
epapEnabled ? timeString ( epap . times [ mouseOverKey ] ) : " "
) ;
QString toolTipLabel = QString ( ) ;
int nc = ipap . chans . size ( ) ;
for ( int i = 0 ; i < nc ; + + i ) {
ChannelID ch = ipap . chans . at ( i ) ;
if ( ! enabled [ ch ] ) continue ;
schema : : Channel & chan = schema : : channel [ ch ] ;
if ( chan . type ( ) ! = schema : : FLAG ) continue ;
int numEvents = ipap . numEvents [ ch ] ;
if ( numEvents = = 0 ) continue ;
int cnt = ipap . events [ ch ] . at ( mouseOverKey ) ;
if ( cnt = = 0 ) continue ;
topBarLabel + = QString ( " %1(%2) " ) . arg ( chan . label ( ) ) . arg ( cnt ) ;
toolTipLabel + = QString ( " %1 %2 \n " ) . arg ( chan . fullname ( ) ) . arg ( cnt ) ;
eventOccured = true ;
}
toolTipLabel . remove ( QRegularExpression ( " \\ n+$ " ) ) ;
# if defined(ENABLE_MOUSE_DEBUG_INFO)
// Add debug for mouse position
QString debugInfo = QString ( " B(%1x%2) D(%3x%4) bucket(%5)@%6:%7:%8 " )
. arg ( boundingRect . left ( ) , 3 , ' f ' , 0 ) . arg ( boundingRect . top ( ) , 3 , ' f ' , 0 ) . arg ( drawingRect . left ( ) , 3 , ' f ' , 0 ) . arg ( drawingRect . top ( ) , 3 , ' f ' , 0 )
. arg ( mouseOverKey )
. arg ( pMousePressure , 3 , ' f ' , 2 )
. arg ( bucketX )
. arg ( pixelsPerPressure , 3 , ' f ' , 1 )
;
debugInfo + = QString ( " mouse(%1 %2)@%3 " ) . arg ( mouse . x ( ) ) . arg ( mouse . y ( ) ) . arg ( pMousePressure , 3 , ' f ' , 2 ) ;
topBarLabel . insert ( 0 , debugInfo ) ;
# endif
// DRAW tooltip information
if ( ! toolTipOff & & graphSelected & & eventOccured ) {
graph . ToolTip ( toolTipLabel , bucketX - 10 , drawingRect . top ( ) + 10 , TT_AlignRight ) ;
}
}
// DRAW top line status information
int topBarTop = boundingRect . top ( ) - 5 ;
graph . renderText ( topBarLabel , boundingRect . left ( ) , topBarTop ) ;
}
// set the opacity and default color
void MapPainter : : setPenColorAlpha ( ChannelID channelId , int opacity ) {
QColor color = schema : : channel [ channelId ] . defaultColor ( ) ;
if ( opacity > 0 & & opacity < = 255 ) {
color . setAlpha ( opacity ) ;
//DEBUG << FULLNAME(channelId) << O(color.name()) << O(opacity);
}
linePen = QPen ( color , AppSetting - > lineThickness ( ) ) ;
pointEnhancePen = QPen ( QColor ( Qt : : black ) , 2.5 * AppSetting - > lineThickness ( ) ) ;
pointSelectionPen = QPen ( color , 1.5 * AppSetting - > lineThickness ( ) ) ;
}
void MapPainter : : setChannelInfo ( ChannelID id , QVector < int > dataArray , EventDataType yPixelsPerUnit , int startBucket , int endBucket )
{
channel = & schema : : channel [ id ] ;
chanType = channel - > type ( ) ; //schema::channel[id].type() ;
this - > dataArray = dataArray ;
this - > yPixelsPerUnit = yPixelsPerUnit ;
this - > startBucket = startBucket ;
this - > endBucket = endBucket ;
setPenColorAlpha ( id , chanType ! = schema : : WAVEFORM ? 50 : 255 ) ;
}
// converts a y value to a graph point
// Adjusting values from the min and max graph ranges.
EventDataType MapPainter : : verifyYaxis ( EventDataType value ) {
EventDataType top = drawingRect . top ( ) ;
EventDataType bottom = drawingRect . bottom ( ) ;
if ( value < = top ) {
return top + 2 ;
} else if ( value > = bottom ) {
return bottom ;
}
return value ;
}
EventDataType MapPainter : : dataToYaxis ( int pp ) {
EventDataType val = verifyYaxis ( drawingRect . bottom ( ) - ( ( pp * yPixelsPerUnit ) ) ) ;
return val ;
}
// Initializes values used based
void MapPainter : : initCatmullRomSpline ( EventDataType pixelsPerBucket , int numberOfPoints )
{
catmullRomSplineNumberOfPoints = numberOfPoints ;
catmullRomSplineIncrement = 1.0 / catmullRomSplineNumberOfPoints ;
catmullRomSplineInterval = 0.0f ;
catmullRomSplineXstep = pixelsPerBucket / catmullRomSplineNumberOfPoints ;
}
// Draws a line between two points
// The line will be stright if anti-aliasing is turned off.
// otherwise the line will be be curved to fit the data.
EventDataType MapPainter : : drawSegment ( int bucket , EventDataType lastxp , EventDataType lastyp )
{
# if defined(ENABLE_SMOOTH_CURVES)
EventDataType xp = lastxp ;
EventDataType dM1 = dataToYaxis ( dataArray [ bucket - 1 ] ) ;
EventDataType yp = dataToYaxis ( dataArray [ bucket + 0 ] ) ;
EventDataType d1 = dataToYaxis ( dataArray [ bucket + 1 ] ) ;
EventDataType d2 = dataToYaxis ( dataArray [ bucket + 2 ] ) ;
catmullRomSplineInterval = catmullRomSplineIncrement ;
for ( int loop = 0 ; loop < catmullRomSplineNumberOfPoints ; loop + + , catmullRomSplineInterval + = catmullRomSplineIncrement ) {
// The catmull-Rom-Spline algorithm calculates N points to be displayed between points P1 and P2.
// The algorithm takes four values (Y-axis) (equival-distant) P0,P1,P2,P3, and an interval
// The interval range: 0<= interval <=1
// Interval = (distance from P1)/(distance from P1 to P2) on the X axis
// When the Interval is zero (0) the algorithm returns P1
// When the Interval is One (1) the algorithm retunrs P2
// P0 and P3 impact the values of the points between P1 and P2 to produce the smooth results.
// The number of points must be greater than zero
// when the numberOfPoints is 1 then a straight line is generated between points P1 and P2.
// Good smoothed graphs are available when the numberOfPoints is 5.
if ( catmullRomSplineNumberOfPoints > 1 ) {
yp = CatmullRomSpline ( dM1 , yp , d1 , d2 , catmullRomSplineInterval ) ;
}
yp = verifyYaxis ( yp ) ;
xp + = catmullRomSplineXstep ;
painter . drawLine ( lastxp , lastyp , xp , yp ) ;
lastxp = xp ;
lastyp = yp ;
}
# else
EventDataType yp = dataToYaxis ( dataArray [ bucket + 1 ] ) ;
yp = verifyYaxis ( yp ) ;
EventDataType xp = lastxp + pixelsPerBucket ;
painter . drawLine ( lastxp , lastyp , xp , yp ) ;
# endif
return yp ;
}
void MapPainter : : drawPoint ( bool accent , int xp , int yp ) {
if ( ! graphSelected ) return ;
if ( yp > = drawingRect . bottom ( ) & & chanType ! = schema : : WAVEFORM ) return ;
//DEBUG << FULLNAME(info.code) << OO(id,channel.id());
if ( accent ) {
painter . setPen ( pointEnhancePen ) ;
} else {
painter . setPen ( pointSelectionPen ) ;
}
int radius = 1 ;
painter . drawEllipse ( QPoint ( xp , yp ) , radius , radius ) ;
painter . setPen ( linePen ) ;
}
// Draw a plot of points on Graphs
// CPAP_Pressure or CPAP_EPAP or Events
void MapPainter : : drawPlot ( ) {
tickPen = QPen ( Qt : : black , 1 ) ;
bool started = false ;
EventDataType yp = drawingRect . bottom ( ) ;
EventDataType xp = drawingRect . left ( ) ;
painter . setPen ( linePen ) ;
for ( int i = startGraphBucket ; i < = endBucket ; + + i , xp + = pixelsPerBucket ) {
//DEBUG << O(i) << OO(data,dataArray[i]);
bool accent = ( i = = mouseOverKey ) ;
if ( ! started ) {
if ( dataArray [ i ] < = 0 ) continue ;
if ( i > = startBucket ) {
//draw vertical line to first point.
started = true ;
// following used to test Y axis labels position.
//int tmp=dataArray[i];
//if (tmp>61000 && tmp<63000) tmp=60000;
//yp = dataToYaxis(tmp);
yp = dataToYaxis ( dataArray [ i ] ) ;
//DEBUG << OO(bucket,i) <<O(xp) << O(yp) <<OO(msec,dataArray[i]);
painter . drawLine ( xp , drawingRect . bottom ( ) , xp , yp ) ;
drawPoint ( accent , xp , drawingRect . bottom ( ) ) ;
} else {
continue ;
}
}
if ( i > = endBucket ) {
// draw vertical line to last point.
EventDataType lastxp = xp ;
if ( yp > = drawingRect . bottom ( ) ) {
//last point was at bottom.
lastxp - = pixelsPerBucket ;
yp = dataToYaxis ( dataArray [ i ] ) ;
}
drawPoint ( accent , xp , yp ) ;
painter . drawLine ( lastxp , drawingRect . bottom ( ) , xp , yp ) ;
return ;
}
drawPoint ( accent , xp , yp ) ;
yp = drawSegment ( i , xp , yp ) ;
}
}
// Draw a an Event tick at the top of ther graph
# ifdef ENABLE_DISPLAY_SPAN_EVENTS_AS_BACKGROUND
void MapPainter : : drawSpanEvents ( ) {
if ( dataArray . isEmpty ( ) ) return ;
EventDataType xp = drawingRect . left ( ) ;
EventDataType pixelsPerBucket = this - > pixelsPerBucket ;
# ifdef ENABLE_BUCKET_PRESSURE_AVERAGE
EventDataType pixelsPerBucket2 = pixelsPerBucket / 2 ;
pixelsPerBucket = pixelsPerBucket2 ;
# endif
QRectF box = QRectF ( xp , boundingRect . top ( ) , pixelsPerBucket , boundingRect . height ( ) ) ;
int tickTop = boundingRect . top ( ) ;
int tickHeight = boundingRect . height ( ) ;
QColor color = schema : : channel [ channel - > id ( ) ] . defaultColor ( ) ;
color . setAlpha ( 128 ) ;
for ( int i = startGraphBucket ; i < dataArray . size ( ) ; + + i ) {
if ( i > = endGraphBucket ) {
# ifdef ENABLE_BUCKET_PRESSURE_AVERAGE
if ( i = = endGraphBucket ) {
pixelsPerBucket = pixelsPerBucket2 ;
} else {
return ;
}
# else
return ;
# endif
}
int data = dataArray [ i ] ;
if ( data > 0 ) {
box . setRect ( xp , tickTop , pixelsPerBucket , tickHeight ) ;
painter . fillRect ( box , color ) ;
}
xp + = pixelsPerBucket ;
# ifdef ENABLE_BUCKET_PRESSURE_AVERAGE
pixelsPerBucket = this - > pixelsPerBucket ;
# endif
}
}
# endif
# ifdef ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS
void MapPainter : : drawEventTick ( ) {
EventDataType xp = drawingRect . left ( ) ;
int tickLength = drawTickLength ;
int top = boundingRect . top ( ) ;
int bottom = boundingRect . bottom ( ) ;
painter . setPen ( tickPen ) ;
for ( int i = startGraphBucket ; i < dataArray . size ( ) ; + + i , xp + = pixelsPerBucket ) {
if ( dataArray [ i ] < = 0 ) continue ;
if ( ( i = = mouseOverKey ) & & ( graphSelected ) ) {
painter . setPen ( tickEnhancePen ) ;
painter . drawLine ( xp , top , xp , top + tickLength ) ;
# ifndef ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH
painter . drawLine ( xp , bottom , xp , bottom - tickLength ) ;
painter . setPen ( tickEnhanceTransparentPen ) ;
painter . drawLine ( xp , top + tickLength , xp , bottom - tickLength ) ;
# else
Q_UNUSED ( bottom ) ;
# endif
painter . setPen ( tickPen ) ;
} else {
# ifndef ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH
painter . setPen ( QColor ( 128 , 128 , 128 , 30 ) ) ;
painter . drawLine ( xp , top + tickLength , xp , bottom ) ;
painter . setPen ( tickPen ) ;
# endif
painter . drawLine ( xp , top , xp , top + tickLength ) ;
}
}
}
# endif
# if defined(ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH) || defined(ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS) || defined(ENABLE_DISPLAY_SPAN_EVENTS_AS_BACKGROUND)
void MapPainter : : drawEvent ( ) {
if ( chanType = = schema : : FLAG ) {
# ifdef ENABLE_DISPLAY_FLAG_EVENTS_AS_GRAPH
//setPenColorAlpha(channel->id(),70);
setPenColorAlpha ( ChannelID ( NoChannel ) , 70 ) ;
drawPlot ( ) ;
# endif
# ifdef ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS
tickPen = QPen ( Qt : : black , 1 ) ;
tickEnhancePen = QPen ( QColor ( 0 , 0 , 255 ) , 3.5 * AppSetting - > lineThickness ( ) ) ;
tickEnhanceTransparentPen = QPen ( QColor ( 0 , 0 , 255 , 60 ) , 3.5 * AppSetting - > lineThickness ( ) ) ;
drawEventTick ( ) ;
# endif
return ;
}
if ( chanType = = schema : : SPAN ) {
# ifdef ENABLE_DISPLAY_SPAN_EVENTS_AS_BACKGROUND
drawSpanEvents ( ) ;
# endif
return ;
}
}
# endif
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST DATA <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# if defined(ENABLE_TEST_CPAP) || defined(ENABLE_TEST_SAWTOOTH) || defined(ENABLE_TEST_SINGLE) || defined(ENABLE_TEST_NODATA)
EventDataType test_inc = 0.0 ;
EventDataType test_start ;
EventDataType test_mid ;
EventDataType test_end ;
EventDataType test_value ;
qint64 test_time ;
qint64 test_time_inc ;
int test_count ;
qint64 test_ELFirst ;
bool testdata ( int e , int ELsize , EventStoreType & raw , qint64 & time , qint64 minTime , qint64 maxTime , EventDataType gain , EventList * EL ) {
if ( e = = 0 ) {
test_ELFirst = EL - > first ( ) ;
test_start = ( 4.0f / gain ) ;
test_mid = ( 7.07f / gain ) ;
test_end = ( 15.0f / gain ) ;
test_value = test_start ;
test_inc = ( test_end - test_start ) / EventDataType ( ELsize ) ;
test_time = time ;
//test_time_inc=(maxTime-minTime)/ELsize;
test_time_inc = ( EL - > last ( ) - EL - > first ( ) ) / ELsize ;
test_count = 0 ;
}
if ( test_ELFirst ! = EL - > first ( ) ) {
test_ELFirst = EL - > first ( ) ;
}
if ( e > = ELsize ) return false ;
# if defined(ENABLE_TEST_CPAP)
raw = EventStoreType ( test_mid ) ;
return true ;
# endif
# if defined(ENABLE_TEST_SINGLE)
int zz = ELsize - 1 ;
if ( e = = zz ) {
raw = ( EventStoreType ) test_mid ;
time = ( minTime + maxTime ) / 2 ;
return true ;
}
return false ;
# endif
# if defined(ENABLE_TEST_NODATA)
return false ;
# endif
// ENABLE_TEST_SAWTOOTH
if ( test_value > = test_end ) {
test_value = test_start ;
} ;
raw = ( EventStoreType ) test_value ; ;
time = ( qint64 ) test_time ;
test_time + = test_time_inc ;
test_value + = test_inc ;
return true ;
Q_UNUSED ( test_count ) ;
Q_UNUSED ( e ) ;
Q_UNUSED ( ELsize ) ;
Q_UNUSED ( EL ) ;
Q_UNUSED ( raw ) ;
Q_UNUSED ( time ) ;
Q_UNUSED ( minTime ) ;
Q_UNUSED ( maxTime ) ;
}
# endif