Communitygithub.com

2016judea/ascii-charts

プレーンなUnicodeブロック文字で棒グラフ、レンジ、バレット、ギャップブラケット、スター・ヒストグラム、ヒートマップ、折れ線、ベン図、円グラフ、ドーナツ、散布図を構築します。依存関係のないPythonスクリプトで、モノスペーステキストだけが生き残るあらゆる場所(ニュースレターのコードブロック、Markdown、ターミナル、プレーンテキストメール、チャット)で使用できます。画像やサードパーティのチャートサービスではなく、リテラルテキストとしてチャートを埋め込む必要がある場合や、ブランディングフッターのない手作り感のあるターミナル風の外観が必要な場合に使用します。

ascii-charts とは?

このスキルは、依存関係のないPythonスクリプト`scripts/ascii_chart.py`を使用して、Unicodeブロック文字だけで構成されたターミナル風のチャートを生成します。ニュースレター、Markdown、ターミナル、プレーンテキストメールなど、モノスペースフォントのみが使用可能な環境で、画像や外部サービスに頼らずにチャートをリテラルテキストとして埋め込むことができます。棒グラフ、レンジバー、バレットチャート、ギャップブラケット、スター・ヒストグラム、ヒートマップ、折れ線グラフ、スパークライン、ベン図、円グラフ、ドーナツグラフ、散布図など、多数のチャートタイプをサポートしています。各チャートは、色を使わずに文字の濃淡(`█▓▒░`)や記号(`+┃★☆●`など)で情報を表現し、`render()`関数でラベルと値の整列を自動処理します。さらに、このツールは「2秒テスト」という設計哲学を持ち、チャートが一目で理解できるかを重視し、凡例なしで意味が伝わらないチャートは使用を推奨しません。これにより、読者に負担をかけず、明確なデータ可視化を提供します。

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

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

ASCII チャート

プレーンなUnicodeブロック文字で構築され、リテラルテキストとして埋め込まれるターミナル風チャート:Markdown内のフェンスドコードブロック、HTML内の<pre>タグ。チャートサービスなし、「made with」フッターなし、外部アカウント不要、そしてモノスペースがレンダリングされる場所ならどこでも同じように表示されます。

scripts/ascii_chart.py にはチャートタイプごとに1つの関数があります。python3 scripts/ascii_chart.py を実行すると、すべてのタイプの例が1つずつ出力され、正常性確認ができます。

ここでは2つのことが測定(当初は誤っていました)されています:390pxの電話ビューポートでは、約44文字を超える行は右端がクリップされ、モノスペースで「丸い」円を描くには約2.49(2.0ではない)の水平アスペクト補正が必要です。「サイジング」と「円グラフ/ドーナツ」のセクションを参照してください。

ニュースレタープラットフォームによって、コードブロックの貼り付け方は異なります。実際に依存する前に、テストチャートを実際のドラフトに貼り付け、Web、アプリ、メールでのレンダリングを確認してください。

文字セット

  • █ ▓ ▒ ░ (U+2588, U+2593, U+2592, U+2591 — 全/濃/中/淡シェード) — 塗りつぶし密度。「最も多い」から「最も少ない」まで。棒グラフは█/░のみ(塗りつぶしvs.トラック)を使用;円グラフ/ドーナツは4つすべて(+スライスが4つを超える場合は▚/▞/▤/▥)をスライスごとの個別の塗りつぶしとして使用;ヒートマップは4つすべてに加えて最低レベルに·(中央ドット、空白ではない)を使用するため、「静かな」セルも意図的なグリッドの一部として読み取られ、ギャップではありません。
  • 数字1–7 — ベン図領域コード(領域が属するセットのビットマスク — 以下のベン図を参照)
  • + — 表示されている上限よりも高くなる可能性があるレンジバー上のオープンエンドフラグ(持っていない数字を偽造するためにバー自体を伸ばさない)
  • ┃ — バレットチャートのターゲットティック。下にある塗りつぶし/シェーディングの上に描画されるため、常に表示されます。
  • └─┘ — ギャップブラケット。2つのバーの端の間にまたがります。
  • ★/☆ — 塗りつぶし/白抜きの星。スター・ヒストグラムの行ラベルを兼ねます。
  • 罫線 (─│╭╮╰╯) — 折れ線グラフとトレンド線のコネクタ。
  • ● — 散布図の点マーカー。

