Initial work

This commit is contained in:
dapplion 2019-07-31 19:47:59 +02:00
parent b0082b303e
commit 9ff526dcd8
18 changed files with 11515 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# IDEs and editors
/.idea
/.vscode
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

35
package.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "augmented-tbc-design",
"version": "0.1.0",
"dependencies": {
"@material-ui/core": "latest",
"@types/react": "latest",
"@types/react-dom": "latest",
"react": "latest",
"react-dom": "latest",
"react-scripts": "latest",
"recharts": "^1.6.2",
"typescript": "latest"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/recharts": "^1.1.20"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

46
public/index.html Normal file
View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Augmented Bonding Curve Design</title>
<!-- Fonts to support Material Design -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

15
public/manifest.json Normal file
View File

@ -0,0 +1,15 @@
{
"short_name": "Your Orders",
"name": "Your Orders",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

358
src/App.tsx Normal file
View File

@ -0,0 +1,358 @@
import React, { useState, useEffect } from "react";
// Material UI
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import Container from "@material-ui/core/Container";
import Typography from "@material-ui/core/Typography";
import Box from "@material-ui/core/Box";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";
import Fade from "@material-ui/core/Fade";
// Components
import Header from "./Header";
import InputParams from "./InputParams";
import SupplyVsDemandChart from "./SupplyVsDemandChart";
import ResultParams from "./ResultParams";
import PriceSimulationChart from "./PriceSimulationChart";
// Utils
import { getLast, getAvg, pause } from "./utils";
// General styles
import "./app.css";
const headerOffset = 10;
const useStyles = makeStyles((theme: Theme) =>
createStyles({
mainContainer: {
"& > div:not(:last-child)": {
paddingBottom: theme.spacing(6)
},
"& > div": {
"& > div": {
paddingTop: "0 !important"
}
}
},
paper: {
width: "100%",
height: "100%",
minHeight: 310
},
box: {
padding: theme.spacing(3, 3),
minHeight: 310
},
boxHeader: {
padding: theme.spacing(3, 3),
height: theme.spacing(headerOffset),
display: "flex",
alignItems: "center",
borderBottom: "1px solid #e0e0e0"
},
boxChart: {
width: "100%",
height: "100%",
minHeight: 310,
maxHeight: 350,
padding: theme.spacing(3, 3),
// Correct the chart excessive margins
paddingRight: "5px",
paddingLeft: "5px"
},
header: {
backgroundColor: "#070a0e",
color: "#f5f7f8",
padding: theme.spacing(9, 0, 6 + headerOffset),
marginBottom: -theme.spacing(headerOffset)
},
button: {
background: "linear-gradient(290deg, #2ad179, #4ab47c)",
color: "white"
}
})
);
export default function App() {
const [d0, setD0] = useState(1e6); // Initial raise, d0 (DAI)
const [theta, setTheta] = useState(0.35); // fraction allocated to reserve (.)
const [p0, setP0] = useState(0.1); // Hatch sale Price p0 (DAI / token)
const [returnF, setReturnF] = useState(3); // Return factor (.)
const [wFee, setWFee] = useState(0.05); // friction coefficient (.)
// Simulation results
const k = returnF / (1 - theta); // Invariant power kappa (.)
const R0 = (1 - theta / 100) * d0; // Initial reserve (DAI)
const S0 = d0 / p0; // initial supply of tokens (token)
const V0 = S0 ** k / R0; // invariant coef
const [priceTimeseries, setPriceTimeseries] = useState([0]);
const [withdrawFeeTimeseries, setWithdrawFeeTimeseries] = useState([0]);
const [totalReserve, setTotalReserve] = useState(R0);
const [withdrawCount, setWithdrawCount] = useState(0);
const [avgSlippage, setAvgSlippage] = useState(0);
const [avgTxSize] = useState(10000);
// Simulation state variables
const [simulationActive, setSimulationActive] = useState(false);
const [simulationRunning, setSimulationRunning] = useState(false);
// #### TEST: Immediate simulation
async function startSimulation() {
// If there's a simulation already active, clear it
clearSimulation();
await pause(0);
// Start simulation by setting it to active
setSimulationActive(true);
}
function clearSimulation() {
// Stop simulation
setSimulationActive(false);
// Clear simulation variables
setWithdrawCount(0);
setPriceTimeseries([0]);
setWithdrawFeeTimeseries([0]);
setAvgSlippage(0);
}
useEffect(() => {
let canContinueSimulation = true;
async function simulateRandomDelta() {
const R_t: number[] = [R0];
const S_t: number[] = [S0];
const p_t: number[] = [R0 / S0];
const wFee_t: number[] = [0];
const slippage_t: number[] = [];
// Random walk
const numSteps = 100;
const updateEveryNth = 1;
// Compute the random deltas
const deltaR_t: number[] = [];
for (let i = 0; i < numSteps; i++) {
const rand = 1 - 2 * Math.random();
const sin = Math.sin((1 / 20) * (i / numSteps));
const ascending = Math.sin((Math.PI / 1) * (i / numSteps));
const deltaR = 1e5 * rand + 1e5 * sin + 2e4 * ascending;
deltaR_t.push(deltaR);
}
// Normalize random deltas with the average transaction size
const deltaR_avg = getAvg(deltaR_t);
const deltaR_t_normalized = deltaR_t.map(
(deltaR: number) => (avgTxSize * deltaR) / deltaR_avg
);
setSimulationRunning(true);
for (let i = 0; i < numSteps; i++) {
const deltaR = deltaR_t_normalized[i];
// Protect against too strong negative deltas
const R = getLast(R_t);
const S = getLast(S_t);
const p = getLast(p_t);
const R_next = R + deltaR;
const deltaS = (V0 * (R + deltaR)) ** (1 / k) - S;
const S_next = S + deltaS;
R_t.push(R_next);
S_t.push(S_next);
p_t.push(R_next / S_next);
// Consider withdraw fees on sales only
if (deltaR < 0) {
wFee_t.push(getLast(wFee_t) - deltaR * wFee);
setWithdrawCount(c => c + 1);
} else {
wFee_t.push(getLast(wFee_t));
}
const p_next = getLast(p_t);
// const realizedPrice = deltaR / deltaS;
const spotPrice = p;
const slippage = Math.abs(p_next - spotPrice) / spotPrice / 2;
slippage_t.push(slippage);
// Stop the simulation if it's no longer active
if (!simulationActive || !canContinueSimulation) break;
if (i % updateEveryNth === 0) {
setPriceTimeseries(p_t);
setWithdrawFeeTimeseries(wFee_t);
setAvgSlippage(getAvg(slippage_t));
setTotalReserve(R_next);
// Make this run non-UI blocking
await pause(5);
}
}
setSimulationRunning(false);
}
if (simulationActive) simulateRandomDelta();
// Return an "unsubscribe" function that halts the run
return () => {
canContinueSimulation = false;
};
}, [simulationActive]);
const inputFields: {
label: string;
value: number;
setter(newValue: any): void;
min: number;
max: number;
step: number;
display(value: number): string;
}[] = [
{
label: "Initial raise",
value: d0,
setter: setD0,
min: 0.1e6,
max: 10e6,
step: 0.1e6,
display: (n: number) => `$${+(n * 1e-6).toFixed(1)}M`
},
{
label: "Allocation to the project",
value: theta,
setter: setTheta,
min: 0,
max: 0.9,
step: 0.01,
display: (n: number) => `${Math.round(100 * n)}%`
},
{
label: "Initial token price",
value: p0,
setter: setP0,
min: 0.01,
max: 1,
step: 0.01,
display: (n: number) => `$${n}`
},
{
label: "Return factor",
value: returnF,
setter: setReturnF,
min: 1,
max: 10,
step: 0.1,
display: (n: number) => `${n}x`
},
{
label: "Withdrawl fee",
value: wFee,
setter: setWFee,
min: 0,
max: 0.1,
step: 0.001,
display: (n: number) => `${+(100 * n).toFixed(1)}%`
}
];
const resultFields = [
{
label: `Average slippage (avg tx size ${avgTxSize} DAI)`,
value: +(100 * avgSlippage).toFixed(3) + "%"
},
{
label: `Capital collected from withdraw fees (${withdrawCount} txs)`,
value:
(+getLast(withdrawFeeTimeseries).toPrecision(3)).toLocaleString() +
" DAI"
},
{
label: `Total reserve`,
value: (+totalReserve.toPrecision(3)).toLocaleString() + " DAI"
}
];
const classes = useStyles();
return (
<>
<header className={classes.header}>
<Container fixed>
<Header />
</Container>
</header>
<Container fixed className={classes.mainContainer}>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Grid
container
direction="row"
justify="space-between"
alignItems="center"
>
<Typography variant="h6">
{simulationActive ? "Results" : "Curve Design"}
</Typography>
<Fade in={simulationActive}>
<Button variant="contained" onClick={clearSimulation}>
Back to design
</Button>
</Fade>
</Grid>
</Box>
<Box className={classes.box}>
{simulationActive ? (
<ResultParams resultFields={resultFields} />
) : (
<InputParams inputFields={inputFields} />
)}
</Box>
</Paper>
</Grid>
<Grid item xs={12} md={6}>
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Grid
container
direction="row"
justify="space-between"
alignItems="center"
>
<Typography variant="h6">Preview</Typography>
<Button
variant="contained"
className={classes.button}
onClick={startSimulation}
disabled={simulationRunning}
>
Run simulation
</Button>
</Grid>
</Box>
<Box className={classes.boxChart}>
{simulationActive ? (
<PriceSimulationChart
priceTimeseries={priceTimeseries}
withdrawFeeTimeseries={withdrawFeeTimeseries}
p0={p0}
/>
) : (
<SupplyVsDemandChart
returnF={returnF}
theta={theta}
d0={d0}
p0={p0}
/>
)}
</Box>
</Paper>
</Grid>
</Grid>
</Container>
</>
);
}

38
src/Header.tsx Normal file
View File

@ -0,0 +1,38 @@
import React from "react";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import Link from "@material-ui/core/Link";
import Typography from "@material-ui/core/Typography";
const strongColor = "#4ab47c";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
margin: theme.spacing(3, 0, 0)
},
lightBulb: {
verticalAlign: "middle",
marginRight: theme.spacing(1)
},
link: {
color: strongColor
}
})
);
export default function Header() {
const classes = useStyles();
return (
<>
<Typography variant="h4" component="h1" gutterBottom>
Augmented Token Bonding Curve Design
</Typography>
<Typography className={classes.root}>
Experiment and test augmented token bonding curves, part of the{" "}
<Link className={classes.link} href="https://commonsstack.org/">
Commons Stack
</Link>
</Typography>
</>
);
}

