Skip to content
Snippets Groups Projects
heatmap.dart 3.35 KiB
Newer Older
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;

  double get chartHeight => 150.0;
  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(
      );
    }

    return AspectRatio(
      aspectRatio: 3,
      child: ScatterChart(
        ScatterChartData(
          scatterSpots: getSpots(),
          minX: 0,
          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;
  }

  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,
    );
  }

  Widget getVerticalTitlesWidgetWithValue(double value, TitleMeta meta) {
    final String dayShortName = getDayShortName(8 - value.toInt());

    return SideTitleWidget(
      space: 10,
      child: Text(
        tr(dayShortName),
        style: TextStyle(