Communitygithub.com

ASCII 图表

使用纯 Unicode 块字符构建图表——条形图、范围图、子弹图、间隙括号图、星形直方图、热力图、折线图、韦恩图、饼图、环形图和散点图——借助一个无依赖的 Python 脚本,适用于任何只能显示等宽文本的场景(新闻通讯代码块、Markdown、终端、纯文本邮件、聊天)。当一个图表需要以文字形式嵌入,而不是作为图像或第三方图表服务时,或者当你想要一个无品牌页脚的手工终端风格时,请使用此技能。

ASCII 图表 是什么?

2016judea/ascii-charts 是一个轻量级、零依赖的 Python 技能,旨在从纯 Unicode 块字符创建终端风格的图表,生成可嵌入为字面文本的图表(例如 Markdown 中的代码块或 HTML 中的 `<pre>` 标签),支持超过 12 种图表类型,包括条形图、范围图、子弹图、间隙括号图、星形直方图、热力图、折线图、火花线、韦恩图、饼图、环形图和散点图。它专为无法呈现图像或外链服务的平台设计,完全避免颜色(因为等宽文本环境不支持样式),并依赖于像 `█▓▒░` 这样的字符来表示密度,`★/☆` 用于星级,`┃` 用于目标标记。该脚本(`scripts/ascii_chart.py`)对每种图表类型提供一个函数,无需外部依赖或 API 密钥。关键原则是“2 秒测试”:图表必须让读者(如小企业主,而非数据分析师)在 2 秒内理解其含义,无需事先读取图例;如果图表需要图例才能理解,则应考虑简化或替换为纯文本。该技能还解决了两个实际的等宽渲染问题:在小手机视口上每行约 44 个字符后行会截断,并且等宽字体中圆形的宽高修正因子约为 2.49(而非许多库假设的 2.0)。这对于无需品牌页脚、图像或外部服务即可在 Markdown、新闻通讯、终端、纯文本邮件或聊天中嵌入图表的开发者尤其有价值。

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

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

ASCII 图表 是做什么的?

基于纯 Unicode 块字符构建的终端风格图表,可像字面文本一样嵌入 Markdown 的围栏代码块或 HTML 的 <pre> 标签中。无需图表服务、无“由……制作”页脚、无需外部帐户,并且在所有等宽字体渲染环境中效果一致。

scripts/ascii_chart.py 为每种图表类型提供一个函数。运行 python3 scripts/ascii_chart.py 可打印每种类型的一个示例,作为健全性检查。

这里有两件事是经过测量而非假设的,而最初它们都是错的:在 390px 的手机视口上,超过约 44 个字符的行会在右边缘被截断;而等宽字体中的“圆形”需要约 2.49(而非 2.0)的水平纵向修正。请参阅“尺寸”和“饼图/环形图”部分。

新闻通讯平台在粘贴代码块时的行为各不相同。在依赖某个平台之前,请先将测试图表粘贴到一个真实草稿中,并检查网页、应用和电子邮件中的渲染效果。

字符集

  • █ ▓ ▒ ░ (U+2588, U+2593, U+2592, U+2591 — 纯/深/中/浅色阴影) — 表示填充密度,从“最多”到“最少”。条形图仅使用 █/░(填充与轨道);饼图/环形图使用全部四种(如果切片数超过 4 个,还会加上 ▚/▞/▤/▥)作为每个切片的区分填充;热力图使用全部四种加上 ·(中间点,不是空格)作为最低级别,因此“低密度”单元格看起来仍是网格中有意的一部分,而非空隙。
  • 数字 1–7 — 韦恩图区域编码(表示区域属于哪个集合的位掩码——参见下面的韦恩图)
  • + — 表示范围条的上限为开放端的标记(如果数值可能高于所示上限,请勿拉伸条本身以伪造不存在的数字)
  • ┃ — 子弹图的目标标记,绘制在任何填充/阴影之上,确保始终可见
  • └─┘ — 间隙括号,跨越两个条末端之间的范围
  • ★/☆ — 实心/空心星号,兼作星形直方图的行标签
  • 框线字符 (─│╭╮╰╯) — 折线图和趋势线连接符
  • ● — 散点图数据点标记

