Skip to content

Commit 0262b7f

Browse files
committed
feat(LineSeed): add line seed widget
1 parent 0469719 commit 0262b7f

File tree

5 files changed

+372
-0
lines changed

5 files changed

+372
-0
lines changed

examples/lineSeed/line_seed.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from pathlib import Path
2+
from trame.app import get_server
3+
from trame.ui.vuetify3 import SinglePageLayout
4+
from trame.widgets import vuetify3, trame
5+
from trame.assets.local import to_url
6+
7+
server = get_server()
8+
IMAGE = str(Path(__file__).with_name("seeds.jpg").resolve())
9+
10+
11+
def new_seed_points(p1, p2):
12+
print(f"{p1=}")
13+
print(f"{p2=}")
14+
15+
16+
with SinglePageLayout(server, full_height=True) as layout:
17+
with layout.toolbar:
18+
vuetify3.VSpacer()
19+
vuetify3.VLabel("{{ width }}")
20+
vuetify3.VSlider(
21+
v_model=("width", 500),
22+
max=500,
23+
min=150,
24+
step=10,
25+
density="compact",
26+
hide_details=True,
27+
)
28+
with layout.content:
29+
with vuetify3.VContainer(classes="fill-height", fluid=True):
30+
trame.LineSeed(
31+
style=("`max-width: ${width}px;`",),
32+
image=to_url(IMAGE),
33+
point_1=("p1", [-0.5, 0, 0]),
34+
point_2=("p2", [-0.5, 0, 1.25]),
35+
bounds=("[-0.5, 1.80, -1.12, 1.11, -0.43, 1.79]",),
36+
update_seed=(new_seed_points, "[]", "$event"),
37+
)
38+
39+
server.start()

examples/lineSeed/seeds.jpg

14.6 KB
Loading

trame_components/widgets/trame.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"GitTree",
1414
"XaiHeatMap",
1515
"XaiImage",
16+
"LineSeed",
1617
]
1718

1819

