anvaka / panzoom

Universal pan and zoom library (DOM, SVG, Custom)
https://anvaka.github.io/panzoom/demo/attach-via-script.html
MIT License
1.82k stars 293 forks source link

Excluding elements from being zoomed - pull request? #183

Open martinweihrauch opened 4 years ago

martinweihrauch commented 4 years ago

Hi! Thanks for that excellent library! I want to add the functionality for adding annotations. These "pins" should not "grow", when an image is zoomed. I would code something that you can exclude elements from getting larger (by reverse transforming). Do you want to implement that yourself or do you want me to implement and add a pull request? I would think that you can add a selector to the options (like "exclude all #divContainer .noZoom").

anvaka commented 4 years ago

Hello!

I'm not sure I see the full picture here. Wouldn't such functionality require inverting transform?

I'd totally appreciate a quick demo to explore the functionality. Please keep in mind that this library supports both SVG/DOM transformations. Ideally graphics-specific code should live in a corresponding controller (e.g. svg controller, dom controller ).

martinweihrauch commented 4 years ago

Yes, I already realized that with inverse transform. I put it in the options, so that you can add a selector

rleddy commented 2 years ago

Is this pull request still out there?

rleddy commented 2 years ago

Use vector-effect.

Values for vector-effect: none non-scaling-stroke non-scaling-size non-rotation fixed-position

Example:

<line x1='45' x2='45' y1='100' y2='0' height='100' stroke-width="5" stroke="rgb(0,0,0)" vector-effect="non-scaling-stroke" />

The following accept vector-effect:

rleddy commented 2 years ago

OK!!! So, there's a bug in the browsers. It only really seems to work for a line. Just tried it. Paths ... sort of.

Information about the bug was pointed out on a post on StackExchange.

The following presents us with a hack: https://muffinman.io/blog/svg-non-scaling-circle-and-rectangle/

rleddy commented 2 years ago

What I have finally done. I am using Svelte so there is a shorthand that result in possibly clumsy update. But, it works well enough.

In another environment, the updates have to be figured out a little more carefully.

I have two groups in my SVG. One is the target of panzoom. Another is behind it. It translates its elements when panzoom emits pan and zoom events.

Here is the initialization:

    let panzoomOptions = {
        maxZoom: 5,
        minZoom: 1,
        initialZoom: 1,
        zoomDoubleClickSpeed: 1,
        transformOrigin: {x: 0, y: 0},
        zoomSpeed: 0.25,
        preserveAspecRatio: false,
        scaleFactors: { x : 1.0, y : 0.0 }
    };

    let line_points = []
    let info_points = []
    let divider_starts = -36;
    let division_width = 200
    let point = { x : divider_starts, y : 0 }
    let left_shift = divider_starts*division_width

    let info_delta = 3

    for ( let i = 0; i < 1000; i++ ) {
        line_points.push({ x : (i*division_width + left_shift), y : 0, index : (divider_starts + i)})
        info_points.push({ x : (i*division_width + left_shift), y : 0, index : (divider_starts + i)})
    }

    let original_info_points = info_points.map(p => {
        return Object.assign({},p)
    })

The HTML part is done in Svelte, which will pick up changes to SVG when the arrays are changed in the panzoom events.

<svg style="width:100%">
    <!-- this is the draggable root -->
    <g>
        {#each info_points as point }
            <text x='{point.x}' y='100' stroke='none' fill='rgb(80,200,90)'vector-effect="non-scaling-size" >{point.index}</text>
        {/each}
    </g>
    <g bind:this={canvasElt} > 
        {#each line_points as point }
            <line x1='{point.x}' x2='{point.x}' y1='0' y2='110' height='100' stroke-width="2"  stroke="rgb(0,0,0)" vector-effect="non-scaling-stroke" />
        {/each}
    </g>
</svg>

Then, in the events, you have to make changes to info_points and track the state of the transform:

    onMount(() => {
        // panzoom

        panzoomInstance = panzoom(canvasElt,panzoomOptions);

        panzoomInstance.on('zoom', (e) => {     /// left to right only
            let tt = e.getTransform()
            tt.y = 0   // this is the keeping vertical tick marks from floating up

            let repoint = original_info_points.map(p => {
                let oo = Object.assign({},p)
                oo.x = oo.x*tt.scaleX + tt.x + (info_delta*tt.scaleX)  // scale + offset + scaled delta
                return oo
            })
            info_points = repoint
        });

        panzoomInstance.on('zoomend', (e) => {      /// left to right only
            let tt = e.getTransform()
            tt.y = 0

            let repoint = original_info_points.map(p => {
                let oo = Object.assign({},p)
                oo.x = oo.x*tt.scaleX + tt.x + (info_delta*tt.scaleX)   // scale + offset + scaled delta
                return oo
            })
            info_points = repoint
        });

        panzoomInstance.on('pan', (e) => {      /// left to right only
            let tt = e.getTransform()
            tt.y = 0

            let repoint = original_info_points.map(p => {
                let oo = Object.assign({},p)
                oo.x = oo.x*tt.scaleX + tt.x + (info_delta*tt.scaleX)   // scale + offset + scaled delta
                return oo
            })
            info_points = repoint

        });

    })

The labels by the tick marks drift when the smooth transition is going. But, they settle smoothly back into place.

Similar means can be programmed for vertical and radial changes. An idea might be to do fixed concentric circles with the panzoom centered at their center.