全图不使用任何颜色。这是媒介本身的特点,而非需要克服的局限——粘贴到 Substack 的代码块中只有字面字符才能保留(内联 CSS、带样式的 span 都无效)。图表需要做的任何区分都存在于字符/数字/图例中,而不是无法使用的颜色通道中。

图表类型菜单

用途函数应用场景
多类别数值比较,均从 0 开始bar(value, max_value, width) + render()加油(5 分钟)对比直流快充(45 分钟)
单个类别的最小-最大范围range_bar(start, end, scale_max, width, open_ended=) + render()“安装成本 $100k–$300k+”
两个数值,前后对比纯文本:f"{start} → {end}"“批发 $220 → 酒馆 $650” — 条形图无额外信息;箭头本身就是图表
一个测量值与一个基准区间加一个目标值的对比bullet(value, scale_max, target=, benchmark=) + render()“您的评分:4.2,正常范围 3.8–4.5,目标 4.7”
显式标注两个条之间的差距gap_bracket(a, b, scale_max, width, label=) via render(bracket=)“您 vs. 市场中位数”——报告的标志性手法
1-5 星(或类似)评分分布star_histogram(counts)Google/Yelp 星评分解构,每行对应一个星级
双分类坐标轴,按强度着色heatmap(matrix, row_labels, col_ticks=)按天×小时的人流量——“您什么时候忙/闲”
单序列时间趋势line_chart(values, height, x_labels=)月收入、一年内的评论数量变化
在句子中内联显示趋势sparkline(values)“每月评论:▂▃▅▆█▇▅▃,自三月以来增长 3 倍”
2 或 3 个命名集合,重叠程度本身就是发现venn(labels)“您的一星评论中有 38% 也提到了等待时间”——重叠程度是重点
整体的部分构成,单次快照pie(slices, radius)收入构成:咖啡 / 热食 / 饮料 / 彩票
整体的部分构成,附带总数标题donut(slices, radius, center_text=)同上,中间显示“247 条评论”
两个数值变量,是否存在关联scatter(points, width, height)价格 vs. 评分,跨竞品
同上,并显示趋势线scatter_with_trend(points, width, height)同上,附带拟合的相关线

如果上表没有适合的选项,请停下来思考清楚,而不是强行使用不匹配的图表类型——一个技术上可渲染但令人困惑的图表,比使用 Chrome 无头 PNG 流水线更糟糕。

图表的 2 秒测试

一个图表需要满足与一个好句子相同的标准:让疲惫的读者在手机上第一眼就能理解,无需重读,无需解码。在选择图表类型之前就应用该测试,而不是在构建之后。

2 秒测试: 目标读者(小企业主,而非数据分析师)能否在 2 秒内说出这个图表的意思,而无需先阅读图例?如果理解任何一个区域或切片都需要先解码图例,那它就是在视觉上使用了“行话”:技术上精确,但迫使读者付出努力,而不是吸引他们。

在真实案例中被发现,由编辑在冷读已发布的内容时发现:

  • 三集合韦恩图的数字位掩码图需要先阅读一个 7 行的图例,才能理解任何区域的含义。编辑:他们都看不懂,那么普通企业主也看不懂。背后的信息——“你需要这三者,而不仅仅是其中之一”——用一句话就能说清楚,而图之后的有序列表已经用通俗的英语说明了。这张图增加了解码成本,却没有提供散文部分已经表达得更清晰的信息。修复方案:删除该图,让句子和列表完成工作。
  • 直流快充成本范围条(单一条,无坐标轴,无对比点)没有对比对象。条形图的价值在于显示相对于某个参考点的大小——比例尺、另一个条、基准。没有参考点的孤立条形图是装饰性的,而非信息性的。它展示的数字($100k–$300k+)已经在前一句话中清楚地说明了。修复方案:同样删除。

