@363045841yyt/klinechart 0.7.1 → 0.7.3-alpha.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 (236) hide show
  1. package/README.md +10 -6
  2. package/dist/components/DrawingStyleToolbar.vue.d.ts +28 -0
  3. package/dist/components/DrawingStyleToolbar.vue.d.ts.map +1 -0
  4. package/dist/{src/components → components}/IndicatorParams.vue.d.ts +3 -3
  5. package/dist/components/IndicatorParams.vue.d.ts.map +1 -0
  6. package/dist/{src/components → components}/IndicatorSelector.vue.d.ts +4 -4
  7. package/dist/components/IndicatorSelector.vue.d.ts.map +1 -0
  8. package/dist/components/KLineChart.vue.d.ts +63 -0
  9. package/dist/components/KLineChart.vue.d.ts.map +1 -0
  10. package/dist/components/KLineTooltip.vue.d.ts +30 -0
  11. package/dist/components/KLineTooltip.vue.d.ts.map +1 -0
  12. package/dist/{src/components → components}/LeftToolbar.vue.d.ts +3 -2
  13. package/dist/components/LeftToolbar.vue.d.ts.map +1 -0
  14. package/dist/components/MarkerTooltip.vue.d.ts +26 -0
  15. package/dist/components/MarkerTooltip.vue.d.ts.map +1 -0
  16. package/dist/components/index.d.ts +8 -0
  17. package/dist/components/index.d.ts.map +1 -0
  18. package/dist/{src/composables → composables}/useFullscreenTeleportTarget.d.ts +1 -0
  19. package/dist/composables/useFullscreenTeleportTarget.d.ts.map +1 -0
  20. package/dist/{src/debug → debug}/canvasProfiler.d.ts +1 -0
  21. package/dist/debug/canvasProfiler.d.ts.map +1 -0
  22. package/dist/index.cjs +2 -49
  23. package/dist/index.d.cts +124 -0
  24. package/dist/index.d.ts +124 -2
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +1390 -23809
  27. package/dist/klinechart.css +1 -1
  28. package/dist/version.d.ts +3 -0
  29. package/dist/version.d.ts.map +1 -0
  30. package/package.json +75 -112
  31. package/src/__tests__/_mockController.ts +192 -0
  32. package/src/__tests__/contract.test.ts +132 -0
  33. package/src/components/DrawingStyleToolbar.vue +199 -0
  34. package/src/components/IndicatorParams.vue +570 -0
  35. package/src/components/IndicatorSelector.vue +1042 -0
  36. package/src/components/KLineChart.vue +1570 -0
  37. package/src/components/KLineTooltip.vue +200 -0
  38. package/src/components/LeftToolbar.vue +844 -0
  39. package/src/components/MarkerTooltip.vue +155 -0
  40. package/src/components/index.ts +7 -0
  41. package/src/composables/useFullscreenTeleportTarget.ts +18 -0
  42. package/src/debug/canvasProfiler.ts +296 -0
  43. package/src/index.ts +402 -0
  44. package/src/version.ts +3 -0
  45. package/LICENSE +0 -21
  46. package/dist/favicon.ico +0 -0
  47. package/dist/mock-stock-data.json +0 -1
  48. package/dist/schema-CzmPW09E.cjs +0 -1
  49. package/dist/schema-DBMGp6Af.js +0 -437
  50. package/dist/src/App.vue.d.ts +0 -4
  51. package/dist/src/api/data/baostock.d.ts +0 -90
  52. package/dist/src/api/data/baostock.integration.test.d.ts +0 -1
  53. package/dist/src/api/data/index.d.ts +0 -26
  54. package/dist/src/api/data/kLine.d.ts +0 -11
  55. package/dist/src/api/data/types.d.ts +0 -33
  56. package/dist/src/api/data/unified.d.ts +0 -37
  57. package/dist/src/components/DrawingStyleToolbar.vue.d.ts +0 -12
  58. package/dist/src/components/KLineChart.vue.d.ts +0 -100
  59. package/dist/src/components/KLineTooltip.vue.d.ts +0 -17
  60. package/dist/src/components/MarkerTooltip.vue.d.ts +0 -13
  61. package/dist/src/components/index.d.ts +0 -2
  62. package/dist/src/config/chartSettings.d.ts +0 -69
  63. package/dist/src/core/chart-store.d.ts +0 -74
  64. package/dist/src/core/chart.d.ts +0 -617
  65. package/dist/src/core/controller/interaction.d.ts +0 -167
  66. package/dist/src/core/controller/markerInteraction.d.ts +0 -28
  67. package/dist/src/core/controller/pinchTracker.d.ts +0 -18
  68. package/dist/src/core/controller/tooltipPosition.d.ts +0 -21
  69. package/dist/src/core/draw/pixelAlign.d.ts +0 -114
  70. package/dist/src/core/drawing/index.d.ts +0 -47
  71. package/dist/src/core/drawing/interaction.d.ts +0 -75
  72. package/dist/src/core/drawing/plugin.d.ts +0 -27
  73. package/dist/src/core/indicators/atrState.d.ts +0 -16
  74. package/dist/src/core/indicators/bollState.d.ts +0 -34
  75. package/dist/src/core/indicators/calculators.d.ts +0 -465
  76. package/dist/src/core/indicators/cciState.d.ts +0 -15
  77. package/dist/src/core/indicators/chaikinVolState.d.ts +0 -18
  78. package/dist/src/core/indicators/cmfState.d.ts +0 -16
  79. package/dist/src/core/indicators/demaState.d.ts +0 -16
  80. package/dist/src/core/indicators/donchianState.d.ts +0 -23
  81. package/dist/src/core/indicators/eneState.d.ts +0 -30
  82. package/dist/src/core/indicators/expmaState.d.ts +0 -30
  83. package/dist/src/core/indicators/fastkState.d.ts +0 -15
  84. package/dist/src/core/indicators/fibState.d.ts +0 -26
  85. package/dist/src/core/indicators/hmaState.d.ts +0 -16
  86. package/dist/src/core/indicators/hvState.d.ts +0 -18
  87. package/dist/src/core/indicators/ichimokuState.d.ts +0 -44
  88. package/dist/src/core/indicators/indicator.worker.d.ts +0 -5
  89. package/dist/src/core/indicators/indicatorDefinitionRegistry.d.ts +0 -30
  90. package/dist/src/core/indicators/indicatorMetadata.d.ts +0 -81
  91. package/dist/src/core/indicators/indicatorRegistry.d.ts +0 -57
  92. package/dist/src/core/indicators/indicatorRuntime.d.ts +0 -126
  93. package/dist/src/core/indicators/kamaState.d.ts +0 -20
  94. package/dist/src/core/indicators/keltnerState.d.ts +0 -27
  95. package/dist/src/core/indicators/kstState.d.ts +0 -21
  96. package/dist/src/core/indicators/maState.d.ts +0 -26
  97. package/dist/src/core/indicators/macdState.d.ts +0 -58
  98. package/dist/src/core/indicators/mfiState.d.ts +0 -16
  99. package/dist/src/core/indicators/momState.d.ts +0 -15
  100. package/dist/src/core/indicators/obvState.d.ts +0 -14
  101. package/dist/src/core/indicators/parkinsonState.d.ts +0 -18
  102. package/dist/src/core/indicators/pivotState.d.ts +0 -29
  103. package/dist/src/core/indicators/pvtState.d.ts +0 -14
  104. package/dist/src/core/indicators/rocState.d.ts +0 -16
  105. package/dist/src/core/indicators/rsiState.d.ts +0 -43
  106. package/dist/src/core/indicators/sarState.d.ts +0 -26
  107. package/dist/src/core/indicators/scheduler.d.ts +0 -262
  108. package/dist/src/core/indicators/soa.d.ts +0 -115
  109. package/dist/src/core/indicators/stateComposer.d.ts +0 -146
  110. package/dist/src/core/indicators/stochState.d.ts +0 -18
  111. package/dist/src/core/indicators/structureState.d.ts +0 -43
  112. package/dist/src/core/indicators/supertrendState.d.ts +0 -22
  113. package/dist/src/core/indicators/temaState.d.ts +0 -16
  114. package/dist/src/core/indicators/trixState.d.ts +0 -20
  115. package/dist/src/core/indicators/vmaState.d.ts +0 -16
  116. package/dist/src/core/indicators/volumeProfileState.d.ts +0 -34
  117. package/dist/src/core/indicators/vwapState.d.ts +0 -16
  118. package/dist/src/core/indicators/wmaState.d.ts +0 -16
  119. package/dist/src/core/indicators/wmsrState.d.ts +0 -15
  120. package/dist/src/core/indicators/workerProtocol.d.ts +0 -496
  121. package/dist/src/core/indicators/zonesState.d.ts +0 -26
  122. package/dist/src/core/layout/pane.d.ts +0 -103
  123. package/dist/src/core/marker/registry.d.ts +0 -174
  124. package/dist/src/core/paneRenderer.d.ts +0 -45
  125. package/dist/src/core/renderers/Indicator/atr.d.ts +0 -17
  126. package/dist/src/core/renderers/Indicator/boll.d.ts +0 -2
  127. package/dist/src/core/renderers/Indicator/cci.d.ts +0 -22
  128. package/dist/src/core/renderers/Indicator/chaikinVol.d.ts +0 -4
  129. package/dist/src/core/renderers/Indicator/cmf.d.ts +0 -4
  130. package/dist/src/core/renderers/Indicator/dema.d.ts +0 -5
  131. package/dist/src/core/renderers/Indicator/donchian.d.ts +0 -5
  132. package/dist/src/core/renderers/Indicator/ene.d.ts +0 -2
  133. package/dist/src/core/renderers/Indicator/expma.d.ts +0 -2
  134. package/dist/src/core/renderers/Indicator/fastk.d.ts +0 -22
  135. package/dist/src/core/renderers/Indicator/fib.d.ts +0 -4
  136. package/dist/src/core/renderers/Indicator/hma.d.ts +0 -5
  137. package/dist/src/core/renderers/Indicator/hv.d.ts +0 -4
  138. package/dist/src/core/renderers/Indicator/ichimoku.d.ts +0 -5
  139. package/dist/src/core/renderers/Indicator/index.d.ts +0 -59
  140. package/dist/src/core/renderers/Indicator/indicatorData.d.ts +0 -13
  141. package/dist/src/core/renderers/Indicator/kama.d.ts +0 -5
  142. package/dist/src/core/renderers/Indicator/keltner.d.ts +0 -5
  143. package/dist/src/core/renderers/Indicator/kst.d.ts +0 -22
  144. package/dist/src/core/renderers/Indicator/ma.d.ts +0 -3
  145. package/dist/src/core/renderers/Indicator/macd.d.ts +0 -50
  146. package/dist/src/core/renderers/Indicator/macdLegend.d.ts +0 -12
  147. package/dist/src/core/renderers/Indicator/mainIndicatorLegend.d.ts +0 -10
  148. package/dist/src/core/renderers/Indicator/mfi.d.ts +0 -4
  149. package/dist/src/core/renderers/Indicator/mom.d.ts +0 -22
  150. package/dist/src/core/renderers/Indicator/obv.d.ts +0 -4
  151. package/dist/src/core/renderers/Indicator/parkinson.d.ts +0 -4
  152. package/dist/src/core/renderers/Indicator/pivot.d.ts +0 -4
  153. package/dist/src/core/renderers/Indicator/pvt.d.ts +0 -4
  154. package/dist/src/core/renderers/Indicator/roc.d.ts +0 -5
  155. package/dist/src/core/renderers/Indicator/rsi.d.ts +0 -33
  156. package/dist/src/core/renderers/Indicator/sar.d.ts +0 -5
  157. package/dist/src/core/renderers/Indicator/scale/atr_scale.d.ts +0 -11
  158. package/dist/src/core/renderers/Indicator/scale/cci_scale.d.ts +0 -11
  159. package/dist/src/core/renderers/Indicator/scale/fastk_scale.d.ts +0 -11
  160. package/dist/src/core/renderers/Indicator/scale/indicator_scale.d.ts +0 -38
  161. package/dist/src/core/renderers/Indicator/scale/kst_scale.d.ts +0 -11
  162. package/dist/src/core/renderers/Indicator/scale/macd_scale.d.ts +0 -14
  163. package/dist/src/core/renderers/Indicator/scale/mom_scale.d.ts +0 -11
  164. package/dist/src/core/renderers/Indicator/scale/rsi_scale.d.ts +0 -11
  165. package/dist/src/core/renderers/Indicator/scale/stoch_scale.d.ts +0 -11
  166. package/dist/src/core/renderers/Indicator/scale/volume_scale.d.ts +0 -14
  167. package/dist/src/core/renderers/Indicator/scale/wmsr_scale.d.ts +0 -11
  168. package/dist/src/core/renderers/Indicator/stoch.d.ts +0 -22
  169. package/dist/src/core/renderers/Indicator/structure.d.ts +0 -4
  170. package/dist/src/core/renderers/Indicator/subPaneConfig.d.ts +0 -9
  171. package/dist/src/core/renderers/Indicator/supertrend.d.ts +0 -5
  172. package/dist/src/core/renderers/Indicator/tema.d.ts +0 -5
  173. package/dist/src/core/renderers/Indicator/trix.d.ts +0 -5
  174. package/dist/src/core/renderers/Indicator/vma.d.ts +0 -4
  175. package/dist/src/core/renderers/Indicator/volumeProfile.d.ts +0 -4
  176. package/dist/src/core/renderers/Indicator/vwap.d.ts +0 -4
  177. package/dist/src/core/renderers/Indicator/wma.d.ts +0 -5
  178. package/dist/src/core/renderers/Indicator/wmsr.d.ts +0 -22
  179. package/dist/src/core/renderers/Indicator/zones.d.ts +0 -4
  180. package/dist/src/core/renderers/candle.d.ts +0 -21
  181. package/dist/src/core/renderers/crosshair.d.ts +0 -17
  182. package/dist/src/core/renderers/customMarkers.d.ts +0 -6
  183. package/dist/src/core/renderers/extremaMarkers.d.ts +0 -5
  184. package/dist/src/core/renderers/gridLines.d.ts +0 -7
  185. package/dist/src/core/renderers/lastPrice.d.ts +0 -9
  186. package/dist/src/core/renderers/paneTitle.d.ts +0 -40
  187. package/dist/src/core/renderers/subVolume.d.ts +0 -13
  188. package/dist/src/core/renderers/timeAxis.d.ts +0 -14
  189. package/dist/src/core/renderers/webgl/candleSurface.d.ts +0 -80
  190. package/dist/src/core/renderers/webgl/sharedWebGLSurface.d.ts +0 -33
  191. package/dist/src/core/renderers/yAxis.d.ts +0 -14
  192. package/dist/src/core/scale/logFormula.d.ts +0 -66
  193. package/dist/src/core/scale/price.d.ts +0 -11
  194. package/dist/src/core/scale/priceScale.d.ts +0 -87
  195. package/dist/src/core/subPaneManager.d.ts +0 -22
  196. package/dist/src/core/theme/colors.d.ts +0 -222
  197. package/dist/src/core/theme/fonts.d.ts +0 -12
  198. package/dist/src/core/utils/klineConfig.d.ts +0 -28
  199. package/dist/src/core/utils/tickCount.d.ts +0 -8
  200. package/dist/src/core/utils/tickPosition.d.ts +0 -24
  201. package/dist/src/core/utils/zoom.d.ts +0 -30
  202. package/dist/src/core/viewport/viewport.d.ts +0 -31
  203. package/dist/src/index.d.ts +0 -8
  204. package/dist/src/main.d.ts +0 -0
  205. package/dist/src/plugin/ConfigManager.d.ts +0 -31
  206. package/dist/src/plugin/EventBus.d.ts +0 -34
  207. package/dist/src/plugin/HookSystem.d.ts +0 -28
  208. package/dist/src/plugin/PluginHost.d.ts +0 -57
  209. package/dist/src/plugin/PluginRegistry.d.ts +0 -40
  210. package/dist/src/plugin/StateStore.d.ts +0 -37
  211. package/dist/src/plugin/index.d.ts +0 -11
  212. package/dist/src/plugin/rendererPluginManager.d.ts +0 -77
  213. package/dist/src/plugin/stateKeys.d.ts +0 -6
  214. package/dist/src/plugin/types.d.ts +0 -445
  215. package/dist/src/semantic/controller.d.ts +0 -35
  216. package/dist/src/semantic/drawShape.d.ts +0 -14
  217. package/dist/src/semantic/index.d.ts +0 -8
  218. package/dist/src/semantic/types.d.ts +0 -298
  219. package/dist/src/semantic/validator.d.ts +0 -43
  220. package/dist/src/test-setup.d.ts +0 -6
  221. package/dist/src/types/kLine.d.ts +0 -3
  222. package/dist/src/types/price.d.ts +0 -31
  223. package/dist/src/types/volumePrice.d.ts +0 -26
  224. package/dist/src/utils/cache.d.ts +0 -33
  225. package/dist/src/utils/dateFormat.d.ts +0 -83
  226. package/dist/src/utils/http.d.ts +0 -14
  227. package/dist/src/utils/kLineDraw/MA.d.ts +0 -7
  228. package/dist/src/utils/kLineDraw/axis.d.ts +0 -150
  229. package/dist/src/utils/kLineDraw/grid.d.ts +0 -30
  230. package/dist/src/utils/kLineDraw/kLine.d.ts +0 -15
  231. package/dist/src/utils/kline/format.d.ts +0 -11
  232. package/dist/src/utils/kline/viewport.d.ts +0 -10
  233. package/dist/src/utils/logger.d.ts +0 -5
  234. package/dist/src/utils/mock/genRandomPriceData.d.ts +0 -3
  235. package/dist/src/utils/priceToY.d.ts +0 -7
  236. package/dist/src/utils/volumePrice.d.ts +0 -54
