@363045841yyt/klinechart 0.1.0 → 0.1.2

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 (43) hide show
  1. package/README.md +286 -48
  2. package/dist/App.vue.d.ts +1 -1
  3. package/dist/components/KLineChart.vue.d.ts +19 -6
  4. package/dist/components/KLineTooltip.vue.d.ts +15 -0
  5. package/dist/composables/useKLineInteraction.d.ts +50 -0
  6. package/dist/composables/useKLineRenderer.d.ts +49 -0
  7. package/dist/core/chart.d.ts +145 -0
  8. package/dist/core/controller/interaction.d.ts +50 -0
  9. package/dist/core/layout/pane.d.ts +72 -0
  10. package/dist/core/renderers/candle.d.ts +6 -0
  11. package/dist/core/renderers/crosshair.d.ts +13 -0
  12. package/dist/core/renderers/crosshairLabels.d.ts +17 -0
  13. package/dist/core/renderers/extremaMarkers.d.ts +8 -0
  14. package/dist/core/renderers/grid.d.ts +6 -0
  15. package/dist/core/renderers/gridLines.d.ts +7 -0
  16. package/dist/core/renderers/lastPrice.d.ts +5 -0
  17. package/dist/core/renderers/ma.d.ts +11 -0
  18. package/dist/core/renderers/maLegend.d.ts +9 -0
  19. package/dist/core/renderers/paneBorder.d.ts +14 -0
  20. package/dist/core/renderers/paneSeparator.d.ts +10 -0
  21. package/dist/core/renderers/paneTitle.d.ts +6 -0
  22. package/dist/core/renderers/timeAxis.d.ts +15 -0
  23. package/dist/core/renderers/yAxis.d.ts +11 -0
  24. package/dist/core/scale/price.d.ts +11 -0
  25. package/dist/core/scale/priceScale.d.ts +18 -0
  26. package/dist/core/viewport/viewport.d.ts +31 -0
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.js +1036 -121
  29. package/dist/klinechart.css +1 -1
  30. package/dist/types/price.d.ts +14 -0
  31. package/dist/utils/dateFormat.d.ts +68 -0
  32. package/dist/utils/kLineDraw/MA.d.ts +8 -0
  33. package/dist/utils/kLineDraw/axis.d.ts +113 -0
  34. package/dist/utils/kLineDraw/grid.d.ts +30 -0
  35. package/dist/utils/{draw → kLineDraw}/kLine.d.ts +6 -8
  36. package/dist/utils/kLineDraw/pixelAlign.d.ts +48 -0
  37. package/dist/utils/kline/format.d.ts +14 -0
  38. package/dist/utils/kline/ma.d.ts +2 -0
  39. package/dist/utils/kline/viewport.d.ts +10 -0
  40. package/dist/utils/logger.d.ts +1 -1
  41. package/dist/utils/priceToY.d.ts +6 -0
  42. package/package.json +57 -55
  43. package/dist/utils/draw/MA.d.ts +0 -5
