Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/alternative_interface/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
covidcast_fluview_locations_mapping = {
"nation:US": "nat",
"hhs:1": "hhs1",
"hhs:2": "hhs2",
"hhs:3": "hhs3",
"hhs:4": "hhs4",
"hhs:5": "hhs5",
"hhs:6": "hhs6",
"hhs:7": "hhs7",
"hhs:8": "hhs8",
"hhs:9": "hhs9",
"hhs:10": "hhs10",
"state:AK": "AK",
"state:AL": "AL",
"state:AR": "AR",
"state:AZ": "AZ",
"state:CA": "CA",
"state:CO": "CO",
"state:CT": "CT",
"state:DC": "DC",
"state:DE": "DE",
"state:FL": "FL",
"state:GA": "GA",
"state:HI": "HI",
"state:IA": "IA",
"state:ID": "ID",
"state:IL": "IL",
"state:IN": "IN",
"state:KS": "KS",
"state:KY": "KY",
"state:LA": "LA",
"state:MA": "MA",
"state:MD": "MD",
"state:ME": "ME",
"state:MI": "MI",
"state:MN": "MN",
"state:MO": "MO",
"state:MS": "MS",
"state:MT": "MT",
"state:NC": "NC",
"state:ND": "ND",
"state:NE": "NE",
"state:NH": "NH",
"state:NJ": "NJ",
"state:NM": "NM",
"state:NV": "NV",
"state:NY": "NY",
"state:OH": "OH",
"state:OK": "OK",
"state:OR": "OR",
"state:PA": "PA",
"state:RI": "RI",
"state:SC": "SC",
"state:SD": "SD",
"state:TN": "TN",
"state:TX": "TX",
"state:UT": "UT",
"state:VA": "VA",
"state:VT": "VT",
"state:WA": "WA",
"state:WI": "WI",
"state:WV": "WV",
"state:WY": "WY",
}
120 changes: 92 additions & 28 deletions src/alternative_interface/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
get_epiweek,
group_by_property,
)
from alternative_interface.helper import covidcast_fluview_locations_mapping


def epiweeks_in_date_range(start_date_str: str, end_date_str: str):
Expand Down Expand Up @@ -72,6 +73,7 @@ def days_in_date_range(start_date_str: str, end_date_str: str):
def get_available_geos(indicators):
geo_values = []
grouped_indicators = group_by_property(indicators, "data_source")
sources = grouped_indicators.keys()
for data_source, indicators in grouped_indicators.items():
indicators_str = ",".join(indicator["name"] for indicator in indicators)
response = requests.get(
Expand All @@ -98,6 +100,27 @@ def get_available_geos(indicators):
.prefetch_related("geo_level")
.order_by("level")
]
if "fluview" in sources:
geographic_granularities.extend(
[
{
"id": f"{geo_unit.geo_level.name}:{geo_unit.geo_id}",
"geoType": geo_unit.geo_level.name,
"text": geo_unit.display_name,
"geoTypeDisplayName": geo_unit.geo_level.display_name,
}
for geo_unit in GeographyUnit.objects.filter(
geo_level__name__in=[
"census-region",
"us-territory",
"us-city",
"ny_minus_jfk",
]
)
.prefetch_related("geo_level")
.order_by("level")
]
)
grouped_geographic_granularities = group_by_property(
geographic_granularities, "geoTypeDisplayName"
)
Expand All @@ -113,26 +136,57 @@ def get_available_geos(indicators):


def get_covidcast_data(indicator, start_date, end_date, geo, api_key):
if indicator["_endpoint"] == "covidcast":
time_values = f"{start_date}--{end_date}"
if indicator["time_type"] == "week":
start_day, end_day = get_epiweek(start_date, end_date)
time_values = f"{start_day}-{end_day}"
geo_type, geo_value = geo.split(":")
params = {
"time_type": indicator["time_type"],
"time_values": time_values,
"data_source": indicator["data_source"],
"signal": indicator["name"],
"geo_type": geo_type,
"geo_values": geo_value.lower(),
"api_key": api_key if api_key else settings.EPIDATA_API_KEY,
}
response = requests.get(f"{settings.EPIDATA_URL}covidcast", params=params)
if response.status_code == 200:
response_data = response.json()
if len(response_data["epidata"]):
return response_data["epidata"]
time_values = f"{start_date}--{end_date}"
if indicator["time_type"] == "week":
start_day, end_day = get_epiweek(start_date, end_date)
time_values = f"{start_day}-{end_day}"
geo_type, geo_value = geo.split(":")
params = {
"time_type": indicator["time_type"],
"time_values": time_values,
"data_source": indicator["data_source"],
"signal": indicator["name"],
"geo_type": geo_type,
"geo_values": geo_value.lower(),
"api_key": api_key if api_key else settings.EPIDATA_API_KEY,
}
response = requests.get(f"{settings.EPIDATA_URL}covidcast", params=params)
if response.status_code == 200:
response_data = response.json()
if len(response_data["epidata"]):
return response_data["epidata"]
return []


