circle_packing 1.0.0
circle_packing: ^1.0.0 copied to clipboard
Circle packing algorithm for Flutter - pack circles of varying sizes into a target circular boundary.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:circle_packing/circle_packing.dart';
void main() => runApp(const App());
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const CirclePackingVizPage(),
);
}
}
class CirclePackingVizPage extends StatefulWidget {
const CirclePackingVizPage({super.key});
@override
State<CirclePackingVizPage> createState() => _CirclePackingVizPageState();
}
class _CirclePackingVizPageState extends State<CirclePackingVizPage> {
final _rng = math.Random(2);
List<double> _values = [];
List<Circle> _circles = [];
double _w = 380, _h = 380;
@override
void initState() {
super.initState();
_values = _randomValues(18);
_recompute();
}
List<double> _randomValues(int n) {
return List<double>.generate(n, (_) {
final u = _rng.nextDouble();
final v = math.pow(u, 2.2).toDouble();
return math.max(0.002, v);
});
}
void _recompute() {
final r = math.min(_w, _h) / 2.0;
final circles = circlePack(values: _values, targetRadius: r);
setState(() => _circles = circles);
}
@override
Widget build(BuildContext context) {
final targetR = math.min(_w, _h) / 2.0;
return Scaffold(
appBar: AppBar(
title: const Text('Circle Packing Visualizer'),
actions: [
TextButton(
onPressed: () {
setState(() => _values = _randomValues(_values.length));
_recompute();
},
child: const Text('Randomize'),
),
],
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Expanded(
child: Slider(
value: _w,
min: 220,
max: 700,
onChanged: (v) => setState(() => _w = v),
onChangeEnd: (_) => _recompute(),
label: 'width ${_w.toStringAsFixed(0)}',
),
),
Expanded(
child: Slider(
value: _h,
min: 220,
max: 700,
onChanged: (v) => setState(() => _h = v),
onChangeEnd: (_) => _recompute(),
label: 'height ${_h.toStringAsFixed(0)}',
),
),
],
),
),
Expanded(
child: Center(
child: SizedBox(
width: _w,
height: _h,
child: CustomPaint(
painter: _CirclesPainter(
circles: _circles,
targetRadius: targetR,
),
),
),
),
),
],
),
);
}
}
class _CirclesPainter extends CustomPainter {
final List<Circle> circles;
final double targetRadius;
_CirclesPainter({required this.circles, required this.targetRadius});
@override
void paint(Canvas canvas, Size size) {
final origin = Offset(size.width / 2, size.height / 2);
canvas.translate(origin.dx, origin.dy);
final enclosurePaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawCircle(Offset.zero, targetRadius, enclosurePaint);
final fill = Paint()
..style = PaintingStyle.fill
..color = const Color(0x22000000);
final stroke = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1.5;
for (final c in circles) {
final center = Offset(c.x, c.y);
canvas.drawCircle(center, c.r, fill);
canvas.drawCircle(center, c.r, stroke);
}
}
@override
bool shouldRepaint(covariant _CirclesPainter oldDelegate) {
return oldDelegate.targetRadius != targetRadius ||
oldDelegate.circles.length != circles.length;
}
}