Как составить график из json на flutter?

Рейтинг: 1Ответов: 1Опубликовано: 26.04.2023

приходит вот такой json

    {
        "sensorTable": [
            {
                "availability": 15.2,
                "avgTemp": 21.3,
                "day": "2021-05-12",
                "hour": "00",
                "maxTemp": 21.4,
                "minTemp": 21.3
            },
            {
                "availability": 62.8,
                "avgTemp": 21.5,
                "day": "2021-05-11",
                "hour": "23",
                "maxTemp": 22,
                "minTemp": 21.3
            },
            {
                "availability": 79,
                "avgTemp": 22.1,
                "day": "2021-05-11",
                "hour": "22",
                "maxTemp": 22.1,
                "minTemp": 22
            },
            {
                "availability": 64.1,
                "avgTemp": 21.5,
                "day": "2021-05-11",
                "hour": "21",
                "maxTemp": 21.5,
                "minTemp": 21.4
            },
            {
                "availability": 100,
                "avgTemp": 21.5,
                "day": "2021-05-11",
                "hour": "20",
                "maxTemp": 21.6,
                "minTemp": 21.5
            },
            {
                "availability": 100,
                "avgTemp": 21.6,
                "day": "2021-05-11",
                "hour": "19",
                "maxTemp": 21.6,
                "minTemp": 21.5
            },
}

(это его часть)

Как составить график x которого это дата (day) а y это температура (avgTemp)?

Ответы

▲ 0Принят

Создать такой график очень просто, сделал вам базовый, чтобы показать, как рисовать свои виджеты во Flutter.

В итоге, вы получите такой график:

Как будет выглядеть график


Шаги:

  1. Создаем модель данных, которая отразит указанный вами json. Список таких моделей мы пробросим в сам виджет в дальнейшем, чтобы он нарисовал график:
class SensorData {
  final double availability;
  final double avgTemp;
  final String day;
  final String hour;
  final double maxTemp;
  final double minTemp;

  SensorData(
    this.availability,
    this.avgTemp,
    this.day,
    this.hour,
    this.maxTemp,
    this.minTemp,
  );

  DateTime get date {
    List<String> dateSegments = this.day.split('-');
    int year = int.parse(dateSegments[0]);
    int month = int.parse(dateSegments[1]);
    int day = int.parse(dateSegments[2]);
    int hours = int.parse(hour);
    return DateTime(year, month, day, hours);
  }
}
  1. Создаем сам виджет. Виджет состоит из двух компонентов:
  • StatelessWidget, который вы будете использовать (SensorDataChart).
  • CustomPainter, который используется в SensorDataChart, чтобы нарисовать график. Туда вы полезете, чтобы кастомизировать график дальше.
class SensorDataChart extends StatelessWidget {
  final List<SensorData> sensorData;

  const SensorDataChart({
    required this.sensorData,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    if (sensorData.isEmpty) {
      return const Center(
        child: Text('Данные с сенсоров не получены'),
      );
    }
    return CustomPaint(
      painter: _SensorDataChartPainter(sensorData),
    );
  }
}

class _SensorDataChartPainter extends CustomPainter {
  static const _celsiusSymbol = '°C';
  static const _textPadding = 10;
  static const _dataPointCircleRadius = 4.0;
  static const _textStyleDeg = TextStyle(
    fontSize: 10,
    color: Colors.black,
  );
  static const _textStyleDate = TextStyle(
    fontSize: 10,
    color: Colors.black,
  );

  final List<SensorData> sensorData;

  _SensorDataChartPainter(this.sensorData);

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint();
    paint.color = Colors.black.withOpacity(0.1);
    paint.strokeCap = StrokeCap.round;
    paint.strokeWidth = 1.0;
    paint.style = PaintingStyle.fill;

    /// Sort by date
    sensorData.sort((e1, e2) => e1.date.compareTo(e2.date));

    /// Find highest and lowest avgTemp
    final firstSensorData = sensorData.first;
    double highestAvgTemp = firstSensorData.avgTemp;
    double lowestAvgTemp = firstSensorData.avgTemp;
    for (final sensorData in sensorData) {
      if (highestAvgTemp < sensorData.avgTemp) {
        highestAvgTemp = sensorData.avgTemp;
      } else if (lowestAvgTemp > sensorData.avgTemp) {
        lowestAvgTemp = sensorData.avgTemp;
      }
    }

    /// Find temp delimiter height in chart
    int highestTempInChart = highestAvgTemp.ceil();
    int lowestTempInChart = lowestAvgTemp.floor();
    int degreesInChart = highestTempInChart - lowestTempInChart;
    double heightPerDegree = size.height / degreesInChart;

    /// Find day/hour width in chart
    final lastSensorData = sensorData.last;
    final hoursInChart = firstSensorData.date.difference(lastSensorData.date).inHours.abs();
    final daysInChart = (firstSensorData.date.difference(lastSensorData.date).inHours / 24).abs();
    final widthPerHour = size.width / hoursInChart;
    final widthPerDay = size.width / daysInChart;

    _drawTempDelimiters(
      canvas,
      paint,
      size,
      degreesInChart,
      heightPerDegree,
      lowestTempInChart,
    );

    _drawDayDelimiters(
      canvas,
      paint,
      size,
      firstSensorData,
      daysInChart,
      widthPerDay,
    );

    paint.color = Colors.black;
    _drawChart(
      canvas,
      size,
      paint,
      firstSensorData,
      lowestTempInChart,
      widthPerHour,
      heightPerDegree,
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;

  void _drawTempDelimiters(
    Canvas canvas,
    Paint paint,
    Size size,
    int degreesInChart,
    double heightPerDegree,
    int lowestTempInChart,
  ) {
    for (int nextDeg = 0; nextDeg <= degreesInChart; nextDeg++) {
      final nextDegY = size.height - nextDeg * heightPerDegree;

      TextSpan degreeText = TextSpan(
        style: _textStyleDeg,
        text: '${lowestTempInChart + nextDeg}$_celsiusSymbol',
      );
      TextPainter textPainter = TextPainter(
        text: degreeText,
        textAlign: TextAlign.left,
        textDirection: TextDirection.ltr,
      );
      textPainter.layout();
      textPainter.paint(
        canvas,
        Offset(
          -textPainter.width - _textPadding,
          nextDegY - textPainter.height * 0.5,
        ),
      );

      canvas.drawLine(
        Offset(0, nextDegY),
        Offset(size.width, nextDegY),
        paint,
      );
    }
  }

  void _drawDayDelimiters(
    Canvas canvas,
    Paint paint,
    Size size,
    SensorData firstSensorData,
    double daysInChart,
    double widthPerDay,
  ) {
    for (int nextDay = 0; nextDay <= daysInChart; nextDay++) {
      final nextDayX = nextDay * widthPerDay;
      final delimiterDate = firstSensorData.date.add(Duration(days: nextDay));

      TextSpan dateText = TextSpan(
        style: _textStyleDate,
        text: delimiterDate.toIso8601String().split('T')[0],
      );
      TextPainter textPainter = TextPainter(
        text: dateText,
        textAlign: TextAlign.center,
        textDirection: TextDirection.ltr,
      );
      textPainter.layout(maxWidth: widthPerDay);
      textPainter.paint(
        canvas,
        Offset(
          nextDayX - textPainter.width * 0.5,
          size.height + _textPadding,
        ),
      );

      canvas.drawLine(
        Offset(nextDayX, 0),
        Offset(nextDayX, size.height),
        paint,
      );
    }
  }

  void _drawChart(
    Canvas canvas,
    Size size,
    Paint paint,
    SensorData firstSensorData,
    int lowestTempInChart,
    double widthPerHour,
    double heightPerDegree,
  ) {
    for (int i = 0; i < sensorData.length - 1; i++) {
      SensorData sensorDataCurrent = sensorData[i];
      SensorData sensorDataNext = sensorData[i + 1];

      final chartXInHours = sensorDataCurrent.date.difference(firstSensorData.date).inHours.abs();
      final chartYInDegrees = sensorDataCurrent.avgTemp - lowestTempInChart;
      final chartXInHoursNext = sensorDataNext.date.difference(firstSensorData.date).inHours.abs();
      final chartYInDegreesNext = sensorDataNext.avgTemp - lowestTempInChart;

      final offsetCurrent = Offset(
        chartXInHours * widthPerHour,
        size.height - chartYInDegrees * heightPerDegree,
      );
      final offsetNext = Offset(
        chartXInHoursNext * widthPerHour,
        size.height - chartYInDegreesNext * heightPerDegree,
      );

      canvas.drawCircle(offsetCurrent, _dataPointCircleRadius, paint);
      canvas.drawCircle(offsetNext, _dataPointCircleRadius, paint);
      canvas.drawLine(offsetCurrent, offsetNext, paint);
    }
  }
}

  1. Используем виджет SensorDataChart. В сам виджет нужно прокинуть лист с моделями, которые вы получаете с сервера. Я создал произвольный лист данных для примера.
@override
Widget build(BuildContext context) {
  return Center(
    child: SizedBox(
      width: 700,
      height: 400,
      child: SensorDataChart(
        sensorData: [
          SensorData(10, 29.7, "2022-04-12", "00", 45.6, 30.1),
          SensorData(10, 45.1, "2022-04-13", "00", 45.1, 30.1),
          SensorData(10, 33.3, "2022-04-16", "00", 45.1, 30.1),
          SensorData(10, 36.4, "2022-04-17", "23", 45.1, 30.1),
          SensorData(10, 32.1, "2022-04-22", "01", 45.1, 30.1),
        ],
      ),
    ),
  );
}