first draft finished
This commit is contained in:
parent
ef1a49e32c
commit
8798c141dc
@ -8,6 +8,7 @@ import HeaderData from './Header/Headerdata';
|
|||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet"/>
|
||||||
<HeaderData/>
|
<HeaderData/>
|
||||||
<Simulation/>
|
<Simulation/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import '../css/Header.css';
|
||||||
|
|
||||||
class HeaderData extends React.Component{
|
class HeaderData extends React.Component{
|
||||||
render(){
|
render(){
|
||||||
return (
|
return (
|
||||||
<h1>Here is the headerdata.</h1>
|
<div>
|
||||||
|
<h1>Network Epidemic Playground</h1>
|
||||||
|
<h2>Continuous-time stochastic simulation of epidemic spreading on human-to-human contact networks</h2>
|
||||||
|
<h3 id="credits">Simulation developed by <a href="https://mosi.uni-saarland.de/people/gerrit/" target="_blank" rel="noreferrer">Gerrit</a>, website developed by <a href="https://juliusherrmann.de" target="_blank" rel="noreferrer">Julius Herrmann</a></h3>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/Simulation/DistributionStatus.jsx
Normal file
18
src/Simulation/DistributionStatus.jsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
class DistributionStatus extends React.Component{
|
||||||
|
render(){
|
||||||
|
if (this.props.valid === true) {
|
||||||
|
return(<div className="DistributionStatus valid"><h3 className="StatusText">
|
||||||
|
The distribution is valid ✅
|
||||||
|
</h3></div>);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return(<div className="DistributionStatus invalid"><h3 className="StatusText">
|
||||||
|
The total distribution must equal 1 ✖️
|
||||||
|
</h3></div>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DistributionStatus;
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import '../css/Dropdown.css'
|
||||||
|
|
||||||
class Dropdown extends React.Component{
|
class Dropdown extends React.Component{
|
||||||
constructor(props){
|
constructor(props){
|
||||||
@ -20,8 +21,8 @@ class Dropdown extends React.Component{
|
|||||||
render(){
|
render(){
|
||||||
return(
|
return(
|
||||||
<div className='DropdownDiv'>
|
<div className='DropdownDiv'>
|
||||||
<label htmlFor='Dropdown' className='dropdownDescription'>{this.props.description}</label>
|
<label htmlFor='Dropdown' className='DropdownDescription'>{this.props.description}</label>
|
||||||
<select id='Dropdown'
|
<select className='Dropdown'
|
||||||
name={this.props.name}
|
name={this.props.name}
|
||||||
onChange={this.changeVal}>
|
onChange={this.changeVal}>
|
||||||
{this.props.options.map(this.createOption)}
|
{this.props.options.map(this.createOption)}
|
||||||
|
|||||||
@ -97,7 +97,9 @@ class GraphCytoscape extends React.Component {
|
|||||||
this.setState({animationDuration: e.target.value});
|
this.setState({animationDuration: e.target.value});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
visualizeSimulation = () => {
|
visualizeSimulation = () => {
|
||||||
|
//check if we pause the animation
|
||||||
console.log("new animation started");
|
console.log("new animation started");
|
||||||
clearInterval(this.animationId);
|
clearInterval(this.animationId);
|
||||||
this.iteration = 0;
|
this.iteration = 0;
|
||||||
@ -107,6 +109,8 @@ class GraphCytoscape extends React.Component {
|
|||||||
this.visualizeOneStep();
|
this.visualizeOneStep();
|
||||||
var stepTime = this.state.animationDuration * 1000 / this.props.simulationData.length;
|
var stepTime = this.state.animationDuration * 1000 / this.props.simulationData.length;
|
||||||
this.animationId = setInterval(this.visualizeOneStep, stepTime);
|
this.animationId = setInterval(this.visualizeOneStep, stepTime);
|
||||||
|
//update the button
|
||||||
|
this.setState({});
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is the method to visualize the simulation
|
//this is the method to visualize the simulation
|
||||||
@ -116,20 +120,24 @@ class GraphCytoscape extends React.Component {
|
|||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var nodeCount = data[0].length
|
|
||||||
var simulationLength = data.length;
|
var simulationLength = data.length;
|
||||||
if (this.iteration >= simulationLength) {
|
if (this.iteration >= simulationLength) {
|
||||||
console.log("finished animation");
|
console.log("finished animation");
|
||||||
clearInterval(this.animationId);
|
clearInterval(this.animationId);
|
||||||
|
this.iteration = 0;
|
||||||
|
this.setState({});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//array of all nodes
|
||||||
|
let allNodes = this.cy.filter('node');
|
||||||
//do one step
|
//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
|
//the current state of the current node
|
||||||
let state = data[this.iteration][i];
|
let state = data[this.iteration][i];
|
||||||
var color = this.props.colors.find(element => element[0] === state)[1];
|
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++;
|
this.iteration++;
|
||||||
@ -137,9 +145,14 @@ class GraphCytoscape extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (<div id="graphDiv">
|
return (<div id="graphDiv">
|
||||||
<button id="runSimulationButton" onClick={this.visualizeSimulation}>Play Simulation</button>
|
<button id="recalculate" onClick={this.props.recalculateFuntion}>Recalculate 🔁</button>
|
||||||
<input type="number" onChange={this.changeAnimationDuration} value={this.state.animationDuration}/>
|
<button id="runSimulationButton" onClick={this.visualizeSimulation}>Play Simulation ▶️</button>
|
||||||
<CytoscapeComponent id="cy" cy={(cy) => { this.cy = cy }} elements={this.props.graphData}/>
|
<div>
|
||||||
|
<h3 id="durationDescription">Duration (seconds): </h3>
|
||||||
|
<input id="animationDuration" type="number" onChange={this.changeAnimationDuration} value={this.state.animationDuration}/>
|
||||||
|
</div>
|
||||||
|
<CytoscapeComponent id="cy" userZoomingEnabled={false} userPanningEnabled={false}
|
||||||
|
cy={(cy) => { this.cy = cy }} elements={this.props.graphData}/>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,9 +13,11 @@ class ModelSelector extends React.Component{
|
|||||||
this.SIModel = new SIModel();
|
this.SIModel = new SIModel();
|
||||||
this.SEIRSModel = new SEIRSModel();
|
this.SEIRSModel = new SEIRSModel();
|
||||||
this.CoronaModel = new CoronaModel();
|
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"};
|
, currentValue: "SIModel"};
|
||||||
this.dropdownChanged = this.dropdownChanged.bind(this);
|
this.dropdownChanged = this.dropdownChanged.bind(this);
|
||||||
this.updateSelectedModel = this.updateSelectedModel.bind(this);
|
this.updateSelectedModel = this.updateSelectedModel.bind(this);
|
||||||
|
|||||||
@ -61,6 +61,17 @@ class Simulation extends React.Component{
|
|||||||
var rules = selectedModel.getRules();
|
var rules = selectedModel.getRules();
|
||||||
var states = selectedModel.getStates();
|
var states = selectedModel.getStates();
|
||||||
var initial_distribution = selectedModel.getDistribution();
|
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
|
//update the values based on selected Models/Networks
|
||||||
//Simulate with given data
|
//Simulate with given data
|
||||||
var newSimulationData = simulate(rules, states, initial_distribution, this.state.selectedNetwork.getGraph(), this.state.horizon);
|
var newSimulationData = simulate(rules, states, initial_distribution, this.state.selectedNetwork.getGraph(), this.state.horizon);
|
||||||
@ -69,23 +80,6 @@ class Simulation extends React.Component{
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(){
|
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 (
|
return (
|
||||||
<div id="Simulation">
|
<div id="Simulation">
|
||||||
<div id="SimulationSettings">
|
<div id="SimulationSettings">
|
||||||
@ -94,10 +88,10 @@ class Simulation extends React.Component{
|
|||||||
currentValue={this.state.horizon}/>
|
currentValue={this.state.horizon}/>
|
||||||
<ContactSelector handleChange={this.networkChange}/>
|
<ContactSelector handleChange={this.networkChange}/>
|
||||||
<ModelSelector handleChange={this.modelChanged}/>
|
<ModelSelector handleChange={this.modelChanged}/>
|
||||||
<button id="recalculate" onClick={this.recalculate}>Recalculate</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="SimulationGraph">
|
<div id="SimulationGraph">
|
||||||
<GraphCytoscape graphData={this.state.graphData} simulationData={this.state.simulationData} colors={this.state.selectedModel.getColors()}/>
|
<GraphCytoscape recalculateFuntion={this.recalculate}
|
||||||
|
graphData={this.state.graphData} simulationData={this.state.simulationData} colors={this.state.selectedModel.getColors()}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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(<div>
|
||||||
|
<h3>Enter the rules like: <code>("S","I",0.1), (("I","I"),("S","I"),0.3)</code></h3>
|
||||||
|
<textarea className="CustomInput CustomModel" type="text" onChange={this.textFieldChanged}
|
||||||
|
value={this.state.input} required pattern="[[(]'?[\w\d]+'?,'?[\w\d]+'?,([0-9]*[.])?[0-9]+[)\]][\s\t]*|[([][([]('?[\w\d]+'?,)+'?[\w\d]+'?[\])],[([]('?[\w\d]+'?,)+'?[\w\d]+'?[\])],([0-9]*[.])?[0-9]+[\])][\s\t]*"
|
||||||
|
placeholder='("S","I",0.1), (("I","I"),("S","I"),0.3)'/>
|
||||||
|
{this.buildSlidersDistribution()}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Custom;
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import DistributionStatus from "../DistributionStatus";
|
||||||
import Slider from "../Slider";
|
import Slider from "../Slider";
|
||||||
|
import '../../css/Model.css';
|
||||||
|
|
||||||
class Model extends React.Component {
|
class Model extends React.Component {
|
||||||
constructor(optionName, states, rules, ruleMax, ruleStep) {
|
constructor(optionName, states, rules, ruleMax, ruleStep) {
|
||||||
@ -9,6 +11,9 @@ class Model extends React.Component {
|
|||||||
this.rules = rules;
|
this.rules = rules;
|
||||||
this.ruleMax = ruleMax;
|
this.ruleMax = ruleMax;
|
||||||
this.ruleStep = ruleStep;
|
this.ruleStep = ruleStep;
|
||||||
|
//this.state = {valid: true};
|
||||||
|
this.valid = true;
|
||||||
|
this.checkValidity();
|
||||||
}
|
}
|
||||||
|
|
||||||
//override default get name
|
//override default get name
|
||||||
@ -58,12 +63,28 @@ class Model extends React.Component {
|
|||||||
this.props.updateSelectedModel(this);
|
this.props.updateSelectedModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkValidity() {
|
||||||
|
let sum = 0;
|
||||||
|
this.getDistribution().forEach((element) => {
|
||||||
|
sum += element;
|
||||||
|
});
|
||||||
|
//check if the distribution is about 1
|
||||||
|
if (sum < 0.98 || sum > 1.02) {
|
||||||
|
//this.setState({valid: false});
|
||||||
|
this.valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//this.setState({valid: true});
|
||||||
|
this.valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
changeDistribution = (spot, e) => {
|
changeDistribution = (spot, e) => {
|
||||||
//update state
|
//update state
|
||||||
this.setState({});
|
this.setState({});
|
||||||
this.states[spot][1] = Number(e.target.value);
|
this.states[spot][1] = Number(e.target.value);
|
||||||
//update
|
//update
|
||||||
this.props.updateSelectedModel(this);
|
this.props.updateSelectedModel(this);
|
||||||
|
this.checkValidity();
|
||||||
}
|
}
|
||||||
|
|
||||||
changeColor = (spot, e) => {
|
changeColor = (spot, e) => {
|
||||||
@ -94,13 +115,18 @@ class Model extends React.Component {
|
|||||||
|
|
||||||
//build the sliders for the initial distribution
|
//build the sliders for the initial distribution
|
||||||
buildSlidersDistribution() {
|
buildSlidersDistribution() {
|
||||||
var output = [];
|
var output = [<DistributionStatus key="validation" valid={this.valid}/>];
|
||||||
var count = 0;
|
var count = 0;
|
||||||
this.states.forEach((s) => {
|
this.states.forEach((s) => {
|
||||||
|
//clamp description
|
||||||
|
let description = s[0];
|
||||||
|
if (description.length > 10) {
|
||||||
|
description = description.substring(0,11) + "...";
|
||||||
|
}
|
||||||
var tempCount = count;
|
var tempCount = count;
|
||||||
output.push(<div key={-tempCount * -tempCount - 100}>
|
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)}/>
|
<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"/>
|
<Slider key={tempCount} description={description} handleChange={(e) => this.changeDistribution(tempCount, e)} min="0" max="1" currentValue={s[1]} step="0.001"/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
count ++;
|
count ++;
|
||||||
|
|||||||
@ -5,10 +5,53 @@ class Custom extends Network {
|
|||||||
constructor(){
|
constructor(){
|
||||||
super("Custom",
|
super("Custom",
|
||||||
[[1,1], [2,1], [3,1], [4,1]]);
|
[[1,1], [2,1], [3,1], [4,1]]);
|
||||||
|
this.state = {input: ""};
|
||||||
|
}
|
||||||
|
|
||||||
|
textFieldChanged = (e) => {
|
||||||
|
let newText = e.target.value;
|
||||||
|
this.setState({input: newText});
|
||||||
|
}
|
||||||
|
|
||||||
|
getGraph() {
|
||||||
|
return this.parseInput(this.state.input);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseInput(input) {
|
||||||
|
//first we clean the input using a regex
|
||||||
|
let re = /[([]{1}\s*([0-9]*)\s*,\s*[0-9]+\s*[)\]]{1}/g;
|
||||||
|
let cleaned = input.match(re);
|
||||||
|
let output = [];
|
||||||
|
|
||||||
|
//strip spaces, tabs and newlines
|
||||||
|
input = input.replaceAll(" ", "");
|
||||||
|
input = input.replaceAll("\t", "");
|
||||||
|
input = input.replaceAll("\n", "");
|
||||||
|
|
||||||
|
//we now match each single number and put it in our output
|
||||||
|
re = /[0-9]+/g;
|
||||||
|
if (cleaned == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
cleaned.forEach((element) => {
|
||||||
|
let newEntry = element.match(re);
|
||||||
|
//convert to numbers
|
||||||
|
newEntry[0] = Number(newEntry[0]);
|
||||||
|
newEntry[1] = Number(newEntry[1]);
|
||||||
|
output.push(newEntry);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(output);
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
return (<h1>Custom</h1>)
|
return (<div>
|
||||||
|
<h3 className="CustomHeader">Enter an edgelist like: <code>(0, 1), (0, 2), (1,2)</code></h3>
|
||||||
|
<textarea className="CustomInput" type="text" onChange={this.textFieldChanged}
|
||||||
|
value={this.state.input} required pattern="[([]{1}\s*([0-9]*)\s*,\s*[0-9]+\s*[)\]]{1}"
|
||||||
|
placeholder="(0, 1), (0, 2), (1,2)"/>
|
||||||
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,12 @@ class KarateClass extends Network {
|
|||||||
//super("Karate",
|
//super("Karate",
|
||||||
//[[2,1], [3,1], [4,1], [5,1]]);
|
//[[2,1], [3,1], [4,1], [5,1]]);
|
||||||
super("Karate",
|
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]]);
|
[[2, 1],[3, 1], [3, 2],[4 ,1], [4, 2], [4, 3],[5, 1],[6, 1],[7, 1], [7, 5], [7, 6],[8, 1], [8, 2], [8, 3], [8, 4],[9, 1], [9, 3],[10, 3],[11, 1], [11, 5], [11, 6],[12, 1],[13, 1], [13, 4],[14, 1], [14, 2], [14, 3], [14, 4],[17, 6], [17, 7],[18, 1], [18, 2],[20, 1], [20, 2],[22, 1], [22, 2],[26, 24], [26, 25],[28, 3], [28, 24], [28, 25],[29, 3],[30, 24], [30, 27],[31, 2], [31, 9],[32, 1], [32, 25], [32, 26], [32, 29],[33, 3], [33, 9], [33, 15], [33, 16], [33, 19], [33, 21], [33, 23], [33, 24], [33, 30], [33, 31], [33, 32], [34, 9], [34, 10], [34, 14], [34, 15], [34, 16], [34, 19], [34, 20], [34, 21], [34, 23], [34, 24], [34, 27], [34, 28], [34, 29], [34, 30], [34, 31], [34, 32], [34, 33]]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
return (<h1>placeholder</h1>);
|
return (<h3 id="karateExplanation">This is the so called "Zachary Karate Club Network". It models a university karate club.</h3>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import '../../css/Network.css'
|
||||||
|
|
||||||
class Network extends React.Component{
|
class Network extends React.Component{
|
||||||
constructor(optionName, graph){
|
constructor(optionName, graph){
|
||||||
|
|||||||
@ -1,29 +1,29 @@
|
|||||||
function edgelistToDot(name, inp){
|
//function edgelistToDot(name, inp){
|
||||||
var output = "strict graph \"" + name + "\" {\n";
|
//var output = "strict graph \"" + name + "\" {\n";
|
||||||
for(var e in inp){
|
//for(var e in inp){
|
||||||
e = inp[e];
|
//e = inp[e];
|
||||||
output += e[0] + " -- " + e[1] + ";\n";
|
//output += e[0] + " -- " + e[1] + ";\n";
|
||||||
}
|
//}
|
||||||
output += "}";
|
//output += "}";
|
||||||
return output;
|
//return output;
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
function dotToEdgelist(graph){
|
//function dotToEdgelist(graph){
|
||||||
var outList = [];
|
//var outList = [];
|
||||||
graph = graph.split("\n");
|
//graph = graph.split("\n");
|
||||||
//var name = graph[0].split(" ")[2];
|
////var name = graph[0].split(" ")[2];
|
||||||
//name = name.slice(1, name.length - 1);
|
////name = name.slice(1, name.length - 1);
|
||||||
for (var i = 0; i < graph.length - 1; i++){
|
//for (var i = 0; i < graph.length - 1; i++){
|
||||||
if(i === 0){
|
//if(i === 0){
|
||||||
continue;
|
//continue;
|
||||||
}
|
//}
|
||||||
var nodes = graph[i].split("--");
|
//var nodes = graph[i].split("--");
|
||||||
var node1 = Number(nodes[0]);
|
//var node1 = Number(nodes[0]);
|
||||||
var node2 = Number(nodes[1].slice(0, nodes[1].length - 1));
|
//var node2 = Number(nodes[1].slice(0, nodes[1].length - 1));
|
||||||
outList.push([node1, node2]);
|
//outList.push([node1, node2]);
|
||||||
}
|
//}
|
||||||
return outList;
|
//return outList;
|
||||||
}
|
//}
|
||||||
|
|
||||||
function edgeListToGraph(list){
|
function edgeListToGraph(list){
|
||||||
let outNodes = [];
|
let outNodes = [];
|
||||||
|
|||||||
@ -1,2 +1,8 @@
|
|||||||
.App {
|
.App {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
color: #eeeeee;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background-color: #222831;
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/css/Dropdown.css
Normal file
18
src/css/Dropdown.css
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.DropdownDescription {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12pt;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dropdown {
|
||||||
|
color: #eeeeee;
|
||||||
|
font-size: 15pt;
|
||||||
|
display: block;
|
||||||
|
background-color: #2d4059;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
height: 50px;
|
||||||
|
width: 85%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
@ -1,6 +1,53 @@
|
|||||||
|
#recalculate {
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 25pt;
|
||||||
|
color: #eeeeee;
|
||||||
|
background-color: #ff5722;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#runSimulationButton {
|
||||||
|
margin-right: 20px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 25pt;
|
||||||
|
color: #eeeeee;
|
||||||
|
background-color: green;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#durationDescription {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 20px;
|
||||||
|
color: #eeeeee;
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#animationDuration {
|
||||||
|
width: 60px;
|
||||||
|
font-size: 20pt;
|
||||||
|
color: #ff5722;
|
||||||
|
border-color: #ff5722;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #222831;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#animationDuration:focus {
|
||||||
|
outline-width: 0;
|
||||||
|
outline: solid;
|
||||||
|
}
|
||||||
|
|
||||||
/* Generated graph container */
|
/* Generated graph container */
|
||||||
#cy {
|
#cy {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 600px;
|
height: 100%;
|
||||||
background-color: blue;
|
min-height: 600px;
|
||||||
|
background-color: #222831;
|
||||||
}
|
}
|
||||||
|
|||||||
4
src/css/Header.css
Normal file
4
src/css/Header.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #ff5722;
|
||||||
|
}
|
||||||
34
src/css/Model.css
Normal file
34
src/css/Model.css
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#karateExplanation {
|
||||||
|
width: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ColorPicker {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 150px;
|
||||||
|
margin-top: 5px;
|
||||||
|
width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
transform: translateY(37px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* valudation text thingy */
|
||||||
|
.DistributionStatus {
|
||||||
|
padding: 1px 15px 1px 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: fit-content;
|
||||||
|
margin-bottom: -10px;
|
||||||
|
transition: width 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.valid {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* padding for the validation below */
|
||||||
|
.CustomModel {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
23
src/css/Network.css
Normal file
23
src/css/Network.css
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/* this also applies to network */
|
||||||
|
.CustomInput {
|
||||||
|
width: 83%;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #2d4059;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
color: #eeeeee;
|
||||||
|
font-size: 15pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CustomInput:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CustomInput:invalid {
|
||||||
|
outline: solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 15pt;
|
||||||
|
}
|
||||||
@ -8,18 +8,22 @@
|
|||||||
width: 60%;
|
width: 60%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex: 50%;
|
flex: 50%;
|
||||||
text-align: center;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
#SimulationGraph {
|
#SimulationGraph {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: sticky;
|
||||||
|
top: 3%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Responsive layout - makes a one column layout instead of a two-column layout */
|
/* Responsive layout - makes a one column layout instead of a two-column layout */
|
||||||
@media screen and (max-width: 800px) {
|
@media screen and (max-width: 800px) {
|
||||||
#SimulationSettings, #SimulationGraph {
|
#SimulationSettings, #SimulationGraph {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
position: inherit;
|
||||||
}
|
}
|
||||||
#Simulation {
|
#Simulation {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -1,16 +1,78 @@
|
|||||||
|
.SliderDiv {
|
||||||
|
padding: 10px 0px 10px 0px;
|
||||||
|
}
|
||||||
.Slider {
|
.Slider {
|
||||||
width: 70%;
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove default slider */
|
||||||
|
input[type="range"] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-color: #222831;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* track */
|
||||||
|
input[type="range"]::-webkit-slider-runnable-track {
|
||||||
|
background: linear-gradient(90deg, rgba(45,64,89,1) 35%, rgba(255,87,34,1) 100%);
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-track {
|
||||||
|
background: linear-gradient(90deg, rgba(45,64,89,1) 35%, rgba(255,87,34,1) 100%);
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* thumb */
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 23px;
|
||||||
|
width: 23px;
|
||||||
|
background: #ff5722;
|
||||||
|
margin-top: -9px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-width: 8px;
|
||||||
|
border-color: #ff5722;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
background: #ff5722;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-width: 4px;
|
||||||
|
border-color: #ff5722;
|
||||||
}
|
}
|
||||||
|
|
||||||
.SliderDescription {
|
.SliderDescription {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding-left: 13%;
|
padding-left: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 15pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.SliderValue {
|
.SliderValue {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 3em;
|
width: 3em;
|
||||||
|
/*transform: translateY(-3px);*/
|
||||||
|
margin-left: 1em;
|
||||||
|
background-color: #222831;
|
||||||
|
color: #ff5722;
|
||||||
|
border-color: #ff5722;
|
||||||
|
border-style: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 15pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SliderValue:focus {
|
||||||
|
outline-width: 0;
|
||||||
|
outline: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove arrows from number input */
|
/* Remove arrows from number input */
|
||||||
@ -21,7 +83,7 @@ input::-webkit-inner-spin-button {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Firefox */
|
/* Firefox remove arrows */
|
||||||
input[type=number] {
|
input[type=number] {
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
}
|
}
|
||||||
|
|||||||
71
ä
Normal file
71
ä
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
.SliderDiv {
|
||||||
|
padding: 10px 0px 10px 0px;
|
||||||
|
}
|
||||||
|
.Slider {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove default slider */
|
||||||
|
input[type="range"] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-color: #222831;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* track */
|
||||||
|
input[type="range"]::-webkit-slider-runnable-track {
|
||||||
|
background: #ff5722;
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-track {
|
||||||
|
background: #ff5722;
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* thumb */
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
background: pink;
|
||||||
|
margin-top: -5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
background: pink;
|
||||||
|
margin-top: -5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SliderDescription {
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SliderValue {
|
||||||
|
display: inline-block;
|
||||||
|
width: 3em;
|
||||||
|
transform: translateY(-6px);
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user