Communitygithub.com

2016judea/ascii-charts

Créez des graphiques en pur texte Unicode block — barres, plages, bullets, gap-brackets, histogrammes en étoiles, heatmaps, lignes, Venn, camemberts, donuts et nuages de points — avec un script Python sans dépendances, pour tout environnement où seul le texte monospace survit (blocs de code dans les newsletters, Markdown, terminaux, emails en texte brut, chat). Utilisez-le quand un graphique doit s'intégrer sous forme de texte littéral plutôt que d'image ou de service tiers, ou pour un rendu manuel d'aspect terminal sans pied de page de marque.

Qu'est-ce que ascii-charts ?

Ce skill permet de générer une grande variété de graphiques au format texte en utilisant uniquement des caractères Unicode block, sans dépendances externes ni services tiers. Il est conçu pour les environnements où seul le texte monospace est fiable, comme les blocs de code Markdown, les emails en texte brut, les newsletters (Substack, etc.) ou les terminaux. Le script `ascii_chart.py` contient une fonction par type de graphique, incluant les barres, plages, bullets, histogrammes en étoiles, heatmaps, lignes, sparklines, diagrammes de Venn, camemberts, donuts et nuages de points. Chaque graphique s'intègre comme du texte littéral, sans image, sans marque ni compte externe. Le skill met un fort accent sur la lisibilité immédiate (le test des 2 secondes), évitant les légendes complexes et les décorations inutiles. Les corrections internes importantes incluent : un facteur d'aspect horizontal de 2.49 pour les cercles en monospace (au lieu de 2.0), une largeur maximale de 44 caractères pour les écrans de téléphone, et l'absence totale de couleur (les distinctions se font par caractères, chiffres et légendes intégrées). Il propose également des règles de bon sens : ne pas graphiquer un nombre qui tient déjà en une phrase claire, couper tout graphique qui nécessite une légende pour être compris, et remplacer un diagramme de Venn par du texte simple quand le message est binaire (tout ou rien). Le skill est idéal pour des rapports rapides, des newsletters techniques, des analyses de données légères ou des emails professionnels où une image ne passerait pas.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/2016judea/brick-mortar-skills/tree/HEAD/skills/ascii-charts

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Graphiques ASCII

Des graphiques de style terminal construits à partir de caractères Unicode block purs et intégrés sous forme de texte littéral : un bloc de code délimité en Markdown, une balise <pre> en HTML. Aucun service de graphiques, aucun pied de page « créé avec », aucun compte externe, et ils s'affichent à l'identique partout où le monospace s'affiche.

scripts/ascii_chart.py contient une fonction par type de graphique. Exécutez python3 scripts/ascii_chart.py pour imprimer un exemple de chaque type à titre de vérification de cohérence.

Deux choses ici ont été mesurées plutôt que supposées, et elles s'avéraient toutes deux erronées au départ : sur un viewport de téléphone de 390 px, les lignes de plus de ~44 caractères sont tronquées sur le bord droit, et un cercle « rond » en monospace nécessite une correction d'aspect horizontal d'environ 2,49 (et non 2,0). Voir la section Dimensionnement et Pie/donut.

Les plateformes de newsletters diffèrent dans la manière dont elles collent les blocs de code. Avant de vous fier à l'une d'elles, collez un graphique de test dans un véritable brouillon et vérifiez les rendus web, application et email.

