added graph generation

This commit is contained in:
genki_angel 2026-03-05 01:13:02 +00:00
parent 334f6bcc14
commit 377a1c0f94
4 changed files with 3238 additions and 63 deletions

2988
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2024"
[dependencies]
charming = { version = "0.6.0", features = ["ssr", "ssr-raster"] }
chrono = "0.4.42"
indexmap = { version = "2.13.0", features = ["serde"] }
serde = {version = "1.0.228", features = ["derive"]}
serde_json = "1.0"

View file

@ -35,7 +35,7 @@ pub struct Stat {
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 jsonstring = fs::read_to_string(chatdatafile).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 {
if message.author["id"] == MAPSTAT_AUTHOR_ID {
let timestamp = DateTime::parse_from_rfc3339(&message.timestamp).unwrap();
if timestamp.year().to_string().as_str() != TARGET_YEAR {
// println!("year is {}, skipping", timestamp.year());
if timestamp.year().to_string().as_str() != TARGET_YEAR || timestamp.month() != target_month as u32 {
// println!("year and month is {} - {}, skipping", timestamp.year(), timestamp.month());
continue;
}
}
let mut map_name = "".to_string();
let mut stat = Stat {
won: false,
timestamp: timestamp.to_string(),
@ -76,12 +76,23 @@ pub fn import() -> HashMap<String, HashMap<String, Stat>> {
&"Server" => {
// println!("{}", embed_value["value"]);
if embed_value["value"].as_str().unwrap() != SERVER {
println!("server is: {} - skipping", embed_value["value"]);
// println!("server is: {} - skipping", embed_value["value"]);
continue;
}
}
&"Map" => {
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();
}
&"Players" => {

View file

@ -2,10 +2,15 @@ use std::{
collections::HashMap,
fs::{File, create_dir_all},
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_json::Value;
mod importer;
@ -27,9 +32,8 @@ struct Stats {
#[derive(Serialize)]
struct OverallStats {
standard: Stats,
obj: Stats,
monthly: HashMap<String, Stats>,
standard: IndexMap<String, Stats>,
obj: IndexMap<String, Stats>,
}
const MAPSTAT_AUTHOR_ID: &str = "879429830628753429";
@ -42,67 +46,143 @@ fn main() {
//check if directory exists and creates it
create_dir_all(RESULT_PATH).expect("Failed to create directory...");
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,
// let mut processedstats: HashMap<String, HashMap<String, Stats>> = HashMap::new();
let mut standardstats: OverallStats = OverallStats {
standard: IndexMap::new(),
obj: IndexMap::new(),
};
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) => {
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())
.expect("Failed to write data...");
}
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() {
// println!("map: {} - played {} times", map, playcount)
@ -117,4 +197,98 @@ fn main() {
// for (map, time, playercount) in highest_playercount {
// 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...");
}