@@ -478,3 +479,23 @@ def __init__(self, children=None, **kwargs):
478479
("color_range_change", "colorRange"),
479480
("full_range_change", "fullRange"),
480481
]
482+
483+
484+
# -----------------------------------------------------------------------------
485+
# TrameLineSeed
486+
# -----------------------------------------------------------------------------
487+
488+
489+
class LineSeed(HtmlElement):
490+
def __init__(self, children=None, **kwargs):
491+
super().__init__("trame-line-seed", children, **kwargs)
492+
self._attr_names += [
493+
("point_1", "point1"),
494+
("point_2", "point2"),
495+
("number_steps", "numberOfSteps"),
496+
"bounds",
497+
"image",
498+
]
499+
self._event_names += [
500+
("update_seed", "update-seed"),
501+
]
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
const { ref, computed, watch, unref, toRefs, onMounted, onBeforeUnmount } =
2+
window.Vue;
3+
4+
export default {
5+
props: {
6+
point1: {
7+
type: Array,
8+
default() {
9+
return [0, 0, 0];
10+
},
11+
},
12+
point2: {
13+
type: Array,
14+
default() {
15+
return [0, 0, 0];
16+
},
17+
},
18+
numberOfSteps: {
19+
type: Number,
20+
default: 255,
21+
},
22+
bounds: {
23+
type: Array,
24+
default() {
25+
return [-1, 1, -1, 1, -1, 1];
26+
},
27+
},
28+
image: {
29+
default: null,
30+
},
31+
},
32+
emit: ["update-seed"],
33+
setup(props, { emit }) {
34+
let dragging = 0;
35+
let lastEvent = null;
36+
let lastPoint = null;
37+
38+
const svgContainer = ref(null);
39+
const normalDelta = ref([0, 0]);
40+
const scaling2d = ref(1);
41+
const radius = ref(10);
42+
const p1 = ref([0, 0]);
43+
const p2 = ref([0, 0]);
44+
const userInput = ref(false);
45+
const x1 = ref(props.point1[0]);
46+
const y1 = ref(props.point1[1]);
47+
const z1 = ref(props.point1[2]);
48+
const x2 = ref(props.point2[0]);
49+
const y2 = ref(props.point2[1]);
50+
const z2 = ref(props.point2[2]);
51+
const sharedDepth = ref(true);
52+
const nbSeeds = ref(50);
53+
54+
const pointColor2 = computed(() =>
55+
unref(sharedDepth) ? "#2196F3" : "#4CAF50"
56+
);
57+
const step = computed(
58+
() => (props.bounds[5] - props.bounds[4]) / props.numberOfSteps
59+
);
60+
// const xScaling = computed(() => (props.bounds[1] - props.bounds[0]) / 500);
61+
const yScaling = computed(() => (props.bounds[3] - props.bounds[2]) / 500);
62+
const zScaling = computed(() => (props.bounds[5] - props.bounds[4]) / 500);
63+
64+
function update2DPoints() {
65+
p1.value = [
66+
500 - (unref(y1) - props.bounds[2]) / unref(yScaling),
67+
500 - (unref(z1) - props.bounds[4]) / unref(zScaling),
68+
];
69+
p2.value = [
70+
500 - (unref(y2) - props.bounds[2]) / unref(yScaling),
71+
500 - (unref(z2) - props.bounds[4]) / unref(zScaling),
72+
];
73+
}
74+
75+
function pushLineSeed() {
76+
if (!unref(userInput)) {
77+
return;
78+
}
79+
const xSelected = unref(sharedDepth) ? x1 : x2;
80+
emit("update-seed", {
81+
p1: [unref(x1), unref(y1), unref(z1)],
82+
p2: [unref(xSelected), unref(y2), unref(z2)],
83+
});
84+
}
85+
86+
function onMouseMove(e) {
87+
if (!unref(userInput)) {
88+
userInput.value = true;
89+
}
90+
if (dragging) {
91+
const dx = lastEvent.clientX - e.clientX;
92+
const dy = lastEvent.clientY - e.clientY;
93+
switch (dragging) {
94+
case 1:
95+
p1.value = [
96+
lastPoint[0] - unref(scaling2d) * dx,
97+
lastPoint[1] - unref(scaling2d) * dy,
98+
];
99+
y1.value = (500 - unref(p1)[0]) * unref(yScaling) + props.bounds[2];
100+
z1.value = (500 - unref(p1)[1]) * unref(zScaling) + props.bounds[4];
101+
break;
102+
case 2:
103+
p2.value = [
104+
lastPoint[0] - unref(scaling2d) * dx,
105+
lastPoint[1] - unref(scaling2d) * dy,
106+
];
107+
y2.value = (500 - unref(p2)[0]) * unref(yScaling) + props.bounds[2];
108+
z2.value = (500 - unref(p2)[1]) * unref(zScaling) + props.bounds[4];
109+
break;
110+
case 3:
111+
p1.value = [
112+
lastPoint[0][0] - unref(scaling2d) * dx,
113+
lastPoint[0][1] - unref(scaling2d) * dy,
114+
];
115+
y1.value = (500 - unref(p1)[0]) * unref(yScaling) + props.bounds[2];
116+
z1.value = (500 - unref(p1)[1]) * unref(zScaling) + props.bounds[4];
117+
p2.value = [
118+
lastPoint[1][0] - unref(scaling2d) * dx,
119+
lastPoint[1][1] - unref(scaling2d) * dy,
120+
];
121+
y2.value = (500 - unref(p2)[0]) * unref(yScaling) + props.bounds[2];
122+
z2.value = (500 - unref(p2)[1]) * unref(zScaling) + props.bounds[4];
123+
break;
124+
default:
125+
break;
126+
}
127+
128+
// Compute delta for line offset connection in svg
129+
const vect = [unref(p1)[0] - unref(p2)[0], unref(p1)[1] - unref(p2)[1]];
130+
const norm = Math.sqrt(vect[0] * vect[0] + vect[1] * vect[1]);
131+
if (norm > 0.0001) {
132+
vect[0] /= norm;
133+
vect[1] /= norm;
134+
}
135+
normalDelta.value = vect;
136+
137+
pushLineSeed();
138+
}
139+
}
140+
141+
function onMousePress(pointIdx, e) {
142+
dragging = pointIdx;
143+
lastEvent = e;
144+
switch (pointIdx) {
145+
case 1:
146+
lastPoint = unref(p1).slice();
147+
break;
148+
case 2:
149+
lastPoint = unref(p2).slice();
150+
break;
151+
case 3:
152+
lastPoint = [unref(p1).slice(), unref(p2).slice()];
153+
break;
154+
default:
155+
break;
156+
}
157+
}
158+
159+
function onMouseRelease() {
160+
dragging = 0;
161+
}
162+
163+
function onResize() {
164+
scaling2d.value = 500 / unref(svgContainer).getBoundingClientRect().width;
165+
}
166+
const resizeObserver = new ResizeObserver(onResize);
167+
onMounted(() => {
168+
console.log("svgContainer", unref(svgContainer));
169+
resizeObserver.observe(unref(svgContainer));
170+
});
171+
onBeforeUnmount(() => resizeObserver.disconnect());
172+
173+
watch(
174+
() => props.point1,
175+
(v) => {
176+
[x1.value, y1.value, z1.value] = v;
177+
update2DPoints();
178+
}
179+
);
180+
watch(
181+
() => props.point2,
182+
(v) => {
183+
[x2.value, y2.value, z2.value] = v;
184+
update2DPoints();
185+
}
186+
);
187+
watch(sharedDepth, pushLineSeed);
188+
189+
update2DPoints();
190+
const { image } = toRefs(props);
191+
return {
192+
svgContainer,
193+
onMouseMove,
194+
onMousePress,
195+
onMouseRelease,
196+
normalDelta,
197+
radius,
198+
p1,
199+
p2,
200+
nbSeeds,
201+
sharedDepth,
202+
x1,
203+
x2,
204+
step,
205+
pointColor2,
206+
pushLineSeed,
207+
image,
208+
};
209+
},
210+
211+
template: `<v-col class="mt-2">
212+
<svg viewBox="0 0 500 500"
213+
ref="svgContainer"
214+
width="100%"
215+
@mousemove="onMouseMove"
216+
@mouseup="onMouseRelease"
217+
>
218+
<image
219+
:href="image"
220+
x="0"
221+
y="0"
222+
width="500"
223+
height="500"
224+
/>
225+
<line
226+
:x1="p1[0] - normalDelta[0] * radius"
227+
:y1="p1[1] - normalDelta[1] * radius"
228+
:x2="p2[0] + normalDelta[0] * radius"
229+
:y2="p2[1] + normalDelta[1] * radius"
230+
style="cursor: grab;stroke: rgba(0,0,0,0.25);stroke-width:8"
231+
@mousedown="onMousePress(3, $event)"
232+
/>
233+
<circle
234+
:cx="p2[0]"
235+
:cy="p2[1]"
236+
:r="radius"
237+
:stroke="pointColor2"
238+
stroke-width="8"
239+
fill="rgba(0,0,0,0)"
240+
@mousedown="onMousePress(2, $event)"
241+
style="cursor: pointer;"
242+
/>
243+
<circle
244+
:cx="p1[0]"
245+
:cy="p1[1]"
246+
r="10"
247+
stroke="#2196F3"
248+
stroke-width="8"
249+
fill="rgba(0,0,0,0)"
250+
@mousedown="onMousePress(1, $event)"
251+
style="cursor: pointer;"
252+
/>
253+
</svg>
254+
<v-col class="pt-0">
255+
<v-row class="align-center">
256+
<v-col cols="12" md="11">
257+
<v-text-field
258+
density="compact"
259+
hide-details
260+
type="number"
261+
min="5"
262+
max="1000"
263+
step="1"
264+
label="Seeds count"
265+
v-model="nbSeeds"
266+
/>
267+
</v-col>
268+
<v-col cols="12" md="1">
269+
<v-switch
270+
style="width: 80px"
271+
class="mt-0"
272+
density="compact"
273+
v-model="sharedDepth"
274+
hide-details
275+
/>
276+
</v-col>
277+
</v-row>
278+
<v-row>
279+
<v-slider
280+
v-show="!sharedDepth"
281+
v-model="x2"
282+
track-color="green"
283+
color="green"
284+
density="compact"
285+
hide-details
286+
@update:modelValue="pushLineSeed"
287+
:min="bounds[0]"
288+
:max="bounds[1]"
289+
:step="step"
290+
/>
291+
</v-row>
292+
<v-row>
293+
<v-slider
294+
v-model="x1"
295+
track-color="blue"
296+
color="blue"
297+
density="compact"
298+
hide-details
299+
@update:modelValue="pushLineSeed"
300+
:min="bounds[0]"
301+
:max="bounds[1]"
302+
:step="step"
303+
/>
304+
</v-row>
305+
</v-col>
306+
</v-col>
307+
`,
308+
};

vue-components/src/components/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import TrameSizeObserver from "./TrameSizeObserver";
1010
import TrameXaiHeatMap from "./TrameXaiHeatMap";
1111
import TrameXaiImage from "./TrameXaiImage";
1212

13+
import TrameLineSeed from "./TrameLineSeed";
14+
1315
export default {
1416
TrameClientStateChange,
1517
TrameClientTriggers,
@@ -22,4 +24,6 @@ export default {
2224
TrameSizeObserver,
2325
TrameXaiHeatMap,
2426
TrameXaiImage,
27+
28+
TrameLineSeed,
2529
};

0 commit comments

Comments
 (0)