Temperature range bars
daily high/low from Open-Meteo
Example from the compendium of canonical charts
Python Code
"""Temperature range bars — daily high/low from Open-Meteo."""
from pathlib import Path
# ── Palette + theme (matching the Plotly Studio gallery these charts ship in) ──
VIOLET, TEAL, GREEN, PINK, ORANGE = "#845EEE", "#52B3D0", "#55B685", "#DA5597", "#E9A23B"
PRIMARY, SECONDARY = VIOLET, TEAL
COLORWAY = [VIOLET, TEAL, GREEN, PINK, ORANGE]
BG, TEXT, GRID, MUTED = "#ffffff", "#1c2024", "#d9d9e0", "#60646c"
FONT = "Inter, -apple-system, BlinkMacSystemFont, sans-serif"
COLORSCALE = [[0, "rgba(132, 94, 238, 0.05)"], [1, "rgba(132, 94, 238, 0.9)"]]
def apply_theme(fig):
"""Light gallery theme: white background, Inter font, soft gridlines."""
fig.update_layout(
paper_bgcolor=BG, plot_bgcolor=BG, colorway=COLORWAY,
font=dict(family=FONT, color=TEXT, size=12),
legend=dict(font=dict(color=TEXT)),
hoverlabel=dict(bgcolor="#f0f0f3", font=dict(color=TEXT, family=FONT), bordercolor=GRID),
)
fig.update_xaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
fig.update_yaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
def fetch_csv(url, **kwargs):
import io
import pandas as pd
import requests
r = requests.get(url, timeout=60)
r.raise_for_status()
return pd.read_csv(io.StringIO(r.text), **kwargs)
def fetch_json(url):
import requests
r = requests.get(url, timeout=60)
r.raise_for_status()
return r.json()
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import requests, warnings
# New York City coords — Open-Meteo free, no key required
OPENMETEO_URL = (
"https://api.open-meteo.com/v1/forecast"
"?latitude=40.7128&longitude=-74.0060"
"&daily=temperature_2m_max,temperature_2m_min"
"&temperature_unit=celsius"
"&timezone=America%2FNew_York"
"&past_days=60"
"&forecast_days=7"
)
ARCHIVE_URL = (
"https://archive-api.open-meteo.com/v1/archive"
"?latitude=40.7128&longitude=-74.0060"
"&start_date=2024-01-01&end_date=2024-12-31"
"&daily=temperature_2m_max,temperature_2m_min"
"&temperature_unit=celsius"
"&timezone=America%2FNew_York"
)
def generate():
print("fetching Open-Meteo temperature data …")
warnings.filterwarnings("ignore")
try:
requests.packages.urllib3.disable_warnings()
except Exception:
pass
data = None
is_forecast = False
# Try archive first (complete past data), then forecast API
for url in [ARCHIVE_URL, OPENMETEO_URL]:
try:
r = requests.get(url, verify=False, timeout=20)
if r.status_code == 200:
data = r.json()
is_forecast = "forecast_days" in url or "past_days" in url
print(f" fetched {len(data.get('daily', {}).get('time', []))} days")
break
except Exception as e:
print(f" {url} failed: {e}")
if data is None:
print(" using synthetic temperature data")
rng = np.random.default_rng(35)
dates = pd.date_range("2024-01-01", periods=90, freq="D")
# NYC seasonal curve
day_of_year = np.array([d.dayofyear for d in dates])
base = -5 + 25 * (1 - np.cos(2 * np.pi * (day_of_year - 10) / 365)) / 2
tmax = base + 5 + rng.normal(0, 2, len(dates))
tmin = base - 5 + rng.normal(0, 2, len(dates))
data = {"daily": {
"time": [str(d.date()) for d in dates],
"temperature_2m_max": list(tmax),
"temperature_2m_min": list(tmin),
}}
is_forecast = False
source = "Synthetic (NYC seasonal profile)"
else:
source = "Open-Meteo — New York City"
daily = data["daily"]
dates = pd.to_datetime(daily["time"])
tmax = np.array(daily["temperature_2m_max"], dtype=float)
tmin = np.array(daily["temperature_2m_min"], dtype=float)
today = pd.Timestamp.today().normalize()
is_future = dates >= today
# Color: past = teal, future = orange
bar_colors = [PINK if f else TEAL for f in is_future]
fig = go.Figure()
# Observed bars
past_mask = ~is_future
future_mask = is_future
for mask, color, label in [(past_mask, TEAL, "Observed"), (future_mask, PINK, "Forecast")]:
if mask.sum() == 0:
continue
fig.add_trace(go.Bar(
x=dates[mask],
y=tmax[mask] - tmin[mask],
base=tmin[mask],
marker=dict(color=color, opacity=0.7),
name=label,
hovertemplate="%{x|%b %d}<br>High: %{customdata[0]:.1f}°C<br>Low: %{customdata[1]:.1f}°C<extra></extra>",
customdata=np.stack([tmax[mask], tmin[mask]], axis=1),
))
# Freeze line
fig.add_hline(
y=0,
line=dict(color="#52B3D0", width=1, dash="dot"),
annotation_text="Freezing",
annotation_position="right",
annotation_font=dict(size=9),
)
fig.update_layout(
title=dict(text=f"Daily Temperature Range — {source}", x=0.5),
xaxis=dict(title=""),
yaxis=dict(title="Temperature (°C)"),
barmode="overlay",
bargap=0.1,
legend=dict(orientation="h", y=1.08),
margin=dict(t=60, b=50, l=70, r=80),
height=440,
)
apply_theme(fig)
return fig
fig = generate()
fig.show()
Made with Plotly