Area Chart
Area charts express data changes over time while visually emphasizing size and volume. As a variation of Line Chart, they effectively represent cumulative values or overall scale by filling the area below the line.
Interface
AreaChart Props
interface AreaChartProps {
// Required properties
data: AreaChartData;
// Optional properties
custom?: Partial<AreaChartCustom>;
title?: string;
getScale?: (data: AreaChartData) => AreaChartScale;
}
Data Structure
type AreaChartData = {
labels: string[]; // X-axis labels (time/categories)
datasets: {
legend: string; // Dataset name
values: number[]; // Y-axis values
}[];
};
type AreaChartScale = {
min: number;
max: number;
step: number;
};
Basic Usage
import Widget from '@meursyphus/flitter-react';
import { AreaChart } from '@meursyphus/headless-chart';
const data = {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
legend: 'Revenue',
values: [1200, 1900, 3000, 2500, 4200, 3800]
}]
};
function BasicAreaChart() {
return (
<Widget
width="600px"
height="400px"
widget={AreaChart({ data })}
/>
);
}
Chart Components
Area Chart has the same hierarchical structure as Line Chart, but uses area
elements instead of line
:
AreaChart
└── Layout (overall layout)
├── Title (chart title)
├── Legend (legend)
└── Plot (plot area)
├── XAxis (X-axis)
├── YAxis (Y-axis)
├── Grid (grid)
└── Series (data series)
└── Area (individual area)
Customizable Elements
You can customize 18 components through the custom
prop:
1. Layout Elements
layout, title, legend
Can be customized in the same way as Line Chart.
2. Data Visualization Elements
series
Container for all areas.
custom: {
series: ({ areas }) => {
return Stack({
children: areas
});
}
}
area
Individual area element. This is the most commonly customized element.
custom: {
area: ({ values, legend, index }) => {
return CustomPaint({
painter: {
svg: {
createDefaultSvgEl: (context) => ({
line: context.createSvgEl("path"),
area: context.createSvgEl("path")
}),
paint: ({ line, area }, { width, height }) => {
// Create line path
const linePath = createLinePath({ values, width, height });
// Line styling
line.setAttribute("fill", "none");
line.setAttribute("stroke", getColor(index));
line.setAttribute("stroke-width", "2");
line.setAttribute("d", linePath.getD());
// Create area path (copy line path and extend to bottom)
const areaPath = linePath.clone();
areaPath
.lineTo({ x: width, y: height })
.lineTo({ x: 0, y: height })
.close();
// Area styling
area.setAttribute("fill", getColor(index));
area.setAttribute("opacity", "0.3");
area.setAttribute("d", areaPath.getD());
}
}
}
});
}
}
3. Axis and Grid Elements
xAxis, yAxis, grid, etc.
Uses the same components as Line Chart.
Real-World Usage Examples
1. Gradient Area Chart
import { CustomPaint, Path } from '@meursyphus/flitter';
const gradientAreaChart = AreaChart({
data,
custom: {
area: ({ values, index }) => {
const colors = [
{ stroke: '#3b82f6', fill: 'url(#gradient-0)' },
{ stroke: '#10b981', fill: 'url(#gradient-1)' },
{ stroke: '#f59e0b', fill: 'url(#gradient-2)' }
];
const { stroke, fill } = colors[index % colors.length];
return CustomPaint({
painter: {
svg: {
createDefaultSvgEl: (context) => {
const defs = context.createSvgEl("defs");
const gradient = context.createSvgEl("linearGradient");
gradient.setAttribute("id", `gradient-${index}`);
gradient.setAttribute("x1", "0%");
gradient.setAttribute("y1", "0%");
gradient.setAttribute("x2", "0%");
gradient.setAttribute("y2", "100%");
const stop1 = context.createSvgEl("stop");
stop1.setAttribute("offset", "0%");
stop1.setAttribute("stop-color", stroke);
stop1.setAttribute("stop-opacity", "0.3");
const stop2 = context.createSvgEl("stop");
stop2.setAttribute("offset", "100%");
stop2.setAttribute("stop-color", stroke);
stop2.setAttribute("stop-opacity", "0");
gradient.appendChild(stop1);
gradient.appendChild(stop2);
defs.appendChild(gradient);
return {
defs,
line: context.createSvgEl("path"),
area: context.createSvgEl("path")
};
},
paint: ({ line, area }, { width, height }) => {
const path = createLinePath({ values, width, height });
// Line
line.setAttribute("fill", "none");
line.setAttribute("stroke", stroke);
line.setAttribute("stroke-width", "2");
line.setAttribute("d", path.getD());
// Area
const areaPath = path.clone();
areaPath
.lineTo({ x: width, y: height })
.lineTo({ x: 0, y: height })
.close();
area.setAttribute("fill", fill);
area.setAttribute("d", areaPath.getD());
}
}
}
});
}
}
});
2. Stacked Area Chart
const stackedAreaChart = AreaChart({
data,
custom: {
series: ({ areas }) => {
// Calculate cumulative values
const stackedAreas = areas.map((area, index) => {
if (index === 0) return area;
// Accumulate values from previous areas
const accumulatedValues = calculateAccumulatedValues(data.datasets, index);
return createStackedArea(area, accumulatedValues);
});
return Stack({
children: stackedAreas.reverse() // Draw from back to front for proper layering
});
}
}
});
function calculateAccumulatedValues(datasets, upToIndex) {
const result = [];
for (let i = 0; i < datasets[0].values.length; i++) {
let sum = 0;
for (let j = 0; j < upToIndex; j++) {
sum += datasets[j].values[i];
}
result.push(sum);
}
return result;
}
3. Spline Curve Area
const smoothAreaChart = AreaChart({
data,
custom: {
area: ({ values, index }) => {
const colors = ['#3b82f6', '#10b981', '#f59e0b'];
const color = colors[index % colors.length];
return CustomPaint({
painter: {
svg: {
createDefaultSvgEl: (context) => ({
line: context.createSvgEl("path"),
area: context.createSvgEl("path")
}),
paint: ({ line, area }, { width, height }) => {
// Create spline curve path
const path = createSplinePath({ values, width, height });
// Line
line.setAttribute("fill", "none");
line.setAttribute("stroke", color);
line.setAttribute("stroke-width", "2");
line.setAttribute("d", path.getD());
// Area
const areaPath = path.clone();
areaPath
.lineTo({ x: width, y: height })
.lineTo({ x: 0, y: height })
.close();
area.setAttribute("fill", color);
area.setAttribute("opacity", "0.2");
area.setAttribute("d", areaPath.getD());
}
}
}
});
}
}
});
function createSplinePath({ values, width, height }) {
const path = new Path();
const points = values.map((value, index) => ({
x: (index / (values.length - 1)) * width,
y: height - (value / Math.max(...values)) * height
}));
// Calculate Bezier control points
const controlPoints = calculateBezierControlPoints(points);
path.moveTo(points[0]);
for (let i = 1; i < points.length; i++) {
const cp1 = controlPoints[i - 1].next;
const cp2 = controlPoints[i].prev;
path.cubicTo(cp1, cp2, points[i]);
}
return path;
}
4. Range Area Chart
const rangeAreaChart = AreaChart({
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
datasets: [
{ legend: 'Min Value', values: [10, 15, 12, 18, 14] },
{ legend: 'Max Value', values: [25, 30, 28, 35, 32] }
]
},
custom: {
series: ({ areas }) => {
// Display range between two datasets
const [minArea, maxArea] = areas;
const minValues = data.datasets[0].values;
const maxValues = data.datasets[1].values;
return CustomPaint({
painter: {
svg: {
createDefaultSvgEl: (context) => ({
range: context.createSvgEl("path"),
minLine: context.createSvgEl("path"),
maxLine: context.createSvgEl("path")
}),
paint: ({ range, minLine, maxLine }, { width, height }) => {
// Range area path
const rangePath = new Path();
// Draw minimum value line
const minPath = createLinePath({ values: minValues, width, height });
// Draw maximum value line (in reverse)
const maxPoints = maxValues.map((value, index) => ({
x: (index / (maxValues.length - 1)) * width,
y: height - (value / Math.max(...maxValues)) * height
})).reverse();
// Create range area
rangePath.moveTo(minPath.getFirstPoint());
minPath.getPoints().forEach(point => rangePath.lineTo(point));
maxPoints.forEach(point => rangePath.lineTo(point));
rangePath.close();
// Apply styling
range.setAttribute("fill", "rgba(59, 130, 246, 0.2)");
range.setAttribute("d", rangePath.getD());
minLine.setAttribute("stroke", "#3b82f6");
minLine.setAttribute("stroke-width", "1");
minLine.setAttribute("stroke-dasharray", "3 3");
minLine.setAttribute("fill", "none");
minLine.setAttribute("d", minPath.getD());
maxLine.setAttribute("stroke", "#3b82f6");
maxLine.setAttribute("stroke-width", "1");
maxLine.setAttribute("stroke-dasharray", "3 3");
maxLine.setAttribute("fill", "none");
maxLine.setAttribute("d", createLinePath({ values: maxValues, width, height }).getD());
}
}
}
});
}
}
});
Adding Animation
import { StatefulWidget, State, AnimationController, Animation, ClipRect, Rect } from '@meursyphus/flitter';
class AnimatedArea extends StatefulWidget {
constructor({ values, color }) {
super();
this.values = values;
this.color = color;
}
createState() {
return new AnimatedAreaState();
}
}
class AnimatedAreaState extends State {
initState() {
super.initState();
this.controller = new AnimationController({
duration: { milliseconds: 1000 }
});
this.animation = new Animation({
controller: this.controller,
value: Tween({ begin: 0, end: 1 })
});
this.controller.forward();
}
build() {
return AnimatedBuilder({
animation: this.animation,
builder: () => {
// Progressive horizontal reveal effect
return ClipRect({
clipper: ({ width, height }) => Rect.fromLTWH({
left: 0,
top: 0,
width: width * this.animation.value,
height: height
}),
child: CustomPaint({
painter: createAreaPainter(this.widget.values, this.widget.color)
})
});
}
});
}
dispose() {
this.controller.dispose();
super.dispose();
}
}
Performance Optimization Tips
- Opacity Optimization: Adjust transparency when multiple areas overlap for better readability
- Data Aggregation: Properly aggregate many data points for display
- SVG vs Canvas: Consider SVG for static charts, Canvas for dynamic charts
- Minimize Layers: Be careful of performance degradation with many overlapping areas
Next Steps
- For more examples, check out the demo page
- To highlight data points, see Scatter Chart
- For proportional data, see Pie Chart