Interactive challenges to master data visualization with ggplot2. Practice your R skills with real datasets in a browser-based environment - no installation required!
Visit ggplotbattles.dev to start battling!
ggplot battles presents you with target plots created with ggplot2. Your mission: recreate them as closely as possible using R code directly in your browser. Each challenge includes:
- Real datasets including penguins, mpg, and more
- Instant feedback with separate pixel and code similarity scores
- Browser-based R environment powered by webR
- Multiple difficulty levels for all skill ranges
We welcome contributions! Here's how to add a new challenge:
Create a new .R file in the challenges-code/ directory with the following structure:
#| title: "Your Challenge Title"
#| dataset-name: "dataset_name"
#| description: "Brief description of what the challenge teaches or focuses on"
#| colours: "none" # or describe color scheme if relevant
#| plot-variable: "p" # variable name that contains the final plot
library(ggplot2)
# Add other required libraries
# Load your dataset
data(your_dataset, package = "package_name")
# Your ggplot2 code here
p <- ggplot(your_dataset, aes(...)) +
geom_...() +
# ... rest of your plot code
# IMPORTANT: The plot must be printed!
print(p)Use these special comments at the top of your file:
#| title:- Display name for the challenge#| dataset-name:- Name of the dataset (shown to users)#| description:- What the challenge teaches/focuses on#| colours:- Color information (use "none" if not applicable)#| plot-variable:- Variable containing the final plot (optional, defaults tolast_plot())
- The plot must be printed - Either use
print(p)or justpon its own line - The file must be entirely self-contained - No external dependencies or files. It can be as long as you like, so feel free to include long data frames if needed.
- Set white backgrounds - Ensure plots have white backgrounds for consistency
After adding your challenge file, run:
source("printer.R")This will:
- Generate a PNG image of your plot in
challenges-images/ - Update the
manifest.jsonwith your challenge metadata
- Frontend: HTML, CSS, JavaScript with Bootstrap
- R Environment: webR for browser-based R
- Plotting: ggplot2 with various extension packages
- Comparison: Canvas-based pixel similarity plus syntax-tree overlap scoring for plot code
- Leaderboard: Vercel API route backed by Supabase Postgres
- Clone the repository
- Serve the files with a local web server (required for webR)
- For new challenges, run
printer.Rto regenerate images and manifest
# Example with Python
python -m http.server 8000
# Example with Node.js
npx http-serverThis repository also contains a procedural Archipelago world and browser client. The client builds independently from archipelago/ for deployment at ap.ggplotbattles.dev; the normal website remains at the repository root.
cd archipelago
npm test
npm run build
cd ../apworld
python -m unittest discover -s tests -vCopy apworld/ggplot_battles into an Archipelago source checkout's worlds/ directory and use the launcher's Build APWorlds component to create the distributable package. Install that package through the Archipelago launcher and use apworld/examples/ggplot_battles.yaml to generate a slot.
The leaderboard is intentionally account-free: users enter a display name, then the site stores the display name, score, date, challenge id, and submitted code.
- Create a Supabase project.
- Run
supabase/leaderboard.sqlin the Supabase SQL editor. - Deploy the site to Vercel so
api/leaderboard.jsis available. - Add these Vercel environment variables:
SUPABASE_URL=https://your-project-ref.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
LEADERBOARD_RATE_LIMIT_MAX=5
LEADERBOARD_RATE_LIMIT_WINDOW_SECONDS=600
LEADERBOARD_RATE_LIMIT_SALT=change-me
LEADERBOARD_DELETE_TOKEN_SALT=change-me-tooIf you already have a leaderboard table, run the updated SQL again to add the delete_token_hash column. The SQL also reloads Supabase's REST schema cache; without that, score submission may temporarily fail with a missing-column schema cache error.
The API validates names, scores, challenge ids, and code length. It hashes the request IP before storing it for basic per-challenge rate limiting, and the browser renders leaderboard text with textContent. New submissions also get a temporary remove token stored in sessionStorage, so users can remove entries they submitted in the current browser tab without needing accounts.
Open source project for educational purposes.
Made by Michael Lydeamore