@fui-org/fui-cli 1.3.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +21 -3
  2. package/dist/fui-cx180e19.js +379 -0
  3. package/dist/fui.js +1 -1
  4. package/package.json +3 -6
  5. package/skills/fui/SKILL.md +9 -41
  6. package/skills/fui-skill/SKILL.md +95 -225
  7. package/skills/fui-skill/assets/projectdefaultstyle-3.0.css +555 -0
  8. package/skills/fui-skill/assets/projectdefaultstyle.css +207 -235
  9. package/skills/fui-skill/references/INDEX.md +105 -137
  10. package/skills/fui-skill/references/advanced-techniques.md +76 -68
  11. package/skills/fui-skill/references/coding-standards.md +56 -56
  12. package/skills/fui-skill/references/component-design.md +166 -173
  13. package/skills/fui-skill/references/component-quickref.md +61 -60
  14. package/skills/fui-skill/references/component-table.md +128 -117
  15. package/skills/fui-skill/references/components-dialog.md +55 -56
  16. package/skills/fui-skill/references/components-display.md +24 -30
  17. package/skills/fui-skill/references/components-echart.md +186 -261
  18. package/skills/fui-skill/references/components-input.md +72 -96
  19. package/skills/fui-skill/references/controls-patterns.md +196 -342
  20. package/skills/fui-skill/references/controls-styling-vocabulary.md +130 -97
  21. package/skills/fui-skill/references/db-table-design.md +24 -28
  22. package/skills/fui-skill/references/db-workflow.md +191 -390
  23. package/skills/fui-skill/references/default-function.md +169 -128
  24. package/skills/fui-skill/references/design-modes.md +35 -63
  25. package/skills/fui-skill/references/echart-templates.md +204 -196
  26. package/skills/fui-skill/references/fastproject.md +62 -60
  27. package/skills/fui-skill/references/fsheet.md +109 -124
  28. package/skills/fui-skill/references/fullstack-workflow.md +90 -128
  29. package/skills/fui-skill/references/module-data-patterns.md +31 -40
  30. package/skills/fui-skill/references/module-json-anatomy.md +47 -52
  31. package/skills/fui-skill/references/module-structure.md +80 -196
  32. package/skills/fui-skill/references/new-session.md +49 -51
  33. package/skills/fui-skill/references/pdfmake.md +17 -17
  34. package/skills/fui-skill/references/permission-system.md +89 -108
  35. package/skills/fui-skill/references/platform-architecture.md +128 -153
  36. package/skills/fui-skill/references/project-config.md +102 -134
  37. package/skills/fui-skill/references/project-provisioning.md +139 -244
  38. package/skills/fui-skill/references/script-map.md +208 -242
  39. package/skills/fui-skill/references/sql-clr-functions.md +98 -97
  40. package/skills/fui-skill/references/system-design.md +63 -88
  41. package/skills/fui-skill/references/tapi-file-api.md +46 -52
  42. package/skills/fui-skill/references/tapi-permission-patterns.md +51 -53
  43. package/skills/fui-skill/references/tapi-reference.md +132 -207
  44. package/skills/fui-skill/references/tools-registry.md +84 -460
  45. package/skills/fui-skill/references/ui-crosswindow-patterns.md +79 -75
  46. package/skills/fui-skill/references/ui-dialog-patterns.md +98 -72
  47. package/skills/fui-skill/references/ui-layout-patterns.md +26 -26
  48. package/skills/fui-skill/references/ui-patterns.md +71 -83
  49. package/skills/fui-skill/references/ui-screenshot-review.md +63 -62
  50. package/skills/fui-skill/references/ui-table-cell-patterns.md +61 -59
  51. package/skills/fui-skill/references/ui-templates.md +16 -23
  52. package/skills/fui-skill/references/verification.md +236 -246
  53. package/skills/fui-skill/references/watcher-patterns.md +30 -63
  54. package/skills/fui-skill/references/websocket-realtime.md +83 -66
  55. package/skills/fui-skill/scripts/component-3.0.js +298 -131
  56. package/skills/fui-skill/scripts/component.js +277 -271
  57. package/skills/fui-skill/scripts/componentTable-3.0.js +182 -53
  58. package/skills/fui-skill/scripts/componentTable.js +171 -49
  59. package/skills/fui-skill/scripts/defaultfunction-3.0.js +88 -3
  60. package/skills/fui-skill/scripts/defaultfunction.js +88 -3
  61. package/skills/fui-skill/scripts/fsheet.js +38 -0
  62. package/skills/fui-target/.claude-plugin/plugin.json +6 -0
  63. package/skills/fui-target/hooks/hooks.json +1 -0
  64. package/skills/fui-target/hooks/register.tsx +71 -0
  65. package/skills/fui-target/types/index.d.ts +7 -0
  66. package/dist/fui-y8an39cn.js +0 -420
  67. package/skills/fui-skill/README.md +0 -112
  68. package/skills/fui-skill/metadata.json +0 -75
  69. package/skills/fui-skill-cli/SKILL.md +0 -139
  70. package/skills/fui-skill-cli/references/INDEX.md +0 -110
  71. package/skills/fui-skill-cli/references/advanced-techniques.md +0 -168
  72. package/skills/fui-skill-cli/references/coding-standards.md +0 -112
  73. package/skills/fui-skill-cli/references/component-design.md +0 -448
  74. package/skills/fui-skill-cli/references/component-quickref.md +0 -78
  75. package/skills/fui-skill-cli/references/component-table.md +0 -248
  76. package/skills/fui-skill-cli/references/components-dialog.md +0 -191
  77. package/skills/fui-skill-cli/references/components-display.md +0 -141
  78. package/skills/fui-skill-cli/references/components-echart.md +0 -316
  79. package/skills/fui-skill-cli/references/components-input.md +0 -335
  80. package/skills/fui-skill-cli/references/controls-patterns.md +0 -701
  81. package/skills/fui-skill-cli/references/controls-styling-vocabulary.md +0 -137
  82. package/skills/fui-skill-cli/references/db-table-design.md +0 -73
  83. package/skills/fui-skill-cli/references/db-workflow.md +0 -288
  84. package/skills/fui-skill-cli/references/default-function.md +0 -425
  85. package/skills/fui-skill-cli/references/design-modes.md +0 -57
  86. package/skills/fui-skill-cli/references/echart-templates.md +0 -489
  87. package/skills/fui-skill-cli/references/fastproject.md +0 -99
  88. package/skills/fui-skill-cli/references/fsheet.md +0 -203
  89. package/skills/fui-skill-cli/references/fullstack-workflow.md +0 -313
  90. package/skills/fui-skill-cli/references/module-data-patterns.md +0 -117
  91. package/skills/fui-skill-cli/references/module-json-anatomy.md +0 -132
  92. package/skills/fui-skill-cli/references/module-structure.md +0 -141
  93. package/skills/fui-skill-cli/references/new-session.md +0 -85
  94. package/skills/fui-skill-cli/references/pdfmake.md +0 -60
  95. package/skills/fui-skill-cli/references/permission-system.md +0 -150
  96. package/skills/fui-skill-cli/references/platform-architecture.md +0 -269
  97. package/skills/fui-skill-cli/references/project-config.md +0 -303
  98. package/skills/fui-skill-cli/references/project-provisioning.md +0 -278
  99. package/skills/fui-skill-cli/references/script-map.md +0 -262
  100. package/skills/fui-skill-cli/references/sql-clr-functions.md +0 -225
  101. package/skills/fui-skill-cli/references/system-design.md +0 -89
  102. package/skills/fui-skill-cli/references/tapi-file-api.md +0 -185
  103. package/skills/fui-skill-cli/references/tapi-permission-patterns.md +0 -156
  104. package/skills/fui-skill-cli/references/tapi-reference.md +0 -474
  105. package/skills/fui-skill-cli/references/tools-registry.md +0 -84
  106. package/skills/fui-skill-cli/references/ui-crosswindow-patterns.md +0 -321
  107. package/skills/fui-skill-cli/references/ui-dialog-patterns.md +0 -255
  108. package/skills/fui-skill-cli/references/ui-layout-patterns.md +0 -176
  109. package/skills/fui-skill-cli/references/ui-patterns.md +0 -303
  110. package/skills/fui-skill-cli/references/ui-screenshot-review.md +0 -95
  111. package/skills/fui-skill-cli/references/ui-table-cell-patterns.md +0 -318
  112. package/skills/fui-skill-cli/references/ui-templates.md +0 -22
  113. package/skills/fui-skill-cli/references/verification.md +0 -236
  114. package/skills/fui-skill-cli/references/watcher-patterns.md +0 -163
  115. package/skills/fui-skill-cli/references/websocket-realtime.md +0 -271
