Skip to content
Conduit

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.

Arthur031221/Graticulecommunitylistedunreviewed

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

Skills do not use an MCP config. Follow the steps in the README below.

README

synced Oct 26, 2026

Graticule

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

CI License: MIT Live demo Release

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 skillThe 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 big5 or data.encoding for 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.

RuleWhat it catches
source-requiredA map with no source line.
title-requiredA map with no title.
legend-titleA legend that doesn't say what the numbers are.
kind-undeclaredA shaded map whose values might be counts.
table-raggedTable rows with the wrong number of cells.
join-blankRows with an empty area name.
join-failedA key column where most entries match no area.
join-unmatchedRows that match nothing, listed with the closest names.
join-ambiguousNames that fit more than one area.
duplicate-keysSeveral rows for one area, such as many years in one column.
raw-countsRaw counts on a shaded map. --fix divides by population.
projection-mercatorMercator on a region over 20 degrees of latitude.
projection-not-equal-areaA shaded map of a large region in a projection that distorts area.
max-classesMore than 7 classes.
class-breaks-coverageBreaks that leave some values without a color.
empty-classA legend class that no area uses.
skewed-classesEqual or manual classes with most of the map in one class.
palette-orderA palette that isn't steadily lighter or darker.
cvd-separationColors that come too close under simulated color vision deficiency.
diverging-centerA diverging palette with no midpoint.
too-many-categoriesMore than 8 categories.
normalize-missingAreas with no population or area to divide by.
values-skippedRows skipped because a cell isn't a number or a denominator is missing or zero.
no-data-shareA map that's mostly grey.
label-overlapLabels 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.

WhatCompared withResult
12 projectionsPROJ 9.8.1, 3,000 points each11 agree to 0.00001 m, Robinson to 10.2 km after one fitted scale
Quantile, equal and Jenks classesmapclassify 2.11.0, 45 casessame edges and counts, before the legend edges are rounded
CIELAB, CIEDE2000, color vision simulationcolour-science 0.4.7, colorspaciouswithin 0.02 and within 2 of 255
Name joins6 public country listsno wrong join in 1,301 names
Counts versus rates33 World Bank indicators0 of 25 rates flagged, 8 of 8 counts caught
SVG, PDF and PNGfontTools, pypdf, Pillowevery 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.kind to bypass it.
  • A cell such as 1,234 is 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. --fix needs 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.

↑↓ navigate openesc close