package/dist/index.js CHANGED
@@ -1,84 +1,862 @@
1
- import { createElementBlock, createElementVNode, defineComponent, nextTick, onMounted, onUnmounted, openBlock, ref, watch } from "vue";
2
- function getKLineTrend(e) {
3
- return e.open > e.close ? "down" : e.open < e.close ? "up" : "flat";
4
- }
5
- function priceToY(e, n, r, i, a, o) {
6
- let s = n - r || 1, c = (e - r) / s;
7
- return a + Math.max(1, i - a - o) * (1 - c);
8
- }
9
- var tagStyle = {
10
- info: "background:#1677ff;color:#fff;border:1px solid #1677ff;",
11
- success: "background:#389e0d;color:#fff;border:1px solid #389e0d;",
12
- warn: "background:#d46b08;color:#fff;border:1px solid #d46b08;",
13
- error: "background:#cf1322;color:#fff;border:1px solid #cf1322;"
14
- }, leftBase = "padding:4px 8px;font-weight:600;border-radius:6px;", rightBase = "padding:4px 10px;border:1px solid #d9d9d9;background:#fff;color:#111;border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;";
15
- function tagLog(e, n, r) {
16
- console.log(`%c${n}%c`, `${leftBase}${tagStyle[e]}`, rightBase, r);
1
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, ref, shallowRef, toDisplayString, unref, watch, withModifiers } from "vue";
2
+ function formatDateToYYYYMMDD(r) {
3
+ let s = new Intl.DateTimeFormat("zh-CN", {
4
+ timeZone: "Asia/Shanghai",
5
+ year: "numeric",
6
+ month: "2-digit",
7
+ day: "2-digit"
8
+ }).formatToParts(new Date(r)).reduce((r, s) => (s.type !== "literal" && (r[s.type] = s.value), r), {});
9
+ return `${s.year}-${s.month}-${s.day}`;
10
+ }
11
+ function formatMonthOrYear(r) {
12
+ let s = new Date(r), c = s.getFullYear(), l = s.getMonth() + 1;
13
+ return l === 1 ? {
14
+ text: String(c),
15
+ isYear: !0
16
+ } : {
17
+ text: String(l).padStart(2, "0"),
18
+ isYear: !1
19
+ };
20
+ }
21
+ function monthKey(r) {
22
+ let s = new Date(r);
23
+ return `${s.getFullYear()}-${s.getMonth()}`;
24
+ }
25
+ const formatShanghaiDate = formatDateToYYYYMMDD, formatYMDShanghai = formatDateToYYYYMMDD, NEUTRAL_COLOR = "rgba(0, 0, 0, 0.78)";
26
+ function getUpDownColor(r) {
27
+ return r > 0 ? "rgba(214, 10, 34, 1)" : r < 0 ? "rgba(3, 123, 102, 1)" : NEUTRAL_COLOR;
28
+ }
29
+ function formatWanYi(r, s = 2) {
30
+ let c = Math.abs(r);
31
+ return c >= 1e8 ? `${(r / 1e8).toFixed(s)}亿` : c >= 1e4 ? `${(r / 1e4).toFixed(s)}万` : `${Math.round(r)}`;
32
+ }
33
+ function formatSignedNumber(r, s = 2) {
34
+ return `${r > 0 ? "+" : ""}${r.toFixed(s)}`;
35
+ }
36
+ function formatPercent(r, s = 2) {
37
+ return `${r.toFixed(s)}%`;
38
+ }
39
+ function formatSignedPercent(r, s = 2) {
40
+ return `${r > 0 ? "+" : ""}${r.toFixed(s)}%`;
41
+ }
42
+ function calcOpenColor(r, s) {
43
+ let c = s?.close ?? r.open;
44
+ return getUpDownColor(r.open - c);
45
+ }
46
+ function calcCloseColor(r) {
47
+ return getUpDownColor(r.close - r.open);
48
+ }
49
+ function calcChangeColor(r) {
50
+ return typeof r.changePercent == "number" ? getUpDownColor(r.changePercent) : typeof r.changeAmount == "number" ? getUpDownColor(r.changeAmount) : NEUTRAL_COLOR;
51
+ }
52
+ var _hoisted_1$1 = { class: "kline-tooltip__title" }, _hoisted_2 = { key: 0 }, _hoisted_3 = { class: "kline-tooltip__grid" }, _hoisted_4 = { class: "row" }, _hoisted_5 = { class: "row" }, _hoisted_6 = { class: "row" }, _hoisted_7 = { class: "row" }, _hoisted_8 = {
53
+ key: 0,
54
+ class: "row"
55
+ }, _hoisted_9 = {
56
+ key: 1,
57
+ class: "row"
58
+ }, _hoisted_10 = {
59
+ key: 2,
60
+ class: "row"
61
+ }, _hoisted_11 = {
62
+ key: 3,
63
+ class: "row"
64
+ }, _hoisted_12 = {
65
+ key: 4,
66
+ class: "row"
67
+ }, _hoisted_13 = {
68
+ key: 5,
69
+ class: "row"
70
+ }, KLineTooltip_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
71
+ __name: "KLineTooltip",
72
+ props: {
73
+ k: {},
74
+ index: {},
75
+ data: {},
76
+ pos: {},
77
+ setEl: { type: Function }
78
+ },
79
+ setup(s) {
80
+ let d = s;
81
+ function f(r) {
82
+ d.setEl?.(r);
83
+ }
84
+ let p = computed(() => {
85
+ let r = d.k;
86
+ if (!r) return NEUTRAL_COLOR;
87
+ let s = d.index;
88
+ return calcOpenColor(r, typeof s == "number" && s > 0 ? d.data[s - 1] : void 0);
89
+ }), h = computed(() => {
90
+ let r = d.k;
91
+ return r ? calcCloseColor(r) : NEUTRAL_COLOR;
92
+ }), g = computed(() => {
93
+ let r = d.k;
94
+ return r ? calcChangeColor(r) : NEUTRAL_COLOR;
95
+ });
96
+ return (r, d) => s.k ? (openBlock(), createElementBlock("div", {
97
+ key: 0,
98
+ ref: f,
99
+ class: "kline-tooltip",
100
+ style: normalizeStyle({
101
+ left: `${s.pos.x}px`,
102
+ top: `${s.pos.y}px`
103
+ })
104
+ }, [createElementVNode("div", _hoisted_1$1, [s.k.stockCode ? (openBlock(), createElementBlock("span", _hoisted_2, toDisplayString(s.k.stockCode), 1)) : createCommentVNode("", !0), createElementVNode("span", null, toDisplayString(unref(formatShanghaiDate)(s.k.timestamp)), 1)]), createElementVNode("div", _hoisted_3, [
105
+ createElementVNode("div", _hoisted_4, [d[0] ||= createElementVNode("span", null, "开", -1), createElementVNode("span", { style: normalizeStyle({ color: p.value }) }, toDisplayString(s.k.open.toFixed(2)), 5)]),
106
+ createElementVNode("div", _hoisted_5, [d[1] ||= createElementVNode("span", null, "高", -1), createElementVNode("span", null, toDisplayString(s.k.high.toFixed(2)), 1)]),
107
+ createElementVNode("div", _hoisted_6, [d[2] ||= createElementVNode("span", null, "低", -1), createElementVNode("span", null, toDisplayString(s.k.low.toFixed(2)), 1)]),
108
+ createElementVNode("div", _hoisted_7, [d[3] ||= createElementVNode("span", null, "收", -1), createElementVNode("span", { style: normalizeStyle({ color: h.value }) }, toDisplayString(s.k.close.toFixed(2)), 5)]),
109
+ typeof s.k.volume == "number" ? (openBlock(), createElementBlock("div", _hoisted_8, [d[4] ||= createElementVNode("span", null, "成交量", -1), createElementVNode("span", null, toDisplayString(unref(formatWanYi)(s.k.volume)), 1)])) : createCommentVNode("", !0),
110
+ typeof s.k.turnover == "number" ? (openBlock(), createElementBlock("div", _hoisted_9, [d[5] ||= createElementVNode("span", null, "成交额", -1), createElementVNode("span", null, toDisplayString(unref(formatWanYi)(s.k.turnover)), 1)])) : createCommentVNode("", !0),
111
+ typeof s.k.amplitude == "number" ? (openBlock(), createElementBlock("div", _hoisted_10, [d[6] ||= createElementVNode("span", null, "振幅", -1), createElementVNode("span", null, toDisplayString(s.k.amplitude), 1)])) : createCommentVNode("", !0),
112
+ typeof s.k.changePercent == "number" ? (openBlock(), createElementBlock("div", _hoisted_11, [d[7] ||= createElementVNode("span", null, "涨跌幅", -1), createElementVNode("span", { style: normalizeStyle({ color: g.value }) }, toDisplayString(unref(formatSignedPercent)(s.k.changePercent)), 5)])) : createCommentVNode("", !0),
113
+ typeof s.k.changeAmount == "number" ? (openBlock(), createElementBlock("div", _hoisted_12, [d[8] ||= createElementVNode("span", null, "涨跌额", -1), createElementVNode("span", { style: normalizeStyle({ color: g.value }) }, toDisplayString(unref(formatSignedNumber)(s.k.changeAmount)), 5)])) : createCommentVNode("", !0),
114
+ typeof s.k.turnoverRate == "number" ? (openBlock(), createElementBlock("div", _hoisted_13, [d[9] ||= createElementVNode("span", null, "换手率", -1), createElementVNode("span", null, toDisplayString(unref(formatPercent)(s.k.turnoverRate)), 1)])) : createCommentVNode("", !0)
115
+ ])], 4)) : createCommentVNode("", !0);
116
+ }
117
+ }), __plugin_vue_export_helper_default = (r, s) => {
118
+ let c = r.__vccOpts || r;
119
+ for (let [r, l] of s) c[r] = l;
120
+ return c;
121
+ }, KLineTooltip_default = /* @__PURE__ */ __plugin_vue_export_helper_default(KLineTooltip_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-8d05acf2"]]);
122
+ function getVisibleRange(r, s, c, l, u) {
123
+ let d = c + l;
124
+ return {
125
+ start: Math.max(0, Math.floor(r / d) - 1),
126
+ end: Math.min(u, Math.ceil((r + s) / d) + 1)
127
+ };
128
+ }
129
+ function getVisiblePriceRange(r, s, c) {
130
+ let l = -Infinity, u = Infinity;
131
+ for (let d = s; d < c && d < r.length; d++) {
132
+ let s = r[d];
133
+ s && (s.high > l && (l = s.high), s.low < u && (u = s.low));
134
+ }
135
+ return !Number.isFinite(l) || !Number.isFinite(u) ? {
136
+ maxPrice: 100,
137
+ minPrice: 0
138
+ } : {
139
+ maxPrice: l,
140
+ minPrice: u
141
+ };
142
+ }
143
+ var PriceScale = class {
144
+ range = {
145
+ maxPrice: 100,
146
+ minPrice: 0
147
+ };
148
+ height = 1;
149
+ paddingTop = 0;
150
+ paddingBottom = 0;
151
+ setRange(r) {
152
+ this.range = r;
153
+ }
154
+ setHeight(r) {
155
+ this.height = Math.max(1, r);
156
+ }
157
+ setPadding(r, s) {
158
+ this.paddingTop = Math.max(0, r), this.paddingBottom = Math.max(0, s);
159
+ }
160
+ getRange() {
161
+ return this.range;
162
+ }
163
+ getPaddingTop() {
164
+ return this.paddingTop;
165
+ }
166
+ getPaddingBottom() {
167
+ return this.paddingBottom;
168
+ }
169
+ priceToY(r) {
170
+ let { maxPrice: s, minPrice: c } = this.range, l = s - c || 1, u = (r - c) / l, d = Math.max(1, this.height - this.paddingTop - this.paddingBottom);
171
+ return this.paddingTop + d * (1 - u);
172
+ }
173
+ }, Pane = class {
174
+ id;
175
+ top = 0;
176
+ height = 0;
177
+ priceRange = {
178
+ maxPrice: 100,
179
+ minPrice: 0
180
+ };
181
+ yAxis = new PriceScale();
182
+ renderers = [];
183
+ constructor(r) {
184
+ this.id = r;
185
+ }
186
+ setLayout(r, s) {
187
+ this.top = r, this.height = Math.max(1, s), this.yAxis.setHeight(this.height);
188
+ }
189
+ setPadding(r, s) {
190
+ this.yAxis.setPadding(r, s);
191
+ }
192
+ addRenderer(r) {
193
+ this.renderers.push(r);
194
+ }
195
+ updateRange(r, s) {
196
+ this.priceRange = getVisiblePriceRange(r, s.start, s.end), this.yAxis.setRange(this.priceRange);
197
+ }
198
+ }, InteractionController = class {
199
+ chart;
200
+ isDragging = !1;
201
+ dragStartX = 0;
202
+ scrollStartX = 0;
203
+ isTouchSession = !1;
204
+ crosshairPos = null;
205
+ crosshairIndex = null;
206
+ hoveredIndex = null;
207
+ activePaneId = null;
208
+ tooltipPos = {
209
+ x: 0,
210
+ y: 0
211
+ };
212
+ tooltipSize = {
213
+ width: 220,
214
+ height: 180
215
+ };
216
+ constructor(r) {
217
+ this.chart = r;
218
+ }
219
+ onPointerDown(r) {
220
+ if (r.isPrimary === !1) return;
221
+ this.isTouchSession = r.pointerType === "touch", this.isDragging = !0, this.updateHoverFromPoint(r.clientX, r.clientY);
222
+ let s = this.chart.getDom().container;
223
+ this.dragStartX = r.clientX, this.scrollStartX = s.scrollLeft, this.chart.scheduleDraw();
224
+ }
225
+ onPointerMove(r) {
226
+ if (r.isPrimary === !1) return;
227
+ let s = this.chart.getDom().container;
228
+ if (this.isDragging) {
229
+ let c = this.dragStartX - r.clientX;
230
+ s.scrollLeft = this.scrollStartX + c, this.updateHoverFromPoint(r.clientX, r.clientY), this.chart.scheduleDraw();
231
+ return;
232
+ }
233
+ this.updateHoverFromPoint(r.clientX, r.clientY), this.chart.scheduleDraw();
234
+ }
235
+ onPointerUp(r) {
236
+ r.isPrimary !== !1 && (this.isDragging = !1);
237
+ }
238
+ onPointerLeave(r) {
239
+ r.isPrimary !== !1 && (this.isDragging = !1, this.isTouchSession = !1, this.clearHover(), this.chart.scheduleDraw());
240
+ }
241
+ onMouseDown(r) {
242
+ if (this.isTouchSession || r.button !== 0) return;
243
+ let s = this.chart.getDom().container;
244
+ this.isDragging = !0, this.dragStartX = r.clientX, this.scrollStartX = s.scrollLeft, this.updateHoverFromPoint(r.clientX, r.clientY), this.chart.scheduleDraw(), r.preventDefault();
245
+ }
246
+ onMouseMove(r) {
247
+ if (this.isTouchSession) return;
248
+ let s = this.chart.getDom().container;
249
+ if (this.isDragging) {
250
+ let c = this.dragStartX - r.clientX;
251
+ s.scrollLeft = this.scrollStartX + c;
252
+ return;
253
+ }
254
+ this.updateHover(r), this.chart.scheduleDraw();
255
+ }
256
+ onMouseUp() {
257
+ this.isTouchSession || (this.isDragging = !1);
258
+ }
259
+ onMouseLeave() {
260
+ this.isTouchSession || (this.isDragging = !1, this.clearHover(), this.chart.scheduleDraw());
261
+ }
262
+ onScroll() {
263
+ this.clearHover(), this.chart.scheduleDraw();
264
+ }
265
+ onWheel(r) {
266
+ let s = this.chart.getDom().container, c = s.getBoundingClientRect(), l = r.clientX - c.left, u = s.scrollLeft;
267
+ this.clearHover(), this.chart.zoomAt(l, u, r.deltaY);
268
+ }
269
+ setTooltipSize(r) {
270
+ this.tooltipSize = r;
271
+ }
272
+ clearHover() {
273
+ this.crosshairPos = null, this.crosshairIndex = null, this.hoveredIndex = null, this.activePaneId = null;
274
+ }
275
+ updateHover(r) {
276
+ this.updateHoverFromPoint(r.clientX, r.clientY);
277
+ }
278
+ updateHoverFromPoint(r, s) {
279
+ let c = this.chart.getDom().container, l = c.getBoundingClientRect(), u = r - l.left, d = s - l.top, f = this.chart.getOption(), p = Math.max(1, Math.round(l.width)), m = Math.max(1, Math.round(l.height)), h = p - f.rightAxisWidth, g = m - f.bottomAxisHeight;
280
+ if (u < 0 || d < 0 || u > h || d > g) {
281
+ this.clearHover();
282
+ return;
283
+ }
284
+ let _ = c.scrollLeft, v = f.kWidth + f.kGap, y = _ + u - f.kGap;
285
+ if (y < 0) {
286
+ this.clearHover();
287
+ return;
288
+ }
289
+ let b = this.chart.getData(), x = Math.floor(y / v), S = this.chart.getPanes().find((r) => d >= r.top && d <= r.top + r.height);
290
+ if (S ? this.activePaneId = S.id : this.activePaneId = null, x >= 0 && x < (b?.length ?? 0)) {
291
+ this.crosshairIndex = x;
292
+ let r = f.kGap + x * v + f.kWidth / 2 - _;
293
+ this.crosshairPos = {
294
+ x: Math.min(Math.max(r, 0), h),
295
+ y: Math.min(Math.max(d, 0), g)
296
+ };
297
+ } else this.crosshairIndex = null, this.crosshairPos = {
298
+ x: Math.min(Math.max(u, 0), h),
299
+ y: Math.min(Math.max(d, 0), g)
300
+ };
301
+ let C = typeof this.crosshairIndex == "number" ? b[this.crosshairIndex] : void 0;
302
+ if (!C || !S) {
303
+ this.hoveredIndex = null;
304
+ return;
305
+ }
306
+ let w = d - S.top, T = S.yAxis.priceToY(C.open), E = S.yAxis.priceToY(C.close), D = S.yAxis.priceToY(C.high), O = S.yAxis.priceToY(C.low), k = Math.min(T, E), A = Math.max(T, E), j = y - this.crosshairIndex * v, M = f.kWidth / 2, N = w >= k && w <= A && j >= 0 && j <= f.kWidth, P = Math.abs(j - M) <= 3 && w >= D && w <= O;
307
+ if (!N && !P) {
308
+ this.hoveredIndex = null;
309
+ return;
310
+ }
311
+ this.hoveredIndex = this.crosshairIndex;
312
+ let F = this.tooltipSize.width, I = this.tooltipSize.height, L = u + 14, R = u - 14 - F, z = L + F + 12 <= p ? L : R, B = d + 14, V = Math.max(12, p - F - 12), H = Math.max(12, m - I - 12);
313
+ this.tooltipPos = {
314
+ x: Math.min(Math.max(z, 12), V),
315
+ y: Math.min(Math.max(B, 12), H)
316
+ };
317
+ }
318
+ };
319
+ function priceToY(r, s, c, l, u, d) {
320
+ let f = s - c || 1, p = (r - c) / f;
321
+ return u + Math.max(1, l - u - d) * (1 - p);
322
+ }
323
+ function yToPrice(r, s, c, l, u, d) {
324
+ let f = s - c || 1, p = Math.max(1, l - u - d);
325
+ return c + (1 - (Math.min(Math.max(r, u), u + p) - u) / p) * f;
326
+ }
327
+ function roundToPhysicalPixel(r, s) {
328
+ return Math.round(r * s) / s;
329
+ }
330
+ function alignToPhysicalPixelCenter(r, s) {
331
+ return (Math.floor(r * s) + .5) / s;
332
+ }
333
+ function alignRect(r, s, c, l, u) {
334
+ let d = roundToPhysicalPixel(r, u), f = roundToPhysicalPixel(s, u), p = roundToPhysicalPixel(r + c, u), m = roundToPhysicalPixel(s + l, u);
335
+ return {
336
+ x: d,
337
+ y: f,
338
+ width: Math.max(1 / u, p - d),
339
+ height: Math.max(1 / u, m - f)
340
+ };
341
+ }
342
+ function createVerticalLineRect(r, s, c, l) {
343
+ if (s === c) return null;
344
+ let u = Math.min(s, c), d = Math.max(s, c), f = Math.round(r * l), p = Math.round(u * l), m = Math.round(d * l);
345
+ return {
346
+ x: f / l,
347
+ y: p / l,
348
+ width: 1 / l,
349
+ height: Math.max(1, m - p) / l
350
+ };
351
+ }
352
+ function createHorizontalLineRect(r, s, c, l) {
353
+ if (r === s) return null;
354
+ let u = Math.min(r, s), d = Math.max(r, s), f = Math.round(u * l), p = Math.round(d * l), m = Math.round(c * l);
355
+ return {
356
+ x: f / l,
357
+ y: m / l,
358
+ width: Math.max(1, p - f) / l,
359
+ height: 1 / l
360
+ };
361
+ }
362
+ function drawPriceAxis(r, s) {
363
+ let { x: c, y: l, width: u, height: d, priceRange: f, yPaddingPx: p = 0, dpr: m, ticks: h = 10, bgColor: g = "rgba(255,255,255,0.85)", textColor: _ = "rgba(0,0,0,0.65)", lineColor: v = "rgba(0,0,0,0.12)", fontSize: y = 16, paddingX: b = 12 } = s, x = p, S = Math.max(0, Math.min(x, Math.floor(d / 2) - 1)), { maxPrice: C, minPrice: w } = f, T = C - w, E = T === 0 ? 0 : T / (Math.max(2, h) - 1);
364
+ r.fillStyle = g, r.fillRect(c, l, u, d), r.strokeStyle = v, r.lineWidth = 1, r.beginPath(), r.moveTo(alignToPhysicalPixelCenter(c, m), l), r.lineTo(alignToPhysicalPixelCenter(c, m), l + d), r.stroke(), r.font = `${y}px -apple-system,BlinkMacSystemFont,Trebuchet MS,Roboto,Ubuntu,sans-serif`, r.textBaseline = "middle", r.textAlign = "left";
365
+ let D = c + b;
366
+ for (let s = 0; s < Math.max(2, h); s++) {
367
+ let u = T === 0 ? C : C - E * s, f = Math.round(priceToY(u, C, w, d, S, S) + l);
368
+ r.strokeStyle = v, r.beginPath();
369
+ let p = alignToPhysicalPixelCenter(f, m);
370
+ r.moveTo(c, p), r.lineTo(c + 4, p), r.stroke(), r.fillStyle = _, r.fillText(u.toFixed(2), roundToPhysicalPixel(D, m), roundToPhysicalPixel(f, m));
371
+ }
372
+ }
373
+ function drawCrosshairTimeLabel(r, s) {
374
+ let { x: c, y: l, width: u, height: d, crosshairX: f, timestamp: p, dpr: m, bgColor: h = "rgba(0,0,0,0.55)", textColor: g = "rgba(255,255,255,0.92)", fontSize: _ = 12, paddingX: v = 8, paddingY: y = 4 } = s, b = formatYMDShanghai(p);
375
+ r.save(), r.font = `${_}px -apple-system,BlinkMacSystemFont,Trebuchet MS,Roboto,Ubuntu,sans-serif`, r.textBaseline = "middle", r.textAlign = "center";
376
+ let x = Math.ceil(r.measureText(b).width), S = Math.min(u, x + v * 2), C = Math.min(d, _ + y * 2), w = Math.min(Math.max(f, c + S / 2), c + u - S / 2), T = l + d / 2, E = w - S / 2, D = T - C / 2;
377
+ r.fillStyle = h, r.fillRect(roundToPhysicalPixel(E, m), roundToPhysicalPixel(D, m), roundToPhysicalPixel(S, m), roundToPhysicalPixel(C, m)), r.fillStyle = g, r.fillText(b, roundToPhysicalPixel(w, m), roundToPhysicalPixel(T, m)), r.restore();
378
+ }
379
+ function drawCrosshairPriceLabel(r, s) {
380
+ let { x: c, y: l, width: u, height: d, crosshairY: f, priceRange: p, yPaddingPx: m = 0, dpr: h, bgColor: g = "rgba(0,0,0,0.55)", textColor: _ = "rgba(255,255,255,0.92)", fontSize: v = 12, paddingX: y = 12 } = s, b = Math.max(0, Math.min(m, Math.floor(d / 2) - 1)), { maxPrice: x, minPrice: S } = p, C = yToPrice(f - l, x, S, d, b, b).toFixed(2);
381
+ r.save(), r.font = `${v}px -apple-system,BlinkMacSystemFont,Trebuchet MS,Roboto,Ubuntu,sans-serif`, r.textBaseline = "middle", r.textAlign = "left";
382
+ let w = r.measureText(C), T = v + 6, E = Math.ceil(w.width + y * 2), D = T, O = Math.min(u, E), k = Math.min(Math.max(f, l + D / 2), l + d - D / 2), A = k - D / 2;
383
+ r.fillStyle = g, r.fillRect(roundToPhysicalPixel(c, h), roundToPhysicalPixel(A, h), roundToPhysicalPixel(O, h), roundToPhysicalPixel(D, h)), r.fillStyle = _;
384
+ let j = c + y;
385
+ r.fillText(C, roundToPhysicalPixel(j, h), roundToPhysicalPixel(k, h)), r.restore();
386
+ }
387
+ function drawTimeAxis(r, s) {
388
+ let { x: c, y: l, width: u, height: d, data: f, scrollLeft: p, kWidth: m, kGap: h, startIndex: g, endIndex: _, dpr: v, bgColor: y = "rgba(255,255,255,0.85)", textColor: b = "rgba(0,0,0,0.65)", lineColor: x = "rgba(0,0,0,0.12)", fontSize: S = 16, paddingX: C = 12 } = s, w = m + h;
389
+ r.fillStyle = y, r.fillRect(c, l, u, d), r.strokeStyle = x, r.lineWidth = 1, r.beginPath(), r.moveTo(c, alignToPhysicalPixelCenter(l, v)), r.lineTo(c + u, alignToPhysicalPixelCenter(l, v)), r.stroke(), r.textAlign = "center", r.textBaseline = "middle";
390
+ let D = l + d / 2;
391
+ for (let s = Math.max(g, 1); s < _ && s < f.length; s++) {
392
+ let c = f[s], d = f[s - 1];
393
+ if (!(!c || !d) && monthKey(c.timestamp) !== monthKey(d.timestamp)) {
394
+ let d = h + s * w - p, f = C, m = Math.max(C, u - C);
395
+ if (d >= f && d <= m) {
396
+ let s = Math.min(Math.max(d, f), m);
397
+ r.strokeStyle = x, r.beginPath();
398
+ let u = alignToPhysicalPixelCenter(s, v);
399
+ r.moveTo(u, l), r.lineTo(u, l + 4), r.stroke();
400
+ let { text: p, isYear: h } = formatMonthOrYear(c.timestamp);
401
+ r.fillStyle = b, r.font = `${h ? "bold " : ""}${S}px -apple-system,BlinkMacSystemFont,Trebuchet MS,Roboto,Ubuntu,sans-serif`, r.fillText(p, roundToPhysicalPixel(s, v), roundToPhysicalPixel(D, v));
402
+ }
403
+ }
404
+ }
405
+ }
406
+ function createYAxisRenderer(r) {
407
+ return { draw({ ctx: s, pane: c, dpr: l }) {
408
+ let u = typeof r.ticks == "number" ? r.ticks : Math.max(2, Math.min(8, Math.round(c.height / 80)));
409
+ drawPriceAxis(s, {
410
+ x: r.axisX,
411
+ y: c.top,
412
+ width: r.axisWidth,
413
+ height: c.height,
414
+ priceRange: c.priceRange,
415
+ yPaddingPx: r.yPaddingPx,
416
+ dpr: l,
417
+ ticks: u
418
+ });
419
+ } };
420
+ }
421
+ function drawTimeAxisLayer(r) {
422
+ let { ctx: s, data: c, scrollLeft: l, kWidth: u, kGap: d, startIndex: f, endIndex: p, dpr: m, crosshair: h } = r, g = s.canvas.width / m, _ = s.canvas.height / m;
423
+ if (s.setTransform(1, 0, 0, 1, 0, 0), s.scale(m, m), s.clearRect(0, 0, g, _), drawTimeAxis(s, {
424
+ x: 0,
425
+ y: 0,
426
+ width: g,
427
+ height: _,
428
+ data: c,
429
+ scrollLeft: l,
430
+ kWidth: u,
431
+ kGap: d,
432
+ startIndex: f,
433
+ endIndex: p,
434
+ dpr: m
435
+ }), h && typeof h.index == "number") {
436
+ let r = c[h.index];
437
+ r && drawCrosshairTimeLabel(s, {
438
+ x: 0,
439
+ y: 0,
440
+ width: g,
441
+ height: _,
442
+ crosshairX: h.x,
443
+ timestamp: r.timestamp,
444
+ dpr: m
445
+ });
446
+ }
447
+ }
448
+ function drawCrosshair(r) {
449
+ let { ctx: s, plotWidth: c, plotHeight: l, dpr: u, x: d, y: f } = r;
450
+ s.save(), s.beginPath(), s.rect(0, 0, c, l), s.clip(), s.fillStyle = "rgba(0,0,0,0.28)";
451
+ let p = createVerticalLineRect(d, 0, l, u);
452
+ p && s.fillRect(p.x, p.y, p.width, p.height);
453
+ let m = createHorizontalLineRect(0, c, f, u);
454
+ m && s.fillRect(m.x, m.y, m.width, m.height), s.restore();
455
+ }
456
+ function drawCrosshairPriceLabelForPane(r) {
457
+ let { ctx: s, pane: c, axisWidth: l, dpr: u, crosshairY: d, yPaddingPx: f } = r;
458
+ drawCrosshairPriceLabel(s, {
459
+ x: 0,
460
+ y: c.top,
461
+ width: l,
462
+ height: c.height,
463
+ crosshairY: d,
464
+ priceRange: c.priceRange,
465
+ yPaddingPx: f,
466
+ dpr: u
467
+ });
468
+ }
469
+ function calcMAAtIndex(r, s, c) {
470
+ if (s < c - 1) return;
471
+ let l = 0;
472
+ for (let u = 0; u < c; u++) {
473
+ let c = r[s - u];
474
+ if (!c) return;
475
+ l += c.close;
476
+ }
477
+ return l / c;
478
+ }
479
+ const MA5_COLOR = "rgba(255, 193, 37, 1)", MA10_COLOR = "rgba(190, 131, 12, 1)", MA20_COLOR = "rgba(69, 112, 249, 1)";
480
+ function drawMALine(r, s, c, l, u, d, f, p, m, h) {
481
+ if (s.length < m) return;
482
+ let g = l, _ = c.yPaddingPx ?? 0, v = Math.max(0, Math.min(_, Math.floor(g / 2) - 1)), y = v, b = v, x, S;
483
+ if (p) x = p.maxPrice, S = p.minPrice;
484
+ else {
485
+ x = -Infinity, S = Infinity;
486
+ for (let r = d; r < f && r < s.length; r++) {
487
+ let c = s[r];
488
+ c && (c.high > x && (x = c.high), c.low < S && (S = c.low));
489
+ }
490
+ }
491
+ if (!Number.isFinite(x) || !Number.isFinite(S)) return;
492
+ let C = c.kWidth + c.kGap;
493
+ r.strokeStyle = h, r.lineWidth = 1, r.lineJoin = "round", r.lineCap = "round", r.beginPath();
494
+ let w = !0, T = Math.max(d, m - 1);
495
+ for (let l = T; l < f && l < s.length; l++) {
496
+ let d = 0;
497
+ for (let r = 0; r < m; r++) {
498
+ let c = s[l - r];
499
+ if (!c) return;
500
+ d += c.close;
501
+ }
502
+ let f = d / m, p = c.kGap + l * C + c.kWidth / 2, h = priceToY(f, x, S, g, y, b), _ = alignToPhysicalPixelCenter(p, u), v = alignToPhysicalPixelCenter(h, u);
503
+ w ? (r.moveTo(_, v), w = !1) : r.lineTo(_, v);
504
+ }
505
+ r.stroke();
506
+ }
507
+ function drawMA5Line(r, s, c, l, u = 1, d = 0, f = s.length, p) {
508
+ drawMALine(r, s, c, l, u, d, f, p, 5, MA5_COLOR);
509
+ }
510
+ function drawMA10Line(r, s, c, l, u = 1, d = 0, f = s.length, p) {
511
+ drawMALine(r, s, c, l, u, d, f, p, 10, MA10_COLOR);
512
+ }
513
+ function drawMA20Line(r, s, c, l, u = 1, d = 0, f = s.length, p) {
514
+ drawMALine(r, s, c, l, u, d, f, p, 20, MA20_COLOR);
515
+ }
516
+ function drawMALegend(r) {
517
+ let { ctx: s, data: c, endIndex: l, showMA: u } = r;
518
+ if (!c.length) return;
519
+ s.save(), s.font = "12px Arial", s.textBaseline = "top", s.textAlign = "left";
520
+ let d = Math.min(l - 1, c.length - 1), f = [];
521
+ if (u.ma5 && f.push({
522
+ label: "MA5",
523
+ color: MA5_COLOR,
524
+ value: calcMAAtIndex(c, d, 5)
525
+ }), u.ma10 && f.push({
526
+ label: "MA10",
527
+ color: MA10_COLOR,
528
+ value: calcMAAtIndex(c, d, 10)
529
+ }), u.ma20 && f.push({
530
+ label: "MA20",
531
+ color: MA20_COLOR,
532
+ value: calcMAAtIndex(c, d, 20)
533
+ }), f.length > 0) {
534
+ let r = s.measureText("均线").width + 10;
535
+ for (let c of f) {
536
+ let l = typeof c.value == "number" ? ` ${c.value.toFixed(2)}` : "";
537
+ r += s.measureText(`${c.label}${l}`).width + 10;
538
+ }
539
+ r -= 10;
540
+ let c = Math.ceil(r + 16);
541
+ s.fillStyle = "rgba(255,255,255,0.85)", s.fillRect(8, 8, c, 24);
542
+ let l = 16;
543
+ s.fillStyle = "#333", s.fillText("均线", l, 14), l += s.measureText("均线").width + 10;
544
+ for (let r of f) {
545
+ let c = typeof r.value == "number" ? ` ${r.value.toFixed(2)}` : "", u = `${r.label}${c}`;
546
+ s.fillStyle = r.color, s.fillText(u, l, 14), l += s.measureText(u).width + 10;
547
+ }
548
+ }
549
+ s.restore();
550
+ }
551
+ function drawPaneTitle(r) {
552
+ let { ctx: s, dpr: c, paneTop: l, title: u } = r;
553
+ s.save(), s.font = "12px Arial", s.textBaseline = "top", s.textAlign = "left", s.fillStyle = "rgba(0,0,0,0.55)";
554
+ let d = l + 8;
555
+ s.fillText(u, 8, d), s.restore();
556
+ }
557
+ function drawPaneBorders(r) {
558
+ let { ctx: s, dpr: c, width: l, panes: u, color: d = "rgba(0,0,0,0.12)", omitOuterTop: f = !1, omitOuterRight: p = !1, omitOuterBottom: m = !1, omitOuterLeft: h = !1 } = r;
559
+ if (!u.length) return;
560
+ s.save(), s.strokeStyle = d, s.lineWidth = 1;
561
+ let g = alignToPhysicalPixelCenter(0, c), _ = alignToPhysicalPixelCenter(l, c), v = Infinity, y = -Infinity;
562
+ for (let r of u) v = Math.min(v, r.top), y = Math.max(y, r.top + r.height);
563
+ v = Number.isFinite(v) ? v : 0, y = Number.isFinite(y) ? y : 0;
564
+ for (let r of u) {
565
+ let l = alignToPhysicalPixelCenter(r.top, c), u = alignToPhysicalPixelCenter(r.top + r.height, c), d = Math.abs(r.top - v) < 1e-6, b = Math.abs(r.top + r.height - y) < 1e-6;
566
+ s.beginPath(), f && d || (s.moveTo(g, l), s.lineTo(_, l)), p || (s.moveTo(_, l), s.lineTo(_, u)), m && b || (s.moveTo(g, u), s.lineTo(_, u)), h || (s.moveTo(g, l), s.lineTo(g, u)), s.stroke();
567
+ }
568
+ s.restore();
569
+ }
570
+ var Chart = class {
571
+ dom;
572
+ opt;
573
+ data = [];
574
+ raf = null;
575
+ viewport = null;
576
+ panes = [];
577
+ interaction;
578
+ onZoomChange;
579
+ setOnZoomChange(r) {
580
+ this.onZoomChange = r;
581
+ }
582
+ constructor(r, s) {
583
+ this.dom = r, this.opt = s, this.interaction = new InteractionController(this), this.initPanes();
584
+ }
585
+ getPanes() {
586
+ return this.panes;
587
+ }
588
+ setPaneRenderers(r, s) {
589
+ let c = this.panes.find((s) => s.id === r);
590
+ if (c) {
591
+ c.renderers.length = 0;
592
+ for (let r of s) c.renderers.push(r);
593
+ this.scheduleDraw();
594
+ }
595
+ }
596
+ getDom() {
597
+ return this.dom;
598
+ }
599
+ getOption() {
600
+ return this.opt;
601
+ }
602
+ updateOptions(r) {
603
+ this.opt = {
604
+ ...this.opt,
605
+ ...r
606
+ }, r.panes && this.initPanes(), this.resize();
607
+ }
608
+ updateData(r) {
609
+ this.data = r ?? [], this.scheduleDraw();
610
+ }
611
+ getData() {
612
+ return this.data;
613
+ }
614
+ getContentWidth() {
615
+ let r = this.data?.length ?? 0;
616
+ return this.opt.kGap + r * (this.opt.kWidth + this.opt.kGap) + this.opt.rightAxisWidth;
617
+ }
618
+ resize() {
619
+ this.computeViewport(), this.layoutPanes(), this.scheduleDraw();
620
+ }
621
+ scheduleDraw() {
622
+ this.raf != null && cancelAnimationFrame(this.raf), this.raf = requestAnimationFrame(() => {
623
+ this.raf = null, this.draw();
624
+ });
625
+ }
626
+ draw() {
627
+ let r = this.computeViewport();
628
+ if (!r) return;
629
+ let s = this.dom.plotCanvas.getContext("2d"), c = this.dom.yAxisCanvas.getContext("2d"), l = this.dom.xAxisCanvas.getContext("2d");
630
+ if (!s || !c || !l) return;
631
+ s.setTransform(1, 0, 0, 1, 0, 0), s.scale(r.dpr, r.dpr), s.clearRect(0, 0, r.plotWidth, r.plotHeight), c.setTransform(1, 0, 0, 1, 0, 0), c.scale(r.dpr, r.dpr), c.clearRect(0, 0, this.opt.rightAxisWidth, r.plotHeight);
632
+ let { start: u, end: d } = getVisibleRange(r.scrollLeft, r.plotWidth, this.opt.kWidth, this.opt.kGap, this.data.length), f = {
633
+ start: u,
634
+ end: d
635
+ };
636
+ for (let l of this.panes) {
637
+ l.updateRange(this.data, f), s.save(), s.beginPath(), s.rect(0, l.top, r.plotWidth, l.height), s.clip(), s.translate(0, l.top);
638
+ for (let c of l.renderers) c.draw({
639
+ ctx: s,
640
+ pane: l,
641
+ data: this.data,
642
+ range: f,
643
+ scrollLeft: r.scrollLeft,
644
+ kWidth: this.opt.kWidth,
645
+ kGap: this.opt.kGap,
646
+ dpr: r.dpr
647
+ });
648
+ s.restore(), createYAxisRenderer({
649
+ axisX: 0,
650
+ axisWidth: this.opt.rightAxisWidth,
651
+ yPaddingPx: this.opt.yPaddingPx,
652
+ ticks: l.id === "sub" ? 2 : void 0
653
+ }).draw({
654
+ ctx: c,
655
+ pane: l,
656
+ data: this.data,
657
+ range: f,
658
+ scrollLeft: r.scrollLeft,
659
+ kWidth: this.opt.kWidth,
660
+ kGap: this.opt.kGap,
661
+ dpr: r.dpr
662
+ }), this.interaction.crosshairPos && this.interaction.activePaneId === l.id && drawCrosshairPriceLabelForPane({
663
+ ctx: c,
664
+ pane: l,
665
+ axisWidth: this.opt.rightAxisWidth,
666
+ dpr: r.dpr,
667
+ crosshairY: this.interaction.crosshairPos.y,
668
+ yPaddingPx: this.opt.yPaddingPx
669
+ });
670
+ }
671
+ drawPaneBorders({
672
+ ctx: s,
673
+ dpr: r.dpr,
674
+ width: r.plotWidth,
675
+ panes: [{
676
+ top: 0,
677
+ height: r.plotHeight
678
+ }]
679
+ });
680
+ let p = this.panes.find((r) => r.id === "sub");
681
+ p && drawPaneTitle({
682
+ ctx: s,
683
+ dpr: r.dpr,
684
+ paneTop: p.top,
685
+ title: "副图(占位)"
686
+ }), drawTimeAxisLayer({
687
+ ctx: l,
688
+ data: this.data,
689
+ scrollLeft: r.scrollLeft,
690
+ kWidth: this.opt.kWidth,
691
+ kGap: this.opt.kGap,
692
+ startIndex: f.start,
693
+ endIndex: f.end,
694
+ dpr: r.dpr,
695
+ crosshair: this.interaction.crosshairPos && typeof this.interaction.crosshairIndex == "number" ? {
696
+ x: this.interaction.crosshairPos.x,
697
+ index: this.interaction.crosshairIndex
698
+ } : null
699
+ }), this.interaction.crosshairPos && (s.save(), s.beginPath(), s.rect(0, 0, r.plotWidth, r.plotHeight), s.clip(), drawCrosshair({
700
+ ctx: s,
701
+ plotWidth: r.plotWidth,
702
+ plotHeight: r.plotHeight,
703
+ dpr: r.dpr,
704
+ x: this.interaction.crosshairPos.x,
705
+ y: this.interaction.crosshairPos.y
706
+ }), s.restore()), drawMALegend({
707
+ ctx: s,
708
+ data: this.data,
709
+ endIndex: f.end,
710
+ showMA: {
711
+ ma5: !0,
712
+ ma10: !0,
713
+ ma20: !0
714
+ },
715
+ dpr: r.dpr
716
+ });
717
+ }
718
+ zoomAt(r, s, c) {
719
+ let l = this.opt.kWidth + this.opt.kGap, u = (s + r) / l, d = c > 0 ? -1 : 1, f = Math.max(this.opt.minKWidth, Math.min(this.opt.maxKWidth, this.opt.kWidth + d));
720
+ if (f === this.opt.kWidth) return;
721
+ let p = this.opt.kGap / this.opt.kWidth, m = Math.max(.5, f * p);
722
+ this.opt = {
723
+ ...this.opt,
724
+ kWidth: f,
725
+ kGap: m
726
+ };
727
+ let h = u * (f + m) - r;
728
+ if (this.onZoomChange) {
729
+ this.onZoomChange(f, m, h);
730
+ return;
731
+ }
732
+ let g = this.dom.container, _ = Math.max(0, g.scrollWidth - g.clientWidth);
733
+ g.scrollLeft = Math.min(Math.max(0, h), _), this.scheduleDraw();
734
+ }
735
+ destroy() {
736
+ this.raf != null && cancelAnimationFrame(this.raf), this.raf = null, this.viewport = null, this.panes = [], this.onZoomChange = void 0;
737
+ }
738
+ initPanes() {
739
+ this.panes = this.opt.panes.map((r) => new Pane(r.id));
740
+ }
741
+ layoutPanes() {
742
+ let r = this.viewport;
743
+ if (!r) return;
744
+ let s = this.opt.panes.reduce((r, s) => r + (s.ratio ?? 0), 0) || 1, c = Math.max(0, this.opt.paneGap ?? 0), l = 0, u = Math.min(this.panes.length, this.opt.panes.length), d = c * Math.max(0, u - 1), f = Math.max(1, r.plotHeight - d);
745
+ for (let r = 0; r < u; r++) {
746
+ let d = this.opt.panes[r], p = this.panes[r];
747
+ if (!d || !p) continue;
748
+ let m = r === u - 1 ? f - l : Math.round(f * (d.ratio / s));
749
+ p.setLayout(l, m), p.setPadding(this.opt.yPaddingPx, this.opt.yPaddingPx), l += m + c;
750
+ }
751
+ }
752
+ computeViewport() {
753
+ let r = this.dom.container;
754
+ if (!r) return null;
755
+ let s = r.getBoundingClientRect(), c = Math.max(1, Math.round(s.width)), l = Math.max(1, Math.round(s.height)), u = r.scrollLeft, d = c - this.opt.rightAxisWidth, f = l - this.opt.bottomAxisHeight, p = window.devicePixelRatio || 1, m = 16 * 1024 * 1024;
756
+ c * p * (l * p) > m && (p = Math.sqrt(m / (c * l))), this.dom.canvasLayer.style.width = `${c}px`, this.dom.canvasLayer.style.height = `${l}px`, this.dom.plotCanvas.style.width = `${d}px`, this.dom.plotCanvas.style.height = `${f}px`, this.dom.plotCanvas.width = Math.round(d * p), this.dom.plotCanvas.height = Math.round(f * p), this.dom.yAxisCanvas.style.width = `${this.opt.rightAxisWidth}px`, this.dom.yAxisCanvas.style.height = `${f}px`, this.dom.yAxisCanvas.width = Math.round(this.opt.rightAxisWidth * p), this.dom.yAxisCanvas.height = Math.round(f * p), this.dom.xAxisCanvas.style.width = `${d}px`, this.dom.xAxisCanvas.style.height = `${this.opt.bottomAxisHeight}px`, this.dom.xAxisCanvas.width = Math.round(d * p), this.dom.xAxisCanvas.height = Math.round(this.opt.bottomAxisHeight * p);
757
+ let h = {
758
+ viewWidth: c,
759
+ viewHeight: l,
760
+ plotWidth: d,
761
+ plotHeight: f,
762
+ scrollLeft: u,
763
+ dpr: p
764
+ };
765
+ return this.viewport = h, h;
766
+ }
767
+ };
768
+ function getKLineTrend(r) {
769
+ return r.open > r.close ? "down" : r.open < r.close ? "up" : "flat";
17
770
  }
18
771
  var UP_COLOR = "rgba(214, 10, 34, 1)", DOWN_COLOR = "rgba(3, 123, 102, 1)";
19
- function kLineDraw(e, n, r, i, a = 1) {
20
- if (tagLog("info", "kLineDraw", n), n.length === 0) return;
21
- let o = i, s = r.yPaddingPx ?? 0, c = Math.max(0, Math.min(s, Math.floor(o / 2) - 1)), l = c, f = c, p = n.reduce((e, n) => Math.max(e, n.high), -Infinity), m = n.reduce((e, n) => Math.min(e, n.low), Infinity);
22
- e.lineWidth = 1 / a;
23
- let v = r.kGap;
24
- for (let i = 0; i < n.length; i++) {
25
- let s = n[i];
26
- if (!s) continue;
27
- let c = priceToY(s.high, p, m, o, l, f), h = priceToY(s.low, p, m, o, l, f), y = priceToY(s.open, p, m, o, l, f), b = priceToY(s.close, p, m, o, l, f), x = Math.min(y, b), S = Math.max(Math.abs(y - b), 2 / a), C = getKLineTrend(s) === "up" ? UP_COLOR : DOWN_COLOR;
28
- e.fillStyle = C, e.fillRect(v, x, r.kWidth, S), e.strokeStyle = C, e.lineWidth = 3 / a;
29
- let w = v + r.kWidth / 2;
30
- e.beginPath(), e.moveTo(w, c), e.lineTo(w, x), e.stroke(), e.beginPath(), e.moveTo(w, x + S), e.lineTo(w, h), e.stroke(), v += r.kWidth + r.kGap;
31
- }
32
- }
33
- function calcMAx(e, n) {
34
- let r = [...e].sort((e, n) => e.timestamp - n.timestamp), i = r.length;
35
- if (n <= 0) throw Error("x must be > 0");
36
- if (i < n) return [];
37
- let a = 0;
38
- for (let e = 0; e < n; e++) a += r[e].close;
39
- let o = Array(i - n + 1);
40
- o[0] = a / n;
41
- for (let e = n; e < i; e++) a += r[e].close - r[e - n].close, o[e - n + 1] = a / n;
42
- return o;
43
- }
44
- function drawMALine(e, n, r, i, a, o, s = 1) {
45
- if (n.length === 0) return;
46
- let c = [...n].sort((e, n) => e.timestamp - n.timestamp), l = calcMAx(c, a);
47
- if (l.length === 0) return;
48
- let u = i, f = r.yPaddingPx ?? 0, p = Math.max(0, Math.min(f, Math.floor(u / 2) - 1)), m = p, h = p, g = c.reduce((e, n) => Math.max(e, n.high), -Infinity), _ = c.reduce((e, n) => Math.min(e, n.low), Infinity);
49
- if (!Number.isFinite(g) || !Number.isFinite(_) || g <= _) return;
50
- let v = Array(l.length);
51
- for (let e = 0; e < l.length; e++) v[e] = priceToY(l[e], g, _, u, m, h);
52
- e.strokeStyle = o, e.lineWidth = 2 / s, e.beginPath();
53
- let b = !1;
54
- for (let n = 0; n < v.length; n++) {
55
- let i = v[n];
56
- if (!Number.isFinite(i)) continue;
57
- let o = n + (a - 1), s = r.kGap + o * (r.kWidth + r.kGap) + r.kWidth / 2;
58
- b ? e.lineTo(s, i) : (e.moveTo(s, i), b = !0);
59
- }
60
- b && e.stroke();
61
- }
62
- function drawMA10Line(e, n, r, i, a = 1) {
63
- drawMALine(e, n, r, i, 10, "rgba(190, 131, 12, 1)", a);
64
- }
65
- function drawMA20Line(e, n, r, i, a = 1) {
66
- drawMALine(e, n, r, i, 20, "rgba(69, 112, 249, 1)", a);
67
- }
68
- function drawMA5Line(e, n, r, i, a = 1) {
69
- drawMALine(e, n, r, i, 5, "rgba(251, 186, 62, 1)", a);
70
- }
71
- const KLineChart = /* @__PURE__ */ ((e, n) => {
72
- let r = e.__vccOpts || e;
73
- for (let [e, i] of n) r[e] = i;
74
- return r;
75
- })(/* @__PURE__ */ defineComponent({
772
+ const CandleRenderer = { draw({ ctx: r, pane: s, data: c, range: l, scrollLeft: u, kWidth: d, kGap: f, dpr: p }) {
773
+ if (!c.length) return;
774
+ let m = d + f;
775
+ r.save(), r.translate(-u, 0);
776
+ for (let u = l.start; u < l.end && u < c.length; u++) {
777
+ let l = c[u];
778
+ if (!l) continue;
779
+ let h = s.yAxis.priceToY(l.open), g = s.yAxis.priceToY(l.close), _ = s.yAxis.priceToY(l.high), v = s.yAxis.priceToY(l.low), y = f + u * m, b = alignRect(y, Math.min(h, g), d, Math.max(Math.abs(h - g), 1), p);
780
+ r.fillStyle = getKLineTrend(l) === "up" ? UP_COLOR : DOWN_COLOR, r.fillRect(b.x, b.y, b.width, b.height);
781
+ let x = y + d / 2, S = b.y, C = b.y + b.height, w = Math.max(l.open, l.close), T = Math.min(l.open, l.close);
782
+ if (l.high > w) {
783
+ let s = createVerticalLineRect(x, _, S, p);
784
+ s && r.fillRect(s.x, s.y, s.width, s.height);
785
+ }
786
+ if (l.low < T) {
787
+ let s = createVerticalLineRect(x, C, v, p);
788
+ s && r.fillRect(s.x, s.y, s.width, s.height);
789
+ }
790
+ }
791
+ r.restore();
792
+ } }, GridLinesRenderer = { draw({ ctx: r, pane: s, data: c, range: l, scrollLeft: u, kWidth: d, kGap: f, dpr: p }) {
793
+ if (!c.length) return;
794
+ let m = d + f, h = s.id === "main" ? 6 : 2;
795
+ r.save(), r.fillStyle = "rgba(0,0,0,0.06)", r.translate(-u, 0);
796
+ let g = r.canvas.width / p, _ = u, v = u + g, y = s.yAxis.getPaddingTop(), b = s.yAxis.getPaddingBottom(), x = y, S = Math.max(y, s.height - b), C = Math.max(0, S - x);
797
+ for (let s = 0; s < h; s++) {
798
+ let c = h <= 1 ? 0 : s / (h - 1), l = createHorizontalLineRect(_, v, Math.round(x + c * C), p);
799
+ l && r.fillRect(l.x, l.y, l.width, l.height);
800
+ }
801
+ function w(r) {
802
+ let s = new Date(r);
803
+ return `${s.getFullYear()}-${s.getMonth()}`;
804
+ }
805
+ for (let u = Math.max(l.start, 1); u < l.end && u < c.length; u++) {
806
+ let l = c[u], d = c[u - 1];
807
+ if (!(!l || !d) && w(l.timestamp) !== w(d.timestamp)) {
808
+ let c = createVerticalLineRect(f + u * m, 0, s.height, p);
809
+ c && r.fillRect(c.x, c.y, c.width, c.height);
810
+ }
811
+ }
812
+ r.restore();
813
+ } }, LastPriceLineRenderer = { draw({ ctx: r, pane: s, data: c, range: l, scrollLeft: u, kWidth: d, kGap: f, dpr: p }) {
814
+ let m = c[c.length - 1];
815
+ if (!m) return;
816
+ r.save(), r.translate(-u, 0);
817
+ let h = Math.round(s.yAxis.priceToY(m.close)), g = d + f, _ = f + l.start * g, v = f + l.end * g;
818
+ r.strokeStyle = "#F59999", r.lineWidth = 1, r.setLineDash([4, 3]), r.beginPath();
819
+ let y = (Math.floor(h * p) + .5) / p;
820
+ r.moveTo(Math.round(_ * p) / p, y), r.lineTo(Math.round(v * p) / p, y), r.stroke(), r.setLineDash([]), r.restore();
821
+ } };
822
+ function createMARenderer(r) {
823
+ return { draw({ ctx: s, pane: c, data: l, range: u, scrollLeft: d, kWidth: f, kGap: p, dpr: m }) {
824
+ s.save(), s.translate(-d, 0);
825
+ let h = {
826
+ kWidth: f,
827
+ kGap: p,
828
+ yPaddingPx: 0
829
+ };
830
+ r.ma5 && drawMA5Line(s, l, h, c.height, m, u.start, u.end, c.priceRange), r.ma10 && drawMA10Line(s, l, h, c.height, m, u.start, u.end, c.priceRange), r.ma20 && drawMA20Line(s, l, h, c.height, m, u.start, u.end, c.priceRange), s.restore();
831
+ } };
832
+ }
833
+ const ExtremaMarkersRenderer = { draw({ ctx: r, pane: s, data: c, range: l, scrollLeft: u, kWidth: d, kGap: f, dpr: p }) {
834
+ if (!c.length || s.id !== "main") return;
835
+ let m = Math.max(0, l.start), h = Math.min(c.length, l.end);
836
+ if (h - m <= 0) return;
837
+ let g = -Infinity, _ = Infinity, v = m, y = m;
838
+ for (let r = m; r < h; r++) {
839
+ let s = c[r];
840
+ s && (s.high >= g && (g = s.high, v = r), s.low <= _ && (_ = s.low, y = r));
841
+ }
842
+ if (!Number.isFinite(g) || !Number.isFinite(_)) return;
843
+ let b = d + f, x = (r) => f + r * b + d / 2;
844
+ r.save(), r.translate(-u, 0), drawPriceMarker(r, x(v), s.yAxis.priceToY(g), g, p), drawPriceMarker(r, x(y), s.yAxis.priceToY(_), _, p), r.restore();
845
+ } };
846
+ function drawPriceMarker(r, s, c, l, u) {
847
+ let d = l.toFixed(2), f = createHorizontalLineRect(s, s + 30, c, u);
848
+ f && (r.fillStyle = "rgba(0,0,0,0.45)", r.fillRect(f.x, f.y, f.width, f.height));
849
+ let p = roundToPhysicalPixel(s + 30, u), m = roundToPhysicalPixel(c, u);
850
+ r.fillStyle = "rgba(0,0,0,0.45)", r.beginPath(), r.arc(p, m, 2, 0, Math.PI * 2), r.fill(), r.font = "12px Arial", r.textBaseline = "middle", r.textAlign = "left", r.fillStyle = "rgba(0,0,0,0.70)", r.fillText(d, roundToPhysicalPixel(s + 30 + 4, u), roundToPhysicalPixel(c, u));
851
+ }
852
+ var _hoisted_1 = { class: "chart-wrapper" };
853
+ const KLineChart = /* @__PURE__ */ __plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
76
854
  __name: "KLineChart",
77
855
  props: {
78
856
  data: {},
79
857
  kWidth: { default: 10 },
80
858
  kGap: { default: 2 },
81
- yPaddingPx: { default: 60 },
859
+ yPaddingPx: { default: 0 },
82
860
  showMA: { default: () => ({
83
861
  ma5: !0,
84
862
  ma10: !0,
@@ -87,63 +865,200 @@ const KLineChart = /* @__PURE__ */ ((e, n) => {
87
865
  autoScrollToRight: {
88
866
  type: Boolean,
89
867
  default: !0
90
- }
868
+ },
869
+ minKWidth: { default: 2 },
870
+ maxKWidth: { default: 50 },
871
+ rightAxisWidth: { default: 70 },
872
+ bottomAxisHeight: { default: 24 },
873
+ paneRatios: { default: () => [.75, .25] }
91
874
  },
92
- setup(r, { expose: u }) {
93
- let d = r, f = ref(null), p = ref(null), m = null;
94
- function h() {
95
- return {
96
- kWidth: d.kWidth,
97
- kGap: d.kGap,
98
- yPaddingPx: d.yPaddingPx
99
- };
875
+ setup(d, { expose: b }) {
876
+ let x = d, w = ref(null), T = ref(null), E = ref(null), D = ref(null), O = ref(null), k = ref(x.kWidth), A = ref(x.kGap), j = shallowRef(null);
877
+ function M() {
878
+ j.value?.scheduleDraw();
100
879
  }
101
- function g() {
102
- let e = f.value, n = p.value;
103
- if (!e || !n || !d.data || d.data.length === 0) return;
104
- let r = e.getContext("2d");
105
- if (!r) return;
106
- let i = d.data, a = n.getBoundingClientRect(), o = Math.max(1, Math.round(a.width)), s = Math.max(1, Math.round(a.height)), c = window.devicePixelRatio || 1, l = h(), u = i.length, m = Math.max(o, l.kGap + u * (l.kWidth + l.kGap));
107
- e.style.width = `${m}px`, e.style.height = `${s}px`, e.width = Math.round(m * c), e.height = Math.round(s * c);
108
- let g = n.scrollLeft;
109
- r.setTransform(1, 0, 0, 1, 0, 0), r.scale(c, c), r.clearRect(0, 0, m, s), r.translate(-g, 0), kLineDraw(r, i, l, s, c), d.showMA.ma5 && drawMA5Line(r, i, l, s, c), d.showMA.ma10 && drawMA10Line(r, i, l, s, c), d.showMA.ma20 && drawMA20Line(r, i, l, s, c);
110
- }
111
- function _() {
112
- m !== null && cancelAnimationFrame(m), m = requestAnimationFrame(() => {
113
- m = null, g();
880
+ let N = ref(null);
881
+ function P(r) {
882
+ if (N.value = r, !r) return;
883
+ let s = r.getBoundingClientRect();
884
+ j.value?.interaction.setTooltipSize({
885
+ width: Math.max(180, Math.round(s.width)),
886
+ height: Math.max(80, Math.round(s.height))
114
887
  });
115
888
  }
116
- function y() {
117
- let e = p.value;
118
- e && (e.scrollLeft = e.scrollWidth, _());
889
+ let F = ref(!1), I = ref(null), L = ref({
890
+ x: 0,
891
+ y: 0
892
+ }), R = computed(() => {
893
+ let r = I.value;
894
+ return typeof r == "number" ? x.data?.[r] ?? null : null;
895
+ }), z = computed(() => I.value), B = computed(() => L.value);
896
+ function V() {
897
+ let r = j.value?.interaction;
898
+ if (!r) {
899
+ I.value = null;
900
+ return;
901
+ }
902
+ I.value = r.hoveredIndex ?? null;
903
+ let s = r.tooltipPos;
904
+ s && (L.value = {
905
+ x: s.x,
906
+ y: s.y
907
+ });
908
+ }
909
+ function H(r) {
910
+ F.value = !0, j.value?.interaction.onMouseDown(r), V();
911
+ }
912
+ function U(r) {
913
+ F.value = !0, j.value?.interaction.onPointerDown(r), V();
914
+ }
915
+ function W(r) {
916
+ j.value?.interaction.onMouseMove(r), V();
917
+ }
918
+ function G(r) {
919
+ j.value?.interaction.onPointerMove(r), V();
920
+ }
921
+ function K() {
922
+ F.value = !1, j.value?.interaction.onMouseUp(), V();
923
+ }
924
+ function q(r) {
925
+ F.value = !1, j.value?.interaction.onPointerUp(r), V();
119
926
  }
120
- return u({
121
- scheduleRender: _,
122
- scrollToRight: y
927
+ function J() {
928
+ F.value = !1, j.value?.interaction.onMouseLeave(), I.value = null;
929
+ }
930
+ function Y(r) {
931
+ F.value = !1, j.value?.interaction.onPointerLeave(r), I.value = null;
932
+ }
933
+ function X() {
934
+ j.value?.interaction.onScroll(), V();
935
+ }
936
+ function Z(r) {
937
+ j.value?.interaction.onWheel(r), V();
938
+ }
939
+ let Q = computed(() => {
940
+ let r = x.data?.length ?? 0;
941
+ return A.value + r * (k.value + A.value) + x.rightAxisWidth;
942
+ });
943
+ function $() {
944
+ let r = O.value;
945
+ r && (r.scrollLeft = r.scrollWidth, M());
946
+ }
947
+ return b({
948
+ scheduleRender: M,
949
+ scrollToRight: $
123
950
  }), onMounted(() => {
124
- window.addEventListener("resize", _, { passive: !0 }), _();
951
+ let r = O.value, s = D.value, c = w.value, l = T.value, u = E.value;
952
+ if (!r || !s || !c || !l || !u) return;
953
+ let d = [{
954
+ id: "main",
955
+ ratio: x.paneRatios[0]
956
+ }, {
957
+ id: "sub",
958
+ ratio: x.paneRatios[1]
959
+ }], p = new Chart({
960
+ container: r,
961
+ canvasLayer: s,
962
+ plotCanvas: c,
963
+ yAxisCanvas: l,
964
+ xAxisCanvas: u
965
+ }, {
966
+ kWidth: k.value,
967
+ kGap: A.value,
968
+ yPaddingPx: x.yPaddingPx,
969
+ rightAxisWidth: x.rightAxisWidth,
970
+ bottomAxisHeight: x.bottomAxisHeight,
971
+ minKWidth: x.minKWidth,
972
+ maxKWidth: x.maxKWidth,
973
+ panes: d,
974
+ paneGap: 0
975
+ });
976
+ p.setOnZoomChange(async (r, s, c) => {
977
+ k.value = r, A.value = s, await nextTick(), await new Promise((r) => requestAnimationFrame(() => r()));
978
+ let l = O.value;
979
+ if (!l) return;
980
+ let u = Math.max(0, l.scrollWidth - l.clientWidth);
981
+ l.scrollLeft = Math.min(Math.max(0, c), u), M();
982
+ }), p.setPaneRenderers("main", [
983
+ GridLinesRenderer,
984
+ LastPriceLineRenderer,
985
+ CandleRenderer,
986
+ ExtremaMarkersRenderer,
987
+ createMARenderer(x.showMA)
988
+ ]), p.setPaneRenderers("sub", [GridLinesRenderer]), j.value = p, p.updateData(x.data), p.resize();
989
+ let m = () => p.resize();
990
+ window.addEventListener("resize", m, { passive: !0 }), p.__onResize = m;
125
991
  }), onUnmounted(() => {
126
- window.removeEventListener("resize", _), m !== null && cancelAnimationFrame(m);
992
+ let r = j.value;
993
+ if (r) {
994
+ let s = r.__onResize;
995
+ s && window.removeEventListener("resize", s), r.destroy();
996
+ }
997
+ j.value = null;
998
+ }), watch(() => [x.kWidth, x.kGap], ([r, s]) => {
999
+ typeof r == "number" && (k.value = r), typeof s == "number" && (A.value = s), j.value?.updateOptions({
1000
+ kWidth: k.value,
1001
+ kGap: A.value
1002
+ });
127
1003
  }), watch(() => [
128
- d.data,
129
- d.kWidth,
130
- d.kGap,
131
- d.yPaddingPx,
132
- d.showMA
1004
+ x.data,
1005
+ x.yPaddingPx,
1006
+ x.showMA
133
1007
  ], async () => {
134
- d.autoScrollToRight ? (await nextTick(), y()) : _();
135
- }, { deep: !0 }), (r, i) => (openBlock(), createElementBlock("div", {
136
- class: "chart-container",
1008
+ j.value?.updateOptions({ yPaddingPx: x.yPaddingPx }), j.value?.updateData(x.data), x.autoScrollToRight ? (await nextTick(), $()) : M();
1009
+ }, { deep: !0 }), (r, d) => (openBlock(), createElementBlock("div", _hoisted_1, [createElementVNode("div", {
1010
+ class: normalizeClass(["chart-container", { "is-dragging": F.value }]),
137
1011
  ref_key: "containerRef",
138
- ref: p,
139
- onScrollPassive: _
140
- }, [createElementVNode("canvas", {
141
- ref_key: "canvasRef",
142
- ref: f,
143
- class: "chart-canvas"
144
- }, null, 512)], 544));
1012
+ ref: O,
1013
+ onScrollPassive: X,
1014
+ onMousedown: H,
1015
+ onMousemove: W,
1016
+ onMouseup: K,
1017
+ onMouseleave: J,
1018
+ onPointerdown: U,
1019
+ onPointermove: G,
1020
+ onPointerup: q,
1021
+ onPointerleave: Y,
1022
+ onWheel: withModifiers(Z, ["prevent"])
1023
+ }, [createElementVNode("div", {
1024
+ class: "scroll-content",
1025
+ style: normalizeStyle({ width: Q.value + "px" })
1026
+ }, [createElementVNode("div", {
1027
+ class: "canvas-layer",
1028
+ ref_key: "canvasLayerRef",
1029
+ ref: D
1030
+ }, [
1031
+ createElementVNode("canvas", {
1032
+ class: "plot-canvas",
1033
+ ref_key: "plotCanvasRef",
1034
+ ref: w
1035
+ }, null, 512),
1036
+ createElementVNode("canvas", {
1037
+ class: "y-axis-canvas",
1038
+ ref_key: "yAxisCanvasRef",
1039
+ ref: T
1040
+ }, null, 512),
1041
+ createElementVNode("canvas", {
1042
+ class: "x-axis-canvas",
1043
+ ref_key: "xAxisCanvasRef",
1044
+ ref: E
1045
+ }, null, 512),
1046
+ R.value ? (openBlock(), createBlock(KLineTooltip_default, {
1047
+ key: 0,
1048
+ k: R.value,
1049
+ index: z.value,
1050
+ data: x.data,
1051
+ pos: B.value,
1052
+ "set-el": P
1053
+ }, null, 8, [
1054
+ "k",
1055
+ "index",
1056
+ "data",
1057
+ "pos"
1058
+ ])) : createCommentVNode("", !0)
1059
+ ], 512)], 4)], 34)]));
145
1060
  }
146
- }), [["__scopeId", "data-v-7f0057f3"]]), KMapPlugin = { install(e) {
147
- e.component("KLineChart", KLineChart);
1061
+ }), [["__scopeId", "data-v-28c7f9b2"]]), KMapPlugin = { install(r) {
1062
+ r.component("KLineChart", KLineChart);
148
1063
  } };
149
1064
  export { KLineChart, KMapPlugin };