@@ -1,230 +1,189 @@
1
- # f-echart — Biểu đồ ECharts V5 (Core)
1
+ # f-echart — ECharts V5 (Core)
2
2
 
3
- > File này sở hữu: **`f-echart` core: import, kiến trúc, props, config, theme, DataView, lỗi thường gặp**. Template từng loại chart xem [echart-templates.md](echart-templates.md).
3
+ > Owns: **`f-echart` core: import, architecture, props, config, theme, DataView, common errors**. Per-chart templates + module.json patterns: [echart-templates.md](echart-templates.md).
4
4
 
5
- Dùng `f-echart` cho mọi loại biểu đồ: bar, line, pie, scatter, gauge, radar, heatmap, sankey, tree, map, graph...
5
+ Use `f-echart` for all charts: bar, line, pie, scatter, gauge, radar, heatmap, sankey, tree, map, graph...
6
6
 
7
- > ⚠️ `:option` / `:config` của `f-echart` là nơi luật **"`:attr` chỉ nhận expression thuần"** hay bị vi phạm nhất — logic dựng option nhiều bước phải nằm trong hàm `script.js` gọi qua `EXE`, không nhét IIFE / `var` / `return` vào JSON (vi phạm ⇒ trang trắng, 0 JS error, 0 Vue warn). Luật đầy đủ: [advanced-techniques.md](advanced-techniques.md) §1.
8
-
9
- File này là **core**: import, kiến trúc, props, config, theme, DataView, events, lỗi thường gặp. **Template từng loại chart + các pattern module.json** đã tách sang [echart-templates.md](echart-templates.md) — load thêm khi đã biết cần vẽ loại chart nào.
7
+ > ⚠️ `:option`/`:config` most often break **"`:attr` takes a pure expression only"**. Multi-step option building → `script.js` function called via `EXE`; no IIFE / `var` / `return` in JSON (⇒ blank page, 0 JS errors, 0 Vue warnings). See [advanced-techniques.md](advanced-techniques.md) §1.
10
8
 
11
9
  ---
12
10
 
13
- ## Import — Bắt buộc, đúng thứ tự
11
+ ## Import — required, in order
14
12
 
15
- `f-echart` yêu cầu 2 file JS nền tảng. Không có → component không tồn tại, trang trắng.
13
+ Missing → no component, blank page.
16
14
 
17
15
  ```
18
16
  1. /include/chart/echarts.min.js ← ECharts core (phải đứng TRƯỚC)
19
17
  2. /include/chart/fechart.js ← FUI wrapper (phải đứng SAU)
20
18
  ```
21
19
 
22
- Quy trình `file_import_list` → `file_import_new` → `file_import_delete`: xem
23
- [component-quickref.md](component-quickref.md). **Điểm đặc thù của f-echart** — sort number quyết
24
- định thứ tự load, và `fechart.js` phụ thuộc biến `echarts` global nên **PHẢI có sort lớn hơn**
25
- `echarts.min.js` (ví dụ `echarts.min.js` = 100, `fechart.js` = 110).
20
+ `fui import list` → `fui import new` → `fui import delete`: [component-quickref.md](component-quickref.md). Sort = load order; `fechart.js` needs global `echarts` → **larger sort** (e.g. 100 / 110).
26
21
 
27
22
  ---
28
23
 
29
- ## Kiến trúc — Direct Mode
30
-
31
- `f-echart` **xây dựng hoàn toàn trên Apache ECharts V5** (echarts.apache.org). `fechart.js` chỉ là lớp bọc mỏng: nạp theme, vài shortcut trong `config`, DataView, events. **Mọi thứ trong `option` chính là ECharts option** — khi cần cấu hình chart, **dùng thẳng kiến thức Apache ECharts** (option, series, axis, visualMap, dataZoom...); chỉ `config` là shortcut riêng của FUI.
24
+ ## Architecture — Direct Mode only
32
25
 
33
- `f-echart` là **Direct Mode only**. Bạn viết ECharts `option` trực tiếp — không có Auto mode, không có `config.type`.
26
+ Built **entirely on Apache ECharts V5**; `fechart.js` is a thin wrapper (themes, `config` shortcuts, DataView, events). **`option` is plain ECharts option** — use ECharts knowledge; only `config` is FUI-specific. No Auto mode, no `config.type`.
34
27
 
35
- | Prop | Vai trò |
36
- |---|---|
37
- | `option` | **Primary** — full ECharts option, truyền thẳng vào `echarts.setOption()`. Dùng kiến thức Apache ECharts |
38
- | `config` | Meta/shortcut riêng FUI: height, theme, title, toolbox... Là defaults, bị `option` merge đè |
28
+ - `option` — **primary**, full ECharts option → `echarts.setOption()`.
29
+ - `config` — FUI shortcuts (height, theme, title, toolbox...); defaults, overridden by `option`.
39
30
 
