Luvoy
Luvoy

Reputation: 1

vue3 Pan bugs when switching between d3.zoom and d3.brush

I used vue3 and d3 to implement a multi-functional scatter plot.

scatter plot

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

Answers (1)

Luvoy
Luvoy

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

Related Questions