どこにも色はありません。それは媒体であり、回避すべき制限ではありません — 貼り付けられたSubstackのコードブロックはリテラル文字であり、それ以外は何も残りません(インラインCSS、スタイル付きスパンは不可)。チャートが行う必要のある区別はすべて、利用できない色チャネルではなく、文字/数字/凡例に生きています。

チャートタイプメニュー

用途関数使用例
すべて0から始まる、複数のカテゴリの大きさ比較bar(value, max_value, width) + render()ガス充填(5分)vs. DC急速充電(45分)
1カテゴリの最小値–最大値の範囲range_bar(start, end, scale_max, width, open_ended=) + render()「$100k–$300k+ 導入済み」
2つの値、前/後プレーンテキスト:f"{start} → {end}"「卸売 $220 → タップルーム $650」 — 棒グラフは何も追加しません;矢印自体がチャートです
ベンチマーク帯とターゲットに対する1つの測定値bullet(value, scale_max, target=, benchmark=) + render()「あなたの評価:4.2、通常帯域 3.8–4.5、ターゲット 4.7」
2つのバー間のギャップを明示的に指摘gap_bracket(a, b, scale_max, width, label=) を render(bracket=) 経由で使用「あなた vs. 市場中央値」 — レポートの特徴的な動き
1–5(または同様の)評価分布star_histogram(counts)Google/Yelp スター内訳、星の数ごとに1行
2つのカテゴリ軸、強度でシェーディングheatmap(matrix, row_labels, col_ticks=)曜日×時間のフットトラフィック — 「いつ忙しい/暇か」
1系列の経時トレンドline_chart(values, height, x_labels=)月間収益、1年間のレビュー数
文中のインライントレンドsparkline(values)「月間レビュー数:▂▃▅▆█▇▅▃、3月から3倍増加」
2つまたは3つの名前付きセットで、重なりの程度自体が発見事項venn(labels)「あなたの1つ星レビューの38%が待ち時間にも言及しています」 — 重なりの大きさがポイント
全体の一部、1つのスナップショットpie(slices, radius)収益構成:コーヒー / 温かい料理 / 飲み物 / 宝くじ
全体の一部、見出しの合計付きdonut(slices, radius, center_text=)同上、「247件のレビュー」が中空の中心に表示される
2つの数値変数、関係性はあるかscatter(points, width, height)競合他社間の価格 vs. 評価
同上、さらに「そしてこれがトレンド」scatter_with_trend(points, width, height)同上、相関を証明する近似線付き

この表に当てはまるものが何もない場合は、適切でない形状を無理に使うのではなく、立ち止まって考えてください — 技術的にはレンダリング可能だが混乱を招くチャートは、ChromeヘッドレスPNGパイプラインに戻るよりも悪いです。

チャートの2秒テスト

チャートは良い文章と同じ基準に答える必要があります:疲れた読者が電話で、一目で、読み返さず、解読せずに理解できること。チャートタイプを選ぶ前にこのテストを適用し、構築した後ではなく適用してください。

2秒テスト: 意図された読者(データアナリストではなく、中小企業の経営者)が、最初に凡例を読むことなく、2秒以内にこのチャートが何を意味するか言えるでしょうか? 1つの領域やスライスを理解するためにまず凡例を解読する必要がある場合、それは専門用語の視覚的同等物です:技術的に正確ですが、読者に作業を強いることになり、読者を引き込むのではなく遠ざけます。

