diff --git a/src/App.js b/src/App.js index 5cd082c..79fb90f 100644 --- a/src/App.js +++ b/src/App.js @@ -8,8 +8,9 @@ import HeaderData from './Header/Headerdata'; function App() { return (
- - + + +
); } diff --git a/src/Header/Headerdata.jsx b/src/Header/Headerdata.jsx index 2c64a09..e8ed810 100644 --- a/src/Header/Headerdata.jsx +++ b/src/Header/Headerdata.jsx @@ -1,9 +1,14 @@ import React from 'react'; +import '../css/Header.css'; class HeaderData extends React.Component{ render(){ return ( -

Here is the headerdata.

+
+

Network Epidemic Playground

+

Continuous-time stochastic simulation of epidemic spreading on human-to-human contact networks

+

Simulation developed by Gerrit, website developed by Julius Herrmann

+
); } } diff --git a/src/Simulation/DistributionStatus.jsx b/src/Simulation/DistributionStatus.jsx new file mode 100644 index 0000000..49133fb --- /dev/null +++ b/src/Simulation/DistributionStatus.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +class DistributionStatus extends React.Component{ + render(){ + if (this.props.valid === true) { + return(

+ The distribution is valid ✅ +

); + } + else { + return(

+ The total distribution must equal 1 ✖️ +

); + } + }; +} + +export default DistributionStatus; diff --git a/src/Simulation/Dropdown.jsx b/src/Simulation/Dropdown.jsx index 92d5f9d..edcfab8 100644 --- a/src/Simulation/Dropdown.jsx +++ b/src/Simulation/Dropdown.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import '../css/Dropdown.css' class Dropdown extends React.Component{ constructor(props){ @@ -20,8 +21,8 @@ class Dropdown extends React.Component{ render(){ return(
- - {this.props.options.map(this.createOption)} diff --git a/src/Simulation/GraphCytoscape.jsx b/src/Simulation/GraphCytoscape.jsx index fe32ef1..c918536 100644 --- a/src/Simulation/GraphCytoscape.jsx +++ b/src/Simulation/GraphCytoscape.jsx @@ -96,8 +96,10 @@ class GraphCytoscape extends React.Component { changeAnimationDuration = (e) => { this.setState({animationDuration: e.target.value}); } + visualizeSimulation = () => { + //check if we pause the animation console.log("new animation started"); clearInterval(this.animationId); this.iteration = 0; @@ -107,6 +109,8 @@ class GraphCytoscape extends React.Component { this.visualizeOneStep(); var stepTime = this.state.animationDuration * 1000 / this.props.simulationData.length; this.animationId = setInterval(this.visualizeOneStep, stepTime); + //update the button + this.setState({}); } //this is the method to visualize the simulation @@ -116,20 +120,24 @@ class GraphCytoscape extends React.Component { if (data == null) { return; } - var nodeCount = data[0].length var simulationLength = data.length; if (this.iteration >= simulationLength) { console.log("finished animation"); clearInterval(this.animationId); + this.iteration = 0; + this.setState({}); return; } + + //array of all nodes + let allNodes = this.cy.filter('node'); //do one step - for (var i = 0; i < nodeCount; i++) { + for (var i = 0; i < allNodes.length; 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.cy.getElementById(allNodes[i].id()).style('background-color', color); } this.iteration++; @@ -137,9 +145,14 @@ class GraphCytoscape extends React.Component { render() { return (
- - - { this.cy = cy }} elements={this.props.graphData}/> + + +
+

Duration (seconds):

+ +
+ { this.cy = cy }} elements={this.props.graphData}/>
); } } diff --git a/src/Simulation/ModelSelector.jsx b/src/Simulation/ModelSelector.jsx index acc921d..584774d 100644 --- a/src/Simulation/ModelSelector.jsx +++ b/src/Simulation/ModelSelector.jsx @@ -13,9 +13,11 @@ class ModelSelector extends React.Component{ 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] + //obligatory custom option + this.custom = new Custom(); + + this.state = {predefinedModels: [this.SIModel, this.SEIRSModel, this.CoronaModel, this.custom] , currentValue: "SIModel"}; this.dropdownChanged = this.dropdownChanged.bind(this); this.updateSelectedModel = this.updateSelectedModel.bind(this); diff --git a/src/Simulation/Simulation.jsx b/src/Simulation/Simulation.jsx index 8d71874..3535c5a 100644 --- a/src/Simulation/Simulation.jsx +++ b/src/Simulation/Simulation.jsx @@ -61,6 +61,17 @@ class Simulation extends React.Component{ var rules = selectedModel.getRules(); var states = selectedModel.getStates(); var initial_distribution = selectedModel.getDistribution(); + + //first check if the distribution is equal to ~1 + let sum = 0; + initial_distribution.forEach((element) => { + sum += element; + }); + //check if the distribution is about 1 + if (sum < 0.95 || sum > 1.05) { + return; + } + //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); @@ -69,23 +80,6 @@ class Simulation extends React.Component{ } render(){ - //return ( - //
- //
- // - // - // - // - //
- //
- // - //
- //
- //); return (
@@ -94,10 +88,10 @@ class Simulation extends React.Component{ currentValue={this.state.horizon}/> -
- +
); diff --git a/src/Simulation/exampleModels/Custom.jsx b/src/Simulation/exampleModels/Custom.jsx index e69de29..d6ec466 100644 --- a/src/Simulation/exampleModels/Custom.jsx +++ b/src/Simulation/exampleModels/Custom.jsx @@ -0,0 +1,106 @@ +import Model from "./Model"; + +class Custom extends Model { + constructor() { + super("Custom", + [["S", 0.97, "#1F85DE"], ["I", 0.03, "#de1f50"]], + [["S", "I", 0.5]], + 20, 0.05); + this.state = {input: ""}; + } + + textFieldChanged = (e) => { + let input = e.target.value; + this.setState({input: input}); + let parsedInput = this.parseInput(e.target.value); + //check if the input is correct, if yes then update + if (parsedInput == null) { + return; + } + this.rules = parsedInput.rules; + this.states = parsedInput.states; + } + + parseInput(input) { + //regex for filtering valid rules (this is longer than it is complicated) + let re = /[[(]"?[\w\d]+"?,"?[\w\d]+"?,([0-9]*[.])?[0-9]+[)\]][\s\t]*|[([][([]("?[\w\d]+"?,)+"?[\w\d]+"?[\])],[([]("?[\w\d]+"?,)+"?[\w\d]+"?[\])],([0-9]*[.])?[0-9]+[\])][\s\t]*/g; + //strip spaces, tabs and newlines + input = input.replaceAll(" ", ""); + input = input.replaceAll("\t", ""); + input = input.replaceAll("\n", ""); + + let cleaned = input.match(re); + if(cleaned == null) { + return null; + } + + let newRules = []; + //regex to match states + cleaned.forEach((element) => { + element = element.split(","); + //test if we have a simple or complex rule + if (element.length === 3) { + newRules.push(this.parseValidRule(element)); + } else { + //check if there are an equal number of states left and right + if (element.length % 2 === 0) { + return; + } + //check if there is a split in the middle if not left != right + if (!element[((element.length - 1) / 2)].includes("(")) { + return; + } + //this rule is correct, we can continue + newRules.push(this.parseValidRule(element)); + } + }); + if (newRules.length === 0) { + return null; + } + + //parse states + let newStates = []; + + //only add states that are not duplicates + for (var i = 0; i < newRules.length; i++) { + for (var j = 0; j < newRules[i].length - 1; j++) { + let currentState = newRules[i][j]; + if (!newStates.includes(currentState)) { + newStates.push(currentState); + } + } + } + + //convert states to states with distribution and color + newStates = newStates.map((x) => [x, 1/newStates.length, "#ffffff"]); + + + return {rules: newRules, states: newStates}; + } + + parseValidRule(rule) { + //filter states + let subString = rule.slice(0, rule.length - 1); + + //match states + let re = /[\w\d]+/g; + subString = subString.map((x) => x.match(re)[0]); + //match probability + re = /([0-9]*[.])?[0-9]+/g + let probability = rule[rule.length - 1].match(re); + return [...subString, Number(probability)]; + } + + //override render + render() { + return(
+

Enter the rules like: ("S","I",0.1), (("I","I"),("S","I"),0.3)

+