Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sim51 committed May 2, 2021
0 parents commit 0b3654b
Show file tree
Hide file tree
Showing 55 changed files with 26,618 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
test/e2e
lib
10 changes: 10 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
]
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
4 changes: 4 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"trailingComma": "all",
"printWidth": 120
}
156 changes: 156 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
= React Sigma v2

A proof of concept (for now) to create Sigma v2 React component.


== Philosophy

This library shares the same philosophy as https://react-leaflet.js.org[react-leaflet],
it just provides some bindings (and helpers) between React and Sigma.

The main component, ie. `SigmaContainer` create a Sigma instance with an empty graph.
If its option `initialSettings` or `graphOptions` is updated, the instance is killed and re-created.

I recommend you to NOT UPDATE those options to avoid performance issues.
Sigma (& graphology) comes with methods that allow the user to update the settings.

Every child has access to the sigma instance (and so the graph instance) via the React context created by the `SigmaContainer`.
In your components, you can use the hook `const sigma = useSigma()` that gives you the sigma instance (and so the underlying graph with `sigma.getGraph()`)

This is an example of how to display a graph :

[source, javascript]
----
// Component that creates the graph
const MyCustomGraph = () => {
const sigma = useSigma()
const graph = sigma.getGraph();
graph.addNode("Jessica", { label: "Jessica", x: 1, y: 1, color: "#FF0", size: 10 });
graph.addNode("Truman", { label: "Truman", x: 0, y: 0, color: "#00F",size: 5 });
graph.addEdge("Jessica", "Truman", { color: "#CCC",size: 1 });
return null;
}
// Put your component as a child of `SigmaContainer`
ReactDOM.render(
<React.StrictMode>
<SigmaContainer>
<MyCustomGraph />
</SigmaContainer>
</React.StrictMode>,
document.getElementById("root"),
);
----

== Components

=== SigmaContainer

This is the component's properties definition :

[source, typescript]
----
interface SigmaContainerProps {
graphOptions?: GraphOptions;
initialSettings?: Settings;
id?: string;
className?: string;
style?: CSSProperties;
children?: ReactNode;
}
----

* `graphOptions` is the options passed to the Graphology constructor. It defines the type of the graph, see https://graphology.github.io/instantiation.html#options
* `initialSettings` is the settings passed to the Sigma constructor.

=== ControlsContainer

It's just a component wrapper that create a `div` with the adequate class, so we know where to display the controls.
The only (optional) property of this component is : `position?: "top-right" | "top-left" | "bottom-right" | "bottom-left";`
Default value is `bottom-right`.

=== ZoomControl

Create the zoom toolbar fir the graph.
There is three controllers : `zoom in`, `zoom out` & `see the whole graph`

=== ForceAtlasControl

This is the component's properties definition :

[source, typescript]
----
interface Props {
/**
* The FA2 worker settings.
*/
settings?: FA2LayoutSupervisorParameters;
/**
* Option to tell what we do when the component is mounted
* - <code>-1</code> means that we do nothing (it's the same as no value)
* - <code>0</code> means that we start the algo (and don't auto stop it)
* - <code>X</code> mans that we start the algo, and stop it after X milliseconds
*/
autoRunFor?: number;
}
----

The component creates an a action button to stop/start a forceatlas2 layout on the graph.
Moreover, if you define the option `autoRunFor`, the layout will be run on component mount.

== Hooks

* `useSigma(): Sigma` : This is the main hook that give you access to the sigma instance.
* `useLoadGraph(): (graph: Graph, clear?: boolean) => void` : It's an helper that allow you to load a graph into the sigma instance. It's pretty much a `sigma.getGraph().import(graph)`
* `useRegisterEvents(): (eventHandlers: Partial<EventHandlers>) => void` : It's an helper that allow you to listen Sigma's events.
* `useSetSettings(): (settings: Partial<Settings>) => void` : It's an helper that allow you to change the Sigma's settings.

== How to install

The package is not yet on npm, so you have to install it directly from the repository.
Moreover, you have to install the peer dependencies that you need.

So if you want to install the full package, this is the command line :

[source, bash]
----
$> npm install sigma graphology graphology-layout-forceatlas2 https://github.com/sim51/react-sigma-v2
----

IMPORTANT: This package is based on the version of SigmaV2 `2.0.0-beta3` that has introduced new features.
So be sure to have this version or an upper one.


== How to use it

=== Import

Package is composed of a css file and a list of react components & hooks.

For the js part, everything is export in the package entrypoint, so you can do this

[source, javascript]
----
import { SigmaContainer, ...} from "react-sigma-v2";
----

