te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>javascript - Is it possible to shape reactflow edges by dragging them? - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Is it possible to shape reactflow edges by dragging them? - Stack Overflow

programmeradmin4浏览0评论

When we draw a line on draw.io it provides a feature to shape the line by providing a handle in the middle that can be dragged to adjust the shape of the line. When that handle is dragged two straight lines are created and two more handles appear in the middle of those lines that can also be stretched and so on. When we bring those handles back to their original position the lines began to merge back and handles start disappearing. And all this happens very smoothly. I want to achieve a similar feat in my application and I've built it based on reactflow library. Is it possible to achieve that in reactflow?

I asked for help from chat gpt and it suggested to proceed with something like this-

import React from 'react';
import { Flow, Controls, Position, ReactFlowProvider } from 'react-flow-renderer';

const CustomEdgeHandle = ({ id, position }) => (
  <div
    id={id}
    style={{
      position: 'absolute',
      background: 'red',
      width: 10,
      height: 10,
      borderRadius: '50%',
      transform: `translate(${position.x - 5}px, ${position.y - 5}px)`,
      cursor: 'pointer',
    }}
  />
);

const CustomEdge = ({ id, sourceX, sourceY, targetX, targetY, ...rest }) => {
  const midpoint = {
    x: (sourceX + targetX) / 2,
    y: (sourceY + targetY) / 2,
  };

  return (
    <>
      <div
        style={{
          position: 'absolute',
          transform: `translate(${midpoint.x}px, ${midpoint.y}px)`,
        }}
      >
        <CustomEdgeHandle id={`handle-${id}`} position={midpoint} />
      </div>
      <div
        style={{
          position: 'absolute',
          pointerEvents: 'none',
        }}
      >
        <div
          style={{
            position: 'absolute',
            border: '1px solid #ddd',
            background: '#fff',
            transform: `translate(${sourceX}px, ${sourceY}px)`,
            width: Math.abs(targetX - sourceX),
            height: Math.abs(targetY - sourceY),
          }}
        />
      </div>
    </>
  );
};

const CustomFlow = () => {
  const elements = [
    { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 100, y: 100 } },
    { id: '2', type: 'output', data: { label: 'Node 2' }, position: { x: 300, y: 100 } },
    { id: 'e1-2', source: '1', target: '2', animated: true, arrowHeadType: 'arrowclosed', 
type: 'customEdge' },
  ];

  const nodeTypes = {
    customEdge: CustomEdge,
  };

  return (
    <ReactFlowProvider>
      <div style={{ height: '400px', border: '1px solid #ddd' }}>
        <Flow elements={elements} nodeTypes={nodeTypes} />
        <Controls />
      </div>
    </ReactFlowProvider>
  );
};

export default CustomFlow;

When we draw a line on draw.io it provides a feature to shape the line by providing a handle in the middle that can be dragged to adjust the shape of the line. When that handle is dragged two straight lines are created and two more handles appear in the middle of those lines that can also be stretched and so on. When we bring those handles back to their original position the lines began to merge back and handles start disappearing. And all this happens very smoothly. I want to achieve a similar feat in my application and I've built it based on reactflow library. Is it possible to achieve that in reactflow?

I asked for help from chat gpt and it suggested to proceed with something like this-

import React from 'react';
import { Flow, Controls, Position, ReactFlowProvider } from 'react-flow-renderer';

const CustomEdgeHandle = ({ id, position }) => (
  <div
    id={id}
    style={{
      position: 'absolute',
      background: 'red',
      width: 10,
      height: 10,
      borderRadius: '50%',
      transform: `translate(${position.x - 5}px, ${position.y - 5}px)`,
      cursor: 'pointer',
    }}
  />
);