Jeu de caractères

  • █ ▓ ▒ ░ (U+2588, U+2593, U+2592, U+2591 — ombre pleine/foncée/moyenne/claire) — densité de remplissage, de « plus » à « moins ». Les barres utilisent uniquement █/░ (rempli vs piste) ; les camemberts/donuts utilisent les quatre (plus ▚/▞/▤/▥ s'il y a plus de 4 parts) en tant que remplissages distincts par part ; la heatmap utilise les quatre plus · (point médian, pas un espace vide) pour le niveau le plus bas, de sorte qu'une cellule « calme » soit toujours perçue comme une partie intentionnelle de la grille, et non un trou.
  • les chiffres 1–7 — codes de région de Venn (un masque de bits des ensemble(s) auxquels une région appartient — voir Venn ci-dessous)
  • + — indicateur ouvert sur une barre de plage qui pourrait dépasser le plafond affiché (n'allongez pas la barre elle-même pour simuler un nombre que vous n'avez pas)
  • ┃ — repère de cible pour le graphique bullet, dessiné par-dessus tout remplissage/ombrage en dessous, donc toujours visible
  • └─┘ — crochet d'écart, s'étendant entre deux extrémités de barre
  • ★/☆ — étoile pleine/vide, servant également d'étiquette de ligne pour l'histogramme en étoiles
  • traits de cadre (─│╭╮╰╯) — connecteurs pour graphiques linéaires et lignes de tendance
  • ● — marqueur de point pour nuage de points

Pas de couleur, nulle part. C'est le support, pas une limitation à contourner — un bloc de code Substack collé est constitué de caractères littéraux, rien d'autre ne survit (pas de CSS en ligne, pas de spans stylisés). Toute distinction qu'un graphique doit faire réside dans le caractère/chiffre/légende, et non dans un canal de couleur indisponible.

Menu des types de graphiques

TâcheFonctionUtilisation pour
Comparaison de grandeurs, plusieurs catégories, toutes commencent à 0bar(valeur, valeur_max, largeur) + render()Plein d'essence (5 min) vs charge rapide DC (45 min)
Une plage min–max, une catégorierange_bar(début, fin, échelle_max, largeur, ouvert=) + render()« Installe entre 100k$ et 300k$+ »
Deux valeurs, avant/aprèsTexte brut : f"{début} → {fin}"« Vente en gros 220$ → dégustation 650$ » — un graphique en barres n'apporte rien ; la flèche est le graphique
Une mesure par rapport à une bande de référence + une ciblebullet(valeur, échelle_max, cible=, référence=) + render()« Votre note : 4,2, bande normale 3,8–4,5, cible 4,7 »
Mettre en évidence l'écart entre deux barresgap_bracket(a, b, échelle_max, largeur, étiquette=) via render(crochet=)« Vous vs médiane du marché » — la signature du rapport
Une distribution de notes de 1 à 5 (ou similaire)star_histogram(compteurs)Répartition des étoiles Google/Yelp, une ligne par nombre d'étoiles
Deux axes catégoriels, ombrés par intensitéheatmap(matrice, étiquettes_lignes, graduations_colonnes=)Trafic piétonnier par jour × heure — « quand êtes-vous occupé/mort »
Tendance dans le temps, une sérieline_chart(valeurs, hauteur, étiquettes_x=)Chiffre d'affaires mensuel, nombre d'avis sur un an
Tendance, en ligne dans une phrasesparkline(valeurs)« Avis par mois : ▂▃▅▆█▇▅▃, en hausse de 3x depuis mars »
2 ou 3 ensembles nommés, où le degré de chevauchement est lui-même la conclusionvenn(étiquettes)« 38 % de vos avis à une étoile mentionnent également le temps d'attente » — la taille du chevauchement est le point
Parties d'un tout, un instantanépie(parts, rayon)Répartition du chiffre d'affaires : café / plats chauds / boissons / loterie
Parties d'un tout, avec un total en vedettedonut(parts, rayon, texte_centre=)Idem, avec « 247 avis » au centre creux
Deux variables numériques, y a-t-il une relationscatter(points, largeur, hauteur)Prix vs note parmi les concurrents
Idem, plus « et voici la tendance »scatter_with_trend(points, largeur, hauteur)Idem, avec une ligne ajustée prouvant la corrélation

Si rien dans ce tableau ne correspond, arrêtez-vous et réfléchissez plutôt que de forcer une forme qui ne convient pas — un graphique techniquement rendu mais déroutant est pire que de recourir au pipeline PNG sans tête Chrome.

Le test des 2 secondes pour les graphiques

Un graphique répond au même standard qu'une bonne phrase : être compris par un lecteur fatigué sur son téléphone, du premier coup d'œil, sans relecture, sans décodage. Appliquez le test avant de choisir un type de graphique, pas après en avoir construit un.

Le test des 2 secondes : le lecteur visé — un propriétaire de petite entreprise, pas un analyste de données — pourrait-il dire ce que ce graphique signifie en 2 secondes, sans lire d'abord une légende ? Si comprendre une seule région ou part nécessite de décoder une légende en premier, c'est l'équivalent visuel du jargon : techniquement précis, mais cela oblige le lecteur à travailler au lieu de le faire avancer.

Pris sur un vrai problème, par le rédacteur lisant le message rendu à froid :

  • Le diagramme de Venn à 3 ensembles avec masque de bits chiffré nécessitait de lire une légende de 7 lignes avant qu'une région n'ait un sens. Le rédacteur : ils ne l'ont pas compris, donc un propriétaire d'entreprise ordinaire non plus. Le fait sous-jacent — « vous avez besoin de ces trois éléments, pas d'un seul » — tient en une phrase, et la liste numérotée juste après le diagramme le disait déjà en langage clair. Le diagramme a ajouté un coût de décodage sans ajouter d'informations que la prose ne portait pas déjà plus clairement. Correction : supprimez-le, laissez la phrase + la liste faire le travail.
  • La barre de plage des coûts du chargeur rapide DC (une seule barre, sans axe, sans point de comparaison) n'avait rien à quoi se comparer. La valeur d'une barre vient du fait de montrer une grandeur relative à quelque chose — une échelle, une autre barre, une référence. Une barre isolée sans point de référence est décorative, pas informative. Le nombre qu'elle illustrait (100k$–300k$+) était déjà énoncé clairement une phrase plus tôt. Correction : supprimez-la aussi.

Règles qui en découlent :

  • Un graphique qui a besoin de sa propre légende pour être lu est un signal d'alarme — pas seulement pour Venn. Une heatmap avec des axes non étiquetés, ou un camembert avec plus de parts que le lecteur ne peut en retenir mentalement, échoue de la même manière.
  • Si une phrase simple énonce le fait aussi clairement que le graphique le ferait, sautez le graphique. Même réflexe que de remplacer « plusieurs/beaucoup/divers » par le chiffre réel dans la prose : utilisez un visuel parce qu'une comparaison ou une tendance est le point, pas simplement parce que les données ont une forme.
  • Chaque barre a besoin d'un axe (render(..., axe=)) ou d'une deuxième barre sur la même échelle pour se comparer. Une seule barre sans l'un ni l'autre ne transmet rien — la même défaillance qu'une phrase en voix passive sans sujet réel. Mais une fois cela satisfait, supprimez l'axe s'il ne ferait que répéter un nombre déjà présent dans une étiquette de valeur par ligne — une barre essence-vs-charge a supprimé sa ligne d'axe 0 ... 45 min pour exactement cette raison : les deux lignes portent déjà leur propre valeur (« 5 min », « 45 min »), donc l'axe ne faisait que répéter « 45 min » une deuxième fois. Même principe que de couper une clause redondante d'une phrase.
  • Ne graphiquez pas un nombre qui tient déjà en une phrase claire. « Installe entre 100k$–300k$+ » est parfaitement clair en texte ; le graphiquer ne révèle pas une comparaison, une tendance ou un chevauchement, il ajoute simplement une étape de décodage sans aucun gain.
  • Lisez le graphique fini à froid, comme le fera le vrai lecteur — un propriétaire de station-service sur son téléphone, pas la personne qui vient de le construire et sait déjà ce qu'il est censé dire.

Flux de travail

Utilisez scripts/ascii_chart.py plutôt que d'aligner manuellement les caractères ou de rasteriser la géométrie vous-même — les mathématiques de colonnes et de cercles/angles sont exactement le genre de choses qu'il est facile de faire de manière subtilement erronée à l'œil et qui ne se manifestent que sous la forme d'un décalage d'un caractère ou d'un cercle asymétrique après coup. Exécutez python3 scripts/ascii_chart.py à tout moment pour imprimer un exemple de chaque type de graphique à titre de vérification de cohérence.

Barre / barre de plage

from ascii_chart import bar, range_bar, render

lignes = [
    ("Plein d'essence", bar(5, 45, 24), "5 min"),
    ("Charge rapide DC", bar(45, 45, 24), "45 min"),
]
print(render(lignes, axe=("0", "45 min")))

ligne = ("Chargeur rapide DC", range_bar(100, 300, 300, 24, ouvert=True), "100k$–300k$+")
print(render([ligne]))

render() prend des tuples (étiquette, chaîne_barre, chaîne_valeur) et gère tout l'alignement des colonnes — passez votre propre chaîne_valeur pré-formatée (devise, unités, tout ce dont la copie a besoin) ; la fonction ne gère que la géométrie.

Graphique bullet

from ascii_chart import bullet, render

lignes = [("Note moyenne", bullet(4.2, 5.0, cible=4.7, référence=(3.8, 4.5, largeur=24)), "4,2 (cible 4,7)")]
print(render(lignes, axe=("0", "5,0")))

Une barre pleine pour la valeur mesurée, dessinée par-dessus une bande de référence ombrée optionnelle (la plage « normale », ▒) avec un repère de cible optionnel (┃, toujours dessiné en dernier pour rester visible par-dessus tout remplissage/ombrage en dessous). Répond à « suis-je dans la normale, et à quelle distance de la cible » en un coup d'œil — il n'y a toujours qu'une seule vraie barre (la mesure) ; la bande est le contexte.

Crochet d'écart

from ascii_chart import bar, gap_bracket, render

lignes = [("Vous", bar(42000, 60000, 24), "42 000$"), ("Médiane du marché", bar(58000, 60000, 24), "58 000$")]
print(render(lignes, crochet=gap_bracket(42000, 58000, 60000, 24)))

Un crochet └─┘ s'étendant entre deux extrémités de barre, avec la taille de l'écart centrée à l'intérieur (formatée automatiquement à partir des deux valeurs, ou passez étiquette= pour votre propre texte). Utilisez le même échelle_max/largeur pour les barres et le crochet, sinon la portée ne s'alignera pas. C'est « vous vs. le marché », rendu explicite au lieu d'être laissé à l'appréciation du lecteur.

Histogramme en étoiles

from ascii_chart import star_histogram

print(star_histogram([(5, 61), (4, 22), (3, 9), (2, 4), (1, 4)]))

compteurs est [(étoiles, compteur), ...] dans l'ordre que vous souhaitez imprimer (généralement 5 à 1). Les glyphes d'étoile pleine/vide (★/☆) servent également d'étiquette de ligne — pas besoin d'écrire « 5 étoiles » en texte. Entièrement construit sur bar() + render().

Heatmap

from ascii_chart import heatmap

print(heatmap(matrice, ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"],
              graduations_colonnes=[(0, "00h"), (6, "6h"), (12, "12h"), (18, "18h")]))

matrice est lignes × colonnes d'intensité numérique (par ex. 7 jours × 24 heures), normalisée sur l'ensemble de la matrice (pas par ligne) pour que l'ombrage soit comparable entre les lignes. graduations_colonnes est [(indice_colonne, étiquette), ...] — pas d'espacement automatique, choisissez des positions qui n'entreront pas en conflit pour les étiquettes que vous passez.

Graphique linéaire / sparkline

from ascii_chart import line_chart, sparkline

print(line_chart([120, 135, 128, 150, 175, 190, 205, 198, 220],
                  hauteur=10, étiquettes_x=("Jan", "Sep")))

print(sparkline([3, 5, 4, 8, 12, 10, 15, 14, 18]))

line_chart dessine une ligne connectée avec des coins de traits de cadre (╭╮╰╯│─) — il n'y a pas de caractère pour une pente intermédiaire arbitraire, donc la largeur est fixée à len(valeurs) - 1 colonnes (une par segment). étiquettes_x, si fourni, est simplement (premier, dernier) imprimé sur la ligne d'axe — dites les points intermédiaires dans la prose environnante, n'essayez pas d'étiqueter chaque graduation. sparkline est une tendance à 8 niveaux sur une seule ligne (▁▂▃▄▅▆▇█) pour une clause dans une phrase, pas un graphique autonome.

Venn (2 ou 3 ensembles)

from ascii_chart import venn

diagramme, légende = venn(["Sortie d'autoroute", "Grand magasin", "Capacité du réseau"])
print(diagramme)
print(légende)

Rastérise des cercles qui se chevauchent et remplit chaque région avec un chiffre (un masque de bits des ensemble(s) auxquels elle appartient : 1, 2, 4 pour les ensembles simples, 3/5/6 pour les chevauchements par paires, 7 pour les trois) plutôt que d'essayer de faire tenir le texte de l'étiquette à l'intérieur d'une limite courbe, ce qui ne survit pas au rendu monospace. La légende (renvoyée séparément, imprimez les deux) mappe chaque chiffre à la combinaison d'étiquettes réelle, par ex. 7 = Sortie d'autoroute + Grand magasin + Capacité du réseau <- tous. Cela était initialement considéré comme impossible en ASCII (un premier article s'est rabattu sur un PNG sans tête Chrome pour son Venn) — la technique de la région chiffrée le résout proprement ; il n'y a plus de cas où Venn a besoin de la solution de repli PNG.

Mais passez-le au test des 2 secondes ci-dessus avant de l'utiliser — la légende des chiffres elle-même est ce qui a rendu un vrai Venn déroutant pour un lecteur non initié. Un Venn mérite sa place lorsque le degré de chevauchement est la conclusion (par ex. « 38 % de vos avis à une étoile mentionnent également le temps d'attente »). Lorsque le vrai message est « vous avez besoin des trois ensemble, pas de crédit partiel », un Venn est le mauvais outil même s'il se rend correctement — le texte brut fait ce travail sans légende requise, par ex. :

Sortie d'autoroute  +  Grand magasin  +  Capacité du réseau
       manquez l'un d'eux, passez le site

Camembert / Donut

from ascii_chart import pie, donut

diagramme, légende = pie([("Café", 40), ("Plats chauds", 25), ("Boissons", 20), ("Loterie", 15)], rayon=9)
print(diagramme); print(légende)

diagramme, légende = donut([("Café", 40), ("Plats chauds", 25), ("Boissons", 20), ("Loterie", 15)],
                         rayon=9, texte_centre="247")
print(diagramme); print(légende)

Les parts commencent à 12 heures et vont dans le sens des aiguilles d'une montre (convention standard du camembert), chacune reçoit un caractère de remplissage distinct (█▓▒░▚▞▤▥, jamais répété sur une part adjacente pour des totaux de 2 à 6 parts) afin que les limites restent lisibles sans couleur. donut est pie avec un centre creux — texte_centre met un nombre/étiquette en vedette (par ex. un total), ce qui est la seule chose qu'un donut ASCII peut faire qu'un vrai gaspillage dans un espace vide. rayon est en unités de lignes ; la fonction corrige en interne le ratio d'aspect des caractères monospace (constante ASPECT partagée, également utilisée par venn()) pour que le résultat soit réellement rond, pas un ovale aplati — n'ajustez pas manuellement la largeur/hauteur séparément, passez simplement rayon.

Correction : ASPECT était codé en dur à une valeur supposée 2.0 et n'avait jamais été testé sur des formes circulaires réelles. Un test de validation a révélé que le ratio d'aspect effectif des caractères monospace dans la plupart des terminaux et environnements Markdown est d'environ 2.49. Cette correction garantit que les camemberts, donuts et diagrammes de Venn sont visuellement ronds, pas des ovales écrasés. Si le vôtre semble encore un peu aplati, ajustez ASPECT manuellement dans le script ou passez un facteur personnalisé via le paramètre aspect=. Notez que le ratio d'aspect peut varier légèrement entre les terminaux et les polices — testez toujours votre sortie finale dans l'environnement où elle sera lue.

Nuage de points

from ascii_chart import scatter, scatter_with_trend

points = [(1, 2), (2, 3), (3, 5), (4, 4), (5, 6), (6, 5), (7, 7), (8, 6), (9, 8), (10, 7)]
print(scatter(points, largeur=20, hauteur=8))
print(scatter_with_trend(points, largeur=20, hauteur=8))

scatter place des marqueurs ● à des coordonnées (x, y) dans une grille de caractères. Les axes sont implicites — les étiquettes ne sont pas générées automatiquement, décrivez les axes dans la prose environnante. scatter_with_trend ajoute une ligne de tendance ajustée (régression linéaire par moindres carrés) tracée avec des caractères ─│╭╮╰╯ et marquée avec un ● à chaque point de données. La ligne de tendance est tracée par-dessus les points, donc les deux sont visibles s'ils se chevauchent.

Contributeurs

Génial par Judea et Nicolasmean.

Licence

ISC

Individual skills in this repo

This repo contains 7 individual skills — each has its own dedicated page.

2016judea/buffett-checklist

Apply Warren Buffett's investment framework as a discipline lens when weighing a buy, hold, or sell decision — circle of competence, economic moat, owner earnings & intrinsic value, margin of safety, management/capital-allocation quality, and market psychology (Mr. Market, fear vs greed, temperament). Use when asked to "run this through Buffett", "what would Buffett say", "does this pass the moat test", "is this cheap enough", or for a second, more skeptical read on a name being evaluated or already held. This is a discipline overlay, not a replacement for a strategy — it applies the Buffett lens to how someone actually invests, rather than issuing generic advice.

2016judea/image-riff

Turn one or more reference images into original art in that reference's own visual language, then cut it to whatever surface it's for. Use when you paste or point at art and say "use this as the base image", "generate art from this", "riff on this", "some creative ideation with these images", "make me a channel banner / avatar / cover / plate", "wilder version of this", or want a spread of options off one piece of reference. This is the TASK skill; the engine is the `make-art` tool skill.

2016judea/leonardo

Draw the way Leonardo da Vinci drew — a study sheet, a notebook folio, a red-chalk or silverpoint study, a machine cutaway, a storm of vortices — in his real materials and page-logic, generated with the make-art engine and anchored on fifteen of his actual (public-domain) sheets. Use when you say "Leonardo", "da Vinci", "like his notebooks", "a codex page of X", "draw this as one of his study sheets", "sanguine", "silverpoint", or wants a design, diagram, or illustration in that hand. Also a reading guide to his notebooks (Richter's 1883 public-domain translation) for "what did Leonardo say about shadow / trees / water". This is a TASK skill; the engine is `make-art`.

2016judea/mcp-stack

Which free MCP server or framework to reach for, and its traps — finance/gov data (SEC EDGAR, Yahoo, SAM.gov, USAspending), web research (Exa, Firecrawl, Crawl4AI, Jina reader), design/art/brand (Canva, Inkscape, Penpot, Figma, Orshot, Hugging Face image gen, vectorizing, charts), data/spatial (DuckDB spatial, live ArcGIS layers, OpenStreetMap/Overpass, Census, MapLibre), and ops (PostHog, iMessage, context7 docs). Use when making a social post, OG card, poster, logo/vector, chart, map, or brand asset; when querying parquet/geojson/duckdb or a city's open-data layer; when an MCP "needs authentication" or fails; or when asked "what tools do we have", "is there an MCP for X", "should we add X".

2016judea/records-request

Get a specific record out of a US government office by public-records request — "request the assessor's model", "file a data practices request", "FOIA this", "open records request", "can we just ask the county for it", "who do I write to at <county>", "chase that request", "did they ever answer". Covers checking the website first, finding the right office and statute, the carve-out paragraph that makes a request answerable instead of refused, web-form traps (hidden character caps, WAFs that reject apostrophes), chasing (there is usually no deadline), whether what comes back may be republished, and parsing multi-year tables without silent errors.

2016judea/sms-native-product

Build a product whose interface is text messages rather than an app or a website — "no app, no website", "they just text a number", "chat with our agent natively in Text Messages", "can we do this over text", "an SMS bot", "a Telegram bot", "make it a group chat", "minimum viable friction". Fires equally when it has already gone wrong — "my campaign got rejected", "our number is blocked", "the texts aren't arriving", "why do I need to register". Covers the gate that decides whether such a product can send at all (US A2P 10DLC registration, SHAFT content rules, permanent rejection codes), group MMS from code, what each channel can't do, an architecture that survives losing the channel, and guarding model-rewritten user content.

2016judea/vertical-market-agent

Build a chat agent that knows one industry in one metro better than any general model because it carries a precomputed digest of real public records (or a mined review corpus) and can pull live records mid-conversation — finding the moat, building a byte-stable substrate, enumerating what the records can't, the data traps that produce plausible wrong numbers, deterministic math in tools, measured prompt cost and cache TTL, shipping as an ad destination, an off-sector ending, and a precomputed spatial "what is this next to" layer. Use when building a vertical AI agent grounded in records rather than general knowledge.

Skills associés