sim51 / react-sigma

Sigma React component
https://sim51.github.io/react-sigma/
MIT License
168 stars 27 forks source link

document how to use a layout #73

Open dcsan opened 1 month ago

dcsan commented 1 month ago

I'm trying to create a graph with my own data rather than the sample static data, but the docs are very hard to follow. trying to use an auto-layout.

this page doesn't really help https://sim51.github.io/react-sigma/docs/api#layout-modules

eg const {positions, assign} = useLayoutCircular(...);

what do i pass to this? how do i use it?

the nearest example I can find is this:

https://github.com/sim51/react-sigma/blob/e6528da2c1fa6f11413366f05bd6fd547f940f03/packages/storybook/stories/LayoutFA2.tsx

but it depends on a multitude of other parts. first error:

Error: Sigma: could not find a valid position (x, y) for node "apple". All your nodes must have a number "x" and "y". Maybe your forgot to apply a layout or your "nodeReducer" is not returning the correct data?

so I think I need to implement some type of reducer?

I found some code here: https://github.com/sim51/react-sigma/blob/main/packages/storybook/stories/common/SampleGraph.tsx#L7

but it's a lot to conform to extract that reducer to use. functions seem to be missing eg:

const graph = sigma.getGraph(); // Property getGraph does not exist on type

so i need to find some interfaces maybe from https://github.com/sim51/react-sigma/blob/main/packages/storybook/stories/MultiDirectedGraph.tsx#L11

there must be a simpler way to use a layout with a graph?

dcsan commented 1 month ago

actually even adding random values I still get an error

Error: Sigma: could not find a valid position (x, y) for node "apple silicon". All your nodes must have a number "x" and "y". Maybe your forgot to apply a layout or your "nodeReducer" is not returning the correct data?

where the data clearly does have an x and y

{
    "options": {
        "type": "mixed",
        "multi": false,
        "allowSelfLoops": true
    },
    "attributes": {},
    "nodes": [
        {
            "key": "apple silicon",
            "x": 0.2415696726063068,
            "y": 0.027308567567481346
        },

...

    "edges": [
        {
            "key": "geid_125_0",
            "source": "apple silicon",
            "target": "industry-leading neural engines",
            "attributes": {
                "type": "integrates",
                "weight": 1
            }
        },

 ... // etc

}

/**
 * https://github.com/sim51/react-sigma/blob/e6528da2c1fa6f11413366f05bd6fd547f940f03/packages/storybook/stories/LayoutFA2.tsx
 */

import { CSSProperties, useEffect } from "react";
import { SigmaContainer, useLoadGraph } from "@react-sigma/core";
import "@react-sigma/core/lib/react-sigma.min.css";

// import { useDispatch, useSelector } from "react-redux";
// import { selectStory, selectChapter, setStory } from "../../redux/semSlice"

import { SemData } from "../../types/sharedTypes/SemTypes";
import Graph from "graphology";
import { useLoaderData } from "react-router-dom";

const sigmaStyle: CSSProperties = {
  right: '0px',
  top: '0px',
  position: "absolute",
  height: "500px",
  width: "500px"
};

// Component that load the graph
export const LoadGraph = () => {
  // const dispatch = useDispatch()
  const semData = useLoaderData() as SemData
  const story = semData
  // const story = useSelector(selectStory)
  // if (semData) {
  //   dispatch(setStory(semData))
  // }
  // const story: SemData | undefined = useSelector(selectStory)
  // if (!story?.graph) return null;

  const loadGraph = useLoadGraph();
  useEffect(() => {
    console.log('LoadGraph / story', story)

    const graphData = { ...story?.graph } // unfreeze
    console.log('graphData', graphData)

    const graph = new Graph();

    graphData.nodes.forEach((node) => {
      const n = ({ ...node, x: Math.random(), y: Math.random() });
      console.log('n', n)
      graph.addNode(n);
    })
    console.log('graph.json', graph.toJSON())

    loadGraph(graph);
  }, [story, loadGraph]);
  return null;
};

// Component that display the graph
export const SemGraphBase = () => {

  return (
    <SigmaContainer style={sigmaStyle}>
      <LoadGraph />
    </SigmaContainer>
  );
};
dcsan commented 1 month ago

added to S/O too

https://stackoverflow.com/questions/78805061/react-sigma-sigmajs-example-using-a-force-layout

sim51 commented 1 month ago

Layout hooks doesn't need parameters, they directly used the graph of the sigma instance. The best example is this one : https://github.com/sim51/react-sigma/blob/main/packages/storybook/stories/LayoutCircular.tsx

If I take your example, it becomes something like that :


// Component that load the graph
export const LoadGraph = () => {
  // Hook for the layout
  const { positions, assign } = useLayoutCircular();
  // Hook to load the graph
  const loadGraph = useLoadGraph();

  const semData = useLoaderData() as SemData
  const story = semData

  useEffect(() => {
    console.log('LoadGraph / story', story)

    const graphData = { ...story?.graph } // unfreeze
    const graph = new Graph();
    graphData.nodes.forEach((node) => {
      const n = ({ ...node, x: Math.random(), y: Math.random() });
      graph.addNode(n);
    })
    // Load the graph in sigma
    loadGraph(graph);
    // Apply the layout
    assign();

  }, [story, loadGraph]);

  return null;
};
sim51 commented 1 month ago

BTW, you don't use the graph.addNode correctly. Check the graphology doc about it : https://graphology.github.io/mutation.html#addnode

You should replace your code by this :

  graphData.nodes.forEach((node) => {
      const n = ({ ...node, x: Math.random(), y: Math.random() });
      graph.addNode(n.key, n);
    })
dcsan commented 1 month ago

great thanks for your help.

re data format / addNode, yes i found that in the end. I'm using graphology to generate the data and storing it but i guess the client loader doesn't load the graph.toJSON() format, i have to walk through and add each node.

re animation So the circularLayout is for a static 'one shot' layout. the non-obvious part is that you can just call assign() with no params, and some magic modifies the positions, I guess using the context api?

the docs show there are some unknown ... params passed? perhaps this is only for layouts that need animations?

const { positions, assign } = useLayoutForce(...);

When I try to switch to useLayoutForce there is no layout applied. So I guess "it depends" which layout.

I guess this type of layout needs the core and an animation worker, and perhaps another nested component? which I can't see any example or clear docs for.

Would you have an example using a forceLayout or any of those?

dcsan commented 1 month ago

nvm I got it to work something like this:

const Fa2: FC = () => {
  const { start, kill } = useWorkerLayoutForceAtlas2({ settings: { slowDown: 10 } });

  useEffect(() => {
    // start FA2
    start();

    // Kill FA2 on unmount
    return () => {
      kill();
    };
  }, [start, kill]);

  return null;
};

// Component that display the graph
export const SemGraphBase = () => {

  return (
    <SigmaContainer style={sigmaStyle}>
      <LoadGraph />
      <Fa2 />
    </SigmaContainer>
  );
};

now i just need to find the other details like directional edges, stopping the animation etc.

sim51 commented 1 month ago