@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,155 @@
1
+ <template>
2
+ <div
3
+ v-if="marker"
4
+ :ref="onRef"
5
+ class="marker-tooltip"
6
+ :class="[{ 'use-anchor': useAnchor }, anchorPlacementClass]"
7
+ :style="useAnchor ? undefined : { left: `${pos.x + 12}px`, top: `${pos.y + 12}px` }"
8
+ >
9
+ <div class="marker-tooltip__title">{{ title }}</div>
10
+ <div v-if="hasMetadata" class="marker-tooltip__content">
11
+ <div v-for="(value, key) in metadata" :key="key" class="row">
12
+ <span>{{ key }}</span>
13
+ <span>{{ formatValue(value) }}</span>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { computed } from 'vue'
21
+ import type { ComponentPublicInstance } from 'vue'
22
+
23
+ interface MarkerEntity {
24
+ markerType: string
25
+ metadata: Record<string, unknown>
26
+ }
27
+
28
+ interface CustomMarkerEntity {
29
+ date: string
30
+ shape: string
31
+ label?: { text: string }
32
+ metadata: Record<string, unknown>
33
+ }
34
+
35
+ const MARKER_TYPE_LABELS: Record<string, string> = {
36
+ support: '支撑位',
37
+ resistance: '阻力位',
38
+ top: '顶部',
39
+ bottom: '底部',
40
+ }
41
+
42
+ const props = defineProps<{
43
+ marker: MarkerEntity | CustomMarkerEntity | null
44
+ pos: { x: number; y: number }
45
+ useAnchor?: boolean
46
+ anchorPlacement?: 'right-bottom' | 'left-bottom'
47
+ setEl?: (el: HTMLDivElement | null) => void
48
+ }>()
49
+
50
+ const useAnchor = computed(() => props.useAnchor === true)
51
+ const anchorPlacementClass = computed(() =>
52
+ props.anchorPlacement === 'left-bottom' ? 'anchor-left-bottom' : 'anchor-right-bottom',
53
+ )
54
+
55
+ function onRef(el: Element | ComponentPublicInstance | null) {
56
+ props.setEl?.(el as HTMLDivElement | null)
57
+ }
58
+
59
+ const isCustomMarker = computed(() => {
60
+ return props.marker && 'date' in props.marker
61
+ })
62
+
63
+ const title = computed(() => {
64
+ if (!props.marker) return ''
65
+ if (isCustomMarker.value) {
66
+ const custom = props.marker as CustomMarkerEntity
67
+ return custom.label?.text || custom.shape
68
+ }
69
+ const standard = props.marker as MarkerEntity
70
+ return MARKER_TYPE_LABELS[standard.markerType] || standard.markerType
71
+ })
72
+
73
+ const metadata = computed(() => {
74
+ if (!props.marker) return {}
75
+ if (isCustomMarker.value) {
76
+ const custom = props.marker as CustomMarkerEntity
77
+ return {
78
+ 日期: custom.date,
79
+ ...custom.metadata,
80
+ }
81
+ }
82
+ return (props.marker as MarkerEntity).metadata
83
+ })
84
+
85
+ const hasMetadata = computed(() => {
86
+ return Object.keys(metadata.value).length > 0
87
+ })
88
+
89
+ function formatValue(value: unknown): string {
90
+ if (typeof value === 'number') {
91
+ return value.toFixed(2)
92
+ }
93
+ return String(value)
94
+ }
95
+ </script>
96
+
97
+ <style scoped>
98
+ .marker-tooltip {
99
+ position: absolute;
100
+ z-index: 10;
101
+ min-width: 180px;
102
+ max-width: 260px;
103
+ padding: 10px 12px;
104
+ border-radius: 8px;
105
+ background: rgba(255, 255, 255, 0.92);
106
+ border: 1px solid rgba(0, 0, 0, 0.12);
107
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
108
+ color: rgba(0, 0, 0, 0.78);
109
+ font-size: 12px;
110
+ line-height: 1.4;
111
+ pointer-events: none;
112
+ backdrop-filter: blur(6px);
113
+ }
114
+
115
+ .marker-tooltip__title {
116
+ display: flex;
117
+ justify-content: space-between;
118
+ gap: 10px;
119
+ font-weight: 600;
120
+ margin-bottom: 6px;
121
+ }
122
+
123
+ .marker-tooltip__content {
124
+ display: grid;
125
+ grid-template-columns: 1fr;
126
+ gap: 2px;
127
+ }
128
+
129
+ .marker-tooltip__content .row {
130
+ display: flex;
131
+ justify-content: space-between;
132
+ gap: 10px;
133
+ }
134
+
135
+ .marker-tooltip__content .row span:first-child {
136
+ color: rgba(0, 0, 0, 0.56);
137
+ }
138
+
139
+ @supports (anchor-name: --kmap-anchor) and (position-anchor: --kmap-anchor) {
140
+ .marker-tooltip.use-anchor {
141
+ position: absolute;
142
+ position-anchor: --marker-tooltip-anchor;
143
+ left: anchor(left);
144
+ top: anchor(top);
145
+ }
146
+
147
+ .marker-tooltip.use-anchor.anchor-right-bottom {
148
+ transform: translate(12px, 12px);
149
+ }
150
+
151
+ .marker-tooltip.use-anchor.anchor-left-bottom {
152
+ transform: translate(calc(-100% - 12px), 12px);
153
+ }
154
+ }
155
+ </style>
@@ -0,0 +1,7 @@
1
+ export { default as DrawingStyleToolbar } from './DrawingStyleToolbar.vue'
2
+ export { default as IndicatorParams } from './IndicatorParams.vue'
3
+ export { default as IndicatorSelector } from './IndicatorSelector.vue'
4
+ export { default as KLineChartVue } from './KLineChart.vue'
5
+ export { default as KLineTooltip } from './KLineTooltip.vue'
6
+ export { default as LeftToolbar } from './LeftToolbar.vue'
7
+ export { default as MarkerTooltip } from './MarkerTooltip.vue'
@@ -0,0 +1,18 @@
1
+ import { inject, provide, computed, type Ref, type InjectionKey } from 'vue'
2
+
3
+ const FULLSCREEN_TARGET_KEY: InjectionKey<Ref<HTMLElement | null>> =
4
+ Symbol('fullscreen-teleport-target')
5
+
6
+ export function provideFullscreenTeleportTarget(targetRef: Ref<HTMLElement | null>): void {
7
+ provide(FULLSCREEN_TARGET_KEY, targetRef)
8
+ }
9
+
10
+ export function useFullscreenTeleportTarget() {
11
+ // null = no provider in ancestor tree (degraded scenario)
12
+ const targetRef = inject(FULLSCREEN_TARGET_KEY, null)
13
+
14
+ return computed<HTMLElement | string>(() => {
15
+ // targetRef null → no provider; targetRef.value null → container not mounted yet
16
+ return targetRef?.value ?? 'body'
17
+ })
18
+ }
@@ -0,0 +1,296 @@
1
+ type MetricEntry = {
2
+ count: number
3
+ totalTime: number
4
+ }
5
+
6
+ type MetricBucket = Record<string, MetricEntry>
7
+
8
+ type CanvasProfilerMetrics = {
9
+ ctxMethods: MetricBucket
10
+ ctxProps: MetricBucket
11
+ canvasProps: MetricBucket
12
+ ctxMethodSources: Record<string, MetricBucket>
13
+ }
14
+
15
+ type CanvasProfilerReportRow = {
16
+ name: string
17
+ count: number
18
+ totalTime: string
19
+ averageTime: string
20
+ }
21
+
22
+ declare global {
23
+ interface Window {
24
+ __KMAP_CANVAS_PROFILER_INSTALLED__?: boolean
25
+ __KMAP_CANVAS_PROFILER_METRICS__?: CanvasProfilerMetrics
26
+ showCanvasReport?: () => void
27
+ resetCanvasReport?: () => void
28
+ }
29
+ }
30
+
31
+ function createBucket(): MetricBucket {
32
+ return Object.create(null) as MetricBucket
33
+ }
34
+
35
+ function createMetrics(): CanvasProfilerMetrics {
36
+ return {
37
+ ctxMethods: createBucket(),
38
+ ctxProps: createBucket(),
39
+ canvasProps: createBucket(),
40
+ ctxMethodSources: Object.create(null) as Record<string, MetricBucket>,
41
+ }
42
+ }
43
+
44
+ function record(bucket: MetricBucket, name: string, duration: number): void {
45
+ const entry = bucket[name] ??= { count: 0, totalTime: 0 }
46
+ entry.count += 1
47
+ entry.totalTime += duration
48
+ }
49
+
50
+ function recordMethodSource(metrics: CanvasProfilerMetrics, methodName: string, source: string, duration: number): void {
51
+ const bucket = metrics.ctxMethodSources[methodName] ??= createBucket()
52
+ record(bucket, source, duration)
53
+ }
54
+
55
+ function toRows(bucket: MetricBucket): CanvasProfilerReportRow[] {
56
+ return Object.entries(bucket)
57
+ .filter(([, entry]) => entry.count > 0)
58
+ .map(([name, entry]) => ({
59
+ name,
60
+ count: entry.count,
61
+ totalTime: entry.totalTime.toFixed(2),
62
+ averageTime: (entry.totalTime / entry.count).toFixed(4),
63
+ }))
64
+ .sort((a, b) => Number(b.totalTime) - Number(a.totalTime))
65
+ }
66
+
67
+ /** 全局开关:是否启用 Canvas Profiler 插桩 */
68
+ let isProfilerEnabled = false
69
+
70
+ /** 保存原始方法用于卸载 */
71
+ const originalMethods = new Map<string, (...args: unknown[]) => unknown>()
72
+ const originalSetters = new Map<string, PropertyDescriptor>()
73
+
74
+ /** 启用/禁用 Canvas Profiler */
75
+ export function setCanvasProfilerEnabled(enabled: boolean): void {
76
+ isProfilerEnabled = enabled
77
+ if (enabled) {
78
+ if (typeof window !== 'undefined' && !window.__KMAP_CANVAS_PROFILER_INSTALLED__) {
79
+ installCanvasProfiler()
80
+ }
81
+ } else {
82
+ uninstallCanvasProfiler()
83
+ }
84
+ }
85
+
86
+ /** 获取 Canvas Profiler 启用状态 */
87
+ export function isCanvasProfilerEnabled(): boolean {
88
+ return isProfilerEnabled
89
+ }
90
+
91
+ function getRelevantStackFrame(): string {
92
+ // 如果未启用,直接返回空字符串避免开销
93
+ if (!isProfilerEnabled) return 'disabled'
94
+
95
+ const stack = new Error().stack
96
+ if (!stack) return 'unknown'
97
+
98
+ const frames = stack
99
+ .split('\n')
100
+ .map((line) => line.trim())
101
+ .filter(Boolean)
102
+
103
+ for (const frame of frames) {
104
+ if (
105
+ frame.includes('canvasProfiler')
106
+ || frame.includes('CanvasRenderingContext2D')
107
+ || frame === 'Error'
108
+ ) {
109
+ continue
110
+ }
111
+
112
+ const normalized = frame.replace(/^at\s+/, '')
113
+ const srcMatch = normalized.match(/((?:src|node_modules)[^\s)]+):(\d+):(\d+)/)
114
+ if (srcMatch) {
115
+ return `${srcMatch[1]}:${srcMatch[2]}`
116
+ }
117
+
118
+ const parenMatch = normalized.match(/\(([^)]+):(\d+):(\d+)\)$/)
119
+ if (parenMatch) {
120
+ return `${parenMatch[1]}:${parenMatch[2]}`
121
+ }
122
+
123
+ return normalized
124
+ }
125
+
126
+ return 'unknown'
127
+ }
128
+
129
+ function wrapMethod(
130
+ proto: object,
131
+ name: string,
132
+ metrics: CanvasProfilerMetrics,
133
+ options?: { captureSource?: boolean }
134
+ ): void {
135
+ const key = `${proto.constructor?.name ?? 'proto'}:${name}`
136
+ if (originalMethods.has(key)) return
137
+
138
+ const original = Reflect.get(proto, name)
139
+ if (typeof original !== 'function') return
140
+
141
+ originalMethods.set(key, original as (...args: unknown[]) => unknown)
142
+
143
+ Reflect.set(proto, name, function (this: object, ...args: unknown[]) {
144
+ // 快速路径:如果 profiler 未启用,直接调用原方法
145
+ if (!isProfilerEnabled) {
146
+ return original.apply(this, args)
147
+ }
148
+ const source = options?.captureSource ? getRelevantStackFrame() : null
149
+ const start = performance.now()
150
+ const result = original.apply(this, args)
151
+ const duration = performance.now() - start
152
+ record(metrics.ctxMethods, name, duration)
153
+ if (source) {
154
+ recordMethodSource(metrics, name, source, duration)
155
+ }
156
+ return result
157
+ })
158
+ }
159
+
160
+ function wrapSetter(proto: object, prop: string, bucket: MetricBucket): void {
161
+ const descriptor = Object.getOwnPropertyDescriptor(proto, prop)
162
+ if (!descriptor?.set || !descriptor.configurable) return
163
+
164
+ const key = `${proto.constructor?.name ?? 'proto'}:${prop}`
165
+ if (originalSetters.has(key)) return
166
+
167
+ originalSetters.set(key, descriptor)
168
+
169
+ Object.defineProperty(proto, prop, {
170
+ configurable: true,
171
+ enumerable: descriptor.enumerable ?? false,
172
+ get: descriptor.get,
173
+ set(this: object, value: unknown) {
174
+ // 快速路径:如果 profiler 未启用,直接调用原 setter
175
+ if (!isProfilerEnabled) {
176
+ descriptor.set!.call(this, value)
177
+ return
178
+ }
179
+ const start = performance.now()
180
+ descriptor.set!.call(this, value)
181
+ record(bucket, prop, performance.now() - start)
182
+ },
183
+ })
184
+ }
185
+
186
+ export function installCanvasProfiler(): void {
187
+ if (typeof window === 'undefined') return
188
+ if (window.__KMAP_CANVAS_PROFILER_INSTALLED__) return
189
+
190
+ const ctxProto = CanvasRenderingContext2D?.prototype
191
+ const canvasProto = HTMLCanvasElement?.prototype
192
+ if (!ctxProto || !canvasProto) return
193
+
194
+ const metrics = createMetrics()
195
+
196
+ wrapMethod(ctxProto, 'fillText', metrics, { captureSource: true })
197
+ wrapMethod(ctxProto, 'measureText', metrics, { captureSource: true })
198
+ wrapMethod(ctxProto, 'drawImage', metrics)
199
+ wrapMethod(ctxProto, 'save', metrics)
200
+ wrapMethod(ctxProto, 'restore', metrics)
201
+ wrapMethod(ctxProto, 'clip', metrics)
202
+ wrapMethod(ctxProto, 'setTransform', metrics)
203
+ wrapMethod(ctxProto, 'scale', metrics)
204
+
205
+ wrapSetter(ctxProto, 'font', metrics.ctxProps)
206
+ wrapSetter(ctxProto, 'filter', metrics.ctxProps)
207
+ wrapSetter(ctxProto, 'shadowBlur', metrics.ctxProps)
208
+ wrapSetter(ctxProto, 'lineWidth', metrics.ctxProps)
209
+
210
+ wrapSetter(canvasProto, 'width', metrics.canvasProps)
211
+ wrapSetter(canvasProto, 'height', metrics.canvasProps)
212
+
213
+ window.__KMAP_CANVAS_PROFILER_METRICS__ = metrics
214
+ window.__KMAP_CANVAS_PROFILER_INSTALLED__ = true
215
+
216
+ window.showCanvasReport = () => {
217
+ const currentMetrics = window.__KMAP_CANVAS_PROFILER_METRICS__
218
+ if (!currentMetrics) return
219
+
220
+ console.group('[kmap] Canvas profiler report')
221
+ console.log('ctx methods')
222
+ console.table(toRows(currentMetrics.ctxMethods))
223
+ console.log('ctx props')
224
+ console.table(toRows(currentMetrics.ctxProps))
225
+ console.log('canvas props')
226
+ console.table(toRows(currentMetrics.canvasProps))
227
+
228
+ for (const methodName of ['fillText', 'measureText']) {
229
+ const bucket = currentMetrics.ctxMethodSources[methodName]
230
+ if (!bucket) continue
231
+ console.log(`${methodName} sources`)
232
+ console.table(toRows(bucket).slice(0, 20))
233
+ }
234
+
235
+ console.groupEnd()
236
+ }
237
+
238
+ window.resetCanvasReport = () => {
239
+ window.__KMAP_CANVAS_PROFILER_METRICS__ = createMetrics()
240
+ }
241
+
242
+ console.info('[kmap] Canvas profiler enabled. Use window.showCanvasReport() and window.resetCanvasReport().')
243
+ }
244
+
245
+ /** 卸载 Canvas Profiler,恢复原始方法 */
246
+ export function uninstallCanvasProfiler(): void {
247
+ if (typeof window === 'undefined') return
248
+ if (!window.__KMAP_CANVAS_PROFILER_INSTALLED__) return
249
+
250
+ const ctxProto = CanvasRenderingContext2D?.prototype
251
+ const canvasProto = HTMLCanvasElement?.prototype
252
+
253
+ // 恢复原始方法
254
+ originalMethods.forEach((original, key) => {
255
+ const match = key.match(/^(.+):(.+)$/)
256
+ if (!match) return
257
+ const [, protoName, methodName] = match
258
+
259
+ let proto: object | null = null
260
+ if (protoName === 'CanvasRenderingContext2D') {
261
+ proto = ctxProto
262
+ } else if (protoName === 'HTMLCanvasElement') {
263
+ proto = canvasProto
264
+ }
265
+ if (proto) {
266
+ Reflect.set(proto, methodName, original)
267
+ }
268
+ })
269
+ originalMethods.clear()
270
+
271
+ // 恢复原始 setter
272
+ originalSetters.forEach((descriptor, key) => {
273
+ const match = key.match(/^(.+):(.+)$/)
274
+ if (!match) return
275
+ const [, protoName, propName] = match
276
+
277
+ let proto: object | null = null
278
+ if (protoName === 'CanvasRenderingContext2D') {
279
+ proto = ctxProto
280
+ } else if (protoName === 'HTMLCanvasElement') {
281
+ proto = canvasProto
282
+ }
283
+ if (proto && descriptor.configurable) {
284
+ Object.defineProperty(proto, propName, descriptor)
285
+ }
286
+ })
287
+ originalSetters.clear()
288
+
289
+ // 清理全局状态
290
+ window.__KMAP_CANVAS_PROFILER_INSTALLED__ = false
291
+ window.__KMAP_CANVAS_PROFILER_METRICS__ = undefined
292
+ window.showCanvasReport = undefined
293
+ window.resetCanvasReport = undefined
294
+
295
+ console.info('[kmap] Canvas profiler disabled.')
296
+ }