139
src/InputParams.tsx Normal file
View File

@ -0,0 +1,139 @@
import React from "react";
import {
createStyles,
makeStyles,
withStyles,
Theme
} from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Slider from "@material-ui/core/Slider";
import Grid from "@material-ui/core/Grid";
const grayColor = "#90a4ae";
const blackColor = "#141e27";
// const commonsGradient = "#67de69 #1c709c";
const strongColor = "#4ab47c";
const PrettoSlider = withStyles({
root: {
color: strongColor,
height: 8
},
thumb: {
height: 24,
width: 24,
backgroundColor: "#fff",
border: "2px solid currentColor",
marginTop: -8,
marginLeft: -12,
"&:focus,&:hover,&$active": {
boxShadow: "inherit"
}
},
active: {},
valueLabel: {
left: "calc(-50% + 4px)"
},
track: {
height: 8,
borderRadius: 4
},
rail: {
height: 8,
borderRadius: 4
},
markLabel: {
color: grayColor,
top: 30
}
})(Slider);
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
margin: theme.spacing(6, 0, 3)
},
lightBulb: {
verticalAlign: "middle",
marginRight: theme.spacing(1)
},
leftContainer: {
color: grayColor
},
centerContainer: {
color: blackColor
},
listBoxContainer: {
"& > div:not(:last-child)": {
paddingBottom: "12px",
marginBottom: "12px",
borderBottom: "1px solid #e0e0e0"
}
},
listBox: {
height: "48px",
"& > div": {
display: "flex",
alignItems: "center",
"& p": {
marginBottom: 0
}
},
"& > div:not(:last-child)": {
paddingRight: "12px"
}
}
})
);
export default function InputParams({
inputFields
}: {
inputFields: {
label: string;
value: number;
setter(newValue: any): void;
min: number;
max: number;
step: number;
display(value: number): string;
}[];
}) {
const classes = useStyles();
return (
<div className={classes.listBoxContainer}>
{inputFields.map(({ label, value, setter, min, max, step, display }) => (
<Grid key={label} container spacing={0} className={classes.listBox}>
<Grid item xs={6} className={classes.leftContainer}>
<Typography id={label} gutterBottom>
{label}
</Typography>
</Grid>
<Grid item xs={2} className={classes.centerContainer}>
<Typography gutterBottom>{display(value)}</Typography>
</Grid>
<Grid item xs={4}>
<PrettoSlider
valueLabelDisplay="auto"
aria-label={label}
defaultValue={value}
onChange={(_, newValue) => setter(Number(newValue))}
min={min}
max={max}
step={step}
valueLabelFormat={value => display(value).replace("$", "")}
// marks={[
// { value: 0, label: "0%" },
// { value: 50, label: "50%" },
// { value: 100, label: "100%" }
// ]}
/>
</Grid>
</Grid>
))}
</div>
);
}