実際の課題で発見されました (編集者が公開された投稿を冷ややかに読んで):

  • 3セットのベン図デジットビットマスク図は、どの領域も意味を持つ前に7行の凡例を読む必要がありました。編集者:彼らは理解できなかったので、一般の事業主も理解できないでしょう。根底にある事実「これら3つすべてが必要であり、1つだけではない」は一文であり、図の直後の番号付きリストですでに平易な英語で述べられていました。図は、散文がすでに明確に伝えている情報に加えて何も提供せず、解読コストを追加しました。修正:削除し、文+リストに任せる。
  • DC急速充電器のコストレンジバー(単一のバー、軸なし、比較ポイントなし)には比較対象がありませんでした。バーの価値は、何かに対する大きさを示すことから生まれます — スケール、別のバー、ベンチマーク。基準点のない単独のバーは装飾的であり、情報的ではありません。それが示していた数字($100k–$300k+)は、1文前にすでに明確に述べられていました。修正:これも削除。

結果としてのルール:

  • 読むために独自の凡例を必要とするチャートは警告サインです — ベン図だけではありません。ラベルのない軸を持つヒートマップ、または読者が頭の中で保持できる数を超えるスライスを持つ円グラフも、同じように失敗します。
  • 平易な文がチャートと同じくらい明確に事実を述べている場合は、チャートを省略します。これは、散文で「いくつかの/多くの/様々な」を実際の数に置き換えるのと同じ感覚です:比較やトレンドがポイントだから視覚的表現に頼るのであって、データに形状があるからだけではありません。
  • すべてのバーには軸(render(..., axis=))か、同じスケールで比較するための2番目のバーが必要です。どちらもない単一のバーは何も伝えません — これは、実質的な主語のない受動態の文と同じ失敗です。しかし、それが満たされたら、各行の値ラベルにすでにある数字を繰り返すだけの場合は軸を削除します — あるガスvs.充電のバーは、まさにこの理由で「0 ... 45 min」の軸線を削除しました:両方の行がすでに独自の値(「5 min」、「45 min」)を持っているため、軸は「45 min」を2回目に繰り返すだけでした。文から冗長な節を削除するのと同じ原理です。
  • すでに1つのきれいな文になっている数字はチャート化しないでください。「$100k–$300k+ 導入済み」はテキストとして最大限明確です;それをチャート化しても比較、トレンド、重なりは明らかにならず、見返りのない解読ステップを追加するだけです。
  • 完成したチャートを、実際の読者が読むように冷ややかに読んでください — ガソリンスタンドの経営者が電話で、ちょうどそれを構築して何を言うべきかすでに知っている人ではありません。

ワークフロー

自分で文字を手動で整列させたりジオメトリをラスタライズしたりするのではなく、scripts/ascii_chart.py を使用してください — 列の計算と円/角度の計算は、目視で微妙に間違えやすく、後になって1文字の位置ずれや歪んだ円としてしか現れない種類のものです。python3 scripts/ascii_chart.py をいつでも実行すると、すべてのチャートタイプの例が1つずつ出力され、正常性確認ができます。

棒グラフ / レンジバー

from ascii_chart import bar, range_bar, render

rows = [
    ("ガス充填", bar(5, 45, 24), "5 min"),
    ("DC急速充電", bar(45, 45, 24), "45 min"),
]
print(render(rows, axis=("0", "45 min")))

