Bubble Chart
Bubble charts represent three-dimensional data on a two-dimensional plane. In addition to X and Y axis coordinates, the size of bubbles visualizes the value of a third dimension, effectively showing complex relationships.
Interface
BubbleChart Props
interface BubbleChartProps {
// Required properties
data: BubbleChartData;
// Optional properties
custom?: Partial<BubbleChartCustom>;
title?: string;
getScale?: (data: BubbleChartData) => BubbleChartScale;
}
Data Structure
type BubbleChartData = {
datasets: {
legend: string; // Dataset name
data: {
x: number; // X-axis value
y: number; // Y-axis value
value: number; // Bubble size value
label: string; // Data point label
}[];
}[];
};
type BubbleChartScale = {
x: {
min: number;
max: number;
step: number;
};
y: {
min: number;
max: number;
step: number;
};
value: {
min: number;
max: number;
step: number;
};
};
Basic Usage
import Widget from '@meursyphus/flitter-react';
import { BubbleChart } from '@meursyphus/headless-chart';
const data = {
datasets: [{
legend: 'Product Performance',
data: [
{ x: 10, y: 20, value: 30, label: 'Product A' },
{ x: 15, y: 35, value: 45, label: 'Product B' },
{ x: 25, y: 30, value: 20, label: 'Product C' },
{ x: 30, y: 45, value: 60, label: 'Product D' }
]
}]
};
function BasicBubbleChart() {
return (
<Widget
width="600px"
height="400px"
widget={BubbleChart({ data })}
/>
);
}
Chart Components
Bubble Chart has a similar structure to Scatter Chart, but uses bubble
elements instead of scatter
:
BubbleChart
└── Layout (overall layout)
├── Title (chart title)
├── Legend (legend)
└── Plot (plot area)
├── XAxis (X-axis)
├── YAxis (Y-axis)
├── Grid (grid)
└── Series (data series)
└── Bubble (individual bubble)
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 other charts.
2. Data Visualization Elements
series
Container for all bubbles.
custom: {
series: ({ points, scale }) => {
return Stack({
children: points.map(point =>
Positioned({
left: calculateX(point.x, scale.x),
top: calculateY(point.y, scale.y),
child: createBubble(point)
})
)
});
}
}
bubble
Individual bubble element. This is the most commonly customized element.
custom: {
bubble: ({ value, label, legend, index }) => {
const colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444'];
const color = colors[index % colors.length];
// Calculate size so area is proportional to value
const size = Math.sqrt(value / Math.PI) * 2;
return Container({
width: size,
height: size,
decoration: new BoxDecoration({
color: `${color}60`,
shape: 'circle',
border: Border.all({ color: color, width: 2 })
})
});
}
}
3. Axis and Grid Elements
xAxis, yAxis, grid, etc.
Uses the same components as Scatter Chart.
Real-World Usage Examples
1. Gradient Bubbles
import { Container, BoxDecoration } from '@meursyphus/flitter';
const gradientBubbleChart = BubbleChart({
data,
custom: {
bubble: ({ value, index }) => {
const gradients = [
{ inner: '#60a5fa', outer: '#3b82f6' },
{ inner: '#86efac', outer: '#10b981' },
{ inner: '#fbbf24', outer: '#f59e0b' }
];
const { inner, outer } = gradients[index % gradients.length];
const size = Math.sqrt(value / Math.PI) * 3;
return Container({
width: size,
height: size,
decoration: new BoxDecoration({
shape: 'circle',
gradient: {
type: 'radial',
colors: [inner, outer],
stops: [0, 1],
center: { x: 0.3, y: 0.3 },
radius: 1
},
boxShadow: [{
color: `${outer}40`,
blurRadius: size / 4,
offset: { x: 0, y: size / 8 }
}]
})
});
}
}
});
2. Bubbles with Data Labels
import { Stack, Center, Text, TextStyle } from '@meursyphus/flitter';
const labeledBubbleChart = BubbleChart({
data,
custom: {
bubble: ({ value, label, index }) => {
const colors = ['#3b82f6', '#10b981', '#f59e0b'];
const color = colors[index % colors.length];
const size = Math.sqrt(value / Math.PI) * 3;
return Stack({
alignment: Alignment.center,
children: [
// Bubble
Container({
width: size,
height: size,
decoration: new BoxDecoration({
shape: 'circle',
color: `${color}40`,
border: Border.all({ color, width: 2 })
})
}),
// Label (only display on sufficiently large bubbles)
if (size > 30)
Column({
mainAxisSize: MainAxisSize.min,
children: [
Text(label, {
style: new TextStyle({
fontSize: Math.min(14, size / 4),
fontWeight: 'bold',
color: color
})
}),
Text(value.toString(), {
style: new TextStyle({
fontSize: Math.min(10, size / 5),
color: '#6b7280'
})
})
]
})
]
});
}
}
});
3. Pattern Bubbles
import { CustomPaint, Path } from '@meursyphus/flitter';
const patternBubbleChart = BubbleChart({
data,
custom: {
bubble: ({ value, index }) => {
const patterns = ['dots', 'lines', 'crosses'];
const pattern = patterns[index % patterns.length];
const size = Math.sqrt(value / Math.PI) * 3;
return CustomPaint({
size: { width: size, height: size },
painter: {
svg: {
createDefaultSvgEl: (context) => {
const g = context.createSvgEl("g");
const circle = context.createSvgEl("circle");
const pattern = context.createSvgEl("pattern");
pattern.setAttribute("id", `pattern-${index}`);
pattern.setAttribute("patternUnits", "userSpaceOnUse");
pattern.setAttribute("width", "10");
pattern.setAttribute("height", "10");
return { g, circle, pattern };
},
paint: ({ g, circle, pattern: patternEl }, { width, height }) => {
// Define pattern
createPattern(patternEl, pattern);
// Draw circle
circle.setAttribute("cx", (width / 2).toString());
circle.setAttribute("cy", (height / 2).toString());
circle.setAttribute("r", (width / 2 - 1).toString());
circle.setAttribute("fill", `url(#pattern-${index})`);
circle.setAttribute("stroke", "#3b82f6");
circle.setAttribute("stroke-width", "2");
g.appendChild(patternEl);
g.appendChild(circle);
}
}
}
});
}
}
});
4. 3D Effect Bubbles
const threeDimensionalBubbleChart = BubbleChart({
data,
custom: {
bubble: ({ value, index }) => {
const colors = ['#3b82f6', '#10b981', '#f59e0b'];
const color = colors[index % colors.length];
const size = Math.sqrt(value / Math.PI) * 3;
return Stack({
children: [
// Shadow
Container({
width: size,
height: size,
margin: EdgeInsets.only({ top: size * 0.1 }),
decoration: new BoxDecoration({
shape: 'circle',
color: 'rgba(0, 0, 0, 0.2)',
filter: 'blur(8px)'
})
}),
// Main bubble
Container({
width: size,
height: size,
decoration: new BoxDecoration({
shape: 'circle',
gradient: {
type: 'radial',
colors: [
`${color}FF`,
`${color}CC`,
`${color}99`
],
center: { x: 0.3, y: 0.3 },
radius: 1
},
border: Border.all({
color: color,
width: 1
})
})
}),
// Highlight
Positioned({
top: size * 0.15,
left: size * 0.15,
child: Container({
width: size * 0.3,
height: size * 0.3,
decoration: new BoxDecoration({
shape: 'circle',
gradient: {
type: 'radial',
colors: [
'rgba(255, 255, 255, 0.8)',
'rgba(255, 255, 255, 0)'
],
radius: 1
}
})
})
})
]
});
}
}
});
Adding Animation
import { StatefulWidget, State, AnimationController, Animation } from '@meursyphus/flitter';
class AnimatedBubble extends StatefulWidget {
constructor({ value, color }) {
super();
this.value = value;
this.color = color;
}
createState() {
return new AnimatedBubbleState();
}
}
class AnimatedBubbleState extends State {
initState() {
super.initState();
this.controller = new AnimationController({
duration: { milliseconds: 1000 }
});
// Size animation
this.sizeAnimation = new Animation({
controller: this.controller,
value: Tween({
begin: 0,
end: Math.sqrt(this.widget.value / Math.PI) * 3
}).chain(
CurveTween({ curve: Curves.elasticOut })
)
});
// Opacity animation
this.opacityAnimation = new Animation({
controller: this.controller,
value: Tween({ begin: 0, end: 0.6 })
});
this.controller.forward();
}
build() {
return AnimatedBuilder({
animation: this.controller,
builder: () => {
const size = this.sizeAnimation.value;
return Container({
width: size,
height: size,
decoration: new BoxDecoration({
shape: 'circle',
color: this.widget.color.replace(')', `, ${this.opacityAnimation.value})`),
border: Border.all({
color: this.widget.color,
width: 2
})
})
});
}
});
}
dispose() {
this.controller.dispose();
super.dispose();
}
}
Performance Optimization Tips
- Size Calculation: Use
Math.sqrt(value)
so area is proportional to value - Overlap Handling: Prevent bubble collisions with force-directed layout
- LOD Application: Keep small bubbles simple, add detail only to large bubbles
- Virtualization: Don’t render bubbles outside the screen
Next Steps
- For more examples, check out the demo page
- For density data, see Heatmap Chart
- For two-dimensional data, see Scatter Chart