Skip to content

Commit

Permalink
Make chart interval controllable
Browse files Browse the repository at this point in the history
  • Loading branch information
sruusk committed Dec 14, 2024
1 parent dbb8a94 commit 3e7e669
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 94 deletions.
9 changes: 9 additions & 0 deletions lib/utils/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,20 @@ class ElectricityApi {
).toList();
}

/// Get prices for the current day
Future<List<ElectricityPrice>> getPricesForDay(DateTime day) async {
final DateTime start = DateTime(day.year, day.month, day.day, 0, 0, 0);
final DateTime end = DateTime(day.year, day.month, day.day, 23, 0, 0);
return getPricesForInterval(start, end);
}

/// Get prices for current + n days.
Future<List<ElectricityPrice>> getPricesForDays(int days) async {
final DateTime now = DateTime.now();
final DateTime start = DateTime(now.year, now.month, now.day, 0, 0, 0);
final DateTime end = DateTime(now.year, now.month, now.day + days, 23, 0, 0);
return getPricesForInterval(start, end);
}
}

class ElectricityPrice {
Expand Down
252 changes: 159 additions & 93 deletions lib/widgets/chart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,109 +3,175 @@ import 'package:mrx_charts/mrx_charts.dart';
import 'package:flutter/material.dart';
import 'package:sahkohinta/utils/api.dart';

class ChartWidget extends StatelessWidget {
ChartWidget({super.key, required PriceModifiers modifiers});
final pricesFuture = ElectricityApi().getPricesForDay(DateTime.now());
final modifiersFuture = getModifiers();
class ChartWidget extends StatefulWidget {
final PriceModifiers modifiers;
const ChartWidget({
required this.modifiers,
super.key,
});

@override
State<ChartWidget> createState() => _ChartWidget();
}

class _ChartWidget extends State<ChartWidget> {
late List<double> interval;
List<ElectricityPrice>? prices;
late PriceModifiers modifiers;
bool isLoading = true;
String? errorMessage;

@override
void initState() {
super.initState();
modifiers = widget.modifiers;
interval = getInterval();
ElectricityApi().getPricesForDay(DateTime.now()).then((value) {
setState(() {
prices = value;
});
}).catchError((error) {
setState(() {
errorMessage = error.toString();
});
});
}

_nextInterval() {
setState(() {
if(prices == null || prices!.length < interval[1].toInt() + 12) return;
interval = [interval[0] + 12, interval[1] + 12];
});
}

_previousInterval() {
if(interval[0] < 12) return;
setState(() {
interval = [interval[0] - 12, interval[1] - 12];
});
}

@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([pricesFuture, modifiersFuture]),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'),
);
} else if(snapshot.data!.isEmpty) {
return const Center(
child: Text('Virhe ladatessa hintatietoja'),
);
} else {
var prices = snapshot.data![0] as List<ElectricityPrice>;
var modifiers = snapshot.data![1] as PriceModifiers;
if(prices.length != 24) {
return const Center(
child: Text('Virhe ladatessa hintatietoja'),
);
}
if(errorMessage != null) return Center(child: Text('Error: $errorMessage'));
if(prices == null) return const Center(child: CircularProgressIndicator());

double maxPrice = prices.map((e) => e.priceWithModifiers(modifiers)).reduce(max).ceilToDouble();
double minPrice = min(0, prices.map((e) => e.priceWithModifiers(modifiers)).reduce(min)).floorToDouble();
return Center(
child: Container(
constraints: const BoxConstraints(
maxHeight: 300.0,
maxWidth: 600
),
padding: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 16.0),
child: Chart(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
layers: [
ChartAxisLayer(
settings: ChartAxisSettings(
x: ChartAxisSettingsAxis(
frequency: 1.0,
max: getInterval()[1],
min: getInterval()[0],
textStyle: TextStyle(
color: Theme.of(context).colorScheme.primary.withOpacity(0.6),
fontSize: 14.0,
),
),
y: ChartAxisSettingsAxis(
frequency: (maxPrice - minPrice) / 5,
max: maxPrice,
min: minPrice,
textStyle: TextStyle(
color: Theme.of(context).colorScheme.primary.withOpacity(0.6),
fontSize: 14.0,
),
),
double maxPrice = prices!.map((e) => e.priceWithModifiers(modifiers)).reduce(max).ceilToDouble();
double minPrice = min(0, prices!.map((e) => e.priceWithModifiers(modifiers)).reduce(min)).floorToDouble();

return Center(
child: Column(
children: [
Row(
children: [
const SizedBox(width: 16.0),
Text(
'${prices![interval[0].toInt()].time.toLocal().month}. ${prices![interval[0].toInt()].time.toLocal().day}. '
'klo ${prices![interval[0].toInt()].time.toLocal().hour} '
'- ${prices![interval[1].toInt()].time.toLocal().hour + 1} välillä',
style: Theme.of(context).textTheme.headlineSmall
),
],
),
Container(
constraints: const BoxConstraints(
maxHeight: 300.0,
maxWidth: 600
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0),
color: Theme.of(context).colorScheme.surfaceContainerLow,
),
margin: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16.0),
child: Chart(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
layers: [
ChartAxisLayer(
settings: ChartAxisSettings(
x: ChartAxisSettingsAxis(
frequency: 1.0,
max: interval[1],
min: interval[0],
textStyle: TextStyle(
color: Theme.of(context).colorScheme.primary.withOpacity(0.6),
fontSize: 14.0,
),
labelX: (value) => value.toInt().toString(),
labelY: (value) => value.toInt().toString(),
),
ChartBarLayer(
items: List.generate(
12,
(index) => ChartBarDataItem(
color: Theme.of(context).colorScheme.primary,
value: prices[index + getInterval()[0].toInt()].priceWithModifiers(modifiers),
x: index.toDouble() + getInterval()[0],
),
),
settings: const ChartBarSettings(
thickness: 14.0,
y: ChartAxisSettingsAxis(
frequency: (maxPrice - minPrice) / 5,
max: maxPrice,
min: minPrice,
textStyle: TextStyle(
color: Theme.of(context).colorScheme.primary.withOpacity(0.6),
fontSize: 14.0,
),
),
ChartTooltipLayer(
shape: () => ChartTooltipBarShape<ChartBarDataItem>(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
radius: 8.0,
currentPos: (item) => item.currentValuePos,
currentSize: (item) => item.currentValueSize,
onTextValue: (item) => '${item.value.toStringAsFixed(2)} sent/kWh',
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
),
)
),
labelX: (value) => value.toInt().toString(),
labelY: (value) => value.toInt().toString(),
),
ChartBarLayer(
items: List.generate(
12,
(index) => ChartBarDataItem(
color: Theme.of(context).colorScheme.primary,
value: prices![index + interval[0].toInt()].priceWithModifiers(modifiers),
x: index.toDouble() + interval[0],
),
),
settings: const ChartBarSettings(
thickness: 14.0,
),
),
ChartTooltipLayer(
shape: () => ChartTooltipBarShape<ChartBarDataItem>(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
radius: 8.0,
currentPos: (item) => item.currentValuePos,
currentSize: (item) => item.currentValueSize,
onTextValue: (item) => '${item.value.toStringAsFixed(2)} sent/kWh',
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
),
)
),
],
)
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _previousInterval,
child: Row(
children: [
const Icon(Icons.arrow_back),
const SizedBox(width: 8.0),
Text('Edelliset 12 tuntia', style: Theme.of(context).textTheme.bodyMedium),
],
)
)
);
}
}
),
),
const SizedBox(width: 16.0),
ElevatedButton(
onPressed: _nextInterval,
child: Row(
children: [
Text('Seuraavat 12 tuntia', style: Theme.of(context).textTheme.bodyMedium),
const SizedBox(width: 8.0),
const Icon(Icons.arrow_forward),
],
),
),
],
)
],
)
);
}

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.4+1
version: 1.0.5+1

environment:
sdk: ^3.5.3
Expand Down

0 comments on commit 3e7e669

Please sign in to comment.