@radishcandy/h-charts 0.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ ## [0.1.0] - 2026-09-04
6
+
7
+ ### Added
8
+
9
+ - Framework-agnostic `HChart` core and public plugin contracts.
10
+ - Cursor linkage with a shared bidirectional recursion guard.
11
+ - Optional Vue 3 `HChartView` adapter and two playground demos.
12
+ - Package metadata, unit-test scaffold, CI, npm publishing workflow, and release documentation.
13
+
14
+ ### Changed
15
+
16
+ - Context-menu localization is provided by labels or a callback instead of application i18n.
17
+ - Plugin event subscriptions now follow enable/disable/destroy lifecycle cleanup.
18
+ - Removed the frequency-multiple analysis plugin and its context-menu entries.
19
+ - Standardized the threshold-line feature as `ThresholdLinePlugin` with the `threshold_line` menu ID.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hCharts contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,528 @@
1
+ # hCharts
2
+
3
+ 面向工业监测与设备状态分析场景的 Highcharts 通用封装与插件库。
4
+
5
+ hCharts 将图表渲染、交互分析和宿主框架适配分层设计:核心能力保持框架无关,可用于原生 TypeScript;Vue 3 与 React 适配层只负责生命周期、DOM 容器、事件转发和实例暴露。这样,同一套频谱、波形和趋势分析能力可以复用于 Vue、React、Web Components 或其他宿主环境。
6
+
7
+ 项目仓库:[Gitee yolanda624/h-charts](https://gitee.com/yolanda624/h-charts)。npm 包名为 `@radishcandy/h-charts`。
8
+
9
+ ## 适用场景
10
+
11
+ - 设备运行状态监测
12
+ - 旋转机械振动分析
13
+ - 频谱、包络谱和时域波形展示
14
+ - 传感器趋势曲线与历史数据回放
15
+ - 多测点、多工况数据对比
16
+ - 故障特征频率、阈值和边频诊断
17
+ - 工业监控大屏和分析工作台
18
+
19
+ ## 核心能力
20
+
21
+ | 能力 | 说明 |
22
+ | --- | --- |
23
+ | 统一图表实例 | 封装 Highcharts 初始化、默认配置、主题变量和自适应重绘 |
24
+ | Cursor 光标 | 数据点吸附、键盘移动、拖动和光标标签格式化 |
25
+ | 多图联动 | 多个测点或不同采样间隔图表之间同步 X 值 |
26
+ | 缩放与平移 | 框选缩放、右键平移、恢复完整数据范围 |
27
+ | 频谱分析 | 边频线、阈值线和特征频率标记 |
28
+ | 数据标注 | 标记当前点、显示读数、拖动标注和批量清理 |
29
+ | 右键菜单 | 插件开关、分析参数输入、子菜单和 PNG 导出 |
30
+ | 动态数据 | 增量更新 Series 数据或 Highcharts 配置,不必重建插件 |
31
+ | 宿主适配 | 提供原生 TypeScript API,以及独立 Vue 3、React 组件入口 |
32
+
33
+ ## 设计原则
34
+
35
+ ### 核心插件保持框架无关
36
+
37
+ 插件本身不依赖 Vue、React、Element Plus、项目路由、全局状态或业务 i18n。插件通过 `IPlugin` 接口与 `HChart` 交互,任何宿主都可以创建、启用、禁用和销毁插件。
38
+
39
+ ### Vue 和 React 只做适配层
40
+
41
+ `HChartView` 不会把所有插件重写成 Vue 组件,只负责:
42
+
43
+ - 在组件挂载后创建 `HChart`
44
+ - 提供图表 DOM 容器
45
+ - 转发 `cursor-change`、`chart-click` 等事件
46
+ - 暴露 `setData`、`setCursorX`、`updateOptions` 等实例方法
47
+ - 在组件卸载时销毁图表、插件和事件监听
48
+
49
+ React 的 `HChartView` 使用相同的核心实例和插件,只将以上职责映射为 DOM ref、effect 清理、回调和 `forwardRef` 暴露的方法。
50
+
51
+ ### 面向工业数据的稳定更新
52
+
53
+ 实时数据或历史回放场景应优先调用 `setSeriesData()`,配置变化再调用 `updateOptions()`。这样可以保留现有光标、分析线和菜单插件状态,减少频繁销毁与重建造成的闪烁。
54
+
55
+ ## 安装
56
+
57
+ ### npm 安装
58
+
59
+ ```bash
60
+ npm install highcharts @radishcandy/h-charts
61
+ ```
62
+
63
+ `highcharts` 是 peer dependency。Vue 3 与 React 只会在对应的 `/vue`、`/react` 子路径被引用时作为可选 peer dependency 使用。
64
+
65
+ ### Gitee 源码安装
66
+
67
+ ```bash
68
+ git clone https://gitee.com/yolanda624/h-charts.git
69
+ cd hCharts
70
+ npm install
71
+ ```
72
+
73
+ ## 原生 TypeScript 使用
74
+
75
+ 核心入口不导入 Vue,适合工业监测大屏、原生前端页面或其他框架适配层:
76
+
77
+ ```ts
78
+ import { HChart, CursorPlugin, ZoomPlugin, ContextMenuPlugin } from '@radishcandy/h-charts'
79
+
80
+ const chart = new HChart({
81
+ container: document.querySelector<HTMLElement>('#spectrum')!,
82
+ options: {
83
+ xAxis: { type: 'linear', title: { text: 'Frequency (Hz)' } },
84
+ yAxis: { title: { text: 'Amplitude' } },
85
+ series: [{ type: 'line', name: 'Sensor A', data: [[0, 8], [1, 15], [2, 12]] }]
86
+ },
87
+ plugins: [new CursorPlugin(), new ZoomPlugin(), new ContextMenuPlugin({ items: ['sideband', 'threshold_line', 'export'] })]
88
+ })
89
+
90
+ chart.setSeriesData([[0, 10], [1, 18], [2, 13]])
91
+ chart.updateOptions({ title: { text: 'Sensor A - running condition 01' } })
92
+ chart.destroy()
93
+ ```
94
+
95
+ 请在容器已经挂载到浏览器 DOM 后创建 `HChart`。服务端渲染环境可以安全导入模块,但必须在客户端生命周期中构造图表。
96
+
97
+ ## 其他宿主环境
98
+
99
+ Web Components、Angular、Svelte 或业务自研运行时无需复制图表逻辑:在宿主的“挂载”钩子中使用容器元素创建 `HChart`,把宿主回调封装为一个 `IPlugin`,并在“卸载”钩子中调用 `destroy()`。这样所有分析插件、光标联动和更新 API 都与原生 TypeScript、Vue、React 保持一致。
100
+
101
+ ## Cursor 多图联动
102
+
103
+ 工业监测中常见“原始波形 + 包络谱”“测点 A + 测点 B”“不同采样间隔数据”的同步查看。`CursorLinkPlugin` 只转发源图表的 X 值,目标图表会根据自身数据找到最近点,因此不要求两组数据长度或采样间隔相同。
104
+
105
+ 双向联动时,两个插件共享一个 `CursorSyncState`,避免事件在两个图表之间递归触发:
106
+
107
+ ```ts
108
+ import { HChart, CursorPlugin, CursorLinkPlugin, createCursorSyncState } from '@radishcandy/h-charts'
109
+
110
+ const syncState = createCursorSyncState()
111
+ let waveform: HChart
112
+ let spectrum: HChart
113
+
114
+ waveform = new HChart({
115
+ container: document.querySelector<HTMLElement>('#waveform')!,
116
+ options: { series: [{ type: 'line', name: 'Waveform', data: [[0, 3], [10, 8], [20, 5]] }] },
117
+ plugins: [new CursorPlugin(), new CursorLinkPlugin({ name: 'waveform-to-spectrum', syncState, target: () => spectrum })]
118
+ })
119
+
120
+ spectrum = new HChart({
121
+ container: document.querySelector<HTMLElement>('#spectrum')!,
122
+ options: { series: [{ type: 'line', name: 'Spectrum', data: [[0, 9], [8, 4], [16, 12], [24, 7]] }] },
123
+ plugins: [new CursorPlugin(), new CursorLinkPlugin({ name: 'spectrum-to-waveform', syncState, target: () => waveform })]
124
+ })
125
+ ```
126
+
127
+ 两张图都需要 `CursorPlugin`;`CursorLinkPlugin` 是独立的联动桥接插件。一个图表添加多个联动方向时,请为每个实例设置不同的 `name`。
128
+
129
+ ## Vue 3 使用
130
+
131
+ 从 Vue 子路径导入适配组件。组件只负责挂载、销毁、事件转发和实例暴露:
132
+
133
+ ```vue
134
+ <script setup lang="ts">
135
+ import { ref } from 'vue'
136
+ import type * as Highcharts from 'highcharts'
137
+ import { CursorPlugin } from '@radishcandy/h-charts'
138
+ import { HChartView } from '@radishcandy/h-charts/vue'
139
+
140
+ const chartRef = ref<InstanceType<typeof HChartView> | null>(null)
141
+ const options: Highcharts.Options = {
142
+ xAxis: { title: { text: 'Frequency (Hz)' } },
143
+ yAxis: { title: { text: 'Amplitude' } },
144
+ series: [{ type: 'line', data: [[0, 2], [1, 8], [2, 4]] }]
145
+ }
146
+ const plugins = [new CursorPlugin()]
147
+
148
+ function refresh() {
149
+ chartRef.value?.setData([[0, 5], [1, 3], [2, 9]])
150
+ }
151
+ </script>
152
+
153
+ <template>
154
+ <section style="height: 320px">
155
+ <HChartView ref="chartRef" :options="options" :plugins="plugins" @cursor-change="(point) => console.log('cursor', point)" />
156
+ </section>
157
+ <button type="button" @click="refresh">刷新测点数据</button>
158
+ </template>
159
+ ```
160
+
161
+ 需要让 Vue 深度监听配置时设置 `:update-on-options-change="true"`。对于高频采集数据,优先使用暴露的 `setData()`,避免因响应式对象变化而频繁更新完整配置。
162
+
163
+ ## React 使用
164
+
165
+ React 适配器位于独立的 `/react` 子路径;它只负责 DOM ref、effect 生命周期和事件回调,图表及插件始终由框架无关的 `HChart` 承载。
166
+
167
+ ```tsx
168
+ import { useRef } from 'react'
169
+ import type * as Highcharts from 'highcharts'
170
+ import { CursorPlugin } from '@radishcandy/h-charts'
171
+ import { HChartView, type HChartReactHandle } from '@radishcandy/h-charts/react'
172
+
173
+ const options: Highcharts.Options = {
174
+ series: [{ type: 'line', data: [[0, 2], [1, 8], [2, 4]] }]
175
+ }
176
+ const plugins = [new CursorPlugin()]
177
+
178
+ export function Spectrum() {
179
+ const chartRef = useRef<HChartReactHandle | null>(null)
180
+
181
+ return (
182
+ <>
183
+ <section style={{ height: 320 }}>
184
+ <HChartView
185
+ ref={chartRef}
186
+ options={options}
187
+ plugins={plugins}
188
+ onCursorChange={(point) => console.log('cursor', point)}
189
+ />
190
+ </section>
191
+ <button onClick={() => chartRef.current?.setData([[0, 5], [1, 3], [2, 9]])}>
192
+ 刷新测点数据
193
+ </button>
194
+ </>
195
+ )
196
+ }
197
+ ```
198
+
199
+ 设置 `updateOnOptionsChange` 后,React 中新的 options 对象会调用 Highcharts `update()`;高频数据请通过 ref 调用 `setData()`、`updateOptions()` 或 `requestReflow()`,避免重建图表和插件。可直接参考 [React 示例](./examples/react/HChartExample.tsx),Vue 3 的可复制示例位于 [Vue 3 示例](./examples/vue3/HChartExample.vue)。
200
+
201
+ ## 插件与生命周期
202
+
203
+ 所有插件遵循统一生命周期:
204
+
205
+ 1. `init(chart)`:保存图表实例,并根据 `enabled` 配置决定是否启用。
206
+ 2. `enable()`:创建分析图形并注册事件。
207
+ 3. `disable()`:移除分析图形和事件监听,但保留插件实例。
208
+ 4. `destroy()`:终止插件,释放全部资源。
209
+
210
+ ```ts
211
+ chart.togglePlugin('ThresholdLinePlugin', true)
212
+ chart.togglePlugin('ThresholdLinePlugin', false)
213
+ chart.removePlugin('ThresholdLinePlugin')
214
+ ```
215
+
216
+ 可用插件包括:
217
+
218
+ - `CursorPlugin`:光标、吸附、键盘移动和拖动
219
+ - `CursorLinkPlugin`:多图 X 值联动
220
+ - `ZoomPlugin`:缩放、平移和恢复范围
221
+ - `SidebandPlugin`:边频线及边频数量调整
222
+ - `ThresholdLinePlugin`:可拖动阈值线,并标记超过阈值的数据点
223
+ - `AnnotationPlugin`:测点标注、标签和连接线
224
+ - `ContextMenuPlugin`:右键菜单、插件开关、参数输入和导出
225
+
226
+ ### 插件参数与方法
227
+
228
+ 所有插件都实现 `IPlugin`:`init(chart)`、`enable()`、`disable()`、`destroy()`、`update(options)` 和可选的 `isEnabled()`。`HChart` 创建时会调用 `init()`;除非传入 `enabled: false`,插件默认启用。运行时可使用 `chart.togglePlugin(name, enabled)`,或先用 `chart.getPlugin(name)` 取得插件实例后调用下列专用方法。
229
+
230
+ | 插件 | 配置接口 | 主要专用方法 |
231
+ | --- | --- | --- |
232
+ | `CursorPlugin` | `CursorPluginOptions` | `updateCursor()` |
233
+ | `CursorLinkPlugin` | `CursorLinkPluginOptions` | `onCursorChange()`(自动调用) |
234
+ | `ZoomPlugin` | `ZoomPluginOptions` | 无(使用图表鼠标手势) |
235
+ | `SidebandPlugin` | `SidebandPluginOptions` | `setLineCount()`、`getLineCount()`、`setDelta()`、`getDelta()`、`setDirectShow()` |
236
+ | `ThresholdLinePlugin` | `ThresholdLinePluginOptions` | `setValue()`、`getValue()` |
237
+ | `AnnotationPlugin` | `AnnotationPluginOptions` | `markCurrentPoint()`、`markPoint()`、`clearMark()`、`getMarkedPoint()`、`getMarkedPoints()` |
238
+ | `ContextMenuPlugin` | `ContextMenuPluginOptions`、`ContextMenuItem` | `BUILTIN_ITEMS`、`DEFAULT_ITEMS` |
239
+
240
+ 以下各节列出每个配置字段的默认值、行为和完整公开方法。
241
+
242
+ #### CursorPlugin
243
+
244
+ ```ts
245
+ interface CursorPluginOptions {
246
+ enabled?: boolean // 默认 true
247
+ multiLine?: boolean // 默认 true;是否为多条可见 Series 绘制读数
248
+ draggable?: boolean // 默认 true;是否允许 Shift+拖动光标线
249
+ labelFormatter?: (x: number, y: number) => string
250
+ onCursorMove?: (point: { x: number; y: number; index: number }) => void
251
+ }
252
+ ```
253
+
254
+ 用途:点击图表背景或数据点时,将 X 吸附到第一条 Series 的最近数据点;支持左右方向键、拖动光标线、缩放后自动恢复,以及在多条 Series 上显示同一 X 的读数。`labelFormatter` 自定义光标标签文本,`onCursorMove` 在点击、拖动或键盘移动完成后回调。
255
+
256
+ 公开方法:
257
+
258
+ - `updateCursor(xValue: number)`:按 X 值查找最近点并绘制光标,同时触发 `chart.setCursor()` 和 `onCursorMove`。
259
+ - `enable()` / `disable()`:绑定或移除键盘、鼠标和缩放事件。
260
+ - `isEnabled()`:返回当前启用状态(继承自 `BasePlugin`)。
261
+
262
+ ```ts
263
+ const cursor = new CursorPlugin({
264
+ draggable: true,
265
+ multiLine: false,
266
+ labelFormatter: (x, y) => `${x.toFixed(2)} Hz / ${y.toFixed(3)}`,
267
+ onCursorMove: (point) => console.log('cursor', point)
268
+ })
269
+ const chart = new HChart({ container: '#chart', options, plugins: [cursor] })
270
+ chart.setCursorX(42) // 等价于 cursor.updateCursor(42)
271
+ ```
272
+
273
+ 此外导出了两个点击抑制辅助函数:`suppressCursorClick(container, ms?)` 用于在拖动后短暂屏蔽点击,`shouldSuppressCursorClick(container)` 用于检查屏蔽状态。
274
+
275
+ #### CursorLinkPlugin
276
+
277
+ ```ts
278
+ interface CursorLinkPluginOptions {
279
+ enabled?: boolean // 默认 true
280
+ target: () => IHChart | null | undefined
281
+ name?: string // 默认 CursorLinkPlugin
282
+ syncState?: CursorSyncState // 双向联动时两端共享
283
+ }
284
+ interface CursorSyncState { active: boolean }
285
+ ```
286
+
287
+ 用途:只转发源图表的 `point.x`,目标图表通过自己的数据重新吸附,因此允许两张图采样间隔不同。`target` 应返回目标 `HChart`,使用函数可以支持两张图初始化顺序不固定;目标不存在时会安全跳过。
288
+
289
+ 公开方法:`onCursorChange(point)` 在源图表光标变化时自动调用;`enable()`、`disable()`、`isEnabled()` 和 `destroy()` 继承自 `BasePlugin`。双向联动必须共享 `createCursorSyncState()` 返回的对象,以避免递归触发。
290
+
291
+ #### ZoomPlugin
292
+
293
+ ```ts
294
+ interface ZoomPluginOptions {
295
+ enabled?: boolean // 默认 true
296
+ }
297
+ ```
298
+
299
+ 用途:左键框选使用 Highcharts 原生 X 轴缩放;右键拖动执行 X 轴平移;从右向左拖动可恢复完整数据范围。插件会自动处理拖动后的点击抑制,并将视图限制在 `dataMin` 到 `dataMax` 内。
300
+
301
+ 公开方法:`enable()`、`disable()`、`destroy()` 和 `isEnabled()`。缩放方向、滚轮缩放等基础行为由 `HChart` 默认 `chart.zoomType = 'x'` 与 `zooming.mouseWheel` 配置控制,可通过 `chart.updateOptions()` 覆盖。
302
+
303
+ #### SidebandPlugin
304
+
305
+ ```ts
306
+ interface SidebandPluginOptions {
307
+ enabled?: boolean // 默认 true
308
+ initialDelta?: number // 默认 0;首次启用时按 X 轴范围 / 20 计算
309
+ lineCount?: number // 默认 5;在中心频率两侧各绘制 N 条
310
+ directShow?: boolean // 默认 false;无光标时也允许保持启用
311
+ color?: string // 默认 green
312
+ dashStyle?: Highcharts.DashStyleValue // 默认 Dash
313
+ labelColor?: string // 默认跟随 color
314
+ xAxisUnit?: 'Hz' | 'ms' | string // 默认 Hz;ms 会换算为 1000 / delta
315
+ onDeltaChange?: (delta: number) => void
316
+ }
317
+ ```
318
+
319
+ 插件依赖 `CursorPlugin` 提供中心点。启用后以当前光标 X 为中心绘制 `±delta、±2*delta ...` 边频线,最近的两条线显示标签;拖动边频线会更新 `delta`。
320
+
321
+ 公开方法:
322
+
323
+ - `setLineCount(count)` / `getLineCount()`:设置或读取每侧边频线数量。
324
+ - `setDelta(delta)` / `getDelta()`:设置或读取边频间隔;设置时立即重绘并触发 `onDeltaChange`。
325
+ - `setDirectShow(show)`:切换直接显示模式。
326
+ - `isEnabled()`、`enable()`、`disable()`、`destroy()`:插件状态与生命周期。
327
+
328
+ ```ts
329
+ const sideband = new SidebandPlugin({ lineCount: 3, initialDelta: 5, color: '#16a34a' })
330
+ chart.addPlugin(sideband)
331
+ sideband.setDelta(2.5)
332
+ sideband.setLineCount(4)
333
+ ```
334
+
335
+ #### ThresholdLinePlugin
336
+
337
+ ```ts
338
+ interface ThresholdLinePluginOptions {
339
+ enabled?: boolean // 默认 true
340
+ initialValue?: number // 默认 0;启用时按 Y 轴范围中点初始化
341
+ }
342
+ ```
343
+
344
+ 用途:在 Y 轴绘制可拖动的水平阈值线,并为超过阈值的第一条 Series 数据点添加序号标记。拖动透明命中区域或可见线即可实时改变阈值。
345
+
346
+ 公开方法:`setValue(value)` 设置阈值并立即重绘;`getValue()` 读取当前阈值;以及继承的 `enable()`、`disable()`、`isEnabled()`、`destroy()`。禁用时会移除阈值线、命中区域和数据点标记。
347
+
348
+ ```ts
349
+ const threshold = new ThresholdLinePlugin({ initialValue: 1.2 })
350
+ chart.addPlugin(threshold)
351
+ threshold.setValue(0.8)
352
+ console.log(threshold.getValue())
353
+ ```
354
+
355
+ #### AnnotationPlugin
356
+
357
+ ```ts
358
+ interface AnnotationPluginOptions {
359
+ enabled?: boolean // 默认 true
360
+ labelFormatter?: (x: number, y: number) => string
361
+ markerRadius?: number // 默认 2
362
+ markerColor?: string // 默认 #ff4d4f
363
+ }
364
+ ```
365
+
366
+ 用途:将当前光标点或任意 `Point` 标记为可拖动注释,绘制标记点、读数标签和连接线;图表重绘、缩放后会自动更新注释位置。
367
+
368
+ 公开方法:
369
+
370
+ - `markCurrentPoint()`:标记 `chart.activePoint`(没有时使用插件收到的最近光标点)。
371
+ - `markPoint(point)`:标记指定 `{ x, y, index, seriesIndex? }` 点。
372
+ - `clearMark()`:清除全部标记。
373
+ - `getMarkedPoint()`:返回最后一个标记,未标记时返回 `null`。
374
+ - `getMarkedPoints()`:返回所有标记的副本。
375
+ - `update(options)`:合并并应用 `markerRadius`、`markerColor`、`labelFormatter`。
376
+
377
+ ```ts
378
+ const annotations = new AnnotationPlugin({
379
+ markerColor: '#f97316',
380
+ labelFormatter: (x, y) => `${x.toFixed(1)}, ${y.toFixed(2)}`
381
+ })
382
+ chart.addPlugin(annotations)
383
+ annotations.markCurrentPoint()
384
+ annotations.markPoint({ x: 10, y: 3.2, index: 10 })
385
+ ```
386
+
387
+ #### ContextMenuPlugin
388
+
389
+ ```ts
390
+ interface ContextMenuPluginOptions {
391
+ enabled?: boolean // 默认 true
392
+ items?: ContextMenuEntry[] // 默认 sideband、threshold_line、band_count、export
393
+ zIndex?: number // 默认 10000
394
+ labels?: Partial<Record<ContextMenuBuiltinId, string>>
395
+ translate?: (id: ContextMenuBuiltinId, fallback: string) => string
396
+ }
397
+ ```
398
+
399
+ 右键打开固定定位菜单;菜单实例之间互斥,点击外部自动关闭。内置项为 `sideband`(边频开关)、`threshold_line`(阈值线开关)、`band_count`(边频数量输入)和 `export`(PNG 导出)。菜单中的插件项不存在时会自动创建对应的禁用插件实例。
400
+
401
+ 自定义菜单项类型如下:
402
+
403
+ ```ts
404
+ interface ContextMenuItem {
405
+ label?: string
406
+ id?: string
407
+ type?: 'builtin' | 'custom'
408
+ pluginName?: string
409
+ action?: (chart: IHChart, item?: ContextMenuItem) => void
410
+ items?: ContextMenuItem[] // 子菜单
411
+ inputType?: 'text' | 'number'
412
+ value?: string | number
413
+ min?: number
414
+ max?: number
415
+ onInput?: (value: string, chart: IHChart) => void
416
+ fileName?: string | ((chart: IHChart) => string)
417
+ exportBackgroundColor?: string
418
+ exportOptions?: Record<string, unknown>
419
+ exportChartOptions?: Record<string, unknown>
420
+ }
421
+ ```
422
+
423
+ 公开方法:`ContextMenuPlugin.BUILTIN_ITEMS` 和 `.DEFAULT_ITEMS` 返回内置菜单定义;`enable()`、`disable()`、`destroy()`、`isEnabled()` 控制菜单生命周期。推荐使用 `createSpectrumContextMenuItems()`、`createEnvelopeContextMenuItems()` 或 `createWaveContextMenuItems()` 作为宿主项目的起点,再传入自定义项或 `labels`/`translate` 完成本地化。
424
+
425
+ #### BasePlugin 与 IPlugin
426
+
427
+ 若要扩展插件,可继承 `BasePlugin` 并实现 `name`。`BasePlugin` 会保存 `chart`、维护 `enabled` 状态,并提供 `isEnabled()`;重写 `enable()` 时注册事件,重写 `disable()`/`destroy()` 时必须成对清理。自定义插件至少需要实现:
428
+
429
+ ```ts
430
+ interface IPlugin {
431
+ name: string
432
+ init(chart: IHChart): void
433
+ destroy(): void
434
+ enable(): void
435
+ disable(): void
436
+ update(options: unknown): void
437
+ isEnabled?(): boolean
438
+ onChartClick?(event: Highcharts.PointerEventObject): void
439
+ onCursorChange?(point: Point): void
440
+ }
441
+ ```
442
+
443
+ ## 右键菜单与本地化
444
+
445
+ `ContextMenuPlugin` 内置以下 ID:
446
+
447
+ ```text
448
+ sideband
449
+ threshold_line
450
+ band_count
451
+ export
452
+ ```
453
+
454
+ 业务系统可以通过 `labels` 或 `translate` 注入中文、英文及其他语言,不需要把业务 i18n 依赖带入核心插件:
455
+
456
+ ```ts
457
+ new ContextMenuPlugin({
458
+ items: ['sideband', 'threshold_line', 'export'],
459
+ labels: { sideband: '显示边频', threshold_line: '阈值线', export: '导出图表' }
460
+ })
461
+ ```
462
+
463
+ 菜单主题支持以下 CSS 变量:
464
+
465
+ ```css
466
+ :root {
467
+ --hchart-primary-color: #409eff;
468
+ --hchart-text-color: #606266;
469
+ --hchart-menu-background: #ffffff;
470
+ --hchart-menu-border: #dcdfe6;
471
+ --hchart-menu-shadow: rgba(0, 0, 0, 0.15);
472
+ --hchart-menu-color: #303133;
473
+ --hchart-menu-hover: #f0f0f0;
474
+ }
475
+ ```
476
+
477
+ ## 项目结构
478
+
479
+ ```text
480
+ src/
481
+ ├─ core/ # HChart、BasePlugin、工具类、Cursor 联动
482
+ ├─ plugins/ # Cursor、缩放、边频、阈值线、标注、右键菜单
483
+ ├─ types/ # 公共 TypeScript 类型与宿主契约
484
+ ├─ vue/ # Vue 3 适配组件,不承载分析逻辑
485
+ ├─ react/ # React 18/19 适配组件,不承载分析逻辑
486
+ ├─ index.ts # 框架无关入口
487
+ ├─ vue.ts # Vue 3 入口
488
+ └─ react.ts # React 入口
489
+
490
+ playground/ # Vue 3 交互示例
491
+ examples/ # Vue 3 与 React 使用示例
492
+ tests/ # 核心逻辑单元测试
493
+ docs/ # 架构说明与发布清单
494
+ ```
495
+
496
+ ## 本地开发
497
+
498
+ ```bash
499
+ npm install
500
+ npm run typecheck
501
+ npm run test
502
+ npm run build
503
+ npm run pack:check
504
+ ```
505
+
506
+ `playground/` 提供 Vue 3 交互示例,`playground-react/` 提供 React 交互示例(可通过 `npm run dev:react` 启动)。两者均展示对同一核心 API 的使用;Vue playground 还包含双向 Cursor 联动、频谱分析插件组合、动态 Series 数据更新、右键菜单和 PNG 导出示例。
507
+
508
+ ## 开源与发布计划
509
+
510
+ - 发布 npm 公共包
511
+ - 提供 Vue 3 在线示例和 API 文档
512
+ - 增加 Web Components 等其他宿主适配层
513
+ - 完善频谱、时域、包络分析测试
514
+ - 增加工业监测主题、暗色大屏主题和国际化示例
515
+ - 建立版本变更记录与自动化发布流程
516
+
517
+ 发布前需要完成的 npm 账号、仓库、Trusted Publishing 和 Gitee 配置,请参阅 [发布清单](./docs/release-checklist.md)。
518
+
519
+ ## 相关文档
520
+
521
+ - [架构边界说明](./docs/architecture.md)
522
+ - [贡献指南](./CONTRIBUTING.md)
523
+ - [安全策略](./SECURITY.md)
524
+ - [变更日志](./CHANGELOG.md)
525
+
526
+ ## License
527
+
528
+ 本项目基于 MIT License 开源。