added graph generation
This commit is contained in:
parent
334f6bcc14
commit
377a1c0f94
4 changed files with 3238 additions and 63 deletions
2988
Cargo.lock
generated
2988
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,8 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
charming = { version = "0.6.0", features = ["ssr", "ssr-raster"] }
|
||||||
chrono = "0.4.42"
|
chrono = "0.4.42"
|
||||||
|
indexmap = { version = "2.13.0", features = ["serde"] }
|
||||||
serde = {version = "1.0.228", features = ["derive"]}
|
serde = {version = "1.0.228", features = ["derive"]}
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ pub struct Stat {
|
||||||
pub player_count: i32,
|
pub player_count: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn import() -> HashMap<String, HashMap<String, Stat>> {
|
pub fn import(obj_mode: bool, target_month: i16) -> HashMap<String, HashMap<String, Stat>> {
|
||||||
let chatdatafile = "./data/rawdata.json";
|
let chatdatafile = "./data/rawdata.json";
|
||||||
let jsonstring = fs::read_to_string(chatdatafile).unwrap();
|
let jsonstring = fs::read_to_string(chatdatafile).unwrap();
|
||||||
let jsondata: ChatData = serde_json::from_str(&jsonstring).unwrap();
|
let jsondata: ChatData = serde_json::from_str(&jsonstring).unwrap();
|
||||||
|
|
@ -45,13 +45,13 @@ pub fn import() -> HashMap<String, HashMap<String, Stat>> {
|
||||||
for message in &jsondata.messages {
|
for message in &jsondata.messages {
|
||||||
if message.author["id"] == MAPSTAT_AUTHOR_ID {
|
if message.author["id"] == MAPSTAT_AUTHOR_ID {
|
||||||
let timestamp = DateTime::parse_from_rfc3339(&message.timestamp).unwrap();
|
let timestamp = DateTime::parse_from_rfc3339(&message.timestamp).unwrap();
|
||||||
|
|
||||||
|
|
||||||
if timestamp.year().to_string().as_str() != TARGET_YEAR {
|
if timestamp.year().to_string().as_str() != TARGET_YEAR || timestamp.month() != target_month as u32 {
|
||||||
// println!("year is {}, skipping", timestamp.year());
|
// println!("year and month is {} - {}, skipping", timestamp.year(), timestamp.month());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut map_name = "".to_string();
|
let mut map_name = "".to_string();
|
||||||
|
|
||||||
let mut stat = Stat {
|
let mut stat = Stat {
|
||||||
won: false,
|
won: false,
|
||||||
timestamp: timestamp.to_string(),
|
timestamp: timestamp.to_string(),
|
||||||
|
|
@ -76,12 +76,23 @@ pub fn import() -> HashMap<String, HashMap<String, Stat>> {
|
||||||
&"Server" => {
|
&"Server" => {
|
||||||
// println!("{}", embed_value["value"]);
|
// println!("{}", embed_value["value"]);
|
||||||
if embed_value["value"].as_str().unwrap() != SERVER {
|
if embed_value["value"].as_str().unwrap() != SERVER {
|
||||||
println!("server is: {} - skipping", embed_value["value"]);
|
// println!("server is: {} - skipping", embed_value["value"]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&"Map" => {
|
&"Map" => {
|
||||||
let map_string = &embed_value["value"].to_string().replace('"', "");
|
let map_string = &embed_value["value"].to_string().replace('"', "");
|
||||||
|
if obj_mode {
|
||||||
|
if !map_string.as_str().starts_with("zs_obj") {
|
||||||
|
// println!("[OBJ MODE] map is not obj, skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if map_string.as_str().starts_with("zs_obj") {
|
||||||
|
// println!("[STANDARD MODE] map is obj, skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
map_name = map_string.clone();
|
map_name = map_string.clone();
|
||||||
}
|
}
|
||||||
&"Players" => {
|
&"Players" => {
|
||||||
|
|
|
||||||
288
src/main.rs
288
src/main.rs
|
|
@ -2,10 +2,15 @@ use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::{File, create_dir_all},
|
fs::{File, create_dir_all},
|
||||||
io::Write,
|
io::Write,
|
||||||
|
vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use charming::{
|
||||||
|
Chart, ImageFormat, ImageRenderer, component::{Axis, Grid, Legend, Title}, datatype::DataPointItem, df, element::{AxisType, BackgroundStyle, ItemStyle, Label, LabelPosition}, series::Bar, theme::Theme
|
||||||
|
};
|
||||||
|
use chrono::Month;
|
||||||
|
use indexmap::IndexMap;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
mod importer;
|
mod importer;
|
||||||
|
|
||||||
|
|
@ -27,9 +32,8 @@ struct Stats {
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct OverallStats {
|
struct OverallStats {
|
||||||
standard: Stats,
|
standard: IndexMap<String, Stats>,
|
||||||
obj: Stats,
|
obj: IndexMap<String, Stats>,
|
||||||
monthly: HashMap<String, Stats>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAPSTAT_AUTHOR_ID: &str = "879429830628753429";
|
const MAPSTAT_AUTHOR_ID: &str = "879429830628753429";
|
||||||
|
|
@ -42,67 +46,143 @@ fn main() {
|
||||||
//check if directory exists and creates it
|
//check if directory exists and creates it
|
||||||
create_dir_all(RESULT_PATH).expect("Failed to create directory...");
|
create_dir_all(RESULT_PATH).expect("Failed to create directory...");
|
||||||
|
|
||||||
let map_stats = importer::import();
|
// let mut processedstats: HashMap<String, HashMap<String, Stats>> = HashMap::new();
|
||||||
|
let mut standardstats: OverallStats = OverallStats {
|
||||||
let processed_data_path = RESULT_PATH.to_owned() + "alldata.json";
|
standard: IndexMap::new(),
|
||||||
|
obj: IndexMap::new(),
|
||||||
match serde_json::to_string(&map_stats) {
|
|
||||||
Ok(json) => {
|
|
||||||
let mut file = File::create(processed_data_path).expect("Unable to write file...");
|
|
||||||
file.write_all(json.as_bytes())
|
|
||||||
.expect("Failed to write data...");
|
|
||||||
}
|
|
||||||
Err(e) => println!("Serialization error: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
// let most_played = sortplaycount(map_stats.clone());
|
|
||||||
|
|
||||||
let mut most_played = vec![];
|
|
||||||
let mut highest_playercount = vec![];
|
|
||||||
let mut most_wins = vec![];
|
|
||||||
let mut most_lost = vec![];
|
|
||||||
let mut most_human = vec![];
|
|
||||||
|
|
||||||
for (map, stats) in map_stats.iter() {
|
|
||||||
most_played.push((map.clone(), stats.len()));
|
|
||||||
let mut wins = vec![];
|
|
||||||
let mut lost = vec![];
|
|
||||||
for (id, stat) in stats.iter() {
|
|
||||||
highest_playercount.push((map.clone(), stat.timestamp.clone(), stat.player_count));
|
|
||||||
if stat.won {
|
|
||||||
wins.push(id);
|
|
||||||
} else {
|
|
||||||
lost.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
most_wins.push((map.clone(), wins.len()));
|
|
||||||
most_lost.push((map.clone(), lost.len()));
|
|
||||||
let win_ratio: f32 = wins.len() as f32 / stats.len() as f32;
|
|
||||||
most_human.push((map.clone(), win_ratio));
|
|
||||||
}
|
|
||||||
|
|
||||||
most_played.sort_by(|a, b| b.1.cmp(&a.1));
|
|
||||||
highest_playercount.sort_by(|a, b| b.2.cmp(&a.2));
|
|
||||||
most_wins.sort_by(|a, b| b.1.cmp(&a.1));
|
|
||||||
most_lost.sort_by(|a, b| b.1.cmp(&a.1));
|
|
||||||
most_human.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
|
||||||
|
|
||||||
let report: Stats = Stats {
|
|
||||||
most_played,
|
|
||||||
most_players: highest_playercount,
|
|
||||||
most_wins: most_wins,
|
|
||||||
most_lost: most_lost,
|
|
||||||
most_human_sided: most_human,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match serde_json::to_string(&report) {
|
for obj in 1..=2 {
|
||||||
|
println!("map type - {}", obj);
|
||||||
|
for month in 1..=12 {
|
||||||
|
println!("month - {}", month);
|
||||||
|
let obj_mode: bool = if obj == 1 { true } else { false };
|
||||||
|
let map_stats = importer::import(obj_mode, month);
|
||||||
|
|
||||||
|
let mut most_played = vec![];
|
||||||
|
let mut highest_playercount = vec![];
|
||||||
|
let mut most_wins = vec![];
|
||||||
|
let mut most_lost = vec![];
|
||||||
|
let mut most_human = vec![];
|
||||||
|
|
||||||
|
for (map, stats) in map_stats.iter() {
|
||||||
|
most_played.push((map.clone(), stats.len()));
|
||||||
|
let mut wins = vec![];
|
||||||
|
let mut lost = vec![];
|
||||||
|
for (id, stat) in stats.iter() {
|
||||||
|
highest_playercount.push((
|
||||||
|
map.clone(),
|
||||||
|
stat.timestamp.clone(),
|
||||||
|
stat.player_count,
|
||||||
|
));
|
||||||
|
if stat.won {
|
||||||
|
wins.push(id);
|
||||||
|
} else {
|
||||||
|
lost.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
most_wins.push((map.clone(), wins.len()));
|
||||||
|
most_lost.push((map.clone(), lost.len()));
|
||||||
|
let win_ratio: f32 = wins.len() as f32 / stats.len() as f32;
|
||||||
|
most_human.push((map.clone(), win_ratio));
|
||||||
|
}
|
||||||
|
|
||||||
|
most_played.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
highest_playercount.sort_by(|a, b| b.2.cmp(&a.2));
|
||||||
|
most_wins.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
most_lost.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
most_human.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||||
|
|
||||||
|
let report: Stats = Stats {
|
||||||
|
most_played,
|
||||||
|
most_players: highest_playercount,
|
||||||
|
most_wins: most_wins,
|
||||||
|
most_lost: most_lost,
|
||||||
|
most_human_sided: most_human,
|
||||||
|
};
|
||||||
|
|
||||||
|
let monthname = Month::try_from(month as u8).unwrap().name();
|
||||||
|
|
||||||
|
if obj_mode {
|
||||||
|
standardstats.obj.insert(monthname.to_string(), report);
|
||||||
|
} else {
|
||||||
|
standardstats.standard.insert(monthname.to_string(), report);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match serde_json::to_string(&standardstats) {
|
||||||
Ok(json) => {
|
Ok(json) => {
|
||||||
let mut file = File::create(RESULT_PATH.to_owned() + "report.json").expect("Unable to write file...");
|
let mut file = File::create(RESULT_PATH.to_owned() + "report.json")
|
||||||
|
.expect("Unable to write file...");
|
||||||
file.write_all(json.as_bytes())
|
file.write_all(json.as_bytes())
|
||||||
.expect("Failed to write data...");
|
.expect("Failed to write data...");
|
||||||
}
|
}
|
||||||
Err(e) => println!("Serialization error: {}", e),
|
Err(e) => println!("Serialization error: {}", e),
|
||||||
}
|
}
|
||||||
|
// let map_stats = importer::import();
|
||||||
|
|
||||||
|
// let processed_data_path = RESULT_PATH.to_owned() + "alldata.json";
|
||||||
|
|
||||||
|
// match serde_json::to_string(&map_stats) {
|
||||||
|
// Ok(json) => {
|
||||||
|
// let mut file = File::create(processed_data_path).expect("Unable to write file...");
|
||||||
|
// file.write_all(json.as_bytes())
|
||||||
|
// .expect("Failed to write data...");
|
||||||
|
// }
|
||||||
|
// Err(e) => println!("Serialization error: {}", e),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // let most_played = sortplaycount(map_stats.clone());
|
||||||
|
|
||||||
|
// let mut most_played = vec![];
|
||||||
|
// let mut highest_playercount = vec![];
|
||||||
|
// let mut most_wins = vec![];
|
||||||
|
// let mut most_lost = vec![];
|
||||||
|
// let mut most_human = vec![];
|
||||||
|
|
||||||
|
// for (map, stats) in map_stats.iter() {
|
||||||
|
// most_played.push((map.clone(), stats.len()));
|
||||||
|
// let mut wins = vec![];
|
||||||
|
// let mut lost = vec![];
|
||||||
|
// for (id, stat) in stats.iter() {
|
||||||
|
// highest_playercount.push((map.clone(), stat.timestamp.clone(), stat.player_count));
|
||||||
|
// if stat.won {
|
||||||
|
// wins.push(id);
|
||||||
|
// } else {
|
||||||
|
// lost.push(id);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// most_wins.push((map.clone(), wins.len()));
|
||||||
|
// most_lost.push((map.clone(), lost.len()));
|
||||||
|
// let win_ratio: f32 = wins.len() as f32 / stats.len() as f32;
|
||||||
|
// most_human.push((map.clone(), win_ratio));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// most_played.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
// highest_playercount.sort_by(|a, b| b.2.cmp(&a.2));
|
||||||
|
// most_wins.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
// most_lost.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
// most_human.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||||
|
|
||||||
|
// let report: Stats = Stats {
|
||||||
|
// most_played,
|
||||||
|
// most_players: highest_playercount,
|
||||||
|
// most_wins: most_wins,
|
||||||
|
// most_lost: most_lost,
|
||||||
|
// most_human_sided: most_human,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// match serde_json::to_string(&report) {
|
||||||
|
// Ok(json) => {
|
||||||
|
// let mut file = File::create(RESULT_PATH.to_owned() + "report.json")
|
||||||
|
// .expect("Unable to write file...");
|
||||||
|
// file.write_all(json.as_bytes())
|
||||||
|
// .expect("Failed to write data...");
|
||||||
|
// }
|
||||||
|
// Err(e) => println!("Serialization error: {}", e),
|
||||||
|
// }
|
||||||
|
|
||||||
// for (map, playcount) in most_played.iter() {
|
// for (map, playcount) in most_played.iter() {
|
||||||
// println!("map: {} - played {} times", map, playcount)
|
// println!("map: {} - played {} times", map, playcount)
|
||||||
|
|
@ -117,4 +197,98 @@ fn main() {
|
||||||
// for (map, time, playercount) in highest_playercount {
|
// for (map, time, playercount) in highest_playercount {
|
||||||
// println!("map: {} - player count: {} @ {}", map, playercount, time)
|
// println!("map: {} - player count: {} @ {}", map, playercount, time)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
let mut months: Vec<String> = vec![];
|
||||||
|
let mut wins: Vec<i32> = vec![];
|
||||||
|
let mut lost: Vec<i32> = vec![];
|
||||||
|
|
||||||
|
let mut totalwins: i32 = 0;
|
||||||
|
let mut totallost: i32 = 0;
|
||||||
|
|
||||||
|
for (monthname, data) in standardstats.obj {
|
||||||
|
println!("{}", monthname.clone());
|
||||||
|
months.push(monthname);
|
||||||
|
let mut totalmonthwins = 0;
|
||||||
|
let mut totalmonthlost = 0;
|
||||||
|
|
||||||
|
for (_map, wins) in data.most_wins.iter() {
|
||||||
|
totalmonthwins += wins
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_map, lost) in data.most_lost.iter() {
|
||||||
|
totalmonthlost += lost
|
||||||
|
}
|
||||||
|
|
||||||
|
wins.push(totalmonthwins.try_into().unwrap());
|
||||||
|
lost.push(totalmonthlost.try_into().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
for win in wins.iter() {
|
||||||
|
totalwins += win
|
||||||
|
};
|
||||||
|
for lost in lost.iter() {
|
||||||
|
totallost += lost
|
||||||
|
};
|
||||||
|
|
||||||
|
let monthobjchart = Chart::new()
|
||||||
|
.title(Title::new().text("2025 ZZ Monthly OBJ Stats"))
|
||||||
|
.legend(Legend::new())
|
||||||
|
.grid(
|
||||||
|
Grid::new()
|
||||||
|
.left("3%")
|
||||||
|
.right("4%")
|
||||||
|
.bottom("3%")
|
||||||
|
.contain_label(true),
|
||||||
|
)
|
||||||
|
.x_axis(Axis::new().type_(AxisType::Category).data(months))
|
||||||
|
.y_axis(
|
||||||
|
Axis::new()
|
||||||
|
.type_(AxisType::Value)
|
||||||
|
.boundary_gap(("0", "0.01")),
|
||||||
|
).series(
|
||||||
|
Bar::new()
|
||||||
|
.name("Won")
|
||||||
|
.item_style(ItemStyle::new().color("#96f06e"))
|
||||||
|
.label(Label::new().show(true).position(LabelPosition::Inside))
|
||||||
|
.data(wins),
|
||||||
|
)
|
||||||
|
.series(
|
||||||
|
Bar::new()
|
||||||
|
.name("Lost")
|
||||||
|
.item_style(ItemStyle::new().color("#fa6982"))
|
||||||
|
.label(Label::new().show(true).position(LabelPosition::Inside))
|
||||||
|
.data(lost),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut renderer = ImageRenderer::new(1600, 800).theme(Theme::Dark);
|
||||||
|
renderer.save_format(ImageFormat::Png, &monthobjchart, RESULT_PATH.to_owned() + "monthly_obj_stats.png").expect("Error rendering image...");
|
||||||
|
|
||||||
|
let totalobjchart = Chart::new()
|
||||||
|
.title(Title::new().text("2025 ZZ Total OBJ Stats"))
|
||||||
|
.grid(
|
||||||
|
Grid::new()
|
||||||
|
.left("3%")
|
||||||
|
.right("4%")
|
||||||
|
.bottom("3%")
|
||||||
|
.contain_label(true),
|
||||||
|
)
|
||||||
|
.x_axis(
|
||||||
|
Axis::new()
|
||||||
|
.type_(AxisType::Category)
|
||||||
|
.data(vec!["Won", "Lost"]))
|
||||||
|
.y_axis(
|
||||||
|
Axis::new()
|
||||||
|
.type_(AxisType::Value)
|
||||||
|
.boundary_gap(("0", "0.01")),
|
||||||
|
).series(
|
||||||
|
Bar::new()
|
||||||
|
.label(Label::new().show(true).position(LabelPosition::Inside))
|
||||||
|
.data(df![
|
||||||
|
DataPointItem::new(totalwins).item_style(ItemStyle::new().color("#96f06e")),
|
||||||
|
DataPointItem::new(totallost).item_style(ItemStyle::new().color("#fa6982"))
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut renderer = ImageRenderer::new(800, 800).theme(Theme::Dark);
|
||||||
|
renderer.save_format(ImageFormat::Png, &totalobjchart, RESULT_PATH.to_owned() + "total_obj_stats.png").expect("Error rendering image...");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue