← Back to writing
Projects95 visits

MLBB Heroes Dashboard

A simple dashboard to explore Mobile Legends heroes, stats, and trends.

A simple dashboard to explore Mobile Legends heroes, stats, and trends.

This isn’t my first MLBB dashboard. Back in 2021, I made one in Tableau (here it is, for nostalgia).

Fast forward a few years: new heroes, new meta, and me wondering “who’s actually strong right now?” So I rebuilt it from scratch, this time with my own data pipeline and a web dashboard 🎮.

🔎 Overview

  • Goal: Make hero stats easy to explore without building a full backend.
  • Use cases: Compare heroes, spot popular picks, check ban rates, and explore roles.
  • Tech stack: Python, CSV, Vite, TypeScript, Papa Parse, and Chart.js.
  • What you get: Interactive charts, role/year/hero filters, KPIs, and rankings.
  • Data: A 132-hero CSV snapshot, not live game data.

🗺️ How Everything Connects

The nerdy view:

flowchart LR
    API["Hero List + Rank Data"] --> PY["Python Scraper"]
    OLD["2021 CSV Baseline"] --> PY
    PY --> CSV["Latest Hero CSV"]
    CSV --> WEB["Static Website"]
    APP["Vite + TypeScript"] --> WEB
    WEB --> USER["Browser"]
    USER --> PARSE["Parse"]
    PARSE --> CHART["Chart.js: Charts + Filters"]

The big-picture view:

flowchart LR
    A["Game Stats"] --> B["Python Collects Data"]
    B --> C["CSV File"]
    C --> D["Website"]
    D --> E["You Explore Charts"]

Easy way to remember: Python prepares the data. The website shows it. Your browser handles the filters.

🔨 From Game Stats to Charts

  1. Collect: Python grabs hero details and win/pick/ban stats.
  2. Combine: It matches everything up and fills some older fields from my 2021 CSV.
  3. Save: The result becomes mlbb_hero_latest.csv.
  4. Load: The website reads the CSV with Papa Parse.
  5. Explore: Chart.js draws the charts, and clicking filters updates them instantly.

Heads-up: Visitors never call the game’s data APIs. New stats mean a new CSV and a new deploy.

🕵️ The Scraper Is Sneakier Than It Looks

This is the part I underestimated. Getting the data was a little detective game 🔍.

  • No hardcoded hero list. The scraper (about 550 lines of Python, standard library only) starts from the game’s own bootstrap config, follows it to the dynamic config, and discovers the current versioned hero list URL from there. So when the game updates its file paths, the scraper still finds them.
  • Plan A and Plan B. Win, pick, and ban rates come from the official rank API (with pagination). If that’s unavailable, it falls back to the older rank CDN files.
  • Knobs for days. Command-line options let me choose the rank tier, match type, page size, and request timeout.
  • Old meets new. Hero names are normalized before matching, so the fresh data can merge cleanly with my 2021 baseline and keep the same column format.
  • Bye, fake heroes. The game data had obvious placeholder entries like “Test Swordsman” and “Reserved String” 😂. They’re filtered out by default (you can include them with a flag if you’re curious).
  • Tested. A few unit tests cover the scraper’s helper logic.

🤔 Decisions I Made (and What They Cost)

Area Current choice Why it’s nice Trade-off
Data collection Python scraper Easy to rerun Needs a refresh process
Data storage CSV file Simple and portable No built-in history
Frontend Vite + TypeScript Fast, lightweight Work happens in the browser
Charts Chart.js Interactive out of the box Less custom than D3
Hosting Static site No always-on backend Not live by default

📊 What’s in the Dashboard?

Feature What it tells you
Win / Pick / Ban stats How heroes perform and how often they’re chosen or banned
Bubble chart Pick rate vs. win rate, with ban pressure
Role & release charts Hero mix by role and known release year
Top picked / banned Quick hero rankings
Clickable filters Focus on a role, year, or specific hero

🔭 Okay, But How Do I Read It?

What you see How to read it
Summary cards Filtered Heroes is the count. Average Win/Pick shows up after you filter. Most Contested has the highest pick rate + ban rate.
Pick vs. Win bubbles Compare popularity, win rate, and ban pressure. See the corner guide below.
Role Share Bigger slice = more heroes in that role, not a stronger role.
Release Rhythm Taller stacked bar = more heroes released that year; colors separate roles.
Top Picked / Banned Longer bar = higher pick or ban rate. Popular doesn’t always mean powerful.
Meta Insights + Leaderboard Quick highlights and a combined impact score based on win (20%), pick (45%), and ban (35%).

🫧 The Bubble Chart, Explained

Each bubble is one hero. Its position shows popularity and performance, and its size shows how often it gets banned.

  • X-axis (Pick Rate): Further right = picked more often.
  • Y-axis (Win Rate): Higher up = wins more often.
  • Bubble size (Ban Rate): Bigger = banned more often.
Position What it means If the bubble is big…
↗ Top-right Popular and wins a lot. Likely a strong meta pick. Highly contested: picked often and banned often.
↖ Top-left Rarely picked but wins a lot. Could be a hidden gem or niche pick. Often banned despite fewer picks; bans might limit opportunities to pick it.
↘ Bottom-right Popular but wins less. Common pick, mixed results. Still attracts bans, possibly due to annoying matchups or reputation.
↙ Bottom-left Rarely picked and wins less. Usually a situational or lower-priority pick. May face targeted bans even if its overall results look weak.

Quick takeaway: a big bubble doesn’t automatically mean a strong hero. It just means it gets banned a lot (maybe everyone’s just tired of facing it, lol). Read all three stats together. And remember, corners are relative to the other heroes on screen, especially after filtering.

Try it: Click a role, year, or hero to filter the charts. See what changes, then hit Reset all filters to start over.

Keep in mind: A high win rate with only a few picks isn’t proof that a hero is the best. This is a data snapshot, not match-by-match analysis. Heroes with a missing release year may show up as 0.

💸 Cost & Performance Tips

Part Keep it simple by…
Hosting Using static hosting and CDN caching
CSV Compressing and caching the file
Charts Keeping browser rendering lightweight
Data updates Refreshing on a schedule instead of every visit
  • Cost advantage: No running server or database needed for the current dashboard.
  • Honest note: Actual monthly cost depends on hosting and traffic, and I don’t have billing data to share.

🔮 What’s Next?

Idea Change Good for
Auto refresh Schedule the Python script Keeping stats more current
Data history Save dated CSV snapshots Comparing trends over time
Quality checks Validate missing and unusual values More reliable charts
API + database Add a backend later Larger data and advanced queries

📝 Design Journal, in a Nutshell

  • Problem: Hero stats are scattered and hard to compare quickly.
  • Choice: Collect once with Python, publish as CSV, explore in a static dashboard.
  • Why it works: Simple setup, fast filtering, and fewer things to maintain.
  • Trade-off: It’s a snapshot, not real-time data.
  • Next improvement: Show missing release years as Unknown, display the real update date, and automate the CSV refresh.