Skip to content
Snippets Groups Projects
heatmap.dart 3.24 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/ui/widgets/abstracts/custom_chart.dart';
    
    import 'package:scrobbles/utils/color_extensions.dart';
    
    
    class ChartHeatmap extends CustomChart {
    
      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(),
    
              8 - 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: getHorizontalTitlesWidgetWithHour,
    
            interval: 4,
          ),
        );
    
        return FlTitlesData(
          show: true,
          bottomTitles: horizontalTitles,
          leftTitles: verticalTitles,
          topTitles: none,
          rightTitles: none,
        );
      }
    
      Widget getVerticalTitlesWidget(double value, TitleMeta meta) {
    
        final String dayShortName = getDayShortName(8 - value.toInt());
    
    
        return SideTitleWidget(
          axisSide: meta.axisSide,
          space: 10,
          child: Text(
            tr(dayShortName),
            style: TextStyle(
              color: AppColors.mainTextColor1,
              fontSize: this.titleFontSize,
            ),
          ),
        );
      }
    }