row = ("DC急速充電器", 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 = [("平均評価", bullet(4.2, 5.0, target=4.7, benchmark=(3.8, 4.5), width=24), "4.2 (ターゲット 4.7)")]
print(render(rows, axis=("0", "5.0")))

測定された value の実線バーが、オプションのシェーディングされた benchmark 帯域(「正常」範囲、▒)の上に描画され、オプションの target ティック(┃、下の塗りつぶし/シェーディングの上に常に表示されるように最後に描画)が付きます。「私は正常範囲内か、ターゲットからどれくらい離れているか」を一目で答えます — 実際のバーは1つだけ(測定値)であり、帯域はコンテキストです。

ギャップブラケット

from ascii_chart import bar, gap_bracket, render

rows = [("あなた", bar(42000, 60000, 24), "$42,000"), ("市場中央値", bar(58000, 60000, 24), "$58,000")]
print(render(rows, bracket=gap_bracket(42000, 58000, 60000, 24)))

2つのバーの端の間にまたがる └─┘ ブラケットで、ギャップのサイズがその中央に表示されます(2つの値から自動フォーマットされるか、独自のテキストのために 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, ["月", "火", "水", "木", "金", "土", "日"],
              col_ticks=[(0, "午前0時"), (6, "午前6時"), (12, "午後12時"), (18, "午後6時")]))

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=("1月", "9月")))

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

line_chart は罫線コーナー(╭╮╰╯│─)で接続線を描画します — 任意の中間勾配の文字はないため、幅は len(values) - 1 列(セグメントごとに1つ)に固定されます。x_labels が指定された場合、それは単に (first, last) で、軸行に印刷されます — 中間の点については周囲の散文で説明し、すべてのティックにラベルを付けようとしないでください。sparkline は、単一行の8レベルトレンド(▁▂▃▄▅▆▇█)で、文の中の1節のためにあり、スタンドアローンのチャートではありません。

ベン図(2つまたは3つのセット)

from ascii_chart import venn

diagram, legend = venn(["高速道路出口", "大型店舗", "送電容量"])
print(diagram)
print(legend)

重なり合う円をラスタライズし、各領域を数字(それが属するセットのビットマスク:単一セットは 1、2、4、ペアワイズ重なりは 3/5/6、3つすべては 7)で塗りつぶします。これは、曲線境界内にラベルテキストを収めようとする代わりであり、モノスペースレンダリングでは機能しません。凡例(別々に返されるので、両方を印刷します)は、各数字を実際のラベルの組み合わせにマッピングします。例:7 = 高速道路出口 + 大型店舗 + 送電容量 <- すべて。これは当初ASCIIでは不可能だと考えられていました(初期の投稿ではベン図にヘッドレスChrome PNGにフォールバックしていました) — 数字領域テクニックがこれをきれいに解決します;ベン図がPNGフォールバックを必要とするケースはもはやありません。

しかし、それを使う前に上記の2秒テストを実行してください — 数字の凡例自体が、実際のベン図を一般の読者にとって混乱させたものです。 ベン図は、重なりの程度が発見事項である場合にその価値を発揮します(例:「あなたの1つ星レビューの38%が待ち時間にも言及しています」)。実際のメッセージが「これら3つすべてが一緒に必要で、部分的なクレジットはありません」である場合、ベン図は正しくレンダリングされても間違ったツールです — プレーンテキストが凡例なしでその仕事をします。例:

高速道路出口  +  大型店舗  +  送電容量
        これらを1つでも欠くと、サイトをスキップ

円グラフ / ドーナツ

from ascii_chart import pie, donut

diagram, legend = pie([("コーヒー", 40), ("温かい料理", 25), ("飲み物", 20), ("宝くじ", 15)], radius=9)
print(diagram); print(legend)

diagram, legend = donut([("コーヒー", 40), ("温かい料理", 25), ("飲み物", 20), ("宝くじ", 15)],
                         radius=9, center_text="247")
print(diagram); print(legend)

スライスは12時の位置から始まり時計回りに進みます(標準的な円グラフの慣習)。各スライスには個別の塗りつぶし文字(█▓▒░▚▞▤▥、典型的な2~6スライスの場合、隣接するスライスで繰り返されることはありません)が割り当てられ、色なしでも境界が判読可能です。donut は中空の中心を持つ pie です — center_text はそこに見出しの数字/ラベル(例:総数)を配置します。これはASCIIドーナツが実際のドーナツが通常空きスペースで無駄にすることを1つだけ実行できることです。radius は行単位です;関数はモノスペース文字のアスペクト比(共有の ASPECT 定数、venn() でも使用)を内部で補正するため、結果は実際に円形になり、平らな楕円にはなりません — 幅/高さを個別に手動調整せず、単に radius を渡してください。

