diff options
author | Determinant <[email protected]> | 2019-04-10 01:09:41 -0400 |
---|---|---|
committer | Determinant <[email protected]> | 2019-04-10 01:09:41 -0400 |
commit | 291895a75856408788925bfb34e4c085c40efbaf (patch) | |
tree | 4fe1ea3e1b1faf5012de93104c49032dc3937a52 /src | |
parent | 94330abf2d1ba606976ec4f2dc10277b78cdd036 (diff) |
finish version 1.0
Diffstat (limited to 'src')
-rw-r--r-- | src/Grid.css | 35 | ||||
-rw-r--r-- | src/Grid.tsx | 35 | ||||
-rw-r--r-- | src/Logo.tsx | 12 | ||||
-rw-r--r-- | src/Snow.tsx | 220 | ||||
-rw-r--r-- | src/index.html | 3 | ||||
-rw-r--r-- | src/index.tsx | 4 |
6 files changed, 258 insertions, 51 deletions
diff --git a/src/Grid.css b/src/Grid.css new file mode 100644 index 0000000..a9c7802 --- /dev/null +++ b/src/Grid.css @@ -0,0 +1,35 @@ +div.grid { + display: inline-block; +} + + +.grid div { + -webkit-transition: background-color 0.1s ease-out; + -moz-transition: background-color 0.1s ease-out; + -o-transition: background-color 0.1s ease-out; + transition: background-color 0.1s ease-out; +} + +.gridCell > div > div { + height: 20px; + width: 20px; + margin: 2px; + display: inline-block; +} + +.smallGridCell > div > div { + height: 15px; + width: 15px; + margin: 1px; + display: inline-block; +} + +.gridRow > div { + height: 24px; + text-align: center; +} + +.smallGridRow > div { + height: 17px; + text-align: center; +} diff --git a/src/Grid.tsx b/src/Grid.tsx index f68c550..995e59f 100644 --- a/src/Grid.tsx +++ b/src/Grid.tsx @@ -5,44 +5,43 @@ import orange from '@material-ui/core/colors/orange'; import blue from '@material-ui/core/colors/blue'; import { Theme, withStyles, StyleRules } from '@material-ui/core/styles'; import Color from 'color'; +import { TransitionGroup, CSSTransition } from "react-transition-group"; +import './Grid.css'; const styles = (theme: Theme): StyleRules => ({ - gridRow: { - height: 24, - textAlign: 'center' - }, - gridCell: { - height: 20, - width: 20, - margin: 2, - display: 'inline-block' - } }); interface GridProps { classes: { - gridRow: string, - gridCell: string }, data: {d: number[], col: number}[][] } +export function getNodeColor(d: number, col: number) { + const color = [orange[300], blue[300]]; + let base = Color(color[col]).hsl().array(); + base[2] = 80; + return Color.hsl(base).darken(Math.min(d / 15, 0.5)).hex(); +} + class Grid extends React.Component<GridProps> { static getColor(s: {d: number[], col: number}) { - const color = [orange[300], blue[300]]; - let {d, col} = s; - return Color(color[col]).darken(Math.min(d[col] / 10, 0.6)).hex(); + return getNodeColor(s.d[s.col], s.col); } render() { const { classes, data } = this.props; + const { gr, gc } = data.length <= 20 ? + { gr: 'gridRow', gc: 'gridCell' } : + { gr: 'smallGridRow', gc: 'smallGridCell' }; return ( - <div style={{margin: '0 auto'}}> + <div className={`grid ${gr} ${gc}`}> { data.map((row, i) => ( - <div key={i} className={classes.gridRow}> + <div key={i}> { row.map((cell, j) => ( - <div key={j} className={classes.gridCell} style={{backgroundColor: Grid.getColor(cell)}}> + <div key={j} + style={{backgroundColor: Grid.getColor(cell)}}> </div> )) } diff --git a/src/Logo.tsx b/src/Logo.tsx new file mode 100644 index 0000000..1bac2b7 --- /dev/null +++ b/src/Logo.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +export default (props: {style: {[key: string]: string | number }}) => + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 97.079997 37.00" + version="1.1" + style={props.style}> +<path + d="M 20.52,0 0,36.02 H 14.85 L 28.03,13.19 Z M 13.7,34.02 H 3.45 l 17.08,-29.98 5.21,9.14 z m 8.79,2 H 41.04 L 31.82,19.85 Z m 3.46,-2 5.86,-10.16 5.79,10.16 z M 76.57,0 56.05,36.02 H 70.9 L 84.08,13.19 Z M 69.74,34.02 H 59.49 L 76.57,4.04 81.78,13.18 Z M 87.87,19.84 78.53,36.01 h 18.55 z m -0.01,4.02 5.79,10.16 H 82 Z M 42.92,0 H 28.07 L 48.59,36.02 56.1,22.84 Z M 31.51,2 h 10.25 l 12.03,20.84 -5.21,9.14 z M 69.11,0 H 50.56 L 59.9,16.17 Z M 54.02,2 h 11.65 l -5.79,10.16 z" + id="path4" + style={{fill:'#f5f1ee'}} /> +</svg> diff --git a/src/Snow.tsx b/src/Snow.tsx index a391f0a..89c5073 100644 --- a/src/Snow.tsx +++ b/src/Snow.tsx @@ -8,13 +8,17 @@ import TableRow from '@material-ui/core/TableRow'; import TableCell from '@material-ui/core/TableCell'; import Button from '@material-ui/core/Button'; import Slider from '@material-ui/lab/Slider'; -import Grid from './Grid'; +import FormGroup from '@material-ui/core/FormGroup'; +import Grid, {getNodeColor} from './Grid'; +import { Line } from 'react-chartjs-2'; +import Color from 'color'; const styles = (theme: Theme): StyleRules => ({ inputLabel: { fontSize: 16, paddingRight: 0, - textAlign: 'right' + textAlign: 'right', + width: '30%' }, inputValue: { textAlign: 'left' @@ -28,6 +32,15 @@ const styles = (theme: Theme): StyleRules => ({ }, slider: { padding: '22px 0px', + }, + errorHint: { + fontSize: 16, + paddingLeft: 16, + lineHeight: '32px', + color: theme.palette.secondary.main + }, + grid: { + textAlign: 'center' } }); @@ -37,7 +50,9 @@ interface SnowProps { inputValue: string, buttonSpacer: string, bottomButtons: string, - slider: string + slider: string, + errorHint: string, + grid: string } } @@ -56,9 +71,11 @@ function getRandomSubarray(arr: number[], size: number) { return shuffled.slice(min); } +const watchedD = [15, 10, 5, 1]; + class Snow extends React.Component<SnowProps> { - static genMatrix(n = 20) { + static genMatrix(n: number) { let d = []; for (let i = 0; i < n; i++) { @@ -71,16 +88,31 @@ class Snow extends React.Component<SnowProps> { } state = { - colorMatrix: Snow.genMatrix(), - k: 10, - alpha: 8, - nodesPerTick: 10, - maxInactiveTicks: 200, + colorMatrix: Snow.genMatrix(20), + n: '20', + k: '10', + alpha: '8', + nodesPerTick: '20', + maxInactiveTicks: '200', + loaded: true, ticking: false, - simulationSpeed: 100 + simulationSpeed: 100, + dialogOpen: false, + dialogMsg: {title: '', message: ''}, + nError: false, + kError: false, + alphaError: false, + nodesPerTickError: false, + maxInactiveTicksError: false, + dcnts: [watchedD.map(() => [] as number[]), + watchedD.map(() => [] as number[])] as number[][][], + ticks: [] as number[], + N: 400 }; config = { + iter: 0, + n: 20, k: 10, alpha: 8, nodesPerTick: 10, @@ -105,8 +137,10 @@ class Snow extends React.Component<SnowProps> { tick(n: number, m: number) { let N = n * n; let active = false; - for (let i = 0; i < m; i++) - { + + let nodes = []; + for (let j = 0; j < N; j++) nodes.push(j); + getRandomSubarray(nodes, m).forEach(v => { let u = getRandomInt(N); let peers = []; for (let j = 0; j < N; j++) @@ -131,6 +165,30 @@ class Snow extends React.Component<SnowProps> { } } } + }); + + if (this.config.iter % 10 == 0) + { + let dcnts = []; + for (let c = 0; c < 2; c++) + { + dcnts.push(watchedD.map((d, i) => { + let dcnt = 0; + for (let i = 0; i < n; i++) + for (let j = 0; j < n; j++) + { + const s = this.state.colorMatrix[i][j]; + if (s.d[c] >= d) + dcnt++; + } + if (c == 0) dcnt = -dcnt; + return [...this.state.dcnts[c][i], dcnt].splice(-50); + })); + } + this.setState({ + dcnts: dcnts, + ticks: [...this.state.ticks, this.config.iter].splice(-50) + }); } return active; } @@ -140,19 +198,65 @@ class Snow extends React.Component<SnowProps> { } startTick() { - this.config.alpha = this.state.alpha; - this.config.k = this.state.k; - this.config.nodesPerTick = this.state.nodesPerTick; + const n = Number(this.state.n); + const N = n * n; + const k = Number(this.state.k); + const alpha = Number(this.state.alpha); + const nodesPerTick = Number(this.state.nodesPerTick); + const maxInactiveTicks = Number(this.state.maxInactiveTicks); + + if (!Number.isInteger(n) || n < 2 || n > 40) + { + this.setState({ nError: true }); + return; + } + if (!Number.isInteger(k) || k < 1 || k > N) + { + this.setState({ kError: true }); + return; + } + if (!Number.isInteger(alpha) || !(k / 2 < alpha && alpha <= k)) + { + this.setState({ alphaError: true }); + return; + } + if (!Number.isInteger(nodesPerTick) || nodesPerTick < 1 || nodesPerTick > N) + { + this.setState({ nodesPerTickError: true }); + return; + } + if (!Number.isInteger(maxInactiveTicks) || maxInactiveTicks < 1 || maxInactiveTicks > 1e6) + { + this.setState({ maxInactiveTicksError: true }); + return; + } + + if (!this.state.loaded) + { + this.config.iter = 0; + this.config.n = n; + this.setState({ + loaded: true, + colorMatrix: Snow.genMatrix(this.config.n), + dcnts: [watchedD.map(() => [] as number[]), + watchedD.map(() => [] as number[])], + ticks: [], + N: n * n + }); + } + this.config.alpha = alpha; + this.config.k = k; + this.config.nodesPerTick = nodesPerTick; this.config.inactiveTicks = 0; - this.config.maxInactiveTicks = this.state.maxInactiveTicks; + this.config.maxInactiveTicks = maxInactiveTicks; this.autoTick(); } autoTick() { this.setState({ticking: true}); setTimeout(() => { - let active = this.tick(20, this.config.nodesPerTick); - console.log(active); + let active = this.tick(this.config.n, this.config.nodesPerTick); + this.config.iter++; if (!active) { if (++this.config.inactiveTicks > this.config.maxInactiveTicks) @@ -169,8 +273,8 @@ class Snow extends React.Component<SnowProps> { reset() { this.setState({ - colorMatrix: Snow.genMatrix(), - ticking: false + ticking: false, + loaded: false }); } @@ -179,22 +283,60 @@ class Snow extends React.Component<SnowProps> { return ( <MGrid container spacing={16} style={{minWidth: 600}}> - <MGrid item lg={6} xs={12}> + <MGrid item lg={6} xs={12} className={classes.grid}> <Grid data={this.state.colorMatrix} /> + <Line data={() => { + let datasets = this.state.dcnts.map((dd, c) => dd.map((line, i) => { + const base = getNodeColor(watchedD[i], c); + return { + data: line, + label: `${c == 0 ? 'A' : 'B'}(d-${watchedD[i]})`, + borderColor: base, + backgroundColor: Color(base).fade(0.5).rgb().string(), + borderWidth: 2 + }; + })).flat(); + return { + datasets, + labels: this.state.ticks + } + }} + options={{ scales: { yAxes: [{ ticks: {min: -this.state.N, max: this.state.N}}]}}}/> </MGrid> <MGrid item lg={4} xs={12}> <Table> <TableBody> <TableRow> <TableCell className={classes.inputLabel}> + n = + </TableCell> + <TableCell> + <TextField + inputProps={{ className: classes.inputValue, maxLength: 2 } as React.CSSProperties} + value={this.state.n} + disabled={this.state.loaded} + style={{width: 40}} + error={this.state.nError} + onChange={event => this.setState({n: event.target.value, nError: false})}/> + <sup>2</sup> + {this.state.nError && + <span className={classes.errorHint}>n must be in 2..40</span>} + </TableCell> + </TableRow> + <TableRow> + <TableCell className={classes.inputLabel}> k = </TableCell> <TableCell> <TextField - inputProps={{ className: classes.inputValue } as React.CSSProperties} + inputProps={{ className: classes.inputValue, maxLength: 4 } as React.CSSProperties} value={this.state.k} disabled={this.state.ticking} - onChange={event => this.setState({k: event.target.value})}/> + style={{width: 40}} + error={this.state.kError} + onChange={event => this.setState({k: event.target.value, kError: false})}/> + {this.state.kError && + <span className={classes.errorHint}>k must be in 1..n</span>} </TableCell> </TableRow> <TableRow> @@ -203,10 +345,14 @@ class Snow extends React.Component<SnowProps> { </TableCell> <TableCell> <TextField - inputProps={{ className: classes.inputValue } as React.CSSProperties} + inputProps={{ className: classes.inputValue, maxLength: 4 } as React.CSSProperties} value={this.state.alpha} disabled={this.state.ticking} - onChange={event => this.setState({alpha: event.target.value})}/> + style={{width: 40}} + error={this.state.alphaError} + onChange={event => this.setState({alpha: event.target.value, alphaError: false})}/> + {this.state.alphaError && + <span className={classes.errorHint}>alpha must be in (k/2, k]</span>} </TableCell> </TableRow> <TableRow> @@ -215,10 +361,14 @@ class Snow extends React.Component<SnowProps> { </TableCell> <TableCell> <TextField - inputProps={{ className: classes.inputValue } as React.CSSProperties} + inputProps={{ className: classes.inputValue, maxLength: 4 } as React.CSSProperties} value={this.state.nodesPerTick} disabled={this.state.ticking} - onChange={event => this.setState({nodesPerTick: event.target.value})}/> + style={{width: 40}} + error={this.state.nodesPerTickError} + onChange={event => this.setState({nodesPerTick: event.target.value, nodesPerTickError: false})}/> + {this.state.nodesPerTickError && + <span className={classes.errorHint}>nodesPerTick must be in 1..n</span>} </TableCell> </TableRow> <TableRow> @@ -230,7 +380,11 @@ class Snow extends React.Component<SnowProps> { inputProps={{ className: classes.inputValue } as React.CSSProperties} value={this.state.maxInactiveTicks} disabled={this.state.ticking} - onChange={event => this.setState({maxInactiveTicks: event.target.value})}/> + style={{width: 50}} + error={this.state.maxInactiveTicksError} + onChange={event => this.setState({maxInactiveTicks: event.target.value, maxInactiveTicksError: false})}/> + {this.state.maxInactiveTicksError && + <span className={classes.errorHint}>maxInactiveTicks must be in 1..1000000</span>} </TableCell> </TableRow> <TableRow> @@ -252,21 +406,27 @@ class Snow extends React.Component<SnowProps> { <div className={classes.bottomButtons}> <MGrid container item spacing={16}> <MGrid item lg={4} xs={12}> + <FormGroup> <Button variant="contained" color="primary" onClick={event => this.startTick()} - disabled={this.state.ticking}>Start</Button> + disabled={this.state.ticking}>Run</Button> + </FormGroup> </MGrid> <MGrid item lg={4} xs={12}> + <FormGroup> <Button variant="contained" color="primary" onClick={event => this.pauseTick()} - disabled={!this.state.ticking}>Pause</Button> + disabled={!this.state.ticking}>Stop</Button> + </FormGroup> </MGrid> <MGrid item lg={4} xs={12}> + <FormGroup> <Button variant="contained" color="primary" onClick={event => this.reset()}>Reset</Button> + </FormGroup> </MGrid> </MGrid> </div> diff --git a/src/index.html b/src/index.html index 708c4fe..ef3bba6 100644 --- a/src/index.html +++ b/src/index.html @@ -7,8 +7,7 @@ content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <meta name="theme-color" content="#000000" /> - <link rel="stylesheet" href="/fonts/TypoPRO-FantasqueSansMono-Regular.css" /> - <title>Snow</title> + <title>Snow BFT Demo</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> diff --git a/src/index.tsx b/src/index.tsx index ad08a87..baa46f5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -16,6 +16,7 @@ import { TransitionGroup, CSSTransition } from "react-transition-group"; import { theme } from './theme'; import Snow from './Snow'; import About from './About'; +import Logo from './Logo'; const styles = (theme: Theme) => ({ root: { @@ -96,7 +97,8 @@ class MainTabs extends React.Component<MainTabsProps> { className={classes.appBar}> <Toolbar className={classes.toolbar}> <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}> - Snow + <Logo style={{width: '5em', verticalAlign: 'bottom', marginRight: '0.5em'}}/> + Snow BFT Demo </Typography> <Tabs classes={{ indicator: classes.indicator }} |