Releasing the CN locale + redesign graphs

This commit is contained in:
dan63047 2024-09-12 23:28:55 +03:00
parent d0a6946ccf
commit 28f0d0ad7a
7 changed files with 417 additions and 208 deletions

View File

@ -151,6 +151,69 @@ class TetraLeague {
pps ?? 0,
vs ?? 0,
decaying);
num? getStatByEnum(Stats stat){
switch (stat) {
case Stats.tr:
return tr;
case Stats.glicko:
return glicko;
case Stats.gxe:
return gxe;
case Stats.s1tr:
return s1tr;
case Stats.rd:
return rd;
case Stats.gp:
return gamesPlayed;
case Stats.gw:
return gamesWon;
case Stats.wr:
return winrate*100;
case Stats.apm:
return apm;
case Stats.pps:
return pps;
case Stats.vs:
return vs;
case Stats.app:
return nerdStats?.app;
case Stats.dss:
return nerdStats?.dss;
case Stats.dsp:
return nerdStats?.dsp;
case Stats.appdsp:
return nerdStats?.appdsp;
case Stats.vsapm:
return nerdStats?.vsapm;
case Stats.cheese:
return nerdStats?.cheese;
case Stats.gbe:
return nerdStats?.gbe;
case Stats.nyaapp:
return nerdStats?.nyaapp;
case Stats.area:
return nerdStats?.area;
case Stats.eTR:
return estTr?.esttr;
case Stats.acceTR:
return esttracc;
case Stats.acceTRabs:
return esttracc?.abs();
case Stats.opener:
return playstyle?.opener;
case Stats.plonk:
return playstyle?.plonk;
case Stats.infDS:
return playstyle?.infds;
case Stats.stride:
return playstyle?.stride;
case Stats.stridemMinusPlonk:
return (playstyle?.stride??0.00) - (playstyle?.plonk??0.00);
case Stats.openerMinusInfDS:
return (playstyle?.opener??0.00) - (playstyle?.infds??0.00);
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};

View File

@ -6,7 +6,7 @@
/// Locales: 3
/// Strings: 1818 (606 per locale)
///
/// Built on 2024-09-11 at 14:14 UTC
/// Built on 2024-09-12 at 20:23 UTC
// coverage:ignore-file
// ignore_for_file: type=lint
@ -1620,7 +1620,7 @@ class _StringsZhCn implements Translations {
many: '只有 ${n} 个记录',
other: '只有 ${n} 个记录',
);
@override String get noRecord => '只有 个记录';
@override String get noRecord => '没有记录';
@override String get botRecord => '机器人不予参加排位赛';
@override String get anonRecord => '匿名用户不予参加排位赛';
@override String get notEnoughData => '没有足够的数据';
@ -3580,7 +3580,7 @@ extension on _StringsZhCn {
many: '只有 ${n} 个记录',
other: '只有 ${n} 个记录',
);
case 'noRecord': return ({required Object n}) => '只有 ${n}记录';
case 'noRecord': return '没有记录';
case 'botRecord': return '机器人不予参加排位赛';
case 'anonRecord': return '匿名用户不予参加排位赛';
case 'notEnoughData': return '没有足够的数据';

View File

@ -16,7 +16,7 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:tetra_stats/views/main_view_tiles.dart';
import 'package:tetra_stats/views/main_view.dart';
import 'package:tetra_stats/views/settings_view.dart';
import 'package:tetra_stats/views/tracked_players_view.dart';
import 'package:tetra_stats/views/calc_view.dart';

View File

@ -546,6 +546,61 @@ class TetrioService extends DB {
}
}
Future<List<Cutoffs>> fetchCutoffsHistory() async {
Uri url = Uri.https('ts.dan63.by', 'beanserver_blaster/history.csv');
try{
final response = await client.get(url);
switch (response.statusCode) {
case 200:
List<List<dynamic>> csv = const CsvToListConverter().convert(response.body)..removeAt(0);
List<Cutoffs> history = [];
for (List<dynamic> entry in csv){
Map<String, double> tr = {};
Map<String, double> glicko = {};
Map<String, double> gxe = {};
for(int i = 0; i < ranks.length; i++){
tr[ranks[ranks.length + i - ranks.length]] = entry[1 + i*3];
glicko[ranks[ranks.length + i - ranks.length]] = entry[2 + i*3];
glicko[ranks[ranks.length + i - ranks.length]] = entry[3 + i*3];
}
history.add(
Cutoffs(
DateTime.fromMillisecondsSinceEpoch(entry[0]),
tr,
glicko,
gxe
)
);
}
return history;
case 404:
developer.log("fetchCutoffsHistory: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
return [];
// if not 200 or 404 - throw a unique for each code exception
case 403:
throw P1nkl0bst3rForbidden();
case 429:
throw P1nkl0bst3rTooManyRequests();
case 418:
throw TetrioOskwareBridgeProblem();
case 500:
case 502:
case 503:
case 504:
developer.log("fetchCutoffsHistory: Cutoffs are unavalable (${response.statusCode})", name: "services/tetrio_crud", error: response.statusCode);
return [];
default:
developer.log("fetchCutoffsHistory: Failed to fetch top Cutoffs", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
}
} on http.ClientException catch (e, s) { // If local http client fails
developer.log("$e, $s");
throw http.ClientException(e.message, e.uri); // just assuming, that our end user don't have acess to the internet
}
}
Future<TetrioPlayerFromLeaderboard> fetchTopOneFromTheLeaderboard() async {
TetrioPlayerFromLeaderboard? cached = _cache.get("topone", TetrioPlayerFromLeaderboard);
if (cached != null) return cached;

View File

@ -23,6 +23,8 @@ import 'package:tetra_stats/data_objects/tetra_league.dart';
import 'package:tetra_stats/data_objects/tetra_league_beta_stream.dart';
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
import 'package:tetra_stats/data_objects/tetrio_player.dart';
import 'package:tetra_stats/data_objects/tetrio_player_from_leaderboard.dart';
import 'package:tetra_stats/data_objects/tetrio_players_leaderboard.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/services/crud_exceptions.dart';
import 'package:tetra_stats/utils/colors_functions.dart';
@ -289,19 +291,22 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
bool fetchData = false;
bool _gamesPlayedInsteadOfDateAndTime = false;
late ZoomPanBehavior _zoomPanBehavior;
late TooltipBehavior _historyTooltipBehavior;
late TooltipBehavior _tooltipBehavior;
String yAxisTitle = "";
bool _smooth = false;
final 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"];
//final 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"];
final List<DropdownMenuItem<Stats>> _yAxis = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
Graph _graph = Graph.history;
int _chartsIndex = 0;
Stats _Ychart = Stats.tr;
Stats _Xchart = Stats.tr;
int _season = currentSeason-1;
late List<List<DropdownMenuItem<List<_HistoryChartSpot>>>> historyData;
//late List<List<DropdownMenuItem<List<_HistoryChartSpot>>>> historyData;
//Duration postSeasonLeft = seasonStart.difference(DateTime.now());
@override
void initState(){
_tooltipBehavior = TooltipBehavior(
_historyTooltipBehavior = TooltipBehavior(
color: Colors.black,
borderColor: Colors.white,
enable: true,
@ -326,6 +331,31 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
);
}
);
_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(
"${data.nickname} (${data.rank.toUpperCase()})",
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 20),
),
),
Text('${f4.format(data.x)} ${chartsShortTitles[_Xchart]}\n${f4.format(data.y)} ${chartsShortTitles[_Ychart]}')
],
),
);
}
);
_zoomPanBehavior = ZoomPanBehavior(
enablePinching: true,
enableSelectionZooming: true,
@ -335,7 +365,7 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
super.initState();
}
Future<List<List<DropdownMenuItem<List<_HistoryChartSpot>>>>> getHistoryData(bool fetchHistory) async {
Future<List<Map<Stats, List<_HistoryChartSpot>>>> getHistoryData(bool fetchHistory) async {
if(fetchHistory){
try{
var history = await teto.fetchAndsaveTLHistory(widget.searchFor);
@ -354,217 +384,261 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
List<List<TetraLeague>> states = await Future.wait<List<TetraLeague>>([
teto.getStates(widget.searchFor, season: 1), teto.getStates(widget.searchFor, season: 2),
]);
if (states.length >= 2){
historyData = [for (List<TetraLeague> s in states) <DropdownMenuItem<List<_HistoryChartSpot>>>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid
DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.tr)], child: Text(t.statCellNum.tr)),
DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.glicko!)], child: const Text("Glicko")),
DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.rd!)], child: const Text("Rating Deviation")),
DropdownMenuItem(value: [for (var tl in s) 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 s) 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 s) 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 s) 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 s) 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 s) 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 s) 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 s) 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 s) 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 s) 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 s) 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 s) 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 s) 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 s) 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 s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.opener)], child: const Text("Opener")),
DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.plonk)], child: const Text("Plonk")),
DropdownMenuItem(value: [for (var tl in s) 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 s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.stride)], child: const Text("Stride")),
]];
}else{
historyData = [];
List<Map<Stats, List<_HistoryChartSpot>>> historyData = []; // [season][metric][spot]
for (int season = 0; season < currentSeason; season++){
if (states[season].length >= 2){
Map<Stats, List<_HistoryChartSpot>> statsMap = {};
for (var stat in Stats.values) statsMap[stat] = [for (var tl in states[season]) if (tl.getStatByEnum(stat) != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.getStatByEnum(stat)!.toDouble())];
historyData.add(statsMap);
}else{
historyData.add({});
break;
}
}
fetchData = false;
return historyData;
}
Future<List<_MyScatterSpot>> getTetraLeagueData(Stats x, Stats y) async {
TetrioPlayersLeaderboard leaderboard = await teto.fetchTLLeaderboard();
List<_MyScatterSpot> _spots = [
for (TetrioPlayerFromLeaderboard entry in leaderboard.leaderboard)
_MyScatterSpot(
entry.getStatByEnum(x).toDouble(),
entry.getStatByEnum(y).toDouble(),
entry.userId,
entry.username,
entry.rank,
rankColors[entry.rank]??Colors.white
)
];
return _spots;
}
Widget getHistoryGraph(){
return FutureBuilder<List<Map<Stats, List<_HistoryChartSpot>>>>(
future: getHistoryData(fetchData),
builder: (context, snapshot) {
switch (snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasData && snapshot.data!.isNotEmpty){
List<_HistoryChartSpot> selectedGraph = snapshot.data![_season][_Ychart]!;
yAxisTitle = chartsShortTitles[_Ychart]!;
return SfCartesianChart(
tooltipBehavior: _historyTooltipBehavior,
zoomPanBehavior: _zoomPanBehavior,
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? const NumericAxis() : const DateTimeAxis(),
primaryYAxis: const NumericAxis(
rangePadding: ChartRangePadding.additional,
),
margin: const EdgeInsets.all(0),
series: <CartesianSeries>[
if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>(
enableTooltip: true,
dataSource: selectedGraph,
animationDuration: 0,
opacity: _smooth ? 0 : 1,
xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed,
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
color: Theme.of(context).colorScheme.primary,
trendlines:<Trendline>[
Trendline(
isVisible: _smooth,
period: (selectedGraph.length/175).floor(),
type: TrendlineType.movingAverage,
color: Theme.of(context).colorScheme.primary)
],
)
else StepLineSeries<_HistoryChartSpot, DateTime>(
enableTooltip: true,
dataSource: selectedGraph,
animationDuration: 0,
opacity: _smooth ? 0 : 1,
xValueMapper: (_HistoryChartSpot data, _) => data.timestamp,
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
color: Theme.of(context).colorScheme.primary,
trendlines:<Trendline>[
Trendline(
isVisible: _smooth,
period: (selectedGraph.length/175).floor(),
type: TrendlineType.movingAverage,
color: Theme.of(context).colorScheme.primary)
],
),
],
);
}else{
return Center(child:
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(snapshot.stackTrace.toString(), textAlign: TextAlign.center),
),
],
)
);
}
}
}
);
}
Widget getLeagueState (){
return FutureBuilder<List<_MyScatterSpot>>(
future: getTetraLeagueData(_Xchart, _Ychart),
builder: (context, snapshot) {
switch (snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasData){
return SfCartesianChart(
tooltipBehavior: _tooltipBehavior,
zoomPanBehavior: _zoomPanBehavior,
//primaryXAxis: CategoryAxis(),
series: [
ScatterSeries(
enableTooltip: true,
dataSource: snapshot.data,
animationDuration: 0,
pointColorMapper: (data, _) => data.color,
xValueMapper: (data, _) => data.x,
yValueMapper: (data, _) => data.y,
onPointTap: (point) => Navigator.push(context, MaterialPageRoute(builder: (context) => MainView(player: snapshot.data![point.pointIndex!].nickname), maintainState: false)),
)
],
);
}else{
return Center(child:
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(snapshot.stackTrace.toString(), textAlign: TextAlign.center),
),
],
)
);
}
}
}
);
}
Widget getCutoffsHistory(){
return Container(); // TODO
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
FutureBuilder<List<List<DropdownMenuItem<List<_HistoryChartSpot>>>>>(
future: getHistoryData(fetchData),
builder: (context, snapshot) {
switch (snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasData && snapshot.data!.isNotEmpty){
List<_HistoryChartSpot> selectedGraph = snapshot.data![_season][_chartsIndex].value!;
yAxisTitle = _historyShortTitles[_chartsIndex];
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
child: Wrap(
spacing: 20,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Padding(padding: EdgeInsets.all(8.0), child: Text("Season:", style: TextStyle(fontSize: 22))),
DropdownButton(
items: [for (int i = 1; i <= currentSeason; i++) DropdownMenuItem(value: i-1, child: Text("$i"))],
value: _season,
onChanged: (value) {
setState(() {
_season = value!;
});
}
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Padding(padding: EdgeInsets.all(8.0), child: Text("X:", style: TextStyle(fontSize: 22))),
DropdownButton(
items: const [DropdownMenuItem(value: false, child: Text("Date & Time")), DropdownMenuItem(value: true, child: Text("Games Played"))],
value: _gamesPlayedInsteadOfDateAndTime,
onChanged: (value) {
setState(() {
_gamesPlayedInsteadOfDateAndTime = value!;
});
}
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Padding(padding: EdgeInsets.all(8.0), child: Text("Y:", style: TextStyle(fontSize: 22))),
DropdownButton(
items: historyData[_season],
value: historyData[_season][_chartsIndex].value,
onChanged: (value) {
setState(() {
_chartsIndex = historyData[_season].indexWhere((element) => element.value == value);
});
}
),
],
),
if (selectedGraph.length > 300) Row(
mainAxisSize: MainAxisSize.min,
children: [
Checkbox(value: _smooth,
checkColor: Colors.black,
onChanged: ((value) {
setState(() {
_smooth = value!;
});
})),
Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22))
],
),
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,)
],
),
),
if(historyData[_season][_chartsIndex].value!.length > 1) Card(
child: SizedBox(
width: MediaQuery.of(context).size.width - 88,
height: MediaQuery.of(context).size.height - 96,
child: Padding( padding: const EdgeInsets.fromLTRB(40, 30, 40, 30),
child: SfCartesianChart(
tooltipBehavior: _tooltipBehavior,
zoomPanBehavior: _zoomPanBehavior,
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? const NumericAxis() : const DateTimeAxis(),
primaryYAxis: const NumericAxis(
rangePadding: ChartRangePadding.additional,
),
margin: const EdgeInsets.all(0),
series: <CartesianSeries>[
if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>(
enableTooltip: true,
dataSource: historyData[_season][_chartsIndex].value!,
animationDuration: 0,
opacity: _smooth ? 0 : 1,
xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed,
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
color: Theme.of(context).colorScheme.primary,
trendlines:<Trendline>[
Trendline(
isVisible: _smooth,
period: (historyData[_season][_chartsIndex].value!.length/175).floor(),
type: TrendlineType.movingAverage,
color: Theme.of(context).colorScheme.primary)
],
)
else StepLineSeries<_HistoryChartSpot, DateTime>(
enableTooltip: true,
dataSource: historyData[_season][_chartsIndex].value!,
animationDuration: 0,
opacity: _smooth ? 0 : 1,
xValueMapper: (_HistoryChartSpot data, _) => data.timestamp,
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
color: Theme.of(context).colorScheme.primary,
trendlines:<Trendline>[
Trendline(
isVisible: _smooth,
period: (historyData[_season][_chartsIndex].value!.length/175).floor(),
type: TrendlineType.movingAverage,
color: Theme.of(context).colorScheme.primary)
],
),
],
),
)
),
)
else if (historyData[_season][_chartsIndex].value!.length <= 1) Center(child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)),
Text(t.errors.actionSuggestion),
TextButton(onPressed: (){setState(() {
fetchData = true;
});}, child: Text(t.fetchAndsaveTLHistory))
],
))
],
),
);
}
if (snapshot.hasError || snapshot.data!.isEmpty){
return Center(child:
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
child: Wrap(
spacing: 20,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Text(snapshot.error != null ? snapshot.error.toString() : t.noHistorySaved, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(snapshot.stackTrace != null ? snapshot.stackTrace.toString() : "lol", textAlign: TextAlign.center),
if (_graph == Graph.history) Row(
mainAxisSize: MainAxisSize.min,
children: [
const Padding(padding: EdgeInsets.all(8.0), child: Text("Season:", style: TextStyle(fontSize: 22))),
DropdownButton(
items: [for (int i = 1; i <= currentSeason; i++) DropdownMenuItem(value: i-1, child: Text("$i"))],
value: _season,
onChanged: (value) {
setState(() {
_season = value!;
});
}
),
],
),
if (_graph != Graph.leagueCutoffs) Row(
mainAxisSize: MainAxisSize.min,
children: [
const Padding(padding: EdgeInsets.all(8.0), child: Text("X:", style: TextStyle(fontSize: 22))),
DropdownButton(
items: switch (_graph){
Graph.history => [DropdownMenuItem(value: false, child: Text("Date & Time")), DropdownMenuItem(value: true, child: Text("Games Played"))],
Graph.leagueState => _yAxis,
Graph.leagueCutoffs => [],
},
value: _graph == Graph.history ? _gamesPlayedInsteadOfDateAndTime : _Xchart,
onChanged: (value) {
setState(() {
if (_graph == Graph.history)
_gamesPlayedInsteadOfDateAndTime = value! as bool;
else _Xchart = value! as Stats;
});
}
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Padding(padding: EdgeInsets.all(8.0), child: Text("Y:", style: TextStyle(fontSize: 22))),
DropdownButton<Stats>(
items: _yAxis,
value: _Ychart,
onChanged: (value) {
setState(() {
_Ychart = value!;
});
}
),
],
),
if (_graph != Graph.leagueState) Row(
mainAxisSize: MainAxisSize.min,
children: [
Checkbox(value: _smooth,
checkColor: Colors.black,
onChanged: ((value) {
setState(() {
_smooth = value!;
});
})),
Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22))
],
),
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,)
],
)
);
}
}
return const Center(child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("lol", style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28)),
],
));
},
),
),
Card(
child: SizedBox(
width: MediaQuery.of(context).size.width - 88,
height: MediaQuery.of(context).size.height - 96,
child: Padding( padding: const EdgeInsets.fromLTRB(40, 30, 40, 30),
child: switch (_graph){
Graph.history => getHistoryGraph(),
Graph.leagueState => getLeagueState(),
Graph.leagueCutoffs => getCutoffsHistory()
},
)
),
)
],
),
),
SegmentedButton<Graph>(
showSelectedIcon: false,
@ -584,6 +658,13 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
onSelectionChanged: (Set<Graph> newSelection) {
setState(() {
_graph = newSelection.first;
switch (newSelection.first){
case Graph.leagueCutoffs:
case Graph.history:
_Ychart = Stats.tr;
case Graph.leagueState:
_Ychart = Stats.apm;
}
});})
],
);
@ -598,6 +679,16 @@ class _HistoryChartSpot{
const _HistoryChartSpot(this.timestamp, this.gamesPlayed, this.rank, this.stat);
}
class _MyScatterSpot{
num x;
num y;
String id;
String nickname;
String rank;
Color color;
_MyScatterSpot(this.x, this.y, this.id, this.nickname, this.rank, this.color);
}
class DestinationHome extends StatefulWidget{
final String searchFor;
//final Function setState;

View File

@ -11,7 +11,7 @@ import 'package:tetra_stats/views/main_view.dart' show MainView;
import 'package:window_manager/window_manager.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
var _chartsShortTitlesDropdowns = <DropdownMenuItem>[for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value),)];
var _chartsShortTitlesDropdowns = <DropdownMenuItem>[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;

View File

@ -2,7 +2,7 @@ name: tetra_stats
description: Track your and other player stats in TETR.IO
publish_to: 'none'
version: 1.6.10+36
version: 1.6.11+37
environment:
sdk: '>=3.0.0'