補正: ASPECT は想定された 2.0 にハードコードされており、決して検証されていませんでした。2.49 がモノスペースの実際の補正であり、結果として得られる円が視覚的に正しい唯一のものです。以前のすべての出力は垂直方向にわずかに圧縮された楕円でした。サイズは適切に見えていたため、誰も気づきませんでした。

散布図

from ascii_chart import scatter, scatter_with_trend


print(scatter([(2, 3), (5, 8), (7, 12), (10, 9), (12, 15), (15, 14), (18, 18), (20, 20)],
               width=20, height=10))

print(scatter_with_trend([(2, 3), (5, 8), (7, 12), (10, 9), (12, 15), (15, 14), (18, 18), (20, 20)],
                          width=20, height=10))

points は (x, y) タプルのリストで、width と height は描画キャンバスの文字単位の寸法です(データ座標ではありません — 関数はすべての点が収まるように正規化します。scatter_with_trend は、正規化されたデータに最小二乗線を追加します。

両関数とも、軸ラベル、グリッド線、または凡例は追加しません — これらは散らかりを追加し、読者がそれらを解読しようとすることで2秒テストに失敗します。代わりに、軸の範囲を周囲の散文で説明し、データが何を示しているかを明確にします。

FAQ

これらのチャートをどこに貼り付けられますか?

これらはリテラルテキストです — Markdownのコードブロック(Gist、GitHub、Substack、Ghost、Notion)、<pre> HTMLタグ、ターミナル出力、チャットアプリ、プレーンテキストメール。モノスペースフォントでレンダリングされる任意の場所。

特定のプラットフォーム向けにテストする必要がありますか?

はい。特にニュースレター(Substack、Buttondown、ConvertKitなど)は、特に長い行を異なる方法で処理する場合があります。実際の下書きにテスト出力を貼り付け、Webとモバイルの両方のプレビューで確認してください。

このツールは私のデータをどこかに送信しますか?

いいえ。すべてはローカルで実行され、依存関係はなく、ネットワークコールもありません。スクリプトは保護された顧客データを決して扱いません。

色が必要な場合はどうすればいいですか?

色は達成しようとしている媒体ではありません。これらのチャートは、貼り付けられたテキストとして生き残る文字に依存しています。色付きの出力が必要な場合は、別のツール(Chromeヘッドレス、Matplotlibなど)が必要になります。

より多くの点を持つ散布図用の文字はありますか?

現在の実装は単一のマーカー ● です。より多くの点がある場合(例:50+)、重なり合う点は依然として1つとしてレンダリングされます。エリアが高密度で色なしの点の塊になるため、これはデータ密度が高すぎることを示しています。その場合は、上記の2秒テストを再検討し、テキストでデータを要約することを検討してください。

なぜASPECTは2.0ではなく2.49なのですか?

2.0は、モノスペースフォントでの実際の表示アスペクト比についての想定でした。最も狭いテスト(半径の異なる2つの描画円を視覚的に比較する)は、2.49 が視覚的に正しい唯一の値であることを示しています。2.0 を使用した以前のすべての出力は、垂直方向にわずかに圧縮された楕円でした。以前の出力はサイズ的には適切に見えていたため、このエラーは何ヶ月も気づかれませんでした — 歪みは微妙で、出力を他のものと比較しない限り目立ちませんでした。

このコードを自分で変更できますか?

はい。スクリプトは単一のファイル scripts/ascii_chart.py で、依存関係はなく、変更が容易です。サポートされているチャートタイプのいずれかの上に新しいチャートタイプを追加したり、文字セットを変更したりできます。

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.

関連スキル