@363045841yyt/klinechart 0.7.3-alpha.1 → 0.7.3-alpha.3

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.
@@ -146,27 +146,27 @@ import {
146
146
 
147
147
  const props = withDefaults(
148
148
  defineProps<{
149
- /** ??????????????? */
149
+ /** 语义化配置(必需,唯一控制源) */
150
150
  semanticConfig: SemanticChartConfig
151
151
 
152
- /** ??????????????????????????? */
152
+ /** 数据获取函数(必需)。框架不绑定数据源,由使用者注入。 */
153
153
  dataFetcher: DataFetcher
154
154
 
155
155
  yPaddingPx?: number
156
156
  minKWidth?: number
157
157
  maxKWidth?: number
158
- /** ??????? */
158
+ /** 右侧价格轴宽度 */
159
159
  rightAxisWidth?: number
160
- /** ??????? */
160
+ /** 底部时间轴高度 */
161
161
  bottomAxisHeight?: number
162
- /** ??????????????????? 60px? */
162
+ /** 价格标签额外宽度(用于显示涨跌幅,默认 60px */
163
163
  priceLabelWidth?: number
164
164
 
165
- /** ????????? 10? */
165
+ /** 缩放级别数量(默认 10 */
166
166
  zoomLevels?: number
167
- /** ???????1 ~ zoomLevels?????? */
167
+ /** 初始缩放级别(1 ~ zoomLevels,默认居中) */
168
168
  initialZoomLevel?: number
169
- /** ???? */
169
+ /** 是否全屏 */
170
170
  isFullscreen?: boolean
171
171
  }>(),
172
172
  {
@@ -195,13 +195,13 @@ const chartMainRef = ref<HTMLDivElement | null>(null)
195
195
  const tooltipLayerRef = ref<HTMLDivElement | null>(null)
196
196
  const toolbarRef = ref<InstanceType<typeof LeftToolbar> | null>(null)
197
197
 
198
- /* ========== ??????????? ========== */
198
+ /* ========== 十字线(鼠标悬停位置) ========== */
199
199
  const chartRef = shallowRef<Chart | null>(null)
200
200
 
201
- /* ========== ?????? ========== */
201
+ /* ========== 语义化控制器 ========== */
202
202
  const semanticController = shallowRef<SemanticChartController | null>(null)
203
203
 
204
- /* ========== ChartStore????????? ========== */
204
+ /* ========== ChartStore(响应式状态中心) ========== */
205
205
  const store = createChartStore({
206
206
  initialZoomLevel: props.initialZoomLevel ?? 1,
207
207
  minKWidth: props.minKWidth,
@@ -211,10 +211,10 @@ const store = createChartStore({
211
211
  priceLabelWidth: props.priceLabelWidth,
212
212
  })
213
213
 
214
- /* ========== ???? ========== */
214
+ /* ========== 主题状态 ========== */
215
215
  const chartTheme = ref<'light' | 'dark'>('light')
216
216
 
217
- // ??? kWidth / kGap
217
+ // 初始化 kWidth / kGap
218
218
  store.actions.setZoomState(
219
219
  store.state.zoomLevel,
220
220
  zoomLevelToKWidth(store.state.zoomLevel, {
@@ -234,7 +234,7 @@ store.actions.setZoomState(
234
234
  ),
235
235
  )
236
236
 
237
- // ????????????
237
+ // 为逐步迁移保留的局部别名
238
238
  const dataLength = computed(() => store.state.dataLength)
239
239
  const viewportDpr = computed(() => store.state.viewportDpr)
240
240
  const zoomLevel = computed(() => store.state.zoomLevel)
@@ -251,7 +251,7 @@ function scheduleRender() {
251
251
  function handleSettingsChange(settings: Record<string, boolean | string>) {
252
252
  chartRef.value?.updateSettings(settings)
253
253
 
254
- // ??K?????
254
+ // 万条K线性能测试
255
255
  if (settings.performanceTest10kKlines) {
256
256
  const testData = generate10kKLineData()
257
257
  console.time('updateData-10k')
@@ -260,28 +260,28 @@ function handleSettingsChange(settings: Record<string, boolean | string>) {
260
260
  store.actions.setDataLength(testData.length)
261
261
  store.actions.bumpDataVersion()
262
262
  } else {
263
- // ???????????????
264
- // ??????????????
263
+ // 如果关闭性能测试,恢复原始数据
264
+ // 通过重新应用语义化配置来恢复
265
265
  if (semanticController.value && chartRef.value?.getData()?.length === 10000) {
266
266
  semanticController.value.applyConfig(props.semanticConfig)
267
267
  }
268
268
  }
269
269
  }
270
270
 
271
- // ??1??K?????
271
+ // 生成1万条K线测试数据
272
272
  function generate10kKLineData() {
273
273
  const data: KLineData[] = []
274
274
  const startTime = new Date('2020-01-01').getTime()
275
275
  const dayMs = 24 * 60 * 60 * 1000
276
276
 
277
- let lastClose = 3000 // ????
277
+ let lastClose = 3000 // 起始价格
278
278
 
279
279
  for (let i = 0; i < 10000; i++) {
280
280
  const timestamp = startTime + i * dayMs
281
281
 
282
- // ??????
283
- const volatility = 0.02 // 2%????
284
- const trend = 0.0001 // ??????
282
+ // 生成随机波动
283
+ const volatility = 0.02 // 2%日波动率
284
+ const trend = 0.0001 // 轻微上涨趋势
285
285
  const change = (Math.random() - 0.5) * 2 * volatility + trend
286
286
 
287
287
  const open = lastClose
@@ -330,11 +330,11 @@ function setMarkerTooltipEl(el: HTMLDivElement | null) {
330
330
  })
331
331
  }
332
332
 
333
- // ===== Marker tooltip ?? =====
333
+ // ===== Marker tooltip 状态 =====
334
334
  const mousePos = ref({ x: 0, y: 0 })
335
335
  const useAnchorPositioning = ref(false)
336
336
 
337
- // ?? rect ????? pointermove ??? getBoundingClientRect ??????
337
+ // 容器 rect 缓存,避免 pointermove 中反复 getBoundingClientRect 强制同步布局
338
338
  let _cachedContainerRect: DOMRect | null = null
339
339
  function invalidateContainerRectCache(): void {
340
340
  _cachedContainerRect = null
@@ -346,7 +346,7 @@ function getContainerRect(container: HTMLDivElement): DOMRect {
346
346
  return _cachedContainerRect
347
347
  }
348
348
 
349
- // ===== ??????????InteractionController snapshot? =====
349
+ // ===== 交互状态(单一来源:InteractionController snapshot =====
350
350
  const interactionState = shallowRef<InteractionSnapshot>({
351
351
  crosshairPos: null,
352
352
  crosshairIndex: null,
@@ -392,7 +392,7 @@ const isHoveringRightAxis = computed(() => interactionState.value.isHoveringRigh
392
392
  const hoveredIdx = computed(() => interactionState.value.hoveredIndex)
393
393
  const crosshairIdx = computed(() => interactionState.value.crosshairIndex)
394
394
 
395
- // ?????????? style ?? CSS ??????????????
395
+ // 统一光标样式:用内联 style 替代 CSS 类后代选择器,切断级联失效链
396
396
  const containerCursor = computed(() => {
397
397
  if (isDragging.value) return 'grabbing'
398
398
  if (isResizingPane.value || isHoveringPaneSeparator.value) return 'ns-resize'
@@ -403,7 +403,7 @@ const containerCursor = computed(() => {
403
403
  const hovered = computed(() => {
404
404
  const idx = interactionState.value.hoveredIndex
405
405
  if (typeof idx !== 'number') return null
406
- void dataVersion.value // ???????
406
+ void dataVersion.value // 建立响应式依赖
407
407
  const data = chartRef.value?.getData()
408
408
  if (data && idx >= 0 && idx < data.length) {
409
409
  return data[idx]
@@ -441,13 +441,13 @@ const markerTooltipAnchorPlacement = computed<'right-bottom' | 'left-bottom'>(()
441
441
  return wouldOverflowRight ? 'left-bottom' : 'right-bottom'
442
442
  })
443
443
 
444
- // ????????
444
+ // 获取当前图表数据
445
445
  const chartData = computed(() => {
446
- void dataVersion.value // ???????????????????
446
+ void dataVersion.value // 建立响应式依赖,确保数据变化时重新求值
447
447
  return chartRef.value?.getData() ?? []
448
448
  })
449
449
 
450
- // ????????????????
450
+ // 通知数据变化(在数据更新后调用)
451
451
  function handleSelectTool(toolId: string) {
452
452
  drawingController.value?.setTool(toolId as DrawingToolId)
453
453
  }
@@ -514,7 +514,7 @@ function onPointerUp(e: PointerEvent) {
514
514
  }
515
515
 
516
516
  function onPointerLeave(e: PointerEvent) {
517
- // pointerleave ???????????????
517
+ // pointerleave 不需要绘图控制器路由,直接调用
518
518
  chartRef.value?.handlePointerEvent(e)
519
519
  }
520
520
 
@@ -538,10 +538,10 @@ function onScroll() {
538
538
  chartRef.value?.handleScrollEvent()
539
539
  }
540
540
 
541
- // ?????????????? subPanes ???
541
+ // 主图指标显式状态(副图指标从 subPanes 派生)
542
542
  const mainActiveIndicators = ref<string[]>([])
543
543
 
544
- // ??????? subPanes ????
544
+ // 副图指标列表从 subPanes 自动派生
545
545
  const subActiveIndicators = computed(() => {
546
546
  const ids: string[] = []
547
547
  const seen = new Set<string>()
@@ -554,26 +554,26 @@ const subActiveIndicators = computed(() => {
554
554
  return ids
555
555
  })
556
556
 
557
- // ????????? + ??????????
557
+ // 最终合并列表(主图 + 副图),保持显示顺序
558
558
  const activeIndicators = computed(() => [
559
559
  ...mainActiveIndicators.value,
560
560
  ...subActiveIndicators.value,
561
561
  ])
562
562
 
563
- // ???????MA ? periods ?????????????
563
+ // 指标参数配置(MA periods 是数组,需要更宽松的类型)
564
564
  const indicatorParams = ref<Record<string, Record<string, unknown>>>({})
565
565
 
566
- // ??????
566
+ // 副图槽位状态
567
567
  interface SubPaneSlot {
568
568
  id: string // pane ID: 'RSI_0', 'MACD_0', ...
569
569
  indicatorId: SubIndicatorType
570
570
  params: Record<string, unknown>
571
571
  }
572
572
 
573
- // ?????????????
573
+ // 副图槽位数组(支持多副图)
574
574
  const subPanes = ref<SubPaneSlot[]>([])
575
575
 
576
- // ??????
576
+ // 最大副图数量
577
577
  const maxSubPanes = 4
578
578
 
579
579
  function buildPaneLayoutIntent(): PaneSpec[] {
@@ -591,14 +591,14 @@ function buildPaneLayoutIntent(): PaneSpec[] {
591
591
  ]
592
592
  }
593
593
 
594
- // ????????
594
+ // 获取指标默认参数
595
595
  function getDefaultParams(
596
596
  indicatorId: SubIndicatorType,
597
597
  ): Record<string, number | boolean | string> {
598
598
  return { ...SUB_PANE_INDICATOR_CONFIGS[indicatorId].defaultParams }
599
599
  }
600
600
 
601
- // ???????????? 'RSI_0', 'MACD_0' ??? paneId
601
+ // 副图实例计数器:用于生成 'RSI_0', 'MACD_0' 这样的 paneId
602
602
  const subPaneCounters = new Map<SubIndicatorType, number>()
603
603
 
604
604
  function generatePaneId(indicatorId: SubIndicatorType): string {
@@ -607,7 +607,7 @@ function generatePaneId(indicatorId: SubIndicatorType): string {
607
607
  return `${indicatorId}_${count}`
608
608
  }
609
609
 
610
- // paneTitle ????????paneId -> rendererName?
610
+ // paneTitle 渲染器名称映射(paneId -> rendererName
611
611
  const paneTitleRendererNames = new Map<string, string>()
612
612
 
613
613
  function mountSubPaneTitle(paneId: string, indicatorId: SubIndicatorType): void {
@@ -628,7 +628,7 @@ function unmountSubPaneTitle(paneId: string): void {
628
628
  }
629
629
  }
630
630
 
631
- // ??????? Chart API?
631
+ // 添加副图(使用 Chart API
632
632
  function addSubPane(
633
633
  indicatorId: SubIndicatorType = 'VOLUME',
634
634
  params?: Record<string, number | boolean | string>,
@@ -639,14 +639,14 @@ function addSubPane(
639
639
 
640
640
  const mergedParams = params ?? getDefaultParams(indicatorId)
641
641
 
642
- // ???? Facade API ??????
642
+ // 使用高层 Facade API 创建副图指标
643
643
  const paneId = chartRef.value?.addIndicator(indicatorId, 'sub', mergedParams)
644
644
  if (!paneId) return false
645
645
 
646
- // ?? paneTitle ????UI ????
646
+ // 创建 paneTitle 渲染器(UI 层职责)
647
647
  mountSubPaneTitle(paneId, indicatorId)
648
648
 
649
- // ??????
649
+ // 更新本地状态
650
650
  subPanes.value.push({
651
651
  id: paneId,
652
652
  indicatorId,
@@ -657,7 +657,7 @@ function addSubPane(
657
657
  return true
658
658
  }
659
659
 
660
- // ????????? Facade API?
660
+ // 移除副图(使用高层 Facade API
661
661
  function removeSubPane(paneId: string): void {
662
662
  const index = subPanes.value.findIndex((p) => p.id === paneId)
663
663
  if (index === -1) return
@@ -665,50 +665,50 @@ function removeSubPane(paneId: string): void {
665
665
  const pane = subPanes.value[index]
666
666
  if (!pane) return
667
667
 
668
- // ?? paneTitle ???
668
+ // 移除 paneTitle 渲染器
669
669
  unmountSubPaneTitle(paneId)
670
670
 
671
- // ???? Facade API ????
671
+ // 使用高层 Facade API 移除指标
672
672
  chartRef.value?.removeIndicator(paneId)
673
673
 
674
- // ??????
674
+ // 更新本地状态
675
675
  subPanes.value.splice(index, 1)
676
676
  }
677
677
 
678
- // ??????????? Facade API?
678
+ // 清除所有副图(使用高层 Facade API
679
679
  function clearAllSubPanes(): void {
680
- // ???? Facade API ????
680
+ // 使用高层 Facade API 逐个移除
681
681
  for (const pane of subPanes.value) {
682
682
  chartRef.value?.removeIndicator(pane.id)
683
683
  unmountSubPaneTitle(pane.id)
684
684
  }
685
685
 
686
- // ??????
686
+ // 清空本地状态
687
687
  subPanes.value = []
688
688
  subPaneCounters.clear()
689
689
  paneTitleRendererNames.clear()
690
690
  }
691
691
 
692
- // ????????????????????config ? chart?
692
+ // 从语义化配置初始化指标状态(单向数据流:config chart
693
693
  function initIndicatorsFromConfig(): void {
694
694
  const config = props.semanticConfig
695
695
  const chart = chartRef.value
696
696
  if (!chart) return
697
697
 
698
- // ??????? - ????Chart API
698
+ // 初始化主图指标 - 直接调用Chart API
699
699
  const mainIndicators = config.indicators?.main
700
700
  if (mainIndicators) {
701
701
  for (const indicator of mainIndicators) {
702
702
  if (indicator.enabled) {
703
- // ??Vue?????UI???
703
+ // 同步Vue状态(用于UI展示)
704
704
  if (!mainActiveIndicators.value.includes(indicator.type)) {
705
705
  mainActiveIndicators.value.push(indicator.type)
706
706
  }
707
- // ????
707
+ // 保存参数
708
708
  if (indicator.params) {
709
709
  indicatorParams.value[indicator.type] = indicator.params as Record<string, unknown>
710
710
  }
711
- // ?????Chart????????
711
+ // 启用指标(Chart内部管理渲染器)
712
712
  chart.enableMainIndicator(
713
713
  indicator.type,
714
714
  indicator.params as Record<string, number | boolean | string>,
@@ -717,18 +717,18 @@ function initIndicatorsFromConfig(): void {
717
717
  }
718
718
  }
719
719
 
720
- // ??????? syncSubPanesFromChart ??
720
+ // 副图指标参数由 syncSubPanesFromChart 处理
721
721
  }
722
722
 
723
- // ??????????????Chart????Chart???Vue??????
723
+ // 监听主图指标参数变化,同步到Chart(状态由Chart管理,Vue只同步参数)
724
724
  watch(
725
725
  [activeIndicators, indicatorParams],
726
726
  ([indicators]) => {
727
727
  const chart = chartRef.value
728
728
  if (!chart) return
729
729
 
730
- // ???mainIndicatorLegend???????????
731
- // ??????/???Chart????
730
+ // 只更新mainIndicatorLegend的配置(用于图例显示)
731
+ // 渲染器的启用/禁用由Chart内部管理
732
732
  chart.updateRendererConfig('mainIndicatorLegend', {
733
733
  indicators: {
734
734
  MA: {
@@ -755,18 +755,18 @@ watch(
755
755
  { deep: true },
756
756
  )
757
757
 
758
- // ? Chart ???????????????????
758
+ // Chart 同步副图状态到本地(语义化配置后调用)
759
759
  function syncSubPanesFromChart(): void {
760
760
  const chartSubPaneEntries = chartRef.value?.getSubPaneEntries() ?? []
761
761
 
762
- // ??????
762
+ // 清空本地状态
763
763
  subPanes.value = []
764
764
  paneTitleRendererNames.clear()
765
765
 
766
766
  for (const entry of chartSubPaneEntries) {
767
767
  const { paneId, indicatorId, params } = entry
768
768
 
769
- // ???????
769
+ // 恢复计数器状态
770
770
  const match = paneId.match(/^(.+)_(\d+)$/)
771
771
  if (match) {
772
772
  const [, indicator, countStr] = match
@@ -777,10 +777,10 @@ function syncSubPanesFromChart(): void {
777
777
  }
778
778
  }
779
779
 
780
- // ?? paneTitle ???
780
+ // 创建 paneTitle 渲染器
781
781
  mountSubPaneTitle(paneId, indicatorId)
782
782
 
783
- // ??????
783
+ // 更新本地状态
784
784
  subPanes.value.push({
785
785
  id: paneId,
786
786
  indicatorId,
@@ -791,23 +791,23 @@ function syncSubPanesFromChart(): void {
791
791
  scheduleRender()
792
792
  }
793
793
 
794
- // ????????? Chart API?
794
+ // 切换副图指标(使用 Chart API
795
795
  function switchSubIndicator(paneId: string, newIndicatorId: SubIndicatorType): void {
796
796
  const pane = subPanes.value.find((p) => p.id === paneId)
797
797
  if (!pane) return
798
798
 
799
799
  const nextParams = getDefaultParams(newIndicatorId)
800
800
 
801
- // ???? paneTitle ???
801
+ // 移除旧的 paneTitle 渲染器
802
802
  unmountSubPaneTitle(paneId)
803
803
 
804
- // ?? Chart API ???????paneId ??????????
804
+ // 使用 Chart API 替换副图指标(paneId 不变,只换指标类型)
805
805
  chartRef.value?.replaceSubPaneIndicator(paneId, newIndicatorId, nextParams)
806
806
 
807
- // ???? paneTitle ???
807
+ // 创建新的 paneTitle 渲染器
808
808
  mountSubPaneTitle(paneId, newIndicatorId)
809
809
 
810
- // ???????paneId ?????
810
+ // 更新本地状态(paneId 保持不变)
811
811
  const index = subPanes.value.findIndex((p) => p.id === paneId)
812
812
  if (index !== -1) {
813
813
  subPanes.value[index] = {
@@ -818,7 +818,7 @@ function switchSubIndicator(paneId: string, newIndicatorId: SubIndicatorType): v
818
818
  }
819
819
  }
820
820
 
821
- // ??????????????? crosshairIdx ? data ??????
821
+ // 获取副图标题信息(带缓存,只在 crosshairIdx data 变化时重算)
822
822
  const _titleInfoCache = new Map<
823
823
  string,
824
824
  { idx: number | null; dataLen: number; result: TitleInfo | null }
@@ -834,7 +834,7 @@ function getSubPaneTitleInfo(paneId: string): TitleInfo | null {
834
834
  const idx = crosshairIdx.value
835
835
  const dataLen = data.length
836
836
 
837
- // ?????crosshairIdx ? dataLen ???
837
+ // 缓存命中:crosshairIdx dataLen 都没变
838
838
  const cached = _titleInfoCache.get(paneId)
839
839
  if (cached && cached.idx === idx && cached.dataLen === dataLen) {
840
840
  return cached.result
@@ -849,12 +849,12 @@ function getSubPaneTitleInfo(paneId: string): TitleInfo | null {
849
849
  return result
850
850
  }
851
851
 
852
- // ??????????? Facade API?
852
+ // 指标切换处理(使用高层 Facade API
853
853
  function handleIndicatorToggle(indicatorId: string, active: boolean) {
854
854
  const chart = chartRef.value
855
855
  if (!chart) return
856
856
 
857
- // ??????
857
+ // 主图指标处理
858
858
  const mainIndicatorIds = [
859
859
  'MA',
860
860
  'BOLL',
@@ -879,11 +879,11 @@ function handleIndicatorToggle(indicatorId: string, active: boolean) {
879
879
  const existingIndicator = mainActiveIndicators.value.find((id) => id === indicatorId)
880
880
 
881
881
  if (active && !existingIndicator) {
882
- // ??????
882
+ // 添加主图指标
883
883
  chart.addIndicator(indicatorId, 'main', indicatorParams.value[indicatorId])
884
884
  mainActiveIndicators.value.push(indicatorId)
885
885
  } else if (!active && existingIndicator) {
886
- // ??????
886
+ // 移除主图指标
887
887
  const instanceId = indicatorId.toUpperCase()
888
888
  chart.removeIndicator(instanceId)
889
889
  mainActiveIndicators.value = mainActiveIndicators.value.filter((id) => id !== indicatorId)
@@ -891,34 +891,34 @@ function handleIndicatorToggle(indicatorId: string, active: boolean) {
891
891
  return
892
892
  }
893
893
 
894
- // ??????
894
+ // 副图指标处理
895
895
  if (SUB_PANE_INDICATORS.includes(indicatorId as SubIndicatorType)) {
896
896
  if (active) {
897
- // ?????????? pane???
897
+ // 如果已存在同类型指标 pane,跳过
898
898
  const existingPane = subPanes.value.find((p) => p.indicatorId === indicatorId)
899
899
  if (existingPane) return
900
900
 
901
- // ????????
901
+ // 副图数量上限检查
902
902
  if (subPanes.value.length >= maxSubPanes) return
903
903
 
904
- // ???? API ??????
904
+ // 使用高层 API 添加副图指标
905
905
  const paneId = chart.addIndicator(indicatorId, 'sub', indicatorParams.value[indicatorId])
906
906
  if (paneId) {
907
- // ?? paneTitle ???
907
+ // 创建 paneTitle 渲染器
908
908
  mountSubPaneTitle(paneId, indicatorId as SubIndicatorType)
909
- // ??????
909
+ // 同步本地状态
910
910
  subPanes.value.push({
911
911
  id: paneId,
912
912
  indicatorId: indicatorId as SubIndicatorType,
913
913
  params: { ...indicatorParams.value[indicatorId] },
914
914
  })
915
915
  } else if (subPanes.value.length > 0) {
916
- // ???????????????????
916
+ // 添加失败(可能达到上限),替换最后一个
917
917
  const lastPane = subPanes.value[subPanes.value.length - 1]
918
918
  switchSubIndicator(lastPane.id, indicatorId as SubIndicatorType)
919
919
  }
920
920
  } else {
921
- // ??????????? pane
921
+ // 找到并移除该指标的所有 pane
922
922
  const panesToRemove = subPanes.value.filter((p) => p.indicatorId === indicatorId)
923
923
  panesToRemove.forEach((pane) => {
924
924
  chart.removeIndicator(pane.id)
@@ -930,7 +930,7 @@ function handleIndicatorToggle(indicatorId: string, active: boolean) {
930
930
  }
931
931
  }
932
932
 
933
- // ??????????
933
+ // 更新主图指标图例配置
934
934
  function updateMainIndicatorLegendConfig() {
935
935
  chartRef.value?.updateRendererConfig('mainIndicatorLegend', {
936
936
  indicators: {
@@ -954,12 +954,12 @@ function updateMainIndicatorLegendConfig() {
954
954
  })
955
955
  }
956
956
 
957
- // ????????
957
+ // 指标参数更新处理
958
958
  function handleUpdateParams(indicatorId: string, params: Record<string, unknown>) {
959
- // ??????
959
+ // 保存参数配置
960
960
  indicatorParams.value[indicatorId] = params
961
961
 
962
- // ???????? - ??Chart API
962
+ // 主图指标参数更新 - 使用Chart API
963
963
  if (
964
964
  indicatorId === 'MA' ||
965
965
  indicatorId === 'BOLL' ||
@@ -1022,21 +1022,21 @@ function handleReorderSubIndicators(orderedIndicatorIds: string[]) {
1022
1022
 
1023
1023
  subPanes.value = nextSubPanes
1024
1024
 
1025
- // activeIndicators ? computed ???????????
1025
+ // activeIndicators computed 自动派生,无需手动同步
1026
1026
 
1027
1027
  const chart = chartRef.value
1028
1028
  if (!chart) return
1029
1029
  chart.updatePaneLayout(buildPaneLayoutIntent())
1030
1030
  }
1031
1031
 
1032
- /* ??????? Vue ????????zoom ??????? */
1032
+ /* 计算总宽度:从 Vue 响应式状态读取,zoom 变化时自动重算 */
1033
1033
  const axisHostWidth = computed(() => props.rightAxisWidth + props.priceLabelWidth)
1034
1034
 
1035
1035
  const TRAILING_DRAWING_SLOTS_VAL = TRAILING_DRAWING_SLOTS
1036
1036
 
1037
1037
  const totalWidth = store.computed.totalWidth
1038
1038
 
1039
- // ??? Chart ???? scrollLeft ??????
1039
+ // 缩放由 Chart 回调驱动 scrollLeft 与渲染时序。
1040
1040
 
1041
1041
  function scrollToRight() {
1042
1042
  const container = containerRef.value
@@ -1049,13 +1049,13 @@ function scrollToRight() {
1049
1049
  const dpr = chart.getCurrentDpr()
1050
1050
  const { unitPx, startXPx } = getPhysicalKLineConfig(kWidth.value, kGap.value, dpr)
1051
1051
 
1052
- // ??????K????????? TRAILING_DRAWING_SLOTS?
1052
+ // 计算最后一根K线的结束位置(不含 TRAILING_DRAWING_SLOTS
1053
1053
  const lastKLineEndPx = (startXPx + dataLength * unitPx) / dpr
1054
1054
 
1055
- // ?????????
1055
+ // 计算最大可滚动距离
1056
1056
  const maxScrollLeft = Math.max(0, container.scrollWidth - container.clientWidth)
1057
1057
 
1058
- // ???????????????K??????
1058
+ // 计算需要的滚动位置,使最后一根K线紧贴最右侧
1059
1059
  const targetScrollLeft = Math.min(
1060
1060
  maxScrollLeft,
1061
1061
  Math.max(0, lastKLineEndPx - container.clientWidth),
@@ -1065,7 +1065,7 @@ function scrollToRight() {
1065
1065
  scheduleRender()
1066
1066
  }
1067
1067
 
1068
- /* ?????????? Chart facade API? */
1068
+ /* 缩放到指定级别(通过 Chart facade API */
1069
1069
  function applyZoomToLevel(targetLevel: number, anchorX?: number) {
1070
1070
  const chart = chartRef.value
1071
1071
  if (!chart) return
@@ -1083,7 +1083,7 @@ defineExpose({
1083
1083
  return chartRef.value?.plugin
1084
1084
  },
1085
1085
 
1086
- // Zoom Level API?Vue SSOT?
1086
+ // Zoom Level APIVue SSOT
1087
1087
  zoomToLevel: applyZoomToLevel,
1088
1088
  zoomIn: (anchorX?: number) => applyZoomToLevel(zoomLevel.value + 1, anchorX),
1089
1089
  zoomOut: (anchorX?: number) => applyZoomToLevel(zoomLevel.value - 1, anchorX),
@@ -1091,7 +1091,7 @@ defineExpose({
1091
1091
  getZoomLevelCount: () => chartRef.value?.getZoomLevelCount() ?? 10,
1092
1092
  })
1093
1093
 
1094
- // ==================== onMounted ???? ====================
1094
+ // ==================== onMounted 拆分函数 ====================
1095
1095
 
1096
1096
  function setupWheelHandler(container: HTMLDivElement): (e: WheelEvent) => void {
1097
1097
  const onWheelHandler = (e: WheelEvent) => {
@@ -1099,7 +1099,7 @@ function setupWheelHandler(container: HTMLDivElement): (e: WheelEvent) => void {
1099
1099
  const chart = chartRef.value
1100
1100
  if (!chart) return
1101
1101
 
1102
- // ?? Chart facade API ??????
1102
+ // 使用 Chart facade API 处理滚轮事件
1103
1103
  chart.handleWheelEvent(e)
1104
1104
  }
1105
1105
  container.addEventListener('wheel', onWheelHandler, { passive: false })
@@ -1131,10 +1131,10 @@ function initChart(
1131
1131
  }
1132
1132
 
1133
1133
  function setupChartCallbacks(chart: Chart): void {
1134
- // ???setOnViewportChange ???? viewport signal ????
1134
+ // 注意:setOnViewportChange 已合并到 viewport signal 订阅者中
1135
1135
 
1136
1136
  chart.setOnPaneLayoutChange(() => {
1137
- // ????????????????????????
1137
+ // 分隔线位置计算(需要实际像素位置,保留在回调中)
1138
1138
  invalidateContainerRectCache()
1139
1139
  const renderers = chart.getPaneRenderers()
1140
1140
  const borderTop = containerRef.value
@@ -1149,27 +1149,27 @@ function setupChartCallbacks(chart: Chart): void {
1149
1149
  })
1150
1150
  })
1151
1151
 
1152
- // ?? paneRatios signal???? Vue store
1152
+ // 订阅 paneRatios signal,同步到 Vue store
1153
1153
  const unsubscribePaneRatios = chart.paneRatios.subscribe(() => {
1154
1154
  const ratios = chart.paneRatios.peek()
1155
1155
  store.actions.setPaneRatios({ ...ratios })
1156
1156
  })
1157
1157
 
1158
- // ?? viewport signal??????DPR?width ??? scrollLeft ??
1158
+ // 订阅 viewport signal,处理缩放、DPRwidth 变化和 scrollLeft 更新
1159
1159
  const unsubscribeViewport = chart.viewport.subscribe(() => {
1160
1160
  const vp = chart.viewport.peek()
1161
1161
 
1162
- // DPR ?????? store
1162
+ // DPR 变化时同步到 store
1163
1163
  if (store.state.viewportDpr !== vp.dpr) {
1164
1164
  store.actions.setViewportDpr(vp.dpr)
1165
1165
  }
1166
1166
 
1167
- // ViewWidth ?????? store
1167
+ // ViewWidth 变化时同步到 store
1168
1168
  if (store.state.viewWidth !== vp.plotWidth) {
1169
1169
  store.actions.setViewWidth(vp.plotWidth)
1170
1170
  }
1171
1171
 
1172
- // ???? zoom state ? Vue store?Chart ? SSOT?
1172
+ // 完整同步 zoom state Vue storeChart SSOT
1173
1173
  if (
1174
1174
  store.state.zoomLevel !== vp.zoomLevel ||
1175
1175
  store.state.kWidth !== vp.kWidth ||
@@ -1178,7 +1178,7 @@ function setupChartCallbacks(chart: Chart): void {
1178
1178
  store.actions.setZoomState(vp.zoomLevel, vp.kWidth, vp.kGap)
1179
1179
  }
1180
1180
 
1181
- // ? nextTick ??? desiredScrollLeft
1181
+ // nextTick 中应用 desiredScrollLeft
1182
1182
  const desiredLeft = vp.desiredScrollLeft
1183
1183
  if (desiredLeft !== undefined && desiredLeft !== containerRef.value?.scrollLeft) {
1184
1184
  invalidateContainerRectCache()
@@ -1193,20 +1193,20 @@ function setupChartCallbacks(chart: Chart): void {
1193
1193
  }
1194
1194
  })
1195
1195
 
1196
- // ?? data signal??? onDataChange ??
1196
+ // 订阅 data signal,替换 onDataChange 回调
1197
1197
  const unsubscribeData = chart.data.subscribe(() => {
1198
1198
  const data = chart.data.peek()
1199
1199
  store.actions.setDataLength(data.length)
1200
1200
  store.actions.bumpDataVersion()
1201
1201
  })
1202
1202
 
1203
- // ?? theme signal???? CSS data-theme
1203
+ // 订阅 theme signal,同步到 CSS data-theme
1204
1204
  const unsubscribeTheme = chart.theme.subscribe(() => {
1205
1205
  const theme = chart.theme.peek()
1206
1206
  chartTheme.value = theme
1207
1207
  })
1208
1208
 
1209
- // ?? unsubscribe ??????
1209
+ // 保存 unsubscribe 函数以便清理
1210
1210
  onUnmounted(() => {
1211
1211
  unsubscribeViewport()
1212
1212
  unsubscribeData()
@@ -1251,7 +1251,7 @@ function setupInteractionCallbacks(chart: Chart): void {
1251
1251
  if (!chart) return
1252
1252
  const container = containerRef.value
1253
1253
  if (!container) return
1254
- // centerClientX ? clientX????????????
1254
+ // centerClientX clientX,需要转换为视口局部坐标
1255
1255
  const rect = container.getBoundingClientRect()
1256
1256
  const centerX = centerClientX - rect.left
1257
1257
  chart.handlePinchZoom(delta, centerX)
@@ -1262,7 +1262,7 @@ function setupInteractionCallbacks(chart: Chart): void {
1262
1262
  chart.resize()
1263
1263
  }
1264
1264
 
1265
- /** ??????????? ? Chart API ??? */
1265
+ /** 语义化控制器:外部配置 Chart API 的桥梁 */
1266
1266
  function setupSemanticController(chart: Chart): void {
1267
1267
  __setDataFetcher(props.dataFetcher)
1268
1268
  semanticController.value = new SemanticChartController(chart)
@@ -1271,13 +1271,13 @@ function setupSemanticController(chart: Chart): void {
1271
1271
  console.error('Semantic config error:', error)
1272
1272
  })
1273
1273
 
1274
- // config:ready ? Chart ???????Vue ????
1274
+ // config:ready Chart 侧已完成创建,Vue 回读状态
1275
1275
  semanticController.value.on('config:ready', () => {
1276
1276
  initIndicatorsFromConfig()
1277
1277
  syncSubPanesFromChart()
1278
1278
  nextTick(() => scrollToRight())
1279
1279
  })
1280
- // ?????????
1280
+ // 应用副图、主图配置
1281
1281
  semanticController.value.applyConfig(props.semanticConfig).then((result) => {
1282
1282
  if (result && !result.success) {
1283
1283
  console.error('Semantic config apply failed:', result.errors)
@@ -1294,32 +1294,32 @@ onMounted(() => {
1294
1294
  const xAxisCanvas = xAxisCanvasRef.value
1295
1295
  if (!container || !canvasLayer || !rightAxisLayer || !xAxisCanvas) return
1296
1296
 
1297
- // 1) ?????passive:false ???????
1297
+ // 1) 滚轮缩放:passive:false 以阻止页面滚动
1298
1298
  const onWheelHandler = setupWheelHandler(container)
1299
1299
 
1300
- // 2) ?? Chart ??????????
1300
+ // 2) 创建 Chart 实例并注册全部渲染器
1301
1301
  const chart = initChart(container, canvasLayer, rightAxisLayer, xAxisCanvas)
1302
1302
  chartRef.value = chart
1303
1303
 
1304
- // 3) ?? / ???? / ??????
1304
+ // 3) 视口 / 面板布局 / 数据变更回调
1305
1305
  setupChartCallbacks(chart)
1306
1306
 
1307
- // 4) ?? zoom ???Vue SSOT ? Chart?
1307
+ // 4) 同步 zoom 状态(Vue SSOT Chart
1308
1308
  chart.applyRenderState(store.state.kWidth, store.state.kGap, store.state.zoomLevel)
1309
1309
 
1310
- // 5) ????????????????
1310
+ // 5) 工具栏初始设置(含性能测试数据)
1311
1311
  applyInitialSettings(chart)
1312
1312
 
1313
- // 6) ??????????/????
1313
+ // 6) 绘图交互控制器(线段/箭头等)
1314
1314
  setupDrawingController(chart)
1315
1315
 
1316
- // 7) ???????????????
1316
+ // 7) 十字线、捏合缩放、初始交互快照
1317
1317
  setupInteractionCallbacks(chart)
1318
1318
 
1319
- // 8) ??????????????????
1319
+ // 8) 语义化配置控制器(最终驱动数据加载)
1320
1320
  setupSemanticController(chart)
1321
1321
 
1322
- // ? onUnmounted ?? wheel ??
1322
+ // onUnmounted 移除 wheel 监听
1323
1323
  ;(chart as any).__onWheel = onWheelHandler
1324
1324
  })
1325
1325
 
@@ -1337,10 +1337,10 @@ onUnmounted(() => {
1337
1337
  drawingController.value = null
1338
1338
  })
1339
1339
 
1340
- // kWidth/kGap ? zoomLevel ??????? props ????
1341
- // ????????????? expose ? zoomToLevel/zoomIn/zoomOut ??
1340
+ // kWidth/kGap zoomLevel 派生,不再通过 props 直接修改
1341
+ // 如需程序化控制缩放,请使用 expose zoomToLevel/zoomIn/zoomOut 方法
1342
1342
 
1343
- // ?? yPaddingPx ??
1343
+ // 监听 yPaddingPx 变化
1344
1344
  watch(
1345
1345
  () => props.yPaddingPx,
1346
1346
  (newVal) => {
@@ -1348,7 +1348,7 @@ watch(
1348
1348
  },
1349
1349
  )
1350
1350
 
1351
- // ?? semanticConfig ?????????
1351
+ // 监听 semanticConfig 变化(唯一数据源)
1352
1352
  watch(
1353
1353
  () => props.semanticConfig,
1354
1354
  async (newConfig, oldConfig) => {