Data Visualization Guide
Create charts and dashboards with Recharts
This guide shows you how to create data visualizations using the built-in Recharts library.
Getting Started#
Access the charts library from the API:
javascript
const { React, charts, components } = window.SubwayBuilderAPI.utils;
const {
ResponsiveContainer, PieChart, Pie, Cell,
BarChart, Bar, LineChart, Line,
XAxis, YAxis, CartesianGrid, Tooltip, Legend
} = charts;
const { Card, CardContent, CardHeader, CardTitle } = components;
const h = React.createElement;Pie Chart: Mode Share#
Display how commuters travel:
javascript
function ModeShareChart() {
const modes = api.gameState.getModeChoiceStats();
const data = [
{ name: 'Transit', value: modes.transit, color: '#22c55e' },
{ name: 'Driving', value: modes.driving, color: '#ef4444' },
{ name: 'Walking', value: modes.walking, color: '#3b82f6' }
];
return h(Card, null, [
h(CardHeader, { key: 'header' },
h(CardTitle, null, 'Mode Share')
),
h(CardContent, { key: 'content' },
h(ResponsiveContainer, { width: '100%', height: 200 },
h(PieChart, null,
h(Pie, {
data: data,
dataKey: 'value',
nameKey: 'name',
cx: '50%',
cy: '50%',
outerRadius: 60,
label: ({ name, percent }) =>
`${name} ${(percent * 100).toFixed(0)}%`
},
data.map((entry, i) =>
h(Cell, { key: i, fill: entry.color })
)
)
)
)
)
]);
}Bar Chart: Station Ridership#
Compare ridership across stations:
javascript
function StationRidershipChart() {
const stations = api.gameState.getStations().slice(0, 10);
const data = stations.map(station => {
const stats = api.gameState.getStationRidership(station.id);
return {
name: station.name.slice(0, 15),
riders: stats?.total || 0
};
});
return h(Card, null, [
h(CardHeader, { key: 'header' },
h(CardTitle, null, 'Top Stations by Ridership')
),
h(CardContent, { key: 'content' },
h(ResponsiveContainer, { width: '100%', height: 300 },
h(BarChart, { data: data }, [
h(CartesianGrid, { key: 'grid', strokeDasharray: '3 3' }),
h(XAxis, {
key: 'x',
dataKey: 'name',
tick: { fontSize: 10 },
angle: -45,
textAnchor: 'end'
}),
h(YAxis, { key: 'y' }),
h(Tooltip, { key: 'tooltip' }),
h(Bar, {
key: 'bar',
dataKey: 'riders',
fill: '#8b5cf6'
})
])
)
)
]);
}Line Chart: Ridership Over Time#
Track ridership trends (requires storing historical data):
javascript
// Store historical data
let ridershipHistory = [];
api.hooks.onDayChange((day) => {
const stats = api.gameState.getRidershipStats();
ridershipHistory.push({
day: day,
riders: stats.totalRidersPerHour
});
// Keep last 30 days
if (ridershipHistory.length > 30) {
ridershipHistory = ridershipHistory.slice(-30);
}
});
function RidershipTrendChart() {
return h(Card, null, [
h(CardHeader, { key: 'header' },
h(CardTitle, null, 'Ridership Trend')
),
h(CardContent, { key: 'content' },
h(ResponsiveContainer, { width: '100%', height: 250 },
h(LineChart, { data: ridershipHistory }, [
h(CartesianGrid, { key: 'grid', strokeDasharray: '3 3' }),
h(XAxis, { key: 'x', dataKey: 'day' }),
h(YAxis, { key: 'y' }),
h(Tooltip, { key: 'tooltip' }),
h(Line, {
key: 'line',
type: 'monotone',
dataKey: 'riders',
stroke: '#22c55e',
strokeWidth: 2
})
])
)
)
]);
}Complete Dashboard#
Combine charts into a dashboard:
javascript
function ModDashboard() {
return h('div', { className: 'space-y-4 p-4' }, [
// Header
h('h2', {
key: 'title',
className: 'text-xl font-bold'
}, 'Network Analytics'),
// Stats row
h('div', {
key: 'stats',
className: 'grid grid-cols-3 gap-4'
}, [
StatCard('Stations', api.gameState.getStations().length, 'MapPin'),
StatCard('Trains', api.gameState.getTrains().length, 'Train'),
StatCard('Budget', `$${api.gameState.getBudget().toLocaleString()}`, 'DollarSign')
]),
// Charts row
h('div', {
key: 'charts',
className: 'grid grid-cols-2 gap-4'
}, [
h(ModeShareChart, { key: 'mode' }),
h(StationRidershipChart, { key: 'stations' })
]),
// Full width chart
h(RidershipTrendChart, { key: 'trend' })
]);
}
function StatCard(label, value, iconName) {
const Icon = api.utils.icons[iconName];
return h(Card, { key: label, className: 'p-4' }, [
h('div', { className: 'flex items-center gap-2 text-muted-foreground text-sm' }, [
h(Icon, { className: 'h-4 w-4' }),
label
]),
h('div', { className: 'text-2xl font-bold mt-1' }, value)
]);
}
// Register as toolbar panel
api.ui.addToolbarPanel({
id: 'analytics-dashboard',
icon: 'BarChart3',
tooltip: 'Analytics Dashboard',
title: 'Network Analytics',
width: 600,
render: ModDashboard
});Real-Time Updates#
For live-updating charts, use React state:
javascript
function LiveRidershipDisplay() {
const [ridership, setRidership] = React.useState(0);
React.useEffect(() => {
const interval = setInterval(() => {
const stats = api.gameState.getRidershipStats();
setRidership(stats.totalRidersPerHour);
}, 1000);
return () => clearInterval(interval);
}, []);
return h('div', { className: 'text-center' }, [
h('div', { className: 'text-4xl font-bold' }, ridership.toLocaleString()),
h('div', { className: 'text-sm text-muted-foreground' }, 'riders/hour')
]);
}Available Chart Types#
From Recharts:
LineChart,Line- Trend dataBarChart,Bar- ComparisonsPieChart,Pie,Cell- ProportionsAreaChart,Area- Volume over timeRadarChart,Radar- Multi-dimensionalComposedChart- Mixed chart types
Common components:
ResponsiveContainer- Auto-sizing wrapperXAxis,YAxis- AxesCartesianGrid- Background gridTooltip- Hover infoLegend- Chart legend
Tips#
- Always use ResponsiveContainer - Charts need a defined size
- Limit data points - Too many points slow down rendering
- Update efficiently - Don't re-render on every frame
- Match colors - Use the game's color palette
- Add tooltips - Help players understand the data