skill
Graticule
A cartography skill for coding agents. It checks a map against 25 rules (raw counts, Mercator, palettes, silent join drops) before it draws, and exports SVG, PNG, PDF and HTML.
Set up
Skills are installed by placing their folder where your agent loads skills.
The download comes from github.com/Arthur031221/Graticule itself, not from our server, so what you get is exactly what its author published.
Install instructions come from the README
README
synced Oct 26, 2026Graticule
A cartography skill for coding agents: 1 skill, 5 map types, 13 regions or your own GeoJSON, 25 rules, 9 languages. It won't draw a bad map. It'll tell you why.
Quick start.
npx skills add Arthur031221/Graticule, then ask your agent: "map COVID deaths by country from deaths.csv".
English | Traditional Chinese | Simplified Chinese | Japanese | Korean | Spanish | French | German | Portuguese
232 of 235 ISO 3166 country names join to the right polygon by name alone. None join to the wrong one. Natural Earth's English names match 202. Names from datasets/country-codes (official_name_en), joined to Natural Earth 1:50m. Wrong joins counted against each name's ISO code. Five more public lists show the same pattern. See docs/verification.md.
| A coding agent, no skill | The same agent with Graticule |
|---|---|
| Shaded raw death counts. The legend uses log bins of counts, so big countries are dark. No source line. | Divided by population and used an equal-area projection. Added a source line and named the 14 unmatched rows. 22 seconds. |
Same prompt and file, fresh sessions of the same agent (Sonnet 5.5). All four sessions without the skill shaded raw counts: two with that agent and two with Codex. Transcripts are in eval/runs.
I wrote down the rules agents skip when drawing maps in a hurry and built a renderer that won't run until they pass. No new map library.
Why the name
A graticule is the grid of latitude and longitude lines on a map. Readers use it to check that the map is where it says it is. This project does the same for maps drawn by agents. These mistakes keep coming up.
- Counts instead of rates. Coloring countries by death counts shows where the people are. Graticule asks for the data kind, or spots values that rank like population, and divides.
- Mercator for the world. Greenland as big as Africa. Equal-area projections are the default. Graticule refuses Mercator above 20 degrees of latitude.
- Rows that vanish. "Korea, Rep." doesn't match "South Korea" and nobody hears about it. Graticule lists every row that fails to join alongside its closest names. If a name fits two places, it won't guess.
- Files that are not UTF-8. A table saved by a Windows spreadsheet is often Big5, Shift_JIS or Windows-1252. Read as UTF-8, it joins nothing. Graticule flags files that aren't UTF-8 and reads them as Windows-1252. It accepts
--encoding big5ordata.encodingfor the rest. - Rainbow palettes. Lightness that goes up and down draws borders that aren't in the data. The palette must get steadily lighter or darker. Its colors must stay apart for the three kinds of color vision deficiency I simulate.
- Labels that turn into empty boxes. Chinese, Japanese and Korean labels need fonts. Each output carries a subset of Noto Sans CJK with just the glyphs it uses.
- No source. Graticule refuses maps without a source line.
What it checks
25 rules, each with a plain reason beside the finding. Errors stop rendering. --fix repairs errors with a single mechanical fix. --allow <id> accepts a rule and adds a note to the report.
| Rule | What it catches |
|---|---|
source-required | A map with no source line. |
title-required | A map with no title. |
legend-title | A legend that doesn't say what the numbers are. |
kind-undeclared | A shaded map whose values might be counts. |
table-ragged | Table rows with the wrong number of cells. |
join-blank | Rows with an empty area name. |
join-failed | A key column where most entries match no area. |
join-unmatched | Rows that match nothing, listed with the closest names. |
join-ambiguous | Names that fit more than one area. |
duplicate-keys | Several rows for one area, such as many years in one column. |
raw-counts | Raw counts on a shaded map. --fix divides by population. |
projection-mercator | Mercator on a region over 20 degrees of latitude. |
projection-not-equal-area | A shaded map of a large region in a projection that distorts area. |
max-classes | More than 7 classes. |
class-breaks-coverage | Breaks that leave some values without a color. |
empty-class | A legend class that no area uses. |
skewed-classes | Equal or manual classes with most of the map in one class. |
palette-order | A palette that isn't steadily lighter or darker. |
cvd-separation | Colors that come too close under simulated color vision deficiency. |
diverging-center | A diverging palette with no midpoint. |
too-many-categories | More than 8 categories. |
normalize-missing | Areas with no population or area to divide by. |
values-skipped | Rows skipped because a cell isn't a number or a denominator is missing or zero. |
no-data-share | A map that's mostly grey. |
label-overlap | Labels printed over each other. |
Exact conditions and thresholds are in docs/rules.md.
Install
npx skills add Arthur031221/Graticule
Run this in a project. It writes .agents/skills/graticule, which Codex reads, and links the skills folder for each other agent you pick. Then ask for a map in plain words.
Run the same command and pick your agent when the skills program asks. I haven't tried these agents myself. If your agent has a skills folder, copy skills/graticule into it. It's about 14 MB. That's all you need.
After cloning, build graticule-skill.zip with npm run package:skill, or get it from a release. Upload it where the app accepts a custom skill. Turn on code execution so the skill can run.
npx -y github:Arthur031221/Graticule render map.json
Or clone and run node skills/graticule/scripts/graticule.mjs --help. Node 20 or newer, no network.
Open the playground. Drop a CSV and fix what the checker finds. Download SVG, PNG, PDF or an interactive HTML page. Everything runs in the page. Nothing is uploaded. The interface is in nine languages. Drop a GeoJSON or TopoJSON file next to the table to draw your own boundaries.
Use it
graticule join deaths.csv --region world # which rows match which areas, before anything is drawn
graticule check map.json # every finding, with its reason
graticule render map.json --fix -f svg,png # the smallest repair for each error, then the files
A spec is a small JSON file:
{
"title": "COVID-19 deaths per 100,000 people",
"source": "WHO COVID-19 dashboard, up to 2026-09-20",
"region": "world",
"data": { "file": "deaths.csv", "key": "country", "value": "deaths", "kind": "count" }
}
render writes map.svg, map.png, optionally map.pdf and map.html, and map.check.json. The report lists what matched, the class edges and every finding. See docs/usage.md for the walk-through and docs/config.md for the fields.
Maps and regions
Five map types: shaded areas (choropleth), proportional circles, categories, locator maps with a small globe, and small multiples that share one legend. Thirteen regions: the world, six continents, US states and counties (Alaska, Hawaii and Puerto Rico as insets), Taiwan counties and townships, Japan prefectures and South Korean provinces. Everything is bundled, so rendering works offline. For any other place, supply a GeoJSON or TopoJSON file: graticule init table.csv --boundaries states.geojson. Every property that differs per feature becomes a key. The extent sets the equal-area projection. See docs/config.md.
docs/gallery.md has all 17, each with its prompt, spec, data and check log.
Labels in nine languages
English, Traditional and Simplified Chinese, Japanese, Korean, Spanish, French, German and Brazilian Portuguese. --lang zh-TW changes the map labels and message language. The playground, messages and these READMEs are translated into each language.
Checked against real systems
Each check runs from a script in this repository. CI repeats most of them. See docs/verification.md for the numbers and methods.
| What | Compared with | Result |
|---|---|---|
| 12 projections | PROJ 9.8.1, 3,000 points each | 11 agree to 0.00001 m, Robinson to 10.2 km after one fitted scale |
| Quantile, equal and Jenks classes | mapclassify 2.11.0, 45 cases | same edges and counts, before the legend edges are rounded |
| CIELAB, CIEDE2000, color vision simulation | colour-science 0.4.7, colorspacious | within 0.02 and within 2 of 255 |
| Name joins | 6 public country lists | no wrong join in 1,301 names |
| Counts versus rates | 33 World Bank indicators | 0 of 25 rates flagged, 8 of 8 counts caught |
| SVG, PDF and PNG | fontTools, pypdf, Pillow | every glyph embedded, fonts embedded, sizes right |
The agent sessions are a small sample: two prompts, two agents, two runs each. Read them as examples. The prompts and a runner are in eval.
Limits
- Boundaries come from Natural Earth (1:50m for countries), the Census 2017 files and the Taiwan Ministry of the Interior files. They're drawn for maps and aren't for surveying. French Guiana, Martinique, Guadeloupe, Mayotte and Reunion are part of France. Gibraltar and Tokelau have no polygon.
- US counties use the 2017 set, so Connecticut's planning regions and Alaska's newer census areas don't join.
- Taiwan townships have English and Traditional Chinese names only. Japan's Ogasawara and Amami islands and Taiwan's Wuqiu aren't drawn.
- The counts detector is a heuristic with a limit of 0.6. Declare
data.kindto bypass it. - A cell such as
1,234is read as 1234 unless another cell in the column shows decimal commas. See docs/config.md for the other number rules. - Fonts cover each language's common characters (5,401 Big5 level 1 characters for Traditional Chinese, for example). Characters outside that set return
glyphs-missing. Scripts that run right to left or need shaping aren't supported. - In the US map, Alaska is drawn at 35 percent of the lower 48 scale. Areas are comparable inside each piece.
- Labels use a greedy placer. Dense maps leave some out, and the report counts them.
- Your own boundaries are drawn as given. A very large file is slow and needs a lot of memory. Simplify it first, for example with mapshaper. Coordinates must be longitude and latitude.
--fixneeds a population property in the file before it can divide raw counts.
More
Install, usage, config, regions, rules, data and licenses, architecture, verification, contributing, changelog.
Natural Earth, the US Census Bureau, the Taiwan Ministry of the Interior, Noto fonts, ColorBrewer, d3, pdfkit, resvg and HarfBuzz make this possible. NOTICE.md has the licenses.
MIT, see LICENSE.
Assisted by Claude/Codex.