Skip to content
Snippets Groups Projects
heatmap.dart 4.04 KiB
Newer Older
  • Learn to ignore specific revisions
  • import 'package:easy_localization/easy_localization.dart';
    import 'package:fl_chart/fl_chart.dart';
    import 'package:flutter/material.dart';
    import 'package:scrobbles/config/app_colors.dart';
    
    import 'package:scrobbles/models/heatmap.dart';
    import 'package:scrobbles/utils/color_extensions.dart';
    
    class ChartHeatmap extends StatelessWidget {
      final HeatmapData chartData;
    
      const ChartHeatmap({super.key, required this.chartData});
    
      final double chartHeight = 150.0;
      final double titleFontSize = 9;
      final Color baseColor = AppColors.contentColorPink;
      final double scale = 8.0;
      final double darkenAmount = 50;
    
      @override
      Widget build(BuildContext context) {
        if (this.chartData.data.keys.length == 0) {
          return SizedBox(
            height: this.chartHeight,
          );
        }
    
        return AspectRatio(
          aspectRatio: 3,
          child: ScatterChart(
            ScatterChartData(
              scatterSpots: getSpots(),
              minX: 0,
              maxX: 24,
              minY: 0,
              maxY: 7,
              borderData: FlBorderData(show: false),
              gridData: FlGridData(show: false),
              titlesData: getTitlesData(),
              scatterTouchData: ScatterTouchData(enabled: false),
            ),
          ),
        );
      }
    
      Color getColorFromNormalizedValue(double value) {
        return baseColor.darken(1 + (darkenAmount * (1 - value)).toInt());
      }
    
      int getMaxCount() {
        int maxValue = 0;
    
        this.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();
    
        this.chartData.data.forEach((day, hours) {
          hours.forEach((hour, count) {
            double normalizedValue = count / maxCount;
    
            spots.add(ScatterSpot(
              hour.toDouble(),
              day.toDouble(),
              color: getColorFromNormalizedValue(normalizedValue),
              radius: scale * normalizedValue,
            ));
          });
        });
    
        return spots;
      }
    
      FlTitlesData getTitlesData() {
        const AxisTitles none = const AxisTitles(
          sideTitles: SideTitles(showTitles: false),
        );
    
        final AxisTitles verticalTitles = AxisTitles(
          sideTitles: SideTitles(
            showTitles: true,
            reservedSize: 40,
            getTitlesWidget: getVerticalTitlesWidget,
            interval: 1,
          ),
        );
    
        final AxisTitles horizontalTitles = AxisTitles(
          sideTitles: SideTitles(
            showTitles: true,
            reservedSize: 20,
            getTitlesWidget: getHorizontalTitlesWidget,
            interval: 4,
          ),
        );
    
        return FlTitlesData(
          show: true,
          bottomTitles: horizontalTitles,
          leftTitles: verticalTitles,
          topTitles: none,
          rightTitles: none,
        );
      }
    
      Widget getVerticalTitlesWidget(double value, TitleMeta meta) {
        final int day = value.toInt();
        String dayShortName = '';
        switch (day) {
          case 1:
            dayShortName = 'MON';
            break;
          case 2:
            dayShortName = 'TUE';
            break;
          case 3:
            dayShortName = 'WED';
            break;
          case 4:
            dayShortName = 'THU';
            break;
          case 5:
            dayShortName = 'FRI';
            break;
          case 6:
            dayShortName = 'SAT';
            break;
          case 7:
            dayShortName = 'SUN';
            break;
          default:
        }
    
        return SideTitleWidget(
          axisSide: meta.axisSide,
          space: 10,
          child: Text(
            tr(dayShortName),
            style: TextStyle(
              color: AppColors.mainTextColor1,
              fontSize: this.titleFontSize,
            ),
          ),
        );
      }
    
      Widget getHorizontalTitlesWidget(double value, TitleMeta meta) {
        String text = '';
    
        if (value % 4 == 0 || value == 23) {
          text = value.toInt().toString().padLeft(2, '0');
        }
    
        return SideTitleWidget(
          axisSide: meta.axisSide,
          space: 2,
          child: Text(
            text,
            style: TextStyle(
              color: AppColors.mainTextColor1,
              fontSize: this.titleFontSize,
            ),
          ),
        );
      }
    }