def get_fluview_data(indicator, geo, start_date, end_date, api_key):
region = None
try:
region = covidcast_fluview_locations_mapping[geo]
except KeyError:
region = geo.split(":")[1]
time_values = f"{start_date}--{end_date}"
if indicator["time_type"] == "week":
start_day, end_day = get_epiweek(start_date, end_date)
time_values = f"{start_day}-{end_day}"
params = {
"regions": region,
"epiweeks": time_values,
"api_key": api_key if api_key else settings.EPIDATA_API_KEY,
}
print(indicator)
response = requests.get(f"{settings.EPIDATA_URL}{indicator['data_source']}", params=params)
if response.status_code == 200:
data = response.json()
if len(data["epidata"]):
return [
{
"time_value": el["epiweek"],
"value": el[indicator["name"]],
"signal": indicator["name"],
"time_type": indicator["time_type"],
}
for el in data["epidata"]
]
return []


Expand Down Expand Up @@ -379,20 +433,30 @@ def get_chart_data(indicators, geography):
chart_data["initialViewEnd"] = end_date

# Fetch data from a wider range (2020 to today) for scrolling
data_start_date = "2010-01-01"
data_start_date = "1990-01-01"
data_end_date = today.strftime("%Y-%m-%d")

for indicator in indicators:
title = generate_epivis_custom_title(indicator, geo_display_name)
color = generate_random_color()
indicator_time_type = indicator.get("time_type", "week")
data = get_covidcast_data(
indicator,
data_start_date,
data_end_date,
geography,
settings.EPIDATA_API_KEY,
)
data = None
if indicator["_endpoint"] == "covidcast":
data = get_covidcast_data(
indicator,
data_start_date,
data_end_date,
geography,
settings.EPIDATA_API_KEY,
)
elif indicator["data_source"] in ["fluview", "fluview_clinical"]:
data = get_fluview_data(
indicator,
geography,
data_start_date,
data_end_date,
settings.EPIDATA_API_KEY,
)
if data:
# Prepare series with full data range for scrolling
series = prepare_chart_series_multi(
Expand Down
71 changes: 62 additions & 9 deletions src/assets/js/alter_dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,41 @@ class AlterDashboard {
}
};

// Plugin to draw vertical tick marks on X axis
const xAxisTickMarksPlugin = {
id: 'xAxisTickMarks',
afterDraw(chart) {
if (chart.animating) return;

const ctx = chart.ctx;
const xAxis = chart.scales.x;
const chartArea = chart.chartArea;

if (!xAxis || !chartArea || !xAxis.ticks || xAxis.ticks.length === 0) {
return;
}

ctx.save();
ctx.strokeStyle = '#64748b';
ctx.lineWidth = 1;

// Draw small vertical lines at each tick position
const tickLength = 4; // Length of tick marks in pixels
const tickY = chartArea.bottom; // Bottom of chart area

xAxis.ticks.forEach((tick) => {
if (tick.label !== '') { // Only draw ticks that have labels
ctx.beginPath();
ctx.moveTo(tick.x, tickY);
ctx.lineTo(tick.x, tickY + tickLength);
ctx.stroke();
}
});

ctx.restore();
}
};

// Dual-level X-axis plugin (weeks on top, days below)
// Optimized to reduce redraws during pan/zoom
const dualAxisPlugin = {
Expand Down Expand Up @@ -375,22 +410,40 @@ class AlterDashboard {
autoSkip: true, // Automatically skip ticks when crowded
autoSkipPadding: 5,
callback: function(value, index) {
// Show day labels, but only for selected ticks
const label = dayLabels[index];
// Use value (data index) instead of index for correct alignment
const dataIndex = Math.round(value);
if (dataIndex < 0 || dataIndex >= dayLabels.length) return '';
const label = dayLabels[dataIndex];
if (!label) return '';
// Format date to be more compact
// Format date with month abbreviation and year in January
try {
const date = new Date(label);
const month = date.getMonth() + 1;
const day = date.getDate();
return month + '/' + day;
// Parse YYYY-MM-DD format explicitly to avoid timezone issues
const parts = String(label).match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!parts) return label;

const year = parseInt(parts[1], 10);
const month = parseInt(parts[2], 10) - 1; // 0-indexed
const day = parseInt(parts[3], 10);

const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const monthName = monthNames[month];

// Show year every January
if (month === 0) {
return monthName + ' ' + day + ' ' + year;
}
return monthName + ' ' + day;
} catch (e) {
return label;
}
},
maxRotation: 45,
minRotation: 45
}
},
// Add vertical tick marks on the X axis
drawOnChartArea: true,
drawTicks: true
},
y: {
display: true,
Expand Down Expand Up @@ -465,7 +518,7 @@ class AlterDashboard {
}
}
},
plugins: [htmlLegendPlugin, dualAxisPlugin]
plugins: [htmlLegendPlugin, dualAxisPlugin, xAxisTickMarksPlugin]
});

// Set initial zoom to last 12 months after chart is created
Expand Down
Loading
Loading