2022-06-18 03:20:44 +00:00
|
|
|
import React, { useEffect, useState, useRef } from "react";
|
2022-06-18 18:18:15 +00:00
|
|
|
import colormap from "colormap";
|
2022-06-18 03:20:44 +00:00
|
|
|
import cytoscape from "cytoscape";
|
|
|
|
import spread from "cytoscape-spread";
|
|
|
|
import {
|
|
|
|
resolveToNumIfPossible,
|
|
|
|
getSquiggleSparkline,
|
|
|
|
} from "../../lib/squiggle.js";
|
|
|
|
import { truncateValueForDisplay } from "../../lib/truncateNums.js";
|
|
|
|
|
|
|
|
// import dagre from "cytoscape-dagre";
|
|
|
|
// import cola from "cytoscape-cola";
|
|
|
|
// import fcose from "cytoscape-fcose";
|
2022-06-18 18:18:15 +00:00
|
|
|
// import avsdf from "cytoscape-avsdf";
|
2022-06-18 03:20:44 +00:00
|
|
|
|
|
|
|
const effectButtonStyle =
|
|
|
|
"bg-transparent m-2 hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded mt-5";
|
|
|
|
|
|
|
|
const getEdgeLabel = async (squiggleString) => {
|
|
|
|
let sparkline = await getSquiggleSparkline(squiggleString);
|
|
|
|
let num = await resolveToNumIfPossible(squiggleString);
|
|
|
|
|
|
|
|
let sparklineConcat = "";
|
|
|
|
if (false && sparkline.success) {
|
|
|
|
console.log(sparkline);
|
|
|
|
|
|
|
|
sparklineConcat =
|
2022-06-18 18:18:15 +00:00
|
|
|
sparklineConcat + " →" + sparkline.sparkline.replace("▁▁▁▁▁▁▁▁▁▁▁▁▁", "");
|
2022-06-18 03:20:44 +00:00
|
|
|
//alert("▁▁▁▁▁▁▁▁▁▁▁");
|
|
|
|
}
|
|
|
|
if (num.asNum) {
|
|
|
|
sparklineConcat =
|
|
|
|
sparklineConcat + " ⇾ " + truncateValueForDisplay(num.num);
|
|
|
|
//alert("▁▁▁▁▁▁▁▁▁▁▁");
|
|
|
|
}
|
|
|
|
|
|
|
|
return squiggleString + sparklineConcat; //sparkline;
|
|
|
|
};
|
|
|
|
|
2022-06-18 18:18:15 +00:00
|
|
|
const getColors = (n) => {
|
|
|
|
let colors = colormap({
|
|
|
|
colormap: "viridis",
|
|
|
|
nshades: n,
|
|
|
|
format: "hex",
|
|
|
|
alpha: 1,
|
|
|
|
});
|
|
|
|
return colors;
|
|
|
|
};
|
|
|
|
|
|
|
|
const cutOffLongNames = (string) => {
|
|
|
|
let maxLength = 40;
|
|
|
|
let result;
|
|
|
|
if (string.length < maxLength) {
|
|
|
|
result = string;
|
|
|
|
} else {
|
|
|
|
result = string.slice(0, maxLength - 4);
|
|
|
|
result = result + "...";
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function Graph({
|
|
|
|
listOfElements,
|
|
|
|
links,
|
|
|
|
isListOrdered,
|
|
|
|
mergeSortOrder,
|
|
|
|
}) {
|
2022-06-18 03:20:44 +00:00
|
|
|
const containerRef = useRef();
|
2022-06-18 18:18:15 +00:00
|
|
|
const [visibility, setVisibility] = useState(""); /// useState("invisible");
|
|
|
|
|
|
|
|
const callEffect = async ({
|
|
|
|
listOfElements,
|
|
|
|
links,
|
|
|
|
isListOrdered,
|
|
|
|
mergeSortOrder,
|
|
|
|
}) => {
|
|
|
|
//setVisibility("invisible");
|
|
|
|
let layoutName = "circle"; //
|
|
|
|
|
|
|
|
// cytoscape.use(circle); // spread, circle,
|
|
|
|
let listOfElementsForGraph = isListOrdered
|
|
|
|
? mergeSortOrder
|
|
|
|
: listOfElements;
|
2022-06-18 03:20:44 +00:00
|
|
|
|
2022-06-18 18:18:15 +00:00
|
|
|
let colors = new Array(listOfElements.length);
|
|
|
|
if (isListOrdered) {
|
|
|
|
colors = getColors(listOfElements.length);
|
|
|
|
}
|
2022-06-18 03:20:44 +00:00
|
|
|
|
2022-06-18 18:18:15 +00:00
|
|
|
let nodeElements = listOfElements.map((element, i) => {
|
|
|
|
return {
|
|
|
|
data: {
|
|
|
|
id: cutOffLongNames(element.name),
|
|
|
|
color: colors[i] || "darkgreen",
|
2022-06-18 18:56:37 +00:00
|
|
|
labelColor:
|
|
|
|
isListOrdered && i >= listOfElementsForGraph.length - 2
|
|
|
|
? "black"
|
|
|
|
: "white",
|
2022-06-18 18:18:15 +00:00
|
|
|
},
|
|
|
|
};
|
2022-06-18 03:20:44 +00:00
|
|
|
});
|
|
|
|
let linkElements = await Promise.all(
|
|
|
|
links.map(async (link, i) => {
|
|
|
|
return {
|
|
|
|
data: {
|
|
|
|
id: `link-${i}`,
|
2022-06-18 18:18:15 +00:00
|
|
|
source: cutOffLongNames(link.source),
|
|
|
|
target: cutOffLongNames(link.target),
|
2022-06-18 03:20:44 +00:00
|
|
|
label: await getEdgeLabel(link.squiggleString),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
})
|
|
|
|
);
|
2022-06-18 18:18:15 +00:00
|
|
|
|
|
|
|
const cytoscapeStylesheet = [
|
|
|
|
{
|
|
|
|
selector: "node",
|
|
|
|
style: {
|
2022-06-18 18:56:37 +00:00
|
|
|
padding: "30px",
|
2022-06-18 18:18:15 +00:00
|
|
|
shape: "round-rectangle",
|
|
|
|
content: "data(id)",
|
|
|
|
"background-color": "data(color)",
|
|
|
|
"text-wrap": "wrap",
|
|
|
|
//"text-overflow-wrap": "anywhere",
|
|
|
|
"text-max-width": 70,
|
|
|
|
"z-index": 1,
|
2022-06-18 03:20:44 +00:00
|
|
|
},
|
2022-06-18 18:18:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
selector: "node[id]",
|
|
|
|
style: {
|
|
|
|
label: "data(id)",
|
2022-06-18 18:56:37 +00:00
|
|
|
"font-size": "13",
|
|
|
|
color: "data(labelColor)",
|
2022-06-18 18:18:15 +00:00
|
|
|
"text-halign": "center",
|
|
|
|
"text-valign": "center",
|
|
|
|
"z-index": 1,
|
2022-06-18 03:20:44 +00:00
|
|
|
},
|
2022-06-18 18:18:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
selector: "edge",
|
|
|
|
style: {
|
|
|
|
"curve-style": "unbundled-bezier",
|
|
|
|
"target-arrow-shape": "vee",
|
|
|
|
width: 1.5,
|
|
|
|
"target-arrow-color": "green",
|
|
|
|
"arrow-scale": 3,
|
|
|
|
"target-arrow-fill": "filled",
|
|
|
|
"text-rotation": "autorotate",
|
|
|
|
"z-index": 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
selector: "edge[label]",
|
|
|
|
style: {
|
|
|
|
label: "data(label)",
|
|
|
|
"font-size": "12",
|
|
|
|
|
|
|
|
"text-background-color": "#f9f9f9",
|
|
|
|
"text-background-opacity": 1,
|
|
|
|
"text-background-padding": "4px",
|
|
|
|
|
|
|
|
"text-border-color": "black",
|
|
|
|
"text-border-style": "solid",
|
|
|
|
"text-border-width": 0.5,
|
|
|
|
"text-border-opacity": 1,
|
|
|
|
"z-index": 3,
|
|
|
|
|
|
|
|
// "text-rotation": "autorotate"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
const config = {
|
|
|
|
container: containerRef.current,
|
|
|
|
style: cytoscapeStylesheet,
|
2022-06-18 03:20:44 +00:00
|
|
|
elements: [
|
|
|
|
/* Dummy data:
|
|
|
|
{ data: { id: "n1" } },
|
|
|
|
{ data: { id: "n2" } },
|
|
|
|
{ data: { id: "e1", source: "n1", target: "n2" } },
|
|
|
|
|
|
|
|
Real data:*/
|
|
|
|
...nodeElements,
|
|
|
|
...linkElements,
|
|
|
|
],
|
|
|
|
layout: {
|
2022-06-18 18:18:15 +00:00
|
|
|
name: layoutName, // circle, grid, dagre
|
2022-06-18 03:20:44 +00:00
|
|
|
minDist: 10,
|
|
|
|
//prelayout: false,
|
2022-06-18 03:38:06 +00:00
|
|
|
// animate: false, // whether to transition the node positions
|
|
|
|
// animationDuration: 250, // duration of animation in ms if enabled
|
2022-06-18 03:20:44 +00:00
|
|
|
},
|
|
|
|
userZoomingEnabled: false,
|
|
|
|
userPanningEnabled: false,
|
|
|
|
};
|
|
|
|
cytoscape(config);
|
2022-06-18 18:18:15 +00:00
|
|
|
// setTimeout(() => setVisibility(""), 700);
|
2022-06-18 03:20:44 +00:00
|
|
|
};
|
|
|
|
useEffect(async () => {
|
2022-06-18 18:18:15 +00:00
|
|
|
await callEffect({ listOfElements, links, isListOrdered, mergeSortOrder });
|
2022-06-18 03:20:44 +00:00
|
|
|
// console.log(JSON.stringify(config, null, 10));
|
2022-06-18 18:56:37 +00:00
|
|
|
}, [listOfElements, links, isListOrdered]);
|
2022-06-18 03:20:44 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<div>
|
2022-06-18 03:38:06 +00:00
|
|
|
<div className={visibility}>
|
2022-06-18 18:18:15 +00:00
|
|
|
<div ref={containerRef} style={{ height: "900px", width: "1000px" }} />
|
2022-06-18 03:38:06 +00:00
|
|
|
</div>
|
2022-06-18 03:20:44 +00:00
|
|
|
<button
|
|
|
|
className={effectButtonStyle}
|
2022-06-18 18:18:15 +00:00
|
|
|
onClick={() =>
|
|
|
|
callEffect({ listOfElements, links, isListOrdered, mergeSortOrder })
|
|
|
|
}
|
2022-06-18 03:20:44 +00:00
|
|
|
>
|
|
|
|
{"Redraw graph"}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
);
|
2022-06-17 22:48:11 +00:00
|
|
|
}
|