@@ -0,0 +1,1570 @@
1
+ <template>
2
+ <div class="chart-wrapper" :data-theme="chartTheme">
3
+ <div
4
+ class="chart-stage"
5
+ :class="{
6
+ 'is-dragging': isDragging,
7
+ 'is-resizing-pane': isResizingPane,
8
+ 'is-hovering-pane-separator': isHoveringPaneSeparator,
9
+ 'is-hovering-right-axis': isHoveringRightAxis,
10
+ 'is-hovering-kline': hoveredIdx !== null,
11
+ }"
12
+ >
13
+ <LeftToolbar
14
+ ref="toolbarRef"
15
+ :is-fullscreen="isFullscreen"
16
+ @select-tool="handleSelectTool"
17
+ @toggle-fullscreen="$emit('toggleFullscreen')"
18
+ @zoom-in="applyZoomToLevel(zoomLevel + 1)"
19
+ @zoom-out="applyZoomToLevel(zoomLevel - 1)"
20
+ @settings-change="handleSettingsChange"
21
+ />
22
+ <div class="chart-main" ref="chartMainRef">
23
+ <div class="pane-separator-layer" aria-hidden="true">
24
+ <div
25
+ v-for="line in paneSeparatorLines"
26
+ :key="line.id"
27
+ class="pane-separator-line"
28
+ :class="{ 'is-active': hoveredPaneBoundaryId === line.id }"
29
+ :style="{ top: `${line.top}px` }"
30
+ ></div>
31
+ </div>
32
+ <div ref="tooltipLayerRef" class="tooltip-layer"></div>
33
+ <div
34
+ class="chart-container"
35
+ :style="{ cursor: containerCursor }"
36
+ ref="containerRef"
37
+ @scroll.passive="onScroll"
38
+ @pointerdown="onPointerDown"
39
+ @pointermove="onPointerMove"
40
+ @pointerup="onPointerUp"
41
+ @pointerleave="onPointerLeave"
42
+ >
43
+ <div class="scroll-content" :style="{ width: totalWidth + 'px' }">
44
+ <div class="canvas-layer" ref="canvasLayerRef">
45
+ <canvas class="x-axis-canvas" ref="xAxisCanvasRef"></canvas>
46
+
47
+ <DrawingStyleToolbar
48
+ v-if="selectedDrawing"
49
+ :drawing="selectedDrawing"
50
+ @update-style="onUpdateDrawingStyle"
51
+ @delete="onDeleteDrawing"
52
+ />
53
+ </div>
54
+ </div>
55
+ </div>
56
+ <Teleport v-if="tooltipLayerRef" :to="tooltipLayerRef">
57
+ <div
58
+ v-if="hovered"
59
+ class="tooltip-anchor kline-tooltip-anchor"
60
+ :class="{ 'use-anchor': useAnchorPositioning }"
61
+ :style="klineTooltipAnchorStyle"
62
+ ></div>
63
+ <div
64
+ v-if="hoveredMarker || hoveredCustomMarker"
65
+ class="tooltip-anchor marker-tooltip-anchor"
66
+ :class="{ 'use-anchor': useAnchorPositioning }"
67
+ :style="markerTooltipAnchorStyle"
68
+ ></div>
69
+ <KLineTooltip
70
+ v-if="hovered"
71
+ :k="hovered"
72
+ :index="hoveredIndex"
73
+ :data="chartData"
74
+ :pos="teleportedTooltipPos"
75
+ :set-el="setTooltipEl"
76
+ :use-anchor="useAnchorPositioning"
77
+ :anchor-placement="tooltipAnchorPlacement"
78
+ />
79
+ <MarkerTooltip
80
+ v-if="hoveredMarker || hoveredCustomMarker"
81
+ :marker="hoveredMarker || hoveredCustomMarker"
82
+ :pos="teleportedMarkerTooltipPos"
83
+ :use-anchor="useAnchorPositioning"
84
+ :anchor-placement="markerTooltipAnchorPlacement"
85
+ :set-el="setMarkerTooltipEl"
86
+ />
87
+ </Teleport>
88
+ <div
89
+ class="right-axis-host"
90
+ ref="rightAxisLayerRef"
91
+ :style="{ width: axisHostWidth + 'px' }"
92
+ @pointerdown="onRightAxisPointerDown"
93
+ @pointermove="onRightAxisPointerMove"
94
+ @pointerup="onRightAxisPointerUp"
95
+ @pointerleave="onRightAxisPointerLeave"
96
+ ></div>
97
+ </div>
98
+ </div>
99
+ <IndicatorSelector
100
+ :active-indicators="activeIndicators"
101
+ :indicator-params="indicatorParams"
102
+ @toggle="handleIndicatorToggle"
103
+ @update-params="handleUpdateParams"
104
+ @reorder-sub-indicators="handleReorderSubIndicators"
105
+ />
106
+ </div>
107
+ </template>
108
+
109
+ <script setup lang="ts">
110
+ import { ref, computed, onMounted, onUnmounted, watch, nextTick, shallowRef } from 'vue'
111
+ import {
112
+ SemanticChartController,
113
+ __setDataFetcher,
114
+ type SemanticChartConfig,
115
+ type DataFetcher,
116
+ } from '@363045841yyt/klinechart-core/semantic'
117
+ import KLineTooltip from './KLineTooltip.vue'
118
+ import MarkerTooltip from './MarkerTooltip.vue'
119
+ import IndicatorSelector from './IndicatorSelector.vue'
120
+ import DrawingStyleToolbar from './DrawingStyleToolbar.vue'
121
+ import { Chart, type PaneSpec } from '@363045841yyt/klinechart-core/engine/chart'
122
+ import type { KLineData } from '@363045841yyt/klinechart-core/types/price'
123
+ import {
124
+ createChartStore,
125
+ TRAILING_DRAWING_SLOTS,
126
+ type ChartStore,
127
+ } from '@363045841yyt/klinechart-core/engine/chart-store'
128
+ import { zoomLevelToKWidth, kGapFromKWidth } from '@363045841yyt/klinechart-core/engine/utils/zoom'
129
+ import { getPhysicalKLineConfig } from '@363045841yyt/klinechart-core/engine/utils/klineConfig'
130
+ import { type SubIndicatorType } from '@363045841yyt/klinechart-core/engine/renderers/Indicator'
131
+ import {
132
+ SUB_PANE_INDICATOR_CONFIGS,
133
+ SUB_PANE_INDICATORS,
134
+ } from '@363045841yyt/klinechart-core/engine/renderers/Indicator/subPaneConfig'
135
+ import {
136
+ createPaneTitleRendererPlugin,
137
+ type TitleInfo,
138
+ } from '@363045841yyt/klinechart-core/engine/renderers/paneTitle'
139
+ import type { InteractionSnapshot } from '@363045841yyt/klinechart-core/engine/controller/interaction'
140
+ import type { DrawingStyle } from '@363045841yyt/klinechart-core/plugin'
141
+ import LeftToolbar from './LeftToolbar.vue'
142
+ import {
143
+ DrawingInteractionController,
144
+ type DrawingToolId,
145
+ } from '@363045841yyt/klinechart-core/engine/drawing'
146
+
147
+ const props = withDefaults(
148
+ defineProps<{
149
+ /** ??????????????? */
150
+ semanticConfig: SemanticChartConfig
151
+
152
+ /** ??????????????????????????? */
153
+ dataFetcher: DataFetcher
154
+
155
+ yPaddingPx?: number
156
+ minKWidth?: number
157
+ maxKWidth?: number
158
+ /** ??????? */
159
+ rightAxisWidth?: number
160
+ /** ??????? */
161
+ bottomAxisHeight?: number
162
+ /** ??????????????????? 60px? */
163
+ priceLabelWidth?: number
164
+
165
+ /** ????????? 10? */
166
+ zoomLevels?: number
167
+ /** ???????1 ~ zoomLevels?????? */
168
+ initialZoomLevel?: number
169
+ /** ???? */
170
+ isFullscreen?: boolean
171
+ }>(),
172
+ {
173
+ yPaddingPx: 20,
174
+ minKWidth: 1,
175
+ maxKWidth: 50,
176
+ rightAxisWidth: 0,
177
+ bottomAxisHeight: 24,
178
+ priceLabelWidth: 60,
179
+ zoomLevels: 20,
180
+ initialZoomLevel: 3,
181
+ isFullscreen: false,
182
+ },
183
+ )
184
+
185
+ const emit = defineEmits<{
186
+ (e: 'zoomLevelChange', level: number, kWidth: number): void
187
+ (e: 'toggleFullscreen'): void
188
+ }>()
189
+
190
+ const xAxisCanvasRef = ref<HTMLCanvasElement | null>(null)
191
+ const canvasLayerRef = ref<HTMLDivElement | null>(null)
192
+ const rightAxisLayerRef = ref<HTMLDivElement | null>(null)
193
+ const containerRef = ref<HTMLDivElement | null>(null)
194
+ const chartMainRef = ref<HTMLDivElement | null>(null)
195
+ const tooltipLayerRef = ref<HTMLDivElement | null>(null)
196
+ const toolbarRef = ref<InstanceType<typeof LeftToolbar> | null>(null)
197
+
198
+ /* ========== ??????????? ========== */
199
+ const chartRef = shallowRef<Chart | null>(null)
200
+
201
+ /* ========== ?????? ========== */
202
+ const semanticController = shallowRef<SemanticChartController | null>(null)
203
+
204
+ /* ========== ChartStore????????? ========== */
205
+ const store = createChartStore({
206
+ initialZoomLevel: props.initialZoomLevel ?? 1,
207
+ minKWidth: props.minKWidth,
208
+ maxKWidth: props.maxKWidth,
209
+ zoomLevels: props.zoomLevels,
210
+ rightAxisWidth: props.rightAxisWidth,
211
+ priceLabelWidth: props.priceLabelWidth,
212
+ })
213
+
214
+ /* ========== ???? ========== */
215
+ const chartTheme = ref<'light' | 'dark'>('light')
216
+
217
+ // ??? kWidth / kGap
218
+ store.actions.setZoomState(
219
+ store.state.zoomLevel,
220
+ zoomLevelToKWidth(store.state.zoomLevel, {
221
+ minKWidth: props.minKWidth,
222
+ maxKWidth: props.maxKWidth,
223
+ zoomLevelCount: props.zoomLevels,
224
+ dpr: store.state.viewportDpr,
225
+ }),
226
+ kGapFromKWidth(
227
+ zoomLevelToKWidth(store.state.zoomLevel, {
228
+ minKWidth: props.minKWidth,
229
+ maxKWidth: props.maxKWidth,
230
+ zoomLevelCount: props.zoomLevels,
231
+ dpr: store.state.viewportDpr,
232
+ }),
233
+ store.state.viewportDpr,
234
+ ),
235
+ )
236
+
237
+ // ????????????
238
+ const dataLength = computed(() => store.state.dataLength)
239
+ const viewportDpr = computed(() => store.state.viewportDpr)
240
+ const zoomLevel = computed(() => store.state.zoomLevel)
241
+ const kWidth = computed(() => store.state.kWidth)
242
+ const kGap = computed(() => store.state.kGap)
243
+ const paneRatios = computed(() => store.state.paneRatios)
244
+ const selectedDrawingId = computed(() => store.state.selectedDrawingId)
245
+ const dataVersion = computed(() => store.state.dataVersion)
246
+
247
+ function scheduleRender() {
248
+ chartRef.value?.scheduleDraw()
249
+ }
250
+
251
+ function handleSettingsChange(settings: Record<string, boolean | string>) {
252
+ chartRef.value?.updateSettings(settings)
253
+
254
+ // ??K?????
255
+ if (settings.performanceTest10kKlines) {
256
+ const testData = generate10kKLineData()
257
+ console.time('updateData-10k')
258
+ chartRef.value?.updateData(testData)
259
+ console.timeEnd('updateData-10k')
260
+ store.actions.setDataLength(testData.length)
261
+ store.actions.bumpDataVersion()
262
+ } else {
263
+ // ???????????????
264
+ // ??????????????
265
+ if (semanticController.value && chartRef.value?.getData()?.length === 10000) {
266
+ semanticController.value.applyConfig(props.semanticConfig)
267
+ }
268
+ }
269
+ }
270
+
271
+ // ??1??K?????
272
+ function generate10kKLineData() {
273
+ const data: KLineData[] = []
274
+ const startTime = new Date('2020-01-01').getTime()
275
+ const dayMs = 24 * 60 * 60 * 1000
276
+
277
+ let lastClose = 3000 // ????
278
+
279
+ for (let i = 0; i < 10000; i++) {
280
+ const timestamp = startTime + i * dayMs
281
+
282
+ // ??????
283
+ const volatility = 0.02 // 2%????
284
+ const trend = 0.0001 // ??????
285
+ const change = (Math.random() - 0.5) * 2 * volatility + trend
286
+
287
+ const open = lastClose
288
+ const close = open * (1 + change)
289
+ const high = Math.max(open, close) * (1 + Math.random() * 0.01)
290
+ const low = Math.min(open, close) * (1 - Math.random() * 0.01)
291
+ const volume = Math.floor(1000000 + Math.random() * 5000000)
292
+
293
+ data.push({
294
+ timestamp,
295
+ open: parseFloat(open.toFixed(2)),
296
+ high: parseFloat(high.toFixed(2)),
297
+ low: parseFloat(low.toFixed(2)),
298
+ close: parseFloat(close.toFixed(2)),
299
+ volume,
300
+ })
301
+
302
+ lastClose = close
303
+ }
304
+
305
+ return data
306
+ }
307
+
308
+ function measureTooltipSize(el: HTMLDivElement, minWidth: number, minHeight: number) {
309
+ const r = el.getBoundingClientRect()
310
+ return {
311
+ width: Math.max(minWidth, Math.round(r.width)),
312
+ height: Math.max(minHeight, Math.round(r.height)),
313
+ }
314
+ }
315
+
316
+ function setTooltipEl(el: HTMLDivElement | null) {
317
+ if (!el) return
318
+ nextTick(() => {
319
+ if (!el.isConnected) return
320
+ const size = measureTooltipSize(el, 180, 80)
321
+ chartRef.value?.interaction.setTooltipSize(size)
322
+ })
323
+ }
324
+
325
+ function setMarkerTooltipEl(el: HTMLDivElement | null) {
326
+ if (!el) return
327
+ nextTick(() => {
328
+ if (!el.isConnected) return
329
+ markerTooltipSize.value = measureTooltipSize(el, 120, 60)
330
+ })
331
+ }
332
+
333
+ // ===== Marker tooltip ?? =====
334
+ const mousePos = ref({ x: 0, y: 0 })
335
+ const useAnchorPositioning = ref(false)
336
+
337
+ // ?? rect ????? pointermove ??? getBoundingClientRect ??????
338
+ let _cachedContainerRect: DOMRect | null = null
339
+ function invalidateContainerRectCache(): void {
340
+ _cachedContainerRect = null
341
+ }
342
+ function getContainerRect(container: HTMLDivElement): DOMRect {
343
+ if (!_cachedContainerRect) {
344
+ _cachedContainerRect = container.getBoundingClientRect()
345
+ }
346
+ return _cachedContainerRect
347
+ }
348
+
349
+ // ===== ??????????InteractionController snapshot? =====
350
+ const interactionState = shallowRef<InteractionSnapshot>({
351
+ crosshairPos: null,
352
+ crosshairIndex: null,
353
+ crosshairPrice: null,
354
+ hoveredIndex: null,
355
+ activePaneId: null,
356
+ tooltipPos: { x: 0, y: 0 },
357
+ tooltipAnchorPlacement: 'right-bottom',
358
+ hoveredMarkerData: null,
359
+ hoveredCustomMarker: null,
360
+ isDragging: false,
361
+ isResizingPaneBoundary: false,
362
+ isHoveringPaneBoundary: false,
363
+ hoveredPaneBoundaryId: null,
364
+ isHoveringRightAxis: false,
365
+ })
366
+
367
+ const drawingController = shallowRef<DrawingInteractionController | null>(null)
368
+ const selectedDrawing = computed(() => {
369
+ const id = selectedDrawingId.value
370
+ if (!id) return null
371
+ return store.state.drawings.find((d) => d.id === id) ?? null
372
+ })
373
+ const paneSeparatorLines = ref<Array<{ id: string; top: number }>>([])
374
+ const markerTooltipSize = ref({ width: 220, height: 120 })
375
+ const tooltipLayerOffset = computed(() => {
376
+ const container = containerRef.value
377
+ const chartMain = chartMainRef.value
378
+ if (!container || !chartMain) return { x: 0, y: 0 }
379
+ return {
380
+ x: container.offsetLeft,
381
+ y: container.offsetTop,
382
+ }
383
+ })
384
+
385
+ const hoveredMarker = computed(() => interactionState.value.hoveredMarkerData)
386
+ const hoveredCustomMarker = computed(() => interactionState.value.hoveredCustomMarker)
387
+ const isDragging = computed(() => interactionState.value.isDragging)
388
+ const isResizingPane = computed(() => interactionState.value.isResizingPaneBoundary)
389
+ const isHoveringPaneSeparator = computed(() => interactionState.value.isHoveringPaneBoundary)
390
+ const hoveredPaneBoundaryId = computed(() => interactionState.value.hoveredPaneBoundaryId)
391
+ const isHoveringRightAxis = computed(() => interactionState.value.isHoveringRightAxis)
392
+ const hoveredIdx = computed(() => interactionState.value.hoveredIndex)
393
+ const crosshairIdx = computed(() => interactionState.value.crosshairIndex)
394
+
395
+ // ?????????? style ?? CSS ??????????????
396
+ const containerCursor = computed(() => {
397
+ if (isDragging.value) return 'grabbing'
398
+ if (isResizingPane.value || isHoveringPaneSeparator.value) return 'ns-resize'
399
+ if (hoveredIdx.value !== null) return 'pointer'
400
+ return 'crosshair'
401
+ })
402
+
403
+ const hovered = computed(() => {
404
+ const idx = interactionState.value.hoveredIndex
405
+ if (typeof idx !== 'number') return null
406
+ void dataVersion.value // ???????
407
+ const data = chartRef.value?.getData()
408
+ if (data && idx >= 0 && idx < data.length) {
409
+ return data[idx]
410
+ }
411
+ return null
412
+ })
413
+ const hoveredIndex = computed(() => interactionState.value.hoveredIndex)
414
+ const tooltipPos = computed(() => interactionState.value.tooltipPos)
415
+ const teleportedTooltipPos = computed(() => ({
416
+ x: tooltipPos.value.x + tooltipLayerOffset.value.x,
417
+ y: tooltipPos.value.y + tooltipLayerOffset.value.y,
418
+ }))
419
+ const klineTooltipAnchorStyle = computed(() => ({
420
+ left: `${teleportedTooltipPos.value.x}px`,
421
+ top: `${teleportedTooltipPos.value.y}px`,
422
+ }))
423
+ const teleportedMarkerTooltipPos = computed(() => ({
424
+ x: mousePos.value.x + tooltipLayerOffset.value.x,
425
+ y: mousePos.value.y + tooltipLayerOffset.value.y,
426
+ }))
427
+ const markerTooltipAnchorStyle = computed(() => ({
428
+ left: `${teleportedMarkerTooltipPos.value.x}px`,
429
+ top: `${teleportedMarkerTooltipPos.value.y}px`,
430
+ }))
431
+ const tooltipAnchorPlacement = computed(() => interactionState.value.tooltipAnchorPlacement)
432
+ const markerTooltipAnchorPlacement = computed<'right-bottom' | 'left-bottom'>(() => {
433
+ const chart = chartRef.value
434
+ const viewport = chart?.getViewport()
435
+ const container = containerRef.value
436
+ const plotWidth = viewport?.plotWidth ?? (container ? container.clientWidth : 0)
437
+ const padding = 12
438
+ const gap = 12
439
+ const rightCandidateX = mousePos.value.x + gap
440
+ const wouldOverflowRight = rightCandidateX + markerTooltipSize.value.width + padding > plotWidth
441
+ return wouldOverflowRight ? 'left-bottom' : 'right-bottom'
442
+ })
443
+
444
+ // ????????
445
+ const chartData = computed(() => {
446
+ void dataVersion.value // ???????????????????
447
+ return chartRef.value?.getData() ?? []
448
+ })
449
+
450
+ // ????????????????
451
+ function handleSelectTool(toolId: string) {
452
+ drawingController.value?.setTool(toolId as DrawingToolId)
453
+ }
454
+
455
+ function onUpdateDrawingStyle(style: Partial<DrawingStyle>) {
456
+ const d = selectedDrawing.value
457
+ if (!d || !drawingController.value) return
458
+ drawingController.value.updateDrawingStyle(d.id, style)
459
+ store.actions.bumpDrawingVersion()
460
+ }
461
+
462
+ function onDeleteDrawing() {
463
+ const d = selectedDrawing.value
464
+ if (!d || !drawingController.value) return
465
+ drawingController.value.removeDrawing(d.id)
466
+ store.actions.setSelectedDrawingId(null)
467
+ store.actions.bumpDrawingVersion()
468
+ store.actions.setDrawings(drawingController.value.getDrawings())
469
+ }
470
+
471
+ function onPointerDown(e: PointerEvent) {
472
+ chartRef.value?.handlePointerEvent(e, {
473
+ onPointerDown: (event, container) => {
474
+ if (drawingController.value?.onPointerDown(event, container)) {
475
+ store.actions.setDrawings(drawingController.value.getDrawings())
476
+ store.actions.bumpDrawingVersion()
477
+ return true
478
+ }
479
+ return false
480
+ },
481
+ })
482
+ }
483
+
484
+ function onPointerMove(e: PointerEvent) {
485
+ const container = containerRef.value
486
+ if (container) {
487
+ const rect = getContainerRect(container)
488
+ mousePos.value = {
489
+ x: e.clientX - rect.left,
490
+ y: e.clientY - rect.top,
491
+ }
492
+ }
493
+ chartRef.value?.handlePointerEvent(e, {
494
+ onPointerMove: (event, container) => {
495
+ if (drawingController.value?.onPointerMove(event, container)) {
496
+ store.actions.setDrawings(drawingController.value.getDrawings())
497
+ return true
498
+ }
499
+ return false
500
+ },
501
+ })
502
+ }
503
+
504
+ function onPointerUp(e: PointerEvent) {
505
+ chartRef.value?.handlePointerEvent(e, {
506
+ onPointerUp: (event, container) => {
507
+ if (drawingController.value?.onPointerUp(event, container)) {
508
+ store.actions.setDrawings(drawingController.value.getDrawings())
509
+ return true
510
+ }
511
+ return false
512
+ },
513
+ })
514
+ }
515
+
516
+ function onPointerLeave(e: PointerEvent) {
517
+ // pointerleave ???????????????
518
+ chartRef.value?.handlePointerEvent(e)
519
+ }
520
+
521
+ function onRightAxisPointerDown(e: PointerEvent) {
522
+ chartRef.value?.handlePointerEvent(e)
523
+ }
524
+
525
+ function onRightAxisPointerMove(e: PointerEvent) {
526
+ chartRef.value?.handlePointerEvent(e)
527
+ }
528
+
529
+ function onRightAxisPointerUp(e: PointerEvent) {
530
+ chartRef.value?.handlePointerEvent(e)
531
+ }
532
+
533
+ function onRightAxisPointerLeave(e: PointerEvent) {
534
+ chartRef.value?.handlePointerEvent(e)
535
+ }
536
+
537
+ function onScroll() {
538
+ chartRef.value?.handleScrollEvent()
539
+ }
540
+
541
+ // ?????????????? subPanes ???
542
+ const mainActiveIndicators = ref<string[]>([])
543
+
544
+ // ??????? subPanes ????
545
+ const subActiveIndicators = computed(() => {
546
+ const ids: string[] = []
547
+ const seen = new Set<string>()
548
+ for (const pane of subPanes.value) {
549
+ if (!seen.has(pane.indicatorId)) {
550
+ seen.add(pane.indicatorId)
551
+ ids.push(pane.indicatorId)
552
+ }
553
+ }
554
+ return ids
555
+ })
556
+
557
+ // ????????? + ??????????
558
+ const activeIndicators = computed(() => [
559
+ ...mainActiveIndicators.value,
560
+ ...subActiveIndicators.value,
561
+ ])
562
+
563
+ // ???????MA ? periods ?????????????
564
+ const indicatorParams = ref<Record<string, Record<string, unknown>>>({})
565
+
566
+ // ??????
567
+ interface SubPaneSlot {
568
+ id: string // pane ID: 'RSI_0', 'MACD_0', ...
569
+ indicatorId: SubIndicatorType
570
+ params: Record<string, unknown>
571
+ }
572
+
573
+ // ?????????????
574
+ const subPanes = ref<SubPaneSlot[]>([])
575
+
576
+ // ??????
577
+ const maxSubPanes = 4
578
+
579
+ function buildPaneLayoutIntent(): PaneSpec[] {
580
+ const mainRatio = paneRatios.value['main'] ?? 3
581
+ return subPanes.value.length === 0
582
+ ? [{ id: 'main', ratio: mainRatio, visible: true, role: 'price' }]
583
+ : [
584
+ { id: 'main', ratio: mainRatio, visible: true, role: 'price' },
585
+ ...subPanes.value.map((pane) => ({
586
+ id: pane.id,
587
+ ratio: paneRatios.value[pane.id] ?? 1,
588
+ visible: true,
589
+ role: 'indicator' as const,
590
+ })),
591
+ ]
592
+ }
593
+
594
+ // ????????
595
+ function getDefaultParams(
596
+ indicatorId: SubIndicatorType,
597
+ ): Record<string, number | boolean | string> {
598
+ return { ...SUB_PANE_INDICATOR_CONFIGS[indicatorId].defaultParams }
599
+ }
600
+
601
+ // ???????????? 'RSI_0', 'MACD_0' ??? paneId
602
+ const subPaneCounters = new Map<SubIndicatorType, number>()
603
+
604
+ function generatePaneId(indicatorId: SubIndicatorType): string {
605
+ const count = subPaneCounters.get(indicatorId) ?? 0
606
+ subPaneCounters.set(indicatorId, count + 1)
607
+ return `${indicatorId}_${count}`
608
+ }
609
+
610
+ // paneTitle ????????paneId -> rendererName?
611
+ const paneTitleRendererNames = new Map<string, string>()
612
+
613
+ function mountSubPaneTitle(paneId: string, indicatorId: SubIndicatorType): void {
614
+ const paneTitleRenderer = createPaneTitleRendererPlugin({
615
+ paneId,
616
+ title: indicatorId,
617
+ getTitleInfo: () => getSubPaneTitleInfo(paneId),
618
+ })
619
+ chartRef.value?.useRenderer(paneTitleRenderer)
620
+ paneTitleRendererNames.set(paneId, paneTitleRenderer.name)
621
+ }
622
+
623
+ function unmountSubPaneTitle(paneId: string): void {
624
+ const rendererName = paneTitleRendererNames.get(paneId)
625
+ if (rendererName) {
626
+ chartRef.value?.removeRenderer(rendererName)
627
+ paneTitleRendererNames.delete(paneId)
628
+ }
629
+ }
630
+
631
+ // ??????? Chart API?
632
+ function addSubPane(
633
+ indicatorId: SubIndicatorType = 'VOLUME',
634
+ params?: Record<string, number | boolean | string>,
635
+ ): boolean {
636
+ if (subPanes.value.length >= maxSubPanes) {
637
+ return false
638
+ }
639
+
640
+ const mergedParams = params ?? getDefaultParams(indicatorId)
641
+
642
+ // ???? Facade API ??????
643
+ const paneId = chartRef.value?.addIndicator(indicatorId, 'sub', mergedParams)
644
+ if (!paneId) return false
645
+
646
+ // ?? paneTitle ????UI ????
647
+ mountSubPaneTitle(paneId, indicatorId)
648
+
649
+ // ??????
650
+ subPanes.value.push({
651
+ id: paneId,
652
+ indicatorId,
653
+ params: mergedParams,
654
+ })
655
+
656
+ scheduleRender()
657
+ return true
658
+ }
659
+
660
+ // ????????? Facade API?
661
+ function removeSubPane(paneId: string): void {
662
+ const index = subPanes.value.findIndex((p) => p.id === paneId)
663
+ if (index === -1) return
664
+
665
+ const pane = subPanes.value[index]
666
+ if (!pane) return
667
+
668
+ // ?? paneTitle ???
669
+ unmountSubPaneTitle(paneId)
670
+
671
+ // ???? Facade API ????
672
+ chartRef.value?.removeIndicator(paneId)
673
+
674
+ // ??????
675
+ subPanes.value.splice(index, 1)
676
+ }
677
+
678
+ // ??????????? Facade API?
679
+ function clearAllSubPanes(): void {
680
+ // ???? Facade API ????
681
+ for (const pane of subPanes.value) {
682
+ chartRef.value?.removeIndicator(pane.id)
683
+ unmountSubPaneTitle(pane.id)
684
+ }
685
+
686
+ // ??????
687
+ subPanes.value = []
688
+ subPaneCounters.clear()
689
+ paneTitleRendererNames.clear()
690
+ }
691
+
692
+ // ????????????????????config ? chart?
693
+ function initIndicatorsFromConfig(): void {
694
+ const config = props.semanticConfig
695
+ const chart = chartRef.value
696
+ if (!chart) return
697
+
698
+ // ??????? - ????Chart API
699
+ const mainIndicators = config.indicators?.main
700
+ if (mainIndicators) {
701
+ for (const indicator of mainIndicators) {
702
+ if (indicator.enabled) {
703
+ // ??Vue?????UI???
704
+ if (!mainActiveIndicators.value.includes(indicator.type)) {
705
+ mainActiveIndicators.value.push(indicator.type)
706
+ }
707
+ // ????
708
+ if (indicator.params) {
709
+ indicatorParams.value[indicator.type] = indicator.params as Record<string, unknown>
710
+ }
711
+ // ?????Chart????????
712
+ chart.enableMainIndicator(
713
+ indicator.type,
714
+ indicator.params as Record<string, number | boolean | string>,
715
+ )
716
+ }
717
+ }
718
+ }
719
+
720
+ // ??????? syncSubPanesFromChart ??
721
+ }
722
+
723
+ // ??????????????Chart????Chart???Vue??????
724
+ watch(
725
+ [activeIndicators, indicatorParams],
726
+ ([indicators]) => {
727
+ const chart = chartRef.value
728
+ if (!chart) return
729
+
730
+ // ???mainIndicatorLegend???????????
731
+ // ??????/???Chart????
732
+ chart.updateRendererConfig('mainIndicatorLegend', {
733
+ indicators: {
734
+ MA: {
735
+ enabled: indicators.includes('MA'),
736
+ params: indicatorParams.value['MA'] || {},
737
+ },
738
+ BOLL: {
739
+ enabled: indicators.includes('BOLL'),
740
+ params: indicatorParams.value['BOLL'] || {},
741
+ },
742
+ EXPMA: {
743
+ enabled: indicators.includes('EXPMA'),
744
+ params: indicatorParams.value['EXPMA'] || {},
745
+ },
746
+ ENE: {
747
+ enabled: indicators.includes('ENE'),
748
+ params: indicatorParams.value['ENE'] || {},
749
+ },
750
+ },
751
+ })
752
+
753
+ scheduleRender()
754
+ },
755
+ { deep: true },
756
+ )
757
+
758
+ // ? Chart ???????????????????
759
+ function syncSubPanesFromChart(): void {
760
+ const chartSubPaneEntries = chartRef.value?.getSubPaneEntries() ?? []
761
+
762
+ // ??????
763
+ subPanes.value = []
764
+ paneTitleRendererNames.clear()
765
+
766
+ for (const entry of chartSubPaneEntries) {
767
+ const { paneId, indicatorId, params } = entry
768
+
769
+ // ???????
770
+ const match = paneId.match(/^(.+)_(\d+)$/)
771
+ if (match) {
772
+ const [, indicator, countStr] = match
773
+ const count = parseInt(countStr!, 10)
774
+ const currentCount = subPaneCounters.get(indicator as SubIndicatorType) ?? 0
775
+ if (count >= currentCount) {
776
+ subPaneCounters.set(indicator as SubIndicatorType, count + 1)
777
+ }
778
+ }
779
+
780
+ // ?? paneTitle ???
781
+ mountSubPaneTitle(paneId, indicatorId)
782
+
783
+ // ??????
784
+ subPanes.value.push({
785
+ id: paneId,
786
+ indicatorId,
787
+ params: { ...params },
788
+ })
789
+ }
790
+
791
+ scheduleRender()
792
+ }
793
+
794
+ // ????????? Chart API?
795
+ function switchSubIndicator(paneId: string, newIndicatorId: SubIndicatorType): void {
796
+ const pane = subPanes.value.find((p) => p.id === paneId)
797
+ if (!pane) return
798
+
799
+ const nextParams = getDefaultParams(newIndicatorId)
800
+
801
+ // ???? paneTitle ???
802
+ unmountSubPaneTitle(paneId)
803
+
804
+ // ?? Chart API ???????paneId ??????????
805
+ chartRef.value?.replaceSubPaneIndicator(paneId, newIndicatorId, nextParams)
806
+
807
+ // ???? paneTitle ???
808
+ mountSubPaneTitle(paneId, newIndicatorId)
809
+
810
+ // ???????paneId ?????
811
+ const index = subPanes.value.findIndex((p) => p.id === paneId)
812
+ if (index !== -1) {
813
+ subPanes.value[index] = {
814
+ id: paneId,
815
+ indicatorId: newIndicatorId,
816
+ params: nextParams,
817
+ }
818
+ }
819
+ }
820
+
821
+ // ??????????????? crosshairIdx ? data ??????
822
+ const _titleInfoCache = new Map<
823
+ string,
824
+ { idx: number | null; dataLen: number; result: TitleInfo | null }
825
+ >()
826
+
827
+ function getSubPaneTitleInfo(paneId: string): TitleInfo | null {
828
+ const pane = subPanes.value.find((p) => p.id === paneId)
829
+ if (!pane) return null
830
+
831
+ const data = chartRef.value?.getData()
832
+ if (!data || data.length === 0) return null
833
+
834
+ const idx = crosshairIdx.value
835
+ const dataLen = data.length
836
+
837
+ // ?????crosshairIdx ? dataLen ???
838
+ const cached = _titleInfoCache.get(paneId)
839
+ if (cached && cached.idx === idx && cached.dataLen === dataLen) {
840
+ return cached.result
841
+ }
842
+
843
+ const config = SUB_PANE_INDICATOR_CONFIGS[pane.indicatorId]
844
+ const params = pane.params as Record<string, number>
845
+ const pluginHost = chartRef.value?.plugin
846
+ const result = pluginHost ? config.getTitleInfo(data, idx, params, pluginHost, paneId) : null
847
+
848
+ _titleInfoCache.set(paneId, { idx, dataLen, result })
849
+ return result
850
+ }
851
+
852
+ // ??????????? Facade API?
853
+ function handleIndicatorToggle(indicatorId: string, active: boolean) {
854
+ const chart = chartRef.value
855
+ if (!chart) return
856
+
857
+ // ??????
858
+ const mainIndicatorIds = [
859
+ 'MA',
860
+ 'BOLL',
861
+ 'EXPMA',
862
+ 'ENE',
863
+ 'WMA',
864
+ 'DEMA',
865
+ 'TEMA',
866
+ 'HMA',
867
+ 'KAMA',
868
+ 'SAR',
869
+ 'SUPERTREND',
870
+ 'KELTNER',
871
+ 'DONCHIAN',
872
+ 'ICHIMOKU',
873
+ 'PIVOT',
874
+ 'FIB',
875
+ 'STRUCTURE',
876
+ 'ZONES',
877
+ ]
878
+ if (mainIndicatorIds.includes(indicatorId)) {
879
+ const existingIndicator = mainActiveIndicators.value.find((id) => id === indicatorId)
880
+
881
+ if (active && !existingIndicator) {
882
+ // ??????
883
+ chart.addIndicator(indicatorId, 'main', indicatorParams.value[indicatorId])
884
+ mainActiveIndicators.value.push(indicatorId)
885
+ } else if (!active && existingIndicator) {
886
+ // ??????
887
+ const instanceId = indicatorId.toUpperCase()
888
+ chart.removeIndicator(instanceId)
889
+ mainActiveIndicators.value = mainActiveIndicators.value.filter((id) => id !== indicatorId)
890
+ }
891
+ return
892
+ }
893
+
894
+ // ??????
895
+ if (SUB_PANE_INDICATORS.includes(indicatorId as SubIndicatorType)) {
896
+ if (active) {
897
+ // ?????????? pane???
898
+ const existingPane = subPanes.value.find((p) => p.indicatorId === indicatorId)
899
+ if (existingPane) return
900
+
901
+ // ????????
902
+ if (subPanes.value.length >= maxSubPanes) return
903
+
904
+ // ???? API ??????
905
+ const paneId = chart.addIndicator(indicatorId, 'sub', indicatorParams.value[indicatorId])
906
+ if (paneId) {
907
+ // ?? paneTitle ???
908
+ mountSubPaneTitle(paneId, indicatorId as SubIndicatorType)
909
+ // ??????
910
+ subPanes.value.push({
911
+ id: paneId,
912
+ indicatorId: indicatorId as SubIndicatorType,
913
+ params: { ...indicatorParams.value[indicatorId] },
914
+ })
915
+ } else if (subPanes.value.length > 0) {
916
+ // ???????????????????
917
+ const lastPane = subPanes.value[subPanes.value.length - 1]
918
+ switchSubIndicator(lastPane.id, indicatorId as SubIndicatorType)
919
+ }
920
+ } else {
921
+ // ??????????? pane
922
+ const panesToRemove = subPanes.value.filter((p) => p.indicatorId === indicatorId)
923
+ panesToRemove.forEach((pane) => {
924
+ chart.removeIndicator(pane.id)
925
+ unmountSubPaneTitle(pane.id)
926
+ })
927
+ subPanes.value = subPanes.value.filter((p) => p.indicatorId !== indicatorId)
928
+ }
929
+ scheduleRender()
930
+ }
931
+ }
932
+
933
+ // ??????????
934
+ function updateMainIndicatorLegendConfig() {
935
+ chartRef.value?.updateRendererConfig('mainIndicatorLegend', {
936
+ indicators: {
937
+ MA: {
938
+ enabled: activeIndicators.value.includes('MA'),
939
+ params: indicatorParams.value['MA'] || {},
940
+ },
941
+ BOLL: {
942
+ enabled: activeIndicators.value.includes('BOLL'),
943
+ params: indicatorParams.value['BOLL'] || {},
944
+ },
945
+ EXPMA: {
946
+ enabled: activeIndicators.value.includes('EXPMA'),
947
+ params: indicatorParams.value['EXPMA'] || {},
948
+ },
949
+ ENE: {
950
+ enabled: activeIndicators.value.includes('ENE'),
951
+ params: indicatorParams.value['ENE'] || {},
952
+ },
953
+ },
954
+ })
955
+ }
956
+
957
+ // ????????
958
+ function handleUpdateParams(indicatorId: string, params: Record<string, unknown>) {
959
+ // ??????
960
+ indicatorParams.value[indicatorId] = params
961
+
962
+ // ???????? - ??Chart API
963
+ if (
964
+ indicatorId === 'MA' ||
965
+ indicatorId === 'BOLL' ||
966
+ indicatorId === 'EXPMA' ||
967
+ indicatorId === 'ENE'
968
+ ) {
969
+ chartRef.value?.updateMainIndicatorParams(
970
+ indicatorId,
971
+ params as Record<string, number | boolean | string>,
972
+ )
973
+ scheduleRender()
974
+ return
975
+ }
976
+
977
+ if (SUB_PANE_INDICATORS.includes(indicatorId as SubIndicatorType)) {
978
+ subPanes.value
979
+ .filter((p) => p.indicatorId === indicatorId)
980
+ .forEach((pane) => {
981
+ chartRef.value?.updateSubPaneParams(pane.id, params)
982
+ pane.params = { ...params }
983
+ })
984
+ scheduleRender()
985
+ return
986
+ }
987
+
988
+ scheduleRender()
989
+ }
990
+
991
+ function handleReorderSubIndicators(orderedIndicatorIds: string[]) {
992
+ if (!orderedIndicatorIds.length || subPanes.value.length <= 1) return
993
+
994
+ const validOrder = orderedIndicatorIds.filter((id): id is SubIndicatorType =>
995
+ SUB_PANE_INDICATORS.includes(id as SubIndicatorType),
996
+ )
997
+ if (!validOrder.length) return
998
+
999
+ const paneByIndicator = new Map(subPanes.value.map((pane) => [pane.indicatorId, pane] as const))
1000
+ const nextSubPanes: SubPaneSlot[] = []
1001
+
1002
+ for (const indicatorId of validOrder) {
1003
+ const pane = paneByIndicator.get(indicatorId)
1004
+ if (pane) {
1005
+ nextSubPanes.push(pane)
1006
+ paneByIndicator.delete(indicatorId)
1007
+ }
1008
+ }
1009
+
1010
+ if (nextSubPanes.length === 0) return
1011
+
1012
+ for (const pane of subPanes.value) {
1013
+ if (paneByIndicator.has(pane.indicatorId)) {
1014
+ nextSubPanes.push(pane)
1015
+ paneByIndicator.delete(pane.indicatorId)
1016
+ }
1017
+ }
1018
+
1019
+ const currentSubIds = subPanes.value.map((p) => p.id)
1020
+ const nextSubIds = nextSubPanes.map((p) => p.id)
1021
+ if (currentSubIds.join('|') === nextSubIds.join('|')) return
1022
+
1023
+ subPanes.value = nextSubPanes
1024
+
1025
+ // activeIndicators ? computed ???????????
1026
+
1027
+ const chart = chartRef.value
1028
+ if (!chart) return
1029
+ chart.updatePaneLayout(buildPaneLayoutIntent())
1030
+ }
1031
+
1032
+ /* ??????? Vue ????????zoom ??????? */
1033
+ const axisHostWidth = computed(() => props.rightAxisWidth + props.priceLabelWidth)
1034
+
1035
+ const TRAILING_DRAWING_SLOTS_VAL = TRAILING_DRAWING_SLOTS
1036
+
1037
+ const totalWidth = store.computed.totalWidth
1038
+
1039
+ // ??? Chart ???? scrollLeft ??????
1040
+
1041
+ function scrollToRight() {
1042
+ const container = containerRef.value
1043
+ const chart = chartRef.value
1044
+ if (!container || !chart) return
1045
+
1046
+ const dataLength = chart.getData()?.length ?? 0
1047
+ if (dataLength === 0) return
1048
+
1049
+ const dpr = chart.getCurrentDpr()
1050
+ const { unitPx, startXPx } = getPhysicalKLineConfig(kWidth.value, kGap.value, dpr)
1051
+
1052
+ // ??????K????????? TRAILING_DRAWING_SLOTS?
1053
+ const lastKLineEndPx = (startXPx + dataLength * unitPx) / dpr
1054
+
1055
+ // ?????????
1056
+ const maxScrollLeft = Math.max(0, container.scrollWidth - container.clientWidth)
1057
+
1058
+ // ???????????????K??????
1059
+ const targetScrollLeft = Math.min(
1060
+ maxScrollLeft,
1061
+ Math.max(0, lastKLineEndPx - container.clientWidth),
1062
+ )
1063
+
1064
+ container.scrollLeft = Math.round(targetScrollLeft * dpr) / dpr
1065
+ scheduleRender()
1066
+ }
1067
+
1068
+ /* ?????????? Chart facade API? */
1069
+ function applyZoomToLevel(targetLevel: number, anchorX?: number) {
1070
+ const chart = chartRef.value
1071
+ if (!chart) return
1072
+ chart.zoomToLevel(targetLevel, anchorX)
1073
+ }
1074
+
1075
+ defineExpose({
1076
+ scheduleRender,
1077
+ scrollToRight,
1078
+ addSubPane,
1079
+ removeSubPane,
1080
+ switchSubIndicator,
1081
+ clearAllSubPanes,
1082
+ get plugin() {
1083
+ return chartRef.value?.plugin
1084
+ },
1085
+
1086
+ // Zoom Level API?Vue SSOT?
1087
+ zoomToLevel: applyZoomToLevel,
1088
+ zoomIn: (anchorX?: number) => applyZoomToLevel(zoomLevel.value + 1, anchorX),
1089
+ zoomOut: (anchorX?: number) => applyZoomToLevel(zoomLevel.value - 1, anchorX),
1090
+ getZoomLevel: () => zoomLevel.value,
1091
+ getZoomLevelCount: () => chartRef.value?.getZoomLevelCount() ?? 10,
1092
+ })
1093
+
1094
+ // ==================== onMounted ???? ====================
1095
+
1096
+ function setupWheelHandler(container: HTMLDivElement): (e: WheelEvent) => void {
1097
+ const onWheelHandler = (e: WheelEvent) => {
1098
+ e.preventDefault()
1099
+ const chart = chartRef.value
1100
+ if (!chart) return
1101
+
1102
+ // ?? Chart facade API ??????
1103
+ chart.handleWheelEvent(e)
1104
+ }
1105
+ container.addEventListener('wheel', onWheelHandler, { passive: false })
1106
+ return onWheelHandler
1107
+ }
1108
+
1109
+ function initChart(
1110
+ container: HTMLDivElement,
1111
+ canvasLayer: HTMLDivElement,
1112
+ rightAxisLayer: HTMLDivElement,
1113
+ xAxisCanvas: HTMLCanvasElement,
1114
+ ): Chart {
1115
+ const chart = new Chart(
1116
+ { container, canvasLayer, rightAxisLayer, xAxisCanvas },
1117
+ {
1118
+ yPaddingPx: props.yPaddingPx,
1119
+ rightAxisWidth: props.rightAxisWidth,
1120
+ bottomAxisHeight: props.bottomAxisHeight,
1121
+ priceLabelWidth: props.priceLabelWidth,
1122
+ minKWidth: props.minKWidth,
1123
+ maxKWidth: props.maxKWidth,
1124
+ panes: [{ id: 'main', ratio: 1 }],
1125
+ paneGap: 0,
1126
+ zoomLevels: props.zoomLevels,
1127
+ initialZoomLevel: props.initialZoomLevel,
1128
+ },
1129
+ )
1130
+ return chart
1131
+ }
1132
+
1133
+ function setupChartCallbacks(chart: Chart): void {
1134
+ // ???setOnViewportChange ???? viewport signal ????
1135
+
1136
+ chart.setOnPaneLayoutChange(() => {
1137
+ // ????????????????????????
1138
+ invalidateContainerRectCache()
1139
+ const renderers = chart.getPaneRenderers()
1140
+ const borderTop = containerRef.value
1141
+ ? parseInt(getComputedStyle(containerRef.value).borderTopWidth) || 0
1142
+ : 0
1143
+ paneSeparatorLines.value = renderers.slice(0, -1).map((renderer) => {
1144
+ const pane = renderer.getPane()
1145
+ return {
1146
+ id: pane.id,
1147
+ top: pane.top + pane.height + borderTop,
1148
+ }
1149
+ })
1150
+ })
1151
+
1152
+ // ?? paneRatios signal???? Vue store
1153
+ const unsubscribePaneRatios = chart.paneRatios.subscribe(() => {
1154
+ const ratios = chart.paneRatios.peek()
1155
+ store.actions.setPaneRatios({ ...ratios })
1156
+ })
1157
+
1158
+ // ?? viewport signal??????DPR?width ??? scrollLeft ??
1159
+ const unsubscribeViewport = chart.viewport.subscribe(() => {
1160
+ const vp = chart.viewport.peek()
1161
+
1162
+ // DPR ?????? store
1163
+ if (store.state.viewportDpr !== vp.dpr) {
1164
+ store.actions.setViewportDpr(vp.dpr)
1165
+ }
1166
+
1167
+ // ViewWidth ?????? store
1168
+ if (store.state.viewWidth !== vp.plotWidth) {
1169
+ store.actions.setViewWidth(vp.plotWidth)
1170
+ }
1171
+
1172
+ // ???? zoom state ? Vue store?Chart ? SSOT?
1173
+ if (
1174
+ store.state.zoomLevel !== vp.zoomLevel ||
1175
+ store.state.kWidth !== vp.kWidth ||
1176
+ store.state.kGap !== vp.kGap
1177
+ ) {
1178
+ store.actions.setZoomState(vp.zoomLevel, vp.kWidth, vp.kGap)
1179
+ }
1180
+
1181
+ // ? nextTick ??? desiredScrollLeft
1182
+ const desiredLeft = vp.desiredScrollLeft
1183
+ if (desiredLeft !== undefined && desiredLeft !== containerRef.value?.scrollLeft) {
1184
+ invalidateContainerRectCache()
1185
+ nextTick(() => {
1186
+ const c = containerRef.value
1187
+ if (!c) return
1188
+ const maxScrollLeft = Math.max(0, c.scrollWidth - c.clientWidth)
1189
+ const clampedScrollLeft = Math.min(Math.max(0, desiredLeft), maxScrollLeft)
1190
+ const dpr = chart.getCurrentDpr()
1191
+ c.scrollLeft = Math.round(clampedScrollLeft * dpr) / dpr
1192
+ })
1193
+ }
1194
+ })
1195
+
1196
+ // ?? data signal??? onDataChange ??
1197
+ const unsubscribeData = chart.data.subscribe(() => {
1198
+ const data = chart.data.peek()
1199
+ store.actions.setDataLength(data.length)
1200
+ store.actions.bumpDataVersion()
1201
+ })
1202
+
1203
+ // ?? theme signal???? CSS data-theme
1204
+ const unsubscribeTheme = chart.theme.subscribe(() => {
1205
+ const theme = chart.theme.peek()
1206
+ chartTheme.value = theme
1207
+ })
1208
+
1209
+ // ?? unsubscribe ??????
1210
+ onUnmounted(() => {
1211
+ unsubscribeViewport()
1212
+ unsubscribeData()
1213
+ unsubscribePaneRatios()
1214
+ unsubscribeTheme()
1215
+ })
1216
+ }
1217
+
1218
+ function applyInitialSettings(chart: Chart): void {
1219
+ const initialSettings = toolbarRef.value?.getSettings() ?? { showVolumePriceMarkers: true }
1220
+ chart.updateSettings(initialSettings)
1221
+
1222
+ if (initialSettings.performanceTest10kKlines) {
1223
+ const testData = generate10kKLineData()
1224
+ console.time('updateData-10k')
1225
+ chart.updateData(testData)
1226
+ console.timeEnd('updateData-10k')
1227
+ }
1228
+ }
1229
+
1230
+ function setupDrawingController(chart: Chart): void {
1231
+ drawingController.value = new DrawingInteractionController(chart)
1232
+ drawingController.value.setCallbacks({
1233
+ onDrawingCreated: (drawing) => {
1234
+ store.actions.setDrawings([...store.state.drawings, drawing])
1235
+ store.actions.setSelectedDrawingId(drawing.id)
1236
+ },
1237
+ onToolChange: () => {},
1238
+ onDrawingSelected: (drawing) => {
1239
+ store.actions.setSelectedDrawingId(drawing?.id ?? null)
1240
+ },
1241
+ })
1242
+ }
1243
+
1244
+ function setupInteractionCallbacks(chart: Chart): void {
1245
+ chart.interaction.setTooltipAnchorPositioning(useAnchorPositioning.value)
1246
+ chart.interaction.setOnInteractionChange((snapshot) => {
1247
+ interactionState.value = snapshot
1248
+ })
1249
+
1250
+ chart.interaction.setOnPinchZoom((delta, centerClientX) => {
1251
+ if (!chart) return
1252
+ const container = containerRef.value
1253
+ if (!container) return
1254
+ // centerClientX ? clientX????????????
1255
+ const rect = container.getBoundingClientRect()
1256
+ const centerX = centerClientX - rect.left
1257
+ chart.handlePinchZoom(delta, centerX)
1258
+ })
1259
+
1260
+ interactionState.value = chart.interaction.getInteractionSnapshot()
1261
+ store.actions.setViewportDpr(chart.getCurrentDpr())
1262
+ chart.resize()
1263
+ }
1264
+
1265
+ /** ??????????? ? Chart API ??? */
1266
+ function setupSemanticController(chart: Chart): void {
1267
+ __setDataFetcher(props.dataFetcher)
1268
+ semanticController.value = new SemanticChartController(chart)
1269
+
1270
+ semanticController.value.on('config:error', (error) => {
1271
+ console.error('Semantic config error:', error)
1272
+ })
1273
+
1274
+ // config:ready ? Chart ???????Vue ????
1275
+ semanticController.value.on('config:ready', () => {
1276
+ initIndicatorsFromConfig()
1277
+ syncSubPanesFromChart()
1278
+ nextTick(() => scrollToRight())
1279
+ })
1280
+ // ?????????
1281
+ semanticController.value.applyConfig(props.semanticConfig).then((result) => {
1282
+ if (result && !result.success) {
1283
+ console.error('Semantic config apply failed:', result.errors)
1284
+ }
1285
+ })
1286
+ }
1287
+
1288
+ onMounted(() => {
1289
+ useAnchorPositioning.value = false
1290
+
1291
+ const container = containerRef.value
1292
+ const canvasLayer = canvasLayerRef.value
1293
+ const rightAxisLayer = rightAxisLayerRef.value
1294
+ const xAxisCanvas = xAxisCanvasRef.value
1295
+ if (!container || !canvasLayer || !rightAxisLayer || !xAxisCanvas) return
1296
+
1297
+ // 1) ?????passive:false ???????
1298
+ const onWheelHandler = setupWheelHandler(container)
1299
+
1300
+ // 2) ?? Chart ??????????
1301
+ const chart = initChart(container, canvasLayer, rightAxisLayer, xAxisCanvas)
1302
+ chartRef.value = chart
1303
+
1304
+ // 3) ?? / ???? / ??????
1305
+ setupChartCallbacks(chart)
1306
+
1307
+ // 4) ?? zoom ???Vue SSOT ? Chart?
1308
+ chart.applyRenderState(store.state.kWidth, store.state.kGap, store.state.zoomLevel)
1309
+
1310
+ // 5) ????????????????
1311
+ applyInitialSettings(chart)
1312
+
1313
+ // 6) ??????????/????
1314
+ setupDrawingController(chart)
1315
+
1316
+ // 7) ???????????????
1317
+ setupInteractionCallbacks(chart)
1318
+
1319
+ // 8) ??????????????????
1320
+ setupSemanticController(chart)
1321
+
1322
+ // ? onUnmounted ?? wheel ??
1323
+ ;(chart as any).__onWheel = onWheelHandler
1324
+ })
1325
+
1326
+ onUnmounted(() => {
1327
+ const chart = chartRef.value
1328
+ if (chart) {
1329
+ const onWheel = (chart as any).__onWheel as
1330
+ | ((this: HTMLElement, ev: WheelEvent) => any)
1331
+ | undefined
1332
+ const container = containerRef.value
1333
+ if (onWheel && container) container.removeEventListener('wheel', onWheel)
1334
+ chart.destroy()
1335
+ }
1336
+ chartRef.value = null
1337
+ drawingController.value = null
1338
+ })
1339
+
1340
+ // kWidth/kGap ? zoomLevel ??????? props ????
1341
+ // ????????????? expose ? zoomToLevel/zoomIn/zoomOut ??
1342
+
1343
+ // ?? yPaddingPx ??
1344
+ watch(
1345
+ () => props.yPaddingPx,
1346
+ (newVal) => {
1347
+ chartRef.value?.updateOptions({ yPaddingPx: newVal })
1348
+ },
1349
+ )
1350
+
1351
+ // ?? semanticConfig ?????????
1352
+ watch(
1353
+ () => props.semanticConfig,
1354
+ async (newConfig, oldConfig) => {
1355
+ if (newConfig && newConfig !== oldConfig) {
1356
+ const result = await semanticController.value?.applyConfig(newConfig)
1357
+ if (result && !result.success) {
1358
+ console.error('Semantic config apply failed:', result.errors)
1359
+ }
1360
+ }
1361
+ },
1362
+ { deep: true },
1363
+ )
1364
+ </script>
1365
+
1366
+ <style scoped>
1367
+ .chart-wrapper {
1368
+ --kmap-height: var(--kmap-chart-height, 100%);
1369
+ --kmap-width: var(--kmap-chart-width, 100%);
1370
+
1371
+ --chart-bg: #ffffff;
1372
+ --chart-bg-secondary: #f8f9fa;
1373
+ --chart-border: #e5e7eb;
1374
+ --chart-border-active: #3b82f6;
1375
+ --chart-text: #374151;
1376
+ --chart-text-secondary: #6b7280;
1377
+
1378
+ display: flex;
1379
+ align-items: center;
1380
+ justify-content: center;
1381
+ width: var(--kmap-width);
1382
+ height: var(--kmap-height);
1383
+ min-height: 300px;
1384
+ flex-direction: column;
1385
+ }
1386
+
1387
+ .chart-wrapper[data-theme='dark'] {
1388
+ --chart-bg: #1a1a2e;
1389
+ --chart-bg-secondary: #16162a;
1390
+ --chart-border: #2d2d44;
1391
+ --chart-border-active: #60a5fa;
1392
+ --chart-text: #e5e7eb;
1393
+ --chart-text-secondary: #9ca3af;
1394
+ }
1395
+
1396
+ .chart-stage {
1397
+ width: 95%;
1398
+ height: 85%;
1399
+ min-height: 255px;
1400
+ display: flex;
1401
+ align-items: stretch;
1402
+ gap: 8px;
1403
+ }
1404
+
1405
+ .chart-main {
1406
+ flex: 1 1 auto;
1407
+ min-width: 0;
1408
+ height: 100%;
1409
+ display: flex;
1410
+ align-items: stretch;
1411
+ gap: 0;
1412
+ position: relative;
1413
+ }
1414
+
1415
+ .pane-separator-layer {
1416
+ position: absolute;
1417
+ inset: 0;
1418
+ pointer-events: none;
1419
+ z-index: 20;
1420
+ }
1421
+
1422
+ .pane-separator-line {
1423
+ position: absolute;
1424
+ left: 0;
1425
+ right: 0;
1426
+ height: 0;
1427
+ border-top: 1px solid var(--chart-border);
1428
+ opacity: 1;
1429
+ box-sizing: border-box;
1430
+ transition:
1431
+ border-top-color 120ms ease,
1432
+ border-top-width 120ms ease,
1433
+ margin-top 120ms ease,
1434
+ opacity 120ms ease;
1435
+ }
1436
+
1437
+ .pane-separator-line.is-active {
1438
+ border-top-color: var(--chart-border-active);
1439
+ border-top-width: 2px;
1440
+ margin-top: -1px;
1441
+ }
1442
+
1443
+ .chart-stage.is-resizing-pane,
1444
+ .chart-stage.is-hovering-pane-separator {
1445
+ cursor: ns-resize;
1446
+ }
1447
+
1448
+ .chart-stage.is-hovering-kline {
1449
+ cursor: pointer;
1450
+ }
1451
+
1452
+ .chart-stage.is-hovering-right-axis {
1453
+ cursor: ns-resize;
1454
+ }
1455
+
1456
+ .chart-stage.is-dragging {
1457
+ cursor: grabbing;
1458
+ }
1459
+
1460
+ .chart-container {
1461
+ position: relative;
1462
+ flex: 1 1 auto;
1463
+ overflow-x: auto;
1464
+ overflow-y: hidden;
1465
+ height: 100%;
1466
+ min-height: inherit;
1467
+ scrollbar-width: none;
1468
+ -ms-overflow-style: none;
1469
+ border: 1px solid var(--chart-border);
1470
+ border-right: 0;
1471
+ border-radius: 6px 0 0 6px;
1472
+ box-sizing: border-box;
1473
+ background: var(--chart-bg);
1474
+
1475
+ -webkit-touch-callout: none;
1476
+ -webkit-user-select: none;
1477
+ user-select: none;
1478
+ touch-action: none;
1479
+ }
1480
+
1481
+ .chart-container::-webkit-scrollbar {
1482
+ display: none;
1483
+ }
1484
+
1485
+ .right-axis-host {
1486
+ position: relative;
1487
+ flex: 0 0 auto;
1488
+ height: 100%;
1489
+ min-height: inherit;
1490
+ box-sizing: border-box;
1491
+ background: var(--chart-bg);
1492
+ overflow: visible;
1493
+ border: 1px solid var(--chart-border);
1494
+ border-top-right-radius: 6px;
1495
+ border-bottom-right-radius: 6px;
1496
+
1497
+ -webkit-touch-callout: none;
1498
+ -webkit-user-select: none;
1499
+ user-select: none;
1500
+ touch-action: none;
1501
+ }
1502
+
1503
+ .scroll-content {
1504
+ height: 100%;
1505
+ min-height: inherit;
1506
+ position: relative;
1507
+ }
1508
+
1509
+ .canvas-layer {
1510
+ position: sticky;
1511
+ left: 0;
1512
+ top: 0;
1513
+ pointer-events: none;
1514
+ }
1515
+
1516
+ .tooltip-layer {
1517
+ position: absolute;
1518
+ inset: 0;
1519
+ pointer-events: none;
1520
+ z-index: 30;
1521
+ }
1522
+
1523
+ .tooltip-anchor {
1524
+ position: absolute;
1525
+ width: 1px;
1526
+ height: 1px;
1527
+ pointer-events: none;
1528
+ }
1529
+
1530
+ .tooltip-anchor.kline-tooltip-anchor.use-anchor {
1531
+ anchor-name: --kline-tooltip-anchor;
1532
+ }
1533
+
1534
+ .tooltip-anchor.marker-tooltip-anchor.use-anchor {
1535
+ anchor-name: --marker-tooltip-anchor;
1536
+ }
1537
+
1538
+ @media (max-width: 768px), (max-height: 640px) {
1539
+ .chart-stage {
1540
+ gap: 6px;
1541
+ }
1542
+ }
1543
+ </style>
1544
+
1545
+ <style>
1546
+ .plot-canvas {
1547
+ position: absolute;
1548
+ left: 0;
1549
+ top: 0;
1550
+ display: block;
1551
+ }
1552
+
1553
+ .right-axis {
1554
+ position: absolute;
1555
+ display: block;
1556
+ left: 0;
1557
+ }
1558
+
1559
+ .x-axis-canvas {
1560
+ position: absolute;
1561
+ left: 0;
1562
+ bottom: 0;
1563
+ display: block;
1564
+ z-index: 10;
1565
+ }
1566
+
1567
+ .right-axis {
1568
+ z-index: 15;
1569
+ }
1570
+ </style>