clean dependencies
This commit is contained in:
parent
470dd45321
commit
ef1a49e32c
33506
package-lock.json
generated
Normal file
33506
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@
|
|||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
"react-cytoscapejs": "^1.2.1",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"web-vitals": "^1.0.1"
|
"web-vitals": "^1.0.1"
|
||||||
|
|||||||
38
src/App.css
38
src/App.css
@ -1,38 +0,0 @@
|
|||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-link {
|
|
||||||
color: #61dafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
24
src/App.js
24
src/App.js
@ -1,23 +1,15 @@
|
|||||||
import logo from './logo.svg';
|
//import logo from './logo.svg';
|
||||||
import './App.css';
|
import './css/App.css';
|
||||||
|
import React from 'react';
|
||||||
|
import Simulation from './Simulation/Simulation.jsx';
|
||||||
|
import HeaderData from './Header/Headerdata';
|
||||||
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<header className="App-header">
|
<HeaderData/>
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
<Simulation/>
|
||||||
<p>
|
|
||||||
Edit <code>src/App.js</code> and save to reload.
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
className="App-link"
|
|
||||||
href="https://reactjs.org"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Learn React
|
|
||||||
</a>
|
|
||||||
</header>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
120
src/Graph.jsx
Normal file
120
src/Graph.jsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { ForceGraph2D } from "react-force-graph";
|
||||||
|
import React from 'react';
|
||||||
|
import './css/Graph.css'
|
||||||
|
|
||||||
|
class DynamicGraph extends React.Component{
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.fgRef = React.createRef();
|
||||||
|
this.ran = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
this.timerID = setInterval(()=>{
|
||||||
|
//this.addNode();
|
||||||
|
//this.fgRef.current.zoomToFit(400);
|
||||||
|
//this.fgRef.current.resumeAnimation();
|
||||||
|
//this.fgRef.current.pauseAnimation();
|
||||||
|
//this.fgRef.current.cooldownTicks(10);
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(){
|
||||||
|
clearInterval(this.timerID);
|
||||||
|
}
|
||||||
|
|
||||||
|
addNode(){
|
||||||
|
if(this.state.nodes.length < 10){
|
||||||
|
const id = this.state.nodes.length;
|
||||||
|
const newNodes = this.state.nodes;
|
||||||
|
const newLinks = this.state.links;
|
||||||
|
newNodes.push({id:id});
|
||||||
|
newLinks.push({source: id, target: Math.round(Math.random() * (id-1))});
|
||||||
|
if(id > 0){
|
||||||
|
newNodes[Math.round(Math.random() * (id-1))].color = "red";
|
||||||
|
}
|
||||||
|
//newNodes[0].color = "red";
|
||||||
|
this.setState((nodes, links)=>({
|
||||||
|
nodes: newNodes,
|
||||||
|
links: newLinks
|
||||||
|
}));
|
||||||
|
}else{
|
||||||
|
clearInterval(this.timerID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCooldownTime(){
|
||||||
|
if(!this.ran){
|
||||||
|
this.ran = true;
|
||||||
|
return 5000;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEngineStop(){
|
||||||
|
console.log(this.fgRef.current)
|
||||||
|
if(this.fgRef.current != null){
|
||||||
|
this.fgRef.current.zoomToFit(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
const data = {nodes: this.props.nodes, links: this.props.links};
|
||||||
|
return <ForceGraph2D
|
||||||
|
enableNodeDrag={false}
|
||||||
|
graphData={data}
|
||||||
|
ref={this.fgRef}
|
||||||
|
cooldownTime={this.setCooldownTime()}
|
||||||
|
onEngineStop={this.onEngineStop()}
|
||||||
|
backgroundColor="black"
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//const { useState, useEffect, useCallback } = React;
|
||||||
|
//
|
||||||
|
//const DynamicGraph = () => {
|
||||||
|
//const [data, setData] = useState({ nodes: [{ id: 0 }], links: [] });
|
||||||
|
//
|
||||||
|
//useEffect(() => {
|
||||||
|
//console.log(state);
|
||||||
|
////if(this.state.nodes.length > 10){
|
||||||
|
////return;
|
||||||
|
////}
|
||||||
|
//setInterval(() => {
|
||||||
|
//// Add a new connected node every second
|
||||||
|
//setData(({ nodes, links }) => {
|
||||||
|
//const id = nodes.length;
|
||||||
|
//nodes[0].color = "red";
|
||||||
|
//console.log(nodes[0].color);
|
||||||
|
//return {
|
||||||
|
//nodes: [...nodes, { id }],
|
||||||
|
//links: [...links, { source: id, target: Math.round(Math.random() * (id-1)) }]
|
||||||
|
//};
|
||||||
|
//});
|
||||||
|
//}, 1000);
|
||||||
|
//}, []);
|
||||||
|
//
|
||||||
|
//const handleClick = useCallback(node => {
|
||||||
|
//const { nodes, links } = data;
|
||||||
|
//
|
||||||
|
//// Remove node on click
|
||||||
|
//const newLinks = links.filter(l => l.source !== node && l.target !== node); // Remove links attached to node
|
||||||
|
//const newNodes = nodes.slice();
|
||||||
|
//newNodes.splice(node.id, 1); // Remove node
|
||||||
|
//newNodes.forEach((n, idx) => { n.id = idx; }); // Reset node ids to array index
|
||||||
|
//
|
||||||
|
//setData({ nodes: newNodes, links: newLinks });
|
||||||
|
//}, [data, setData]);
|
||||||
|
//
|
||||||
|
//return <ForceGraph2D
|
||||||
|
//enableNodeDrag={false}
|
||||||
|
//onNodeClick={handleClick}
|
||||||
|
//graphData={data}
|
||||||
|
//ndoeAutoColorBy={d => d.id%12}
|
||||||
|
///>;
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
export default DynamicGraph;
|
||||||
11
src/Header/Headerdata.jsx
Normal file
11
src/Header/Headerdata.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
class HeaderData extends React.Component{
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<h1>Here is the headerdata.</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HeaderData;
|
||||||
57
src/Simulation.jsx
Normal file
57
src/Simulation.jsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import DynamicGraph from './Graph.jsx';
|
||||||
|
import simulate from './SimulationScripts/simulation.js'
|
||||||
|
import edgeListToGraph from './SimulationScripts/graphUtils.js';
|
||||||
|
|
||||||
|
class Simulation extends React.Component{
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
//this.graph = React.createRef();
|
||||||
|
|
||||||
|
//Example data
|
||||||
|
let rules = [[ "I", "R", 1 ], // spontaneous rule I -> R with rate 1.0
|
||||||
|
[ "R", "S", 0.7 ], // spontaneous rule R -> S with rate 0.7
|
||||||
|
[ [ "I","S","I" ],[ "I","I","I" ], 0.8 ]]; // contact rule I+S -> I+I with rate 0.8
|
||||||
|
let states = ["S", "I", "R"];
|
||||||
|
let initial_distribution = [0.5, 0.5, 0.0]; // gleiche Reihenfolge wie states, musss zu rules passen und normalisiert werden
|
||||||
|
//end of example data
|
||||||
|
|
||||||
|
|
||||||
|
let graph_as_edgelist = [[ 0, 4 ], [ 0, 1 ], [ 1, 5 ], [ 1, 2 ], [ 2, 6 ], [ 2, 3 ], [ 3, 7 ], [ 4, 8 ], [ 4, 5 ], [ 5, 9 ], [ 5, 6 ], [ 6, 10 ], [ 6, 7 ], [ 7, 11 ], [ 8, 12 ], [ 8, 9 ], [ 9, 13 ], [ 9, 10 ], [ 10, 14 ], [ 10, 11 ], [ 11, 15 ], [ 12, 13 ], [ 13, 14 ], [ 14, 15 ]];
|
||||||
|
let horizon = 20.0;
|
||||||
|
let simulationData = simulate(rules, states, initial_distribution, graph_as_edgelist, horizon);
|
||||||
|
|
||||||
|
|
||||||
|
let graphData = edgeListToGraph(graph_as_edgelist);
|
||||||
|
this.state = {rules: rules, states:states, initial_distribution: initial_distribution, simulationData: simulationData, graphData: graphData};
|
||||||
|
//this.graph.current.test("alksdj");
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
//this.id = setInterval(() => {
|
||||||
|
//this.setState((prev)=>{
|
||||||
|
//prev.graphData.nodes[Math.round(Math.random() * (prev.graphData.nodes.length - 1))].color = "red";
|
||||||
|
//return prev;
|
||||||
|
//})
|
||||||
|
//},1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(){
|
||||||
|
clearTimeout(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(e){
|
||||||
|
console.log(e.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<div id="Simulation">
|
||||||
|
<DynamicGraph
|
||||||
|
nodes={this.state.graphData.nodes}
|
||||||
|
links={this.state.graphData.links}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default Simulation;
|
||||||
62
src/Simulation/ContactSelector.jsx
Normal file
62
src/Simulation/ContactSelector.jsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Dropdown from './Dropdown.jsx'
|
||||||
|
import KarateClass from './exampleNetworks/Karate.jsx';
|
||||||
|
import Grid from './exampleNetworks/Grid.jsx';
|
||||||
|
import Custom from './exampleNetworks/Custom.jsx';
|
||||||
|
|
||||||
|
class ContactSelector extends React.Component{
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
//predefined networks go here
|
||||||
|
this.Karate = new KarateClass();
|
||||||
|
this.Grid = new Grid();
|
||||||
|
|
||||||
|
//obligatory custom network
|
||||||
|
this.custom = new Custom();
|
||||||
|
|
||||||
|
this.state = {predefinedNetworks: [this.Karate, this.Grid, this.custom]
|
||||||
|
, currentValue: "Karate"};
|
||||||
|
this.dropdownChanged = this.dropdownChanged.bind(this);
|
||||||
|
this.updateGraphObject = this.updateGraphObject.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdownChanged(e){
|
||||||
|
this.setState({currentValue: e.name});
|
||||||
|
this.props.handleChange(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
//when the object changed we need to update it again
|
||||||
|
updateGraphObject(e) {
|
||||||
|
this.props.handleChange(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCustomHtml(){
|
||||||
|
switch(this.state.currentValue){
|
||||||
|
case "Karate":
|
||||||
|
return <KarateClass updateGraphObject={this.updateGraphObject}/>
|
||||||
|
case "2D-Grid":
|
||||||
|
return <Grid updateGraphObject={this.updateGraphObject}/>
|
||||||
|
case "Custom":
|
||||||
|
return <Custom updateGraphObject={this.updateGraphObject}/>
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return(
|
||||||
|
<div id='ContactSelector'>
|
||||||
|
<h2 id='ContactSelectorName' className='selectorName'>
|
||||||
|
Contact Network</h2>
|
||||||
|
<Dropdown name='Select Network'
|
||||||
|
description='Select a network'
|
||||||
|
options={this.state.predefinedNetworks}
|
||||||
|
handleChange={this.dropdownChanged}/>
|
||||||
|
{this.renderCustomHtml()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContactSelector;
|
||||||
34
src/Simulation/Dropdown.jsx
Normal file
34
src/Simulation/Dropdown.jsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
class Dropdown extends React.Component{
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.changeVal = this.changeVal.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
createOption(data, index){
|
||||||
|
return (
|
||||||
|
<option value={data.name} key={index}>{data.name}</option>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeVal(e){
|
||||||
|
let newVal = this.props.options[e.target.selectedIndex];
|
||||||
|
this.props.handleChange(newVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return(
|
||||||
|
<div className='DropdownDiv'>
|
||||||
|
<label htmlFor='Dropdown' className='dropdownDescription'>{this.props.description}</label>
|
||||||
|
<select id='Dropdown'
|
||||||
|
name={this.props.name}
|
||||||
|
onChange={this.changeVal}>
|
||||||
|
{this.props.options.map(this.createOption)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dropdown;
|
||||||
147
src/Simulation/GraphCytoscape.jsx
Normal file
147
src/Simulation/GraphCytoscape.jsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import CytoscapeComponent from 'react-cytoscapejs';
|
||||||
|
import '../css/Graph.css'
|
||||||
|
|
||||||
|
class GraphCytoscape extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.cy = React.createRef();
|
||||||
|
this.iteration = 0;
|
||||||
|
this.state ={animationDuration: 4};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
//initial layout
|
||||||
|
this.layoutGraph();
|
||||||
|
this.iteration = 0;
|
||||||
|
this.visualizeOneStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
//only recalculate the layout if graph has changed
|
||||||
|
if (prevProps.graphData !== this.props.graphData) {
|
||||||
|
this.layoutGraph();
|
||||||
|
this.iteration = 0;
|
||||||
|
clearInterval(this.animationId);
|
||||||
|
this.visualizeOneStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
//we could check here if the simulation should start displaying
|
||||||
|
}
|
||||||
|
|
||||||
|
//layouting algorithm
|
||||||
|
layoutGraph() {
|
||||||
|
let options = {
|
||||||
|
name: 'cose',
|
||||||
|
// Called on `layoutready`
|
||||||
|
ready: function(){},
|
||||||
|
// Called on `layoutstop`
|
||||||
|
stop: function(){},
|
||||||
|
// Whether to animate while running the layout
|
||||||
|
// true : Animate continuously as the layout is running
|
||||||
|
// false : Just show the end result
|
||||||
|
// 'end' : Animate with the end result, from the initial positions to the end positions
|
||||||
|
animate: 'end',
|
||||||
|
// Easing of the animation for animate:'end'
|
||||||
|
animationEasing: undefined,
|
||||||
|
// The duration of the animation for animate:'end'
|
||||||
|
animationDuration: undefined,
|
||||||
|
// A function that determines whether the node should be animated
|
||||||
|
// All nodes animated by default on animate enabled
|
||||||
|
// Non-animated nodes are positioned immediately when the layout starts
|
||||||
|
animateFilter: function ( node, i ){ return true; },
|
||||||
|
// The layout animates only after this many milliseconds for animate:true
|
||||||
|
// (prevents flashing on fast runs)
|
||||||
|
animationThreshold: 250,
|
||||||
|
// Number of iterations between consecutive screen positions update
|
||||||
|
refresh: 20,
|
||||||
|
// Whether to fit the network view after when done
|
||||||
|
fit: true,
|
||||||
|
// Padding on fit
|
||||||
|
padding: 30,
|
||||||
|
// Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
|
||||||
|
boundingBox: undefined,
|
||||||
|
// Excludes the label when calculating node bounding boxes for the layout algorithm
|
||||||
|
nodeDimensionsIncludeLabels: false,
|
||||||
|
// Randomize the initial positions of the nodes (true) or use existing positions (false)
|
||||||
|
randomize: false,
|
||||||
|
// Extra spacing between components in non-compound graphs
|
||||||
|
componentSpacing: 40,
|
||||||
|
// Node repulsion (non overlapping) multiplier
|
||||||
|
nodeRepulsion: function( node ){ return 2048; },
|
||||||
|
// Node repulsion (overlapping) multiplier
|
||||||
|
nodeOverlap: 4,
|
||||||
|
// Ideal edge (non nested) length
|
||||||
|
idealEdgeLength: function( edge ){ return 32; },
|
||||||
|
// Divisor to compute edge forces
|
||||||
|
edgeElasticity: function( edge ){ return 32; },
|
||||||
|
// Nesting factor (multiplier) to compute ideal edge length for nested edges
|
||||||
|
nestingFactor: 1.2,
|
||||||
|
// Gravity force (constant)
|
||||||
|
gravity: 1,
|
||||||
|
// Maximum number of iterations to perform
|
||||||
|
numIter: 1000,
|
||||||
|
// Initial temperature (maximum node displacement)
|
||||||
|
initialTemp: 1000,
|
||||||
|
// Cooling factor (how the temperature is reduced between consecutive iterations
|
||||||
|
coolingFactor: 0.99,
|
||||||
|
// Lower temperature threshold (below this point the layout will end)
|
||||||
|
minTemp: 1.0
|
||||||
|
};
|
||||||
|
|
||||||
|
var layout = this.cy.layout( options );
|
||||||
|
layout.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
changeAnimationDuration = (e) => {
|
||||||
|
this.setState({animationDuration: e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
visualizeSimulation = () => {
|
||||||
|
console.log("new animation started");
|
||||||
|
clearInterval(this.animationId);
|
||||||
|
this.iteration = 0;
|
||||||
|
if (this.state.animationDuration <= 0) {
|
||||||
|
this.setState({animationDuration: 4});
|
||||||
|
}
|
||||||
|
this.visualizeOneStep();
|
||||||
|
var stepTime = this.state.animationDuration * 1000 / this.props.simulationData.length;
|
||||||
|
this.animationId = setInterval(this.visualizeOneStep, stepTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
//this is the method to visualize the simulation
|
||||||
|
visualizeOneStep = () => {
|
||||||
|
//the data of the simulation is stored in: this.props.simulationData
|
||||||
|
var data = this.props.simulationData;
|
||||||
|
if (data == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var nodeCount = data[0].length
|
||||||
|
var simulationLength = data.length;
|
||||||
|
if (this.iteration >= simulationLength) {
|
||||||
|
console.log("finished animation");
|
||||||
|
clearInterval(this.animationId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//do one step
|
||||||
|
for (var i = 0; i < nodeCount; i++) {
|
||||||
|
//the current state of the current node
|
||||||
|
let state = data[this.iteration][i];
|
||||||
|
var color = this.props.colors.find(element => element[0] === state)[1];
|
||||||
|
|
||||||
|
this.cy.getElementById(i).style('background-color', color);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.iteration++;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (<div id="graphDiv">
|
||||||
|
<button id="runSimulationButton" onClick={this.visualizeSimulation}>Play Simulation</button>
|
||||||
|
<input type="number" onChange={this.changeAnimationDuration} value={this.state.animationDuration}/>
|
||||||
|
<CytoscapeComponent id="cy" cy={(cy) => { this.cy = cy }} elements={this.props.graphData}/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GraphCytoscape;
|
||||||
19
src/Simulation/HorizonSelector.jsx
Normal file
19
src/Simulation/HorizonSelector.jsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Slider from './Slider';
|
||||||
|
|
||||||
|
class HorizonSelector extends React.Component{
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<div id='HorizonSelector'>
|
||||||
|
<h2 id='HorizonSelectorName' className='selectorName'>
|
||||||
|
Select the animation horizon</h2>
|
||||||
|
<Slider description='Select Horizon'
|
||||||
|
handleChange={this.props.handleChange}
|
||||||
|
currentValue={this.props.currentValue}
|
||||||
|
min='1' max='200'/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HorizonSelector;
|
||||||
64
src/Simulation/ModelSelector.jsx
Normal file
64
src/Simulation/ModelSelector.jsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Dropdown from './Dropdown.jsx'
|
||||||
|
import SIModel from './exampleModels/SIModel.jsx';
|
||||||
|
import SEIRSModel from './exampleModels/SEIRSModel.jsx';
|
||||||
|
import CoronaModel from './exampleModels/CoronaModel.jsx';
|
||||||
|
import Custom from './exampleModels/Custom.jsx';
|
||||||
|
|
||||||
|
class ModelSelector extends React.Component{
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
//predefined networks go here
|
||||||
|
this.SIModel = new SIModel();
|
||||||
|
this.SEIRSModel = new SEIRSModel();
|
||||||
|
this.CoronaModel = new CoronaModel();
|
||||||
|
//this.custom = new Custom();
|
||||||
|
|
||||||
|
this.state = {predefinedModels: [this.SIModel, this.SEIRSModel, this.CoronaModel]
|
||||||
|
, currentValue: "SIModel"};
|
||||||
|
this.dropdownChanged = this.dropdownChanged.bind(this);
|
||||||
|
this.updateSelectedModel = this.updateSelectedModel.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdownChanged(e){
|
||||||
|
this.setState({currentValue: e.name});
|
||||||
|
this.props.handleChange(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
//when the object changed we need to update it again
|
||||||
|
updateSelectedModel(e) {
|
||||||
|
this.props.handleChange(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCustomHtml(){
|
||||||
|
switch(this.state.currentValue){
|
||||||
|
case "SIModel":
|
||||||
|
return <SIModel updateSelectedModel={this.updateSelectedModel}/>
|
||||||
|
case "SEIRSModel":
|
||||||
|
return <SEIRSModel updateSelectedModel={this.updateSelectedModel}/>
|
||||||
|
case "CoronaModel":
|
||||||
|
return <CoronaModel updateSelectedModel={this.updateSelectedModel}/>
|
||||||
|
case "Custom":
|
||||||
|
return <Custom updateSelectedModel={this.updateSelectedModel}/>
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return(
|
||||||
|
<div id='ModelSelector'>
|
||||||
|
<h2 id='ModelSelectorName' className='selectorName'>
|
||||||
|
Contact Model</h2>
|
||||||
|
<Dropdown name='Select Model'
|
||||||
|
description='Select a network'
|
||||||
|
options={this.state.predefinedModels}
|
||||||
|
handleChange={this.dropdownChanged}/>
|
||||||
|
{this.renderCustomHtml()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelSelector;
|
||||||
106
src/Simulation/Simulation.jsx
Normal file
106
src/Simulation/Simulation.jsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import simulate from '../SimulationScripts/simulation.js'
|
||||||
|
import edgeListToGraph from '../SimulationScripts/graphUtils.js';
|
||||||
|
|
||||||
|
import '../css/Simulation.css'
|
||||||
|
|
||||||
|
import HorizonSelector from './HorizonSelector.jsx';
|
||||||
|
import ContactSelector from './ContactSelector.jsx';
|
||||||
|
import KarateClass from './exampleNetworks/Karate.jsx';
|
||||||
|
import ModelSelector from './ModelSelector.jsx';
|
||||||
|
import GraphCytoscape from './GraphCytoscape.jsx';
|
||||||
|
import SIModel from './exampleModels/SIModel.jsx';
|
||||||
|
|
||||||
|
class Simulation extends React.Component{
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
// bind this to the function as it is calles from elsewhere
|
||||||
|
this.horizonChange = this.horizonChange.bind(this);
|
||||||
|
this.networkChange = this.networkChange.bind(this);
|
||||||
|
this.modelChanged = this.modelChanged.bind(this);
|
||||||
|
this.recalculate = this.recalculate.bind(this);
|
||||||
|
|
||||||
|
//HERE WE SET THE DEFAULT VALUES, THIS MUST BE CONSISTEN!!!!!
|
||||||
|
this.networkObject = new KarateClass();
|
||||||
|
this.modelObject = new SIModel();
|
||||||
|
|
||||||
|
//initialize the state correctly
|
||||||
|
var selectedModel = this.modelObject;
|
||||||
|
var graphData = edgeListToGraph(this.networkObject.getGraph());
|
||||||
|
var rules = selectedModel.getRules();
|
||||||
|
var states = selectedModel.getStates();
|
||||||
|
var initial_distribution = selectedModel.getDistribution();
|
||||||
|
|
||||||
|
this.state = {rules: rules, states: states, initial_distribution: initial_distribution, graphData: graphData, horizon: 20.0, selectedNetwork: this.networkObject, selectedModel: this.modelObject, simulationData: undefined,};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
this.recalculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
horizonChange(e){
|
||||||
|
if(e.target.value > 200){
|
||||||
|
e.target.value = 200
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
horizon: e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
networkChange(newNetwork){
|
||||||
|
this.setState({selectedNetwork: newNetwork});
|
||||||
|
}
|
||||||
|
|
||||||
|
modelChanged(newModel) {
|
||||||
|
this.setState({selectedModel: newModel});
|
||||||
|
}
|
||||||
|
|
||||||
|
recalculate(){
|
||||||
|
var selectedModel = this.state.selectedModel;
|
||||||
|
var graphData = edgeListToGraph(this.state.selectedNetwork.getGraph());
|
||||||
|
var rules = selectedModel.getRules();
|
||||||
|
var states = selectedModel.getStates();
|
||||||
|
var initial_distribution = selectedModel.getDistribution();
|
||||||
|
//update the values based on selected Models/Networks
|
||||||
|
//Simulate with given data
|
||||||
|
var newSimulationData = simulate(rules, states, initial_distribution, this.state.selectedNetwork.getGraph(), this.state.horizon);
|
||||||
|
|
||||||
|
this.setState({graphData: graphData, rules: rules, states: states, initial_distribution: initial_distribution, simulationData: newSimulationData});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
//return (
|
||||||
|
//<div id="Simulation">
|
||||||
|
//<div id="SimulationSettings">
|
||||||
|
//<HorizonSelector
|
||||||
|
//handleChange={this.horizonChange}
|
||||||
|
//currentValue={this.state.horizon}/>
|
||||||
|
//<ContactSelector handleChange={this.networkChange}/>
|
||||||
|
//<ModelSelector handleChange={this.modelChanged}/>
|
||||||
|
//<button id="recalculate" onClick={this.recalculate}>Recalculate</button>
|
||||||
|
//</div>
|
||||||
|
//<div id="SimulationGraph">
|
||||||
|
//<DynamicGraph
|
||||||
|
//nodes={this.state.graphData.nodes}
|
||||||
|
//links={this.state.graphData.links}/>
|
||||||
|
//</div>
|
||||||
|
//</div>
|
||||||
|
//);
|
||||||
|
return (
|
||||||
|
<div id="Simulation">
|
||||||
|
<div id="SimulationSettings">
|
||||||
|
<HorizonSelector
|
||||||
|
handleChange={this.horizonChange}
|
||||||
|
currentValue={this.state.horizon}/>
|
||||||
|
<ContactSelector handleChange={this.networkChange}/>
|
||||||
|
<ModelSelector handleChange={this.modelChanged}/>
|
||||||
|
<button id="recalculate" onClick={this.recalculate}>Recalculate</button>
|
||||||
|
</div>
|
||||||
|
<div id="SimulationGraph">
|
||||||
|
<GraphCytoscape graphData={this.state.graphData} simulationData={this.state.simulationData} colors={this.state.selectedModel.getColors()}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default Simulation;
|
||||||
21
src/Simulation/Slider.jsx
Normal file
21
src/Simulation/Slider.jsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
import '../css/Slider.css'
|
||||||
|
|
||||||
|
class Slider extends React.Component{
|
||||||
|
componentDidMount() {
|
||||||
|
|
||||||
|
}
|
||||||
|
render(){
|
||||||
|
return (
|
||||||
|
<div className='SliderDiv'>
|
||||||
|
<label htmlFor='Slider' className='SliderDescription'>{this.props.description}</label>
|
||||||
|
<input className='Slider' type='range' min={this.props.min} max={this.props.max} value={this.props.currentValue}
|
||||||
|
onChange={this.props.handleChange} step={this.props.step}
|
||||||
|
/>
|
||||||
|
<input type="number" htmlFor='Slider' className='SliderValue' value={this.props.currentValue} onChange={this.props.handleChange}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Slider;
|
||||||
21
src/Simulation/exampleModels/CoronaModel.jsx
Normal file
21
src/Simulation/exampleModels/CoronaModel.jsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Model from "./Model";
|
||||||
|
|
||||||
|
class CoronaModel extends Model {
|
||||||
|
constructor() {
|
||||||
|
super("CoronaModel",
|
||||||
|
[["S", 0.965, "#1F85DE"], ["I1", 0.005, "#de1f50"], ["I2", 0.005, "#b91fde"], ["I3", 0.005, "#1fdea7"], ["R", 0.005, "#8fde1f"], ["V", 0.005, "#de801f"], ["D", 0.005, "#3b0b0b"]],
|
||||||
|
[[["I1", "S"], ["I2", "E"], 0.15],
|
||||||
|
[["I2", "S"], ["I2", "E"], 0.05],
|
||||||
|
[["I3", "S"], ["I3", "E"], 0.05],
|
||||||
|
["E", "I1", 0.2],
|
||||||
|
["I1", "I2", 0.03],
|
||||||
|
["I2", "I3", 0.04],
|
||||||
|
["I3", "D", 0.25],
|
||||||
|
["I1", "R", 0.13],
|
||||||
|
["I2", "R", 0.13],
|
||||||
|
["I3", "R", 0.12]],
|
||||||
|
1, 0.01);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CoronaModel;
|
||||||
0
src/Simulation/exampleModels/Custom.jsx
Normal file
0
src/Simulation/exampleModels/Custom.jsx
Normal file
124
src/Simulation/exampleModels/Model.jsx
Normal file
124
src/Simulation/exampleModels/Model.jsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Slider from "../Slider";
|
||||||
|
|
||||||
|
class Model extends React.Component {
|
||||||
|
constructor(optionName, states, rules, ruleMax, ruleStep) {
|
||||||
|
super();
|
||||||
|
this.optionName = optionName;
|
||||||
|
this.states = states;
|
||||||
|
this.rules = rules;
|
||||||
|
this.ruleMax = ruleMax;
|
||||||
|
this.ruleStep = ruleStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
//override default get name
|
||||||
|
get name() {
|
||||||
|
return this.optionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRules() {
|
||||||
|
return this.rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getStates() {
|
||||||
|
var output = [];
|
||||||
|
this.states.forEach((s) => {
|
||||||
|
output.push(s[0]);
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColors() {
|
||||||
|
var output = [];
|
||||||
|
this.states.forEach((s) => {
|
||||||
|
output.push([s[0], s[2]]);
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDistribution() {
|
||||||
|
var output = [];
|
||||||
|
this.states.forEach((s) => {
|
||||||
|
output.push(s[1]);
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
//update
|
||||||
|
this.props.updateSelectedModel(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeProbability = (spot, e) => {
|
||||||
|
//update state
|
||||||
|
this.setState({});
|
||||||
|
this.rules[spot][2] = Number(e.target.value);
|
||||||
|
//update
|
||||||
|
this.props.updateSelectedModel(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeDistribution = (spot, e) => {
|
||||||
|
//update state
|
||||||
|
this.setState({});
|
||||||
|
this.states[spot][1] = Number(e.target.value);
|
||||||
|
//update
|
||||||
|
this.props.updateSelectedModel(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeColor = (spot, e) => {
|
||||||
|
this.setState({});
|
||||||
|
this.states[spot][2] = e.target.value;
|
||||||
|
//update
|
||||||
|
this.props.updateSelectedModel(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//build the description of the slider
|
||||||
|
buildDesc(r) {
|
||||||
|
var output = r[0] + "->" + r[1];
|
||||||
|
return output.replaceAll(",", "+");
|
||||||
|
}
|
||||||
|
|
||||||
|
//build all sliders
|
||||||
|
buildSliders(max, step) {
|
||||||
|
var output = [];
|
||||||
|
var count = 0;
|
||||||
|
this.getRules().forEach((r) => {
|
||||||
|
var tempCount = count;
|
||||||
|
output.push(<Slider key={tempCount} description={this.buildDesc(r)} handleChange={(e) => this.changeProbability(tempCount, e)} min="0" max={max} currentValue={r[2]} step={step}/>)
|
||||||
|
count++;
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
//build the sliders for the initial distribution
|
||||||
|
buildSlidersDistribution() {
|
||||||
|
var output = [];
|
||||||
|
var count = 0;
|
||||||
|
this.states.forEach((s) => {
|
||||||
|
var tempCount = count;
|
||||||
|
output.push(<div key={-tempCount * -tempCount - 100}>
|
||||||
|
<input key={-tempCount - 1} type="color" className="ColorPicker" value={this.states[tempCount][2]} onChange={(e) => this.changeColor(tempCount, e)}/>
|
||||||
|
<Slider key={tempCount} description={s[0]} handleChange={(e) => this.changeDistribution(tempCount, e)} min="0" max="1" currentValue={s[1]} step="0.001"/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
count ++;
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="modelSelector">
|
||||||
|
<h3 id="selectRulesHeader">Change state transition probabilities</h3>
|
||||||
|
{this.buildSliders(this.ruleMax, this.ruleStep)}
|
||||||
|
<h3 id="selectDistributionHeader">Change initial distribution of states</h3>
|
||||||
|
{this.buildSlidersDistribution()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Model;
|
||||||
15
src/Simulation/exampleModels/SEIRSModel.jsx
Normal file
15
src/Simulation/exampleModels/SEIRSModel.jsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Model from "./Model";
|
||||||
|
|
||||||
|
class SEIRSModel extends Model {
|
||||||
|
constructor() {
|
||||||
|
super("SEIRSModel",
|
||||||
|
[["E", 0.03, "#1F85DE"], ["I", 0.03, "#de1f50"], ["R", 0.03, "#b91fde"], ["S", 0.91, "#1fdea7"]],
|
||||||
|
[[["S", "I"], ["I", "E"], 0.5],
|
||||||
|
["E", "I", 0.5],
|
||||||
|
["I", "R", 0.5],
|
||||||
|
["R", "S", 0.5]],
|
||||||
|
20, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SEIRSModel;
|
||||||
12
src/Simulation/exampleModels/SIModel.jsx
Normal file
12
src/Simulation/exampleModels/SIModel.jsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Model from "./Model";
|
||||||
|
|
||||||
|
class SIModel extends Model {
|
||||||
|
constructor() {
|
||||||
|
super("SIModel",
|
||||||
|
[["S", 0.97, "#1F85DE"], ["I", 0.03, "#de1f50"]],
|
||||||
|
[["S", "I", 0.5]],
|
||||||
|
20, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SIModel;
|
||||||
16
src/Simulation/exampleNetworks/Custom.jsx
Normal file
16
src/Simulation/exampleNetworks/Custom.jsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Network from "./Network";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
class Custom extends Network {
|
||||||
|
constructor(){
|
||||||
|
super("Custom",
|
||||||
|
[[1,1], [2,1], [3,1], [4,1]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (<h1>Custom</h1>)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Custom;
|
||||||
47
src/Simulation/exampleNetworks/Grid.jsx
Normal file
47
src/Simulation/exampleNetworks/Grid.jsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import Network from "./Network";
|
||||||
|
import Slider from "../Slider";
|
||||||
|
|
||||||
|
class Grid extends Network {
|
||||||
|
constructor(){
|
||||||
|
super("2D-Grid", []);
|
||||||
|
this.state = {dimension: 3};
|
||||||
|
}
|
||||||
|
|
||||||
|
changeDimension = (e) => {
|
||||||
|
let newD = e.target.value;
|
||||||
|
if(newD < 1 || newD > 100){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({dimension: Number(newD)});
|
||||||
|
|
||||||
|
//Update state
|
||||||
|
this.props.updateGraphObject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateGraph(){
|
||||||
|
//We calculate our edgelist here
|
||||||
|
this.graph = [];
|
||||||
|
for(let i = 0; i < this.state.dimension * this.state.dimension; i++){
|
||||||
|
if((i + 1) % this.state.dimension !== 0) {
|
||||||
|
this.graph.push([i, i + 1]);
|
||||||
|
}
|
||||||
|
if(i < (this.state.dimension * this.state.dimension) - this.state.dimension) {
|
||||||
|
this.graph.push([i, i + this.state.dimension]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.graph;
|
||||||
|
//return edgeListToGraph(this.graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
getGraph() {
|
||||||
|
return this.calculateGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (<div id='dimensionSlider'> <Slider description='Select a dimension' handleChange={this.changeDimension}
|
||||||
|
min='2' max='12' currentValue={this.state.dimension}/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Grid;
|
||||||
16
src/Simulation/exampleNetworks/Karate.jsx
Normal file
16
src/Simulation/exampleNetworks/Karate.jsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Network from "./Network";
|
||||||
|
|
||||||
|
class KarateClass extends Network {
|
||||||
|
constructor(){
|
||||||
|
//super("Karate",
|
||||||
|
//[[2,1], [3,1], [4,1], [5,1]]);
|
||||||
|
super("Karate",
|
||||||
|
[[0,1], [1,2], [3,4], [4,5], [6,7], [7,8], [0,3], [3,6], [1,4], [4,7], [2,5], [5,8]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return (<h1>placeholder</h1>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KarateClass;
|
||||||
25
src/Simulation/exampleNetworks/Network.jsx
Normal file
25
src/Simulation/exampleNetworks/Network.jsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
class Network extends React.Component{
|
||||||
|
constructor(optionName, graph){
|
||||||
|
super()
|
||||||
|
this.optionName = optionName;
|
||||||
|
this.graph = graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
//override default name getter
|
||||||
|
get name(){
|
||||||
|
return this.optionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
getGraph() {
|
||||||
|
return this.graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
//update state on select
|
||||||
|
this.props.updateGraphObject(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Network;
|
||||||
55
src/SimulationScripts/graphUtils.js
Normal file
55
src/SimulationScripts/graphUtils.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
function edgelistToDot(name, inp){
|
||||||
|
var output = "strict graph \"" + name + "\" {\n";
|
||||||
|
for(var e in inp){
|
||||||
|
e = inp[e];
|
||||||
|
output += e[0] + " -- " + e[1] + ";\n";
|
||||||
|
}
|
||||||
|
output += "}";
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dotToEdgelist(graph){
|
||||||
|
var outList = [];
|
||||||
|
graph = graph.split("\n");
|
||||||
|
//var name = graph[0].split(" ")[2];
|
||||||
|
//name = name.slice(1, name.length - 1);
|
||||||
|
for (var i = 0; i < graph.length - 1; i++){
|
||||||
|
if(i === 0){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var nodes = graph[i].split("--");
|
||||||
|
var node1 = Number(nodes[0]);
|
||||||
|
var node2 = Number(nodes[1].slice(0, nodes[1].length - 1));
|
||||||
|
outList.push([node1, node2]);
|
||||||
|
}
|
||||||
|
return outList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function edgeListToGraph(list){
|
||||||
|
let outNodes = [];
|
||||||
|
let outLinks = [];
|
||||||
|
for (var entry in list){
|
||||||
|
entry = list[entry];
|
||||||
|
outNodes.push(entry[0]);
|
||||||
|
outNodes.push(entry[1]);
|
||||||
|
outLinks.push({data: {source: entry[0], target:entry[1]}});
|
||||||
|
}
|
||||||
|
//sort
|
||||||
|
outNodes = outNodes.sort((a, b) => (a > b));
|
||||||
|
//remove duplicates
|
||||||
|
let finalArray = [];
|
||||||
|
var last = -1;
|
||||||
|
for(var i = 0; i < outNodes.length; i ++){
|
||||||
|
if(last === outNodes[i]){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
finalArray.push({data: {id: outNodes[i], label: ""}})
|
||||||
|
last = outNodes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outNodes = finalArray;
|
||||||
|
return [...outNodes, ...outLinks]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default edgeListToGraph;
|
||||||
208
src/SimulationScripts/simulation.js
Normal file
208
src/SimulationScripts/simulation.js
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
//let states = ["S", "I", "R"];
|
||||||
|
|
||||||
|
//let rules = [[ "I", "R", 1 ], // spontaneous rule I -> R with rate 1.0
|
||||||
|
//[ "R", "S", 0.7 ], // spontaneous rule R -> S with rate 0.7
|
||||||
|
//[ [ "I","S","I" ],[ "I","I","I" ], 0.8 ]]; // contact rule I+S -> I+I with rate 0.8
|
||||||
|
|
||||||
|
|
||||||
|
//output
|
||||||
|
let simulation = []
|
||||||
|
|
||||||
|
|
||||||
|
//let graph_as_edgelist = [[ 0, 4 ], [ 0, 1 ], [ 1, 5 ], [ 1, 2 ], [ 2, 6 ], [ 2, 3 ], [ 3, 7 ], [ 4, 8 ], [ 4, 5 ], [ 5, 9 ], [ 5, 6 ], [ 6, 10 ], [ 6, 7 ], [ 7, 11 ], [ 8, 12 ], [ 8, 9 ], [ 9, 13 ], [ 9, 10 ], [ 10, 14 ], [ 10, 11 ], [ 11, 15 ], [ 12, 13 ], [ 13, 14 ], [ 14, 15 ]];
|
||||||
|
|
||||||
|
//let horizon = 20.0; // wie lange wird simuliert
|
||||||
|
//let initial_distribution = [0.5, 0.5, 0.0]; // gleiche Reihenfolge wie states, musss zu rules passen und normalisiert werden
|
||||||
|
let timepoint_num = 101;
|
||||||
|
|
||||||
|
function get_next_state(current_labels){
|
||||||
|
let fastes_firing_time = 10000000.0; //dummy
|
||||||
|
let firing_rule = null;
|
||||||
|
let firing_node = null;
|
||||||
|
let firing_edge = null;
|
||||||
|
|
||||||
|
//if(current_labels == null)
|
||||||
|
//iterate over nodes
|
||||||
|
for(var currentNode in nodes){
|
||||||
|
currentNode = nodes[currentNode];
|
||||||
|
let current_state = current_labels[currentNode];
|
||||||
|
for(var rule in rules){
|
||||||
|
rule = rules[rule];
|
||||||
|
if(rule[0] instanceof Array){
|
||||||
|
//is contact rule
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(current_state === rule[0]){
|
||||||
|
let current_fireing_time = randomExponential(rule[2]);
|
||||||
|
//addFiringTime(current_fireing_time);
|
||||||
|
if(current_fireing_time < fastes_firing_time){
|
||||||
|
fastes_firing_time = current_fireing_time;
|
||||||
|
firing_rule = rule;
|
||||||
|
firing_node = currentNode;
|
||||||
|
firing_edge = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//iterate over edges
|
||||||
|
for(var edge in graph_as_edgelist){
|
||||||
|
edge = graph_as_edgelist[edge];
|
||||||
|
let node1 = edge;
|
||||||
|
let node2 = edge;
|
||||||
|
let current_state1 = current_labels[node1];
|
||||||
|
let current_state2 = current_labels[node2];
|
||||||
|
for(var currentRule in rules){
|
||||||
|
currentRule = rules[currentRule];
|
||||||
|
if(typeof currentRule[0] == typeof ""){
|
||||||
|
//is spont. rule
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if((current_state1 === currentRule[0][0] && current_state2 === currentRule[0][1]) || (current_state2 === currentRule[0][0] && current_state1 === currentRule[0][1])){
|
||||||
|
let current_fireing_time = randomExponential(currentRule[2]);
|
||||||
|
if(current_fireing_time < fastes_firing_time){
|
||||||
|
fastes_firing_time = current_fireing_time;
|
||||||
|
firing_rule = currentRule;
|
||||||
|
firing_node = null;
|
||||||
|
firing_edge = edge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(firing_rule == null){
|
||||||
|
//no rule could fire
|
||||||
|
return [null, fastes_firing_time]; //would happen anyway but still
|
||||||
|
}
|
||||||
|
//apply rule
|
||||||
|
let new_labels = Array.from(current_labels);
|
||||||
|
|
||||||
|
if(firing_node != null){
|
||||||
|
new_labels[firing_node] = firing_rule[1];
|
||||||
|
return [new_labels, fastes_firing_time];
|
||||||
|
}
|
||||||
|
console.assert(firing_edge != null);
|
||||||
|
let change_node1 = firing_edge[0];
|
||||||
|
let change_node2 = firing_edge[1];
|
||||||
|
//we have to check which node changes in which direction
|
||||||
|
if(new_labels[change_node1] === firing_rule[0][0] && new_labels[change_node2] === firing_rule[0][1]){
|
||||||
|
new_labels[change_node1] = firing_rule[1][0];
|
||||||
|
new_labels[change_node2] = firing_rule[1][1];
|
||||||
|
}else{
|
||||||
|
new_labels[change_node1] = firing_rule[1][1];
|
||||||
|
new_labels[change_node2] = firing_rule[1][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [new_labels, fastes_firing_time];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function count_states(current_labels){
|
||||||
|
let counter = [states.length];
|
||||||
|
|
||||||
|
simulation.push(current_labels);
|
||||||
|
for(var label in current_labels){
|
||||||
|
label = current_labels[label];
|
||||||
|
let index = states[ label ];
|
||||||
|
counter[index] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function generateNodes(edgelist){
|
||||||
|
let allNodes = [];
|
||||||
|
for(var e in edgelist){
|
||||||
|
e = edgelist[e];
|
||||||
|
allNodes.push(e[0]);
|
||||||
|
allNodes.push(e[1]);
|
||||||
|
}
|
||||||
|
//sort
|
||||||
|
allNodes = allNodes.sort((a, b) => (a > b));
|
||||||
|
//remove duplicates
|
||||||
|
let finalArray = [];
|
||||||
|
var last = -1;
|
||||||
|
for(var i = 0; i < allNodes.length; i ++){
|
||||||
|
if(last === allNodes[i]){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
finalArray.push(allNodes[i])
|
||||||
|
last = allNodes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return finalArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// np helper functions
|
||||||
|
function randomExponential(rate){
|
||||||
|
if(rate === 0){ return 10000000 }
|
||||||
|
return -Math.log(Math.random()) / rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function linspace(start, end, num){
|
||||||
|
console.assert(start < end);
|
||||||
|
console.assert(num > 0);
|
||||||
|
let distance = (end - start) / (num - 1);
|
||||||
|
let current = start;
|
||||||
|
let out = [];
|
||||||
|
while(current <= end){
|
||||||
|
out.push(Math.round(current * 100) / 100);
|
||||||
|
current += distance;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomChoice(states, num, distr){
|
||||||
|
let out = [];
|
||||||
|
let s = distr.reduce((a,e) => a + e);
|
||||||
|
for(var i = 0; i < num; i++){
|
||||||
|
let r = Math.random() * s;
|
||||||
|
out.push(states.find((_,i) => (r -= distr[i]) < 0));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//This is to be set by parent
|
||||||
|
let rules;
|
||||||
|
let states;
|
||||||
|
let initial_distribution;
|
||||||
|
let graph_as_edgelist;
|
||||||
|
let horizon;
|
||||||
|
|
||||||
|
let nodes;
|
||||||
|
|
||||||
|
//setup
|
||||||
|
let timepoints_samples;
|
||||||
|
let current_labels;
|
||||||
|
let global_clock;
|
||||||
|
let labels = [];
|
||||||
|
let state_counts = [];
|
||||||
|
|
||||||
|
function simulate(newRules, newStates, newDistr, newGraph, newHorizon){
|
||||||
|
simulation = [];
|
||||||
|
rules = newRules;
|
||||||
|
states = newStates;
|
||||||
|
initial_distribution = newDistr;
|
||||||
|
graph_as_edgelist = newGraph;
|
||||||
|
horizon = newHorizon;
|
||||||
|
//setup
|
||||||
|
nodes = generateNodes(graph_as_edgelist);
|
||||||
|
timepoints_samples = linspace(0, horizon, timepoint_num);
|
||||||
|
current_labels = randomChoice(states, nodes.length, initial_distribution);
|
||||||
|
global_clock = 0;
|
||||||
|
labels = [];
|
||||||
|
state_counts = [];
|
||||||
|
while(timepoints_samples.length > 0){
|
||||||
|
let [new_labels, time_passed] = get_next_state(current_labels);
|
||||||
|
global_clock += time_passed;
|
||||||
|
while(timepoints_samples.length > 0 && global_clock > timepoints_samples[0]){
|
||||||
|
labels.push(Array.from(current_labels));
|
||||||
|
state_counts.push(count_states(current_labels));
|
||||||
|
timepoints_samples = timepoints_samples.slice(1, timepoints_samples.length);
|
||||||
|
}
|
||||||
|
current_labels = new_labels;
|
||||||
|
}
|
||||||
|
return simulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default simulate;
|
||||||
64
src/VisNetwork.js
Normal file
64
src/VisNetwork.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import { Network } from "vis-network";
|
||||||
|
|
||||||
|
class Graph extends React.Component{
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
nodes:[
|
||||||
|
{ id: 1, label: "Node 1" },
|
||||||
|
{ id: 2, label: "Node 2" },
|
||||||
|
{ id: 3, label: "Node 3" },
|
||||||
|
{ id: 4, label: "Node 4" },
|
||||||
|
{ id: 5, label: "Node 5" },
|
||||||
|
],
|
||||||
|
edges:[
|
||||||
|
{ from: 1, to: 3 },
|
||||||
|
{ from: 1, to: 2 },
|
||||||
|
{ from: 2, to: 4 },
|
||||||
|
{ from: 2, to: 5 },
|
||||||
|
{ from: 3, to: 3 },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentDidMount(){
|
||||||
|
this.id = setInterval(()=>this.tick(), 1000);
|
||||||
|
}
|
||||||
|
componentWillUnmount(){
|
||||||
|
clearInterval(this.id);
|
||||||
|
}
|
||||||
|
tick(){
|
||||||
|
if(this.state.nodes[0].label === "Node 1"){
|
||||||
|
var copy = this.state;
|
||||||
|
copy.nodes[0].label = "TEUZET";
|
||||||
|
this.setState(copy)
|
||||||
|
}else{
|
||||||
|
var copy = this.state;
|
||||||
|
copy.nodes[0].label = "Node 1";
|
||||||
|
this.setState(copy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render(){
|
||||||
|
console.log(this.state);
|
||||||
|
return (<VisNetwork inp={this.state}/>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function VisNetwork(inp) {
|
||||||
|
const visJsRef = useRef(false);
|
||||||
|
var nodes = inp.inp.nodes;
|
||||||
|
var edges = inp.inp.edges;
|
||||||
|
useEffect(() => {
|
||||||
|
visJsRef.current.graph = new Network(visJsRef.current, { nodes, edges }, {});
|
||||||
|
// Use `network` here to configure events, etc
|
||||||
|
}, [visJsRef, nodes, edges]);
|
||||||
|
|
||||||
|
//redraw the graph
|
||||||
|
if(visJsRef.current.graph != null){
|
||||||
|
console.log("red")
|
||||||
|
visJsRef.current.graph.redraw();
|
||||||
|
}
|
||||||
|
return <div ref={visJsRef} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Graph;
|
||||||
2
src/css/App.css
Normal file
2
src/css/App.css
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.App {
|
||||||
|
}
|
||||||
6
src/css/Graph.css
Normal file
6
src/css/Graph.css
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/* Generated graph container */
|
||||||
|
#cy {
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
27
src/css/Simulation.css
Normal file
27
src/css/Simulation.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#Simulation {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#SimulationSettings {
|
||||||
|
width: 60%;
|
||||||
|
height: 100%;
|
||||||
|
flex: 50%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#SimulationGraph {
|
||||||
|
width: 40%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive layout - makes a one column layout instead of a two-column layout */
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
#SimulationSettings, #SimulationGraph {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#Simulation {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/css/Slider.css
Normal file
27
src/css/Slider.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
.Slider {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SliderDescription {
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 13%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SliderValue {
|
||||||
|
display: inline-block;
|
||||||
|
width: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove arrows from number input */
|
||||||
|
/* Chrome, Safari, Edge, Opera */
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
13
src/css/index.css
Normal file
13
src/css/index.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/*body {*/
|
||||||
|
/*margin: 0;*/
|
||||||
|
/*font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',*/
|
||||||
|
/*'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',*/
|
||||||
|
/*sans-serif;*/
|
||||||
|
/*-webkit-font-smoothing: antialiased;*/
|
||||||
|
/*-moz-osx-font-smoothing: grayscale;*/
|
||||||
|
/*}*/
|
||||||
|
/**/
|
||||||
|
/*code {*/
|
||||||
|
/*font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',*/
|
||||||
|
/*monospace;*/
|
||||||
|
/*}*/
|
||||||
@ -1,13 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
||||||
@ -1,13 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import './index.css';
|
import './css/index.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
//Strict mode was removed because of cytoscape
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<App />,
|
||||||
<App />
|
|
||||||
</React.StrictMode>,
|
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user