@e-infra/react-molstar-wrapper 0.0.8 → 0.0.9
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/README.md +438 -9
- package/dist/index.cjs.js +71 -6
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +71 -6
- package/dist/index.es.js.map +1 -1
- package/dist/types/core/Plugin.d.ts +8 -1
- package/dist/types/core/Plugin.d.ts.map +1 -1
- package/dist/types/core/types.d.ts +1 -1
- package/dist/types/core/types.d.ts.map +1 -1
- package/dist/types/react/Viewer.d.ts +10 -5
- package/dist/types/react/Viewer.d.ts.map +1 -1
- package/dist/types/react/types.d.ts +1 -0
- package/dist/types/react/types.d.ts.map +1 -1
- package/package.json +15 -16
package/README.md
CHANGED
|
@@ -5,6 +5,52 @@
|
|
|
5
5
|
|
|
6
6
|
A React wrapper for the Mol* molecular visualization library, providing seamless integration of molecular structure visualization capabilities into React applications.
|
|
7
7
|
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Features](#features)
|
|
11
|
+
- [Overview](#overview)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Usage](#usage)
|
|
14
|
+
- [React](#react)
|
|
15
|
+
- [Next.js Integration](#nextjs-integration)
|
|
16
|
+
- [Important Notice](#important-notice)
|
|
17
|
+
- [Props](#props)
|
|
18
|
+
- [Data Source (Required)](#data-source-required)
|
|
19
|
+
- [Configuration](#configuration)
|
|
20
|
+
- [Animation Controls](#animation-controls)
|
|
21
|
+
- [ViewerRef Methods](#viewerref-methods)
|
|
22
|
+
- [Protein Type](#protein-type)
|
|
23
|
+
- [Examples](#examples)
|
|
24
|
+
- [Basic Example](#basic-example)
|
|
25
|
+
- [Example with Multiple Proteins and Custom Styling](#example-with-multiple-proteins-and-custom-styling)
|
|
26
|
+
- [Example with Ref Methods](#example-with-ref-methods)
|
|
27
|
+
- [Example with Rock Animation](#example-with-rock-animation)
|
|
28
|
+
- [Example with Local File](#example-with-local-file)
|
|
29
|
+
- [Example with Custom Model Source URLs](#example-with-custom-model-source-urls)
|
|
30
|
+
- [Advanced Usage](#advanced-usage)
|
|
31
|
+
- [Using Precomputed MVS Data](#using-precomputed-mvs-data)
|
|
32
|
+
- [Combining Multiple Features](#combining-multiple-features)
|
|
33
|
+
- [Documentation](#documentation)
|
|
34
|
+
- [License](#license)
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- **Molecular Visualization** - Display 3D molecular structures using the Mol* visualization engine
|
|
39
|
+
- **React Component** - Simple React component with minimal setup
|
|
40
|
+
- **Multiple Data Sources** - Load proteins from UniProt IDs or local files (PDB, CIF formats)
|
|
41
|
+
- **Customizable Appearance** - Configure background colors, labels, and UI presets (minimal, standard, expanded)
|
|
42
|
+
- **Animations** - Support for spin and rock animations with configurable speeds
|
|
43
|
+
- **Domain Highlighting** - Focus on specific protein domains using chopping data
|
|
44
|
+
- **Transform Controls** - Update protein superposition with custom rotation and translation
|
|
45
|
+
- **Imperative API** - Access viewer methods via forwarded ref for programmatic control
|
|
46
|
+
- **Next.js Compatible** - Works with Next.js using dynamic imports
|
|
47
|
+
- **Plugin Lifecycle Management** - Shared plugin instance with reference counting and automatic garbage collection
|
|
48
|
+
- **Multiple Representations** - Choose from cartoon, ball-and-stick, spacefill, line, surface, or backbone representations
|
|
49
|
+
- **Custom Model Sources** - Configure custom URLs for remote model retrieval
|
|
50
|
+
- **Precomputed MVS Support** - Load precomputed Mol* View State data directly
|
|
51
|
+
- **Styling Support** - Apply custom CSS classes and heights to the viewer container
|
|
52
|
+
- **Responsive Design** - Viewer fills available height by default, with optional explicit sizing
|
|
53
|
+
|
|
8
54
|
## Overview
|
|
9
55
|
|
|
10
56
|
This package provides a lightweight React wrapper around the [Mol*](https://molstar.org/) molecular visualization library, enabling developers to easily integrate 3D molecular structure visualization into their React applications.
|
|
@@ -52,37 +98,416 @@ const Viewer = dynamic(
|
|
|
52
98
|
|
|
53
99
|
This approach prevents errors related to `document` not being defined during server-side rendering, as Mol* expects the DOM to be available during initialization.
|
|
54
100
|
|
|
55
|
-
### Notice
|
|
101
|
+
### Important Notice
|
|
102
|
+
|
|
103
|
+
> [!IMPORTANT]
|
|
104
|
+
> It is important to include library styles as well! Otherwise loader and error view will be broken.
|
|
105
|
+
> ```typescript
|
|
106
|
+
> import "react-molstar-wrapper/style.css";
|
|
107
|
+
> ```
|
|
108
|
+
|
|
109
|
+
## Props
|
|
110
|
+
|
|
111
|
+
The `Viewer` component accepts the following props:
|
|
112
|
+
|
|
113
|
+
### Data Source (Required)
|
|
114
|
+
|
|
115
|
+
Exactly one of these must be provided:
|
|
116
|
+
|
|
117
|
+
| Prop | Type | Description |
|
|
118
|
+
|------|------|-------------|
|
|
119
|
+
| `proteins` | `Protein[]` | Array of protein objects to visualize. When provided, the component will call `createMVS` to compute the view state. |
|
|
120
|
+
| `mvs` | `MVSData` | Precomputed MVS (Mol* View State) data. If provided, the viewer loads this directly without calling `createMVS`. |
|
|
121
|
+
|
|
122
|
+
### Configuration
|
|
123
|
+
|
|
124
|
+
| Prop | Type | Default | Description |
|
|
125
|
+
|------|------|---------|-------------|
|
|
126
|
+
| `modelSourceUrls` | `Partial<ModelSourceUrls>` | `undefined` | Optional lookup mapping used by `createMVS` to resolve model source URLs for remote model retrieval when `proteins` is used. Format: `{ uniProtId: (id: string) => string }`. |
|
|
127
|
+
| `initialUI` | `"minimal" \| "standard" \| "expanded"` | `"standard"` | Which initial UI preset to use for the embedded plugin. Controls the visibility of control chrome. |
|
|
128
|
+
| `bgColor` | `ColorHEX` | `"#ffffff"` | Background color for the viewer (any valid CSS hex color). |
|
|
129
|
+
| `labels` | `boolean` | `true` | Whether to show labels in the viewer. |
|
|
130
|
+
| `height` | `number` | `undefined` | Optional explicit height (in pixels) for the outer wrapper. If omitted, the wrapper will fill available height (`100%`). |
|
|
131
|
+
| `className` | `string` | `undefined` | Optional CSS class to apply to the outer wrapper. |
|
|
132
|
+
|
|
133
|
+
### Animation Controls
|
|
134
|
+
|
|
135
|
+
At most one of these may be provided:
|
|
136
|
+
|
|
137
|
+
| Prop | Type | Default | Description |
|
|
138
|
+
|------|------|---------|-------------|
|
|
139
|
+
| `spin` | `boolean` | `false` | Whether to enable continuous spin animation. Mutually exclusive with `rock`. |
|
|
140
|
+
| `rock` | `boolean` | `false` | Whether to enable rock animation (back-and-forth). Mutually exclusive with `spin`. |
|
|
141
|
+
| `spinSpeed` | `number` | `0.05` | Speed multiplier for the spin animation. |
|
|
142
|
+
| `rockSpeed` | `number` | `0.2` | Speed multiplier for the rock animation. |
|
|
143
|
+
|
|
144
|
+
## ViewerRef Methods
|
|
145
|
+
|
|
146
|
+
The component forwards a ref exposing the following async methods:
|
|
147
|
+
|
|
148
|
+
### `highlight(proteinIndex: number, label: string): Promise<void>`
|
|
149
|
+
|
|
150
|
+
Focuses/highlights a domain within the specified protein by matching the label against the protein's chopping data. The domain's first range (start/end) is used to focus the view.
|
|
151
|
+
|
|
152
|
+
- **Parameters:**
|
|
153
|
+
- `proteinIndex` - Index of the protein in the `proteins` array
|
|
154
|
+
- `label` - Label of the domain to highlight (must match a label in the protein's `chopping` data)
|
|
155
|
+
|
|
156
|
+
- **Behavior:** No-op if plugin, proteins, or matching domain is not available.
|
|
157
|
+
|
|
158
|
+
### `reset(): Promise<void>`
|
|
159
|
+
|
|
160
|
+
Resets the plugin's view to its default/original pose.
|
|
161
|
+
|
|
162
|
+
### `updateSuperposition(proteinIndex: number, translation?, rotation?): Promise<void>`
|
|
163
|
+
|
|
164
|
+
Updates the transform for a loaded structure (protein) without reloading the entire scene.
|
|
165
|
+
|
|
166
|
+
- **Parameters:**
|
|
167
|
+
- `proteinIndex` - Index of the protein to update
|
|
168
|
+
- `translation` - Optional `[x, y, z]` tuple for translation
|
|
169
|
+
- `rotation` - Optional 3x3 matrix represented as `[[r11,r12,r13],[r21,r22,r23],[r31,r32,r33]]`
|
|
170
|
+
|
|
171
|
+
- **Behavior:** This method relies on the Mol* plugin API to update transforms in-place.
|
|
172
|
+
|
|
173
|
+
## Protein Type
|
|
174
|
+
|
|
175
|
+
The `Protein` type represents a protein structure to visualize:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
type Protein = {
|
|
179
|
+
// Exactly one of these must be provided
|
|
180
|
+
uniProtId?: string; // UniProt ID for remote fetching
|
|
181
|
+
file?: File; // Local file to load
|
|
182
|
+
|
|
183
|
+
// Optional properties
|
|
184
|
+
chain?: string; // Chain identifier
|
|
185
|
+
superposition?: {
|
|
186
|
+
rotation: Matrix3D; // 3x3 rotation matrix
|
|
187
|
+
translation: Vector3D; // [x, y, z] translation vector
|
|
188
|
+
};
|
|
189
|
+
chopping?: Chopping[]; // Domain definitions for highlighting
|
|
190
|
+
representation?: "cartoon" | "ball_and_stick" | "spacefill" | "line" | "surface" | "backbone";
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Chopping Type
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
type Chopping = {
|
|
198
|
+
label: string; // Domain label for identification
|
|
199
|
+
showLabel?: boolean; // Whether to show the label in the viewer
|
|
200
|
+
ranges: {
|
|
201
|
+
start: number; // Residue start position
|
|
202
|
+
end: number; // Residue end position
|
|
203
|
+
}[];
|
|
204
|
+
}[];
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Examples
|
|
208
|
+
|
|
209
|
+
### Basic Example
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import "react-molstar-wrapper/style.css";
|
|
213
|
+
import Viewer from "react-molstar-wrapper";
|
|
214
|
+
import type { Protein } from "react-molstar-wrapper";
|
|
215
|
+
|
|
216
|
+
const proteins: Protein[] = [
|
|
217
|
+
{
|
|
218
|
+
uniProtId: "P12345",
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
function App() {
|
|
223
|
+
return (
|
|
224
|
+
<Viewer
|
|
225
|
+
proteins={proteins}
|
|
226
|
+
spin={true}
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Example with Multiple Proteins and Custom Styling
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import "react-molstar-wrapper/style.css";
|
|
236
|
+
import Viewer from "react-molstar-wrapper";
|
|
237
|
+
import type { Protein } from "react-molstar-wrapper";
|
|
238
|
+
|
|
239
|
+
const proteins: Protein[] = [
|
|
240
|
+
{
|
|
241
|
+
uniProtId: "P12345",
|
|
242
|
+
representation: "cartoon",
|
|
243
|
+
chopping: [
|
|
244
|
+
{
|
|
245
|
+
label: "Domain A",
|
|
246
|
+
ranges: [{ start: 1, end: 100 }],
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
label: "Domain B",
|
|
250
|
+
ranges: [{ start: 101, end: 200 }],
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
uniProtId: "P67890",
|
|
256
|
+
representation: "ball_and_stick",
|
|
257
|
+
superposition: {
|
|
258
|
+
rotation: [
|
|
259
|
+
[1, 0, 0],
|
|
260
|
+
[0, 1, 0],
|
|
261
|
+
[0, 0, 1],
|
|
262
|
+
],
|
|
263
|
+
translation: [10, 0, 0],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
function App() {
|
|
269
|
+
return (
|
|
270
|
+
<Viewer
|
|
271
|
+
proteins={proteins}
|
|
272
|
+
initialUI="minimal"
|
|
273
|
+
bgColor="#1a1a2e"
|
|
274
|
+
labels={true}
|
|
275
|
+
height={600}
|
|
276
|
+
className="my-viewer"
|
|
277
|
+
/>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
```
|
|
56
281
|
|
|
57
|
-
|
|
282
|
+
### Example with Ref Methods
|
|
58
283
|
|
|
59
284
|
```typescript
|
|
60
285
|
import "react-molstar-wrapper/style.css";
|
|
286
|
+
import Viewer, { type ViewerRef } from "react-molstar-wrapper";
|
|
287
|
+
import type { Protein } from "react-molstar-wrapper";
|
|
288
|
+
import { useRef } from "react";
|
|
289
|
+
|
|
290
|
+
const proteins: Protein[] = [
|
|
291
|
+
{
|
|
292
|
+
uniProtId: "P12345",
|
|
293
|
+
chopping: [
|
|
294
|
+
{
|
|
295
|
+
label: "Active Site",
|
|
296
|
+
ranges: [{ start: 50, end: 75 }],
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
function App() {
|
|
303
|
+
const viewerRef = useRef<ViewerRef | null>(null);
|
|
304
|
+
|
|
305
|
+
const handleHighlight = async () => {
|
|
306
|
+
await viewerRef.current?.highlight(0, "Active Site");
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const handleReset = async () => {
|
|
310
|
+
await viewerRef.current?.reset();
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const handleUpdateTransform = async () => {
|
|
314
|
+
await viewerRef.current?.updateSuperposition(
|
|
315
|
+
0,
|
|
316
|
+
[5, 0, 0],
|
|
317
|
+
[
|
|
318
|
+
[1, 0, 0],
|
|
319
|
+
[0, 1, 0],
|
|
320
|
+
[0, 0, 1],
|
|
321
|
+
]
|
|
322
|
+
);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<div>
|
|
327
|
+
<div style={{ marginBottom: "10px" }}>
|
|
328
|
+
<button onClick={handleHighlight}>Highlight Active Site</button>
|
|
329
|
+
<button onClick={handleReset}>Reset View</button>
|
|
330
|
+
<button onClick={handleUpdateTransform}>Update Transform</button>
|
|
331
|
+
</div>
|
|
332
|
+
<Viewer
|
|
333
|
+
ref={viewerRef}
|
|
334
|
+
proteins={proteins}
|
|
335
|
+
bgColor="#ffffff"
|
|
336
|
+
height={500}
|
|
337
|
+
/>
|
|
338
|
+
</div>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
61
341
|
```
|
|
62
342
|
|
|
63
|
-
### Example
|
|
343
|
+
### Example with Rock Animation
|
|
64
344
|
|
|
65
345
|
```typescript
|
|
346
|
+
import "react-molstar-wrapper/style.css";
|
|
347
|
+
import Viewer from "react-molstar-wrapper";
|
|
348
|
+
import type { Protein } from "react-molstar-wrapper";
|
|
349
|
+
|
|
350
|
+
const proteins: Protein[] = [
|
|
351
|
+
{
|
|
352
|
+
uniProtId: "P12345",
|
|
353
|
+
},
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
function App() {
|
|
357
|
+
return (
|
|
358
|
+
<Viewer
|
|
359
|
+
proteins={proteins}
|
|
360
|
+
rock={true}
|
|
361
|
+
rockSpeed={0.3}
|
|
362
|
+
bgColor="#f0f0f0"
|
|
363
|
+
/>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Example with Local File
|
|
66
369
|
|
|
370
|
+
```typescript
|
|
371
|
+
import "react-molstar-wrapper/style.css";
|
|
372
|
+
import Viewer from "react-molstar-wrapper";
|
|
373
|
+
import type { Protein } from "react-molstar-wrapper";
|
|
374
|
+
import { useState } from "react";
|
|
375
|
+
|
|
376
|
+
function App() {
|
|
377
|
+
const [protein, setProtein] = useState<Protein | undefined>();
|
|
378
|
+
|
|
379
|
+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
380
|
+
const file = event.target.files?.[0];
|
|
381
|
+
if (file) {
|
|
382
|
+
setProtein({ file });
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
<div>
|
|
388
|
+
<input type="file" onChange={handleFileChange} accept=".pdb,.cif" />
|
|
389
|
+
{protein && (
|
|
390
|
+
<Viewer
|
|
391
|
+
proteins={[protein]}
|
|
392
|
+
height={500}
|
|
393
|
+
/>
|
|
394
|
+
)}
|
|
395
|
+
</div>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Example with Custom Model Source URLs
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
67
403
|
import "react-molstar-wrapper/style.css";
|
|
68
404
|
import Viewer from "react-molstar-wrapper";
|
|
69
405
|
import type { Protein } from "react-molstar-wrapper";
|
|
70
406
|
|
|
71
407
|
const proteins: Protein[] = [
|
|
72
408
|
{
|
|
73
|
-
uniProtId: "P12345"
|
|
409
|
+
uniProtId: "P12345",
|
|
74
410
|
},
|
|
75
411
|
];
|
|
76
412
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
413
|
+
const modelSourceUrls = {
|
|
414
|
+
uniProtId: (id: string) => `https://api.example.com/protein/${id}`,
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
function App() {
|
|
418
|
+
return (
|
|
419
|
+
<Viewer
|
|
420
|
+
proteins={proteins}
|
|
421
|
+
modelSourceUrls={modelSourceUrls}
|
|
422
|
+
bgColor="#ffffff"
|
|
423
|
+
/>
|
|
424
|
+
);
|
|
425
|
+
}
|
|
81
426
|
```
|
|
82
427
|
|
|
83
428
|
## Advanced Usage
|
|
84
429
|
|
|
85
|
-
|
|
430
|
+
### Using Precomputed MVS Data
|
|
431
|
+
|
|
432
|
+
If you have precomputed MVS (Mol* View State) data, you can pass it directly to the viewer:
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
import "react-molstar-wrapper/style.css";
|
|
436
|
+
import Viewer from "react-molstar-wrapper";
|
|
437
|
+
import type { MVSData } from "molstar/lib/extensions/mvs/mvs-data.d.ts";
|
|
438
|
+
|
|
439
|
+
const mvsData: MVSData = {
|
|
440
|
+
// Your precomputed MVS data
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
function App() {
|
|
444
|
+
return (
|
|
445
|
+
<Viewer
|
|
446
|
+
mvs={mvsData}
|
|
447
|
+
bgColor="#ffffff"
|
|
448
|
+
/>
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Combining Multiple Features
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import "react-molstar-wrapper/style.css";
|
|
457
|
+
import Viewer, { type ViewerRef } from "react-molstar-wrapper";
|
|
458
|
+
import type { Protein } from "react-molstar-wrapper";
|
|
459
|
+
import { useRef, useEffect } from "react";
|
|
460
|
+
|
|
461
|
+
const proteins: Protein[] = [
|
|
462
|
+
{
|
|
463
|
+
uniProtId: "P12345",
|
|
464
|
+
representation: "cartoon",
|
|
465
|
+
chopping: [
|
|
466
|
+
{ label: "N-terminal", ranges: [{ start: 1, end: 50 }] },
|
|
467
|
+
{ label: "Core", ranges: [{ start: 51, end: 150 }] },
|
|
468
|
+
{ label: "C-terminal", ranges: [{ start: 151, end: 200 }] },
|
|
469
|
+
],
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
uniProtId: "P67890",
|
|
473
|
+
representation: "surface",
|
|
474
|
+
superposition: {
|
|
475
|
+
rotation: [
|
|
476
|
+
[0.9, -0.1, 0],
|
|
477
|
+
[0.1, 0.9, 0],
|
|
478
|
+
[0, 0, 1],
|
|
479
|
+
],
|
|
480
|
+
translation: [15, 0, 0],
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
];
|
|
484
|
+
|
|
485
|
+
function App() {
|
|
486
|
+
const viewerRef = useRef<ViewerRef | null>(null);
|
|
487
|
+
|
|
488
|
+
// Auto-highlight a domain after viewer loads
|
|
489
|
+
useEffect(() => {
|
|
490
|
+
const timer = setTimeout(async () => {
|
|
491
|
+
await viewerRef.current?.highlight(0, "Core");
|
|
492
|
+
}, 2000);
|
|
493
|
+
|
|
494
|
+
return () => clearTimeout(timer);
|
|
495
|
+
}, []);
|
|
496
|
+
|
|
497
|
+
return (
|
|
498
|
+
<Viewer
|
|
499
|
+
ref={viewerRef}
|
|
500
|
+
proteins={proteins}
|
|
501
|
+
initialUI="expanded"
|
|
502
|
+
bgColor="#0d1117"
|
|
503
|
+
labels={true}
|
|
504
|
+
spin={false}
|
|
505
|
+
height={700}
|
|
506
|
+
className="custom-viewer"
|
|
507
|
+
/>
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
```
|
|
86
511
|
|
|
87
512
|
## Documentation
|
|
88
513
|
|
|
@@ -92,3 +517,7 @@ const proteins: Protein[] = [
|
|
|
92
517
|
## License
|
|
93
518
|
|
|
94
519
|
See [LICENSE.md](LICENSE.md) for details.
|
|
520
|
+
|
|
521
|
+
## Acknowledgments
|
|
522
|
+
|
|
523
|
+
This library is built on top of [Mol*](https://molstar.org/), an open-source molecular visualization toolkit.
|
package/dist/index.cjs.js
CHANGED
|
@@ -478,6 +478,8 @@ function ErrorView() {
|
|
|
478
478
|
class Plugin {
|
|
479
479
|
plugin;
|
|
480
480
|
objectUrls;
|
|
481
|
+
labelsVisible = true;
|
|
482
|
+
storedLabels = [];
|
|
481
483
|
constructor(plugin) {
|
|
482
484
|
this.plugin = plugin;
|
|
483
485
|
this.objectUrls = /* @__PURE__ */ new Set();
|
|
@@ -557,6 +559,60 @@ class Plugin {
|
|
|
557
559
|
renderer: { backgroundColor: color.Color.fromHexString(hexString) }
|
|
558
560
|
});
|
|
559
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* Set the visibility of labels by removing or adding them to the state tree.
|
|
564
|
+
* This approach is more reliable than trying to modify the isHidden property.
|
|
565
|
+
*/
|
|
566
|
+
async setLabelsVisibility(visible) {
|
|
567
|
+
if (this.labelsVisible === visible) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
try {
|
|
571
|
+
const state = this.plugin.state.data;
|
|
572
|
+
if (visible) {
|
|
573
|
+
if (this.storedLabels.length > 0) {
|
|
574
|
+
const update = state.build();
|
|
575
|
+
for (const storedLabel of this.storedLabels) {
|
|
576
|
+
const parentCell = state.cells.get(storedLabel.parentRef);
|
|
577
|
+
if (!parentCell) continue;
|
|
578
|
+
update.to(storedLabel.parentRef).apply(
|
|
579
|
+
storedLabel.transform.transformer,
|
|
580
|
+
storedLabel.transform.params
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
await this.plugin.runTask(state.updateTree(update));
|
|
584
|
+
}
|
|
585
|
+
this.labelsVisible = true;
|
|
586
|
+
} else {
|
|
587
|
+
const allObjects = state.selectQ((q) => q.root.subtree());
|
|
588
|
+
const labels = allObjects.filter((cell) => {
|
|
589
|
+
if (!cell.obj) return false;
|
|
590
|
+
const label = cell.obj.label || "";
|
|
591
|
+
const description = cell.obj.description || "";
|
|
592
|
+
return label === "MVS Custom Label" || description === "MVS Custom Label";
|
|
593
|
+
});
|
|
594
|
+
this.storedLabels = [];
|
|
595
|
+
if (labels.length > 0) {
|
|
596
|
+
const update = state.build();
|
|
597
|
+
for (const labelCell of labels) {
|
|
598
|
+
const parentRef = labelCell.transform.parent;
|
|
599
|
+
this.storedLabels.push({
|
|
600
|
+
parentRef,
|
|
601
|
+
transform: {
|
|
602
|
+
transformer: labelCell.transform.transformer,
|
|
603
|
+
params: labelCell.transform.params
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
update.delete(labelCell.transform.ref);
|
|
607
|
+
}
|
|
608
|
+
await this.plugin.runTask(state.updateTree(update));
|
|
609
|
+
}
|
|
610
|
+
this.labelsVisible = false;
|
|
611
|
+
}
|
|
612
|
+
} catch (e) {
|
|
613
|
+
console.error("Error setting label visibility:", e);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
560
616
|
setAnimation(type, speed) {
|
|
561
617
|
if (type === "off") {
|
|
562
618
|
this.plugin.canvas3d?.setProps({
|
|
@@ -584,15 +640,15 @@ class Plugin {
|
|
|
584
640
|
}
|
|
585
641
|
});
|
|
586
642
|
}
|
|
587
|
-
async focusOnDomain(domainStart, domainEnd) {
|
|
643
|
+
async focusOnDomain(domainStart, domainEnd, proteinIndex = 0) {
|
|
588
644
|
const state = this.plugin.state.data;
|
|
589
645
|
const structures = state.selectQ(
|
|
590
646
|
(q) => q.rootsOfType(objects.PluginStateObject.Molecule.Structure)
|
|
591
647
|
);
|
|
592
|
-
if (structures.length === 0) {
|
|
648
|
+
if (structures.length === 0 || proteinIndex >= structures.length) {
|
|
593
649
|
return;
|
|
594
650
|
}
|
|
595
|
-
const structureCell = structures[
|
|
651
|
+
const structureCell = structures[proteinIndex];
|
|
596
652
|
if (!structureCell?.obj) {
|
|
597
653
|
return;
|
|
598
654
|
}
|
|
@@ -773,7 +829,7 @@ function normalizeChoppingData(chopping) {
|
|
|
773
829
|
}
|
|
774
830
|
return chopping.map((entry) => ({
|
|
775
831
|
label: entry.label,
|
|
776
|
-
showLabel: entry.showLabel,
|
|
832
|
+
showLabel: entry.showLabel ?? true,
|
|
777
833
|
ranges: entry.ranges.map((range) => ({
|
|
778
834
|
start: Math.min(range.start, range.end),
|
|
779
835
|
end: Math.max(range.start, range.end)
|
|
@@ -1109,7 +1165,8 @@ const Viewer = require$$0.forwardRef(function Viewer2({
|
|
|
1109
1165
|
rock = false,
|
|
1110
1166
|
rockSpeed = 0.2,
|
|
1111
1167
|
height,
|
|
1112
|
-
className
|
|
1168
|
+
className,
|
|
1169
|
+
labels = true
|
|
1113
1170
|
}, ref) {
|
|
1114
1171
|
const containerRef = require$$0.useRef(null);
|
|
1115
1172
|
const pluginRef = require$$0.useRef(null);
|
|
@@ -1132,7 +1189,7 @@ const Viewer = require$$0.forwardRef(function Viewer2({
|
|
|
1132
1189
|
const start = domain?.ranges[0]?.start;
|
|
1133
1190
|
const end = domain?.ranges[0]?.end;
|
|
1134
1191
|
if (start !== void 0 && end !== void 0) {
|
|
1135
|
-
await pluginRef.current.focusOnDomain(start, end);
|
|
1192
|
+
await pluginRef.current.focusOnDomain(start, end, proteinIndex);
|
|
1136
1193
|
}
|
|
1137
1194
|
},
|
|
1138
1195
|
reset: async () => {
|
|
@@ -1229,6 +1286,14 @@ const Viewer = require$$0.forwardRef(function Viewer2({
|
|
|
1229
1286
|
pluginRef.current.setBackgroundColor(bgColor);
|
|
1230
1287
|
}
|
|
1231
1288
|
}, [state, bgColor]);
|
|
1289
|
+
require$$0.useEffect(() => {
|
|
1290
|
+
if (state !== "success" || !pluginRef.current) {
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
pluginRef.current.setLabelsVisibility(labels).catch((error) => {
|
|
1294
|
+
console.error("Error setting label visibility:", error);
|
|
1295
|
+
});
|
|
1296
|
+
}, [state, labels]);
|
|
1232
1297
|
const styles = {
|
|
1233
1298
|
height: height ? `${height}px` : "100%",
|
|
1234
1299
|
position: "relative"
|