You can also import just the components you need, they are exposed under the folder `./lib/esm` :

[source, javascript]
----
import { SigmaContainer, ...} from "react-sigma-v2/lib/esm/SigmaContainer";
----

For the css, you need to import the file `./lib/react-sigma-v2.css`.

== Npm scripts

* `npm run build` : Build the project
* `npm run examples` : Run the examples on http://localshot:8080

== Example

[source, javascript]
----
include::./examples/index.tsx[]
----
17 changes: 17 additions & 0 deletions examples/index.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title><%= htmlWebpackPlugin.options.title %></title>
<style>
html,body, #root {
height:100%;
width: 100%;
margin:0;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>
100 changes: 100 additions & 0 deletions examples/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { UndirectedGraph } from "graphology";
import { NodeKey, EdgeKey } from "graphology-types";
import erdosRenyi from "graphology-generators/random/erdos-renyi";
import randomLayout from "graphology-layout/random";
import chroma from "chroma-js";
import faker from "faker";
import { Sigma } from "sigma/sigma";
import { Settings } from "sigma/settings";
import {
ControlsContainer,
EventHandlers,
ForceAtlasControl,
useSigma,
useRegisterEvents,
useLoadGraph,
useSetSettings,
SigmaContainer,
ZoomControl,
} from "../src/index";
import "../src/assets/index.scss";

export const MyCustomGraph: React.FC<React.PropsWithChildren> = ({ children }) => {
const sigma = useSigma();
const registerEvents = useRegisterEvents();
const loadGraph = useLoadGraph();
const setSettings = useSetSettings();

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

// Register the events
registerEvents({
enterNode: event => {
const graph = sigma.getGraph();
graph.updateEachNodeAttributes((node, attr) => {
return { ...attr, background: true, highlighted: false };
});
graph.setNodeAttribute(event.node, "background", false);
graph.setNodeAttribute(event.node, "highlighted", true);
graph.setNodeAttribute(event.node, "selected", true);
graph.forEachNeighbor(event.node, (node, data) => {
graph.setNodeAttribute(node, "background", false);
graph.setNodeAttribute(node, "highlighted", true);
});
sigma.refresh();
},
leaveNode: event => {
const graph = sigma.getGraph();
graph.updateEachNodeAttributes((node, attr) => {
attr.highlighted = false;
attr.background = false;
attr.selected = false;
return attr;
});
sigma.refresh();
},
});

setSettings({
nodeReducer: (node, data) => {
if (data.background) return { ...data, color: "#eee", label: "" };
return data;
},
edgeReducer: (edge, data) => {
const graph = sigma.getGraph();
const nodes = graph.extremities(edge).map(node => graph.getNodeAttributes(node));
if (nodes && (nodes[0].background || nodes[1].background)) return { ...data, hidden: true };
if (nodes && (nodes[0].selected || nodes[1].selected)) return { ...data, color: "#000", hidden: false };
return { ...data, color: "#aaa", hidden: false };
},
});
}, []);

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

ReactDOM.render(
<React.StrictMode>
<SigmaContainer>
<MyCustomGraph />
<ControlsContainer position={"bottom-right"}>
<ZoomControl />
<ForceAtlasControl autoRunFor={2000} />
</ControlsContainer>
</SigmaContainer>
</React.StrictMode>,
document.getElementById("root"),
);
14 changes: 14 additions & 0 deletions examples/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"rootDir": "./../",
"outDir": "./lib/esm",
"sourceMap": true,
"declaration": true,
"declarationDir": "./lib/esm",
"target": "es6",
"esModuleInterop": true,
"module": "commonjs",
"jsx": "react",
"lib": ["dom", "dom.iterable", "esnext"]
},
}
44 changes: 44 additions & 0 deletions examples/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
mode: "development",
entry: {
index: "./examples/index.tsx",
},
output: {
filename: "[name].js",
},
devtool: "source-map",
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
plugins: [
new HtmlWebpackPlugin({
filename: `index.html`,
title: `Example`,
template: "examples/index.ejs",
}),
],
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
},
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.svg$/,
loader: "svg-url-loader",
options: {
noquotes: true,
},
},
],
},
devServer: {
contentBase: "./",
},
};
7 changes: 7 additions & 0 deletions lib/esm/ControlsContainer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React, { ReactNode } from "react";
interface Props {
children?: ReactNode;
position?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
}
export declare const ControlsContainer: React.FC<Props>;
export {};
12 changes: 12 additions & 0 deletions lib/esm/ControlsContainer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/esm/ControlsContainer.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 0b3654b

Please sign in to comment.