import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; import 'package:scrobbles/config/app_colors.dart'; import 'package:scrobbles/models/data/heatmap.dart'; import 'package:scrobbles/ui/widgets/abstracts/custom_chart.dart'; class ChartHeatmap extends CustomChart { const ChartHeatmap({super.key, required this.chartData}); final HeatmapData chartData; @override double get chartHeight => 150.0; @override double get titleFontSize => 9; final Color baseColor = AppColors.contentColorPink; final double scale = 8.0; final double darkenAmount = 50; @override Widget build(BuildContext context) { if (chartData.data.keys.isEmpty) { return SizedBox( height: chartHeight, ); } return AspectRatio( aspectRatio: 3, child: ScatterChart( ScatterChartData( scatterSpots: getSpots(), minX: 0, maxX: 23, minY: 0, maxY: 7, borderData: FlBorderData(show: false), gridData: const FlGridData(show: false), titlesData: getTitlesData(), scatterTouchData: ScatterTouchData(enabled: false), ), ), ); } Color getColorFromNormalizedValue(double value) { Color colorFrom = Colors.white; Color colorTo = Colors.pink; return Color.lerp(colorFrom, colorTo, value) ?? colorFrom; } int getMaxCount() { int maxValue = 0; chartData.data.forEach((day, hours) { hours.forEach((hour, count) { if (count > maxValue) { maxValue = count; } }); }); return maxValue; } List<ScatterSpot> getSpots() { List<ScatterSpot> spots = []; final int maxCount = getMaxCount(); chartData.data.forEach((day, hours) { // hours.removeWhere((h, i) => h == 24); hours.forEach((hour, count) { double normalizedValue = count / maxCount; spots.add(ScatterSpot( (hour % 24).toDouble(), 8 - day.toDouble(), dotPainter: FlDotSquarePainter( color: getColorFromNormalizedValue(normalizedValue), size: 12, strokeWidth: 0, ), )); }); }); return spots; } @override FlTitlesData getTitlesData() { const AxisTitles none = AxisTitles( sideTitles: SideTitles(showTitles: false), ); final AxisTitles verticalTitles = AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, getTitlesWidget: getVerticalTitlesWidgetWithValue, interval: 1, ), ); final AxisTitles horizontalTitles = AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 20, getTitlesWidget: getHorizontalTitlesWidgetWithHour, interval: 4, ), ); return FlTitlesData( show: true, bottomTitles: horizontalTitles, leftTitles: verticalTitles, topTitles: none, rightTitles: none, ); } @override Widget getVerticalTitlesWidgetWithValue(double value, TitleMeta meta) { final String dayShortName = getDayShortName(8 - value.toInt()); return SideTitleWidget( meta: meta, space: 10, child: Text( tr(dayShortName), style: TextStyle( fontSize: titleFontSize, ), ), ); } }