med-viewer-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +253 -0
  2. package/dist/adapters/vue/MedViewer.d.ts +17 -0
  3. package/dist/adapters/vue/index.d.ts +2 -0
  4. package/dist/core/AnnoAnnotator.d.ts +15 -0
  5. package/dist/core/BaseAnnotator.d.ts +33 -0
  6. package/dist/core/ColorAdjustPlugin.d.ts +29 -0
  7. package/dist/core/Coords.d.ts +6 -0
  8. package/dist/core/Engine.d.ts +57 -0
  9. package/dist/core/KonvaAnnotator.d.ts +36 -0
  10. package/dist/core/Scalebar.d.ts +42 -0
  11. package/dist/core/SelectionPlugin.d.ts +102 -0
  12. package/dist/core/Toolbar.d.ts +32 -0
  13. package/dist/med-viewer-sdk.d.ts +6 -0
  14. package/dist/med-viewer-sdk.mjs +14248 -0
  15. package/dist/med-viewer-sdk.umd.js +2 -0
  16. package/dist/style.css +1 -0
  17. package/package.json +34 -0
  18. package/src/adapters/vue/MedViewer.ts +37 -0
  19. package/src/adapters/vue/index.ts +4 -0
  20. package/src/assets/icons/button_grouphover.png +0 -0
  21. package/src/assets/icons/button_hover.png +0 -0
  22. package/src/assets/icons/button_pressed.png +0 -0
  23. package/src/assets/icons/button_rest.png +0 -0
  24. package/src/assets/icons/flip_grouphover.png +0 -0
  25. package/src/assets/icons/flip_hover.png +0 -0
  26. package/src/assets/icons/flip_pressed.png +0 -0
  27. package/src/assets/icons/flip_rest.png +0 -0
  28. package/src/assets/icons/fullpage_grouphover.png +0 -0
  29. package/src/assets/icons/fullpage_hover.png +0 -0
  30. package/src/assets/icons/fullpage_pressed.png +0 -0
  31. package/src/assets/icons/fullpage_rest.png +0 -0
  32. package/src/assets/icons/home_grouphover.png +0 -0
  33. package/src/assets/icons/home_hover.png +0 -0
  34. package/src/assets/icons/home_pressed.png +0 -0
  35. package/src/assets/icons/home_rest.png +0 -0
  36. package/src/assets/icons/next_grouphover.png +0 -0
  37. package/src/assets/icons/next_hover.png +0 -0
  38. package/src/assets/icons/next_pressed.png +0 -0
  39. package/src/assets/icons/next_rest.png +0 -0
  40. package/src/assets/icons/previous_grouphover.png +0 -0
  41. package/src/assets/icons/previous_hover.png +0 -0
  42. package/src/assets/icons/previous_pressed.png +0 -0
  43. package/src/assets/icons/previous_rest.png +0 -0
  44. package/src/assets/icons/rotateleft_grouphover.png +0 -0
  45. package/src/assets/icons/rotateleft_hover.png +0 -0
  46. package/src/assets/icons/rotateleft_pressed.png +0 -0
  47. package/src/assets/icons/rotateleft_rest.png +0 -0
  48. package/src/assets/icons/rotateright_grouphover.png +0 -0
  49. package/src/assets/icons/rotateright_hover.png +0 -0
  50. package/src/assets/icons/rotateright_pressed.png +0 -0
  51. package/src/assets/icons/rotateright_rest.png +0 -0
  52. package/src/assets/icons/selection_cancel_grouphover.png +0 -0
  53. package/src/assets/icons/selection_cancel_hover.png +0 -0
  54. package/src/assets/icons/selection_cancel_pressed.png +0 -0
  55. package/src/assets/icons/selection_cancel_rest.png +0 -0
  56. package/src/assets/icons/selection_confirm_grouphover.png +0 -0
  57. package/src/assets/icons/selection_confirm_hover.png +0 -0
  58. package/src/assets/icons/selection_confirm_pressed.png +0 -0
  59. package/src/assets/icons/selection_confirm_rest.png +0 -0
  60. package/src/assets/icons/selection_grouphover.png +0 -0
  61. package/src/assets/icons/selection_hover.png +0 -0
  62. package/src/assets/icons/selection_pressed.png +0 -0
  63. package/src/assets/icons/selection_rest.png +0 -0
  64. package/src/assets/icons/tool_anno.png +0 -0
  65. package/src/assets/icons/tool_selection.png +0 -0
  66. package/src/assets/icons/zoomin_grouphover.png +0 -0
  67. package/src/assets/icons/zoomin_hover.png +0 -0
  68. package/src/assets/icons/zoomin_pressed.png +0 -0
  69. package/src/assets/icons/zoomin_rest.png +0 -0
  70. package/src/assets/icons/zoomout_grouphover.png +0 -0
  71. package/src/assets/icons/zoomout_hover.png +0 -0
  72. package/src/assets/icons/zoomout_pressed.png +0 -0
  73. package/src/assets/icons/zoomout_rest.png +0 -0
  74. package/src/core/AnnoAnnotator.ts +102 -0
  75. package/src/core/BaseAnnotator.ts +43 -0
  76. package/src/core/ColorAdjustPlugin.ts +256 -0
  77. package/src/core/Coords.ts +9 -0
  78. package/src/core/Engine.ts +246 -0
  79. package/src/core/KonvaAnnotator.ts +185 -0
  80. package/src/core/Scalebar.ts +87 -0
  81. package/src/core/SelectionPlugin.ts +252 -0
  82. package/src/core/Toolbar.ts +370 -0
  83. package/src/index.ts +21 -0
  84. package/src/plugins/ShapeLabelsFormatter.js +435 -0
  85. package/src/plugins/openseadragon-scalebar.js +592 -0
  86. package/src/plugins/openseadragon-selection.js +657 -0
  87. package/src/types/type.d.ts +9 -0
