From 291895a75856408788925bfb34e4c085c40efbaf Mon Sep 17 00:00:00 2001 From: Determinant Date: Wed, 10 Apr 2019 01:09:41 -0400 Subject: finish version 1.0 --- src/Grid.css | 35 +++++++++ src/Grid.tsx | 35 +++++---- src/Logo.tsx | 12 ++++ src/Snow.tsx | 220 +++++++++++++++++++++++++++++++++++++++++++++++++-------- src/index.html | 3 +- src/index.tsx | 4 +- 6 files changed, 258 insertions(+), 51 deletions(-) create mode 100644 src/Grid.css create mode 100644 src/Logo.tsx (limited to 'src') 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 { 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 ( -
+
{ data.map((row, i) => ( -
+
{ row.map((cell, j) => ( -
+
)) } 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 }}) => + + + 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 { - 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 { } 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 { 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 { } } } + }); + + 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 { } 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 { reset() { this.setState({ - colorMatrix: Snow.genMatrix(), - ticking: false + ticking: false, + loaded: false }); } @@ -179,22 +283,60 @@ class Snow extends React.Component { return ( - + + { + 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}}]}}}/> + + + n = + + + this.setState({n: event.target.value, nError: false})}/> + 2 + {this.state.nError && + n must be in 2..40} + + k = 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 && + k must be in 1..n} @@ -203,10 +345,14 @@ class Snow extends React.Component { 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 && + alpha must be in (k/2, k]} @@ -215,10 +361,14 @@ class Snow extends React.Component { 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 && + nodesPerTick must be in 1..n} @@ -230,7 +380,11 @@ class Snow extends React.Component { 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 && + maxInactiveTicks must be in 1..1000000} @@ -252,21 +406,27 @@ class Snow extends React.Component {
+ + disabled={this.state.ticking}>Run + + + disabled={!this.state.ticking}>Stop + + +
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" /> - - Snow + Snow BFT Demo 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 { className={classes.appBar}> - Snow + + Snow BFT Demo