@judah-silva/rnacanvas 0.0.5 → 0.0.7
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 +0 -5
- package/dist/index.d.mts +8 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.js +53 -47
- package/dist/index.mjs +51 -45
- package/judah-silva-rnacanvas-0.0.6.tgz +0 -0
- package/package.json +6 -2
package/README.md
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -112,6 +112,7 @@ declare class Motif extends Group<Residue> {
|
|
|
112
112
|
rotateByQuaternion(quat: Quat): void;
|
|
113
113
|
setQuaternion(quat: Quat): void;
|
|
114
114
|
multiplyScalar(scalar: number): void;
|
|
115
|
+
setScale(scale: number): void;
|
|
115
116
|
get uuid(): string;
|
|
116
117
|
get quat(): Quat;
|
|
117
118
|
get scale(): number;
|
|
@@ -334,7 +335,7 @@ declare class RenderScene {
|
|
|
334
335
|
* @author Judah Silva <jusilva@csumb.edu>
|
|
335
336
|
*/
|
|
336
337
|
|
|
337
|
-
declare function
|
|
338
|
+
declare function parseAtomCoords(meshObject: MotifMesh): Promise<Vec3[]>;
|
|
338
339
|
|
|
339
340
|
/**
|
|
340
341
|
* Copyright (c) 2025 RNA3DS Lab CSUMB.
|
|
@@ -342,6 +343,7 @@ declare function updateAllMotifs(motifMeshArray: Motif[]): Promise<void>;
|
|
|
342
343
|
* @author Judah Silva
|
|
343
344
|
*/
|
|
344
345
|
|
|
346
|
+
type MotifMesh = Record<string, any>;
|
|
345
347
|
/**
|
|
346
348
|
* ________________________________________________________________________________________________
|
|
347
349
|
*/
|
|
@@ -356,7 +358,7 @@ declare function updateAllMotifs(motifMeshArray: Motif[]): Promise<void>;
|
|
|
356
358
|
* @returns {Promise<Motif>}
|
|
357
359
|
* @async
|
|
358
360
|
*/
|
|
359
|
-
declare function getMotif(
|
|
361
|
+
declare function getMotif(motifName: string, motifMesh: MotifMesh, motifColorHex?: string, includeAtoms?: boolean): Promise<Motif>;
|
|
360
362
|
|
|
361
363
|
/**
|
|
362
364
|
* Copyright (c) 2025 RNA3DS Lab CSUMB.
|
|
@@ -370,7 +372,7 @@ declare function getMotif(motifJSONFileName: string, motifColorHex?: string): Pr
|
|
|
370
372
|
* @param nucleotideData {number[][]}
|
|
371
373
|
* @returns Object of { vertices: number[][], indices: number[][] }
|
|
372
374
|
*/
|
|
373
|
-
declare function getPoints(nucleotideData: number[][]): {
|
|
375
|
+
declare function getPoints(nucleotideData: number[][], includeAtoms: boolean): {
|
|
374
376
|
vertices: number[][];
|
|
375
377
|
indices: number[][];
|
|
376
378
|
};
|
|
@@ -411,6 +413,7 @@ interface CanvasProps {
|
|
|
411
413
|
rendererBackgroundColor?: string;
|
|
412
414
|
rendererSizeIsWindow?: boolean;
|
|
413
415
|
cameraPositionZ?: number;
|
|
416
|
+
showRMSD?: boolean;
|
|
414
417
|
motifProps: MotifProps[];
|
|
415
418
|
customEventProps?: AnyEventProps[];
|
|
416
419
|
}
|
|
@@ -430,7 +433,7 @@ interface CanvasProps {
|
|
|
430
433
|
* @function Canvas {JSX.Element}
|
|
431
434
|
* @returns {JSX.Element}
|
|
432
435
|
*/
|
|
433
|
-
declare function Canvas({ rendererHeight, rendererWidth, rendererBackgroundColor, rendererSizeIsWindow, cameraPositionZ, motifProps, customEventProps, }: CanvasProps): JSX.Element;
|
|
436
|
+
declare function Canvas({ rendererHeight, rendererWidth, rendererBackgroundColor, rendererSizeIsWindow, cameraPositionZ, showRMSD, motifProps, customEventProps, }: CanvasProps): JSX.Element;
|
|
434
437
|
|
|
435
438
|
/**
|
|
436
439
|
* Copyright (c) 2025 RNA3DS Lab CSUMB.
|
|
@@ -521,4 +524,4 @@ declare function calculateRMSDSlide(coordinates1: Vec3[], coordinates2: Vec3[]):
|
|
|
521
524
|
|
|
522
525
|
declare function rotateAllPoints(atomCoords: Vec3[], quat: Quat): Vec3[];
|
|
523
526
|
|
|
524
|
-
export { Canvas, CanvasAttributeTypes, CanvasDataManager, type CanvasProps, type CustomEventProps, EventManager, Events, Group, Matrix4, MeshObject, Motif, type MotifProps, Quat, RenderScene, Residue, type ScoreInfo, Vec3, calculateAllKabschRMSD, calculateRMSD, calculateRMSDSlide, getMotif, getPoints, getRMSD, kabschSlidingWindow,
|
|
527
|
+
export { Canvas, CanvasAttributeTypes, CanvasDataManager, type CanvasProps, type CustomEventProps, EventManager, Events, Group, Matrix4, MeshObject, Motif, type MotifMesh, type MotifProps, Quat, RenderScene, Residue, type ScoreInfo, Vec3, calculateAllKabschRMSD, calculateRMSD, calculateRMSDSlide, getMotif, getPoints, getRMSD, kabschSlidingWindow, parseAtomCoords, rotateAllPoints };
|
package/dist/index.d.ts
CHANGED
|
@@ -112,6 +112,7 @@ declare class Motif extends Group<Residue> {
|
|
|
112
112
|
rotateByQuaternion(quat: Quat): void;
|
|
113
113
|
setQuaternion(quat: Quat): void;
|
|
114
114
|
multiplyScalar(scalar: number): void;
|
|
115
|
+
setScale(scale: number): void;
|
|
115
116
|
get uuid(): string;
|
|
116
117
|
get quat(): Quat;
|
|
117
118
|
get scale(): number;
|
|
@@ -334,7 +335,7 @@ declare class RenderScene {
|
|
|
334
335
|
* @author Judah Silva <jusilva@csumb.edu>
|
|
335
336
|
*/
|
|
336
337
|
|
|
337
|
-
declare function
|
|
338
|
+
declare function parseAtomCoords(meshObject: MotifMesh): Promise<Vec3[]>;
|
|
338
339
|
|
|
339
340
|
/**
|
|
340
341
|
* Copyright (c) 2025 RNA3DS Lab CSUMB.
|
|
@@ -342,6 +343,7 @@ declare function updateAllMotifs(motifMeshArray: Motif[]): Promise<void>;
|
|
|
342
343
|
* @author Judah Silva
|
|
343
344
|
*/
|
|
344
345
|
|
|
346
|
+
type MotifMesh = Record<string, any>;
|
|
345
347
|
/**
|
|
346
348
|
* ________________________________________________________________________________________________
|
|
347
349
|
*/
|
|
@@ -356,7 +358,7 @@ declare function updateAllMotifs(motifMeshArray: Motif[]): Promise<void>;
|
|
|
356
358
|
* @returns {Promise<Motif>}
|
|
357
359
|
* @async
|
|
358
360
|
*/
|
|
359
|
-
declare function getMotif(
|
|
361
|
+
declare function getMotif(motifName: string, motifMesh: MotifMesh, motifColorHex?: string, includeAtoms?: boolean): Promise<Motif>;
|
|
360
362
|
|
|
361
363
|
/**
|
|
362
364
|
* Copyright (c) 2025 RNA3DS Lab CSUMB.
|
|
@@ -370,7 +372,7 @@ declare function getMotif(motifJSONFileName: string, motifColorHex?: string): Pr
|
|
|
370
372
|
* @param nucleotideData {number[][]}
|
|
371
373
|
* @returns Object of { vertices: number[][], indices: number[][] }
|
|
372
374
|
*/
|
|
373
|
-
declare function getPoints(nucleotideData: number[][]): {
|
|
375
|
+
declare function getPoints(nucleotideData: number[][], includeAtoms: boolean): {
|
|
374
376
|
vertices: number[][];
|
|
375
377
|
indices: number[][];
|
|
376
378
|
};
|
|
@@ -411,6 +413,7 @@ interface CanvasProps {
|
|
|
411
413
|
rendererBackgroundColor?: string;
|
|
412
414
|
rendererSizeIsWindow?: boolean;
|
|
413
415
|
cameraPositionZ?: number;
|
|
416
|
+
showRMSD?: boolean;
|
|
414
417
|
motifProps: MotifProps[];
|
|
415
418
|
customEventProps?: AnyEventProps[];
|
|
416
419
|
}
|
|
@@ -430,7 +433,7 @@ interface CanvasProps {
|
|
|
430
433
|
* @function Canvas {JSX.Element}
|
|
431
434
|
* @returns {JSX.Element}
|
|
432
435
|
*/
|
|
433
|
-
declare function Canvas({ rendererHeight, rendererWidth, rendererBackgroundColor, rendererSizeIsWindow, cameraPositionZ, motifProps, customEventProps, }: CanvasProps): JSX.Element;
|
|
436
|
+
declare function Canvas({ rendererHeight, rendererWidth, rendererBackgroundColor, rendererSizeIsWindow, cameraPositionZ, showRMSD, motifProps, customEventProps, }: CanvasProps): JSX.Element;
|
|
434
437
|
|
|
435
438
|
/**
|
|
436
439
|
* Copyright (c) 2025 RNA3DS Lab CSUMB.
|
|
@@ -521,4 +524,4 @@ declare function calculateRMSDSlide(coordinates1: Vec3[], coordinates2: Vec3[]):
|
|
|
521
524
|
|
|
522
525
|
declare function rotateAllPoints(atomCoords: Vec3[], quat: Quat): Vec3[];
|
|
523
526
|
|
|
524
|
-
export { Canvas, CanvasAttributeTypes, CanvasDataManager, type CanvasProps, type CustomEventProps, EventManager, Events, Group, Matrix4, MeshObject, Motif, type MotifProps, Quat, RenderScene, Residue, type ScoreInfo, Vec3, calculateAllKabschRMSD, calculateRMSD, calculateRMSDSlide, getMotif, getPoints, getRMSD, kabschSlidingWindow,
|
|
527
|
+
export { Canvas, CanvasAttributeTypes, CanvasDataManager, type CanvasProps, type CustomEventProps, EventManager, Events, Group, Matrix4, MeshObject, Motif, type MotifMesh, type MotifProps, Quat, RenderScene, Residue, type ScoreInfo, Vec3, calculateAllKabschRMSD, calculateRMSD, calculateRMSDSlide, getMotif, getPoints, getRMSD, kabschSlidingWindow, parseAtomCoords, rotateAllPoints };
|
package/dist/index.js
CHANGED
|
@@ -50,8 +50,8 @@ __export(index_exports, {
|
|
|
50
50
|
getPoints: () => getPoints,
|
|
51
51
|
getRMSD: () => getRMSD,
|
|
52
52
|
kabschSlidingWindow: () => kabschSlidingWindow,
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
parseAtomCoords: () => parseAtomCoords,
|
|
54
|
+
rotateAllPoints: () => rotateAllPoints
|
|
55
55
|
});
|
|
56
56
|
module.exports = __toCommonJS(index_exports);
|
|
57
57
|
|
|
@@ -269,6 +269,13 @@ var Motif = class extends Group {
|
|
|
269
269
|
this._node.scaling.z * scalar
|
|
270
270
|
);
|
|
271
271
|
}
|
|
272
|
+
setScale(scale) {
|
|
273
|
+
this._node.scaling = new import_core5.Vector3(
|
|
274
|
+
scale,
|
|
275
|
+
scale,
|
|
276
|
+
scale
|
|
277
|
+
);
|
|
278
|
+
}
|
|
272
279
|
get uuid() {
|
|
273
280
|
return this._node.uniqueId.toString();
|
|
274
281
|
}
|
|
@@ -813,12 +820,10 @@ var RenderScene = class {
|
|
|
813
820
|
};
|
|
814
821
|
|
|
815
822
|
// src/3D/utils/GetAtomInfo.ts
|
|
816
|
-
async function parseAtomCoords(
|
|
817
|
-
const response = await fetch(`/${fileName}`);
|
|
818
|
-
const jsonData = await response.json();
|
|
823
|
+
async function parseAtomCoords(meshObject) {
|
|
819
824
|
const coordinates = [];
|
|
820
|
-
for (const [key] of Object.entries(
|
|
821
|
-
const atomMap =
|
|
825
|
+
for (const [key] of Object.entries(meshObject)) {
|
|
826
|
+
const atomMap = meshObject[key][0];
|
|
822
827
|
if (atomMap[`"C1'"`]) {
|
|
823
828
|
coordinates.push(
|
|
824
829
|
new Vec3(
|
|
@@ -831,26 +836,12 @@ async function parseAtomCoords(fileName) {
|
|
|
831
836
|
}
|
|
832
837
|
return coordinates;
|
|
833
838
|
}
|
|
834
|
-
async function getAtomCoords(fileName) {
|
|
835
|
-
const coordinates = await parseAtomCoords(fileName);
|
|
836
|
-
return coordinates;
|
|
837
|
-
}
|
|
838
|
-
async function updateAllMotifs(motifMeshArray) {
|
|
839
|
-
const fileNames = [];
|
|
840
|
-
for (const motif of motifMeshArray) {
|
|
841
|
-
fileNames.push(motif.userData.fileName);
|
|
842
|
-
}
|
|
843
|
-
const atomInfoLists = await Promise.all(fileNames.map((o) => getAtomCoords(o)));
|
|
844
|
-
for (let i = 0; i < motifMeshArray.length; i += 1) {
|
|
845
|
-
motifMeshArray[i].userData.atomInfo = atomInfoLists[i];
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
839
|
|
|
849
840
|
// src/3D/utils/GetPoints.ts
|
|
850
|
-
function getPoints(nucleotideData) {
|
|
841
|
+
function getPoints(nucleotideData, includeAtoms) {
|
|
851
842
|
const vertices = [];
|
|
852
843
|
const indices = [];
|
|
853
|
-
for (let i = 1; i < nucleotideData.length; i += 2) {
|
|
844
|
+
for (let i = includeAtoms ? 1 : 0; i < nucleotideData.length; i += 2) {
|
|
854
845
|
vertices.push(nucleotideData[i]);
|
|
855
846
|
indices.push(nucleotideData[i + 1]);
|
|
856
847
|
}
|
|
@@ -858,12 +849,10 @@ function getPoints(nucleotideData) {
|
|
|
858
849
|
}
|
|
859
850
|
|
|
860
851
|
// src/3D/utils/GetMotif.ts
|
|
861
|
-
async function getMotif(
|
|
862
|
-
const motif = new Motif(`${
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
for (const [key] of Object.entries(jsonObject)) {
|
|
866
|
-
const { vertices, indices } = getPoints(jsonObject[key]);
|
|
852
|
+
async function getMotif(motifName, motifMesh, motifColorHex = "0xcc2900", includeAtoms = true) {
|
|
853
|
+
const motif = new Motif(`${motifName}_motif`);
|
|
854
|
+
for (const [key] of Object.entries(motifMesh)) {
|
|
855
|
+
const { vertices, indices } = getPoints(motifMesh[key], includeAtoms);
|
|
867
856
|
const residue = new Residue("residue");
|
|
868
857
|
const backboneMesh = new MeshObject(`backbone_${key}`);
|
|
869
858
|
backboneMesh.applyVertexData(vertices[0], indices[0]);
|
|
@@ -875,7 +864,8 @@ async function getMotif(motifJSONFileName, motifColorHex = "0xcc2900") {
|
|
|
875
864
|
residue.addChild(ringMesh);
|
|
876
865
|
motif.addChild(residue);
|
|
877
866
|
}
|
|
878
|
-
motif.userData.fileName =
|
|
867
|
+
motif.userData.fileName = motifName;
|
|
868
|
+
if (includeAtoms) motif.userData.atomInfo = await parseAtomCoords(motifMesh);
|
|
879
869
|
return motif;
|
|
880
870
|
}
|
|
881
871
|
|
|
@@ -970,6 +960,7 @@ function getRMSD(a, b) {
|
|
|
970
960
|
if (a instanceof Motif && b instanceof Motif) {
|
|
971
961
|
newCoords1 = rotateAllPoints(a.userData.atomInfo, a.quat);
|
|
972
962
|
newCoords2 = rotateAllPoints(b.userData.atomInfo, b.quat);
|
|
963
|
+
return calculateRMSDSlide(newCoords1, newCoords2);
|
|
973
964
|
} else {
|
|
974
965
|
return -1;
|
|
975
966
|
}
|
|
@@ -1143,6 +1134,7 @@ function Canvas({
|
|
|
1143
1134
|
rendererBackgroundColor = "#040a20",
|
|
1144
1135
|
rendererSizeIsWindow = false,
|
|
1145
1136
|
cameraPositionZ = 1e3,
|
|
1137
|
+
showRMSD = true,
|
|
1146
1138
|
motifProps,
|
|
1147
1139
|
customEventProps
|
|
1148
1140
|
}) {
|
|
@@ -1179,7 +1171,6 @@ function Canvas({
|
|
|
1179
1171
|
if (!selectedMotifMeshState.current.has(motif)) {
|
|
1180
1172
|
return;
|
|
1181
1173
|
}
|
|
1182
|
-
console.log("removing motif: ", motif);
|
|
1183
1174
|
selectedMotifMeshState.current.delete(motif);
|
|
1184
1175
|
setSelectedmotifIds((prevState) => {
|
|
1185
1176
|
const newState = new Set(prevState);
|
|
@@ -1254,7 +1245,7 @@ function Canvas({
|
|
|
1254
1245
|
element.rotate(axisVec, angle);
|
|
1255
1246
|
}
|
|
1256
1247
|
});
|
|
1257
|
-
setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1248
|
+
if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1258
1249
|
}
|
|
1259
1250
|
}
|
|
1260
1251
|
}
|
|
@@ -1267,10 +1258,12 @@ function Canvas({
|
|
|
1267
1258
|
const zoomSpeed = 0.1;
|
|
1268
1259
|
const zoomDirection = event.originalEvent.deltaY > 0 ? -1 : 1;
|
|
1269
1260
|
selectedMotifMeshState.current.forEach((element) => {
|
|
1270
|
-
if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
|
|
1261
|
+
if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !(element.scale <= 1 && zoomDirection === -1) && !(element.scale >= 30 && zoomDirection === 1)) {
|
|
1271
1262
|
const scaleFactor = 1 + zoomDirection * zoomSpeed;
|
|
1272
1263
|
element.multiplyScalar(scaleFactor);
|
|
1273
1264
|
}
|
|
1265
|
+
if (element.scale < 1) element.setScale(1);
|
|
1266
|
+
if (element.scale > 30) element.setScale(30);
|
|
1274
1267
|
});
|
|
1275
1268
|
}
|
|
1276
1269
|
}
|
|
@@ -1297,7 +1290,7 @@ function Canvas({
|
|
|
1297
1290
|
element.rotate(event.rotationAxis, angle);
|
|
1298
1291
|
}
|
|
1299
1292
|
});
|
|
1300
|
-
setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1293
|
+
if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1301
1294
|
}
|
|
1302
1295
|
function onKeyboardTranslate(event) {
|
|
1303
1296
|
if (event.translationDirection.equals(Vec3.Zero)) {
|
|
@@ -1323,10 +1316,8 @@ function Canvas({
|
|
|
1323
1316
|
}
|
|
1324
1317
|
const motif = motifs[Number(event.key) - 1];
|
|
1325
1318
|
if (selectedMotifMeshState.current.has(motif)) {
|
|
1326
|
-
console.log("removing motif");
|
|
1327
1319
|
removeMotif(motif);
|
|
1328
1320
|
} else {
|
|
1329
|
-
console.log("adding motif");
|
|
1330
1321
|
addMotif(motif);
|
|
1331
1322
|
}
|
|
1332
1323
|
}
|
|
@@ -1345,6 +1336,21 @@ function Canvas({
|
|
|
1345
1336
|
}
|
|
1346
1337
|
return positions;
|
|
1347
1338
|
};
|
|
1339
|
+
const updateMotifs = () => {
|
|
1340
|
+
const positions = calculatePositions(motifs.length);
|
|
1341
|
+
motifs.forEach((motifMesh, index) => {
|
|
1342
|
+
if (!scene.current?.children.has(motifMesh.uuid)) return;
|
|
1343
|
+
if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
|
|
1344
|
+
motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
|
|
1345
|
+
if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
|
|
1346
|
+
let scale = canvasRef.current.width / 250;
|
|
1347
|
+
if (motifProps[index].scale) scale = motifProps[index].scale;
|
|
1348
|
+
motifMesh.setScale(scale);
|
|
1349
|
+
});
|
|
1350
|
+
};
|
|
1351
|
+
(0, import_react.useEffect)(() => {
|
|
1352
|
+
updateMotifs();
|
|
1353
|
+
}, [motifProps]);
|
|
1348
1354
|
(0, import_react.useEffect)(() => {
|
|
1349
1355
|
const unsubscribe = CanvasDataManager.subscribe("selectedMotifs" /* SELECTED_MOTIFS */, () => {
|
|
1350
1356
|
setSelectedmotifIds(CanvasDataManager.selectedMotifIds);
|
|
@@ -1362,7 +1368,7 @@ function Canvas({
|
|
|
1362
1368
|
}
|
|
1363
1369
|
});
|
|
1364
1370
|
updateGlow();
|
|
1365
|
-
setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1371
|
+
if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1366
1372
|
}, [selectedMotifIds]);
|
|
1367
1373
|
(0, import_react.useEffect)(() => {
|
|
1368
1374
|
const unsubscribe = CanvasDataManager.subscribe("lockedMotifIds" /* LOCKED_MOTIF_IDS */, () => {
|
|
@@ -1380,25 +1386,31 @@ function Canvas({
|
|
|
1380
1386
|
hardLockedMotifIds = CanvasDataManager.hardLockedMotifIds;
|
|
1381
1387
|
}, [CanvasDataManager.hardLockedMotifIds]);
|
|
1382
1388
|
(0, import_react.useEffect)(() => {
|
|
1389
|
+
if (!showRMSD) return;
|
|
1383
1390
|
const unsubscribe = CanvasDataManager.subscribe("scoreRMSD" /* SCORE_RMSD */, () => {
|
|
1384
1391
|
setScoreRMSD(CanvasDataManager.scoreRMSD);
|
|
1385
1392
|
});
|
|
1386
1393
|
return () => unsubscribe();
|
|
1387
1394
|
}, []);
|
|
1388
1395
|
(0, import_react.useEffect)(() => {
|
|
1396
|
+
if (!showRMSD) return;
|
|
1389
1397
|
if (CanvasDataManager.scoreRMSD !== scoreRMSD) {
|
|
1390
1398
|
CanvasDataManager.setScoreRMSD(scoreRMSD);
|
|
1391
1399
|
}
|
|
1392
1400
|
}, [scoreRMSD]);
|
|
1393
1401
|
(0, import_react.useEffect)(() => {
|
|
1402
|
+
if (!showRMSD) return;
|
|
1394
1403
|
const unsubscribe = CanvasDataManager.subscribe("kabschRMSD" /* KABSCH_RMSD */, () => {
|
|
1395
1404
|
setKabschRMSD(CanvasDataManager.kabschRMSD);
|
|
1396
1405
|
});
|
|
1397
1406
|
return () => unsubscribe();
|
|
1398
1407
|
}, []);
|
|
1399
1408
|
(0, import_react.useEffect)(() => {
|
|
1409
|
+
if (!showRMSD) return;
|
|
1400
1410
|
if (CanvasDataManager.kabschRMSD !== kabschRMSD) {
|
|
1401
1411
|
CanvasDataManager.setKabschRMSD(kabschRMSD);
|
|
1412
|
+
} else if (CanvasDataManager.kabschRMSD.length !== motifs.length) {
|
|
1413
|
+
CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs));
|
|
1402
1414
|
}
|
|
1403
1415
|
}, [kabschRMSD]);
|
|
1404
1416
|
(0, import_react.useEffect)(() => {
|
|
@@ -1418,12 +1430,10 @@ function Canvas({
|
|
|
1418
1430
|
rendererSizeIsWindow ? window.innerHeight : rendererHeight
|
|
1419
1431
|
);
|
|
1420
1432
|
}
|
|
1421
|
-
const positions = calculatePositions(motifs.length);
|
|
1422
1433
|
if (motifs.length > 0) {
|
|
1423
|
-
|
|
1424
|
-
setKabschRMSD(calculateAllKabschRMSD(motifs));
|
|
1425
|
-
});
|
|
1434
|
+
if (showRMSD) setKabschRMSD(calculateAllKabschRMSD(motifs));
|
|
1426
1435
|
if (scene.current.children.size !== motifs.length) {
|
|
1436
|
+
const positions = calculatePositions(motifs.length);
|
|
1427
1437
|
motifs.forEach((motifMesh, index) => {
|
|
1428
1438
|
scene.current?.add(motifMesh);
|
|
1429
1439
|
if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
|
|
@@ -1494,10 +1504,6 @@ function Canvas({
|
|
|
1494
1504
|
}
|
|
1495
1505
|
});
|
|
1496
1506
|
}
|
|
1497
|
-
} else {
|
|
1498
|
-
motifs.forEach((motifMesh) => {
|
|
1499
|
-
scene.current?.add(motifMesh);
|
|
1500
|
-
});
|
|
1501
1507
|
}
|
|
1502
1508
|
}
|
|
1503
1509
|
scene.current?.start();
|
|
@@ -1526,6 +1532,6 @@ function Canvas({
|
|
|
1526
1532
|
getPoints,
|
|
1527
1533
|
getRMSD,
|
|
1528
1534
|
kabschSlidingWindow,
|
|
1529
|
-
|
|
1530
|
-
|
|
1535
|
+
parseAtomCoords,
|
|
1536
|
+
rotateAllPoints
|
|
1531
1537
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -212,6 +212,13 @@ var Motif = class extends Group {
|
|
|
212
212
|
this._node.scaling.z * scalar
|
|
213
213
|
);
|
|
214
214
|
}
|
|
215
|
+
setScale(scale) {
|
|
216
|
+
this._node.scaling = new Vector32(
|
|
217
|
+
scale,
|
|
218
|
+
scale,
|
|
219
|
+
scale
|
|
220
|
+
);
|
|
221
|
+
}
|
|
215
222
|
get uuid() {
|
|
216
223
|
return this._node.uniqueId.toString();
|
|
217
224
|
}
|
|
@@ -756,12 +763,10 @@ var RenderScene = class {
|
|
|
756
763
|
};
|
|
757
764
|
|
|
758
765
|
// src/3D/utils/GetAtomInfo.ts
|
|
759
|
-
async function parseAtomCoords(
|
|
760
|
-
const response = await fetch(`/${fileName}`);
|
|
761
|
-
const jsonData = await response.json();
|
|
766
|
+
async function parseAtomCoords(meshObject) {
|
|
762
767
|
const coordinates = [];
|
|
763
|
-
for (const [key] of Object.entries(
|
|
764
|
-
const atomMap =
|
|
768
|
+
for (const [key] of Object.entries(meshObject)) {
|
|
769
|
+
const atomMap = meshObject[key][0];
|
|
765
770
|
if (atomMap[`"C1'"`]) {
|
|
766
771
|
coordinates.push(
|
|
767
772
|
new Vec3(
|
|
@@ -774,26 +779,12 @@ async function parseAtomCoords(fileName) {
|
|
|
774
779
|
}
|
|
775
780
|
return coordinates;
|
|
776
781
|
}
|
|
777
|
-
async function getAtomCoords(fileName) {
|
|
778
|
-
const coordinates = await parseAtomCoords(fileName);
|
|
779
|
-
return coordinates;
|
|
780
|
-
}
|
|
781
|
-
async function updateAllMotifs(motifMeshArray) {
|
|
782
|
-
const fileNames = [];
|
|
783
|
-
for (const motif of motifMeshArray) {
|
|
784
|
-
fileNames.push(motif.userData.fileName);
|
|
785
|
-
}
|
|
786
|
-
const atomInfoLists = await Promise.all(fileNames.map((o) => getAtomCoords(o)));
|
|
787
|
-
for (let i = 0; i < motifMeshArray.length; i += 1) {
|
|
788
|
-
motifMeshArray[i].userData.atomInfo = atomInfoLists[i];
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
782
|
|
|
792
783
|
// src/3D/utils/GetPoints.ts
|
|
793
|
-
function getPoints(nucleotideData) {
|
|
784
|
+
function getPoints(nucleotideData, includeAtoms) {
|
|
794
785
|
const vertices = [];
|
|
795
786
|
const indices = [];
|
|
796
|
-
for (let i = 1; i < nucleotideData.length; i += 2) {
|
|
787
|
+
for (let i = includeAtoms ? 1 : 0; i < nucleotideData.length; i += 2) {
|
|
797
788
|
vertices.push(nucleotideData[i]);
|
|
798
789
|
indices.push(nucleotideData[i + 1]);
|
|
799
790
|
}
|
|
@@ -801,12 +792,10 @@ function getPoints(nucleotideData) {
|
|
|
801
792
|
}
|
|
802
793
|
|
|
803
794
|
// src/3D/utils/GetMotif.ts
|
|
804
|
-
async function getMotif(
|
|
805
|
-
const motif = new Motif(`${
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
for (const [key] of Object.entries(jsonObject)) {
|
|
809
|
-
const { vertices, indices } = getPoints(jsonObject[key]);
|
|
795
|
+
async function getMotif(motifName, motifMesh, motifColorHex = "0xcc2900", includeAtoms = true) {
|
|
796
|
+
const motif = new Motif(`${motifName}_motif`);
|
|
797
|
+
for (const [key] of Object.entries(motifMesh)) {
|
|
798
|
+
const { vertices, indices } = getPoints(motifMesh[key], includeAtoms);
|
|
810
799
|
const residue = new Residue("residue");
|
|
811
800
|
const backboneMesh = new MeshObject(`backbone_${key}`);
|
|
812
801
|
backboneMesh.applyVertexData(vertices[0], indices[0]);
|
|
@@ -818,7 +807,8 @@ async function getMotif(motifJSONFileName, motifColorHex = "0xcc2900") {
|
|
|
818
807
|
residue.addChild(ringMesh);
|
|
819
808
|
motif.addChild(residue);
|
|
820
809
|
}
|
|
821
|
-
motif.userData.fileName =
|
|
810
|
+
motif.userData.fileName = motifName;
|
|
811
|
+
if (includeAtoms) motif.userData.atomInfo = await parseAtomCoords(motifMesh);
|
|
822
812
|
return motif;
|
|
823
813
|
}
|
|
824
814
|
|
|
@@ -913,6 +903,7 @@ function getRMSD(a, b) {
|
|
|
913
903
|
if (a instanceof Motif && b instanceof Motif) {
|
|
914
904
|
newCoords1 = rotateAllPoints(a.userData.atomInfo, a.quat);
|
|
915
905
|
newCoords2 = rotateAllPoints(b.userData.atomInfo, b.quat);
|
|
906
|
+
return calculateRMSDSlide(newCoords1, newCoords2);
|
|
916
907
|
} else {
|
|
917
908
|
return -1;
|
|
918
909
|
}
|
|
@@ -1086,6 +1077,7 @@ function Canvas({
|
|
|
1086
1077
|
rendererBackgroundColor = "#040a20",
|
|
1087
1078
|
rendererSizeIsWindow = false,
|
|
1088
1079
|
cameraPositionZ = 1e3,
|
|
1080
|
+
showRMSD = true,
|
|
1089
1081
|
motifProps,
|
|
1090
1082
|
customEventProps
|
|
1091
1083
|
}) {
|
|
@@ -1122,7 +1114,6 @@ function Canvas({
|
|
|
1122
1114
|
if (!selectedMotifMeshState.current.has(motif)) {
|
|
1123
1115
|
return;
|
|
1124
1116
|
}
|
|
1125
|
-
console.log("removing motif: ", motif);
|
|
1126
1117
|
selectedMotifMeshState.current.delete(motif);
|
|
1127
1118
|
setSelectedmotifIds((prevState) => {
|
|
1128
1119
|
const newState = new Set(prevState);
|
|
@@ -1197,7 +1188,7 @@ function Canvas({
|
|
|
1197
1188
|
element.rotate(axisVec, angle);
|
|
1198
1189
|
}
|
|
1199
1190
|
});
|
|
1200
|
-
setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1191
|
+
if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1201
1192
|
}
|
|
1202
1193
|
}
|
|
1203
1194
|
}
|
|
@@ -1210,10 +1201,12 @@ function Canvas({
|
|
|
1210
1201
|
const zoomSpeed = 0.1;
|
|
1211
1202
|
const zoomDirection = event.originalEvent.deltaY > 0 ? -1 : 1;
|
|
1212
1203
|
selectedMotifMeshState.current.forEach((element) => {
|
|
1213
|
-
if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
|
|
1204
|
+
if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !(element.scale <= 1 && zoomDirection === -1) && !(element.scale >= 30 && zoomDirection === 1)) {
|
|
1214
1205
|
const scaleFactor = 1 + zoomDirection * zoomSpeed;
|
|
1215
1206
|
element.multiplyScalar(scaleFactor);
|
|
1216
1207
|
}
|
|
1208
|
+
if (element.scale < 1) element.setScale(1);
|
|
1209
|
+
if (element.scale > 30) element.setScale(30);
|
|
1217
1210
|
});
|
|
1218
1211
|
}
|
|
1219
1212
|
}
|
|
@@ -1240,7 +1233,7 @@ function Canvas({
|
|
|
1240
1233
|
element.rotate(event.rotationAxis, angle);
|
|
1241
1234
|
}
|
|
1242
1235
|
});
|
|
1243
|
-
setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1236
|
+
if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1244
1237
|
}
|
|
1245
1238
|
function onKeyboardTranslate(event) {
|
|
1246
1239
|
if (event.translationDirection.equals(Vec3.Zero)) {
|
|
@@ -1266,10 +1259,8 @@ function Canvas({
|
|
|
1266
1259
|
}
|
|
1267
1260
|
const motif = motifs[Number(event.key) - 1];
|
|
1268
1261
|
if (selectedMotifMeshState.current.has(motif)) {
|
|
1269
|
-
console.log("removing motif");
|
|
1270
1262
|
removeMotif(motif);
|
|
1271
1263
|
} else {
|
|
1272
|
-
console.log("adding motif");
|
|
1273
1264
|
addMotif(motif);
|
|
1274
1265
|
}
|
|
1275
1266
|
}
|
|
@@ -1288,6 +1279,21 @@ function Canvas({
|
|
|
1288
1279
|
}
|
|
1289
1280
|
return positions;
|
|
1290
1281
|
};
|
|
1282
|
+
const updateMotifs = () => {
|
|
1283
|
+
const positions = calculatePositions(motifs.length);
|
|
1284
|
+
motifs.forEach((motifMesh, index) => {
|
|
1285
|
+
if (!scene.current?.children.has(motifMesh.uuid)) return;
|
|
1286
|
+
if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
|
|
1287
|
+
motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
|
|
1288
|
+
if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
|
|
1289
|
+
let scale = canvasRef.current.width / 250;
|
|
1290
|
+
if (motifProps[index].scale) scale = motifProps[index].scale;
|
|
1291
|
+
motifMesh.setScale(scale);
|
|
1292
|
+
});
|
|
1293
|
+
};
|
|
1294
|
+
useEffect(() => {
|
|
1295
|
+
updateMotifs();
|
|
1296
|
+
}, [motifProps]);
|
|
1291
1297
|
useEffect(() => {
|
|
1292
1298
|
const unsubscribe = CanvasDataManager.subscribe("selectedMotifs" /* SELECTED_MOTIFS */, () => {
|
|
1293
1299
|
setSelectedmotifIds(CanvasDataManager.selectedMotifIds);
|
|
@@ -1305,7 +1311,7 @@ function Canvas({
|
|
|
1305
1311
|
}
|
|
1306
1312
|
});
|
|
1307
1313
|
updateGlow();
|
|
1308
|
-
setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1314
|
+
if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
|
|
1309
1315
|
}, [selectedMotifIds]);
|
|
1310
1316
|
useEffect(() => {
|
|
1311
1317
|
const unsubscribe = CanvasDataManager.subscribe("lockedMotifIds" /* LOCKED_MOTIF_IDS */, () => {
|
|
@@ -1323,25 +1329,31 @@ function Canvas({
|
|
|
1323
1329
|
hardLockedMotifIds = CanvasDataManager.hardLockedMotifIds;
|
|
1324
1330
|
}, [CanvasDataManager.hardLockedMotifIds]);
|
|
1325
1331
|
useEffect(() => {
|
|
1332
|
+
if (!showRMSD) return;
|
|
1326
1333
|
const unsubscribe = CanvasDataManager.subscribe("scoreRMSD" /* SCORE_RMSD */, () => {
|
|
1327
1334
|
setScoreRMSD(CanvasDataManager.scoreRMSD);
|
|
1328
1335
|
});
|
|
1329
1336
|
return () => unsubscribe();
|
|
1330
1337
|
}, []);
|
|
1331
1338
|
useEffect(() => {
|
|
1339
|
+
if (!showRMSD) return;
|
|
1332
1340
|
if (CanvasDataManager.scoreRMSD !== scoreRMSD) {
|
|
1333
1341
|
CanvasDataManager.setScoreRMSD(scoreRMSD);
|
|
1334
1342
|
}
|
|
1335
1343
|
}, [scoreRMSD]);
|
|
1336
1344
|
useEffect(() => {
|
|
1345
|
+
if (!showRMSD) return;
|
|
1337
1346
|
const unsubscribe = CanvasDataManager.subscribe("kabschRMSD" /* KABSCH_RMSD */, () => {
|
|
1338
1347
|
setKabschRMSD(CanvasDataManager.kabschRMSD);
|
|
1339
1348
|
});
|
|
1340
1349
|
return () => unsubscribe();
|
|
1341
1350
|
}, []);
|
|
1342
1351
|
useEffect(() => {
|
|
1352
|
+
if (!showRMSD) return;
|
|
1343
1353
|
if (CanvasDataManager.kabschRMSD !== kabschRMSD) {
|
|
1344
1354
|
CanvasDataManager.setKabschRMSD(kabschRMSD);
|
|
1355
|
+
} else if (CanvasDataManager.kabschRMSD.length !== motifs.length) {
|
|
1356
|
+
CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs));
|
|
1345
1357
|
}
|
|
1346
1358
|
}, [kabschRMSD]);
|
|
1347
1359
|
useEffect(() => {
|
|
@@ -1361,12 +1373,10 @@ function Canvas({
|
|
|
1361
1373
|
rendererSizeIsWindow ? window.innerHeight : rendererHeight
|
|
1362
1374
|
);
|
|
1363
1375
|
}
|
|
1364
|
-
const positions = calculatePositions(motifs.length);
|
|
1365
1376
|
if (motifs.length > 0) {
|
|
1366
|
-
|
|
1367
|
-
setKabschRMSD(calculateAllKabschRMSD(motifs));
|
|
1368
|
-
});
|
|
1377
|
+
if (showRMSD) setKabschRMSD(calculateAllKabschRMSD(motifs));
|
|
1369
1378
|
if (scene.current.children.size !== motifs.length) {
|
|
1379
|
+
const positions = calculatePositions(motifs.length);
|
|
1370
1380
|
motifs.forEach((motifMesh, index) => {
|
|
1371
1381
|
scene.current?.add(motifMesh);
|
|
1372
1382
|
if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
|
|
@@ -1437,10 +1447,6 @@ function Canvas({
|
|
|
1437
1447
|
}
|
|
1438
1448
|
});
|
|
1439
1449
|
}
|
|
1440
|
-
} else {
|
|
1441
|
-
motifs.forEach((motifMesh) => {
|
|
1442
|
-
scene.current?.add(motifMesh);
|
|
1443
|
-
});
|
|
1444
1450
|
}
|
|
1445
1451
|
}
|
|
1446
1452
|
scene.current?.start();
|
|
@@ -1468,6 +1474,6 @@ export {
|
|
|
1468
1474
|
getPoints,
|
|
1469
1475
|
getRMSD,
|
|
1470
1476
|
kabschSlidingWindow,
|
|
1471
|
-
|
|
1472
|
-
|
|
1477
|
+
parseAtomCoords,
|
|
1478
|
+
rotateAllPoints
|
|
1473
1479
|
};
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@judah-silva/rnacanvas",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "A 3D Canvas for displaying and interacting with custom RNA models. Powered by BabylonJS.",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"main": "./dist/index.js",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/RNAStructureAnalysisLab/RNACanvas"
|
|
10
|
+
},
|
|
6
11
|
"module": "./dist/index.mjs",
|
|
7
12
|
"scripts": {
|
|
8
13
|
"build": "tsup"
|
|
@@ -14,7 +19,6 @@
|
|
|
14
19
|
"test"
|
|
15
20
|
],
|
|
16
21
|
"author": "Judah Silva",
|
|
17
|
-
"license": "MIT",
|
|
18
22
|
"dependencies": {
|
|
19
23
|
"@babylonjs/core": "^7.50.0",
|
|
20
24
|
"numeric": "^1.2.6",
|