diff --git a/backend/assets/mock_response.json b/backend/assets/mock_response.json index a5b89cd7..12677da9 100644 --- a/backend/assets/mock_response.json +++ b/backend/assets/mock_response.json @@ -33,1034 +33,948 @@ }, "epochs": { "timestamps": [ - 1602210380, - 1602210410, - 1602210440, - 1602210470, - 1602210500, - 1602210530, - 1602210560, - 1602210590, - 1602210620, - 1602210650, - 1602210680, - 1602210710, - 1602210740, - 1602210770, - 1602210800, - 1602210830, - 1602210860, - 1602210890, - 1602210920, - 1602210950, - 1602210980, - 1602211010, - 1602211040, - 1602211070, - 1602211100, - 1602211130, - 1602211160, - 1602211190, - 1602211220, - 1602211250, - 1602211280, - 1602211310, - 1602211340, - 1602211370, - 1602211400, - 1602211430, - 1602211460, - 1602211490, - 1602211520, - 1602211550, - 1602211580, - 1602211610, - 1602211640, - 1602211670, - 1602211700, - 1602211730, - 1602211760, - 1602211790, - 1602211820, - 1602211850, - 1602211880, - 1602211910, - 1602211940, - 1602211970, - 1602212000, - 1602212030, - 1602212060, - 1602212090, - 1602212120, - 1602212150, - 1602212180, - 1602212210, - 1602212240, - 1602212270, - 1602212300, - 1602212330, - 1602212360, - 1602212390, - 1602212420, - 1602212450, - 1602212480, - 1602212510, - 1602212540, - 1602212570, - 1602212600, - 1602212630, - 1602212660, - 1602212690, - 1602212720, - 1602212750, - 1602212780, - 1602212810, - 1602212840, - 1602212870, - 1602212900, - 1602212930, - 1602212960, - 1602212990, - 1602213020, - 1602213050, - 1602213080, - 1602213110, - 1602213140, - 1602213170, - 1602213200, - 1602213230, - 1602213260, - 1602213290, - 1602213320, - 1602213350, - 1602213380, - 1602213410, - 1602213440, - 1602213470, - 1602213500, - 1602213530, - 1602213560, - 1602213590, - 1602213620, - 1602213650, - 1602213680, - 1602213710, - 1602213740, - 1602213770, - 1602213800, - 1602213830, - 1602213860, - 1602213890, - 1602213920, - 1602213950, - 1602213980, - 1602214010, - 1602214040, - 1602214070, - 1602214100, - 1602214130, - 1602214160, - 1602214190, - 1602214220, - 1602214250, - 1602214280, - 1602214310, - 1602214340, - 1602214370, - 1602214400, - 1602214430, - 1602214460, - 1602214490, - 1602214520, - 1602214550, - 1602214580, - 1602214610, - 1602214640, - 1602214670, - 1602214700, - 1602214730, - 1602214760, - 1602214790, - 1602214820, - 1602214850, - 1602214880, - 1602214910, - 1602214940, - 1602214970, - 1602215000, - 1602215030, - 1602215060, - 1602215090, - 1602215120, - 1602215150, - 1602215180, - 1602215210, - 1602215240, - 1602215270, - 1602215300, - 1602215330, - 1602215360, - 1602215390, - 1602215420, - 1602215450, - 1602215480, - 1602215510, - 1602215540, - 1602215570, - 1602215600, - 1602215630, - 1602215660, - 1602215690, - 1602215720, - 1602215750, - 1602215780, - 1602215810, - 1602215840, - 1602215870, - 1602215900, - 1602215930, - 1602215960, - 1602215990, - 1602216020, - 1602216050, - 1602216080, - 1602216110, - 1602216140, - 1602216170, - 1602216200, - 1602216230, - 1602216260, - 1602216290, - 1602216320, - 1602216350, - 1602216380, - 1602216410, - 1602216440, - 1602216470, - 1602216500, - 1602216530, - 1602216560, - 1602216590, - 1602216620, - 1602216650, - 1602216680, - 1602216710, - 1602216740, - 1602216770, - 1602216800, - 1602216830, - 1602216860, - 1602216890, - 1602216920, - 1602216950, - 1602216980, - 1602217010, - 1602217040, - 1602217070, - 1602217100, - 1602217130, - 1602217160, - 1602217190, - 1602217220, - 1602217250, - 1602217280, - 1602217310, - 1602217340, - 1602217370, - 1602217400, - 1602217430, - 1602217460, - 1602217490, - 1602217520, - 1602217550, - 1602217580, - 1602217610, - 1602217640, - 1602217670, - 1602217700, - 1602217730, - 1602217760, - 1602217790, - 1602217820, - 1602217850, - 1602217880, - 1602217910, - 1602217940, - 1602217970, - 1602218000, - 1602218030, - 1602218060, - 1602218090, - 1602218120, - 1602218150, - 1602218180, - 1602218210, - 1602218240, - 1602218270, - 1602218300, - 1602218330, - 1602218360, - 1602218390, - 1602218420, - 1602218450, - 1602218480, - 1602218510, - 1602218540, - 1602218570, - 1602218600, - 1602218630, - 1602218660, - 1602218690, - 1602218720, - 1602218750, - 1602218780, - 1602218810, - 1602218840, - 1602218870, - 1602218900, - 1602218930, - 1602218960, - 1602218990, - 1602219020, - 1602219050, - 1602219080, - 1602219110, - 1602219140, - 1602219170, - 1602219200, - 1602219230, - 1602219260, - 1602219290, - 1602219320, - 1602219350, - 1602219380, - 1602219410, - 1602219440, - 1602219470, - 1602219500, - 1602219530, - 1602219560, - 1602219590, - 1602219620, - 1602219650, - 1602219680, - 1602219710, - 1602219740, - 1602219770, - 1602219800, - 1602219830, - 1602219860, - 1602219890, - 1602219920, - 1602219950, - 1602219980, - 1602220010, - 1602220040, - 1602220070, - 1602220100, - 1602220130, - 1602220160, - 1602220190, - 1602220220, - 1602220250, - 1602220280, - 1602220310, - 1602220340, - 1602220370, - 1602220400, - 1602220430, - 1602220460, - 1602220490, - 1602220520, - 1602220550, - 1602220580, - 1602220610, - 1602220640, - 1602220670, - 1602220700, - 1602220730, - 1602220760, - 1602220790, - 1602220820, - 1602220850, - 1602220880, - 1602220910, - 1602220940, - 1602220970, - 1602221000, - 1602221030, - 1602221060, - 1602221090, - 1602221120, - 1602221150, - 1602221180, - 1602221210, - 1602221240, - 1602221270, - 1602221300, - 1602221330, - 1602221360, - 1602221390, - 1602221420, - 1602221450, - 1602221480, - 1602221510, - 1602221540, - 1602221570, - 1602221600, - 1602221630, - 1602221660, - 1602221690, - 1602221720, - 1602221750, - 1602221780, - 1602221810, - 1602221840, - 1602221870, - 1602221900, - 1602221930, - 1602221960, - 1602221990, - 1602222020, - 1602222050, - 1602222080, - 1602222110, - 1602222140, - 1602222170, - 1602222200, - 1602222230, - 1602222260, - 1602222290, - 1602222320, - 1602222350, - 1602222380, - 1602222410, - 1602222440, - 1602222470, - 1602222500, - 1602222530, - 1602222560, - 1602222590, - 1602222620, - 1602222650, - 1602222680, - 1602222710, - 1602222740, - 1602222770, - 1602222800, - 1602222830, - 1602222860, - 1602222890, - 1602222920, - 1602222950, - 1602222980, - 1602223010, - 1602223040, - 1602223070, - 1602223100, - 1602223130, - 1602223160, - 1602223190, - 1602223220, - 1602223250, - 1602223280, - 1602223310, - 1602223340, - 1602223370, - 1602223400, - 1602223430, - 1602223460, - 1602223490, - 1602223520, - 1602223550, - 1602223580, - 1602223610, - 1602223640, - 1602223670, - 1602223700, - 1602223730, - 1602223760, - 1602223790, - 1602223820, - 1602223850, - 1602223880, - 1602223910, - 1602223940, - 1602223970, - 1602224000, - 1602224030, - 1602224060, - 1602224090, - 1602224120, - 1602224150, - 1602224180, - 1602224210, - 1602224240, - 1602224270, - 1602224300, - 1602224330, - 1602224360, - 1602224390, - 1602224420, - 1602224450, - 1602224480, - 1602224510, - 1602224540, - 1602224570, - 1602224600, - 1602224630, - 1602224660, - 1602224690, - 1602224720, - 1602224750, - 1602224780, - 1602224810, - 1602224840, - 1602224870, - 1602224900, - 1602224930, - 1602224960, - 1602224990, - 1602225020, - 1602225050, - 1602225080, - 1602225110, - 1602225140, - 1602225170, - 1602225200, - 1602225230, - 1602225260, - 1602225290, - 1602225320, - 1602225350, - 1602225380, - 1602225410, - 1602225440, - 1602225470, - 1602225500, - 1602225530, - 1602225560, - 1602225590, - 1602225620, - 1602225650, - 1602225680, - 1602225710, - 1602225740, - 1602225770, - 1602225800, - 1602225830, - 1602225860, - 1602225890, - 1602225920, - 1602225950, - 1602225980, - 1602226010, - 1602226040, - 1602226070, - 1602226100, - 1602226130, - 1602226160, - 1602226190, - 1602226220, - 1602226250, - 1602226280, - 1602226310, - 1602226340, - 1602226370, - 1602226400, - 1602226430, - 1602226460, - 1602226490, - 1602226520, - 1602226550, - 1602226580, - 1602226610, - 1602226640, - 1602226670, - 1602226700, - 1602226730, - 1602226760, - 1602226790, - 1602226820, - 1602226850, - 1602226880, - 1602226910, - 1602226940, - 1602226970, - 1602227000, - 1602227030, - 1602227060, - 1602227090, - 1602227120, - 1602227150, - 1602227180, - 1602227210, - 1602227240, - 1602227270, - 1602227300, - 1602227330, - 1602227360, - 1602227390, - 1602227420, - 1602227450, - 1602227480, - 1602227510, - 1602227540, - 1602227570, - 1602227600, - 1602227630, - 1602227660, - 1602227690, - 1602227720, - 1602227750, - 1602227780, - 1602227810, - 1602227840, - 1602227870, - 1602227900, - 1602227930, - 1602227960, - 1602227990, - 1602228020, - 1602228050, - 1602228080, - 1602228110, - 1602228140, - 1602228170, - 1602228200, - 1602228230, - 1602228260, - 1602228290, - 1602228320, - 1602228350, - 1602228380, - 1602228410, - 1602228440, - 1602228470, - 1602228500, - 1602228530, - 1602228560, - 1602228590, - 1602228620, - 1602228650, - 1602228680, - 1602228710, - 1602228740, - 1602228770, - 1602228800, - 1602228830, - 1602228860, - 1602228890, - 1602228920, - 1602228950, - 1602228980, - 1602229010, - 1602229040, - 1602229070, - 1602229100, - 1602229130, - 1602229160, - 1602229190, - 1602229220, - 1602229250, - 1602229280, - 1602229310, - 1602229340, - 1602229370, - 1602229400, - 1602229430, - 1602229460, - 1602229490, - 1602229520, - 1602229550, - 1602229580, - 1602229610, - 1602229640, - 1602229670, - 1602229700, - 1602229730, - 1602229760, - 1602229790, - 1602229820, - 1602229850, - 1602229880, - 1602229910, - 1602229940, - 1602229970, - 1602230000, - 1602230030, - 1602230060, - 1602230090, - 1602230120, - 1602230150, - 1602230180, - 1602230210, - 1602230240, - 1602230270, - 1602230300, - 1602230330, - 1602230360, - 1602230390, - 1602230420, - 1602230450, - 1602230480, - 1602230510, - 1602230540, - 1602230570, - 1602230600, - 1602230630, - 1602230660, - 1602230690, - 1602230720, - 1602230750, - 1602230780, - 1602230810, - 1602230840, - 1602230870, - 1602230900, - 1602230930, - 1602230960, - 1602230990, - 1602231020, - 1602231050, - 1602231080, - 1602231110, - 1602231140, - 1602231170, - 1602231200, - 1602231230, - 1602231260, - 1602231290, - 1602231320, - 1602231350, - 1602231380, - 1602231410, - 1602231440, - 1602231470, - 1602231500, - 1602231530, - 1602231560, - 1602231590, - 1602231620, - 1602231650, - 1602231680, - 1602231710, - 1602231740, - 1602231770, - 1602231800, - 1602231830, - 1602231860, - 1602231890, - 1602231920, - 1602231950, - 1602231980, - 1602232010, - 1602232040, - 1602232070, - 1602232100, - 1602232130, - 1602232160, - 1602232190, - 1602232220, - 1602232250, - 1602232280, - 1602232310, - 1602232340, - 1602232370, - 1602232400, - 1602232430, - 1602232460, - 1602232490, - 1602232520, - 1602232550, - 1602232580, - 1602232610, - 1602232640, - 1602232670, - 1602232700, - 1602232730, - 1602232760, - 1602232790, - 1602232820, - 1602232850, - 1602232880, - 1602232910, - 1602232940, - 1602232970, - 1602233000, - 1602233030, - 1602233060, - 1602233090, - 1602233120, - 1602233150, - 1602233180, - 1602233210, - 1602233240, - 1602233270, - 1602233300, - 1602233330, - 1602233360, - 1602233390, - 1602233420, - 1602233450, - 1602233480, - 1602233510, - 1602233540, - 1602233570, - 1602233600, - 1602233630, - 1602233660, - 1602233690, - 1602233720, - 1602233750, - 1602233780, - 1602233810, - 1602233840, - 1602233870, - 1602233900, - 1602233930, - 1602233960, - 1602233990, - 1602234020, - 1602234050, - 1602234080, - 1602234110, - 1602234140, - 1602234170, - 1602234200, - 1602234230, - 1602234260, - 1602234290, - 1602234320, - 1602234350, - 1602234380, - 1602234410, - 1602234440, - 1602234470, - 1602234500, - 1602234530, - 1602234560, - 1602234590, - 1602234620, - 1602234650, - 1602234680, - 1602234710, - 1602234740, - 1602234770, - 1602234800, - 1602234830, - 1602234860, - 1602234890, - 1602234920, - 1602234950, - 1602234980, - 1602235010, - 1602235040, - 1602235070, - 1602235100, - 1602235130, - 1602235160, - 1602235190, - 1602235220, - 1602235250, - 1602235280, - 1602235310, - 1602235340, - 1602235370, - 1602235400, - 1602235430, - 1602235460, - 1602235490, - 1602235520, - 1602235550, - 1602235580, - 1602235610, - 1602235640, - 1602235670, - 1602235700, - 1602235730, - 1602235760, - 1602235790, - 1602235820, - 1602235850, - 1602235880, - 1602235910, - 1602235940, - 1602235970, - 1602236000, - 1602236030, - 1602236060, - 1602236090, - 1602236120, - 1602236150, - 1602236180, - 1602236210, - 1602236240, - 1602236270, - 1602236300, - 1602236330, - 1602236360, - 1602236390, - 1602236420, - 1602236450, - 1602236480, - 1602236510, - 1602236540, - 1602236570, - 1602236600, - 1602236630, - 1602236660, - 1602236690, - 1602236720, - 1602236750, - 1602236780, - 1602236810, - 1602236840, - 1602236870, - 1602236900, - 1602236930, - 1602236960, - 1602236990, - 1602237020, - 1602237050, - 1602237080, - 1602237110, - 1602237140, - 1602237170, - 1602237200, - 1602237230, - 1602237260, - 1602237290, - 1602237320, - 1602237350, - 1602237380, - 1602237410, - 1602237440, - 1602237470, - 1602237500, - 1602237530, - 1602237560, - 1602237590, - 1602237620, - 1602237650, - 1602237680, - 1602237710, - 1602237740, - 1602237770, - 1602237800, - 1602237830, - 1602237860, - 1602237890, - 1602237920, - 1602237950, - 1602237980, - 1602238010, - 1602238040, - 1602238070, - 1602238100, - 1602238130, - 1602238160, - 1602238190, - 1602238220, - 1602238250, - 1602238280, - 1602238310, - 1602238340, - 1602238370, - 1602238400, - 1602238430, - 1602238460, - 1602238490, - 1602238520, - 1602238550, - 1602238580, - 1602238610, - 1602238640, - 1602238670, - 1602238700, - 1602238730, - 1602238760, - 1602238790, - 1602238820, - 1602238850, - 1602238880, - 1602238910, - 1602238940, - 1602238970, - 1602239000, - 1602239030, - 1602239060, - 1602239090, - 1602239120, - 1602239150, - 1602239180, - 1602239210, - 1602239240, - 1602239270, - 1602239300, - 1602239330, - 1602239360, - 1602239390, - 1602239420, - 1602239450, - 1602239480, - 1602239510, - 1602239540, - 1602239570, - 1602239600, - 1602239630, - 1602239660, - 1602239690, - 1602239720, - 1602239750, - 1602239780, - 1602239810, - 1602239840, - 1602239870, - 1602239900, - 1602239930, - 1602239960, - 1602239990, - 1602240020, - 1602240050, - 1602240080, - 1602240110, - 1602240140, - 1602240170, - 1602240200, - 1602240230, - 1602240260, - 1602240290, - 1602240320, - 1602240350, - 1602240380, - 1602240410, - 1602240440, - 1602240470, - 1602240500, - 1602240530, - 1602240560, - 1602240590, - 1602240620, - 1602240650, - 1602240680, - 1602240710, - 1602240740, - 1602240770, - 1602240800, - 1602240830, - 1602240860, - 1602240890, - 1602240920, - 1602240950, - 1602240980, - 1602241010, - 1602241040, - 1602241070, - 1602241100, - 1602241130, - 1602241160, - 1602241190 + 1582423980, + 1582424010, + 1582424040, + 1582424070, + 1582424100, + 1582424130, + 1582424160, + 1582424190, + 1582424220, + 1582424250, + 1582424280, + 1582424310, + 1582424340, + 1582424370, + 1582424400, + 1582424430, + 1582424460, + 1582424490, + 1582424520, + 1582424550, + 1582424580, + 1582424610, + 1582424640, + 1582424670, + 1582424700, + 1582424730, + 1582424760, + 1582424790, + 1582424820, + 1582424850, + 1582424880, + 1582424910, + 1582424940, + 1582424970, + 1582425000, + 1582425030, + 1582425060, + 1582425090, + 1582425120, + 1582425150, + 1582425180, + 1582425210, + 1582425240, + 1582425270, + 1582425300, + 1582425330, + 1582425360, + 1582425390, + 1582425420, + 1582425450, + 1582425480, + 1582425510, + 1582425540, + 1582425570, + 1582425600, + 1582425630, + 1582425660, + 1582425690, + 1582425720, + 1582425750, + 1582425780, + 1582425810, + 1582425840, + 1582425870, + 1582425900, + 1582425930, + 1582425960, + 1582425990, + 1582426020, + 1582426050, + 1582426080, + 1582426110, + 1582426140, + 1582426170, + 1582426200, + 1582426230, + 1582426260, + 1582426290, + 1582426320, + 1582426350, + 1582426380, + 1582426410, + 1582426440, + 1582426470, + 1582426500, + 1582426530, + 1582426560, + 1582426590, + 1582426620, + 1582426650, + 1582426680, + 1582426710, + 1582426740, + 1582426770, + 1582426800, + 1582426830, + 1582426860, + 1582426890, + 1582426920, + 1582426950, + 1582426980, + 1582427010, + 1582427040, + 1582427070, + 1582427100, + 1582427130, + 1582427160, + 1582427190, + 1582427220, + 1582427250, + 1582427280, + 1582427310, + 1582427340, + 1582427370, + 1582427400, + 1582427430, + 1582427460, + 1582427490, + 1582427520, + 1582427550, + 1582427580, + 1582427610, + 1582427640, + 1582427670, + 1582427700, + 1582427730, + 1582427760, + 1582427790, + 1582427820, + 1582427850, + 1582427880, + 1582427910, + 1582427940, + 1582427970, + 1582428000, + 1582428030, + 1582428060, + 1582428090, + 1582428120, + 1582428150, + 1582428180, + 1582428210, + 1582428240, + 1582428270, + 1582428300, + 1582428330, + 1582428360, + 1582428390, + 1582428420, + 1582428450, + 1582428480, + 1582428510, + 1582428540, + 1582428570, + 1582428600, + 1582428630, + 1582428660, + 1582428690, + 1582428720, + 1582428750, + 1582428780, + 1582428810, + 1582428840, + 1582428870, + 1582428900, + 1582428930, + 1582428960, + 1582428990, + 1582429020, + 1582429050, + 1582429080, + 1582429110, + 1582429140, + 1582429170, + 1582429200, + 1582429230, + 1582429260, + 1582429290, + 1582429320, + 1582429350, + 1582429380, + 1582429410, + 1582429440, + 1582429470, + 1582429500, + 1582429530, + 1582429560, + 1582429590, + 1582429620, + 1582429650, + 1582429680, + 1582429710, + 1582429740, + 1582429770, + 1582429800, + 1582429830, + 1582429860, + 1582429890, + 1582429920, + 1582429950, + 1582429980, + 1582430010, + 1582430040, + 1582430070, + 1582430100, + 1582430130, + 1582430160, + 1582430190, + 1582430220, + 1582430250, + 1582430280, + 1582430310, + 1582430340, + 1582430370, + 1582430400, + 1582430430, + 1582430460, + 1582430490, + 1582430520, + 1582430550, + 1582430580, + 1582430610, + 1582430640, + 1582430670, + 1582430700, + 1582430730, + 1582430760, + 1582430790, + 1582430820, + 1582430850, + 1582430880, + 1582430910, + 1582430940, + 1582430970, + 1582431000, + 1582431030, + 1582431060, + 1582431090, + 1582431120, + 1582431150, + 1582431180, + 1582431210, + 1582431240, + 1582431270, + 1582431300, + 1582431330, + 1582431360, + 1582431390, + 1582431420, + 1582431450, + 1582431480, + 1582431510, + 1582431540, + 1582431570, + 1582431600, + 1582431630, + 1582431660, + 1582431690, + 1582431720, + 1582431750, + 1582431780, + 1582431810, + 1582431840, + 1582431870, + 1582431900, + 1582431930, + 1582431960, + 1582431990, + 1582432020, + 1582432050, + 1582432080, + 1582432110, + 1582432140, + 1582432170, + 1582432200, + 1582432230, + 1582432260, + 1582432290, + 1582432320, + 1582432350, + 1582432380, + 1582432410, + 1582432440, + 1582432470, + 1582432500, + 1582432530, + 1582432560, + 1582432590, + 1582432620, + 1582432650, + 1582432680, + 1582432710, + 1582432740, + 1582432770, + 1582432800, + 1582432830, + 1582432860, + 1582432890, + 1582432920, + 1582432950, + 1582432980, + 1582433010, + 1582433040, + 1582433070, + 1582433100, + 1582433130, + 1582433160, + 1582433190, + 1582433220, + 1582433250, + 1582433280, + 1582433310, + 1582433340, + 1582433370, + 1582433400, + 1582433430, + 1582433460, + 1582433490, + 1582433520, + 1582433550, + 1582433580, + 1582433610, + 1582433640, + 1582433670, + 1582433700, + 1582433730, + 1582433760, + 1582433790, + 1582433820, + 1582433850, + 1582433880, + 1582433910, + 1582433940, + 1582433970, + 1582434000, + 1582434030, + 1582434060, + 1582434090, + 1582434120, + 1582434150, + 1582434180, + 1582434210, + 1582434240, + 1582434270, + 1582434300, + 1582434330, + 1582434360, + 1582434390, + 1582434420, + 1582434450, + 1582434480, + 1582434510, + 1582434540, + 1582434570, + 1582434600, + 1582434630, + 1582434660, + 1582434690, + 1582434720, + 1582434750, + 1582434780, + 1582434810, + 1582434840, + 1582434870, + 1582434900, + 1582434930, + 1582434960, + 1582434990, + 1582435020, + 1582435050, + 1582435080, + 1582435110, + 1582435140, + 1582435170, + 1582435200, + 1582435230, + 1582435260, + 1582435290, + 1582435320, + 1582435350, + 1582435380, + 1582435410, + 1582435440, + 1582435470, + 1582435500, + 1582435530, + 1582435560, + 1582435590, + 1582435620, + 1582435650, + 1582435680, + 1582435710, + 1582435740, + 1582435770, + 1582435800, + 1582435830, + 1582435860, + 1582435890, + 1582435920, + 1582435950, + 1582435980, + 1582436010, + 1582436040, + 1582436070, + 1582436100, + 1582436130, + 1582436160, + 1582436190, + 1582436220, + 1582436250, + 1582436280, + 1582436310, + 1582436340, + 1582436370, + 1582436400, + 1582436430, + 1582436460, + 1582436490, + 1582436520, + 1582436550, + 1582436580, + 1582436610, + 1582436640, + 1582436670, + 1582436700, + 1582436730, + 1582436760, + 1582436790, + 1582436820, + 1582436850, + 1582436880, + 1582436910, + 1582436940, + 1582436970, + 1582437000, + 1582437030, + 1582437060, + 1582437090, + 1582437120, + 1582437150, + 1582437180, + 1582437210, + 1582437240, + 1582437270, + 1582437300, + 1582437330, + 1582437360, + 1582437390, + 1582437420, + 1582437450, + 1582437480, + 1582437510, + 1582437540, + 1582437570, + 1582437600, + 1582437630, + 1582437660, + 1582437690, + 1582437720, + 1582437750, + 1582437780, + 1582437810, + 1582437840, + 1582437870, + 1582437900, + 1582437930, + 1582437960, + 1582437990, + 1582438020, + 1582438050, + 1582438080, + 1582438110, + 1582438140, + 1582438170, + 1582438200, + 1582438230, + 1582438260, + 1582438290, + 1582438320, + 1582438350, + 1582438380, + 1582438410, + 1582438440, + 1582438470, + 1582438500, + 1582438530, + 1582438560, + 1582438590, + 1582438620, + 1582438650, + 1582438680, + 1582438710, + 1582438740, + 1582438770, + 1582438800, + 1582438830, + 1582438860, + 1582438890, + 1582438920, + 1582438950, + 1582438980, + 1582439010, + 1582439040, + 1582439070, + 1582439100, + 1582439130, + 1582439160, + 1582439190, + 1582439220, + 1582439250, + 1582439280, + 1582439310, + 1582439340, + 1582439370, + 1582439400, + 1582439430, + 1582439460, + 1582439490, + 1582439520, + 1582439550, + 1582439580, + 1582439610, + 1582439640, + 1582439670, + 1582439700, + 1582439730, + 1582439760, + 1582439790, + 1582439820, + 1582439850, + 1582439880, + 1582439910, + 1582439940, + 1582439970, + 1582440000, + 1582440030, + 1582440060, + 1582440090, + 1582440120, + 1582440150, + 1582440180, + 1582440210, + 1582440240, + 1582440270, + 1582440300, + 1582440330, + 1582440360, + 1582440390, + 1582440420, + 1582440450, + 1582440480, + 1582440510, + 1582440540, + 1582440570, + 1582440600, + 1582440630, + 1582440660, + 1582440690, + 1582440720, + 1582440750, + 1582440780, + 1582440810, + 1582440840, + 1582440870, + 1582440900, + 1582440930, + 1582440960, + 1582440990, + 1582441020, + 1582441050, + 1582441080, + 1582441110, + 1582441140, + 1582441170, + 1582441200, + 1582441230, + 1582441260, + 1582441290, + 1582441320, + 1582441350, + 1582441380, + 1582441410, + 1582441440, + 1582441470, + 1582441500, + 1582441530, + 1582441560, + 1582441590, + 1582441620, + 1582441650, + 1582441680, + 1582441710, + 1582441740, + 1582441770, + 1582441800, + 1582441830, + 1582441860, + 1582441890, + 1582441920, + 1582441950, + 1582441980, + 1582442010, + 1582442040, + 1582442070, + 1582442100, + 1582442130, + 1582442160, + 1582442190, + 1582442220, + 1582442250, + 1582442280, + 1582442310, + 1582442340, + 1582442370, + 1582442400, + 1582442430, + 1582442460, + 1582442490, + 1582442520, + 1582442550, + 1582442580, + 1582442610, + 1582442640, + 1582442670, + 1582442700, + 1582442730, + 1582442760, + 1582442790, + 1582442820, + 1582442850, + 1582442880, + 1582442910, + 1582442940, + 1582442970, + 1582443000, + 1582443030, + 1582443060, + 1582443090, + 1582443120, + 1582443150, + 1582443180, + 1582443210, + 1582443240, + 1582443270, + 1582443300, + 1582443330, + 1582443360, + 1582443390, + 1582443420, + 1582443450, + 1582443480, + 1582443510, + 1582443540, + 1582443570, + 1582443600, + 1582443630, + 1582443660, + 1582443690, + 1582443720, + 1582443750, + 1582443780, + 1582443810, + 1582443840, + 1582443870, + 1582443900, + 1582443930, + 1582443960, + 1582443990, + 1582444020, + 1582444050, + 1582444080, + 1582444110, + 1582444140, + 1582444170, + 1582444200, + 1582444230, + 1582444260, + 1582444290, + 1582444320, + 1582444350, + 1582444380, + 1582444410, + 1582444440, + 1582444470, + 1582444500, + 1582444530, + 1582444560, + 1582444590, + 1582444620, + 1582444650, + 1582444680, + 1582444710, + 1582444740, + 1582444770, + 1582444800, + 1582444830, + 1582444860, + 1582444890, + 1582444920, + 1582444950, + 1582444980, + 1582445010, + 1582445040, + 1582445070, + 1582445100, + 1582445130, + 1582445160, + 1582445190, + 1582445220, + 1582445250, + 1582445280, + 1582445310, + 1582445340, + 1582445370, + 1582445400, + 1582445430, + 1582445460, + 1582445490, + 1582445520, + 1582445550, + 1582445580, + 1582445610, + 1582445640, + 1582445670, + 1582445700, + 1582445730, + 1582445760, + 1582445790, + 1582445820, + 1582445850, + 1582445880, + 1582445910, + 1582445940, + 1582445970, + 1582446000, + 1582446030, + 1582446060, + 1582446090, + 1582446120, + 1582446150, + 1582446180, + 1582446210, + 1582446240, + 1582446270, + 1582446300, + 1582446330, + 1582446360, + 1582446390, + 1582446420, + 1582446450, + 1582446480, + 1582446510, + 1582446540, + 1582446570, + 1582446600, + 1582446630, + 1582446660, + 1582446690, + 1582446720, + 1582446750, + 1582446780, + 1582446810, + 1582446840, + 1582446870, + 1582446900, + 1582446930, + 1582446960, + 1582446990, + 1582447020, + 1582447050, + 1582447080, + 1582447110, + 1582447140, + 1582447170, + 1582447200, + 1582447230, + 1582447260, + 1582447290, + 1582447320, + 1582447350, + 1582447380, + 1582447410, + 1582447440, + 1582447470, + 1582447500, + 1582447530, + 1582447560, + 1582447590, + 1582447620, + 1582447650, + 1582447680, + 1582447710, + 1582447740, + 1582447770, + 1582447800, + 1582447830, + 1582447860, + 1582447890, + 1582447920, + 1582447950, + 1582447980, + 1582448010, + 1582448040, + 1582448070, + 1582448100, + 1582448130, + 1582448160, + 1582448190, + 1582448220, + 1582448250, + 1582448280, + 1582448310, + 1582448340, + 1582448370, + 1582448400, + 1582448430, + 1582448460, + 1582448490, + 1582448520, + 1582448550, + 1582448580, + 1582448610, + 1582448640, + 1582448670, + 1582448700, + 1582448730, + 1582448760, + 1582448790, + 1582448820, + 1582448850, + 1582448880, + 1582448910, + 1582448940, + 1582448970, + 1582449000, + 1582449030, + 1582449060, + 1582449090, + 1582449120, + 1582449150, + 1582449180, + 1582449210, + 1582449240, + 1582449270, + 1582449300, + 1582449330, + 1582449360, + 1582449390, + 1582449420, + 1582449450, + 1582449480, + 1582449510, + 1582449540, + 1582449570, + 1582449600, + 1582449630, + 1582449660, + 1582449690, + 1582449720, + 1582449750, + 1582449780, + 1582449810, + 1582449840, + 1582449870, + 1582449900, + 1582449930, + 1582449960, + 1582449990, + 1582450020, + 1582450050, + 1582450080, + 1582450110, + 1582450140, + 1582450170, + 1582450200, + 1582450230, + 1582450260, + 1582450290, + 1582450320, + 1582450350, + 1582450380, + 1582450410, + 1582450440, + 1582450470, + 1582450500, + 1582450530, + 1582450560, + 1582450590, + 1582450620, + 1582450650, + 1582450680, + 1582450710, + 1582450740, + 1582450770, + 1582450800, + 1582450830, + 1582450860, + 1582450890, + 1582450920, + 1582450950, + 1582450980, + 1582451010, + 1582451040, + 1582451070, + 1582451100, + 1582451130, + 1582451160, + 1582451190, + 1582451220, + 1582451250, + 1582451280, + 1582451310, + 1582451340, + 1582451370, + 1582451400, + 1582451430, + 1582451460, + 1582451490, + 1582451520, + 1582451550, + 1582451580, + 1582451610, + 1582451640, + 1582451670, + 1582451700, + 1582451730, + 1582451760, + 1582451790, + 1582451820, + 1582451850, + 1582451880, + 1582451910, + 1582451940, + 1582451970, + 1582452000, + 1582452030, + 1582452060, + 1582452090, + 1582452120, + 1582452150, + 1582452180, + 1582452210 ], "stages": [ "W", @@ -1097,32 +1011,26 @@ "W", "W", "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", "N1", "N1", "N1", "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N2", - "N2", - "N2", - "N2", - "N2", "N2", "N2", "N2", "N2", + "W", + "W", + "W", + "N1", "N2", "N2", "N2", @@ -1133,6 +1041,24 @@ "N2", "N2", "N2", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "N1", + "N1", + "N1", + "N1", "N2", "N2", "N2", @@ -1148,12 +1074,13 @@ "N2", "N2", "N3", - "N2", - "N2", - "N2", - "N2", "N3", - "N2", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", "N3", "N2", "N2", @@ -1163,22 +1090,86 @@ "N2", "N2", "N2", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", "N2", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", "N2", "N2", "N2", @@ -1191,25 +1182,6 @@ "REM", "REM", "REM", - "REM", - "REM", - "REM", - "REM", - "N1", - "N1", - "N1", - "N1", - "N2", - "N2", - "N2", - "N2", - "N2", - "N1", - "N1", - "N1", - "N1", - "N1", - "N2", "N2", "N2", "N2", @@ -1219,23 +1191,6 @@ "N2", "N2", "N2", - "REM", - "REM", - "REM", - "REM", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "W", - "N1", - "N1", - "N1", - "N1", - "N2", - "N2", "N2", "N2", "N2", @@ -1262,15 +1217,7 @@ "N2", "N3", "N3", - "N2", - "N2", "N3", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", "N3", "N3", "N3", @@ -1282,23 +1229,17 @@ "N3", "N3", "N3", - "N2", "N3", - "N2", - "N2", "N3", "N3", - "N2", "N3", "N3", "N3", - "N2", "N3", "N3", "N3", "N3", "N3", - "N2", "N3", "N2", "N2", @@ -1307,9 +1248,6 @@ "N2", "N2", "N2", - "N3", - "N2", - "N2", "N2", "N2", "N2", @@ -1321,8 +1259,6 @@ "N2", "N2", "N2", - "N3", - "N3", "N2", "N2", "N2", @@ -1342,12 +1278,36 @@ "N3", "N3", "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", "N2", "N2", "N2", "N2", "N2", - "N3", "N2", "N2", "N2", @@ -1355,9 +1315,6 @@ "N2", "N2", "N2", - "N3", - "N3", - "N3", "N2", "N2", "N2", @@ -1372,43 +1329,29 @@ "N2", "N2", "N2", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "N1", "N2", "N2", "N2", - "W", - "N1", - "N1", - "N1", - "N1", - "N1", "N2", "N2", "N2", "N2", - "REM", - "REM", - "REM", - "REM", - "REM", - "REM", - "REM", - "N1", - "N1", - "N1", - "REM", - "REM", - "REM", - "REM", - "W", - "N1", - "N1", - "N1", - "REM", - "REM", - "REM", - "REM", - "REM", - "REM", "N2", "N2", "N2", @@ -1417,22 +1360,11 @@ "N2", "N2", "N2", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", "N2", "N2", "N2", "N2", "N2", - "REM", - "REM", - "N1", "N2", "N2", "N2", @@ -1465,23 +1397,69 @@ "N2", "N2", "N2", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", "N2", "N2", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "W", - "W", - "W", - "N1", - "N1", - "N1", - "N1", + "N2", + "N2", + "N2", + "N2", + "N2", + "N2", + "N2", + "N2", + "N2", + "N2", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N2", + "N2", + "N2", + "N2", + "N2", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", "N1", "N1", "N2", @@ -1529,6 +1507,28 @@ "N2", "N2", "N2", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", "N2", "N2", "N2", @@ -1542,7 +1542,11 @@ "N2", "N2", "N2", - "N2", + "N3", + "N3", + "N3", + "N3", + "N3", "N3", "N3", "N3", @@ -1553,16 +1557,6 @@ "N2", "N2", "N2", - "W", - "W", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", "N2", "N2", "N2", @@ -1572,33 +1566,56 @@ "N2", "N2", "N2", + "N3", + "N3", + "N2", "N2", "N2", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", "N2", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", "N2", "N2", "N2", "N2", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "N1", - "N1", "REM", "REM", "REM", @@ -1651,57 +1668,42 @@ "REM", "REM", "REM", - "W", - "W", - "W", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N2", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N2", - "N2", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", "N2", "N2", "N2", @@ -1741,6 +1743,17 @@ "N2", "N2", "N2", + "N1", + "N1", + "N1", + "N1", + "N1", + "N1", + "N1", + "N1", + "N1", + "N1", + "N1", "N2", "N2", "N2", @@ -1795,6 +1808,21 @@ "N2", "N2", "N2", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", + "N3", "N2", "N2", "N2", @@ -1816,386 +1844,83 @@ "REM", "REM", "REM", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "N1", - "W", - "W", - "W", - "N1", - "W", - "W", - "N1", - "W", - "W", - "W", - "W", - "N1", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "N1", - "W", - "N1", - "N1", - "W", - "W", - "W", - "W", - "W", - "N1", - "W", - "W", - "N1", - "W", - "N1", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", - "W", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", + "REM", "W", "W", "W" ] }, - "annotations": [ - [1602210380, "W"], - [1602211400, "N1"], - [1602211910, "N2"], - [1602212900, "N3"], - [1602212930, "N2"], - [1602213050, "N3"], - [1602213080, "N2"], - [1602213110, "N3"], - [1602213140, "N2"], - [1602213380, "N1"], - [1602213620, "N2"], - [1602214040, "REM"], - [1602214340, "N1"], - [1602214460, "N2"], - [1602214610, "N1"], - [1602214760, "N2"], - [1602215060, "REM"], - [1602215180, "N1"], - [1602215360, "W"], - [1602215390, "N1"], - [1602215510, "N2"], - [1602216290, "N3"], - [1602216350, "N2"], - [1602216410, "N3"], - [1602216440, "N2"], - [1602216620, "N3"], - [1602216950, "N2"], - [1602216980, "N3"], - [1602217010, "N2"], - [1602217070, "N3"], - [1602217130, "N2"], - [1602217160, "N3"], - [1602217250, "N2"], - [1602217280, "N3"], - [1602217430, "N2"], - [1602217460, "N3"], - [1602217490, "N2"], - [1602217700, "N3"], - [1602217730, "N2"], - [1602218120, "N3"], - [1602218180, "N2"], - [1602218360, "N3"], - [1602218750, "N2"], - [1602218900, "N3"], - [1602218930, "N2"], - [1602219140, "N3"], - [1602219230, "N2"], - [1602219740, "W"], - [1602219770, "N1"], - [1602219920, "N2"], - [1602220040, "REM"], - [1602220250, "N1"], - [1602220340, "REM"], - [1602220460, "W"], - [1602220490, "N1"], - [1602220580, "REM"], - [1602220760, "N2"], - [1602221000, "N1"], - [1602221240, "N2"], - [1602221390, "REM"], - [1602221450, "N1"], - [1602221480, "N2"], - [1602222500, "N1"], - [1602222740, "W"], - [1602222830, "N1"], - [1602223010, "N2"], - [1602224780, "N3"], - [1602224930, "N2"], - [1602225080, "W"], - [1602225140, "N1"], - [1602225380, "N2"], - [1602225710, "N1"], - [1602226040, "N2"], - [1602226190, "W"], - [1602226400, "N1"], - [1602226460, "REM"], - [1602228020, "W"], - [1602228110, "N1"], - [1602228410, "N2"], - [1602229070, "N1"], - [1602229490, "N2"], - [1602232580, "REM"], - [1602232970, "W"], - [1602234290, "N1"], - [1602234320, "W"], - [1602234410, "N1"], - [1602234440, "W"], - [1602234500, "N1"], - [1602234530, "W"], - [1602234650, "N1"], - [1602234680, "W"], - [1602238310, "N1"], - [1602238820, "W"], - [1602238850, "N1"], - [1602238910, "W"], - [1602239060, "N1"], - [1602239090, "W"], - [1602239150, "N1"], - [1602239180, "W"], - [1602239210, "N1"], - [1602239240, "W"] - ], "spectrograms": { "frequencies": [ 0.78125, diff --git a/backend/assets/readme.md b/backend/assets/readme.md index 0f279686..09e50e65 100644 --- a/backend/assets/readme.md +++ b/backend/assets/readme.md @@ -42,16 +42,6 @@ ... // Stage of each timestamp. This array has the same size that timestamps. Possible values are "W", "REM", "N1", "N2", "N3" ] }, - "annotations": [ - [1602210380, "W"], // Unix timestamp of the epoch that transitioned to the "W" state - [1602211400, "N1"], - [1602211910, "N2"], - [1602212900, "N3"], - ... - [1602239180, "W"], - [1602239210, "N1"], - [1602239240, "W"] - ], "spectrograms": { "frequencies": [...], "fpz-cz": [ // Contains an array for each timestamps in epochs.timestamps for the fpz-cz eeg channel diff --git a/web/package.json b/web/package.json index c97e43db..f61ccdf6 100644 --- a/web/package.json +++ b/web/package.json @@ -31,12 +31,13 @@ "d3": "^5.16.0", "d3-tip": "^0.9.1", "headroom.js": "^0.11.0", - "moment": "^2.27.0", + "luxon": "^1.25.0", "node-sass": "4.14.1", "prop-types": "^15.7.2", "react": "16.12.0", "react-dom": "16.12.0", "react-hook-form": "^6.8.4", + "react-hooks-global-state": "^1.0.1", "react-router": "5.1.2", "react-router-dom": "5.1.2", "react-scripts": "3.4.0", diff --git a/web/src/assets/css/visualisation.css b/web/src/assets/css/visualisation.css deleted file mode 100644 index dc066411..00000000 --- a/web/src/assets/css/visualisation.css +++ /dev/null @@ -1,74 +0,0 @@ -/* body { - display: inline-block; - margin: 20px 0px ; - font-family: Arial, sans-serif; - background-color: #181C28; -} -*/ - -svg { - display: inline-block; - /* background-color: #181C28; */ -} - -.axis { - color: black; -} - -.axis text { - font-weight: 540; - font-size: 12pt; -} - -.vis2 { - border-right: 1px solid #000000; -} - -.axis line { - fill: none; - stroke: black; - stroke-width: 1.5px; - shape-rendering: crispEdges; -} - -.y.axis path { - fill: none; - stroke: black; - shape-rendering: crispEdges; -} - -.brush .extent { - stroke: black; - fill-opacity: 0.125; - shape-rendering: crispEdges; -} - -.line { - fill: none; -} - -.label-sleepType { - opacity: 0; - fill: black; - font-size: '25px'; - font-weight: 600; - text-anchor: start; - alignment-baseline: 'middle'; -} - -.pourcentage { - text-anchor: middle; - font-family: sans-serif; - font-size: 20px; - fill: black; -} - -/***** Tooltip *****/ -.d3-tip { - line-height: 1; - padding: 12px; - border-radius: 2px; - color: #0d0d0d; - background: rgba(235, 235, 235, 0.9); - /* Color Theme Swatches in Hex */ -} diff --git a/web/src/d3/evolving_chart/chart_states.js b/web/src/d3/evolving_chart/chart_states.js index 9ef668b5..af2cb284 100644 --- a/web/src/d3/evolving_chart/chart_states.js +++ b/web/src/d3/evolving_chart/chart_states.js @@ -1,10 +1,12 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { BAR_HEIGHT, DIMENSION } from './constants'; import { EPOCH_DURATION_MS, TRANSITION_TIME_MS, STAGES_ORDERED } from '../constants'; +import '../style.css'; + export const createTimelineChartCallbacks = (g, xTime, xTimeAxis, color, tooltip) => Object({ fromInitial: () => { @@ -12,12 +14,15 @@ export const createTimelineChartCallbacks = (g, xTime, xTimeAxis, color, tooltip setAttrOnAnnotationRects(annotationRects, xTime, 0, color, tooltip); - g.append('g').attr('class', 'x axis').attr('transform', `translate(0, ${BAR_HEIGHT})`).call(xTimeAxis); + g.append('g') + .attr('class', 'x visualization__axis') + .attr('transform', `translate(0, ${BAR_HEIGHT})`) + .call(xTimeAxis); }, fromInstance: () => { const annotationRects = g.selectAll('.rect-stacked').interrupt(); - g.selectAll('.y.axis').remove(); + g.selectAll('.y.visualization__axis').remove(); setAttrOnAnnotationRects(annotationRects, xTime, 0, color, tooltip); @@ -41,7 +46,7 @@ export const createInstanceChartCallbacks = (g, data, xTime, xTimeAxis, yAxis, c g.selectAll('text.proportion').remove(); - g.select('.x.axis').interrupt().transition().call(xTimeAxis); + g.select('.x.visualization__axis').interrupt().transition().call(xTimeAxis); transitionHorizontalAxis(g, STAGES_ORDERED.length * BAR_HEIGHT); setAttrOnAnnotationRects(annotationRects, xTime, getVerticalPositionCallback, color, tooltip); @@ -55,7 +60,7 @@ export const createBarChartCallbacks = (g, data, xAxisLinear, yAxis, color, tip) const annotationRects = g.selectAll('.rect-stacked').interrupt(); const xProportionCallback = getOffsetSleepStageProportionCallback(data); - g.select('.x.axis').transition().call(xAxisLinear); + g.select('.x.visualization__axis').transition().call(xAxisLinear); transitionHorizontalAxis(g, STAGES_ORDERED.length * BAR_HEIGHT); setTooltip(annotationRects, tip) @@ -65,14 +70,14 @@ export const createBarChartCallbacks = (g, data, xAxisLinear, yAxis, color, tip) .attr('x', xProportionCallback) .on('end', () => { // Only keep the first rectangle of each stage to be visible - g.selectAll('.rect-stacked').attr('x', 0).attr('width', getFirstRectangleProportionWidthCallback(firstStageIndexes, stageTimeProportions)); + g.selectAll('.rect-stacked') + .attr('x', 0) + .attr('width', getFirstRectangleProportionWidthCallback(firstStageIndexes, stageTimeProportions)); createProportionLabels(g, data); }); }, fromStackedBarChart: () => { const annotationRects = g.selectAll('.rect-stacked').interrupt(); - - g.selectAll('text.label-sleepType').remove(); g.selectAll('text.proportion').remove(); createVerticalAxis(g, yAxis, color); @@ -95,10 +100,11 @@ export const createStackedBarChartCallbacks = (g, data) => const { annotations, firstStageIndexes, stageTimeProportions, epochs } = data; const firstAnnotationsByStage = _.filter(annotations, ({ stage }, index) => firstStageIndexes[stage] === index); const getHorizontalPositionSleepStage = ({ stage }) => - (getCumulativeProportionOfNightAtStart(stage, stageTimeProportions) + stageTimeProportions[stage] / 2) * DIMENSION.WIDTH; + (getCumulativeProportionOfNightAtStart(stage, stageTimeProportions) + stageTimeProportions[stage] / 2) * + DIMENSION.WIDTH; const annotationRects = g.selectAll('.rect-stacked').interrupt(); - g.selectAll('.y.axis').remove(); + g.selectAll('.y.visualization__axis').remove(); g.selectAll('text.proportion').remove(); transitionHorizontalAxis(g, BAR_HEIGHT); @@ -119,7 +125,11 @@ export const createStackedBarChartCallbacks = (g, data) => .attr('class', 'proportion') .style('text-anchor', 'middle') .append('tspan') - .text(({ stage }) => moment.utc(stageTimeProportions[stage] * epochs.length * EPOCH_DURATION_MS).format('HH:mm')) + .text(({ stage }) => + DateTime.fromMillis(stageTimeProportions[stage] * epochs.length * EPOCH_DURATION_MS) + .toUTC() + .toFormat('hh:mm:ss'), + ) .attr('x', getHorizontalPositionSleepStage) .attr('y', 40) .append('tspan') @@ -158,7 +168,7 @@ const getFirstRectangleProportionWidthCallback = (firstStageIndexes, stageTimePr const createVerticalAxis = (g, yAxis, color) => g .append('g') - .attr('class', 'y axis') + .attr('class', 'y visualization__axis') .style('font-size', '1.5rem') .transition() .duration(TRANSITION_TIME_MS) @@ -172,7 +182,11 @@ const createVerticalAxis = (g, yAxis, color) => .style('alignment-baseline', 'middle'); const transitionHorizontalAxis = (g, yPosition) => - g.select('.x.axis').transition().duration(TRANSITION_TIME_MS).attr('transform', `translate(0, ${yPosition})`); + g + .select('.x.visualization__axis') + .transition() + .duration(TRANSITION_TIME_MS) + .attr('transform', `translate(0, ${yPosition})`); const createProportionLabels = (g, data) => g @@ -181,7 +195,9 @@ const createProportionLabels = (g, data) => .enter() .append('text') .attr('class', 'proportion') - .text(({ stage }, i) => (i === data.firstStageIndexes[stage] ? `${_.round(data.stageTimeProportions[stage] * 100, 2)}%` : '')) + .text(({ stage }, i) => + i === data.firstStageIndexes[stage] ? `${_.round(data.stageTimeProportions[stage] * 100, 2)}%` : '', + ) .attr('x', DIMENSION.WIDTH / 20) .attr('y', ({ stage }) => BAR_HEIGHT * STAGES_ORDERED.indexOf(stage) + BAR_HEIGHT / 2) .style('fill', 'black'); @@ -209,7 +225,8 @@ const getOffsetSleepStageProportionCallback = (data) => { ); }); - return (d, index) => DIMENSION.WIDTH * stageTimeProportions[d.stage] * cumulSumProportions[d.stage][annotationIndexSleepStage[index]]; + return (d, index) => + DIMENSION.WIDTH * stageTimeProportions[d.stage] * cumulSumProportions[d.stage][annotationIndexSleepStage[index]]; }; const cumulSum = (annotationsProportionByStage) => diff --git a/web/src/d3/evolving_chart/evolving_chart.js b/web/src/d3/evolving_chart/evolving_chart.js index ac5822ce..02946cf0 100644 --- a/web/src/d3/evolving_chart/evolving_chart.js +++ b/web/src/d3/evolving_chart/evolving_chart.js @@ -1,10 +1,15 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { preprocessData } from './preproc'; import { createLegend } from './legend'; -import { createTimelineChartCallbacks, createInstanceChartCallbacks, createBarChartCallbacks, createStackedBarChartCallbacks } from './chart_states'; +import { + createTimelineChartCallbacks, + createInstanceChartCallbacks, + createBarChartCallbacks, + createStackedBarChartCallbacks, +} from './chart_states'; import { MARGIN, CANVAS_DIMENSION, BAR_HEIGHT, DIMENSION } from './constants'; import { STAGES_ORDERED, STAGE_TO_COLOR } from '../constants'; import { initializeTooltips } from './mouse_over'; @@ -27,10 +32,10 @@ const initializeScales = () => { const setDomainOnScales = (xTime, xLinear, y, colors, epochs) => { const start = _.first(epochs).timestamp; const end = _.last(epochs).timestamp; - const nightDuration = moment.duration(moment(end).diff(start)); + const nightDuration = DateTime.fromJSDate(end).diff(DateTime.fromJSDate(start), ['hours']); xTime.domain([start, end]); - xLinear.domain([0, nightDuration.asHours()]); + xLinear.domain([0, nightDuration.hours]); y.domain(STAGES_ORDERED); colors.domain(STAGES_ORDERED); }; @@ -45,7 +50,8 @@ const initializeAxes = (xTime, xLinear, y) => { const createDrawingGroup = (svg) => svg.append('g').attr('transform', `translate(${MARGIN.LEFT}, ${MARGIN.TOP})`); -const bindAnnotationsToRects = (g, annotations) => g.selectAll('.rect').data(annotations).enter().append('rect').attr('class', 'rect-stacked'); +const bindAnnotationsToRects = (g, annotations) => + g.selectAll('.rect').data(annotations).enter().append('rect').attr('class', 'rect-stacked'); const createEvolvingChart = (containerNode, data) => { const svg = d3.select(containerNode).attr('viewBox', `0, 0, ${CANVAS_DIMENSION.WIDTH}, ${CANVAS_DIMENSION.HEIGHT}`); diff --git a/web/src/d3/evolving_chart/mouse_over.js b/web/src/d3/evolving_chart/mouse_over.js index 64abb43e..e9587828 100644 --- a/web/src/d3/evolving_chart/mouse_over.js +++ b/web/src/d3/evolving_chart/mouse_over.js @@ -1,31 +1,34 @@ import tip from 'd3-tip'; -import moment from 'moment'; +import './style.css'; import { EPOCH_DURATION_MS } from '../constants'; +import { DateTime, Duration } from 'luxon'; export const initializeTooltips = (svg, data) => { const barToolTip = initializeTooltip(svg, getBarToolTipText); - const stackedToolTip = initializeTooltip(svg, (d) => getStackedToolTipText(d, data.stageTimeProportions, data.epochs.length)); + const stackedToolTip = initializeTooltip(svg, (d) => + getStackedToolTipText(d, data.stageTimeProportions, data.epochs.length), + ); return { barToolTip, stackedToolTip }; }; const initializeTooltip = (svg, getToolTipText) => { - const tooltip = tip().attr('class', 'd3-tip').offset([-10, 0]); + const tooltip = tip().attr('class', 'evolving_chart__tooltip').offset([-10, 0]); svg.call(tooltip); tooltip.html(getToolTipText); return tooltip; }; -const getBarToolTipText = (d) => { - return `Stage : ${d.stage}
- Range : ${moment(d.start).format('HH:mm:ss')} - - ${moment(d.end).format('HH:mm:ss')}
- Duration: ${moment.utc(moment(d.end).diff(d.start)).format('HH:mm:ss')} `; -}; +const getBarToolTipText = (d) => `Stage : ${d.stage}
+ Range : ${DateTime.fromJSDate(d.start).toFormat('hh:mm:ss')} + - ${DateTime.fromJSDate(d.end).toFormat('hh:mm:ss')}
+ Duration: ${DateTime.fromJSDate(d.end) + .diff(DateTime.fromJSDate(d.start)) + .toFormat('hh:mm:ss')} `; const getStackedToolTipText = (d, stageTimeProportions, nbEpochs) => - `Stage : ${d.stage}
Duration : ${moment - .utc(stageTimeProportions[d.stage] * nbEpochs * EPOCH_DURATION_MS) - .format('HH:mm:ss')}
`; + `Stage : ${d.stage}
Duration : ${Duration.fromMillis( + stageTimeProportions[d.stage] * nbEpochs * EPOCH_DURATION_MS, + ).toFormat('hh:mm:ss')}
`; diff --git a/web/src/d3/evolving_chart/preproc.js b/web/src/d3/evolving_chart/preproc.js index 01987222..d96c37ff 100644 --- a/web/src/d3/evolving_chart/preproc.js +++ b/web/src/d3/evolving_chart/preproc.js @@ -1,9 +1,10 @@ import _ from 'lodash'; -import { convertTimestampsToDates, convertEpochsToAnnotations } from '../utils'; +import { convertAPIFormatToCSVFormat, convertTimestampsToDates, convertEpochsToAnnotations } from '../utils'; import { STAGES_ORDERED } from '../constants'; export const preprocessData = (data) => { + data = convertAPIFormatToCSVFormat(data); data = convertTimestampsToDates(data); const annotations = convertEpochsToAnnotations(data); const stageTimeProportions = getStageTimeProportions(data); diff --git a/web/src/d3/evolving_chart/style.css b/web/src/d3/evolving_chart/style.css new file mode 100644 index 00000000..7654106f --- /dev/null +++ b/web/src/d3/evolving_chart/style.css @@ -0,0 +1,7 @@ +.evolving_chart__tooltip { + line-height: 1; + padding: 12px; + border-radius: 2px; + color: #0d0d0d; + background: rgba(235, 235, 235, 0.9); +} diff --git a/web/src/d3/hypnogram/hypnogram.js b/web/src/d3/hypnogram/hypnogram.js index 8262f863..0dbf932b 100644 --- a/web/src/d3/hypnogram/hypnogram.js +++ b/web/src/d3/hypnogram/hypnogram.js @@ -4,7 +4,7 @@ import _ from 'lodash'; import createHypnogramChart from './line_charts'; import { DIMENSION, MARGIN, COMPARATIVE_COLORS, CANVAS_DIMENSION } from './constants'; import { STAGES_ORDERED } from '../constants'; -import { convertTimestampsToDates } from '../utils'; +import { convertAPIFormatToCSVFormat, convertTimestampsToDates } from '../utils'; const initializeScales = (comparativeColors) => Object({ @@ -22,8 +22,8 @@ const initializeAxes = (x, y) => const createDrawingGroup = (svg) => svg.append('g').attr('transform', `translate(${MARGIN.LEFT}, ${MARGIN.TOP})`); const preprocessData = (data, hypnogramNames) => { + data = data.map(convertAPIFormatToCSVFormat); data = data.map((hypno) => convertTimestampsToDates(hypno)); - return _.zip(data, hypnogramNames).map((x) => Object({ name: x[1], diff --git a/web/src/d3/hypnogram/line_charts.js b/web/src/d3/hypnogram/line_charts.js index 5199a131..b84af89d 100644 --- a/web/src/d3/hypnogram/line_charts.js +++ b/web/src/d3/hypnogram/line_charts.js @@ -4,15 +4,19 @@ import _ from 'lodash'; import createMouseOver from './mouse_over'; import { DIMENSION, MARGIN } from './constants'; +import '../style.css'; + +import './style.css'; + const createHypnogramChart = (g, data, x, y, xAxis, yAxis, color, chartTitle, hypnogramNames, comparativeColors) => { const line = createLine(x, y); - const g_chart = g.append('g').attr('class', 'hypnogram-lines'); + const g_chart = g.append('g'); g_chart .selectAll() .data(data) .enter() .append('path') - .attr('class', 'line') + .attr('class', 'hypnogram__line') .attr('fill', 'none') .attr('d', (x) => line(x.values)) .attr('stroke', (x) => color(x.name)) @@ -34,9 +38,9 @@ const createLine = (x, y) => const createAxes = (g, xAxis, yAxis) => { const { HEIGHT, WIDTH } = DIMENSION; - g.append('g').attr('class', 'x axis').attr('transform', `translate(0,${HEIGHT})`).call(xAxis); + g.append('g').attr('class', 'x visualization__axis').attr('transform', `translate(0,${HEIGHT})`).call(xAxis); - g.append('g').attr('class', 'y axis').call(yAxis); + g.append('g').attr('class', 'y visualization__axis').call(yAxis); g.append('text') .text('Time') diff --git a/web/src/d3/hypnogram/style.css b/web/src/d3/hypnogram/style.css new file mode 100644 index 00000000..7a944e35 --- /dev/null +++ b/web/src/d3/hypnogram/style.css @@ -0,0 +1,3 @@ +.hypnogram__line { + fill: none; +} diff --git a/web/src/d3/spectrogram/axes_legend.js b/web/src/d3/spectrogram/axes_legend.js index 2921a356..85801931 100644 --- a/web/src/d3/spectrogram/axes_legend.js +++ b/web/src/d3/spectrogram/axes_legend.js @@ -1,23 +1,13 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { - MARGIN, - NB_POINTS_COLOR_INTERPOLATION, - TITLE_FONT_SIZE, - TITLE_POSITION_Y, -} from './constants'; +import { MARGIN, NB_POINTS_COLOR_INTERPOLATION, TITLE_FONT_SIZE, TITLE_POSITION_Y } from './constants'; + +import '../style.css'; const createDrawingGroups = (g, spectrogramWidth) => Object({ - spectrogramDrawingGroup: g - .append('g') - .attr('transform', `translate(${MARGIN.LEFT}, ${MARGIN.TOP})`), - legendDrawingGroup: g - .append('g') - .attr( - 'transform', - `translate(${MARGIN.LEFT + spectrogramWidth}, ${MARGIN.TOP})`, - ), + spectrogramDrawingGroup: g.append('g').attr('transform', `translate(${MARGIN.LEFT}, ${MARGIN.TOP})`), + legendDrawingGroup: g.append('g').attr('transform', `translate(${MARGIN.LEFT + spectrogramWidth}, ${MARGIN.TOP})`), }); const drawTitle = (g, channelName, spectrogramWidth) => @@ -29,15 +19,9 @@ const drawTitle = (g, channelName, spectrogramWidth) => .style('font-size', TITLE_FONT_SIZE) .text(`Spectrogram of channel ${channelName}`); -const drawAxes = ( - g, - xAxis, - yAxis, - singleSpectrogramHeight, - spectrogramWidth, -) => { +const drawAxes = (g, xAxis, yAxis, singleSpectrogramHeight, spectrogramWidth) => { g.append('text') - .attr('class', 'x axis') + .attr('class', 'x visualization__axis') .attr('y', singleSpectrogramHeight + MARGIN.BOTTOM) .attr('x', spectrogramWidth / 2) .attr('fill', 'currentColor') @@ -45,7 +29,7 @@ const drawAxes = ( .text('Time'); g.append('text') - .attr('class', 'y axis') + .attr('class', 'y visualization__axis') .attr('transform', 'rotate(-90)') .attr('y', -MARGIN.LEFT) .attr('x', -singleSpectrogramHeight / 2) @@ -55,12 +39,12 @@ const drawAxes = ( .text('Frequency (Hz)'); g.append('g') - .attr('class', 'x axis') + .attr('class', 'x visualization__axis') .attr('transform', `translate(0, ${singleSpectrogramHeight})`) .call(xAxis) .selectAll('text'); - g.append('g').attr('class', 'y axis').call(yAxis).selectAll('text'); + g.append('g').attr('class', 'y visualization__axis').call(yAxis).selectAll('text'); }; const drawLegend = (svg, color, y, spectrogramHeight) => { @@ -97,14 +81,14 @@ const drawLegend = (svg, color, y, spectrogramHeight) => { const yAxis = d3.axisRight(y).ticks(5, 's'); svg .append('g') - .attr('class', 'y axis') + .attr('class', 'y visualization__axis') .attr('transform', `translate(${MARGIN.RIGHT / 3.7},0)`) .call(yAxis) .selectAll('text'); svg .append('text') - .attr('class', 'y axis') + .attr('class', 'y visualization__axis') .attr('transform', 'rotate(90)') .attr('y', -MARGIN.RIGHT) .attr('x', spectrogramHeight / 2) @@ -118,40 +102,23 @@ const drawSpectrogramAxesAndLegend = ( svg, scalesAndAxesBySpectrogram, data, - { - canvasWidth, - spectrogramWidth, - singleSpectrogramCanvasHeight, - singleSpectrogramHeight, - }, + { canvasWidth, spectrogramWidth, singleSpectrogramCanvasHeight, singleSpectrogramHeight }, ) => - _.forEach( - _.zip(scalesAndAxesBySpectrogram, data), - ([{ xAxis, yAxis, color, yColor }, { channel }], index) => { - const currentSpectrogramDrawingGroup = svg - .append('g') - .attr( - 'transform', - `translate(0, ${index * singleSpectrogramCanvasHeight[index]})`, - ) - .attr('width', canvasWidth) - .attr('height', singleSpectrogramCanvasHeight[index]); - - const { - spectrogramDrawingGroup, - legendDrawingGroup, - } = createDrawingGroups(currentSpectrogramDrawingGroup, spectrogramWidth); - - drawTitle(spectrogramDrawingGroup, channel, spectrogramWidth); - drawAxes( - spectrogramDrawingGroup, - xAxis, - yAxis, - singleSpectrogramHeight, - spectrogramWidth, - ); - drawLegend(legendDrawingGroup, color, yColor, singleSpectrogramHeight); - }, - ); + _.forEach(_.zip(scalesAndAxesBySpectrogram, data), ([{ xAxis, yAxis, color, yColor }, { channel }], index) => { + const currentSpectrogramDrawingGroup = svg + .append('g') + .attr('transform', `translate(0, ${index * singleSpectrogramCanvasHeight[index]})`) + .attr('width', canvasWidth) + .attr('height', singleSpectrogramCanvasHeight[index]); + + const { spectrogramDrawingGroup, legendDrawingGroup } = createDrawingGroups( + currentSpectrogramDrawingGroup, + spectrogramWidth, + ); + + drawTitle(spectrogramDrawingGroup, channel, spectrogramWidth); + drawAxes(spectrogramDrawingGroup, xAxis, yAxis, singleSpectrogramHeight, spectrogramWidth); + drawLegend(legendDrawingGroup, color, yColor, singleSpectrogramHeight); + }); export default drawSpectrogramAxesAndLegend; diff --git a/web/src/d3/spectrogram/constants.js b/web/src/d3/spectrogram/constants.js index d4b69980..9b1a7f8d 100644 --- a/web/src/d3/spectrogram/constants.js +++ b/web/src/d3/spectrogram/constants.js @@ -1,7 +1,7 @@ export const PADDING = 100; export const NB_SPECTROGRAM = 2; export const FREQUENCY_KEY = 'frequencies'; -export const HYPNOGRAM_KEY = 'hypnogram'; +export const HYPNOGRAM_KEY = 'epochs'; export const NB_POINTS_COLOR_INTERPOLATION = 3; export const NOT_HIGHLIGHTED_RECTANGLE_OPACITY = 0.5; export const CANVAS_WIDTH_TO_HEIGHT_RATIO = 700 / 1000; // width to height ratio diff --git a/web/src/d3/spectrogram/spectrogram.js b/web/src/d3/spectrogram/spectrogram.js index 73bdaa70..89fe5905 100644 --- a/web/src/d3/spectrogram/spectrogram.js +++ b/web/src/d3/spectrogram/spectrogram.js @@ -13,7 +13,7 @@ import { } from './constants'; import { STAGES_ORDERED } from '../constants'; import drawSpectrogramAxesAndLegend from './axes_legend'; -import { convertTimestampsToDates } from '../utils'; +import { convertAPIFormatToCSVFormat, convertTimestampsToDates } from '../utils'; // keys are the sleep stage for which we want to display the spectrogram // accepted keys are: null (when all stages are highlighted), W, N1, N2, N3, REM @@ -49,7 +49,8 @@ const getDimensions = (parentDiv) => { const preprocessData = (channel, data) => { const powerAmplitudesByTimestamp = data[channel]; const frequencies = data[FREQUENCY_KEY]; - const hypnogram = convertTimestampsToDates(data[HYPNOGRAM_KEY]); + const epochs = convertAPIFormatToCSVFormat(data.epochs); + const hypnogram = convertTimestampsToDates(epochs); return { channel, @@ -57,15 +58,13 @@ const preprocessData = (channel, data) => { rectangles: _.flatMap( _.zip(powerAmplitudesByTimestamp, hypnogram), ([powerAmplitudeSingleTimestamp, { sleepStage, timestamp }]) => - _.map( - _.zip(powerAmplitudeSingleTimestamp, frequencies), - ([intensity, frequency]) => - Object({ - intensity, - frequency, - timestamp, - sleepStage, - }), + _.map(_.zip(powerAmplitudeSingleTimestamp, frequencies), ([intensity, frequency]) => + Object({ + intensity, + frequency, + timestamp, + sleepStage, + }), ), ), }; @@ -80,14 +79,7 @@ const initializeScales = ({ spectrogramWidth, singleSpectrogramHeight }) => color: d3.scaleSequential().interpolator(d3.interpolatePlasma), }); -const setDomainOnScales = ( - { rectangles, frequencies }, - x, - yBand, - yLinear, - color, - yColor, -) => { +const setDomainOnScales = ({ rectangles, frequencies }, x, yBand, yLinear, color, yColor) => { x.domain([_.first(rectangles).timestamp, _.last(rectangles).timestamp]); yBand.domain(frequencies); yLinear.domain([_.first(frequencies), _.last(frequencies)]); @@ -118,39 +110,23 @@ const drawSpectrogramRectangles = ( highlightedSleepStage, ) => { const context = canvas.node().getContext('2d'); - const isHighlighted = (sleepStage) => - highlightedSleepStage === null || highlightedSleepStage === sleepStage; - - _.each( - _.zip(scalesAndAxesBySpectrogram, data), - ([{ x, yBand, color }, { rectangles, frequencies }], index) => { - const rectangleWidth = - x(rectangles[frequencies.length].timestamp) - - x(rectangles[0].timestamp); - - context.resetTransform(); - context.translate( - MARGIN.LEFT, - MARGIN.TOP + index * singleSpectrogramCanvasHeight[index], - ); - - _.each(rectangles, ({ timestamp, frequency, intensity, sleepStage }) => { - context.beginPath(); - context.fillRect( - x(timestamp), - yBand(frequency), - rectangleWidth, - yBand.bandwidth(), - ); - context.globalAlpha = isHighlighted(sleepStage) - ? 1 - : NOT_HIGHLIGHTED_RECTANGLE_OPACITY; - context.fillStyle = color(intensity); - context.fill(); - context.stroke(); - }); - }, - ); + const isHighlighted = (sleepStage) => highlightedSleepStage === null || highlightedSleepStage === sleepStage; + + _.each(_.zip(scalesAndAxesBySpectrogram, data), ([{ x, yBand, color }, { rectangles, frequencies }], index) => { + const rectangleWidth = x(rectangles[frequencies.length].timestamp) - x(rectangles[0].timestamp); + + context.resetTransform(); + context.translate(MARGIN.LEFT, MARGIN.TOP + index * singleSpectrogramCanvasHeight[index]); + + _.each(rectangles, ({ timestamp, frequency, intensity, sleepStage }) => { + context.beginPath(); + context.fillRect(x(timestamp), yBand(frequency), rectangleWidth, yBand.bandwidth()); + context.globalAlpha = isHighlighted(sleepStage) ? 1 : NOT_HIGHLIGHTED_RECTANGLE_OPACITY; + context.fillStyle = color(intensity); + context.fill(); + context.stroke(); + }); + }); }; const createSpectrogram = (containerNode, data) => { @@ -171,25 +147,13 @@ const createSpectrogram = (containerNode, data) => { .attr('width', dimensions.canvasWidth) .attr('height', dimensions.canvasHeight) .style('position', 'absolute'); - const svg = parentDiv - .append('svg') - .attr('width', dimensions.canvasWidth) - .attr('height', dimensions.canvasHeight); + const svg = parentDiv.append('svg').attr('width', dimensions.canvasWidth).attr('height', dimensions.canvasHeight); - const channelNames = _.filter( - _.keys(data), - (keyName) => !_.includes([FREQUENCY_KEY, HYPNOGRAM_KEY], keyName), - ); - const preprocessedData = _.map(channelNames, (channel) => - preprocessData(channel, data), - ); - const scalesAndAxesBySpectrogram = _.map(preprocessedData, (data) => - getScalesAndAxes(data, dimensions), - ); + const channelNames = _.filter(_.keys(data), (keyName) => !_.includes([FREQUENCY_KEY, HYPNOGRAM_KEY], keyName)); + const preprocessedData = _.map(channelNames, (channel) => preprocessData(channel, data)); + const scalesAndAxesBySpectrogram = _.map(preprocessedData, (data) => getScalesAndAxes(data, dimensions)); - const createSpectrogramWithHighlightedStageCallback = ( - highlightedSleepStage, - ) => () => { + const createSpectrogramWithHighlightedStageCallback = (highlightedSleepStage) => () => { const ctx = canvas.node().getContext('2d'); ctx.resetTransform(); ctx.clearRect(0, 0, dimensions.canvasWidth, dimensions.canvasHeight); @@ -197,26 +161,13 @@ const createSpectrogram = (containerNode, data) => { svg.selectAll('*').remove(); - drawSpectrogramRectangles( - canvas, - scalesAndAxesBySpectrogram, - preprocessedData, - dimensions, - highlightedSleepStage, - ); - drawSpectrogramAxesAndLegend( - svg, - scalesAndAxesBySpectrogram, - preprocessedData, - dimensions, - ); + drawSpectrogramRectangles(canvas, scalesAndAxesBySpectrogram, preprocessedData, dimensions, highlightedSleepStage); + drawSpectrogramAxesAndLegend(svg, scalesAndAxesBySpectrogram, preprocessedData, dimensions); }; spectrogramCallbacks = _.zipObject( [null, ...STAGES_ORDERED], - _.map([null, ...STAGES_ORDERED], (stage) => - createSpectrogramWithHighlightedStageCallback(stage), - ), + _.map([null, ...STAGES_ORDERED], (stage) => createSpectrogramWithHighlightedStageCallback(stage)), ); spectrogramCallbacks[null](); }; diff --git a/web/src/d3/style.css b/web/src/d3/style.css new file mode 100644 index 00000000..e99bf39e --- /dev/null +++ b/web/src/d3/style.css @@ -0,0 +1,21 @@ +.visualization__axis { + color: black; +} + +.visualization__axis text { + font-weight: 540; + font-size: 12pt; +} + +.visualization__axis line { + fill: none; + stroke: black; + stroke-width: 1.5px; + shape-rendering: crispEdges; +} + +.y.visualization__axis path { + fill: none; + stroke: black; + shape-rendering: crispEdges; +} diff --git a/web/src/d3/utils.js b/web/src/d3/utils.js index a91bae52..ae25a397 100644 --- a/web/src/d3/utils.js +++ b/web/src/d3/utils.js @@ -1,5 +1,10 @@ +import _ from 'lodash'; + import { EPOCH_DURATION_SEC } from './constants'; +export const convertAPIFormatToCSVFormat = (epochs) => + _.zip(epochs.timestamps, epochs.stages).map((el) => Object({ timestamp: el[0], sleepStage: el[1] })); + export const convertTimestampsToDates = (data) => data.map((row) => Object({ @@ -22,8 +27,7 @@ export const convertEpochsToAnnotations = (data) => { let currentSleepStage = data[0].sleepStage; let currentAnnotationEpochCount = 0; - const isSleepStageTransition = (sleepStage, index) => - sleepStage !== currentSleepStage || index === data.length - 1; + const isSleepStageTransition = (sleepStage, index) => sleepStage !== currentSleepStage || index === data.length - 1; const saveCurrentAnnotation = (timestamp) => { annotations.push({ diff --git a/web/src/hooks/useGlobalState.js b/web/src/hooks/useGlobalState.js new file mode 100644 index 00000000..cd35479c --- /dev/null +++ b/web/src/hooks/useGlobalState.js @@ -0,0 +1,9 @@ +import { createGlobalState } from 'react-hooks-global-state'; + +const { useGlobalState } = createGlobalState({ + response: null, + isFormSubmitted: false, + postFormError: null, +}); + +export default useGlobalState; diff --git a/web/src/index.js b/web/src/index.js index 960a5894..1a404c36 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -6,14 +6,13 @@ import { Container } from 'reactstrap'; import 'argon-design-system-react/src/assets/vendor/nucleo/css/nucleo.css'; import 'argon-design-system-react/src/assets/vendor/font-awesome/css/font-awesome.min.css'; import 'argon-design-system-react/src/assets/scss/argon-design-system-react.scss'; -import 'assets/css/visualisation.css'; import Header from 'components/header'; import Footer from 'components/footer'; import Navbar from 'components/navbar'; -import SleepAnalysis from 'views/sleep-analysis'; +import SleepAnalysisResults from 'views/sleep_analysis_results'; import Performance from 'views/performance'; -import AnalyzeSleep from 'views/analyze-sleep'; +import AnalyzeSleep from 'views/analyze_sleep'; import ScrollToTop from 'components/scroll_to_top'; import Emoji from 'components/emoji'; @@ -47,9 +46,10 @@ ReactDOM.render( - } /> + } /> + } /> } /> diff --git a/web/src/requests/analayze-sleep.js b/web/src/requests/analyze-sleep.js similarity index 100% rename from web/src/requests/analayze-sleep.js rename to web/src/requests/analyze-sleep.js diff --git a/web/src/views/analyze-sleep/upload-form.js b/web/src/views/analyze-sleep/upload-form.js deleted file mode 100644 index e5bbf648..00000000 --- a/web/src/views/analyze-sleep/upload-form.js +++ /dev/null @@ -1,199 +0,0 @@ -import React from 'react'; -import { Button, Container, CustomInput, Form, FormGroup, Label, Input, InputGroup, Col, Row } from 'reactstrap'; -import { useForm } from 'react-hook-form'; -import { analyzeSleep } from 'requests/analayze-sleep'; - -const dateFieldSuffix = '-date'; -const timeFieldSuffix = '-time'; - -const filterInDateTimeFields = (data) => - Object.keys(data) - .filter((field) => field.endsWith(timeFieldSuffix)) - .map((field) => field.replace(timeFieldSuffix, '')); - -const filterOutDateTimeFields = (data) => - Object.entries(data).filter(([name, value]) => !(name.endsWith(dateFieldSuffix) || name.endsWith(timeFieldSuffix))); - -const mergeDateTimeFields = (data) => - filterInDateTimeFields(data).map((fieldPrefix) => [ - fieldPrefix, - new Date( - Object.entries(data) - .filter(([fieldName, value]) => fieldName.startsWith(fieldPrefix)) - .map(([fieldName, value]) => value) - .join(' '), - ).getTime(), - ]); - -const onSubmit = async (data) => { - let formData = Object.fromEntries([...filterOutDateTimeFields(data), ...mergeDateTimeFields(data)]); - formData = { ...formData, file: formData.file[0] }; - try { - const results = await analyzeSleep(formData).toPromise(); - console.log(results); - } catch (error) { - console.error(error); - } -}; - -const UploadForm = () => { - const { register, handleSubmit } = useForm(); - - return ( - - - -
-

Please enter your EEG recorded data and the information related to it:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
-
- ); -}; - -export default UploadForm; diff --git a/web/src/views/analyze_sleep/analysis_in_progress.js/index.js b/web/src/views/analyze_sleep/analysis_in_progress.js/index.js new file mode 100644 index 00000000..1d8fb528 --- /dev/null +++ b/web/src/views/analyze_sleep/analysis_in_progress.js/index.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { Redirect } from 'react-router'; +import { Col, Container, Row, Spinner } from 'reactstrap'; + +import useGlobalState from 'hooks/useGlobalState'; + +const AnalysisInProgress = () => { + const [response] = useGlobalState('response'); + + if (!response) { + return ( + +

Analyzing your sleep...

+

Your sleep is currently being analyzed

+ + + + + +
+ ); + } + + return ( + + ); +}; + +export default AnalysisInProgress; diff --git a/web/src/views/analyze_sleep/constants.js b/web/src/views/analyze_sleep/constants.js new file mode 100644 index 00000000..4ad2d0cd --- /dev/null +++ b/web/src/views/analyze_sleep/constants.js @@ -0,0 +1 @@ +export const PING_PERIOD = 1000; diff --git a/web/src/views/analyze-sleep/index.js b/web/src/views/analyze_sleep/index.js similarity index 60% rename from web/src/views/analyze-sleep/index.js rename to web/src/views/analyze_sleep/index.js index 429583ff..1899c398 100644 --- a/web/src/views/analyze-sleep/index.js +++ b/web/src/views/analyze_sleep/index.js @@ -1,16 +1,20 @@ import React, { useEffect, useState } from 'react'; import { Container } from 'reactstrap'; import Header from 'components/header'; -import WaitingForServerToBeReady from './waiting-for-server-to-be-ready'; -import UploadForm from './upload-form'; +import WaitingForServer from './waiting_for_server'; +import UploadForm from './upload_form'; import text from './text.json'; import { periodicPingServer } from 'requests/ping-server'; +import { PING_PERIOD } from './constants'; +import AnalysisInProgress from './analysis_in_progress.js'; +import useGlobalState from 'hooks/useGlobalState'; const AnalyzeSleep = () => { const [serverReady, setServerReady] = useState(false); + const [isFormSubmitted] = useGlobalState('isFormSubmitted'); useEffect(() => { - const subscription = periodicPingServer(200).subscribe( + const subscription = periodicPingServer(PING_PERIOD).subscribe( (ready) => setServerReady(ready), () => null, ); @@ -27,11 +31,14 @@ const AnalyzeSleep = () => { description={text['header_description']} /> - diff --git a/web/src/views/analyze-sleep/text.json b/web/src/views/analyze_sleep/text.json similarity index 100% rename from web/src/views/analyze-sleep/text.json rename to web/src/views/analyze_sleep/text.json diff --git a/web/src/views/analyze_sleep/upload_form/constants.js b/web/src/views/analyze_sleep/upload_form/constants.js new file mode 100644 index 00000000..e5b947e4 --- /dev/null +++ b/web/src/views/analyze_sleep/upload_form/constants.js @@ -0,0 +1,9 @@ +export const MAX_FILE_UPLOAD_SIZE = 1.6e9; +export const ACCEPTED_FILE_TYPE = 'text/plain'; +export const ACCEPTED_FILE_EXTENSION = '.txt'; +export const DEVICES = { + CYTON: 'CYTON', + GANGLION: 'GANGLION', +}; +export const MIN_AGE = 0; +export const MAX_AGE = 125; diff --git a/web/src/views/analyze_sleep/upload_form/index.js b/web/src/views/analyze_sleep/upload_form/index.js new file mode 100644 index 00000000..60703fcb --- /dev/null +++ b/web/src/views/analyze_sleep/upload_form/index.js @@ -0,0 +1,314 @@ +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { Button, Container, CustomInput, Form, FormGroup, Label, Input, InputGroup, Col, Row, Alert } from 'reactstrap'; +import { DateTime } from 'luxon'; + +import { + ACCEPTED_FILE_EXTENSION, + ACCEPTED_FILE_TYPE, + DEVICES, + MAX_AGE, + MAX_FILE_UPLOAD_SIZE, + MIN_AGE, +} from './constants'; + +import './style.css'; +import useGlobalState from 'hooks/useGlobalState'; +import { analyzeSleep } from 'requests/analyze-sleep'; + +const dateFieldSuffix = '_date'; +const timeFieldSuffix = '_time'; + +const filterInDateTimeFields = (data) => + Object.keys(data) + .filter((field) => field.endsWith(timeFieldSuffix)) + .map((field) => field.replace(timeFieldSuffix, '')); + +const filterOutDateTimeFields = (data) => + Object.entries(data).filter(([name, value]) => !(name.endsWith(dateFieldSuffix) || name.endsWith(timeFieldSuffix))); + +const mergeDateTimeFields = (data) => + filterInDateTimeFields(data).map((fieldPrefix) => [ + fieldPrefix, + new Date( + Object.entries(data) + .filter(([fieldName, value]) => fieldName.startsWith(fieldPrefix)) + .map(([fieldName, value]) => value) + .join(' '), + ).getTime(), + ]); + +const postForm = async (formData, setResponse, setPostFormError, setIsFormSubmitted) => { + try { + const response = await analyzeSleep(formData).toPromise(); + setResponse(response); + } catch (error) { + console.error(error); + setPostFormError(error); + setIsFormSubmitted(false); + } +}; + +const UploadForm = () => { + const { register, handleSubmit, getValues, errors } = useForm({ mode: 'onChange' }); + const [, setResponse] = useGlobalState('response'); + const [postFormError, setPostFormError] = useGlobalState('postFormError'); + const [, setIsFormSubmitted] = useGlobalState('isFormSubmitted'); + + return ( + + + +
{ + let formData = Object.fromEntries([...filterOutDateTimeFields(data), ...mergeDateTimeFields(data)]); + formData = { ...formData, file: formData.file[0] }; + setIsFormSubmitted(true); + await postForm(formData, setResponse, setPostFormError, setIsFormSubmitted); + })} + > +

Let's analyze your sleep EEG recording

+
+ { + const file = files[0]; + if (file.type !== ACCEPTED_FILE_TYPE || !file.name.endsWith(ACCEPTED_FILE_EXTENSION)) { + return 'A valid .txt raw OpenBCI EEG file must be selected.'; + } else if (file.size >= MAX_FILE_UPLOAD_SIZE) { + return 'File is too large. Must be less than 1.6 Gb.'; + } + }, + })} + bsSize="lg" + accept=".txt, text/plain" + type="file" + id="file" + name="file" + label={ +
+ + Upload your OpenBCI file +
+ } + /> +
{errors.file?.message}
+
+ +

Journal

+

+ While recording your EEG data, you also gathered some informations about your device, yourself and your + sleep: +

+ + + + + + + + + +
{errors.device?.message}
+ +
+
+ + + + + + + +
{errors.sex?.message}
+
+ + + + + +
{errors.age?.message}
+
+ +
+ +
+ + + + + + { + const streamStart = new Date( + getValues('stream_start_date') + ' ' + getValues('stream_start_time'), + ); + const bedTime = new Date(getValues('bedtime_date') + ' ' + getValues('bedtime_time')); + if (streamStart > bedTime) { + return 'Stream start must be prior to bedtime.'; + } + }, + })} + max={DateTime.local().toISODate()} + type="date" + name={`stream_start${dateFieldSuffix}`} + id={`stream_start${dateFieldSuffix}`} + /> + + +
+ {errors.stream_start_date?.message} + {errors.stream_start_date &&
} + {errors.stream_start_time?.message} +
+
+ +
+ + + + + + { + const bedtime = new Date(getValues('bedtime_date') + ' ' + getValues('bedtime_time')); + const wakeup = new Date(getValues('wakeup_date') + ' ' + getValues('wakeup_time')); + if (bedtime > wakeup) { + return 'Bedtime must be prior to wake up.'; + } + }, + })} + max={DateTime.local().toISODate()} + type="date" + name={`bedtime${dateFieldSuffix}`} + id={`bedtime${dateFieldSuffix}`} + /> + + +
+ {errors.bedtime_date?.message} + {errors.bedtime_date &&
} + {errors.bedtime_time?.message} +
+
+ + + + + + { + const wakeup = new Date(getValues('wakeup_date') + ' ' + getValues('wakeup_time')); + if (wakeup > Date.now()) { + return 'Wake up must be prior to now.'; + } + }, + })} + max={DateTime.local().toISODate()} + type="date" + name={`wakeup${dateFieldSuffix}`} + id={`wakeup${dateFieldSuffix}`} + /> + + +
+ {errors.wakeup_date?.message} + {errors.wakeup_date &&
} + {errors.wakeup_time?.message} +
+
+ +
+
+ + + + + {postFormError && ( + + + + An occured while processing your file... +
+ Make sure you uploaded the correct file. Perhaps the information you entered in your journal does + not match the file provided. +
+
+
+ )} + + +
+
+ + * Your age and sex are used to improve the quality of our prediction. Age and sex are features that have a + significant impact on sleep. + +
+ ); +}; + +export default UploadForm; diff --git a/web/src/views/analyze_sleep/upload_form/style.css b/web/src/views/analyze_sleep/upload_form/style.css new file mode 100644 index 00000000..d497acc9 --- /dev/null +++ b/web/src/views/analyze_sleep/upload_form/style.css @@ -0,0 +1,47 @@ +.upload-form__file-input { + font-size: 5.5em; + margin-left: 2%; +} + +.upload-form__file-input-label-text { + font-size: 1.5em; + margin: 0 10%; + vertical-align: 100%; +} + +.custom-file { + height: 6em; +} + +.custom-file-input { + height: 100%; +} + +.custom-file-label { + height: 100%; + border-radius: 0.5rem; + color: white; + background-color: #515175; +} + +.custom-file-label::after { + height: 100%; + color: white; + background-color: #515175; + padding-top: 2.7em; +} + +.upload-form__journal-title { + margin-top: 3em; +} + +.upload-form__file-input-title { + margin-bottom: 2em; +} + +.upload-form__error-text { + color: #f5365c; + font-size: 0.875em; + font-weight: lighter; + margin-top: 0.5em; +} diff --git a/web/src/views/analyze-sleep/waiting-for-server-to-be-ready.js b/web/src/views/analyze_sleep/waiting_for_server/index.js similarity index 55% rename from web/src/views/analyze-sleep/waiting-for-server-to-be-ready.js rename to web/src/views/analyze_sleep/waiting_for_server/index.js index 547db5f7..597159b1 100644 --- a/web/src/views/analyze-sleep/waiting-for-server-to-be-ready.js +++ b/web/src/views/analyze_sleep/waiting_for_server/index.js @@ -1,9 +1,9 @@ import React from 'react'; -const WaitingForServerToBeReady = () => ( +const WaitingForServer = () => (

Waiting for local server to be running...

); -export default WaitingForServerToBeReady; +export default WaitingForServer; diff --git a/web/src/views/performance/index.js b/web/src/views/performance/index.js index a956a29d..9df05826 100644 --- a/web/src/views/performance/index.js +++ b/web/src/views/performance/index.js @@ -64,27 +64,29 @@ const Performance = () => {

- Ever wonder what is the value of this application? This page aims to illustrate the relative performance of our sleep scoring compared to - clinical hypnogram scoring (which is usually considered the state-of-the-art technique). + Ever wonder what is the value of this application? This page aims to illustrate the relative performance of + our sleep scoring compared to clinical hypnogram scoring (which is usually considered the state-of-the-art + technique).

Here is the plan:

  • - First, we will check how our classifier’s scoring agrees with the scoring within the Physionet's Sleep-EDF dataset. Of course, we will - perform this agreement test on a subset of EEG data that was never trained on. This subset is composed of full nights of sleep coming from - five subject of a different age group.{' '} + First, we will check how our classifier’s scoring agrees with the scoring within the Physionet's Sleep-EDF + dataset. Of course, we will perform this agreement test on a subset of EEG data that was never trained on. + This subset is composed of full nights of sleep coming from five subject of a different age group.{' '}
  • - Then, we will check how this classifier performs on a full night recorded on one of our members. In order to be able to make comparisons, - we ask for the help of a medical electrophysiologist to score our data. This manual scoring will serve as reference to get an idea of the - accuracy of our model on data acquired using an OpenBCI under non-clinical conditions. The AASM manual was used for scoring. + Then, we will check how this classifier performs on a full night recorded on one of our members. In order to + be able to make comparisons, we ask for the help of a medical electrophysiologist to score our data. This + manual scoring will serve as reference to get an idea of the accuracy of our model on data acquired using an + OpenBCI under non-clinical conditions. The AASM manual was used for scoring.
  • - Finally, we will present the scoring differences between the medical electrophysiologist and Sleep-EDF. To do this, we will take a random - night in our dataset. This will allow us to qualify somewhat the previous results and maybe get an idea of the usual disagreement level - between professional scorers. + Finally, we will present the scoring differences between the medical electrophysiologist and Sleep-EDF. To + do this, we will take a random night in our dataset. This will allow us to qualify somewhat the previous + results and maybe get an idea of the usual disagreement level between professional scorers.

Classifier's accuracy according to Sleep-EDF

@@ -105,7 +107,11 @@ const Performance = () => {

Classifier's accuracy according to the electrophysiologist

createComparativeHypnogram(svg, data, ['Classifier', 'Electrophysiologist'])} - data={csvDataPredictedOpenBCI && csvDataOpenBCIElectrophysiologist ? [csvDataPredictedOpenBCI, csvDataOpenBCIElectrophysiologist] : null} + data={ + csvDataPredictedOpenBCI && csvDataOpenBCIElectrophysiologist + ? [csvDataPredictedOpenBCI, csvDataOpenBCIElectrophysiologist] + : null + } /> { - const csvDataSleepEDF = useCSVData(hypnogramDataSleepEDFPath); - - return ( -
-
- - - - -

- Of course, we are analyzing only one night of sleep so it is therefore - tricky to draw general conclusions about your sleep. It is however - fascinating to see how your night was. -

-

Without further ado, this is what was your night of sleep:

- -

- We have seen that sleep can be decomposed in mainly two stages, - whereas REM and NREM, and that we can observe different stage - proportions across age, gender and different sleep disorders. We’ve - also defined other measures of your sleep architecture, such as your - sleep latency, efficiency and total sleep time. In order to improve - your sleep hygiene, many elements can be considered: -

-
    -
  • - Alimentation: having a balanced diet and avoiding sources of - caffeine can have a positive impact on one’s sleep. Chocolate, soft - drink, tea and decaffeinated coffee are unexpected sources of - caffeine. -
  • -
  • - Routine: going to sleep about at the same time, in a darkened and - quiet environment. -
  • -
  • - Routine: going to sleep about at the same time, in a darkened and - quiet environment. -
  • -
  • - Routine: going to sleep about at the same time, in a darkened and - quiet environment. -
  • -
-

- Although we’ve looked at many aspects of your night’s sleep, we - haven’t properly looked at your sleep dynamics, whereas how your sleep - evolves overnight. -

-

Hypnogram

-

- A hypnogram allows you to visually inspect the evolution of your - night, through time. The vertical axis represents how hard it is to - wake up, namely the sleep deepness. We see that REM is one of the - lightest sleep stages (along with N1), because we unknowingly wake up - from that stage. Those short periods of arousal often last no longer - than 15 seconds, are followed by a lighter sleep stage, and cannot be - remembered the next morning. If they are too frequent, they can affect - your sleep quality. [5] We can see that, throughout the night, stages - follow about the same pattern, whereas we go from NREM (either N1, N2 - and N3) and then to REM, and so on. We call those sleep cycles, and - those typically range from four to six, each one lasting from 90 to - 110 minutes. Another commonly looked at measurement is the time - between sleep onset and the first REM epoch, namely REM latency, which - corresponds to 20 minutes. -

- -

- Sleep cycles take place in a broader process, named the circadian - rhythm. It is the one that regulates our wake and sleep cycles over a - 24 hours period. -

-

- You’ve been able to visualize and inspect your night of sleep, which - we’ve classified only based on your EEG recordings. In a sleep lab, - electrophysiology technicians generally look at your EEG, EOG and - submental EMG, and then manually classify each epoch of 30 seconds - that compose your night. By looking at your EEG recordings, we can see - some patterns that can help electrophysiology technicians, and our - classifier, discriminate sleep stages throughout the night. -

-

Spectrogram

-

- Above, we can see the same chart from the first visualization, which - represents your sleep stages through the night. Below it, there are - spectrograms of both your EEG channels. Spectrograms can be viewed as - if we took all of your nights signal, we’ve separated it in contiguous - 30 seconds chunks, stacked then horizontally and to which we’ve - applied the fast fourier transform. We then have, for each 30 seconds - epoch, the corresponding amplitudes for each frequency that makes up - the signal, hence the spectra. We then converted the scale to - logarithmic, to better see the differences in the spectrums. We then - speak of signal power instead of signal amplitude, because we look at - the spectrums in a logarithmic scale. -

-

- How to read it? -

-

- Red therefore means that in that 30 seconds time frame, that - particular frequency had a big amplitude. Green means that you had - that frequency with a lower amplitude. Dark blue means that you didn’t - have that frequency in the signal. -

-

- To get a better understanding at how spectrograms work, you can check - out - - {' '} - this visualization{' '} - - that decomposes sound frequency from your microphone. -

- -

- Generally, when talking about brain waves, we group certain - frequencies together into bands. There are overall five frequency - bands, where each has a general associated behaviour, or state of - mind. We will cover those when looking at time frames corresponding to - each sleep stage. -

-

- We can associate wake stages with low-amplitude activity in the 15 to - 60 Hz frequency range, called the beta band. By slowly falling asleep, - the signal frequencies tend to decrease into the 4 to 8 Hz range, or - the theta band, and to have larger amplitudes. These characteristics - are associated with N1. N2 stage has the same characteristics, and - also includes sleep spindles. They last only a few seconds and are a - large oscillation in the 10 to 15 hz band. Because they do not occur - during all of the 30 seconds period, they cannot be seen here. Stage - N3, also called slow wave sleep, is characterized by slower waves - between 0.5 and 4 Hz, known as the delta range, with large amplitudes. - REM stage has the same characteristics as Wake stage, whereas there - are low voltage high frequency activity. -

-

Wanna know how accurate this data is?

- -
-
- ); -}; - -export default SleepAnalysis; diff --git a/web/src/views/sleep-analysis/spectrogram_scrollytelling.js b/web/src/views/sleep-analysis/spectrogram_scrollytelling.js deleted file mode 100644 index 0e1c0343..00000000 --- a/web/src/views/sleep-analysis/spectrogram_scrollytelling.js +++ /dev/null @@ -1,169 +0,0 @@ -import React, { useState } from 'react'; -import { Container, Card, CardBody } from 'reactstrap'; - -import { HYPNOGRAM_KEY } from '../../d3/spectrogram/constants'; -import createSpectrogram, { - spectrogramCallbacks, -} from '../../d3/spectrogram/spectrogram'; -import D3ComponentScrollyTelling from '../../components/d3component_scrollytelling'; -import WaypointDirection from '../../components/waypoint_direction'; - -import { useCSVData } from '../../hooks/api_hooks'; - -import hypnogramDataSleepEDFPath from 'assets/data/hypnogram-openbci-predicted.csv'; -import spectrogramData from 'assets/data/spectrograms-openbci-predicted.json'; - -const SpectrogramScrollyTelling = () => { - const csvDataSleepEDF = useCSVData(hypnogramDataSleepEDFPath); - const spectrogramWithHypnogramData = csvDataSleepEDF - ? { ...spectrogramData, [HYPNOGRAM_KEY]: csvDataSleepEDF } - : null; - const [isInitialized, setIsInitialized] = useState(false); - - return ( - -
- -
-
- - -

- Here is represented spectrograms of both your EEG channels. - Spectrograms can be viewed as if we took all of your nights signal, - we’ve separated it in contiguous 30 seconds chunks, stacked then - horizontally and to which we’ve applied the fast fourier transform. - We then have, for each 30 seconds epoch, the corresponding - amplitudes for each frequency that makes up the signal, hence the - spectra. -

-

- We then converted the scale to logarithmic, to better see the - differences in the spectrums. We then speak of signal power instead - of signal amplitude, because we look at the spectrums in a - logarithmic scale. -

-
How to read it?
-

- Red therefore means that in that 30 seconds time frame, that - particular frequency had a big amplitude. Green means that you had - that frequency with a lower amplitude. Dark blue means that you - didn’t have that frequency in the signal. -

-

- To get a better understanding at how spectrograms work, you can{' '} - - check out this example - {' '} - that decomposes sound frequency from your microphone. -

-
-
-
- - -

- Generally, when talking about brain waves, we group certain - frequencies together into bands. There are overall five frequency - bands, where each has a general associated behaviour, or state of - mind. We will cover those when looking at time frames corresponding - to each sleep stage. -

-
-
- {isInitialized && ( - - )} -
- - -

- We can associate wake stages with low-amplitude activity in the 15 - to 60 Hz frequency range, called the beta band. [6] -

-
-
- {isInitialized && ( - - )} -
- - -

- By slowly falling asleep, the signal frequencies tend to decrease - into the 4 to 8 Hz range, or the theta band, and to have larger - amplitudes. These characteristics are associated with N1. -

-
-
- {isInitialized && ( - - )} -
- - -

- N2 stage has the same characteristics as N1, and also includes sleep - spindles. They last only a few seconds and are a large oscillation - in the 10 to 15 hz band. Because they do not occur during all of the - 30 seconds period, they cannot be seen here. [6] -

-
-
- {isInitialized && ( - - )} -
- - -

- Stage N3, also called slow wave sleep, is characterized by slower - waves between 0.5 and 4 Hz, known as the delta range, with large - amplitudes. [6] -

-
-
- {isInitialized && ( - - )} -
- - -

- REM stage has the same characteristics as Wake stage, whereas there - are low voltage high frequency activity. [6] -

-
-
-
-   - - ); -}; - -export default SpectrogramScrollyTelling; diff --git a/web/src/views/sleep_analysis_results/index.js b/web/src/views/sleep_analysis_results/index.js new file mode 100644 index 00000000..3c30dbd3 --- /dev/null +++ b/web/src/views/sleep_analysis_results/index.js @@ -0,0 +1,154 @@ +import React from 'react'; +import { Container, Row, Button } from 'reactstrap'; +import { Link, Redirect } from 'react-router-dom'; + +import Header from 'components/header'; +import D3Component from 'components/d3component'; +import WIPWarning from 'components/wip_warning'; + +import { createSingleHypnogram } from 'd3/hypnogram/hypnogram'; + +import text from './text.json'; +import StackedBarChartScrollyTelling from './stacked_bar_chart_scrollytelling'; +import SpectrogramScrollyTelling from './spectrogram_scrollytelling'; +import useGlobalState from 'hooks/useGlobalState'; + +import './style.css'; + +const SleepAnalysisResults = () => { + const [response] = useGlobalState('response'); + if (!response) { + return ( + + ); + } + const data = response.data; + + return ( +
+
+ + + + +

+ Of course, we are analyzing only one night of sleep so it is therefore tricky to draw general conclusions + about your sleep. It is however fascinating to see how your night was. +

+

Without further ado, this is what was your night of sleep:

+ +

+ We have seen that sleep can be decomposed in mainly two stages, whereas REM and NREM, and that we can observe + different stage proportions across age, gender and different sleep disorders. We’ve also defined other + measures of your sleep architecture, such as your sleep latency, efficiency and total sleep time. In order to + improve your sleep hygiene, many elements can be considered: +

+
    +
  • + Alimentation: having a balanced diet and avoiding sources of caffeine can have a positive impact on one’s + sleep. Chocolate, soft drink, tea and decaffeinated coffee are unexpected sources of caffeine. +
  • +
  • Routine: going to sleep about at the same time, in a darkened and quiet environment.
  • +
  • Routine: going to sleep about at the same time, in a darkened and quiet environment.
  • +
  • Routine: going to sleep about at the same time, in a darkened and quiet environment.
  • +
+

+ Although we’ve looked at many aspects of your night’s sleep, we haven’t properly looked at your sleep + dynamics, whereas how your sleep evolves overnight. +

+

Hypnogram

+

+ A hypnogram allows you to visually inspect the evolution of your night, through time. The vertical axis + represents how hard it is to wake up, namely the sleep deepness. We see that REM is one of the lightest sleep + stages (along with N1), because we unknowingly wake up from that stage. Those short periods of arousal often + last no longer than 15 seconds, are followed by a lighter sleep stage, and cannot be remembered the next + morning. If they are too frequent, they can affect your sleep quality. [5] We can see that, throughout the + night, stages follow about the same pattern, whereas we go from NREM (either N1, N2 and N3) and then to REM, + and so on. We call those sleep cycles, and those typically range from four to six, each one lasting from 90 to + 110 minutes. Another commonly looked at measurement is the time between sleep onset and the first REM epoch, + namely REM latency, which corresponds to 20 minutes. +

+ +

+ Sleep cycles take place in a broader process, named the circadian rhythm. It is the one that regulates our + wake and sleep cycles over a 24 hours period. +

+

+ You’ve been able to visualize and inspect your night of sleep, which we’ve classified only based on your EEG + recordings. In a sleep lab, electrophysiology technicians generally look at your EEG, EOG and submental EMG, + and then manually classify each epoch of 30 seconds that compose your night. By looking at your EEG + recordings, we can see some patterns that can help electrophysiology technicians, and our classifier, + discriminate sleep stages throughout the night. +

+

Spectrogram

+

+ Above, we can see the same chart from the first visualization, which represents your sleep stages through the + night. Below it, there are spectrograms of both your EEG channels. Spectrograms can be viewed as if we took + all of your nights signal, we’ve separated it in contiguous 30 seconds chunks, stacked then horizontally and + to which we’ve applied the fast fourier transform. We then have, for each 30 seconds epoch, the corresponding + amplitudes for each frequency that makes up the signal, hence the spectra. We then converted the scale to + logarithmic, to better see the differences in the spectrums. We then speak of signal power instead of signal + amplitude, because we look at the spectrums in a logarithmic scale. +

+

+ How to read it? +

+

+ Red therefore means that in that 30 seconds time frame, that particular frequency had a big amplitude. Green + means that you had that frequency with a lower amplitude. Dark blue means that you didn’t have that frequency + in the signal. +

+

+ To get a better understanding at how spectrograms work, you can check out + + {' '} + this visualization{' '} + + that decomposes sound frequency from your microphone. +

+ +

+ Generally, when talking about brain waves, we group certain frequencies together into bands. There are overall + five frequency bands, where each has a general associated behaviour, or state of mind. We will cover those + when looking at time frames corresponding to each sleep stage. +

+

+ We can associate wake stages with low-amplitude activity in the 15 to 60 Hz frequency range, called the beta + band. By slowly falling asleep, the signal frequencies tend to decrease into the 4 to 8 Hz range, or the theta + band, and to have larger amplitudes. These characteristics are associated with N1. N2 stage has the same + characteristics, and also includes sleep spindles. They last only a few seconds and are a large oscillation in + the 10 to 15 hz band. Because they do not occur during all of the 30 seconds period, they cannot be seen here. + Stage N3, also called slow wave sleep, is characterized by slower waves between 0.5 and 4 Hz, known as the + delta range, with large amplitudes. REM stage has the same characteristics as Wake stage, whereas there are + low voltage high frequency activity. +

+

Wanna know how accurate this data is?

+ + + + +
+
+ ); +}; + +export default SleepAnalysisResults; diff --git a/web/src/views/sleep_analysis_results/spectrogram_scrollytelling.js b/web/src/views/sleep_analysis_results/spectrogram_scrollytelling.js new file mode 100644 index 00000000..e5b8b069 --- /dev/null +++ b/web/src/views/sleep_analysis_results/spectrogram_scrollytelling.js @@ -0,0 +1,117 @@ +import React, { useState } from 'react'; +import { Container, Card, CardBody } from 'reactstrap'; + +import createSpectrogram, { spectrogramCallbacks } from '../../d3/spectrogram/spectrogram'; +import D3ComponentScrollyTelling from '../../components/d3component_scrollytelling'; +import WaypointDirection from '../../components/waypoint_direction'; + +const SpectrogramScrollyTelling = ({ spectrograms, epochs }) => { + const [isInitialized, setIsInitialized] = useState(false); + + return ( + +
+ +
+
+ + +

+ Here is represented spectrograms of both your EEG channels. Spectrograms can be viewed as if we took all of + your nights signal, we’ve separated it in contiguous 30 seconds chunks, stacked then horizontally and to + which we’ve applied the fast fourier transform. We then have, for each 30 seconds epoch, the corresponding + amplitudes for each frequency that makes up the signal, hence the spectra. +

+

+ We then converted the scale to logarithmic, to better see the differences in the spectrums. We then speak of + signal power instead of signal amplitude, because we look at the spectrums in a logarithmic scale. +

+
How to read it?
+

+ Red therefore means that in that 30 seconds time frame, that particular frequency had a big amplitude. Green + means that you had that frequency with a lower amplitude. Dark blue means that you didn’t have that + frequency in the signal. +

+

+ To get a better understanding at how spectrograms work, you can{' '} + + check out this example + {' '} + that decomposes sound frequency from your microphone. +

+
+
+
+ + +

+ Generally, when talking about brain waves, we group certain frequencies together into bands. There are + overall five frequency bands, where each has a general associated behaviour, or state of mind. We will cover + those when looking at time frames corresponding to each sleep stage. +

+
+
+ {isInitialized && } +
+ + +

+ We can associate wake stages with low-amplitude activity in the 15 to 60 Hz frequency range, called the beta + band. [6] +

+
+
+ {isInitialized && } +
+ + +

+ By slowly falling asleep, the signal frequencies tend to decrease into the 4 to 8 Hz range, or the theta + band, and to have larger amplitudes. These characteristics are associated with N1. +

+
+
+ {isInitialized && } +
+ + +

+ N2 stage has the same characteristics as N1, and also includes sleep spindles. They last only a few seconds + and are a large oscillation in the 10 to 15 hz band. Because they do not occur during all of the 30 seconds + period, they cannot be seen here. [6] +

+
+
+ {isInitialized && } +
+ + +

+ Stage N3, also called slow wave sleep, is characterized by slower waves between 0.5 and 4 Hz, known as the + delta range, with large amplitudes. [6] +

+
+
+ {isInitialized && } +
+ + +

+ REM stage has the same characteristics as Wake stage, whereas there are low voltage high frequency activity. + [6] +

+
+
+
+   + + ); +}; + +export default SpectrogramScrollyTelling; diff --git a/web/src/views/sleep-analysis/stacked_bar_chart_scrollytelling.js b/web/src/views/sleep_analysis_results/stacked_bar_chart_scrollytelling.js similarity index 51% rename from web/src/views/sleep-analysis/stacked_bar_chart_scrollytelling.js rename to web/src/views/sleep_analysis_results/stacked_bar_chart_scrollytelling.js index 0f1f8fb1..9b14459a 100644 --- a/web/src/views/sleep-analysis/stacked_bar_chart_scrollytelling.js +++ b/web/src/views/sleep_analysis_results/stacked_bar_chart_scrollytelling.js @@ -1,8 +1,6 @@ import React, { useState } from 'react'; import { Container, Card, CardBody } from 'reactstrap'; -import hypnogramCSVPath from 'assets/data/hypnogram-openbci-predicted.csv'; - import D3ComponentScrollyTelling from 'components/d3component_scrollytelling'; import WaypointDirection from 'components/waypoint_direction'; @@ -12,10 +10,8 @@ import createEvolvingChart, { barChartCallbacks, stackedBarChartCallbacks, } from 'd3/evolving_chart/evolving_chart'; -import { useCSVData } from 'hooks/api_hooks'; -const StackedBarChartScrollyTelling = () => { - const csvData = useCSVData(hypnogramCSVPath); +const StackedBarChartScrollyTelling = ({ epochs }) => { const [isInitialized, setIsInitialized] = useState(false); return ( @@ -23,7 +19,7 @@ const StackedBarChartScrollyTelling = () => {
@@ -32,50 +28,31 @@ const StackedBarChartScrollyTelling = () => {

- We can see that each colored block represents a part of your night. - They are ordered from bed time to out of bed timestamps you’ve - written in your journal. Each color is associated with a specific - sleep stage. You went to bed at 12:22 am and you got out of bed at - 9:47 am, which adds up to 9 hours and 25 minutes of time spent in - bed. Of this total time, you spent 7 hours and 27 minutes actually - sleeping. You first fell asleep at XX:XX, to which we will refer to - as sleep onset. The last non wake block ended at XX:XX, which can - also be referred to as sleep offset. During that night's sleep, you - went through each of the 5 five stages. Let's try to see a little - better what happened about each of them. + We can see that each colored block represents a part of your night. They are ordered from bed time to out of + bed timestamps you’ve written in your journal. Each color is associated with a specific sleep stage. You + went to bed at 12:22 am and you got out of bed at 9:47 am, which adds up to 9 hours and 25 minutes of time + spent in bed. Of this total time, you spent 7 hours and 27 minutes actually sleeping. You first fell asleep + at XX:XX, to which we will refer to as sleep onset. The last non wake block ended at XX:XX, which can also + be referred to as sleep offset. During that night's sleep, you went through each of the 5 five stages. Let's + try to see a little better what happened about each of them.

{isInitialized && ( - + )}
-

- Wake stage is of course the stage we want to minimize when in bed. - It can be decomposed into two parts: -

+

Wake stage is of course the stage we want to minimize when in bed. It can be decomposed into two parts:

    +
  • Sleep latency : Time spent before falling asleep, which corresponds to X minutes in your case.
  • +
  • Wake after sleep onset (WASO): Time spent awake after first falling asleep and before waking up.
  • {' '} - Sleep latency : Time spent before falling asleep, which - corresponds to X minutes in your case.{' '} -
  • -
  • - {' '} - Wake after sleep onset (WASO): Time spent awake after first - falling asleep and before waking up.{' '} -
  • -
  • - {' '} - For healthy adults, it is normal to experience small awakenings - during the night. Unprovoked awakenings are mostly during or after - REM stages.{' '} + For healthy adults, it is normal to experience small awakenings during the night. Unprovoked awakenings + are mostly during or after REM stages.{' '}
@@ -84,45 +61,36 @@ const StackedBarChartScrollyTelling = () => {

- REM stage stands for “Rapid Eyes Movements” and is - also known as “paradoxical sleep”. It is associated with dreaming - and, as the National Sleep Foundation says,{' '} + REM stage stands for “Rapid Eyes Movements” and is also known as “paradoxical sleep”. It is + associated with dreaming and, as the National Sleep Foundation says,{' '} “the brain is awake and body paralyzed.”

- N1 stage is associated with that drowsy feeling - before falling asleep. Most people wouldn’t say they fell asleep if - they’ve been woken up from N1 sleep. + N1 stage is associated with that drowsy feeling before falling asleep. Most people wouldn’t + say they fell asleep if they’ve been woken up from N1 sleep.

- N2 stage still corresponds to a light sleep, but - where the muscle activity decreases more, and the eyes have stopped - moving. It is called, along with N1, light sleep. + N2 stage still corresponds to a light sleep, but where the muscle activity decreases more, + and the eyes have stopped moving. It is called, along with N1, light sleep.

- N3 stage is when you are deeply asleep, hence it’s - also called deep sleep, or sometimes{' '} - slow wave sleep, and is the most difficult to wake - up from. It is during those stages that your cells get repaired, and - that tissue grows. But how much time did you spend in each stage + N3 stage is when you are deeply asleep, hence it’s also called deep sleep, + or sometimes slow wave sleep, and is the most difficult to wake up from. It is during those + stages that your cells get repaired, and that tissue grows. But how much time did you spend in each stage during the whole night?

{isInitialized && ( - + )}

- From here, we can look at your sleep efficiency, which is the - proportion of time spent asleep over the overall time spent in bed. - In your case, it corresponds to 79%, or 7h27. + From here, we can look at your sleep efficiency, which is the proportion of time spent asleep over the + overall time spent in bed. In your case, it corresponds to 79%, or 7h27.

@@ -130,11 +98,9 @@ const StackedBarChartScrollyTelling = () => {

- We are currently looking at your in bed sleep stage proportions. - Wake time may be overrepresented, because it includes your sleep - latency and the time you spent in bed after waking up. In order to - look at your actual stage proportions, we must cut them out from - wake time to only keep WASO. + We are currently looking at your in bed sleep stage proportions. Wake time may be overrepresented, because + it includes your sleep latency and the time you spent in bed after waking up. In order to look at your + actual stage proportions, we must cut them out from wake time to only keep WASO.

@@ -142,9 +108,8 @@ const StackedBarChartScrollyTelling = () => {

- We can see that the most prominent sleep stage is N2, which in your - case corresponds to XXXX. How does your night compare to other - people’s night? + We can see that the most prominent sleep stage is N2, which in your case corresponds to XXXX. How does your + night compare to other people’s night?

@@ -159,9 +124,8 @@ const StackedBarChartScrollyTelling = () => {

- As a rule of thumb, adults approximately stay 5% of their total - sleep time in N1; 50% in N2; and 20% is in N3. The remaining 25% is - REM stage sleep. + As a rule of thumb, adults approximately stay 5% of their total sleep time in N1; 50% in N2; and 20% is in + N3. The remaining 25% is REM stage sleep.

diff --git a/web/src/views/sleep_analysis_results/style.css b/web/src/views/sleep_analysis_results/style.css new file mode 100644 index 00000000..43784a03 --- /dev/null +++ b/web/src/views/sleep_analysis_results/style.css @@ -0,0 +1,5 @@ +.scrollytelling-container__buttons { + display: flex; + justify-content: space-between; + margin: 1rem 0.1rem; +} diff --git a/web/src/views/sleep-analysis/text.json b/web/src/views/sleep_analysis_results/text.json similarity index 100% rename from web/src/views/sleep-analysis/text.json rename to web/src/views/sleep_analysis_results/text.json diff --git a/web/yarn.lock b/web/yarn.lock index f4ba9f46..0af92a60 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -7147,6 +7147,11 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +luxon@^1.25.0: + version "1.25.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.25.0.tgz#d86219e90bc0102c0eb299d65b2f5e95efe1fe72" + integrity sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ== + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -7469,11 +7474,6 @@ moment@2.24.0: resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== -moment@^2.27.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.0.tgz#fcbef955844d91deb55438613ddcec56e86a3425" - integrity sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA== - move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -9394,6 +9394,11 @@ react-hook-form@^6.8.4: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-6.9.0.tgz#e3fb510970cdf1fbab4e1095cc7e6e2a97935643" integrity sha512-QLRUYI+vZfoFu4mF2CaKeq5tHgLvTe+H2ks3iVKaG07D9nnbV/pemWkg93ODb0l4kFsRb28Ti3s/1K1DHi8Z0g== +react-hooks-global-state@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/react-hooks-global-state/-/react-hooks-global-state-1.0.1.tgz#3e4e1009f3e8a6fd001cd507808b4f1b289e9b27" + integrity sha512-GFe/7KOdf8toByd8eF5LBhogMn65XSyghIzInsnA1e8vqIxGdT2Ohs2Ohdclp8CX0QU9xNj72ZIt+WD/SQEh9Q== + react-is@^16.6.0, react-is@^16.6.3, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"