diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index a6842f9..f9315c1 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -9,9 +9,9 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:http/http.dart'; import 'package:intl/intl.dart'; import 'dart:math'; -import 'package:fl_chart/fl_chart.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter/services.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/services/tetrio_crud.dart'; @@ -36,6 +36,7 @@ import 'package:go_router/go_router.dart'; final TetrioService teto = TetrioService(); // thing, that manadge our local DB int _chartsIndex = 0; bool _gamesPlayedInsteadOfDateAndTime = false; +late ZoomPanBehavior _zoomPanBehavior; bool _smooth = false; List _historyShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR", "Opener", "Plonk", "Inf. DS", "Stride"]; late ScrollController _scrollController; @@ -88,10 +89,7 @@ class _MainState extends State with TickerProviderStateMixin { String _searchFor = "6098518e3d5155e6ec429cdc"; // who we looking for String _titleNickname = ""; /// Each dropdown menu item contains list of dots for the graph - List>> chartsData = []; - List>>? smoothChartsData; - List>> chartsDataGamesPlayed = []; - List>>? smoothChartsDataGamesPlayed; + List>> chartsData = []; //var tableData = []; final bodyGlobalKey = GlobalKey(); bool _showSearchBar = false; @@ -108,7 +106,12 @@ class _MainState extends State with TickerProviderStateMixin { _scrollController = ScrollController(); _tabController = TabController(length: 6, vsync: this); _wideScreenTabController = TabController(length: 4, vsync: this); - + _zoomPanBehavior = ZoomPanBehavior( + enablePinching: true, + enableSelectionZooming: true, + enableMouseWheelZooming : true, + enablePanning: true, + ); // We need to show something if (widget.player != null){ // if we have user input, changePlayer(widget.player!); // it's gonna be user input @@ -267,95 +270,32 @@ class _MainState extends State with TickerProviderStateMixin { if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1); } // Also i need previous Tetra League State for comparison if avaliable - // tableData = [ - // TableRow(children: [ Text("Date & Time"), Text("Tr"), Text("Glicko"), Text("RD"), Text("GP"), Text("GW"), Text("APM"), Text("PPS"), Text("VS"), Text("APP"), Text("VS/APM"), Text("DS/S"), Text("DS/P"), Text("APP+DS/P"), Text("Cheese"), Text("GbE"), Text("wAPP"), Text("Area"), Text("eTR"), Text("±eTR"), Text("Opener"), Text("Plonk"), Text("Inf. DS"), Text("Stride")], - // decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.white)))), - // for (var state in states) TableRow(children: [Text(dateFormat.format(state.tlSeason1.timestamp)), Text(f4.format(state.tlSeason1.rating)), Text(f4.format(state.tlSeason1.glicko)), Text(f4.format(state.tlSeason1.rd)), Text(f0.format(state.tlSeason1.gamesPlayed)), Text(f0.format(state.tlSeason1.gamesWon)), Text(f2.format(state.tlSeason1.apm)), Text(f2.format(state.tlSeason1.pps)), Text(state.tlSeason1.vs != null ? f2.format(state.tlSeason1.vs) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.app) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.vsapm) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.dss) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.dsp) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.appdsp) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.cheese) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.gbe) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.nyaapp) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.area) : "---"), Text(state.tlSeason1.estTr != null ? f4.format(state.tlSeason1.estTr?.esttr) : "---"), Text(state.tlSeason1.esttracc != null ? f4.format(state.tlSeason1.esttracc) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.opener) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.plonk) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.infds) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.stride) : "---")]), - // ]; if (uniqueTL.length >= 2){ compareWith = uniqueTL.toList().elementAtOrNull(uniqueTL.length - 2); - chartsData = >>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.rating)], child: Text(t.statCellNum.tr)), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.glicko!)], child: const Text("Glicko")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.rd!)], child: const Text("Rating Deviation")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.apm != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.apm!)], child: Text(t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.pps != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.pps!)], child: Text(t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.vs != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.vs!)], child: Text(t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.nerdStats!.app)], child: Text(t.statCellNum.app.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.nerdStats!.dss)], child: Text(t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.nerdStats!.dsp)], child: Text(t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.nerdStats!.appdsp)], child: const Text("APP + DS/P")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.nerdStats!.vsapm)], child: const Text("VS/APM")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.nerdStats!.cheese)], child: Text(t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.nerdStats!.gbe)], child: Text(t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.nerdStats!.nyaapp)], child: Text(t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.nerdStats!.area)], child: Text(t.statCellNum.area.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.estTr != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.esttracc != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.playstyle!.opener)], child: const Text("Opener")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.playstyle!.plonk)], child: const Text("Plonk")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.playstyle!.infds)], child: const Text("Inf. DS")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.playstyle!.stride)], child: const Text("Stride")), + //chartsData = [for (var tl in uniqueTL) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, [tl.rating, tl.glicko, tl.rd, tl.apm, tl.pps, tl.vs, tl.nerdStats?.app, tl.nerdStats?.dss, tl.nerdStats?.dsp, tl.nerdStats?.appdsp, tl.nerdStats?.vsapm, tl.nerdStats?.cheese, tl.nerdStats?.gbe, tl.nerdStats?.nyaapp, tl.nerdStats?.area, tl.estTr?.esttr, tl.esttracc, tl.playstyle?.opener, tl.playstyle?.plonk, tl.playstyle?.infds, tl.playstyle?.stride])]; + chartsData = >>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.rating)], child: Text(t.statCellNum.tr)), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.glicko!)], child: const Text("Glicko")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.rd!)], child: const Text("Rating Deviation")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.apm != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.apm!)], child: Text(t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.pps != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.pps!)], child: Text(t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.vs != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.vs!)], child: Text(t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.app)], child: Text(t.statCellNum.app.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.dss)], child: Text(t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.dsp)], child: Text(t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.appdsp)], child: const Text("APP + DS/P")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.vsapm)], child: const Text("VS/APM")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.cheese)], child: Text(t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.gbe)], child: Text(t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.nyaapp)], child: Text(t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.area)], child: Text(t.statCellNum.area.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.estTr != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.esttracc != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.opener)], child: const Text("Opener")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.plonk)], child: const Text("Plonk")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.infds)], child: const Text("Inf. DS")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.stride)], child: const Text("Stride")), ]; - chartsDataGamesPlayed = >>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.gamesPlayed.toDouble(), tl.rating)], child: Text(t.statCellNum.tr)), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.gamesPlayed.toDouble(), tl.glicko!)], child: const Text("Glicko")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.gamesPlayed.toDouble(), tl.rd!)], child: const Text("Rating Deviation")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.apm != null) FlSpot(tl.gamesPlayed.toDouble(), tl.apm!)], child: Text(t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.pps != null) FlSpot(tl.gamesPlayed.toDouble(), tl.pps!)], child: Text(t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.vs != null) FlSpot(tl.gamesPlayed.toDouble(), tl.vs!)], child: Text(t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.app)], child: Text(t.statCellNum.app.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.dss)], child: Text(t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.dsp)], child: Text(t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.appdsp)], child: const Text("APP + DS/P")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.vsapm)], child: const Text("VS/APM")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.cheese)], child: Text(t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.gbe)], child: Text(t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.nyaapp)], child: Text(t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.area)], child: Text(t.statCellNum.area.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.estTr != null) FlSpot(tl.gamesPlayed.toDouble(), tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.esttracc != null) FlSpot(tl.gamesPlayed.toDouble(), tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.opener)], child: const Text("Opener")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.plonk)], child: const Text("Plonk")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.infds)], child: const Text("Inf. DS")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.stride)], child: const Text("Stride")), - ]; - if (chartsData[0].value!.length > 200){ - smoothChartsData = []; - smoothChartsDataGamesPlayed = []; - for (var chart in chartsData) { - int valuesPerDot = (chart.value!.length / 200).floor(); - int lastDotEntries = chart.value!.length - (valuesPerDot * 199); - List spots = []; - for (int i=0; i < 200; i++){ - double avgX = 0, avgY = 0; - for (int k = i * valuesPerDot; k < (i == 199 ? chart.value!.length : i * valuesPerDot + valuesPerDot); k++) { - avgX += chart.value![k].x; - avgY += chart.value![k].y; - } - avgX /= i == 199 ? lastDotEntries : valuesPerDot; - avgY /= i == 199 ? lastDotEntries : valuesPerDot; - spots.add(FlSpot(avgX, avgY)); - } - smoothChartsData!.add(DropdownMenuItem(value: spots, child: chart.child)); - } - for (var chart in chartsDataGamesPlayed) { - int valuesPerDot = (chart.value!.length / 200).floor(); - int lastDotEntries = chart.value!.length - (valuesPerDot * 199); - List spots = []; - for (int i=0; i < 200; i++){ - double avgX = 0, avgY = 0; - for (int k = i * valuesPerDot; k < (i == 199 ? chart.value!.length : i * valuesPerDot + valuesPerDot); k++) { - avgX += chart.value![k].x; - avgY += chart.value![k].y; - } - avgX /= i == 199 ? lastDotEntries : valuesPerDot; - avgY /= i == 199 ? lastDotEntries : valuesPerDot; - spots.add(FlSpot(avgX, avgY)); - } - smoothChartsDataGamesPlayed!.add(DropdownMenuItem(value: spots, child: chart.child)); - } - } }else{ compareWith = null; chartsData = []; @@ -527,7 +467,7 @@ class _MainState extends State with TickerProviderStateMixin { child: _TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched, separateScrollController: true,) ), ],), - _History(chartsData: chartsData, chartsDataGamesPlayed: chartsDataGamesPlayed, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, smoothChartsData: smoothChartsData, smoothChartsDataGamesPlayed: smoothChartsDataGamesPlayed), + _History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0), _TwoRecordsThingy(sprint: snapshot.data![1]['sprint'], blitz: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank,), _OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],) ] : [ @@ -542,7 +482,7 @@ class _MainState extends State with TickerProviderStateMixin { lbPositions: meAmongEveryone ), _TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched), - _History(chartsData: chartsData, chartsDataGamesPlayed: chartsDataGamesPlayed, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, smoothChartsData: smoothChartsData, smoothChartsDataGamesPlayed: smoothChartsDataGamesPlayed), + _History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0), _RecordThingy(record: snapshot.data![1]['sprint'], rank: snapshot.data![0].tlSeason1.percentileRank), _RecordThingy(record: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank), _OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],) @@ -805,10 +745,7 @@ class _TLRecords extends StatelessWidget { } class _History extends StatelessWidget{ - final List>> chartsData; - final List>>? smoothChartsData; - final List>> chartsDataGamesPlayed; - final List>>? smoothChartsDataGamesPlayed; + final List>> chartsData; final String userID; final Function update; final Function changePlayer; @@ -816,7 +753,7 @@ class _History extends StatelessWidget{ /// Widget, that can show history of some stat of the player on the graph. /// Requires player [states], which is list of states and function [update], which rebuild widgets - const _History({required this.chartsData, required this.chartsDataGamesPlayed, required this.userID, required this.changePlayer, required this.update, required this.wasActiveInTL, this.smoothChartsData, this.smoothChartsDataGamesPlayed}); + const _History({required this.chartsData, required this.userID, required this.changePlayer, required this.update, required this.wasActiveInTL}); @override Widget build(BuildContext context) { @@ -831,8 +768,8 @@ class _History extends StatelessWidget{ )); } bool bigScreen = MediaQuery.of(context).size.width > 768; - var selectedGraph = _gamesPlayedInsteadOfDateAndTime ? chartsDataGamesPlayed[_chartsIndex].value! : chartsData[_chartsIndex].value!; - var smoothSelectedGraph = _gamesPlayedInsteadOfDateAndTime ? (smoothChartsDataGamesPlayed?[_chartsIndex].value) : (smoothChartsData?[_chartsIndex].value); + //List<_HistoryChartSpot> selectedGraph = _gamesPlayedInsteadOfDateAndTime ? chartsDataGamesPlayed[_chartsIndex].value! : chartsData[_chartsIndex].value!; + List<_HistoryChartSpot> selectedGraph = chartsData[_chartsIndex].value!; return SingleChildScrollView( scrollDirection: Axis.vertical, child: SingleChildScrollView( @@ -873,7 +810,7 @@ class _History extends StatelessWidget{ ), ], ), - if (smoothSelectedGraph != null) Row( + if (selectedGraph.length > 300) Row( mainAxisSize: MainAxisSize.min, children: [ Checkbox(value: _smooth, @@ -884,10 +821,11 @@ class _History extends StatelessWidget{ })), Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22)) ], - ) + ), + IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: Icon(Icons.refresh), alignment: Alignment.center,) ], ), - if(chartsData[_chartsIndex].value!.length > 1) _HistoryChartThigy(data: selectedGraph, smoothData: smoothSelectedGraph, smooth: _smooth, yAxisTitle: _historyShortTitles[_chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(), xFormat: NumberFormat.compact()) + if(chartsData[_chartsIndex].value!.length > 1) _HistoryChartThigy(data: selectedGraph, smooth: _smooth, yAxisTitle: _historyShortTitles[_chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(), xFormat: NumberFormat.compact()) else if (chartsData[_chartsIndex].value!.length <= 1) Center(child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -903,9 +841,16 @@ class _History extends StatelessWidget{ } } +class _HistoryChartSpot{ + final DateTime timestamp; + final int gamesPlayed; + final String rank; + final double stat; + const _HistoryChartSpot(this.timestamp, this.gamesPlayed, this.rank, this.stat); +} + class _HistoryChartThigy extends StatefulWidget{ - final List data; - final List? smoothData; + final List<_HistoryChartSpot> data; final bool smooth; final String yAxisTitle; final bool bigScreen; @@ -916,7 +861,7 @@ class _HistoryChartThigy extends StatefulWidget{ /// Implements graph for the _History widget. Requires [data] which is a list of dots for the graph. [yAxisTitle] used to keep track of changes. /// [bigScreen] tells if screen wide enough, [leftSpace] sets size, reserved for titles on the left from the graph and [yFormat] sets number format /// for left titles - const _HistoryChartThigy({required this.data, this.smoothData, required this.smooth, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat, this.xFormat}); + const _HistoryChartThigy({required this.data, required this.smooth, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat, this.xFormat}); @override State<_HistoryChartThigy> createState() => _HistoryChartThigyState(); @@ -925,282 +870,94 @@ class _HistoryChartThigy extends StatefulWidget{ class _HistoryChartThigyState extends State<_HistoryChartThigy> { late String previousAxisTitle; late bool previousGamesPlayedInsteadOfDateAndTime; - late double minX; - late double maxX; - late double minY; - late double actualMinY; - late double maxY; - late double actualMaxY; - late double xScale; - late double yScale; - String headerTooltip = t.pseudoTooltipHeaderInit; - String footerTooltip = t.pseudoTooltipFooterInit; - int hoveredPointId = -1; - double scaleFactor = 5e2; - double dragFactor = 7e2; + late TooltipBehavior _tooltipBehavior; + @override void initState(){ super.initState(); - minX = widget.data.first.x; - maxX = widget.data.last.x; - setMinMaxY(); + _tooltipBehavior = TooltipBehavior( + color: Colors.black, + borderColor: Colors.white, + enable: true, + animationDuration: 0, + builder: (dynamic data, dynamic point, dynamic series, + int pointIndex, int seriesIndex) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text( + "${f4.format(data.stat)} ${widget.yAxisTitle}", + style: TextStyle(fontFamily: "Eurostile Round", fontSize: 20), + ), + ), + Text(_gamesPlayedInsteadOfDateAndTime ? "${f0.format(data.gamesPlayed)} games played" : _dateFormat.format(data.timestamp)) + ], + ), + ); + } + ); previousAxisTitle = widget.yAxisTitle; previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime; - actualMaxY = maxY; - actualMinY = minY; - recalculateScales(); } @override void dispose(){ super.dispose(); - actualMinY = 0; - minY = 0; } - /// Calculates and assignes maximum and minimum values in list of dots - void setMinMaxY(){ - actualMinY = widget.data.reduce((value, element){ - num n = min(value.y, element.y); - if (value.y == n) { - return value; - } else { - return element; - } - }).y; - actualMaxY = widget.data.reduce((value, element){ - num n = max(value.y, element.y); - if (value.y == n) { - return value; - } else { - return element; - } - }).y; - minY = actualMinY; - maxY = actualMaxY; - } - - /// Calculates and assignes scales, which is difference between maximum and minimum visible axis value - void recalculateScales(){ - xScale = maxX - minX; - yScale = maxY - minY; - } - - /// Accepts [dragUpdDet] and changes minX, maxX, minY, maxY based on that - void dragHandler(DragUpdateDetails dragUpdDet){ - setState(() { - // Changing min and max values according to drag delta and considering scales - minX -= (xScale / dragFactor) * dragUpdDet.delta.dx; - maxX -= (xScale / dragFactor) * dragUpdDet.delta.dx; - minY += (yScale / dragFactor) * dragUpdDet.delta.dy; - maxY += (yScale / dragFactor) * dragUpdDet.delta.dy; - - // If values are out of bounds - putting them back - if (minX < widget.data.first.x) { - minX = widget.data.first.x; - maxX = widget.data.first.x + xScale; - } - if (maxX > widget.data.last.x) { - maxX = widget.data.last.x; - minX = maxX - xScale; - } - if(minY < actualMinY){ - minY = actualMinY; - maxY = actualMinY + yScale; - } - if(maxY > actualMaxY){ - maxY = actualMaxY; - minY = actualMaxY - yScale; - } - }); - } - - /// Accepts scale [details] and changes minX, maxX, minY, maxY in a way to change xScale and yScale. - /// [graphKey] required for sizes calculations, as well, as [graphStartX] and [graphEndX]. - /// Not used yet, because GestureDetector works like shit - void scaleHandler(ScaleUpdateDetails details, GlobalKey> graphKey, double graphStartX, double graphEndX){ - RenderBox graphBox = graphKey.currentContext?.findRenderObject() as RenderBox; - - // calculating relative position of scale gesture - Offset graphPosition = graphBox.localToGlobal(Offset.zero); - // 0 - very left position of graph; 1 - very right position of graph - double gesturePosRelativeX = (details.focalPoint.dx - graphStartX) / (graphEndX - graphStartX); - // 0 - very top position of graph; 1 - very bottom position of graph - double gesturePosRelativeY = (details.focalPoint.dy - graphPosition.dy) / (graphBox.size.height - 30); // size - bottom titles height - - double newMinX, newMaxX, newMinY, newMaxY; // calcutating new values based on gesture and considering scales - newMinX = minX - (xScale / scaleFactor) * (details.horizontalScale-1) * gesturePosRelativeX; - newMaxX = maxX + (xScale / scaleFactor) * (details.horizontalScale-1) * (1-gesturePosRelativeX); - newMinY = minY - (yScale / scaleFactor) * (details.horizontalScale-1) * (1-gesturePosRelativeY); - newMaxY = maxY + (yScale / scaleFactor) * (details.horizontalScale-1) * gesturePosRelativeY; - - // cancel changes if minimum is more, than maximun - if ((newMaxX - newMinX).isNegative) return; - if ((newMaxY - newMinY).isNegative) return; - - // apply changes if everything ok + can't go past boundaries - setState(() { - minX = max(newMinX, widget.data.first.x); - maxX = min(newMaxX, widget.data.last.x); - minY = max(newMinY, actualMinY); - maxY = min(newMaxY, actualMaxY); - recalculateScales(); - }); - } - @override Widget build(BuildContext context) { - GlobalKey graphKey = GlobalKey(); if ((previousAxisTitle != widget.yAxisTitle) || (previousGamesPlayedInsteadOfDateAndTime != _gamesPlayedInsteadOfDateAndTime)) { - minX = widget.data.first.x; - maxX = widget.data.last.x; - recalculateScales(); - setMinMaxY(); previousAxisTitle = widget.yAxisTitle; previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime; setState((){}); } - double xInterval = widget.bigScreen ? max(1, xScale / 8) : max(1, xScale / 4); // how far away xTitles should be between each other EdgeInsets padding = widget.bigScreen ? const EdgeInsets.fromLTRB(40, 30, 40, 30) : const EdgeInsets.fromLTRB(0, 40, 16, 48); - double graphStartX = padding.left+widget.leftSpace; - double graphEndX = MediaQuery.sizeOf(context).width - padding.right; return SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height - 104, + child: Padding( padding: padding, child: Listener( behavior: HitTestBehavior.translucent, onPointerSignal: (signal) { - if (signal is PointerScrollEvent) { - RenderBox graphBox = graphKey.currentContext?.findRenderObject() as RenderBox; - - // calculating relative position of pointer - Offset graphPosition = graphBox.localToGlobal(Offset.zero); - // 0 - very left position of graph; 1 - very right position of graph - double scrollPosRelativeX = (signal.position.dx - graphStartX) / (graphEndX - graphStartX); - // 0 - very top position of graph; 1 - very bottom position of graph - double scrollPosRelativeY = (signal.position.dy - graphPosition.dy) / (graphBox.size.height - 30); // size - bottom titles height - - double newMinX, newMaxX, newMinY, newMaxY; // calcutating new values based on pointer position and considering scales - newMinX = minX - (xScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeX; - newMaxX = maxX + (xScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeX); - newMinY = minY - (yScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeY); - newMaxY = maxY + (yScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeY; - - // cancel changes if minimum is more, than maximun - if ((newMaxX - newMinX).isNegative) return; - if ((newMaxY - newMinY).isNegative) return; - - // apply changes if everything ok + can't go past boundaries + if (signal is PointerScrollEvent) { setState(() { - minX = max(newMinX, widget.data.first.x); - maxX = min(newMaxX, widget.data.last.x); - minY = max(newMinY, actualMinY); - maxY = min(newMaxY, actualMaxY); - recalculateScales(); _scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy); // TODO: find a better way to stop scrolling in NestedScrollView }); } }, - child: - GestureDetector( - behavior: HitTestBehavior.translucent, - onDoubleTap: () { - setState(() { - minX = widget.data.first.x; - maxX = widget.data.last.x; - minY = actualMinY; - maxY = actualMaxY; - recalculateScales(); - }); - }, - // TODO: onScaleUpdate:(details) => scaleHandler(details, graphKey, graphStartX, graphEndX), - // TODO: Figure out wtf is going on with gestures - // TODO: Somehow highlight touched spot (handleBuiltInTouches breaks getTooltipItems and getTouchedSpotIndicator) - child: Padding( padding: padding, - child: Stack( - children: [ - LineChart( - key: graphKey, - curve: Curves.elasticInOut, - LineChartData( - lineBarsData: [ - LineChartBarData( - show: !_smooth, - spots: widget.data, - dotData: FlDotData(show: false), - isCurved: true, - curveSmoothness: 0.35, - preventCurveOverShooting: true - ), - if (widget.smoothData != null) LineChartBarData( - show: _smooth, - spots: widget.smoothData!, - dotData: FlDotData(show: false), - isCurved: true, - curveSmoothness: 0.35, - preventCurveOverShooting: true, - ) - ], - clipData: const FlClipData.all(), - borderData: FlBorderData(show: false), - gridData: FlGridData(verticalInterval: xInterval), - minX: minX, - maxX: maxX, - minY: minY, - maxY: maxY, - titlesData: FlTitlesData(topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), - rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), - bottomTitles: AxisTitles(sideTitles: SideTitles(interval: xInterval, showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){ - return value != meta.min && value != meta.max ? SideTitleWidget( - axisSide: meta.axisSide, - child: Text(widget.xFormat != null && _gamesPlayedInsteadOfDateAndTime ? widget.xFormat!.format(value.round()) : DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).format(DateTime.fromMillisecondsSinceEpoch(value.floor()))), - ) : Container(); - })), - leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: widget.leftSpace, getTitlesWidget: (double value, TitleMeta meta){ - return value != meta.min && value != meta.max ? SideTitleWidget( - axisSide: meta.axisSide, - child: Text(widget.yFormat.format(value)), - ) : Container(); - }))), - lineTouchData: LineTouchData( - handleBuiltInTouches: false, - touchCallback:(touchEvent, touchResponse) { - if (touchEvent is FlPanUpdateEvent){ - dragHandler(touchEvent.details); - return; - } - if (touchEvent is FlPointerHoverEvent){ - setState(() { - if (touchResponse?.lineBarSpots?.first == null) { - hoveredPointId = -1; // not hovering over any point - } else { - hoveredPointId = touchResponse!.lineBarSpots!.first.spotIndex; - headerTooltip = "${f4.format(touchResponse.lineBarSpots!.first.y)} ${widget.yAxisTitle}"; - footerTooltip = _gamesPlayedInsteadOfDateAndTime ? "${f0.format(touchResponse.lineBarSpots!.first.x)} games played" : _dateFormat.format(DateTime.fromMillisecondsSinceEpoch(touchResponse.lineBarSpots!.first.x.floor())); - } - }); - } - if (touchEvent is FlPointerExitEvent){ - setState(() {hoveredPointId = -1;}); - } - }, - ) - ) - ), - Padding( - padding: EdgeInsets.only(left: widget.leftSpace), - child: Column( - children: [ - AnimatedDefaultTextStyle(style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 24, color: Color.fromARGB(hoveredPointId == -1 ? 100 : 255, 255, 255, 255), shadows: hoveredPointId != -1 ? textShadow : null), duration: Durations.medium1, curve: Curves.elasticInOut, child: Text(headerTooltip)), - AnimatedDefaultTextStyle(style: TextStyle(fontFamily: "Eurostile Round", color: Color.fromARGB(hoveredPointId == -1 ? 100 : 255, 255, 255, 255), shadows: hoveredPointId != -1 ? textShadow : null), duration: Durations.medium1, curve: Curves.elasticInOut, child: Text(footerTooltip)), - ], - ), - ) - ], - ), - ), + child: SfCartesianChart( + tooltipBehavior: _tooltipBehavior, + zoomPanBehavior: _zoomPanBehavior, + primaryXAxis: DateTimeAxis(), + primaryYAxis: NumericAxis( + rangePadding: ChartRangePadding.additional, ), + series: [ + StepLineSeries<_HistoryChartSpot, DateTime>( + enableTooltip: true, + // splineType: SplineType.cardinal, + // cardinalSplineTension: 0.2, + dataSource: widget.data, + animationDuration: 0, + opacity: 1, + xValueMapper: (_HistoryChartSpot data, _) => data.timestamp, + yValueMapper: (_HistoryChartSpot data, _) => data.stat, + // trendlines:[ + // Trendline( + // period: (widget.data.length/175).floor(), + // type: TrendlineType.movingAverage, + // color: Colors.blue) + // ], + ), + ], + ), + ), ) ); } diff --git a/lib/views/rank_averages_view.dart b/lib/views/rank_averages_view.dart index 12cfde1..aab2a18 100644 --- a/lib/views/rank_averages_view.dart +++ b/lib/views/rank_averages_view.dart @@ -1,7 +1,5 @@ import 'dart:io'; import 'dart:math'; -import 'dart:ui'; -import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -12,12 +10,12 @@ import 'package:tetra_stats/views/main_view.dart' show MainView; import 'package:tetra_stats/utils/text_shadow.dart'; import 'package:window_manager/window_manager.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/sparkcharts.dart'; var _chartsShortTitlesDropdowns = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value),)]; Stats _chartsX = Stats.tr; Stats _chartsY = Stats.apm; late TooltipBehavior _tooltipBehavior; +late ZoomPanBehavior _zoomPanBehavior; List _itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))]; List<_MyScatterSpot> _spots = []; Stats _sortBy = Stats.tr; @@ -61,7 +59,15 @@ class RankState extends State with SingleTickerProviderStateMixin { void initState() { _scrollController = ScrollController(); _tabController = TabController(length: 6, vsync: this); + _zoomPanBehavior = ZoomPanBehavior( + enablePinching: true, + enableSelectionZooming: true, + enableMouseWheelZooming : true, + enablePanning: true, + ); _tooltipBehavior = TooltipBehavior( + color: Colors.black, + borderColor: Colors.white, enable: true, animationDuration: 0, builder: (dynamic data, dynamic point, dynamic series, @@ -75,13 +81,10 @@ class RankState extends State with SingleTickerProviderStateMixin { padding: const EdgeInsets.only(bottom: 8.0), child: Text( "${data.nickname} (${data.rank.toUpperCase()})", - style: TextStyle(color: Colors.black, fontFamily: "Eurostile Round Extended", fontSize: 20), + style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 20), ), ), - Text( - '${_f4.format(data.x)} ${chartsShortTitles[_chartsX]}\n${_f4.format(data.y)} ${chartsShortTitles[_chartsY]}', - style: TextStyle(color: Colors.black), - ), + Text('${_f4.format(data.x)} ${chartsShortTitles[_chartsX]}\n${_f4.format(data.y)} ${chartsShortTitles[_chartsY]}') ], ), ); @@ -95,43 +98,6 @@ class RankState extends State with SingleTickerProviderStateMixin { previousAxisTitles = _chartsX.toString()+_chartsY.toString(); they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country); createSpots(); - recalculateBoundaries(); - resetScale(); - } - - void recalculateBoundaries(){ - actualMinX = (widget.rank[1]["entries"] as List).reduce((value, element) { - num n = min(value.getStatByEnum(_chartsX), element.getStatByEnum(_chartsX)); - if (value.getStatByEnum(_chartsX) == n) { - return value; - } else { - return element; - } - }).getStatByEnum(_chartsX).toDouble(); - actualMaxX = (widget.rank[1]["entries"] as List).reduce((value, element) { - num n = max(value.getStatByEnum(_chartsX), element.getStatByEnum(_chartsX)); - if (value.getStatByEnum(_chartsX) == n) { - return value; - } else { - return element; - } - }).getStatByEnum(_chartsX).toDouble(); - actualMinY = (widget.rank[1]["entries"] as List).reduce((value, element) { - num n = min(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY)); - if (value.getStatByEnum(_chartsY) == n) { - return value; - } else { - return element; - } - }).getStatByEnum(_chartsY).toDouble(); - actualMaxY = (widget.rank[1]["entries"] as List).reduce((value, element) { - num n = max(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY)); - if (value.getStatByEnum(_chartsY) == n) { - return value; - } else { - return element; - } - }).getStatByEnum(_chartsY).toDouble(); } void createSpots(){ @@ -148,19 +114,6 @@ class RankState extends State with SingleTickerProviderStateMixin { ) ]; } - - void resetScale(){ - maxX = actualMaxX; - minX = actualMinX; - maxY = actualMaxY; - minY = actualMinY; - recalculateScales(); - } - - void recalculateScales(){ - xScale = maxX - minX; - yScale = maxY - minY; - } @override void dispose() { @@ -170,47 +123,15 @@ class RankState extends State with SingleTickerProviderStateMixin { super.dispose(); } - void dragHandler(DragUpdateDetails dragUpdDet){ - setState(() { - minX -= (xScale / dragFactor) * dragUpdDet.delta.dx; - maxX -= (xScale / dragFactor) * dragUpdDet.delta.dx; - minY += (yScale / dragFactor) * dragUpdDet.delta.dy; - maxY += (yScale / dragFactor) * dragUpdDet.delta.dy; - - if (minX < actualMinX) { - minX = actualMinX; - maxX = actualMinX + xScale; - } - if (maxX > actualMaxX) { - maxX = actualMaxX; - minX = maxX - xScale; - } - if(minY < actualMinY){ - minY = actualMinY; - maxY = actualMinY + yScale; - } - if(maxY > actualMaxY){ - maxY = actualMaxY; - minY = actualMaxY - yScale; - } - }); - } - void _justUpdate() { setState(() {}); } @override Widget build(BuildContext context) { - GlobalKey graphKey = GlobalKey(); bool bigScreen = MediaQuery.of(context).size.width > 768; - EdgeInsets padding = bigScreen ? const EdgeInsets.fromLTRB(40, 40, 40, 48) : const EdgeInsets.fromLTRB(0, 40, 16, 48); - double graphStartX = padding.left; - double graphEndX = MediaQuery.sizeOf(context).width - padding.right; if (previousAxisTitles != _chartsX.toString()+_chartsY.toString()){ createSpots(); - recalculateBoundaries(); - resetScale(); previousAxisTitles = _chartsX.toString()+_chartsY.toString(); } final t = Translations.of(context); @@ -278,6 +199,7 @@ class RankState extends State with SingleTickerProviderStateMixin { Wrap( direction: Axis.horizontal, alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.end, spacing: 25, children: [ Column( @@ -319,63 +241,39 @@ class RankState extends State with SingleTickerProviderStateMixin { ), ], ), + IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: Icon(Icons.refresh), alignment: Alignment.center,) ], ), if (widget.rank[1]["entries"].length > 1) SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height - 104, - child: Listener( - behavior: HitTestBehavior.translucent, - onPointerSignal: (signal) { - if (signal is PointerScrollEvent) { - RenderBox graphBox = graphKey.currentContext?.findRenderObject() as RenderBox; - Offset graphPosition = graphBox.localToGlobal(Offset.zero); - double scrollPosRelativeX = (signal.position.dx - graphStartX) / (graphEndX - graphStartX); - double scrollPosRelativeY = (signal.position.dy - graphPosition.dy) / (graphBox.size.height - 30); // size - bottom titles height - double newMinX, newMaxX, newMinY, newMaxY; - newMinX = minX - (xScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeX; - newMaxX = maxX + (xScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeX); - newMinY = minY - (yScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeY); - newMaxY = maxY + (yScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeY; - if ((newMaxX - newMinX).isNegative) return; - if ((newMaxY - newMinY).isNegative) return; - setState(() { - minX = max(newMinX, actualMinX); - maxX = min(newMaxX, actualMaxX); - minY = max(newMinY, actualMinY); - maxY = min(newMaxY, actualMaxY); - recalculateScales(); - _scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy); - }); - }}, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onDoubleTap: () { + child: Padding( + padding: bigScreen ? const EdgeInsets.fromLTRB(40, 10, 40, 20) : const EdgeInsets.fromLTRB(0, 10, 16, 20), + child: Listener( + behavior: HitTestBehavior.translucent, + onPointerSignal: (signal) { + if (signal is PointerScrollEvent) { setState(() { - minX = actualMinX; - maxX = actualMaxX; - minY = actualMinY; - maxY = actualMaxY; - recalculateScales(); + _scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy); // TODO: find a better way to stop scrolling in NestedScrollView }); - }, - child: Padding( - padding: bigScreen ? const EdgeInsets.fromLTRB(40, 40, 40, 48) : const EdgeInsets.fromLTRB(0, 40, 16, 48), - child: SfCartesianChart( - tooltipBehavior: _tooltipBehavior, - //primaryXAxis: CategoryAxis(), - series: [ - ScatterSeries( - enableTooltip: true, - dataSource: _spots, - animationDuration: 0, - pointColorMapper: (data, _) => data.color, - xValueMapper: (data, _) => data.x, - yValueMapper: (data, _) => data.y - ) - ], - ), + } + }, + child: SfCartesianChart( + tooltipBehavior: _tooltipBehavior, + zoomPanBehavior: _zoomPanBehavior, + //primaryXAxis: CategoryAxis(), + series: [ + ScatterSeries( + enableTooltip: true, + dataSource: _spots, + animationDuration: 0, + pointColorMapper: (data, _) => data.color, + xValueMapper: (data, _) => data.x, + yValueMapper: (data, _) => data.y, + onPointTap: (point) => Navigator.push(context, MaterialPageRoute(builder: (context) => MainView(player: _spots[point.pointIndex!].nickname), maintainState: false)), + ) + ], ), ), )) diff --git a/pubspec.lock b/pubspec.lock index b4e8059..3b52e11 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -145,14 +145,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.16.4+3" - equatable: - dependency: transitive - description: - name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 - url: "https://pub.dev" - source: hosted - version: "2.0.5" fake_async: dependency: transitive description: @@ -249,14 +241,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+1" - fl_chart: - dependency: "direct main" - description: - name: fl_chart - sha256: "00b74ae680df6b1135bdbea00a7d1fc072a9180b7c3f3702e4b19a9943f5ed7d" - url: "https://pub.dev" - source: hosted - version: "0.66.2" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 79ace8e..7923569 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,7 +27,6 @@ dependencies: sqflite_common_ffi_web: '>=0.1.0-dev.1' path_provider: ^2.0.15 path: ^1.8.2 - fl_chart: ^0.66.0 package_info_plus: ^5.0.1 shared_preferences: ^2.1.1 intl: ^0.18.0