40
- **Thứ tự merge:** `config` xây base → `option` merge đè lên bằng `_.merge` (lodash).
41
- - Plain objects → **deep-merge**
42
- - Primitives → **replace**
43
- - Arrays → **merge theo chỉ số**, KHÔNG phải replace ⚠️
44
-
45
- Bẫy của merge-theo-chỉ-số: `config` chỉ sinh ra hai mảng là `color` (từ `config.color`) và `dataZoom` (từ `config.zoom`). Nếu cả `config.color` lẫn `option.color` cùng có, mảng ngắn hơn **không xoá được đuôi của mảng dài hơn**:
31
+ **Merge:** `config` base, then lodash `_.merge` with `option`: objects deep-merge, primitives replace, **arrays merge by index, NOT replace** ⚠️. `config` produces only `color` (`config.color`) and `dataZoom` (`config.zoom`); with both `config.color` and `option.color` the shorter can't drop the longer's tail:
46
32
 
47
33
  ```js
48
- config.color = ['#111','#222','#333','#444']; option.color = ['#aaa','#bbb'];
34
+ config.color = ["#111", "#222", "#333", "#444"];
35
+ option.color = ["#aaa", "#bbb"];
49
36
  // → ['#aaa','#bbb','#333','#444'] ← 2 màu cũ còn sót lại
50
37
  ```
51
38
 
52
- ⇒ Chọn **một chỗ duy nhất** để khai palette (khuyến nghị `config.color`), đừng khai cả hai. Với `series`/`xAxis`/`yAxis`/`grid` thì `config` không bao giờ sinh ra nên không có va chạm — cứ khai thoải mái trong `option`.
39
+ ⇒ Palette in **one place** (prefer `config.color`). `series`/`xAxis`/`yAxis`/`grid` never come from `config` — declare freely.
53
40
 
54
- Sau khi merge, chart luôn được vẽ bằng `setOption(opt, true)` (**notMerge**) — mỗi lần render là thay toàn bộ option, không sót cấu hình của lần trước.
41
+ Rendered with `setOption(opt, true)` (**notMerge**): each render replaces the whole option.
55
42
 
56
43
  ---
57
44
 
58
- ## Quy ước hiển thị MẶC ĐỊNH (làm đúng ngay từ đầu, không chờ user nhắc)
59
-
60
- Đúc kết từ các yêu cầu chỉnh sửa lặp lại nhiều lần của user thật. Áp dụng mặc định cho mọi chart mới/refactor — chỉ bỏ qua khi user yêu cầu rõ kiểu khác:
45
+ ## DEFAULT conventions (apply unprompted, unless user asks otherwise)
61
46
 
62
- 1. **Luôn hiển thị giá trị trên chart** — bar: `label: { show: true, position: 'top' }` (horizontal bar → `position: 'right'`); line: `label: { show: true, position: 'top' }`.
63
- 2. **Grid padding**: mặc định chart có hệ trục dùng `left: 0`, `right: 0`, `top: 50`, `bottom: 50`; `top`/`bottom` tự tăng nếu title/legend cần thêm chỗ. Chỉ khai cạnh mà user yêu cầu đổi; thêm `containLabel: true` khi nhãn trục có nguy cơ bị cắt. Xem "Grid padding và boundaryGap".
64
- 3. **Số hiển thị đầy đủ** có phân cách nghìn (`1,500,000`) — không rút gọn `1.5M`/`1.2k` trừ khi user yêu cầu (mặc định `formatFull` của tooltip đã đúng — đừng override).
65
- 4. **Pie**: không để labelLine ("râu") dài rối; label ngắn gọn (tên + % nếu cần) hoặc dồn thông tin về legend + tooltip.
66
- 5. **Không tự dựng nút "Xem dữ liệu"** — dùng `config.dataView: true` có sẵn của f-echart.
47
+ 1. **Show values** — bar: `label: { show: true, position: 'top' }` (horizontal → `'right'`); line: `label: { show: true, position: 'top' }`.
48
+ 2. **Grid**: axis charts default `left: 0`, `right: 0`, `top: 50`, `bottom: 50` (top/bottom auto-grow for title/legend). Declare only sides the user wants changed; `containLabel: true` if labels may clip.
49
+ 3. **Full numbers** (`1,500,000`), no `1.5M`/`1.2k` unless asked; tooltip default `formatFull` is correct — don't override.
50
+ 4. **Pie**: no long messy labelLines; short labels (name + % if needed) or rely on legend + tooltip.
51
+ 5. **No hand-built "View data" button** — `config.dataView: true`.
67
52
 
68
53
  ---
69
54
 
70
55
  ## Props
71
56
 
72
- | Prop | Type | Default | Mô tả |
73
- |---|---|---|---|
74
- | `config` | Object | `{}` | Shortcut settings |
75
- | `option` | Object | `{}` | Full ECharts option — primary |
57
+ Only `config` (Object, `{}`) and `option` (Object, `{}`, primary). **No `data` prop.**
76
58
 
77
- > Component chỉ có 2 props: `config` và `option`. **Không có `data` prop.**
59
+ ## config keys
60
+
61
+ | Key | Default | Notes |
62
+ | -------------------------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `height` | `400` | number = px; string e.g. `'50vh'` |
64
+ | `theme` | `'fui'` | **Only** `'fui'`\|`'shine'`\|`'v5'`; others (incl. `echarts.registerTheme` names) or `null` **silently → `'fui'`**. Recolor via `config.color` |
65
+ | `backgroundColor` | `'transparent'` | |
66
+ | `title` / `subtitle` | | → `option.title.text` / `.subtext` |
67
+ | `titleAlign` | `'center'` | `'left'`/`'center'`/`'right'` (old alias `titleLeft`) |
68
+ | `titleSize` | `16` | px |
69
+ | `titleColor` | | empty = theme |
70
+ | `titleWeight` | `'bold'` | `'normal'`/`'bold'`/`500`... |
71
+ | `titleFont` | | font-family, title + subtitle |
72
+ | `titleStyle` | | full ECharts textStyle — **wins over all `title*`** |
73
+ | `subtitleSize` / `subtitleColor` / `subtitleStyle` | `12` | same, subtitle |
74
+ | `color` | | palette (string[]) |
75
+ | `animation` | | `false` = off |
76
+ | `animationDuration` / `animationEasing` | | ms / ECharts easing |
77
+ | `legend` | | `false` hide; `true`/Object show, merged with `{ type:'scroll', left:'center' }`. **No top/bottom** — engine sets it |
78
+ | `legendPosition` | `'bottom'` | `'top'`/`'bottom'`; **only when legend enabled via `config.legend`/`config.legendConfig`** |
79
+ | `legendRows` | `1` | rows to reserve; >1 when items wrap. Any legend declaration |
80
+ | `legendConfig` | | extra legend props (after `legend`) |
81
+ | `tooltip` | | merged into `{ confine: true }`. **No default `trigger`** → `'item'`; multi-series axis charts need `tooltip: { trigger: 'axis' }` in `option` |
82
+ | `tooltipFormat` | `'auto'` | `'number'`/`'percent'`/`'currency'`/`'short'`/`'auto'`; `'auto'` = full with separators; `'short'` = `1.5M`, only if asked |
83
+ | `decimals` | | for `'number'`/`'percent'`/`'currency'` |
84
+ | `symbol` | `'₫'` | for `'currency'` |
85
+ | `tooltipFormatter` | | Function, **wins over `tooltipFormat`** |
86
+ | `toolbox` | `false` | saveAsImage + restore |
87
+ | `toolboxConfig` | | override/extend toolbox |
88
+ | `dataView` | `false` | table button (Vue overlay), data from `option` |
89
+ | `zoom` | | dataZoom inside + slider |
90
+ | `zoomConfig` | | Array, override dataZoom |
91
+ | `refreshInterval` | | ms auto-refresh |
92
+
93
+ **Default tooltip needs no config** (centered category title; dot + left label + right value; full numbers `2,535` not `2.5K`). Use `tooltipFormat`/`tooltipFormatter` only for another format.
78
94
 
