Web application for visualizing energy forecast data generated by the Generator and served by the API.
Explorer is a modular visual framework built with SvelteKit that provides interactive visualizations for energy demand forecasts. It features:
- Interactive Charts: Time series, histograms, segment breakdowns, and geographic comparisons
- Dynamic Maps: Mapbox GL JS integration for geographic visualization
- Scenario Comparison: Compare multiple forecast scenarios side-by-side
- Responsive Design: Works seamlessly on mobile, tablet, and desktop
- Export Functionality: Export charts as PNG, SVG, or data as CSV/JSON
- Chart Parameter Control: Per-chart parameter overrides with visual indicators
- SvelteKit - Full-stack web framework
- Svelte 5 - UI framework with modern runes syntax
- TypeScript - Type safety throughout
- Tailwind CSS - Utility-first CSS with container queries
- LayerChart - Primary charting library
- Mapbox GL JS - Interactive maps
- Lucide Svelte - Icon library
- Start the API server (from project root):
cd api
npm start # Runs on port 4010- Ensure data has been generated:
cd api
node generate-api.js --defaultsStart the development server:
npm install
npm run devThe application will be available at http://localhost:5173.
Create a production build:
npm run build
npm run preview # Preview production build/explorer/src/
├── routes/
│ ├── +page.svelte # Main introductory report page
│ ├── +page.ts # Data loader
│ ├── charts/ # Chart library gallery
│ │ ├── +page.svelte # All charts with parameter controls
│ │ └── +page.ts # Chart data loader
│ └── +layout.svelte # Root layout with navigation
├── lib/
│ ├── components/
│ │ ├── AreaChart.svelte # Yearly time series
│ │ ├── TimeLine.svelte # Daily/weekly timeline
│ │ ├── Histogram.svelte # Distribution visualization
│ │ ├── SegmentBars.svelte # Segment breakdown
│ │ ├── GeoBarChart.svelte # Geographic comparison
│ │ ├── map/
│ │ │ └── Map.svelte # Interactive map
│ │ ├── shared/
│ │ │ ├── ChartContainer.svelte # Chart wrapper with export
│ │ │ ├── LazyChart.svelte # Intersection observer loading
│ │ │ ├── LoadingSkeleton.svelte # Loading states
│ │ │ ├── ErrorState.svelte # Error displays
│ │ │ ├── EmptyState.svelte # Empty data states
│ │ │ └── ScenarioLegend.svelte # Scenario comparison legend
│ │ └── controls/
│ │ ├── ChartParameterPill.svelte # Per-chart parameter control
│ │ ├── YearSelector.svelte
│ │ ├── GeographySelector.svelte
│ │ ├── SegmentSelector.svelte
│ │ ├── ResolutionSelector.svelte
│ │ └── AggregationSelector.svelte
│ ├── dataService.ts # API communication layer
│ ├── utilities.ts # Helper functions
│ ├── comparisonUtils.ts # Scenario comparison utilities
│ ├── chartConfig.ts # Chart configuration
│ ├── exportUtils.ts # Chart export functionality
│ └── stores/
│ ├── scenario.svelte.ts # Global scenario state
│ └── chartParameters.svelte.ts # Per-chart parameter overrides
└── types/
├── api.ts # API response types
├── controls.ts # Control component types
└── ChartComponent.interface.ts # Chart component interfaces
The /charts route displays all available visualizations with:
- Responsive grid layout: 2-column on desktop, 1-column on mobile
- Individual parameter controls: Each chart can override global parameters
- Visual indicators: Blue pill badges show when chart has custom parameters
- Export functionality: Download charts as PNG/SVG or data as CSV/JSON
Charts use a hierarchical parameter system:
- Global parameters: Default values for all charts (geography, year, segment, etc.)
- Chart-specific overrides: Individual charts can override any parameter
- Visual feedback: Parameter pills show blue badge with override count
- Isolated updates: Changing one chart's parameters doesn't affect others
Compare multiple forecast scenarios:
- Side-by-side visualization: Overlaid charts with color-coded scenarios
- Interactive legend: Hover/click to highlight specific scenarios
- Consistent colors: Scenarios maintain colors across all charts
- Metadata display: Show scenario parameters and statistics
All chart components follow consistent patterns:
- Hybrid data loading: Accept data as props OR fetch internally
- Shared UI components: LoadingSkeleton, ErrorState, EmptyState
- TypeScript interfaces: Standardized props via ChartComponent.interface.ts
- Container queries: Responsive to container width, not viewport
- Export support: All charts provide data for export
Explorer communicates with the API server via dataService.ts:
import {
fetchDemandData,
makeDemandQuery,
fetchGeographies,
fetchScenarios
} from '$lib/dataService';
// Fetch time series data
const query = makeDemandQuery({
start: '2025-01-01',
end: '2026-01-01',
resolution: '1d',
aggregation: 'sum',
geography: 'SE01',
segment: 'housing',
scenarioId: 'housing_electrification=2,...'
});
const data = await fetchDemandData(query);
// Fetch metadata
const geographies = await fetchGeographies('json');
const scenarios = await fetchScenarios();Always use modern Svelte 5 syntax:
$state()for reactive state$derived()for computed values$effect()for side effects$props()for component props- Snippets for component composition
let { data = [], geography, year, scenario }: TimeSeriesChartProps = $props();
let loading = $state(false);
let error = $state<string | null>(null);
// Fetch data when parameters change
$effect(() => {
if (geography && year && scenario) {
fetchData();
}
});- Use viewport breakpoints for layout:
lg:grid-cols-2 - Use container queries for components:
@lg:text-lg - Mobile-first approach with progressive enhancement
- Test at all breakpoints: mobile (320px), tablet (768px), desktop (1024px+)
npm run check # TypeScript type checking
npm run lint # ESLint
npm run format # Prettier
npm test # Unit tests (when added)
npm run test:e2e # E2E tests (when added)Create .env file:
VITE_API_BASE_URL=http://localhost:4010 # API server URL
PUBLIC_MAPBOX_TOKEN=your_token_here # Mapbox access token- Verify API server is running on port 4010
- Check browser console for API errors
- Ensure data has been generated with
node api/generate-api.js --defaults
- Verify Mapbox token is set in
.env - Check that geographies.geojson exists in
/data/ - Ensure geography IDs match between data and GeoJSON
- Check that all dependencies are included in
$effect()hooks - Verify chartParametersStore is updating correctly
- Check browser console for reactivity issues
For detailed architecture and development guidelines, see:
/CLAUDE.md- Project overview and patterns/api/README.md- API documentation/api/openapi.yaml- API specification