Skip to content

Commit 6dac1c1

Browse files
committed
update BZvis to now track camera position
1 parent ebe507a commit 6dac1c1

9 files changed

Lines changed: 5099 additions & 47 deletions

File tree

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
],
3333
"dependencies": {
3434
"bands-visualiser": "^0.0.2",
35-
"brillouinzone-visualizer": "^0.4.2",
35+
"brillouinzone-visualizer": "^0.4.3",
3636
"mc-react-structure-visualizer": "^0.8.3",
3737
"plotly.js-basic-dist-min": "^3.1.2",
3838
"react-json-view-lite": "^2.5.0",
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { useState, useRef, useEffect } from "react";
2+
3+
const labelStyle = "ae:flex ae:items-center ae:gap-1 ae:text-xs";
4+
5+
// Control div rendered above teh BZVis component.
6+
export default function BZControls({
7+
showPathpoints,
8+
setShowPathpoints,
9+
showBVectors,
10+
setShowBVectors,
11+
showAxes,
12+
setShowAxes,
13+
}) {
14+
const [expanded, setExpanded] = useState(false);
15+
const panelRef = useRef(null);
16+
17+
// Close when clicking outside
18+
useEffect(() => {
19+
function handleClickOutside(event) {
20+
if (panelRef.current && !panelRef.current.contains(event.target)) {
21+
setExpanded(false);
22+
}
23+
}
24+
25+
document.addEventListener("click", handleClickOutside);
26+
return () => document.removeEventListener("click", handleClickOutside);
27+
}, []);
28+
29+
// Close when escape is pressed
30+
useEffect(() => {
31+
function handleKey(e) {
32+
if (e.key === "Escape") setExpanded(false);
33+
}
34+
document.addEventListener("click", handleKey);
35+
return () => document.removeEventListener("click", handleKey);
36+
}, []);
37+
38+
return (
39+
<div
40+
ref={panelRef}
41+
className={`ae:absolute ae:top-1 ae:right-1 ae:z-10 ae:overflow-hidden ae:rounded-md ae:bg-slate-50 ae:transition-all ae:duration-300 ${
42+
expanded ? "ae:w-22" : "ae:w-6"
43+
}`}
44+
>
45+
<button
46+
onClick={() => setExpanded((e) => !e)}
47+
className="ae:text-xl ae:w-full ae:flex ae:justify-end ae:rounded-md ae:hover:cursor-pointer ae:hover:bg-slate-100"
48+
aria-expanded={expanded}
49+
>
50+
⚙️
51+
</button>
52+
53+
{expanded && (
54+
<div className="ae:flex ae:flex-col ae:gap-1 ae:pb-2 ae:pl-2">
55+
<label className={`${labelStyle}`}>
56+
<input
57+
type="checkbox"
58+
checked={showPathpoints}
59+
onChange={(e) => setShowPathpoints(e.target.checked)}
60+
/>
61+
Kpoints
62+
</label>
63+
<label className={`${labelStyle}`}>
64+
<input
65+
type="checkbox"
66+
checked={showAxes}
67+
onChange={(e) => setShowAxes(e.target.checked)}
68+
/>
69+
Axes
70+
</label>
71+
<label className={`${labelStyle}`}>
72+
<input
73+
type="checkbox"
74+
checked={showBVectors}
75+
onChange={(e) => setShowBVectors(e.target.checked)}
76+
/>
77+
Bvectors
78+
</label>
79+
</div>
80+
)}
81+
</div>
82+
);
83+
}

src/AiidaExplorer/VisualiserPane/Rich/KpointsDataVisualiser/BZVis.jsx

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,63 @@ import React, { useState, useEffect, useRef } from "react";
22
import { createBZVisualizer } from "brillouinzone-visualizer";
33
import useContainerBucket from "../../../hooks/useContainerBucket";
44

5+
import BZControls from "./BZControls";
6+
57
export default function BZVisualizer({ data }) {
68
const containerRef = useRef(null);
9+
const bzRef = useRef(null);
10+
const cameraStateRef = useRef(null);
711

8-
// ----- Local state for the checkbox -----
912
const [showPathpoints, setShowPathpoints] = useState(false);
13+
const [showBVectors, setShowBVectors] = useState(true);
14+
const [showAxes, setShowAxes] = useState(true);
1015

11-
// ----- Options object -----
12-
const options = {
13-
showAxes: true,
14-
showBVectors: true,
15-
showPathpoints,
16-
disableInteractOverlay: true,
17-
};
18-
19-
// Reset the visualizer whenever the container width changes significantly
20-
const widthBucket = useContainerBucket(containerRef, 100);
16+
const widthBucket = useContainerBucket(containerRef, 50);
2117

2218
useEffect(() => {
2319
if (!containerRef.current) return;
2420

25-
containerRef.current.innerHTML = "";
26-
createBZVisualizer(containerRef.current, data, options);
27-
}, [data, showPathpoints, widthBucket]); // re-run if data, checkbox, or bucket changes
21+
const timer = setTimeout(() => {
22+
if (bzRef.current?.getCameraState) {
23+
cameraStateRef.current = bzRef.current.getCameraState();
24+
}
25+
26+
// clear the container.
27+
containerRef.current.innerHTML = "";
28+
29+
bzRef.current = createBZVisualizer(
30+
containerRef.current,
31+
data,
32+
{
33+
showAxes,
34+
showBVectors,
35+
showPathpoints,
36+
disableInteractOverlay: true,
37+
},
38+
(bzInstance) => {
39+
// use the saved camera state to update the camera.
40+
if (cameraStateRef.current) {
41+
bzInstance.camera.position.copy(cameraStateRef.current.position);
42+
bzInstance.controls.target.copy(cameraStateRef.current.target);
43+
bzInstance.controls.update();
44+
}
45+
}
46+
);
47+
}, 100); //100ms debounce stops fastdrags from resetting view.
48+
49+
return () => clearTimeout(timer);
50+
}, [data, showAxes, showBVectors, showPathpoints, widthBucket]);
2851