@@ -0,0 +1,246 @@
1
+ import OpenSeadragon from "openseadragon";
2
+
3
+ import { KonvaAnnotator } from "./KonvaAnnotator";
4
+ import { AnnoAnnotator } from "./AnnoAnnotator";
5
+ import { MedToolbar, type ToolbarOptions } from "./Toolbar";
6
+ import {
7
+ ColorAdjustPlugin,
8
+ type ColorAdjustOptions,
9
+ } from "./ColorAdjustPlugin";
10
+ import { SelectionPlugin, type SelectionOptions } from './SelectionPlugin';
11
+ import { ScalebarPlugin, type ScalebarOptions } from './Scalebar';
12
+
13
+ /**
14
+ * 引擎配置接口
15
+ */
16
+ export interface MedEngineOptions {
17
+ element: HTMLElement; // 承载视图的 DOM 容器
18
+ tileSource: string | any; // DZI 路径、Tile 配置或 SVS 切片接口
19
+ navigatorBorderRadius?: number; // 导航器圆角半径
20
+ prefixUrl?: string; // OSD 图标资源路径
21
+ plugins?: {
22
+ konva?: boolean | any; // 是否启用 Konva 插件
23
+ annotorious?: boolean | any; // 是否启用 Annotorious 插件
24
+ toolbar?: boolean | ToolbarOptions; // 是否启用工具栏插件
25
+ colorAdjust?: boolean | ColorAdjustOptions; // 是否启用色彩调节插件
26
+ selection?: boolean | SelectionOptions; // 是否启用选择插件
27
+ scalebar?: boolean | ScalebarOptions; // 是否启用比例尺插件
28
+ };
29
+ }
30
+
31
+ /**
32
+ * 医学影像核心引擎
33
+ * 职责:初始化 OpenSeadragon,管理插件生命周期
34
+ */
35
+ export class MedViewerEngine {
36
+ public viewer: OpenSeadragon.Viewer;
37
+ public konva: KonvaAnnotator | null = null;
38
+ public anno: AnnoAnnotator | null = null;
39
+ public toolbar: MedToolbar | null = null;
40
+ public colorAdjust: ColorAdjustPlugin | null = null;
41
+ public selection: SelectionPlugin | null = null;
42
+ public scalebar: ScalebarPlugin | null = null;
43
+ private options: MedEngineOptions;
44
+
45
+ constructor(options: MedEngineOptions) {
46
+ // 合并默认配置
47
+ const openseadragonOptions = {
48
+ id: "osd-container",
49
+ crossOriginPolicy: "Anonymous",
50
+ prefixUrl: options.prefixUrl || '',
51
+ // --- Viewer 基础 ---
52
+ //useCanvas: true, // Canvas 渲染更稳定 //已经弃用了
53
+ drawer: [ "webgl","canvas"],
54
+ opacity: 1,
55
+ preload: false,
56
+ immediateRender: false,
57
+ defaultZoomLevel: 0,
58
+ degrees: 0,
59
+ flipped: false,
60
+
61
+ // --- 缩放控制 ---
62
+ minZoomImageRatio: 0.9, // 文档/病理混合场景最佳
63
+ maxZoomPixelRatio: 2.0, // 允许更高分辨率细节
64
+ smoothTileEdgesMinZoom: 1.1,
65
+ animationTime: 0.8, // 动画速度更自然
66
+ springStiffness: 10, // 更适合标注(减少漂移)
67
+ pixelsPerWheelLine: 120, // 标准滚轮灵敏度
68
+ minScrollDeltaTime: 20,
69
+
70
+ // --- 平移控制 ---
71
+ panHorizontal: true,
72
+ panVertical: true,
73
+ constrainDuringPan: true, // 必须开:Konva overlay 才不会漂移
74
+ visibilityRatio: 0.2, // 边缘可见比例
75
+
76
+ // --- 禁止 wrap(你现在的配置是错误的)---
77
+ wrapHorizontal: false,
78
+ wrapVertical: false,
79
+
80
+ // --- Navigator 小图 ---
81
+ showNavigator: true,
82
+ navigatorSizeRatio: 0.15,
83
+ navigatorPosition: "TOP_RIGHT",
84
+ navigatorAutoFade: false,
85
+ navigatorOpacity: 0.8,
86
+ navigatorBackground: "#fff",
87
+ navigatorBorderColor: "#aaa",
88
+ navigatorDisplayRegionColor: "#f21616",
89
+
90
+ // --- 控件 ---
91
+ showNavigationControl: false, // 你自己做 UI 更专业
92
+ rotationIncrement: 90,
93
+
94
+ // --- 性能 ---
95
+ maxTilesPerFrame: 3,
96
+ maxImageCacheCount: 200,
97
+
98
+ // --- 手势 ---
99
+ gestureSettingsMouse: {
100
+ dragToPan: true,
101
+ clickToZoom: false,
102
+ dblClickToZoom: false,
103
+ contextMenu: true,
104
+ },
105
+ gestureSettingsTouch: {
106
+ flickEnabled: false,
107
+ pinchToZoom: true,
108
+ panToNextImage: false,
109
+ contextMenu: true,
110
+ },
111
+ plugins: {
112
+ konva: false,
113
+ annotorious: false,
114
+ toolbar: false,
115
+ selection: false,
116
+ },
117
+ ...options,
118
+ };
119
+
120
+ this.options = openseadragonOptions;
121
+
122
+ // 1. 初始化 OpenSeadragon
123
+ this.viewer = OpenSeadragon(this.options);
124
+
125
+
126
+ this.viewer.addOnceHandler("open", () => {
127
+ // const isWebGL = this.viewer.drawer instanceof OpenSeadragon.WebGLDrawer;
128
+ });
129
+
130
+ // 2. 初始化插件
131
+ this.initPlugins();
132
+ }
133
+
134
+ /**
135
+ * 内部插件初始化逻辑
136
+ * 解决异步 DOM 挂载问题,防止 Annotorious 初始化时找不到 Canvas
137
+ */
138
+ private initPlugins(): void {
139
+ const { plugins } = this.options;
140
+ if (!plugins) return;
141
+
142
+ // --- Konva 插件初始化 ---
143
+ // Konva 依赖于容器尺寸,通常在 OSD 构造后即可初始化
144
+ if (plugins.konva) {
145
+ this.konva = new KonvaAnnotator(this);
146
+ console.log("[MedEngine] Konva plugin initialized.");
147
+ }
148
+
149
+ // --- Annotorious 插件初始化 ---
150
+ // 关键:解决 "Cannot set properties of undefined (setting 'display')"
151
+ // Annotorious v3 必须等待 OSD 的 Canvas 元素创建并挂载后才能初始化
152
+ if (plugins.annotorious) {
153
+ const annoConfig =
154
+ typeof plugins.annotorious === "object" ? plugins.annotorious : {};
155
+
156
+ if (this.viewer.isOpen()) {
157
+ // 如果已经打开(极少见,除非 TileSource 是同步加载的本地对象)
158
+ this.mountAnnotorious(annoConfig);
159
+ } else {
160
+ // 推荐:监听 open 事件,确保 OSD 内部 DOM 结构完全就绪
161
+ this.viewer.addOnceHandler("open", () => {
162
+ this.mountAnnotorious(annoConfig);
163
+ });
164
+ }
165
+ }
166
+
167
+ // --- Toolbar 插件初始化 ---
168
+ if (plugins.toolbar) {
169
+ const toolbarConfig =
170
+ typeof plugins.toolbar === "object" ? plugins.toolbar : {};
171
+ this.toolbar = new MedToolbar(this, toolbarConfig);
172
+ }
173
+
174
+ // --- Selection 插件初始化 ---
175
+ if (plugins.selection) {
176
+ const selectionConfig =
177
+ typeof plugins.selection === "object" ? plugins.selection : {};
178
+ this.selection = new SelectionPlugin(this.viewer, selectionConfig);
179
+ }
180
+
181
+ // --- Scalebar 插件初始化 ---
182
+ if (plugins.scalebar) {
183
+ const scalebarConfig =
184
+ typeof plugins.scalebar === "object" ? plugins.scalebar : {};
185
+ this.scalebar = new ScalebarPlugin(this.viewer, scalebarConfig);
186
+ }
187
+
188
+ // --- ColorAdjust 插件初始化 ---
189
+ // --- ColorAdjust 插件初始化 ---
190
+ // if (plugins.colorAdjust) {
191
+ // const adjustConfig =
192
+ // typeof plugins.colorAdjust === "object" ? plugins.colorAdjust : {};
193
+ // this.colorAdjust = new ColorAdjustPlugin(this, adjustConfig);
194
+ // }
195
+ }
196
+
197
+ /**
198
+ * 真正挂载 Annotorious 的私有方法
199
+ */
200
+ private mountAnnotorious(config: any): void {
201
+ try {
202
+ this.anno = new AnnoAnnotator(this, config);
203
+
204
+ console.log("[MedEngine] Annotorious plugin initialized.", this.anno);
205
+ } catch (error) {
206
+ console.error("[MedEngine] Failed to initialize Annotorious:", error);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * 统一切换交互模式
212
+ * @param mode 'browse' | 'konva' | 'anno'
213
+ */
214
+ public setInteractionEffect(mode: "browse" | "konva" | "anno"): void {
215
+ // 先重置状态
216
+ this.konva?.setEnabled(false);
217
+ this.anno?.setEnabled(false);
218
+
219
+ switch (mode) {
220
+ case "konva":
221
+ this.konva?.setEnabled(true);
222
+ break;
223
+ case "anno":
224
+ this.anno?.setEnabled(true);
225
+ break;
226
+ case "browse":
227
+ default:
228
+ // 默认 OSD 导航已在上述 setEnabled(false) 中恢复
229
+ break;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * 销毁引擎与所有插件
235
+ */
236
+ public destroy(): void {
237
+ this.konva?.destroy();
238
+ this.anno?.destroy();
239
+ this.toolbar?.destroy();
240
+ this.selection?.destroy();
241
+ this.scalebar?.destroy();
242
+ this.colorAdjust?.destroy();
243
+ this.viewer.destroy();
244
+ this.options.element.innerHTML = "";
245
+ }
246
+ }
@@ -0,0 +1,185 @@
1
+ import Konva from 'konva';
2
+ import { MedViewerEngine } from './Engine';
3
+ import { BaseAnnotator } from './BaseAnnotator';
4
+
5
+ export class KonvaAnnotator extends BaseAnnotator {
6
+ public stage: Konva.Stage;
7
+ public layer: Konva.Layer;
8
+ private overlay: HTMLDivElement;
9
+
10
+ // 内部绘图状态
11
+ private isDrawing: boolean = false;
12
+ private currentShape: Konva.Rect | Konva.Arrow | null = null;
13
+ private currentTool: 'rect' | 'arrow' | null = null;
14
+ private startPoint: { x: number; y: number } = { x: 0, y: 0 };
15
+
16
+ constructor(engine: MedViewerEngine) {
17
+ super(engine);
18
+
19
+ const viewerElement = this.engine.viewer.element;
20
+
21
+ // 1. 创建覆盖层
22
+ this.overlay = document.createElement('div');
23
+ this.overlay.className = 'konva-annotator-overlay';
24
+ this.overlay.style.cssText = `
25
+ position: absolute;
26
+ top: 0; left: 0;
27
+ width: 100%; height: 100%;
28
+ pointer-events: none;
29
+ z-index: 10;
30
+ `;
31
+ viewerElement.appendChild(this.overlay);
32
+
33
+ // 2. 初始化 Konva Stage
34
+ this.stage = new Konva.Stage({
35
+ container: this.overlay,
36
+ width: viewerElement.clientWidth,
37
+ height: viewerElement.clientHeight
38
+ });
39
+
40
+ this.layer = new Konva.Layer();
41
+ this.stage.add(this.layer);
42
+
43
+ // 3. 开启同步与事件监听
44
+ // this.initSync();
45
+ this.initDrawingEvents();
46
+ }
47
+
48
+ /**
49
+ * 实现基类方法:启用/禁用标注层
50
+ */
51
+ public setEnabled(enabled: boolean): void {
52
+ this.isEnabled = enabled;
53
+ this.overlay.style.pointerEvents = enabled ? 'auto' : 'none';
54
+ this.engine.viewer.setMouseNavEnabled(!enabled); // 绘图时禁用 OSD 拖拽
55
+
56
+ if (enabled) {
57
+ this.stage.container().style.cursor = 'crosshair';
58
+ } else {
59
+ this.stage.container().style.cursor = 'default';
60
+ this.setTool(null);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * 实现基类方法:设置工具
66
+ */
67
+ public setTool(tool: 'rect' | 'arrow' | null): void {
68
+ this.currentTool = tool;
69
+ }
70
+
71
+ /**
72
+ * 核心同步逻辑:保持 Canvas 与 OSD 图片位置重合
73
+ */
74
+ // private initSync(): void {
75
+ // const sync = () => {
76
+ // const viewport = this.engine.viewer.viewport;
77
+ // const zoom = viewport.getZoom(true);
78
+ // const containerWidth = viewport.getContainerSize().x;
79
+ // const pixelPos = viewport.pixelFromPoint(new Konva.util.Point(0, 0), true);
80
+
81
+ // const konvaScale = zoom * containerWidth;
82
+
83
+ // this.stage.position({ x: pixelPos.x, y: pixelPos.y });
84
+ // this.stage.scale({ x: konvaScale, y: konvaScale });
85
+ // this.stage.batchDraw();
86
+ // };
87
+
88
+ // this.engine.viewer.addHandler('animation', sync);
89
+ // this.engine.viewer.addHandler('canvas-drag', sync);
90
+ // this.engine.viewer.addHandler('viewport-change', sync);
91
+
92
+ // this.engine.viewer.addHandler('resize', () => {
93
+ // const el = this.engine.viewer.element;
94
+ // this.stage.width(el.clientWidth);
95
+ // this.stage.height(el.clientHeight);
96
+ // sync();
97
+ // });
98
+
99
+ // sync();
100
+ // }
101
+
102
+ /**
103
+ * 监听绘图相关的鼠标事件
104
+ */
105
+ private initDrawingEvents(): void {
106
+ this.stage.on('mousedown touchstart', (e) => {
107
+ if (!this.isEnabled || !this.currentTool) return;
108
+
109
+ this.isDrawing = true;
110
+ const pos = this.getRelativePointerPosition();
111
+ this.startPoint = pos;
112
+
113
+ if (this.currentTool === 'rect') {
114
+ this.currentShape = new Konva.Rect({
115
+ x: pos.x,
116
+ y: pos.y,
117
+ width: 0,
118
+ height: 0,
119
+ stroke: '#00ff00',
120
+ strokeWidth: 2,
121
+ strokeScaleEnabled: false, // 关键:保证缩放时线宽不变
122
+ draggable: true
123
+ });
124
+ }
125
+
126
+ if (this.currentShape) {
127
+ this.layer.add(this.currentShape);
128
+ }
129
+ });
130
+
131
+ this.stage.on('mousemove touchmove', () => {
132
+ if (!this.isDrawing || !this.currentShape) return;
133
+
134
+ const pos = this.getRelativePointerPosition();
135
+
136
+ if (this.currentTool === 'rect') {
137
+ const shape = this.currentShape as Konva.Rect;
138
+ shape.width(pos.x - this.startPoint.x);
139
+ shape.height(pos.y - this.startPoint.y);
140
+ }
141
+
142
+ this.layer.batchDraw();
143
+ });
144
+
145
+ this.stage.on('mouseup touchend', () => {
146
+ this.isDrawing = false;
147
+ this.currentShape = null;
148
+ });
149
+ }
150
+
151
+ /**
152
+ * 工具方法:获取相对于图像(0~1)空间的鼠标位置
153
+ */
154
+ private getRelativePointerPosition() {
155
+ const transform = this.stage.getAbsoluteTransform().copy().invert();
156
+ const pos = this.stage.getPointerPosition() || { x: 0, y: 0 };
157
+ return transform.point(pos);
158
+ }
159
+
160
+ // --- 实现基类要求的其他数据方法 ---
161
+
162
+ public getAnnotations(): any[] {
163
+ // 导出为简单 JSON 数组
164
+ return this.layer.getChildren().map(node => node.toObject());
165
+ }
166
+
167
+ public setAnnotations(data: any[]): void {
168
+ this.clear();
169
+ data.forEach(obj => {
170
+ const node = Konva.Node.create(obj);
171
+ this.layer.add(node);
172
+ });
173
+ this.layer.draw();
174
+ }
175
+
176
+ public clear(): void {
177
+ this.layer.destroyChildren();
178
+ this.layer.draw();
179
+ }
180
+
181
+ public destroy(): void {
182
+ this.stage.destroy();
183
+ this.overlay.remove();
184
+ }
185
+ }
@@ -0,0 +1,87 @@
1
+ import OpenSeadragon from "openseadragon";
2
+ import "../plugins/openseadragon-scalebar.js";
3
+
4
+ export enum ScalebarType {
5
+ NONE = 0,
6
+ MICROSCOPY = 1,
7
+ MAP = 2,
8
+ }
9
+
10
+ export enum ScalebarLocation {
11
+ NONE = 0,
12
+ TOP_LEFT = 1,
13
+ TOP_RIGHT = 2,
14
+ BOTTOM_RIGHT = 3,
15
+ BOTTOM_LEFT = 4,
16
+ }
17
+
18
+ export interface ScalebarOptions {
19
+ type?: ScalebarType;
20
+ pixelsPerMeter?: number;
21
+ xOffset?: number;
22
+ yOffset?: number;
23
+ stayInsideImage?: boolean;
24
+ color?: string;
25
+ fontColor?: string;
26
+ backgroundColor?: string;
27
+ fontSize?: string;
28
+ fontFamily?: string;
29
+ barThickness?: number;
30
+ minWidth?: string;
31
+ location?: ScalebarLocation;
32
+ referenceItemIdx?: number;
33
+ sizeAndTextRenderer?: (
34
+ ppm: number,
35
+ minSize: number,
36
+ ) => { size: number; text: string };
37
+ }
38
+
39
+ export class ScalebarPlugin {
40
+ private viewer: OpenSeadragon.Viewer;
41
+ private options: ScalebarOptions;
42
+ private scalebar: any; // OpenSeadragonScalebar instance
43
+ constructor(viewer: OpenSeadragon.Viewer, options: ScalebarOptions = {}) {
44
+ this.viewer = viewer;
45
+ //type传入字符串时,转换为枚举类型
46
+ if (typeof options.type === "string") {
47
+ options.type = ScalebarType[options.type as keyof typeof ScalebarType];
48
+ }
49
+ //location传入字符串时,转换为枚举类型
50
+ if (typeof options.location === "string") {
51
+ options.location =
52
+ ScalebarLocation[options.location as keyof typeof ScalebarLocation];
53
+ }
54
+ this.options = {
55
+ type: ScalebarType.MAP,
56
+ pixelsPerMeter: 1,
57
+ xOffset: 10,
58
+ yOffset: 10,
59
+ stayInsideImage: true,
60
+ color: "rgb(0, 0, 0)",
61
+ fontColor: "rgb(0, 0, 0)",
62
+ fontSize: "14px",
63
+ fontFamily: "sans-serif",
64
+ barThickness: 1.5,
65
+ minWidth: "75px",
66
+ location: ScalebarLocation.NONE,
67
+ referenceItemIdx: 0,
68
+ ...options,
69
+ };
70
+ console.log(this.options);
71
+
72
+ this.init();
73
+ }
74
+
75
+ private init(): void {
76
+ // Cast viewer to any to access the scalebar method added by the plugin
77
+ (this.viewer as any).scalebar(this.options);
78
+ }
79
+
80
+ public destroy(): void {
81
+ // Cleanup if necessary
82
+ if (this.scalebar && typeof this.scalebar.destroy === "function") {
83
+ this.scalebar.destroy();
84
+ }
85
+ this.scalebar = null;
86
+ }
87
+ }