kopia lustrzana https://github.com/fediversespace/fediverse.space
207 wiersze
5.5 KiB
TypeScript
207 wiersze
5.5 KiB
TypeScript
import cytoscape from "cytoscape";
|
|
// import cola from "cytoscape-cola";
|
|
import * as React from "react";
|
|
import { connect } from "react-redux";
|
|
|
|
import { Dispatch } from "redux";
|
|
import styled from "styled-components";
|
|
import { DEFAULT_NODE_COLOR, SELECTED_NODE_COLOR } from "../constants";
|
|
import { selectAndLoadInstance } from "../redux/actions";
|
|
import { IAppState, IGraph } from "../redux/types";
|
|
import { ErrorState } from "./ErrorState";
|
|
// import { FloatingLayoutSelect } from "./FloatingLayoutSelect";
|
|
import { FloatingResetButton } from "./FloatingResetButton";
|
|
|
|
interface IGraphProps {
|
|
graph?: IGraph;
|
|
currentInstanceName: string | null;
|
|
selectAndLoadInstance: (name: string) => void;
|
|
}
|
|
interface IGraphState {
|
|
layoutAlgorithm: string;
|
|
isLayouting: boolean;
|
|
didError: boolean;
|
|
}
|
|
class GraphImpl extends React.Component<IGraphProps, IGraphState> {
|
|
private cy?: cytoscape.Core;
|
|
// private layout?: cytoscape.Layouts;
|
|
private cytoscapeDiv: React.RefObject<HTMLElement>;
|
|
|
|
public constructor(props: IGraphProps) {
|
|
super(props);
|
|
this.cytoscapeDiv = React.createRef();
|
|
this.state = { layoutAlgorithm: "cola", isLayouting: false, didError: false };
|
|
}
|
|
|
|
public render() {
|
|
if (this.state.didError) {
|
|
return <ErrorState />;
|
|
}
|
|
|
|
const FullDiv = styled.div`
|
|
position: absolute;
|
|
top: 50px;
|
|
bottom: 0;
|
|
right: 0;
|
|
left: 0;
|
|
`;
|
|
|
|
return (
|
|
<div>
|
|
<FullDiv id="cytoscape" ref={this.cytoscapeDiv as any} />
|
|
{/* <FloatingLayoutSelect
|
|
onItemSelect={this.handleLayoutSelect}
|
|
currentLayoutKey={this.state.layoutAlgorithm}
|
|
startLayout={this.startLayout}
|
|
stopLayout={this.stopLayout}
|
|
/> */}
|
|
<FloatingResetButton onClick={this.resetGraph} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
public componentDidMount() {
|
|
let { graph } = this.props;
|
|
if (!graph) {
|
|
this.setState({ didError: true });
|
|
return;
|
|
}
|
|
|
|
// Check that all nodes have size & coordinates; otherwise the graph will look messed up
|
|
const lengthBeforeFilter = graph.nodes.length;
|
|
graph = { ...graph, nodes: graph.nodes.filter(n => n.size && n.x && n.y) };
|
|
if (graph.nodes.length !== lengthBeforeFilter) {
|
|
// tslint:disable-next-line:no-console
|
|
console.error(
|
|
"Some nodes were missing details: " + graph.nodes.filter(n => !n.size || !n.x || !n.y).map(n => n.label)
|
|
);
|
|
this.setState({ didError: true });
|
|
}
|
|
|
|
// cytoscape.use(cola as any);
|
|
this.initGraph();
|
|
}
|
|
|
|
public componentDidUpdate() {
|
|
this.initGraph();
|
|
}
|
|
|
|
// private handleLayoutSelect = (layout: string) => {
|
|
// this.setState({ layoutAlgorithm: layout });
|
|
// };
|
|
|
|
// private startLayout = () => {
|
|
// if (!this.cy) {
|
|
// return;
|
|
// }
|
|
// const options = {
|
|
// cola: {
|
|
// animate: true,
|
|
// convergenceThreshold: 0.1,
|
|
// edgeLength: (edge: any) => 1 / edge.data("weight"),
|
|
// name: "cola"
|
|
// },
|
|
// cose: {
|
|
// animate: false,
|
|
// idealEdgeLength: (edge: any) => 1 / edge.data("weight"),
|
|
// name: "cose",
|
|
// numIter: 100
|
|
// }
|
|
// };
|
|
// this.layout = this.cy.layout(options[this.state.layoutAlgorithm] as any);
|
|
// this.layout.run();
|
|
// };
|
|
|
|
// private stopLayout = () => {
|
|
// if (!this.layout) {
|
|
// return;
|
|
// }
|
|
// this.layout.stop();
|
|
// };
|
|
|
|
private initGraph = () => {
|
|
const { graph } = this.props;
|
|
if (this.state.didError || !graph) {
|
|
return;
|
|
}
|
|
this.cy = cytoscape({
|
|
autoungrabify: true,
|
|
container: this.cytoscapeDiv.current,
|
|
elements: {
|
|
edges: graph.edges.map(edge => ({
|
|
data: {
|
|
id: edge.id || `${edge.source}${edge.target}`,
|
|
source: edge.source,
|
|
target: edge.target,
|
|
weight: edge.size
|
|
},
|
|
group: "edges" as "edges"
|
|
})),
|
|
nodes: graph.nodes.map(node => ({
|
|
data: {
|
|
id: node.id
|
|
},
|
|
group: "nodes" as "nodes",
|
|
position: {
|
|
x: node.x,
|
|
y: node.y
|
|
}
|
|
}))
|
|
},
|
|
layout: {
|
|
name: "preset"
|
|
},
|
|
selectionType: "single",
|
|
style: [
|
|
{
|
|
selector: "node:selected",
|
|
style: {
|
|
"background-color": SELECTED_NODE_COLOR,
|
|
label: "data(id)"
|
|
}
|
|
},
|
|
{
|
|
selector: "node",
|
|
style: {
|
|
"background-color": DEFAULT_NODE_COLOR
|
|
}
|
|
}
|
|
]
|
|
});
|
|
this.cy.nodes().on("select", e => {
|
|
const instanceId = e.target.data("id");
|
|
if (instanceId) {
|
|
// console.log(`selecting ${instanceId}`);
|
|
// console.log(`now selected: ${this.cy && this.cy.$(":selected")}`);
|
|
this.props.selectAndLoadInstance(instanceId);
|
|
}
|
|
});
|
|
this.cy.nodes().on("unselect", e => {
|
|
const instanceId = e.target.data("id");
|
|
if (instanceId) {
|
|
// console.log(`unselecting ${instanceId}`);
|
|
this.props.selectAndLoadInstance("");
|
|
}
|
|
});
|
|
};
|
|
|
|
private resetGraph = () => {
|
|
if (!this.cy) {
|
|
return;
|
|
}
|
|
this.cy.reset();
|
|
};
|
|
}
|
|
|
|
const mapStateToProps = (state: IAppState) => ({
|
|
currentInstanceName: state.currentInstance.currentInstanceName,
|
|
graph: state.data.graph
|
|
});
|
|
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|
selectAndLoadInstance: (instanceName: string) => dispatch(selectAndLoadInstance(instanceName) as any)
|
|
});
|
|
export const CytoscapeGraph = connect(
|
|
mapStateToProps,
|
|
mapDispatchToProps
|
|
)(GraphImpl);
|