79
95
  ---
80
96
 
81
- ## config keys
97
+ ## Grid padding vs `boundaryGap`
98
+
99
+ - Plot area ↔ canvas edge → `option.grid.top`/`bottom`/`left`/`right`.
100
+ - First/last point/bar ↔ axis ends → `boundaryGap` on that `xAxis`/`yAxis`.
101
+
102
+ No `padding` prop; `boundaryGap` doesn't replace `grid`. Numbers = px, `'10%'` = percent. Cartesian default:
103
+
104
+ ```js
105
+ grid: { top: 50, bottom: 50, left: 0, right: 0 }
106
+ ```
107
+
108
+ `top`/`bottom` are minimums (engine grows them for title/legend). A side declared in `option.grid` wins and is no longer auto-computed. Only horizontal change → declare only those:
109
+
110
+ ```js
111
+ grid: { left: 12, right: 24, containLabel: true }
112
+ ```
113
+
114
+ Exact values for all four → declare all four; check title/legend don't overlap:
115
+
116
+ ```js
117
+ grid: { top: 40, bottom: 60, left: 12, right: 24, containLabel: true }
118
+ ```
119
+
120
+ `containLabel: true` keeps long/edge axis labels inside. `grid` affects only axis charts; pie, funnel, treemap, sankey, graph ignore it → `series.top`/`bottom`/`left`/`right` (or `center`/`radius`).
121
+
122
+ No `boundaryGap` set by `fechart.js`; ECharts defaults: `category` → `true`; `value`/`time`/`log` → `[0, 0]`. Line touching both category ends:
123
+
124
+ ```js
125
+ xAxis: { type: 'category', boundaryGap: false, data: labels }
126
+ ```
127
+
128
+ Bars usually keep `true`. Horizontal bar → `yAxis.boundaryGap`. Value/time margins:
82
129
 
83
- | Key | Type | Default | Mô tả |
84
- |---|---|---|---|
85
- | `height` | number\|string | `400` | Số = `px`; string = `'50vh'`... |
86
- | `theme` | string | `'fui'` | **Chỉ nhận** `'fui'`\|`'shine'`\|`'v5'`. Tên khác (kể cả theme tự `echarts.registerTheme`) và `null` đều **âm thầm rơi về `'fui'`** — đổi màu thì dùng `config.color` |
87
- | `backgroundColor` | string | `'transparent'` | Override background |
88
- | `title` | string | | → `option.title.text` |
89
- | `subtitle` | string | | → `option.title.subtext` |
90
- | `titleAlign` | string | `'center'` | `'left'`/`'center'`/`'right'` (bí danh cũ: `titleLeft`) |
91
- | `titleSize` | number | `16` | Cỡ chữ title (px) |
92
- | `titleColor` | string | | Màu chữ title — bỏ trống thì theo theme |
93
- | `titleWeight` | string\|number | `'bold'` | `'normal'`/`'bold'`/`500`... |
94
- | `titleFont` | string | | font-family cho cả title lẫn subtitle |
95
- | `titleStyle` | Object | | ECharts textStyle đầy đủ — **thắng mọi key `title*` ở trên** |
96
- | `subtitleSize` / `subtitleColor` / `subtitleStyle` | | `12` | Tương tự, cho subtitle |
97
- | `color` | string[] | | Override color palette |
98
- | `animation` | boolean | | `false` = tắt animation |
99
- | `animationDuration` | number | | ms |
100
- | `animationEasing` | string | | ECharts easing name |
101
- | `legend` | true\|false\|Object | | `false` = ẩn; `true`/Object = bật, merge với `{ type:'scroll', left:'center' }` — **không đặt top/bottom**, vị trí dọc do layout engine tính |
102
- | `legendPosition` | string | `'bottom'` | Cạnh legend neo vào: `'top'`/`'bottom'`. **Chỉ có tác dụng khi legend được bật qua `config.legend`/`config.legendConfig`** — xem "Khoảng trống & bố cục dọc" |
103
- | `legendRows` | number | `1` | Số hàng legend cần chừa chỗ — đặt >1 khi nhiều mục xuống dòng. Có tác dụng với mọi cách khai legend |
104
- | `legendConfig` | Object | | Extra legend props (merge sau `legend`) |
105
- | `tooltip` | Object | | Merge vào default `{ confine: true }`. **Không có `trigger` mặc định** → ECharts dùng `'item'`; chart nhiều series theo trục phải tự khai `tooltip: { trigger: 'axis' }` trong `option` |
106
- | `tooltipFormat` | string | `'auto'` | `'number'`/`'percent'`/`'currency'`/`'short'`/`'auto'`. `'auto'` = số đầy đủ có phân cách nghìn (`1,500,000`); `'short'` = co gọn (`1.5M`) — chỉ dùng khi user yêu cầu rõ |
107
- | `decimals` | number | | Số chữ số thập phân cho `tooltipFormat` `'number'`/`'percent'`/`'currency'` |
108
- | `symbol` | string | `'₫'` | Ký hiệu tiền tệ cho `tooltipFormat: 'currency'` |
109
- | `tooltipFormatter` | Function | | Custom tooltip fn — **thắng `tooltipFormat`** |
110
- | `toolbox` | boolean | `false` | Hiện toolbox (saveAsImage + restore) |
111
- | `toolboxConfig` | Object | | Override/extend toolbox features |
112
- | `dataView` | boolean | `false` | Hiện nút bảng (Vue overlay) — tự trích xuất data từ `option` |
113
- | `zoom` | boolean | | dataZoom inside + slider |
114
- | `zoomConfig` | Array | | Override dataZoom config |
115
- | `refreshInterval` | number | | ms — auto-refresh |
116
-
117
- **Tooltip mặc định đã đẹp sẵn — KHÔNG cần cấu hình gì thêm:** title (tên category) canh giữa, mỗi dòng series gồm chấm màu + label canh trái, giá trị canh phải, số hiển thị đầy đủ có phân cách nghìn (`2,535` chứ không phải `2.5K`). Chỉ đụng vào `tooltipFormat`/`tooltipFormatter` khi user yêu cầu format khác (`percent`, `currency`, co gọn `short`...).
130
+ ```js
131
+ xAxis: { type: 'time', boundaryGap: ['5%', '5%'] }
132
+ ```
133
+
134
+ "Blank space at line ends" → `boundaryGap`; "padding around chart/canvas" → `grid`.
118
135
 
