diff --git a/assets/custom-functions.js b/assets/custom-functions.js index 708bbe3c12be3f36a9e5bce5415153f3c09a92d6..a5b12214d7b8d1e6b25b88cfb56def40859e5cd4 100644 --- a/assets/custom-functions.js +++ b/assets/custom-functions.js @@ -145,3 +145,63 @@ $(document).ready(function () { }; }); }); + +// COLLAPSE NAVBAR HAMBURGER RESIZE LARGER +$(window).on('resize', function () { + if (window.innerWidth > 1045) $('.navbar-collapse').removeClass('show') +}) + +$(document).ready(function () { + $(document).on('click', ".SingleDatePickerInput_clearDate", function() { + var date = document.getElementById('date'); + //ADD SPACES AT APPROPRIATE AREAS FOR DATE SELECTOR + date.addEventListener('input', function(e) { + this.type = 'text'; + var input = this.value; + + //CREATE FUNCTION TO ALLOW DELETION OF SPACES + date.onkeydown = function() { + var key = event.keyCode || event.charCode; + inputText = String(input); + if(key == 8 && (input.length==2 || input.length==6)){ + this.value = inputText.substring(0, inputText.length); + return + } + }; + + //ESCAPE FUNCTION IF FINAL CHARACTER IS ENTERED + if(input.length == 11) return; + var values = input.split(' '); + // ADD FUNCTION TO ADD SPACES + var output = values.map(function(v, i) { + // ADD SPACE AFTER FIRST TWO CHARACTERS + if (v.length == 2 && i < 1){ + return v = v + ' '; + // ADD SPACE AFTER SECOND THREE CHARACTERS + }else if (v.length == 3 && i < 2){ + return v = v + ' '; + } else { + return v + } + }); + this.value = output.join('').substr(0, 11); + }); + }); +}); + +// // CREATE WAIT FUNCTION TO WAIT FOR ELEMENT TO BECOME AVAILABLE +// function waitForElement(selector, func, timeout = 1000) { +// //wait for element to be available to change its position +// const start = Date.now(); +// let interval = setInterval(() => { +// var el = document.getElementById(selector); +// if (el) { +// clearInterval(interval); +// return func(el); +// } else if (Date.now() - start > timeout) { +// clearInterval(interval); +// } +// }, 100); +// } +// + diff --git a/assets/sidebar.css b/assets/sidebar.css index 7114bac632bdd44b412b6211756151cafa4690b4..baec1cacb2b19ed7669329e7b2e86fa0aa4ab129 100644 --- a/assets/sidebar.css +++ b/assets/sidebar.css @@ -1,19 +1,25 @@ /* The side navigation menu */ :root { --blue: #2B383E; + --dark_grey: #6c757d; --yellow: #F1B545; } .sidebar { - position: absolute; - top: 0; - left: 0; - width: 200px; - height: 100%; - font-family: "Roboto", sans-serif; - color: var(--blue) !important; - background-color: #FAFAFA; - overflow: hidden; + /* display: flex; */ + /* flex-direction: column; */ + /* flex-basis: 200px; */ + + position: absolute; + top: 0; + left: 0; + width: 200px; + height: 100%; + font-family: "Roboto", sans-serif; + color: var(--blue) !important; + background-color: #FAFAFA; + overflow: hidden; + z-index: 10000; } .sidebar-first-item { @@ -206,10 +212,12 @@ position: absolute; width: 100%; /* height: 48px; */ + /* height: 5vh; */ margin: 0; padding: 0; bottom: 0px; z-index: 1002 !important; + border-right: 1px solid var(--dark_grey); } /* Info button on the sidebar bottom */ @@ -217,17 +225,22 @@ background-image: url("/daily_dashboard/assets/images/Icon_Info.png"); background-position: center; width: 52px !important; - height: 52px !important; + /* height: 52px !important; */ + /* height: 46.8px; */ + height: 5vh; + background-color: var(--blue); } /* Download button on the sidebar bottom */ .sidebar-bottom #download-button { background-image: url("/daily_dashboard/assets/images/Icon_Download.png"); background-position: 1rem; - background-color: var(--blue); - padding-left: 2rem; + background-color: #6C7B82; width: 100%; - height: 100%; + height: 5vh; + color: white; + font-weight: 700; + padding-top: 0.6rem; } #models-apply, @@ -420,17 +433,17 @@ div.tab-parent { bottom: 0; } -/* On screens that are less than 700px wide, make the sidebar into a topbar */ -@media screen and (max-width: 700px) { - .sidebar { - width: 100%; - height: auto; - position: relative; - } - .sidebar a {float: left;} - div.content {margin-left: 0;} -} - +/* /* On screens that are less than 700px wide, make the sidebar into a topbar */ */ +/* @media screen and (max-width: 700px) { */ +/* .sidebar { */ +/* width: 100%; */ +/* height: auto; */ +/* position: relative; */ +/* } */ +/* .sidebar a {float: left;} */ +/* div.content {margin-left: 0;} */ +/* } */ +/**/ /* On screens that are less than 400px, display the bar vertically, instead of horizontally */ @media screen and (max-width: 400px) { .sidebar a { diff --git a/assets/style.css b/assets/style.css index 75570f94bb8ff19896a3251d41316b7527d8d040..6be0e97a0a549687b992e73dc9226fef26427e19 100644 --- a/assets/style.css +++ b/assets/style.css @@ -1,10 +1,12 @@ :root { --blue: #2B383E; + --dark_grey: #6c757d; --yellow: #F1B545; } body { - background-color: #212529; + overflow-y: hidden; + background-color: #212529; } a.modebar-btn { @@ -108,73 +110,68 @@ a.modebar-btn { } .description-title>p { - height: 35px; - color: #2Ferror_7; - font-family: "Libre Baskerville"; - font-size: 24px; - font-weight: bold; - letter-spacing: 0; - line-height: 35px; - margin: 1rem; + height: 35px; + font-family: "Libre Baskerville"; + font-size: 24px; + font-weight: bold; + letter-spacing: 0; + line-height: 35px; + margin: 1rem; } .description-body>p { - /*height: 47px;*/ - opacity: 0.8; - color: var(--blue); - font-family: "Roboto", sans-serif !important; - font-size: 14px; - letter-spacing: 0; - line-height: 22px; - margin: .5rem 1rem; + opacity: 0.8; + color: var(--blue); + font-family: "Roboto", sans-serif !important; + font-size: 14px; + letter-spacing: 0; + line-height: 22px; + margin: .5rem 1rem; } .disclaimer>p, .disclaimer>span>p { - position: relative; - opacity: 0.8; - background-color: #ffffff !important; - color: var(--blue); - font-family: "Roboto", sans-serif !important; - font-size: 14px; - letter-spacing: 0; - line-height: 22px; - margin-left: .5rem; - margin-top: .1rem; - bottom: .1rem; - display: block; - z-index: 1000 !important; -} - -.disclaimer>span#forecast-issued>p { - float: left; + position: relative; + opacity: 0.8; + background-color: #ffffff !important; + color: var(--blue); + font-family: "Roboto", sans-serif !important; + font-size: 14px; + letter-spacing: 0; + line-height: 22px; + margin-left: .5rem; + margin-top: .1rem; + bottom: .1rem; + display: block; + z-index: 1000 !important; } + .disclaimer>span#forecast-disclaimer>p { float: right; - margin-right: 2rem; + margin-right: 1rem; } .layout-dropdown .disclaimer>p { - margin: .3rem 0rem; + margin: .3rem 0rem; } .btn-link { - color: var(--blue); - font-family: "Roboto", sans-serif !important; + color: var(--blue); + font-family: "Roboto", sans-serif !important; } #eval-apply, #scores-apply, #scores-map-apply { - background-color: var(--blue); - color: #FFFFFF; - font-family: "Roboto", sans-serif !important; - letter-spacing: 0; - height: 36px; - border: none; - border-radius: 0; - text-align: center; + background-color: var(--blue); + color: #FFFFFF; + font-family: "Roboto", sans-serif !important; + letter-spacing: 0; + height: 36px; + border: none; + border-radius: 0; + text-align: center; } button#scores-apply, button#eval-apply { - width: 5rem; + width: 5rem; } button#scores-map-apply { @@ -195,7 +192,7 @@ button#scores-map-apply { font-size: 1rem; font-weight: bold; text-transform: uppercase; - padding: 0.9rem 0rem 0.6rem 1.1rem; + padding: 0.7rem 0rem 0.6rem 1.1rem; background-color: var(--blue) !important; border: none !important; cursor: pointer; @@ -225,24 +222,11 @@ input.DateInput_input { border-right: 0.3em solid transparent; border-bottom: 0.3em solid; border-left: 0.3em solid transparent; - /* display: inline-block; - font-variant: normal; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - font-family: "Font Awesome 5 Brands","Font Awesome 5 Free"; - font-size: 15px; - font-weight: 900; - font-style: normal; - content: "\f107"; - float: right; - color: grey; - float: right; - */ } div.SingleDatePickerInput { border: none; - background-color: var(--blue) !important; + background-color: var(--blue) !important; } .SingleDatePicker_picker { @@ -255,14 +239,14 @@ div.SingleDatePickerInput { } .SingleDatePickerInput_clearDate { - width: 34px; - height: 34px; - outline: none !important; - margin: 2px 3px 3px 5px; + width: 34px; + height: 34px; + outline: none !important; + margin: 2px 3px 3px 5px; } .SingleDatePickerInput_clearDate_svg { - visibility: hidden; + visibility: hidden; fill: white; height: 15px; width: 15px; @@ -291,11 +275,11 @@ div.SingleDatePickerInput { #was-slider-graph, #prob-slider-graph, #obs-vis-slider-graph { width: 10rem; - padding: 0.4rem 35px 25px !important; + padding: 0.3rem 35px 25px !important; } #slider-graph, #obs-slider-graph, #obs-aod-slider-graph { - padding: 0.4rem 25px 25px !important; + padding: 0.2rem 25px 25px !important; width: 30rem; } @@ -303,7 +287,6 @@ div.SingleDatePickerInput { display: block; float: left; margin: 0.5rem; - /* width: 1rem; */ } #rgb-buttons .btn { @@ -326,18 +309,6 @@ div.SingleDatePickerInput { color: #FFFFFF !important; } - -.centered-image { - /* text-align: center !important; - text-align: -moz-center !important; */ -/* - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 3rem; -*/ -} - .centered-image .layout-dropdown { display: flex; align-items: center !important; @@ -345,12 +316,6 @@ div.SingleDatePickerInput { bottom: 18px !important; } -/* -.centered-image .timeslider { - width: 100% !important; -} - -*/ #rgb-image, #aod-image { display: flex; align-items: center !important; @@ -362,7 +327,6 @@ div.SingleDatePickerInput { max-width: 45rem; max-height: 45rem; margin-left: 27%; - /* width: 35rem; */ } .timesliderline>button { @@ -386,25 +350,15 @@ div.SingleDatePickerInput { background-color: #FFFFFF !important; } -.timesliderline>button::before { -} - .text-center { display: inline-block; text-align: center; } .anim-buttons { - margin-left: 5px; - padding: 0.4rem 0 0.4rem 0.4rem; + padding: 0.3rem 0 0.4rem 0.4rem; } -/* -.playbutton { - background-image: url('/daily_dashboard/assets/images/Play.png'); - background-repeat: no-repeat; - background-color: var(--blue); -}*/ #obs-dropdown, #obs-mod-dropdown { min-width: 12rem; @@ -412,7 +366,6 @@ div.SingleDatePickerInput { .linetool { display: table-cell; - /* float: left; */ padding: 1rem 0.5rem; } @@ -487,48 +440,102 @@ div.SingleDatePickerInput { color: var(--blue); } +.navbar-timebar { + display: flex; + flex-direction: row; + justify-content: flex-start; + background-color: var(--blue) !important; + padding: 0 !important; +} + +.centered-image>.layout-dropdown>#rgb-navbar>.container-fluid>.collapse>.navbar-nav { + justify-content: center; +} + +.rgb-layout-dropdown>.disclaimer { + justify-content: flex-end !important; +} + +.navbar-nav { + margin: 0px; + width: -webkit-fill-available; + width: -moz-available; +} + +.navbar>.container-fluid { + padding: 0px !important; +} + +.navbar-timebar>.show { + height: 18vh !important; +} + +.navbar-toggler { + height: 5vh; +} + +.disclaimer { + display: flex; + z-index: 1000; + position: fixed; + bottom: 3.5vh; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-end; + width: -webkit-fill-available; + width: -moz-available; +} + +.fixed-bottom { + /* to adapt for sidebar width; */ + left: 200px; +} + .timesliderline { - display: inline-block; + display: inline-flex; + flex-wrap: wrap; + align-items: center; vertical-align: middle; height: 100%; background-color: var(--blue); + border-right: 1px solid var(--dark_grey); + height: 5vh; } .timeslider { - position: absolute; + /* position: absolute; */ background-color: transparent !important; height: 100%; - /* width: 704px; - height: 100%; - padding: 0.2rem; - background-color: var(--blue);*/ } /* Position the parent of the timeslider */ .layout-dropdown { - position: absolute; + display: flex; + flex-wrap: wrap; + justify-content: flex-start; left: 0; bottom: 30px; width: 100%; - height: 48px; - padding: 0 2rem; } .layout-dropdown .dropdown, .layout-dropdown .timeslider { z-index: 1002 !important; } +#map-view-dropdown-div { + border-right: 1px solid var(--dark_grey); +} + .layout-dropdown #map-view-dropdown { - position: absolute; - left: 750px; - bottom: 0; - width: 122px; height: 100%; + width: 122px; +} + +#map-view-dropdown.dropup.dropdown.show { + z-index: 1001 !important; } .layout-dropdown #map-layers-dropdown { - position: absolute; - left: 880px; bottom: 0; width: 127px; height: 100%; @@ -545,8 +552,8 @@ div.SingleDatePickerInput { background-repeat: no-repeat; background-position: 1rem; height: 1.75rem; + padding-top: 0.6rem; text-align: right; - margin-left: .5rem; } .layout-dropdown #map-view-dropdown>button { @@ -569,6 +576,11 @@ div.SingleDatePickerInput { min-width: 8rem; } +div.dropdown-menu.show { + position: absolute; + z-index: 100000; +} + .layout-dropdown .dropdown-menu { border-radius: 0 !important; background-color: #6C7B82; @@ -782,23 +794,23 @@ div.SingleDatePickerInput { } -@media screen and (max-width: 1250px) { - .layout-dropdown #map-view-dropdown { - left: 24px; - bottom: 120%; - height: 80%; - } - - .layout-dropdown #map-layers-dropdown { - left: 156px; - bottom: 120%; - height: 80%; - } - - #slider-graph, #obs-slider-graph, #obs-aod-slider-graph { - - } -} +/* @media screen and (max-width: 1250px) { */ +/* .layout-dropdown #map-view-dropdown { */ +/* left: 24px; */ +/* bottom: 120%; */ +/* height: 80%; */ +/* } */ +/**/ +/* .layout-dropdown #map-layers-dropdown { */ +/* left: 156px; */ +/* bottom: 120%; */ +/* height: 80%; */ +/* } */ + +/* #slider-graph, #obs-slider-graph, #obs-aod-slider-graph { */ +/* */ +/* } */ +/* } */ @media screen and (max-width: 870px) { diff --git a/data_handler.py b/data_handler.py index d60186ddc58fb63c15dc9ef4c12539f45ce3fc31..6232007bf6feafa94ae88586fe1cb1b9c2e45763 100644 --- a/data_handler.py +++ b/data_handler.py @@ -235,11 +235,11 @@ INFO_STYLE = { "fontWeight": "bold" } -DISCLAIMER_NO_FORECAST = [html.Span(html.P("""Dust data ©2022 WMO Barcelona Dust Regional Center."""), id='forecast-disclaimer')] +DISCLAIMER_NO_FORECAST = [html.Span(html.P("""Dust data ©2023 WMO Barcelona Dust Regional Center."""), id='forecast-disclaimer')] -DISCLAIMER_MODELS = [html.Span(html.P("""FORECAST ISSUED"""), id='forecast-issued'), html.Span(html.P("""Dust data ©2022 WMO Barcelona Dust Regional Center."""), id='forecast-disclaimer')] +DISCLAIMER_MODELS = [html.Span(html.P("""Dust data ©2023 WMO Barcelona Dust Regional Center."""), id='forecast-disclaimer')] -DISCLAIMER_OBS = html.P("""Aerosol data ©2022 WMO Barcelona Dust Regional Center, NASA.""") +DISCLAIMER_OBS = html.P("""Aerosol data ©2023 WMO Barcelona Dust Regional Center, NASA.""") GEOJSON_TEMPLATE = "{}/geojson/{}/{:02d}_{}_{}.geojson" NETCDF_TEMPLATE = "{}/netcdf/{}{}.nc" diff --git a/tabs/forecast.py b/tabs/forecast.py index 742d074a256d617a05035200ccb1af3ae167f285..a98975ec46a01420d2cde0da8a96a37a570136e1 100644 --- a/tabs/forecast.py +++ b/tabs/forecast.py @@ -24,339 +24,333 @@ end_date = DATES['end_date'] or (dt.now() - timedelta(days=1)).strftime("%Y%m%d" forecast_days = ('Today', 'Tomorrow') -#login_modal = html.Div( -# id='open-login', -# children=[ -# dbc.Modal([ -# dbc.ModalHeader("Download authentication"), -# dbc.ModalBody([ -# dbc.Alert( -# "The username/password is incorrect. Please try again or click outside the window to exit.", -# id="alert-login-error", -# is_open=False, -# duration=6000, -# fade=True, -# color="primary", -# style={ 'overflow': 'auto', 'marginBottom': 0 } -# ), -# dbc.Alert( -# "Sorry, you don't have permission to download the latest forecast. Please download a previous forecast or click outside the window to exit.", -# id="alert-login-wrong", -# is_open=False, -# duration=6000, -# fade=True, -# color="primary", -# style={ 'overflow': 'auto', 'marginBottom': 0 } -# ), -# dcc.Input( -# id="input_username", -# type="text", -# placeholder="username", -# ), -# dcc.Input( -# id="input_password", -# type="password", -# placeholder="password", -# ), -# html.Button('Login', id='submit-login', n_clicks=0), -# ]), -# ], -# id='login-modal', -# size='sm', -# centered=True, -# is_open=False, -# ), -# ] -# #style={'display': 'none'}, -#) +def layout_view(): + """ Return the menu for the various mapview types""" -time_series = html.Div( - id='open-timeseries', - children=[ - dbc.Spinner( - id='loading-ts-modal', - fullscreen=True, - fullscreen_style={'opacity': '0.5', 'zIndex' : '200000'}, - show_initially=False, - # debounce=200, - children=[ - dbc.Modal([], - id='ts-modal', - size='xl', - centered=True, - is_open=False, + return html.Div([ + html.Span( + dbc.DropdownMenu( + id='map-view-dropdown', + label='VIEW', + children=[ + dbc.DropdownMenuItem( + STYLES[style]['name'], + id=dict( + tag='view-style', + index=style + ), + active=active + ) + for style, active in zip(list(STYLES.keys()), [True if i == 'carto-positron' + else False for i in STYLES]) + ], + direction="up", + in_navbar=True, ), - ], - )], - #style={'display': 'none'}, -) - + ) + ], + id='map-view-dropdown-div') -layout_view = html.Div([ - html.Span( - dbc.DropdownMenu( - id='map-view-dropdown', - label='VIEW', +def time_series(): + """ Return the timeseries element""" + return html.Div( + id='open-timeseries', children=[ - dbc.DropdownMenuItem( - STYLES[style]['name'], - id=dict( - tag='view-style', - index=style - ), - active=active + dbc.Spinner( + id='loading-ts-modal', + fullscreen=True, + fullscreen_style={'opacity': '0.5', 'zIndex' : '200000'}, + show_initially=False, + # debounce=200, + children=[ + dbc.Modal([], + id='ts-modal', + size='xl', + centered=True, + is_open=False, + ), + ], + )], + #style={'display': 'none'}, ) - for style, active in zip(list(STYLES.keys()), [True if i == 'carto-positron' - else False for i in STYLES]) - ], - direction="up", - ), - )]) - -layout_layers = html.Div([ - html.Span( - dbc.DropdownMenu( - id='map-layers-dropdown', - label='LAYERS', - children=[ - dbc.DropdownMenuItem('AIRPORTS', id='airports') - ], - direction="up", - ), - )]) - -time_slider = html.Div([ - html.Span( - dcc.DatePickerSingle( - id='model-date-picker', - min_date_allowed=dt.strptime(start_date, "%Y%m%d"), - max_date_allowed=dt.strptime(end_date, "%Y%m%d"), - initial_visible_month=dt.strptime(end_date, "%Y%m%d"), - display_format='DD MMM YYYY', - date=end_date, - clearable=True, - placeholder='DD MON YYYY', - reopen_calendar_on_clear=True, - ), - className="timesliderline", - ), - html.Span( - children=[ - html.Button(title='Play', - id='btn-play', n_clicks=0, - className='fa fa-play text-center'), - html.Button(title='Stop', - id='btn-stop', n_clicks=0, - className='fa fa-pause text-center')], - className="timesliderline anim-buttons", - ), - html.Span( - dcc.Slider( - id='slider-graph', - min=0, max=72, step=3, value=0, - marks={ - tstep: '{:d}'.format(tstep) - # if tstep%2 == 0 else '' - for tstep in range(0, 75, 3) - }, - # updatemode='drag', - ), - className="timesliderline", - ), - html.Div(DISCLAIMER_MODELS, - className='disclaimer'), - ], - className="timeslider" -) +def layout_layers(): + return html.Div([ + html.Span( + dbc.DropdownMenu( + id='map-layers-dropdown', + label='LAYERS', + children=[ + dbc.DropdownMenuItem('AIRPORTS', id='airports') + ], + direction="up", + ), + )]) -prob_time_slider = html.Div([ - html.Span( - dcc.DatePickerSingle( - id='prob-date-picker', - min_date_allowed=dt.strptime(start_date, "%Y%m%d"), - max_date_allowed=dt.strptime(end_date, "%Y%m%d"), - initial_visible_month=dt.strptime(end_date, "%Y%m%d"), - display_format='DD MMM YYYY', - date=end_date, - clearable=True, - placeholder='DD MON YYYY', - reopen_calendar_on_clear=True, - ), - className="timesliderline", - ), - html.Span( - dcc.Slider( - id='prob-slider-graph', - min=0, max=1, step=1, value=0, - marks={ - tstep: forecast_days[tstep] - for tstep in range(2) - }, - ), - className="timesliderline", - ), - html.Div(DISCLAIMER_MODELS, - className='disclaimer'), - ], - className="timeslider" -) +def time_slider(end_date=end_date): + """ Return the html for the timeslider for timeseries animations """ + return html.Div([ + html.Span( + dcc.DatePickerSingle( + id='model-date-picker', + min_date_allowed=dt.strptime(start_date, "%Y%m%d"), + max_date_allowed=dt.strptime(end_date, "%Y%m%d"), + initial_visible_month=dt.strptime(end_date, "%Y%m%d"), + display_format='DD MMM YYYY', + date=end_date, + clearable=True, + placeholder='DD MON YYYY', + reopen_calendar_on_clear=True, + ), + className="timesliderline", + ), + html.Span( + children=[ + html.Button(title='Play', + id='btn-play', n_clicks=0, + className='fa fa-play text-center'), + ], + className="timesliderline anim-buttons", + ), + html.Span( + dcc.Slider( + id='slider-graph', + min=0, max=72, step=3, value=0, + marks={ + tstep: '{:d}'.format(tstep) + # if tstep%2 == 0 else '' + for tstep in range(0, 75, 3) + }, + # updatemode='drag', + ), + className="timesliderline", + ), + ], + className="timeslider" + ) +def prob_time_slider(end_date=end_date): + """ Return the slider for the probability maps """ + return html.Div([ + html.Span( + dcc.DatePickerSingle( + id='prob-date-picker', + min_date_allowed=dt.strptime(start_date, "%Y%m%d"), + max_date_allowed=dt.strptime(end_date, "%Y%m%d"), + initial_visible_month=dt.strptime(end_date, "%Y%m%d"), + display_format='DD MMM YYYY', + date=end_date, + clearable=True, + placeholder='DD MON YYYY', + reopen_calendar_on_clear=True, + ), + className="timesliderline", + ), + html.Span( + dcc.Slider( + id='prob-slider-graph', + min=0, max=1, step=1, value=0, + marks={ + tstep: forecast_days[tstep] + for tstep in range(2) + }, + ), + className="timesliderline", + ), + ], + className="timeslider" + ) -was_time_slider = html.Div([ - html.Span( - dcc.DatePickerSingle( - id='was-date-picker', - min_date_allowed=dt.strptime(start_date, "%Y%m%d"), - max_date_allowed=dt.strptime(end_date, "%Y%m%d"), - initial_visible_month=dt.strptime(end_date, "%Y%m%d"), - display_format='DD MMM YYYY', - date=end_date, - clearable=True, - placeholder='DD MON YYYY', - reopen_calendar_on_clear=True, - ), - className="timesliderline", - ), - html.Span( - dcc.Slider( - id='was-slider-graph', - min=1, max=2, step=1, value=1, - marks={ - tstep: forecast_days[tstep-1] - for tstep in range(1, 3) - }, - ), - className="timesliderline", - ), - html.Div(DISCLAIMER_MODELS, - className='disclaimer'), - ], - className="timeslider" -) +def was_time_slider(end_date): + """ Return the slider for the Warning Adivsory System maps""" + return html.Div([ + html.Span( + dcc.DatePickerSingle( + id='was-date-picker', + min_date_allowed=dt.strptime(start_date, "%Y%m%d"), + max_date_allowed=dt.strptime(end_date, "%Y%m%d"), + initial_visible_month=dt.strptime(end_date, "%Y%m%d"), + display_format='DD MMM YYYY', + date=end_date, + clearable=True, + placeholder='DD MON YYYY', + reopen_calendar_on_clear=True, + ), + className="timesliderline", + ), + html.Span( + dcc.Slider( + id='was-slider-graph', + min=1, max=2, step=1, value=1, + marks={ + tstep: forecast_days[tstep-1] + for tstep in range(1, 3) + }, + ), + className="timesliderline", + ), + ], + className="timeslider" + ) -def tab_forecast(window='models', end_date=end_date): - models_children = [ +def models_children(end_date=end_date): + """ Return the html for models maps """ + return [ html.Div( id=dict( tag='tab-name', index='models', ) ), -# login_modal, - dbc.Alert( - "To explore the forecast, please select a variable and click on APPLY.", - id="alert-forecast", - is_open=True, - duration=6000, - fade=True, - color="primary", - style={ 'overflow': 'auto', 'marginBottom': 0 } - ), - dbc.Alert( - "If you close the location tooltip, please refresh the page before clicking on another specific location on the map.", - id="alert-popup", - is_open=False, - duration=6000, - fade=True, - color="primary", - style={ 'overflow': 'auto', 'marginBottom': 0 } - ), - html.Div( - id='div-collection', -# children=[dbc.Spinner( -# id='loading-graph-collection', -# #debounce=10, -# show_initially=False, - children=[ - dbc.Container( - id='graph-collection', - children=[ + # login_modal, + dbc.Alert( + "To explore the forecast, please select a variable and click on APPLY.", + id="alert-forecast", + is_open=True, + duration=6000, + fade=True, + color="primary", + style={ 'overflow': 'auto', 'marginBottom': 0 } + ), + dbc.Alert( + "If you close the location tooltip, please refresh the page before clicking on another specific location on the map.", + id="alert-popup", + is_open=False, + duration=6000, + fade=True, + color="primary", + style={ 'overflow': 'auto', 'marginBottom': 0 } + ), + html.Div( + id='div-collection', + # children=[dbc.Spinner( + # id='loading-graph-collection', + # #debounce=10, + # show_initially=False, + children=[ + dbc.Container( + id='graph-collection', + children=[ + ], + fluid=True, + ), + html.Div(DISCLAIMER_MODELS, + className='disclaimer'), + ] + # )], + ), + html.Div( + [dcc.Store(id="model-clicked-coords"), + dcc.Store(id="current-popups-stored")] + ), + html.Div( + dcc.Interval(id='slider-interval', + interval=1000, + n_intervals=0, + disabled=True + )), + dbc.NavbarSimple([ + html.Div([ + time_slider(end_date), + layout_view(), + time_series(), + # layout_layers(), ], - fluid=True, - ), - ] -# )], - ), - html.Div( - [dcc.Store(id="model-clicked-coords"), - dcc.Store(id="current-popups-stored")] - ), - html.Div( - dcc.Interval(id='slider-interval', - interval=1000, - n_intervals=0, - disabled=True - )), - html.Div([ - time_slider, - layout_view, - # layout_layers, - ], - id='layout-dropdown', - className="layout-dropdown", - ), - time_series, - ] + id='layout-dropdown', + className="layout-dropdown", + ), + ], + className='fixed-bottom navbar-timebar', + fluid=True, + expand='lg', + dark=True, + fixed='bottom',) + ] - was_children = [ +def prob_children(end_date=end_date): + """ Return html for probablility maps""" + return [ html.Div( id=dict( tag='tab-name', - index='was', + index='prob', ) ), - dbc.Spinner( html.Div( - children=[ - html.Div( - id="{'index':'None', 'tag': 'empty-map'}") - ], - id='was-graph', + id='prob-graph', className='graph-with-slider'), - ), - html.Div([ - was_time_slider, - layout_view, - # layout_layers, - ], - id='layout-dropdown', - className="layout-dropdown", - ), - ] + html.Div(DISCLAIMER_MODELS, + className='disclaimer'), + dbc.NavbarSimple([ + html.Div([ + prob_time_slider(end_date), + layout_view(), + # layout_layers(), + ], + id='layout-dropdown', + className="layout-dropdown", + ), + ], + className='fixed-bottom navbar-timebar', + fluid=True, + expand='lg', + dark=True, + fixed='bottom', + ) + ] - prob_children = [ +def was_children(end_date=end_date): + """ Return html for WAS maps""" + return [ html.Div( id=dict( tag='tab-name', - index='prob', + index='was', ) ), - html.Div( - id='prob-graph', - className='graph-with-slider'), - html.Div([ - prob_time_slider, - layout_view, - # layout_layers, - ], - id='layout-dropdown', - className="layout-dropdown", - ), - ] + dbc.Spinner( + html.Div( + children=[ + html.Div( + id="{'index':'None', 'tag': 'empty-map'}") + ], + id='was-graph', + className='graph-with-slider'), + ), + html.Div(DISCLAIMER_MODELS, + className='disclaimer'), + dbc.NavbarSimple([ + html.Div([ + was_time_slider(end_date), + layout_view(), + # layout_layers(), + ], + id='layout-dropdown', + className="layout-dropdown", + ), + ], + className='fixed-bottom navbar-timebar', + fluid=True, + expand='lg', + dark=True, + fixed='bottom', + ) + ] +def tab_forecast(window='models', end_date=end_date): + """ The MAIN function to return all appropriate html for selected tab and sidebar selection """ windows = { - 'models': models_children, - 'was': was_children, - 'prob': prob_children, - } - + 'models': models_children(end_date), + 'was': was_children(end_date), + 'prob': prob_children(end_date), + } + return dcc.Tab(label='Forecast', - id='forecast-tab', - value='forecast-tab', - className='horizontal-menu', - children=windows[window], - ) + id='forecast-tab', + value='forecast-tab', + className='horizontal-menu', + children=windows[window], + ) def expand_dropdown(window): @@ -458,12 +452,12 @@ def sidebar_forecast(variables, default_var, models, default_model, window='mode dbc.Card([ dbc.CardHeader(html.H2( dbc.Button(children=["Warning Advisory System", - html.Span( - html.I(className='fa fa-solid fa-angle-up'), - id='caret3', className="caret-span-closed")], - color="link", id='group-3-toggle', className='dropdown', - disabled=False) - )), + html.Span( + html.I(className='fa fa-solid fa-angle-up'), + id='caret3', className="caret-span-closed")], + color="link", id='group-3-toggle', className='dropdown', + disabled=False) + )), dbc.Collapse( id='collapse-3', is_open=dropdown['was'], @@ -475,150 +469,150 @@ def sidebar_forecast(variables, default_var, models, default_model, window='mode 'value': was} for was in WAS], value=country, className="sidebar-dropdown" - ), + ), html.Div( dcc.Store(id="was-previous"), - ), + ), html.Span([ html.Button('APPLY', id='was-apply', n_clicks=0), - ] - )] - ) - ] - )], - ), + ] + )] + ) + ] + )], + ), ], className="accordion" ), html.Div([ dbc.Row([ - dbc.Col( - dbc.Button( - "", - id="info-button", - ), - width=3, - ), - dbc.Col( - dbc.Button( - "DOWNLOAD", - id="download-button", - ), - width=9, - ), - ], - no_gutters=True, - ), + dbc.Col( + dbc.Button( + "", + id="info-button", + ), + width=3, + ), + dbc.Col( + dbc.Button( + "DOWNLOAD", + id="download-button", + ), + width=9, + ), + ], + no_gutters=True, + ), dbc.Row([ dbc.Col([ - dbc.Collapse( - dbc.Card(dbc.CardBody( - [ - dbc.Button('USER GUIDE', - id='btn-userguide-download', - n_clicks=0, - href="https://dust.aemet.es/products/overview/user-guide/@@download", - className='download-section', - ), - html.P(""" - Please check out the User Guide for more information."""), - ], - className="card-text", - )), - id="info-collapse", - is_open=False, - ), - dbc.Collapse( - dbc.Card(dbc.CardBody( - [ - html.Label("CURRENT SELECTION"), - dbc.Button('PNG FRAME', - id='btn-frame-download', - n_clicks=0, - href="#", - className='download-section', - ), - # dbc.Spinner( + dbc.Collapse( + dbc.Card(dbc.CardBody( + [ + dbc.Button('USER GUIDE', + id='btn-userguide-download', + n_clicks=0, + href="https://dust.aemet.es/products/overview/user-guide/@@download", + className='download-section', + ), + html.P(""" + Please check out the User Guide for more information."""), + ], + className="card-text", + )), + id="info-collapse", + is_open=False, + ), + dbc.Collapse( + dbc.Card(dbc.CardBody( + [ + html.Label("CURRENT SELECTION"), + dbc.Button('PNG FRAME', + id='btn-frame-download', + n_clicks=0, + href="#", + className='download-section', + ), + # dbc.Spinner( dcc.Download( id="frame-download", base64=True, - ), - # ), - dbc.Button('GIF ANIM', - id='btn-anim-download', - n_clicks=0, - href="#", - external_link=True, - target="_blank", - className='download-section', - ), -# html.Button('GIF ANIM', -# id='btn-anim-download', -# n_clicks=0, -# className='download-section', -# ), + ), + # ), + dbc.Button('GIF ANIM', + id='btn-anim-download', + n_clicks=0, + href="#", + external_link=True, + target="_blank", + className='download-section', + ), + # html.Button('GIF ANIM', + # id='btn-anim-download', + # n_clicks=0, + # className='download-section', + # ), + # dbc.Spinner( + # dcc.Download( + # id="anim-download", + # base64=True, + # ), + # ), + # html.Label("ALL MODELS"), + # dbc.Button('PNG FRAME', + # id='btn-all-frame-download', + # n_clicks=0, + # href="#", + # external_link=True, + # target="_blank", + # className='download-section', + # ), + ## dbc.Spinner( + ## dcc.Download( + ## id="all-frame-download", + ## base64=True, + ## ), + ## ), + # dbc.Button('GIF ANIM', + # id='btn-all-anim-download', + # n_clicks=0, + # href="#", + # external_link=True, + # target="_blank", + # className='download-section', + # ), # dbc.Spinner( -# dcc.Download( -# id="anim-download", -# base64=True, -# ), -# ), -# html.Label("ALL MODELS"), -# dbc.Button('PNG FRAME', -# id='btn-all-frame-download', -# n_clicks=0, -# href="#", -# external_link=True, -# target="_blank", -# className='download-section', -# ), -## dbc.Spinner( -## dcc.Download( -## id="all-frame-download", -## base64=True, -## ), -## ), -# dbc.Button('GIF ANIM', -# id='btn-all-anim-download', -# n_clicks=0, -# href="#", -# external_link=True, -# target="_blank", -# className='download-section', -# ), -# dbc.Spinner( -# dcc.Download( -# id="all-anim-download", -# base64=True, -# ), -# ), - html.Label("NUMERICAL DATA"), + # dcc.Download( + # id="all-anim-download", + # base64=True, + # ), + # ), +html.Label("NUMERICAL DATA"), dbc.Button('NETCDF', - id='btn-netcdf-download', - n_clicks=0, - href="/products/data-download", - external_link=True, - target="_blank", - className='download-section', - ), + id='btn-netcdf-download', + n_clicks=0, + href="/products/data-download", + external_link=True, + target="_blank", + className='download-section', + ), # html.A('TEST', -# id='btn-img-download', -# href="#", -# # target="_blank", -# className='download-section', -# ), + # id='btn-img-download', + # href="#", + # # target="_blank", + # className='download-section', + # ), # dbc.Spinner( -# dcc.Download( -# id="netcdf-download", -# base64=True, -# ), -# ), + # dcc.Download( + # id="netcdf-download", + # base64=True, + # ), + # ), # html.P("""This button allows you to get selected models netCDF files."""), # html.P([ -# """To get access to the forecast archive please click """, -# dcc.Link('here', href="https://dust03.bsc.es/products/data-download"), -# ]), - ], + # """To get access to the forecast archive please click """, + # dcc.Link('here', href="https://dust03.bsc.es/products/data-download"), + # ]), +], className="card-text", )), id="download-collapse", @@ -633,3 +627,49 @@ def sidebar_forecast(variables, default_var, models, default_model, window='mode ) ] +#login_modal = html.Div( +# id='open-login', +# children=[ +# dbc.Modal([ +# dbc.ModalHeader("Download authentication"), +# dbc.ModalBody([ +# dbc.Alert( +# "The username/password is incorrect. Please try again or click outside the window to exit.", +# id="alert-login-error", +# is_open=False, +# duration=6000, +# fade=True, +# color="primary", +# style={ 'overflow': 'auto', 'marginBottom': 0 } +# ), +# dbc.Alert( +# "Sorry, you don't have permission to download the latest forecast. Please download a previous forecast or click outside the window to exit.", +# id="alert-login-wrong", +# is_open=False, +# duration=6000, +# fade=True, +# color="primary", +# style={ 'overflow': 'auto', 'marginBottom': 0 } +# ), +# dcc.Input( +# id="input_username", +# type="text", +# placeholder="username", +# ), +# dcc.Input( +# id="input_password", +# type="password", +# placeholder="password", +# ), +# html.Button('Login', id='submit-login', n_clicks=0), +# ]), +# ], +# id='login-modal', +# size='sm', +# centered=True, +# is_open=False, +# ), +# ] +# #style={'display': 'none'}, +#) + diff --git a/tabs/forecast_callbacks.py b/tabs/forecast_callbacks.py index 9ae77f7bfcfb6f3e4461422ec3fe9abb855ae2e8..3ddd38695383c6660f0e75d7ea335c86d0dff57d 100644 --- a/tabs/forecast_callbacks.py +++ b/tabs/forecast_callbacks.py @@ -954,15 +954,15 @@ def zoom_country(n_clicks, tstep, date, model, variable, static, view, zoom, lat [Output('slider-interval', 'disabled'), Output('slider-interval', 'n_intervals'), Output('open-timeseries', 'style'), + Output('btn-play', 'className'), #Output('div-collection', 'children'), ], - [Input('btn-play', 'n_clicks'), - Input('btn-stop', 'n_clicks')], + Input('btn-play', 'n_clicks'), [State('slider-interval', 'disabled'), State('slider-graph', 'value')], prevent_initial_call=True ) -def start_stop_autoslider(n_play, n_stop, disabled, value): +def start_stop_autoslider(n_play, disabled, value): """ Play/Pause map animation """ ctx = dash.callback_context if DEBUG: print("VALUE", value) @@ -973,10 +973,10 @@ def start_stop_autoslider(n_play, n_stop, disabled, value): button_id = ctx.triggered[0]["prop_id"].split(".")[0] if button_id == 'btn-play' and disabled: ts_style = { 'display': 'none' } - return not disabled, int(value/FREQ), ts_style - elif button_id == 'btn-stop' and not disabled: + return not disabled, int(value/FREQ), ts_style, 'fa fa-pause text-center' + elif button_id == 'btn-play' and not disabled: ts_style = { 'display': 'block' } - return not disabled, int(value/FREQ), ts_style + return not disabled, int(value/FREQ), ts_style, 'fa fa-play text-center' raise PreventUpdate diff --git a/tabs/observations.py b/tabs/observations.py index e24a6a41b8e3ab0017d1816b49aeb0188acd66e9..be6e267cd3e6837719be99e74a72124fe89bc715 100644 --- a/tabs/observations.py +++ b/tabs/observations.py @@ -68,9 +68,6 @@ def obs_time_slider(div='obs', start=0, end=23, step=1): html.Button(title='Play', id='btn-{}-play'.format(div), n_clicks=0, className='fa fa-play'), - html.Button(title='Stop', - id='btn-{}-stop'.format(div), n_clicks=0, - className='fa fa-pause'), ], className="timesliderline anim-buttons", ) @@ -89,20 +86,23 @@ def obs_time_slider(div='obs', start=0, end=23, step=1): className="timesliderline", ) if div == 'obs-vis': - return html.Div([ - date_picker, - slider, - ], - className="timeslider" - ) + play_button = None - return html.Div([ + return dbc.NavbarSimple([ + html.Div([ date_picker, play_button, slider, - ], - className="timeslider" - ) + ], + className="timeslider" + )], + id='rgb-navbar', + className='fixed-bottom navbar-timebar', + fluid=True, + expand='lg', + dark=True, + fixed='bottom', + ) def tab_observations(window='rgb'): """ """ @@ -222,7 +222,7 @@ def tab_observations(window='rgb'): html.Div(DISCLAIMER_NO_FORECAST, className='disclaimer'), ], - className="layout-dropdown", + className="layout-dropdown rgb-layout-dropdown", ), ] diff --git a/tabs/observations_callbacks.py b/tabs/observations_callbacks.py index bc1072c9570fa7edd4157b10fa5b3c3351d1943b..f4c3c29a2155990297cd6a18a1c7cfb5a4f66b7d 100644 --- a/tabs/observations_callbacks.py +++ b/tabs/observations_callbacks.py @@ -184,14 +184,14 @@ def update_image_src(btn_fulldisc, btn_middleeast, date, tstep, btn_fulldisc_act # start/stop animation @dash.callback( [Output('obs-slider-interval', 'disabled'), - Output('obs-slider-interval', 'n_intervals')], - [Input('btn-obs-play', 'n_clicks'), - Input('btn-obs-stop', 'n_clicks')], + Output('obs-slider-interval', 'n_intervals'), + Output('btn-obs-play', 'className')], + Input('btn-obs-play', 'n_clicks'), [State('obs-slider-interval', 'disabled'), State('obs-slider-graph', 'value')], prevent_initial_call=True ) -def start_stop_obs_autoslider(n_play, n_stop, disabled, value): +def start_stop_obs_autoslider(n_play, disabled, value): """ Play/Pause map animation """ ctx = dash.callback_context if DEBUG: print("VALUE", value) @@ -200,9 +200,9 @@ def start_stop_obs_autoslider(n_play, n_stop, disabled, value): if ctx.triggered: button_id = ctx.triggered[0]["prop_id"].split(".")[0] if button_id == 'btn-obs-play' and disabled: - return not disabled, int(value) - elif button_id == 'btn-obs-stop' and not disabled: - return not disabled, int(value) + return not disabled, int(value), 'fa fa-pause text-center' + elif button_id == 'btn-obs-play' and not disabled: + return not disabled, int(value), 'fa fa-play text-center' raise PreventUpdate diff --git a/tests/test_forecast.py b/tests/test_forecast.py new file mode 100644 index 0000000000000000000000000000000000000000..24a209efdb6a16a3df3335d57bdb786aa6d5c5c2 --- /dev/null +++ b/tests/test_forecast.py @@ -0,0 +1,58 @@ +import pytest +import importlib +import dash +from data_handler import VARS +from data_handler import MODELS +code = importlib.import_module('tabs.forecast') + +def test_layout_view(): + assert "Div(children=[Span(DropdownMenu(children=[DropdownMenuItem(children='Light', id={'tag': 'view-style', 'index': 'carto-positron'}, active=True), DropdownMenuItem(children='Open street map', id={'tag': 'view-style', 'index': 'open-street-map'}, active=False), DropdownMenuItem(children='Terrain', id={'tag': 'view-style', 'index': 'stamen-terrain'}, active=False), DropdownMenuItem(children='ESRI', id={'tag': 'view-style', 'index': 'esri-world'}, active=False)], id='map-view-dropdown', direction='up', in_navbar=True, label='VIEW'))], id='map-view-dropdown-div')" in str(code.layout_view()) + +def test_time_series(): + assert "Div(children=[Spinner(children=[Modal(children=[], id='ts-modal', centered=True, is_open=False, size='xl')], id='loading-ts-modal', fullscreen=True, fullscreen_style={'opacity': '0.5', 'zIndex': '200000'}, show_initially=False)], id='open-timeseries')" in str(code.time_series()) + +def test_layout_layers(): + assert "Div([Span(DropdownMenu(children=[DropdownMenuItem(children='AIRPORTS', id='airports')], id='map-layers-dropdown', direction='up', label='LAYERS'))])" in str(code.layout_layers()) + +def test_time_slider(): + assert "Div(children=[Span(children=DatePickerSingle(date='20220808', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 8, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 8, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='model-date-picker'), className='timesliderline'), Span(children=[Button(id='btn-play', className='fa fa-play text-center', n_clicks=0, title='Play')], className='timesliderline anim-buttons'), Span(children=Slider(min=0, max=72, step=3, marks={0: '0', 3: '3', 6: '6', 9: '9', 12: '12', 15: '15', 18: '18', 21: '21', 24: '24', 27: '27', 30: '30', 33: '33', 36: '36', 39: '39', 42: '42', 45: '45', 48: '48', 51: '51', 54: '54', 57: '57', 60: '60', 63: '63', 66: '66', 69: '69', 72: '72'}, value=0, id='slider-graph'), className='timesliderline')], className='timeslider')" in str(code.time_slider('20220808')) + +def test_prob_time_slider(): + assert "Div(children=[Span(children=DatePickerSingle(date='20220809', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 9, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 9, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='prob-date-picker'), className='timesliderline'), Span(children=Slider(min=0, max=1, step=1, marks={0: 'Today', 1: 'Tomorrow'}, value=0, id='prob-slider-graph'), className='timesliderline')], className='timeslider')" in str(code.prob_time_slider('20220809')) + +def test_was_time_slider(): + assert "Div(children=[Span(children=DatePickerSingle(date='20220808', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 8, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 8, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='was-date-picker'), className='timesliderline'), Span(children=Slider(min=1, max=2, step=1, marks={1: 'Today', 2: 'Tomorrow'}, value=1, id='was-slider-graph'), className='timesliderline')], className='timeslider')" in str(code.was_time_slider('20220808')) + +def test_models_children(): + assert "[Div(id={'tag': 'tab-name', 'index': 'models'}), Alert(children='To explore the forecast, please select a variable and click on APPLY.', id='alert-forecast', color='primary', duration=6000, fade=True, is_open=True, style={'overflow': 'auto', 'marginBottom': 0}), Alert(children='If you close the location tooltip, please refresh the page before clicking on another specific location on the map.', id='alert-popup', color='primary', duration=6000, fade=True, is_open=False, style={'overflow': 'auto', 'marginBottom': 0}), Div(children=[Container(children=[], id='graph-collection', fluid=True), Div(children=[Span(children=P('Dust data ©2023 WMO Barcelona Dust Regional Center.'), id='forecast-disclaimer')], className='disclaimer')], id='div-collection'), Div([Store(id='model-clicked-coords'), Store(id='current-popups-stored')]), Div(Interval(id='slider-interval', disabled=True, interval=1000, n_intervals=0))" in str(code.models_children('20220808')) + +def test_prob_children(): + assert "[Div(id={'tag': 'tab-name', 'index': 'prob'}), Div(id='prob-graph', className='graph-with-slider'), Div(children=[Span(children=P('Dust data ©2023 WMO Barcelona Dust Regional Center.'), id='forecast-disclaimer')], className='disclaimer'), NavbarSimple(children=[Div(children=[Div(children=[Span(children=DatePickerSingle(date='20220808', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 8, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 8, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='prob-date-picker'), className='timesliderline'), Span(children=Slider(min=0, max=1, step=1, marks={0: 'Today', 1: 'Tomorrow'}, value=0, id='prob-slider-graph'), className='timesliderline')], className='timeslider'), Div(children=[Span(DropdownMenu(children=[DropdownMenuItem(children='Light', id={'tag': 'view-style', 'index': 'carto-positron'}, active=True), DropdownMenuItem(children='Open street map', id={'tag': 'view-style', 'index': 'open-street-map'}, active=False), DropdownMenuItem(children='Terrain', id={'tag': 'view-style', 'index': 'stamen-terrain'}, active=False), DropdownMenuItem(children='ESRI', id={'tag': 'view-style', 'index': 'esri-world'}, active=False)], id='map-view-dropdown', direction='up', in_navbar=True, label='VIEW'))], id='map-view-dropdown-div')], id='layout-dropdown', className='layout-dropdown')], className='fixed-bottom navbar-timebar', dark=True, expand='lg', fixed='bottom', fluid=True)]" in str(code.prob_children('20220808')) + +def test_was_children(): + assert "id='was-graph', className='graph-with-slider')), Div(children=[Span(children=P('Dust data ©2023 WMO Barcelona Dust Regional Center.'), id='forecast-disclaimer')], className='disclaimer'), NavbarSimple(children=[Div(children=[Div(children=[Span(children=DatePickerSingle(date='20220808', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 8, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 8, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='was-date-picker'), className='timesliderline'), Span(children=Slider(min=1, max=2, step=1, marks={1: 'Today', 2: 'Tomorrow'}, value=1, id='was-slider-graph'), className='timesliderline')], className='timeslider'), Div(children=[Span(DropdownMenu(children=[DropdownMenuItem(children='Light', id={'tag': 'view-style', 'index': 'carto-positron'}, active=True), DropdownMenuItem(children='Open street map', id={'tag': 'view-style', 'index': 'open-street-map'}, active=False), DropdownMenuItem(children='Terrain', id={'tag': 'view-style', 'index': 'stamen-terrain'}, active=False), DropdownMenuItem(children='ESRI', id={'tag': 'view-style', 'index': 'esri-world'}, active=False)], id='map-view-dropdown', direction='up', in_navbar=True, label='VIEW'))], id='map-view-dropdown-div')], id='layout-dropdown', className='layout-dropdown')], className='fixed-bottom navbar-timebar', dark=True, expand='lg', fixed='bottom', fluid=True)]" in str(code.was_children('20220808')) + +def test_tab_forecast(): + #TEST MODELS + assert " '57', 60: '60', 63: '63', 66: '66', 69: '69', 72: '72'}, value=0, id='slider-graph'), className='timesliderline')], className='timeslider'), Div(children=[Span(DropdownMenu(children=[DropdownMenuItem(children='Light', id={'tag': 'view-style', 'index': 'carto-positron'}, active=True), DropdownMenuItem(children='Open street map', id={'tag': 'view-style', 'index': 'open-street-map'}, active=False), DropdownMenuItem(children='Terrain', id={'tag': 'view-style', 'index': 'stamen-terrain'}, active=False), DropdownMenuItem(children='ESRI', id={'tag': 'view-style', 'index': 'esri-world'}, active=False)], id='map-view-dropdown', direction='up', in_navbar=True, label='VIEW'))], id='map-view-dropdown-div'), Div(children=[Spinner(children=[Modal(children=[], id='ts-modal', centered=True, is_open=False, size='xl')], id='loading-ts-modal', fullscreen=True, fullscreen_style={'opacity': '0.5', 'zIndex': '200000'}, show_initially=False)], id='open-timeseries')], id='layout-dropdown', className='layout-dropdown')], className='fixed-bottom navbar-timebar', dark=True, expand='lg', fixed='bottom', fluid=True)], id='forecast-tab', className='horizontal-menu', label='Forecast', value='forecast-tab')" in str(code.tab_forecast('models', '20220808')) + + #TEST WAS + assert "id='was-graph', className='graph-with-slider')), Div(children=[Span(children=P('Dust data ©2023 WMO Barcelona Dust Regional Center.'), id='forecast-disclaimer')], className='disclaimer'), NavbarSimple(children=[Div(children=[Div(children=[Span(children=DatePickerSingle(date='20220808', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 8, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 8, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='was-date-picker'), className='timesliderline'), Span(children=Slider(min=1, max=2, step=1, marks={1: 'Today', 2: 'Tomorrow'}, value=1, id='was-slider-graph'), className='timesliderline')], className='timeslider'), Div(children=[Span(DropdownMenu(children=[DropdownMenuItem(children='Light', id={'tag': 'view-style', 'index': 'carto-positron'}, active=True), DropdownMenuItem(children='Open street map', id={'tag': 'view-style', 'index': 'open-street-map'}, active=False), DropdownMenuItem(children='Terrain', id={'tag': 'view-style', 'index': 'stamen-terrain'}, active=False), DropdownMenuItem(children='ESRI', id={'tag': 'view-style', 'index': 'esri-world'}, active=False)], id='map-view-dropdown', direction='up', in_navbar=True, label='VIEW'))], id='map-view-dropdown-div')], id='layout-dropdown', className='layout-dropdown')], className='fixed-bottom navbar-timebar', dark=True, expand='lg', fixed='bottom', fluid=True)], id='forecast-tab', className='horizontal-menu', label='Forecast', value='forecast-tab')" in str(code.tab_forecast('was', '20220808')) + + #TEST PROB + assert "Tab(children=[Div(id={'tag': 'tab-name', 'index': 'prob'}), Div(id='prob-graph', className='graph-with-slider'), Div(children=[Span(children=P('Dust data ©2023 WMO Barcelona Dust Regional Center.'), id='forecast-disclaimer')], className='disclaimer'), NavbarSimple(children=[Div(children=[Div(children=[Span(children=DatePickerSingle(date='20220808', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 8, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 8, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='prob-date-picker'), className='timesliderline'), Span(children=Slider(min=0, max=1, step=1, marks={0: 'Today', 1: 'Tomorrow'}, value=0, id='prob-slider-graph'), className='timesliderline')], className='timeslider'), Div(children=[Span(DropdownMenu(children=[DropdownMenuItem(children='Light', id={'tag': 'view-style', 'index': 'carto-positron'}, active=True), DropdownMenuItem(children='Open street map', id={'tag': 'view-style', 'index': 'open-street-map'}, active=False), DropdownMenuItem(children='Terrain', id={'tag': 'view-style', 'index': 'stamen-terrain'}, active=False), DropdownMenuItem(children='ESRI', id={'tag': 'view-style', 'index': 'esri-world'}, active=False)], id='map-view-dropdown', direction='up', in_navbar=True, label='VIEW'))], id='map-view-dropdown-div')], id='layout-dropdown', className='layout-dropdown')], className='fixed-bottom navbar-timebar', dark=True, expand='lg', fixed='bottom', fluid=True)], id='forecast-tab', className='horizontal-menu', label='Forecast', value='forecast-tab')" in str(code.tab_forecast('prob', '20220808')) + +def test_expand_dropdown(): + assert "{'models': True, 'prob': False, 'was': False}" in str(code.expand_dropdown('models')) + assert "{'models': False, 'prob': True, 'was': False}" in str(code.expand_dropdown('prob')) + assert "{'models': False, 'prob': False, 'was': True}" in str(code.expand_dropdown('was')) + +def test_sidebar_forecast(): + #TEST MODELS + assert "[Div(children=[Label('Variable'), Dropdown(options=[{'label': 'AOD', 'value': 'OD550_DUST'}, {'label': 'Concentration', 'value': 'SCONC_DUST'}, {'label': 'Dry deposition', 'value': 'DUST_DEPD'}, {'label': 'Wet deposition', 'value': 'DUST_DEPW'}, {'label': 'Load', 'value': 'DUST_LOAD'}, {'label': 'Extinction', 'value': 'DUST_EXT_SFC'}], value=['OD550_DUST'], clearable=False, searchable=False, optionHeight=50, maxHeight=400, id='variable-dropdown-forecast')], className='sidebar-first-item'), Div(children=[Card([CardHeader(H2(Button(children=['Models', Span(children=I(className='fa fa-solid fa-angle-up'), id='caret1', className='caret-span')], id='group-1-toggle', className='dropdown', color='link'))), Collapse(children=[CardBody([Checklist(id='model-dropdown', className='sidebar-dropdown', options=[{'label': 'MULTI-MODEL', 'value': 'median'}, {'label': 'MONARCH', 'value': 'monarch'}, {'label': 'CAMS-IFS', 'value': 'cams'}, {'label': 'DREAM8-CAMS', 'value': 'dream8-macc'}, {'label': 'NASA-GEOS', 'value': 'nasa-geos'}, {'label': 'MetOffice-UM', 'value': 'metoffice'}, {'label': 'NCEP-GEFS', 'value': 'ncep-gefs'}, {'label': 'EMA-RegCM4', 'value': 'ema-regcm4'}" in str(code.sidebar_forecast(VARS, ['OD550_DUST'], MODELS, ['median'], window='models', country='burkinafaso')) + + #TEST PROB + assert "[Div(children=[Label('Variable'), Dropdown(options=[{'label': 'AOD', 'value': 'OD550_DUST'}, {'label': 'Concentration', 'value': 'SCONC_DUST'}, {'label': 'Dry deposition', 'value': 'DUST_DEPD'}, {'label': 'Wet deposition', 'value': 'DUST_DEPW'}, {'label': 'Load', 'value': 'DUST_LOAD'}, {'label': 'Extinction', 'value': 'DUST_EXT_SFC'}], value=['OD550_DUST'], clearable=False, searchable=False, optionHeight=50, maxHeight=400, id='variable-dropdown-forecast')], className='sidebar-first-item'), Div(children=[Card([CardHeader(H2(Button(children=['Models', Span(children=I(className='fa fa-solid fa-angle-up'), id='caret1', className='caret-span')], id='group-1-toggle', className='dropdown', color='link'))), Collapse(children=[CardBody([Checklist(id='model-dropdown', className='sidebar-dropdown', options=[{'label': 'MULTI-MODEL', 'value': 'median'}, {'label': 'MONARCH', 'value': 'monarch'}, {'label': 'CAMS-IFS', 'value': 'cams'}, {'label': 'DREAM8-CAMS', 'value': 'dream8-macc'}, {'label': 'NASA-GEOS', 'value': 'nasa-geos'}, {'label': 'MetOffice-UM', " in str(code.sidebar_forecast(VARS, ['OD550_DUST'], MODELS, ['median'], window='prob', country='burkinafaso')) + + #TEST WAS + assert "[Div(children=[Label('Variable'), Dropdown(options=[{'label': 'AOD', 'value': 'OD550_DUST'}, {'label': 'Concentration', 'value': 'SCONC_DUST'}, {'label': 'Dry deposition', 'value': 'DUST_DEPD'}, {'label': 'Wet deposition', 'value': 'DUST_DEPW'}, {'label': 'Load', 'value': 'DUST_LOAD'}, {'label': 'Extinction', 'value': 'DUST_EXT_SFC'}], value=['OD550_DUST'], clearable=False, searchable=False, optionHeight=50, maxHeight=400, id='variable-dropdown-forecast')], className='sidebar-first-item'), Div(children=[Card([CardHeader(H2(Button(children=['Models', Span(children=I(className='fa fa-solid fa-angle-up'), id='caret1', className='caret-span')], id='group-1-toggle', className='dropdown', color='link'))), Collapse(children=[CardBody([Checklist(id='model-dropdown', className='sidebar-dropdown', options=[{'label': 'MULTI-MODEL', 'value': 'median'}, {'label': 'MONARCH', 'value': 'monarch'}, {'label': 'CAMS-IFS', 'value': 'cams'}, {'label': 'DREAM8-CAMS', 'value': 'dream8-macc'}, {'label': 'NASA-GEOS', 'value': 'nasa-geos'}, {'label': 'MetOffice-UM', 'value': 'metoffice'}, {'label': 'NCEP-GEFS', 'value': 'ncep-gefs'}, {'label': 'EMA-RegCM4', 'value': 'ema-regcm4'}, {'label': 'SILAM'," in str(code.sidebar_forecast(VARS, ['OD550_DUST'], MODELS, ['median'], window='was', country='chad')) diff --git a/tests/test_observations.py b/tests/test_observations.py index c3cf5360deb5467c5224f2ccfe8f54af83be6c31..5f4044fe4d1a561cb441b594c7af776e09885843 100644 --- a/tests/test_observations.py +++ b/tests/test_observations.py @@ -9,7 +9,29 @@ from dash._utils import AttributeDict code = importlib.import_module('tabs.observations') -#=================test sidebar observations =========================== +#=================TEST OBS_TIME_SLIDER=========================== +def test_obs_time_slider(): + #TEST DIV=OBS + assert "NavbarSimple(children=[Div(children=[Span(children=DatePickerSingle(date='20220831', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 31, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 31, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='obs-date-picker'), className='timesliderline'), Span(children=[Button(id='btn-obs-play', className='fa fa-play', n_clicks=0, title='Play')], className='timesliderline anim-buttons'), Span(children=Slider(min=0, max=23, step=1, marks={0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12', 13: '13', 14: '14', 15: '15', 16: '16', 17: '17', 18: '18', 19: '19', 20: '20', 21: '21', 22: '22', 23: '23'}, value=0, id='obs-slider-graph'), className='timesliderline')], className='timeslider')], id='rgb-navbar', className='fixed-bottom navbar-timebar', dark=True, expand='lg', fixed='bottom', fluid=True)" in str (code.obs_time_slider(div='obs', start=0, end=23, step=1)) + + #TEST DIV=OBS-VIS + assert "NavbarSimple(children=[Div(children=[Span(children=DatePickerSingle(date='20220831', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 31, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 31, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='obs-vis-date-picker'), className='timesliderline'), None, Span(children=Slider(min=0, max=23, step=1, marks={0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12', 13: '13', 14: '14', 15: '15', 16: '16', 17: '17', 18: '18', 19: '19', 20: '20', 21: '21', 22: '22', 23: '23'}, value=0, id='obs-vis-slider-graph'), className='timesliderline')], className='timeslider')], id='rgb-navbar', className='fixed-bottom navbar-timebar', dark=True, expand='lg', fixed='bottom', fluid=True)" in str (code.obs_time_slider(div='obs-vis', start=0, end=23, step=1)) + + #TEST DIV = OBS-AOD + assert "NavbarSimple(children=[Div(children=[Span(children=DatePickerSingle(date='20210318', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2021, 3, 18, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2021, 3, 18, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='obs-aod-date-picker'), className='timesliderline'), Span(children=[Button(id='btn-obs-aod-play', className='fa fa-play', n_clicks=0, title='Play')], className='timesliderline anim-buttons'), Span(children=Slider(min=0, max=23, step=1, marks={0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12', 13: '13', 14: '14', 15: '15', 16: '16', 17: '17', 18: '18', 19: '19', 20: '20', 21: '21', 22: '22', 23: '23'}, value=0, id='obs-aod-slider-graph'), className='timesliderline')], className='timeslider')], id='rgb-navbar', className='fixed-bottom navbar-timebar', dark=True, expand='lg', fixed='bottom', fluid=True)" in str (code.obs_time_slider(div='obs-aod', start=0, end=23, step=1)) + + + +#=================TEST SIDEBAR OBSERVATIONS =========================== def test_sidebar_observations(): assert "[Button(children='EUMETSAT RGB', id='rgb', color='link', style={'fontWeight': 'bold'})]" in str(code.sidebar_observations('rgb')) assert "Button(children='Visibility', id='visibility', color='link', style={'fontWeight': 'bold'})" in str(code.sidebar_observations('visibility')) + + +#=================TEST TAB_OBSERVATIONS=========================== +def test_tab_obseravtions(): + #TEST RGB + assert "Tab(children=[Span(children=P('EUMETSAT RGB'), className='description-title'), Span(children=P([B('\\n You can explore key observations that can be used to track dust events. \\n '), ' All observations are kindly offered by Partners of the WMO Barcelona Dust Regional Center. RGB is a qualitative satellite product that indicates desert dust in the entire atmospheric column (represented by pink colour).']), className='description-body'), Div(children=[Button(children='HEMISPHERIC', id='btn-fulldisc', active=True), Button(children='MIDDLE EAST', id='btn-middleeast', active=False)], id='rgb-buttons'), Div(children=[Img(id='rgb-image', alt='EUMETSAT RGB - NOT AVAILABLE', src='./assets/eumetsat/FullDiscHD/archive/20220831/FRAME_OIS_RGB-dust-all_202208310000.gif'), Div(children=NavbarSimple(children=[Div(children=[Span(children=DatePickerSingle(date='20220831', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 31, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 31, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='obs-date-picker'), className='timesliderline'), Span(children=[Button(id='btn-obs-play', className='fa fa-play', n_clicks=0, title='Play')], className='timesliderline anim-buttons'), Span(children=Slider(min=0, max=23, step=1, marks={0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12', 13: '13', 14: '14', 15: '15', 16: '16', 17: '17', 18: '18', 19: '19', 20: '20', 21: '21', 22: '22', 23: '23'}, value=0, id='obs-slider-graph'), className='timesliderline')], className='timeslider')], id='rgb-navbar', className='fixed-bottom navbar-timebar', dark=True, expand='lg', fixed='bottom', fluid=True), className='layout-dropdown')], className='centered-image'), Div(Interval(id='obs-slider-interval', disabled=True, interval=1000, n_intervals=0))], id='observations-tab', className='horizontal-menu', label='Observations', value='observations-tab')" in str(code.tab_observations('rgb')) + + #TEST VISIBILITY + assert "Tab(children=[Span(children=P('Visibility'), className='description-title'), Span(children=P([B('You can explore key observations that can be used to track dust events. '), 'All observations are kindly offered by Partners of the WMO Barcelona Dust Regional Center. The reduction of VISIBILITY is an indirect measure of the occurrence of sand and dust storms on the surface.']), className='description-body'), Div(children=[], id='obs-vis-graph'), Div(children=[NavbarSimple(children=[Div(children=[Span(children=DatePickerSingle(date='20220831', min_date_allowed=datetime.datetime(2012, 1, 20, 0, 0), max_date_allowed=datetime.datetime(2022, 8, 31, 0, 0), placeholder='DD MON YYYY', initial_visible_month=datetime.datetime(2022, 8, 31, 0, 0), clearable=True, reopen_calendar_on_clear=True, display_format='DD MMM YYYY', id='obs-vis-date-picker'), className='timesliderline'), None, Span(children=Slider(min=0, max=18, step=6, marks={0: '0', 6: '6', 12: '12', 18: '18'}, value=0, id='obs-vis-slider-graph'), className='timesliderline')], className='timeslider')], id='rgb-navbar', className='fixed-bottom navbar-timebar', dark=True, expand='lg', fixed='bottom', fluid=True), Br(None), Br(None), Div(children=[Span(children=P('Dust data ©2023 WMO Barcelona Dust Regional Center.'), id='forecast-disclaimer')], className='disclaimer')], className='layout-dropdown rgb-layout-dropdown')], id='observations-tab', className='horizontal-menu', label='Observations', value='observations-tab')" in str(code.tab_observations('visibility'))