landscape-widget 0.1.18 → 0.3.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +218 -0
- package/css/widget.css +8 -0
- package/dist/index.js +1 -1
- package/lib/Analysis.types.js +26 -0
- package/lib/Analysis.utils.js +5 -0
- package/lib/ForceAtlasAnimator.js +147 -0
- package/lib/GraphContainer.js +105 -0
- package/lib/GraphLayout.js +440 -0
- package/lib/GraphLayout.types.js +6 -0
- package/lib/InitialLayoutWorker.js +31 -0
- package/lib/IterateNoIntercomponentRepel.js +282 -0
- package/lib/LoadGraph.js +132 -0
- package/lib/SigmaLasso.js +118 -0
- package/lib/enum/layout.js +24 -0
- package/lib/explicitComponentLayout.js +155 -0
- package/lib/extension.js +18 -0
- package/lib/icons/AnalysisIcon.js +4 -0
- package/lib/icons/ChevronSolid.js +4 -0
- package/lib/icons/ClearSelection.js +5 -0
- package/lib/icons/FullScreenEnter.js +8 -0
- package/lib/icons/FullScreenExit.js +8 -0
- package/lib/icons/Gradient.js +6 -0
- package/lib/icons/Grid.js +4 -0
- package/lib/icons/Invert.js +5 -0
- package/lib/icons/Lasso.js +5 -0
- package/lib/icons/List.js +9 -0
- package/lib/icons/Minus.js +4 -0
- package/lib/icons/Plus.js +5 -0
- package/lib/icons/Reset.js +5 -0
- package/lib/icons/index.js +13 -0
- package/lib/index.js +3 -0
- package/lib/landscape.js +226 -0
- package/lib/mui/Tooltip.js +30 -0
- package/lib/plugin.js +27 -0
- package/lib/public-path.js +4 -0
- package/lib/version.js +16 -0
- package/package.json +107 -1
- package/NOTICE.txt +0 -274
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export var COLORING_OPTIONS;
|
|
2
|
+
(function (COLORING_OPTIONS) {
|
|
3
|
+
COLORING_OPTIONS["none"] = "none";
|
|
4
|
+
COLORING_OPTIONS["selectedFeatures"] = "selected-features";
|
|
5
|
+
COLORING_OPTIONS["selectedDataPoints"] = "selected-data-points";
|
|
6
|
+
})(COLORING_OPTIONS || (COLORING_OPTIONS = {}));
|
|
7
|
+
export var SIDEBAR_TABS;
|
|
8
|
+
(function (SIDEBAR_TABS) {
|
|
9
|
+
SIDEBAR_TABS[SIDEBAR_TABS["CONFIGURE"] = 0] = "CONFIGURE";
|
|
10
|
+
SIDEBAR_TABS[SIDEBAR_TABS["EXPLORE"] = 1] = "EXPLORE";
|
|
11
|
+
})(SIDEBAR_TABS || (SIDEBAR_TABS = {}));
|
|
12
|
+
export var COLORMAP_TYPE;
|
|
13
|
+
(function (COLORMAP_TYPE) {
|
|
14
|
+
COLORMAP_TYPE["sequential"] = "sequential";
|
|
15
|
+
COLORMAP_TYPE["diverging"] = "diverging";
|
|
16
|
+
COLORMAP_TYPE["discrete"] = "discrete";
|
|
17
|
+
})(COLORMAP_TYPE || (COLORMAP_TYPE = {}));
|
|
18
|
+
export var SELECTION_SOURCES;
|
|
19
|
+
(function (SELECTION_SOURCES) {
|
|
20
|
+
SELECTION_SOURCES["LANDSCAPE"] = "landscape";
|
|
21
|
+
SELECTION_SOURCES["EXPLORER"] = "explorer";
|
|
22
|
+
SELECTION_SOURCES["SCATTER_PLOT"] = "scatter-plot";
|
|
23
|
+
SELECTION_SOURCES["HISTOGRAM"] = "histogram";
|
|
24
|
+
SELECTION_SOURCES["GROUP"] = "group";
|
|
25
|
+
})(SELECTION_SOURCES || (SELECTION_SOURCES = {}));
|
|
26
|
+
//# sourceMappingURL=Analysis.types.js.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createEdgeWeightGetter } from 'graphology-utils/getters';
|
|
2
|
+
import { connectedComponents } from 'graphology-components';
|
|
3
|
+
import { PPN } from './enum/layout';
|
|
4
|
+
import { iterateNoIntercomponentRepel } from './IterateNoIntercomponentRepel';
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
6
|
+
const { assignLayoutChanges, graphToByteArrays } = require('graphology-layout-forceatlas2/helpers');
|
|
7
|
+
export const defaultSettings = {
|
|
8
|
+
linLogMode: false,
|
|
9
|
+
outboundAttractionDistribution: false,
|
|
10
|
+
adjustSizes: false,
|
|
11
|
+
edgeWeightInfluence: 1,
|
|
12
|
+
scalingRatio: 1,
|
|
13
|
+
strongGravityMode: false,
|
|
14
|
+
gravity: 1,
|
|
15
|
+
slowDown: 1,
|
|
16
|
+
barnesHutOptimize: false,
|
|
17
|
+
barnesHutTheta: 0.5,
|
|
18
|
+
};
|
|
19
|
+
export class ForceAtlasAnimator {
|
|
20
|
+
constructor(graph, layoutParams, config) {
|
|
21
|
+
this.graph = graph;
|
|
22
|
+
this.graph_components = connectedComponents(graph);
|
|
23
|
+
this.params = layoutParams;
|
|
24
|
+
this.frameID = null;
|
|
25
|
+
this.running = false;
|
|
26
|
+
// the default edge weight getter required by graphToByteArrays
|
|
27
|
+
this.getEdgeWeight = createEdgeWeightGetter(undefined).fromEntry;
|
|
28
|
+
this.graphArrays = graphToByteArrays(graph, this.getEdgeWeight);
|
|
29
|
+
this.outputReducer = null;
|
|
30
|
+
this.niters = 0;
|
|
31
|
+
this.maxiters = config.maxiters;
|
|
32
|
+
this.miniters = config.miniters;
|
|
33
|
+
this.convergenceThreshold = config.convergenceThreshold;
|
|
34
|
+
this.frameListeners = [];
|
|
35
|
+
}
|
|
36
|
+
isRunning() {
|
|
37
|
+
return this.running;
|
|
38
|
+
}
|
|
39
|
+
resetNIters() {
|
|
40
|
+
this.niters = 0;
|
|
41
|
+
}
|
|
42
|
+
updateNodePropsArrays(graphArrays) {
|
|
43
|
+
// updates the arrays of node positions and properties
|
|
44
|
+
// must guarantee this.graphArrays is defined before calling
|
|
45
|
+
// number of array entries per node
|
|
46
|
+
let i = 0;
|
|
47
|
+
const _graphArrays = graphArrays;
|
|
48
|
+
this.graph.forEachNode((_, attr) => {
|
|
49
|
+
if (attr.fixed) {
|
|
50
|
+
_graphArrays.nodes[i] = attr.x;
|
|
51
|
+
_graphArrays.nodes[i + 1] = attr.y;
|
|
52
|
+
}
|
|
53
|
+
// turns out that keeping the dx/dy state across time points makes for a fairly ugly animation.
|
|
54
|
+
// probably makes it converge to a good stable state faster, though.
|
|
55
|
+
_graphArrays.nodes[i + 2] = 0; // dx
|
|
56
|
+
_graphArrays.nodes[i + 3] = 0; // dy
|
|
57
|
+
_graphArrays.nodes[i + 4] = 0; // old_dx
|
|
58
|
+
_graphArrays.nodes[i + 5] = 0; // old_dy
|
|
59
|
+
_graphArrays.nodes[i + 9] = attr.fixed ? 1 : 0;
|
|
60
|
+
i += PPN;
|
|
61
|
+
});
|
|
62
|
+
return _graphArrays;
|
|
63
|
+
}
|
|
64
|
+
forceNodePositionUpdate() {
|
|
65
|
+
const _graphArrays = this.graphArrays;
|
|
66
|
+
if (!_graphArrays)
|
|
67
|
+
return;
|
|
68
|
+
let i = 0;
|
|
69
|
+
this.graph.forEachNode((_, attr) => {
|
|
70
|
+
_graphArrays.nodes[i] = attr.x;
|
|
71
|
+
_graphArrays.nodes[i + 1] = attr.y;
|
|
72
|
+
_graphArrays.nodes[i + 2] = 0; // dx
|
|
73
|
+
_graphArrays.nodes[i + 3] = 0; // dy
|
|
74
|
+
_graphArrays.nodes[i + 4] = 0; // old_dx
|
|
75
|
+
_graphArrays.nodes[i + 5] = 0; // old_dy
|
|
76
|
+
i += PPN;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
hasConverged() {
|
|
80
|
+
let i = 0;
|
|
81
|
+
if (!this.graphArrays) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
for (i = 0; i < this.graphArrays.nodes.length; i += PPN) {
|
|
85
|
+
// this is called NODE_CONVERGENCE in the code, and is computed from the node speed, but I don't actually know what it means.
|
|
86
|
+
if (this.graphArrays.nodes[i + 7] > this.convergenceThreshold) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
onFrame(listener) {
|
|
93
|
+
this.frameListeners.push(listener);
|
|
94
|
+
}
|
|
95
|
+
runFrame() {
|
|
96
|
+
let graphArrays;
|
|
97
|
+
if (this.graphArrays) {
|
|
98
|
+
graphArrays = this.updateNodePropsArrays(this.graphArrays);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
graphArrays = graphToByteArrays(this.graph, this.getEdgeWeight);
|
|
102
|
+
this.graphArrays = graphArrays;
|
|
103
|
+
}
|
|
104
|
+
if (this.params.settings !== undefined) {
|
|
105
|
+
iterateNoIntercomponentRepel(this.params.settings, graphArrays.nodes, graphArrays.edges, this.graph_components);
|
|
106
|
+
assignLayoutChanges(this.graph, graphArrays.nodes, this.outputReducer);
|
|
107
|
+
this.frameListeners.forEach((f) => f());
|
|
108
|
+
this.niters += 1;
|
|
109
|
+
// TODO: add convergence check. there is a nodewise "convergence" value whose meaning I don't understand
|
|
110
|
+
if (this.niters > this.maxiters) {
|
|
111
|
+
this.stop();
|
|
112
|
+
}
|
|
113
|
+
else if (this.niters > this.miniters && this.hasConverged()) {
|
|
114
|
+
this.stop();
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
this.frameID = window.requestAnimationFrame(() => this.runFrame());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
console.error(`No Settings Provided`);
|
|
122
|
+
// This is very unexpected.
|
|
123
|
+
this.stop();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
stop() {
|
|
127
|
+
this.running = false;
|
|
128
|
+
this.resetNIters();
|
|
129
|
+
if (this.frameID !== null) {
|
|
130
|
+
window.cancelAnimationFrame(this.frameID);
|
|
131
|
+
this.frameID = null;
|
|
132
|
+
}
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
start() {
|
|
136
|
+
if (this.running)
|
|
137
|
+
return;
|
|
138
|
+
this.running = true;
|
|
139
|
+
this.runFrame();
|
|
140
|
+
}
|
|
141
|
+
kill() {
|
|
142
|
+
this.stop();
|
|
143
|
+
this.frameListeners = [];
|
|
144
|
+
delete this.graphArrays;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=ForceAtlasAnimator.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
13
|
+
import classNames from 'classnames';
|
|
14
|
+
import { ControlsContainer, SigmaContainer, useSigma, useCamera } from '@react-sigma/core';
|
|
15
|
+
import { Tooltip } from './mui/Tooltip';
|
|
16
|
+
import { Lasso } from './icons/Lasso';
|
|
17
|
+
import { Plus, Minus, Reset, FullScreenEnter, FullScreenExit, ClearSelection, Invert, } from './icons';
|
|
18
|
+
import { LoadGraph } from './LoadGraph';
|
|
19
|
+
import { SigmaLasso } from './SigmaLasso';
|
|
20
|
+
import '@react-sigma/core/lib/react-sigma.min.css';
|
|
21
|
+
import './GraphLayout.scss';
|
|
22
|
+
export const GraphContainer = ({ graph, animate, animator, selectedNodes, setSelectedNodes, addSelectedNodes, isShowColorLegend, colorLegend, isFullScreen, toggleFullScreen, shouldLayoutUpdate, setShouldLayoutUpdate, clearSigma, setClearSigma, }) => {
|
|
23
|
+
const [isLassoActive, setIsLassoActive] = useState(false);
|
|
24
|
+
const { zoomIn, zoomOut, reset } = useCamera({ duration: 200, factor: 1.5 });
|
|
25
|
+
const sigma = useSigma();
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (shouldLayoutUpdate && setShouldLayoutUpdate) {
|
|
28
|
+
new Promise((resolve) => {
|
|
29
|
+
resolve(sigma.refresh());
|
|
30
|
+
}).then(() => {
|
|
31
|
+
setShouldLayoutUpdate(false);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}, [shouldLayoutUpdate]);
|
|
35
|
+
const onClearSelection = useCallback(() => {
|
|
36
|
+
setSelectedNodes([]);
|
|
37
|
+
}, [setSelectedNodes]);
|
|
38
|
+
const onInvertSelection = useCallback(() => {
|
|
39
|
+
const newSelectedNodes = [];
|
|
40
|
+
graph.forEachNode((node, attr) => {
|
|
41
|
+
const nodeID = Number(node);
|
|
42
|
+
if (!selectedNodes.some((n) => n === nodeID)) {
|
|
43
|
+
newSelectedNodes.push(nodeID);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
setSelectedNodes(newSelectedNodes);
|
|
47
|
+
}, [selectedNodes, setSelectedNodes]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
sigma.refresh();
|
|
50
|
+
}, [sigma, isFullScreen]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (sigma && clearSigma) {
|
|
53
|
+
sigma.clear();
|
|
54
|
+
}
|
|
55
|
+
setClearSigma(false);
|
|
56
|
+
}, [clearSigma]);
|
|
57
|
+
return (!!graph &&
|
|
58
|
+
!!animator && (React.createElement(React.Fragment, null,
|
|
59
|
+
React.createElement(LoadGraph, { graph: graph, animator: animator, animate: animate, selectedNodes: selectedNodes, setSelectedNodes: setSelectedNodes, addSelectedNodes: addSelectedNodes }),
|
|
60
|
+
React.createElement(SigmaLasso, { active: isLassoActive, setSelectedNodes: setSelectedNodes, addSelectedNodes: addSelectedNodes }),
|
|
61
|
+
React.createElement(ControlsContainer, { position: 'bottom-right' },
|
|
62
|
+
React.createElement(Tooltip, { title: 'Zoom in', placement: 'top' },
|
|
63
|
+
React.createElement("div", { className: 'react-sigma-control' },
|
|
64
|
+
React.createElement("button", { type: 'button', className: 'zoom-in', onClick: () => zoomIn() },
|
|
65
|
+
React.createElement(Plus, null)))),
|
|
66
|
+
React.createElement(Tooltip, { title: 'Zoom out', placement: 'top' },
|
|
67
|
+
React.createElement("div", { className: 'react-sigma-control' },
|
|
68
|
+
React.createElement("button", { type: 'button', className: 'zoom-out', onClick: () => zoomOut() },
|
|
69
|
+
React.createElement(Minus, null)))),
|
|
70
|
+
React.createElement(Tooltip, { title: isFullScreen ? 'Exit fullscreen' : 'Enter fullscreen', placement: 'top' },
|
|
71
|
+
React.createElement("div", { className: 'react-sigma-control' },
|
|
72
|
+
React.createElement("button", { type: 'button', onClick: toggleFullScreen }, isFullScreen ? React.createElement(FullScreenExit, null) : React.createElement(FullScreenEnter, null)))),
|
|
73
|
+
React.createElement(Tooltip, { title: 'Lasso selection', placement: 'top' },
|
|
74
|
+
React.createElement("div", { className: 'react-sigma-control' },
|
|
75
|
+
React.createElement("button", { type: 'button', className: classNames('lasso-selection', {
|
|
76
|
+
active: isLassoActive,
|
|
77
|
+
}), onClick: () => setIsLassoActive((prevState) => !prevState) },
|
|
78
|
+
React.createElement(Lasso, null)))),
|
|
79
|
+
React.createElement(Tooltip, { title: 'Reset scale', placement: 'top' },
|
|
80
|
+
React.createElement("div", { className: 'react-sigma-control' },
|
|
81
|
+
React.createElement("button", { type: 'button', className: 'reset-scale', onClick: () => reset() },
|
|
82
|
+
React.createElement(Reset, null)))),
|
|
83
|
+
React.createElement(Tooltip, { title: 'Clear selection', placement: 'top' },
|
|
84
|
+
React.createElement("div", { className: 'react-sigma-control' },
|
|
85
|
+
React.createElement("button", { type: 'button', disabled: selectedNodes.length === 0, className: 'clear-selection', onClick: onClearSelection },
|
|
86
|
+
React.createElement(ClearSelection, null)))),
|
|
87
|
+
React.createElement(Tooltip, { title: 'Invert selection', placement: 'top' },
|
|
88
|
+
React.createElement("div", { className: 'react-sigma-control' },
|
|
89
|
+
React.createElement("button", { type: 'button', className: 'invert-selection', onClick: onInvertSelection },
|
|
90
|
+
React.createElement(Invert, null))))))));
|
|
91
|
+
};
|
|
92
|
+
export const GraphWrapper = (_a) => {
|
|
93
|
+
var { isFullScreen } = _a, props = __rest(_a, ["isFullScreen"]);
|
|
94
|
+
const fullScreenStyles = useMemo(() => isFullScreen
|
|
95
|
+
? {
|
|
96
|
+
position: 'fixed',
|
|
97
|
+
top: 0,
|
|
98
|
+
left: 0,
|
|
99
|
+
zIndex: 1400,
|
|
100
|
+
}
|
|
101
|
+
: {}, [isFullScreen]);
|
|
102
|
+
return (React.createElement(SigmaContainer, { id: 'graph-container', style: fullScreenStyles },
|
|
103
|
+
React.createElement(GraphContainer, Object.assign({ isFullScreen: isFullScreen }, props))));
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=GraphContainer.js.map
|