119
136
  ---
120
137
 
121
- ## Grid padding và `boundaryGap` — chọn đúng cấu hình
122
-
123
- Hai khái niệm này giải quyết hai loại khoảng trống khác nhau:
124
-
125
- | User muốn thay đổi | Cấu hình đúng | Vị trí khai |
126
- |---|---|---|
127
- | Khoảng cách từ vùng vẽ tới mép canvas | `grid.top` / `grid.bottom` / `grid.left` / `grid.right` | `option.grid` |
128
- | Khoảng hở từ điểm/cột đầu và cuối tới hai đầu trục | `boundaryGap` | ngay trong `xAxis` hoặc `yAxis` tương ứng |
129
-
130
- `f-echart` không có prop `padding`, và `boundaryGap` không thay thế cho `grid`. Số trong `grid` là px; chuỗi như `'10%'` dùng phần trăm. Mặc định wrapper cho chart Cartesian là:
131
-
132
- ```js
133
- grid: { top: 50, bottom: 50, left: 0, right: 0 }
134
- ```
135
-
136
- `top`/`bottom` là mức tối thiểu: layout engine có thể tăng để tránh title/legend. Mỗi cạnh user khai trong `option.grid` sẽ thắng mặc định và không còn được engine tự tính. Vì vậy, nếu user chỉ yêu cầu đổi padding ngang, chỉ khai hai cạnh ngang:
137
-
138
- ```js
139
- grid: { left: 12, right: 24, containLabel: true }
140
- ```
141
-
142
- Nếu user yêu cầu con số chính xác cho cả bốn cạnh, khai đủ bốn cạnh; cần kiểm tra title/legend không đè vùng vẽ:
143
-
144
- ```js
145
- grid: { top: 40, bottom: 60, left: 12, right: 24, containLabel: true }
146
- ```
147
-
148
- `containLabel: true` giúp giữ nhãn trục trong vùng `grid`; dùng khi nhãn dài hoặc sát mép. `grid` chỉ điều khiển chart Cartesian có `xAxis`/`yAxis`. Pie, funnel, treemap, sankey và graph bỏ qua `grid`; chỉnh khoảng trống của chúng bằng `series.top` / `bottom` / `left` / `right` (hoặc `center` / `radius` theo loại chart).
149
-
150
- `fechart.js` không đặt `boundaryGap`, nên giữ mặc định ECharts:
151
-
152
- - Trục `category`: `boundaryGap: true`.
153
- - Trục `value`, `time`, `log`: `boundaryGap: [0, 0]`.
154
-
155
- Khi user muốn line chart chạm hai đầu trục category, đặt `boundaryGap: false` trên chính trục category:
156
-
157
- ```js
158
- xAxis: { type: 'category', boundaryGap: false, data: labels }
159
- ```
160
-
161
- Bar chart thường giữ `boundaryGap: true` để cột đầu/cuối không bị ép sát mép. Với horizontal bar, trục category là `yAxis`, nên đặt ở `yAxis.boundaryGap`. Với trục `value`/`time`, có thể chừa biên theo hai phía:
162
-
163
- ```js
164
- xAxis: { type: 'time', boundaryGap: ['5%', '5%'] }
165
- ```
166
-
167
- Khi user nói "bỏ khoảng trắng hai đầu đường dữ liệu", ưu tiên kiểm tra `boundaryGap`; khi user nói "giảm padding quanh biểu đồ/canvas", chỉnh `grid`.
168
-
169
- ---
170
-
171
- ## Khoảng trống & bố cục dọc (layout box model)
172
-
173
- ECharts đặt title / legend / vùng vẽ bằng toạ độ tuyệt đối — nó **không** biết "khối này nằm dưới khối kia". `f-echart` bù lại bằng một layout engine chạy sau khi merge `config` + `option`: đi từ mép trên xuống và từ mép dưới lên, mỗi khối hiện diện lấy **đúng chiều cao của mình + một khoảng hở**, phần chưa ai lấy là vùng vẽ.
174
-
175
- | Hằng số | px | Dùng cho |
176
- |---|---|---|
177
- | padding | 8 | khoảng hở với mép canvas |
178
- | gap | 12 | khoảng hở tối thiểu giữa hai khối bất kỳ |
179
- | grid trái / phải | 0 / 0 | mặc định vùng vẽ Cartesian; module có thể override từng cạnh |
180
- | grid trên / dưới | 50 / 50 | mức tối thiểu khi module không khai; title/legend có thể làm tăng |
181
- | chiều cao title | `round(fontSize × 1.4)` | mặc định `16px` → 22 |
182
- | chiều cao subtitle | `4 + round(fontSize × 1.4)` | mặc định `12px` → 4+17 |
183
- | một hàng legend | 24 | nhân với `legendRows` |
184
- | chừa nhãn trục hoành | 26 | cộng thêm vào `grid.bottom` |
185
- | chừa nhãn trục tung | 10 | cộng thêm vào `grid.top` |
186
-
187
- Vùng vẽ nhận phần còn lại qua **hai cơ chế khác nhau**:
188
- - chart **có hệ trục** (`xAxis`/`yAxis`) → `grid.top` / `grid.bottom`
189
- - **pie / funnel / treemap / sankey / graph** → `series.top` / `series.bottom`, vì các loại này **bỏ qua `grid` hoàn toàn**. Ngoại lệ: series khai `center` bằng px thì engine không đụng vào (đổi box sẽ dịch hình đi).
190
-
191
- Số thực tế (đo trên chính `fechart.js`), chart có trục, title mặc định:
192
-
193
- | Cấu hình | `title.top` | `legend` | `grid.top` | `grid.bottom` |
194
- |---|---|---|---|---|
195
- | không title, không legend | — | — | 50 | 50 |
196
- | chỉ title | 8 | — | 52 | 50 |
197
- | title + `config.legend: true` | 8 | `bottom: 8` | 52 | 70 |
198
- | title + `legendPosition:'top'` | 8 | `top: 42` | 88 | 50 |
199
- | như trên + `legendRows: 2` | 8 | `top: 42` | 112 | 50 |
200
- | title `titleSize: 24` + subtitle | 8 | — | 85 | 50 |
201
-
202
- ### Legend neo cạnh nào — luật quyết định
203
-
204
- Đọc theo thứ tự, dừng ở điều kiện đầu tiên đúng:
205
-
206
- 1. `legend.top` có giá trị → neo **trên**; `legend.bottom` có giá trị → neo **dưới** (giá trị module khai được giữ nguyên).
207
- 2. Legend bật qua `config.legend` / `config.legendConfig` → `config.legendPosition`, mặc định **`'bottom'`**.
208
- 3. Legend chỉ khai trong `option.legend` (không qua `config`) → neo **trên**. ⚠️ Trong ca này `config.legendPosition` **không có tác dụng** — muốn legend xuống đáy thì hoặc đặt `option.legend.bottom: 0`, hoặc bật legend qua `config.legend: true`.
209
-
210
- **Hệ quả khi viết module:** đừng tự chỉnh `grid.top`/`grid.bottom`/`legend.top` để "né" title — thêm title, đổi `titleSize`, hay chuyển `legendPosition` thì khoảng trống tự cộng lại. Giá trị **do module tự khai luôn được tôn trọng** (khai `grid.top: 5` thì engine không đụng vào, và khối sau vẫn cộng dồn từ giá trị đó), nên chỉ khai khi thật sự cần một bố cục riêng. Legend nhiều mục xuống dòng thì báo `legendRows` thay vì tự đẩy `grid.top`.
211
-
212
- Hai giới hạn đã biết: (a) engine chỉ tính được `top`/`bottom` dạng **số px** — khai `'10%'`/`'middle'` thì nó chỉ giữ một mức tối thiểu, phần còn lại tự canh; (b) `option.grid` dạng **mảng** (multi-grid) thì engine bỏ qua hoàn toàn, module tự lo bố cục.
138
+ ## Vertical layout engine
139
+
140
+ ECharts places title/legend/plot absolutely. After merge, `f-echart` stacks top-down and bottom-up: each present block takes **its height + a gap**; remainder = plot.
141
+
142
+ | Constant | px |
143
+ | ---------------------- | ----------------------------------------- |
144
+ | padding to canvas edge | 8 |
145
+ | min gap between blocks | 12 |
146
+ | grid left / right | 0 / 0 (overridable) |
147
+ | grid top / bottom min | 50 / 50 |
148
+ | title height | `round(fontSize × 1.4)` (16px → 22) |
149
+ | subtitle height | `4 + round(fontSize × 1.4)` (12px → 4+17) |
150
+ | legend row | 24 × `legendRows` |
151
+ | x-axis label reserve | +26 to `grid.bottom` |
152
+ | y-axis label reserve | +10 to `grid.top` |
153
+
154
+ Axis charts get the remainder as `grid.top`/`grid.bottom`; **pie / funnel / treemap / sankey / graph** as `series.top`/`series.bottom`, except a series with px `center` (left untouched).
155
+
156
+ Measured (axis chart, default title):
157
+
158
+ | Config | `title.top` | `legend` | `grid.top` | `grid.bottom` |
159
+ | -------------------------------- | ----------- | ----------- | ---------- | ------------- |
160
+ | no title, no legend | — | — | 50 | 50 |
161
+ | title only | 8 | — | 52 | 50 |
162
+ | title + `config.legend: true` | 8 | `bottom: 8` | 52 | 70 |
163
+ | title + `legendPosition:'top'` | 8 | `top: 42` | 88 | 50 |
164
+ | as above + `legendRows: 2` | 8 | `top: 42` | 112 | 50 |
165
+ | title `titleSize: 24` + subtitle | 8 | — | 85 | 50 |
166
+
167
+ **Legend edge, first match wins:**
168
+
169
+ 1. `legend.top` set → top; `legend.bottom` set → bottom (value kept).
170
+ 2. Enabled via `config.legend`/`config.legendConfig` → `config.legendPosition` (default **`'bottom'`**).
171
+ 3. Only in `option.legend` → **top**; ⚠️ `config.legendPosition` ignored — use `option.legend.bottom: 0` or `config.legend: true`.
172
+
173
+ Don't tweak `grid.top`/`grid.bottom`/`legend.top` to dodge a title — space is re-added. **Module-declared values are always kept** (`grid.top: 5` stays; later blocks stack from it) → declare only for a custom layout. Wrapping legend → `legendRows`.
174
+
175
+ Limits: only **px numbers** are computed (`'10%'`/`'middle'` keep just a minimum); array `option.grid` (multi-grid) is ignored — module handles layout.
213
176
 
