jsplumb / community-edition

The community edition of jsPlumb, versions 1.x - 6.x
https://jsplumbtoolkit.com
Other
224 stars 18 forks source link

how to keep size of canvas when JsPlumb instance is zoomed #70

Open knowledgeplaces opened 2 years ago

knowledgeplaces commented 2 years ago

I want to implement a zoom feature in my Angular app which uses JsPlumb Community to display flowcharts.

I have followed the sample given here: https://docs.jsplumbtoolkit.com/community-2.x/current/articles/zooming.html

I have been able to make it work, but the result is not exactly what I expect.

Let say we have a very large flowchart diagram.

On initial display with a scale of 1, only a portion of it is displayed in browser viewport, and I can use the scollbars to view what is hidden (see screencopy below). screencopy1

When I apply a sufficiently smaller scale factor, I would like the whole flowchart to be redrawed in the canvas.

Instead of that, the portion of the flowchart initially displayed is redrawn with the scale factor, including the scrollbars, but the size of the canvas is reduced and there is a lot of empty space around it (see screencopy below), so as is, this is not really usefull. screencopy2

Is there a way to fix this? Where did I go wrong?

knowledgeplaces commented 2 years ago

Some more information. Here is my HTML code, which uses Angular Material and Flex layout:

    <mat-card fxLayout="column" fxFlex="grow" ngClass="kps-mat-card-graph" ngClass.xs="kps-mat-card-graph-xs">
        <div ngClass="kps-flowchart kps-graph" id="canvas">
            <div #learningPlanGraphNode 
                                *ngFor="let learningPlanGraphNode of learningPlanGraphNodes"
                class="window-clickable" id="{{'courseWindow' + learningPlanGraphNode.stepId}}"
                [style.top]="learningPlanGraphNode.top" [style.left]="learningPlanGraphNode.left"
                [style.borderColor]="learningPlanGraphNode.color"
                (pointerdown)="this.saveCurrentGraphNodePosition(learningPlanGraphNode)"
                (pointerup)="this.enableSaveLayoutIfGraphNodeMoved()">
                <div (pointerup)="openLearningPlanStepForm(learningPlanGraphNode.stepId)">
                    <mat-icon class="kps-node-top-right-icon-active">edit</mat-icon>
                </div>
                <mat-label>{{learningPlanGraphNode.courseName}}</mat-label>
            </div>
        </div>
    </mat-card>

And here is the TypeScript function called when you drag the slider at the bottom right of the screen:

    setZoom(zoom: number): void {
        const el = this.graphInstance.getContainer();
        el.style.transform = 'scale(' + zoom + ')';
        el.style.transformOrigin = '0% 0%';
        this.graphInstance.setZoom(zoom);
        this.graphInstance.repaintEverything();
    }

And here is the CSS code for the '#canvas" container:

#canvas {
    width: 100%;
    height: 100%;
}
knowledgeplaces commented 2 years ago

I have been able to solve the zooming issue this way. Here is my Angular HTML template for the JSPlumb graph instance:

    <mat-card fxLayout="column" fxFlex="grow" ngClass="kps-mat-card-graph" ngClass.xs="kps-mat-card-graph-xs">
        <div fxLayout="column" fxFlex="grow" id="canvasContainerForZoom" [style.overflow]="'auto'">
            <div ngClass="kps-flowchart kps-graph" id="canvas" [style.height]="canvasHeight"
                [style.width]="canvasWidth">
                <div #learningPlanGraphNode *ngFor="let learningPlanGraphNode of learningPlanGraphNodes"
                    class="window-clickable" id="{{'courseWindow' + learningPlanGraphNode.stepId}}"
                    [style.top]="learningPlanGraphNode.top" [style.left]="learningPlanGraphNode.left"
                    [style.borderColor]="learningPlanGraphNode.color"
                    (pointerdown)="this.saveCurrentGraphNodePosition(learningPlanGraphNode)"
                    (pointerup)="this.enableSaveLayoutIfGraphNodeMoved()">
                    <div (pointerup)="openLearningPlanStepForm(learningPlanGraphNode.stepId)">
                        <mat-icon class="kps-node-top-right-icon-active">edit</mat-icon>
                    </div>
                    <mat-label>{{learningPlanGraphNode.courseName}}</mat-label>
                </div>
            </div>
        </div>
    </mat-card>

Here is what is triggering the zoom:

        <mat-slider thumbLabel value="100" (input)="setZoom(graphInstance, $event.value/100)"
            [style.margin-right]="'5px'">
        </mat-slider>

And finally, here is the code which does the trick, i.e. basically set width and height of graph parent container:

    setZoom(graphInstance: BrowserJsPlumbInstance, zoom: number): void {
        const el = graphInstance.getContainer();
        el.style.transform = 'scale(' + zoom + ')';
        el.style.transformOrigin = '0% 0%';
        const elContainerForZoom = document.getElementById(el.parentId);
        this.canvasHeight = elContainerForZoom.clientHeight + 'px';
        this.canvasWidth = elContainerForZoom.clientWidth + 'px';
        this.graphInstance.setZoom(zoom);
        this.graphInstance.repaintEverything();
    }

Everything works fine, except when I want to drag and drop nodes or connections. The mouse goes far away of the dragged item, as the div is scaled. How to avoid this?

dries commented 2 years ago

I used a CSS variable to deal with this. This can be updated from javascript.

:root {
  --zoom-scale: 1;
}

.container {
  position: relative;
  border: solid black 1px;
  width: calc(80vw / var(--zoom-scale));
  height: calc(70vh / var(--zoom-scale));
  -webkit-transform: scale(var(--zoom-scale));
  -moz-transform: scale(var(--zoom-scale));
  -ms-transform: scale(var(--zoom-scale));
  -o-transform: scale(var(--zoom-scale));
  transform: scale(var(--zoom-scale));
  transform-origin: top left;
}