wms-gfs / app_p.py
odisha's picture
Update app_p.py
fb9df2d verified
raw
history blame
23.6 kB
import streamlit as st
import datetime
import matplotlib.pyplot as plt
import xarray as xr
from ncep_data_req import get_data_preprocess # Update this to your actual package/module
import io
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from cartopy.mpl.ticker import (LongitudeFormatter, LatitudeFormatter,
LatitudeLocator)
import matplotlib.ticker as mticker
import time
import metpy.calc as mpcalc
import numpy as np
import tempfile
from streamlit_folium import st_folium
import folium
from folium.plugins import Draw
from PIL import Image
import io
from folium.raster_layers import ImageOverlay
from metpy.units import units
from metpy.calc import dewpoint_from_relative_humidity
from metpy.plots import SkewT, Hodograph
import seaborn as sns
import colormaps as cmaps
from colormaps.utils import show_cmaps_all
from colormaps.utils import show_cmaps_collection
import json
sns.set_theme( style='ticks',font_scale=1.75)
color = sns.color_palette('tab10')
# with open('DISTRICT_BOUNDARY.json', 'r') as f:
# geojson_data = json.load(f)
def plot_to_numpy_array(data, colormap="viridis", robust=False):
fig, ax = plt.subplots(figsize=(6, 6), dpi=150)
data.plot.contourf(ax=ax, cmap=colormap, robust=robust,
add_colorbar=False)
ax.set_axis_off()
buf = io.BytesIO()
plt.savefig(buf, format="png", bbox_inches="tight", pad_inches=0, transparent=True)
buf.seek(0)
plt.close(fig)
img = Image.open(buf).convert("RGBA")
return np.array(img) # Return as NumPy array
def plotc_to_numpy_array(data, linewidth=5,color='k'):
fig, ax = plt.subplots(figsize=(6, 6), dpi=150)
CS=data.plot.contour(ax=ax,levels=11,linewidths=linewidth,color='black',kwargs=dict(inline=True))
ax.clabel(CS)
ax.set_axis_off()
buf = io.BytesIO()
plt.savefig(buf, format="png", bbox_inches="tight", pad_inches=0, transparent=True)
buf.seek(0)
plt.close(fig)
img = Image.open(buf).convert("RGBA")
return np.array(img) # Return as NumPy array
gfs_pressure_variables = [
# "absvprs", # Absolute vorticity [[1]]
# "clwmrprs", # Cloud water mixing ratio [[1]]
"dzdtprs", # Vertical velocity (dz/dt)
#"grleprs", # Graupel mixing ratio
"hgtprs", # Geopotential height [[1]]
#"icmrprs", # Ice mixing ratio
"o3mrprs", # Ozone mixing ratio
"rhprs", # Relative humidity [[1]]
# "rwmrprs", # Rainwater mixing ratio
# "snmrprs", # Snow mixing ratio
"spfhprs", # Specific humidity [[1]]
#"tcdcprs", # Total cloud cover
"tmpprs", # Temperature [[1]]
"ugrdprs", # U-component of wind (east-west) [[1]]
"vgrdprs", # V-component of wind (north-south) [[1]]
"vvelprs" # Vertical velocity (pressure vertical velocity) [[1]]
]
surface_variables = [
'tmax2m','tmin2m','rh2m','tmp2m','spfh2m','dpt2m',
# "no4lftxsfc", # Surface best (4 layer) lifted index [K]
"acpcpsfc", # Surface convective precipitation [kg/m^2]
# "albdosfc", # Surface albedo [%]
"apcpsfc", # Surface total precipitation [kg/m^2]
"capesfc", # Surface convective available potential energy [J/kg]
# "cfrzravesfc", # Surface categorical freezing rain [-]
# "cfrzrsfc", # Surface categorical freezing rain [-]
# "cicepavesfc", # Surface categorical ice pellets [-]
# "cicepsfc", # Surface categorical ice pellets [-]
"cinsfc", # Surface convective inhibition [J/kg]
# "cnwatsfc", # Surface plant canopy surface water [kg/m^2]
# "cpofpsfc", # Surface percent frozen precipitation [%]
# "cpratavesfc", # Surface convective precipitation rate [kg/m^2/s]
# "cpratsfc", # Surface convective precipitation rate [kg/m^2/s]
# "crainavesfc", # Surface categorical rain [-]
# "crainsfc", # Surface categorical rain [-]
# "csnowavesfc", # Surface categorical snow [-]
# "csnowsfc", # Surface categorical snow [-]
# "dlwrfsfc", # Surface downward long-wave radiation flux [W/m^2]
"dswrfsfc", # Surface downward short-wave radiation flux [W/m^2]
# "fldcpsfc", # Surface field capacity [fraction]
# "fricvsfc", # Surface frictional velocity [m/s]
# "gfluxsfc", # Surface ground heat flux [W/m^2]
"gustsfc", # Surface wind speed (gust) [m/s]
"hgtsfc", # Surface geopotential height [gpm]
# "hindexsfc", # Surface Haines index [numeric]
"hpblsfc", # Surface planetary boundary layer height [m]
# "icecsfc", # Surface ice cover [proportion]
# "icetksfc", # Surface ice thickness [m]
# "icetmpsfc", # Surface ice temperature [K]
# "landsfc", # Surface land cover (0=sea, 1=land) [proportion]
# "lftxsfc", # Surface surface lifted index [K]
"lhtflsfc", # Surface latent heat net flux [W/m^2]
"pevprsfc", # Surface potential evaporation rate [W/m^2]
"prateavesfc", # Surface precipitation rate [kg/m^2/s]
"pratesfc", # Surface precipitation rate [kg/m^2/s]
"pressfc", # Surface pressure [Pa]
"sfcrsfc", # Surface surface roughness [m]
"shtflsfc", # Surface sensible heat net flux [W/m^2]
# "snodsfc", # Surface snow depth [m]
# "sotypsfc", # Surface soil type [-]
# "sunsdsfc", # Surface sunshine duration [s]
"tmpsfc", # Surface temperature [K]
# "ugwdsfc", # Surface zonal flux of gravity wave stress [N/m^2]
"uflxsfc", # Surface momentum flux, u-component [N/m^2]
"ulwrfsfc", # Surface upward long-wave radiation flux [W/m^2]
"uswrfsfc", # Surface upward short-wave radiation flux [W/m^2]
# "vgwdsfc", # Surface meridional flux of gravity wave stress [N/m^2]
# "vegsfc", # Surface vegetation [%]
"vflxsfc", # Surface momentum flux, v-component [N/m^2]
"vissfc", # Surface visibility [m]
"watrsfc", # Surface water runoff [kg/m^2]
# "weasdsfc", # Surface water equivalent of accumulated snow depth [kg/m^2]
"wiltsfc", # Surface wilting point [fraction]
"prmslmsl" # Mean seal level pressure
]
def plot_skewt(T,rh,u,v,z1,lon=60,lat=60,ax=None):
T=T.values* units.K
rh=rh.values
z=z1
T=T.to(units.degC)
Td=dewpoint_from_relative_humidity(T, rh * units.percent)
lev=[1000.0, 975.0, 950.0, 925.0, 900.0, 850.0, 800.0,
750.0, 700.0, 650.0, 600.0, 550.0, 500.0, 450.0, 400.0, 350.0, 300.0,
250.0, 200.0, 150.0, 100.0, 70.0, 50.0, 40.0, 30.0, 20.0]
p=lev*units.hPa
# start_date = datetime.datetime(yy,mm,dd, tzinfo=datetime.timezone.utc)+ timedelta(hours=ft)+timedelta(hours=utc)
if ax is None:
fig = plt.figure(figsize=(9,12))
else:
fig = ax.figure
skew = SkewT(fig, rotation=45)
skew.plot(p, T, 'r')
skew.plot(p, Td, 'g')
skew.plot_barbs(p, u, v)
skew.ax.set_ylim(1000, 100)
# skew.ax.set_xlim(-40, 60)
# Set some better labels than the default
skew.ax.set_xlabel(f'Temperature ({T.units:~P})')
skew.ax.set_ylabel(f'Pressure ({p.units:~P})')
timestamp = f'Atmospheric sounding at {lon:.2f}N {lat:.2f}E '
plt.title(timestamp)
# plt.figtext(0.8, 0.2, "\u00A9 Subhrajit,2025",fontsize=12,fontweight='bold',
# horizontalalignment="right")
# plt.figtext(0.6, 0.2, "Data Source: NCEP_NOMADS",fontsize=12,fontweight='bold',
# horizontalalignment="right")
lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0])
skew.plot(lcl_pressure, lcl_temperature, 'ko', markerfacecolor='black')
# Calculate full parcel profile and add to plot as black line
prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')
skew.plot(p, prof, 'k', linewidth=2)
skew.shade_cin(p, T, prof, Td)
skew.shade_cape(p, T, prof)
# An example of a slanted line at constant T -- in this case the 0
# isotherm
skew.ax.axvline(0, color='c', linestyle='--', linewidth=2)
# Add the relevant special lines
skew.plot_dry_adiabats()
skew.plot_moist_adiabats()
skew.plot_mixing_lines()
plt.tight_layout()
return fig, ax
# # ax = plt.axes((1.01, 0.5, 0.2, 0.2))
# # h = Hodograph(ax, component_range=60.)
# # h.add_grid(increment=15)
# # h.plot(u, v)
# # print(T)
# plt.show()
# plt.savefig('skewt.png', dpi=300, bbox_inches='tight')
def plot_vertical_wind_shear(ds, var_name,var_name1, extent=None, time_index=0,ax=None):
# Set default extent if none provided
if extent is None:
extent = [60,100,0,40]
# Select the time slice
ds_time = ds
wslice = 10
# Extract u and v wind components at 850 hPa and 500 hPa
u_850 = ds_time['ugrdprs']['ugrdprs'].isel(time=time_index).sel(levels=850,method='nearest') #* units('m/s')
v_850 = ds_time['vgrdprs']['vgrdprs'].isel(time=time_index).sel(levels=850,method='nearest')# * units('m/s')
u_500 = ds_time['ugrdprs']['ugrdprs'].isel(time=time_index).sel(levels=200,method='nearest') #* units('m/s')
v_500 = ds_time[f'vgrdprs']['vgrdprs'].isel(time=time_index).sel(levels=200,method='nearest')# * units('m/s')
# Calculate wind shear components
u_shear = (u_500 - u_850)*1
v_shear = (v_500 - v_850)*1
# Create meshgrid for plotting
lon, lat = u_850['lon'], u_850['lat']
lon_2d, lat_2d = np.meshgrid(lon, lat)
if ax is None:
fig = plt.figure( facecolor='white')
# ax = plt.axes(projection=ccrs.PlateCarree())
# ax.set_extent(extent)
# ax.patch.set_fill(False)
# ax.add_feature(cfeature.STATES, edgecolor='white', linewidth=2)
# Subsample for quiver density
wslice = slice(1, None, 7)
# Quiver plots
ax.quiver(lon_2d[wslice, wslice], lat_2d[wslice, wslice],
u_850[wslice, wslice], v_850[wslice, wslice],
headlength=4, headwidth=3, angles='xy', scale_units='xy',
color='gold', scale=10, label='850 hPa wind')
ax.quiver(lon_2d[wslice, wslice], lat_2d[wslice, wslice],
u_500[wslice, wslice], v_500[wslice, wslice],
headlength=4, headwidth=3, angles='xy', scale_units='xy',
color='cornflowerblue', scale=8, label='200 hPa wind')
ax.quiver(lon_2d[wslice, wslice], lat_2d[wslice, wslice],
u_shear[wslice, wslice], v_shear[wslice, wslice],
headlength=4, headwidth=3, angles='xy', scale_units='xy',
scale=10, color='deeppink', label='200–850 hPa shear')
# Add legend and title only if using standalone figure
if ax.get_figure():
ax.legend(loc='lower right',bbox_to_anchor=(1.3, -0.01),fontsize=8)
# ax.set_title('850/500 hPa Wind & Vertical Shear', color='white',)
return ax
gfs_vars=gfs_pressure_variables + surface_variables
#["tmpprs", "rhprs", 'rh2m','pratesfc', 'apcpsfc','prmslmsl', "ugrdprs","ugrdprs", "vgrdprs",'uflxsfc']
st.set_page_config(layout="wide")
st.title(" GFS Viewer")
st.markdown("""
<style>
.block-container {
padding-top: 1.9rem;
padding-left:10px;
}
</style>
""", unsafe_allow_html=True)
# with st.expander("Map selection"):
# st.markdown(" **Draw Bounding Box on Map**")
# m = folium.Map(location=[20, 80], zoom_start=4)
# # Add drawing control
# draw = Draw(export=True, draw_options={
# 'polyline': False,
# 'circle': False,
# 'polygon': False,
# 'marker': True,
# 'circlemarker': False,
# 'rectangle': True,
# })
# draw.add_to(m)
# output = st_folium(m, height=600, width=900, key="bbox_draw")
# bbox_coords = None
# if output and output.get("last_active_drawing") and output["last_active_drawing"]["geometry"]["type"] == "Polygon":
# coords = output["last_active_drawing"]["geometry"]["coordinates"][0]
# lons = [c[0] for c in coords]
# lats = [c[1] for c in coords]
# lon_min, lon_max = min(lons), max(lons)
# lat_min, lat_max = min(lats), max(lats)
# bbox_coords = (lon_min, lon_max, lat_min, lat_max)
# st.success(f" Bounding Box Selected:\nLon: {lon_min:.2f}–{lon_max:.2f}, Lat: {lat_min:.2f}–{lat_max:.2f}")
# else:
# st.warning(" Please draw a rectangular bounding box on the map.")
# if "show_map" not in st.session_state:
# st.session_state["show_map"] = True
col1, col2,col3,col4 = st.columns([20, 40,20,25])
with st.sidebar:
st.subheader("Parameter setting") # Optional title
with st.expander("📅 Forecast Parameters", expanded=False):
selected_date = st.date_input("Forecast Start Date", datetime.date.today())
selected_utc = st.selectbox("UTC Initialization Hour", [0, 6, 12, 18])
forecast_hour = st.slider("Forecast Length (Hours)", 1, 120, 36)
with st.expander("🌐 Geographic Bounds", expanded=False):
lon_min = st.number_input("Longitude Min", value=60.0)
lon_max = st.number_input("Longitude Max", value=100.0)
lat_min = st.number_input("Latitude Min", value=0.0)
lat_max = st.number_input("Latitude Max", value=40.0)
bbox_coords = (lon_min, lon_max, lat_min, lat_max)
with st.expander(":variables Variables", expanded=True):
variables = st.multiselect(
"Select Variable(s)",
gfs_vars,
['prmslmsl','tmpprs','rhprs','hgtprs','ugrdprs','vgrdprs']
)
pressure_level_vars = [v for v in variables if v in gfs_pressure_variables]
surface_vars = [v for v in variables if v in surface_variables]
st.markdown(selected_date)
# Fetch Data Button at the bottom of sidebar
fetch = st.button("Fetch Data")
# Data fetching logic
if fetch:
st.session_state["show_map"] = False
with st.spinner("Fetching and processing data..."):
ds_dict = {}
# Fetch surface variables
for var in surface_vars:
ds = get_data_preprocess(
date=selected_date,
utc=selected_utc,
ft=forecast_hour,
var=var,
pvar="no",
lon_range=(bbox_coords[0], bbox_coords[1]),
lat_range=(bbox_coords[2], bbox_coords[3])
)
ds_dict[var] = ds
print("done surface_var")
# Fetch pressure-level variables
for var in pressure_level_vars:
ds = get_data_preprocess(
date=selected_date,
utc=selected_utc,
ft=forecast_hour,
var=var,
pvar="yes",
lon_range=(bbox_coords[0], bbox_coords[1]),
lat_range=(bbox_coords[2], bbox_coords[3])
)
ds_dict[var] = ds
print("done pressure_var")
# Save to session state
st.session_state["ds_dict"] = ds_dict
st.session_state["variables"] = variables
st.session_state["pressure_level_vars"] = pressure_level_vars
st.session_state["surface_vars"] = surface_vars
st.success("All variables fetched!")
# Download button
if "ds_dict" in st.session_state:
merged_ds = xr.merge([ds for ds in st.session_state["ds_dict"].values()])
with tempfile.NamedTemporaryFile(suffix=".nc", delete=False) as tmpfile:
merged_ds.to_netcdf(tmpfile.name)
tmpfile.flush()
with open(tmpfile.name, "rb") as f:
st.download_button(
data=f,
label="📥 Download Data (NetCDF)",
file_name="gfs_data.nc",
mime="application/x-netcdf"
)
with col1:
with st.expander("🎨 Visualization Options", expanded=True):
colormap = st.selectbox(
"Select Colormap",
options=["viridis", "plasma", "inferno", "magma", "cividis", "coolwarm", "jet", "turbo"],
index=6,
)
add_windshear = st.checkbox("Overlay Wind Shear", value=False)
add_pressure_contours = st.checkbox("Overlay Surface Pressure (pressfc)", value=False)
rob = st.checkbox("Robust set?", value=True)
with col2:
sns.set_theme( style='ticks',font="arial", font_scale=1.75)
color = sns.color_palette('tab10')
if "ds_dict" in st.session_state:
ds_dict = st.session_state["ds_dict"]
variables = st.session_state["variables"]
viewable_vars = [v for v in variables if v != "prmslmsl"]
selected_var = st.selectbox(" Select Variable to View", viewable_vars)
ds = ds_dict[selected_var]
if selected_var == pressure_level_vars:
is_pressure_level = st.checkbox("Is this a pressure-level variable?", value=False)
else:
is_pressure_level = st.checkbox("Is this a pressure-level variable?", value=True)
st.session_state["is_pressure_level"] = is_pressure_level
if selected_var == pressure_level_vars:
is_pressure_level = st.session_state["is_pressure_level"]
st.markdown(f" Plot of `{selected_var}`")
if is_pressure_level:
time_idx = st.slider("Time Index", 1, len(ds.time) - 1, 1)
level_idx = st.slider("Pressure Level Index", 0, len(ds.levels) - 1, 0)
data = ds[selected_var].isel(time=time_idx, levels=level_idx)
else:
# time_idx = st.slider("Time Index", 0, len(ds.time) - 1, 0)
time_idx = st.slider(" Time Index", 1, len(ds.time) - 1, key="time_slider")
data = ds[selected_var].isel(time=time_idx)
if add_windshear:
if "ugrdprs" not in st.session_state["ds_dict"]:
st.warning(" 'ugrdprs' not loaded — add it to your variable list to plot contours.")
add_pressure_contours = False # disable if not available
else:
uwind = st.session_state["ds_dict"]
# vwind = st.session_state["ds_dict"]["vgrdprs"]
wind_data = uwind#.isel(time=time_idx)
dat1 = data.copy()
if selected_var in ["pratesfc"]:
dat1 = dat1 * 3600
dat1 = dat1.where(dat1 >= 0.01)
# dat1['pratesfc'] = xr.where(dat1['pratesfc'],dat1['pratesfc'] >= 0.1, 0)
elif selected_var in ["apcpsfc"]:
dat1 = dat1.where(dat1 >= 0.01)
else:
dat1 = data
st.session_state["dat1"] = dat1
if add_pressure_contours:
if "prmslmsl" not in st.session_state["ds_dict"]:
st.warning(" 'prmslmsl' not loaded — add it to your variable list to plot contours.")
add_pressure_contours = False # disable if not available
else:
pressfc_ds = st.session_state["ds_dict"]["prmslmsl"]
pressure_data = pressfc_ds["prmslmsl"].isel(time=time_idx)/100
fig, ax = plt.subplots(subplot_kw={'projection': ccrs.PlateCarree()})
mm=dat1.plot.contourf(ax=ax, cmap=colormap, robust=rob,
transform=ccrs.PlateCarree(),
cbar_kwargs={
"orientation": "vertical",
"shrink": 0.6,
"aspect": 20,
"pad": 0.005, "label":selected_var })
cbar = mm.colorbar
cbar.ax.tick_params(labelsize=12)
cbar.set_label(selected_var, fontsize=14, fontweight='bold',labelpad=-0.5)
title = f"{str(dat1.time.values)[:-13]}" # Adjust this based on your data structure
ax.set_title(title,fontsize=12)
levels=(bbox_coords[1]-bbox_coords[0])/2
if add_pressure_contours:
cs = pressure_data.plot.contour(ax=ax, colors='black', linewidths=1,
levels=levels+1, transform=ccrs.PlateCarree())
ax.clabel(cs, fontsize=8) # Set font size to 3
title = f"{str(dat1.time.values)[:-13]} and overlay MSLP" # Adjust this based on your data structure
ax.set_title(title,fontsize=12)
if add_windshear:
cs= plot_vertical_wind_shear(wind_data,'ugrdprs','vgrdprs',extent=bbox_coords,
time_index=time_idx,ax=ax)
title = f"{str(dat1.time.values)[:-13]} and overlay Wind shear , vector " # Adjust this based on your data structure
ax.set_title(title,fontsize=12)
ax.coastlines(resolution='50m')
gl = ax.gridlines(draw_labels=True)
gl.top_labels = False
gl.right_labels = False
# gl.xlines = True
# gl.ylines = True
gl.ylocator = LatitudeLocator()
gl.xformatter = LongitudeFormatter()
gl.yformatter = LatitudeFormatter()
# gl.xlocator = mticker.MaxNLocator(11)
# gl.ylocator = mticker.MaxNLocator(11)
XTEXT_SIZE = 8
YTEXT_SIZE = 8
gl.xlabel_style = {'size': XTEXT_SIZE, 'color': 'k', 'rotation':45, 'ha':'right'}
gl.ylabel_style = {'size':YTEXT_SIZE, 'color': 'k', 'weight': 'normal'}
with st.expander("🎨 Sounder ", expanded=True):
st.pyplot(fig)
with col3:
if "ds_dict" in st.session_state:
# with st.expander(" Show Time Series and Data Table"):
st.subheader(" Time Series at Location")
lat1 = st.number_input("Latitude ", min_value=-90.0, max_value=90.0, value=20.0, step=0.1)
lon1 = st.number_input("Longitude ", min_value=0.0, max_value=360.0, value=80.0, step=0.1)
if selected_var == pressure_level_vars:
is_pressure_level = st.session_state["is_pressure_level"]
# Extract time series at nearest point
if is_pressure_level:
ts_data = ds[selected_var].isel(levels=level_idx).sel(lat=lat1, lon=lon1,method='nearest')[1:]
ts_data_1= ds[selected_var].isel(time=time_idx).sel(lat=lat1, lon=lon1,method='nearest')[1:]
# ts_data_1.plot(ax=ax3, y='levels',marker='*',color='b')
# ax3.set_title(f"Time Series of `{selected_var}` at ({lat1:.2f}, {lon1:.2f})")
# ax3.set_xlabel(selected_var)
# ax3.set_ylabel("level")
# plt.gca().invert_yaxis()
# st.pyplot(fig3)
else:
ts_data = ds[selected_var].sel(lat=lat1, lon=lon1,method='nearest')[1:]
# Scale time series if needed
if selected_var == "pratesfc":
ts_data = ts_data * 3600
ts_data = ts_data.where(ts_data >= 0.1)
elif selected_var == "apcpsfc":
ts_data = ts_data.where(ts_data >= 0.1)
else:
ts_data=ts_data
with st.expander("🎨 Timeseries Options", expanded=False):
# st.pyplot(fig2)
fig2, ax2 = plt.subplots()
ts_data.plot(ax=ax2, marker='o')
ax2.set_title(f"Time Series of `{selected_var}` at ({lat1:.2f}, {lon1:.2f})")
ax2.set_xlabel("Time")
ax2.set_ylabel(selected_var)
st.pyplot(fig2)
# st.dataframe(ts_data.to_dataframe().reset_index())
with col4:
with st.expander("🎨 Sounder ", expanded=True):
T=ds_dict['tmpprs']['tmpprs'].isel(time=time_idx).sel(lat=lat1, lon=lon1,method='nearest')[:]
rh=ds_dict['rhprs']['rhprs'].isel(time=time_idx).sel(lat=lat1, lon=lon1,method='nearest')[:]
u=ds_dict['ugrdprs']['ugrdprs'].isel(time=time_idx).sel(lat=lat1, lon=lon1,method='nearest')[:]
v=ds_dict['vgrdprs']['vgrdprs'].isel(time=time_idx).sel(lat=lat1, lon=lon1,method='nearest')[:]
z11=ds_dict['hgtprs']['hgtprs'].isel(time=time_idx).sel(lat=lat1, lon=lon1,method='nearest')[:]
fig = plt.figure(figsize=(9, 12))
fig4, ax = plot_skewt(T, rh, u, v, z1=z11,lon=lon1, lat=lat1, ax=None)
# st.markdown(fig4)
st.pyplot(fig4)
# st.dataframe(ts_data1.to_dataframe().reset_index())