@baron1996/klinecharts-adapter 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +27 -2
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +645 -26
- package/dist/conversion/chart-options.d.ts.map +1 -1
- package/dist/conversion/chart-options.js +6 -0
- package/dist/conversion/overlays.d.ts +2 -1
- package/dist/conversion/overlays.d.ts.map +1 -1
- package/dist/conversion/overlays.js +1 -1
- package/dist/conversion/price.d.ts.map +1 -1
- package/dist/conversion/price.js +10 -39
- package/dist/drawing/engine-port.d.ts +71 -0
- package/dist/drawing/engine-port.d.ts.map +1 -0
- package/dist/drawing/engine-port.js +1 -0
- package/dist/drawing/interaction-normalization.d.ts +18 -0
- package/dist/drawing/interaction-normalization.d.ts.map +1 -0
- package/dist/drawing/interaction-normalization.js +61 -0
- package/dist/drawing/kline-projection-policy.d.ts +8 -0
- package/dist/drawing/kline-projection-policy.d.ts.map +1 -0
- package/dist/drawing/kline-projection-policy.js +69 -0
- package/dist/drawing/overlay-conversion.d.ts +9 -0
- package/dist/drawing/overlay-conversion.d.ts.map +1 -0
- package/dist/drawing/overlay-conversion.js +245 -0
- package/dist/drawing/projection-policy.d.ts +34 -0
- package/dist/drawing/projection-policy.d.ts.map +1 -0
- package/dist/drawing/projection-policy.js +10 -0
- package/dist/drawing/time-series-projection-policy.d.ts +6 -0
- package/dist/drawing/time-series-projection-policy.d.ts.map +1 -0
- package/dist/drawing/time-series-projection-policy.js +28 -0
- package/dist/engine.d.ts +2 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +14 -1
- package/dist/extensions/price-measurement.d.ts +1 -1
- package/dist/extensions/price-measurement.d.ts.map +1 -1
- package/dist/extensions/price-measurement.js +8 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/main-series-presentation.d.ts +35 -0
- package/dist/main-series-presentation.d.ts.map +1 -0
- package/dist/main-series-presentation.js +52 -0
- package/dist/time-series/adapter.d.ts +25 -2
- package/dist/time-series/adapter.d.ts.map +1 -1
- package/dist/time-series/adapter.js +889 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/adapter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseChartScene, SceneError, } from '@baron1996/kline-scene-schema';
|
|
1
|
+
import { parseDrawableWorkspaceDocument, parseChartScene, SceneError, } from '@baron1996/kline-scene-schema';
|
|
2
2
|
import { createEngine } from './engine.js';
|
|
3
3
|
import { registerProjectOverlays } from './extensions/register.js';
|
|
4
4
|
import { createEngineIdMap, } from './conversion/id-map.js';
|
|
@@ -6,6 +6,10 @@ import { applyPanes, overrideSceneYAxis } from './conversion/panes.js';
|
|
|
6
6
|
import { createSceneOverlays, fromEngineOverlay, toEngineOverlay, toEngineOverlayDrawing, } from './conversion/overlays.js';
|
|
7
7
|
import { normalizePriceValue } from './conversion/price.js';
|
|
8
8
|
import { applyViewport } from './conversion/viewport.js';
|
|
9
|
+
import { createStaticDataLoader } from './static-data-loader.js';
|
|
10
|
+
import { toOverlayStyles } from './conversion/overlays.js';
|
|
11
|
+
import { drawingToSceneOverlay, sceneOverlayToDrawing, } from './drawing/overlay-conversion.js';
|
|
12
|
+
import { MainSeriesPresentationError, presentationToSceneCandle, STANDARD_CLOSE_LINE_PRESENTATION, } from './main-series-presentation.js';
|
|
9
13
|
import { createDragCandidate, } from './interaction/dragging.js';
|
|
10
14
|
import { hitTestOverlayGeometries, } from './interaction/hit-testing.js';
|
|
11
15
|
import { shouldIgnoreStaleOverlayDeselection } from './interaction/selection-arbitration.js';
|
|
@@ -34,6 +38,8 @@ export class KLineChartsSceneAdapter {
|
|
|
34
38
|
#chart;
|
|
35
39
|
/** 引擎模块句柄,用于版本读取和精确销毁。 */
|
|
36
40
|
#engine;
|
|
41
|
+
/** 引擎点击仲裁显式复位;每次绘制开始前调用,保证新绘制首击独立成单击。 */
|
|
42
|
+
#resetClickArbitration;
|
|
37
43
|
/** 场景 ID 与引擎内部 ID 的双向映射。 */
|
|
38
44
|
#idMap;
|
|
39
45
|
/** 当前最后一次成功提交、可导出的规范化场景。 */
|
|
@@ -54,11 +60,29 @@ export class KLineChartsSceneAdapter {
|
|
|
54
60
|
#interactivePriceMeasurement;
|
|
55
61
|
/** 确定性 opaque 交互 ID 序号。 */
|
|
56
62
|
#interactionSequence = 0;
|
|
63
|
+
/** 显式 Workspace 模式;Legacy 与 Workspace 状态严格隔离。 */
|
|
64
|
+
#workspaceMode = false;
|
|
65
|
+
/** Workspace 模式与引擎同步的 Overlay 几何(不写入 #scene.overlays)。 */
|
|
66
|
+
#workspaceOverlays = [];
|
|
67
|
+
/** Workspace 模式权威业务 Drawing(含 granularity/target/text)。 */
|
|
68
|
+
#workspaceSources = new Map();
|
|
69
|
+
/** 公共 Drawing 端口监听器。 */
|
|
70
|
+
#portListeners = new Set();
|
|
71
|
+
/** 交互启用开关。 */
|
|
72
|
+
#mutationsEnabled = true;
|
|
73
|
+
/** 主序列回滚失败后的只能销毁终止态。 */
|
|
74
|
+
#terminated = false;
|
|
75
|
+
/** 当前拖动会话的编辑维度。 */
|
|
76
|
+
#interactionDimensions = {
|
|
77
|
+
horizontal: false,
|
|
78
|
+
vertical: false,
|
|
79
|
+
};
|
|
57
80
|
constructor(container, scene, handle, idMap, originalBackground) {
|
|
58
81
|
this.#container = container;
|
|
59
82
|
this.#scene = scene;
|
|
60
83
|
this.#chart = handle.chart;
|
|
61
84
|
this.#engine = handle.module;
|
|
85
|
+
this.#resetClickArbitration = handle.resetClickArbitration;
|
|
62
86
|
this.#idMap = idMap;
|
|
63
87
|
this.#originalBackground = originalBackground;
|
|
64
88
|
this.#installInteractionListeners();
|
|
@@ -91,11 +115,126 @@ export class KLineChartsSceneAdapter {
|
|
|
91
115
|
throw error;
|
|
92
116
|
}
|
|
93
117
|
}
|
|
118
|
+
/** 显式 Workspace factory:非空 Legacy overlays 由工作区语义校验拒绝。 */
|
|
119
|
+
static async createWorkspace(container, value) {
|
|
120
|
+
const workspace = parseDrawableWorkspaceDocument(value);
|
|
121
|
+
if (workspace.scene.kind !== 'chart') {
|
|
122
|
+
throw new SceneError('INVALID_REFERENCE', '/scene/kind', 'KLineChartsSceneAdapter requires a chart Workspace Scene.');
|
|
123
|
+
}
|
|
124
|
+
const scene = workspace.scene.document;
|
|
125
|
+
const originalBackground = container.style.backgroundColor;
|
|
126
|
+
let handle;
|
|
127
|
+
let adapter;
|
|
128
|
+
try {
|
|
129
|
+
handle = await createEngine(container, scene);
|
|
130
|
+
registerProjectOverlays(handle.module.registerOverlay);
|
|
131
|
+
const idMap = createEngineIdMap(scene, handle.chart);
|
|
132
|
+
applyPanes(scene, handle.chart, idMap);
|
|
133
|
+
adapter = new KLineChartsSceneAdapter(container, scene, handle, idMap, originalBackground);
|
|
134
|
+
applyViewport(handle.chart, scene.viewport);
|
|
135
|
+
container.style.backgroundColor = scene.chart.layout.backgroundColor;
|
|
136
|
+
adapter.#workspaceMode = true;
|
|
137
|
+
adapter.#restoreWorkspaceDrawings(workspace.drawings.drawings.map((drawing) => snapshotOfDrawing(drawing)));
|
|
138
|
+
return adapter;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (adapter !== undefined) {
|
|
142
|
+
adapter.dispose();
|
|
143
|
+
}
|
|
144
|
+
else if (handle !== undefined) {
|
|
145
|
+
handle.module.dispose(container);
|
|
146
|
+
}
|
|
147
|
+
container.replaceChildren();
|
|
148
|
+
container.style.backgroundColor = originalBackground;
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
94
152
|
#assertActive() {
|
|
95
153
|
if (this.#disposed) {
|
|
96
154
|
throw new SceneError('RUNTIME_INIT_FAILED', '/', 'The Adapter has already been disposed.');
|
|
97
155
|
}
|
|
98
156
|
}
|
|
157
|
+
#assertNotTerminated() {
|
|
158
|
+
if (this.#terminated) {
|
|
159
|
+
throw new MainSeriesPresentationError('MAIN_SERIES_PRESENTATION_ROLLBACK_FAILED', '/chart/candle', 'Adapter is in a destroy-only terminal error state.');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** 当前模式下的 Overlay 列表:Legacy 用 #scene.overlays,Workspace 用独立状态。 */
|
|
163
|
+
#activeOverlays() {
|
|
164
|
+
return this.#workspaceMode ? this.#workspaceOverlays : this.#scene.overlays;
|
|
165
|
+
}
|
|
166
|
+
/** 提交 Overlay 列表变更;Workspace 模式不触碰 #scene.overlays。 */
|
|
167
|
+
#setActiveOverlays(next) {
|
|
168
|
+
if (this.#workspaceMode) {
|
|
169
|
+
this.#workspaceOverlays = structuredClone(next);
|
|
170
|
+
this.#syncWorkspaceSources();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
this.#scene = parseChartScene({
|
|
174
|
+
...structuredClone(this.#scene),
|
|
175
|
+
overlays: structuredClone(next),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
#syncWorkspaceSources() {
|
|
179
|
+
const next = new Map();
|
|
180
|
+
for (const overlay of this.#workspaceOverlays) {
|
|
181
|
+
const source = this.#workspaceSources.get(overlay.id);
|
|
182
|
+
if (source === undefined) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
next.set(overlay.id, sceneOverlayToDrawing(overlay, source));
|
|
186
|
+
}
|
|
187
|
+
this.#workspaceSources = next;
|
|
188
|
+
}
|
|
189
|
+
#paneIdFor(paneRole) {
|
|
190
|
+
if (paneRole === 'candle') {
|
|
191
|
+
const pane = this.#scene.panes.find((candidate) => candidate.kind === 'candle');
|
|
192
|
+
if (pane === undefined) {
|
|
193
|
+
throw new SceneError('INVALID_REFERENCE', '/panes', 'Candle pane is missing.');
|
|
194
|
+
}
|
|
195
|
+
return pane.id;
|
|
196
|
+
}
|
|
197
|
+
if (paneRole.startsWith('indicator:')) {
|
|
198
|
+
const indicatorId = paneRole.slice('indicator:'.length);
|
|
199
|
+
const pane = this.#scene.panes.find((candidate) => candidate.indicators.some((indicator) => indicator.id === indicatorId));
|
|
200
|
+
if (pane === undefined) {
|
|
201
|
+
throw new SceneError('INVALID_REFERENCE', '/panes', `Indicator pane is missing: ${indicatorId}.`);
|
|
202
|
+
}
|
|
203
|
+
return pane.id;
|
|
204
|
+
}
|
|
205
|
+
throw new SceneError('INVALID_REFERENCE', '/drawings/target', `Cannot map pane role: ${paneRole}.`);
|
|
206
|
+
}
|
|
207
|
+
#emitPort(event) {
|
|
208
|
+
for (const listener of this.#portListeners) {
|
|
209
|
+
listener(structuredClone(event));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
#restoreWorkspaceDrawings(drawings) {
|
|
213
|
+
for (const overlay of this.#workspaceOverlays) {
|
|
214
|
+
this.#chart.removeOverlay({ id: overlay.id });
|
|
215
|
+
}
|
|
216
|
+
this.#workspaceOverlays = [];
|
|
217
|
+
this.#workspaceSources = new Map();
|
|
218
|
+
for (const snapshot of drawings) {
|
|
219
|
+
const drawing = drawingFromSnapshot(snapshot);
|
|
220
|
+
this.#workspaceSources.set(drawing.id, drawing);
|
|
221
|
+
const overlay = drawingToSceneOverlay(drawing, this.#paneIdFor(drawing.target.paneRole));
|
|
222
|
+
const result = this.#chart.createOverlay(toEngineOverlay(overlay, this.#idMap, `/drawings/${this.#workspaceOverlays.length}`, this.#overlayCallbacks(overlay)));
|
|
223
|
+
if (result !== overlay.id) {
|
|
224
|
+
throw new SceneError('RUNTIME_INIT_FAILED', `/drawings/${this.#workspaceOverlays.length}`, `KLineCharts failed to restore Drawing ${overlay.id}.`);
|
|
225
|
+
}
|
|
226
|
+
this.#workspaceOverlays.push(structuredClone(overlay));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
#fromPixelToData(point, paneId) {
|
|
230
|
+
const filter = this.#primaryAxisFilter(paneId);
|
|
231
|
+
const converted = this.#chart.convertFromPixel([point], filter);
|
|
232
|
+
const value = converted[0];
|
|
233
|
+
return {
|
|
234
|
+
...(value?.timestamp !== undefined ? { timestamp: value.timestamp } : {}),
|
|
235
|
+
...(value?.value !== undefined ? { value: value.value } : {}),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
99
238
|
#engineOverlays() {
|
|
100
239
|
return this.#chart.getOverlays();
|
|
101
240
|
}
|
|
@@ -110,27 +249,63 @@ export class KLineChartsSceneAdapter {
|
|
|
110
249
|
return;
|
|
111
250
|
}
|
|
112
251
|
this.#selectedOverlayId = id;
|
|
252
|
+
if (this.#workspaceMode) {
|
|
253
|
+
this.#emitPort({
|
|
254
|
+
type: id === null ? 'deselected' : 'selected',
|
|
255
|
+
id: id ?? previousId ?? '',
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
113
259
|
this.#emit({ type: 'overlay-selection-changed', previousId, id });
|
|
114
260
|
if (id !== null) {
|
|
115
261
|
this.#emit({ type: 'overlay-selected', id });
|
|
116
262
|
}
|
|
117
263
|
}
|
|
264
|
+
#dimensionsForHit(overlay, hit) {
|
|
265
|
+
if (hit.target !== 'anchor') {
|
|
266
|
+
return { horizontal: true, vertical: true };
|
|
267
|
+
}
|
|
268
|
+
switch (overlay.type) {
|
|
269
|
+
case 'horizontalStraightLine':
|
|
270
|
+
case 'priceLine':
|
|
271
|
+
case 'simpleTag':
|
|
272
|
+
case 'horizontalRayLine':
|
|
273
|
+
case 'horizontalSegment':
|
|
274
|
+
return { horizontal: false, vertical: true };
|
|
275
|
+
case 'verticalStraightLine':
|
|
276
|
+
case 'verticalRayLine':
|
|
277
|
+
case 'verticalSegment':
|
|
278
|
+
return { horizontal: true, vertical: false };
|
|
279
|
+
default:
|
|
280
|
+
return { horizontal: true, vertical: true };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
118
283
|
#commitEngineOverlay(engineOverlay, source, kind) {
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
const
|
|
284
|
+
const active = this.#activeOverlays();
|
|
285
|
+
const existingIndex = active.findIndex((overlay) => overlay.id === source.id);
|
|
286
|
+
const path = existingIndex < 0 ? `/overlays/${active.length}` : `/overlays/${existingIndex}`;
|
|
287
|
+
const currentSource = existingIndex < 0 ? source : active[existingIndex];
|
|
122
288
|
const overlay = fromEngineOverlay(engineOverlay, currentSource, this.#idMap, path, this.#scene.symbol.pricePrecision);
|
|
123
|
-
const overlays = structuredClone(
|
|
289
|
+
const overlays = structuredClone(active);
|
|
124
290
|
if (existingIndex < 0) {
|
|
125
291
|
overlays.push(overlay);
|
|
126
292
|
}
|
|
127
293
|
else {
|
|
128
294
|
overlays[existingIndex] = overlay;
|
|
129
295
|
}
|
|
130
|
-
this.#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
296
|
+
this.#setActiveOverlays(overlays);
|
|
297
|
+
if (this.#workspaceMode) {
|
|
298
|
+
const drawing = this.#workspaceSources.get(overlay.id);
|
|
299
|
+
if (drawing !== undefined) {
|
|
300
|
+
this.#emitPort({
|
|
301
|
+
type: kind === 'created' ? 'created' : 'updated',
|
|
302
|
+
id: overlay.id,
|
|
303
|
+
drawing: snapshotOfDrawing(sceneOverlayToDrawing(overlay, drawing)),
|
|
304
|
+
editDimensions: structuredClone(this.#interactionDimensions),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
134
309
|
this.#emit({
|
|
135
310
|
type: kind === 'created' ? 'overlay-created' : 'overlay-updated',
|
|
136
311
|
overlay,
|
|
@@ -172,14 +347,16 @@ export class KLineChartsSceneAdapter {
|
|
|
172
347
|
if (this.#interactivePriceMeasurement?.source.id === overlay.id) {
|
|
173
348
|
this.#interactivePriceMeasurement = undefined;
|
|
174
349
|
}
|
|
175
|
-
if (this.#
|
|
176
|
-
this.#
|
|
177
|
-
...structuredClone(this.#scene),
|
|
178
|
-
overlays: this.#scene.overlays.filter((candidate) => candidate.id !== overlay.id),
|
|
179
|
-
});
|
|
350
|
+
if (this.#activeOverlays().some((candidate) => candidate.id === overlay.id)) {
|
|
351
|
+
this.#setActiveOverlays(this.#activeOverlays().filter((candidate) => candidate.id !== overlay.id));
|
|
180
352
|
if (this.#selectedOverlayId === overlay.id) {
|
|
181
353
|
this.#selectOverlay(null);
|
|
182
354
|
}
|
|
355
|
+
if (this.#workspaceMode) {
|
|
356
|
+
this.#workspaceSources.delete(overlay.id);
|
|
357
|
+
this.#emitPort({ type: 'removed', id: overlay.id });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
183
360
|
this.#emit({ type: 'overlay-removed', id: overlay.id });
|
|
184
361
|
}
|
|
185
362
|
},
|
|
@@ -283,8 +460,9 @@ export class KLineChartsSceneAdapter {
|
|
|
283
460
|
}
|
|
284
461
|
#overlayGeometries() {
|
|
285
462
|
const geometries = [];
|
|
286
|
-
|
|
287
|
-
|
|
463
|
+
const active = this.#activeOverlays();
|
|
464
|
+
for (let sceneIndex = 0; sceneIndex < active.length; sceneIndex++) {
|
|
465
|
+
const overlay = active[sceneIndex];
|
|
288
466
|
if (overlay === undefined || !overlay.visible) {
|
|
289
467
|
continue;
|
|
290
468
|
}
|
|
@@ -361,11 +539,12 @@ export class KLineChartsSceneAdapter {
|
|
|
361
539
|
return;
|
|
362
540
|
}
|
|
363
541
|
this.#pointerInteraction = undefined;
|
|
542
|
+
this.#interactionDimensions = { horizontal: false, vertical: false };
|
|
364
543
|
this.#stopPointerCapture(interaction);
|
|
365
544
|
if (!interaction.started) {
|
|
366
545
|
return;
|
|
367
546
|
}
|
|
368
|
-
const index = this.#
|
|
547
|
+
const index = this.#activeOverlays().findIndex((overlay) => overlay.id === interaction.before.id);
|
|
369
548
|
if (index < 0 ||
|
|
370
549
|
!this.#chart.overrideOverlay(toEngineOverlay(interaction.before, this.#idMap, `/overlays/${index}`, this.#overlayCallbacks(interaction.before)))) {
|
|
371
550
|
throw new SceneError('RUNTIME_INIT_FAILED', '/overlays', `KLineCharts failed to restore Overlay ${interaction.before.id}.`);
|
|
@@ -380,7 +559,10 @@ export class KLineChartsSceneAdapter {
|
|
|
380
559
|
}
|
|
381
560
|
}
|
|
382
561
|
#handlePointerDown = (event) => {
|
|
383
|
-
if (this.#disposed ||
|
|
562
|
+
if (this.#disposed ||
|
|
563
|
+
event.button !== 0 ||
|
|
564
|
+
this.#pointerInteraction !== undefined ||
|
|
565
|
+
!this.#mutationsEnabled) {
|
|
384
566
|
return;
|
|
385
567
|
}
|
|
386
568
|
const coordinate = this.#pointerCoordinate(event);
|
|
@@ -409,13 +591,13 @@ export class KLineChartsSceneAdapter {
|
|
|
409
591
|
}
|
|
410
592
|
const hit = hitTestOverlayGeometries(coordinate, this.#overlayGeometries());
|
|
411
593
|
if (hit === null) {
|
|
412
|
-
const selected = this.#
|
|
594
|
+
const selected = this.#activeOverlays().find((overlay) => overlay.id === this.#selectedOverlayId);
|
|
413
595
|
if (selected !== undefined && isControlledInteractionOverlay(selected)) {
|
|
414
596
|
this.#selectOverlay(null);
|
|
415
597
|
}
|
|
416
598
|
return;
|
|
417
599
|
}
|
|
418
|
-
const before = this.#
|
|
600
|
+
const before = this.#activeOverlays().find((overlay) => overlay.id === hit.overlayId);
|
|
419
601
|
if (before === undefined) {
|
|
420
602
|
return;
|
|
421
603
|
}
|
|
@@ -429,6 +611,7 @@ export class KLineChartsSceneAdapter {
|
|
|
429
611
|
return;
|
|
430
612
|
}
|
|
431
613
|
this.#container.setPointerCapture(event.pointerId);
|
|
614
|
+
this.#interactionDimensions = this.#dimensionsForHit(before, hit);
|
|
432
615
|
this.#pointerInteraction = {
|
|
433
616
|
pointerId: event.pointerId,
|
|
434
617
|
originClient: coordinate,
|
|
@@ -460,14 +643,20 @@ export class KLineChartsSceneAdapter {
|
|
|
460
643
|
}
|
|
461
644
|
try {
|
|
462
645
|
const candidate = createDragCandidate(interaction.before, interaction.hit, interaction.originData, this.#fromPixel(coordinate, interaction.before.paneId), this.#scene.data.map((bar) => bar.timestamp), this.#scene.symbol.pricePrecision);
|
|
463
|
-
const
|
|
464
|
-
const
|
|
646
|
+
const active = this.#activeOverlays();
|
|
647
|
+
const index = active.findIndex((overlay) => overlay.id === candidate.id);
|
|
648
|
+
const overlays = structuredClone(active);
|
|
465
649
|
overlays[index] = candidate;
|
|
466
|
-
const
|
|
467
|
-
|
|
650
|
+
const normalized = this.#workspaceMode
|
|
651
|
+
? candidate
|
|
652
|
+
: parseChartScene({ ...structuredClone(this.#scene), overlays }).overlays[index];
|
|
468
653
|
if (!this.#chart.overrideOverlay(toEngineOverlay(normalized, this.#idMap, `/overlays/${index}`, this.#overlayCallbacks(normalized)))) {
|
|
469
654
|
throw new SceneError('RUNTIME_INIT_FAILED', `/overlays/${index}`, `KLineCharts failed to preview Overlay ${normalized.id}.`);
|
|
470
655
|
}
|
|
656
|
+
if (this.#workspaceMode) {
|
|
657
|
+
this.#setActiveOverlays(overlays);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
471
660
|
interaction.candidate = normalized;
|
|
472
661
|
this.#emit({
|
|
473
662
|
type: 'overlay-dragging',
|
|
@@ -491,14 +680,29 @@ export class KLineChartsSceneAdapter {
|
|
|
491
680
|
event.preventDefault();
|
|
492
681
|
event.stopImmediatePropagation();
|
|
493
682
|
this.#pointerInteraction = undefined;
|
|
683
|
+
this.#interactionDimensions = { horizontal: false, vertical: false };
|
|
494
684
|
this.#stopPointerCapture(interaction);
|
|
495
685
|
if (!interaction.started) {
|
|
496
686
|
return;
|
|
497
687
|
}
|
|
498
688
|
const overlay = interaction.candidate ?? interaction.before;
|
|
499
|
-
const
|
|
500
|
-
const
|
|
689
|
+
const active = this.#activeOverlays();
|
|
690
|
+
const index = active.findIndex((candidate) => candidate.id === overlay.id);
|
|
691
|
+
const overlays = structuredClone(active);
|
|
501
692
|
overlays[index] = overlay;
|
|
693
|
+
if (this.#workspaceMode) {
|
|
694
|
+
this.#setActiveOverlays(overlays);
|
|
695
|
+
const drawing = this.#workspaceSources.get(overlay.id);
|
|
696
|
+
if (drawing !== undefined) {
|
|
697
|
+
this.#emitPort({
|
|
698
|
+
type: 'updated',
|
|
699
|
+
id: overlay.id,
|
|
700
|
+
drawing: snapshotOfDrawing(sceneOverlayToDrawing(overlay, drawing)),
|
|
701
|
+
editDimensions: structuredClone(this.#interactionDimensions),
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
502
706
|
this.#scene = parseChartScene({ ...structuredClone(this.#scene), overlays });
|
|
503
707
|
const committed = this.#scene.overlays[index];
|
|
504
708
|
this.#emit({
|
|
@@ -571,6 +775,296 @@ export class KLineChartsSceneAdapter {
|
|
|
571
775
|
this.#scene = candidate;
|
|
572
776
|
return structuredClone(candidate);
|
|
573
777
|
}
|
|
778
|
+
/** 同结构场景替换:symbol/period/data/viewport 原子更新,失败恢复旧场景。 */
|
|
779
|
+
replaceScene(value) {
|
|
780
|
+
this.#assertActive();
|
|
781
|
+
const candidate = parseChartScene(value);
|
|
782
|
+
const paneShape = (scene) => JSON.stringify(scene.panes.map((pane) => ({
|
|
783
|
+
id: pane.id,
|
|
784
|
+
kind: pane.kind,
|
|
785
|
+
order: pane.order,
|
|
786
|
+
})));
|
|
787
|
+
if (paneShape(candidate) !== paneShape(this.#scene)) {
|
|
788
|
+
throw new SceneError('INVALID_REFERENCE', '/panes', 'Scene replacement requires an identical Pane structure.');
|
|
789
|
+
}
|
|
790
|
+
const previous = this.#scene;
|
|
791
|
+
const previousBackground = this.#container.style.backgroundColor;
|
|
792
|
+
try {
|
|
793
|
+
this.#chart.setSymbol({
|
|
794
|
+
ticker: candidate.symbol.ticker,
|
|
795
|
+
pricePrecision: candidate.symbol.pricePrecision,
|
|
796
|
+
volumePrecision: candidate.symbol.volumePrecision,
|
|
797
|
+
...(candidate.symbol.name === undefined ? {} : { name: candidate.symbol.name }),
|
|
798
|
+
});
|
|
799
|
+
this.#chart.setPeriod(structuredClone(candidate.period));
|
|
800
|
+
this.#chart.setDataLoader(createStaticDataLoader(candidate.data));
|
|
801
|
+
this.#chart.resetData();
|
|
802
|
+
applyViewport(this.#chart, candidate.viewport);
|
|
803
|
+
this.#scene = candidate;
|
|
804
|
+
this.#container.style.backgroundColor =
|
|
805
|
+
candidate.chart.layout.backgroundColor;
|
|
806
|
+
return structuredClone(candidate);
|
|
807
|
+
}
|
|
808
|
+
catch (error) {
|
|
809
|
+
try {
|
|
810
|
+
this.#chart.setSymbol({
|
|
811
|
+
ticker: previous.symbol.ticker,
|
|
812
|
+
pricePrecision: previous.symbol.pricePrecision,
|
|
813
|
+
volumePrecision: previous.symbol.volumePrecision,
|
|
814
|
+
...(previous.symbol.name === undefined ? {} : { name: previous.symbol.name }),
|
|
815
|
+
});
|
|
816
|
+
this.#chart.setPeriod(structuredClone(previous.period));
|
|
817
|
+
this.#chart.setDataLoader(createStaticDataLoader(previous.data));
|
|
818
|
+
this.#chart.resetData();
|
|
819
|
+
applyViewport(this.#chart, previous.viewport);
|
|
820
|
+
this.#container.style.backgroundColor = previousBackground;
|
|
821
|
+
}
|
|
822
|
+
catch {
|
|
823
|
+
// 回滚失败:保留原错误,Adapter 状态由调用方决定是否终止。
|
|
824
|
+
}
|
|
825
|
+
throw error;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
get sceneKind() {
|
|
829
|
+
return 'chart';
|
|
830
|
+
}
|
|
831
|
+
restoreDrawings(drawings) {
|
|
832
|
+
this.#assertActive();
|
|
833
|
+
this.#assertNotTerminated();
|
|
834
|
+
if (!this.#workspaceMode) {
|
|
835
|
+
throw new SceneError('INVALID_REFERENCE', '/', 'Drawing port operations require the Workspace factory.');
|
|
836
|
+
}
|
|
837
|
+
this.#restoreWorkspaceDrawings(drawings);
|
|
838
|
+
}
|
|
839
|
+
startDrawing(request) {
|
|
840
|
+
this.#assertActive();
|
|
841
|
+
this.#assertNotTerminated();
|
|
842
|
+
const drawing = placeholderDrawing(request);
|
|
843
|
+
if (this.#workspaceSources.has(request.id)) {
|
|
844
|
+
throw new SceneError('DUPLICATE_ID', `/drawings/${request.id}`, `Drawing ${request.id} already exists.`);
|
|
845
|
+
}
|
|
846
|
+
this.#workspaceSources.set(request.id, drawing);
|
|
847
|
+
const overlay = drawingToSceneOverlay(drawing, this.#paneIdFor(request.target.paneRole));
|
|
848
|
+
const result = this.#chart.createOverlay(toEngineOverlayDrawing({
|
|
849
|
+
...structuredClone(request),
|
|
850
|
+
...structuredClone(overlay),
|
|
851
|
+
}, this.#idMap, this.#overlayCallbacks(overlay, true)));
|
|
852
|
+
if (result !== request.id) {
|
|
853
|
+
this.#workspaceSources.delete(request.id);
|
|
854
|
+
throw new SceneError('RUNTIME_INIT_FAILED', '/drawings', `KLineCharts failed to start Drawing ${request.id}.`);
|
|
855
|
+
}
|
|
856
|
+
return request.id;
|
|
857
|
+
}
|
|
858
|
+
listDrawings() {
|
|
859
|
+
this.#assertActive();
|
|
860
|
+
return Array.from(this.#workspaceSources.values(), (drawing) => snapshotOfDrawing(drawing));
|
|
861
|
+
}
|
|
862
|
+
getDrawing(id) {
|
|
863
|
+
this.#assertActive();
|
|
864
|
+
const drawing = this.#workspaceSources.get(id);
|
|
865
|
+
return drawing === undefined ? undefined : snapshotOfDrawing(drawing);
|
|
866
|
+
}
|
|
867
|
+
updateDrawingStyles(id, styles) {
|
|
868
|
+
this.#assertActive();
|
|
869
|
+
this.#assertNotTerminated();
|
|
870
|
+
const source = this.#workspaceSources.get(id);
|
|
871
|
+
const overlay = this.#workspaceOverlays.find((candidate) => candidate.id === id);
|
|
872
|
+
if (source === undefined || overlay === undefined) {
|
|
873
|
+
throw new SceneError('INVALID_REFERENCE', `/drawings/${id}`, `Drawing ${id} does not exist.`);
|
|
874
|
+
}
|
|
875
|
+
if (!this.#chart.overrideOverlay({
|
|
876
|
+
id,
|
|
877
|
+
styles: toOverlayStyles(styles),
|
|
878
|
+
})) {
|
|
879
|
+
throw new SceneError('RUNTIME_INIT_FAILED', `/drawings/${id}/styles`, `KLineCharts failed to update Drawing ${id} styles.`);
|
|
880
|
+
}
|
|
881
|
+
const updated = {
|
|
882
|
+
...structuredClone(source),
|
|
883
|
+
styles: structuredClone(styles),
|
|
884
|
+
};
|
|
885
|
+
this.#workspaceSources.set(id, updated);
|
|
886
|
+
this.#emitPort({
|
|
887
|
+
type: 'updated',
|
|
888
|
+
id,
|
|
889
|
+
drawing: snapshotOfDrawing(updated),
|
|
890
|
+
editDimensions: { horizontal: false, vertical: false },
|
|
891
|
+
});
|
|
892
|
+
return snapshotOfDrawing(updated);
|
|
893
|
+
}
|
|
894
|
+
updateDrawingText(id, text) {
|
|
895
|
+
this.#assertActive();
|
|
896
|
+
this.#assertNotTerminated();
|
|
897
|
+
const source = this.#workspaceSources.get(id);
|
|
898
|
+
const overlay = this.#workspaceOverlays.find((candidate) => candidate.id === id);
|
|
899
|
+
if (source === undefined || overlay === undefined) {
|
|
900
|
+
throw new SceneError('INVALID_REFERENCE', `/drawings/${id}`, `Drawing ${id} does not exist.`);
|
|
901
|
+
}
|
|
902
|
+
if (!this.#chart.overrideOverlay({ id, extendData: text })) {
|
|
903
|
+
throw new SceneError('RUNTIME_INIT_FAILED', `/drawings/${id}/text`, `KLineCharts failed to update Drawing ${id} text.`);
|
|
904
|
+
}
|
|
905
|
+
const updated = withDrawingText(structuredClone(source), text);
|
|
906
|
+
this.#workspaceSources.set(id, updated);
|
|
907
|
+
this.#emitPort({
|
|
908
|
+
type: 'updated',
|
|
909
|
+
id,
|
|
910
|
+
drawing: snapshotOfDrawing(updated),
|
|
911
|
+
editDimensions: { horizontal: false, vertical: false },
|
|
912
|
+
});
|
|
913
|
+
return snapshotOfDrawing(updated);
|
|
914
|
+
}
|
|
915
|
+
removeDrawing(id) {
|
|
916
|
+
this.#assertActive();
|
|
917
|
+
this.#assertNotTerminated();
|
|
918
|
+
return this.#chart.removeOverlay({ id });
|
|
919
|
+
}
|
|
920
|
+
restoreDrawing(snapshot) {
|
|
921
|
+
this.#assertActive();
|
|
922
|
+
this.#assertNotTerminated();
|
|
923
|
+
const drawing = drawingFromSnapshot(snapshot);
|
|
924
|
+
const existing = this.#workspaceOverlays.find((overlay) => overlay.id === drawing.id);
|
|
925
|
+
const overlay = drawingToSceneOverlay(drawing, this.#paneIdFor(drawing.target.paneRole));
|
|
926
|
+
if (existing !== undefined) {
|
|
927
|
+
if (!this.#chart.overrideOverlay(toEngineOverlay(overlay, this.#idMap, `/drawings/${drawing.id}`, this.#overlayCallbacks(overlay)))) {
|
|
928
|
+
throw new SceneError('RUNTIME_INIT_FAILED', `/drawings/${drawing.id}`, `KLineCharts failed to restore Drawing ${drawing.id}.`);
|
|
929
|
+
}
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
const result = this.#chart.createOverlay(toEngineOverlay(overlay, this.#idMap, `/drawings/${drawing.id}`, this.#overlayCallbacks(overlay)));
|
|
933
|
+
if (result !== drawing.id) {
|
|
934
|
+
throw new SceneError('RUNTIME_INIT_FAILED', `/drawings/${drawing.id}`, `KLineCharts failed to restore Drawing ${drawing.id}.`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
selectDrawing(id) {
|
|
938
|
+
this.#assertActive();
|
|
939
|
+
this.#selectOverlay(id);
|
|
940
|
+
}
|
|
941
|
+
hitTestDrawing(point) {
|
|
942
|
+
this.#assertActive();
|
|
943
|
+
return this.hitTestOverlay(point)?.overlayId ?? null;
|
|
944
|
+
}
|
|
945
|
+
projectToPixel(anchor, paneRole) {
|
|
946
|
+
this.#assertActive();
|
|
947
|
+
const paneId = this.#paneIdFor(paneRole);
|
|
948
|
+
return this.#toPixel(anchor, paneId);
|
|
949
|
+
}
|
|
950
|
+
unprojectFromPixel(point, paneRole) {
|
|
951
|
+
this.#assertActive();
|
|
952
|
+
return this.#fromPixelToData(point, this.#paneIdFor(paneRole));
|
|
953
|
+
}
|
|
954
|
+
setMutationsEnabled(enabled) {
|
|
955
|
+
this.#assertActive();
|
|
956
|
+
this.#mutationsEnabled = enabled;
|
|
957
|
+
}
|
|
958
|
+
subscribeDrawingEvents(listener) {
|
|
959
|
+
this.#portListeners.add(listener);
|
|
960
|
+
return () => {
|
|
961
|
+
this.#portListeners.delete(listener);
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
applyMainSeriesPresentation(presentation) {
|
|
965
|
+
this.#assertActive();
|
|
966
|
+
this.#assertNotTerminated();
|
|
967
|
+
if (presentation.type === 'area') {
|
|
968
|
+
const expected = STANDARD_CLOSE_LINE_PRESENTATION;
|
|
969
|
+
if (presentation.value !== expected.value ||
|
|
970
|
+
presentation.line.color !== expected.line.color ||
|
|
971
|
+
presentation.line.size !== expected.line.size ||
|
|
972
|
+
presentation.backgroundColor !== expected.backgroundColor ||
|
|
973
|
+
presentation.smooth !== expected.smooth ||
|
|
974
|
+
presentation.pointVisible !== expected.pointVisible) {
|
|
975
|
+
throw new MainSeriesPresentationError('MAIN_SERIES_PRESENTATION_INVALID', '/chart/candle/area', 'Area presentation must use the standard frozen close-line configuration.');
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
const candidate = parseChartScene(presentationToSceneCandle(this.#scene, presentation));
|
|
979
|
+
const oldStyles = structuredClone(this.#chart.getStyles());
|
|
980
|
+
const overlayBefore = this.#chart.getOverlays().map((overlay) => ({
|
|
981
|
+
id: overlay.id,
|
|
982
|
+
currentStep: overlay.currentStep,
|
|
983
|
+
points: structuredClone(overlay.points),
|
|
984
|
+
}));
|
|
985
|
+
const selectionBefore = this.#selectedOverlayId;
|
|
986
|
+
const targetType = presentation.type;
|
|
987
|
+
const newStyles = presentation.type === 'area'
|
|
988
|
+
? {
|
|
989
|
+
candle: {
|
|
990
|
+
type: 'area',
|
|
991
|
+
area: {
|
|
992
|
+
lineColor: presentation.line.color,
|
|
993
|
+
lineSize: presentation.line.size,
|
|
994
|
+
value: presentation.value,
|
|
995
|
+
backgroundColor: presentation.backgroundColor,
|
|
996
|
+
smooth: presentation.smooth,
|
|
997
|
+
point: { show: false, animation: false },
|
|
998
|
+
},
|
|
999
|
+
},
|
|
1000
|
+
}
|
|
1001
|
+
: {
|
|
1002
|
+
candle: {
|
|
1003
|
+
type: presentation.type,
|
|
1004
|
+
},
|
|
1005
|
+
};
|
|
1006
|
+
try {
|
|
1007
|
+
this.#chart.setStyles(newStyles);
|
|
1008
|
+
}
|
|
1009
|
+
catch (error) {
|
|
1010
|
+
this.#rollbackPresentation(oldStyles, overlayBefore, selectionBefore);
|
|
1011
|
+
}
|
|
1012
|
+
if (!this.#verifyPresentation(targetType) ||
|
|
1013
|
+
!this.#verifyOverlaySnapshot(overlayBefore, selectionBefore)) {
|
|
1014
|
+
this.#rollbackPresentation(oldStyles, overlayBefore, selectionBefore);
|
|
1015
|
+
}
|
|
1016
|
+
this.#scene = candidate;
|
|
1017
|
+
return { activeType: targetType };
|
|
1018
|
+
}
|
|
1019
|
+
#verifyPresentation(type) {
|
|
1020
|
+
const styles = this.#chart.getStyles();
|
|
1021
|
+
if (styles.candle.type !== type) {
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
if (type !== 'area') {
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
const area = styles.candle.area;
|
|
1028
|
+
return area.value === 'close'
|
|
1029
|
+
&& area.lineColor === 'rgba(41, 98, 255, 1)'
|
|
1030
|
+
&& area.lineSize === 2
|
|
1031
|
+
&& area.backgroundColor === 'rgba(0, 0, 0, 0)'
|
|
1032
|
+
&& area.smooth === false
|
|
1033
|
+
&& area.point.show === false;
|
|
1034
|
+
}
|
|
1035
|
+
#verifyOverlaySnapshot(before, selection) {
|
|
1036
|
+
const after = this.#chart.getOverlays();
|
|
1037
|
+
if (after.length !== before.length) {
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
for (let index = 0; index < after.length; index++) {
|
|
1041
|
+
const left = after[index];
|
|
1042
|
+
const right = before[index];
|
|
1043
|
+
if (left.id !== right.id ||
|
|
1044
|
+
left.currentStep !== right.currentStep ||
|
|
1045
|
+
JSON.stringify(left.points) !== JSON.stringify(right.points)) {
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return this.#selectedOverlayId === selection;
|
|
1050
|
+
}
|
|
1051
|
+
#rollbackPresentation(oldStyles, overlayBefore, selection) {
|
|
1052
|
+
let rollbackOk = false;
|
|
1053
|
+
try {
|
|
1054
|
+
this.#chart.setStyles(oldStyles);
|
|
1055
|
+
rollbackOk =
|
|
1056
|
+
JSON.stringify(this.#chart.getStyles()) === JSON.stringify(oldStyles) &&
|
|
1057
|
+
this.#verifyOverlaySnapshot(overlayBefore, selection);
|
|
1058
|
+
}
|
|
1059
|
+
catch {
|
|
1060
|
+
rollbackOk = false;
|
|
1061
|
+
}
|
|
1062
|
+
if (rollbackOk) {
|
|
1063
|
+
throw new MainSeriesPresentationError('MAIN_SERIES_PRESENTATION_APPLY_FAILED', '/chart/candle', 'KLineCharts failed to apply the main series presentation; the previous styles were restored.');
|
|
1064
|
+
}
|
|
1065
|
+
this.#terminated = true;
|
|
1066
|
+
throw new MainSeriesPresentationError('MAIN_SERIES_PRESENTATION_ROLLBACK_FAILED', '/chart/candle', 'KLineCharts failed to roll back the main series presentation; the Adapter is destroy-only.');
|
|
1067
|
+
}
|
|
574
1068
|
projectPoint(point, paneId) {
|
|
575
1069
|
this.#assertActive();
|
|
576
1070
|
const targetPane = paneId ?? this.#scene.panes.find((pane) => pane.kind === 'candle').id;
|
|
@@ -660,6 +1154,10 @@ export class KLineChartsSceneAdapter {
|
|
|
660
1154
|
this.#engineOverlays().some((overlay) => overlay.id === request.id)) {
|
|
661
1155
|
throw new SceneError('DUPLICATE_ID', '/overlays/id', `Overlay ${request.id} already exists.`);
|
|
662
1156
|
}
|
|
1157
|
+
// 下一次绘制开始前显式复位引擎点击仲裁:klinecharts 10.0.0 会在 500ms 窗口内
|
|
1158
|
+
// 相距 ≥5px 的第二次 mouseup 上既不派发 click 也不派发 double-click,直接丢弃该次点击,
|
|
1159
|
+
// 导致“上一绘制提交后紧接着的新绘制首击”被吞。复位后新绘制首击总是独立的单击。
|
|
1160
|
+
this.#resetClickArbitration();
|
|
663
1161
|
const candidate = request.type === 'priceMeasurement'
|
|
664
1162
|
? parseChartScene(promoteSceneToM2(this.#scene))
|
|
665
1163
|
: this.#scene;
|
|
@@ -722,8 +1220,129 @@ export class KLineChartsSceneAdapter {
|
|
|
722
1220
|
this.#disposed = true;
|
|
723
1221
|
this.#removeInteractionListeners();
|
|
724
1222
|
this.#listeners.clear();
|
|
1223
|
+
this.#portListeners.clear();
|
|
1224
|
+
this.#workspaceSources.clear();
|
|
1225
|
+
this.#workspaceOverlays = [];
|
|
725
1226
|
this.#engine.dispose(this.#container);
|
|
726
1227
|
this.#container.replaceChildren();
|
|
727
1228
|
this.#container.style.backgroundColor = this.#originalBackground;
|
|
728
1229
|
}
|
|
729
1230
|
}
|
|
1231
|
+
function snapshotOfDrawing(drawing) {
|
|
1232
|
+
return {
|
|
1233
|
+
id: drawing.id,
|
|
1234
|
+
type: drawing.type,
|
|
1235
|
+
target: structuredClone(drawing.target),
|
|
1236
|
+
geometry: structuredClone(drawing.geometry),
|
|
1237
|
+
styles: structuredClone(drawing.styles),
|
|
1238
|
+
locked: drawing.locked,
|
|
1239
|
+
visible: drawing.visible,
|
|
1240
|
+
zLevel: drawing.zLevel,
|
|
1241
|
+
mode: drawing.mode,
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
function drawingFromSnapshot(snapshot) {
|
|
1245
|
+
return {
|
|
1246
|
+
id: snapshot.id,
|
|
1247
|
+
type: snapshot.type,
|
|
1248
|
+
target: structuredClone(snapshot.target),
|
|
1249
|
+
geometry: structuredClone(snapshot.geometry),
|
|
1250
|
+
styles: structuredClone(snapshot.styles),
|
|
1251
|
+
visible: snapshot.visible,
|
|
1252
|
+
locked: snapshot.locked,
|
|
1253
|
+
zLevel: snapshot.zLevel,
|
|
1254
|
+
mode: snapshot.mode,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
function placeholderGeometry(type) {
|
|
1258
|
+
switch (type) {
|
|
1259
|
+
case 'horizontalStraightLine':
|
|
1260
|
+
case 'priceLine':
|
|
1261
|
+
return { value: 0 };
|
|
1262
|
+
case 'simpleTag':
|
|
1263
|
+
return { value: 0, text: '' };
|
|
1264
|
+
case 'verticalStraightLine':
|
|
1265
|
+
return { time: 0 };
|
|
1266
|
+
case 'horizontalRayLine':
|
|
1267
|
+
case 'horizontalSegment':
|
|
1268
|
+
return { value: 0, startTime: 0, endTime: 0 };
|
|
1269
|
+
case 'verticalRayLine':
|
|
1270
|
+
case 'verticalSegment':
|
|
1271
|
+
return { time: 0, startValue: 0, endValue: 0 };
|
|
1272
|
+
case 'rayLine':
|
|
1273
|
+
case 'segment':
|
|
1274
|
+
case 'straightLine':
|
|
1275
|
+
case 'fibonacciLine':
|
|
1276
|
+
return {
|
|
1277
|
+
points: [
|
|
1278
|
+
{ timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1279
|
+
{ timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1280
|
+
],
|
|
1281
|
+
};
|
|
1282
|
+
case 'priceChannelLine':
|
|
1283
|
+
case 'parallelStraightLine':
|
|
1284
|
+
return {
|
|
1285
|
+
points: [
|
|
1286
|
+
{ timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1287
|
+
{ timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1288
|
+
{ timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1289
|
+
],
|
|
1290
|
+
};
|
|
1291
|
+
case 'brush':
|
|
1292
|
+
return {
|
|
1293
|
+
points: [
|
|
1294
|
+
{ timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1295
|
+
{ timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1296
|
+
],
|
|
1297
|
+
};
|
|
1298
|
+
case 'simpleAnnotation':
|
|
1299
|
+
case 'callout':
|
|
1300
|
+
case 'text':
|
|
1301
|
+
return {
|
|
1302
|
+
point: { timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1303
|
+
text: '',
|
|
1304
|
+
};
|
|
1305
|
+
case 'crossLine':
|
|
1306
|
+
return {
|
|
1307
|
+
point: { timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1308
|
+
};
|
|
1309
|
+
case 'rectangle':
|
|
1310
|
+
case 'arrow':
|
|
1311
|
+
case 'priceMeasurement':
|
|
1312
|
+
return {
|
|
1313
|
+
start: { timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1314
|
+
end: { timestamp: 0, granularity: { type: 'day', span: 1 }, value: 0 },
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
function placeholderDrawing(request) {
|
|
1319
|
+
return {
|
|
1320
|
+
id: request.id,
|
|
1321
|
+
type: request.type,
|
|
1322
|
+
target: structuredClone(request.target),
|
|
1323
|
+
geometry: placeholderGeometry(request.type),
|
|
1324
|
+
styles: structuredClone(request.styles),
|
|
1325
|
+
visible: true,
|
|
1326
|
+
locked: false,
|
|
1327
|
+
zLevel: 0,
|
|
1328
|
+
mode: 'normal',
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
function withDrawingText(drawing, text) {
|
|
1332
|
+
switch (drawing.type) {
|
|
1333
|
+
case 'simpleTag':
|
|
1334
|
+
return {
|
|
1335
|
+
...structuredClone(drawing),
|
|
1336
|
+
geometry: { ...drawing.geometry, text },
|
|
1337
|
+
};
|
|
1338
|
+
case 'simpleAnnotation':
|
|
1339
|
+
case 'callout':
|
|
1340
|
+
case 'text':
|
|
1341
|
+
return {
|
|
1342
|
+
...structuredClone(drawing),
|
|
1343
|
+
geometry: { ...drawing.geometry, text },
|
|
1344
|
+
};
|
|
1345
|
+
default:
|
|
1346
|
+
return structuredClone(drawing);
|
|
1347
|
+
}
|
|
1348
|
+
}
|