214
177
  ---
215
178
 
216
179
  ## DataView
217
180
 
218
- `config.dataView: true` → hiện icon bảng (góc trên phải chart). Click → overlay bảng dữ liệu phủ lên chart.
219
-
220
- **DataView tự trích xuất từ `option` — không cần truyền data riêng:**
221
- - Ưu tiên `option.dataset.source` (object-array hoặc array-of-arrays)
222
- - Fallback: `option.series[].data` + `xAxis.data` / `yAxis.data`
223
- - Không có data phù hợp → nút bảng ẩn tự động
181
+ `config.dataView: true` → table icon top-right → overlay table. Data from `option.dataset.source` (object-array or array-of-arrays), else `option.series[].data` + `xAxis.data`/`yAxis.data`; none → button hidden.
224
182
 
225
183
  ```json
226
184
  {
227
- "el": "f-echart", "w": "12",
185
+ "el": "f-echart",
186
+ "w": "12",
228
187
  "attr": {
229
188
  ":config": "{ title: 'Doanh thu', dataView: true, height: 350 }",
230
189
  ":option": "vueData.chartOpt"
@@ -232,51 +191,32 @@ Hai giới hạn đã biết: (a) engine chỉ tính được `top`/`bottom` d
232
191
  }
233
192
  ```
234
193
 
235
- > DataView là **Vue overlay tùy chỉnh** — **không phải** ECharts toolbox DataView. `config.toolbox: true` chỉ thêm saveAsImage + restore.
236
-
237
- Bỏ `dataView` khi option không có series array rõ ràng (gauge giá trị đơn, sankey node/link, tree hierarchical...).
194
+ Custom **Vue overlay**, **not** ECharts toolbox DataView (`config.toolbox: true` = saveAsImage + restore only). Omit without a clear series array (single-value gauge, sankey node/link, hierarchical tree...).
238
195
 
239
196
  ---
240
197
 
241
- ## Series auto-enhancement
242
-
243
- `f-echart` tự động áp style theo loại series — điều khiển bởi **enhancement policy** của theme:
198
+ ## Series auto-enhancement (theme policy)
244
199
 
245
- | Policy | Áp cho | Hành vi |
246
- |---|---|---|
247
- | `lighten` | `bar`, `pie`, `funnel` | Fill màu nhạt 50% + viền màu gốc |
248
- | `separator` | `treemap`, `sunburst` | Viền trắng mờ giữa các ô/lớp |
249
- | `outline` | `scatter`, `effectScatter` | Viền trắng quanh điểm |
200
+ | Policy | Applies to | Effect |
201
+ | ----------- | -------------------------- | ------------------------------------------------------------------------------------------- |
202
+ | `lighten` | `bar`, `pie`, `funnel` | 50% lighter fill + original-color border; bar per-series, pie/funnel per-item palette color |
203
+ | `separator` | `treemap`, `sunburst` | faint white border between cells/layers |
204
+ | `outline` | `scatter`, `effectScatter` | white outline around points |
250
205
 
