Reputation: 1
I used vue3 and d3 to implement a multi-functional scatter plot.
pan
and brush
: when we click one, enable the feature and disable anther.
pinia
as a global store. And when the component onMounted
, it saves function brushEnableFunc
and panEnableFunc
to UIStore
. So buttons can bind to the 2 functions.resize
on the svg container box to make my plot resizable.
watch
to listen to the changes of width and height, and recalculate the coordinates.When I switch between brush and pan, it works well.
However, after I resize the container, the brush and zoom behavior still works, but the pan has bugs:
Sometimes, I can't drag in pan mode. Sometimes, in brush mode, brush and pan works together (even if mouse are hovered, not clicked, svg is still panning and dragging).
What should I do to fix the bugs? Or is there some ways to decouple the code?
Some related codes:
Brush and zoom are reactive to width and height.
const brush = computed(() =>
d3
.brush()
.on("start", brushStart)
.on("end", brushEnd)
.extent([
[0, 0],
[props.width, props.height],
])
);
const zoom = computed(() =>
d3
.zoom()
.extent([
[0, 0],
[props.width, props.height],
])
.scaleExtent([1 / 2, 16])
.translateExtent([
[0, 0],
[props.width, props.height],
])
);
On mounted lifecycle hook:
onMounted(() => {
const gBrush = d3.select(svgRef.value).append("g").attr("class", "gBrush");
zoom.value.on("zoom", (event) => {
transformRef.value = event.transform; // keep the transform value to apply to some coords value
gBrush.attr("transform", event.transform); //when we zoom, brush rect should also zoom
});
if (UIStore.isBrushEnabled) { //at the first time, brush is enabled, or pan?
zoom.value.filter((event) => event.type === "wheel"); // only zoom in and out
gBrush.call(brush.value);
} else {
zoom.value.filter(
(event) =>
(!event.ctrlKey || event.type === "wheel") && !event.button //default
);
}
//always bind zoom
d3.select(svgRef.value).call(zoom.value);
UIStore.brushEnableFunc = () => {
const gBrush = d3.select(svgRef.value).select(".gBrush");
zoom.value.filter((event) => event.type === "wheel");
gBrush.style("display", "inherit"); //
gBrush.call(brush.value); //rebind
};
UIStore.panEnableFunc = () => {
const gBrush = d3.select(svgRef.value).select(".gBrush");
gBrush.style("display", "none"); // hide rect,
zoom.value.filter(
(event) =>
(!event.ctrlKey || event.type === "wheel") && !event.button //default
);
};
}); // onMounted END
so button can be bound to:
const handlePanClick = (e) => {
UIStore.isBrushEnabled = false;
UIStore.panEnableFunc();
};
const handleBrushClick = (e) => {
UIStore.isBrushEnabled = true;
UIStore.brushEnableFunc();
};
watch the changes of width and height (resized)
watch(
[() => props.width, () => props.height],
([newWidth, newHeight], [oldWidth, oldHeight]) => {
const gBrush = d3.select(svgRef.value).select(".gBrush");
points.value = rescaleCoords( // recalculate the coords
points.value || [],
props.width,
props.height,
(d) => d[0],
(d) => d[1]
);
const rect = d3.brushSelection(gBrush.node()); //get the rect
if (rect) {
const [[x0, y0], [x1, y1]] = rect;
gBrush.call(brush.value.move, (datum, i, node) => { // move the rect
return [ [ (x0 * newWidth) / oldWidth, (y0 * newHeight) / oldHeight],
[(x1 * newWidth) / oldWidth, (y1 * newHeight) / oldHeight] ];
});
}
gBrush.call(brush.value);
}
);
Upvotes: 0
Views: 86
Reputation: 1
emmm, the zoom
is defined as computed
, that's where the bugs hide.
I rewrite zoom
with plain d3.zoom()
, and dynamically change its extends in watch
, then the plot works well.
But brush
is also defined as computed
and has no bugs. 🤔
Upvotes: 0