From 9c4de97ae3ebff27a9a1f07e51f1e922eeea2ecd Mon Sep 17 00:00:00 2001 From: dapplion Date: Sat, 10 Aug 2019 03:30:49 +0200 Subject: [PATCH] Re-structure inputs + better tooltips --- src/App.tsx | 36 +++++-- src/CurveDesignInputParams.tsx | 103 ++++++++++++++++++++ src/InputParams.tsx | 170 ++++----------------------------- src/PrettoSlider.tsx | 34 +++++++ src/PriceSimulationChart.tsx | 57 +++++++++-- src/SimulationInputParams.tsx | 49 ++++++++++ src/SupplyVsDemandChart.tsx | 65 ++++++++++--- src/theme.tsx | 4 +- src/types.ts | 23 +++++ 9 files changed, 362 insertions(+), 179 deletions(-) create mode 100644 src/CurveDesignInputParams.tsx create mode 100644 src/PrettoSlider.tsx create mode 100644 src/SimulationInputParams.tsx create mode 100644 src/types.ts diff --git a/src/App.tsx b/src/App.tsx index 27bb18f..5964467 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,7 +9,8 @@ import Grid from "@material-ui/core/Grid"; import Button from "@material-ui/core/Button"; // Components import Header from "./Header"; -import InputParams from "./InputParams"; +import CurveDesignInputParams from "./CurveDesignInputParams"; +import SimulationInputParams from "./SimulationInputParams"; import SupplyVsDemandChart from "./SupplyVsDemandChart"; import ResultParams from "./ResultParams"; import PriceSimulationChart from "./PriceSimulationChart"; @@ -53,8 +54,7 @@ const useStyles = makeStyles((theme: Theme) => backgroundColor: "#293640" }, box: { - padding: theme.spacing(3, 3), - minHeight: 310 + padding: theme.spacing(3, 3) }, boxButton: { padding: theme.spacing(3, 3) @@ -66,6 +66,12 @@ const useStyles = makeStyles((theme: Theme) => alignItems: "center", borderBottom: "1px solid #313d47" }, + boxBorderBottom: { + borderBottom: "1px solid #313d47" + }, + initialRaise: { + justifyContent: "space-between" + }, boxChart: { width: "100%", height: "100%", @@ -93,7 +99,8 @@ const useStyles = makeStyles((theme: Theme) => marginBottom: -theme.spacing(headerOffset) }, button: { - background: "linear-gradient(290deg, #2ad179, #4ab47c)", + // background: "linear-gradient(290deg, #2ad179, #4ab47c)", // Green gradient + background: "linear-gradient(290deg, #1880e0, #3873d8)", // blue gradient color: "white" }, // Descriptions @@ -170,11 +177,11 @@ const resultParameterDescriptions = [ export default function App() { const [curveParams, setCurveParams] = useState({ - d0: 1e6, // Initial raise, d0 (DAI) theta: 0.35, // fraction allocated to reserve (.) p0: 0.1, // Hatch sale price p0 (DAI / token) p1: 0.3, // Return factor (.) - wFee: 0.05 // friction coefficient (.) + wFee: 0.05, // friction coefficient (.) + d0: 3e6 // Initial raise, d0 (DAI) }); const { d0, theta, p0, p1, wFee } = curveParams; @@ -321,6 +328,7 @@ export default function App() { ]; const classes = useStyles(); + return ( <>
@@ -364,8 +372,22 @@ export default function App() { /> + + + + + + Run parameters + + - + diff --git a/src/CurveDesignInputParams.tsx b/src/CurveDesignInputParams.tsx new file mode 100644 index 0000000..22c0a1f --- /dev/null +++ b/src/CurveDesignInputParams.tsx @@ -0,0 +1,103 @@ +import React, { useState, useEffect } from "react"; +import { InputFieldInterface, CurveParamsInterface } from "./types"; +import InputParams from "./InputParams"; + +export default function CurveDesignInputParams({ + curveParams, + setCurveParams +}: { + curveParams: CurveParamsInterface; + setCurveParams(newCurveParams: any): void; +}) { + const [theta, setTheta] = useState(0.35); // fraction allocated to reserve (.) + const [p0, setP0] = useState(0.1); // Hatch sale Price p0 (DAI / token) + const [p1, setP1] = useState(0.3); // Return factor (.) + const [wFee, setWFee] = useState(0.05); // friction coefficient (.) + + useEffect(() => { + setTheta(curveParams.theta); + setP0(curveParams.p0); + setP1(curveParams.p1); + setWFee(curveParams.wFee); + }, [curveParams]); + + function _setP0(newP0: number) { + setP0(newP0); + if (p1 < newP0) setP1(newP0); + else if (p1 > newP0 * maxReturnRate) setP1(newP0 * maxReturnRate); + } + + function setParentCurveParams() { + setCurveParams((params: CurveParamsInterface) => ({ + ...params, + theta, + p0, + p1, + wFee + })); + } + + const maxReturnRate = 10; + + const inputFields: InputFieldInterface[] = [ + { + label: "Allocation to funding pool", + value: theta, + setter: setTheta, + min: 0, + max: 0.9, + step: 0.01, + unit: "%", + suffix: "%", + format: (n: number) => `${Math.round(100 * n)}%`, + toText: (n: number) => String(+(n * 1e2).toFixed(0)), + toNum: (n: string) => parseFloat(n) * 1e-2 + }, + { + label: "Hatch price", + value: p0, + setter: _setP0, + min: 0.01, + max: 1, + step: 0.01, + unit: "$", + prefix: "$", + toText: (n: number) => String(+n.toFixed(2)), + toNum: (n: string) => parseFloat(n), + format: (n: number) => `$${n}` + }, + { + label: "Post-hatch price", + value: p1, + setter: setP1, + min: p0 || 0.1, + max: Number((maxReturnRate * p0).toFixed(2)), + step: 0.01, + unit: "$", + prefix: "$", + toText: (n: number) => String(+n.toFixed(2)), + toNum: (n: string) => parseFloat(n), + format: (n: number) => `$${n}` + }, + { + label: "Exit tribute", + value: wFee, + setter: setWFee, + min: 0, + max: 0.1, + step: 0.001, + unit: "%", + suffix: "%", + format: (n: number) => `${+(100 * n).toFixed(1)}%`, + toText: (n: number) => String(+(n * 1e2).toFixed(1)), + toNum: (n: string) => parseFloat(n) * 1e-2 + } + ]; + + return ( + + ); +} diff --git a/src/InputParams.tsx b/src/InputParams.tsx index 66cb4e3..fd9f603 100644 --- a/src/InputParams.tsx +++ b/src/InputParams.tsx @@ -1,47 +1,11 @@ -import React, { useState } from "react"; -import { - createStyles, - makeStyles, - withStyles, - Theme -} from "@material-ui/core/styles"; +import React from "react"; +import { createStyles, makeStyles, 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"; import TextField from "@material-ui/core/TextField"; import NumberFormat from "react-number-format"; - -const PrettoSlider = withStyles({ - root: { - 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: { - top: 30 - } -})(Slider); +import { InputFieldInterface } from "./types"; +import PrettoSlider from "./PrettoSlider"; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -79,6 +43,9 @@ const useStyles = makeStyles((theme: Theme) => }, slider: { color: theme.palette.primary.main + }, + secondaryColor: { + color: theme.palette.secondary.light } }) ); @@ -101,118 +68,12 @@ function NumberFormatCustom(props: any) { } export default function InputParams({ - curveParams, - setCurveParams + inputFields, + onChangeCommited }: { - curveParams?: { - d0: number; - theta: number; - p0: number; - p1: number; - wFee: number; - }; - setCurveParams(newCurveParams: any): void; + inputFields: InputFieldInterface[]; + onChangeCommited(): void; }) { - 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 [p1, setP1] = useState(0.3); // Return factor (.) - const [wFee, setWFee] = useState(0.05); // friction coefficient (.) - - function _setP0(newP0: number) { - setP0(newP0); - if (p1 < newP0) setP1(newP0); - else if (p1 > newP0 * maxReturnRate) setP1(newP0 * maxReturnRate); - } - - function setParentCurveParams() { - setCurveParams({ d0, theta, p0, p1, wFee }); - } - - const maxReturnRate = 10; - - const inputFields: { - label: string; - value: number; - setter(newValue: any): void; - min: number; - max: number; - step: number; - unit?: string; - prefix?: string; - suffix?: string; - toText?(value: number): string; - toNum?(value: string): number; - format(value: number): string; - }[] = [ - { - label: "Initial raise", - value: d0, - setter: setD0, - min: 0.1e6, - max: 10e6, - step: 0.1e6, - unit: "$M", - prefix: "$", - suffix: "M", - format: (n: number) => `$${+(n * 1e-6).toFixed(1)}M`, - toText: (n: number) => String(+(n * 1e-6).toFixed(1)), - toNum: (n: string) => Math.floor(parseFloat(n) * 1e6) - }, - { - label: "Allocation to funding pool", - value: theta, - setter: setTheta, - min: 0, - max: 0.9, - step: 0.01, - unit: "%", - suffix: "%", - format: (n: number) => `${Math.round(100 * n)}%`, - toText: (n: number) => String(+(n * 1e2).toFixed(0)), - toNum: (n: string) => parseFloat(n) * 1e-2 - }, - { - label: "Hatch price", - value: p0, - setter: _setP0, - min: 0.01, - max: 1, - step: 0.01, - unit: "$", - prefix: "$", - toText: (n: number) => String(+n.toFixed(2)), - toNum: (n: string) => parseFloat(n), - format: (n: number) => `$${n}` - }, - { - label: "Post-hatch price", - value: p1, - setter: setP1, - min: p0 || 0.1, - max: Number((maxReturnRate * p0).toFixed(2)), - step: 0.01, - unit: "$", - prefix: "$", - toText: (n: number) => String(+n.toFixed(2)), - toNum: (n: string) => parseFloat(n), - format: (n: number) => `$${n}` - }, - { - label: "Exit tribute", - value: wFee, - setter: setWFee, - min: 0, - max: 0.1, - step: 0.001, - unit: "%", - suffix: "%", - format: (n: number) => `${+(100 * n).toFixed(1)}%`, - toText: (n: number) => String(+(n * 1e2).toFixed(1)), - toNum: (n: string) => parseFloat(n) * 1e-2 - } - ]; - const classes = useStyles(); return ( @@ -227,6 +88,7 @@ export default function InputParams({ step, prefix, suffix, + secondaryColor, format, toText, toNum @@ -253,7 +115,7 @@ export default function InputParams({ sanitizeInput( toNum ? toNum(e.target.value) : parseFloat(e.target.value) ); - setParentCurveParams(); + onChangeCommited(); }} InputProps={{ inputComponent: NumberFormatCustom, @@ -269,12 +131,14 @@ export default function InputParams({ sanitizeInput(Number(newValue))} - onChangeCommitted={setParentCurveParams} + onChangeCommitted={onChangeCommited} value={value} min={min} max={max} diff --git a/src/PrettoSlider.tsx b/src/PrettoSlider.tsx new file mode 100644 index 0000000..846e242 --- /dev/null +++ b/src/PrettoSlider.tsx @@ -0,0 +1,34 @@ +import { withStyles } from "@material-ui/core/styles"; +import Slider from "@material-ui/core/Slider"; + +export default withStyles({ + root: { + 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: { + top: 30 + } +})(Slider); diff --git a/src/PriceSimulationChart.tsx b/src/PriceSimulationChart.tsx index 022bac6..bcc0e40 100644 --- a/src/PriceSimulationChart.tsx +++ b/src/PriceSimulationChart.tsx @@ -10,9 +10,25 @@ import { ResponsiveContainer, Tooltip } from "recharts"; +import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; import { useTheme } from "@material-ui/styles"; import { linspace } from "./utils"; +const keyHorizontal = "x"; +const keyVerticalLeft = "Price (DAI/token)"; +const keyVerticalRight = "Total exit tributes (DAI)"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + tooltip: { + border: "1px solid #313d47", + backgroundColor: "#384b59", + padding: theme.spacing(1), + color: "#c7ccd2" + } + }) +); + function PriceSimulationChart({ priceTimeseries, withdrawFeeTimeseries, @@ -30,10 +46,6 @@ function PriceSimulationChart({ // returnF - Return factor (.) // wFee - friction coefficient (.) - const keyHorizontal = "x"; - const keyVerticalLeft = "Price (DAI / token)"; - const keyVerticalRight = "Total exit tributes (DAI)"; - const data = []; for (let t = 0; t < priceTimeseries.length; t++) { data.push({ @@ -46,6 +58,7 @@ function PriceSimulationChart({ // Chart components const theme: any = useTheme(); + const classes = useStyles(); function renderColorfulLegendText(value: string) { return {value}; @@ -67,6 +80,35 @@ function PriceSimulationChart({ ); } + function CustomTooltip({ active, payload, label }: any) { + if (active) { + const price = payload[0].value; + const exit = payload[1].value; + const weekNum = label; + const toolTipData: string[][] = [ + ["Price", price.toFixed(2), "DAI/tk"], + ["Exit t.", formatter(exit), "DAI"], + ["Week", weekNum, ""] + ]; + + return ( +
+ + + {toolTipData.map(([name, value, unit]) => ( + + + + + + ))} + +
{name}{value}{unit}
+
+ ); + } else return null; + } + return ( - Number(value)} /> + } /> {/* { + setD0(curveParams.d0); + }, [curveParams]); + + function setParentCurveParams() { + setCurveParams((params: CurveParamsInterface) => ({ + ...params, + d0 + })); + } + + const inputFields: InputFieldInterface[] = [ + { + label: "Initial raise", + value: d0, + setter: setD0, + min: 0.1e6, + max: 10e6, + step: 0.1e6, + unit: "$M", + prefix: "$", + suffix: "M", + format: (n: number) => `$${+(n * 1e-6).toFixed(1)}M`, + toText: (n: number) => String(+(n * 1e-6).toFixed(1)), + toNum: (n: string) => Math.floor(parseFloat(n) * 1e6), + secondaryColor: true + } + ]; + + return ( + + ); +} diff --git a/src/SupplyVsDemandChart.tsx b/src/SupplyVsDemandChart.tsx index 69e08ad..54c76a7 100644 --- a/src/SupplyVsDemandChart.tsx +++ b/src/SupplyVsDemandChart.tsx @@ -10,10 +10,25 @@ import { ResponsiveContainer, Tooltip } from "recharts"; +import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; import { getLinspaceTicks } from "./utils"; -import { getInitialParams } from "./math"; +import { getInitialParams, getPriceR } from "./math"; import { useTheme } from "@material-ui/styles"; +const keyHorizontal = "x"; +const keyVertical = "Supply (tokens) / Reserve (DAI)"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + tooltip: { + border: "1px solid #313d47", + backgroundColor: "#384b59", + padding: theme.spacing(1), + color: "#c7ccd2" + } + }) +); + function SupplyVsDemandChart({ theta, d0, @@ -35,21 +50,23 @@ function SupplyVsDemandChart({ const { k, // Invariant power kappa (.) R0, // Initial reserve (DAI) - S0 // initial supply of tokens (token) + S0, // initial supply of tokens (token) + V0 // invariant coef } = getInitialParams({ d0, theta, p0, p1 }); - const S_of_R = (R: number) => S0 * (R / R0) ** (1 / k); + const R0_round = Math.round(R0); + const S_of_R = (R: number) => S0 * (R / R0_round) ** (1 / k); // Function setup const f = S_of_R; const from = 0; - const to = 4 * R0; + const to = 4 * R0_round; const steps = 100 + 1; // Add 1 for the ticks to match - const step = (to - from) / steps; + const step = Math.round((to - from) / (steps - 1)); /** * Prettify the result converting 1000000 to 1M @@ -68,12 +85,9 @@ function SupplyVsDemandChart({ : // No scale [1, ""]; - const keyHorizontal = "x"; - const keyVertical = "Supply (tokens) / Reserve (DAI)"; - const data = []; for (let i = 0; i < steps; i++) { - const x = from + step * i; + const x = Math.round(from + step * i); data.push({ [keyHorizontal]: x, [keyVertical]: f(x) @@ -83,6 +97,7 @@ function SupplyVsDemandChart({ // Chart components const theme: any = useTheme(); + const classes = useStyles(); const formatter = (n: number) => (+(n / scaling).toPrecision(2)).toLocaleString(); @@ -105,6 +120,34 @@ function SupplyVsDemandChart({ ); } + function CustomTooltip({ active, payload, label }: any) { + if (active) { + const supply = payload[0].value; + const reserve = label; + const price = getPriceR({ R: reserve, V0, k }); + const toolTipData: string[][] = [ + ["Supply", formatter(supply) + unit, "tokens"], + ["Reserve", formatter(reserve) + unit, "DAI"], + ["Price", price.toFixed(2), "DAI/token"] + ]; + return ( +
+ + + {toolTipData.map(([name, value, unit]) => ( + + + + + + ))} + +
{name}{value}{unit}
+
+ ); + } else return null; + } + return ( - formatter(Number(value))} /> + } /> } diff --git a/src/theme.tsx b/src/theme.tsx index f95dfca..e9a362a 100644 --- a/src/theme.tsx +++ b/src/theme.tsx @@ -9,7 +9,9 @@ const theme = createMuiTheme({ main: "#2ecd79" }, secondary: { - main: "#116be0" + main: "#116be0", + light: "#0f8bff", + dark: "#116be0" }, error: { main: red.A400 diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..2fcc5e2 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,23 @@ +export interface InputFieldInterface { + label: string; + value: number; + setter(newValue: any): void; + min: number; + max: number; + step: number; + unit?: string; + prefix?: string; + suffix?: string; + secondaryColor?: boolean; + toText?(value: number): string; + toNum?(value: string): number; + format(value: number): string; +} + +export interface CurveParamsInterface { + d0: number; + theta: number; + p0: number; + p1: number; + wFee: number; +}