遵循的规则:

  • 一个需要自身图例才能阅读的图表是一个危险信号——不仅仅针对韦恩图。坐标轴未标注的热力图,或切片数超出读者脑容量的饼图,同样会失败。
  • 如果一句平实的陈述已经和图表一样清晰地表达了事实,请跳过图表。同样,在散文中用实际数字替换“几个/许多/各种”的直觉:只有在比较或趋势是重点时才使用可视化手段,而不仅仅是因为数据具有形状。
  • 每个条形图都需要一个坐标轴(render(..., axis=))或一个在同一比例尺上的第二个条来进行对比。两者皆无的单个条无法传达任何信息——就像被动语态且无真正主语的句子一样失败。但是,一旦满足了这个条件,如果坐标轴只是重复了每行数值标签中已有的数字,则可以删除它——一个加油 vs. 充电的条就因此删除了它的“0 ... 45 min”坐标轴线:因为两行都已经携带了自己的值(“5 min”、“45 min”),所以坐标轴只是重复了第二次“45 min”。与从句子中删除冗余从句的原则相同。
  • 如果一个数字已经能用一句干净的陈述表达,不要将其制成图表。“$100k–$300k+ installed”作为文字已经极其清晰;将其制成图表不会揭示比较、趋势或重叠,只会增加一个无回报的解码步骤。
  • 以冷读的方式阅读完成的图表,就像实际读者会做的那样——一位在手机上的加油站老板,而不是刚刚构建它并且已经知道它应该说什么的人。

工作流

使用 scripts/ascii_chart.py,而不是手动对齐字符或栅格化几何图形——列计算和圆/角度计算正是那种容易因目测而出现细微错误,并且事后才以单个字符错位或圆形不对称的形式显现出来的事情。随时运行 python3 scripts/ascii_chart.py 可打印每种图表类型的一个示例,作为健全性检查。

条形图 / 范围图

from ascii_chart import bar, range_bar, render

rows = [
    ("Gas fill-up", bar(5, 45, 24), "5 min"),
    ("DC fast charge", bar(45, 45, 24), "45 min"),
]
print(render(rows, axis=("0", "45 min")))

row = ("DC fast charger", range_bar(100, 300, 300, 24, open_ended=True), "$100k–$300k+")
print(render([row]))

render() 接收 (label, bar_string, value_str) 元组,并处理所有列对齐——传入您自己预格式化的 value_str(货币、单位等,根据文案需要);函数仅处理几何部分。

子弹图

from ascii_chart import bullet, render

rows = [("Avg rating", bullet(4.2, 5.0, target=4.7, benchmark=(3.8, 4.5), width=24), "4.2 (target 4.7)")]
print(render(rows, axis=("0", "5.0")))

一个实心条表示测量的 value,绘制在可选的阴影 benchmark 区间(“正常”范围,▒)上方,并带有一个可选的目标标记(┃,始终最后绘制,以确保它在任何填充/阴影下方仍然可见)。一瞥之间回答“我是否在正常范围内,距离目标有多远”——这里仍然只有一个真正的条(测量值);区间只是背景信息。

间隙括号

from ascii_chart import bar, gap_bracket, render

rows = [("You", bar(42000, 60000, 24), "$42,000"), ("Market median", bar(58000, 60000, 24), "$58,000")]
print(render(rows, bracket=gap_bracket(42000, 58000, 60000, 24)))

一个 └─┘ 括号跨越两个条末端之间,间隙大小居中显示(根据两个数值自动格式化,或传入 label= 自定义文本)。对条和括号使用相同的 scale_max/width,否则跨度无法对齐。这是“您 vs. 市场”的显式表达,无需读者目测。

