首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在下一个js中实现力有向图

在下一个js中实现力有向图
EN

Stack Overflow用户
提问于 2022-04-08 19:28:22
回答 1查看 713关注 0票数 0

我试图创建一个force-directed graph来映射机构中课程之间的交互作用。在我的前端使用Next JS + TypeScript。

已经尝试过几次使用react-flowdagrevis-network来绘制这幅图,但是正在得到一个window : undefined错误,或者是我定义的框内没有强制指向的节点的该死的对齐。

在我立即开始实现d3-force之前,请有人推荐其他解决方案吗?

下面是我的节点和边缘的样子:

下面是我对reactflow & dagre的尝试:

代码语言:javascript
复制
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中的尝试:(注意:我在使用它时稍微修改了一些边,使其具有从-到而不是源-目标)

代码语言:javascript
复制
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的尝试

代码语言:javascript
复制
import React, { ReactNode, useEffect, useState } from "react";
import ReactDOM from "react-dom";
import { UndirectedGraph } from "graphology";
import erdosRenyi from "graphology-generators/random/erdos-renyi";
import randomLayout from "graphology-layout/random";
import chroma from "chroma-js";

import { Attributes } from "graphology-types";
import { ControlsContainer, ForceAtlasControl, SearchControl, SigmaContainer, useLoadGraph, useRegisterEvents, useSetSettings, useSigma, ZoomControl } from "react-sigma-v2/lib/esm";

interface MyCustomGraphProps {
  children?: ReactNode;
}

export const MyCustomGraph: React.FC<MyCustomGraphProps> = ({ children }) => {
  const sigma = useSigma();
  const registerEvents = useRegisterEvents();
  const loadGraph = useLoadGraph();
  const setSettings = useSetSettings();
  const [hoveredNode, setHoveredNode] = useState<any>(null);

  useEffect(() => {
    // Create the graph
    const graph = erdosRenyi(UndirectedGraph, { order: 100, probability: 0.2 });
    randomLayout.assign(graph);
    graph.nodes().forEach(node => {
    graph.mergeNodeAttributes(node, {
        label: "label",
        size: Math.max(4, Math.random() * 10),
        color: chroma.random().hex(),
      });
    });
    loadGraph(graph);

    // Register the events
    registerEvents({
      enterNode: event => setHoveredNode(event.node),
      leaveNode: () => setHoveredNode(null),
    });
  }, []);

  useEffect(() => {
    setSettings({
      nodeReducer: (node, data) => {
        const graph = sigma.getGraph();
        const newData: Attributes = { ...data, highlighted: data.highlighted || false };

        if (hoveredNode) {
          //TODO : add type safety
          if (node === hoveredNode || (graph as any).neighbors(hoveredNode).includes(node)) {
            newData.highlighted = true;
          } else {
            newData.color = "#E2E2E2";
            newData.highlighted = false;
          }
        }
        return newData;
      },
      edgeReducer: (edge, data) => {
        const graph = sigma.getGraph();
        const newData = { ...data, hidden: false };

        //TODO : add type safety
        if (hoveredNode && !(graph as any).extremities(edge).includes(hoveredNode)) {
          newData.hidden = true;
        }
        return newData;
      },
    });
  }, [hoveredNode]);

  return <>{children}</>;
};

ReactDOM.render(
  <React.StrictMode>
    <SigmaContainer>
      <MyCustomGraph />
      <ControlsContainer position={"bottom-right"}>
        <ZoomControl />
        <ForceAtlasControl autoRunFor={2000} />
      </ControlsContainer>
      <ControlsContainer position={"top-right"}>
        <SearchControl />
      </ControlsContainer>
    </SigmaContainer>
  </React.StrictMode>,
  document.getElementById("root"),
);
代码语言:javascript
复制
import { useCourseNodes } from '@hooks/useCourseNodes'
import dynamic from 'next/dynamic';
import React from 'react'
import { useSigma } from 'react-sigma-v2/lib/esm';

const GraphLayoutThree = () => {
  const isBrowser = () => typeof window !== "undefined"
  const { courseList, edgeList } = useCourseNodes()
  const sigma = useSigma();
    if(isBrowser) {
    const SigmaContainer = dynamic(import("react-sigma-v2").then(mod => mod.SigmaContainer), {ssr: false});
    const MyGraph = dynamic(import("./CustomGraph").then(mod => mod.MyCustomGraph), {ssr: false});
    return (
      <SigmaContainer style={{ height: "500px", width: "500px" }} >
        <MyGraph/>
      </SigmaContainer>
    )
  }
  else return (<p>NOT AVAILABLE</p>)
}

export default GraphLayoutThree

下面是我在react-force-graph中的尝试(注意:我在处理这个时稍微修改了一些边,使其具有从-而不是源-目标)。

代码语言:javascript
复制
import dynamic from "next/dynamic";

const GraphLayoutTwo = () => {
  const isBrowser = () => typeof window !== "undefined"
    if(isBrowser) {
    const MyGraph = dynamic(import("./CustomGraphTwo").then(mod => mod.default), {ssr: false});
    return (
        <MyGraph/>
    )
  }
  else return (<p>NOT AVAILABLE</p>)
}

export default GraphLayoutTwo
代码语言:javascript
复制
import dynamic from "next/dynamic";

const GraphLayoutTwo = () => {
  const isBrowser = () => typeof window !== "undefined"
    if(isBrowser) {
    const MyGraph = dynamic(import("./CustomGraphTwo").then(mod => mod.default), {ssr: false});
    return (
        <MyGraph/>
    )
  }
  else return (<p>NOT AVAILABLE</p>)
}

export default GraphLayoutTwo
EN

回答 1

Stack Overflow用户

发布于 2022-04-13 07:56:56

为了实现类似的东西,我们在nextjs应用程序中使用react-graph-vis

如果窗口未定义错误,只需包装组件并使用dynamic导入它。

代码语言:javascript
复制
// components/graph.tsx

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

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

      )
}

然后在你的页面上

代码语言:javascript
复制
// pages/index.ts

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

const Index = () => {

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

}

export default Index;
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71802266

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档