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