diff --git a/Cargo.lock b/Cargo.lock index ab6c230..39df4f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -252,7 +258,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -311,6 +317,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" + [[package]] name = "byteorder" version = "1.5.0" @@ -379,6 +391,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colored" version = "2.1.0" @@ -499,6 +517,42 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types 0.5.0", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.13" @@ -523,6 +577,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -659,6 +722,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -670,6 +754,15 @@ dependencies = [ "syn 2.0.75", ] +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -682,6 +775,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" +[[package]] +name = "dwrote" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + [[package]] name = "either" version = "1.13.0" @@ -768,6 +873,31 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fdeflate" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.0", +] + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + [[package]] name = "flume" version = "0.11.0" @@ -785,13 +915,59 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "font-kit" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64b34f4efd515f905952d91bc185039863705592c0c53ae6d979805dd154520" +dependencies = [ + "bitflags 2.6.0", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.75", ] [[package]] @@ -800,6 +976,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -809,6 +991,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "futures" version = "0.3.30" @@ -932,6 +1125,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.29.0" @@ -1344,6 +1547,20 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-traits", + "png", +] + [[package]] name = "indexmap" version = "2.4.0" @@ -1387,6 +1604,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.70" @@ -1644,12 +1867,32 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + [[package]] name = "libsqlite3-sys" version = "0.27.0" @@ -1797,6 +2040,8 @@ dependencies = [ "minesweeper-lib", "nanoid", "oauth2", + "plotters", + "plotters-canvas", "regex", "reqwest 0.12.7", "serde", @@ -1832,6 +2077,16 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.0.2" @@ -2026,7 +2281,7 @@ checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", "cfg-if", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", "openssl-macros", @@ -2062,6 +2317,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "or_poisoned" version = "0.1.0" @@ -2114,6 +2375,25 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf07ef4804cfa9aea3b04a7bbdd5a40031dbb6b4f2cbaf2b011666c80c5b4f2" +dependencies = [ + "rustc_version", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2188,6 +2468,77 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "chrono", + "font-kit", + "image", + "lazy_static", + "num-traits", + "pathfinder_geometry", + "plotters-backend", + "plotters-bitmap", + "plotters-svg", + "ttf-parser", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-bitmap" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ce181e3f6bf82d6c1dc569103ca7b1bd964c60ba03d7e6cdfbb3e3eb7f7405" +dependencies = [ + "gif", + "image", + "plotters-backend", +] + +[[package]] +name = "plotters-canvas" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498a82bf654581fa4df6c1bd73e56f8556bdeec5eca5a0989d170ca0728ccace" +dependencies = [ + "js-sys", + "plotters-backend", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2390,6 +2741,17 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.6" @@ -2602,6 +2964,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.34" @@ -2755,6 +3126,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "send_wrapper" version = "0.6.0" @@ -2962,6 +3339,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "simple_logger" version = "5.0.0" @@ -3824,6 +4207,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + [[package]] name = "tungstenite" version = "0.21.0" @@ -4131,6 +4520,12 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "whoami" version = "1.5.1" @@ -4378,6 +4773,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + [[package]] name = "xxhash-rust" version = "0.8.12" @@ -4390,6 +4794,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/web/Cargo.toml b/web/Cargo.toml index 9f08eea..b988793 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -47,6 +47,8 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = tr wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ["WebSocket", "Performance"] } getrandom = { version = "0.2", features = ["js"] } +plotters = "0.3.7" +plotters-canvas = "0.3.0" [features] hydrate = ["leptos/hydrate", "leptos/nightly"] diff --git a/web/src/app/profile/stats.rs b/web/src/app/profile/stats.rs index 7a488c0..78a6043 100644 --- a/web/src/app/profile/stats.rs +++ b/web/src/app/profile/stats.rs @@ -1,6 +1,9 @@ use std::collections::VecDeque; +use anyhow::{bail, Result}; use leptos::prelude::*; +use plotters::prelude::*; +use plotters_canvas::CanvasBackend; use serde::{Deserialize, Serialize}; use super::GameMode; @@ -98,7 +101,8 @@ pub async fn get_timeline_stats() -> Result #[component] pub fn PlayerStatsTable() -> impl IntoView { - let td_class = "border border-slate-100 dark:border-slate-700 py-1 px-2"; + let td_class = + "border border-slate-100 dark:border-slate-700 py-1 px-2 text-gray-900 dark:text-gray-200"; let header_class = "border dark:border-slate-600 font-medium p-4 text-gray-900 dark:text-gray-200 bg-neutral-500/50"; let player_stats = Resource::new(|| (), move |_| async { get_player_stats().await }); @@ -169,72 +173,146 @@ pub fn PlayerStatsTable() -> impl IntoView { } } -#[derive(Clone, Copy, Debug)] -struct GraphPoint { - games: usize, - winrate: f64, - speed: f64, +fn draw_chart(mode: GameMode, stats: &Vec<(bool, i64)>) -> Result<()> { + let len = stats.len(); + let (_, _, speed_series, winrate_series) = stats.iter().enumerate().fold( + ( + VecDeque::with_capacity(10), + VecDeque::with_capacity(10), + Vec::with_capacity(len), + Vec::with_capacity(len), + ), + |(mut speed_acc, mut wr_acc, mut speed_series, mut wr_series), (i, (v, s))| { + if wr_acc.len() == 10 { + wr_acc.pop_front(); + } + wr_acc.push_back(*v); + if *v { + if speed_acc.len() == 10 { + speed_acc.pop_front(); + } + speed_acc.push_back(*s); + } + let ave_wr = + wr_acc.iter().copied().filter(|b| *b).count() as f64 / wr_acc.len() as f64 * 100.0; + let ave_time = + speed_acc.iter().map(|x| *x as f64).sum::() / speed_acc.len() as f64; + speed_series.push((i, ave_time)); + wr_series.push((i, ave_wr)); + (speed_acc, wr_acc, speed_series, wr_series) + }, + ); + + let max = speed_series + .iter() + .map(|(_, y)| *y) + .max_by(|a, b| a.total_cmp(b)) + .expect("Should be able to find max"); + let canvas = match mode { + GameMode::ClassicBeginner => "beginner_stats", + GameMode::ClassicIntermediate => "intermediate_stats", + GameMode::ClassicExpert => "expert_stats", + _ => bail!("Mode is not Classic"), + }; + + let backend = CanvasBackend::new(canvas).expect("canvas should exist"); + let root = backend.into_drawing_area(); + let largefont: FontDesc = ("sans-serif", 20.0).into(); + let small_font: FontDesc = ("sans-serif", 14.0).into(); + let tiny_font: FontDesc = ("sans-serif", 12.0).into(); + + root.fill(&WHITE)?; + + let root = root.titled(&format!("{} Stats", mode.short_name()), largefont)?; + + let mut chart = ChartBuilder::on(&root) + .margin(2) + .caption("10 game moving average", small_font.clone()) + .x_label_area_size(35) + .y_label_area_size(40) + .right_y_label_area_size(40) + .build_cartesian_2d(0usize..len, 0.0..max + 5.0)? + .set_secondary_coord(0usize..len - 1, 0.0..100.0); + + let drop_decimal_places = |x: &f64| format!("{:.0}", x); + + chart + .configure_mesh() + .max_light_lines(1) + .disable_x_mesh() + .x_labels(20.min(len)) + .x_desc("Games") + .y_labels(10) + .y_desc("Seconds") + .y_label_formatter(&drop_decimal_places) + .y_label_offset(-10) + .axis_desc_style(small_font.clone()) + .y_label_style(tiny_font.clone()) + .draw()?; + + chart + .configure_secondary_axes() + .y_labels(10) + .y_desc("Winrate") + .y_label_formatter(&drop_decimal_places) + .y_label_offset(-10) + .axis_desc_style(small_font) + .label_style(tiny_font) + .draw()?; + + let speed_style = ShapeStyle { + color: RED.into(), + filled: false, + stroke_width: 4, + }; + chart + .draw_series(LineSeries::new(speed_series.into_iter(), speed_style).point_size(1))? + .label("Time") + .legend(|(x, y)| PathElement::new(vec![(x, y - 5), (x + 20, y - 5)], RED)); + + let time_style = ShapeStyle { + color: BLUE.into(), + filled: false, + stroke_width: 4, + }; + chart + .draw_secondary_series( + LineSeries::new(winrate_series.into_iter(), time_style).point_size(1), + )? + .label("Winrate") + .legend(|(x, y)| PathElement::new(vec![(x, y - 5), (x + 20, y - 5)], BLUE)); + + chart + .configure_series_labels() + .position(SeriesLabelPosition::LowerLeft) + .background_style(RGBAColor(128, 128, 128, 0.4)) + .draw()?; + + root.present()?; + Ok(()) } #[component] pub fn TimelineStatsGraphs() -> impl IntoView { let timeline_stats = Resource::new(|| (), move |_| async { get_timeline_stats().await }); - let mode_view = move |mode: GameMode, stats: Vec<(bool, i64)>| { - let len = stats.len(); - // TODO - when this is > 100, chunk it up - let (_, _, collections) = stats.into_iter().enumerate().fold( - ( - VecDeque::with_capacity(10), - VecDeque::with_capacity(10), - Vec::with_capacity(len), - ), - |(mut speed_acc, mut wr_acc, mut collections), (i, (v, s))| { - if wr_acc.len() == 10 { - wr_acc.pop_front(); - } - wr_acc.push_back(v); - if v { - if speed_acc.len() == 10 { - speed_acc.pop_front(); - } - speed_acc.push_back(s); - } - let ave_wr = wr_acc.iter().copied().filter(|b| *b).count() as f64 - / wr_acc.len() as f64 - * 100.0; - let ave_time = - speed_acc.iter().map(|x| *x as f64).sum::() / speed_acc.len() as f64; - collections.push(GraphPoint { - games: i, - winrate: ave_wr, - speed: ave_time, - }); - (speed_acc, wr_acc, collections) - }, - ); - - view! { -
{mode.short_name()}": "
-
{format!("{:?}", collections)}
- } - }; + Effect::watch( + move || timeline_stats.get(), + |tstats, _, _| { + log::debug!("{:?}", tstats); + let stats = if let Some(Ok(stats)) = tstats { + stats + } else { + return; + }; + if let Err(e) = draw_chart(GameMode::ClassicBeginner, &stats.beginner) { + log::debug!("Unable to draw chart: {}", e); + }; + }, + true, + ); view! { - - {move || Suspend::new(async move { - let stats = timeline_stats.await; - stats - .map(|stats| { - view! { - <> - {mode_view(GameMode::ClassicBeginner, stats.beginner)} - {mode_view(GameMode::ClassicIntermediate, stats.intermediate)} - {mode_view(GameMode::ClassicExpert, stats.expert)} - - } - }) - })} - + } }