const CustomEdge = ({ id, sourceX, sourceY, targetX, targetY, ...rest }) => {
  const midpoint = {
    x: (sourceX + targetX) / 2,
    y: (sourceY + targetY) / 2,
  };

  return (
    <>
      <div
        style={{
          position: 'absolute',
          transform: `translate(${midpoint.x}px, ${midpoint.y}px)`,
        }}
      >
        <CustomEdgeHandle id={`handle-${id}`} position={midpoint} />
      </div>
      <div
        style={{
          position: 'absolute',
          pointerEvents: 'none',
        }}
      >
        <div
          style={{
            position: 'absolute',
            border: '1px solid #ddd',
            background: '#fff',
            transform: `translate(${sourceX}px, ${sourceY}px)`,
            width: Math.abs(targetX - sourceX),
            height: Math.abs(targetY - sourceY),
          }}
        />
      </div>
    </>
  );
};

const CustomFlow = () => {
  const elements = [
    { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 100, y: 100 } },
    { id: '2', type: 'output', data: { label: 'Node 2' }, position: { x: 300, y: 100 } },
    { id: 'e1-2', source: '1', target: '2', animated: true, arrowHeadType: 'arrowclosed', 
type: 'customEdge' },
  ];

  const nodeTypes = {
    customEdge: CustomEdge,
  };

  return (
    <ReactFlowProvider>
      <div style={{ height: '400px', border: '1px solid #ddd' }}>
        <Flow elements={elements} nodeTypes={nodeTypes} />
        <Controls />
      </div>
    </ReactFlowProvider>
  );
};

export default CustomFlow;
Share Improve this question edited Jan 17, 2024 at 10:41 Mike24 255 bronze badges asked Jan 17, 2024 at 9:00 Alok AbhijeetAlok Abhijeet 911 silver badge3 bronze badges 4
  • I am looking for the same thing. – Christian Commented Jan 18, 2024 at 16:11
  • Sadly, No -- it's a feature that has been asked for over and over, but they have no plans of adding it (as far as I've heard). Your best bet is implementing it with a custom edge, as was suggested by ChatGPT, if you choose to stick with React Flow. – Steve G Commented Jan 21, 2024 at 5:27
  • 1 @AlokAbhijeet if you consider Elna Haim's answer as correct please select it as the correct answer using the green check under the answer. The bounty is about to expire and if you don't chose a selected answer she/he may only get half of the bounty. Thanks :) – Daniel Cruz Commented Mar 8, 2024 at 19:19
  • FWIW: ReactFlow (now called xyzFlow) now has an example implementation of this on their [paid] Pro site, mentioned here in their blog: xyflow./blog/… – Steve G Commented May 1, 2024 at 1:12
Add a ment  | 

5 Answers 5

Reset to default 6 +250

I created a simple implementation of what you are looking for. To get to the point of having the exact behavior like draw.io you will need to develop the base code (I can help with it - it is achievable, but requires more work).

I will try to break down what I did so you will have a good understanding, and will be easier to develop the code more.

Sandbox example: https://codesandbox.io/p/sandbox/floral-framework-st6npy

(EDIT) - Full implementation: https://codesandbox.io/p/sandbox/floral-framework-forked-2ytjqc (Still need some extra work in order to cover all cases, but the idea how to get what you are looking for is there)

First we will start with creating a custom edge

Our initial-elements.js will look like this (creating 3 nodes and 2 edges):

export const nodes = [
  {
    id: "1",
    type: "input",
    data: {
      label: "Input Node",
    },
    position: { x: 250, y: 0 },
  },
  {
    id: "2",
    data: {
      label: "Default Node",
    },
    position: { x: 100, y: 100 },
  },
  {
    id: "3",
    type: "output",
    data: {
      label: "Output Node",
    },
    position: { x: 400, y: 100 },
  },
];

export const edges = [
  {
    id: "edge-button",
    source: "1",
    target: "2",
    type: "buttonedge",
  },
  {
    id: "edge-button-2",
    source: "1",
    target: "3",
    type: "buttonedge",
  },
];

Now lets create the custom edge (im breaking down the code to explain it, at the bottom i will put the all custom edge code together)

  1. out side the ponent we will have this function - i copied this function from the lib, probably you can import it directly from there. i modiefied the return - to return object.

This function is returning the edge center.

//copied from reactflow lib - probably you can import this util directly from
function getEdgeCenter({ sourceX, sourceY, targetX, targetY }) {
  const xOffset = Math.abs(targetX - sourceX) / 2;
  const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;

  const yOffset = Math.abs(targetY - sourceY) / 2;
  const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;

  return { centerX, centerY, xOffset, yOffset };
}
  1. Then out side the ponent we will have two varaibles in order to store some presist date ( we will use it later inside the ponent - we need it out side to prevent re-rendering)
