mcpField
J
json-canvas

json-canvas

github.com/julianobarbosa生産性15102 回インストール3 日前に更新
リポジトリ

json-canvas について

json-canvasは、Claude CodeでJSON Canvas形式の`.canvas`ファイルを作成・編集するスキルです。ノードやエッジの構造、配置、検証時の注意点を説明し、キャンバスファイルの作成を支援します。

主な機能

  1. 01`.canvas`ファイルの作成と編集
  2. 02テキスト、ファイル、リンク、グループのノード
  3. 03ラベル付きエッジによるノード接続
  4. 04色と背景スタイルの設定
  5. 05配置と検証のガイド

活用シーン

  1. 01Obsidianキャンバスの新規作成
  2. 02ノードの位置や重なり順の編集
  3. 03フローチャートやリサーチボードの作成
  4. 04保存前のエッジとフィールドの確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/julianobarbosa/claude-code-skills --skill json-canvas

インストール後、Claude Code で/json-canvasを入力して読み込まれたか確認します。

詳細

JSON Canvas スキル

このスキルにより、Claude Code は Obsidian などのアプリケーションで使用される、有効な JSON Canvas ファイル(.canvas)を作成・編集できるようになります。

概要

JSON Canvas は、無限キャンバスのデータдля 扱う オープンなファイル形式です。Canvas ファイルは .canvas 拡張子を使用し、JSON Canvas Spec 1.0 に従った有効な JSON を含みます。

ファイル構造

Canvas ファイルには、トップレベルの配列が 2 つ含まれます:

{
  "nodes": [],
  "edges": []
}
  • nodes(任意):ノードオブジェクトの配列
  • edges(任意):ノード同士を接続するエッジオブジェクトの配列

ノード

ノードは、キャンバス上に配置されるオブジェクトです。ノードには 4 種類があります:

  • text - Markdown を含むテキストコンテンツ
  • file - ファイル/添付ファイルへの参照
  • link - 外部 URL
  • group - 他のノードを囲む視覚的なコンテナ
Z インデックスの順序

ノードは配列内の z インデックスによって順序付けられます:

  • 最初のノード = 最下層(他のノードより下に表示)
  • 最後のノード = 最上層(他のノードより上に表示)
共通のノード属性

すべてのノードは、以下の属性を共有します:

属性必須型説明
idはいstringノードの一意な識別子
typeはいstringノードの種類:text、file、link、または group
xはいintegerピクセル単位の X 座標
yはいintegerピクセル単位の Y 座標
widthはいintegerピクセル単位の幅
heightはいintegerピクセル単位の高さ
colorいいえcanvasColorノードの色(Color セクションを参照)
テキストノード

テキストノードには Markdown コンテンツが含まれます。

{
  "id": "6f0ad84f44ce9c17",
  "type": "text",
  "x": 0,
  "y": 0,
  "width": 400,
  "height": 200,
  "text": "# Hello World\n\nThis is **Markdown** content."
}
属性必須型説明
textはいstringMarkdown 構文を含むプレーンテキスト
ファイルノード

ファイルノードは、ファイルや添付ファイル(画像、動画、PDF、ノートなど)を参照します。