星形直方图

from ascii_chart import star_histogram

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

counts 是 [(stars, count), ...] 格式,按任意顺序打印(通常从 5 到 1)。实心/空心星号字形(★/☆)兼作行标签——不需要再在文字中写出“5 星”。完全基于 bar() + render() 构建。

热力图

from ascii_chart import heatmap

print(heatmap(matrix, ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
              col_ticks=[(0, "12am"), (6, "6am"), (12, "12pm"), (18, "6pm")]))

matrix 是行×列的数值强度(例如 7 天 × 24 小时),基于整个矩阵(而非每行)进行归一化,使得各行之间的阴影具有可比性。col_ticks 是 [(column_index, label), ...] 格式——没有自动间距,请自行选择不会冲突的位置。

折线图 / 火花线

from ascii_chart import line_chart, sparkline

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

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

line_chart 使用框线拐角(╭╮╰╯│─)绘制一条折线——没有字符表示任意中间斜率,因此宽度固定为 len(values) - 1 列。x_labels 如果给出,只是一个在坐标轴行上打印的 (first, last) 元组——中间的点在周围的散文中说明,不要尝试标注每个刻度。sparkline 是单行的 8 级趋势(▁▂▃▄▅▆▇█),用于句子中的一个子句,而非独立图表。

韦恩图(2 或 3 个集合)

from ascii_chart import venn

diagram, legend = venn(["Highway exit", "Big store", "Grid capacity"])
print(diagram)
print(legend)

将重叠的圆形栅格化,并用数字(位掩码,表示区域属于哪个集合:1、2、4 表示独立集合,3/5/6 表示两两重叠,7 表示三者重叠)填充每个区域,而不是试图将标签文本塞入弯曲的边界内(这在等宽渲染中无法实现)。图例(单独返回,打印两者)将每个数字映射回实际的标签组合,例如 7 = Highway exit + Big store + Grid capacity <- all of them。最初,这被认为在 ASCII 中不可能实现——数字区域技术干净地解决了这个问题;现在不再有韦恩图需要 PNG 回退的情况。

但在使用它之前,请先通过上面的 2 秒测试——数字图例本身正是导致一个真实韦恩图让普通读者感到困惑的原因。 只有当重叠程度是发现点时,韦恩图才有其价值(例如,“您的一星评论中有 38% 也提到了等待时间”)。当实际信息是“您需要这三者一起,没有部分学分”时,即使韦恩图渲染正确,它也是一个错误的工具——纯文本可以无需图例地完成这项工作,例如:

Highway exit  +  Big store  +  Grid capacity
        miss one of these, skip the site

饼图 / 环形图

from ascii_chart import pie, donut

diagram, legend = pie([("Coffee", 40), ("Hot food", 25), ("Drinks", 20), ("Lottery", 15)], radius=9)
print(diagram); print(legend)

diagram, legend = donut([("Coffee", 40), ("Hot food", 25), ("Drinks", 20), ("Lottery", 15)],
                         radius=9, center_text="247")
print(diagram); print(legend)

切片从 12 点钟方向开始顺时针排列(标准的饼图惯例),每个切片获得一个独特的填充字符(█▓▒░▚▞▤▥,在典型的 2-6 个切片计数中,相邻切片不会重复),使得边界在没有颜色的情况下仍然可辨。donut 是带有空心中心的 pie——center_text 将标题数字/标签置于中心(例如总数),这是 ASCII 环形图能够做到而真实环形图通常浪费在空白空间上的事情之一。radius 以行为单位;函数内部自动修正等宽字符的宽高比(共享的 ASPECT 常量,venn() 也使用),因此结果是真正的圆形,而不是扁平的椭圆——不要单独调整宽度/高度,只需传入 radius。

修正: ASPECT 原本硬编码为假设的 2.0,从未……

……[截断以进行翻译]

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.

相关技能