first draft finished
This commit is contained in:
parent
ef1a49e32c
commit
8798c141dc
@ -8,6 +8,7 @@ import HeaderData from './Header/Headerdata';
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet"/>
|
||||
<HeaderData/>
|
||||
<Simulation/>
|
||||
</div>
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import React from 'react';
|
||||
import '../css/Header.css';
|
||||
|
||||
class HeaderData extends React.Component{
|
||||
render(){
|
||||
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 '../css/Dropdown.css'
|
||||
|
||||
class Dropdown extends React.Component{
|
||||
constructor(props){
|
||||
@ -20,8 +21,8 @@ class Dropdown extends React.Component{
|
||||
render(){
|
||||
return(
|
||||
<div className='DropdownDiv'>
|
||||
<label htmlFor='Dropdown' className='dropdownDescription'>{this.props.description}</label>
|
||||
<select id='Dropdown'
|
||||
<label htmlFor='Dropdown' className='DropdownDescription'>{this.props.description}</label>
|
||||
<select className='Dropdown'
|
||||
name={this.props.name}
|
||||
onChange={this.changeVal}>
|
||||
{this.props.options.map(this.createOption)}
|
||||
|
||||
@ -97,7 +97,9 @@ class GraphCytoscape extends React.Component {
|
||||
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 (<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}/>
|
||||
<button id="recalculate" onClick={this.props.recalculateFuntion}>Recalculate 🔁</button>
|
||||
<button id="runSimulationButton" onClick={this.visualizeSimulation}>Play Simulation ▶️</button>
|
||||
<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>);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 (
|
||||
//<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">
|
||||
@ -94,10 +88,10 @@ class Simulation extends React.Component{
|
||||
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()}/>
|
||||
<GraphCytoscape recalculateFuntion={this.recalculate}
|
||||
graphData={this.state.graphData} simulationData={this.state.simulationData} colors={this.state.selectedModel.getColors()}/>
|
||||
</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 DistributionStatus from "../DistributionStatus";
|
||||
import Slider from "../Slider";
|
||||
import '../../css/Model.css';
|
||||
|
||||
class Model extends React.Component {
|
||||
constructor(optionName, states, rules, ruleMax, ruleStep) {
|
||||
@ -9,6 +11,9 @@ class Model extends React.Component {
|
||||
this.rules = rules;
|
||||
this.ruleMax = ruleMax;
|
||||
this.ruleStep = ruleStep;
|
||||
//this.state = {valid: true};
|
||||
this.valid = true;
|
||||
this.checkValidity();
|
||||
}
|
||||
|
||||
//override default get name
|
||||
@ -58,12 +63,28 @@ class Model extends React.Component {
|
||||
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) => {
|
||||
//update state
|
||||
this.setState({});
|
||||
this.states[spot][1] = Number(e.target.value);
|
||||
//update
|
||||
this.props.updateSelectedModel(this);
|
||||
this.checkValidity();
|
||||
}
|
||||
|
||||
changeColor = (spot, e) => {
|
||||
@ -94,13 +115,18 @@ class Model extends React.Component {
|
||||
|
||||
//build the sliders for the initial distribution
|
||||
buildSlidersDistribution() {
|
||||
var output = [];
|
||||
var output = [<DistributionStatus key="validation" valid={this.valid}/>];
|
||||
var count = 0;
|
||||
this.states.forEach((s) => {
|
||||
//clamp description
|
||||
let description = s[0];
|
||||
if (description.length > 10) {
|
||||
description = description.substring(0,11) + "...";
|
||||
}
|
||||
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)}/>
|
||||
<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>
|
||||
);
|
||||
count ++;
|
||||
|
||||
@ -5,10 +5,53 @@ class Custom extends Network {
|
||||
constructor(){
|
||||
super("Custom",
|
||||
[[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(){
|
||||
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",
|
||||
//[[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]]);
|
||||
[[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(){
|
||||
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 '../../css/Network.css'
|
||||
|
||||
class Network extends React.Component{
|
||||
constructor(optionName, graph){
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
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 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 = [];
|
||||
|
||||
@ -1,2 +1,8 @@
|
||||
.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 */
|
||||
#cy {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
background-color: blue;
|
||||
height: 100%;
|
||||
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%;
|
||||
height: 100%;
|
||||
flex: 50%;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#SimulationGraph {
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
position: sticky;
|
||||
top: 3%;
|
||||
}
|
||||
|
||||
|
||||
/* Responsive layout - makes a one column layout instead of a two-column layout */
|
||||
@media screen and (max-width: 800px) {
|
||||
#SimulationSettings, #SimulationGraph {
|
||||
width: 100%;
|
||||
position: inherit;
|
||||
}
|
||||
#Simulation {
|
||||
flex-direction: column;
|
||||
|
||||
@ -1,16 +1,78 @@
|
||||
.SliderDiv {
|
||||
padding: 10px 0px 10px 0px;
|
||||
}
|
||||
.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 {
|
||||
display: block;
|
||||
text-align: left;
|
||||
padding-left: 13%;
|
||||
padding-left: 5px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 15pt;
|
||||
}
|
||||
|
||||
.SliderValue {
|
||||
display: inline-block;
|
||||
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 */
|
||||
@ -21,7 +83,7 @@ input::-webkit-inner-spin-button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
/* Firefox remove arrows */
|
||||
input[type=number] {
|
||||
-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