| month ↕ | revenue ↕ | users ↕ | region ↕ | status ↕ |
|---|---|---|---|---|
| Jan | 12400 | 340 | North | Active |
| Feb | 18200 | 415 | South | Active |
| Mar | 15800 | 398 | North | Paused |
| Apr | 22100 | 512 | East | Active |
| May | 19600 | 487 | West | Active |
| Jun | 28400 | 634 | East | Active |
| Jul | 24300 | 589 | South | Paused |
| Aug | 31200 | 712 | North | Active |
| Sep | 29800 | 681 | West | Active |
| Oct | 35600 | 798 | East | Active |
import { useState, useMemo } from 'react';
import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer, Legend } from 'recharts';
const COLORS = ['#6366f1','#3b82f6','#10b981','#f59e0b','#ef4444','#8b5cf6','#06b6d4','#ec4899'];
function fmtVal(n) {
const abs = Math.abs(n);
if (abs >= 1e9) return (n/1e9).toFixed(1)+'B';
if (abs >= 1e6) return (n/1e6).toFixed(1)+'M';
if (abs >= 1e3) return (n/1e3).toFixed(1)+'K';
return Math.round(n).toLocaleString();
}
const data = [
{
"month": "Jan",
"revenue": 12400,
"users": 340,
"region": "North",
"status": "Active"
},
{
"month": "Feb",
"revenue": 18200,
"users": 415,
"region": "South",
"status": "Active"
},
{
"month": "Mar",
"revenue": 15800,
"users": 398,
"region": "North",
"status": "Paused"
},
{
"month": "Apr",
"revenue": 22100,
"users": 512,
"region": "East",
"status": "Active"
},
{
"month": "May",
"revenue": 19600,
"users": 487,
"region": "West",
"status": "Active"
},
{
"month": "Jun",
"revenue": 28400,
"users": 634,
"region": "East",
"status": "Active"
},
{
"month": "Jul",
"revenue": 24300,
"users": 589,
"region": "South",
"status": "Paused"
},
{
"month": "Aug",
"revenue": 31200,
"users": 712,
"region": "North",
"status": "Active"
},
{
"month": "Sep",
"revenue": 29800,
"users": 681,
"region": "West",
"status": "Active"
},
{
"month": "Oct",
"revenue": 35600,
"users": 798,
"region": "East",
"status": "Active"
},
{
"month": "Nov",
"revenue": 32100,
"users": 745,
"region": "South",
"status": "Paused"
},
{
"month": "Dec",
"revenue": 41800,
"users": 923,
"region": "North",
"status": "Active"
}
];
const cardStyle = { background: '#1f2937', border: '1px solid #374151', borderLeft: '3px solid #6366f1', borderRadius: 8, padding: '12px 16px', minWidth: 120, flex: 1 };
const thStyle = { padding: '9px 12px', fontSize: 11, fontWeight: 700, color: '#9ca3af', textAlign: 'left', background: '#111827', borderBottom: '2px solid #374151', whiteSpace: 'nowrap', cursor: 'pointer' };
const tdStyle = { padding: '8px 12px', fontSize: 11.5, color: '#d1d5db', borderBottom: '1px solid #1f2937', maxWidth: 180, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' };
export default function Dashboard() {
const [filter_region, setFilter_region] = useState('');
const [filter_status, setFilter_status] = useState('');
const [sort, setSort] = useState('');
const [dir, setDir] = useState('asc');
const filtered = data.filter(row =>
(!filter_region || String(row['region']) === filter_region) &&
(!filter_status || String(row['status']) === filter_status)
);
const chartData = useMemo(() => {
const map = {};
filtered.forEach(row => {
const key = String(row['region'] ?? '');
if (!map[key]) map[key] = { 'region': key, 'revenue': 0 };
map[key]['revenue'] += Number(row['revenue']) || 0;
});
return Object.values(map).slice(0, 30);
}, [filtered]);
const sorted = sort
? [...filtered].sort((a, b) => {
const va = a[sort], vb = b[sort];
const num = typeof va === 'number' && typeof vb === 'number';
return dir === 'asc'
? num ? va - vb : String(va).localeCompare(String(vb))
: num ? vb - va : String(vb).localeCompare(String(va));
})
: filtered;
const tableRows = sorted.slice(0, 50);
function handleSort(field) {
if (sort === field) setDir(d => d === 'asc' ? 'desc' : 'asc');
else { setSort(field); setDir('asc'); }
}
return (
<div style={{ fontFamily: 'system-ui, sans-serif', background: '#111827', color: '#f9fafb', padding: 24, minHeight: '100vh' }}>
{/* Stat Cards */}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, marginBottom: 16 }}>
<div style={cardStyle}>
<div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', color: '#9ca3af', marginBottom: 4 }}>REVENUE</div>
<div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'monospace' }}>{fmtVal(filtered.reduce((sum, r) => sum + (Number(r['revenue']) || 0), 0))}</div>
<div style={{ fontSize: 10, color: '#6b7280', marginTop: 2 }}>{filtered.length} rows</div>
</div>
<div style={cardStyle}>
<div style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', color: '#9ca3af', marginBottom: 4 }}>USERS</div>
<div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'monospace' }}>{fmtVal(filtered.reduce((sum, r) => sum + (Number(r['users']) || 0), 0))}</div>
<div style={{ fontSize: 10, color: '#6b7280', marginTop: 2 }}>{filtered.length} rows</div>
</div>
</div>
{/* Filters */}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', padding: '10px 14px', background: '#1f2937', borderRadius: 8, border: '1px solid #374151', marginBottom: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<label style={{ fontSize: 11, color: '#6b7280', fontWeight: 600 }}>region:</label>
<select value={filter_region} onChange={e => setFilter_region(e.target.value)} style={{ fontSize: 11, padding: '3px 6px', borderRadius: 4, border: '1px solid #374151', background: '#1f2937', color: '#f9fafb', cursor: 'pointer' }}>
<option value="">All</option>
<option value="North">North</option>
<option value="South">South</option>
<option value="East">East</option>
<option value="West">West</option>
</select>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<label style={{ fontSize: 11, color: '#6b7280', fontWeight: 600 }}>status:</label>
<select value={filter_status} onChange={e => setFilter_status(e.target.value)} style={{ fontSize: 11, padding: '3px 6px', borderRadius: 4, border: '1px solid #374151', background: '#1f2937', color: '#f9fafb', cursor: 'pointer' }}>
<option value="">All</option>
<option value="Active">Active</option>
<option value="Paused">Paused</option>
</select>
</div>
</div>
{/* Chart */}
<div style={{ background: '#1f2937', border: '1px solid #374151', borderRadius: 8, padding: '14px 16px', marginBottom: 16 }}>
<div style={{ fontSize: 11, fontWeight: 700, color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 10 }}>revenue by region</div>
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie data={chartData} dataKey="revenue" nameKey="region" cx="50%" cy="50%" outerRadius={100} innerRadius={50} label>
{chartData.map((_, i) => <Cell key={i} fill={COLORS[i % COLORS.length]} />)}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
{/* Table */}
<div style={{ background: '#1f2937', border: '1px solid #374151', borderRadius: 8, overflow: 'hidden' }}>
<div style={{ overflowX: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
<th key="month" style={thStyle} onClick={() => setSort('month')}>month {sort === 'month' ? (dir === 'asc' ? '↑' : '↓') : ''}</th>
<th key="revenue" style={thStyle} onClick={() => setSort('revenue')}>revenue {sort === 'revenue' ? (dir === 'asc' ? '↑' : '↓') : ''}</th>
<th key="users" style={thStyle} onClick={() => setSort('users')}>users {sort === 'users' ? (dir === 'asc' ? '↑' : '↓') : ''}</th>
<th key="region" style={thStyle} onClick={() => setSort('region')}>region {sort === 'region' ? (dir === 'asc' ? '↑' : '↓') : ''}</th>
<th key="status" style={thStyle} onClick={() => setSort('status')}>status {sort === 'status' ? (dir === 'asc' ? '↑' : '↓') : ''}</th>
</tr>
</thead>
<tbody>
{tableRows.map((row, i) => (
<tr key={i} style={{ background: i % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.02)' }}>
<td key="month" style={tdStyle}>{String(row['month'] ?? '')}</td>
<td key="revenue" style={tdStyle}>{String(row['revenue'] ?? '')}</td>
<td key="users" style={tdStyle}>{String(row['users'] ?? '')}</td>
<td key="region" style={tdStyle}>{String(row['region'] ?? '')}</td>
<td key="status" style={tdStyle}>{String(row['status'] ?? '')}</td>
</tr>
))}
</tbody>
</table>
</div>
{sorted.length > 50 && (
<div style={{ fontSize: 11, color: '#6b7280', padding: '8px 12px', borderTop: '1px solid #374151' }}>
Showing 50 of {sorted.length} rows
</div>
)}
</div>
</div>
);
}
You Might Also Like
JSON Dashboard Generator — Visualize Any API Response Instantly
About this tool
Stop Building Dashboards by Hand — Paste JSON and Get One Instantly
Every developer has been there: you get a JSON response from an API, you need to understand the data quickly, and you end up writing throwaway React code to render a table, manually picking columns, hard-coding filter dropdowns, and writing aggregation logic that you'll delete the moment the real dashboard is built. It's tedious, slow, and you repeat it every time you work with a new API endpoint.
This tool eliminates that cycle. Paste any JSON — a raw array, a paginated API response with a data or results wrapper, or a single object — and it instantly generates a live dashboard you can actually use: stat cards for every numeric field, a bar, line, or pie chart (automatically chosen based on your data types), filter dropdowns for category fields, and a sortable, paginated table. No configuration, no API keys, no installation. It runs entirely in your browser.
Automatic field type detection is what makes this work without configuration. The tool inspects every value across all rows for every field. Number fields (including numeric strings like "12400") become stat cards and chart axes. String fields with 25 or fewer unique values — like "region", "status", or "category" — become category filters and pie chart segments. Date strings (ISO 8601, readable formats) trigger a line chart for time-series visualization. Boolean fields become filter toggles. ID fields (named id, _id, uuid, or unique-valued columns) are hidden from the table by default to reduce noise. One level of nested objects is automatically flattened: a row like {"user": {"name": "Alice", "role": "admin"}} becomes {"user.name": "Alice", "user.role": "admin"}.
Chart type selection uses the data to make an informed choice. If a date field exists, the tool selects it as the X axis and renders a line chart — the right representation for time series because it shows continuity and trend. If a category field exists with six or fewer unique values, a donut-style pie chart shows proportional breakdown. Larger category fields and string fields default to a bar chart for easy comparison. You can override the chart type, X axis field, and Y axis field at any time using the controls in the left panel — the chart updates instantly.
Paginated API responses are handled transparently. If your JSON is an object rather than a plain array, the tool looks for the array inside common wrapper keys: data, results, items, records, rows, list. Once found, the remaining keys (page, total, per_page, cursor, count, etc.) appear as a meta banner above the dashboard — so you can paste a raw API response with pagination metadata and see it all at once. This lets you work directly with real-world API shapes like those from REST APIs, GraphQL responses, and frameworks like Laravel's paginate() or Django REST Framework's PageNumberPagination.
The React code export is where the tool saves the most time. Click the React (Recharts) tab in the code panel and you get a complete, production-quality React component: import statements for Recharts, the first 100 rows embedded as a data constant, inline style objects, filter state with useState, aggregated chart data with useMemo, stat cards for numeric fields, filter dropdowns with all unique values populated, the correct Recharts chart component (BarChart, LineChart, or PieChart) wrapped in ResponsiveContainer, and a data table with sortable column headers. Install Recharts (npm install recharts), paste the component into your project, and it renders correctly with zero changes. The JSON Data tab exports the currently filtered rows as clean JSON — useful for seeding tests, feeding into other tools, or quickly slicing a large dataset.
For testing the API endpoint that produced your JSON, use our API Request Generator & Tester to send requests and copy the response. For formatting and validating your JSON before pasting, the JSON Formatter handles syntax highlighting, pretty-printing, and error detection with line-level error messages.
Features
- Auto field type detection — number, category, date, boolean, string, ID — inspects all values across all rows
- Smart chart selection — line chart for date fields, pie for low-cardinality categories, bar for everything else
- Pure SVG charts — bar, line (with gradient area fill), and donut pie — no chart library dependency in the live preview
- Stat cards — auto-generated for every numeric field showing sum across filtered rows + row count
- Category filter dropdowns — auto-generated from string fields with ≤25 unique values; stacks with multiple active filters
- Sortable, paginated table — click any column header to sort ascending/descending; 10 rows per page with prev/next controls
- Paginated API response unwrapping — detects data, results, items, records, rows, list wrapper keys; shows meta fields as a banner
- One-level object flattening — nested objects like {"user":{"name":"Alice"}} become "user.name" columns automatically
- React (Recharts) code export — complete component with useState filters, useMemo aggregation, stat cards, and responsive chart
- JSON Data export — export the current filtered view (up to 100 rows) as clean JSON
- Configurable X and Y axes — override auto-selection for any combination of fields
- Configurable filter fields — check/uncheck which category fields appear as dashboard filter dropdowns
- Meta banner — shows pagination metadata (page, total, cursor) from API response wrappers
- Works immediately — sample monthly revenue dataset is pre-loaded so the tool demonstrates itself on first open
- 100% browser-based — your data never leaves your device; no server, no sign-up, no usage limits
How to Use
- 1Paste your JSONClick in the JSON Input textarea on the left and paste any JSON array, API response object, or paginated response. The tool accepts plain arrays ([{...}]), wrapped responses ({"data":[...],"meta":{...}}), and single objects. The sample dataset loads automatically when you open the tool so you can explore it immediately.
- 2Review detected fieldsThe Detected Fields panel shows every field in your JSON with a colored type badge: # for numbers, Aⓐ for categories, 📅 for dates, T/F for booleans, Aa for strings, and ID for identifier fields. Number fields show their total sum; category fields show the unique value count. This lets you verify the tool understood your data correctly before configuring the chart.
- 3Read the stat cardsStat cards appear automatically for every numeric field — up to four cards showing the sum across all visible rows. The count underneath each card updates as you apply filters, so you can see totals for filtered subsets (e.g., "total revenue for East region" by selecting the East filter).
- 4Apply filtersIf your JSON has category or boolean fields, filter dropdowns appear in the filter bar above the chart. Select a value from any dropdown to narrow the data — the chart, stat cards, and table all update instantly. Use multiple filters at once to drill down. Click ✕ Clear to reset all filters at once.
- 5Configure the chartIn the left panel under Chart, use the Bar / Line / Pie segmented control to switch chart types. Use the X axis dropdown to choose the grouping field and the Y axis dropdown to choose which numeric field to aggregate. The chart re-renders immediately — use this to explore different angles of your data without writing any code.
- 6Sort and page through the tableClick any column header in the table to sort by that field. Click again to reverse direction. Use the pagination buttons (« ‹ › ») to step through 10 rows at a time. The row counter below the table shows the current range and total, updating as you filter.
- 7Copy the React codeClick the React (Recharts) tab in the code panel at the bottom. The generated component includes your actual data (first 100 rows), all active filter fields as dropdowns, the selected chart type, and a data table. Click Copy to copy to clipboard. Install Recharts in your project (npm install recharts) and paste the component — it renders immediately with no edits required. Switch to the JSON Data tab to export the filtered dataset as clean JSON.
Common Use Cases
Frequently Asked Questions
Paste your JSON array directly into this tool and it will instantly generate bar charts, line charts, stat cards, and a sortable table — no configuration needed. The tool auto-detects field types (numbers, categories, dates, booleans) and selects the most appropriate chart type: line charts for date fields, pie charts for low-cardinality categories, and bar charts otherwise. You can override the chart type, X axis, and Y axis using the controls in the left panel.
This tool generates a complete, copy-pasteable React component using the Recharts library. Click the "React (Recharts)" tab in the code panel to see the generated component, which includes filter state with useState, aggregated chart data with useMemo, stat cards for numeric fields, filter dropdowns for category fields, a Recharts chart (BarChart, LineChart, or PieChart) wrapped in ResponsiveContainer, and a data table showing the first 50 filtered rows. Paste the code into your project, run npm install recharts, and it works immediately.
The tool automatically unwraps common paginated response shapes. If your JSON is an object (not an array), it looks for a nested array under any of these keys: data, results, items, records, rows, or list. Once found, it uses that array as the dataset. Remaining keys — like page, total, per_page, count, or cursor — are shown as a "meta banner" with key-value chips above the dashboard. This means you can paste a raw API response like {"data": [...], "meta": {"page": 1, "total": 2500}} without pre-processing it.
The tool groups all rows by the selected X axis field, summing the selected Y axis (numeric) field for each unique X value. This aggregation happens client-side using useMemo and updates instantly when you change filters. For example, if your data has rows with region and revenue fields, selecting X=region and Y=revenue produces a chart showing total revenue per region across all filtered rows. Up to 30 unique X values are shown in a single chart.
The generated React code includes fully working sort and filter logic. Clicking a column header sorts the table by that field — a second click reverses the direction. Filter dropdowns are generated for every category or boolean field in your JSON. The filtering logic uses Array.filter with conditions chained per active dropdown, and sorting uses Array.sort with numeric and string comparisons. The table shows up to 50 rows to keep rendering fast, with a row count indicator showing how many total rows match the active filters.
When the tool detects a date field in your JSON (a string that parses as a valid date with at least 8 characters), it automatically selects the X axis as that date field and sets the chart type to LineChart — the right choice for time series data because it preserves the continuous, ordered nature of timestamps. The generated code uses <LineChart> with <Line type="monotone"> and <XAxis dataKey="yourDateField"> for correct rendering. If you need a bar chart for the same data, switch the chart type selector to "Bar" and the generated code updates to use <BarChart> instead.
The tool detects category fields automatically: any string field with 25 or fewer unique values where cardinality is below 90% of the row count is classified as a category. In the left panel, check the filter fields you want to expose as dropdowns. In the dashboard, a filter bar appears with a labeled dropdown for each selected category field. In the generated React code, each filter field gets a useState hook and the filtered array is computed with a single data.filter() call that chains all active conditions.
The tool inspects all values in each field across all rows. A field is typed as "number" if every value is a JavaScript number or a parseable numeric string. It is typed as "date" if every value is a string of at least 8 characters that passes Date.parse(). It is typed as "boolean" if every value is a true/false boolean. It is "id" if the field name is id, _id, uuid, key, or pk, or if all values are unique and look like IDs (long strings or pure number sequences). It is "category" if it is a string field with 25 or fewer unique values and cardinality below 90% of rows. Everything else is typed as "string". One-level-deep nested objects are flattened into parent.child keys.