Как составить график из json на flutter?
приходит вот такой 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)?
Ответы (1 шт):
Автор решения: Maxgmer
→ Ссылка
Создать такой график очень просто, сделал вам базовый, чтобы показать, как рисовать свои виджеты во Flutter.
В итоге, вы получите такой график:
Шаги:
- Создаем модель данных, которая отразит указанный вами 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);
}
}
- Создаем сам виджет. Виджет состоит из двух компонентов:
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);
}
}
}
- Используем виджет
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),
],
),
),
);
}
