clean dependencies

This commit is contained in:
JuliusHerrmann 2021-10-12 01:54:51 +02:00
parent 470dd45321
commit ef1a49e32c
34 changed files with 43972 additions and 8820 deletions

33506
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"react": "^17.0.2",
"react-cytoscapejs": "^1.2.1",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"

View File

@ -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);
}
}

View File

@ -1,23 +1,15 @@
import logo from './logo.svg';
import './App.css';
//import logo from './logo.svg';
import './css/App.css';
import React from 'react';
import Simulation from './Simulation/Simulation.jsx';
import HeaderData from './Header/Headerdata';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<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>
<HeaderData/>
<Simulation/>
</div>
);
}

120
src/Graph.jsx Normal file
View 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
View 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
View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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
View 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;

View 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;

View File

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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
View 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
View File

@ -0,0 +1,2 @@
.App {
}

6
src/css/Graph.css Normal file
View File

@ -0,0 +1,6 @@
/* Generated graph container */
#cy {
width: 100%;
height: 600px;
background-color: blue;
}

27
src/css/Simulation.css Normal file
View 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
View 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
View 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;*/
/*}*/

View File

@ -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;
}

View File

@ -1,13 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import './css/index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
//Strict mode was removed because of cytoscape
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
<App />,
document.getElementById('root')
);

17884
yarn.lock

File diff suppressed because it is too large Load Diff