2952
return (
30-
<div className="relative w-full" style={{ height: "400px" }}>
31-
{/* Overlayed checkbox */}
32-
<label
33-
style={{
34-
position: "absolute",
35-
top: 8,
36-
right: 8,
37-
zIndex: 10,
38-
background: "rgba(255,255,255,0.8)",
39-
padding: "4px 8px",
40-
borderRadius: 4,
41-
fontSize: 12,
42-
}}
43-
>
44-
<input
45-
type="checkbox"
46-
checked={showPathpoints}
47-
onChange={(e) => setShowPathpoints(e.target.checked)}
48-
style={{ marginRight: 4 }}
49-
/>
50-
Overlay Kpoints
51-
</label>
52-
53-
{/* Visualizer container */}
53+
<div className="ae:relative ae:w-full" style={{ height: "400px" }}>
54+
<BZControls
55+
showPathpoints={showPathpoints}
56+
setShowPathpoints={setShowPathpoints}
57+
showBVectors={showBVectors}
58+
setShowBVectors={setShowBVectors}
59+
showAxes={showAxes}
60+
setShowAxes={setShowAxes}
61+
/>
5462
<div ref={containerRef} style={{ width: "100%", height: "100%" }} />
5563
</div>
5664
);

src/AiidaExplorer/VisualiserPane/Rich/KpointsDataVisualiser/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export default function KpointsDataVisualiser({ nodeData = {} }) {
163163
{attributes?.cell && bzData && (
164164
<CardContainer
165165
header={`Brillouin Zone${path ? ` (${path})` : ""}`}
166-
className="ae:!px-1.5 ae:!py-2"
166+
className="ae:px-1.5! ae:py-2!"
167167
childrenClassName="ae:!p-0"
168168
>
169169
<div className="ae:w-full">

src/AiidaExplorer/VisualiserPane/Rich/KpointsDataVisualiser/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function reciprocalLattice(cell) {
4040
return [b1, b2, b3];
4141
}
4242

43-
// Static columns (won’t change between renders)
43+
// Static columns for tables.
4444
export const Columns = {
4545
recipColumns: ["", "x", "y", "z"],
4646
cellColumns: ["x", "y", "z"],

src/AiidaExplorer/components/Overlay.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export default function Overlay({
6565
{title && <div className="ae:text-lg ae:font-medium">{title}</div>}
6666
<button
6767
onClick={onClose}
68-
className="ae:text-gray-600 ae:hover:text-black ae:p-1"
68+
className="ae:text-gray-600 ae:hover:text-black ae:hover:bg-slate-50 ae:rounded-lg ae:hover:cursor-pointer ae:p-1"
6969
>
7070
7171
</button>

0 commit comments

Comments
 (0)