View File

@ -0,0 +1,122 @@
import React from "react";
import {
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Legend,
ResponsiveContainer,
Tooltip
} from "recharts";
const grayColor = "#90a4ae";
const strongColor = "#4ab47c";
const strongOpositeColor = "#b44a9b";
function PriceSimulationChart({
priceTimeseries,
withdrawFeeTimeseries,
p0
}: {
priceTimeseries: number[];
withdrawFeeTimeseries: number[];
p0: number;
}) {
// d0 - Initial raise, d0 (DAI)
// theta - fraction allocated to reserve (.)
// p0 - Hatch sale Price p0 (DAI / token)
// returnF - Return factor (.)
// wFee - friction coefficient (.)
const keyHorizontal = "x";
const keyVerticalLeft = "Price (DAI / token)";
const keyVerticalRight = "Collected withdraw fee (DAI)";
const data = [];
for (let t = 0; t < priceTimeseries.length; t++) {
data.push({
[keyHorizontal]: t,
[keyVerticalLeft]: priceTimeseries[t] || 0,
[keyVerticalRight]: withdrawFeeTimeseries[t] || 0
});
}
function renderColorfulLegendText(value: string) {
return <span style={{ color: grayColor }}>{value}</span>;
}
const formatter = (n: number) => (+n.toPrecision(3)).toLocaleString();
return (
<ResponsiveContainer debounce={1}>
<AreaChart
width={0}
height={400}
data={data}
margin={{
top: 10,
right: 30,
left: 0,
bottom: 0
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey={keyHorizontal}
tick={{ fill: grayColor }}
stroke={grayColor}
/>
<YAxis
yAxisId="left"
domain={[Math.min(...priceTimeseries), Math.max(...priceTimeseries)]}
tickFormatter={formatter}
tick={{ fill: grayColor }}
stroke={grayColor}
/>
{/* Capital collected from withdraw fees - AXIS */}
<YAxis
yAxisId="right"
// domain={[
// Math.floor(Math.min(...withdrawFeeTimeseries)),
// Math.ceil(Math.max(...withdrawFeeTimeseries))
// ]}
orientation="right"
tick={{ fill: grayColor }}
stroke={grayColor}
/>
<Tooltip formatter={value => Number(value)} />
<Area
isAnimationActive={false}
yAxisId="left"
type="monotone"
dataKey={keyVerticalLeft}
stroke={strongColor}
fill={strongColor}
/>
{/* Capital collected from withdraw fees - AREA */}
<Area
isAnimationActive={false}
yAxisId="right"
type="monotone"
dataKey={keyVerticalRight}
stroke={strongOpositeColor}
fill={strongOpositeColor}
/>
{/* <ReferenceLine
x={R0}
stroke="#90a4ae"
strokeDasharray="6 3"
label={<ReferenceLabel />}
/> */}
<Legend formatter={renderColorfulLegendText} />
</AreaChart>
</ResponsiveContainer>
);
}
export default PriceSimulationChart;

74
src/ResultParams.tsx Normal file
View File

@ -0,0 +1,74 @@
import React from "react";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Grid from "@material-ui/core/Grid";
const grayColor = "#90a4ae";
const blackColor = "#141e27";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
margin: theme.spacing(6, 0, 3)
},
lightBulb: {
verticalAlign: "middle",
marginRight: theme.spacing(1)
},
leftContainer: {
color: grayColor
},
centerContainer: {
color: blackColor
},
listBoxContainer: {
"& > div:not(:last-child)": {
marginBottom: "12px",
borderBottom: "1px solid #e0e0e0"
}
},
listBox: {
height: "48px",
paddingBottom: "12px",
"& > div": {
display: "flex",
alignItems: "center",
"& p": {
marginBottom: 0
}
},
"& > div:not(:last-child)": {
paddingRight: "12px"
}
}
})
);
export default function ResultParams({
resultFields
}: {
resultFields: {
label: string;
value: number | string;
}[];
}) {
const classes = useStyles();
return (
<div className={classes.listBoxContainer}>
{resultFields.map(({ label, value }) => (
<Grid key={label} container spacing={0} className={classes.listBox}>
<Grid item xs={8} className={classes.leftContainer}>
<Typography id={label} gutterBottom>
{label}
</Typography>
</Grid>
<Grid item xs={4} className={classes.centerContainer}>
<Typography gutterBottom>{value}</Typography>
</Grid>
</Grid>
))}
</div>
);
}

