PicoBot/.qoder/skills/lark-sheets/references/lark-sheets-chart.md
ooodc a7883dbed9 refactor(todo): 重构待办事项管理逻辑及更新状态规则
- 移除 TodoItem 中的 priority、created_at 和 updated_at 字段
- 强制每个任务都必须有唯一 id,且由用户负责生成
- 修改合并模式逻辑,merge=true 下保留未提及的旧任务
- 支持已完成和已取消任务重新激活(状态改回 pending 或 in_progress)
- 禁止 in_progress 状态退回到 pending,必须标记为 completed 或 cancelled
- 优化状态转换校验,允许特定状态间合法切换
- 简化任务变更消息,移除详细的新增/更新/移除统计
- 更新文档和示例,明确 id 必须由用户生成和使用
- 修复和补充测试,增强状态转换和合并模式验证
- 调整任务时间戳生成逻辑,统一使用当前时间及索引
- 该变更提供更合理的任务状态机械及管理模式,提升稳定性和易用性
2026-06-13 09:22:33 +08:00

18 KiB
Raw Blame History

Lark Sheet Chart

真对象硬约束

当用户要求"画个图 / 数据可视化 / 趋势图 / 对比图 / 占比图"时,必须通过 +chart-{create|update|delete} 创建真实的图表对象。禁止用本地脚本调 matplotlib / seaborn 生成图片再插入到表格代替——静态图片无法随源数据更新,且失去交互能力。判断标准:交付后 +chart-list 必须能返回该对象。

使用场景

读写图表对象。本 reference 覆盖 4 个 shortcut

