first draft finished

This commit is contained in:
JuliusHerrmann 2021-10-13 03:38:55 +02:00
parent ef1a49e32c
commit 8798c141dc
22 changed files with 552 additions and 72 deletions

View File

@ -8,8 +8,9 @@ import HeaderData from './Header/Headerdata';
function App() { function App() {
return ( return (
<div className="App"> <div className="App">
<HeaderData/> <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet"/>
<Simulation/> <HeaderData/>
<Simulation/>
</div> </div>
); );
} }

View File

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

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

View File

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

View File

@ -96,8 +96,10 @@ class GraphCytoscape extends React.Component {
changeAnimationDuration = (e) => { changeAnimationDuration = (e) => {
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>);
} }
} }

View File

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

View File

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

View File

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

View File

@ -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
@ -49,7 +54,7 @@ class Model extends React.Component {
//update //update
this.props.updateSelectedModel(this); this.props.updateSelectedModel(this);
} }
changeProbability = (spot, e) => { changeProbability = (spot, e) => {
//update state //update state
this.setState({}); this.setState({});
@ -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 ++;

View File

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

View File

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

View File

@ -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){

View File

@ -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 = [];

View File

@ -1,2 +1,8 @@
.App { .App {
font-family: 'Roboto', sans-serif;
color: #eeeeee;
}
html {
background-color: #222831;
} }

18
src/css/Dropdown.css Normal file
View 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;
}

View File

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

@ -0,0 +1,4 @@
a {
text-decoration: none;
color: #ff5722;
}

34
src/css/Model.css Normal file
View 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
View 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;
}

View File

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

View File

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