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.cjs
CHANGED
|
@@ -80,6 +80,82 @@ var ORGAN_NAMES_KO = {
|
|
|
80
80
|
prenatal: "\uD0DC\uC544",
|
|
81
81
|
blood: "\uD608\uC561"
|
|
82
82
|
};
|
|
83
|
+
var HPO_LABELS = [
|
|
84
|
+
"others",
|
|
85
|
+
"Growth abnormality",
|
|
86
|
+
"Abnormality of the genitourinary system",
|
|
87
|
+
"Abnormality of the immune system",
|
|
88
|
+
"Abnormality of the digestive system",
|
|
89
|
+
"Abnormality of metabolism/homeostasis",
|
|
90
|
+
"Abnormality of head or neck",
|
|
91
|
+
"Abnormality of the musculoskeletal system",
|
|
92
|
+
"Abnormality of the nervous system",
|
|
93
|
+
"Abnormality of the respiratory system",
|
|
94
|
+
"Abnormality of the eye",
|
|
95
|
+
"Abnormality of the cardiovascular system",
|
|
96
|
+
"Abnormality of the ear",
|
|
97
|
+
"Abnormality of prenatal development or birth",
|
|
98
|
+
"Abnormality of the integument",
|
|
99
|
+
"Abnormality of the breast",
|
|
100
|
+
"Abnormality of the endocrine system",
|
|
101
|
+
"Abnormality of blood and blood-forming tissues",
|
|
102
|
+
"Abnormality of limbs",
|
|
103
|
+
"Abnormality of the voice",
|
|
104
|
+
"Constitutional symptom",
|
|
105
|
+
"Neoplasm",
|
|
106
|
+
"Abnormal cellular phenotype",
|
|
107
|
+
"Abnormality of the thoracic cavity"
|
|
108
|
+
];
|
|
109
|
+
var HPO_LABEL_TO_ORGAN = {
|
|
110
|
+
"Growth abnormality": "growth",
|
|
111
|
+
"Abnormality of the genitourinary system": "kidney",
|
|
112
|
+
"Abnormality of the immune system": "immune",
|
|
113
|
+
"Abnormality of the digestive system": "digestive",
|
|
114
|
+
"Abnormality of metabolism/homeostasis": "metabolism",
|
|
115
|
+
"Abnormality of head or neck": "head",
|
|
116
|
+
"Abnormality of the musculoskeletal system": "muscle",
|
|
117
|
+
"Abnormality of the nervous system": "nervous",
|
|
118
|
+
"Abnormality of the respiratory system": "lung",
|
|
119
|
+
"Abnormality of the eye": "eye",
|
|
120
|
+
"Abnormality of the cardiovascular system": "heart",
|
|
121
|
+
"Abnormality of the ear": "ear",
|
|
122
|
+
"Abnormality of prenatal development or birth": "prenatal",
|
|
123
|
+
"Abnormality of the integument": "integument",
|
|
124
|
+
"Abnormality of the breast": "breast",
|
|
125
|
+
"Abnormality of the endocrine system": "endocrine",
|
|
126
|
+
"Abnormality of blood and blood-forming tissues": "blood",
|
|
127
|
+
"Abnormality of limbs": "limbs",
|
|
128
|
+
"Abnormality of the voice": "voice",
|
|
129
|
+
"Constitutional symptom": "constitutional",
|
|
130
|
+
Neoplasm: "neoplasm",
|
|
131
|
+
"Abnormal cellular phenotype": "cell",
|
|
132
|
+
"Abnormality of the thoracic cavity": "thoracicCavity"
|
|
133
|
+
};
|
|
134
|
+
var ORGAN_TO_HPO_LABEL = {
|
|
135
|
+
growth: "Growth abnormality",
|
|
136
|
+
kidney: "Abnormality of the genitourinary system",
|
|
137
|
+
immune: "Abnormality of the immune system",
|
|
138
|
+
digestive: "Abnormality of the digestive system",
|
|
139
|
+
metabolism: "Abnormality of metabolism/homeostasis",
|
|
140
|
+
head: "Abnormality of head or neck",
|
|
141
|
+
muscle: "Abnormality of the musculoskeletal system",
|
|
142
|
+
nervous: "Abnormality of the nervous system",
|
|
143
|
+
lung: "Abnormality of the respiratory system",
|
|
144
|
+
eye: "Abnormality of the eye",
|
|
145
|
+
heart: "Abnormality of the cardiovascular system",
|
|
146
|
+
ear: "Abnormality of the ear",
|
|
147
|
+
prenatal: "Abnormality of prenatal development or birth",
|
|
148
|
+
integument: "Abnormality of the integument",
|
|
149
|
+
breast: "Abnormality of the breast",
|
|
150
|
+
endocrine: "Abnormality of the endocrine system",
|
|
151
|
+
blood: "Abnormality of blood and blood-forming tissues",
|
|
152
|
+
limbs: "Abnormality of limbs",
|
|
153
|
+
voice: "Abnormality of the voice",
|
|
154
|
+
constitutional: "Constitutional symptom",
|
|
155
|
+
neoplasm: "Neoplasm",
|
|
156
|
+
cell: "Abnormal cellular phenotype",
|
|
157
|
+
thoracicCavity: "Abnormality of the thoracic cavity"
|
|
158
|
+
};
|
|
83
159
|
var DEFAULT_COLOR_PALETTE = {
|
|
84
160
|
blue: {
|
|
85
161
|
100: "#DBEAFE",
|
|
@@ -116,7 +192,19 @@ var DEFAULT_COLOR_NAME = "blue";
|
|
|
116
192
|
var DEFAULT_STROKE_COLOR = "#ff142d";
|
|
117
193
|
var DEFAULT_STROKE_WIDTH = 0.5;
|
|
118
194
|
var ANIMATION_DURATION_MS = 150;
|
|
119
|
-
var
|
|
195
|
+
var TRANSITION_PROPERTIES = [
|
|
196
|
+
"fill",
|
|
197
|
+
"stroke",
|
|
198
|
+
"stroke-width",
|
|
199
|
+
"opacity",
|
|
200
|
+
"filter",
|
|
201
|
+
"stop-color",
|
|
202
|
+
"stop-opacity",
|
|
203
|
+
"visibility"
|
|
204
|
+
];
|
|
205
|
+
var TRANSITION_STYLE = TRANSITION_PROPERTIES.map(
|
|
206
|
+
(property) => `${property} ${ANIMATION_DURATION_MS}ms ease-out`
|
|
207
|
+
).join(", ");
|
|
120
208
|
var BODY_VIEWBOX = {
|
|
121
209
|
width: 122,
|
|
122
210
|
height: 358
|
|
@@ -148,6 +236,20 @@ function createStrictColorPalette(palette) {
|
|
|
148
236
|
return result;
|
|
149
237
|
}, {});
|
|
150
238
|
}
|
|
239
|
+
|
|
240
|
+
// src/lib/organControlState.ts
|
|
241
|
+
var createUniformOrganColorSchemes = (organIds, scheme) => {
|
|
242
|
+
return organIds.reduce(
|
|
243
|
+
(acc, organId) => {
|
|
244
|
+
acc[organId] = scheme;
|
|
245
|
+
return acc;
|
|
246
|
+
},
|
|
247
|
+
{}
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
var createOrganOutlineSet = (organIds, enabled) => {
|
|
251
|
+
return enabled ? new Set(organIds) : /* @__PURE__ */ new Set();
|
|
252
|
+
};
|
|
151
253
|
function Blood({ style, colorScale, isActive = false, className }) {
|
|
152
254
|
const defaultColor = colorScale[200];
|
|
153
255
|
const activeColor = colorScale[300];
|
|
@@ -985,18 +1087,22 @@ function Neoplasm({ style, colorScale, isActive = false, className }) {
|
|
|
985
1087
|
const defaultColor = colorScale[100];
|
|
986
1088
|
const activeColor = colorScale[300];
|
|
987
1089
|
const fill = style?.fill ?? (isActive ? activeColor : defaultColor);
|
|
988
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1090
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("g", { className, "data-organ": "neoplasm", children: [
|
|
1091
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1092
|
+
"path",
|
|
1093
|
+
{
|
|
1094
|
+
d: NEOPLASM_PATH,
|
|
1095
|
+
fill,
|
|
1096
|
+
stroke: style?.stroke,
|
|
1097
|
+
strokeWidth: style?.strokeWidth,
|
|
1098
|
+
style: { transition: TRANSITION_STYLE }
|
|
1099
|
+
}
|
|
1100
|
+
),
|
|
1101
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: OUTLINE_NEOPLASM_PATH, fill: "transparent", style: { transition: TRANSITION_STYLE } })
|
|
1102
|
+
] });
|
|
998
1103
|
}
|
|
999
1104
|
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";
|
|
1105
|
+
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";
|
|
1000
1106
|
function Nervous({ style, colorScale, isActive = false, className }) {
|
|
1001
1107
|
const defaultColor = colorScale[100];
|
|
1002
1108
|
const activeColor = colorScale[200];
|
|
@@ -1161,25 +1267,21 @@ function OrganSvg({
|
|
|
1161
1267
|
};
|
|
1162
1268
|
return mergeStyles(userStyle, strokeStyle);
|
|
1163
1269
|
}, [config?.style, showOutline]);
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1270
|
+
const [minX = 0, minY = 0, viewBoxWidth, viewBoxHeight] = viewBox.split(" ").map(Number);
|
|
1271
|
+
const scaleX = viewBoxWidth ? width / viewBoxWidth : 1;
|
|
1272
|
+
const scaleY = viewBoxHeight ? height / viewBoxHeight : 1;
|
|
1273
|
+
const transform = `translate(${x} ${y}) scale(${scaleX} ${scaleY}) translate(${-minX} ${-minY})`;
|
|
1274
|
+
const groupStyle = {
|
|
1275
|
+
transition: `${TRANSITION_STYLE}, visibility 0s`
|
|
1170
1276
|
};
|
|
1171
1277
|
const filter = isActive ? "blur(1px)" : void 0;
|
|
1172
1278
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1173
|
-
"
|
|
1279
|
+
"g",
|
|
1174
1280
|
{
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
viewBox,
|
|
1178
|
-
style: svgStyle,
|
|
1281
|
+
transform,
|
|
1282
|
+
style: groupStyle,
|
|
1179
1283
|
filter,
|
|
1180
1284
|
"aria-label": organId,
|
|
1181
|
-
overflow: "visible",
|
|
1182
|
-
pointerEvents: "none",
|
|
1183
1285
|
opacity: isVisible ? 1 : 0,
|
|
1184
1286
|
visibility: isVisible ? "visible" : "hidden",
|
|
1185
1287
|
children: [
|
|
@@ -1265,14 +1367,8 @@ function useOrganInteraction(options = {}) {
|
|
|
1265
1367
|
setInternalSelected(newSelected);
|
|
1266
1368
|
}
|
|
1267
1369
|
onSelect?.(newSelected);
|
|
1268
|
-
if (newSelected === null) {
|
|
1269
|
-
if (!isHoverControlled) {
|
|
1270
|
-
setInternalHovered(null);
|
|
1271
|
-
}
|
|
1272
|
-
onHover?.(null);
|
|
1273
|
-
}
|
|
1274
1370
|
},
|
|
1275
|
-
[selectedOrgan, isSelectControlled,
|
|
1371
|
+
[selectedOrgan, isSelectControlled, onSelect]
|
|
1276
1372
|
);
|
|
1277
1373
|
const state = react.useMemo(
|
|
1278
1374
|
() => ({
|
|
@@ -1303,6 +1399,338 @@ function useOrganInteraction(options = {}) {
|
|
|
1303
1399
|
isHovered
|
|
1304
1400
|
};
|
|
1305
1401
|
}
|
|
1402
|
+
var ZOOM_ANIMATION_MS = 180;
|
|
1403
|
+
var easeOutCubic = (t) => 1 - (1 - t) ** 3;
|
|
1404
|
+
function useZoom(options = {}) {
|
|
1405
|
+
const { minZoom = 1, maxZoom = 5, zoomStep = 0.5, wheelZoom = true, viewBox } = options;
|
|
1406
|
+
const [zoom, setZoom] = react.useState(1);
|
|
1407
|
+
const [pan, setPan] = react.useState({ x: 0, y: 0 });
|
|
1408
|
+
const [isDragging, setIsDragging] = react.useState(false);
|
|
1409
|
+
const dragStartRef = react.useRef({ x: 0, y: 0 });
|
|
1410
|
+
const panStartRef = react.useRef({ x: 0, y: 0 });
|
|
1411
|
+
const containerRef = react.useRef(null);
|
|
1412
|
+
const zoomRef = react.useRef(1);
|
|
1413
|
+
const panRef = react.useRef({ x: 0, y: 0 });
|
|
1414
|
+
const animationRef = react.useRef(null);
|
|
1415
|
+
const clampZoom = react.useCallback(
|
|
1416
|
+
(value) => Math.max(minZoom, Math.min(maxZoom, value)),
|
|
1417
|
+
[minZoom, maxZoom]
|
|
1418
|
+
);
|
|
1419
|
+
const stopAnimation = react.useCallback(() => {
|
|
1420
|
+
if (animationRef.current !== null) {
|
|
1421
|
+
cancelAnimationFrame(animationRef.current);
|
|
1422
|
+
animationRef.current = null;
|
|
1423
|
+
}
|
|
1424
|
+
}, []);
|
|
1425
|
+
const animateZoom = react.useCallback(
|
|
1426
|
+
(targetZoom, options2) => {
|
|
1427
|
+
const { targetPan, durationMs = ZOOM_ANIMATION_MS } = options2 ?? {};
|
|
1428
|
+
stopAnimation();
|
|
1429
|
+
const startZoom = zoomRef.current;
|
|
1430
|
+
const clampedTargetZoom = clampZoom(targetZoom);
|
|
1431
|
+
const startPan = panRef.current;
|
|
1432
|
+
const hasPanTarget = targetPan !== void 0;
|
|
1433
|
+
const finalPan = targetPan ?? startPan;
|
|
1434
|
+
if (durationMs <= 0 || clampedTargetZoom === startZoom && (!hasPanTarget || finalPan.x === startPan.x && finalPan.y === startPan.y)) {
|
|
1435
|
+
setZoom(clampedTargetZoom);
|
|
1436
|
+
zoomRef.current = clampedTargetZoom;
|
|
1437
|
+
if (hasPanTarget) {
|
|
1438
|
+
setPan(finalPan);
|
|
1439
|
+
panRef.current = finalPan;
|
|
1440
|
+
}
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
const startTime = performance.now();
|
|
1444
|
+
const step = (now) => {
|
|
1445
|
+
const elapsed = now - startTime;
|
|
1446
|
+
const t = Math.min(elapsed / durationMs, 1);
|
|
1447
|
+
const eased = easeOutCubic(t);
|
|
1448
|
+
const nextZoom = startZoom + (clampedTargetZoom - startZoom) * eased;
|
|
1449
|
+
zoomRef.current = nextZoom;
|
|
1450
|
+
setZoom(nextZoom);
|
|
1451
|
+
if (hasPanTarget) {
|
|
1452
|
+
const nextPan = {
|
|
1453
|
+
x: startPan.x + (finalPan.x - startPan.x) * eased,
|
|
1454
|
+
y: startPan.y + (finalPan.y - startPan.y) * eased
|
|
1455
|
+
};
|
|
1456
|
+
panRef.current = nextPan;
|
|
1457
|
+
setPan(nextPan);
|
|
1458
|
+
}
|
|
1459
|
+
if (t < 1) {
|
|
1460
|
+
animationRef.current = requestAnimationFrame(step);
|
|
1461
|
+
} else {
|
|
1462
|
+
animationRef.current = null;
|
|
1463
|
+
setZoom(clampedTargetZoom);
|
|
1464
|
+
zoomRef.current = clampedTargetZoom;
|
|
1465
|
+
if (hasPanTarget) {
|
|
1466
|
+
setPan(finalPan);
|
|
1467
|
+
panRef.current = finalPan;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
animationRef.current = requestAnimationFrame(step);
|
|
1472
|
+
},
|
|
1473
|
+
[clampZoom, stopAnimation]
|
|
1474
|
+
);
|
|
1475
|
+
const zoomIn = react.useCallback(() => {
|
|
1476
|
+
const nextZoom = clampZoom(zoomRef.current + zoomStep);
|
|
1477
|
+
animateZoom(nextZoom);
|
|
1478
|
+
}, [animateZoom, clampZoom, zoomStep]);
|
|
1479
|
+
const zoomOut = react.useCallback(() => {
|
|
1480
|
+
const nextZoom = clampZoom(zoomRef.current - zoomStep);
|
|
1481
|
+
animateZoom(nextZoom);
|
|
1482
|
+
}, [animateZoom, clampZoom, zoomStep]);
|
|
1483
|
+
const resetZoom = react.useCallback(() => {
|
|
1484
|
+
animateZoom(1, { targetPan: { x: 0, y: 0 } });
|
|
1485
|
+
}, [animateZoom]);
|
|
1486
|
+
const getViewBoxSize = react.useCallback(
|
|
1487
|
+
(container) => {
|
|
1488
|
+
if (viewBox?.width && viewBox?.height) {
|
|
1489
|
+
return { width: viewBox.width, height: viewBox.height };
|
|
1490
|
+
}
|
|
1491
|
+
const rect = container.getBoundingClientRect();
|
|
1492
|
+
return { width: rect.width || 1, height: rect.height || 1 };
|
|
1493
|
+
},
|
|
1494
|
+
[viewBox?.width, viewBox?.height]
|
|
1495
|
+
);
|
|
1496
|
+
const getViewBoxMetrics = react.useCallback(
|
|
1497
|
+
(container) => {
|
|
1498
|
+
const rect = container.getBoundingClientRect();
|
|
1499
|
+
const { width: viewBoxWidth, height: viewBoxHeight } = getViewBoxSize(container);
|
|
1500
|
+
const safeWidth = viewBoxWidth || 1;
|
|
1501
|
+
const safeHeight = viewBoxHeight || 1;
|
|
1502
|
+
const scale = rect.width > 0 && rect.height > 0 ? Math.min(rect.width / safeWidth, rect.height / safeHeight) : 0;
|
|
1503
|
+
const contentWidth = safeWidth * scale;
|
|
1504
|
+
const contentHeight = safeHeight * scale;
|
|
1505
|
+
const offsetX = (rect.width - contentWidth) / 2;
|
|
1506
|
+
const offsetY = (rect.height - contentHeight) / 2;
|
|
1507
|
+
return { rect, viewBoxWidth: safeWidth, viewBoxHeight: safeHeight, scale, offsetX, offsetY };
|
|
1508
|
+
},
|
|
1509
|
+
[getViewBoxSize]
|
|
1510
|
+
);
|
|
1511
|
+
const getPointerPosition = react.useCallback(
|
|
1512
|
+
(container, clientX, clientY) => {
|
|
1513
|
+
const ctm = container.getScreenCTM();
|
|
1514
|
+
if (ctm) {
|
|
1515
|
+
if (typeof DOMPoint !== "undefined") {
|
|
1516
|
+
const point = new DOMPoint(clientX, clientY);
|
|
1517
|
+
const { x, y } = point.matrixTransform(ctm.inverse());
|
|
1518
|
+
return { x, y };
|
|
1519
|
+
}
|
|
1520
|
+
if ("createSVGPoint" in container) {
|
|
1521
|
+
const point = container.createSVGPoint();
|
|
1522
|
+
point.x = clientX;
|
|
1523
|
+
point.y = clientY;
|
|
1524
|
+
const { x, y } = point.matrixTransform(ctm.inverse());
|
|
1525
|
+
return { x, y };
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
const { rect, scale, offsetX, offsetY } = getViewBoxMetrics(container);
|
|
1529
|
+
if (scale <= 0) return null;
|
|
1530
|
+
return {
|
|
1531
|
+
x: (clientX - rect.left - offsetX) / scale,
|
|
1532
|
+
y: (clientY - rect.top - offsetY) / scale
|
|
1533
|
+
};
|
|
1534
|
+
},
|
|
1535
|
+
[getViewBoxMetrics]
|
|
1536
|
+
);
|
|
1537
|
+
const clampPan = react.useCallback(
|
|
1538
|
+
(newPan, currentZoom, viewBoxWidth, viewBoxHeight) => {
|
|
1539
|
+
if (currentZoom <= 1) {
|
|
1540
|
+
return { x: 0, y: 0 };
|
|
1541
|
+
}
|
|
1542
|
+
const maxPanX = viewBoxWidth * (currentZoom - 1) / 2;
|
|
1543
|
+
const maxPanY = viewBoxHeight * (currentZoom - 1) / 2;
|
|
1544
|
+
return {
|
|
1545
|
+
x: Math.max(-maxPanX, Math.min(maxPanX, newPan.x)),
|
|
1546
|
+
y: Math.max(-maxPanY, Math.min(maxPanY, newPan.y))
|
|
1547
|
+
};
|
|
1548
|
+
},
|
|
1549
|
+
[]
|
|
1550
|
+
);
|
|
1551
|
+
react.useEffect(() => {
|
|
1552
|
+
zoomRef.current = zoom;
|
|
1553
|
+
}, [zoom]);
|
|
1554
|
+
react.useEffect(() => {
|
|
1555
|
+
panRef.current = pan;
|
|
1556
|
+
}, [pan]);
|
|
1557
|
+
react.useEffect(() => {
|
|
1558
|
+
return () => stopAnimation();
|
|
1559
|
+
}, [stopAnimation]);
|
|
1560
|
+
react.useEffect(() => {
|
|
1561
|
+
const container = containerRef.current;
|
|
1562
|
+
if (!container || !wheelZoom) return;
|
|
1563
|
+
const wheelZoomStep = 0.1;
|
|
1564
|
+
const handleWheel = (e) => {
|
|
1565
|
+
e.preventDefault();
|
|
1566
|
+
stopAnimation();
|
|
1567
|
+
const { viewBoxWidth, viewBoxHeight } = getViewBoxMetrics(container);
|
|
1568
|
+
const pointer = getPointerPosition(container, e.clientX, e.clientY);
|
|
1569
|
+
if (!pointer) return;
|
|
1570
|
+
const { x: mouseX, y: mouseY } = pointer;
|
|
1571
|
+
const centerX = viewBoxWidth / 2;
|
|
1572
|
+
const centerY = viewBoxHeight / 2;
|
|
1573
|
+
const delta = e.deltaY > 0 ? -wheelZoomStep : wheelZoomStep;
|
|
1574
|
+
const currentZoom = zoomRef.current;
|
|
1575
|
+
const nextZoom = clampZoom(currentZoom + delta);
|
|
1576
|
+
if (nextZoom === currentZoom) return;
|
|
1577
|
+
const zoomRatio = nextZoom / currentZoom;
|
|
1578
|
+
const nextPan = {
|
|
1579
|
+
x: (mouseX - centerX) * (1 - zoomRatio) + panRef.current.x * zoomRatio,
|
|
1580
|
+
y: (mouseY - centerY) * (1 - zoomRatio) + panRef.current.y * zoomRatio
|
|
1581
|
+
};
|
|
1582
|
+
const clampedPan = clampPan(nextPan, nextZoom, viewBoxWidth, viewBoxHeight);
|
|
1583
|
+
zoomRef.current = nextZoom;
|
|
1584
|
+
panRef.current = clampedPan;
|
|
1585
|
+
setZoom(nextZoom);
|
|
1586
|
+
setPan(clampedPan);
|
|
1587
|
+
};
|
|
1588
|
+
container.addEventListener("wheel", handleWheel, { passive: false });
|
|
1589
|
+
return () => {
|
|
1590
|
+
container.removeEventListener("wheel", handleWheel);
|
|
1591
|
+
};
|
|
1592
|
+
}, [clampZoom, wheelZoom, getViewBoxMetrics, getPointerPosition, clampPan, stopAnimation]);
|
|
1593
|
+
react.useEffect(() => {
|
|
1594
|
+
const container = containerRef.current;
|
|
1595
|
+
if (!container) return;
|
|
1596
|
+
const { viewBoxWidth, viewBoxHeight } = getViewBoxMetrics(container);
|
|
1597
|
+
setPan((currentPan) => clampPan(currentPan, zoom, viewBoxWidth, viewBoxHeight));
|
|
1598
|
+
}, [zoom, clampPan, getViewBoxMetrics]);
|
|
1599
|
+
const handleMouseDown = react.useCallback(
|
|
1600
|
+
(e) => {
|
|
1601
|
+
if (e.button !== 0 || zoom <= 1) return;
|
|
1602
|
+
stopAnimation();
|
|
1603
|
+
setIsDragging(true);
|
|
1604
|
+
dragStartRef.current = { x: e.clientX, y: e.clientY };
|
|
1605
|
+
panStartRef.current = { ...pan };
|
|
1606
|
+
e.preventDefault();
|
|
1607
|
+
},
|
|
1608
|
+
[pan, zoom, stopAnimation]
|
|
1609
|
+
);
|
|
1610
|
+
const handleMouseMove = react.useCallback(
|
|
1611
|
+
(e) => {
|
|
1612
|
+
if (!isDragging || zoom <= 1) return;
|
|
1613
|
+
const container = containerRef.current;
|
|
1614
|
+
if (!container) return;
|
|
1615
|
+
const { scale, viewBoxWidth, viewBoxHeight } = getViewBoxMetrics(container);
|
|
1616
|
+
if (scale <= 0) return;
|
|
1617
|
+
const dx = e.clientX - dragStartRef.current.x;
|
|
1618
|
+
const dy = e.clientY - dragStartRef.current.y;
|
|
1619
|
+
const newPan = {
|
|
1620
|
+
x: panStartRef.current.x + dx / scale,
|
|
1621
|
+
y: panStartRef.current.y + dy / scale
|
|
1622
|
+
};
|
|
1623
|
+
setPan(clampPan(newPan, zoom, viewBoxWidth, viewBoxHeight));
|
|
1624
|
+
},
|
|
1625
|
+
[isDragging, zoom, clampPan, getViewBoxMetrics]
|
|
1626
|
+
);
|
|
1627
|
+
const handleMouseUp = react.useCallback(() => {
|
|
1628
|
+
setIsDragging(false);
|
|
1629
|
+
}, []);
|
|
1630
|
+
const isDefaultZoom = zoom === 1 && pan.x === 0 && pan.y === 0;
|
|
1631
|
+
return {
|
|
1632
|
+
zoom,
|
|
1633
|
+
pan,
|
|
1634
|
+
zoomIn,
|
|
1635
|
+
zoomOut,
|
|
1636
|
+
resetZoom,
|
|
1637
|
+
handleMouseDown,
|
|
1638
|
+
handleMouseMove,
|
|
1639
|
+
handleMouseUp,
|
|
1640
|
+
isDragging,
|
|
1641
|
+
isDefaultZoom,
|
|
1642
|
+
containerRef
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
var buttonBaseStyle = {
|
|
1646
|
+
width: 32,
|
|
1647
|
+
height: 32,
|
|
1648
|
+
display: "flex",
|
|
1649
|
+
alignItems: "center",
|
|
1650
|
+
justifyContent: "center",
|
|
1651
|
+
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
|
1652
|
+
border: "1px solid #d1d5db",
|
|
1653
|
+
borderRadius: 6,
|
|
1654
|
+
cursor: "pointer",
|
|
1655
|
+
fontSize: 18,
|
|
1656
|
+
fontWeight: "bold",
|
|
1657
|
+
color: "#374151",
|
|
1658
|
+
transition: "background-color 0.15s ease",
|
|
1659
|
+
userSelect: "none"
|
|
1660
|
+
};
|
|
1661
|
+
var buttonDisabledStyle = {
|
|
1662
|
+
...buttonBaseStyle,
|
|
1663
|
+
opacity: 0.4,
|
|
1664
|
+
cursor: "not-allowed"
|
|
1665
|
+
};
|
|
1666
|
+
var getContainerStyle = (isVisible) => ({
|
|
1667
|
+
position: "absolute",
|
|
1668
|
+
bottom: 12,
|
|
1669
|
+
right: 12,
|
|
1670
|
+
display: "flex",
|
|
1671
|
+
flexDirection: "column",
|
|
1672
|
+
gap: 4,
|
|
1673
|
+
zIndex: 10,
|
|
1674
|
+
opacity: isVisible ? 1 : 0,
|
|
1675
|
+
transition: "opacity 0.2s ease-in-out",
|
|
1676
|
+
pointerEvents: isVisible ? "auto" : "none"
|
|
1677
|
+
});
|
|
1678
|
+
var getResetContainerStyle = (isVisible) => ({
|
|
1679
|
+
position: "absolute",
|
|
1680
|
+
bottom: 12,
|
|
1681
|
+
left: 12,
|
|
1682
|
+
zIndex: 10,
|
|
1683
|
+
opacity: isVisible ? 1 : 0,
|
|
1684
|
+
transition: "opacity 0.2s ease-in-out",
|
|
1685
|
+
pointerEvents: isVisible ? "auto" : "none"
|
|
1686
|
+
});
|
|
1687
|
+
var resetButtonStyle = {
|
|
1688
|
+
...buttonBaseStyle,
|
|
1689
|
+
width: "auto",
|
|
1690
|
+
padding: "6px 12px",
|
|
1691
|
+
fontSize: 12,
|
|
1692
|
+
fontWeight: 500
|
|
1693
|
+
};
|
|
1694
|
+
function ZoomControls({
|
|
1695
|
+
onZoomIn,
|
|
1696
|
+
onZoomOut,
|
|
1697
|
+
onReset,
|
|
1698
|
+
showResetButton,
|
|
1699
|
+
zoom,
|
|
1700
|
+
minZoom,
|
|
1701
|
+
maxZoom,
|
|
1702
|
+
isVisible
|
|
1703
|
+
}) {
|
|
1704
|
+
const isMinZoom = zoom <= minZoom;
|
|
1705
|
+
const isMaxZoom = zoom >= maxZoom;
|
|
1706
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1707
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: getContainerStyle(isVisible), children: [
|
|
1708
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1709
|
+
"button",
|
|
1710
|
+
{
|
|
1711
|
+
type: "button",
|
|
1712
|
+
onClick: onZoomIn,
|
|
1713
|
+
disabled: isMaxZoom,
|
|
1714
|
+
style: isMaxZoom ? buttonDisabledStyle : buttonBaseStyle,
|
|
1715
|
+
"aria-label": "Zoom in",
|
|
1716
|
+
children: "+"
|
|
1717
|
+
}
|
|
1718
|
+
),
|
|
1719
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1720
|
+
"button",
|
|
1721
|
+
{
|
|
1722
|
+
type: "button",
|
|
1723
|
+
onClick: onZoomOut,
|
|
1724
|
+
disabled: isMinZoom,
|
|
1725
|
+
style: isMinZoom ? buttonDisabledStyle : buttonBaseStyle,
|
|
1726
|
+
"aria-label": "Zoom out",
|
|
1727
|
+
children: "\u2212"
|
|
1728
|
+
}
|
|
1729
|
+
)
|
|
1730
|
+
] }),
|
|
1731
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: getResetContainerStyle(isVisible && showResetButton), children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onReset, style: resetButtonStyle, "aria-label": "Reset zoom", children: "\u21BA" }) })
|
|
1732
|
+
] });
|
|
1733
|
+
}
|
|
1306
1734
|
var ORGAN_POSITIONS = {
|
|
1307
1735
|
growth: { x: 104, y: 1, width: 12, height: 349, viewBox: "0 0 12 349" },
|
|
1308
1736
|
constitutional: { x: 0, y: 0, width: 122, height: 358, viewBox: "0 0 122 358" },
|
|
@@ -1353,6 +1781,7 @@ var FOREGROUND_ORGANS = [
|
|
|
1353
1781
|
"prenatal",
|
|
1354
1782
|
"blood"
|
|
1355
1783
|
];
|
|
1784
|
+
var MIN_ZOOM = 1;
|
|
1356
1785
|
function HpoVisualizer({
|
|
1357
1786
|
organs,
|
|
1358
1787
|
visibleOrgans,
|
|
@@ -1361,12 +1790,28 @@ function HpoVisualizer({
|
|
|
1361
1790
|
onHover,
|
|
1362
1791
|
onSelect,
|
|
1363
1792
|
colorPalette: inputColorPalette,
|
|
1364
|
-
width =
|
|
1365
|
-
height =
|
|
1793
|
+
width = "100%",
|
|
1794
|
+
height = "100%",
|
|
1366
1795
|
className,
|
|
1367
|
-
style
|
|
1796
|
+
style,
|
|
1797
|
+
maxZoom = 5,
|
|
1798
|
+
wheelZoom = true
|
|
1368
1799
|
}) {
|
|
1369
1800
|
const visualizerID = react.useId();
|
|
1801
|
+
const [isHovering, setIsHovering] = react.useState(false);
|
|
1802
|
+
const {
|
|
1803
|
+
zoom,
|
|
1804
|
+
pan,
|
|
1805
|
+
zoomIn,
|
|
1806
|
+
zoomOut,
|
|
1807
|
+
resetZoom,
|
|
1808
|
+
handleMouseDown,
|
|
1809
|
+
handleMouseMove,
|
|
1810
|
+
handleMouseUp,
|
|
1811
|
+
isDragging,
|
|
1812
|
+
isDefaultZoom,
|
|
1813
|
+
containerRef
|
|
1814
|
+
} = useZoom({ minZoom: MIN_ZOOM, maxZoom, wheelZoom, viewBox: BODY_VIEWBOX });
|
|
1370
1815
|
const colorPalette = react.useMemo(
|
|
1371
1816
|
() => createStrictColorPalette(inputColorPalette),
|
|
1372
1817
|
[inputColorPalette]
|
|
@@ -1389,12 +1834,48 @@ function HpoVisualizer({
|
|
|
1389
1834
|
}
|
|
1390
1835
|
return map;
|
|
1391
1836
|
}, [organs]);
|
|
1392
|
-
const { handlers, isHovered, isSelected } = useOrganInteraction({
|
|
1837
|
+
const { handlers, isHovered, isSelected, state } = useOrganInteraction({
|
|
1393
1838
|
hoveredOrgan: controlledHovered,
|
|
1394
1839
|
selectedOrgan: controlledSelected,
|
|
1395
1840
|
onHover,
|
|
1396
1841
|
onSelect
|
|
1397
1842
|
});
|
|
1843
|
+
const selectedOrganId = state.selectedOrgan;
|
|
1844
|
+
const renderEntries = react.useMemo(() => {
|
|
1845
|
+
const base = [
|
|
1846
|
+
...BACKGROUND_ORGANS.map((organId) => ({ type: "organ", organId })),
|
|
1847
|
+
{ type: "body" },
|
|
1848
|
+
...FOREGROUND_ORGANS.map((organId) => ({ type: "organ", organId }))
|
|
1849
|
+
];
|
|
1850
|
+
if (!selectedOrganId) {
|
|
1851
|
+
return base;
|
|
1852
|
+
}
|
|
1853
|
+
const selectedIndex = base.findIndex(
|
|
1854
|
+
(entry) => entry.type === "organ" && entry.organId === selectedOrganId
|
|
1855
|
+
);
|
|
1856
|
+
if (selectedIndex === -1) {
|
|
1857
|
+
return base;
|
|
1858
|
+
}
|
|
1859
|
+
const selectedEntry = base[selectedIndex];
|
|
1860
|
+
if (!selectedEntry || selectedEntry.type !== "organ") {
|
|
1861
|
+
return base;
|
|
1862
|
+
}
|
|
1863
|
+
return [...base.slice(0, selectedIndex), ...base.slice(selectedIndex + 1), selectedEntry];
|
|
1864
|
+
}, [selectedOrganId]);
|
|
1865
|
+
const {
|
|
1866
|
+
padding,
|
|
1867
|
+
paddingTop,
|
|
1868
|
+
paddingRight,
|
|
1869
|
+
paddingBottom,
|
|
1870
|
+
paddingLeft,
|
|
1871
|
+
paddingInline,
|
|
1872
|
+
paddingInlineStart,
|
|
1873
|
+
paddingInlineEnd,
|
|
1874
|
+
paddingBlock,
|
|
1875
|
+
paddingBlockStart,
|
|
1876
|
+
paddingBlockEnd,
|
|
1877
|
+
...containerStyleOverrides
|
|
1878
|
+
} = style ?? {};
|
|
1398
1879
|
const containerStyle = {
|
|
1399
1880
|
display: "flex",
|
|
1400
1881
|
justifyContent: "center",
|
|
@@ -1403,21 +1884,50 @@ function HpoVisualizer({
|
|
|
1403
1884
|
position: "relative",
|
|
1404
1885
|
width,
|
|
1405
1886
|
height,
|
|
1406
|
-
...
|
|
1887
|
+
...containerStyleOverrides,
|
|
1888
|
+
// Apply overflow after style spread to ensure it takes precedence
|
|
1889
|
+
overflow: "clip"
|
|
1890
|
+
};
|
|
1891
|
+
const contentStyle = {
|
|
1892
|
+
display: "flex",
|
|
1893
|
+
justifyContent: "center",
|
|
1894
|
+
alignItems: "flex-end",
|
|
1895
|
+
width: "100%",
|
|
1896
|
+
height: "100%",
|
|
1897
|
+
boxSizing: "border-box",
|
|
1898
|
+
padding,
|
|
1899
|
+
paddingTop,
|
|
1900
|
+
paddingRight,
|
|
1901
|
+
paddingBottom,
|
|
1902
|
+
paddingLeft,
|
|
1903
|
+
paddingInline,
|
|
1904
|
+
paddingInlineStart,
|
|
1905
|
+
paddingInlineEnd,
|
|
1906
|
+
paddingBlock,
|
|
1907
|
+
paddingBlockStart,
|
|
1908
|
+
paddingBlockEnd
|
|
1909
|
+
};
|
|
1910
|
+
const svgStyle = {
|
|
1911
|
+
width: "100%",
|
|
1912
|
+
height: "100%",
|
|
1913
|
+
display: "block",
|
|
1914
|
+
cursor: isDragging ? "grabbing" : zoom > 1 ? "grab" : "default",
|
|
1915
|
+
overflow: "visible"
|
|
1407
1916
|
};
|
|
1408
1917
|
const viewBox = `0 0 ${BODY_VIEWBOX.width} ${BODY_VIEWBOX.height}`;
|
|
1409
|
-
const
|
|
1410
|
-
const
|
|
1918
|
+
const centerX = BODY_VIEWBOX.width / 2;
|
|
1919
|
+
const centerY = BODY_VIEWBOX.height / 2;
|
|
1920
|
+
const zoomTransform = `translate(${centerX} ${centerY}) scale(${zoom}) translate(${-centerX} ${-centerY})`;
|
|
1411
1921
|
const renderOrgan = (organId, isVisible) => {
|
|
1412
1922
|
const position = ORGAN_POSITIONS[organId];
|
|
1413
1923
|
const config = organConfigMap.get(organId);
|
|
1414
1924
|
if (config?.style?.visible === false) {
|
|
1415
1925
|
return null;
|
|
1416
1926
|
}
|
|
1417
|
-
const x =
|
|
1418
|
-
const y = position.y
|
|
1419
|
-
const width2 = position.width
|
|
1420
|
-
const height2 = position.height
|
|
1927
|
+
const x = position.x;
|
|
1928
|
+
const y = position.y;
|
|
1929
|
+
const width2 = position.width;
|
|
1930
|
+
const height2 = position.height;
|
|
1421
1931
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1422
1932
|
OrganSvg,
|
|
1423
1933
|
{
|
|
@@ -1440,44 +1950,83 @@ function HpoVisualizer({
|
|
|
1440
1950
|
`${visualizerID}-${organId}`
|
|
1441
1951
|
);
|
|
1442
1952
|
};
|
|
1443
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
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
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1953
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1954
|
+
"div",
|
|
1955
|
+
{
|
|
1956
|
+
className,
|
|
1957
|
+
style: containerStyle,
|
|
1958
|
+
onMouseEnter: () => setIsHovering(true),
|
|
1959
|
+
onMouseLeave: () => {
|
|
1960
|
+
setIsHovering(false);
|
|
1961
|
+
handleMouseUp();
|
|
1962
|
+
},
|
|
1963
|
+
onMouseDown: handleMouseDown,
|
|
1964
|
+
onMouseMove: handleMouseMove,
|
|
1965
|
+
onMouseUp: handleMouseUp,
|
|
1966
|
+
role: "application",
|
|
1967
|
+
tabIndex: 0,
|
|
1968
|
+
children: [
|
|
1969
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: contentStyle, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1970
|
+
"svg",
|
|
1971
|
+
{
|
|
1972
|
+
ref: containerRef,
|
|
1973
|
+
width: "100%",
|
|
1974
|
+
height: "100%",
|
|
1975
|
+
viewBox,
|
|
1976
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
1977
|
+
style: svgStyle,
|
|
1978
|
+
"aria-label": "Human organ visualizer",
|
|
1979
|
+
children: [
|
|
1980
|
+
/* @__PURE__ */ jsxRuntime.jsx("title", { children: "Human organ visualizer" }),
|
|
1981
|
+
/* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${pan.x} ${pan.y})`, children: /* @__PURE__ */ jsxRuntime.jsx("g", { transform: zoomTransform, children: renderEntries.map((entry) => {
|
|
1982
|
+
if (entry.type === "body") {
|
|
1983
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1984
|
+
Body,
|
|
1985
|
+
{
|
|
1986
|
+
colorScale: colorPalette[DEFAULT_COLOR_NAME],
|
|
1987
|
+
style: {
|
|
1988
|
+
fill: "#fff"
|
|
1989
|
+
}
|
|
1990
|
+
},
|
|
1991
|
+
`${visualizerID}-body`
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
return renderOrgan(entry.organId, visibleOrganIds.includes(entry.organId));
|
|
1995
|
+
}) }) })
|
|
1996
|
+
]
|
|
1997
|
+
}
|
|
1998
|
+
) }),
|
|
1999
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2000
|
+
ZoomControls,
|
|
2001
|
+
{
|
|
2002
|
+
onZoomIn: zoomIn,
|
|
2003
|
+
onZoomOut: zoomOut,
|
|
2004
|
+
onReset: resetZoom,
|
|
2005
|
+
showResetButton: !isDefaultZoom,
|
|
2006
|
+
zoom,
|
|
2007
|
+
minZoom: MIN_ZOOM,
|
|
2008
|
+
maxZoom,
|
|
2009
|
+
isVisible: isHovering
|
|
2010
|
+
}
|
|
2011
|
+
)
|
|
2012
|
+
]
|
|
2013
|
+
}
|
|
2014
|
+
);
|
|
1471
2015
|
}
|
|
1472
2016
|
|
|
1473
2017
|
exports.ANIMATION_DURATION_MS = ANIMATION_DURATION_MS;
|
|
1474
2018
|
exports.DEFAULT_COLOR_PALETTE = DEFAULT_COLOR_PALETTE;
|
|
2019
|
+
exports.HPO_LABELS = HPO_LABELS;
|
|
2020
|
+
exports.HPO_LABEL_TO_ORGAN = HPO_LABEL_TO_ORGAN;
|
|
1475
2021
|
exports.HpoVisualizer = HpoVisualizer;
|
|
1476
2022
|
exports.ORGAN_COMPONENTS = ORGAN_COMPONENTS;
|
|
1477
2023
|
exports.ORGAN_IDS = ORGAN_IDS;
|
|
1478
2024
|
exports.ORGAN_NAMES_EN = ORGAN_NAMES_EN;
|
|
1479
2025
|
exports.ORGAN_NAMES_KO = ORGAN_NAMES_KO;
|
|
2026
|
+
exports.ORGAN_TO_HPO_LABEL = ORGAN_TO_HPO_LABEL;
|
|
1480
2027
|
exports.OrganSvg = OrganSvg;
|
|
2028
|
+
exports.createOrganOutlineSet = createOrganOutlineSet;
|
|
2029
|
+
exports.createUniformOrganColorSchemes = createUniformOrganColorSchemes;
|
|
1481
2030
|
exports.useOrganInteraction = useOrganInteraction;
|
|
1482
2031
|
//# sourceMappingURL=index.cjs.map
|
|
1483
2032
|
//# sourceMappingURL=index.cjs.map
|