251
- **`lighten` detail:**
252
- - `bar` — per-series: một màu nhạt + viền cho toàn bộ cột
253
- - `pie` / `funnel` — per-item: mỗi lát/phần tử lấy màu riêng từ palette rồi làm nhạt
254
-
255
- **Override:**
256
- - Tắt lighten cho bar: đặt `itemStyle.color` tường minh trong `option.series[]`
257
- - Tắt lighten cho pie item: đặt `itemStyle.color` vào từng phần tử `data[]`
258
- - Tắt separator/outline: đặt `itemStyle.borderWidth: 0` trong `option.series[]`
206
+ Disable: bar → `itemStyle.color` in `option.series[]`; pie item → `itemStyle.color` on each `data[]` item; separator/outline → `itemStyle.borderWidth: 0` in `option.series[]`.
259
207
 
260
208
  ---
261
209
 
262
210
  ## Theme
263
211
 
264
- 3 theme nhúng sẵn trong `fechart.js` — không cần file ngoài:
265
-
266
- | Theme | Màu | Đặc điểm |
267
- |---|---|---|
268
- | `'fui'` | 56 màu | **Mặc định** — palette rộng, nhiều series |
269
- | `'shine'` | 8 màu | Classic axis style, màu đậm nét |
270
- | `'v5'` | 9 màu | ECharts V5 default style |
212
+ Built in: `'fui'` (56 colors, **default**), `'shine'` (8, classic axis, strong colors), `'v5'` (9, ECharts V5 default).
271
213
 
272
214
  ```json
273
215
  ":config": "{ theme: 'shine' }"
274
216
  ":config": "{ theme: 'v5' }"
275
217
  ```
276
218
 
277
- > ⚠️ **Chỉ ba tên trên là hợp lệ.** Mọi tên khác — kể cả theme tự đăng ký bằng `echarts.registerTheme('myTheme', …)` — và cả `theme: null` đều **âm thầm rơi về `'fui'`**, không lỗi, không cảnh báo. Đừng dùng `config.theme` cho theme custom.
278
-
279
- **Đổi bảng màu thì dùng `config.color`** — đây là đường được hỗ trợ, và cũng là bảng màu mà series auto-enhancement đọc để tính màu fill/viền:
219
+ ⚠️ **Only these three.** Other names (incl. `echarts.registerTheme('myTheme', …)`) and `null` **silently → `'fui'`**, no error. Recolor with `config.color` (also read by auto-enhancement):
280
220
 
281
221
  ```json
282
222
  ":config": "{ color: ['#e63946', '#457b9d', '#2a9d8f'] }"
@@ -284,65 +224,54 @@ Bỏ `dataView` khi option không có series array rõ ràng (gauge giá trị
284
224
 
285
225
  ---
286
226
 
287
- ## Events
227
+ ## Events & methods
288
228
 
289
- | Event | Payload | Mô tả |
290
- |---|---|---|
291
- | `chart-click` | params | Click data point |
292
- | `drilldown` | params | Alias của `chart-click` |
293
- | `legend-change` | params | Chọn/bỏ legend item |
294
- | `rendered` | — | Sau mỗi lần `setOption` |
295
- | `finished` | — | Sau khi animation xong |
229
+ Events: `chart-click` (params) · `drilldown` (alias) · `legend-change` (params) · `rendered` (after each `setOption`) · `finished` (animation done).
296
230
 
297
231
  ```json
298
232
  { "v-on:drilldown": "CALL(vueData.loadDetail, { row: $event })" }
299
233
  ```
300
234
 
301
- ---
302
-
303
- ## Exposed methods (`$refs`)
304
-
305
235
  ```js
306
- this.$refs.myChart.getInstance() // ECharts instance gốc
307
- this.$refs.myChart.refresh() // Force re-render (gọi lại setOption)
308
- this.$refs.myChart.exportImage('chart.png')
309
- this.$refs.myChart.resizeChart() // Hiếm khi cần — xem ghi chú dưới
236
+ this.$refs.myChart.getInstance(); // ECharts instance gốc
237
+ this.$refs.myChart.refresh(); // Force re-render (gọi lại setOption)
238
+ this.$refs.myChart.exportImage("chart.png");
239
+ this.$refs.myChart.resizeChart(); // Hiếm khi cần — xem ghi chú dưới
310
240
  ```
311
241
 
312
- > **Không cần gọi `resizeChart()` thủ công trong hầu hết trường hợp**: component đã tự nghe `window.resize` **và** `ResizeObserver` trên chính thẻ chứa chart, nên chart co giãn đúng cả khi container đổi kích thước mà viewport không đổi (Vuetify grid settle sau mount, mở tab/dialog, thu gọn sidebar).
242
+ `resizeChart()` rarely needed: listens to `window.resize` **and** a `ResizeObserver` on its container (Vuetify grid settle, tab/dialog open, sidebar collapse).
313
243
 
314
244
  ```js
315
245
  // Native ECharts API
316
246
  const chart = vueData.$refs.myChart.getInstance();
317
- chart.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: 2 });
318
- chart.on('mouseover', p => { vueData.hoverLabel = p.name; });
247
+ chart.dispatchAction({ type: "highlight", seriesIndex: 0, dataIndex: 2 });
248
+ chart.on("mouseover", (p) => {
249
+ vueData.hoverLabel = p.name;
250
+ });
319
251
  ```
320
252
 
321
253
  ---
322
254
 
323
- ## FEchartFormatter (global, dùng trong script.js)
324
-
325
- Đúng 6 hàm, không có gì khác:
255
+ ## FEchartFormatter (global, script.js) — exactly 6 functions
326
256
 
327
257
  ```js
328
- FEchartFormatter.build('currency', { symbol: '₫', decimals: 0 })(1500000) // → '₫1,500,000'
329
- FEchartFormatter.formatShort(1500000) // → '1.5M' (co gọn — chỉ dùng khi cần tiết kiệm chỗ, vd axis label)
330
- FEchartFormatter.formatNumber(1500000) // → '1,500,000' (decimals thứ 2, mặc định 0)
331
- FEchartFormatter.formatFull(2535.678) // → '2,535.68' (đầy đủ, tối đa 2 số thập phân — mặc định của tooltip)
258
+ FEchartFormatter.build("currency", { symbol: "₫", decimals: 0 })(1500000); // → '₫1,500,000'
259
+ FEchartFormatter.formatShort(1500000); // → '1.5M' (co gọn — chỉ dùng khi cần tiết kiệm chỗ, vd axis label)
260
+ FEchartFormatter.formatNumber(1500000); // → '1,500,000' (decimals thứ 2, mặc định 0)
261
+ FEchartFormatter.formatFull(2535.678); // → '2,535.68' (đầy đủ, tối đa 2 số thập phân — mặc định của tooltip)
332
262
  // Tạo tooltip formatter từ config.tooltipFormat ('auto'/không khai → formatFull, số đầy đủ)