{
  "id": "a1b2c3d4e5f67890",
  "type": "file",
  "x": 500,
  "y": 0,
  "width": 400,
  "height": 300,
  "file": "Attachments/diagram.png"
}
{
  "id": "b2c3d4e5f6789012",
  "type": "file",
  "x": 500,
  "y": 400,
  "width": 400,
  "height": 300,
  "file": "Notes/Project Overview.md",
  "subpath": "#Implementation"
}
属性必須型説明
fileはいstringシステム内のファイルへのパス
subpathいいえstring見出しまたはブロックへのリンク(# で始まる)
リンクノード

リンクノードは外部 URL を表示します。

{
  "id": "c3d4e5f678901234",
  "type": "link",
  "x": 1000,
  "y": 0,
  "width": 400,
  "height": 200,
  "url": "https://obsidian.md"
}
属性必須型説明
urlはいstring外部 URL
グループノード

グループノードは、他のノードを整理するための視覚的なコンテナです。

{
  "id": "d4e5f6789012345a",
  "type": "group",
  "x": -50,
  "y": -50,
  "width": 1000,
  "height": 600,
  "label": "Project Overview",
  "color": "4"
}
{
  "id": "e5f67890123456ab",
  "type": "group",
  "x": 0,
  "y": 700,
  "width": 800,
  "height": 500,
  "label": "Resources",
  "background": "Attachments/background.png",
  "backgroundStyle": "cover"
}
属性必須型説明
labelいいえstringグループのテキストラベル
backgroundいいえstring背景画像へのパス
backgroundStyleいいえstring背景の描画スタイル
背景スタイル
値説明
coverノードの幅と高さ全体を埋める
ratio背景画像のアスペクト比を維持する
repeat画像を縦横両方向にパターンとして繰り返す

エッジ

エッジは、ノード同士を結ぶ線です。

{
  "id": "f67890123456789a",
  "fromNode": "6f0ad84f44ce9c17",
  "toNode": "a1b2c3d4e5f67890"
}
{
  "id": "0123456789abcdef",
  "fromNode": "6f0ad84f44ce9c17",
  "fromSide": "right",
  "fromEnd": "none",
  "toNode": "b2c3d4e5f6789012",
  "toSide": "left",
  "toEnd": "arrow",
  "color": "1",
  "label": "leads to"
}
属性必須型デフォルト説明
idはいstring-エッジの一意な識別子
fromNodeはいstring-接続の開始元となるノードの ID
fromSideいいえstring-エッジが開始する辺
fromEndいいえstringnoneエッジ開始端の形状
toNodeはいstring-接続の終了先となるノードの ID
toSideいいえstring-エッジが終了する辺
toEndいいえstringarrowエッジ終了端の形状
colorいいえcanvasColor-線の色
labelいいえstring-エッジのテキストラベル
辺の値
値説明
topノードの上辺
rightノードの右辺
bottomノードの下辺
leftノードの左辺
端点の形状
値説明
none端点の形状なし
arrow矢印の端点

色

canvasColor 型は、次の 2 つの方法で指定できます。

16 進数カラー
{
  "color": "#FF0000"
}
プリセットカラー
{
  "color": "1"
}
プリセット色
"1"赤
"2"オレンジ
"3"黄色
"4"緑
"5"シアン
"6"紫

注:アプリケーションが独自のブランドカラーを使用できるよう、プリセットの具体的な色の値は意図的に定義されていません。

完全な例

テキストと接続を含むシンプルなキャンバス
{
  "nodes": [
    {
      "id": "8a9b0c1d2e3f4a5b",
      "type": "text",
      "x": 0,
      "y": 0,
      "width": 300,
      "height": 150,
      "text": "# Main Idea\n\nThis is the central concept."
    },
    {
      "id": "1a2b3c4d5e6f7a8b",
      "type": "text",
      "x": 400,
      "y": -100,
      "width": 250,
      "height": 100,
      "text": "## Supporting Point A\n\nDetails here."
    },
    {
      "id": "2b3c4d5e6f7a8b9c",
      "type": "text",
      "x": 400,
      "y": 100,
      "width": 250,
      "height": 100,
      "text": "## Supporting Point B\n\nMore details."
    }
  ],
  "edges": [
    {
      "id": "3c4d5e6f7a8b9c0d",
      "fromNode": "8a9b0c1d2e3f4a5b",
      "fromSide": "right",
      "toNode": "1a2b3c4d5e6f7a8b",
      "toSide": "left"
    },
    {
      "id": "4d5e6f7a8b9c0d1e",
      "fromNode": "8a9b0c1d2e3f4a5b",
      "fromSide": "right",
      "toNode": "2b3c4d5e6f7a8b9c",
      "toSide": "left"
    }
  ]
}
グループを含むプロジェクトボード
{
  "nodes": [
    {
      "id": "5e6f7a8b9c0d1e2f",
      "type": "group",
      "x": 0,
      "y": 0,
      "width": 300,
      "height": 500,
      "label": "To Do",
      "color": "1"
    },
    {
      "id": "6f7a8b9c0d1e2f3a",
      "type": "group",
      "x": 350,
      "y": 0,
      "width": 300,
      "height": 500,
      "label": "In Progress",
      "color": "3"
    },
    {
      "id": "7a8b9c0d1e2f3a4b",
      "type": "group",
      "x": 700,
      "y": 0,
      "width": 300,
      "height": 500,
      "label": "Done",
      "color": "4"
    },
    {
      "id": "8b9c0d1e2f3a4b5c",
      "type": "text",
      "x": 20,
      "y": 50,
      "width": 260,
      "height": 80,
      "text": "## Task 1\n\nImplement feature X"
    },
    {
      "id": "9c0d1e2f3a4b5c6d",
      "type": "text",
      "x": 370,
      "y": 50,
      "width": 260,
      "height": 80,
      "text": "## Task 2\n\nReview PR #123",
      "color": "2"
    },
    {
      "id": "0d1e2f3a4b5c6d7e",
      "type": "text",
      "x": 720,
      "y": 50,
      "width": 260,
      "height": 80,
      "text": "## Task 3\n\n~~Setup CI/CD~~"
    }
  ],
  "edges": []
}
ファイルとリンクを含むリサーチキャンバス
{
  "nodes": [
    {
      "id": "1e2f3a4b5c6d7e8f",
      "type": "text",
      "x": 300,
      "y": 200,
      "width": 400,
      "height": 200,
      "text": "# Research Topic\n\n## Key Questions\n\n- How does X affect Y?\n- What are the implications?",
      "color": "5"
    },
    {
      "id": "2f3a4b5c6d7e8f9a",
      "type": "file",
      "x": 0,
      "y": 0,
      "width": 250,
      "height": 150,
      "file": "Literature/Paper A.pdf"
    },
    {
      "id": "3a4b5c6d7e8f9a0b",
      "type": "file",
      "x": 0,
      "y": 200,
      "width": 250,
      "height": 150,
      "file": "Notes/Meeting Notes.md",
      "subpath": "#Key Insights"
    },
    {
      "id": "4b5c6d7e8f9a0b1c",
      "type": "link",
      "x": 0,
      "y": 400,
      "width": 250,
      "height": 100,
      "url": "https://example.com/research"
    },
    {
      "id": "5c6d7e8f9a0b1c2d",
      "type": "file",
      "x": 750,
      "y": 150,
      "width": 300,
      "height": 250,
      "file": "Attachments/diagram.png"
    }
  ],
  "edges": [
    {
      "id": "6d7e8f9a0b1c2d3e",
      "fromNode": "2f3a4b5c6d7e8f9a",
      "fromSide": "right",
      "toNode": "1e2f3a4b5c6d7e8f",
      "toSide": "left",
      "label": "supports"
    },
    {
      "id": "7e8f9a0b1c2d3e4f",
      "fromNode": "3a4b5c6d7e8f9a0b",
      "fromSide": "right",
      "toNode": "1e2f3a4b5c6d7e8f",
      "toSide": "left",
      "label": "informs"
    },
    {
      "id": "8f9a0b1c2d3e4f5a",
      "fromNode": "4b5c6d7e8f9a0b1c",
      "fromSide": "right",
      "toNode": "1e2f3a4b5c6d7e8f",
      "toSide": "left",
      "toEnd": "arrow",
      "color": "6"
    },
    {
      "id": "9a0b1c2d3e4f5a6b",
      "fromNode": "1e2f3a4b5c6d7e8f",
      "fromSide": "right",
      "toNode": "5c6d7e8f9a0b1c2d",
      "toSide": "left",
      "label": "visualized by"
    }
  ]
}
フローチャート
{
  "nodes": [
    {
      "id": "a0b1c2d3e4f5a6b7",
      "type": "text",
      "x": 200,
      "y": 0,
      "width": 150,
      "height": 60,
      "text": "**Start**",
      "color": "4"
    },
    {
      "id": "b1c2d3e4f5a6b7c8",
      "type": "text",
      "x": 200,
      "y": 100,
      "width": 150,
      "height": 60,
      "text": "Step 1:\nGather data"
    },
    {
      "id": "c2d3e4f5a6b7c8d9",
      "type": "text",
      "x": 200,
      "y": 200,
      "width": 150,
      "height": 80,
      "text": "**Decision**\n\nIs data valid?",
      "color": "3"
    },
    {
      "id": "d3e4f5a6b7c8d9e0",
      "type": "text",
      "x": 400,
      "y": 200,
      "width": 150,
      "height": 60,
      "text": "Process data"
    },
    {
      "id": "e4f5a6b7c8d9e0f1",
      "type": "text",
      "x": 0,
      "y": 200,
      "width": 150,
      "height": 60,
      "text": "Request new data",
      "color": "1"
    },
    {
      "id": "f5a6b7c8d9e0f1a2",
      "type": "text",
      "x": 400,
      "y": 320,
      "width": 150,
      "height": 60,
      "text": "**End**",
      "color": "4"
    }
  ],
  "edges": [
    {
      "id": "a6b7c8d9e0f1a2b3",
      "fromNode": "a0b1c2d3e4f5a6b7",
      "fromSide": "bottom",
      "toNode": "b1c2d3e4f5a6b7c8",
      "toSide": "top"
    },
    {
      "id": "b7c8d9e0f1a2b3c4",
      "fromNode": "b1c2d3e4f5a6b7c8",
      "fromSide": "bottom",
      "toNode": "c2d3e4f5a6b7c8d9",
      "toSide": "top"
    },
    {
      "id": "c8d9e0f1a2b3c4d5",
      "fromNode": "c2d3e4f5a6b7c8d9",
      "fromSide": "right",
      "toNode": "d3e4f5a6b7c8d9e0",
      "toSide": "left",
      "label": "Yes",
      "color": "4"
    },
    {
      "id": "d9e0f1a2b3c4d5e6",
      "fromNode": "c2d3e4f5a6b7c8d9",
      "fromSide": "left",
      "toNode": "e4f5a6b7c8d9e0f1",
      "toSide": "right",
      "label": "No",
      "color": "1"
    },
    {
      "id": "e0f1a2b3c4d5e6f7",
      "fromNode": "e4f5a6b7c8d9e0f1",
      "fromSide": "top",
      "fromEnd": "none",
      "toNode": "b1c2d3e4f5a6b7c8",
      "toSide": "left",
      "toEnd": "arrow"
    },
    {
      "id": "f1a2b3c4d5e6f7a8",
      "fromNode": "d3e4f5a6b7c8d9e0",
      "fromSide": "bottom",
      "toNode": "f5a6b7c8d9e0f1a2",
      "toSide": "top"
    }
  ]
}

ID の生成

ノードとエッジの ID は一意の文字列である必要があります。Obsidian は 16 文字の 16 進数 ID を生成します。

"id": "6f0ad84f44ce9c17"
"id": "a3b2c1d0e9f8g7h6"
"id": "1234567890abcdef"

この形式は、16 文字の小文字の 16 進数文字列(64 ビットのランダム値)です。

レイアウトのガイドライン

配置
  • 座標には負の値を指定できます(キャンバスは無限に広がります)
  • x は右方向に増加します
  • y は下方向に増加します
  • 位置はノードの左上隅を指します
推奨サイズ
ノードタイプ推奨幅推奨高さ
小さいテキスト200-30080-150
中くらいのテキスト300-450150-300
大きいテキスト400-600300-500
ファイルプレビュー300-500200-400
リンクプレビュー250-400100-200
グループ可変可変
間隔
  • グループ内には 20-50px の余白を設けます
  • 読みやすさのため、ノード同士を 50-100px 離します
  • すっきりしたレイアウトにするため、ノードをグリッド(10 または 20 の倍数)に揃えます

検証ルール

  1. すべての id 値はノードとエッジ全体で一意である必要があります
  2. fromNode と toNode は、既存のノード ID を参照する必要があります
  3. 各ノードタイプに必要なフィールドが存在する必要があります
  4. type は次のいずれかである必要があります:text、file、link、group
  5. backgroundStyle は次のいずれかである必要があります:cover、ratio、repeat
  6. fromSide と toSide は次のいずれかである必要があります:top、right、bottom、left
  7. fromEnd と toEnd は次のいずれかである必要があります:none、arrow
  8. カラープリセットには "1" から "6" のいずれか、または有効な 16 進数カラーを指定する必要があります

参考資料


注意点

  • ノード配列の順序は z-index です: .canvas を読み込むエディターは、array[0] を最背面、array[-1] を最前面に描画します。JSON を並べ替えて「整理」すると、どのノードがどのノードを覆うかが気付かないうちに逆転します。ファイルを開くまで、見た目の差異に気付きにくいでしょう。
  • fromNode/toNode は既存の ID を参照する必要がありますが、JSON には外部キーの整合性チェックがありません: ノードを削除しても関連するエッジを削除しなければ、孤立したエッジが残ります。Obsidian では接続先のない接続として表示されますが、アプリによっては黙って破棄されたり、エラーが発生したりします。ノードを削除するたびにエッジを検証してください。
  • 座標は DPI を考慮しないピクセル単位です: Retina(論理 px)で設計したキャンバスは、1x ディスプレイでは異なる表示になります。scale フィールドはありません。既知の基準に合わせてサイズを事前計算し、マシン間で表示がずれることを受け入れてください。
  • 16 文字の 16 進数 ID は慣例であり、仕様上の必須条件ではありません: 一意の文字列であれば何でも使えます。ただし、Obsidian の「ノードを複製」機能は 16 文字の 16 進数を再生成します。同じファイル内で UUID と 16 進数 ID を混在させると、grep を使った手動編集が困難になります。
  • subpath には # プレフィックスが必要で、一部のアプリでは大文字と小文字が区別されます: subpath: "Heading" は何も表示せず失敗しますが、"#Heading" は機能します。"#heading" が機能するかどうかは、対象ファイルの見出しがその大文字と小文字を正確に一致させているかによります。
  • グループノードは子ノードを所有しません。子ノードの位置を決めるのは座標です: グループを移動しても、その中のノードは移動しません。子ノードの x/y 座標がたまたまグループの境界内にあるため、視覚的にグループ内に見えるだけです。まとめて移動するには、すべての子ノードの座標を更新する必要があります。

コメント