Skip to content

Commit

Permalink
feat(LineSeed): add line seed widget
Browse files Browse the repository at this point in the history
  • Loading branch information
jourdain committed Apr 15, 2024
1 parent 0469719 commit 0262b7f
Show file tree
Hide file tree
Showing 5 changed files with 372 additions and 0 deletions.
39 changes: 39 additions & 0 deletions examples/lineSeed/line_seed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from pathlib import Path
from trame.app import get_server
from trame.ui.vuetify3 import SinglePageLayout
from trame.widgets import vuetify3, trame
from trame.assets.local import to_url

server = get_server()
IMAGE = str(Path(__file__).with_name("seeds.jpg").resolve())


def new_seed_points(p1, p2):
print(f"{p1=}")
print(f"{p2=}")


with SinglePageLayout(server, full_height=True) as layout:
with layout.toolbar:
vuetify3.VSpacer()
vuetify3.VLabel("{{ width }}")
vuetify3.VSlider(
v_model=("width", 500),
max=500,
min=150,
step=10,
density="compact",
hide_details=True,
)
with layout.content:
with vuetify3.VContainer(classes="fill-height", fluid=True):
trame.LineSeed(
style=("`max-width: ${width}px;`",),
image=to_url(IMAGE),
point_1=("p1", [-0.5, 0, 0]),
point_2=("p2", [-0.5, 0, 1.25]),
bounds=("[-0.5, 1.80, -1.12, 1.11, -0.43, 1.79]",),
update_seed=(new_seed_points, "[]", "$event"),
)

server.start()
Binary file added examples/lineSeed/seeds.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions trame_components/widgets/trame.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"GitTree",
"XaiHeatMap",
"XaiImage",
"LineSeed",
]


Expand Down Expand Up @@ -478,3 +479,23 @@ def __init__(self, children=None, **kwargs):
("color_range_change", "colorRange"),
("full_range_change", "fullRange"),
]


# -----------------------------------------------------------------------------
# TrameLineSeed
# -----------------------------------------------------------------------------


class LineSeed(HtmlElement):
def __init__(self, children=None, **kwargs):
super().__init__("trame-line-seed", children, **kwargs)
self._attr_names += [
("point_1", "point1"),
("point_2", "point2"),
("number_steps", "numberOfSteps"),
"bounds",
"image",
]
self._event_names += [
("update_seed", "update-seed"),
]
308 changes: 308 additions & 0 deletions vue-components/src/components/TrameLineSeed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
const { ref, computed, watch, unref, toRefs, onMounted, onBeforeUnmount } =
window.Vue;