146
src/SupplyVsDemandChart.tsx Normal file
View File

@ -0,0 +1,146 @@
import React from "react";
import {
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Legend,
ReferenceLine,
ResponsiveContainer,
Tooltip
} from "recharts";
import { linspace } from "./utils";
const grayColor = "#90a4ae";
// const blackColor = "#141e27";
const strongColor = "#4ab47c";
const softColor = "#bbe3cd";
function SupplyVsDemandChart({
returnF,
theta,
d0,
p0
}: {
returnF: number;
theta: number;
d0: number;
p0: number;
}) {
// d0 - Initial raise, d0 (DAI)
// theta - fraction allocated to reserve (.)
// p0 - Hatch sale Price p0 (DAI / token)
// returnF - Return factor (.)
// wFee - friction coefficient (.)
// Hatch parameters
const k = returnF / (1 - theta); // Invariant power kappa (.)
const R0 = (1 - theta / 100) * d0; // Initial reserve (DAI)
const S0 = d0 / p0; // initial supply of tokens (token)
const S_of_R = (R: number) => S0 * (R / R0) ** (1 / k);
// Function setup
const f = S_of_R;
const from = 0;
const to = 4 * R0;
const steps = 100;
const step = (to - from) / steps;
/**
* Prettify the result converting 1000000 to 1M
*/
const biggest = Math.max(to, f(to));
const [scaling, unit] =
// Billion
biggest > 0.5e9
? [1e9, "B"]
: // Million
biggest > 0.5e6
? [1e6, "M"]
: // 1 thousand
biggest > 0.5e3
? [1e3, "K"]
: // No scale
[1, ""];
const keyHorizontal = "x";
const keyVertical = "Supply (tokens) / Reserve (DAI)";
const data = [];
for (let x = from; x <= to; x += step) {
data.push({
[keyHorizontal]: x,
[keyVertical]: f(x)
});
}
const formatter = (n: number) =>
(+(n / scaling).toPrecision(2)).toLocaleString();
function renderColorfulLegendText(value: string) {
return <span style={{ color: grayColor }}>{value}</span>;
}
return (
<ResponsiveContainer debounce={1}>
<AreaChart
width={0}
height={400}
data={data}
margin={{
top: 10,
right: 30,
left: 0,
bottom: 0
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
interval={"preserveStartEnd"}
ticks={linspace({ to: to, steps: 4 })}
dataKey={keyHorizontal}
tickFormatter={formatter}
unit={unit}
tick={{ fill: grayColor }}
stroke={grayColor}
/>
<YAxis
interval={"preserveStartEnd"}
ticks={linspace({ to: f(to), steps: 3 })}
tickFormatter={formatter}
unit={unit}
tick={{ fill: grayColor }}
domain={[0, f(to)]}
stroke={grayColor}
/>
<Tooltip formatter={value => formatter(Number(value))} />
<Area
isAnimationActive={false}
type="monotone"
dataKey={keyVertical}
stroke={strongColor}
fill={softColor}
/>
<ReferenceLine
x={R0}
stroke="#90a4ae"
strokeDasharray="6 3"
label={<ReferenceLabel />}
/>
<Legend formatter={renderColorfulLegendText} />
</AreaChart>
</ResponsiveContainer>
);
}
function ReferenceLabel(props: any) {
const { textAnchor, viewBox } = props;
return (
<text x={viewBox.x + 10} y={30} fill={grayColor} textAnchor={textAnchor}>
Initial value
</text>
);
}
export default SupplyVsDemandChart;

4
src/app.css Normal file
View File

@ -0,0 +1,4 @@
#root {
background-color: #f5f7f8;
min-height: 100vh;
}

15
src/index.tsx Normal file
View File

@ -0,0 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from '@material-ui/styles';
import App from './App';
import theme from './theme';
ReactDOM.render(
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<App />
</ThemeProvider>,
document.querySelector('#root'),
);

1
src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

22
src/theme.tsx Normal file
View File

@ -0,0 +1,22 @@
import red from '@material-ui/core/colors/red';
import { createMuiTheme } from '@material-ui/core/styles';
// A custom theme for this app
const theme = createMuiTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
error: {
main: red.A400,
},
background: {
default: '#fff',
},
},
});
export default theme;

37
src/utils.ts Normal file
View File

@ -0,0 +1,37 @@
/**
* Returns an equally spaced array of numbers `from`, `to` with `steps`.
*/
export function linspace({
from = 0,
to,
steps
}: {
from?: number;
to: number;
steps: number;
}) {
const arr = [];
for (let x = from; x <= to; x += (to - from) / steps) arr.push(x);
return arr;
}
/**
* Returns the last element of an array
*/
export function getLast(a: number[]) {
return a[a.length - 1];
}
/**
* Returns the average of an array
*/
export function getAvg(a: number[]) {
return a.reduce((t, c) => t + Math.abs(c), 0) / a.length;
}
/**
* Waits `ms` miliseconds and resolves
*/
export function pause(ms: number) {
return new Promise(r => setTimeout(r, ms));
}

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": ["src"]
}

10417
yarn.lock Normal file

File diff suppressed because it is too large Load Diff