Skip to content
  • Place to introduce & feedback about this space

    4 Topics
    12 Posts
    D

    Hi all! 👋
    My name is Mateusz, and I’m a Software Developer with over 6 years of experience working in data visualization and diagramming at Synergy Codes.
    Feel free to reach out if you have questions, need advice, or want to collaborate. 😁

  • Products & Projects related with data vis

    4 Topics
    8 Posts
    PMarcinP

    Big update in our documentation.
    Code Customization - New Nodes - see video
    https://synergy-codes.gitbook.io/workflow-builder/code-customization-new-nodes

  • Discousion and tech hub

    2 Topics
    2 Posts
    lukasz.jazwaL

    Changing a node’s dimensions sounds simple. But doing it consistently in React Flow isn’t so straightforward.

    When you look at the Node type, you’ll notice width and height attributes. It might seem like you can just update those. But according to the React Flow docs, those values are read-only. The same goes for measured: { width: number; height: number }.

    So how do you actually change a node’s dimensions?

    The React Flow team recommends using CSS. From the docs:

    “You shouldn’t try to set the width or height of a node directly. It is calculated internally by React Flow and used when rendering the node in the viewport. To control a node’s size you should use the style or className props to apply CSS styles instead.”

    That usually works. But there’s a catch.

    If you want a consistent way to both set and get a node’s dimensions, relying on the style prop might not be enough. Why? Because of the NodeResizer component.

    NodeResizer lets users resize nodes manually using UI handles. But when they do, React Flow doesn't update the style prop. Instead, it directly modifies the width and height attributes using internal state changes.

    If you look at how NodeResizer works under the hood, you’ll see that React Flow uses a NodeDimensionChange to update the node’s size.

    So how can you trigger that same kind of change programmatically using only official React Flow methods?

    Use applyNodeChanges from the React Flow API:

    const customDimensionsChange: NodeDimensionChange = { id: 'node-1', type: 'dimensions', dimensions: { width: 300, height: 200 }, setAttributes: true, }; setNodes((nodes) => applyNodeChanges([customDimensionsChange], nodes));

    This updates the width and height attributes properly. It’s the most consistent way to change node dimensions programatically in React Flow.

  • Discousion and Tech Hub

    12 Topics
    13 Posts
    D
    Enhancing GoJS Zoom Functionality: A Better Approach to zoomToRect Introduction

    When working with GoJS diagrams, one of the most common requirements is to focus the view on specific nodes or areas of the diagram. While GoJS provides built-in methods like zoomToFit() and zoomToRect(), these methods have several limitations that can affect the user experience in real-world applications.

    In this post, I'll explore how we can create an enhanced version of the zoom functionality that addresses these limitations and provides a more polished user experience. We'll look at:

    Adding smooth animations for zoom transitions Providing precise control over padding around the zoomed area Accounting for floating UI elements (like toolbars or panels) when calculating zoom bounds Creating a more intuitive zoom behavior that considers the diagram's context Current Limitations

    Let's examine the current behavior of GoJS's zoom functionality, particularly when working with floating UI elements. In the demonstration below, we can see several issues:

    gif-1.gif

    Abrupt Transitions: The diagram jumps directly to the target node without any smooth animation, creating a jarring user experience.

    Improper Centering: When using centerRect() or zoomToFit(), the diagram doesn't properly account for floating UI elements. As shown in the GIF, the node is not centered vertically in the available space, as it doesn't consider the floating toolbar at the top.

    Inconsistent Padding: The current implementation doesn't provide fine-grained control over padding around the zoomed area, often resulting in nodes being either too close to the edges or having inconsistent spacing.

    Implementation

    Let's start by creating a new utility function that will handle our enhanced zoom functionality. We'll create this in a new file called zoomToRect.ts:

    import * as go from "gojs"; type UiElement = { element: HTMLElement; side: "top" | "bottom" | "left" | "right"; }; type ZoomToRectOptions = { uiElements?: UiElement[]; easing?: go.EasingFunction; scale?: number; duration?: number; }; export function zoomToRect( diagram: go.Diagram, rect: go.Rect, options?: ZoomToRectOptions ): Promise<void> {}

    This utility function provides enhanced zooming capabilities for GoJS diagrams by:

    Taking a diagram and target rectangle to zoom to Accepting optional configuration for: UI elements to avoid overlapping (like toolbars and controls) Custom easing function for smooth animations Target scale factor Animation duration

    This will be enough for a base function, of course depending on your requirements you will be able to modify this method to fit your needs.

    We will start with writing logic to calculate padding of UI elements which float over the diagram. Thanks to that we will have the real viewport size of the diagram.

    function calculateUiPadding( diagram: go.Diagram, uiElements: UiElement[] = [] ): go.Margin { const margin = new go.Margin(0, 0, 0, 0); if (!diagram?.div) { return margin; } const diagramRect = diagram.div.getBoundingClientRect(); for (const { element, side } of uiElements) { const elementRect = element.getBoundingClientRect(); if (side === "bottom") { margin.bottom = Math.max( margin.bottom, diagramRect.bottom - elementRect.top ); } else if (side === "left") { margin.left = Math.max(margin.left, elementRect.right - diagramRect.left); } else if (side === "right") { margin.right = Math.max( margin.right, diagramRect.right - elementRect.left ); } else if (side === "top") { margin.top = Math.max(margin.top, elementRect.bottom - diagramRect.top); } } return margin; }

    Above function calculates margin of every side for all of the passed UI elements.

    We need to know the side on which the UI element is to properly get the padding. Then we are trying to find the padding and on every side we are extracting the element which covers the diagram the most since there can be more than one element on every side.

    Our next step will be firstly using above method in our core logic and properly apply padding to passed properties.

    export function zoomToRect( diagram: go.Diagram, rect: go.Rect, options?: ZoomToRectOptions ): Promise<void> { const uiPadding = calculateUiPadding(diagram, options?.uiElements); // Use above method const diagramPadding = typeof diagram.padding === "number" ? new go.Margin(diagram.padding) : diagram.padding; // Get diagram padding in proper format const actualViewportWidth = diagram.viewportBounds.width * diagram.scale - uiPadding.left - uiPadding.right; // Calculate real viewport width const actualViewportHeight = diagram.viewportBounds.height * diagram.scale - uiPadding.top - uiPadding.bottom; // Calculate real viewport height const actualRectWidth = rect.width + diagramPadding.left + diagramPadding.right; // Calculate real rect width to fix on the screen const actualRectHeight = rect.height + diagramPadding.top + diagramPadding.bottom; // Calculate real rect height to fix on the screen const actualRectX = rect.x - diagramPadding.left; // Calculate real rect X position const actualRectY = rect.y - diagramPadding.top; // Calculate real rect Y position }

    We need to properly align all of the sizes depending on our UI padding and diagram padding.

    As a next step we will extract previous position and previous scale.

    ... const prevPosition = diagram.position.copy(); const prevScale = diagram.scale; ...

    Now we can calculate new values

    ... const newScale = Math.min( options?.scale || Infinity, actualViewportWidth / actualRectWidth ); const freeVerticalSpace = actualViewportHeight / newScale - actualRectHeight; const freeHorizontalSpace = actualViewportWidth / newScale - actualRectWidth; const newX = actualRectX - uiPadding.left / newScale - freeHorizontalSpace / 2; const newY = actualRectY - uiPadding.top / newScale - freeVerticalSpace / 2; const newPosition = new go.Point(newX, newY); ...

    Firstly we calculate new scale. We can either take it from the passed prop if there is a need of some specific zoom level or we are disabling zooming in and out. If scale is not passed we take Infinity so it will be always greater than second argument which is the actual width of our viewport divided by actual rect width.

    Thanks to new scale we can calculate left horizontal and vertical space which will be used to align new document position so our element will be centered in both axes.

    Then we calculate new position of the document getting our actual rect x and y, minus scaled properly ui padding and moved by the half of the left space so we ensure that our rect will be centered in left space in the middle.

    As a last step we just need to run the animation

    ... return new Promise<void>((resolve) => { const animation = new go.Animation(); animation.duration = options?.duration || 500; animation.easing = options?.easing || go.Animation.EaseInOutQuad; animation.add(diagram, "position", prevPosition, newPosition); animation.add(diagram, "scale", prevScale, newScale); animation.finished = () => { window.requestAnimationFrame(() => resolve()); }; animation.start(); });

    We are creating promise which is resolved when the animation finishes. Thanks to that we can await our zoom method if we want to for example firstly zoom to node and then highlight or do something with it.

    How to use it?

    If we want now to call our method we just need to call it with proper properties as in example below:

    zoomToRect(diagram, node.actualBounds, { uiElements: [ { element: document.getElementById("toolbar")!, side: "top" }, { element: document.getElementById("inspector-sidebar")!, side: "right", }, { element: document.getElementById("zoom-controls")!, side: "bottom", }, ], });

    Above code will zoom to the node and taking into account some of the HTML UI elements.

    If we would like to have zoomToFit() functionality we can just pass proper rect to our custom zoomToRect() method as below.

    export const zoomToFit = (diagram: go.Diagram, options?: ZoomToRectOptions) => { if (!diagram) { return new go.Rect(0, 0); } const visibleNodes = diagram.nodes.filter((node) => node.isVisible()); const bounds = diagram.computePartsBounds(visibleNodes); zoomToRect(diagram, bounds, options); }; Result

    gif-2.gif

    Summary

    This enhanced zoom functionality provides a significant improvement over GoJS's built-in zoom methods. By implementing smooth animations, proper UI element handling, and customizable options, we've created a more polished and user-friendly zoom experience. The solution is flexible enough to handle both single-node focusing, full diagram fitting or any other custom rectangle.

    The code itself is pretty simple which enable modifying it and aligning for your needs any way you want.

  • Lets talk about ML (AI)

    1 Topics
    1 Posts
    PMarcinP

    Based on interviews and a survey of diagrammatic experts - we have prepared a report.
    Direct link here.
    If you have any suggestions or questions - please write boldly

    de0f65a8-dcb8-4940-8e4f-f5cd0c8f39a6-image.png