d3.js 在next js中实现强制定向图

rjee0c15  于 2022-11-12  发布在  其他
关注(0)|答案(1)|浏览(205)

我试图创建一个force-directed graph来Map一个机构中课程之间的交互。使用Next JS + TypeScript作为我的前端。
我已经尝试了几次使用react-flowdagrevis-network来绘制这个图表,但是我得到了一个window : undefined错误或者只是节点的该死的对齐没有被强制指向我定义的框内。
在我继续实现d3-force之前,有人能推荐一个替代解决方案吗?
以下是我的节点和边的外观:
下面是我对reactflow & dagre的尝试:

import React, { useCallback, useEffect, useState } from 'react';
import ReactFlow, {
  addEdge,
  useNodesState,
  useEdgesState,
  Edge,
  Node,
  Position,
  ConnectionLineType,
  ReactFlowProvider,
  MiniMap,
  Controls,
  Background,
} from 'react-flow-renderer';
import dagre from 'dagre';
import { NodeData, useCourseNodes } from 'src/hooks/useCourseNodes';
import { useDepartment } from '@contexts/ActiveDepartmentContext';
import {
  useUpdateActiveCourse,
} from '@contexts/ActiveCourseContext';
import { useDrawerOpen, useUpdateDrawerOpen } from '@contexts/DrawerContext';

const dagreGraph = new dagre.graphlib.Graph({directed:true});
dagreGraph.setDefaultEdgeLabel(() => ({}));

const nodeWidth = 10.2;
const nodeHeight = 6.6;

const getLayoutedElements = (
  nodes: Node[],
  edges:Edge[],
) => {
    // const isHorizontal = direction === 'LR';
    dagreGraph.setGraph( {width:900, height:900, nodesep:20, ranker:'longest-path' });

    nodes.forEach((node: Node) => {
      dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight  });
    });

    edges.forEach((edge: Edge) => {
      dagreGraph.setEdge(edge.source, edge.target);
    });

    dagre.layout(dagreGraph);

    nodes.forEach((node) => {
      const nodeWithPosition = dagreGraph.node(node.id);
      // node.targetPosition = isHorizontal ? Position.Left : Position.Top;
      // node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
      node.targetPosition = Position.Top;
      node.sourcePosition = Position.Bottom;

      // We are shifting the dagre node position (anchor=center center) to the top left
      // so it matches the React Flow node anchor point (top left).
      node.position = {
        x: nodeWithPosition.x - nodeWidth / 2,
        y: nodeWithPosition.y - nodeHeight / 2,
      };
      console.log(nodeWithPosition)

      return node;
    })
  return { layoutedNodes:nodes, layoutedEdges:edges };
};

const LayoutFlow = () => {
  const activeDept = useDepartment();
  const setActiveCourse = useUpdateActiveCourse();
  const setDrawerOpen = useUpdateDrawerOpen()
  const drawerOpen = useDrawerOpen();
  const {courseList, edgeList} = useCourseNodes()
  const { layoutedNodes, layoutedEdges } = getLayoutedElements(courseList, edgeList)
  const [nodes, setNodes, onNodesChange] = useNodesState(layoutedNodes);
  const [edges, setEdges,onEdgesChange] = useEdgesState(layoutedEdges);
  console.log(layoutedNodes)

  const onConnect = useCallback(
    (params) =>
      setEdges((eds) =>
        addEdge({ ...params, type: ConnectionLineType.SimpleBezier, animated: true }, eds),
      ),
    [],
  );

  // ? For switching between layouts (horizontal & vertical) for phone & desktop
  // const onLayout = useCallback(
  //   (direction) => {
  //     const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
  //       nodes,
  //       edges,
  //       direction
  //     );

  //     setNodes([...layoutedNodes]);
  //     setEdges([...layoutedEdges]);
  //   },
  //   [nodes, edges]
  // );

  // ? M1 - for force re-rendering react flow graph on state change - https://github.com/wbkd/react-flow/issues/1168
  // ? M2 - (Applied currently in useEffect block below)for force re-rendering react flow graph on state change - https://github.com/wbkd/react-flow/issues/1168
  useEffect(() => {
    const {layoutedNodes, layoutedEdges} = getLayoutedElements(courseList, edgeList)
    setNodes([...layoutedNodes]);
    setEdges([...layoutedEdges]);
  }, [activeDept, drawerOpen]);
  return (
    <div style={{ width: '100%', height: '100%' }} className="layoutflow">
      <ReactFlowProvider>
        <ReactFlow
          nodes={nodes}
          edges={edges}
          onNodesChange={onNodesChange}
          onEdgesChange={onEdgesChange}
          onConnect={onConnect}
          onNodeClick={(e: React.MouseEvent, node: Node<NodeData>) => {
            e.preventDefault();
            // created a copy of the node since we're only deleting the "label" property from the node object to conveniently map the rest of the data to the "data" property of the active course
            const nodeCopy = JSON.parse(JSON.stringify(node))
            const { data } = nodeCopy;
            const { label } = data
            delete data.label
            setActiveCourse({
              courseId: label,
              data
            });
            setDrawerOpen(true);
          }}
          connectionLineType={ConnectionLineType.SimpleBezier}
          fitView
        >
          <MiniMap />
          <Controls />
          {/* <Background /> */}
        </ReactFlow>
      </ReactFlowProvider>
      <div className="controls">
        {/* <button onClick={() => onLayout('TB')}>vertical layout</button>
        <button onClick={() => onLayout('LR')}>horizontal layout</button> */}
      </div>
    </div>
  );
};

export default LayoutFlow;

下面是我对vis-network的尝试:(注意:在使用此方法时,我稍微修改了边,使其具有from-to而不是source-target)

import { useCourseNodes } from "@hooks/useCourseNodes";
import React, { useEffect, useRef } from "react";
import { Network } from "vis-network";

const GraphLayoutFour: React.FC = () => {
  const {courseList:nodes, edgeList:edges} = useCourseNodes()
    // Create a ref to provide DOM access
    const visJsRef = useRef<HTMLDivElement>(null);
    useEffect(() => {
        const network =
            visJsRef.current &&
            new Network(visJsRef.current, { nodes, edges } );
        // Use `network` here to configure events, etc
    }, [visJsRef, nodes, edges]);
    return typeof window !== "undefined" ? <div ref={visJsRef} /> : <p>NOT AVAILABLE</p>;
};

export default GraphLayoutFour;

下面是我对react-sigma的尝试
第一个
下面是我对react-force-graph的尝试(注:我在使用此方法时对边进行了轻微修改,使其具有from-to而不是source-target)
第一个

ia2d9nvy

ia2d9nvy1#

为了实现类似的功能,我们在一个nextjs应用程序中使用react-graph-vis
如果出现window is not defined错误,只需 Package 组件并使用dynamic导入即可

// components/graph.tsx

export const Graph = ({data, options, events, ...props}) => {

      return (
              <GraphVis
                graph={transformData(data)}
                options={options}
                events={events}
              />

      )
}

然后在页面中

// pages/index.ts

const Graph = dynamic(() => (import("../components/graph").then(cmp => cmp.Graph)), { ssr: false })

const Index = () => {

    return (
          <>
             <Graph data={...} .... />
          </>
    )

}

export default Index;

相关问题