diff --git a/assets/custom-functions.js b/assets/custom-functions.js index 0d005059062c1f9768528e9175f270f7f3451c11..ff8bd97ade469081533a92dca2153029dcb545b9 100644 --- a/assets/custom-functions.js +++ b/assets/custom-functions.js @@ -2,25 +2,25 @@ window.forecastTab = Object.assign({}, window.forecastTab, { forecastMaps: { styleHandle: function(feature, context){ // get props from hideout - const {bounds, colorscale, style, colorProp} = context.props.hideout; + const {bounds, colorscale, style, colorProp} = context.props.hideout; // get value the determines the color const value = feature.properties[colorProp]; - for (let i = 0; i < bounds.length; ++i) { - if (value > bounds[i]) { - // set the fill color according to the class - style.fillColor = colorscale[i]; - } - } + for (let i = 0; i < bounds.length; ++i) { + if (value > bounds[i]) { + // set the fill color according to the class + style.fillColor = colorscale[i]; + } + } return style; }, - pointToLayer: function(feature, latlng, context){ + pointToLayer: function(feature, latlng, context){ const {min, max, colorscale, circleOptions, colorProp} = context.props.hideout; // set color based on color prop. circleOptions.fillColor = feature.properties[colorProp]; // sender a simple circle marker. return L.circleMarker(latlng, circleOptions); }, - bindTooltip: function(feature, layer, context) { + bindTooltip: function(feature, layer, context) { const props = feature.properties; // delete props.cluster; layer.bindTooltip(JSON.stringify(props.value), { opacity: 1.0 }) @@ -29,10 +29,10 @@ window.forecastTab = Object.assign({}, window.forecastTab, { wasMaps: { styleHandle: function(feature, context){ // get props from hideout - const {bounds, colorscale, style, colorProp} = context.props.hideout; + const {bounds, colorscale, style, colorProp} = context.props.hideout; // get value the determines the color const value = feature.properties[colorProp]; - style.fillColor = colorscale[value]; + style.fillColor = colorscale[value]; return style; }, } @@ -40,7 +40,7 @@ window.forecastTab = Object.assign({}, window.forecastTab, { window.evaluationTab = Object.assign({}, window.evaluationTab, { evaluationMaps: { - pointToLayer: function(feature, latlng, context){ + pointToLayer: function(feature, latlng, context){ const {circleOptions} = context.props.hideout; // sender a simple circle marker. return L.circleMarker(latlng, circleOptions); @@ -50,7 +50,7 @@ window.evaluationTab = Object.assign({}, window.evaluationTab, { window.observationsTab = Object.assign({}, window.observationsTab, { observationsMaps: { - pointToLayer: function(feature, latlng, context){ + pointToLayer: function(feature, latlng, context){ const {colorscale, circleOptions, colorProp} = context.props.hideout; const value = feature.properties[colorProp]; circleOptions.fillColor = colorscale[value]; @@ -62,60 +62,48 @@ window.observationsTab = Object.assign({}, window.observationsTab, { $(document).ready(function () { $(document).on('click', ".leaflet-popup-close-button", function () { - elements = document.getElementsByClassName("leaflet-popup-close-button"); - - for (let i = 0; i < elements.length; i++) { - var elem = elements[i]; - // elem.replaceWith(elem.clone()); - alert(elem.innerHTML); - } + elements = document.getElementsByClassName("leaflet-popup-close-button"); + + for (let i = 0; i < elements.length; i++) { + var elem = elements[i]; + // elem.replaceWith(elem.clone()); + alert(elem.innerHTML); + } - alert('Closing!'); + alert('Closing!'); elements[0].close(); }); }); // Add logos for the animated gifs $(document).ready(function () { - $(document).on('click', "#slider-graph", function () { - const logos = document.getElementById('logos'); - if(!logos) { - var img = new Image(); - img.src = './assets/images/logoline.png'; - img.style.position = 'absolute'; - img.style.width = '40rem'; - img.style.height = '5rem'; - img.style.top = '0'; - img.style.left = '23rem'; - img.style.zIndex = '999'; - img.setAttribute('id','logos'); - img.style.display = 'none'; + $(document).on('click', "#slider-graph", function () { + const logos = document.getElementById('logos'); + if(!logos) { + var img = new Image(); + img.src = './assets/images/logoline.png'; + img.style.position = 'absolute'; + img.style.width = '40rem'; + img.style.height = '5rem'; + img.style.top = '0'; + img.style.left = '23rem'; + img.style.zIndex = '999'; + img.setAttribute('id','logos'); + img.style.display = 'none'; - // Get the element to overlay the image on - var div = document.querySelectorAll('.leaflet-map-pane')[0]; + // Get the element to overlay the image on + var div = document.querySelectorAll('.leaflet-map-pane')[0]; - // Append the image to the element - div.appendChild(img); - } - }); + // Append the image to the element + div.appendChild(img); + } + }); }); -// MAKE FULLSCREEN ICON SMALLER WHEN APP IS FULLSCREEN -function changeFullscreenIcon() { - const isFullscreen = window.innerWidth === screen.width && window.innerHeight === screen.height; - const button = document.getElementById('fullscreen-tab'); - if (isFullscreen) { - button.classList.remove('small'); - } else { - button.classList.add('small'); - } -} - // SEND FULLSCREEN REQUEST TO PARENT WINDOW $(document).ready(function () { $(document).on('click', "#fullscreen-tab", function () { parent.postMessage('fullscreen', '*'); - changeFullscreenIcon(); }) }); @@ -123,11 +111,11 @@ $(document).ready(function () { $(window).on('load', function removeFullscreen () { const tab = document.getElementById('fullscreen-tab'); if (tab != null) { - if (window.self === window.top){ - tab.style.visibility = "hidden"; - } + if (window.self === window.top){ + tab.style.visibility = "hidden"; + } } else { - setTimeout(removeFullscreen, 5); + setTimeout(removeFullscreen, 5); } }); @@ -155,39 +143,39 @@ $(window).on('resize', function () { $(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; + 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 - } - }; + //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); - }); + //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); + }); }); }); @@ -312,8 +300,8 @@ $(document).ready(function () { }) $(document).ready(function () { - setTimeout(tiltValues, 1500); - setTimeout(setWidthForColorbars, 1500); + setTimeout(tiltValues, 900); + setTimeout(setWidthForColorbars, 900); }); //==================END Funcfions to flip adjust colorbar values for only concentration var ========== diff --git a/assets/download-img.js b/assets/download-img.js index b811e093a10d06d2cac3f3c6b8af5251affd142c..69a5ec9212b9a02a43d3183980e38b60ab68dad2 100644 --- a/assets/download-img.js +++ b/assets/download-img.js @@ -21,7 +21,7 @@ function getCanvas(element) { allowTaint: true, useCORS: true, async: true, - windowWidth: element.offsetWidth, + windowWidth: element.offsetWidth + 30, windowHeight: element.offsetHeight, logging: true, imageTimeout: 0, diff --git a/assets/style.css b/assets/style.css index f7584c9e78a8ef9fb3562609713e6d862362a339..88c65691a5d5be82082773655a98a8c6f7da897b 100644 --- a/assets/style.css +++ b/assets/style.css @@ -910,7 +910,11 @@ div.dropdown-menu.show { } .rc-slider-mark-text-active { - color:var(--yellow); + color: #999 !important; +} + +.rc-slider-mark-text { + color: var(--yellow); } .rc-slider-handle{ diff --git a/conf/coords.json b/conf/coords.json index 20b98dfaf048e05fee559056a3d8d177f7fe6622..82910f32e973b234ecd867a618e67af8c425f1a9 100644 --- a/conf/coords.json +++ b/conf/coords.json @@ -6,7 +6,8 @@ "width": "1280", "height": "768", "paddingRight": "5px", - "marginTop": "0px" + "marginTop": "0px", + "logos": true }, "spain":{ "zoom":"5", @@ -15,7 +16,8 @@ "width": "1280", "height": "768", "paddingRight": "5px", - "marginTop": "0px" + "marginTop": "0px", + "logos": true }, "fit":{ "zoom":"4", @@ -24,7 +26,8 @@ "width": "1250", "height": "1100", "paddingRight": "5px", - "marginTop": "0px" + "marginTop": "0px", + "logos": true }, "monarch_fit":{ "zoom":"3", @@ -33,7 +36,8 @@ "width": "1180", "height": "720", "paddingRight": "35px", - "marginTop": "20px" + "marginTop": "20px", + "logos": true } } diff --git a/js/create_model_loop_zoom_fit.js b/js/create_model_loop_zoom_fit.js index 3af6a669987aaf670078056cdfc7cb82d90fcb4e..77d3b4ce1981d9087e88f38ee11b132197762376 100644 --- a/js/create_model_loop_zoom_fit.js +++ b/js/create_model_loop_zoom_fit.js @@ -7,8 +7,8 @@ const { Cluster } = require('puppeteer-cluster'); const util = require('util'); const path = require('path'); -const url = 'http://127.0.0.1:9000/dashboard/' -// const url = 'http://0.0.0.0:7778/dashboard/' +// const url = 'http://127.0.0.1:9000/dashboard/' +const url = 'http://0.0.0.0:7778/dashboard/' const modelDict = {'od550_dust':'AOD', 'sconc_dust':'Concentration', 'dust_depd':'Dry deposition', @@ -54,7 +54,7 @@ const RunCluster = async (anim, curmodel, seldate, variable, fit) => { waitUntil: 'networkidle0', }); await page.waitForSelector("#graph-collection"); - await page.waitForSelector(".graph-with-slider"); + // await page.waitForSelector(".graph-with-slider"); // select variable try { const sel = await page.$('#variable-dropdown-forecast'); @@ -188,15 +188,16 @@ const RunCluster = async (anim, curmodel, seldate, variable, fit) => { } }, '.leaflet-bar'); - //reveal logos - process.stdout.write("REVEALING LOGOS" + "\n"); - - //style logos - let logos = await page.$('#logos'); - await logos.evaluate((el) => el.style.display = 'inline'); - await logos.evaluate((el, margin) => {el.style.marginTop = margin}, coords[fit].marginTop); - await logos.evaluate((el, padding) => {el.style.paddingRight = padding}, coords[fit].paddingRight); + if (coords[fit].logos == true){ + //reveal logos + process.stdout.write("REVEALING LOGOS" + "\n"); + //style logos + let logos = await page.$('#logos'); + await logos.evaluate((el) => el.style.display = 'inline'); + await logos.evaluate((el, margin) => {el.style.marginTop = margin}, coords[fit].marginTop); + await logos.evaluate((el, padding) => {el.style.paddingRight = padding}, coords[fit].paddingRight); + }; // Handle output and take screenshot const outputFile = './tmp/' + variable.toLowerCase() + '/' + fit + '/' + seldate + '_' + curmodel + '_' + fit + '_' + num + '.png'; process.stdout.write("TAKE SCREENSHOT => " + outputFile + "\n"); diff --git a/tabs/evaluation_callbacks.py b/tabs/evaluation_callbacks.py index c019f4cad8d308a30ea44e3e8e9591ba8e502a38..dcb4ae333b3f403de978360c7672524721e13797 100644 --- a/tabs/evaluation_callbacks.py +++ b/tabs/evaluation_callbacks.py @@ -253,6 +253,33 @@ def format_floats(df): df.at[i, col] = '{:.2f}'.format(float(val)) return df +def alphabetize_stations(df): +# def alphabetize_stations(filepath, tab_name): + """ This will alphabetize the stations for each region in the aeronet stats table""" + # Define a list of regions to sort between (in the desired order) + regions = ['Europe', 'Mediterranean', 'MiddleEast', 'NAfrica', 'Total'] + region_dfs = [] + # Iterate over each pair of consecutive regions and sort the rows between them + for i in range(len(regions)-1): + # Get the indices of the current and next regions + current_region_idx = df[df['station'] == regions[i]].index[0] + next_region_idx = df[df['station'] == regions[i+1]].index[0] + # Slice the dataframe to select the rows between the current and next regions + subset_df = df.loc[current_region_idx+1:next_region_idx-1] + # Sort the rows alphabetically based on the 'station' column + subset_df = subset_df.sort_values(by='station') + # Create a new dataframe containing only the current region row + current_region_row = df.loc[current_region_idx].to_frame().T + # Append the current region row and sorted subset dataframe to the list of region DataFrames + region_dfs.append(current_region_row) + region_dfs.append(subset_df) + # Add the 'Total' row back to the end of the sorted dataframe + total_row_idx = df[df['station'] == 'Total'].index[0] + total_row = df.loc[total_row_idx].to_frame().T + region_dfs.append(total_row) + # Concatenate all of the region DataFrames into a single sorted DataFrame + sorted_df = pd.concat(region_dfs) + return sorted_df @dash.callback( extend_l([ @@ -345,9 +372,8 @@ def aeronet_scores_tables_retrieve(n, *args): # *activel_cells, models, stat, n return no_data df = pd.read_hdf(filepath, tab_name) - # replace "tables" columns - # import pudb; pudb.set_trace() df = format_floats(df) + df = alphabetize_stations(df) ret_tables[ret_idx] = [{'name': i in MODELS and [STATS[SCORES[table_idx]], MODELS[i]['name']] or diff --git a/tabs/forecast_callbacks.py b/tabs/forecast_callbacks.py index fa8f36f3e2cdbe8722b956a6517870fd141ccf24..eb78b077b0015d168dc69e0e0fd897d69493ecde 100644 --- a/tabs/forecast_callbacks.py +++ b/tabs/forecast_callbacks.py @@ -771,6 +771,22 @@ def zoom_country(n_clicks, model, zoom, lat, lon): center = [[float(lat), float(lon)]] return zoom*count, center*count +@dash.callback( + Output('country-focus', 'n_clicks'), + Output('country-zoom', 'value'), + Output('country-lat', 'value'), + Output('country-lon', 'value'), + Input({'tag': 'model-map', 'index': ALL, "n_clicks": ALL}, 'viewport'), + Input({'tag': 'model-map', 'index': ALL, "n_clicks": ALL}, 'id'), + prevent_initial_call=True + ) +def zooms(viewport, models): + """Syncronize all maps to have same center and zoom in mosaic""" + ctx = dash.callback_context + changed = dict(ctx.triggered_id) + index = models.index(changed) + return 1, viewport[index]['zoom'], viewport[index]['center'][0], viewport[index]['center'][1] + # start/stop animation @dash.callback( diff --git a/tests/test_observations.py b/tests/test_observations.py index 5f4044fe4d1a561cb441b594c7af776e09885843..32103deba3fe4293ea6591c9c26847d92393ac90 100644 --- a/tests/test_observations.py +++ b/tests/test_observations.py @@ -15,7 +15,7 @@ def test_obs_time_slider(): 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)) + 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: {'label': '00-01'}, 1: {'label': '01-02'}, 2: {'label': '02-03'}, 3: {'label': '03-04'}, 4: {'label': '04-05'}, 5: {'label': '05-06'}, 6: {'label': '06-07'}, 7: {'label': '07-08'}, 8: {'label': '08-09'}, 9: {'label': '09-10'}, 10: {'label': '10-11'}, 11: {'label': '11-12'}, 12: {'label': '12-13'}, 13: {'label': '13-14'}, 14: {'label': '14-15'}, 15: {'label': '15-16'}, 16: {'label': '16-17'}, 17: {'label': '17-18'}, 18: {'label': '18-19'}, 19: {'label': '19-20'}, 20: {'label': '20-21'}, 21: {'label': '21-22'}, 22: {'label': '22-23'}, 23: {'label': '23-24', 'style': {'left': '', 'right': '-32px'}}}, value=6, 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)) @@ -34,4 +34,4 @@ def test_tab_obseravtions(): 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')) + 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', className='graph-with-slider'), 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: {'label': '00-06'}, 6: {'label': '06-12'}, 12: {'label': '12-18'}, 18: {'label': '18-24', 'style': {'left': '', 'right': '-32px'}}}, value=6, 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'))