export default {
props: {
point1: {
type: Array,
default() {
return [0, 0, 0];
},
},
point2: {
type: Array,
default() {
return [0, 0, 0];
},
},
numberOfSteps: {
type: Number,
default: 255,
},
bounds: {
type: Array,
default() {
return [-1, 1, -1, 1, -1, 1];
},
},
image: {
default: null,
},
},
emit: ["update-seed"],
setup(props, { emit }) {
let dragging = 0;
let lastEvent = null;
let lastPoint = null;

const svgContainer = ref(null);
const normalDelta = ref([0, 0]);
const scaling2d = ref(1);
const radius = ref(10);
const p1 = ref([0, 0]);
const p2 = ref([0, 0]);
const userInput = ref(false);
const x1 = ref(props.point1[0]);
const y1 = ref(props.point1[1]);
const z1 = ref(props.point1[2]);
const x2 = ref(props.point2[0]);
const y2 = ref(props.point2[1]);
const z2 = ref(props.point2[2]);
const sharedDepth = ref(true);
const nbSeeds = ref(50);

const pointColor2 = computed(() =>
unref(sharedDepth) ? "#2196F3" : "#4CAF50"
);
const step = computed(
() => (props.bounds[5] - props.bounds[4]) / props.numberOfSteps
);
// const xScaling = computed(() => (props.bounds[1] - props.bounds[0]) / 500);
const yScaling = computed(() => (props.bounds[3] - props.bounds[2]) / 500);
const zScaling = computed(() => (props.bounds[5] - props.bounds[4]) / 500);

function update2DPoints() {
p1.value = [
500 - (unref(y1) - props.bounds[2]) / unref(yScaling),
500 - (unref(z1) - props.bounds[4]) / unref(zScaling),
];
p2.value = [
500 - (unref(y2) - props.bounds[2]) / unref(yScaling),
500 - (unref(z2) - props.bounds[4]) / unref(zScaling),
];
}

function pushLineSeed() {
if (!unref(userInput)) {
return;
}
const xSelected = unref(sharedDepth) ? x1 : x2;
emit("update-seed", {
p1: [unref(x1), unref(y1), unref(z1)],
p2: [unref(xSelected), unref(y2), unref(z2)],
});
}

function onMouseMove(e) {
if (!unref(userInput)) {
userInput.value = true;
}
if (dragging) {
const dx = lastEvent.clientX - e.clientX;
const dy = lastEvent.clientY - e.clientY;
switch (dragging) {
case 1:
p1.value = [
lastPoint[0] - unref(scaling2d) * dx,
lastPoint[1] - unref(scaling2d) * dy,
];
y1.value = (500 - unref(p1)[0]) * unref(yScaling) + props.bounds[2];
z1.value = (500 - unref(p1)[1]) * unref(zScaling) + props.bounds[4];
break;
case 2:
p2.value = [
lastPoint[0] - unref(scaling2d) * dx,
lastPoint[1] - unref(scaling2d) * dy,
];
y2.value = (500 - unref(p2)[0]) * unref(yScaling) + props.bounds[2];
z2.value = (500 - unref(p2)[1]) * unref(zScaling) + props.bounds[4];
break;
case 3:
p1.value = [
lastPoint[0][0] - unref(scaling2d) * dx,
lastPoint[0][1] - unref(scaling2d) * dy,
];
y1.value = (500 - unref(p1)[0]) * unref(yScaling) + props.bounds[2];
z1.value = (500 - unref(p1)[1]) * unref(zScaling) + props.bounds[4];
p2.value = [
lastPoint[1][0] - unref(scaling2d) * dx,
lastPoint[1][1] - unref(scaling2d) * dy,
];
y2.value = (500 - unref(p2)[0]) * unref(yScaling) + props.bounds[2];
z2.value = (500 - unref(p2)[1]) * unref(zScaling) + props.bounds[4];
break;
default:
break;
}

// Compute delta for line offset connection in svg
const vect = [unref(p1)[0] - unref(p2)[0], unref(p1)[1] - unref(p2)[1]];
const norm = Math.sqrt(vect[0] * vect[0] + vect[1] * vect[1]);
if (norm > 0.0001) {
vect[0] /= norm;
vect[1] /= norm;
}
normalDelta.value = vect;

pushLineSeed();
}
}

function onMousePress(pointIdx, e) {
dragging = pointIdx;
lastEvent = e;
switch (pointIdx) {
case 1:
lastPoint = unref(p1).slice();
break;
case 2:
lastPoint = unref(p2).slice();
break;
case 3:
lastPoint = [unref(p1).slice(), unref(p2).slice()];
break;
default:
break;
}
}

function onMouseRelease() {
dragging = 0;
}

function onResize() {
scaling2d.value = 500 / unref(svgContainer).getBoundingClientRect().width;
}
const resizeObserver = new ResizeObserver(onResize);
onMounted(() => {
console.log("svgContainer", unref(svgContainer));
resizeObserver.observe(unref(svgContainer));
});
onBeforeUnmount(() => resizeObserver.disconnect());

watch(
() => props.point1,
(v) => {
[x1.value, y1.value, z1.value] = v;
update2DPoints();
}
);
watch(
() => props.point2,
(v) => {
[x2.value, y2.value, z2.value] = v;
update2DPoints();
}
);
watch(sharedDepth, pushLineSeed);

update2DPoints();
const { image } = toRefs(props);
return {
svgContainer,
onMouseMove,
onMousePress,
onMouseRelease,
normalDelta,
radius,
p1,
p2,
nbSeeds,
sharedDepth,
x1,
x2,
step,
pointColor2,
pushLineSeed,
image,
};
},

template: `<v-col class="mt-2">
<svg viewBox="0 0 500 500"
ref="svgContainer"
width="100%"
@mousemove="onMouseMove"
@mouseup="onMouseRelease"
>
<image
:href="image"
x="0"
y="0"
width="500"
height="500"
/>
<line
:x1="p1[0] - normalDelta[0] * radius"
:y1="p1[1] - normalDelta[1] * radius"
:x2="p2[0] + normalDelta[0] * radius"
:y2="p2[1] + normalDelta[1] * radius"
style="cursor: grab;stroke: rgba(0,0,0,0.25);stroke-width:8"
@mousedown="onMousePress(3, $event)"
/>
<circle
:cx="p2[0]"
:cy="p2[1]"
:r="radius"
:stroke="pointColor2"
stroke-width="8"
fill="rgba(0,0,0,0)"
@mousedown="onMousePress(2, $event)"
style="cursor: pointer;"
/>
<circle
:cx="p1[0]"
:cy="p1[1]"
r="10"
stroke="#2196F3"
stroke-width="8"
fill="rgba(0,0,0,0)"
@mousedown="onMousePress(1, $event)"
style="cursor: pointer;"
/>
</svg>
<v-col class="pt-0">
<v-row class="align-center">
<v-col cols="12" md="11">
<v-text-field
density="compact"
hide-details
type="number"
min="5"
max="1000"
step="1"
label="Seeds count"
v-model="nbSeeds"
/>
</v-col>
<v-col cols="12" md="1">
<v-switch
style="width: 80px"
class="mt-0"
density="compact"
v-model="sharedDepth"
hide-details
/>
</v-col>
</v-row>
<v-row>
<v-slider
v-show="!sharedDepth"
v-model="x2"
track-color="green"
color="green"
density="compact"
hide-details
@update:modelValue="pushLineSeed"
:min="bounds[0]"
:max="bounds[1]"
:step="step"
/>
</v-row>
<v-row>
<v-slider
v-model="x1"
track-color="blue"
color="blue"
density="compact"
hide-details
@update:modelValue="pushLineSeed"
:min="bounds[0]"
:max="bounds[1]"
:step="step"
/>
</v-row>
</v-col>
</v-col>
`,
};
4 changes: 4 additions & 0 deletions vue-components/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import TrameSizeObserver from "./TrameSizeObserver";
import TrameXaiHeatMap from "./TrameXaiHeatMap";
import TrameXaiImage from "./TrameXaiImage";

import TrameLineSeed from "./TrameLineSeed";

export default {
TrameClientStateChange,
TrameClientTriggers,
Expand All @@ -22,4 +24,6 @@ export default {
TrameSizeObserver,
TrameXaiHeatMap,
TrameXaiImage,

TrameLineSeed,
};

0 comments on commit 0262b7f

Please sign in to comment.