hpo-react-visualizer 0.0.1 → 0.0.3
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/index.cjs +619 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -6
- package/dist/index.d.ts +34 -6
- package/dist/index.js +617 -73
- package/dist/index.js.map +1 -1
- package/package.json +17 -17
- package/src/HpoVisualizer.tsx +164 -32
- package/src/OrganSvg.tsx +13 -17
- package/src/ZoomControls.tsx +125 -0
- package/src/__tests__/hpoLabel.test.ts +71 -0
- package/src/__tests__/hpoVisualizerSizing.test.tsx +22 -0
- package/src/__tests__/organControlState.test.ts +26 -0
- package/src/__tests__/renderOrder.test.tsx +24 -37
- package/src/__tests__/transitionStyle.test.ts +11 -0
- package/src/__tests__/useOrganInteraction.test.ts +31 -0
- package/src/__tests__/useZoom.test.ts +144 -0
- package/src/__tests__/zoomControls.test.tsx +106 -0
- package/src/constants.ts +104 -2
- package/src/index.ts +5 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/organControlState.ts +21 -0
- package/src/svg/Neoplasm.tsx +3 -0
- package/src/types.ts +35 -0
- package/src/useOrganInteraction.ts +2 -9
- package/src/useZoom.ts +347 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useMemo, useState, useCallback, useId } from 'react';
|
|
3
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo, useState, useCallback, useId, useRef, useEffect } from 'react';
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
// src/constants.ts
|
|
6
6
|
var ORGAN_IDS = [
|
|
@@ -78,6 +78,82 @@ var ORGAN_NAMES_KO = {
|
|
|
78
78
|
prenatal: "\uD0DC\uC544",
|
|
79
79
|
blood: "\uD608\uC561"
|
|
80
80
|
};
|
|
81
|
+
var HPO_LABELS = [
|
|
82
|
+
"others",
|
|
83
|
+
"Growth abnormality",
|
|
84
|
+
"Abnormality of the genitourinary system",
|
|
85
|
+
"Abnormality of the immune system",
|
|
86
|
+
"Abnormality of the digestive system",
|
|
87
|
+
"Abnormality of metabolism/homeostasis",
|
|
88
|
+
"Abnormality of head or neck",
|
|
89
|
+
"Abnormality of the musculoskeletal system",
|
|
90
|
+
"Abnormality of the nervous system",
|
|
91
|
+
"Abnormality of the respiratory system",
|
|
92
|
+
"Abnormality of the eye",
|
|
93
|
+
"Abnormality of the cardiovascular system",
|
|
94
|
+
"Abnormality of the ear",
|
|
95
|
+
"Abnormality of prenatal development or birth",
|
|
96
|
+
"Abnormality of the integument",
|
|
97
|
+
"Abnormality of the breast",
|
|
98
|
+
"Abnormality of the endocrine system",
|
|
99
|
+
"Abnormality of blood and blood-forming tissues",
|
|
100
|
+
"Abnormality of limbs",
|
|
101
|
+
"Abnormality of the voice",
|
|
102
|
+
"Constitutional symptom",
|
|
103
|
+
"Neoplasm",
|
|
104
|
+
"Abnormal cellular phenotype",
|
|
105
|
+
"Abnormality of the thoracic cavity"
|
|
106
|
+
];
|
|
107
|
+
var HPO_LABEL_TO_ORGAN = {
|
|
108
|
+
"Growth abnormality": "growth",
|
|
109
|
+
"Abnormality of the genitourinary system": "kidney",
|
|
110
|
+
"Abnormality of the immune system": "immune",
|
|
111
|
+
"Abnormality of the digestive system": "digestive",
|
|
112
|
+
"Abnormality of metabolism/homeostasis": "metabolism",
|
|
113
|
+
"Abnormality of head or neck": "head",
|
|
114
|
+
"Abnormality of the musculoskeletal system": "muscle",
|
|
115
|
+
"Abnormality of the nervous system": "nervous",
|
|
116
|
+
"Abnormality of the respiratory system": "lung",
|
|
117
|
+
"Abnormality of the eye": "eye",
|
|
118
|
+
"Abnormality of the cardiovascular system": "heart",
|
|
119
|
+
"Abnormality of the ear": "ear",
|
|
120
|
+
"Abnormality of prenatal development or birth": "prenatal",
|
|
121
|
+
"Abnormality of the integument": "integument",
|
|
122
|
+
"Abnormality of the breast": "breast",
|
|
123
|
+
"Abnormality of the endocrine system": "endocrine",
|
|
124
|
+
"Abnormality of blood and blood-forming tissues": "blood",
|
|
125
|
+
"Abnormality of limbs": "limbs",
|
|
126
|
+
"Abnormality of the voice": "voice",
|
|
127
|
+
"Constitutional symptom": "constitutional",
|
|
128
|
+
Neoplasm: "neoplasm",
|
|
129
|
+
"Abnormal cellular phenotype": "cell",
|
|
130
|
+
"Abnormality of the thoracic cavity": "thoracicCavity"
|
|
131
|
+
};
|
|
132
|
+
var ORGAN_TO_HPO_LABEL = {
|
|
133
|
+
growth: "Growth abnormality",
|
|
134
|
+
kidney: "Abnormality of the genitourinary system",
|
|
135
|
+
immune: "Abnormality of the immune system",
|
|
136
|
+
digestive: "Abnormality of the digestive system",
|
|
137
|
+
metabolism: "Abnormality of metabolism/homeostasis",
|
|
138
|
+
head: "Abnormality of head or neck",
|
|
139
|
+
muscle: "Abnormality of the musculoskeletal system",
|
|
140
|
+
nervous: "Abnormality of the nervous system",
|
|
141
|
+
lung: "Abnormality of the respiratory system",
|
|
142
|
+
eye: "Abnormality of the eye",
|
|
143
|
+
heart: "Abnormality of the cardiovascular system",
|
|
144
|
+
ear: "Abnormality of the ear",
|
|
145
|
+
prenatal: "Abnormality of prenatal development or birth",
|
|
146
|
+
integument: "Abnormality of the integument",
|
|
147
|
+
breast: "Abnormality of the breast",
|
|
148
|
+
endocrine: "Abnormality of the endocrine system",
|
|
149
|
+
blood: "Abnormality of blood and blood-forming tissues",
|
|
150
|
+
limbs: "Abnormality of limbs",
|
|
151
|
+
voice: "Abnormality of the voice",
|
|
152
|
+
constitutional: "Constitutional symptom",
|
|
153
|
+
neoplasm: "Neoplasm",
|
|
154
|
+
cell: "Abnormal cellular phenotype",
|
|
155
|
+
thoracicCavity: "Abnormality of the thoracic cavity"
|
|
156
|
+
};
|
|
81
157
|
var DEFAULT_COLOR_PALETTE = {
|
|
82
158
|
blue: {
|
|
83
159
|
100: "#DBEAFE",
|
|
@@ -114,7 +190,19 @@ var DEFAULT_COLOR_NAME = "blue";
|
|
|
114
190
|
var DEFAULT_STROKE_COLOR = "#ff142d";
|
|
115
191
|
var DEFAULT_STROKE_WIDTH = 0.5;
|
|
116
192
|
var ANIMATION_DURATION_MS = 150;
|
|
117
|
-
var
|
|
193
|
+
var TRANSITION_PROPERTIES = [
|
|
194
|
+
"fill",
|
|
195
|
+
"stroke",
|
|
196
|
+
"stroke-width",
|
|
197
|
+
"opacity",
|
|
198
|
+
"filter",
|
|
199
|
+
"stop-color",
|
|
200
|
+
"stop-opacity",
|
|
201
|
+
"visibility"
|
|
202
|
+
];
|
|
203
|
+
var TRANSITION_STYLE = TRANSITION_PROPERTIES.map(
|
|
204
|
+
(property) => `${property} ${ANIMATION_DURATION_MS}ms ease-out`
|
|
205
|
+
).join(", ");
|
|
118
206
|
var BODY_VIEWBOX = {
|
|
119
207
|
width: 122,
|
|
120
208
|
height: 358
|
|
@@ -146,6 +234,20 @@ function createStrictColorPalette(palette) {
|
|
|
146
234
|
return result;
|
|
147
235
|
}, {});
|
|
148
236
|
}
|
|
237
|
+
|
|
238
|
+
// src/lib/organControlState.ts
|
|
239
|
+
var createUniformOrganColorSchemes = (organIds, scheme) => {
|
|
240
|
+
return organIds.reduce(
|
|
241
|
+
(acc, organId) => {
|
|
242
|
+
acc[organId] = scheme;
|
|
243
|
+
return acc;
|
|
244
|
+
},
|
|
245
|
+
{}
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
var createOrganOutlineSet = (organIds, enabled) => {
|
|
249
|
+
return enabled ? new Set(organIds) : /* @__PURE__ */ new Set();
|
|
250
|
+
};
|
|
149
251
|
function Blood({ style, colorScale, isActive = false, className }) {
|
|
150
252
|
const defaultColor = colorScale[200];
|
|
151
253
|
const activeColor = colorScale[300];
|
|
@@ -983,18 +1085,22 @@ function Neoplasm({ style, colorScale, isActive = false, className }) {
|
|
|
983
1085
|
const defaultColor = colorScale[100];
|
|
984
1086
|
const activeColor = colorScale[300];
|
|
985
1087
|
const fill = style?.fill ?? (isActive ? activeColor : defaultColor);
|
|
986
|
-
return /* @__PURE__ */
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1088
|
+
return /* @__PURE__ */ jsxs("g", { className, "data-organ": "neoplasm", children: [
|
|
1089
|
+
/* @__PURE__ */ jsx(
|
|
1090
|
+
"path",
|
|
1091
|
+
{
|
|
1092
|
+
d: NEOPLASM_PATH,
|
|
1093
|
+
fill,
|
|
1094
|
+
stroke: style?.stroke,
|
|
1095
|
+
strokeWidth: style?.strokeWidth,
|
|
1096
|
+
style: { transition: TRANSITION_STYLE }
|
|
1097
|
+
}
|
|
1098
|
+
),
|
|
1099
|
+
/* @__PURE__ */ jsx("path", { d: OUTLINE_NEOPLASM_PATH, fill: "transparent", style: { transition: TRANSITION_STYLE } })
|
|
1100
|
+
] });
|
|
996
1101
|
}
|
|
997
1102
|
var NEOPLASM_PATH = "M2.7706 0.345092C3.77204 -0.328406 4.67398 0.0748652 5.51994 0.775559C5.66929 0.84341 5.84084 0.854726 6.00335 0.870091C6.75039 0.90371 7.23202 1.47634 7.71526 1.9904C7.9442 2.15206 8.23991 2.13397 8.49487 2.22712C9.42032 2.53001 9.97875 3.53109 9.82848 4.52946C9.80496 4.72076 9.82624 4.9113 9.95893 5.05368C10.8667 5.72367 11.227 6.90427 10.8552 8.00289C10.8197 8.17335 10.8234 8.33612 10.8552 8.50914C11.1667 9.57255 10.4627 10.7371 9.42026 10.9099C9.00986 10.9605 8.83573 11.0538 8.57313 11.3857C7.90654 12.1783 6.69443 12.1982 5.99107 11.4787C5.5305 10.923 5.59744 10.7815 4.83778 10.6419C4.274 10.5136 4.05225 9.90237 3.47347 9.82554C2.48067 9.75886 1.64607 8.89953 1.56283 7.86148C1.52718 7.50386 1.66051 7.13178 1.52369 6.78727C1.3217 6.37915 1.32115 5.92532 1.23057 5.49352C1.10172 5.15452 0.710231 4.98692 0.513122 4.69274C-0.603239 3.37518 0.214433 1.09705 1.8828 0.870872C2.29014 0.811338 2.46031 0.574268 2.7706 0.345092ZM8.66675 8.95211C9.38993 8.35162 8.63882 7.25972 7.84647 7.70601C7.45807 7.89616 7.2217 7.60732 6.78065 7.86148C5.19403 8.99021 7.21312 10.9585 8.28615 9.18961C8.40479 9.09758 8.54993 9.05178 8.66675 8.95211ZM4.7104 6.27711L4.27303 6.33727C4.12492 6.3579 3.97681 6.38207 3.83565 6.41852C2.87888 6.71014 2.93127 8.12397 3.79805 8.44664C4.34702 8.66977 4.91047 8.31107 5.14548 7.78492C5.25444 7.61852 5.42561 7.46995 5.49231 7.2693C5.67652 6.73389 5.241 6.21214 4.7104 6.27711ZM8.15954 5.40524C8.913 4.35054 7.9177 2.94878 6.76301 3.66071C6.55641 3.79164 6.34141 3.80113 6.11308 3.86227C5.20786 4.14228 5.25696 5.42232 6.06934 5.76071C6.24349 5.83273 6.44219 5.82242 6.57808 5.96071C6.82774 6.35991 7.1629 6.64138 7.63085 6.42633C8.04922 6.26821 8.02001 5.76856 8.15954 5.40524ZM4.52778 2.60368C4.44638 2.07782 3.90894 1.82777 3.46273 2.07087C3.13279 2.34905 2.82713 2.08984 2.46597 2.10681C1.16526 2.23008 1.24741 4.22477 2.54961 4.25133C2.72681 4.26447 2.8382 4.34664 2.91869 4.51149C3.71452 6.01766 5.6609 4.68642 4.53008 3.18805C4.47929 2.99637 4.57022 2.79792 4.52778 2.60368Z";
|
|
1103
|
+
var OUTLINE_NEOPLASM_PATH = "M2.7706 0.345092C3.77204 -0.328406 4.67398 0.0748652 5.51994 0.775559C5.66929 0.84341 5.84084 0.854726 6.00335 0.870091C6.75039 0.90371 7.23202 1.47634 7.71526 1.9904C7.9442 2.15206 8.23991 2.13397 8.49487 2.22712C9.42032 2.53001 9.97875 3.53109 9.82848 4.52946C9.80497 4.72076 9.82624 4.9113 9.95893 5.05368C10.8667 5.72367 11.227 6.90427 10.8552 8.00289C10.8197 8.17335 10.8234 8.33612 10.8552 8.50914C11.1667 9.57254 10.4627 10.7371 9.42026 10.9099C9.00986 10.9605 8.83573 11.0538 8.57313 11.3857C7.90654 12.1783 6.69443 12.1982 5.99108 11.4787C5.5305 10.923 5.59744 10.7815 4.83778 10.6419C4.274 10.5136 4.05225 9.90237 3.47347 9.82554C2.48067 9.75886 1.64607 8.89953 1.56283 7.86148C1.52718 7.50386 1.66051 7.13178 1.52369 6.78727C1.3217 6.37915 1.32115 5.92532 1.23057 5.49352C1.10172 5.15452 0.710231 4.98692 0.513122 4.69274C-0.603239 3.37518 0.214433 1.09705 1.8828 0.870872C2.29014 0.811338 2.46031 0.574268 2.7706 0.345092Z";
|
|
998
1104
|
function Nervous({ style, colorScale, isActive = false, className }) {
|
|
999
1105
|
const defaultColor = colorScale[100];
|
|
1000
1106
|
const activeColor = colorScale[200];
|
|
@@ -1159,25 +1265,21 @@ function OrganSvg({
|
|
|
1159
1265
|
};
|
|
1160
1266
|
return mergeStyles(userStyle, strokeStyle);
|
|
1161
1267
|
}, [config?.style, showOutline]);
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1268
|
+
const [minX = 0, minY = 0, viewBoxWidth, viewBoxHeight] = viewBox.split(" ").map(Number);
|
|
1269
|
+
const scaleX = viewBoxWidth ? width / viewBoxWidth : 1;
|
|
1270
|
+
const scaleY = viewBoxHeight ? height / viewBoxHeight : 1;
|
|
1271
|
+
const transform = `translate(${x} ${y}) scale(${scaleX} ${scaleY}) translate(${-minX} ${-minY})`;
|
|
1272
|
+
const groupStyle = {
|
|
1273
|
+
transition: `${TRANSITION_STYLE}, visibility 0s`
|
|
1168
1274
|
};
|
|
1169
1275
|
const filter = isActive ? "blur(1px)" : void 0;
|
|
1170
1276
|
return /* @__PURE__ */ jsxs(
|
|
1171
|
-
"
|
|
1277
|
+
"g",
|
|
1172
1278
|
{
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
viewBox,
|
|
1176
|
-
style: svgStyle,
|
|
1279
|
+
transform,
|
|
1280
|
+
style: groupStyle,
|
|
1177
1281
|
filter,
|
|
1178
1282
|
"aria-label": organId,
|
|
1179
|
-
overflow: "visible",
|
|
1180
|
-
pointerEvents: "none",
|
|
1181
1283
|
opacity: isVisible ? 1 : 0,
|
|
1182
1284
|
visibility: isVisible ? "visible" : "hidden",
|
|
1183
1285
|
children: [
|
|
@@ -1263,14 +1365,8 @@ function useOrganInteraction(options = {}) {
|
|
|
1263
1365
|
setInternalSelected(newSelected);
|
|
1264
1366
|
}
|
|
1265
1367
|
onSelect?.(newSelected);
|
|
1266
|
-
if (newSelected === null) {
|
|
1267
|
-
if (!isHoverControlled) {
|
|
1268
|
-
setInternalHovered(null);
|
|
1269
|
-
}
|
|
1270
|
-
onHover?.(null);
|
|
1271
|
-
}
|
|
1272
1368
|
},
|
|
1273
|
-
[selectedOrgan, isSelectControlled,
|
|
1369
|
+
[selectedOrgan, isSelectControlled, onSelect]
|
|
1274
1370
|
);
|
|
1275
1371
|
const state = useMemo(
|
|
1276
1372
|
() => ({
|
|
@@ -1301,6 +1397,338 @@ function useOrganInteraction(options = {}) {
|
|
|
1301
1397
|
isHovered
|
|
1302
1398
|
};
|
|
1303
1399
|
}
|
|
1400
|
+
var ZOOM_ANIMATION_MS = 180;
|
|
1401
|
+
var easeOutCubic = (t) => 1 - (1 - t) ** 3;
|
|
1402
|
+
function useZoom(options = {}) {
|
|
1403
|
+
const { minZoom = 1, maxZoom = 5, zoomStep = 0.5, wheelZoom = true, viewBox } = options;
|
|
1404
|
+
const [zoom, setZoom] = useState(1);
|
|
1405
|
+
const [pan, setPan] = useState({ x: 0, y: 0 });
|
|
1406
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
1407
|
+
const dragStartRef = useRef({ x: 0, y: 0 });
|
|
1408
|
+
const panStartRef = useRef({ x: 0, y: 0 });
|
|
1409
|
+
const containerRef = useRef(null);
|
|
1410
|
+
const zoomRef = useRef(1);
|
|
1411
|
+
const panRef = useRef({ x: 0, y: 0 });
|
|
1412
|
+
const animationRef = useRef(null);
|
|
1413
|
+
const clampZoom = useCallback(
|
|
1414
|
+
(value) => Math.max(minZoom, Math.min(maxZoom, value)),
|
|
1415
|
+
[minZoom, maxZoom]
|
|
1416
|
+
);
|
|
1417
|
+
const stopAnimation = useCallback(() => {
|
|
1418
|
+
if (animationRef.current !== null) {
|
|
1419
|
+
cancelAnimationFrame(animationRef.current);
|
|
1420
|
+
animationRef.current = null;
|
|
1421
|
+
}
|
|
1422
|
+
}, []);
|
|
1423
|
+
const animateZoom = useCallback(
|
|
1424
|
+
(targetZoom, options2) => {
|
|
1425
|
+
const { targetPan, durationMs = ZOOM_ANIMATION_MS } = options2 ?? {};
|
|
1426
|
+
stopAnimation();
|
|
1427
|
+
const startZoom = zoomRef.current;
|
|
1428
|
+
const clampedTargetZoom = clampZoom(targetZoom);
|
|
1429
|
+
const startPan = panRef.current;
|
|
1430
|
+
const hasPanTarget = targetPan !== void 0;
|
|
1431
|
+
const finalPan = targetPan ?? startPan;
|
|
1432
|
+
if (durationMs <= 0 || clampedTargetZoom === startZoom && (!hasPanTarget || finalPan.x === startPan.x && finalPan.y === startPan.y)) {
|
|
1433
|
+
setZoom(clampedTargetZoom);
|
|
1434
|
+
zoomRef.current = clampedTargetZoom;
|
|
1435
|
+
if (hasPanTarget) {
|
|
1436
|
+
setPan(finalPan);
|
|
1437
|
+
panRef.current = finalPan;
|
|
1438
|
+
}
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
const startTime = performance.now();
|
|
1442
|
+
const step = (now) => {
|
|
1443
|
+
const elapsed = now - startTime;
|
|
1444
|
+
const t = Math.min(elapsed / durationMs, 1);
|
|
1445
|
+
const eased = easeOutCubic(t);
|
|
1446
|
+
const nextZoom = startZoom + (clampedTargetZoom - startZoom) * eased;
|
|
1447
|
+
zoomRef.current = nextZoom;
|
|
1448
|
+
setZoom(nextZoom);
|
|
1449
|
+
if (hasPanTarget) {
|
|
1450
|
+
const nextPan = {
|
|
1451
|
+
x: startPan.x + (finalPan.x - startPan.x) * eased,
|
|
1452
|
+
y: startPan.y + (finalPan.y - startPan.y) * eased
|
|
1453
|
+
};
|
|
1454
|
+
panRef.current = nextPan;
|
|
1455
|
+
setPan(nextPan);
|
|
1456
|
+
}
|
|
1457
|
+
if (t < 1) {
|
|
1458
|
+
animationRef.current = requestAnimationFrame(step);
|
|
1459
|
+
} else {
|
|
1460
|
+
animationRef.current = null;
|
|
1461
|
+
setZoom(clampedTargetZoom);
|
|
1462
|
+
zoomRef.current = clampedTargetZoom;
|
|
1463
|
+
if (hasPanTarget) {
|
|
1464
|
+
setPan(finalPan);
|
|
1465
|
+
panRef.current = finalPan;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
animationRef.current = requestAnimationFrame(step);
|
|
1470
|
+
},
|
|
1471
|
+
[clampZoom, stopAnimation]
|
|
1472
|
+
);
|
|
1473
|
+
const zoomIn = useCallback(() => {
|
|
1474
|
+
const nextZoom = clampZoom(zoomRef.current + zoomStep);
|
|
1475
|
+
animateZoom(nextZoom);
|
|
1476
|
+
}, [animateZoom, clampZoom, zoomStep]);
|
|
1477
|
+
const zoomOut = useCallback(() => {
|
|
1478
|
+
const nextZoom = clampZoom(zoomRef.current - zoomStep);
|
|
1479
|
+
animateZoom(nextZoom);
|
|
1480
|
+
}, [animateZoom, clampZoom, zoomStep]);
|
|
1481
|
+
const resetZoom = useCallback(() => {
|
|
1482
|
+
animateZoom(1, { targetPan: { x: 0, y: 0 } });
|
|
1483
|
+
}, [animateZoom]);
|
|
1484
|
+
const getViewBoxSize = useCallback(
|
|
1485
|
+
(container) => {
|
|
1486
|
+
if (viewBox?.width && viewBox?.height) {
|
|
1487
|
+
return { width: viewBox.width, height: viewBox.height };
|
|
1488
|
+
}
|
|
1489
|
+
const rect = container.getBoundingClientRect();
|
|
1490
|
+
return { width: rect.width || 1, height: rect.height || 1 };
|
|
1491
|
+
},
|
|
1492
|
+
[viewBox?.width, viewBox?.height]
|
|
1493
|
+
);
|
|
1494
|
+
const getViewBoxMetrics = useCallback(
|
|
1495
|
+
(container) => {
|
|
1496
|
+
const rect = container.getBoundingClientRect();
|
|
1497
|
+
const { width: viewBoxWidth, height: viewBoxHeight } = getViewBoxSize(container);
|
|
1498
|
+
const safeWidth = viewBoxWidth || 1;
|
|
1499
|
+
const safeHeight = viewBoxHeight || 1;
|
|
1500
|
+
const scale = rect.width > 0 && rect.height > 0 ? Math.min(rect.width / safeWidth, rect.height / safeHeight) : 0;
|
|
1501
|
+
const contentWidth = safeWidth * scale;
|
|
1502
|
+
const contentHeight = safeHeight * scale;
|
|
1503
|
+
const offsetX = (rect.width - contentWidth) / 2;
|
|
1504
|
+
const offsetY = (rect.height - contentHeight) / 2;
|
|
1505
|
+
return { rect, viewBoxWidth: safeWidth, viewBoxHeight: safeHeight, scale, offsetX, offsetY };
|
|
1506
|
+
},
|
|
1507
|
+
[getViewBoxSize]
|
|
1508
|
+
);
|
|
1509
|
+
const getPointerPosition = useCallback(
|
|
1510
|
+
(container, clientX, clientY) => {
|
|
1511
|
+
const ctm = container.getScreenCTM();
|
|
1512
|
+
if (ctm) {
|
|
1513
|
+
if (typeof DOMPoint !== "undefined") {
|
|
1514
|
+
const point = new DOMPoint(clientX, clientY);
|
|
1515
|
+
const { x, y } = point.matrixTransform(ctm.inverse());
|
|
1516
|
+
return { x, y };
|
|
1517
|
+
}
|
|
1518
|
+
if ("createSVGPoint" in container) {
|
|
1519
|
+
const point = container.createSVGPoint();
|
|
1520
|
+
point.x = clientX;
|
|
1521
|
+
point.y = clientY;
|
|
1522
|
+
const { x, y } = point.matrixTransform(ctm.inverse());
|
|
1523
|
+
return { x, y };
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
const { rect, scale, offsetX, offsetY } = getViewBoxMetrics(container);
|
|
1527
|
+
if (scale <= 0) return null;
|
|
1528
|
+
return {
|
|
1529
|
+
x: (clientX - rect.left - offsetX) / scale,
|
|
1530
|
+
y: (clientY - rect.top - offsetY) / scale
|
|
1531
|
+
};
|
|
1532
|
+
},
|
|
1533
|
+
[getViewBoxMetrics]
|
|
1534
|
+
);
|
|
1535
|
+
const clampPan = useCallback(
|
|
1536
|
+
(newPan, currentZoom, viewBoxWidth, viewBoxHeight) => {
|
|
1537
|
+
if (currentZoom <= 1) {
|
|
1538
|
+
return { x: 0, y: 0 };
|
|
1539
|
+
}
|
|
1540
|
+
const maxPanX = viewBoxWidth * (currentZoom - 1) / 2;
|
|
1541
|
+
const maxPanY = viewBoxHeight * (currentZoom - 1) / 2;
|
|
1542
|
+
return {
|
|
1543
|
+
x: Math.max(-maxPanX, Math.min(maxPanX, newPan.x)),
|
|
1544
|
+
y: Math.max(-maxPanY, Math.min(maxPanY, newPan.y))
|
|
1545
|
+
};
|
|
1546
|
+
},
|
|
1547
|
+
[]
|
|
1548
|
+
);
|
|
1549
|
+
useEffect(() => {
|
|
1550
|
+
zoomRef.current = zoom;
|
|
1551
|
+
}, [zoom]);
|
|
1552
|
+
useEffect(() => {
|
|
1553
|
+
panRef.current = pan;
|
|
1554
|
+
}, [pan]);
|
|
1555
|
+
useEffect(() => {
|
|
1556
|
+
return () => stopAnimation();
|
|
1557
|
+
}, [stopAnimation]);
|
|
1558
|
+
useEffect(() => {
|
|
1559
|
+
const container = containerRef.current;
|
|
1560
|
+
if (!container || !wheelZoom) return;
|
|
1561
|
+
const wheelZoomStep = 0.1;
|
|
1562
|
+
const handleWheel = (e) => {
|
|
1563
|
+
e.preventDefault();
|
|
1564
|
+
stopAnimation();
|
|
1565
|
+
const { viewBoxWidth, viewBoxHeight } = getViewBoxMetrics(container);
|
|
1566
|
+
const pointer = getPointerPosition(container, e.clientX, e.clientY);
|
|
1567
|
+
if (!pointer) return;
|
|
1568
|
+
const { x: mouseX, y: mouseY } = pointer;
|
|
1569
|
+
const centerX = viewBoxWidth / 2;
|
|
1570
|
+
const centerY = viewBoxHeight / 2;
|
|
1571
|
+
const delta = e.deltaY > 0 ? -wheelZoomStep : wheelZoomStep;
|
|
1572
|
+
const currentZoom = zoomRef.current;
|
|
1573
|
+
const nextZoom = clampZoom(currentZoom + delta);
|
|
1574
|
+
if (nextZoom === currentZoom) return;
|
|
1575
|
+
const zoomRatio = nextZoom / currentZoom;
|
|
1576
|
+
const nextPan = {
|
|
1577
|
+
x: (mouseX - centerX) * (1 - zoomRatio) + panRef.current.x * zoomRatio,
|
|
1578
|
+
y: (mouseY - centerY) * (1 - zoomRatio) + panRef.current.y * zoomRatio
|
|
1579
|
+
};
|
|
1580
|
+
const clampedPan = clampPan(nextPan, nextZoom, viewBoxWidth, viewBoxHeight);
|
|
1581
|
+
zoomRef.current = nextZoom;
|
|
1582
|
+
panRef.current = clampedPan;
|
|
1583
|
+
setZoom(nextZoom);
|
|
1584
|
+
setPan(clampedPan);
|
|
1585
|
+
};
|
|
1586
|
+
container.addEventListener("wheel", handleWheel, { passive: false });
|
|
1587
|
+
return () => {
|
|
1588
|
+
container.removeEventListener("wheel", handleWheel);
|
|
1589
|
+
};
|
|
1590
|
+
}, [clampZoom, wheelZoom, getViewBoxMetrics, getPointerPosition, clampPan, stopAnimation]);
|
|
1591
|
+
useEffect(() => {
|
|
1592
|
+
const container = containerRef.current;
|
|
1593
|
+
if (!container) return;
|
|
1594
|
+
const { viewBoxWidth, viewBoxHeight } = getViewBoxMetrics(container);
|
|
1595
|
+
setPan((currentPan) => clampPan(currentPan, zoom, viewBoxWidth, viewBoxHeight));
|
|
1596
|
+
}, [zoom, clampPan, getViewBoxMetrics]);
|
|
1597
|
+
const handleMouseDown = useCallback(
|
|
1598
|
+
(e) => {
|
|
1599
|
+
if (e.button !== 0 || zoom <= 1) return;
|
|
1600
|
+
stopAnimation();
|
|
1601
|
+
setIsDragging(true);
|
|
1602
|
+
dragStartRef.current = { x: e.clientX, y: e.clientY };
|
|
1603
|
+
panStartRef.current = { ...pan };
|
|
1604
|
+
e.preventDefault();
|
|
1605
|
+
},
|
|
1606
|
+
[pan, zoom, stopAnimation]
|
|
1607
|
+
);
|
|
1608
|
+
const handleMouseMove = useCallback(
|
|
1609
|
+
(e) => {
|
|
1610
|
+
if (!isDragging || zoom <= 1) return;
|
|
1611
|
+
const container = containerRef.current;
|
|
1612
|
+
if (!container) return;
|
|
1613
|
+
const { scale, viewBoxWidth, viewBoxHeight } = getViewBoxMetrics(container);
|
|
1614
|
+
if (scale <= 0) return;
|
|
1615
|
+
const dx = e.clientX - dragStartRef.current.x;
|
|
1616
|
+
const dy = e.clientY - dragStartRef.current.y;
|
|
1617
|
+
const newPan = {
|
|
1618
|
+
x: panStartRef.current.x + dx / scale,
|
|
1619
|
+
y: panStartRef.current.y + dy / scale
|
|
1620
|
+
};
|
|
1621
|
+
setPan(clampPan(newPan, zoom, viewBoxWidth, viewBoxHeight));
|
|
1622
|
+
},
|
|
1623
|
+
[isDragging, zoom, clampPan, getViewBoxMetrics]
|
|
1624
|
+
);
|
|
1625
|
+
const handleMouseUp = useCallback(() => {
|
|
1626
|
+
setIsDragging(false);
|
|
1627
|
+
}, []);
|
|
1628
|
+
const isDefaultZoom = zoom === 1 && pan.x === 0 && pan.y === 0;
|
|
1629
|
+
return {
|
|
1630
|
+
zoom,
|
|
1631
|
+
pan,
|
|
1632
|
+
zoomIn,
|
|
1633
|
+
zoomOut,
|
|
1634
|
+
resetZoom,
|
|
1635
|
+
handleMouseDown,
|
|
1636
|
+
handleMouseMove,
|
|
1637
|
+
handleMouseUp,
|
|
1638
|
+
isDragging,
|
|
1639
|
+
isDefaultZoom,
|
|
1640
|
+
containerRef
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
var buttonBaseStyle = {
|
|
1644
|
+
width: 32,
|
|
1645
|
+
height: 32,
|
|
1646
|
+
display: "flex",
|
|
1647
|
+
alignItems: "center",
|
|
1648
|
+
justifyContent: "center",
|
|
1649
|
+
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
|
1650
|
+
border: "1px solid #d1d5db",
|
|
1651
|
+
borderRadius: 6,
|
|
1652
|
+
cursor: "pointer",
|
|
1653
|
+
fontSize: 18,
|
|
1654
|
+
fontWeight: "bold",
|
|
1655
|
+
color: "#374151",
|
|
1656
|
+
transition: "background-color 0.15s ease",
|
|
1657
|
+
userSelect: "none"
|
|
1658
|
+
};
|
|
1659
|
+
var buttonDisabledStyle = {
|
|
1660
|
+
...buttonBaseStyle,
|
|
1661
|
+
opacity: 0.4,
|
|
1662
|
+
cursor: "not-allowed"
|
|
1663
|
+
};
|
|
1664
|
+
var getContainerStyle = (isVisible) => ({
|
|
1665
|
+
position: "absolute",
|
|
1666
|
+
bottom: 12,
|
|
1667
|
+
right: 12,
|
|
1668
|
+
display: "flex",
|
|
1669
|
+
flexDirection: "column",
|
|
1670
|
+
gap: 4,
|
|
1671
|
+
zIndex: 10,
|
|
1672
|
+
opacity: isVisible ? 1 : 0,
|
|
1673
|
+
transition: "opacity 0.2s ease-in-out",
|
|
1674
|
+
pointerEvents: isVisible ? "auto" : "none"
|
|
1675
|
+
});
|
|
1676
|
+
var getResetContainerStyle = (isVisible) => ({
|
|
1677
|
+
position: "absolute",
|
|
1678
|
+
bottom: 12,
|
|
1679
|
+
left: 12,
|
|
1680
|
+
zIndex: 10,
|
|
1681
|
+
opacity: isVisible ? 1 : 0,
|
|
1682
|
+
transition: "opacity 0.2s ease-in-out",
|
|
1683
|
+
pointerEvents: isVisible ? "auto" : "none"
|
|
1684
|
+
});
|
|
1685
|
+
var resetButtonStyle = {
|
|
1686
|
+
...buttonBaseStyle,
|
|
1687
|
+
width: "auto",
|
|
1688
|
+
padding: "6px 12px",
|
|
1689
|
+
fontSize: 12,
|
|
1690
|
+
fontWeight: 500
|
|
1691
|
+
};
|
|
1692
|
+
function ZoomControls({
|
|
1693
|
+
onZoomIn,
|
|
1694
|
+
onZoomOut,
|
|
1695
|
+
onReset,
|
|
1696
|
+
showResetButton,
|
|
1697
|
+
zoom,
|
|
1698
|
+
minZoom,
|
|
1699
|
+
maxZoom,
|
|
1700
|
+
isVisible
|
|
1701
|
+
}) {
|
|
1702
|
+
const isMinZoom = zoom <= minZoom;
|
|
1703
|
+
const isMaxZoom = zoom >= maxZoom;
|
|
1704
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1705
|
+
/* @__PURE__ */ jsxs("div", { style: getContainerStyle(isVisible), children: [
|
|
1706
|
+
/* @__PURE__ */ jsx(
|
|
1707
|
+
"button",
|
|
1708
|
+
{
|
|
1709
|
+
type: "button",
|
|
1710
|
+
onClick: onZoomIn,
|
|
1711
|
+
disabled: isMaxZoom,
|
|
1712
|
+
style: isMaxZoom ? buttonDisabledStyle : buttonBaseStyle,
|
|
1713
|
+
"aria-label": "Zoom in",
|
|
1714
|
+
children: "+"
|
|
1715
|
+
}
|
|
1716
|
+
),
|
|
1717
|
+
/* @__PURE__ */ jsx(
|
|
1718
|
+
"button",
|
|
1719
|
+
{
|
|
1720
|
+
type: "button",
|
|
1721
|
+
onClick: onZoomOut,
|
|
1722
|
+
disabled: isMinZoom,
|
|
1723
|
+
style: isMinZoom ? buttonDisabledStyle : buttonBaseStyle,
|
|
1724
|
+
"aria-label": "Zoom out",
|
|
1725
|
+
children: "\u2212"
|
|
1726
|
+
}
|
|
1727
|
+
)
|
|
1728
|
+
] }),
|
|
1729
|
+
/* @__PURE__ */ jsx("div", { style: getResetContainerStyle(isVisible && showResetButton), children: /* @__PURE__ */ jsx("button", { type: "button", onClick: onReset, style: resetButtonStyle, "aria-label": "Reset zoom", children: "\u21BA" }) })
|
|
1730
|
+
] });
|
|
1731
|
+
}
|
|
1304
1732
|
var ORGAN_POSITIONS = {
|
|
1305
1733
|
growth: { x: 104, y: 1, width: 12, height: 349, viewBox: "0 0 12 349" },
|
|
1306
1734
|
constitutional: { x: 0, y: 0, width: 122, height: 358, viewBox: "0 0 122 358" },
|
|
@@ -1351,6 +1779,7 @@ var FOREGROUND_ORGANS = [
|
|
|
1351
1779
|
"prenatal",
|
|
1352
1780
|
"blood"
|
|
1353
1781
|
];
|
|
1782
|
+
var MIN_ZOOM = 1;
|
|
1354
1783
|
function HpoVisualizer({
|
|
1355
1784
|
organs,
|
|
1356
1785
|
visibleOrgans,
|
|
@@ -1359,12 +1788,28 @@ function HpoVisualizer({
|
|
|
1359
1788
|
onHover,
|
|
1360
1789
|
onSelect,
|
|
1361
1790
|
colorPalette: inputColorPalette,
|
|
1362
|
-
width =
|
|
1363
|
-
height =
|
|
1791
|
+
width = "100%",
|
|
1792
|
+
height = "100%",
|
|
1364
1793
|
className,
|
|
1365
|
-
style
|
|
1794
|
+
style,
|
|
1795
|
+
maxZoom = 5,
|
|
1796
|
+
wheelZoom = true
|
|
1366
1797
|
}) {
|
|
1367
1798
|
const visualizerID = useId();
|
|
1799
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
1800
|
+
const {
|
|
1801
|
+
zoom,
|
|
1802
|
+
pan,
|
|
1803
|
+
zoomIn,
|
|
1804
|
+
zoomOut,
|
|
1805
|
+
resetZoom,
|
|
1806
|
+
handleMouseDown,
|
|
1807
|
+
handleMouseMove,
|
|
1808
|
+
handleMouseUp,
|
|
1809
|
+
isDragging,
|
|
1810
|
+
isDefaultZoom,
|
|
1811
|
+
containerRef
|
|
1812
|
+
} = useZoom({ minZoom: MIN_ZOOM, maxZoom, wheelZoom, viewBox: BODY_VIEWBOX });
|
|
1368
1813
|
const colorPalette = useMemo(
|
|
1369
1814
|
() => createStrictColorPalette(inputColorPalette),
|
|
1370
1815
|
[inputColorPalette]
|
|
@@ -1387,12 +1832,48 @@ function HpoVisualizer({
|
|
|
1387
1832
|
}
|
|
1388
1833
|
return map;
|
|
1389
1834
|
}, [organs]);
|
|
1390
|
-
const { handlers, isHovered, isSelected } = useOrganInteraction({
|
|
1835
|
+
const { handlers, isHovered, isSelected, state } = useOrganInteraction({
|
|
1391
1836
|
hoveredOrgan: controlledHovered,
|
|
1392
1837
|
selectedOrgan: controlledSelected,
|
|
1393
1838
|
onHover,
|
|
1394
1839
|
onSelect
|
|
1395
1840
|
});
|
|
1841
|
+
const selectedOrganId = state.selectedOrgan;
|
|
1842
|
+
const renderEntries = useMemo(() => {
|
|
1843
|
+
const base = [
|
|
1844
|
+
...BACKGROUND_ORGANS.map((organId) => ({ type: "organ", organId })),
|
|
1845
|
+
{ type: "body" },
|
|
1846
|
+
...FOREGROUND_ORGANS.map((organId) => ({ type: "organ", organId }))
|
|
1847
|
+
];
|
|
1848
|
+
if (!selectedOrganId) {
|
|
1849
|
+
return base;
|
|
1850
|
+
}
|
|
1851
|
+
const selectedIndex = base.findIndex(
|
|
1852
|
+
(entry) => entry.type === "organ" && entry.organId === selectedOrganId
|
|
1853
|
+
);
|
|
1854
|
+
if (selectedIndex === -1) {
|
|
1855
|
+
return base;
|
|
1856
|
+
}
|
|
1857
|
+
const selectedEntry = base[selectedIndex];
|
|
1858
|
+
if (!selectedEntry || selectedEntry.type !== "organ") {
|
|
1859
|
+
return base;
|
|
1860
|
+
}
|
|
1861
|
+
return [...base.slice(0, selectedIndex), ...base.slice(selectedIndex + 1), selectedEntry];
|
|
1862
|
+
}, [selectedOrganId]);
|
|
1863
|
+
const {
|
|
1864
|
+
padding,
|
|
1865
|
+
paddingTop,
|
|
1866
|
+
paddingRight,
|
|
1867
|
+
paddingBottom,
|
|
1868
|
+
paddingLeft,
|
|
1869
|
+
paddingInline,
|
|
1870
|
+
paddingInlineStart,
|
|
1871
|
+
paddingInlineEnd,
|
|
1872
|
+
paddingBlock,
|
|
1873
|
+
paddingBlockStart,
|
|
1874
|
+
paddingBlockEnd,
|
|
1875
|
+
...containerStyleOverrides
|
|
1876
|
+
} = style ?? {};
|
|
1396
1877
|
const containerStyle = {
|
|
1397
1878
|
display: "flex",
|
|
1398
1879
|
justifyContent: "center",
|
|
@@ -1401,21 +1882,50 @@ function HpoVisualizer({
|
|
|
1401
1882
|
position: "relative",
|
|
1402
1883
|
width,
|
|
1403
1884
|
height,
|
|
1404
|
-
...
|
|
1885
|
+
...containerStyleOverrides,
|
|
1886
|
+
// Apply overflow after style spread to ensure it takes precedence
|
|
1887
|
+
overflow: "clip"
|
|
1888
|
+
};
|
|
1889
|
+
const contentStyle = {
|
|
1890
|
+
display: "flex",
|
|
1891
|
+
justifyContent: "center",
|
|
1892
|
+
alignItems: "flex-end",
|
|
1893
|
+
width: "100%",
|
|
1894
|
+
height: "100%",
|
|
1895
|
+
boxSizing: "border-box",
|
|
1896
|
+
padding,
|
|
1897
|
+
paddingTop,
|
|
1898
|
+
paddingRight,
|
|
1899
|
+
paddingBottom,
|
|
1900
|
+
paddingLeft,
|
|
1901
|
+
paddingInline,
|
|
1902
|
+
paddingInlineStart,
|
|
1903
|
+
paddingInlineEnd,
|
|
1904
|
+
paddingBlock,
|
|
1905
|
+
paddingBlockStart,
|
|
1906
|
+
paddingBlockEnd
|
|
1907
|
+
};
|
|
1908
|
+
const svgStyle = {
|
|
1909
|
+
width: "100%",
|
|
1910
|
+
height: "100%",
|
|
1911
|
+
display: "block",
|
|
1912
|
+
cursor: isDragging ? "grabbing" : zoom > 1 ? "grab" : "default",
|
|
1913
|
+
overflow: "visible"
|
|
1405
1914
|
};
|
|
1406
1915
|
const viewBox = `0 0 ${BODY_VIEWBOX.width} ${BODY_VIEWBOX.height}`;
|
|
1407
|
-
const
|
|
1408
|
-
const
|
|
1916
|
+
const centerX = BODY_VIEWBOX.width / 2;
|
|
1917
|
+
const centerY = BODY_VIEWBOX.height / 2;
|
|
1918
|
+
const zoomTransform = `translate(${centerX} ${centerY}) scale(${zoom}) translate(${-centerX} ${-centerY})`;
|
|
1409
1919
|
const renderOrgan = (organId, isVisible) => {
|
|
1410
1920
|
const position = ORGAN_POSITIONS[organId];
|
|
1411
1921
|
const config = organConfigMap.get(organId);
|
|
1412
1922
|
if (config?.style?.visible === false) {
|
|
1413
1923
|
return null;
|
|
1414
1924
|
}
|
|
1415
|
-
const x =
|
|
1416
|
-
const y = position.y
|
|
1417
|
-
const width2 = position.width
|
|
1418
|
-
const height2 = position.height
|
|
1925
|
+
const x = position.x;
|
|
1926
|
+
const y = position.y;
|
|
1927
|
+
const width2 = position.width;
|
|
1928
|
+
const height2 = position.height;
|
|
1419
1929
|
return /* @__PURE__ */ jsx(
|
|
1420
1930
|
OrganSvg,
|
|
1421
1931
|
{
|
|
@@ -1438,36 +1948,70 @@ function HpoVisualizer({
|
|
|
1438
1948
|
`${visualizerID}-${organId}`
|
|
1439
1949
|
);
|
|
1440
1950
|
};
|
|
1441
|
-
return /* @__PURE__ */ jsxs(
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1951
|
+
return /* @__PURE__ */ jsxs(
|
|
1952
|
+
"div",
|
|
1953
|
+
{
|
|
1954
|
+
className,
|
|
1955
|
+
style: containerStyle,
|
|
1956
|
+
onMouseEnter: () => setIsHovering(true),
|
|
1957
|
+
onMouseLeave: () => {
|
|
1958
|
+
setIsHovering(false);
|
|
1959
|
+
handleMouseUp();
|
|
1960
|
+
},
|
|
1961
|
+
onMouseDown: handleMouseDown,
|
|
1962
|
+
onMouseMove: handleMouseMove,
|
|
1963
|
+
onMouseUp: handleMouseUp,
|
|
1964
|
+
role: "application",
|
|
1965
|
+
tabIndex: 0,
|
|
1966
|
+
children: [
|
|
1967
|
+
/* @__PURE__ */ jsx("div", { style: contentStyle, children: /* @__PURE__ */ jsxs(
|
|
1968
|
+
"svg",
|
|
1969
|
+
{
|
|
1970
|
+
ref: containerRef,
|
|
1971
|
+
width: "100%",
|
|
1972
|
+
height: "100%",
|
|
1973
|
+
viewBox,
|
|
1974
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
1975
|
+
style: svgStyle,
|
|
1976
|
+
"aria-label": "Human organ visualizer",
|
|
1977
|
+
children: [
|
|
1978
|
+
/* @__PURE__ */ jsx("title", { children: "Human organ visualizer" }),
|
|
1979
|
+
/* @__PURE__ */ jsx("g", { transform: `translate(${pan.x} ${pan.y})`, children: /* @__PURE__ */ jsx("g", { transform: zoomTransform, children: renderEntries.map((entry) => {
|
|
1980
|
+
if (entry.type === "body") {
|
|
1981
|
+
return /* @__PURE__ */ jsx(
|
|
1982
|
+
Body,
|
|
1983
|
+
{
|
|
1984
|
+
colorScale: colorPalette[DEFAULT_COLOR_NAME],
|
|
1985
|
+
style: {
|
|
1986
|
+
fill: "#fff"
|
|
1987
|
+
}
|
|
1988
|
+
},
|
|
1989
|
+
`${visualizerID}-body`
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
return renderOrgan(entry.organId, visibleOrganIds.includes(entry.organId));
|
|
1993
|
+
}) }) })
|
|
1994
|
+
]
|
|
1995
|
+
}
|
|
1996
|
+
) }),
|
|
1997
|
+
/* @__PURE__ */ jsx(
|
|
1998
|
+
ZoomControls,
|
|
1999
|
+
{
|
|
2000
|
+
onZoomIn: zoomIn,
|
|
2001
|
+
onZoomOut: zoomOut,
|
|
2002
|
+
onReset: resetZoom,
|
|
2003
|
+
showResetButton: !isDefaultZoom,
|
|
2004
|
+
zoom,
|
|
2005
|
+
minZoom: MIN_ZOOM,
|
|
2006
|
+
maxZoom,
|
|
2007
|
+
isVisible: isHovering
|
|
2008
|
+
}
|
|
2009
|
+
)
|
|
2010
|
+
]
|
|
2011
|
+
}
|
|
2012
|
+
);
|
|
1469
2013
|
}
|
|
1470
2014
|
|
|
1471
|
-
export { ANIMATION_DURATION_MS, DEFAULT_COLOR_PALETTE, HpoVisualizer, ORGAN_COMPONENTS, ORGAN_IDS, ORGAN_NAMES_EN, ORGAN_NAMES_KO, OrganSvg, useOrganInteraction };
|
|
2015
|
+
export { ANIMATION_DURATION_MS, DEFAULT_COLOR_PALETTE, HPO_LABELS, HPO_LABEL_TO_ORGAN, HpoVisualizer, ORGAN_COMPONENTS, ORGAN_IDS, ORGAN_NAMES_EN, ORGAN_NAMES_KO, ORGAN_TO_HPO_LABEL, OrganSvg, createOrganOutlineSet, createUniformOrganColorSchemes, useOrganInteraction };
|
|
1472
2016
|
//# sourceMappingURL=index.js.map
|
|
1473
2017
|
//# sourceMappingURL=index.js.map
|