heart_rate_chart 1.1.1
heart_rate_chart: ^1.1.1 copied to clipboard
A lightweight Flutter heart‑rate range chart (hour buckets + spikes) with rich customization.
import 'package:flutter/material.dart';
import 'package:heart_rate_chart/heart_rate_chart.dart';
void main() => runApp(const DemoApp());
class DemoApp extends StatelessWidget {
const DemoApp({super.key});
@override
Widget build(BuildContext context) {
final today = DateTime.now();
final start = DateTime(today.year, today.month, today.day);
final data = List.generate(24, (h) {
final t = start.add(Duration(hours: h));
final min = 55 + (h % 5) * 3;
final max = min + 30 + (h % 3) * 10;
return BucketWithSpikes(
HeartRateBucket(
time: t, minBpm: min, maxBpm: max, medianBpm: (min + max) ~/ 2),
h % 6 == 0
? [HeartSpike(t.add(const Duration(minutes: 30)), max + 30)]
: const [],
);
});
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Heart Rate Chart Example')),
body: Center(
child: SizedBox(
height: 260,
child: HeartRateChart(
buckets: data,
range: DateTimeRange(
start: start, end: start.add(const Duration(days: 1))),
style: const HeartRateChartStyle(
barWidthFactor: 0.55,
spikeRadius: 5,
),
config: HeartRateChartConfig(
minY: 40,
maxY: 200,
showMedianLine: true,
yTicks: const [40, 100, 160, 200],
xTimeLabelFormatter: (t) =>
'${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}',
tooltipFormatter: (d) =>
'${d.minBpm}–${d.maxBpm} bpm (median: ${d.medianBpm ?? '-'} )\n'
'${d.start.hour.toString().padLeft(2, '0')}:00 – '
'${d.end.hour.toString().padLeft(2, '0')}:00',
),
onTapBucket: (b) {
debugPrint(
'Tapped bucket @ ${b.time}: ${b.minBpm}–${b.maxBpm}');
},
),
),
),
),
);
}
}
class _Comparison extends StatelessWidget {
const _Comparison({super.key});
@override
Widget build(BuildContext context) {
final today = DateTime.now();
final start = DateTime(today.year, today.month, today.day);
// Fake data for comparison (no health plugin required at runtime)
final base = List.generate(24, (h) {
final t = start.add(Duration(hours: h));
final min = 55 + (h % 5) * 3;
final max = min + 30 + (h % 3) * 10;
return BucketWithSpikes(
HeartRateBucket(
time: t, minBpm: min, maxBpm: max, medianBpm: (min + max) ~/ 2),
h % 6 == 0
? [HeartSpike(t.add(const Duration(minutes: 30)), max + 30)]
: const [],
);
});
// Two charts stacked: exclude spikes (default) vs include spikes
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('Exclude spikes from bars (default)',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
SizedBox(
height: 220,
child: HeartRateChart(
buckets: base,
range: DateTimeRange(
start: start, end: start.add(const Duration(days: 1))),
style: const HeartRateChartStyle(barWidthFactor: 0.55),
config: const HeartRateChartConfig(
minY: 40, maxY: 200, showMedianLine: true),
),
),
const SizedBox(height: 16),
const Text('Include spikes in bars',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
SizedBox(
height: 220,
child: HeartRateChart(
buckets: base
.map((b) => BucketWithSpikes(
HeartRateBucket(
time: b.bucket.time,
minBpm: b.bucket.minBpm,
maxBpm: b.bucket.maxBpm,
medianBpm: b.bucket.medianBpm),
b.spikes,
))
.toList(),
range: DateTimeRange(
start: start, end: start.add(const Duration(days: 1))),
style: const HeartRateChartStyle(
barWidthFactor: 0.55, barColor: Color(0xffe91e63)),
config: const HeartRateChartConfig(
minY: 40, maxY: 200, showMedianLine: true),
),
),
],
),
),
);
}
}