333
- FEchartFormatter.buildTooltip({ tooltipFormat: 'currency', symbol: '₫' })
334
- FEchartFormatter.buildDefault() // formatter tooltip mặc định (formatFull)
263
+ FEchartFormatter.buildTooltip({ tooltipFormat: "currency", symbol: "₫" });
264
+ FEchartFormatter.buildDefault(); // formatter tooltip mặc định (formatFull)
335
265
  ```
336
266
 
337
- Hai điểm dễ sai:
338
- - **Không có** `formatPercent` / `formatCurrency` rời — muốn hai kiểu đó thì đi qua `build('percent', { decimals })` / `build('currency', { symbol, decimals })`.
339
- - Truy cập bằng **tên trần** `FEchartFormatter`, **không phải** `window.FEchartFormatter` (khai bằng `const` ở cấp script nên không gắn lên `window`; `window.FEchartFormatter` là `undefined`).
267
+ - **No** `formatPercent`/`formatCurrency` → `build('percent', { decimals })` / `build('currency', { symbol, decimals })`.
268
+ - Bare `FEchartFormatter` only; `window.FEchartFormatter` is `undefined` (script-level `const`).
340
269
 
341
270
  ---
342
271
 
343
- ## Sort dữ liệu
272
+ ## Sorting
344
273
 
345
- Không có `config.sort`. Sort trong `script.js` trước khi gán:
274
+ No `config.sort`; sort in `script.js` before assigning. Never ECharts `dataset.transform sort` on object-arrays (unstable in V5).
346
275
 
347
276
  ```js
348
277
  vueData.rows = (d.data || []).slice().sort((a, b) => b.doanhThu - a.doanhThu);
@@ -350,11 +279,9 @@ vueData.rows = (d.data || []).slice().sort((a, b) => b.doanhThu - a.doanhThu);
350
279
  vueData.rows = (d.data || []).slice().sort((a, b) => a.name.localeCompare(b.name));
351
280
  ```
352
281
 
353
- > **Không dùng ECharts `dataset.transform sort`** với object-array — không ổn định với ECharts V5.
354
-
355
282
  ---
356
283
 
357
- ## Pattern trong module.json (controls)
284
+ ## module.json pattern
358
285
 
359
286
  ```json
360
287
  {
@@ -367,25 +294,23 @@ vueData.rows = (d.data || []).slice().sort((a, b) => a.name.localeCompare(b.name
367
294
  }
368
295
  ```
369
296
 
370
- `vueData.chartOpt` được build trong `script.js` (callback sau khi fetch data). `dataView: true` tự trích xuất dữ liệu từ `option` để hiển thị bảng.
371
-
372
- > **Template từng loại chart (bar/line/pie/gauge/radar/heatmap/sankey/tree/graph...) + 5 pattern module.json (inline, vueData ref, dynamic height, conditional option, no-dataview):** xem [echart-templates.md](echart-templates.md).
297
+ Build `vueData.chartOpt` in `script.js` (fetch callback). Per-type templates + 5 patterns (inline, vueData ref, dynamic height, conditional option, no-dataview): [echart-templates.md](echart-templates.md).
373
298
 
374
299
  ---
375
300
 
376
- ## Lỗi thường gặp
377
-
378
- | Lỗi | Nguyên nhân | Giải pháp |
379
- |---|---|---|
380
- | Trang trắng / component không tồn tại | Thiếu `fechart.js` trong import | `file_import_list` → thêm nếu thiếu |
381
- | `echarts is not defined` | `fechart.js` sort nhỏ hơn `echarts.min.js` | Đảo sort: `echarts.min.js` phải có số nhỏ hơn |
382
- | Chart trống dù data có | Data nằm ngoài `option` (ví dụ cố truyền qua prop `data` không tồn tại) | Data chart phải trong `option.series` — không có prop `data` |
383
- | Nút DataView không hiện | `option` không có series array hoặc axis data | DataView chỉ hiện khi tự trích được data từ `option` |
384
- | Bar fill nhạt | Auto lighten enhancement (50%) | Set `itemStyle.color` tường minh trong `option.series[]` |
385
- | Chart không update khi filter thay đổi | `config`/`option` đều có **deep watch** nên sửa lồng sâu vẫn vẽ lại — không update nghĩa là biến **chưa khai trong `data[]`** (Vue không theo dõi được) | Khai key trong `data[]` module.json rồi mới gán; cùng lắm gọi `$refs.myChart.refresh()` |
386
- | `config.type` không có tác dụng | f-echart không có config.type | Đặt `type` trong `option.series[].type` |
387
- | Theme custom không ăn | `config.theme` chỉ nhận `'fui'`/`'shine'`/`'v5'`, tên khác **âm thầm** về `'fui'` | Đổi màu bằng `config.color` |
388
- | Palette lạ, có màu không khai ở đâu | Khai màu ở **cả** `config.color` lẫn `option.color` → merge theo chỉ số, đuôi mảng dài hơn còn sót | Chỉ khai palette ở một chỗ |
389
- | Legend nằm trên đầu dù đặt `legendPosition: 'bottom'` | Legend khai trong `option` (không qua `config.legend`) thì neo **trên**, `legendPosition` vô hiệu | `option.legend.bottom: 0`, hoặc bật legend bằng `config.legend: true` |
390
- | Tooltip chỉ hiện 1 series khi rê chuột | Không có `trigger` mặc định → ECharts dùng `'item'` | Khai `tooltip: { trigger: 'axis' }` trong `option` |
391
- | Legend nhiều hàng đè lên chart | Engine mặc định chừa chỗ cho **1 hàng** legend | `config.legendRows: 2` (hoặc hơn) |
301
+ ## Common errors
302
+
303
+ | Symptom | Cause | Fix |
304
+ | ------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
305
+ | Blank page / no component | `fechart.js` not imported | `fui import list` → add |
306
+ | `echarts is not defined` | `fechart.js` sort < `echarts.min.js` | swap sorts |
307
+ | Empty chart with data | data outside `option` (no `data` prop) | `option.series` |
308
+ | No DataView button | nothing extractable from `option` | add series/axis data |
309
+ | Pale bar fill | lighten (50%) | `itemStyle.color` in `option.series[]` |
310
+ | No update on filter change | `config`/`option` are **deep-watched** ⇒ variable **not declared in `data[]`** | declare in module.json `data[]`; at worst `$refs.myChart.refresh()` |
311
+ | `config.type` ignored | doesn't exist | `option.series[].type` |
312
+ | Custom theme ignored | silently `'fui'` | `config.color` |
313
+ | Stray colors | palette in both `config.color` and `option.color` | one place |
314
+ | Legend on top despite `legendPosition: 'bottom'` | legend in `option`, not `config.legend` | `option.legend.bottom: 0` or `config.legend: true` |
315
+ | Tooltip shows 1 series | no default `trigger` | `tooltip: { trigger: 'axis' }` in `option` |
316
+ | Multi-row legend overlaps | engine reserves **1 row** | `config.legendRows: 2`+ |