diff --git a/shortcuts/sheets/batch_op_contract_test.go b/shortcuts/sheets/batch_op_contract_test.go index f0ae57a4b8..f2525ac883 100644 --- a/shortcuts/sheets/batch_op_contract_test.go +++ b/shortcuts/sheets/batch_op_contract_test.go @@ -223,6 +223,24 @@ func TestBatchOp_BodyMatchesStandalone(t *testing.T) { args: []string{"--sheet-id", "sh1", "--chart-id", "c1"}, subInput: `{"sheet-id":"sh1","chart-id":"c1"}`, }, + { + shortcut: "+chart-create-basic", + sc: ChartCreateBasic, + args: []string{"--sheet-id", "sh1", "--chart-type", "column", "--data-range", "A1:C10", "--title", "Sales", "--data-labels", "value", "--anchor-cell", "F2"}, + subInput: `{"sheet-id":"sh1","chart-type":"column","data-range":"A1:C10","title":"Sales","data-labels":"value","anchor-cell":"F2"}`, + }, + { + shortcut: "+chart-config-update", + sc: ChartConfigUpdate, + args: []string{"--sheet-id", "sh1", "--chart-id", "c1", "--title", "Updated", "--data-labels", "category", "--data-label-position", "top"}, + subInput: `{"sheet-id":"sh1","chart-id":"c1","title":"Updated","data-labels":"category","data-label-position":"top"}`, + }, + { + shortcut: "+chart-data-update", + sc: ChartDataUpdate, + args: []string{"--sheet-id", "sh1", "--chart-id", "c1", "--data-range", "'Sheet1'!A1:M6", "--data-direction", "column", "--dim1-index", "1", "--dim2-indexes", "4,8"}, + subInput: `{"sheet-id":"sh1","chart-id":"c1","data-range":"'Sheet1'!A1:M6","data-direction":"column","dim1-index":1,"dim2-indexes":"4,8"}`, + }, { shortcut: "+pivot-create", sc: PivotCreate, @@ -424,6 +442,22 @@ func TestBatchOp_ErrorEquivalence(t *testing.T) { subInput: `{}`, wantContains: "specify at least one of --sheet-id or --sheet-name", }, + { + name: "+chart-data-update invalid dim1 index", + shortcut: ChartDataUpdate, + args: []string{"--sheet-id", "sh1", "--chart-id", "c1", "--data-range", "A1:C4", "--dim1-index", "0"}, + subShortcut: "+chart-data-update", + subInput: `{"sheet-id":"sh1","chart-id":"c1","data-range":"A1:C4","dim1-index":0}`, + wantContains: "--dim1-index must be a positive 1-based index", + }, + { + name: "+chart-data-update dim1 and dim2 conflict", + shortcut: ChartDataUpdate, + args: []string{"--sheet-id", "sh1", "--chart-id", "c1", "--data-range", "A1:C4", "--dim1-index", "2", "--dim2-indexes", "2,3"}, + subShortcut: "+chart-data-update", + subInput: `{"sheet-id":"sh1","chart-id":"c1","data-range":"A1:C4","dim1-index":2,"dim2-indexes":"2,3"}`, + wantContains: "--dim2-indexes must not contain the dim1 index 2", + }, { name: "+float-image-create both image-token and image-uri", shortcut: FloatImageCreate, @@ -662,6 +696,18 @@ func TestBatchOp_RejectsBadSubOpInput(t *testing.T) { `{"sheet-id":"sh1","properties":{"title":"T"}}`, "--chart-id is required", }, + { + "+chart-data-update missing --chart-id", + "+chart-data-update", + `{"sheet-id":"sh1","data-range":"A1:C4"}`, + "--chart-id is required", + }, + { + "+chart-data-update missing --data-range", + "+chart-data-update", + `{"sheet-id":"sh1","chart-id":"c1"}`, + "--data-range is required", + }, { "+filter-create missing --range", "+filter-create", diff --git a/shortcuts/sheets/batch_op_dispatch.go b/shortcuts/sheets/batch_op_dispatch.go index ec706fda5e..2cbb64f2c9 100644 --- a/shortcuts/sheets/batch_op_dispatch.go +++ b/shortcuts/sheets/batch_op_dispatch.go @@ -168,9 +168,12 @@ var batchOpDispatch = map[string]batchOpMapping{ }}, // ─── 对象族 CRUD (manage_*_object, operation 区分) ───────────── - "+chart-create": {"manage_chart_object", objCreateTranslate(chartSpec)}, - "+chart-update": {"manage_chart_object", objUpdateTranslate(chartSpec)}, - "+chart-delete": {"manage_chart_object", objDeleteTranslate(chartSpec)}, + "+chart-create": {"manage_chart_object", objCreateTranslate(chartSpec)}, + "+chart-update": {"manage_chart_object", objUpdateTranslate(chartSpec)}, + "+chart-delete": {"manage_chart_object", objDeleteTranslate(chartSpec)}, + "+chart-create-basic": {"manage_chart_object", chartCreateBasicInput}, + "+chart-config-update": {"manage_chart_object", chartConfigUpdateInput}, + "+chart-data-update": {"manage_chart_object", chartDataUpdateInput}, "+pivot-create": {"manage_pivot_table_object", objCreateTranslate(pivotSpec)}, "+pivot-update": {"manage_pivot_table_object", objUpdateTranslate(pivotSpec)}, diff --git a/shortcuts/sheets/data/flag-defs.json b/shortcuts/sheets/data/flag-defs.json index 28f2780640..3d90a748c7 100644 --- a/shortcuts/sheets/data/flag-defs.json +++ b/shortcuts/sheets/data/flag-defs.json @@ -3249,6 +3249,559 @@ } ] }, + "+chart-create-basic": { + "risk": "write", + "flags": [ + { + "name": "url", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Spreadsheet URL (XOR with `--spreadsheet-token`)" + }, + { + "name": "spreadsheet-token", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Spreadsheet token (XOR with `--url`)" + }, + { + "name": "sheet-id", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Sheet reference_id (XOR with `--sheet-name`)" + }, + { + "name": "sheet-name", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Sheet name (XOR with `--sheet-id`)" + }, + { + "name": "chart-type", + "kind": "own", + "type": "string", + "required": "required", + "desc": "Chart type", + "enum": [ + "column", + "bar", + "line", + "area", + "pie", + "scatter", + "combo", + "radar" + ] + }, + { + "name": "data-range", + "kind": "own", + "type": "string", + "required": "required", + "desc": "One contiguous A1 range including headers, or comma-separated same-sheet ranges; aligned non-overlapping ranges stay independent, otherwise they merge to the smallest enclosing rectangle" + }, + { + "name": "data-direction", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Data series direction; column uses the first column as categories, row uses the first row", + "default": "column", + "enum": [ + "column", + "row" + ] + }, + { + "name": "title", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Chart title" + }, + { + "name": "subtitle", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Chart subtitle" + }, + { + "name": "legend-position", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Legend position; hidden removes the legend", + "enum": [ + "top", + "bottom", + "left", + "right", + "hidden" + ] + }, + { + "name": "x-axis-title", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "X-axis title" + }, + { + "name": "y-axis-title", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Left Y-axis title" + }, + { + "name": "secondary-y-axis-title", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Right Y-axis title" + }, + { + "name": "x-axis-label-angle", + "kind": "own", + "type": "int", + "required": "optional", + "desc": "X-axis label angle", + "enum": [ + "-90", + "-45", + "0", + "45", + "90" + ] + }, + { + "name": "y-axis-label-angle", + "kind": "own", + "type": "int", + "required": "optional", + "desc": "Left Y-axis label angle", + "enum": [ + "-90", + "-45", + "0", + "45", + "90" + ] + }, + { + "name": "data-labels", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Data label content; none removes labels; category_percentage is normalized to value_percentage", + "enum": [ + "none", + "value", + "percentage", + "value_percentage", + "category_percentage", + "category", + "series" + ] + }, + { + "name": "data-label-position", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Data label position", + "enum": [ + "auto", + "top", + "bottom", + "left", + "right", + "center", + "inside", + "outside" + ] + }, + { + "name": "stack", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Stacking mode", + "enum": [ + "none", + "normal", + "percent" + ] + }, + { + "name": "stacked", + "kind": "own", + "type": "bool", + "required": "optional", + "desc": "Compatibility alias for --stack normal", + "hidden": true + }, + { + "name": "smooth", + "kind": "own", + "type": "bool", + "required": "optional", + "desc": "Use smooth curves; accepts both --smooth=false and --smooth false" + }, + { + "name": "color-palette", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Preset chart-level color palette; mutually exclusive with --colors", + "enum": [ + "brandColorSeries@v2", + "rainbowColorSeries@v2", + "complementaryColorSeries@v2", + "converseColorSeries@v2", + "primaryColorSeries@v2", + "singleColorSeries-B-@v2", + "singleColorSeries-W-@v2", + "singleColorSeries-G-@v2", + "singleColorSeries-Y-@v2", + "singleColorSeries-O-@v2", + "singleColorSeries-R-@v2", + "singleColorSeries-D-@v2" + ] + }, + { + "name": "colors", + "kind": "own", + "type": "string_slice", + "required": "optional", + "desc": "Custom chart-level series colors as a comma-separated list of at least two hex colors; mutually exclusive with --color-palette" + }, + { + "name": "anchor-cell", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Optional chart anchor cell such as F2; defaults to the right of the data range" + }, + { + "name": "width", + "kind": "own", + "type": "int", + "required": "optional", + "desc": "Optional chart width; must be paired with --height" + }, + { + "name": "height", + "kind": "own", + "type": "int", + "required": "optional", + "desc": "Optional chart height; must be paired with --width" + }, + { + "name": "dry-run", + "kind": "system", + "type": "bool", + "required": "optional", + "desc": "Print the request template; no side effects" + } + ] + }, + "+chart-config-update": { + "risk": "write", + "flags": [ + { + "name": "url", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Spreadsheet URL (XOR with `--spreadsheet-token`)" + }, + { + "name": "spreadsheet-token", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Spreadsheet token (XOR with `--url`)" + }, + { + "name": "sheet-id", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Sheet reference_id (XOR with `--sheet-name`)" + }, + { + "name": "sheet-name", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Sheet name (XOR with `--sheet-id`)" + }, + { + "name": "chart-id", + "kind": "own", + "type": "string", + "required": "required", + "desc": "Target chart reference_id" + }, + { + "name": "title", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Chart title" + }, + { + "name": "subtitle", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Chart subtitle" + }, + { + "name": "legend-position", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Legend position; hidden removes the legend", + "enum": [ + "top", + "bottom", + "left", + "right", + "hidden" + ] + }, + { + "name": "x-axis-title", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "X-axis title" + }, + { + "name": "y-axis-title", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Left Y-axis title" + }, + { + "name": "secondary-y-axis-title", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Right Y-axis title" + }, + { + "name": "x-axis-label-angle", + "kind": "own", + "type": "int", + "required": "optional", + "desc": "X-axis label angle", + "enum": [ + "-90", + "-45", + "0", + "45", + "90" + ] + }, + { + "name": "y-axis-label-angle", + "kind": "own", + "type": "int", + "required": "optional", + "desc": "Left Y-axis label angle", + "enum": [ + "-90", + "-45", + "0", + "45", + "90" + ] + }, + { + "name": "data-labels", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Data label content; none removes labels; category_percentage is normalized to value_percentage", + "enum": [ + "none", + "value", + "percentage", + "value_percentage", + "category_percentage", + "category", + "series" + ] + }, + { + "name": "data-label-position", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Data label position", + "enum": [ + "auto", + "top", + "bottom", + "left", + "right", + "center", + "inside", + "outside" + ] + }, + { + "name": "stack", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Stacking mode", + "enum": [ + "none", + "normal", + "percent" + ] + }, + { + "name": "stacked", + "kind": "own", + "type": "bool", + "required": "optional", + "desc": "Compatibility alias for --stack normal", + "hidden": true + }, + { + "name": "smooth", + "kind": "own", + "type": "bool", + "required": "optional", + "desc": "Use smooth curves; accepts both --smooth=false and --smooth false" + }, + { + "name": "color-palette", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Preset chart-level color palette; mutually exclusive with --colors", + "enum": [ + "brandColorSeries@v2", + "rainbowColorSeries@v2", + "complementaryColorSeries@v2", + "converseColorSeries@v2", + "primaryColorSeries@v2", + "singleColorSeries-B-@v2", + "singleColorSeries-W-@v2", + "singleColorSeries-G-@v2", + "singleColorSeries-Y-@v2", + "singleColorSeries-O-@v2", + "singleColorSeries-R-@v2", + "singleColorSeries-D-@v2" + ] + }, + { + "name": "colors", + "kind": "own", + "type": "string_slice", + "required": "optional", + "desc": "Custom chart-level series colors as a comma-separated list of at least two hex colors; mutually exclusive with --color-palette" + }, + { + "name": "dry-run", + "kind": "system", + "type": "bool", + "required": "optional", + "desc": "Print the request template; no side effects" + } + ] + }, + "+chart-data-update": { + "risk": "write", + "flags": [ + { + "name": "url", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Spreadsheet URL (XOR with `--spreadsheet-token`)" + }, + { + "name": "spreadsheet-token", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Spreadsheet token (XOR with `--url`)" + }, + { + "name": "sheet-id", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Sheet reference_id (XOR with `--sheet-name`)" + }, + { + "name": "sheet-name", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Sheet name (XOR with `--sheet-id`)" + }, + { + "name": "chart-id", + "kind": "own", + "type": "string", + "required": "required", + "desc": "Target chart reference_id" + }, + { + "name": "data-range", + "kind": "own", + "type": "string", + "required": "required", + "desc": "New data range including headers; accepts comma-separated same-sheet ranges and normalizes misaligned or overlapping ranges" + }, + { + "name": "data-direction", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Data series direction; defaults to the existing chart direction when omitted", + "enum": [ + "column", + "row" + ] + }, + { + "name": "dim1-index", + "kind": "own", + "type": "int", + "required": "optional", + "desc": "1-based category/X-axis dimension index within the data range; defaults to the first dimension" + }, + { + "name": "dim2-indexes", + "kind": "own", + "type": "string", + "required": "optional", + "desc": "Comma-separated 1-based value/Y-axis series indexes within the data range; defaults to all dimensions except dim1" + }, + { + "name": "dry-run", + "kind": "system", + "type": "bool", + "required": "optional", + "desc": "Print the request template; no side effects" + } + ] + }, "+chart-create": { "risk": "write", "flags": [ @@ -3343,7 +3896,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "Full or sufficiently complete chart config JSON (read back with `+chart-list` first, then patch)", + "desc": "Chart config patch JSON; send changed fields only by default; omitted fields are preserved, objects merge recursively, and arrays replace as a whole", "input": [ "file", "stdin" diff --git a/shortcuts/sheets/data/flag-schemas.json b/shortcuts/sheets/data/flag-schemas.json index 3d6180038e..efdc459ba1 100644 --- a/shortcuts/sheets/data/flag-schemas.json +++ b/shortcuts/sheets/data/flag-schemas.json @@ -49,6 +49,9 @@ "+chart-create", "+chart-update", "+chart-delete", + "+chart-create-basic", + "+chart-config-update", + "+chart-data-update", "+pivot-create", "+pivot-update", "+pivot-delete", @@ -833,209 +836,71 @@ ] }, "snapshot": { - "type": "object", - "description": "图表快照配置。更新图表时必须传入完整的图表属性定义,不能只传修改的部分。应先通过 get_chart_objects 获取当前图表快照,修改需要变更的字段后,将完整快照传入。", - "properties": { - "title": { - "type": "object", - "description": "图表标题配置", - "properties": { - "text": { - "type": "string", - "description": "标题文本" - }, - "textAlign": { - "type": "string", - "description": "标题对齐方式", - "enum": [ - "left", - "center", - "right" - ] - }, - "fontSize": { - "type": "number", - "description": "字体大小" - }, - "bold": { - "type": "boolean", - "description": "是否加粗" - }, - "italic": { - "type": "boolean", - "description": "是否斜体" - }, - "underline": { - "type": "boolean", - "description": "是否下划线" - }, - "strikethrough": { - "type": "boolean", - "description": "是否删除线" - }, - "color": { - "type": "string", - "description": "字体颜色,格式为 #RRGGBB" - } - }, - "required": [ - "text" - ] - }, - "subTitle": { - "type": "object", - "description": "图表副标题配置", - "properties": { - "text": { - "type": "string", - "description": "副标题文本" - }, - "textAlign": { - "type": "string", - "description": "副标题对齐方式", - "enum": [ - "left", - "center", - "right" - ] - }, - "fontSize": { - "type": "number", - "description": "字体大小" - }, - "bold": { - "type": "boolean", - "description": "是否加粗" - }, - "italic": { - "type": "boolean", - "description": "是否斜体" - }, - "underline": { - "type": "boolean", - "description": "是否下划线" - }, - "strikethrough": { - "type": "boolean", - "description": "是否删除线" - }, - "color": { - "type": "string", - "description": "字体颜色,格式为 #RRGGBB" - } - }, - "required": [ - "text" - ] - }, - "style": { + "description": "图表快照配置。create 需要完整配置;update 可只提供需要变更的字段,对象字段递归合并,数组整段替换,未提供的字段保持现状。", + "anyOf": [ + { "type": "object", - "description": "图表样式配置", "properties": { - "background": { - "type": "object", - "description": "背景配置", - "properties": { - "color": { - "type": "string", - "description": "背景颜色,格式为 #RRGGBB" - } - } - }, - "font": { - "type": "object", - "description": "字体配置", - "properties": { - "size": { - "type": "number", - "description": "字体大小" - }, - "color": { - "type": "string", - "description": "字体颜色,格式为 #RRGGBB" - } - } - }, - "border": { + "title": { "type": "object", - "description": "边框配置", + "description": "图表标题配置", "properties": { - "color": { + "text": { "type": "string", - "description": "边框颜色,格式为 #RRGGBB" - }, - "width": { - "type": "number", - "description": "边框宽度" + "description": "标题文本" }, - "style": { + "textAlign": { "type": "string", - "description": "边框样式", + "description": "标题对齐方式", "enum": [ - "solid", - "dashed", - "dotted" + "left", + "center", + "right" ] }, - "radius": { + "fontSize": { "type": "number", - "description": "边框圆角" - } - } - }, - "colorTheme": { - "type": "array", - "description": "颜色主题配置。支持两种模式:1. 仅传一个预设主题名称(字符串数组,长度为1);2. 传多个自定义十六进制颜色字符串。预设主题:brandColorSeries@v2、rainbowColorSeries@v2、complementaryColorSeries@v2、converseColorSeries@v2、primaryColorSeries@v2、singleColorSeries-B-@v2、singleColorSeries-W-@v2、singleColorSeries-G-@v2、singleColorSeries-Y-@v2、singleColorSeries-O-@v2、singleColorSeries-R-@v2、singleColorSeries-D-@v2。十六进制格式:#RRGGBB", - "oneOf": [ - { - "minItems": 1, - "maxItems": 1, - "items": { - "type": "string", - "enum": [ - "brandColorSeries@v2", - "rainbowColorSeries@v2", - "complementaryColorSeries@v2", - "converseColorSeries@v2", - "primaryColorSeries@v2", - "singleColorSeries-B-@v2", - "singleColorSeries-W-@v2", - "singleColorSeries-G-@v2", - "singleColorSeries-Y-@v2", - "singleColorSeries-O-@v2", - "singleColorSeries-R-@v2", - "singleColorSeries-D-@v2" - ] - } + "description": "字体大小" }, - { - "minItems": 2, - "items": { - "type": "string", - "description": "颜色字符串,十六进制格式:#RRGGBB" - } + "bold": { + "type": "boolean", + "description": "是否加粗" + }, + "italic": { + "type": "boolean", + "description": "是否斜体" + }, + "underline": { + "type": "boolean", + "description": "是否下划线" + }, + "strikethrough": { + "type": "boolean", + "description": "是否删除线" + }, + "color": { + "type": "string", + "description": "字体颜色,格式为 #RRGGBB" } + }, + "required": [ + "text" ] }, - "colorGradient": { - "type": "boolean", - "description": "是否启用颜色渐变" - } - } - }, - "legend": { - "oneOf": [ - { + "subTitle": { "type": "object", - "description": "图例配置", + "description": "图表副标题配置", "properties": { - "position": { + "text": { + "type": "string", + "description": "副标题文本" + }, + "textAlign": { "type": "string", - "description": "图例位置", + "description": "副标题对齐方式", "enum": [ - "top", - "bottom", "left", + "center", "right" ] }, @@ -1063,355 +928,1278 @@ "type": "string", "description": "字体颜色,格式为 #RRGGBB" } - } + }, + "required": [ + "text" + ] }, - { - "type": "boolean", - "description": "false 表示隐藏图例" - } - ] - }, - "plotArea": { - "type": "object", - "description": "绘图区域配置", - "properties": { - "plot": { + "style": { "type": "object", - "description": "绘图配置", + "description": "图表样式配置", "properties": { - "type": { - "type": "string", - "description": "图表类型", - "enum": [ - "bar", - "column", - "line", - "area", - "combo", - "pie", - "radar", - "scatter" - ] + "background": { + "type": "object", + "description": "背景配置", + "properties": { + "color": { + "type": "string", + "description": "背景颜色,格式为 #RRGGBB" + } + } }, - "comboType": { - "type": "string", - "description": "组合图表默认类型", - "enum": [ - "column", - "line", - "area" - ] + "font": { + "type": "object", + "description": "字体配置", + "properties": { + "size": { + "type": "number", + "description": "字体大小" + }, + "color": { + "type": "string", + "description": "字体颜色,格式为 #RRGGBB" + } + } }, - "yAxisPosition": { - "type": "string", - "description": "Y轴位置", - "enum": [ - "left", - "right" + "border": { + "type": "object", + "description": "边框配置", + "properties": { + "color": { + "type": "string", + "description": "边框颜色,格式为 #RRGGBB" + }, + "width": { + "type": "number", + "description": "边框宽度" + }, + "style": { + "type": "string", + "description": "边框样式", + "enum": [ + "solid", + "dashed", + "dotted" + ] + }, + "radius": { + "type": "number", + "description": "边框圆角" + } + } + }, + "colorTheme": { + "type": "array", + "description": "颜色主题配置。支持两种模式:1. 仅传一个预设主题名称(字符串数组,长度为1);2. 传多个自定义十六进制颜色字符串。预设主题:brandColorSeries@v2、rainbowColorSeries@v2、complementaryColorSeries@v2、converseColorSeries@v2、primaryColorSeries@v2、singleColorSeries-B-@v2、singleColorSeries-W-@v2、singleColorSeries-G-@v2、singleColorSeries-Y-@v2、singleColorSeries-O-@v2、singleColorSeries-R-@v2、singleColorSeries-D-@v2。十六进制格式:#RRGGBB", + "oneOf": [ + { + "minItems": 1, + "maxItems": 1, + "items": { + "type": "string", + "enum": [ + "brandColorSeries@v2", + "rainbowColorSeries@v2", + "complementaryColorSeries@v2", + "converseColorSeries@v2", + "primaryColorSeries@v2", + "singleColorSeries-B-@v2", + "singleColorSeries-W-@v2", + "singleColorSeries-G-@v2", + "singleColorSeries-Y-@v2", + "singleColorSeries-O-@v2", + "singleColorSeries-R-@v2", + "singleColorSeries-D-@v2" + ] + } + }, + { + "minItems": 2, + "items": { + "type": "string", + "description": "颜色字符串,十六进制格式:#RRGGBB" + } + } ] }, - "extra": { + "colorGradient": { + "type": "boolean", + "description": "是否启用颜色渐变" + } + } + }, + "legend": { + "oneOf": [ + { "type": "object", - "description": "额外配置", + "description": "图例配置", "properties": { - "smooth": { + "position": { + "type": "string", + "description": "图例位置", + "enum": [ + "top", + "bottom", + "left", + "right" + ] + }, + "fontSize": { + "type": "number", + "description": "字体大小" + }, + "bold": { "type": "boolean", - "description": "是否平滑曲线" + "description": "是否加粗" }, - "step": { + "italic": { "type": "boolean", - "description": "是否阶梯图" + "description": "是否斜体" }, - "stack": { - "type": "object", - "description": "堆叠配置", - "properties": { - "percentage": { - "type": "boolean", - "description": "是否百分比堆叠" - } - } + "underline": { + "type": "boolean", + "description": "是否下划线" }, - "radar": { - "type": "object", - "description": "雷达图配置", - "properties": { - "shape": { - "type": "string", - "description": "雷达图形状", - "enum": [ - "polygon", - "circle" - ] - }, - "area": { - "type": "boolean", - "description": "是否填充区域" - } - } + "strikethrough": { + "type": "boolean", + "description": "是否删除线" + }, + "color": { + "type": "string", + "description": "字体颜色,格式为 #RRGGBB" } } }, - "points": { + { + "type": "boolean", + "description": "false 表示隐藏图例" + } + ] + }, + "plotArea": { + "type": "object", + "description": "绘图区域配置", + "properties": { + "plot": { "type": "object", - "description": "全系列数据点配置,折线图、面积图、雷达图、散点图、组合图生效。", + "description": "绘图配置", "properties": { - "color": { + "type": { "type": "string", - "description": "数据点颜色" + "description": "图表类型", + "enum": [ + "bar", + "column", + "line", + "area", + "combo", + "pie", + "radar", + "scatter" + ] }, - "shape": { + "comboType": { "type": "string", - "description": "数据点形状", + "description": "组合图表默认类型", "enum": [ - "circle", - "triangle", - "rect", - "diamond", - "square" + "column", + "line", + "area" ] }, - "size": { - "type": "number", - "description": "数据点大小" + "yAxisPosition": { + "type": "string", + "description": "Y轴位置", + "enum": [ + "left", + "right" + ] }, - "fillGradient": { + "extra": { "type": "object", - "description": "数据点填充渐变配置", + "description": "额外配置", "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" + "smooth": { + "type": "boolean", + "description": "是否平滑曲线" }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" + "step": { + "type": "boolean", + "description": "是否阶梯图" }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" + "stack": { + "type": "object", + "description": "堆叠配置", + "properties": { + "percentage": { + "type": "boolean", + "description": "是否百分比堆叠" + } + } }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" + "radar": { + "type": "object", + "description": "雷达图配置", + "properties": { + "shape": { + "type": "string", + "description": "雷达图形状", + "enum": [ + "polygon", + "circle" + ] + }, + "area": { + "type": "boolean", + "description": "是否填充区域" + } + } + } + } + }, + "points": { + "type": "object", + "description": "全系列数据点配置,折线图、面积图、雷达图、散点图、组合图生效。", + "properties": { + "color": { + "type": "string", + "description": "数据点颜色" }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" + "shape": { + "type": "string", + "description": "数据点形状", + "enum": [ + "circle", + "triangle", + "rect", + "diamond", + "square" + ] }, - "r0": { + "size": { "type": "number", - "description": "径向渐变起点半径" + "description": "数据点大小" }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" + "fillGradient": { + "type": "object", + "description": "数据点填充渐变配置", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" + "strokeGradient": { + "type": "object", + "description": "数据点描边渐变配置", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] }, - "stops": { + "point": { "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, + "description": "单个数据点配置数组", "items": { "type": "object", "properties": { - "offset": { + "index": { "type": "number", - "description": "色标位置 0-1" + "description": "数据点索引" }, "color": { "type": "string", - "description": "色标颜色" + "description": "颜色" + }, + "shape": { + "type": "string", + "description": "形状" }, - "opacity": { + "size": { "type": "number", - "description": "色标透明度 0-1" + "description": "大小" + }, + "fillGradient": { + "type": "object", + "description": "单个数据点填充渐变配置", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] } }, "required": [ - "offset", - "color" + "index" ] } } - }, - "required": [ - "type", - "stops" - ] + } }, - "strokeGradient": { + "lines": { "type": "object", - "description": "数据点描边渐变配置", + "description": "全系列线条配置,折线图、面积图、雷达图、组合图生效。", "properties": { - "type": { + "color": { "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" + "description": "线条颜色" }, - "x0": { + "width": { "type": "number", - "description": "起点 x,0-1 归一化坐标" + "description": "线条宽度" }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" + "style": { + "type": "string", + "description": "线条样式", + "enum": [ + "solid", + "dashed", + "dotted" + ] }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" + "invalidType": { + "type": "string", + "description": "无效值处理方式", + "enum": [ + "break", + "zero", + "link" + ] + }, + "strokeGradient": { + "type": "object", + "description": "线条描边渐变配置", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] + } + } + }, + "areas": { + "type": "object", + "description": "全系列面积填充配置,面积图、雷达图、组合图生效。", + "properties": { + "color": { + "type": "string", + "description": "区域填充颜色" + }, + "fillGradient": { + "type": "object", + "description": "区域填充渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] + } + } + }, + "bars": { + "type": "object", + "description": "全系列柱状图、条形图、组合图生效。", + "properties": { + "color": { + "type": "string", + "description": "柱子颜色" + }, + "borderColor": { + "type": "string", + "description": "边框颜色" }, - "y1": { + "borderWidth": { "type": "number", - "description": "终点 y,0-1 归一化坐标" + "description": "边框宽度" }, - "r0": { + "borderStyle": { + "type": "string", + "description": "边框样式", + "enum": [ + "solid", + "dashed", + "dotted" + ] + }, + "width": { "type": "number", - "description": "径向渐变起点半径" + "description": "柱子宽度" }, - "r1": { + "gap": { "type": "number", - "description": "径向渐变终点半径" + "description": "柱子间距比例,0-1之间" }, - "gradientMethod": { + "backgroundColor": { "type": "string", - "description": "渐变插值方法" + "description": "背景颜色" + }, + "fillGradient": { + "type": "object", + "description": "柱子填充渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] + }, + "strokeGradient": { + "type": "object", + "description": "柱子描边渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] }, - "stops": { + "bar": { "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, + "description": "单个柱子配置数组", "items": { "type": "object", "properties": { - "offset": { + "index": { "type": "number", - "description": "色标位置 0-1" + "description": "柱子索引" }, "color": { "type": "string", - "description": "色标颜色" + "description": "颜色" }, - "opacity": { + "borderColor": { + "type": "string", + "description": "边框颜色" + }, + "borderWidth": { "type": "number", - "description": "色标透明度 0-1" + "description": "边框宽度" + }, + "borderStyle": { + "type": "string", + "description": "边框样式" + }, + "fillGradient": { + "type": "object", + "description": "单个柱子填充渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] } }, "required": [ - "offset", - "color" + "index" ] } } - }, - "required": [ - "type", - "stops" - ] + } + }, + "labels": { + "type": "object", + "description": "数据标签配置。labels 对象的存在性即开关:不显示数据标签时省略整个 labels 字段;一旦传入 labels(即便 value/category/series/percentage 全部置为 false),数据标签仍会显示,且默认兜底显示 value。", + "properties": { + "position": { + "type": "string", + "description": "标签位置", + "enum": [ + "auto", + "top", + "bottom", + "left", + "right", + "center", + "inside", + "outside" + ] + }, + "series": { + "type": "boolean", + "description": "是否显示系列名" + }, + "category": { + "type": "boolean", + "description": "是否显示类别名" + }, + "value": { + "type": "boolean", + "description": "是否显示值" + }, + "percentage": { + "type": "boolean", + "description": "是否显示百分比" + }, + "fontSize": { + "type": "number", + "description": "字体大小" + }, + "bold": { + "type": "boolean", + "description": "是否加粗" + }, + "italic": { + "type": "boolean", + "description": "是否斜体" + }, + "underline": { + "type": "boolean", + "description": "是否下划线" + }, + "strikethrough": { + "type": "boolean", + "description": "是否删除线" + }, + "color": { + "type": "string", + "description": "字体颜色" + } + } }, - "point": { + "series": { "type": "array", - "description": "单个数据点配置数组", + "description": "单个系列配置数组", "items": { "type": "object", + "description": "系列配置", "properties": { "index": { "type": "number", - "description": "数据点索引" + "description": "数据列索引,与 dim2.series[].index 值一致,从 1 开始(1 通常对应首列标签维度,2+ 对应后续数据维度列)" }, - "color": { + "comboType": { "type": "string", - "description": "颜色" + "description": "组合图下该系列的图表类型,仅在 type 为 combo 时生效", + "enum": [ + "column", + "line", + "area" + ] }, - "shape": { + "yAxisPosition": { "type": "string", - "description": "形状" + "description": "Y轴位置", + "enum": [ + "left", + "right" + ] }, - "size": { - "type": "number", - "description": "大小" + "points": { + "type": "object", + "description": "数据点配置,配置项同 plotArea.points" + }, + "line": { + "type": "object", + "description": "线条配置,配置项同 plotArea.lines" }, - "fillGradient": { + "area": { "type": "object", - "description": "单个数据点填充渐变配置", + "description": "区域填充配置,配置项同 plotArea.areas" + }, + "bars": { + "type": "object", + "description": "柱状图配置,配置项同 plotArea.bars" + }, + "labels": { + "type": "object", + "description": "数据标签配置(覆盖单系列,配置项同 plotArea.plot.labels)。不覆盖该系列数据标签时省略整个 labels 字段;一旦传入 labels(即便显示开关全部置为 false),该系列仍会显示数据标签。" + }, + "sectors": { + "type": "object", + "description": "扇区配置(饼图)", "properties": { - "type": { + "borderColor": { "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" + "description": "边框颜色" }, - "y0": { + "innerRadius": { "type": "number", - "description": "起点 y,0-1 归一化坐标" + "description": "内半径比例,0-1之间" }, - "x1": { + "offsetRadius": { "type": "number", - "description": "终点 x,0-1 归一化坐标" + "description": "偏移半径比例" }, - "y1": { + "startAngle": { "type": "number", - "description": "终点 y,0-1 归一化坐标" + "description": "起始角度,0-359" }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" + "fillGradient": { + "type": "object", + "description": "扇区填充渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" - }, - "stops": { + "sector": { "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, + "description": "单个扇区配置数组", "items": { "type": "object", "properties": { - "offset": { + "index": { "type": "number", - "description": "色标位置 0-1" + "description": "扇区索引" }, - "color": { + "borderColor": { "type": "string", - "description": "色标颜色" + "description": "边框颜色" }, - "opacity": { + "offsetRadius": { "type": "number", - "description": "色标透明度 0-1" + "description": "偏移半径" + }, + "color": { + "type": "string", + "description": "颜色" + }, + "fillGradient": { + "type": "object", + "description": "单个扇区填充渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] } }, "required": [ - "offset", - "color" + "index" ] } } - }, - "required": [ - "type", - "stops" - ] + } } }, "required": [ @@ -1419,1112 +2207,335 @@ ] } } - } + }, + "required": [ + "type" + ] }, - "lines": { - "type": "object", - "description": "全系列线条配置,折线图、面积图、雷达图、组合图生效。", - "properties": { - "color": { - "type": "string", - "description": "线条颜色" - }, - "width": { - "type": "number", - "description": "线条宽度" - }, - "style": { - "type": "string", - "description": "线条样式", - "enum": [ - "solid", - "dashed", - "dotted" - ] - }, - "invalidType": { - "type": "string", - "description": "无效值处理方式", - "enum": [ - "break", - "zero", - "link" - ] - }, - "strokeGradient": { - "type": "object", - "description": "线条描边渐变配置", - "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" - }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" + "axes": { + "type": "array", + "description": "坐标轴配置数组", + "items": { + "type": "object", + "description": "坐标轴配置", + "properties": { + "type": { + "type": "string", + "description": "坐标轴类型", + "enum": [ + "x", + "y", + "angle", + "radius" + ] + }, + "position": { + "type": "string", + "description": "坐标轴位置", + "enum": [ + "left", + "right", + "bottom" + ] + }, + "max": { + "type": "number", + "description": "最大值" + }, + "min": { + "type": "number", + "description": "最小值" + }, + "valueType": { + "type": "string", + "description": "是否将横轴数字视为文本,linear 表示将数字视为数值, ordinal 表示将数字视为文本", + "enum": [ + "ordinal", + "linear" + ] + }, + "title": { + "type": "object", + "description": "坐标轴标题配置", + "properties": { + "text": { + "type": "string", + "description": "标题文本" + }, + "fontSize": { + "type": "number", + "description": "字体大小" + }, + "bold": { + "type": "boolean", + "description": "是否加粗" + }, + "italic": { + "type": "boolean", + "description": "是否斜体" + }, + "underline": { + "type": "boolean", + "description": "是否下划线" + }, + "strikethrough": { + "type": "boolean", + "description": "是否删除线" + }, + "color": { + "type": "string", + "description": "字体颜色" + } }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { + "required": [ + "text" + ] + }, + "label": { + "type": "object", + "description": "坐标轴标签配置", + "properties": { + "angle": { + "type": "number", + "description": "旋转角度,可选值:-90, -45, 0, 45, 90" + }, + "fontSize": { + "type": "number", + "description": "字体大小" + }, + "bold": { + "type": "boolean", + "description": "是否加粗" + }, + "italic": { + "type": "boolean", + "description": "是否斜体" + }, + "underline": { + "type": "boolean", + "description": "是否下划线" + }, + "strikethrough": { + "type": "boolean", + "description": "是否删除线" + }, + "color": { + "type": "string", + "description": "字体颜色" + } + } + }, + "axisLine": { + "type": "boolean", + "description": "是否显示轴线" + }, + "gridLine": { + "oneOf": [ + { "type": "object", + "description": "网格线配置", "properties": { - "offset": { + "width": { "type": "number", - "description": "色标位置 0-1" + "description": "网格线宽度" }, "color": { "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" + "description": "网格线颜色" } - }, - "required": [ - "offset", - "color" - ] + } + }, + { + "type": "boolean", + "description": "false 表示隐藏网格线" } - } - }, - "required": [ - "type", - "stops" - ] - } + ] + } + }, + "required": [ + "type" + ] + } + } + }, + "required": [ + "plot" + ] + }, + "data": { + "type": "object", + "description": "图表数据配置", + "properties": { + "isStaticData": { + "type": "boolean", + "description": "是否为静态数据" + }, + "includeHiddenOrFilter": { + "type": "boolean", + "description": "是否包含隐藏或过滤的数据" + }, + "direction": { + "type": "string", + "description": "数据方向,可选值:row(行方向)或column(列方向),常用值为column", + "enum": [ + "row", + "column" + ] + }, + "headerMode": { + "type": "string", + "description": "表头模式。inline 表示 refs 首行/首列就是表头;detached 表示 refs 仅覆盖实际数据范围,维度名和系列名需通过 nameRef 显式指定。", + "enum": [ + "inline", + "detached" + ] + }, + "refs": { + "type": "array", + "description": "数据源引用范围数组", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "图表数据源范围,格式为 'SheetName!A1:D100' 的连续区域" + } + }, + "required": [ + "value" + ] } }, - "areas": { + "dim1": { "type": "object", - "description": "全系列面积填充配置,面积图、雷达图、组合图生效。", + "description": "维度1配置(类别维度)", "properties": { - "color": { - "type": "string", - "description": "区域填充颜色" - }, - "fillGradient": { + "serie": { "type": "object", - "description": "区域填充渐变", + "description": "系列配置(非静态数据时传此参数)", "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { + "index": { "type": "number", - "description": "径向渐变起点半径" + "description": "系列索引,数据源从左往右,从 1 开始计数对应数据源的列索引;如果数据方向direction为 'row',则为从上往下,从 1 开始计数对应数据源的行索引" }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" + "aggregate": { + "type": "boolean", + "description": "是否汇总相同的类别,默认为true,true 情况下 aggregateType 才生效" }, - "gradientMethod": { + "nameRef": { "type": "string", - "description": "渐变插值方法" - }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } + "description": "可选。维度名称的单元格引用(A1 表示法,如 'Sheet1!A1')。当维度名称不直接来自 refs 首行/首列时,可通过此字段把维度名显式指向目标表头单元格,图表会以该单元格当前值作为类别维度名展示。" } }, "required": [ - "type", - "stops" - ] - } - } - }, - "bars": { - "type": "object", - "description": "全系列柱状图、条形图、组合图生效。", - "properties": { - "color": { - "type": "string", - "description": "柱子颜色" - }, - "borderColor": { - "type": "string", - "description": "边框颜色" - }, - "borderWidth": { - "type": "number", - "description": "边框宽度" - }, - "borderStyle": { - "type": "string", - "description": "边框样式", - "enum": [ - "solid", - "dashed", - "dotted" + "index" ] }, - "width": { - "type": "number", - "description": "柱子宽度" - }, - "gap": { - "type": "number", - "description": "柱子间距比例,0-1之间" - }, - "backgroundColor": { - "type": "string", - "description": "背景颜色" - }, - "fillGradient": { + "field": { "type": "object", - "description": "柱子填充渐变", + "description": "字段配置(静态数据时传此参数)", "properties": { - "type": { + "valueType": { "type": "string", + "description": "值类型", "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" + "number", + "string" + ] }, - "gradientMethod": { + "name": { "type": "string", - "description": "渐变插值方法" + "description": "字段名称" }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } + "text": { + "type": "string", + "description": "字段文本数据" } }, "required": [ - "type", - "stops" + "text" ] - }, - "strokeGradient": { - "type": "object", - "description": "柱子描边渐变", - "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" - }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" - }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] - }, - "bar": { - "type": "array", - "description": "单个柱子配置数组", - "items": { - "type": "object", - "properties": { - "index": { - "type": "number", - "description": "柱子索引" - }, - "color": { - "type": "string", - "description": "颜色" - }, - "borderColor": { - "type": "string", - "description": "边框颜色" - }, - "borderWidth": { - "type": "number", - "description": "边框宽度" - }, - "borderStyle": { - "type": "string", - "description": "边框样式" - }, - "fillGradient": { - "type": "object", - "description": "单个柱子填充渐变", - "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" - }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" - }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] - } - }, - "required": [ - "index" - ] - } - } - } - }, - "labels": { - "type": "object", - "description": "数据标签配置。labels 对象的存在性即开关:不显示数据标签时省略整个 labels 字段;一旦传入 labels(即便 value/category/series/percentage 全部置为 false),数据标签仍会显示,且默认兜底显示 value。", - "properties": { - "position": { - "type": "string", - "description": "标签位置", - "enum": [ - "auto", - "top", - "bottom", - "left", - "right", - "center", - "inside", - "outside" - ] - }, - "series": { - "type": "boolean", - "description": "是否显示系列名" - }, - "category": { - "type": "boolean", - "description": "是否显示类别名" - }, - "value": { - "type": "boolean", - "description": "是否显示值" - }, - "percentage": { - "type": "boolean", - "description": "是否显示百分比" - }, - "fontSize": { - "type": "number", - "description": "字体大小" - }, - "bold": { - "type": "boolean", - "description": "是否加粗" - }, - "italic": { - "type": "boolean", - "description": "是否斜体" - }, - "underline": { - "type": "boolean", - "description": "是否下划线" - }, - "strikethrough": { - "type": "boolean", - "description": "是否删除线" - }, - "color": { - "type": "string", - "description": "字体颜色" - } - } - }, - "series": { - "type": "array", - "description": "单个系列配置数组", - "items": { - "type": "object", - "description": "系列配置", - "properties": { - "index": { - "type": "number", - "description": "数据列索引,与 dim2.series[].index 值一致,从 1 开始(1 通常对应首列标签维度,2+ 对应后续数据维度列)" - }, - "comboType": { - "type": "string", - "description": "组合图下该系列的图表类型,仅在 type 为 combo 时生效", - "enum": [ - "column", - "line", - "area" - ] - }, - "yAxisPosition": { - "type": "string", - "description": "Y轴位置", - "enum": [ - "left", - "right" - ] - }, - "points": { - "type": "object", - "description": "数据点配置,配置项同 plotArea.points" - }, - "line": { - "type": "object", - "description": "线条配置,配置项同 plotArea.lines" - }, - "area": { - "type": "object", - "description": "区域填充配置,配置项同 plotArea.areas" - }, - "bars": { - "type": "object", - "description": "柱状图配置,配置项同 plotArea.bars" - }, - "labels": { - "type": "object", - "description": "数据标签配置(覆盖单系列,配置项同 plotArea.plot.labels)。不覆盖该系列数据标签时省略整个 labels 字段;一旦传入 labels(即便显示开关全部置为 false),该系列仍会显示数据标签。" - }, - "sectors": { - "type": "object", - "description": "扇区配置(饼图)", - "properties": { - "borderColor": { - "type": "string", - "description": "边框颜色" - }, - "innerRadius": { - "type": "number", - "description": "内半径比例,0-1之间" - }, - "offsetRadius": { - "type": "number", - "description": "偏移半径比例" - }, - "startAngle": { - "type": "number", - "description": "起始角度,0-359" - }, - "fillGradient": { - "type": "object", - "description": "扇区填充渐变", - "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" - }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" - }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] - }, - "sector": { - "type": "array", - "description": "单个扇区配置数组", - "items": { - "type": "object", - "properties": { - "index": { - "type": "number", - "description": "扇区索引" - }, - "borderColor": { - "type": "string", - "description": "边框颜色" - }, - "offsetRadius": { - "type": "number", - "description": "偏移半径" - }, - "color": { - "type": "string", - "description": "颜色" - }, - "fillGradient": { - "type": "object", - "description": "单个扇区填充渐变", - "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" - }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" - }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] - } - }, - "required": [ - "index" - ] - } - } - } - } - }, - "required": [ - "index" - ] - } - } - }, - "required": [ - "type" - ] - }, - "axes": { - "type": "array", - "description": "坐标轴配置数组", - "items": { - "type": "object", - "description": "坐标轴配置", - "properties": { - "type": { - "type": "string", - "description": "坐标轴类型", - "enum": [ - "x", - "y", - "angle", - "radius" - ] - }, - "position": { - "type": "string", - "description": "坐标轴位置", - "enum": [ - "left", - "right", - "bottom" - ] - }, - "max": { - "type": "number", - "description": "最大值" - }, - "min": { - "type": "number", - "description": "最小值" - }, - "valueType": { - "type": "string", - "description": "是否将横轴数字视为文本,linear 表示将数字视为数值, ordinal 表示将数字视为文本", - "enum": [ - "ordinal", - "linear" - ] - }, - "title": { - "type": "object", - "description": "坐标轴标题配置", - "properties": { - "text": { - "type": "string", - "description": "标题文本" - }, - "fontSize": { - "type": "number", - "description": "字体大小" - }, - "bold": { - "type": "boolean", - "description": "是否加粗" - }, - "italic": { - "type": "boolean", - "description": "是否斜体" - }, - "underline": { - "type": "boolean", - "description": "是否下划线" - }, - "strikethrough": { - "type": "boolean", - "description": "是否删除线" - }, - "color": { - "type": "string", - "description": "字体颜色" - } - }, - "required": [ - "text" - ] - }, - "label": { - "type": "object", - "description": "坐标轴标签配置", - "properties": { - "angle": { - "type": "number", - "description": "旋转角度,可选值:-90, -45, 0, 45, 90" - }, - "fontSize": { - "type": "number", - "description": "字体大小" - }, - "bold": { - "type": "boolean", - "description": "是否加粗" - }, - "italic": { - "type": "boolean", - "description": "是否斜体" - }, - "underline": { - "type": "boolean", - "description": "是否下划线" - }, - "strikethrough": { - "type": "boolean", - "description": "是否删除线" - }, - "color": { - "type": "string", - "description": "字体颜色" - } - } - }, - "axisLine": { - "type": "boolean", - "description": "是否显示轴线" - }, - "gridLine": { - "oneOf": [ - { - "type": "object", - "description": "网格线配置", - "properties": { - "width": { - "type": "number", - "description": "网格线宽度" - }, - "color": { - "type": "string", - "description": "网格线颜色" - } - } - }, - { - "type": "boolean", - "description": "false 表示隐藏网格线" - } - ] - } - }, - "required": [ - "type" - ] - } - } - }, - "required": [ - "plot" - ] - }, - "data": { - "type": "object", - "description": "图表数据配置", - "properties": { - "isStaticData": { - "type": "boolean", - "description": "是否为静态数据" - }, - "includeHiddenOrFilter": { - "type": "boolean", - "description": "是否包含隐藏或过滤的数据" - }, - "direction": { - "type": "string", - "description": "数据方向,可选值:row(行方向)或column(列方向),常用值为column", - "enum": [ - "row", - "column" - ] - }, - "headerMode": { - "type": "string", - "description": "表头模式。inline 表示 refs 首行/首列就是表头;detached 表示 refs 仅覆盖实际数据范围,维度名和系列名需通过 nameRef 显式指定。", - "enum": [ - "inline", - "detached" - ] - }, - "refs": { - "type": "array", - "description": "数据源引用范围数组", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "图表数据源范围,格式为 'SheetName!A1:D100' 的连续区域" - } - }, - "required": [ - "value" - ] - } - }, - "dim1": { - "type": "object", - "description": "维度1配置(类别维度)", - "properties": { - "serie": { - "type": "object", - "description": "系列配置(非静态数据时传此参数)", - "properties": { - "index": { - "type": "number", - "description": "系列索引,数据源从左往右,从 1 开始计数对应数据源的列索引;如果数据方向direction为 'row',则为从上往下,从 1 开始计数对应数据源的行索引" - }, - "aggregate": { - "type": "boolean", - "description": "是否汇总相同的类别,默认为true,true 情况下 aggregateType 才生效" - }, - "nameRef": { - "type": "string", - "description": "可选。维度名称的单元格引用(A1 表示法,如 'Sheet1!A1')。当维度名称不直接来自 refs 首行/首列时,可通过此字段把维度名显式指向目标表头单元格,图表会以该单元格当前值作为类别维度名展示。" } - }, - "required": [ - "index" - ] + } }, - "field": { + "dim2": { "type": "object", - "description": "字段配置(静态数据时传此参数)", + "description": "维度2配置(值维度)", "properties": { - "valueType": { - "type": "string", - "description": "值类型", - "enum": [ - "number", - "string" - ] - }, - "name": { - "type": "string", - "description": "字段名称" - }, - "text": { - "type": "string", - "description": "字段文本数据" - } - }, - "required": [ - "text" - ] - } - } - }, - "dim2": { - "type": "object", - "description": "维度2配置(值维度)", - "properties": { - "series": { - "type": "array", - "description": "系列配置数组(非静态数据时传此参数)", - "items": { - "type": "object", - "properties": { - "index": { - "type": "number", - "description": "系列索引,数据源从左往右,从 1 开始计数对应数据源的列索引;如果数据方向direction为 'row',则为从上往下,从 1 开始计数对应数据源的行索引" - }, - "aggregateType": { - "type": "string", - "description": "汇总方式,默认为'sum',仅在 aggregate 为 true 时生效。count 只统计数值单元格;counta 统计所有非空单元格(含文本),按文本/分类列统计出现次数(如各类别的数量、频次分布)时用 counta。", - "enum": [ - "sum", - "average", - "count", - "counta", - "min", - "max", - "median" + "series": { + "type": "array", + "description": "系列配置数组(非静态数据时传此参数)", + "items": { + "type": "object", + "properties": { + "index": { + "type": "number", + "description": "系列索引,数据源从左往右,从 1 开始计数对应数据源的列索引;如果数据方向direction为 'row',则为从上往下,从 1 开始计数对应数据源的行索引" + }, + "aggregateType": { + "type": "string", + "description": "汇总方式,默认为'sum',仅在 aggregate 为 true 时生效。count 只统计数值单元格;counta 统计所有非空单元格(含文本),按文本/分类列统计出现次数(如各类别的数量、频次分布)时用 counta。", + "enum": [ + "sum", + "average", + "count", + "counta", + "min", + "max", + "median" + ] + }, + "nameRef": { + "type": "string", + "description": "可选。系列名称的单元格引用(A1 表示法,如 'Sheet1!C1')。当系列名称不直接来自 refs 首行/首列时,可通过此字段把系列名显式指向目标表头单元格,图表会以该单元格当前值作为系列名(图例/tooltip)展示。" + } + }, + "required": [ + "index" ] - }, - "nameRef": { - "type": "string", - "description": "可选。系列名称的单元格引用(A1 表示法,如 'Sheet1!C1')。当系列名称不直接来自 refs 首行/首列时,可通过此字段把系列名显式指向目标表头单元格,图表会以该单元格当前值作为系列名(图例/tooltip)展示。" } }, - "required": [ - "index" - ] - } - }, - "fields": { - "type": "array", - "description": "字段配置数组(静态数据时传此参数)", - "items": { - "type": "object", - "properties": { - "valueType": { - "type": "string", - "description": "值类型", - "enum": [ - "number" + "fields": { + "type": "array", + "description": "字段配置数组(静态数据时传此参数)", + "items": { + "type": "object", + "properties": { + "valueType": { + "type": "string", + "description": "值类型", + "enum": [ + "number" + ] + }, + "name": { + "type": "string", + "description": "字段名称" + }, + "text": { + "type": "string", + "description": "字段文本数据" + } + }, + "required": [ + "text" ] - }, - "name": { - "type": "string", - "description": "字段名称" - }, - "text": { - "type": "string", - "description": "字段文本数据" } - }, - "required": [ - "text" - ] + } } } } } - } + }, + "required": [ + "plotArea", + "data" + ] + }, + { + "type": "object", + "description": "update 的局部图表快照变更" } - }, - "required": [ - "plotArea", - "data" ] } } @@ -2589,209 +2600,25 @@ ] }, "snapshot": { - "type": "object", - "description": "图表快照配置。更新图表时必须传入完整的图表属性定义,不能只传修改的部分。应先通过 get_chart_objects 获取当前图表快照,修改需要变更的字段后,将完整快照传入。", - "properties": { - "title": { - "type": "object", - "description": "图表标题配置", - "properties": { - "text": { - "type": "string", - "description": "标题文本" - }, - "textAlign": { - "type": "string", - "description": "标题对齐方式", - "enum": [ - "left", - "center", - "right" - ] - }, - "fontSize": { - "type": "number", - "description": "字体大小" - }, - "bold": { - "type": "boolean", - "description": "是否加粗" - }, - "italic": { - "type": "boolean", - "description": "是否斜体" - }, - "underline": { - "type": "boolean", - "description": "是否下划线" - }, - "strikethrough": { - "type": "boolean", - "description": "是否删除线" - }, - "color": { - "type": "string", - "description": "字体颜色,格式为 #RRGGBB" - } - }, - "required": [ - "text" - ] - }, - "subTitle": { - "type": "object", - "description": "图表副标题配置", - "properties": { - "text": { - "type": "string", - "description": "副标题文本" - }, - "textAlign": { - "type": "string", - "description": "副标题对齐方式", - "enum": [ - "left", - "center", - "right" - ] - }, - "fontSize": { - "type": "number", - "description": "字体大小" - }, - "bold": { - "type": "boolean", - "description": "是否加粗" - }, - "italic": { - "type": "boolean", - "description": "是否斜体" - }, - "underline": { - "type": "boolean", - "description": "是否下划线" - }, - "strikethrough": { - "type": "boolean", - "description": "是否删除线" - }, - "color": { - "type": "string", - "description": "字体颜色,格式为 #RRGGBB" - } - }, - "required": [ - "text" - ] - }, - "style": { + "description": "图表快照配置。create 需要完整配置;update 可只提供需要变更的字段,对象字段递归合并,数组整段替换,未提供的字段保持现状。", + "anyOf": [ + { "type": "object", - "description": "图表样式配置", "properties": { - "background": { - "type": "object", - "description": "背景配置", - "properties": { - "color": { - "type": "string", - "description": "背景颜色,格式为 #RRGGBB" - } - } - }, - "font": { - "type": "object", - "description": "字体配置", - "properties": { - "size": { - "type": "number", - "description": "字体大小" - }, - "color": { - "type": "string", - "description": "字体颜色,格式为 #RRGGBB" - } - } - }, - "border": { + "title": { "type": "object", - "description": "边框配置", + "description": "图表标题配置", "properties": { - "color": { - "type": "string", - "description": "边框颜色,格式为 #RRGGBB" - }, - "width": { - "type": "number", - "description": "边框宽度" - }, - "style": { + "text": { "type": "string", - "description": "边框样式", - "enum": [ - "solid", - "dashed", - "dotted" - ] - }, - "radius": { - "type": "number", - "description": "边框圆角" - } - } - }, - "colorTheme": { - "type": "array", - "description": "颜色主题配置。支持两种模式:1. 仅传一个预设主题名称(字符串数组,长度为1);2. 传多个自定义十六进制颜色字符串。预设主题:brandColorSeries@v2、rainbowColorSeries@v2、complementaryColorSeries@v2、converseColorSeries@v2、primaryColorSeries@v2、singleColorSeries-B-@v2、singleColorSeries-W-@v2、singleColorSeries-G-@v2、singleColorSeries-Y-@v2、singleColorSeries-O-@v2、singleColorSeries-R-@v2、singleColorSeries-D-@v2。十六进制格式:#RRGGBB", - "oneOf": [ - { - "minItems": 1, - "maxItems": 1, - "items": { - "type": "string", - "enum": [ - "brandColorSeries@v2", - "rainbowColorSeries@v2", - "complementaryColorSeries@v2", - "converseColorSeries@v2", - "primaryColorSeries@v2", - "singleColorSeries-B-@v2", - "singleColorSeries-W-@v2", - "singleColorSeries-G-@v2", - "singleColorSeries-Y-@v2", - "singleColorSeries-O-@v2", - "singleColorSeries-R-@v2", - "singleColorSeries-D-@v2" - ] - } + "description": "标题文本" }, - { - "minItems": 2, - "items": { - "type": "string", - "description": "颜色字符串,十六进制格式:#RRGGBB" - } - } - ] - }, - "colorGradient": { - "type": "boolean", - "description": "是否启用颜色渐变" - } - } - }, - "legend": { - "oneOf": [ - { - "type": "object", - "description": "图例配置", - "properties": { - "position": { + "textAlign": { "type": "string", - "description": "图例位置", + "description": "标题对齐方式", "enum": [ - "top", - "bottom", "left", + "center", "right" ] }, @@ -2819,1468 +2646,1660 @@ "type": "string", "description": "字体颜色,格式为 #RRGGBB" } - } + }, + "required": [ + "text" + ] }, - { - "type": "boolean", - "description": "false 表示隐藏图例" - } - ] - }, - "plotArea": { - "type": "object", - "description": "绘图区域配置", - "properties": { - "plot": { + "subTitle": { "type": "object", - "description": "绘图配置", + "description": "图表副标题配置", "properties": { - "type": { - "type": "string", - "description": "图表类型", - "enum": [ - "bar", - "column", - "line", - "area", - "combo", - "pie", - "radar", - "scatter" - ] - }, - "comboType": { + "text": { "type": "string", - "description": "组合图表默认类型", - "enum": [ - "column", - "line", - "area" - ] + "description": "副标题文本" }, - "yAxisPosition": { + "textAlign": { "type": "string", - "description": "Y轴位置", + "description": "副标题对齐方式", "enum": [ "left", + "center", "right" ] }, - "extra": { - "type": "object", - "description": "额外配置", - "properties": { - "smooth": { - "type": "boolean", - "description": "是否平滑曲线" - }, - "step": { - "type": "boolean", - "description": "是否阶梯图" - }, - "stack": { - "type": "object", - "description": "堆叠配置", - "properties": { - "percentage": { - "type": "boolean", - "description": "是否百分比堆叠" - } - } - }, - "radar": { - "type": "object", - "description": "雷达图配置", - "properties": { - "shape": { - "type": "string", - "description": "雷达图形状", - "enum": [ - "polygon", - "circle" - ] - }, - "area": { - "type": "boolean", - "description": "是否填充区域" - } - } - } - } + "fontSize": { + "type": "number", + "description": "字体大小" + }, + "bold": { + "type": "boolean", + "description": "是否加粗" + }, + "italic": { + "type": "boolean", + "description": "是否斜体" }, - "points": { + "underline": { + "type": "boolean", + "description": "是否下划线" + }, + "strikethrough": { + "type": "boolean", + "description": "是否删除线" + }, + "color": { + "type": "string", + "description": "字体颜色,格式为 #RRGGBB" + } + }, + "required": [ + "text" + ] + }, + "style": { + "type": "object", + "description": "图表样式配置", + "properties": { + "background": { "type": "object", - "description": "全系列数据点配置,折线图、面积图、雷达图、散点图、组合图生效。", + "description": "背景配置", "properties": { "color": { "type": "string", - "description": "数据点颜色" - }, - "shape": { - "type": "string", - "description": "数据点形状", - "enum": [ - "circle", - "triangle", - "rect", - "diamond", - "square" - ] - }, + "description": "背景颜色,格式为 #RRGGBB" + } + } + }, + "font": { + "type": "object", + "description": "字体配置", + "properties": { "size": { "type": "number", - "description": "数据点大小" - }, - "fillGradient": { - "type": "object", - "description": "数据点填充渐变配置", - "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" - }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" - }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] - }, - "strokeGradient": { - "type": "object", - "description": "数据点描边渐变配置", - "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" - }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" - }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] + "description": "字体大小" }, - "point": { - "type": "array", - "description": "单个数据点配置数组", - "items": { - "type": "object", - "properties": { - "index": { - "type": "number", - "description": "数据点索引" - }, - "color": { - "type": "string", - "description": "颜色" - }, - "shape": { - "type": "string", - "description": "形状" - }, - "size": { - "type": "number", - "description": "大小" - }, - "fillGradient": { - "type": "object", - "description": "单个数据点填充渐变配置", - "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" - }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" - }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] - } - }, - "required": [ - "index" - ] - } + "color": { + "type": "string", + "description": "字体颜色,格式为 #RRGGBB" } } }, - "lines": { + "border": { "type": "object", - "description": "全系列线条配置,折线图、面积图、雷达图、组合图生效。", + "description": "边框配置", "properties": { "color": { "type": "string", - "description": "线条颜色" + "description": "边框颜色,格式为 #RRGGBB" }, "width": { "type": "number", - "description": "线条宽度" + "description": "边框宽度" }, "style": { "type": "string", - "description": "线条样式", + "description": "边框样式", "enum": [ "solid", "dashed", "dotted" ] }, - "invalidType": { + "radius": { + "type": "number", + "description": "边框圆角" + } + } + }, + "colorTheme": { + "type": "array", + "description": "颜色主题配置。支持两种模式:1. 仅传一个预设主题名称(字符串数组,长度为1);2. 传多个自定义十六进制颜色字符串。预设主题:brandColorSeries@v2、rainbowColorSeries@v2、complementaryColorSeries@v2、converseColorSeries@v2、primaryColorSeries@v2、singleColorSeries-B-@v2、singleColorSeries-W-@v2、singleColorSeries-G-@v2、singleColorSeries-Y-@v2、singleColorSeries-O-@v2、singleColorSeries-R-@v2、singleColorSeries-D-@v2。十六进制格式:#RRGGBB", + "oneOf": [ + { + "minItems": 1, + "maxItems": 1, + "items": { + "type": "string", + "enum": [ + "brandColorSeries@v2", + "rainbowColorSeries@v2", + "complementaryColorSeries@v2", + "converseColorSeries@v2", + "primaryColorSeries@v2", + "singleColorSeries-B-@v2", + "singleColorSeries-W-@v2", + "singleColorSeries-G-@v2", + "singleColorSeries-Y-@v2", + "singleColorSeries-O-@v2", + "singleColorSeries-R-@v2", + "singleColorSeries-D-@v2" + ] + } + }, + { + "minItems": 2, + "items": { + "type": "string", + "description": "颜色字符串,十六进制格式:#RRGGBB" + } + } + ] + }, + "colorGradient": { + "type": "boolean", + "description": "是否启用颜色渐变" + } + } + }, + "legend": { + "oneOf": [ + { + "type": "object", + "description": "图例配置", + "properties": { + "position": { "type": "string", - "description": "无效值处理方式", + "description": "图例位置", "enum": [ - "break", - "zero", - "link" + "top", + "bottom", + "left", + "right" ] }, - "strokeGradient": { - "type": "object", - "description": "线条描边渐变配置", - "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" - }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" - }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] + "fontSize": { + "type": "number", + "description": "字体大小" + }, + "bold": { + "type": "boolean", + "description": "是否加粗" + }, + "italic": { + "type": "boolean", + "description": "是否斜体" + }, + "underline": { + "type": "boolean", + "description": "是否下划线" + }, + "strikethrough": { + "type": "boolean", + "description": "是否删除线" + }, + "color": { + "type": "string", + "description": "字体颜色,格式为 #RRGGBB" } } }, - "areas": { + { + "type": "boolean", + "description": "false 表示隐藏图例" + } + ] + }, + "plotArea": { + "type": "object", + "description": "绘图区域配置", + "properties": { + "plot": { "type": "object", - "description": "全系列面积填充配置,面积图、雷达图、组合图生效。", + "description": "绘图配置", "properties": { - "color": { + "type": { + "type": "string", + "description": "图表类型", + "enum": [ + "bar", + "column", + "line", + "area", + "combo", + "pie", + "radar", + "scatter" + ] + }, + "comboType": { + "type": "string", + "description": "组合图表默认类型", + "enum": [ + "column", + "line", + "area" + ] + }, + "yAxisPosition": { "type": "string", - "description": "区域填充颜色" + "description": "Y轴位置", + "enum": [ + "left", + "right" + ] }, - "fillGradient": { + "extra": { "type": "object", - "description": "区域填充渐变", + "description": "额外配置", "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" + "smooth": { + "type": "boolean", + "description": "是否平滑曲线" }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" + "step": { + "type": "boolean", + "description": "是否阶梯图" }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" + "stack": { + "type": "object", + "description": "堆叠配置", + "properties": { + "percentage": { + "type": "boolean", + "description": "是否百分比堆叠" + } + } }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" + "radar": { + "type": "object", + "description": "雷达图配置", + "properties": { + "shape": { + "type": "string", + "description": "雷达图形状", + "enum": [ + "polygon", + "circle" + ] + }, + "area": { + "type": "boolean", + "description": "是否填充区域" + } + } + } + } + }, + "points": { + "type": "object", + "description": "全系列数据点配置,折线图、面积图、雷达图、散点图、组合图生效。", + "properties": { + "color": { + "type": "string", + "description": "数据点颜色" }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" + "shape": { + "type": "string", + "description": "数据点形状", + "enum": [ + "circle", + "triangle", + "rect", + "diamond", + "square" + ] }, - "r0": { + "size": { "type": "number", - "description": "径向渐变起点半径" + "description": "数据点大小" }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" + "fillGradient": { + "type": "object", + "description": "数据点填充渐变配置", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" + "strokeGradient": { + "type": "object", + "description": "数据点描边渐变配置", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] }, - "stops": { + "point": { "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, + "description": "单个数据点配置数组", "items": { "type": "object", "properties": { - "offset": { + "index": { "type": "number", - "description": "色标位置 0-1" + "description": "数据点索引" }, "color": { "type": "string", - "description": "色标颜色" + "description": "颜色" + }, + "shape": { + "type": "string", + "description": "形状" }, - "opacity": { + "size": { "type": "number", - "description": "色标透明度 0-1" + "description": "大小" + }, + "fillGradient": { + "type": "object", + "description": "单个数据点填充渐变配置", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] } }, "required": [ - "offset", - "color" + "index" ] } } - }, - "required": [ - "type", - "stops" - ] - } - } - }, - "bars": { - "type": "object", - "description": "全系列柱状图、条形图、组合图生效。", - "properties": { - "color": { - "type": "string", - "description": "柱子颜色" - }, - "borderColor": { - "type": "string", - "description": "边框颜色" - }, - "borderWidth": { - "type": "number", - "description": "边框宽度" - }, - "borderStyle": { - "type": "string", - "description": "边框样式", - "enum": [ - "solid", - "dashed", - "dotted" - ] - }, - "width": { - "type": "number", - "description": "柱子宽度" - }, - "gap": { - "type": "number", - "description": "柱子间距比例,0-1之间" - }, - "backgroundColor": { - "type": "string", - "description": "背景颜色" + } }, - "fillGradient": { + "lines": { "type": "object", - "description": "柱子填充渐变", + "description": "全系列线条配置,折线图、面积图、雷达图、组合图生效。", "properties": { - "type": { + "color": { "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" + "description": "线条颜色" }, - "x1": { + "width": { "type": "number", - "description": "终点 x,0-1 归一化坐标" + "description": "线条宽度" }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" + "style": { + "type": "string", + "description": "线条样式", + "enum": [ + "solid", + "dashed", + "dotted" + ] }, - "gradientMethod": { + "invalidType": { "type": "string", - "description": "渐变插值方法" + "description": "无效值处理方式", + "enum": [ + "break", + "zero", + "link" + ] }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } + "strokeGradient": { + "type": "object", + "description": "线条描边渐变配置", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" }, - "required": [ - "offset", - "color" - ] - } + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] } - }, - "required": [ - "type", - "stops" - ] + } }, - "strokeGradient": { + "areas": { "type": "object", - "description": "柱子描边渐变", + "description": "全系列面积填充配置,面积图、雷达图、组合图生效。", "properties": { - "type": { + "color": { "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" + "description": "区域填充颜色" }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" + "fillGradient": { + "type": "object", + "description": "区域填充渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] + } + } + }, + "bars": { + "type": "object", + "description": "全系列柱状图、条形图、组合图生效。", + "properties": { + "color": { + "type": "string", + "description": "柱子颜色" }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" + "borderColor": { + "type": "string", + "description": "边框颜色" }, - "x1": { + "borderWidth": { "type": "number", - "description": "终点 x,0-1 归一化坐标" + "description": "边框宽度" }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" + "borderStyle": { + "type": "string", + "description": "边框样式", + "enum": [ + "solid", + "dashed", + "dotted" + ] }, - "r0": { + "width": { "type": "number", - "description": "径向渐变起点半径" + "description": "柱子宽度" }, - "r1": { + "gap": { "type": "number", - "description": "径向渐变终点半径" + "description": "柱子间距比例,0-1之间" }, - "gradientMethod": { + "backgroundColor": { "type": "string", - "description": "渐变插值方法" + "description": "背景颜色" }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } + "fillGradient": { + "type": "object", + "description": "柱子填充渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] - }, - "bar": { - "type": "array", - "description": "单个柱子配置数组", - "items": { - "type": "object", - "properties": { - "index": { - "type": "number", - "description": "柱子索引" - }, - "color": { - "type": "string", - "description": "颜色" - }, - "borderColor": { - "type": "string", - "description": "边框颜色" - }, - "borderWidth": { - "type": "number", - "description": "边框宽度" + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } }, - "borderStyle": { - "type": "string", - "description": "边框样式" + "required": [ + "type", + "stops" + ] + }, + "strokeGradient": { + "type": "object", + "description": "柱子描边渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } }, - "fillGradient": { + "required": [ + "type", + "stops" + ] + }, + "bar": { + "type": "array", + "description": "单个柱子配置数组", + "items": { "type": "object", - "description": "单个柱子填充渐变", "properties": { - "type": { - "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { + "index": { "type": "number", - "description": "终点 x,0-1 归一化坐标" + "description": "柱子索引" }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" + "color": { + "type": "string", + "description": "颜色" }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" + "borderColor": { + "type": "string", + "description": "边框颜色" }, - "r1": { + "borderWidth": { "type": "number", - "description": "径向渐变终点半径" + "description": "边框宽度" }, - "gradientMethod": { + "borderStyle": { "type": "string", - "description": "渐变插值方法" + "description": "边框样式" }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } + "fillGradient": { + "type": "object", + "description": "单个柱子填充渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" }, - "required": [ - "offset", - "color" - ] - } + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] } }, "required": [ - "type", - "stops" + "index" ] } + } + } + }, + "labels": { + "type": "object", + "description": "数据标签配置。labels 对象的存在性即开关:不显示数据标签时省略整个 labels 字段;一旦传入 labels(即便 value/category/series/percentage 全部置为 false),数据标签仍会显示,且默认兜底显示 value。", + "properties": { + "position": { + "type": "string", + "description": "标签位置", + "enum": [ + "auto", + "top", + "bottom", + "left", + "right", + "center", + "inside", + "outside" + ] }, - "required": [ - "index" - ] + "series": { + "type": "boolean", + "description": "是否显示系列名" + }, + "category": { + "type": "boolean", + "description": "是否显示类别名" + }, + "value": { + "type": "boolean", + "description": "是否显示值" + }, + "percentage": { + "type": "boolean", + "description": "是否显示百分比" + }, + "fontSize": { + "type": "number", + "description": "字体大小" + }, + "bold": { + "type": "boolean", + "description": "是否加粗" + }, + "italic": { + "type": "boolean", + "description": "是否斜体" + }, + "underline": { + "type": "boolean", + "description": "是否下划线" + }, + "strikethrough": { + "type": "boolean", + "description": "是否删除线" + }, + "color": { + "type": "string", + "description": "字体颜色" + } } - } - } - }, - "labels": { - "type": "object", - "description": "数据标签配置。labels 对象的存在性即开关:不显示数据标签时省略整个 labels 字段;一旦传入 labels(即便 value/category/series/percentage 全部置为 false),数据标签仍会显示,且默认兜底显示 value。", - "properties": { - "position": { - "type": "string", - "description": "标签位置", - "enum": [ - "auto", - "top", - "bottom", - "left", - "right", - "center", - "inside", - "outside" - ] }, "series": { - "type": "boolean", - "description": "是否显示系列名" - }, - "category": { - "type": "boolean", - "description": "是否显示类别名" - }, - "value": { - "type": "boolean", - "description": "是否显示值" - }, - "percentage": { - "type": "boolean", - "description": "是否显示百分比" - }, - "fontSize": { - "type": "number", - "description": "字体大小" - }, - "bold": { - "type": "boolean", - "description": "是否加粗" - }, - "italic": { - "type": "boolean", - "description": "是否斜体" - }, - "underline": { - "type": "boolean", - "description": "是否下划线" - }, - "strikethrough": { - "type": "boolean", - "description": "是否删除线" - }, - "color": { - "type": "string", - "description": "字体颜色" - } - } - }, - "series": { - "type": "array", - "description": "单个系列配置数组", - "items": { - "type": "object", - "description": "系列配置", - "properties": { - "index": { - "type": "number", - "description": "数据列索引,与 dim2.series[].index 值一致,从 1 开始(1 通常对应首列标签维度,2+ 对应后续数据维度列)" - }, - "comboType": { - "type": "string", - "description": "组合图下该系列的图表类型,仅在 type 为 combo 时生效", - "enum": [ - "column", - "line", - "area" - ] - }, - "yAxisPosition": { - "type": "string", - "description": "Y轴位置", - "enum": [ - "left", - "right" - ] - }, - "points": { - "type": "object", - "description": "数据点配置,配置项同 plotArea.points" - }, - "line": { - "type": "object", - "description": "线条配置,配置项同 plotArea.lines" - }, - "area": { - "type": "object", - "description": "区域填充配置,配置项同 plotArea.areas" - }, - "bars": { - "type": "object", - "description": "柱状图配置,配置项同 plotArea.bars" - }, - "labels": { - "type": "object", - "description": "数据标签配置(覆盖单系列,配置项同 plotArea.plot.labels)。不覆盖该系列数据标签时省略整个 labels 字段;一旦传入 labels(即便显示开关全部置为 false),该系列仍会显示数据标签。" - }, - "sectors": { + "type": "array", + "description": "单个系列配置数组", + "items": { "type": "object", - "description": "扇区配置(饼图)", + "description": "系列配置", "properties": { - "borderColor": { + "index": { + "type": "number", + "description": "数据列索引,与 dim2.series[].index 值一致,从 1 开始(1 通常对应首列标签维度,2+ 对应后续数据维度列)" + }, + "comboType": { "type": "string", - "description": "边框颜色" + "description": "组合图下该系列的图表类型,仅在 type 为 combo 时生效", + "enum": [ + "column", + "line", + "area" + ] }, - "innerRadius": { - "type": "number", - "description": "内半径比例,0-1之间" + "yAxisPosition": { + "type": "string", + "description": "Y轴位置", + "enum": [ + "left", + "right" + ] }, - "offsetRadius": { - "type": "number", - "description": "偏移半径比例" + "points": { + "type": "object", + "description": "数据点配置,配置项同 plotArea.points" }, - "startAngle": { - "type": "number", - "description": "起始角度,0-359" + "line": { + "type": "object", + "description": "线条配置,配置项同 plotArea.lines" + }, + "area": { + "type": "object", + "description": "区域填充配置,配置项同 plotArea.areas" + }, + "bars": { + "type": "object", + "description": "柱状图配置,配置项同 plotArea.bars" + }, + "labels": { + "type": "object", + "description": "数据标签配置(覆盖单系列,配置项同 plotArea.plot.labels)。不覆盖该系列数据标签时省略整个 labels 字段;一旦传入 labels(即便显示开关全部置为 false),该系列仍会显示数据标签。" }, - "fillGradient": { + "sectors": { "type": "object", - "description": "扇区填充渐变", + "description": "扇区配置(饼图)", "properties": { - "type": { + "borderColor": { "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" - }, - "y0": { - "type": "number", - "description": "起点 y,0-1 归一化坐标" - }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" + "description": "边框颜色" }, - "y1": { + "innerRadius": { "type": "number", - "description": "终点 y,0-1 归一化坐标" + "description": "内半径比例,0-1之间" }, - "r0": { + "offsetRadius": { "type": "number", - "description": "径向渐变起点半径" + "description": "偏移半径比例" }, - "r1": { + "startAngle": { "type": "number", - "description": "径向渐变终点半径" + "description": "起始角度,0-359" }, - "gradientMethod": { - "type": "string", - "description": "渐变插值方法" + "fillGradient": { + "type": "object", + "description": "扇区填充渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" + }, + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] }, - "stops": { + "sector": { "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, + "description": "单个扇区配置数组", "items": { "type": "object", "properties": { - "offset": { + "index": { "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" + "description": "扇区索引" }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } - }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] - }, - "sector": { - "type": "array", - "description": "单个扇区配置数组", - "items": { - "type": "object", - "properties": { - "index": { - "type": "number", - "description": "扇区索引" - }, - "borderColor": { - "type": "string", - "description": "边框颜色" - }, - "offsetRadius": { - "type": "number", - "description": "偏移半径" - }, - "color": { - "type": "string", - "description": "颜色" - }, - "fillGradient": { - "type": "object", - "description": "单个扇区填充渐变", - "properties": { - "type": { + "borderColor": { "type": "string", - "enum": [ - "linear", - "radial" - ], - "description": "渐变类型" - }, - "x0": { - "type": "number", - "description": "起点 x,0-1 归一化坐标" + "description": "边框颜色" }, - "y0": { + "offsetRadius": { "type": "number", - "description": "起点 y,0-1 归一化坐标" + "description": "偏移半径" }, - "x1": { - "type": "number", - "description": "终点 x,0-1 归一化坐标" - }, - "y1": { - "type": "number", - "description": "终点 y,0-1 归一化坐标" - }, - "r0": { - "type": "number", - "description": "径向渐变起点半径" - }, - "r1": { - "type": "number", - "description": "径向渐变终点半径" - }, - "gradientMethod": { + "color": { "type": "string", - "description": "渐变插值方法" + "description": "颜色" }, - "stops": { - "type": "array", - "description": "渐变色标数组,至少 2 个", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "offset": { - "type": "number", - "description": "色标位置 0-1" - }, - "color": { - "type": "string", - "description": "色标颜色" - }, - "opacity": { - "type": "number", - "description": "色标透明度 0-1" - } + "fillGradient": { + "type": "object", + "description": "单个扇区填充渐变", + "properties": { + "type": { + "type": "string", + "enum": [ + "linear", + "radial" + ], + "description": "渐变类型" }, - "required": [ - "offset", - "color" - ] - } - } - }, - "required": [ - "type", - "stops" - ] - } - }, - "required": [ - "index" - ] - } - } - } - } - }, - "required": [ - "index" - ] - } - } - }, - "required": [ - "type" - ] - }, - "axes": { - "type": "array", - "description": "坐标轴配置数组", - "items": { - "type": "object", - "description": "坐标轴配置", - "properties": { - "type": { - "type": "string", - "description": "坐标轴类型", - "enum": [ - "x", - "y", - "angle", - "radius" - ] - }, - "position": { - "type": "string", - "description": "坐标轴位置", - "enum": [ - "left", - "right", - "bottom" - ] - }, - "max": { - "type": "number", - "description": "最大值" - }, - "min": { - "type": "number", - "description": "最小值" - }, - "valueType": { - "type": "string", - "description": "是否将横轴数字视为文本,linear 表示将数字视为数值, ordinal 表示将数字视为文本", - "enum": [ - "ordinal", - "linear" - ] + "x0": { + "type": "number", + "description": "起点 x,0-1 归一化坐标" + }, + "y0": { + "type": "number", + "description": "起点 y,0-1 归一化坐标" + }, + "x1": { + "type": "number", + "description": "终点 x,0-1 归一化坐标" + }, + "y1": { + "type": "number", + "description": "终点 y,0-1 归一化坐标" + }, + "r0": { + "type": "number", + "description": "径向渐变起点半径" + }, + "r1": { + "type": "number", + "description": "径向渐变终点半径" + }, + "gradientMethod": { + "type": "string", + "description": "渐变插值方法" + }, + "stops": { + "type": "array", + "description": "渐变色标数组,至少 2 个", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "色标位置 0-1" + }, + "color": { + "type": "string", + "description": "色标颜色" + }, + "opacity": { + "type": "number", + "description": "色标透明度 0-1" + } + }, + "required": [ + "offset", + "color" + ] + } + } + }, + "required": [ + "type", + "stops" + ] + } + }, + "required": [ + "index" + ] + } + } + } + } + }, + "required": [ + "index" + ] + } + } }, - "title": { + "required": [ + "type" + ] + }, + "axes": { + "type": "array", + "description": "坐标轴配置数组", + "items": { "type": "object", - "description": "坐标轴标题配置", + "description": "坐标轴配置", "properties": { - "text": { + "type": { "type": "string", - "description": "标题文本" - }, - "fontSize": { - "type": "number", - "description": "字体大小" - }, - "bold": { - "type": "boolean", - "description": "是否加粗" - }, - "italic": { - "type": "boolean", - "description": "是否斜体" - }, - "underline": { - "type": "boolean", - "description": "是否下划线" - }, - "strikethrough": { - "type": "boolean", - "description": "是否删除线" + "description": "坐标轴类型", + "enum": [ + "x", + "y", + "angle", + "radius" + ] }, - "color": { + "position": { "type": "string", - "description": "字体颜色" - } - }, - "required": [ - "text" - ] - }, - "label": { - "type": "object", - "description": "坐标轴标签配置", - "properties": { - "angle": { - "type": "number", - "description": "旋转角度,可选值:-90, -45, 0, 45, 90" + "description": "坐标轴位置", + "enum": [ + "left", + "right", + "bottom" + ] }, - "fontSize": { + "max": { "type": "number", - "description": "字体大小" - }, - "bold": { - "type": "boolean", - "description": "是否加粗" + "description": "最大值" }, - "italic": { - "type": "boolean", - "description": "是否斜体" + "min": { + "type": "number", + "description": "最小值" }, - "underline": { - "type": "boolean", - "description": "是否下划线" + "valueType": { + "type": "string", + "description": "是否将横轴数字视为文本,linear 表示将数字视为数值, ordinal 表示将数字视为文本", + "enum": [ + "ordinal", + "linear" + ] }, - "strikethrough": { - "type": "boolean", - "description": "是否删除线" + "title": { + "type": "object", + "description": "坐标轴标题配置", + "properties": { + "text": { + "type": "string", + "description": "标题文本" + }, + "fontSize": { + "type": "number", + "description": "字体大小" + }, + "bold": { + "type": "boolean", + "description": "是否加粗" + }, + "italic": { + "type": "boolean", + "description": "是否斜体" + }, + "underline": { + "type": "boolean", + "description": "是否下划线" + }, + "strikethrough": { + "type": "boolean", + "description": "是否删除线" + }, + "color": { + "type": "string", + "description": "字体颜色" + } + }, + "required": [ + "text" + ] }, - "color": { - "type": "string", - "description": "字体颜色" - } - } - }, - "axisLine": { - "type": "boolean", - "description": "是否显示轴线" - }, - "gridLine": { - "oneOf": [ - { + "label": { "type": "object", - "description": "网格线配置", + "description": "坐标轴标签配置", "properties": { - "width": { + "angle": { + "type": "number", + "description": "旋转角度,可选值:-90, -45, 0, 45, 90" + }, + "fontSize": { "type": "number", - "description": "网格线宽度" + "description": "字体大小" + }, + "bold": { + "type": "boolean", + "description": "是否加粗" + }, + "italic": { + "type": "boolean", + "description": "是否斜体" + }, + "underline": { + "type": "boolean", + "description": "是否下划线" + }, + "strikethrough": { + "type": "boolean", + "description": "是否删除线" }, "color": { "type": "string", - "description": "网格线颜色" + "description": "字体颜色" } } }, - { + "axisLine": { "type": "boolean", - "description": "false 表示隐藏网格线" + "description": "是否显示轴线" + }, + "gridLine": { + "oneOf": [ + { + "type": "object", + "description": "网格线配置", + "properties": { + "width": { + "type": "number", + "description": "网格线宽度" + }, + "color": { + "type": "string", + "description": "网格线颜色" + } + } + }, + { + "type": "boolean", + "description": "false 表示隐藏网格线" + } + ] } + }, + "required": [ + "type" ] } - }, - "required": [ - "type" - ] - } - } - }, - "required": [ - "plot" - ] - }, - "data": { - "type": "object", - "description": "图表数据配置", - "properties": { - "isStaticData": { - "type": "boolean", - "description": "是否为静态数据" - }, - "includeHiddenOrFilter": { - "type": "boolean", - "description": "是否包含隐藏或过滤的数据" - }, - "direction": { - "type": "string", - "description": "数据方向,可选值:row(行方向)或column(列方向),常用值为column", - "enum": [ - "row", - "column" - ] - }, - "headerMode": { - "type": "string", - "description": "表头模式。inline 表示 refs 首行/首列就是表头;detached 表示 refs 仅覆盖实际数据范围,维度名和系列名需通过 nameRef 显式指定。", - "enum": [ - "inline", - "detached" + } + }, + "required": [ + "plot" ] }, - "refs": { - "type": "array", - "description": "数据源引用范围数组", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "图表数据源范围,格式为 'SheetName!A1:D100' 的连续区域" - } - }, - "required": [ - "value" - ] - } - }, - "dim1": { + "data": { "type": "object", - "description": "维度1配置(类别维度)", + "description": "图表数据配置", "properties": { - "serie": { - "type": "object", - "description": "系列配置(非静态数据时传此参数)", - "properties": { - "index": { - "type": "number", - "description": "系列索引,数据源从左往右,从 1 开始计数对应数据源的列索引;如果数据方向direction为 'row',则为从上往下,从 1 开始计数对应数据源的行索引" - }, - "aggregate": { - "type": "boolean", - "description": "是否汇总相同的类别,默认为true,true 情况下 aggregateType 才生效" - }, - "nameRef": { - "type": "string", - "description": "可选。维度名称的单元格引用(A1 表示法,如 'Sheet1!A1')。当维度名称不直接来自 refs 首行/首列时,可通过此字段把维度名显式指向目标表头单元格,图表会以该单元格当前值作为类别维度名展示。" - } - }, - "required": [ - "index" + "isStaticData": { + "type": "boolean", + "description": "是否为静态数据" + }, + "includeHiddenOrFilter": { + "type": "boolean", + "description": "是否包含隐藏或过滤的数据" + }, + "direction": { + "type": "string", + "description": "数据方向,可选值:row(行方向)或column(列方向),常用值为column", + "enum": [ + "row", + "column" ] }, - "field": { - "type": "object", - "description": "字段配置(静态数据时传此参数)", - "properties": { - "valueType": { - "type": "string", - "description": "值类型", - "enum": [ - "number", - "string" - ] - }, - "name": { - "type": "string", - "description": "字段名称" - }, - "text": { - "type": "string", - "description": "字段文本数据" - } - }, - "required": [ - "text" + "headerMode": { + "type": "string", + "description": "表头模式。inline 表示 refs 首行/首列就是表头;detached 表示 refs 仅覆盖实际数据范围,维度名和系列名需通过 nameRef 显式指定。", + "enum": [ + "inline", + "detached" ] - } - } - }, - "dim2": { - "type": "object", - "description": "维度2配置(值维度)", - "properties": { - "series": { + }, + "refs": { "type": "array", - "description": "系列配置数组(非静态数据时传此参数)", + "description": "数据源引用范围数组", "items": { "type": "object", "properties": { - "index": { - "type": "number", - "description": "系列索引,数据源从左往右,从 1 开始计数对应数据源的列索引;如果数据方向direction为 'row',则为从上往下,从 1 开始计数对应数据源的行索引" - }, - "aggregateType": { - "type": "string", - "description": "汇总方式,默认为'sum',仅在 aggregate 为 true 时生效。count 只统计数值单元格;counta 统计所有非空单元格(含文本),按文本/分类列统计出现次数(如各类别的数量、频次分布)时用 counta。", - "enum": [ - "sum", - "average", - "count", - "counta", - "min", - "max", - "median" - ] - }, - "nameRef": { + "value": { "type": "string", - "description": "可选。系列名称的单元格引用(A1 表示法,如 'Sheet1!C1')。当系列名称不直接来自 refs 首行/首列时,可通过此字段把系列名显式指向目标表头单元格,图表会以该单元格当前值作为系列名(图例/tooltip)展示。" + "description": "图表数据源范围,格式为 'SheetName!A1:D100' 的连续区域" } }, "required": [ - "index" + "value" ] } }, - "fields": { - "type": "array", - "description": "字段配置数组(静态数据时传此参数)", - "items": { - "type": "object", - "properties": { - "valueType": { - "type": "string", - "description": "值类型", - "enum": [ - "number" - ] + "dim1": { + "type": "object", + "description": "维度1配置(类别维度)", + "properties": { + "serie": { + "type": "object", + "description": "系列配置(非静态数据时传此参数)", + "properties": { + "index": { + "type": "number", + "description": "系列索引,数据源从左往右,从 1 开始计数对应数据源的列索引;如果数据方向direction为 'row',则为从上往下,从 1 开始计数对应数据源的行索引" + }, + "aggregate": { + "type": "boolean", + "description": "是否汇总相同的类别,默认为true,true 情况下 aggregateType 才生效" + }, + "nameRef": { + "type": "string", + "description": "可选。维度名称的单元格引用(A1 表示法,如 'Sheet1!A1')。当维度名称不直接来自 refs 首行/首列时,可通过此字段把维度名显式指向目标表头单元格,图表会以该单元格当前值作为类别维度名展示。" + } }, - "name": { - "type": "string", - "description": "字段名称" + "required": [ + "index" + ] + }, + "field": { + "type": "object", + "description": "字段配置(静态数据时传此参数)", + "properties": { + "valueType": { + "type": "string", + "description": "值类型", + "enum": [ + "number", + "string" + ] + }, + "name": { + "type": "string", + "description": "字段名称" + }, + "text": { + "type": "string", + "description": "字段文本数据" + } }, - "text": { - "type": "string", - "description": "字段文本数据" + "required": [ + "text" + ] + } + } + }, + "dim2": { + "type": "object", + "description": "维度2配置(值维度)", + "properties": { + "series": { + "type": "array", + "description": "系列配置数组(非静态数据时传此参数)", + "items": { + "type": "object", + "properties": { + "index": { + "type": "number", + "description": "系列索引,数据源从左往右,从 1 开始计数对应数据源的列索引;如果数据方向direction为 'row',则为从上往下,从 1 开始计数对应数据源的行索引" + }, + "aggregateType": { + "type": "string", + "description": "汇总方式,默认为'sum',仅在 aggregate 为 true 时生效。count 只统计数值单元格;counta 统计所有非空单元格(含文本),按文本/分类列统计出现次数(如各类别的数量、频次分布)时用 counta。", + "enum": [ + "sum", + "average", + "count", + "counta", + "min", + "max", + "median" + ] + }, + "nameRef": { + "type": "string", + "description": "可选。系列名称的单元格引用(A1 表示法,如 'Sheet1!C1')。当系列名称不直接来自 refs 首行/首列时,可通过此字段把系列名显式指向目标表头单元格,图表会以该单元格当前值作为系列名(图例/tooltip)展示。" + } + }, + "required": [ + "index" + ] } }, - "required": [ - "text" - ] + "fields": { + "type": "array", + "description": "字段配置数组(静态数据时传此参数)", + "items": { + "type": "object", + "properties": { + "valueType": { + "type": "string", + "description": "值类型", + "enum": [ + "number" + ] + }, + "name": { + "type": "string", + "description": "字段名称" + }, + "text": { + "type": "string", + "description": "字段文本数据" + } + }, + "required": [ + "text" + ] + } + } } } } } - } + }, + "required": [ + "plotArea", + "data" + ] + }, + { + "type": "object", + "description": "update 的局部图表快照变更" } - }, - "required": [ - "plotArea", - "data" ] } } diff --git a/shortcuts/sheets/flag_defs_gen.go b/shortcuts/sheets/flag_defs_gen.go index 6d6111d907..9709dcc860 100644 --- a/shortcuts/sheets/flag_defs_gen.go +++ b/shortcuts/sheets/flag_defs_gen.go @@ -200,6 +200,32 @@ var flagDefs = map[string]commandDef{ {Name: "end-revision", Kind: "own", Type: "int", Required: "optional", Desc: "End version (CS revision); defaults to the latest revision. Gap (end-start+1) must be <= 20", Default: "-1"}, }, }, + "+chart-config-update": { + Risk: "write", + Flags: []flagDef{ + {Name: "url", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet URL (XOR with `--spreadsheet-token`)"}, + {Name: "spreadsheet-token", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet token (XOR with `--url`)"}, + {Name: "sheet-id", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet reference_id (XOR with `--sheet-name`)"}, + {Name: "sheet-name", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet name (XOR with `--sheet-id`)"}, + {Name: "chart-id", Kind: "own", Type: "string", Required: "required", Desc: "Target chart reference_id"}, + {Name: "title", Kind: "own", Type: "string", Required: "optional", Desc: "Chart title"}, + {Name: "subtitle", Kind: "own", Type: "string", Required: "optional", Desc: "Chart subtitle"}, + {Name: "legend-position", Kind: "own", Type: "string", Required: "optional", Desc: "Legend position; hidden removes the legend", Enum: []string{"top", "bottom", "left", "right", "hidden"}}, + {Name: "x-axis-title", Kind: "own", Type: "string", Required: "optional", Desc: "X-axis title"}, + {Name: "y-axis-title", Kind: "own", Type: "string", Required: "optional", Desc: "Left Y-axis title"}, + {Name: "secondary-y-axis-title", Kind: "own", Type: "string", Required: "optional", Desc: "Right Y-axis title"}, + {Name: "x-axis-label-angle", Kind: "own", Type: "int", Required: "optional", Desc: "X-axis label angle", Enum: []string{"-90", "-45", "0", "45", "90"}}, + {Name: "y-axis-label-angle", Kind: "own", Type: "int", Required: "optional", Desc: "Left Y-axis label angle", Enum: []string{"-90", "-45", "0", "45", "90"}}, + {Name: "data-labels", Kind: "own", Type: "string", Required: "optional", Desc: "Data label content; none removes labels; category_percentage is normalized to value_percentage", Enum: []string{"none", "value", "percentage", "value_percentage", "category_percentage", "category", "series"}}, + {Name: "data-label-position", Kind: "own", Type: "string", Required: "optional", Desc: "Data label position", Enum: []string{"auto", "top", "bottom", "left", "right", "center", "inside", "outside"}}, + {Name: "stack", Kind: "own", Type: "string", Required: "optional", Desc: "Stacking mode", Enum: []string{"none", "normal", "percent"}}, + {Name: "stacked", Kind: "own", Type: "bool", Required: "optional", Desc: "Compatibility alias for --stack normal", Hidden: true}, + {Name: "smooth", Kind: "own", Type: "bool", Required: "optional", Desc: "Use smooth curves; accepts both --smooth=false and --smooth false"}, + {Name: "color-palette", Kind: "own", Type: "string", Required: "optional", Desc: "Preset chart-level color palette; mutually exclusive with --colors", Enum: []string{"brandColorSeries@v2", "rainbowColorSeries@v2", "complementaryColorSeries@v2", "converseColorSeries@v2", "primaryColorSeries@v2", "singleColorSeries-B-@v2", "singleColorSeries-W-@v2", "singleColorSeries-G-@v2", "singleColorSeries-Y-@v2", "singleColorSeries-O-@v2", "singleColorSeries-R-@v2", "singleColorSeries-D-@v2"}}, + {Name: "colors", Kind: "own", Type: "string_slice", Required: "optional", Desc: "Custom chart-level series colors as a comma-separated list of at least two hex colors; mutually exclusive with --color-palette"}, + {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional", Desc: "Print the request template; no side effects"}, + }, + }, "+chart-create": { Risk: "write", Flags: []flagDef{ @@ -211,6 +237,52 @@ var flagDefs = map[string]commandDef{ {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional", Desc: "Print the request template; no side effects"}, }, }, + "+chart-create-basic": { + Risk: "write", + Flags: []flagDef{ + {Name: "url", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet URL (XOR with `--spreadsheet-token`)"}, + {Name: "spreadsheet-token", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet token (XOR with `--url`)"}, + {Name: "sheet-id", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet reference_id (XOR with `--sheet-name`)"}, + {Name: "sheet-name", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet name (XOR with `--sheet-id`)"}, + {Name: "chart-type", Kind: "own", Type: "string", Required: "required", Desc: "Chart type", Enum: []string{"column", "bar", "line", "area", "pie", "scatter", "combo", "radar"}}, + {Name: "data-range", Kind: "own", Type: "string", Required: "required", Desc: "One contiguous A1 range including headers, or comma-separated same-sheet ranges; aligned non-overlapping ranges stay independent, otherwise they merge to the smallest enclosing rectangle"}, + {Name: "data-direction", Kind: "own", Type: "string", Required: "optional", Desc: "Data series direction; column uses the first column as categories, row uses the first row", Default: "column", Enum: []string{"column", "row"}}, + {Name: "title", Kind: "own", Type: "string", Required: "optional", Desc: "Chart title"}, + {Name: "subtitle", Kind: "own", Type: "string", Required: "optional", Desc: "Chart subtitle"}, + {Name: "legend-position", Kind: "own", Type: "string", Required: "optional", Desc: "Legend position; hidden removes the legend", Enum: []string{"top", "bottom", "left", "right", "hidden"}}, + {Name: "x-axis-title", Kind: "own", Type: "string", Required: "optional", Desc: "X-axis title"}, + {Name: "y-axis-title", Kind: "own", Type: "string", Required: "optional", Desc: "Left Y-axis title"}, + {Name: "secondary-y-axis-title", Kind: "own", Type: "string", Required: "optional", Desc: "Right Y-axis title"}, + {Name: "x-axis-label-angle", Kind: "own", Type: "int", Required: "optional", Desc: "X-axis label angle", Enum: []string{"-90", "-45", "0", "45", "90"}}, + {Name: "y-axis-label-angle", Kind: "own", Type: "int", Required: "optional", Desc: "Left Y-axis label angle", Enum: []string{"-90", "-45", "0", "45", "90"}}, + {Name: "data-labels", Kind: "own", Type: "string", Required: "optional", Desc: "Data label content; none removes labels; category_percentage is normalized to value_percentage", Enum: []string{"none", "value", "percentage", "value_percentage", "category_percentage", "category", "series"}}, + {Name: "data-label-position", Kind: "own", Type: "string", Required: "optional", Desc: "Data label position", Enum: []string{"auto", "top", "bottom", "left", "right", "center", "inside", "outside"}}, + {Name: "stack", Kind: "own", Type: "string", Required: "optional", Desc: "Stacking mode", Enum: []string{"none", "normal", "percent"}}, + {Name: "stacked", Kind: "own", Type: "bool", Required: "optional", Desc: "Compatibility alias for --stack normal", Hidden: true}, + {Name: "smooth", Kind: "own", Type: "bool", Required: "optional", Desc: "Use smooth curves; accepts both --smooth=false and --smooth false"}, + {Name: "color-palette", Kind: "own", Type: "string", Required: "optional", Desc: "Preset chart-level color palette; mutually exclusive with --colors", Enum: []string{"brandColorSeries@v2", "rainbowColorSeries@v2", "complementaryColorSeries@v2", "converseColorSeries@v2", "primaryColorSeries@v2", "singleColorSeries-B-@v2", "singleColorSeries-W-@v2", "singleColorSeries-G-@v2", "singleColorSeries-Y-@v2", "singleColorSeries-O-@v2", "singleColorSeries-R-@v2", "singleColorSeries-D-@v2"}}, + {Name: "colors", Kind: "own", Type: "string_slice", Required: "optional", Desc: "Custom chart-level series colors as a comma-separated list of at least two hex colors; mutually exclusive with --color-palette"}, + {Name: "anchor-cell", Kind: "own", Type: "string", Required: "optional", Desc: "Optional chart anchor cell such as F2; defaults to the right of the data range"}, + {Name: "width", Kind: "own", Type: "int", Required: "optional", Desc: "Optional chart width; must be paired with --height"}, + {Name: "height", Kind: "own", Type: "int", Required: "optional", Desc: "Optional chart height; must be paired with --width"}, + {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional", Desc: "Print the request template; no side effects"}, + }, + }, + "+chart-data-update": { + Risk: "write", + Flags: []flagDef{ + {Name: "url", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet URL (XOR with `--spreadsheet-token`)"}, + {Name: "spreadsheet-token", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet token (XOR with `--url`)"}, + {Name: "sheet-id", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet reference_id (XOR with `--sheet-name`)"}, + {Name: "sheet-name", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet name (XOR with `--sheet-id`)"}, + {Name: "chart-id", Kind: "own", Type: "string", Required: "required", Desc: "Target chart reference_id"}, + {Name: "data-range", Kind: "own", Type: "string", Required: "required", Desc: "New data range including headers; accepts comma-separated same-sheet ranges and normalizes misaligned or overlapping ranges"}, + {Name: "data-direction", Kind: "own", Type: "string", Required: "optional", Desc: "Data series direction; defaults to the existing chart direction when omitted", Enum: []string{"column", "row"}}, + {Name: "dim1-index", Kind: "own", Type: "int", Required: "optional", Desc: "1-based category/X-axis dimension index within the data range; defaults to the first dimension"}, + {Name: "dim2-indexes", Kind: "own", Type: "string", Required: "optional", Desc: "Comma-separated 1-based value/Y-axis series indexes within the data range; defaults to all dimensions except dim1"}, + {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional", Desc: "Print the request template; no side effects"}, + }, + }, "+chart-delete": { Risk: "high-risk-write", Flags: []flagDef{ @@ -242,7 +314,7 @@ var flagDefs = map[string]commandDef{ {Name: "sheet-id", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet reference_id (XOR with `--sheet-name`)"}, {Name: "sheet-name", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet name (XOR with `--sheet-id`)"}, {Name: "chart-id", Kind: "own", Type: "string", Required: "required", Desc: "Target chart reference_id"}, - {Name: "properties", Kind: "own", Type: "string", Required: "required", Desc: "Full or sufficiently complete chart config JSON (read back with `+chart-list` first, then patch)", Input: []string{"file", "stdin"}}, + {Name: "properties", Kind: "own", Type: "string", Required: "required", Desc: "Chart config patch JSON; send changed fields only by default; omitted fields are preserved, objects merge recursively, and arrays replace as a whole", Input: []string{"file", "stdin"}}, {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional"}, }, }, diff --git a/shortcuts/sheets/flag_schema.go b/shortcuts/sheets/flag_schema.go index ffa1c0e6e0..1b114c0c53 100644 --- a/shortcuts/sheets/flag_schema.go +++ b/shortcuts/sheets/flag_schema.go @@ -159,7 +159,7 @@ func splitSchemaPath(flagName string) (string, []string) { // sliceSchemaByPath walks a decoded JSON Schema along dotted path segments. // Each segment matches a key under "properties"; array levels are descended // implicitly through "items" (an explicit "items" segment also works), and -// oneOf branches are searched for the first one carrying the key. A miss +// oneOf / anyOf branches are searched for the first one carrying the key. A miss // errors with the keys actually available at that level so the caller can // re-issue the path without a full dump. func sliceSchemaByPath(schema interface{}, flagName string, path []string) (interface{}, error) { @@ -179,7 +179,7 @@ func sliceSchemaByPath(schema interface{}, flagName string, path []string) (inte } // schemaChild resolves one path segment against a schema node, descending -// through items / oneOf wrappers as needed. +// through items / oneOf / anyOf wrappers as needed. func schemaChild(node interface{}, seg string) (interface{}, bool) { for depth := 0; depth < 8; depth++ { m, ok := node.(map[string]interface{}) @@ -207,13 +207,20 @@ func schemaChild(node interface{}, seg string) (interface{}, bool) { } } } + if branches, ok := m["anyOf"].([]interface{}); ok { + for _, b := range branches { + if child, ok := schemaChild(b, seg); ok { + return child, true + } + } + } return nil, false } return nil, false } // schemaChildKeys lists the property keys reachable at a schema node (through -// items / oneOf wrappers), for the path-miss error. +// items / oneOf / anyOf wrappers), for the path-miss error. func schemaChildKeys(node interface{}) []string { seen := map[string]struct{}{} var collect func(n interface{}, depth int) @@ -240,6 +247,11 @@ func schemaChildKeys(node interface{}) []string { collect(b, depth+1) } } + if branches, ok := m["anyOf"].([]interface{}); ok { + for _, b := range branches { + collect(b, depth+1) + } + } } collect(node, 0) keys := make([]string, 0, len(seen)) diff --git a/shortcuts/sheets/lark_sheet_chart.go b/shortcuts/sheets/lark_sheet_chart.go new file mode 100644 index 0000000000..ef3dd71617 --- /dev/null +++ b/shortcuts/sheets/lark_sheet_chart.go @@ -0,0 +1,605 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package sheets + +import ( + "context" + "regexp" + "strconv" + "strings" + + "github.com/larksuite/cli/shortcuts/common" + "github.com/spf13/cobra" +) + +var chartHexColorPattern = regexp.MustCompile(`^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$`) + +var chartSemanticConfigFlags = []string{ + "title", + "subtitle", + "legend-position", + "x-axis-title", + "y-axis-title", + "secondary-y-axis-title", + "x-axis-label-angle", + "y-axis-label-angle", + "data-labels", + "data-label-position", + "stack", + "color-palette", +} + +// ChartCreateBasic creates a complete server-side chart snapshot from a chart +// type and a rectangular source range. The CLI only forwards semantic input; +// it deliberately does not own or duplicate the full chart snapshot template. +var ChartCreateBasic = common.Shortcut{ + Service: "sheets", + Command: "+chart-create-basic", + Description: "Create a basic chart from a chart type and data range; the server builds and validates the full snapshot.", + Risk: "write", + Scopes: []string{"sheets:spreadsheet:write_only"}, + AuthTypes: []string{"user", "bot"}, + HasFormat: true, + Flags: flagsFor("+chart-create-basic"), + PostMount: configureChartSemanticCommand, + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + token, err := resolveSpreadsheetToken(runtime) + if err != nil { + return err + } + sheetID, sheetName, err := resolveSheetSelector(runtime) + if err != nil { + return err + } + _, err = chartCreateBasicInput(runtime, token, sheetID, sheetName) + return err + }, + DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + token, _ := resolveSpreadsheetToken(runtime) + sheetID, sheetName, _ := resolveSheetSelector(runtime) + input, _ := chartCreateBasicInput(runtime, token, sheetID, sheetName) + return invokeToolDryRun(token, ToolKindWrite, "manage_chart_object", input) + }, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + token, err := resolveSpreadsheetTokenExec(runtime) + if err != nil { + return err + } + sheetID, sheetName, err := resolveSheetSelector(runtime) + if err != nil { + return err + } + input, err := chartCreateBasicInput(runtime, token, sheetID, sheetName) + if err != nil { + return err + } + out, err := callTool(ctx, runtime, token, ToolKindWrite, "manage_chart_object", input) + if err != nil { + return err + } + runtime.Out(out, nil) + return nil + }, +} + +// ChartConfigUpdate updates the common chart settings that repeatedly caused +// full-snapshot retries in eval traces. Advanced per-series and marker styling +// remains on +chart-update --properties. +var ChartConfigUpdate = common.Shortcut{ + Service: "sheets", + Command: "+chart-config-update", + Description: "Update common chart titles, axes, legend, labels, stacking, smoothing, or chart-level colors without sending a snapshot.", + Risk: "write", + Scopes: []string{"sheets:spreadsheet:write_only"}, + AuthTypes: []string{"user", "bot"}, + HasFormat: true, + Flags: flagsFor("+chart-config-update"), + PostMount: configureChartSemanticCommand, + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + token, err := resolveSpreadsheetToken(runtime) + if err != nil { + return err + } + sheetID, sheetName, err := resolveSheetSelector(runtime) + if err != nil { + return err + } + _, err = chartConfigUpdateInput(runtime, token, sheetID, sheetName) + return err + }, + DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + token, _ := resolveSpreadsheetToken(runtime) + sheetID, sheetName, _ := resolveSheetSelector(runtime) + input, _ := chartConfigUpdateInput(runtime, token, sheetID, sheetName) + return invokeToolDryRun(token, ToolKindWrite, "manage_chart_object", input) + }, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + token, err := resolveSpreadsheetTokenExec(runtime) + if err != nil { + return err + } + sheetID, sheetName, err := resolveSheetSelector(runtime) + if err != nil { + return err + } + input, err := chartConfigUpdateInput(runtime, token, sheetID, sheetName) + if err != nil { + return err + } + out, err := callTool(ctx, runtime, token, ToolKindWrite, "manage_chart_object", input) + if err != nil { + return err + } + runtime.Out(out, nil) + return nil + }, +} + +// ChartDataUpdate rebinds an existing chart to a new source range. The server +// reads the current snapshot, rebuilds its data mapping, and preserves the +// chart's layout and visual configuration. +var ChartDataUpdate = common.Shortcut{ + Service: "sheets", + Command: "+chart-data-update", + Description: "Update an existing chart's data range or direction while preserving its layout and visual configuration.", + Risk: "write", + Scopes: []string{"sheets:spreadsheet:write_only"}, + AuthTypes: []string{"user", "bot"}, + HasFormat: true, + Flags: flagsFor("+chart-data-update"), + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + token, err := resolveSpreadsheetToken(runtime) + if err != nil { + return err + } + sheetID, sheetName, err := resolveSheetSelector(runtime) + if err != nil { + return err + } + _, err = chartDataUpdateInput(runtime, token, sheetID, sheetName) + return err + }, + DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + token, _ := resolveSpreadsheetToken(runtime) + sheetID, sheetName, _ := resolveSheetSelector(runtime) + input, _ := chartDataUpdateInput(runtime, token, sheetID, sheetName) + return invokeToolDryRun(token, ToolKindWrite, "manage_chart_object", input) + }, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + token, err := resolveSpreadsheetTokenExec(runtime) + if err != nil { + return err + } + sheetID, sheetName, err := resolveSheetSelector(runtime) + if err != nil { + return err + } + input, err := chartDataUpdateInput(runtime, token, sheetID, sheetName) + if err != nil { + return err + } + out, err := callTool(ctx, runtime, token, ToolKindWrite, "manage_chart_object", input) + if err != nil { + return err + } + runtime.Out(out, nil) + return nil + }, +} + +func chartCreateBasicInput(rt flagView, token, sheetID, sheetName string) (map[string]interface{}, error) { + if err := requireSheetSelector(sheetID, sheetName); err != nil { + return nil, err + } + chartType := strings.TrimSpace(rt.Str("chart-type")) + if chartType == "" { + return nil, sheetsValidationForFlag("chart-type", "--chart-type is required") + } + dataRange := strings.TrimSpace(rt.Str("data-range")) + if dataRange == "" { + return nil, sheetsValidationForFlag("data-range", "--data-range is required") + } + direction := rt.Str("data-direction") + if direction == "" { + direction = "column" + } + normalizedDataRange, dimensionCount, dataPointCount, err := normalizeBasicChartDataRanges(dataRange, direction) + if err != nil { + return nil, err + } + if dimensionCount < 2 || dataPointCount < 2 { + return nil, sheetsValidationForFlag("data-range", "--data-range must provide at least 2 data points and 2 dimensions") + } + if chartType == "combo" && dimensionCount < 3 { + return nil, sheetsValidationForFlag("data-range", "combo chart requires at least 3 rows or columns along --data-direction") + } + + basic := map[string]interface{}{ + "chart_type": chartType, + "data_range": normalizedDataRange, + } + if rt.Changed("data-direction") { + basic["data_direction"] = rt.Str("data-direction") + } + if err := validateChartColorFlags(rt); err != nil { + return nil, err + } + if err := validateChartSemanticEnums(rt); err != nil { + return nil, err + } + addChartSemanticConfig(rt, basic) + + if rt.Changed("anchor-cell") { + anchor := strings.TrimSpace(rt.Str("anchor-cell")) + _, row, ok := splitCellRef(anchor) + if !ok { + return nil, sheetsValidationForFlag("anchor-cell", "--anchor-cell must be a single A1 cell such as F2") + } + colEnd := 0 + for colEnd < len(anchor) && ((anchor[colEnd] >= 'A' && anchor[colEnd] <= 'Z') || (anchor[colEnd] >= 'a' && anchor[colEnd] <= 'z')) { + colEnd++ + } + basic["position"] = map[string]interface{}{"row": row, "col": strings.ToUpper(anchor[:colEnd])} + } + widthChanged := rt.Changed("width") + heightChanged := rt.Changed("height") + if widthChanged != heightChanged { + return nil, common.ValidationErrorf("--width and --height must be provided together").WithParams( + sheetsInvalidParam("width", "must be paired with --height"), + sheetsInvalidParam("height", "must be paired with --width"), + ) + } + if widthChanged { + if rt.Int("width") < 10 || rt.Int("height") < 10 { + return nil, common.ValidationErrorf("--width and --height must be at least 10") + } + basic["size"] = map[string]interface{}{"width": rt.Int("width"), "height": rt.Int("height")} + } + + input := map[string]interface{}{"excel_id": token, "operation": "create", "basic_chart": basic} + sheetSelectorForToolInput(input, sheetID, sheetName) + if err := validateInputAgainstSchema(rt, input); err != nil { + return nil, err + } + return input, nil +} + +func chartConfigUpdateInput(rt flagView, token, sheetID, sheetName string) (map[string]interface{}, error) { + if err := requireSheetSelector(sheetID, sheetName); err != nil { + return nil, err + } + chartID := strings.TrimSpace(rt.Str("chart-id")) + if chartID == "" { + return nil, sheetsValidationForFlag("chart-id", "--chart-id is required") + } + updates := map[string]interface{}{} + if err := validateChartColorFlags(rt); err != nil { + return nil, err + } + if err := validateChartSemanticEnums(rt); err != nil { + return nil, err + } + addChartSemanticConfig(rt, updates) + if len(updates) == 0 { + return nil, common.ValidationErrorf("at least one chart configuration flag is required") + } + input := map[string]interface{}{ + "excel_id": token, + "operation": "update", + "chart_id": chartID, + "config_updates": updates, + } + sheetSelectorForToolInput(input, sheetID, sheetName) + if err := validateInputAgainstSchema(rt, input); err != nil { + return nil, err + } + return input, nil +} + +func chartDataUpdateInput(rt flagView, token, sheetID, sheetName string) (map[string]interface{}, error) { + if err := requireSheetSelector(sheetID, sheetName); err != nil { + return nil, err + } + chartID := strings.TrimSpace(rt.Str("chart-id")) + if chartID == "" { + return nil, sheetsValidationForFlag("chart-id", "--chart-id is required") + } + dataRange := strings.TrimSpace(rt.Str("data-range")) + if dataRange == "" { + return nil, sheetsValidationForFlag("data-range", "--data-range is required") + } + ranges, err := splitChartDataRanges(dataRange) + if err != nil { + return nil, sheetsValidationForFlag("data-range", "invalid --data-range %q: %v", dataRange, err) + } + explicitSheet := "" + for _, value := range ranges { + item, parseErr := parseChartDataRange(value) + if parseErr != nil { + return nil, sheetsValidationForFlag("data-range", "invalid --data-range item %q: %v", value, parseErr) + } + if item.sheet != "" { + if explicitSheet != "" && item.sheet != explicitSheet { + return nil, sheetsValidationForFlag("data-range", "all --data-range items must belong to the same sheet") + } + explicitSheet = item.sheet + } + } + + updates := map[string]interface{}{"data_range": dataRange} + if rt.Changed("data-direction") { + updates["data_direction"] = rt.Str("data-direction") + } + dim1Index := 1 + if rt.Changed("dim1-index") { + dim1Index = rt.Int("dim1-index") + if dim1Index < 1 { + return nil, sheetsValidationForFlag("dim1-index", "--dim1-index must be a positive 1-based index") + } + updates["dim1_index"] = dim1Index + } + if rt.Changed("dim2-indexes") { + dim2Indexes, parseErr := parseChartDim2Indexes(rt.Str("dim2-indexes")) + if parseErr != nil { + return nil, sheetsValidationForFlag("dim2-indexes", "%v", parseErr) + } + for _, index := range dim2Indexes { + if index == dim1Index { + return nil, sheetsValidationForFlag( + "dim2-indexes", + "--dim2-indexes must not contain the dim1 index %d", + dim1Index, + ) + } + } + updates["dim2_indexes"] = dim2Indexes + } + input := map[string]interface{}{ + "excel_id": token, + "operation": "update", + "chart_id": chartID, + "data_updates": updates, + } + sheetSelectorForToolInput(input, sheetID, sheetName) + if err := validateInputAgainstSchema(rt, input); err != nil { + return nil, err + } + return input, nil +} + +func parseChartDim2Indexes(raw string) ([]int, error) { + parts := strings.Split(raw, ",") + indexes := make([]int, 0, len(parts)) + seen := make(map[int]struct{}, len(parts)) + for _, part := range parts { + value := strings.TrimSpace(part) + if value == "" { + return nil, common.ValidationErrorf("--dim2-indexes must be a comma-separated list of positive 1-based indexes") + } + index, err := strconv.Atoi(value) + if err != nil || index < 1 { + return nil, common.ValidationErrorf("--dim2-indexes must contain only positive 1-based indexes") + } + if _, exists := seen[index]; exists { + return nil, common.ValidationErrorf("--dim2-indexes must not contain duplicate index %d", index) + } + seen[index] = struct{}{} + indexes = append(indexes, index) + } + return indexes, nil +} + +type chartDataRange struct { + sheet string + row, col int + rowCount, colCount int +} + +func normalizeBasicChartDataRanges(dataRange, direction string) (normalized string, dimensionCount, dataPointCount int, err error) { + ranges, err := splitChartDataRanges(dataRange) + if err != nil { + return "", 0, 0, sheetsValidationForFlag("data-range", "invalid --data-range %q: %v", dataRange, err) + } + parsed := make([]chartDataRange, 0, len(ranges)) + for _, value := range ranges { + item, parseErr := parseChartDataRange(value) + if parseErr != nil { + return "", 0, 0, sheetsValidationForFlag("data-range", "invalid --data-range item %q: %v", value, parseErr) + } + parsed = append(parsed, item) + } + first := parsed[0] + explicitSheet := "" + spans := make([][2]int, 0, len(parsed)) + aligned := true + minRow, minCol := first.row, first.col + maxRow, maxCol := first.row+first.rowCount, first.col+first.colCount + for _, item := range parsed { + if item.sheet != "" { + if explicitSheet != "" && item.sheet != explicitSheet { + return "", 0, 0, sheetsValidationForFlag("data-range", "all --data-range items must belong to the same sheet") + } + explicitSheet = item.sheet + } + if direction == "row" { + if item.col != first.col || item.colCount != first.colCount { + aligned = false + } + dimensionCount += item.rowCount + spans = append(spans, [2]int{item.row, item.row + item.rowCount}) + } else { + if item.row != first.row || item.rowCount != first.rowCount { + aligned = false + } + dimensionCount += item.colCount + spans = append(spans, [2]int{item.col, item.col + item.colCount}) + } + minRow = min(minRow, item.row) + minCol = min(minCol, item.col) + maxRow = max(maxRow, item.row+item.rowCount) + maxCol = max(maxCol, item.col+item.colCount) + } + overlapping := false + for i, current := range spans { + for j := 0; j < i; j++ { + if current[0] < spans[j][1] && spans[j][0] < current[1] { + overlapping = true + } + } + } + normalized = strings.Join(ranges, ",") + if len(ranges) > 1 && (!aligned || overlapping) { + prefix := "" + if explicitSheet != "" { + prefix = explicitSheet + "!" + } + normalized = prefix + columnIndexToLetter(minCol) + strconv.Itoa(minRow+1) + ":" + columnIndexToLetter(maxCol-1) + strconv.Itoa(maxRow) + dimensionCount = maxCol - minCol + dataPointCount = maxRow - minRow + if direction == "row" { + dimensionCount, dataPointCount = dataPointCount, dimensionCount + } + return normalized, dimensionCount, dataPointCount, nil + } + if direction == "row" { + dataPointCount = first.colCount + } else { + dataPointCount = first.rowCount + } + return normalized, dimensionCount, dataPointCount, nil +} + +func splitChartDataRanges(value string) ([]string, error) { + var ranges []string + start := 0 + inQuote := false + for i := 0; i <= len(value); i++ { + if i < len(value) && value[i] == '\'' { + if inQuote && i+1 < len(value) && value[i+1] == '\'' { + i++ + } else { + inQuote = !inQuote + } + } + if i == len(value) || (value[i] == ',' && !inQuote) { + part := strings.TrimSpace(value[start:i]) + if part == "" { + return nil, common.ValidationErrorf("range list contains an empty item") + } + ranges = append(ranges, part) + start = i + 1 + } + } + if inQuote { + return nil, common.ValidationErrorf("unterminated quoted sheet name") + } + return ranges, nil +} + +func parseChartDataRange(value string) (chartDataRange, error) { + item := chartDataRange{} + ref := strings.TrimSpace(value) + if bang := strings.LastIndex(ref, "!"); bang >= 0 { + item.sheet = strings.TrimSpace(ref[:bang]) + ref = strings.TrimSpace(ref[bang+1:]) + } + parts := strings.SplitN(ref, ":", 2) + if len(parts) != 2 { + return item, common.ValidationErrorf("expected a rectangular A1 range such as A1:C10") + } + startCol, startRow, startOK := splitCellRef(parts[0]) + endCol, endRow, endOK := splitCellRef(parts[1]) + if !startOK || !endOK || endCol < startCol || endRow < startRow { + return item, common.ValidationErrorf("expected a rectangular A1 range such as A1:C10") + } + item.row, item.col = startRow, startCol + item.rowCount, item.colCount = endRow-startRow+1, endCol-startCol+1 + return item, nil +} + +func configureChartSemanticCommand(cmd *cobra.Command) { + if cmd.Flags().Lookup("stacked") == nil { + cmd.Flags().Bool("stacked", false, "compatibility alias for --stack normal") + _ = cmd.Flags().MarkHidden("stacked") + } + originalArgs := cmd.Args + cmd.Args = func(cmd *cobra.Command, args []string) error { + if len(args) == 1 && cmd.Flags().Changed("smooth") && (args[0] == "true" || args[0] == "false") { + return cmd.Flags().Set("smooth", args[0]) + } + return originalArgs(cmd, args) + } + cmd.SetFlagErrorFunc(func(_ *cobra.Command, err error) error { + message := err.Error() + if strings.Contains(message, "unknown flag: --stacked") { + return sheetsValidationForFlag("stacked", "--stacked is not supported; use --stack normal (or --stack percent for 100%% stacking)") + } + return err + }) +} + +func addChartSemanticConfig(rt flagView, out map[string]interface{}) { + for _, flag := range chartSemanticConfigFlags { + if !rt.Changed(flag) { + continue + } + key := strings.ReplaceAll(flag, "-", "_") + if flag == "x-axis-label-angle" || flag == "y-axis-label-angle" { + out[key] = rt.Int(flag) + } else if flag == "data-labels" && rt.Str(flag) == "category_percentage" { + out[key] = "value_percentage" + } else { + out[key] = rt.Str(flag) + } + } + if rt.Changed("stacked") { + out["stack"] = "normal" + } + if rt.Changed("smooth") { + out["smooth"] = rt.Bool("smooth") + } + if rt.Changed("colors") { + out["colors"] = normalizedChartColors(rt) + } +} + +func validateChartSemanticEnums(rt flagView) error { + if rt.Changed("stack") && rt.Changed("stacked") { + return common.ValidationErrorf("--stack and --stacked are mutually exclusive").WithParams( + sheetsInvalidParam("stack", "cannot be used with --stacked"), + sheetsInvalidParam("stacked", "cannot be used with --stack"), + ) + } + return nil +} + +func validateChartColorFlags(rt flagView) error { + if rt.Changed("color-palette") && rt.Changed("colors") { + return common.ValidationErrorf("--color-palette and --colors are mutually exclusive").WithParams( + sheetsInvalidParam("color-palette", "cannot be used with --colors"), + sheetsInvalidParam("colors", "cannot be used with --color-palette"), + ) + } + if rt.Changed("colors") { + colors := normalizedChartColors(rt) + if len(colors) < 2 { + return sheetsValidationForFlag("colors", "--colors must contain at least two hex colors") + } + for _, color := range colors { + if !chartHexColorPattern.MatchString(color) { + return sheetsValidationForFlag("colors", "--colors contains invalid hex color %q", color) + } + } + } + return nil +} + +func normalizedChartColors(rt flagView) []string { + raw := rt.StrSlice("colors") + colors := make([]string, len(raw)) + for i := range raw { + colors[i] = strings.TrimSpace(raw[i]) + } + return colors +} diff --git a/shortcuts/sheets/lark_sheet_chart_test.go b/shortcuts/sheets/lark_sheet_chart_test.go new file mode 100644 index 0000000000..3457d30f0f --- /dev/null +++ b/shortcuts/sheets/lark_sheet_chart_test.go @@ -0,0 +1,346 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package sheets + +import "testing" + +func TestChartCreateBasic_AllTypes(t *testing.T) { + t.Parallel() + + types := []string{"column", "bar", "line", "area", "pie", "scatter", "combo", "radar"} + for _, chartType := range types { + chartType := chartType + t.Run(chartType, func(t *testing.T) { + t.Parallel() + rangeValue := "A1:C4" + if chartType == "combo" { + rangeValue = "A1:D4" + } + body := parseDryRunBody(t, ChartCreateBasic, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-type", chartType, + "--data-range", rangeValue, + }) + input := decodeToolInput(t, body, "manage_chart_object") + if input["operation"] != "create" { + t.Fatalf("operation = %v, want create", input["operation"]) + } + if _, ok := input["properties"]; ok { + t.Fatal("semantic create must not send properties") + } + basic, _ := input["basic_chart"].(map[string]interface{}) + if basic["chart_type"] != chartType || basic["data_range"] != rangeValue { + t.Fatalf("basic_chart = %#v", basic) + } + }) + } +} + +func TestChartCreateBasic_ConfigAndPlacement(t *testing.T) { + t.Parallel() + body := parseDryRunBody(t, ChartCreateBasic, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-type", "line", + "--data-range", "A1:C4", + "--anchor-cell", "f2", + "--width", "640", + "--height", "360", + "--title", "Trend", + "--legend-position", "bottom", + "--smooth=false", + "--data-direction", "row", + "--color-palette", "brandColorSeries@v2", + }) + input := decodeToolInput(t, body, "manage_chart_object") + basic, _ := input["basic_chart"].(map[string]interface{}) + position, _ := basic["position"].(map[string]interface{}) + size, _ := basic["size"].(map[string]interface{}) + if position["col"] != "F" || position["row"] != float64(1) { + t.Errorf("position = %#v, want F2 as zero-based row 1", position) + } + if size["width"] != float64(640) || size["height"] != float64(360) { + t.Errorf("size = %#v", size) + } + if basic["title"] != "Trend" || basic["legend_position"] != "bottom" || basic["smooth"] != false || + basic["data_direction"] != "row" || basic["color_palette"] != "brandColorSeries@v2" { + t.Errorf("semantic config = %#v", basic) + } +} + +func TestChartCreateBasic_MultipleAlignedRanges(t *testing.T) { + t.Parallel() + rangeValue := "'Data, 2026'!A1:A10,'Data, 2026'!K1:L10" + body := parseDryRunBody(t, ChartCreateBasic, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-type", "line", + "--data-range", rangeValue, + }) + input := decodeToolInput(t, body, "manage_chart_object") + basic := input["basic_chart"].(map[string]interface{}) + if basic["data_range"] != rangeValue { + t.Fatalf("basic_chart.data_range = %v, want %q", basic["data_range"], rangeValue) + } +} + +func TestChartCreateBasic_MergesMisalignedOrOverlappingRanges(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + expected string + }{ + {name: "separated rows", input: "'Sheet1'!A1:M1,'Sheet1'!A3:M3", expected: "'Sheet1'!A1:M3"}, + {name: "overlapping columns", input: "A1:B10,B1:C10", expected: "A1:C10"}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + body := parseDryRunBody(t, ChartCreateBasic, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-type", "line", + "--data-range", tt.input, + }) + input := decodeToolInput(t, body, "manage_chart_object") + basic := input["basic_chart"].(map[string]interface{}) + if basic["data_range"] != tt.expected { + t.Fatalf("basic_chart.data_range = %v, want %q", basic["data_range"], tt.expected) + } + }) + } +} + +func TestChartCreateBasic_RejectsCrossSheetRanges(t *testing.T) { + t.Parallel() + _, _, err := runShortcutCapturingErr(t, ChartCreateBasic, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-type", "line", + "--data-range", "'A'!A1:A10,'B'!C1:D10", + }) + if err == nil { + t.Fatal("expected cross-sheet ranges to fail") + } +} + +func TestChartSemanticShortcuts_InBatchUpdate(t *testing.T) { + body := parseDryRunBody(t, BatchUpdate, []string{ + "--url", testURL, + "--operations", `[ + {"shortcut":"+chart-create-basic","input":{"sheet-id":"sh1","chart-type":"column","data-range":"A1:C10","title":"Sales"}}, + {"shortcut":"+chart-create-basic","input":{"sheet-id":"sh1","chart-type":"line","data-range":"E1:G10","title":"Trend"}} + ]`, + "--yes", + }) + input := decodeToolInput(t, body, "batch_update") + ops := input["operations"].([]interface{}) + if len(ops) != 2 { + t.Fatalf("operations len = %d, want 2", len(ops)) + } + for i, op := range ops { + item := op.(map[string]interface{}) + if item["tool_name"] != "manage_chart_object" { + t.Fatalf("operations[%d].tool_name = %v", i, item["tool_name"]) + } + chartInput := item["input"].(map[string]interface{}) + if chartInput["operation"] != "create" { + t.Fatalf("operations[%d].input.operation = %v", i, chartInput["operation"]) + } + if _, ok := chartInput["basic_chart"].(map[string]interface{}); !ok { + t.Fatalf("operations[%d].input.basic_chart = %#v", i, chartInput["basic_chart"]) + } + } +} + +func TestChartConfigUpdate_PartialFields(t *testing.T) { + t.Parallel() + body := parseDryRunBody(t, ChartConfigUpdate, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-id", "chart-1", + "--y-axis-title", "Revenue", + "--stack", "percent", + "--smooth=false", + "--colors", "#112233,#445566", + }) + input := decodeToolInput(t, body, "manage_chart_object") + if input["operation"] != "update" || input["chart_id"] != "chart-1" { + t.Fatalf("input = %#v", input) + } + if _, ok := input["properties"]; ok { + t.Fatal("semantic update must not send properties") + } + updates, _ := input["config_updates"].(map[string]interface{}) + if updates["y_axis_title"] != "Revenue" || updates["stack"] != "percent" || updates["smooth"] != false { + t.Errorf("config_updates = %#v", updates) + } + colors, _ := updates["colors"].([]interface{}) + if len(colors) != 2 || colors[0] != "#112233" || colors[1] != "#445566" { + t.Errorf("config_updates.colors = %#v", updates["colors"]) + } +} + +func TestChartConfigUpdate_SpacedSmoothBool(t *testing.T) { + t.Parallel() + body := parseDryRunBody(t, ChartConfigUpdate, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-id", "chart-1", + "--smooth", "false", + }) + input := decodeToolInput(t, body, "manage_chart_object") + updates := input["config_updates"].(map[string]interface{}) + if updates["smooth"] != false { + t.Fatalf("config_updates.smooth = %v, want false", updates["smooth"]) + } +} + +func TestChartSemanticShortcuts_CompatibleAliases(t *testing.T) { + t.Parallel() + body := parseDryRunBody(t, ChartConfigUpdate, []string{ + "--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chart-1", "--stacked", + }) + updates := decodeToolInput(t, body, "manage_chart_object")["config_updates"].(map[string]interface{}) + if updates["stack"] != "normal" { + t.Fatalf("--stacked normalized stack = %v, want normal", updates["stack"]) + } + body = parseDryRunBody(t, ChartConfigUpdate, []string{ + "--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chart-1", "--data-labels", "category_percentage", + }) + updates = decodeToolInput(t, body, "manage_chart_object")["config_updates"].(map[string]interface{}) + if updates["data_labels"] != "value_percentage" { + t.Fatalf("data-labels normalized value = %v, want value_percentage", updates["data_labels"]) + } +} + +func TestChartSemanticShortcuts_CompatibleAliasesInBatch(t *testing.T) { + t.Parallel() + body := parseDryRunBody(t, BatchUpdate, []string{ + "--url", testURL, + "--operations", `[{"shortcut":"+chart-config-update","input":{"sheet_id":"sh1","chart_id":"chart-1","stacked":true,"data_labels":"category_percentage","smooth":false}}]`, + "--yes", + }) + input := decodeToolInput(t, body, "batch_update") + ops := input["operations"].([]interface{}) + chartInput := ops[0].(map[string]interface{})["input"].(map[string]interface{}) + updates := chartInput["config_updates"].(map[string]interface{}) + if updates["stack"] != "normal" || updates["data_labels"] != "value_percentage" || updates["smooth"] != false { + t.Fatalf("batch config_updates = %#v", updates) + } +} + +func TestChartDataUpdate_PreservesSnapshotServerSide(t *testing.T) { + t.Parallel() + body := parseDryRunBody(t, ChartDataUpdate, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-id", "chart-1", + "--data-range", "'Sheet1'!A1:M6", + }) + input := decodeToolInput(t, body, "manage_chart_object") + if input["operation"] != "update" || input["chart_id"] != "chart-1" { + t.Fatalf("input = %#v", input) + } + if _, ok := input["properties"]; ok { + t.Fatal("semantic data update must not send properties") + } + updates, _ := input["data_updates"].(map[string]interface{}) + if updates["data_range"] != "'Sheet1'!A1:M6" { + t.Errorf("data_updates = %#v", updates) + } + if _, ok := updates["data_direction"]; ok { + t.Errorf("omitted --data-direction must preserve the server-side direction: %#v", updates) + } +} + +func TestChartDataUpdate_ExplicitDirectionAndMultipleRanges(t *testing.T) { + t.Parallel() + body := parseDryRunBody(t, ChartDataUpdate, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-id", "chart-1", + "--data-range", "'Sheet1'!A1:A10,'Sheet1'!K1:L10", + "--data-direction", "column", + }) + input := decodeToolInput(t, body, "manage_chart_object") + updates := input["data_updates"].(map[string]interface{}) + if updates["data_range"] != "'Sheet1'!A1:A10,'Sheet1'!K1:L10" || updates["data_direction"] != "column" { + t.Errorf("data_updates = %#v", updates) + } +} + +func TestChartDataUpdate_ExplicitSeriesIndexes(t *testing.T) { + t.Parallel() + body := parseDryRunBody(t, ChartDataUpdate, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-id", "chart-1", + "--data-range", "'Sheet1'!A1:M6", + "--dim1-index", "1", + "--dim2-indexes", "4, 8", + }) + input := decodeToolInput(t, body, "manage_chart_object") + updates := input["data_updates"].(map[string]interface{}) + if updates["dim1_index"] != float64(1) { + t.Errorf("data_updates.dim1_index = %#v", updates["dim1_index"]) + } + indexes, _ := updates["dim2_indexes"].([]interface{}) + if len(indexes) != 2 || indexes[0] != float64(4) || indexes[1] != float64(8) { + t.Errorf("data_updates.dim2_indexes = %#v", updates["dim2_indexes"]) + } +} + +func TestChartSemanticShortcuts_Validation(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + args []string + }{ + {name: "unsupported type", args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-type", "donut", "--data-range", "A1:C4"}}, + {name: "invalid semantic enum", args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-type", "line", "--data-range", "A1:C4", "--legend-position", "diagonal"}}, + {name: "range too small", args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-type", "line", "--data-range", "A1:A4"}}, + {name: "combo needs two series", args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-type", "combo", "--data-range", "A1:B4"}}, + {name: "invalid direction", args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-type", "line", "--data-range", "A1:C4", "--data-direction", "horizontal"}}, + {name: "colors need two values", args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-type", "line", "--data-range", "A1:C4", "--colors", "#112233"}}, + {name: "palette and colors are exclusive", args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-type", "line", "--data-range", "A1:C4", "--color-palette", "brandColorSeries@v2", "--colors", "#112233,#445566"}}, + {name: "size must be paired", args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-type", "line", "--data-range", "A1:C4", "--width", "640"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, _, err := runShortcutCapturingErr(t, ChartCreateBasic, tt.args) + if err == nil { + t.Fatal("expected validation error") + } + }) + } + + _, _, err := runShortcutCapturingErr(t, ChartConfigUpdate, []string{ + "--url", testURL, + "--sheet-id", testSheetID, + "--chart-id", "chart-1", + }) + if err == nil { + t.Fatal("expected config update with no changed field to fail") + } + + for _, args := range [][]string{ + {"--url", testURL, "--sheet-id", testSheetID, "--data-range", "A1:C4"}, + {"--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chart-1"}, + {"--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chart-1", "--data-range", "A1:C4", "--data-direction", "horizontal"}, + {"--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chart-1", "--data-range", "A1:C4", "--dim1-index", "0"}, + {"--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chart-1", "--data-range", "A1:C4", "--dim2-indexes", "2,2"}, + {"--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chart-1", "--data-range", "A1:C4", "--dim2-indexes", "1,2"}, + } { + _, _, err = runShortcutCapturingErr(t, ChartDataUpdate, args) + if err == nil { + t.Fatalf("expected chart data update validation error for args %#v", args) + } + } +} diff --git a/shortcuts/sheets/shortcuts.go b/shortcuts/sheets/shortcuts.go index f12df64901..ba90adb497 100644 --- a/shortcuts/sheets/shortcuts.go +++ b/shortcuts/sheets/shortcuts.go @@ -151,6 +151,7 @@ func shortcutList() []common.Shortcut { // Object CRUD (3 per skill) ChartCreate, ChartUpdate, ChartDelete, + ChartCreateBasic, ChartConfigUpdate, ChartDataUpdate, PivotCreate, PivotUpdate, PivotDelete, CondFormatCreate, CondFormatUpdate, CondFormatDelete, FilterCreate, FilterUpdate, FilterDelete, diff --git a/skills/lark-sheets/SKILL.md b/skills/lark-sheets/SKILL.md index 5d7724ccff..bdf79a9aeb 100644 --- a/skills/lark-sheets/SKILL.md +++ b/skills/lark-sheets/SKILL.md @@ -68,7 +68,7 @@ metadata: | 批量清除多区域 | `+cells-batch-clear --yes`(需确认;`--scope`) | `lark-sheets-batch-update` | `--target` | | 调整列宽 / 行高 | `+cols-resize` / `+rows-resize`(行、列是两个独立命令) | `lark-sheets-range-operations` | `--dimension`(无此 flag) | | 分组汇总 / 透视 | `+pivot-create`(默认不传落点 flag → 自动新建子表,零覆盖) | `lark-sheets-pivot-table` | 用 SUMIF / 本地脚本拼一张假透视表 | -| 画图表 / 可视化(柱 / 折线 / 饼 / 条 / 散点 / 组合…) | `+chart-create`(先 `+chart-create --print-example ` 本地拿最小可用 `--properties` 模板,改 refs / index 即可用) | `lark-sheets-chart` | matplotlib / 本地画图再贴图(原生图表可交互、随数据更新) | +| 画图表 / 可视化(柱 / 折线 / 饼 / 条 / 散点 / 组合…) | 先读 `lark-sheets-chart`;普通图用 `+chart-create-basic`,多图用一次 `+batch-update --continue-on-error`,已有图的数据源用 `+chart-data-update`、常用配置用 `+chart-config-update`;只有单系列 / 单数据点 / 高级引擎字段才用完整 `+chart-create` / `+chart-update` snapshot | `lark-sheets-chart` | matplotlib / 本地画图再贴图(原生图表可交互、随数据更新) | | 条件高亮 / 数据条 / 色阶 / 重复值标记 | `+cond-format-create` | `lark-sheets-conditional-format` | `+highlight`、`+conditional-format`、逐格 `+cells-set-style` 硬凑 | | 筛选 / 只看符合条件的行 | `+filter-create` | `lark-sheets-filter` | pandas filter 后覆盖写回(会毁原数据;要保存多份筛选状态用 `+filter-view-create`) | @@ -200,7 +200,7 @@ lark-cli sheets +csv-get --url "https://.../sheets/shtXXX" --sheet-name "<真实 > ⚠️ **high-risk-write 命令清单(首次调用就带 `--yes`,别等 exit 10 再补;或先 `--dry-run` 预览)**:`+batch-update`、`+cells-clear`、`+cells-batch-clear`、`+sheet-delete`、`+dim-delete`、`+dropdown-delete`,以及各对象删除 `+chart-delete` / `+pivot-delete` / `+cond-format-delete` / `+filter-delete` / `+filter-view-delete` / `+sparkline-delete` / `+float-image-delete`。 -**Agent 使用提示**:写复合 JSON flag 前对结构不确定时,先 `--print-schema --flag-name `(深层字段用点分路径切片)再构造 payload;图表直接 `+chart-create --print-example ` 拿最小可用模板改参。reference 的 `## Schemas` 段只给一层结构。 +**Agent 使用提示**:写复合 JSON flag 前对结构不确定时,先 `--print-schema --flag-name `(深层字段用点分路径切片)再构造 payload。图表任务必须先读 `lark-sheets-chart`:能用 `+chart-create-basic` / `+chart-data-update` / `+chart-config-update` 的语义参数就不得探查 schema 或构造 snapshot,互不依赖的多图创建 / 更新用一次 `+batch-update --continue-on-error`;只有单系列、单数据点或高级引擎字段无语义参数时,才用 `+chart-create --print-example ` 或点分 schema 构造完整 snapshot。reference 的 `## Schemas` 段只给一层结构。 ### flag 内容类型与输出约定(术语速记) diff --git a/skills/lark-sheets/references/lark-sheets-batch-update.md b/skills/lark-sheets/references/lark-sheets-batch-update.md index 1f300ad0ac..0b976aa50e 100644 --- a/skills/lark-sheets/references/lark-sheets-batch-update.md +++ b/skills/lark-sheets/references/lark-sheets-batch-update.md @@ -5,8 +5,8 @@ `+batch-update` 把多次写入打包成单次请求,但每个子操作仍受编辑类任务硬性默认规则约束: 1. **目标 range 必须落在用户授权范围内**:除用户明示要修改的区域外,子操作禁止扩张到无关单元格 / 列 / Sheet。规划 range 时先确认每个子操作的边界。 -2. **批次完成后必须回读校验**:整个 `+batch-update` 执行成功后,用 `+csv-get` 或 `+cells-get` 抽样回读受影响区域,至少校验 3-5 个代表性单元格(首 / 中 / 末),与本地脚本预先计算的预期值对照。 -3. **预期条数前置断言**:涉及"批量填充 N 行"或"对 M 个区域分别写入"时,先把 N、M 硬编码进代码,回读后断言实际等于预期;不一致就再发一轮 `+batch-update` 补齐,禁止交付半成品。 +2. **批次完成后必须回读校验**:整个 `+batch-update` 执行成功后,单元格写入用 `+csv-get` 或 `+cells-get` 抽样回读受影响区域,至少校验 3-5 个代表性单元格(首 / 中 / 末);图表写入用一次 `+chart-list` 核对对象数量、类型、系列和范围。 +3. **预期条数前置断言**:涉及"批量填充 N 行"、"对 M 个区域分别写入"或“每个 / 每天 / 分别各建一张图”时,先从数据数出 N、M 并写进清单;图表场景要断言 operations 中的创建数 = 独立实体图数 + 汇总图数。回读后断言实际等于预期,禁止用一张多系列汇总图替代多张独立图,也禁止交付半成品。 若本次 `+batch-update` 的任一子操作写入了公式、复制了公式模板、或导入了含公式的数据块,**回读校验之后还必须继续执行 `+formula-verify`**。`+batch-update` 的原子提交只保证“写入动作都执行了”,不保证整批公式运行结果 zero-error。 @@ -20,10 +20,11 @@ - 需要对**多个**不同区域执行 `+cells-{merge|unmerge}` 时(如按分组合并多列相同内容) - 需要先插入行列再写入数据时(`+dim-{insert|delete|hide|unhide|freeze|group|ungroup}` + `+cells-set`) - 需要对多个区域执行不同写入操作时(多次 `+cells-set` + `+cells-clear` 等组合) +- 需要创建多张基础图,或统一更新多张图的数据源、标题、坐标轴、图例、标签、堆叠和平滑配置时(多个 `+chart-create-basic` / `+chart-data-update` / `+chart-config-update`) **行高列宽批量不走这里**:多行 / 多列不同尺寸直接用 `+rows-resize --heights` / `+cols-resize --widths` 的 map 形态(如 `--widths '{"A":100,"C:E":120}'`,见 `lark-sheets-range-operations`),一次调用原子完成;map 形态不可作为 `--operations` 子操作嵌入(子操作里仍可用单区间形态 `range` + `height`/`width`)。 -当同一工具需要对多个区域重复调用时,**必须**改用 `+batch-update` 合并为单次请求——`+batch-update` 是原子提交(要么全成功要么整批回滚);逐个调用非原子,中途失败会留下半成品。 +当同一工具需要对多个区域重复调用时,**必须**改用 `+batch-update` 合并为单次请求。存在依赖关系的操作保持默认严格事务(要么全成功要么整批回滚);互不依赖的多图表创建/更新使用 `--continue-on-error`,保留成功图表并根据逐项错误只重试失败项。 **公式相关批处理的默认闭环**: - 写前:先读 `lark-sheets-formula-translation`,把公式改写成飞书可执行语义。 @@ -112,7 +113,7 @@ _公共:URL/token(无 sheet 定位) · 系统:`--yes`、`--dry-run`_ _要批量执行的 CLI shortcut 操作列表,按声明顺序串行执行;任一失败立即中断_ **数组项**(类型 object): -- `shortcut` (enum) — CLI shortcut 名(不是底层 MCP tool 名) [+cells-set / +cells-set-style / +cells-clear / +cells-merge / +cells-unmerge / +cells-replace / +csv-put / +dropdown-set / +dim-insert / +dim-delete / +dim-hide / +dim-unhide / +dim-freeze / +dim-group / +dim-ungroup / +rows-resize / +cols-resize / +range-move / +range-copy / +range-fill / +range-sort / +sheet-create / +sheet-delete / +sheet-rename / +sheet-move / +sheet-copy / +sheet-hide / +sheet-unhide / +sheet-set-tab-color / +sheet-show-gridline / +sheet-hide-gridline / +chart-create / +chart-update / +chart-delete / +pivot-create / +pivot-update / +pivot-delete / +cond-format-create / +cond-format-update / +cond-format-delete / +filter-create / +filter-update / +filter-delete / +filter-view-create / +filter-view-update / +filter-view-delete / +sparkline-create / +sparkline-update / +sparkline-delete / +float-image-create / +float-image-update / +float-image-delete] +- `shortcut` (enum) — CLI shortcut 名(不是底层 MCP tool 名) [+cells-set / +cells-set-style / +cells-clear / +cells-merge / +cells-unmerge / +cells-replace / +csv-put / +dropdown-set / +dim-insert / +dim-delete / +dim-hide / +dim-unhide / +dim-freeze / +dim-group / +dim-ungroup / +rows-resize / +cols-resize / +range-move / +range-copy / +range-fill / +range-sort / +sheet-create / +sheet-delete / +sheet-rename / +sheet-move / +sheet-copy / +sheet-hide / +sheet-unhide / +sheet-set-tab-color / +sheet-show-gridline / +sheet-hide-gridline / +chart-create / +chart-update / +chart-delete / +chart-create-basic / +chart-config-update / +chart-data-update / +pivot-create / +pivot-update / +pivot-delete / +cond-format-create / +cond-format-update / +cond-format-delete / +filter-create / +filter-update / +filter-delete / +filter-view-create / +filter-view-update / +filter-view-delete / +sparkline-create / +sparkline-update / +sparkline-delete / +float-image-create / +float-image-update / +float-image-delete] - `input` (object) — 该 shortcut 的入参集——含子表定位 sheet_id(或 sheet_name),但不含 spreadsheet token/url(后者只在顶层 … ### `+cells-batch-set-style` `--border-styles` @@ -169,6 +170,19 @@ lark-cli sheets +batch-update --url "https://example.feishu.cn/sheets/shtXXX" -- > ] > ``` +> **多图表组合**:先完成全部辅助数据,再把每张图的完整语义输入放进同一个批次,并在顶层传 `--continue-on-error`;每项同时记录精确表头范围、数据方向和预期系列数。批次完成后,每个受影响的 sheet 各调用一次 `+chart-list`,不要每创建一张图就读取、调整数据后再删除重建。若数据范围或系列数不符,用 `+chart-data-update` 修正已有图表,不要删除后重建。 +> +> ```json +> [ +> {"shortcut":"+chart-create-basic","input":{"sheet_name":"Sheet1","chart_type":"column","data_range":"'Sheet1'!A1:C10","title":"分类对比","anchor_cell":"F2"}}, +> {"shortcut":"+chart-create-basic","input":{"sheet_name":"Sheet1","chart_type":"line","data_range":"'Sheet1'!E1:G10","title":"趋势变化","anchor_cell":"F18"}} +> ] +> ``` +> +> ```bash +> lark-cli sheets +batch-update --url "..." --operations @ops.json --continue-on-error --yes +> ``` + ### `+cells-batch-set-style` 多 range 应用同一组 style(服务端走 `+batch-update` 原子事务): diff --git a/skills/lark-sheets/references/lark-sheets-chart.md b/skills/lark-sheets/references/lark-sheets-chart.md index 41458a01b6..4405bd213b 100644 --- a/skills/lark-sheets/references/lark-sheets-chart.md +++ b/skills/lark-sheets/references/lark-sheets-chart.md @@ -2,18 +2,30 @@ ## 真对象硬约束 -当用户要求"画个图 / 数据可视化 / 趋势图 / 对比图 / 占比图"时,**必须**通过 `+chart-{create|update|delete}` 创建真实的图表对象。**禁止**用本地脚本调 matplotlib / seaborn 生成图片再插入到表格代替——静态图片无法随源数据更新,且失去交互能力。判断标准:交付后 `+chart-list` 必须能返回该对象。 +当用户要求"画个图 / 数据可视化 / 趋势图 / 对比图 / 占比图"时,**必须**通过图表创建命令创建真实的图表对象。**禁止**用本地脚本调 matplotlib / seaborn 生成图片再插入到表格代替——静态图片无法随源数据更新,且失去交互能力。判断标准:交付后 `+chart-list` 必须能返回该对象。 ## 使用场景 -读写图表对象。本 reference 覆盖 4 个 shortcut: +读写图表对象。基础创建和常用更新优先用语义 shortcut,只在高级配置时使用原始 snapshot: | 操作需求 | 使用工具 | 说明 | |---------|---------|------| | 查看已有图表 | `+chart-list` | 获取图表的类型、数据源和样式配置 | -| 创建/更新/删除图表 | `+chart-{create|update|delete}` | 对图表对象执行写入操作 | +| 按类型和范围创建基础图 | `+chart-create-basic` | 支持 column/bar/line/area/pie/scatter/combo/radar、行/列方向与整图配色;无需构造 snapshot | +| 修正已有图表的数据范围或方向 | `+chart-data-update` | 服务端重建数据映射,保留标题、样式、位置和尺寸 | +| 批量创建或更新多个独立图表 | `+batch-update --continue-on-error` | 保留成功图表,并逐项返回失败原因;只重试失败项 | +| 更新标题、轴、图例、标签、堆叠、平滑或整图配色 | `+chart-config-update` | 无需回写 snapshot,未传字段保持不变 | +| 高级创建/更新、删除图表 | `+chart-{create|update|delete}` | 按系列/数据点精细设置等高级需求才使用原始 properties | -典型工作流:先读取现有图表了解配置 → 执行创建/更新/删除 → 再次读取验证结果。 +典型工作流:先确认表头和精确数据范围,用 `+chart-create-basic` 一次创建并尽量在同次调用中带上已知标题/轴/标签要求;创建后用 `+chart-list` 验证。已有图表的数据范围或方向错误时用 `+chart-data-update`,常用配置修正用 `+chart-config-update`。只有用户要求单个系列、数据点或高级引擎字段时,才读取现有 snapshot 并调 `+chart-update --properties`。不要为了常用配置先输出整份 schema,也不要删除重建已经创建成功的图表。 + +**多图表工作流**:先完成所有辅助数据和表头,列出每张目标图的类型、精确数据范围、标题和落点;确认清单后,用一次 `+batch-update --continue-on-error` 批量执行 `+chart-create-basic`。图表之间独立时允许部分成功:按返回的逐项结果定位失败图表,只重试失败项,不要重复创建成功图表。批次后每个受影响的 sheet 各调用一次 `+chart-list`,验证数量、类型、系列和范围。数据尚未稳定时不要提前创建图表;已经成功创建的图表有数据源差异时批量使用 `+chart-data-update`,有配置差异时批量使用 `+chart-config-update`,不要删除重建。 + +**数量词必须展开**:用户说“每个 / 每天 / 分别 / 逐一 / 各一张图”时,先从数据中数出实体数 `N`,把这 `N` 张图逐项写进清单,再加上其它汇总图得到目标总数 `M`;一个包含全部实体的多系列图不能替代这 `N` 张独立图。批次前断言 operations 中恰有 `M` 个图表创建,批次后断言图表总数、逐图标题与实体集合一致。 + +**范围与系列前置校验**:清单中同时记录每张图的表头范围、纳入列、明确排除列、数据方向和预期系列数。表头在首列时用默认 `--data-direction column`;表头在首行、每行代表一个系列时用 `--data-direction row`。创建前根据实际表头确认边界,不凭字母猜范围;创建后范围、方向或系列数不符时,使用 `+chart-data-update` 修正,服务端会重建 `refs` / `dim1` / `dim2.series` 并保留其它配置,不要删除后重建。批次跨多个 sheet 时,每个受影响的 sheet 各调用一次 `+chart-list`。 + +**整图配色优先走语义参数**:只要求统一主题或一组系列颜色时,在创建时传 `--color-palette` 或 `--colors`,已有图表用 `+chart-config-update` 更新;二者互斥。只有指定某个系列或某个数据点的颜色时才使用原始 snapshot。 ## 需求→图表类型映射(创建前必查) @@ -29,6 +41,8 @@ **`--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-example ` 拿最小可用模板改参**(本地即时返回);查深层字段用点分路径切片 `--print-schema --flag-name properties.snapshot.plotArea.axes`,别整篇 dump 翻页。完整结构以 `--print-schema --flag-name properties` 为准。 +**`+chart-update` 局部更新硬规则(更新前必读)**:默认只在 `--properties` 中传实际变化的字段,未传字段保持不变;不要复制并回写完整 snapshot。`snapshot` 内普通对象递归合并,`refs` / `axes` / `series` 等数组整体替换——修改数组中的一项时,应先读取当前数组、改好后只回写该完整数组,不需要携带 snapshot 的其它字段。`snapshot.data.isStaticData` 不能通过 update 改变;需要切换静态/非静态数据时删除后重建。 + **常见配置错误(必须注意)**: - **图表类型选择错误**:用户说"堆积柱形图/百分比堆积"时,应在 `properties.snapshot.plotArea.plot.extra.stack` 中配置堆叠;百分比堆叠需在该 stack 下设置 `percentage: true`。用户说"占比/比例"时,优先考虑饼图或百分比堆积图。注意区分 `column`(柱形图,纵向)与 `bar`(条形图,横向)是两个不同的 type 取值,"对比/各 XX" 类纵向柱默认用 `column` - **数据标签开关**:`plotArea.plot.labels` 对象的**存在性即开关**—— @@ -104,6 +118,9 @@ | Shortcut | Risk | 分组 | | --- | --- | --- | | `+chart-list` | read | 对象 | +| `+chart-create-basic` | write | 对象 | +| `+chart-config-update` | write | 对象 | +| `+chart-data-update` | write | 对象 | | `+chart-create` | write | 对象 | | `+chart-update` | write | 对象 | | `+chart-delete` | high-risk-write | 对象 | @@ -118,6 +135,69 @@ _公共四件套 · 系统:`--dry-run`_ | --- | --- | --- | --- | | `--chart-id` | string | optional | 指定单个图表 reference_id 过滤 | +### `+chart-create-basic` + +_公共四件套 · 系统:`--dry-run`_ + +| Flag | Type | 必填 | 说明 | +| --- | --- | --- | --- | +| `--chart-type` | string | required | 图表类型(可选值:`column` / `bar` / `line` / `area` / `pie` / `scatter` / `combo` / `radar`) | +| `--data-range` | string | required | 含表头的一个连续 A1 范围,或逗号分隔的同表多范围;对齐且不重叠时保留独立引用,否则合并为最小包围矩形 | +| `--data-direction` | string | optional | 数据系列方向;column 表示首列为类别,row 表示首行为类别(可选值:`column` / `row`)(默认 `column`) | +| `--title` | string | optional | 图表标题 | +| `--subtitle` | string | optional | 图表副标题 | +| `--legend-position` | string | optional | 图例位置;hidden 隐藏图例(可选值:`top` / `bottom` / `left` / `right` / `hidden`) | +| `--x-axis-title` | string | optional | X 轴标题 | +| `--y-axis-title` | string | optional | 左 Y 轴标题 | +| `--secondary-y-axis-title` | string | optional | 右 Y 轴标题 | +| `--x-axis-label-angle` | int | optional | X 轴标签旋转角度(可选值:`-90` / `-45` / `0` / `45` / `90`) | +| `--y-axis-label-angle` | int | optional | 左 Y 轴标签旋转角度(可选值:`-90` / `-45` / `0` / `45` / `90`) | +| `--data-labels` | string | optional | 数据标签内容;none 隐藏标签;兼容 category_percentage 并自动按 value_percentage 处理(可选值:`none` / `value` / `percentage` / `value_percentage` / `category_percentage` / `category` / `series`) | +| `--data-label-position` | string | optional | 数据标签位置(可选值:`auto` / `top` / `bottom` / `left` / `right` / `center` / `inside` / `outside`) | +| `--stack` | string | optional | 堆叠模式(可选值:`none` / `normal` / `percent`) | +| `--stacked` | bool | optional | 兼容别名;等价于 --stack normal(隐藏 flag:不在 `--help` 列出,但可正常传入) | +| `--smooth` | bool | optional | 是否使用平滑曲线;支持 --smooth=false 和 --smooth false | +| `--color-palette` | string | optional | 预设整图配色主题;与 --colors 互斥(可选值:`brandColorSeries@v2` / `rainbowColorSeries@v2` / `complementaryColorSeries@v2` / `converseColorSeries@v2` / `primaryColorSeries@v2` / `singleColorSeries-B-@v2` / `singleColorSeries-W-@v2` / `singleColorSeries-G-@v2` / `singleColorSeries-Y-@v2` / `singleColorSeries-O-@v2` / `singleColorSeries-R-@v2` / `singleColorSeries-D-@v2`) | +| `--colors` | string_slice | optional | 自定义整图系列颜色,逗号分隔且至少 2 个十六进制色值;与 --color-palette 互斥 | +| `--anchor-cell` | string | optional | 可选图表锚点单元格,如 F2;省略时放到数据范围右侧 | +| `--width` | int | optional | 可选图表宽度;必须与 --height 同时传 | +| `--height` | int | optional | 可选图表高度;必须与 --width 同时传 | + +### `+chart-config-update` + +_公共四件套 · 系统:`--dry-run`_ + +| Flag | Type | 必填 | 说明 | +| --- | --- | --- | --- | +| `--chart-id` | string | required | 目标图表 reference_id | +| `--title` | string | optional | 图表标题 | +| `--subtitle` | string | optional | 图表副标题 | +| `--legend-position` | string | optional | 图例位置;hidden 隐藏图例(可选值:`top` / `bottom` / `left` / `right` / `hidden`) | +| `--x-axis-title` | string | optional | X 轴标题 | +| `--y-axis-title` | string | optional | 左 Y 轴标题 | +| `--secondary-y-axis-title` | string | optional | 右 Y 轴标题 | +| `--x-axis-label-angle` | int | optional | X 轴标签旋转角度(可选值:`-90` / `-45` / `0` / `45` / `90`) | +| `--y-axis-label-angle` | int | optional | 左 Y 轴标签旋转角度(可选值:`-90` / `-45` / `0` / `45` / `90`) | +| `--data-labels` | string | optional | 数据标签内容;none 隐藏标签;兼容 category_percentage 并自动按 value_percentage 处理(可选值:`none` / `value` / `percentage` / `value_percentage` / `category_percentage` / `category` / `series`) | +| `--data-label-position` | string | optional | 数据标签位置(可选值:`auto` / `top` / `bottom` / `left` / `right` / `center` / `inside` / `outside`) | +| `--stack` | string | optional | 堆叠模式(可选值:`none` / `normal` / `percent`) | +| `--stacked` | bool | optional | 兼容别名;等价于 --stack normal(隐藏 flag:不在 `--help` 列出,但可正常传入) | +| `--smooth` | bool | optional | 是否使用平滑曲线;支持 --smooth=false 和 --smooth false | +| `--color-palette` | string | optional | 预设整图配色主题;与 --colors 互斥(可选值:`brandColorSeries@v2` / `rainbowColorSeries@v2` / `complementaryColorSeries@v2` / `converseColorSeries@v2` / `primaryColorSeries@v2` / `singleColorSeries-B-@v2` / `singleColorSeries-W-@v2` / `singleColorSeries-G-@v2` / `singleColorSeries-Y-@v2` / `singleColorSeries-O-@v2` / `singleColorSeries-R-@v2` / `singleColorSeries-D-@v2`) | +| `--colors` | string_slice | optional | 自定义整图系列颜色,逗号分隔且至少 2 个十六进制色值;与 --color-palette 互斥 | + +### `+chart-data-update` + +_公共四件套 · 系统:`--dry-run`_ + +| Flag | Type | 必填 | 说明 | +| --- | --- | --- | --- | +| `--chart-id` | string | required | 目标图表 reference_id | +| `--data-range` | string | required | 新的含表头数据范围;支持逗号分隔的同表多范围,错位或重叠时自动合并 | +| `--data-direction` | string | optional | 数据系列方向;省略时沿用现有图表方向(可选值:`column` / `row`) | +| `--dim1-index` | int | optional | 类别/X 轴维度在数据范围中的 1-based 索引;省略时使用第 1 个维度 | +| `--dim2-indexes` | string | optional | 值/Y 轴系列在数据范围中的 1-based 索引,逗号分隔;省略时使用除 dim1 外的全部维度 | + ### `+chart-create` _公共四件套 · 系统:`--dry-run`_ @@ -133,7 +213,7 @@ _公共四件套 · 系统:`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | | `--chart-id` | string | required | 目标图表 reference_id | -| `--properties` | string + File + Stdin(复合 JSON) | required | 完整或足够完整的图表配置 JSON(先 `+chart-list` 回读再 patch) | +| `--properties` | string + File + Stdin(复合 JSON) | required | 图表配置补丁 JSON;默认只传变化字段,未传字段保持不变;普通对象递归合并,数组整体替换 | ### `+chart-delete` @@ -155,7 +235,7 @@ _创建/更新的图表属性_ - `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 项 } +- `snapshot` (oneOf?) — 图表快照配置 ## Examples @@ -165,6 +245,86 @@ _创建/更新的图表属性_ 输出契约:返回按工作表分组的图表列表,每个图表含 `chart_id` / `position` / `details.snapshot` 等。 +### `+chart-create-basic` + +首列自动作为维度,后续列作为数值系列。饼图只使用第二列数值;散点图以首列为 X、后续列为 Y;组合图以第二列为左轴柱,后续列为右轴折线。数据范围必须包含真实表头;如果类别列与数值列不连续,可以给 `--data-range` 传逗号分隔的多个范围,例如 `"'Sheet1'!A1:A10,'Sheet1'!K1:L10"`。对齐且不重叠的范围会保留为独立引用,不会把 B:J 的间隔列纳入图表;错行、错列或重叠的范围会自动合并为同一工作表内的最小包围矩形。跨工作表范围仍会拒绝。如果数据子集的表头在范围外,改用高级 `+chart-create` 的 detached 模式。 + +```bash +# 柱形图:默认放在数据范围右侧 +lark-cli sheets +chart-create-basic --url "..." --sheet-name "Sheet1" \ + --chart-type column --data-range "'Sheet1'!A1:C10" \ + --title "销售额对比" --x-axis-title "品类" --y-axis-title "销售额" \ + --legend-position bottom --data-labels value --data-label-position top + +# 双轴组合图:首个数值列为左轴柱,其余数值列为右轴折线 +lark-cli sheets +chart-create-basic --url "..." --sheet-name "Sheet1" \ + --chart-type combo --data-range "'Sheet1'!A1:D13" \ + --title "价格与效率" --y-axis-title "价格" --secondary-y-axis-title "效率" \ + --anchor-cell F2 --width 700 --height 400 +``` + +多张基础图一次创建。先把所有数据准备完成,再生成 `ops.json`: + +```json +[ + { + "shortcut": "+chart-create-basic", + "input": { + "sheet_name": "Sheet1", + "chart_type": "column", + "data_range": "'Sheet1'!A1:C10", + "title": "分类对比", + "anchor_cell": "F2" + } + }, + { + "shortcut": "+chart-create-basic", + "input": { + "sheet_name": "Sheet1", + "chart_type": "line", + "data_range": "'Sheet1'!E1:G10", + "title": "趋势变化", + "anchor_cell": "F18" + } + } +] +``` + +```bash +lark-cli sheets +batch-update --url "..." --operations @ops.json --continue-on-error --yes +lark-cli sheets +chart-list --url "..." --sheet-name "Sheet1" +``` + +### `+chart-data-update` + +当创建后发现漏列、范围过宽、辅助分类列发生变化、系列选择错误或数据方向错误时,只更新数据源。`--data-direction` 省略时沿用现有图表方向;新范围必须包含表头。默认使用第 1 个维度作为 dim1、其余维度作为 dim2;需要精确选择时,用 1-based 的 `--dim1-index` 和逗号分隔的 `--dim2-indexes`。工具返回实际采用的 `normalized_data_ranges`,随后用 `+chart-list` 验证范围和系列数。 + +```bash +# 把遗漏的最后一列纳入原折线图,保留标题、配色、图例和落点 +lark-cli sheets +chart-data-update --url "..." --sheet-id "$SID" --chart-id "chrXXX" \ + --data-range "'Sheet1'!A1:M6" + +# 改用按行组织的数据源 +lark-cli sheets +chart-data-update --url "..." --sheet-id "$SID" --chart-id "chrXXX" \ + --data-range "'Sheet1'!A1:M6" --data-direction row + +# 第 1 列作为类别,只使用第 4、8 列作为数值系列 +lark-cli sheets +chart-data-update --url "..." --sheet-id "$SID" --chart-id "chrXXX" \ + --data-range "'Sheet1'!A1:M6" --dim1-index 1 --dim2-indexes "4,8" +``` + +### `+chart-config-update` + +只传需要改的字段。`--data-labels none` 会删除数据标签;`--legend-position hidden` 会隐藏图例;`--smooth=false` 和 `--smooth false` 都可显式关闭平滑曲线。为减少参数重试,`--stacked` 自动按 `--stack normal` 处理,`--data-labels category_percentage` 自动按 `value_percentage` 处理;新调用仍优先使用规范参数。 + +```bash +lark-cli sheets +chart-config-update --url "..." --sheet-id "$SID" --chart-id "chrXXX" \ + --title "新标题" --x-axis-label-angle -45 --legend-position right + +lark-cli sheets +chart-config-update --url "..." --sheet-id "$SID" --chart-id "chrXXX" \ + --data-labels value_percentage --data-label-position outside --stack percent +``` + ### `+chart-create` > **`snapshot.data` 必填 `dim1.serie.index` 或 `dim2.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`,即便侥幸通过也只会渲染空图。 @@ -292,22 +452,23 @@ JSON ### `+chart-update` -**Update 三步法**(缺一步会丢字段): - -1. `+chart-list --chart-id ` 拿到完整 snapshot -2. 在拿到的 snapshot 上**局部**修改要改的字段,其余保持不变 -3. 把**完整 snapshot** 整个回写到 `--properties.snapshot` +默认提交**最小 patch**。例如只修改标题时,只传标题字段: ```bash 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":{"title":{"text":"新的图表标题"}} }' ``` -> 关键:**不能只提交局部 snapshot**,否则未传字段会被还原为默认值。`+chart-update` 的语义是 PUT(整体覆盖),不是 PATCH。 +只调整尺寸时,不需要传 `snapshot`: + +```bash +lark-cli sheets +chart-update --url "..." --sheet-id "$SID" --chart-id "chrXXX" \ + --properties '{"size":{"width":640,"height":360}}' +``` + +> 数组采用整体替换语义。比如只修改一个坐标轴时,先用 `+chart-list --chart-id ` 取得当前 `snapshot.plotArea.axes`,修改目标项后,仅回写 `{"snapshot":{"plotArea":{"axes":[...]}}}`;不要同时回写标题、数据源、图例等未变化字段。 ### `+chart-delete` @@ -325,8 +486,8 @@ lark-cli sheets +chart-delete --url "https://example.feishu.cn/sheets/shtXXX" -- ### Validate / DryRun / Execute 约束 -- `Validate`:XOR 公共四件套;`+chart-create` / `+chart-update` 的 `--properties` 必须能解析为合法 JSON;`+chart-delete`(high-risk-write)校验 `--yes` 或 `--dry-run` 至少一个。 -- `DryRun`:`+chart-create` / `+chart-update` 输出"将要 POST 的 body 模板";`+chart-delete` 输出"将要删除的 chart_id 及隶属 sheet",零网络副作用。 +- `Validate`:XOR 公共四件套;`+chart-data-update` 要求 `--chart-id` 和 `--data-range`,并校验 `--dim1-index` / `--dim2-indexes` 是正整数索引;`+chart-create` / `+chart-update` 的 `--properties` 必须能解析为合法 JSON;`+chart-delete`(high-risk-write)校验 `--yes` 或 `--dry-run` 至少一个。 +- `DryRun`:`+chart-data-update` / `+chart-create` / `+chart-update` 输出"将要 POST 的 body 模板";`+chart-delete` 输出"将要删除的 chart_id 及隶属 sheet",零网络副作用。 - `Execute`:写操作执行后不自动回读;如需确认,自行调用 `+chart-list` 比对结果。 > `+chart-create` / `+chart-update` 是 write 级别,按需可用 `--dry-run` 预览,不要求 `--yes`。只有 `+chart-delete`(high-risk-write)必须 `--yes`。