Every table on the web
becomes a data visualization.

Practical Data is a Chrome Extension that applies conditional formatting for data tables as you browse. It does tricky stuff automatically, like figuring out which direction is "better", or how to parse messy data, using a decision model.

Docs and tests How it works TLDR About this project

Examples

Figures out which direction is better

If we shaded all the columns in this table with a uniform "big numbers = green, small numbers = red" rule, it would go against the meaning of a few of them (Losses, and ERA is better when it's lower, for example). The model picks the direction for each column, given its title and context.

docs/tests view it live at baseball-reference.com

Standard Pitching Table
SeasonAgeTeamWLERAIPSOWHIPFIPERA+SO9
202023DET145.6332.0371.2195.758110.4
202124DET8124.34149.11641.2595.09989.9
202225DET783.52117.21171.1562.961118.9
202326DET732.8080.11020.8962.0015511.4
202427DET1842.39192.02280.9222.4917410.7
202528DET1362.21195.12410.8912.4518511.1
2026292TM1272.72158.21860.9642.5115510.6
Ignoreda point in time (year, season, age)Ignoreda point in time (year, season, age)Ignoredlabels with no natural orderAmounthigher is better · log scaleAmountlower is better · linearAmountlower is better · linearAmounthigher is better · linearAmounthigher is better · linearAmountlower is better · linearAmountlower is better · linearAmounthigher is better · linearAmounthigher is better · linear

Knows which numbers are amounts

A lot of numbers aren't amounts; if you try to shade everything numeric, identifying things like Jersey # or Zip Code would look like they're on a scale. Practical Data asks the decision model whether a column is really a measure first, so identifiers stay are ignored.

docs/tests synthetic test page

Roster
PlayerJersey #BornZip codeHeight (in)Points per game
Ada Park231998941107421.4
Ben Ortiz72001100277912.9
Cy Moreau11199560614818.2
Di Okafor341999303077717.5
Eli Sato0200398103725.1
Ignoredlabels with no natural orderIgnoredan identifier, not an amountIgnoreda point in time (year, season, age)Ignoredan identifier, not an amountAmountno better direction · linearAmounthigher is better · linear

Knows when to use a log or linear scale

On a linear scale, the one $237 card here would flatten everything under $30 into nearly the same red colour. The model notices when ratios matter more than differences and moves that column to a log scale, so the cheap cards still read as different from each other.

docs/tests view it live at mtggoldfish.com

Price History
CardSetRarityPriceWeekly (%)
Mox Opal #223MM2Mythic237.09-4.00%
Sheoldred, the Apocalypse #107DMUMythic105.010.00%
Mox Amber #224DOMMythic92.22-2.00%
Gemstone Caverns #274PLISTRare63.15-1.00%
Minamo, School at Water's Edge #118 FoilFMB1Rare57.870.00%
Cavern of Souls #269LCIMythic51.740.00%
Boseiju, Who Endures #266NEORare51.400.00%
Amulet of Vigor #121WWKRare50.990.00%
Claws of Gix #107TSBSpecial12.52+5.00%
Untimely Malfunction #161DSKUncommon9.74+6.00%
Sink into Stupor #241MH3Uncommon8.82+3.00%
Relic of Progenitus #213MMAUncommon7.48-3.00%
Faerie Macabre #86MMACommon6.960.00%
Pyretic Ritual #153M11Common6.50+7.00%
Desperate Ritual #163CHKCommon6.460.00%
Witch Enchanter #239MH3Uncommon5.99+1.00%
Lorien Revealed #60LTRCommon4.59-7.00%
Ignorednames or free textIgnorednames or free textOrdered labelscommon → uncommon → rare → special → mythicAmountlower is better · log scaleAmounthigher is better · linear

Puts labels in order

Works on words, not just numbers! The model can put labels in order when the presence of that order is implied (like tropical storms, or card rarity, or medal rankings).

docs/tests view it live at en.wikipedia.org

Costliest Atlantic hurricanes
NameNominal damage (Billions USD)SeasonStorm classification at peak intensity
Katrina$125.02005Category 5 hurricane
Harvey$125.02017Category 4 hurricane
Ian$112.02022Category 5 hurricane
Maria$91.62017Category 5 hurricane
Helene$78.72024Category 4 hurricane
Irma$77.22017Category 5 hurricane
Ida$75.32021Category 4 hurricane
Sandy$68.72012Category 3 hurricane
Ike$38.02008Category 4 hurricane
Milton$34.62024Category 5 hurricane
Andrew$27.31992Category 5 hurricane
Wilma$26.52005Category 5 hurricane
Ivan$26.12004Category 5 hurricane
Michael$25.52018Category 5 hurricane
Florence$24.22018Category 4 hurricane
Laura$23.32020Category 4 hurricane
Ignorednames or free textAmountlower is better · linearIgnoreda point in time (year, season, age)Ordered labelscategory 3 hurricane → category 4 hurricane → category 5 hurricane

Ignores Total and Average rows

The first row here is the World total; considered alongside the others, it would dwarf all their numbers and make them almost indistinguishable. The model flags totals and averages as aggregates and scales around them.

docs/tests view it live at en.wikipedia.org

List of countries and territories by total population
LocationPopulation% of worldDate
World8,232,000,000100%13 Jun 2025
India1,429,404,00017.3%1 Jul 2026
China1,404,890,00017.0%31 Dec 2025
United States341,784,8574.1%1 Jul 2025
Indonesia288,315,0893.5%31 Dec 2025
Pakistan241,499,4312.9%1 Mar 2023
Nigeria223,800,0002.7%1 Jul 2023
Brazil214,211,9512.6%1 Jul 2026
Bangladesh169,828,9112.1%14 Jun 2022
Russia146,028,3251.8%1 Jan 2025
Mexico131,231,9191.6%30 Jun 2026
Japan122,650,0001.5%1 Sep 2026
IgnorednamesAmountno better direction · log scaleAmountno better direction · log scaleDatesearliest to latest

Rows judged to be totals and kept out of the scale: "World".

Multiple tables can share a scale

Practical data looks for same-named columns in similar contexts and aligns their scales accordingly so they're more easily compared at a glance.

docs/tests view it live at docs.github.com

OpenAI
ModelCategoryInputCached inputOutput
GPT-5 miniLightweight$0.25$0.025$2.00
GPT-5.3-CodexPowerful$1.75$0.175$14.00
GPT-5.4Versatile$2.50$0.25$15.00
GPT-5.4Versatile$5.00$0.50$22.50
GPT-5.4 miniLightweight$0.75$0.075$4.50
GPT-5.4 nanoLightweight$0.20$0.02$1.25
GPT-5.5Powerful$5.00$0.50$30.00
GPT-5.5Powerful$10.00$1.00$45.00
GPT-5.6 LunaLightweight$0.20$0.02$1.20
GPT-5.6 LunaLightweight$0.40$0.04$1.80
Ignorednames or versionsOrdered labelslightweight → versatile → powerfulAmountlower is better · log scale · shared scaleAmountlower is better · log scale · shared scaleAmountlower is better · log scale · shared scale
Anthropic
ModelCategoryInputCached inputOutput
Claude Haiku 4.5Versatile$1.00$0.10$5.00
Claude Sonnet 4Versatile$3.00$0.30$15.00
Claude Sonnet 4.6Versatile$3.00$0.30$15.00
Claude Opus 4.8Powerful$5.00$0.50$25.00
Claude Opus 5Powerful$5.00$0.50$25.00
Claude Opus 5.5Powerful$4.00$0.20$20.00
Claude Sonnet 5Versatile$2.00$0.20$10.00
Claude Sonnet 5.5Versatile$2.00$0.20$10.00
Ignorednames or versionsOrdered labelslightweight → versatile → powerfulAmountlower is better · log scale · shared scaleAmountlower is better · log scale · shared scaleAmountlower is better · log scale · shared scale

Notices when the table is meant to be read row-wise

Not every table compares down the column. Here each row is one benchmark and the columns are models, so column-wise shading would compare numbers that have nothing to do with each other. The model reads the orientation and gives each row its own scale.

docs/tests view it live at blog.cloudflare.com

Benchmark scores by model
BenchmarkClefClef-flashJevDiffusionGemma JevKev 9BLaya
BFCL · case exact98.4798.7695.7596.5294.5138.13
ToolRet · nDCG@1069.1966.4365.2861.2164.2612.69
API-Bank · accuracy91.9393.1188.1983.6656.3011.41
Home appliances · case exact82.9597.7352.2742.0525.000.00
When2Call · accuracy72.3765.5880.9775.4449.6211.94
BANKING77 · macro-F194.2090.9379.7474.2884.8314.29
CLINC150+OOS · macro-F197.4366.7789.2783.4979.033.19
BRIGHT · nDCG@1045.9139.2647.5242.9438.5319.90
Amazon ESCI · macro-F157.4857.3955.2153.3749.2224.40
PhishNChips · accuracy79.6075.0562.5585.3550.7550.15
Ignorednames or versionsAmounthigher is better · linearAmounthigher is better · linearAmounthigher is better · linearAmounthigher is better · linearAmounthigher is better · linearAmounthigher is better · linear

Understands times and durations

It would be easy to treat 2:42 as text, or a ratio. Practical Data parses it as a duration, and understands minutes/hours correctly (3:00 is one more than 2:59).

docs/tests view it live at baseball-reference.com

Time of Game For Season
YearGAvgTime%≥3:30Avg Tm Btwn PAAvg Tm Btwn BIP
202622182:420.52:103:15
202522162:380.12:083:10
202422092:360.32:063:09
202322262:390.42:083:13
202222043:0310.52:293:42
202120893:1018.72:333:52
20207203:0715.62:313:51
201922113:0513.52:283:43
201822103:008.92:253:37
201722463:0512.22:283:39
201622363:008.32:253:32
201522132:567.12:233:25
201421943:0211.12:283:33
201321802:588.32:253:27
201222312:555.82:223:24
201121852:514.52:193:16
Ignoreda point in time (year, season, age)Amountno better direction · linearAmountlower is better · linearAmountlower is better · log scaleAmountlower is better · linearAmountlower is better · linear

How it works (TLDR)

  1. 1. Read the page

    When you scroll a table into view, the extension figures out if it's got data or not. If it does, the extension standardizes its values (for parsing; your view of it doesn't change): Things like $0.025, .312, 6' 6", 1.5B, $50–60K, 2:59, ≤ 272K, etc.

  2. 2. Reason about the data

    A System One model (Jev) answers narrow questions with probabilities:

    • Is this column an amount? an identifier? a timestamp? a rank?
    • Is a higher value better, worse, or neither?
    • Does it make sense to show it in linear or log scale?
    • Is this row a total, or part of the data?
    • Do same-named columns in other tables share a scale?
    • Are values compared down each column, or across each row?
  3. 3. Paint the table

    The extension uses D3 to map each value onto a colour scale and applies it over the page's own background.

  • If you bring your own Jev or Clef key, the extension sends the data to the provider directly. If not, it sends it to an intermediary Cloudflare worker with a (daily limited, but these things are pretty cheap!) Jev API key. I don't retain anything either way (Privacy policy)
  • Nothing is sent on sites you turn off. The toolbar popup turns a page or a whole site off, and ⌥⇧H pauses the current page.
  • A typical page costs about a tenth of a cent on your own key (or mine).

Open Source (MIT)

You can Bring Your Own Key (BYOK) for Jev or Clef, or use the extension without a key (with a usage-limited free endpoint). System one models are pretty cheap :)

$ git clone https://github.com/philrenaud/practical-data
$ cd practical-data && npm install
$ npx playwright install chromium
$ npm run check

Install

  1. Install Practical Data from the Chrome Web Store.
  2. It works without a key. To use your own, paste a TypeSafe or Clef API key on the options page.