操作需求 使用工具 说明
查看已有图表 +chart-list 获取图表的类型、数据源和样式配置
创建/更新/删除图表 `+chart-{create update

典型工作流:先读取现有图表了解配置 → 执行创建/更新/删除 → 再次读取验证结果。

需求→图表类型映射(创建前必查)

用户说 图表类型 备注
"占比"、"比例"、"各XX占多少" 饼图pie 单维度占比首选
"对比"、"各XX的YY" 柱形图column纵向 多类别数值对比;横向条形用 bar
"趋势"、"变化"、"走势" 折线图line 时间序列首选
"堆积"、"组成构成" 堆积柱形图column + stack 多系列累加
"分布"、"相关性" 散点图scatter 两变量关系

多图表需求:当用户同时提到多种分析(如"统计占比 + 对比数量"),必须创建多个图表,每个对应一种类型,不要只做一个。

--properties 结构锚点(构造前必读)--properties 顶层只有 position / offset / size / snapshot 四个字段,没有顶层 data,也没有再嵌一层 properties。图表数据配置全部挂在 snapshot.data 下——下文及示例里出现的 refs / headerMode / dim1 / dim2 / nameRef 一律指 snapshot.data.refs / snapshot.data.headerMode / snapshot.data.dim1 / snapshot.data.dim2(及其下的 serie.nameRef / series[].nameRef);样式 / 堆叠 / 数据标签等在 snapshot.plotArea 下。完整结构以 lark-cli sheets +chart-create --print-schema --flag-name properties 为准。

常见配置错误(必须注意)

  • 图表类型选择错误:用户说"堆积柱形图/百分比堆积"时,应在 properties.snapshot.plotArea.plot.extra.stack 中配置堆叠;百分比堆叠需在该 stack 下设置 percentage: true。用户说"占比/比例"时,优先考虑饼图或百分比堆积图。注意区分 column(柱形图,纵向)与 bar(条形图,横向)是两个不同的 type 取值,"对比/各 XX" 类纵向柱默认用 column
  • 数据标签缺失:用户需要看到具体数值时,需配置 properties.snapshot.plotArea.plot.labels(数据标签)相关字段
  • 数据源范围与系列名来源要对齐
    • 默认情况inline 模式)refs 范围应包含表头行(首行/首列即系列名),且范围要精确覆盖目标数据,不要多选或少选。
    • 合并标题行要跳过:如果表格在表头上方存在合并的标题行(如"员工统计表"横跨多列的大标题),refs 必须跳过标题行、从真正的列标题行开始。例如表头在第 3 行、数据在第 4-20 行,则 refs 应为 A3:G20 而非 A1:G20。包含合并标题行会导致列名识别错误、表头被当作数据参与聚合计算。
    • 数据与表头分离时必须用 detached 模式:当 refs 只覆盖完整数据的一个子集(按筛选/分组只画其中一段),而真正的语义表头在该子集之外时,必须设置 snapshot.data.headerMode='detached'refs 仅传纯数据范围,维度名/系列名通过 snapshot.data.dim1.serie.nameRef / snapshot.data.dim2.series[].nameRef 指向真正的表头单元格。详见下文"硬性规则:数据与表头分离场景必须使用 detached 模式"。
  • axes[].label 不接受 format / number_format 字段:想给坐标轴数值加千分位、百分号等格式化时,不要在 axes[i].label 里传 formatnumber_formatschema 未定义,会报 unexpected property "format" is not defined in schema)。数值格式化统一在源数据单元格的 cell_styles.number_format 里设置(写 +cells-set 时),图表会沿用单元格格式。
  • 创建后必须验证:图表创建后必须调用 +chart-list 验证配置是否正确

⚠️ 硬性规则:当用户通过列标题名称(而非列索引)指定横轴/纵轴系列时,必须先读取表格首行(表头)来确定列名与列索引的对应关系,再设置 dim1/dim2index 例如用户说"横轴为车型系列纵轴为Q1-Q4的销量",你不能猜测列索引,必须先通过读取表格数据源范围的首行内容(使用 lark-sheets-read-data+cells-get 或其他读取单元格的工具),确认"车型系列"是第几列、"Q1"~"Q4"分别是第几列,然后再将正确的列索引填入 dim1.serie.indexdim2.series[].index

⚠️ 硬性规则:数据与表头分离场景必须使用 detached 模式。refs 仅覆盖数据的一个子集,而真正的语义表头行/列位于该子集之外时,必须 snapshot.data.headerMode='detached' 并配上 nameRef。不能用 inline 模式 + 把 refs 多带 1 行兜底表头来替代——那种写法已废弃。否则图表会把错误的首行/首列当系列名,或图例显示成"系列1/系列2"等默认名,或者 refs 里混入相邻分组的数据。

触发该规则的典型信号(满足任意一条都必须走 detached

  • 用户要求"针对 X 类的数据画图"、"只看某个分组"、"只画筛选后的部分",而 X 类对应的行段在数据中间或末尾,与表头不连续;
  • 用户要求"按 X 分别画图"、"按某个维度(部门/品类/地区/时间段等)拆图"——多张图共享同一组表头
  • refs 起始行 > 表头行(如表头在第 1 行,但 refs 从第 11 行开始);
  • refs 起始列 > 表头列(如表头在 A 列,但 refs 从 C 列开始)。

正确做法

  1. data 下显式设置 "headerMode": "detached"
  2. refs 只覆盖该子集的纯数据,不要向上/向左多带 1 行/列,也不要把全局表头整段并进来(否则会把其它分组的数据混进图);
  3. nameRef 必填:给 dim1.serie.nameRef 写真正表头中"类别名"那一格的 A1 引用(如 'Sheet2'!A1sheet 名按 A1 标准单引号包裹),给每个 dim2.series[i].nameRef 写对应数值列的 A1 引用(如 'Sheet2'!C1'Sheet2'!D1)。任一缺失会被校验拦下并报 headerMode=detached requires ... nameRef
  4. refs[i].value 必须是单元格或普通矩形范围CELL / NORMAL不接受整行/整列/开区间;direction='column' 时起始行必须 > 0direction='row' 时起始列必须 > 0
  5. index 仍按 refs 内的列/行号填,从 1 开始。

两种场景对照(互斥,二选一)

场景 何时命中 写法
A. 表头与数据连在一起 单张图、refs 首行/首列就是表头(典型整段画图) 省略 headerMode(默认 inlinerefs 含表头,不写 nameRef
B. 表头与数据分离 上面 4 条信号任一命中(数据子集、按维度拆图等) headerMode='detached'refs 仅纯数据,nameRef 必填

反向约束:场景 A 下不要写 nameRef——首行命名已经生效,多写反而冗余。nameRef 仅在场景 B 下使用(且必填)。

⚠️ chart 数据源引用 pivot 时必须排除总计行(高频致命错误)

当 chart 要基于刚创建的 pivot 产物画图时,禁止凭猜写 refs。pivot 默认启用 show_row_grand_total / show_col_grand_total,产物最后一行/一列通常是"总计"。如果 refs 把总计行一并框进去:

  • 柱形图末尾会多一根天文数字柱子(=所有数据求和),把其他柱子压扁到看不见
  • 饼图会多一个"总计"扇区占 33%+,真实类别的比例完全失真

正确流程

  1. +pivot-create create 返回 sheet_id + pivot_table_id
  2. +csv-get(sheet_id, 'A1:E30')+pivot-list 读 pivot 产物的实际数据范围
  3. 识别并排除"总计"/"小计"行(通常最后一行;嵌套 pivot 还要排除中间层小计)
  4. +chart-create createsnapshot.data.refs 精确到数据行(如 pivot 占 A1:D9、总计在 row9 → chart 用 A1:D8

图表位置选择(创建前必做)

凭感觉挑列号/行号会被 API 拒(position is out of sheet range)。按以下四步走:

  1. 查尺寸+workbook-info 拿该 sheet 的 row_count / column_count(下文记为 rowCount / columnCount+sheet-info 只返回布局,不含行列总数)。
  2. 估跨度:默认单元格 105 px 宽 × 27 px 高needCols = ceil(width/105)needRows = ceil(height/27)
  3. 校验position.row + needRows ≤ rowCountcol_idx + needCols ≤ columnCountcol 按 A=0、B=1、…、Z=25、AA=26… 换算)。
  4. 不够就先扩表,二选一,禁止硬塞越界位置:
    • 优先放数据下方空区:position = {row: data_end_row + 2, col: "A"}
    • 否则先调 +dim-insertlark-sheets-sheet-structure)扩行/列,再 create。

示例21 列 sheet 放 600×400 图 → needCols=6, needRows=15

  • {row: 0, col: "W"} — col=22 越界
  • {row: 42, col: "A"} — 放数据下方
  • +dim-insert --dimension column --start 21 --end 27(在 U 列后插 6 列U=index 20after 即从 21 起),再放图到 {row: 0, col: "V"}

Shortcuts

Shortcut Risk 分组
+chart-list read 对象
+chart-create write 对象
+chart-update write 对象
+chart-delete high-risk-write 对象

Flags

+chart-list

公共四件套 · 系统:--dry-run

Flag Type 必填 说明
--chart-id string optional 指定单个图表 reference_id 过滤

+chart-create

公共四件套 · 系统:--dry-run

Flag Type 必填 说明
--properties string + File + Stdin复合 JSON required 图表完整配置 JSON。顶层字段为 position / offset / size / snapshot(无顶层 data,也无再嵌一层 properties);图表数据配置在 snapshot.data 下(含 refs / headerMode / dim1 / dim2)。结构嵌套深,完整结构跑 --print-schema --flag-name properties

+chart-update

公共四件套 · 系统:--dry-run

Flag Type 必填 说明
--chart-id string required 目标图表 reference_id
--properties string + File + Stdin复合 JSON required 完整或足够完整的图表配置 JSON+chart-list 回读再 patch

+chart-delete

公共四件套 · 系统:--yes--dry-run

Flag Type 必填 说明
--chart-id string required 目标图表 reference_id

Schemas

复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 ## Examples,或用 --print-schema 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。

+chart-create --properties / +chart-update --properties

创建/更新的图表属性

顶层字段

  • position (object) — 必填 { row: number, col: string }
  • offset (object?) — 可选 { row_offset?: number, col_offset?: number }
  • size (object) — 必填 { width: number, height: number }
  • snapshot (object?) — 图表快照配置 { title?: object, subTitle?: object, style?: object, legend?: oneOf, plotArea: object, …共 6 项 }

Examples

公共四件套:所有 shortcut 顶部排列 --url / --spreadsheet-token / --sheet-id / --sheet-nameXOR 规则同 +csv-get)。

+chart-list

输出契约:返回按工作表分组的图表列表,每个图表含 chart_id / position / details.snapshot 等。

+chart-create

snapshot.data 必填 dim1.serie.indexdim2.series[].index 之一1-based对应 refs.value 范围内的列序。schema 允许传空 {} 但 server 运行时强制:缺则被拒为 snapshot.data.dim1.serie.index and dim2.series[].index are both missing; at least one must be set,即便侥幸通过也只会渲染空图。

最小可用列图inline 模式refs 含表头行):

lark-cli sheets +chart-create --url "https://example.feishu.cn/sheets/shtXXX" \
  --sheet-name "Sheet1" --properties '{
    "position":{"row":42,"col":"A"},
    "size":{"width":600,"height":400},
    "snapshot":{
      "data":{
        "refs":[{"value":"'Sheet1'!A1:B10"}],
        "dim1":{"serie":{"index":1}},
        "dim2":{"series":[{"index":2}]}
      },
      "plotArea":{"plot":{"type":"column"}}
    }
  }'

# 走文件(推荐配置较多时)
lark-cli sheets +chart-create --url "..." --sheet-name "Sheet1" --properties @chart-config.json

饼图专属示例sectors 必须嵌在 plotArea.plot.series[i].sectors.sector[],且 sector[].index 1-based

饼图比 column / bar 更复杂:sectors 是 object里面再包一个单数 sector 数组——CLI 不替你 normalize写错路径会被 server schema 直接拒。

lark-cli sheets +chart-create --url "..." --sheet-name "Sheet1" --properties '{
  "position":{"row":24,"col":"F"},
  "size":{"width":600,"height":450},
  "snapshot":{
    "title":{"text":"各部门员工人数占比"},
    "plotArea":{"plot":{
      "type":"pie",
      "series":[{
        "index":1,
        "sectors":{"sector":[{"index":1,"offsetRadius":0.05}]}
      }]
    }},
    "data":{
      "refs":[{"value":"'Sheet1'!A1:B11"}],
      "dim1":{"serie":{"index":1,"aggregate":true}},
      "dim2":{"series":[{"index":2,"aggregateType":"sum"}]}
    }
  }
}'

数据与表头分离(必须用 detached + nameRef

场景:周度销量明细表,真实表头在第 1 行A1=周次、C1=订单量、D1=退款量),数据按 B 列"店铺"分段;用户只要"3 号店"那一段(第 1117 行)。

lark-cli sheets +chart-create --url "..." --sheet-name "Sheet2" --properties '{
  "position":{"row":7,"col":"F"},
  "size":{"width":600,"height":360},
  "snapshot":{
    "title":{"text":"3 号店周度订单/退款"},
    "plotArea":{"plot":{"type":"column"}},
    "data":{
      "headerMode":"detached",
      "direction":"column",
      "refs":[{"value":"'Sheet2'!A11:D17"}],
      "dim1":{"serie":{"index":1,"nameRef":"'Sheet2'!A1"}},
      "dim2":{"series":[
        {"index":3,"nameRef":"'Sheet2'!C1"},
        {"index":4,"nameRef":"'Sheet2'!D1"}
      ]}
    }
  }
}'

约束:

  • refs 只覆盖纯数据 A11:D17不要把表头行 A1 并进来
  • nameRef 在 detached 模式下必填,缺了被校验报 headerMode=detached requires ... nameRef
  • index 按 refs 内的列序算A=1、B=2、C=3、D=4不是全表列号
  • nameRef 必须配对应的 index;单写 nameRef 不传 index 直接报参数错

多张图共享同一组表头(按维度拆图,必须用 detached

场景:销售明细表头在 A1:E1月份/区域/销售额/订单数/客单价),数据按区域分 3 段(华北 A2:E9、华东 A10:E17、华南 A18:E25要分别画 3 张图。

常见错误:

// 错误 1refs 含全局表头但跨段 —— 多个区域被混进同一张图
{"data":{"refs":[{"value":"'Sheet'!A1:E17"}], ... }}   // 华东图混进华北 8 行
// 错误 2inline + refs 只取数据段、不写 detached/nameRef —— 图例显示成具体数据值
{"data":{"refs":[{"value":"'Sheet'!A10:E17"}],"dim1":{"serie":{"index":1}}, ... }}

正确模式3 张图各自 detached、refs 干净不重叠:

// 图 1华北
{"data":{
  "headerMode":"detached","direction":"column",
  "refs":[{"value":"'Sheet'!A2:E9"}],
  "dim1":{"serie":{"index":1,"nameRef":"'Sheet'!A1"}},
  "dim2":{"series":[
    {"index":3,"nameRef":"'Sheet'!C1"},
    {"index":4,"nameRef":"'Sheet'!D1"}
  ]}
}}
// 图 2华东 —— refs 改 'Sheet'!A10:E17其余同上
// 图 3华南 —— refs 改 'Sheet'!A18:E25其余同上

--properties JSON 关键字段:

  • position.row / position.col 必须留足空间,越界会被 API 拒(按本文件"图表位置选择"四步走)
  • snapshot.data.headerMode:默认 inline当 refs 仅覆盖数据子集而语义表头在子集之外,必须 detached + nameRef
  • chart 引用 pivot 输出时,snapshot.data.refs 必须排除总计 / 小计行

+chart-update

Update 三步法(缺一步会丢字段):

  1. +chart-list --chart-id <id> 拿到完整 snapshot
  2. 在拿到的 snapshot 上局部修改要改的字段,其余保持不变
  3. 完整 snapshot 整个回写到 --properties.snapshot
lark-cli sheets +chart-update --url "..." --sheet-id "$SID" --chart-id "chrXXX" \
  --properties '{
    "position":{"row":0,"col":"A"},
    "size":{"width":480,"height":320},
    "snapshot": <完整快照(由 +chart-list 取回后局部修改)>
  }'

关键:不能只提交局部 snapshot,否则未传字段会被还原为默认值。+chart-update 的语义是 PUT整体覆盖不是 PATCH。

+chart-delete

示例:

# dry-run 先看会删什么sheet 定位必填)
lark-cli sheets +chart-delete --url "https://example.feishu.cn/sheets/shtXXX" --sheet-id "$SID" \
  --chart-id "chrXXX" --dry-run

# 真正执行
lark-cli sheets +chart-delete --url "https://example.feishu.cn/sheets/shtXXX" --sheet-id "$SID" \
  --chart-id "chrXXX" --yes

Validate / DryRun / Execute 约束

  • ValidateXOR 公共四件套;+chart-create / +chart-update--properties 必须能解析为合法 JSON+chart-deletehigh-risk-write校验 --yes--dry-run 至少一个。
  • DryRun+chart-create / +chart-update 输出"将要 POST 的 body 模板"+chart-delete 输出"将要删除的 chart_id 及隶属 sheet",零网络副作用。
  • Execute:写操作执行后不自动回读;如需确认,自行调用 +chart-list 比对结果。

+chart-create / +chart-update 是 write 级别,按需可用 --dry-run 预览,不要求 --yes。只有 +chart-deletehigh-risk-write必须 --yes