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
75 changes: 56 additions & 19 deletions src/alternative_interface/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,57 @@
from django.shortcuts import render
from indicators.models import Indicator
from base.models import Pathogen, Geography
from base.models import Pathogen, GeographyUnit

from indicatorsets.utils import group_by_property
import requests
from django.conf import settings

HEADER_DESCRIPTION = "Discover, display and download real-time infectious disease indicators (time series) that track a variety of pathogens, diseases and syndromes in a variety of locations (primarily within the USA). Browse the list, or filter it first by locations and pathogens of interest, by surveillance categories, and more. Expand any row to expose and select from a set of related indicators, then hit 'Show Selected Indicators' at bottom to plot or export your selected indicators, or to generate code snippets to retrieve them from the Delphi Epidata API. Most indicators are served from the Delphi Epidata real-time repository, but some may be available only from third parties or may require prior approval."


def get_available_geos(indicators):
geo_values = []
grouped_indicators = group_by_property(indicators, "data_source")
for data_source, indicators in grouped_indicators.items():
indicators_str = ",".join(indicator["name"] for indicator in indicators)
response = requests.get(
f"{settings.EPIDATA_URL}covidcast/geo_indicator_coverage",
params={"data_source": data_source, "signals": indicators_str},
auth=("epidata", settings.EPIDATA_API_KEY),
)
if response.status_code == 200:
data = response.json()
if len(data["epidata"]):
geo_values.extend(data["epidata"])
unique_values = set(geo_values)
geo_levels = set([el.split(":")[0] for el in unique_values])
geo_unit_ids = set([geo_value.split(":")[1] for geo_value in unique_values])
geographic_granularities = [
{
"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=geo_levels)
.filter(geo_id__in=geo_unit_ids)
.prefetch_related("geo_level")
.order_by("level")
]
grouped_geographic_granularities = group_by_property(
geographic_granularities, "geoTypeDisplayName"
)
geographic_granularities = []
for key, value in grouped_geographic_granularities.items():
geographic_granularities.append(
{
"text": key,
"children": value,
}
)
return geographic_granularities


def alternative_interface_view(request):
try:
ctx = {}
Expand All @@ -18,42 +64,33 @@ def alternative_interface_view(request):
)
)

# Fetch geographies for dropdown
ctx["geographies"] = list(
Geography.objects.filter(used_in="indicatorsets").order_by(
"display_order_number"
)
)

# Get filters from URL parameters
pathogen_filter = request.GET.get("pathogen", "")
geography_filter = request.GET.get("geography", "")
ctx["selected_pathogen"] = pathogen_filter
ctx["selected_geography"] = geography_filter

# Build queryset with optional filtering
indicators_qs = Indicator.objects.prefetch_related("pathogens", "available_geographies").all()
indicators_qs = Indicator.objects.prefetch_related(
"pathogens", "available_geographies", "indicator_set"
).all()

if pathogen_filter:
indicators_qs = indicators_qs.filter(pathogens__id=pathogen_filter)

if geography_filter:
indicators_qs = indicators_qs.filter(available_geographies__id=geography_filter)
indicators_qs = indicators_qs.filter(pathogens__id=pathogen_filter, indicator_set__epidata_endpoint="covidcast")

# Convert to list of dictionaries
ctx["indicators"] = [
{
"id": indicator.id,
"_endpoint": indicator.indicator_set.epidata_endpoint,
"name": indicator.name,
"source": indicator.source.name if indicator.source else "Unknown",
"geographic_scope": indicator.geographic_scope.name if indicator.geographic_scope else "Unknown",
"temporal_scope_end": indicator.temporal_scope_end,
"description": indicator.description,
"pathogens": [pathogen.name for pathogen in indicator.pathogens.all()],
"data_source": indicator.source.name if indicator.source else "Unknown",
"time_type": indicator.time_type,
}
for indicator in indicators_qs
]

ctx["available_geos"] = get_available_geos(ctx["indicators"])

return render(
request, "alternative_interface/alter_dashboard.html", context=ctx
)
Expand Down
110 changes: 110 additions & 0 deletions src/assets/css/alter_dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,39 @@
box-sizing: border-box;
}

/* Page Loader */
.page-loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.95);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
backdrop-filter: blur(4px);
}

.loader-content {
text-align: center;
}

.loader-content .spinner-border {
width: 3rem;
height: 3rem;
border-width: 0.3rem;
margin-bottom: 1rem;
}

.loader-text {
color: var(--dark-text);
font-size: 1rem;
font-weight: 500;
margin-top: 1rem;
}

body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: var(--light-bg);
Expand Down Expand Up @@ -181,6 +214,83 @@ body {
max-height: 100%;
}

/* Chart Controls */
.chart-controls {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-color);
}

.controls-group {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
align-items: center;
}

.btn-control {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--light-bg);
border: 1px solid var(--border-color);
border-radius: 6px;
color: var(--dark-text);
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: var(--transition);
white-space: nowrap;
}

.btn-control:hover {
background: #f1f5f9;
border-color: var(--primary-color);
color: var(--primary-color);
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}

.btn-control.active {
background: var(--primary-color);
border-color: var(--primary-color);
color: white;
}

.btn-control.active:hover {
background: var(--primary-dark);
border-color: var(--primary-dark);
color: white;
}

.control-icon {
font-size: 1rem;
line-height: 1;
}

.control-text {
line-height: 1;
}

/* Enhanced Legend Styling */
canvas + div {
margin-top: 1rem;
}

/* Responsive Controls */
@media (max-width: 768px) {
.controls-group {
flex-direction: column;
align-items: stretch;
}

.btn-control {
width: 100%;
justify-content: center;
}
}

/* Footer */
footer {
background: white;
Expand Down
Loading
Loading