Data Structure
Headless Chart uses consistent and intuitive data structures. This chapter covers how to pass data to charts and the type system.
Basic Data Structure
CartesianData Type
Most 2D charts (Bar, Line, Area, Scatter) use a common data structure:
type CartesianData = {
labels: string[];
datasets: {
legend: string;
values: number[];
}[];
};
Basic Example
const data = {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
datasets: [
{
legend: '2023 Sales',
values: [120, 190, 300, 250, 420]
},
{
legend: '2024 Sales',
values: [150, 230, 280, 320, 480]
}
]
};
Data Structure by Chart Type
Bar Chart
Uses the basic CartesianData structure as is:
const barData = {
labels: ['Seoul', 'Busan', 'Daegu', 'Incheon', 'Gwangju'],
datasets: [
{
legend: 'Population',
values: [9.7, 3.4, 2.4, 2.9, 1.5]
}
]
};
BarChart({ data: barData });
Line Chart
Line Chart also uses the same structure:
const lineData = {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
datasets: [
{
legend: 'Visitors',
values: [1200, 1900, 3000, 2500, 4200]
},
{
legend: 'Page Views',
values: [3400, 4800, 7200, 6100, 9800]
}
]
};
LineChart({ data: lineData });
Scatter Chart
Scatter Chart has a different structure using x, y coordinates:
const scatterData = {
datasets: [
{
legend: 'Group A',
data: [
{ x: 10, y: 20 },
{ x: 15, y: 35 },
{ x: 20, y: 30 }
]
},
{
legend: 'Group B',
data: [
{ x: 25, y: 40 },
{ x: 30, y: 50 },
{ x: 35, y: 45 }
]
}
]
};
ScatterChart({ data: scatterData });
Bubble Chart
Bubble Chart adds size information:
const bubbleData = {
datasets: [
{
legend: 'Product A',
data: [
{ x: 10, y: 20, r: 5 }, // r is radius
{ x: 15, y: 35, r: 10 },
{ x: 20, y: 30, r: 15 }
]
}
]
};
BubbleChart({ data: bubbleData });
Heatmap Chart
Heatmap uses a 2D array structure:
const heatmapData = {
xLabels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
yLabels: ['Morning', 'Afternoon', 'Evening'],
data: [
[10, 20, 30, 25, 15], // Morning
[25, 30, 35, 40, 30], // Afternoon
[15, 10, 20, 15, 10] // Evening
]
};
HeatmapChart({ data: heatmapData });
Data Validation and Type Safety
Using TypeScript Types
import type { CartesianData } from '@meursyphus/headless-chart';
// Type validation
const validateData = (data: CartesianData): boolean => {
// Check if labels and values have the same length
return data.datasets.every(
dataset => dataset.values.length === data.labels.length
);
};
// Type-safe data
const typedData: CartesianData = {
labels: ['A', 'B', 'C'],
datasets: [
{
legend: 'Series 1',
values: [1, 2, 3] // Same length as labels
}
]
};
Data Transformation Helpers
// Transform object array to chart data
const transformData = (rawData) => {
const labels = rawData.map(item => item.month);
const salesValues = rawData.map(item => item.sales);
const profitValues = rawData.map(item => item.profit);
return {
labels,
datasets: [
{ legend: 'Sales', values: salesValues },
{ legend: 'Profit', values: profitValues }
]
};
};
// Usage example
const rawData = [
{ month: 'Jan', sales: 100, profit: 20 },
{ month: 'Feb', sales: 120, profit: 25 },
{ month: 'Mar', sales: 140, profit: 30 }
];
const chartData = transformData(rawData);
Dynamic Data Processing
Real-time Data Updates
function RealtimeChart() {
const [data, setData] = useState({
labels: [],
datasets: [{ legend: 'Real-time Data', values: [] }]
});
useEffect(() => {
const interval = setInterval(() => {
setData(prev => {
const newLabels = [...prev.labels, new Date().toLocaleTimeString()];
const newValues = [...prev.datasets[0].values, Math.random() * 100];
// Keep only the latest 10 entries
if (newLabels.length > 10) {
newLabels.shift();
newValues.shift();
}
return {
labels: newLabels,
datasets: [{ legend: 'Real-time Data', values: newValues }]
};
});
}, 1000);
return () => clearInterval(interval);
}, []);
return <Widget widget={LineChart({ data })} />;
}
Filtering and Sorting
// Data filtering
const filterByDateRange = (data, startDate, endDate) => {
const startIndex = data.labels.findIndex(label => label >= startDate);
const endIndex = data.labels.findIndex(label => label > endDate);
return {
labels: data.labels.slice(startIndex, endIndex),
datasets: data.datasets.map(dataset => ({
...dataset,
values: dataset.values.slice(startIndex, endIndex)
}))
};
};
// Data sorting
const sortByValue = (data, ascending = true) => {
const indices = [...Array(data.labels.length).keys()];
indices.sort((a, b) => {
const sumA = data.datasets.reduce((sum, d) => sum + d.values[a], 0);
const sumB = data.datasets.reduce((sum, d) => sum + d.values[b], 0);
return ascending ? sumA - sumB : sumB - sumA;
});
return {
labels: indices.map(i => data.labels[i]),
datasets: data.datasets.map(dataset => ({
...dataset,
values: indices.map(i => dataset.values[i])
}))
};
};
Scale Settings
Auto Scale vs Manual Scale
// Auto scale (default)
BarChart({ data: myData });
// Manual scale setting
BarChart({
data: myData,
getScale: ({ data }) => {
// Set upper limit to 110% of max value
const maxValue = Math.max(
...data.datasets.flatMap(d => d.values)
);
return {
min: 0,
max: Math.ceil(maxValue * 1.1),
step: Math.ceil(maxValue / 5)
};
}
});
Log Scale Implementation
const logScale = ({ data }) => {
const values = data.datasets.flatMap(d => d.values);
const minValue = Math.min(...values.filter(v => v > 0));
const maxValue = Math.max(...values);
return {
min: Math.pow(10, Math.floor(Math.log10(minValue))),
max: Math.pow(10, Math.ceil(Math.log10(maxValue))),
transform: (value) => Math.log10(value),
inverse: (value) => Math.pow(10, value)
};
};
Data Formatting
Label Formatters
// Number formatting
const formatNumber = (value) => {
if (value >= 1000000) return `${(value / 1000000).toFixed(1)}M`;
if (value >= 1000) return `${(value / 1000).toFixed(1)}K`;
return value.toString();
};
// Date formatting
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
});
};
// Usage
BarChart({
data: myData,
custom: {
yAxisLabel: ({ name }) => Text(formatNumber(name)),
xAxisLabel: ({ name }) => Text(formatDate(name))
}
});
Empty Data Handling
// Empty data check
const EmptyAwareChart = ({ data }) => {
if (!data.datasets.length || !data.labels.length) {
return Container({
child: Center({
child: Text('No data available', {
style: new TextStyle({
fontSize: 16,
color: '#6b7280'
})
})
})
});
}
return BarChart({ data });
};
Next Steps
Now that you understand data structures, it’s time to create actual charts!
Start implementing real charts in the Bar Chart Guide.