// just to store some data outside the function so we can avoid re-rendering
let storeYVal = {};
// getting zoom from store - using it to calc mouse movement.
let zoom = 0;
  1. inside the ponent we will get the edge center - we will use it later to place the circle on the edge's center. (the function's arg ing from the ponent props)
const { centerX, centerY } = getEdgeCenter({
    sourceX,
    sourceY,
    targetX,
    targetY,
  });
  1. adding drag and drop (I'm using d3-drag lib, but you can use native html drag and drop) - and calculating the Y point - in order to modify the edge path - we are using the var from above. later we will use this var to help us to regenerate the edge path.
 const [labelPointY, setLabelPointY] = useState(storeYVal?.[id] || 0);

  useStore((state) => {
    zoom = state.transform[2];
  });

  const edgeRef = useRef(null);
  useEffect(() => {
    if (edgeRef.current) {
      const d3Selection = select(edgeRef.current);

      d3Selection.call(
        drag().on("drag", (e) => {
          setLabelPointY((prev) => prev - e.dy / zoom);
          // Storing previous change in a simple way to not reset on re-render.
          // you can improve it by having context.
          // or you can try to use useStore hook.
          storeYVal[id] = (storeYVal[id] || 0) - e.dy / zoom;
        })
      );
    }
  }, [edgeRef]);
  1. We will create and fire our path generator (orthogonal edge) function (later you can modify this funciton to have more plex logic like modifying the x point - adding more points - generating any kind of path you want.

The function args ing from the ponent props

// creating a new funciton that generate path to the edge (in my opinion easier to hanlde).
    function generateOrthogonalEdgePath(
    startX,
    startY,
    endX,
    endY,
    padding,
    Hoffset = 0
  ) {
    // Calculate horizontal and vertical distances
    var dx = endX - startX;
    var dy = endY - startY;

    // Create a path string
    var path = "M" + startX + "," + startY + " ";

    // Horizontal then vertical - calc the Horizontal offset in order to create the edge change.
    path += "V" + (startY + dy / 2 - Hoffset) + " ";
    path += "H" + (endX - padding) + " ";
    path += "V" + endY + "";

    return path;
  }
  // generating the path
  var path = generateOrthogonalEdgePath(
    sourceX,
    sourceY,
    targetX,
    targetY,
    0,
    labelPointY
  );
  1. Now we can actually generate the edge it self - including a circle on the cneter of the path that can change the path horizontal (y point) line. (im using inline styling only to show you the values, but of course you should put it inside css file - beside the left and top of course)
return (
    <>
      <BaseEdge key={id} path={[path]} style={style} />
      <EdgeLabelRenderer>
        <div
          ref={edgeRef}
          style={{
            position: "absolute",
            left: `${centerX - 5}px`,
            top: `${centerY - 5 - labelPointY}px`,
            zIndex: 9999,
            opacity: 1,
            width: "10px",
            height: "10px",
            pointerEvents: "all",
            borderRadius: "50%",
            background: "black",
            cursor: "pointer",
          }}
        />
      </EdgeLabelRenderer>
    </>
  );

Here is the all custom edge code together:

import { useEffect, useRef, useState } from "react";
import { BaseEdge, EdgeLabelRenderer, useStore } from "reactflow";

import { drag } from "d3-drag";
import { select } from "d3-selection";

//copied from reactflow lib - probably you can import this util directly from
function getEdgeCenter({ sourceX, sourceY, targetX, targetY }) {
  const xOffset = Math.abs(targetX - sourceX) / 2;
  const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;

  const yOffset = Math.abs(targetY - sourceY) / 2;
  const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;

  return { centerX, centerY, xOffset, yOffset };
}

// just to store some data outside the function so we can avoid re-rendering
let storeYVal = {};
// getting zoom from store - using it to calc mouse movement.
let zoom = 0;

export default CustomEdge = ({
  id,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition,
  targetPosition,
  markerEnd,
  style = {},
  ...rest
}) => {
  const { centerX, centerY } = getEdgeCenter({
    sourceX,
    sourceY,
    targetX,
    targetY,
  });
  const [labelPointY, setLabelPointY] = useState(storeYVal?.[id] || 0);

  useStore((state) => {
    zoom = state.transform[2];
  });

  const edgeRef = useRef(null);
  useEffect(() => {
    if (edgeRef.current) {
      const d3Selection = select(edgeRef.current);

      d3Selection.call(
        drag().on("drag", (e) => {
          setLabelPointY((prev) => prev - e.dy / zoom);
          // Storing previous change in a simple way to not reset on re-render.
          // you can improve it by having context.
          // or you can try to use useStore hook.
          storeYVal[id] = (storeYVal[id] || 0) - e.dy / zoom;
        })
      );
    }
  }, [edgeRef]);

  // creating a new funciton that generate path to the edge (in my opinion easier to hanlde).
  function generateOrthogonalEdgePath(
    startX,
    startY,
    endX,
    endY,
    padding,
    Hoffset = 0
  ) {
    // Calculate horizontal and vertical distances
    var dx = endX - startX;
    var dy = endY - startY;

    // Calculate the absolute values of horizontal and vertical distances
    var adx = Math.abs(dx);
    var ady = Math.abs(dy);

    // Create a path string
    var path = "M" + startX + "," + startY + " ";

    if (adx > ady) {
      // Horizontal then vertical - calc the Horizontal offset in order to create the edge change.
      path += "V" + (startY + dy / 2 - Hoffset) + " ";
      path += "H" + (endX - padding) + " ";
      path += "V" + endY + "";
    } else {
      // Vertical then horizontal
      path += "H" + (startX + dx / 2 - padding) + " ";
      path += "V" + (endY - padding) + " ";
      path += "H" + endX + " ";
    }

    return path;
  }
  // generating the path
  var path = generateOrthogonalEdgePath(
    sourceX,
    sourceY,
    targetX,
    targetY,
    0,
    labelPointY
  );

  return (
    <>
      <BaseEdge key={id} path={[path]} style={style} />
      <EdgeLabelRenderer>
        <div
          ref={edgeRef}
          style={{
            position: "absolute",
            left: `${centerX - 5}px`,
            top: `${centerY - 5 - labelPointY}px`,
            zIndex: 9999,
            opacity: 1,
            width: "10px",
            height: "10px",
            pointerEvents: "all",
            borderRadius: "50%",
            background: "black",
            cursor: "pointer",
          }}
        />
      </EdgeLabelRenderer>
    </>
  );
};
  1. Last thing is to create the flow and put it all together:
import React, { useCallback } from "react";
import ReactFlow, {
  addEdge,
  MiniMap,
  Controls,
  Background,
  useNodesState,
  useEdgesState,
  ConnectionMode,
} from "reactflow";

import {
  nodes as initialNodes,
  edges as initialEdges,
} from "./initial-elements";

import "reactflow/dist/style.css";
import ButtonEdge from "./ButtonEdge";

const onInit = (reactFlowInstance) =>
  console.log("flow loaded:", reactFlowInstance);

const OverviewFlow = () => {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
  const onConnect = useCallback(
    (params) => setEdges((eds) => addEdge(params, eds)),
    []
  );

  // we are using a bit of a shortcut here to adjust the edge type
  // this could also be done with a custom edge for example
  const edgesWithUpdatedTypes = edges.map((edge) => {
    if (edge.sourceHandle) {
      const edgeType = nodes.find((node) => node.type === "custom").data
        .selects[edge.sourceHandle];
      edge.type = edgeType;
    }

    return edge;
  });

  const edgeTypes = {
    buttonedge: ButtonEdge,
  };

  return (
    <ReactFlow
      nodes={nodes}
      edges={edgesWithUpdatedTypes}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
      onInit={onInit}
      edgeTypes={edgeTypes}
      fitView
      attributionPosition="top-right"
    >
      <Controls />
      <Background color="#aaa" gap={16} />
    </ReactFlow>
  );
};

export default OverviewFlow;

I was using the reactFlow lib doc as reference. here is the link for the doc i used as reference: https://reactflow.dev/examples/edges/custom-edges

And here is the codeSandBox example: https://codesandbox.io/p/sandbox/floral-framework-st6npy

As i said at the beggining, This is a simple version of what you are trying to achieve. with a bit more code - you can have exactly what you are looking for.

what you will need to do next:

  • adding functionality to add more circles on the edge.
  • calc X / Y position of each point using the drag and drop on drag event and store it in an object or array of points.
  • re-generate the path based on other circles position (you will need probably array of points - and in the path generator function to loop and create the path based on the points position and type (Y - horizontal or X - vertical).

You can also add styling as you want - opacity on hover, changing cursor on drag....

** When i will have more time, I will implement the whole logic - exactly like deaw.io - if i do so, I will edit my answer with a new codesandbox example**


EDIT: here is a full implementation. the code is abit dirty/messy/duplicated. If needed i can improve it, clean it up and make a full implementation, without limit on extra point.

But this is the idea how to achieve what you are looking for. https://codesandbox.io/p/sandbox/floral-framework-forked-2ytjqc

You can do this with custom edges

I created an edge that allows you to insert new handle points by clicking the edge, remove them with right click and drag them around to acodate the edge as you want

Interactive example + code: https://codesandbox.io/p/sandbox/goofy-wave-hp25zj

Instructions:

  • click on the edge (line) to add a new handler
  • dragg the handlers to change the shape of the edge
  • right click to delete a handler

Explanation:

The concept for my solution is simple, you create segments that behave like edges that conect between them by handlers, this way we can reuse the path calculation logic of reacflow and use the different edge types that it offers

You can read more about the implementation on the sandbox, because the code is too long for me to put it here, but here is the gist of it

I created a PositionableEdge ponent that has a list of positionHandlers with its coordinates, and creates a list of edgeSegments between them. Each the source node is taken as the first handler and the destination node as the last, and each segment conects the nth handler with the n+1 handler like so

  for (let i = 0; i < edgeSegmentsCount; i++) {
    let segmentSourceX, segmentSourceY, segmentTargetX, segmentTargetY;

    if (i === 0) {
      segmentSourceX = sourceX;
      segmentSourceY = sourceY;
    } else {
      const handler = positionHandlers[i - 1];
      segmentSourceX = handler.x;
      segmentSourceY = handler.y;
    }

    if (i === edgeSegmentsCount - 1) {
      segmentTargetX = targetX;
      segmentTargetY = targetY;
    } else {
      const handler = positionHandlers[i];
      segmentTargetX = handler.x;
      segmentTargetY = handler.y;
    }
  }

Each handler is rendered as just a button that when dragged updates its own coordinates. The between the segments the line is calculated like so

const [edgePath, labelX, labelY] = pathFunction({
  sourceX: segmentSourceX,
  sourceY: segmentSourceY,
  sourcePosition,
  targetX: segmentTargetX,
  targetY: segmentTargetY,
  targetPosition,
});

By default the BaseEdge element does not expose events to interact with it, so I created a version of it called ClickableBaseEdge.

const ClickableBaseEdge = ({
  id,
  path,
  style,
  markerEnd,
  markerStart,
  interactionWidth = 20,
  onClick,
}) => {
  return (
    <>
      <path
        id={id}
        style={style}
        d={path}
        fill="none"
        className="react-flow__edge-path"
        markerEnd={markerEnd}
        markerStart={markerStart}
      />
      {interactionWidth && (
        <path
          d={path}
          fill="none"
          strokeOpacity={0}
          strokeWidth={interactionWidth}
          className="react-flow__edge-interaction"
          onClick={onClick}
        />
      )}
    </>
  );
};

This allows you to interact with the edge segment itself and create more segments inside an already existing one, thus giving you the flexibility of having as many handlers as you need, and you can create them on the go. Like so

To avoid any extra dependencies I used the events of mousemove, mousedown and mouse up, and for better user experience, when you mousedown, the area for mouse move grows so the mouse move event is only captured when the mouse has been pressed. The mouse down activates a handler, and the mouse move moves it when its activated like so

So importing the ponent you can initialize your edges like this

const initialEdges: Edge[] = [
    {
      id: "SmoothStepEdge",
      source: "SmoothStepA",
      target: "SmoothStepB",
      type: "positionableedge", // use this type
      data: {
        type: "smoothstep", // this way you can pass the edge type, be it straight, smoothstep or default
        positionHandlers: [
          {
            x: 150.0, // these are the default handlers
            y: 100.0, // leave it empty and it will behave like a normal edge
          },
          {
            x: 250.0,
            y: 150.0,
          },
        ],
      },
    },
]

then just add the type to your initialization. The final code would look something like this:

import React, { useCallback, useState } from "react";
import ReactFlow, {
  useNodesState,
  useEdgesState,
  addEdge,
  MiniMap,
  Controls,
  Background,
  Node,
  Edge,
  Position,
  ConnectionMode,
  MarkerType,
} from "reactflow";
import "reactflow/dist/style.css";

import PositionableEdge from "./PositionableEdge";

const EdgesFlow = () => {
  const initialNodes: Node[] = [
    // Smooth step
    {
      id: "SmoothStepA",
      type: "input",
      data: { label: "SmoothStepA" },
      position: { x: 125, y: 0 },
    },
    {
      id: "SmoothStepB",
      type: "output",
      data: { label: "SmoothStepB" },
      position: { x: 125, y: 200 },
    },
    // bezier
    {
      id: "BezierA",
      type: "input",
      data: { label: "BezierA" },
      position: { x: 325, y: 0 },
    },
    {
      id: "BezierB",
      type: "output",
      data: { label: "BezierB" },
      position: { x: 325, y: 200 },
    },
  ];

  const initialEdges: Edge[] = [
    {
      id: "SmoothStepEdge",
      source: "SmoothStepA",
      target: "SmoothStepB",
      type: "positionableedge",
      data: {
        type: "smoothstep",
        positionHandlers: [
          {
            x: 150.0,
            y: 100.0,
          },
          {
            x: 250.0,
            y: 150.0,
          },
        ],
      },
    },
    {
      id: "BezierEdge",
      source: "BezierA",
      target: "BezierB",
      type: "positionableedge",
      data: {
        type: "default",
        positionHandlers: [
          {
            x: 350.0,
            y: 100.0,
          },
          {
            x: 450.0,
            y: 150.0,
          },
        ],
      },
    },
  ];
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const edgeTypes = {
    positionableedge: PositionableEdge,
  };

  const nodeTypes = {};

  const onConnect = useCallback(
    (params) => setEdges((eds) => addEdge(params, eds)),
    []
  );

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      onConnect={onConnect}
      snapToGrid={false}
      edgeTypes={edgeTypes}
      nodeTypes={nodeTypes}
      fitView
      attributionPosition="top-right"
      connectionMode={ConnectionMode.Loose}
    >
      <Controls />
      <Background />
    </ReactFlow>
  );
};

export default EdgesFlow;

and you're good to go! You can read the full code or see it working here: https://codesandbox.io/p/sandbox/goofy-wave-hp25zj

I hope it helps, let me know if you have any questions

I am unable to ment, you might be looking for slpines: https://github./linwe2012/Spline

ProcessingJS may be able to help with that. If you use the mouseX and mouseY auto-updating variables and check for .isMouseDown in a certain location, it may be possible.

I have updated the (really great) example provided by daniel-cruz to get it working with current reactflow version. See it here: https://codesandbox.io/p/sandbox/pensive-cohen-lv4hfx

There are also some other changes:

  • Hanlders have always the same color as edge, size is the strokeWidth + 2
  • Right mouse click deletes the edge (if clicked on edge) or deletes the handler (if clicked on handler)
  • For some reason reactFlowInstance.setEdges() by passing edges in a function does not work correctly, therefore I changed it to bination reactFlowInstance.getEdges() / reactFlowInstance.setEdges(new_edges).

P.S. I am very new to react/web app development (just for 2 days now), it means some parts probably can be made much better.

Example for editable edge in reactflow

发布评论

评论列表(0)

  1. 暂无评论