Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
62d5114dfe
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -11,3 +11,5 @@ lib/*
|
||||||
build
|
build
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
*.bs.js
|
*.bs.js
|
||||||
|
# Local Netlify folder
|
||||||
|
.netlify
|
|
@ -3,10 +3,16 @@
|
||||||
"reason": {
|
"reason": {
|
||||||
"react-jsx": 3
|
"react-jsx": 3
|
||||||
},
|
},
|
||||||
"sources": {
|
"sources": [{
|
||||||
"dir": "src",
|
"dir": "src",
|
||||||
"subdirs": true
|
"subdirs": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"dir": "showcase",
|
||||||
|
"type": "dev",
|
||||||
|
"subdirs": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"bsc-flags": ["-bs-super-errors", "-bs-no-version-header"],
|
"bsc-flags": ["-bs-super-errors", "-bs-no-version-header"],
|
||||||
"package-specs": [{
|
"package-specs": [{
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
|
@ -15,9 +21,11 @@
|
||||||
"suffix": ".bs.js",
|
"suffix": ".bs.js",
|
||||||
"namespace": true,
|
"namespace": true,
|
||||||
"bs-dependencies": [
|
"bs-dependencies": [
|
||||||
|
"@foretold/components",
|
||||||
"bs-ant-design-alt",
|
"bs-ant-design-alt",
|
||||||
"reason-react",
|
"reason-react",
|
||||||
"bs-reform",
|
"bs-reform",
|
||||||
|
"bs-css",
|
||||||
"rationale",
|
"rationale",
|
||||||
"bs-moment",
|
"bs-moment",
|
||||||
"reschema"
|
"reschema"
|
||||||
|
@ -25,5 +33,5 @@
|
||||||
"refmt": 3,
|
"refmt": 3,
|
||||||
"ppx-flags": [
|
"ppx-flags": [
|
||||||
"lenses-ppx/ppx"
|
"lenses-ppx/ppx"
|
||||||
],
|
]
|
||||||
}
|
}
|
13
package.json
13
package.json
|
@ -9,6 +9,7 @@
|
||||||
"clean": "bsb -clean-world",
|
"clean": "bsb -clean-world",
|
||||||
"parcel": "parcel ./src/index.html --public-url / --no-autoinstall -- watch",
|
"parcel": "parcel ./src/index.html --public-url / --no-autoinstall -- watch",
|
||||||
"parcel-build": "parcel build ./src/index.html --no-source-maps --no-autoinstall",
|
"parcel-build": "parcel build ./src/index.html --no-source-maps --no-autoinstall",
|
||||||
|
"showcase": "PORT=12345 parcel showcase/index.html",
|
||||||
"server": "moduleserve ./ --port 8000",
|
"server": "moduleserve ./ --port 8000",
|
||||||
"predeploy": "yarn build && parcel build ./src/index.html --no-source-maps --no-autoinstall",
|
"predeploy": "yarn build && parcel build ./src/index.html --no-source-maps --no-autoinstall",
|
||||||
"deploy": "gh-pages -d dist",
|
"deploy": "gh-pages -d dist",
|
||||||
|
@ -22,13 +23,19 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@foretold/cdf": "^1.0.15",
|
||||||
|
"@foretold/components": "^0.0.3",
|
||||||
|
"@foretold/guesstimator": "^1.0.10",
|
||||||
"antd": "3.17.0",
|
"antd": "3.17.0",
|
||||||
"autoprefixer": "^9.7.4",
|
"autoprefixer": "^9.7.4",
|
||||||
"bs-ant-design-alt": "2.0.0-alpha.31",
|
"bs-ant-design-alt": "2.0.0-alpha.31",
|
||||||
|
"bs-css": "^11.0.0",
|
||||||
"bs-moment": "0.4.4",
|
"bs-moment": "0.4.4",
|
||||||
"bs-reform": "9.7.1",
|
"bs-reform": "9.7.1",
|
||||||
|
"d3": "^5.15.0",
|
||||||
"lenses-ppx": "4.0.0",
|
"lenses-ppx": "4.0.0",
|
||||||
"less": "^3.10.3",
|
"less": "^3.10.3",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"parcel-bundler": "^1.12.4",
|
"parcel-bundler": "^1.12.4",
|
||||||
"parcel-plugin-less-js-enabled": "^1.0.2",
|
"parcel-plugin-less-js-enabled": "^1.0.2",
|
||||||
|
@ -40,10 +47,14 @@
|
||||||
"reschema": "^1.3.0"
|
"reschema": "^1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bs-platform": "^5.0.6",
|
"bs-platform": "5.2.1",
|
||||||
"bsb-js": "^1.1.7",
|
"bsb-js": "^1.1.7",
|
||||||
"gh-pages": "^2.2.0",
|
"gh-pages": "^2.2.0",
|
||||||
"moduleserve": "^0.9.0",
|
"moduleserve": "^0.9.0",
|
||||||
"tailwindcss": "^1.2.0"
|
"tailwindcss": "^1.2.0"
|
||||||
|
},
|
||||||
|
"alias": {
|
||||||
|
"react": "./node_modules/react",
|
||||||
|
"react-dom": "./node_modules/react-dom"
|
||||||
}
|
}
|
||||||
}
|
}
|
1
showcase/Entries.re
Normal file
1
showcase/Entries.re
Normal file
|
@ -0,0 +1 @@
|
||||||
|
let entries = EntryTypes.[Continuous.entry];
|
30
showcase/EntryTypes.re
Normal file
30
showcase/EntryTypes.re
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
type compEntry = {
|
||||||
|
mutable id: string,
|
||||||
|
title: string,
|
||||||
|
render: unit => React.element,
|
||||||
|
container: containerType,
|
||||||
|
}
|
||||||
|
and folderEntry = {
|
||||||
|
mutable id: string,
|
||||||
|
title: string,
|
||||||
|
children: list(navEntry),
|
||||||
|
}
|
||||||
|
and navEntry =
|
||||||
|
| CompEntry(compEntry)
|
||||||
|
| FolderEntry(folderEntry)
|
||||||
|
and containerType =
|
||||||
|
| FullWidth
|
||||||
|
| Sidebar;
|
||||||
|
|
||||||
|
let entry = (~title, ~render): navEntry => {
|
||||||
|
CompEntry({id: "", title, render, container: FullWidth});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Maybe different api, this avoids breaking changes
|
||||||
|
let sidebar = (~title, ~render): navEntry => {
|
||||||
|
CompEntry({id: "", title, render, container: Sidebar});
|
||||||
|
};
|
||||||
|
|
||||||
|
let folder = (~title, ~children): navEntry => {
|
||||||
|
FolderEntry({id: "", title, children});
|
||||||
|
};
|
200
showcase/Lib.re
Normal file
200
showcase/Lib.re
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
open EntryTypes;
|
||||||
|
|
||||||
|
module HS = Belt.HashMap.String;
|
||||||
|
|
||||||
|
let entriesByPath: HS.t(navEntry) = HS.make(~hintSize=100);
|
||||||
|
|
||||||
|
/* Creates unique id's per scope based on title */
|
||||||
|
let buildIds = entries => {
|
||||||
|
let genId = (title, path) => {
|
||||||
|
let noSpaces = Js.String.replaceByRe([%bs.re "/\\s+/g"], "-", title);
|
||||||
|
if (!HS.has(entriesByPath, path ++ "/" ++ noSpaces)) {
|
||||||
|
noSpaces;
|
||||||
|
} else {
|
||||||
|
let rec loop = num => {
|
||||||
|
let testId = noSpaces ++ "-" ++ string_of_int(num);
|
||||||
|
if (!HS.has(entriesByPath, path ++ "/" ++ testId)) {
|
||||||
|
testId;
|
||||||
|
} else {
|
||||||
|
loop(num + 1);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
loop(2);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let rec processFolder = (f: folderEntry, curPath) => {
|
||||||
|
f.id = curPath ++ "/" ++ genId(f.title, curPath);
|
||||||
|
HS.set(entriesByPath, f.id, FolderEntry(f));
|
||||||
|
f.children
|
||||||
|
|> E.L.iter(e =>
|
||||||
|
switch (e) {
|
||||||
|
| CompEntry(c) => processEntry(c, f.id)
|
||||||
|
| FolderEntry(f) => processFolder(f, f.id)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
and processEntry = (c: compEntry, curPath) => {
|
||||||
|
c.id = curPath ++ "/" ++ genId(c.title, curPath);
|
||||||
|
HS.set(entriesByPath, c.id, CompEntry(c));
|
||||||
|
};
|
||||||
|
entries
|
||||||
|
|> E.L.iter(e =>
|
||||||
|
switch (e) {
|
||||||
|
| CompEntry(c) => processEntry(c, "")
|
||||||
|
| FolderEntry(f) => processFolder(f, "")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let entries = Entries.entries;
|
||||||
|
buildIds(entries);
|
||||||
|
|
||||||
|
module Styles = {
|
||||||
|
open Css;
|
||||||
|
let pageContainer = style([display(`flex), height(`vh(100.))]);
|
||||||
|
let leftNav =
|
||||||
|
style([
|
||||||
|
padding(`em(2.)),
|
||||||
|
flexBasis(`px(200)),
|
||||||
|
flexShrink(0.),
|
||||||
|
backgroundColor(`hex("eaeff3")),
|
||||||
|
boxShadows([
|
||||||
|
Shadow.box(
|
||||||
|
~x=px(-1),
|
||||||
|
~blur=px(1),
|
||||||
|
~inset=true,
|
||||||
|
rgba(0, 0, 0, 0.1),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let folderNav =
|
||||||
|
style([
|
||||||
|
selector(
|
||||||
|
">h4",
|
||||||
|
[
|
||||||
|
cursor(`pointer),
|
||||||
|
margin2(~v=`em(0.3), ~h=`zero),
|
||||||
|
hover([color(`hex("7089ad"))]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
let folderChildren = style([paddingLeft(`px(7))]);
|
||||||
|
let compNav =
|
||||||
|
style([
|
||||||
|
cursor(`pointer),
|
||||||
|
paddingBottom(`px(3)),
|
||||||
|
hover([color(`hex("7089ad"))]),
|
||||||
|
]);
|
||||||
|
let compContainer = style([padding(`em(2.)), flexGrow(1.)]);
|
||||||
|
// Approximate sidebar container for entry
|
||||||
|
let sidebarContainer = style([maxWidth(`px(430))]);
|
||||||
|
let folderChildContainer = style([marginBottom(`em(2.))]);
|
||||||
|
};
|
||||||
|
|
||||||
|
let baseUrl = "/showcase/index.html";
|
||||||
|
|
||||||
|
module Index = {
|
||||||
|
type state = {route: ReasonReactRouter.url};
|
||||||
|
|
||||||
|
type action =
|
||||||
|
| ItemClick(string)
|
||||||
|
| ChangeRoute(ReasonReactRouter.url);
|
||||||
|
|
||||||
|
let changeId = (id: string) => {
|
||||||
|
ReasonReactRouter.push(baseUrl ++ "#" ++ id);
|
||||||
|
();
|
||||||
|
};
|
||||||
|
|
||||||
|
let buildNav = setRoute => {
|
||||||
|
let rec buildFolder = (f: folderEntry) => {
|
||||||
|
<div key={f.id} className=Styles.folderNav>
|
||||||
|
<h4 onClick={_e => changeId(f.id)}> f.title->React.string </h4>
|
||||||
|
<div className=Styles.folderChildren>
|
||||||
|
{(
|
||||||
|
f.children
|
||||||
|
|> E.L.fmap(e =>
|
||||||
|
switch (e) {
|
||||||
|
| FolderEntry(folder) => buildFolder(folder)
|
||||||
|
| CompEntry(entry) => buildEntry(entry)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> E.L.toArray
|
||||||
|
)
|
||||||
|
->React.array}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
and buildEntry = (e: compEntry) => {
|
||||||
|
<div key={e.id} className=Styles.compNav onClick={_e => changeId(e.id)}>
|
||||||
|
e.title->React.string
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
(
|
||||||
|
entries
|
||||||
|
|> E.L.fmap(e =>
|
||||||
|
switch (e) {
|
||||||
|
| FolderEntry(folder) => buildFolder(folder)
|
||||||
|
| CompEntry(entry) => buildEntry(entry)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> E.L.toArray
|
||||||
|
)
|
||||||
|
->React.array;
|
||||||
|
};
|
||||||
|
|
||||||
|
let renderEntry = e => {
|
||||||
|
switch (e.container) {
|
||||||
|
| FullWidth => e.render()
|
||||||
|
| Sidebar => <div className=Styles.sidebarContainer> {e.render()} </div>
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
[@react.component]
|
||||||
|
let make = () => {
|
||||||
|
let (route, setRoute) =
|
||||||
|
React.useState(() => {
|
||||||
|
let url: ReasonReactRouter.url = {path: [], hash: "", search: ""};
|
||||||
|
url;
|
||||||
|
});
|
||||||
|
|
||||||
|
React.useState(() => {
|
||||||
|
ReasonReactRouter.watchUrl(url => setRoute(_ => url));
|
||||||
|
();
|
||||||
|
})
|
||||||
|
|> ignore;
|
||||||
|
|
||||||
|
<div className=Styles.pageContainer>
|
||||||
|
<div className=Styles.leftNav> {buildNav(setRoute)} </div>
|
||||||
|
<div className=Styles.compContainer>
|
||||||
|
{if (route.hash == "") {
|
||||||
|
React.null;
|
||||||
|
} else {
|
||||||
|
switch (HS.get(entriesByPath, route.hash)) {
|
||||||
|
| Some(navEntry) =>
|
||||||
|
switch (navEntry) {
|
||||||
|
| CompEntry(c) => renderEntry(c)
|
||||||
|
| FolderEntry(f) =>
|
||||||
|
/* Rendering immediate children */
|
||||||
|
(
|
||||||
|
f.children
|
||||||
|
|> E.L.fmap(child =>
|
||||||
|
switch (child) {
|
||||||
|
| CompEntry(c) =>
|
||||||
|
<div className=Styles.folderChildContainer key={c.id}>
|
||||||
|
{renderEntry(c)}
|
||||||
|
</div>
|
||||||
|
| _ => React.null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> E.L.toArray
|
||||||
|
)
|
||||||
|
->React.array
|
||||||
|
}
|
||||||
|
| None => <div> "Component not found"->React.string </div>
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
};
|
2
showcase/ShowcaseIndex.re
Normal file
2
showcase/ShowcaseIndex.re
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ReactDOMRe.renderToElementWithId(<div> <Lib.Index /> </div>, "main");
|
||||||
|
ReasonReactRouter.push("");
|
54
showcase/entries/Continuous.re
Normal file
54
showcase/entries/Continuous.re
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
open ForetoldComponents.Base;
|
||||||
|
|
||||||
|
let data: DistributionTypes.xyShape = {
|
||||||
|
xs: [|1., 10., 10., 200., 250., 292., 330.|],
|
||||||
|
ys: [|0.0, 0.0, 0.1, 0.3, 0.5, 0.2, 0.1|],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mixedDist =
|
||||||
|
GenericDistribution.make(
|
||||||
|
~generationSource=GuesstimatorString("mm(3, normal(5,1), [.5,.5])"),
|
||||||
|
~probabilityType=Pdf,
|
||||||
|
~domain=Complete,
|
||||||
|
~unit=Unspecified,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
|> GenericDistribution.renderIfNeeded(~sampleCount=3000);
|
||||||
|
|
||||||
|
let timeDist =
|
||||||
|
GenericDistribution.make(
|
||||||
|
~generationSource=GuesstimatorString("mm(3, normal(5,1), [.5,.5])"),
|
||||||
|
~probabilityType=Pdf,
|
||||||
|
~domain=Complete,
|
||||||
|
~unit=Time({zero: MomentRe.momentNow(), unit: `years}),
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
|> GenericDistribution.renderIfNeeded(~sampleCount=3000);
|
||||||
|
|
||||||
|
let domainLimitedDist =
|
||||||
|
GenericDistribution.make(
|
||||||
|
~generationSource=GuesstimatorString("mm(3, normal(5,1), [.5,.5])"),
|
||||||
|
~probabilityType=Pdf,
|
||||||
|
~domain=RightLimited({xPoint: 6.0, excludingProbabilityMass: 0.3}),
|
||||||
|
~unit=Unspecified,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
|> GenericDistribution.renderIfNeeded(~sampleCount=3000);
|
||||||
|
|
||||||
|
let distributions = () =>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<h2> {"Basic Mixed Distribution" |> ReasonReact.string} </h2>
|
||||||
|
<GenericDistributionChart dist=mixedDist />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2> {"Time Distribution" |> ReasonReact.string} </h2>
|
||||||
|
<GenericDistributionChart dist=timeDist />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2> {"Domain Limited Distribution" |> ReasonReact.string} </h2>
|
||||||
|
<GenericDistributionChart dist=domainLimitedDist />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
let entry = EntryTypes.(entry(~title="Pdf", ~render=distributions));
|
24
showcase/index.html
Normal file
24
showcase/index.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
|
||||||
|
<link href="../src/styles/index.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>Showcase</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="main"></div>
|
||||||
|
<script src=" ./ShowcaseIndex.bs.js "></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
33
src/_obsolete/LimitedDomainCdf.re
Normal file
33
src/_obsolete/LimitedDomainCdf.re
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// type t = {
|
||||||
|
// distribution: Types.ContinuousDistribution.t,
|
||||||
|
// domainMaxX: float,
|
||||||
|
// };
|
||||||
|
// let make = (~distribution, ~domainMaxX): t => {distribution, domainMaxX};
|
||||||
|
// let fromCdf =
|
||||||
|
// (
|
||||||
|
// cdf: Types.ContinuousDistribution.t,
|
||||||
|
// domainMaxX: float,
|
||||||
|
// probabilityAtMaxX: float,
|
||||||
|
// ) => {
|
||||||
|
// let distribution: Types.ContinuousDistribution.t = {
|
||||||
|
// xs: cdf.xs,
|
||||||
|
// ys: cdf.ys |> E.A.fmap(r => r *. probabilityAtMaxX),
|
||||||
|
// };
|
||||||
|
// {distribution, domainMaxX};
|
||||||
|
// };
|
||||||
|
// let _lastElement = (a: array('a)) =>
|
||||||
|
// switch (Belt.Array.size(a)) {
|
||||||
|
// | 0 => None
|
||||||
|
// | n => Belt.Array.get(a, n)
|
||||||
|
// };
|
||||||
|
// let probabilityBeforeDomainMax = (t: t) => _lastElement(t.distribution.ys);
|
||||||
|
// let domainMaxX = (t: t) => t.domainMaxX /* CdfLibrary.Distribution.findX(yPoint, t.distribution)*/;
|
||||||
|
// let probabilityDistribution = (t: t) =>
|
||||||
|
// t.distribution |> CdfLibrary.Distribution.toPdf;
|
||||||
|
// let probability = (t: t, xPoint: float) =>
|
||||||
|
// CdfLibrary.Distribution.findY(xPoint, probabilityDistribution(t));
|
||||||
|
// let probabilityInverse = (t: t, yPoint: float) =>
|
||||||
|
// CdfLibrary.Distribution.findX(yPoint, probabilityDistribution(t));
|
||||||
|
// let cumulativeProbability = (t: t, xPoint: float) =>
|
||||||
|
// CdfLibrary.Distribution.findY(xPoint, t.distribution);
|
||||||
|
/* let cumulativeProbabilityInverse = (t: t, yPoint: float) =*/
|
|
@ -27,7 +27,7 @@ type otherSettings = {currentYear: int};
|
||||||
let sharesOutstanding = (price, marketCap) =>
|
let sharesOutstanding = (price, marketCap) =>
|
||||||
switch (price, marketCap) {
|
switch (price, marketCap) {
|
||||||
| (Some(price), Some(marketCap)) =>
|
| (Some(price), Some(marketCap)) =>
|
||||||
Some(FloatCdf.divide(marketCap, price))
|
Some(GuesstimatorDist.divide(marketCap, price))
|
||||||
| _ => None
|
| _ => None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -42,11 +42,15 @@ let rec run =
|
||||||
| (SHARE_PRICE, year, Some(price), _) when year > 2019 && year < 2030 =>
|
| (SHARE_PRICE, year, Some(price), _) when year > 2019 && year < 2030 =>
|
||||||
let diffYears = year - otherSettings.currentYear;
|
let diffYears = year - otherSettings.currentYear;
|
||||||
let diffPerYear = 0.1;
|
let diffPerYear = 0.1;
|
||||||
Some(FloatCdf.normal(price, float_of_int(diffYears) *. diffPerYear));
|
Some(
|
||||||
|
GuesstimatorDist.normal(price, float_of_int(diffYears) *. diffPerYear),
|
||||||
|
);
|
||||||
| (MARKET_CAP, year, _, Some(price)) when year > 2019 && year < 2030 =>
|
| (MARKET_CAP, year, _, Some(price)) when year > 2019 && year < 2030 =>
|
||||||
let diffYears = year - otherSettings.currentYear;
|
let diffYears = year - otherSettings.currentYear;
|
||||||
let diffPerYear = 0.1;
|
let diffPerYear = 0.1;
|
||||||
Some(FloatCdf.normal(price, float_of_int(diffYears) *. diffPerYear));
|
Some(
|
||||||
|
GuesstimatorDist.normal(price, float_of_int(diffYears) *. diffPerYear),
|
||||||
|
);
|
||||||
| (SHARES_OUTSTANDING, year, _, _) when year > 2019 && year < 2030 =>
|
| (SHARES_OUTSTANDING, year, _, _) when year > 2019 && year < 2030 =>
|
||||||
let price = run(company, year, SHARE_PRICE, otherSettings);
|
let price = run(company, year, SHARE_PRICE, otherSettings);
|
||||||
let marketCap = run(company, year, MARKET_CAP, otherSettings);
|
let marketCap = run(company, year, MARKET_CAP, otherSettings);
|
38
src/_obsolete/TimeLimitedDomainCdf.re
Normal file
38
src/_obsolete/TimeLimitedDomainCdf.re
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// open TimeTypes;
|
||||||
|
// type t = {
|
||||||
|
// timeVector,
|
||||||
|
// limitedDomainCdf: LimitedDomainCdf.t,
|
||||||
|
// };
|
||||||
|
// let make =
|
||||||
|
// (
|
||||||
|
// ~timeVector: timeVector,
|
||||||
|
// ~distribution: Types.ContinuousDistribution.t,
|
||||||
|
// ~probabilityAtMaxX: float,
|
||||||
|
// ~maxX: [ | `time(MomentRe.Moment.t) | `x(float)],
|
||||||
|
// )
|
||||||
|
// : t => {
|
||||||
|
// let domainMaxX =
|
||||||
|
// switch (maxX) {
|
||||||
|
// | `time(m) => TimePoint.fromMoment(timeVector, m)
|
||||||
|
// | `x(r) => r
|
||||||
|
// };
|
||||||
|
// let limitedDomainCdf =
|
||||||
|
// LimitedDomainCdf.fromCdf(distribution, domainMaxX, probabilityAtMaxX);
|
||||||
|
// {timeVector, limitedDomainCdf};
|
||||||
|
// };
|
||||||
|
// let probabilityBeforeDomainMax = (t: t) =>
|
||||||
|
// LimitedDomainCdf.probabilityBeforeDomainMax(t.limitedDomainCdf);
|
||||||
|
// let domainMaxX = (t: t) =>
|
||||||
|
// LimitedDomainCdf.probabilityBeforeDomainMax(t.limitedDomainCdf) /* |> (r => RelativeTimePoint.toTime(t.timeVector, XValue(r)))*/;
|
||||||
|
// let probability = (t: t, m: MomentRe.Moment.t) => {
|
||||||
|
// RelativeTimePoint.toXValue(t.timeVector, Time(m))
|
||||||
|
// |> LimitedDomainCdf.probability(t.limitedDomainCdf);
|
||||||
|
// };
|
||||||
|
// let probabilityInverse = (t: t, y: float) =>
|
||||||
|
// LimitedDomainCdf.probabilityInverse(t.limitedDomainCdf, y)
|
||||||
|
// |> (r => RelativeTimePoint.toTime(t.timeVector, XValue(r)));
|
||||||
|
// let cumulativeProbability = (t: t, m: MomentRe.Moment.t) =>
|
||||||
|
// RelativeTimePoint.toXValue(t.timeVector, Time(m))
|
||||||
|
// |> LimitedDomainCdf.cumulativeProbability(t.limitedDomainCdf);
|
||||||
|
// let cumulativeProbabilityInverse = (t: t, y: float) =>
|
||||||
|
/* LimitedDomainCdf.cumulativeProbabilityInverse(t.limitedDomainCdf, y*/
|
43
src/components/charts/CdfChart__Base.re
Normal file
43
src/components/charts/CdfChart__Base.re
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
[@bs.module "./cdfChartReact.js"]
|
||||||
|
external cdfChart: ReasonReact.reactClass = "default";
|
||||||
|
|
||||||
|
type primaryDistribution = {
|
||||||
|
.
|
||||||
|
"xs": array(float),
|
||||||
|
"ys": array(float),
|
||||||
|
};
|
||||||
|
|
||||||
|
[@react.component]
|
||||||
|
let make =
|
||||||
|
(
|
||||||
|
~height=?,
|
||||||
|
~verticalLine=?,
|
||||||
|
~showVerticalLine=?,
|
||||||
|
~marginBottom=?,
|
||||||
|
~marginTop=?,
|
||||||
|
~showDistributionLines=?,
|
||||||
|
~maxX=?,
|
||||||
|
~minX=?,
|
||||||
|
~onHover=(f: float) => (),
|
||||||
|
~primaryDistribution=?,
|
||||||
|
~children=[||],
|
||||||
|
) =>
|
||||||
|
ReasonReact.wrapJsForReason(
|
||||||
|
~reactClass=cdfChart,
|
||||||
|
~props=
|
||||||
|
makeProps(
|
||||||
|
~height?,
|
||||||
|
~verticalLine?,
|
||||||
|
~marginBottom?,
|
||||||
|
~marginTop?,
|
||||||
|
~onHover,
|
||||||
|
~showVerticalLine?,
|
||||||
|
~showDistributionLines?,
|
||||||
|
~maxX?,
|
||||||
|
~minX?,
|
||||||
|
~primaryDistribution?,
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
children,
|
||||||
|
)
|
||||||
|
|> ReasonReact.element;
|
41
src/components/charts/CdfChart__Plain.re
Normal file
41
src/components/charts/CdfChart__Plain.re
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
module Styles = {
|
||||||
|
open Css;
|
||||||
|
let textOverlay = style([position(`absolute)]);
|
||||||
|
let mainText = style([fontSize(`em(1.1))]);
|
||||||
|
let secondaryText = style([fontSize(`em(0.9))]);
|
||||||
|
|
||||||
|
let graph = chartColor =>
|
||||||
|
style([
|
||||||
|
position(`relative),
|
||||||
|
selector(".axis", [fontSize(`px(9))]),
|
||||||
|
selector(".domain", [display(`none)]),
|
||||||
|
selector(".tick line", [display(`none)]),
|
||||||
|
selector(".tick text", [color(`hex("bfcad4"))]),
|
||||||
|
selector(".chart .area-path", [SVG.fill(chartColor)]),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
[@react.component]
|
||||||
|
let make =
|
||||||
|
(
|
||||||
|
~data,
|
||||||
|
~minX=?,
|
||||||
|
~maxX=?,
|
||||||
|
~height=200,
|
||||||
|
~color=`hex("111"),
|
||||||
|
~onHover: float => unit,
|
||||||
|
) => {
|
||||||
|
<div className={Styles.graph(color)}>
|
||||||
|
<CdfChart__Base
|
||||||
|
height
|
||||||
|
?minX
|
||||||
|
?maxX
|
||||||
|
marginBottom=50
|
||||||
|
marginTop=0
|
||||||
|
onHover
|
||||||
|
showVerticalLine=false
|
||||||
|
showDistributionLines=false
|
||||||
|
primaryDistribution={data |> Shape.XYShape.toJs}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
};
|
24
src/components/charts/ChartSimple.re
Normal file
24
src/components/charts/ChartSimple.re
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
module Styles = {
|
||||||
|
open Css;
|
||||||
|
let graph = chartColor =>
|
||||||
|
style([
|
||||||
|
selector(".axis", [fontSize(`px(9))]),
|
||||||
|
selector(".domain", [display(`none)]),
|
||||||
|
selector(".tick line", [display(`none)]),
|
||||||
|
selector(".tick text", [color(`hex("bfcad4"))]),
|
||||||
|
selector(".chart .area-path", [SVG.fill(chartColor)]),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
[@react.component]
|
||||||
|
let make = (~minX=None, ~maxX=None, ~height=50, ~color=`hex("7e9db7")) =>
|
||||||
|
<div className={Styles.graph(color)}>
|
||||||
|
<CdfChart__Base
|
||||||
|
height
|
||||||
|
?minX
|
||||||
|
?maxX
|
||||||
|
marginBottom=20
|
||||||
|
showVerticalLine=false
|
||||||
|
showDistributionLines=false
|
||||||
|
/>
|
||||||
|
</div>;
|
69
src/components/charts/GenericDistributionChart.re
Normal file
69
src/components/charts/GenericDistributionChart.re
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
module Continuous = {
|
||||||
|
[@react.component]
|
||||||
|
let make = (~data) => {
|
||||||
|
let (x, setX) = React.useState(() => 0.);
|
||||||
|
let chart =
|
||||||
|
React.useMemo1(
|
||||||
|
() =>
|
||||||
|
<CdfChart__Plain
|
||||||
|
data
|
||||||
|
color={`hex("333")}
|
||||||
|
onHover={r => setX(_ => r)}
|
||||||
|
/>,
|
||||||
|
[|data|],
|
||||||
|
);
|
||||||
|
<div>
|
||||||
|
chart
|
||||||
|
<table className="table-auto">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-2"> {"X Point" |> ReasonReact.string} </th>
|
||||||
|
<th className="px-4 py-2"> {"Y Pount" |> ReasonReact.string} </th>
|
||||||
|
<th className="px-4 py-2">
|
||||||
|
{"Y Integral to Point" |> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-2 border ">
|
||||||
|
{x |> E.Float.toString |> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 border ">
|
||||||
|
{Shape.Continuous.findY(x, data)
|
||||||
|
|> E.Float.with2DigitsPrecision
|
||||||
|
|> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 border ">
|
||||||
|
{Shape.Continuous.findY(x, Shape.XYShape.integral(data))
|
||||||
|
|> E.Float.with2DigitsPrecision
|
||||||
|
|> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div />
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
[@react.component]
|
||||||
|
let make = (~dist) => {
|
||||||
|
switch ((dist: option(DistributionTypes.genericDistribution))) {
|
||||||
|
| Some({
|
||||||
|
generationSource:
|
||||||
|
Shape(
|
||||||
|
Mixed({
|
||||||
|
continuous: n,
|
||||||
|
discrete: d,
|
||||||
|
discreteProbabilityMassFraction: f,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}) =>
|
||||||
|
<div>
|
||||||
|
<Continuous data={n |> Shape.Continuous.toPdf} />
|
||||||
|
{d |> Shape.Discrete.scaleYToTotal(f) |> Shape.Discrete.render}
|
||||||
|
</div>
|
||||||
|
| _ => <div />
|
||||||
|
};
|
||||||
|
};
|
66
src/components/charts/cdfChartReact.js
Normal file
66
src/components/charts/cdfChartReact.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useSize } from 'react-use';
|
||||||
|
|
||||||
|
import chart from './cdfChartd3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param min
|
||||||
|
* @param max
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function getRandomInt(min, max) {
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example input:
|
||||||
|
* {
|
||||||
|
* xs: [50,100,300,400,500,600],
|
||||||
|
* ys: [0.1, 0.4, 0.6, 0.7,0.8, 0.9]}
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function CdfChart(props) {
|
||||||
|
const id = "chart-" + getRandomInt(0, 100000);
|
||||||
|
const [sized, { width }] = useSize(() => {
|
||||||
|
return React.createElement("div", {
|
||||||
|
key: "resizable-div",
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
width: props.width,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
chart()
|
||||||
|
.svgWidth(width)
|
||||||
|
.svgHeight(props.height)
|
||||||
|
.maxX(props.maxX)
|
||||||
|
.minX(props.minX)
|
||||||
|
.onHover(props.onHover)
|
||||||
|
.marginBottom(props.marginBottom || 15)
|
||||||
|
.marginLeft(5)
|
||||||
|
.marginRight(5)
|
||||||
|
.marginTop(5)
|
||||||
|
.showDistributionLines(props.showDistributionLines)
|
||||||
|
.verticalLine(props.verticalLine)
|
||||||
|
.showVerticalLine(props.showVerticalLine)
|
||||||
|
.container("#" + id)
|
||||||
|
.data({ primary: props.primaryDistribution }).render();
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = !!props.width ? { width: props.width + "px" } : {};
|
||||||
|
const key = id;
|
||||||
|
|
||||||
|
return React.createElement("div", {
|
||||||
|
style: {
|
||||||
|
paddingLeft: "10px",
|
||||||
|
paddingRight: "10px",
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
sized,
|
||||||
|
React.createElement("div", { id, style, key }),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CdfChart;
|
302
src/components/charts/cdfChartd3.js
Normal file
302
src/components/charts/cdfChartd3.js
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
|
function chart() {
|
||||||
|
// Id for event handlings.
|
||||||
|
var attrs = {
|
||||||
|
id: 'ID' + Math.floor(Math.random() * 1000000),
|
||||||
|
svgWidth: 400,
|
||||||
|
svgHeight: 400,
|
||||||
|
|
||||||
|
marginTop: 5,
|
||||||
|
marginBottom: 5,
|
||||||
|
marginRight: 50,
|
||||||
|
marginLeft: 5,
|
||||||
|
|
||||||
|
container: 'body',
|
||||||
|
minX: false,
|
||||||
|
maxX: false,
|
||||||
|
scale: 'linear',
|
||||||
|
showDistributionLines: true,
|
||||||
|
areaColors: ['#E1E5EC', '#E1E5EC'],
|
||||||
|
logBase: 10,
|
||||||
|
verticalLine: 110,
|
||||||
|
showVerticalLine: true,
|
||||||
|
data: null,
|
||||||
|
onHover: (e) => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
var main = function main() {
|
||||||
|
// Drawing containers.
|
||||||
|
var container = d3.select(attrs.container);
|
||||||
|
|
||||||
|
if (container.node() === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var containerRect = container.node().getBoundingClientRect();
|
||||||
|
if (containerRect.width > 0) {
|
||||||
|
attrs.svgWidth = containerRect.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculated properties.
|
||||||
|
// id for event handlings.
|
||||||
|
var calc = {};
|
||||||
|
calc.id = 'ID' + Math.floor(Math.random() * 1000000);
|
||||||
|
calc.chartLeftMargin = attrs.marginLeft;
|
||||||
|
calc.chartTopMargin = attrs.marginTop;
|
||||||
|
calc.chartWidth = attrs.svgWidth - attrs.marginRight - attrs.marginLeft;
|
||||||
|
calc.chartHeight = attrs.svgHeight - attrs.marginBottom - attrs.marginTop;
|
||||||
|
|
||||||
|
var areaColor = d3.scaleOrdinal().range(attrs.areaColors);
|
||||||
|
|
||||||
|
var dataPoints = [getDatapoints('primary')];
|
||||||
|
|
||||||
|
// Scales.
|
||||||
|
var xScale;
|
||||||
|
|
||||||
|
var xMin = d3.min(attrs.data.primary.xs);
|
||||||
|
var xMax = d3.max(attrs.data.primary.xs);
|
||||||
|
|
||||||
|
if (attrs.scale === 'linear') {
|
||||||
|
xScale = d3.scaleLinear()
|
||||||
|
.domain([
|
||||||
|
attrs.minX || xMin,
|
||||||
|
attrs.maxX || xMax
|
||||||
|
])
|
||||||
|
.range([0, calc.chartWidth]);
|
||||||
|
} else {
|
||||||
|
xScale = d3.scaleLog()
|
||||||
|
.base(attrs.logBase)
|
||||||
|
.domain([
|
||||||
|
attrs.minX,
|
||||||
|
attrs.maxX,
|
||||||
|
])
|
||||||
|
.range([0, calc.chartWidth]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var yMin = d3.min(attrs.data.primary.ys);
|
||||||
|
var yMax = d3.max(attrs.data.primary.ys);
|
||||||
|
|
||||||
|
var yScale = d3.scaleLinear()
|
||||||
|
.domain([
|
||||||
|
yMin,
|
||||||
|
yMax,
|
||||||
|
])
|
||||||
|
.range([calc.chartHeight, 0]);
|
||||||
|
|
||||||
|
// Axis generator.
|
||||||
|
var xAxis = d3.axisBottom(xScale)
|
||||||
|
.ticks(3)
|
||||||
|
.tickFormat(d => {
|
||||||
|
if (Math.abs(d) < 1) {
|
||||||
|
return d3.format(".2")(d);
|
||||||
|
} else if (xMin > 1000 && xMax < 3000) {
|
||||||
|
// Condition which identifies years; 2019, 2020, 2021.
|
||||||
|
return d3.format(".0")(d);
|
||||||
|
} else {
|
||||||
|
var prefix = d3.formatPrefix(".0", d);
|
||||||
|
var output = prefix(d);
|
||||||
|
return output.replace("G", "B");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Line generator.
|
||||||
|
var line = d3.line()
|
||||||
|
.x(function (d, i) {
|
||||||
|
return xScale(d.x);
|
||||||
|
})
|
||||||
|
.y(function (d, i) {
|
||||||
|
return yScale(d.y);
|
||||||
|
});
|
||||||
|
|
||||||
|
var area = d3.area()
|
||||||
|
.x(function (d, i) {
|
||||||
|
return xScale(d.x);
|
||||||
|
})
|
||||||
|
.y1(function (d, i) {
|
||||||
|
return yScale(d.y);
|
||||||
|
})
|
||||||
|
.y0(calc.chartHeight);
|
||||||
|
|
||||||
|
// Add svg.
|
||||||
|
var svg = container
|
||||||
|
.patternify({ tag: 'svg', selector: 'svg-chart-container' })
|
||||||
|
.attr('width', "100%")
|
||||||
|
.attr('height', attrs.svgHeight)
|
||||||
|
.attr('pointer-events', 'none');
|
||||||
|
|
||||||
|
// Add container g element.
|
||||||
|
var chart = svg
|
||||||
|
.patternify({ tag: 'g', selector: 'chart' })
|
||||||
|
.attr(
|
||||||
|
'transform',
|
||||||
|
'translate(' + calc.chartLeftMargin + ',' + calc.chartTopMargin + ')',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add axis.
|
||||||
|
chart.patternify({ tag: 'g', selector: 'axis' })
|
||||||
|
.attr('transform', 'translate(' + 0 + ',' + calc.chartHeight + ')')
|
||||||
|
.call(xAxis);
|
||||||
|
|
||||||
|
// Draw area.
|
||||||
|
chart
|
||||||
|
.patternify({
|
||||||
|
tag: 'path',
|
||||||
|
selector: 'area-path',
|
||||||
|
data: dataPoints
|
||||||
|
})
|
||||||
|
.attr('d', area)
|
||||||
|
.attr('fill', (d, i) => areaColor(i))
|
||||||
|
.attr('opacity', (d, i) => i === 0 ? 0.7 : 1);
|
||||||
|
|
||||||
|
// Draw line.
|
||||||
|
if (attrs.showDistributionLines) {
|
||||||
|
chart
|
||||||
|
.patternify({
|
||||||
|
tag: 'path',
|
||||||
|
selector: 'line-path',
|
||||||
|
data: dataPoints
|
||||||
|
})
|
||||||
|
.attr('d', line)
|
||||||
|
.attr('id', (d, i) => 'line-' + (i + 1))
|
||||||
|
.attr('opacity', (d, i) => {
|
||||||
|
return i === 0 ? 0.7 : 1
|
||||||
|
})
|
||||||
|
.attr('fill', 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrs.showVerticalLine) {
|
||||||
|
chart.patternify({ tag: 'line', selector: 'v-line' })
|
||||||
|
.attr('x1', xScale(attrs.verticalLine))
|
||||||
|
.attr('x2', xScale(attrs.verticalLine))
|
||||||
|
.attr('y1', 0)
|
||||||
|
.attr('y2', calc.chartHeight)
|
||||||
|
.attr('stroke-width', 1.5)
|
||||||
|
.attr('stroke-dasharray', '6 6')
|
||||||
|
.attr('stroke', 'steelblue');
|
||||||
|
}
|
||||||
|
|
||||||
|
var hoverLine = chart.patternify({ tag: 'line', selector: 'hover-line' })
|
||||||
|
.attr('x1', 0)
|
||||||
|
.attr('x2', 0)
|
||||||
|
.attr('y1', 0)
|
||||||
|
.attr('y2', calc.chartHeight)
|
||||||
|
.attr('opacity', 0)
|
||||||
|
.attr('stroke-width', 1.5)
|
||||||
|
.attr('stroke-dasharray', '6 6')
|
||||||
|
.attr('stroke', '#22313F');
|
||||||
|
|
||||||
|
// Add drawing rectangle.
|
||||||
|
chart.patternify({ tag: 'rect', selector: 'mouse-rect' })
|
||||||
|
.attr('width', calc.chartWidth)
|
||||||
|
.attr('height', calc.chartHeight)
|
||||||
|
.attr('fill', 'transparent')
|
||||||
|
.attr('pointer-events', 'all')
|
||||||
|
.on('mouseover', mouseover)
|
||||||
|
.on('mousemove', mouseover)
|
||||||
|
.on('mouseout', mouseout);
|
||||||
|
|
||||||
|
function mouseover() {
|
||||||
|
var mouse = d3.mouse(this);
|
||||||
|
|
||||||
|
hoverLine.attr('opacity', 1)
|
||||||
|
.attr('x1', mouse[0])
|
||||||
|
.attr('x2', mouse[0]);
|
||||||
|
|
||||||
|
var range = [
|
||||||
|
xScale(dataPoints[dataPoints.length - 1][0].x),
|
||||||
|
xScale(
|
||||||
|
dataPoints
|
||||||
|
[dataPoints.length - 1]
|
||||||
|
[dataPoints[dataPoints.length - 1].length - 1].x,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
var xValue = xScale.invert(mouse[0]).toFixed(2);
|
||||||
|
|
||||||
|
if (mouse[0] > range[0] && mouse[0] < range[1]) {
|
||||||
|
attrs.onHover(xValue);
|
||||||
|
} else {
|
||||||
|
attrs.onHover(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseout() {
|
||||||
|
hoverLine.attr('opacity', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param key
|
||||||
|
* @returns {[]}
|
||||||
|
*/
|
||||||
|
function getDatapoints(key) {
|
||||||
|
var dt = [];
|
||||||
|
var data = attrs.data[key];
|
||||||
|
var len = data.xs.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
dt.push({
|
||||||
|
x: data.xs[i],
|
||||||
|
y: data.ys[i]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return dt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
d3.selection.prototype.patternify = function patternify(params) {
|
||||||
|
var container = this;
|
||||||
|
var selector = params.selector;
|
||||||
|
var elementTag = params.tag;
|
||||||
|
var data = params.data || [selector];
|
||||||
|
|
||||||
|
// Pattern in action.
|
||||||
|
var selection = container.selectAll('.' + selector).data(data, (d, i) => {
|
||||||
|
if (typeof d === 'object') {
|
||||||
|
if (d.id) {
|
||||||
|
return d.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
});
|
||||||
|
|
||||||
|
selection.exit().remove();
|
||||||
|
selection = selection.enter().append(elementTag).merge(selection);
|
||||||
|
selection.attr('class', selector);
|
||||||
|
return selection;
|
||||||
|
};
|
||||||
|
|
||||||
|
// @todo: Do not do like that.
|
||||||
|
// Dynamic keys functions.
|
||||||
|
// Attach variables to main function.
|
||||||
|
Object.keys(attrs).forEach((key) => {
|
||||||
|
main[key] = function (_) {
|
||||||
|
if (!arguments.length) {
|
||||||
|
return attrs[key];
|
||||||
|
}
|
||||||
|
attrs[key] = _;
|
||||||
|
return main;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set attrs as property.
|
||||||
|
main.attrs = attrs;
|
||||||
|
|
||||||
|
//Exposed update functions.
|
||||||
|
main.data = function data(value) {
|
||||||
|
if (!arguments.length) return attrs.data;
|
||||||
|
attrs.data = value;
|
||||||
|
return main;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run visual.
|
||||||
|
main.render = function render() {
|
||||||
|
main();
|
||||||
|
return main;
|
||||||
|
};
|
||||||
|
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default chart;
|
49
src/core/DistributionTypes.re
Normal file
49
src/core/DistributionTypes.re
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
type domainLimit = {
|
||||||
|
xPoint: float,
|
||||||
|
excludingProbabilityMass: float,
|
||||||
|
};
|
||||||
|
|
||||||
|
type domain =
|
||||||
|
| Complete
|
||||||
|
| LeftLimited(domainLimit)
|
||||||
|
| RightLimited(domainLimit)
|
||||||
|
| LeftAndRightLimited(domainLimit, domainLimit);
|
||||||
|
|
||||||
|
type xyShape = {
|
||||||
|
xs: array(float),
|
||||||
|
ys: array(float),
|
||||||
|
};
|
||||||
|
|
||||||
|
type continuousShape = xyShape;
|
||||||
|
type discreteShape = xyShape;
|
||||||
|
|
||||||
|
type mixedShape = {
|
||||||
|
continuous: continuousShape,
|
||||||
|
discrete: discreteShape,
|
||||||
|
discreteProbabilityMassFraction: float,
|
||||||
|
};
|
||||||
|
|
||||||
|
type pointsType =
|
||||||
|
| Mixed(mixedShape)
|
||||||
|
| Discrete(discreteShape)
|
||||||
|
| Continuous(continuousShape);
|
||||||
|
|
||||||
|
type generationSource =
|
||||||
|
| GuesstimatorString(string)
|
||||||
|
| Shape(pointsType);
|
||||||
|
|
||||||
|
type distributionUnit =
|
||||||
|
| Unspecified
|
||||||
|
| Time(TimeTypes.timeVector);
|
||||||
|
|
||||||
|
type probabilityType =
|
||||||
|
| Cdf
|
||||||
|
| Pdf
|
||||||
|
| Arbitrary;
|
||||||
|
|
||||||
|
type genericDistribution = {
|
||||||
|
generationSource,
|
||||||
|
probabilityType,
|
||||||
|
domain,
|
||||||
|
unit: distributionUnit,
|
||||||
|
};
|
34
src/core/GenericDistribution.re
Normal file
34
src/core/GenericDistribution.re
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
open DistributionTypes;
|
||||||
|
let make =
|
||||||
|
(
|
||||||
|
~generationSource,
|
||||||
|
~probabilityType=Pdf,
|
||||||
|
~domain=Complete,
|
||||||
|
~unit=Unspecified,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
: genericDistribution => {
|
||||||
|
generationSource,
|
||||||
|
probabilityType,
|
||||||
|
domain,
|
||||||
|
unit,
|
||||||
|
};
|
||||||
|
|
||||||
|
let renderIfNeeded =
|
||||||
|
(~sampleCount=1000, t: genericDistribution): option(genericDistribution) => {
|
||||||
|
switch (t.generationSource) {
|
||||||
|
| GuesstimatorString(s) =>
|
||||||
|
let shape = Guesstimator.stringToMixedShape(~string=s, ~sampleCount, ());
|
||||||
|
shape
|
||||||
|
|> E.O.fmap((shape: DistributionTypes.mixedShape) =>
|
||||||
|
make(
|
||||||
|
~generationSource=Shape(Mixed(shape)),
|
||||||
|
~probabilityType=Cdf,
|
||||||
|
~domain=t.domain,
|
||||||
|
~unit=t.unit,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
| Shape(_) => Some(t)
|
||||||
|
};
|
||||||
|
};
|
14
src/core/MixedCdf.re
Normal file
14
src/core/MixedCdf.re
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
type t = DistributionTypes.mixedShape;
|
||||||
|
|
||||||
|
type yPdfPoint = {
|
||||||
|
continuous: float,
|
||||||
|
discrete: float,
|
||||||
|
};
|
||||||
|
|
||||||
|
let getY = (t: t, x: float): yPdfPoint => {
|
||||||
|
continuous: Shape.Continuous.findY(x, t.continuous),
|
||||||
|
discrete: Shape.Discrete.findY(x, t.discrete),
|
||||||
|
} /* discrete: Shape.Discrete.findY(x, t.discrete)*/;
|
||||||
|
|
||||||
|
// let getIntegralY = (t: t, x: float): float => {
|
||||||
|
// continuous: Shape.Continuous.findY(x, t.continuous),
|
63
src/core/MixedShapeBuilder.re
Normal file
63
src/core/MixedShapeBuilder.re
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
type assumption =
|
||||||
|
| ADDS_TO_1
|
||||||
|
| ADDS_TO_CORRECT_PROBABILITY;
|
||||||
|
type assumptions = {
|
||||||
|
continuous: assumption,
|
||||||
|
discrete: assumption,
|
||||||
|
discreteProbabilityMass: option(float),
|
||||||
|
};
|
||||||
|
let build = (~continuous, ~discrete, ~assumptions) =>
|
||||||
|
switch (assumptions) {
|
||||||
|
| {
|
||||||
|
continuous: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
|
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
|
discreteProbabilityMass: Some(r),
|
||||||
|
} =>
|
||||||
|
// TODO: Fix this, it's wrong :(
|
||||||
|
Some(
|
||||||
|
Shape.Mixed.make(
|
||||||
|
~continuous,
|
||||||
|
~discrete,
|
||||||
|
~discreteProbabilityMassFraction=r,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
| {
|
||||||
|
continuous: ADDS_TO_1,
|
||||||
|
discrete: ADDS_TO_1,
|
||||||
|
discreteProbabilityMass: Some(r),
|
||||||
|
} =>
|
||||||
|
Some(
|
||||||
|
Shape.Mixed.make(
|
||||||
|
~continuous,
|
||||||
|
~discrete,
|
||||||
|
~discreteProbabilityMassFraction=r,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
| {
|
||||||
|
continuous: ADDS_TO_1,
|
||||||
|
discrete: ADDS_TO_1,
|
||||||
|
discreteProbabilityMass: None,
|
||||||
|
} =>
|
||||||
|
None
|
||||||
|
| {
|
||||||
|
continuous: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
|
discrete: ADDS_TO_1,
|
||||||
|
discreteProbabilityMass: None,
|
||||||
|
} =>
|
||||||
|
None
|
||||||
|
| {
|
||||||
|
continuous: ADDS_TO_1,
|
||||||
|
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
|
discreteProbabilityMass: None,
|
||||||
|
} =>
|
||||||
|
let discreteProbabilityMassFraction = Shape.Discrete.ySum(discrete);
|
||||||
|
let discrete = Shape.Discrete.scaleYToTotal(1.0, discrete);
|
||||||
|
Some(
|
||||||
|
Shape.Mixed.make(
|
||||||
|
~continuous,
|
||||||
|
~discrete,
|
||||||
|
~discreteProbabilityMassFraction,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
| _ => None
|
||||||
|
};
|
107
src/core/Shape.re
Normal file
107
src/core/Shape.re
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
open DistributionTypes;
|
||||||
|
|
||||||
|
let _lastElement = (a: array('a)) =>
|
||||||
|
switch (Belt.Array.size(a)) {
|
||||||
|
| 0 => None
|
||||||
|
| n => Belt.Array.get(a, n - 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
module XYShape = {
|
||||||
|
type t = xyShape;
|
||||||
|
|
||||||
|
let toJs = (t: t) => {
|
||||||
|
{"xs": t.xs, "ys": t.ys};
|
||||||
|
};
|
||||||
|
|
||||||
|
let fmap = (t: t, y): t => {xs: t.xs, ys: t.ys |> E.A.fmap(y)};
|
||||||
|
|
||||||
|
let yFold = (fn, t: t) => {
|
||||||
|
E.A.fold_left(fn, 0., t.ys);
|
||||||
|
};
|
||||||
|
|
||||||
|
let ySum = yFold((a, b) => a +. b);
|
||||||
|
|
||||||
|
let fromArrays = (xs, ys): t => {xs, ys};
|
||||||
|
|
||||||
|
let transverse = (fn, p: t) => {
|
||||||
|
let (xs, ys) =
|
||||||
|
Belt.Array.zip(p.xs, p.ys)
|
||||||
|
->Belt.Array.reduce([||], (items, (x, y)) =>
|
||||||
|
switch (_lastElement(items)) {
|
||||||
|
| Some((_, yLast)) =>
|
||||||
|
Belt.Array.concat(items, [|(x, fn(y, yLast))|])
|
||||||
|
| None => [|(x, y)|]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> Belt.Array.unzip;
|
||||||
|
fromArrays(xs, ys);
|
||||||
|
};
|
||||||
|
|
||||||
|
let integral = transverse((aCurrent, aLast) => aCurrent +. aLast);
|
||||||
|
let derivative = transverse((aCurrent, aLast) => aCurrent -. aLast);
|
||||||
|
};
|
||||||
|
|
||||||
|
module Continuous = {
|
||||||
|
let fromArrays = XYShape.fromArrays;
|
||||||
|
let toJs = XYShape.toJs;
|
||||||
|
let toPdf = CdfLibrary.Distribution.toPdf;
|
||||||
|
let toCdf = CdfLibrary.Distribution.toCdf;
|
||||||
|
let findX = CdfLibrary.Distribution.findX;
|
||||||
|
let findY = CdfLibrary.Distribution.findY;
|
||||||
|
};
|
||||||
|
|
||||||
|
module Discrete = {
|
||||||
|
type t = discreteShape;
|
||||||
|
let fromArrays = XYShape.fromArrays;
|
||||||
|
let toJs = XYShape.toJs;
|
||||||
|
let ySum = XYShape.ySum;
|
||||||
|
let zip = t => Belt.Array.zip(t.xs, t.ys);
|
||||||
|
|
||||||
|
let scaleYToTotal = (totalDesired, t: t): t => {
|
||||||
|
let difference = totalDesired /. ySum(t);
|
||||||
|
XYShape.fmap(t, y => y *. difference);
|
||||||
|
};
|
||||||
|
|
||||||
|
let render = (t: t) =>
|
||||||
|
Belt.Array.zip(t.xs, t.ys)
|
||||||
|
|> E.A.fmap(((x, y)) =>
|
||||||
|
<div>
|
||||||
|
{E.Float.toFixed(x)
|
||||||
|
++ "---"
|
||||||
|
++ E.Float.with3DigitsPrecision(y *. 100.)
|
||||||
|
|> ReasonReact.string}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|> ReasonReact.array;
|
||||||
|
|
||||||
|
let findY = (x: float, t: t) =>
|
||||||
|
switch (E.A.getBy(zip(t), ((ix, _)) => ix == x)) {
|
||||||
|
| Some((_, y)) => y
|
||||||
|
| None => 0.
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module Mixed = {
|
||||||
|
let make = (~continuous, ~discrete, ~discreteProbabilityMassFraction) => {
|
||||||
|
continuous,
|
||||||
|
discrete,
|
||||||
|
discreteProbabilityMassFraction,
|
||||||
|
};
|
||||||
|
|
||||||
|
type yPdfPoint = {
|
||||||
|
continuous: float,
|
||||||
|
discrete: float,
|
||||||
|
};
|
||||||
|
|
||||||
|
let getY = (t: DistributionTypes.mixedShape, x: float): yPdfPoint => {
|
||||||
|
continuous: Continuous.findY(x, t.continuous),
|
||||||
|
discrete: Discrete.findY(x, t.discrete),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module DomainMixed = {
|
||||||
|
type t = {
|
||||||
|
mixedShape,
|
||||||
|
domain,
|
||||||
|
};
|
||||||
|
};
|
59
src/core/TimeTypes.re
Normal file
59
src/core/TimeTypes.re
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
type timeUnit = [
|
||||||
|
| `days
|
||||||
|
| `hours
|
||||||
|
| `milliseconds
|
||||||
|
| `minutes
|
||||||
|
| `months
|
||||||
|
| `quarters
|
||||||
|
| `seconds
|
||||||
|
| `weeks
|
||||||
|
| `years
|
||||||
|
];
|
||||||
|
|
||||||
|
type timeVector = {
|
||||||
|
zero: MomentRe.Moment.t,
|
||||||
|
unit: timeUnit,
|
||||||
|
};
|
||||||
|
|
||||||
|
type timePoint = {
|
||||||
|
timeVector,
|
||||||
|
value: float,
|
||||||
|
};
|
||||||
|
|
||||||
|
module TimePoint = {
|
||||||
|
let fromTimeVector = (timeVector, value): timePoint => {timeVector, value};
|
||||||
|
|
||||||
|
let toMoment = (timePoint: timePoint) => {
|
||||||
|
timePoint.timeVector.zero
|
||||||
|
|> MomentRe.Moment.add(
|
||||||
|
~duration=
|
||||||
|
MomentRe.duration(timePoint.value, timePoint.timeVector.unit),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let fromMoment = (timeVector: timeVector, moment: MomentRe.Moment.t) =>
|
||||||
|
MomentRe.diff(timeVector.zero, moment, timeVector.unit);
|
||||||
|
};
|
||||||
|
|
||||||
|
module RelativeTimePoint = {
|
||||||
|
type timeInVector =
|
||||||
|
| Time(MomentRe.Moment.t)
|
||||||
|
| XValue(float);
|
||||||
|
|
||||||
|
let toTime = (timeVector: timeVector, timeInVector: timeInVector) =>
|
||||||
|
switch (timeInVector) {
|
||||||
|
| Time(r) => r
|
||||||
|
| XValue(r) =>
|
||||||
|
timeVector.zero
|
||||||
|
|> MomentRe.Moment.add(~duration=MomentRe.duration(r, timeVector.unit))
|
||||||
|
};
|
||||||
|
|
||||||
|
let _timeToX = (time, timeStart, timeUnit) =>
|
||||||
|
MomentRe.diff(timeStart, time, timeUnit);
|
||||||
|
|
||||||
|
let toXValue = (timeVector: timeVector, timeInVector: timeInVector) =>
|
||||||
|
switch (timeInVector) {
|
||||||
|
| Time(r) => _timeToX(r, timeVector.zero, timeVector.unit)
|
||||||
|
| XValue(r) => r
|
||||||
|
};
|
||||||
|
};
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Foretold.io</title>
|
<title>Estiband</title>
|
||||||
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
|
||||||
<link href="./styles/index.css" rel="stylesheet">
|
<link href="./styles/index.css" rel="stylesheet">
|
||||||
<script src="./Index.re" defer></script>
|
<script src="./Index.re" defer></script>
|
||||||
|
|
|
@ -13,6 +13,39 @@ let makeHelpers = (combo): formState => {
|
||||||
{combo, setCombo, setInputValue};
|
{combo, setCombo, setInputValue};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let propValue = (t: Prop.Value.t) => {
|
||||||
|
switch (t) {
|
||||||
|
| SelectSingle(r) => r |> ReasonReact.string
|
||||||
|
| ConditionalArray(r) => "Array" |> ReasonReact.string
|
||||||
|
| GenericDistribution(r) =>
|
||||||
|
let newDistribution =
|
||||||
|
GenericDistribution.renderIfNeeded(~sampleCount=1000, r);
|
||||||
|
switch (newDistribution) {
|
||||||
|
| Some({
|
||||||
|
generationSource:
|
||||||
|
Shape(
|
||||||
|
Mixed({
|
||||||
|
continuous: n,
|
||||||
|
discrete: d,
|
||||||
|
discreteProbabilityMassFraction: f,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}) =>
|
||||||
|
<div>
|
||||||
|
<Chart height=100 data={n |> Shape.Continuous.toJs} />
|
||||||
|
{d |> Shape.Discrete.scaleYToTotal(f) |> Shape.Discrete.render}
|
||||||
|
</div>
|
||||||
|
| None => "Something went wrong" |> ReasonReact.string
|
||||||
|
| _ => <div />
|
||||||
|
};
|
||||||
|
| FloatCdf(_) => <div />
|
||||||
|
| Probability(r) =>
|
||||||
|
(r *. 100. |> Js.Float.toFixed) ++ "%" |> ReasonReact.string
|
||||||
|
| DateTime(r) => r |> MomentRe.Moment.defaultFormat |> ReasonReact.string
|
||||||
|
| FloatPoint(r) => r |> Js.Float.toFixed |> ReasonReact.string
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
module ModelForm = {
|
module ModelForm = {
|
||||||
let handleChange = (handleChange, event) =>
|
let handleChange = (handleChange, event) =>
|
||||||
handleChange(ReactEvent.Form.target(event)##value);
|
handleChange(ReactEvent.Form.target(event)##value);
|
||||||
|
@ -51,10 +84,8 @@ module ModelForm = {
|
||||||
)
|
)
|
||||||
|> ReasonReact.array}
|
|> ReasonReact.array}
|
||||||
<div className="bg-green-100 p-2 rounded-sm mt-6 text-lg">
|
<div className="bg-green-100 p-2 rounded-sm mt-6 text-lg">
|
||||||
{model.run(formState.combo)
|
{model.run(Prop.Combo.InputValues.toValueArray(formState.combo))
|
||||||
|> E.O.fmap(Value.to_string)
|
|> E.O.React.fmapOrNull(propValue)}
|
||||||
|> E.O.default("")
|
|
||||||
|> ReasonReact.string}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>;
|
</div>;
|
|
@ -1,31 +1,95 @@
|
||||||
module Value = {
|
module Value = {
|
||||||
type binaryConditional =
|
type conditional = {
|
||||||
| Selected(bool)
|
name: string,
|
||||||
| Unselected;
|
truthValue: bool,
|
||||||
|
};
|
||||||
|
|
||||||
type t =
|
type t =
|
||||||
| BinaryConditional(binaryConditional)
|
|
||||||
| SelectSingle(string)
|
| SelectSingle(string)
|
||||||
| DateTime(MomentRe.Moment.t)
|
| DateTime(MomentRe.Moment.t)
|
||||||
| FloatPoint(float)
|
| FloatPoint(float)
|
||||||
| Probability(float)
|
| Probability(float)
|
||||||
|
| GenericDistribution(DistributionTypes.genericDistribution)
|
||||||
|
| ConditionalArray(array(conditional))
|
||||||
| FloatCdf(string);
|
| FloatCdf(string);
|
||||||
|
|
||||||
let to_string = (t: t) => {
|
module ConditionalArray = {
|
||||||
switch (t) {
|
let get = (conditionals: array(conditional), name: string) =>
|
||||||
| BinaryConditional(binaryConditional) =>
|
Belt.Array.getBy(conditionals, (c: conditional) => c.name == name);
|
||||||
switch (binaryConditional) {
|
|
||||||
| Selected(r) => r ? "True" : "False"
|
|
||||||
| Unselected => ""
|
|
||||||
}
|
|
||||||
| SelectSingle(r) => r
|
|
||||||
| FloatCdf(r) => r
|
|
||||||
| Probability(r) => (r *. 100. |> Js.Float.toFixed) ++ "%"
|
|
||||||
| DateTime(r) => r |> MomentRe.Moment.defaultFormat
|
|
||||||
| FloatPoint(r) => r |> Js.Float.toFixed
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module ValueCombination = {
|
||||||
|
type pointsToEvenlySample = int;
|
||||||
|
|
||||||
|
type dateTimeRange = {
|
||||||
|
startTime: MomentRe.Moment.t,
|
||||||
|
endTime: MomentRe.Moment.t,
|
||||||
|
pointsWithin: int,
|
||||||
|
};
|
||||||
|
|
||||||
|
type floatPointRange = {
|
||||||
|
startTime: float,
|
||||||
|
endTime: float,
|
||||||
|
pointsWithin: int,
|
||||||
|
};
|
||||||
|
|
||||||
|
type range('a) = {
|
||||||
|
beginning: 'a,
|
||||||
|
ending: 'a,
|
||||||
|
pointsToEvenlySample,
|
||||||
|
};
|
||||||
|
|
||||||
|
type t =
|
||||||
|
| SelectSingle
|
||||||
|
| DateTime(range(MomentRe.Moment.t))
|
||||||
|
| FloatPoint(range(MomentRe.Moment.t))
|
||||||
|
| Probability(pointsToEvenlySample);
|
||||||
|
};
|
||||||
|
|
||||||
|
module ValueCluster = {
|
||||||
|
type conditional = {
|
||||||
|
name: string,
|
||||||
|
truthValue: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
type pointsToEvenlySample = int;
|
||||||
|
|
||||||
|
type dateTimeRange = {
|
||||||
|
startTime: MomentRe.Moment.t,
|
||||||
|
endTime: MomentRe.Moment.t,
|
||||||
|
pointsWithin: int,
|
||||||
|
};
|
||||||
|
|
||||||
|
type floatPointRange = {
|
||||||
|
startTime: float,
|
||||||
|
endTime: float,
|
||||||
|
pointsWithin: int,
|
||||||
|
};
|
||||||
|
|
||||||
|
type range('a) = {
|
||||||
|
beginning: 'a,
|
||||||
|
ending: 'a,
|
||||||
|
pointsToEvenlySample,
|
||||||
|
};
|
||||||
|
|
||||||
|
type t =
|
||||||
|
| SelectSingle([ | `combination | `item(string)])
|
||||||
|
| DateTime(
|
||||||
|
[
|
||||||
|
| `combination(range(MomentRe.Moment.t))
|
||||||
|
| `item(MomentRe.Moment.t)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
| FloatPoint(
|
||||||
|
[ | `combination(range(MomentRe.Moment.t)) | `item(string)],
|
||||||
|
)
|
||||||
|
| Probability([ | `item(string)])
|
||||||
|
| GenericDistribution([ | `item(DistributionTypes.genericDistribution)])
|
||||||
|
| ConditionalArray([ | `item(array(conditional))])
|
||||||
|
| FloatCdf([ | `item(string)]);
|
||||||
|
};
|
||||||
|
|
||||||
module Type = {
|
module Type = {
|
||||||
type selectOption = {
|
type selectOption = {
|
||||||
id: string,
|
id: string,
|
||||||
|
@ -37,6 +101,16 @@ module Type = {
|
||||||
default: option(string),
|
default: option(string),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type conditionals = {
|
||||||
|
defaults: array(Value.conditional),
|
||||||
|
options: array(string),
|
||||||
|
};
|
||||||
|
|
||||||
|
let makeConditionals = (defaults, options): conditionals => {
|
||||||
|
defaults,
|
||||||
|
options,
|
||||||
|
};
|
||||||
|
|
||||||
type floatPoint = {validatations: list(float => bool)};
|
type floatPoint = {validatations: list(float => bool)};
|
||||||
|
|
||||||
type withDefaultMinMax('a) = {
|
type withDefaultMinMax('a) = {
|
||||||
|
@ -48,17 +122,17 @@ module Type = {
|
||||||
type withDefault('a) = {default: option('a)};
|
type withDefault('a) = {default: option('a)};
|
||||||
|
|
||||||
type t =
|
type t =
|
||||||
| BinaryConditional
|
|
||||||
| SelectSingle(selectSingle)
|
| SelectSingle(selectSingle)
|
||||||
| FloatPoint(withDefaultMinMax(float))
|
| FloatPoint(withDefaultMinMax(float))
|
||||||
| Probability(withDefault(float))
|
| Probability(withDefault(float))
|
||||||
| DateTime(withDefaultMinMax(MomentRe.Moment.t))
|
| DateTime(withDefaultMinMax(MomentRe.Moment.t))
|
||||||
| Year(withDefaultMinMax(float))
|
| Year(withDefaultMinMax(float))
|
||||||
|
| Conditionals(conditionals)
|
||||||
| FloatCdf;
|
| FloatCdf;
|
||||||
|
|
||||||
let default = (t: t) =>
|
let default = (t: t) =>
|
||||||
switch (t) {
|
switch (t) {
|
||||||
| BinaryConditional => Some(Value.BinaryConditional(Unselected))
|
| Conditionals(s) => Some(Value.ConditionalArray(s.defaults))
|
||||||
| Year(r) => r.default->Belt.Option.map(p => Value.FloatPoint(p))
|
| Year(r) => r.default->Belt.Option.map(p => Value.FloatPoint(p))
|
||||||
| FloatPoint(r) => r.default->Belt.Option.map(p => Value.FloatPoint(p))
|
| FloatPoint(r) => r.default->Belt.Option.map(p => Value.FloatPoint(p))
|
||||||
| Probability(r) => r.default->Belt.Option.map(p => Value.Probability(p))
|
| Probability(r) => r.default->Belt.Option.map(p => Value.Probability(p))
|
||||||
|
@ -85,6 +159,22 @@ module ValueMap = {
|
||||||
let fromOptionalArray = (r): t => MS.fromArray(r) |> fromOptionalMap;
|
let fromOptionalArray = (r): t => MS.fromArray(r) |> fromOptionalMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module ValueClusterMap = {
|
||||||
|
module MS = Belt.Map.String;
|
||||||
|
type t = MS.t(ValueCluster.t);
|
||||||
|
let get = (t: t, s) => MS.get(t, s);
|
||||||
|
let keys = MS.keysToArray;
|
||||||
|
let map = MS.map;
|
||||||
|
let fromArray = (r): t => MS.fromArray(r);
|
||||||
|
let values = (t: t) => t |> MS.valuesToArray;
|
||||||
|
let update = (t, k, v) => MS.update(t, k, _ => v);
|
||||||
|
let toArray = MS.toArray;
|
||||||
|
let fromOptionalMap = (t: MS.t(option(ValueCluster.t))): t =>
|
||||||
|
MS.keep(t, (_, d) => E.O.isSome(d))
|
||||||
|
->MS.map(d => E.O.toExn("This should not have happened", d));
|
||||||
|
let fromOptionalArray = (r): t => MS.fromArray(r) |> fromOptionalMap;
|
||||||
|
};
|
||||||
|
|
||||||
module TypeWithMetadata = {
|
module TypeWithMetadata = {
|
||||||
// TODO: Figure out a better name for assumptionType
|
// TODO: Figure out a better name for assumptionType
|
||||||
type assumptionType =
|
type assumptionType =
|
||||||
|
@ -120,7 +210,7 @@ module TypeWithMetadata = {
|
||||||
let currentYear =
|
let currentYear =
|
||||||
make(
|
make(
|
||||||
~id="currentYear",
|
~id="currentYear",
|
||||||
~name="Current Year",
|
~name="Current Day",
|
||||||
~description=None,
|
~description=None,
|
||||||
~type_=
|
~type_=
|
||||||
DateTime({
|
DateTime({
|
||||||
|
@ -141,7 +231,7 @@ module Model = {
|
||||||
version: string,
|
version: string,
|
||||||
inputTypes: array(TypeWithMetadata.t),
|
inputTypes: array(TypeWithMetadata.t),
|
||||||
outputTypes: array(TypeWithMetadata.t),
|
outputTypes: array(TypeWithMetadata.t),
|
||||||
run: combo => option(Value.t),
|
run: array(option(Value.t)) => option(Value.t),
|
||||||
}
|
}
|
||||||
and combo = {
|
and combo = {
|
||||||
model: t,
|
model: t,
|
||||||
|
@ -152,11 +242,14 @@ module Model = {
|
||||||
module InputTypes = {
|
module InputTypes = {
|
||||||
let keys = (t: t) =>
|
let keys = (t: t) =>
|
||||||
t.inputTypes |> E.A.fmap((r: TypeWithMetadata.t) => r.id);
|
t.inputTypes |> E.A.fmap((r: TypeWithMetadata.t) => r.id);
|
||||||
|
|
||||||
|
let getBy = (t: t, fn) => t.inputTypes |> E.A.getBy(_, fn);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
module Combo = {
|
module Combo = {
|
||||||
type t = Model.combo;
|
type t = Model.combo;
|
||||||
|
type valueArray = array(option(Value.t));
|
||||||
|
|
||||||
module InputValues = {
|
module InputValues = {
|
||||||
let defaults = (t: Model.t) =>
|
let defaults = (t: Model.t) =>
|
||||||
|
@ -173,10 +266,10 @@ module Combo = {
|
||||||
let update = (t: t, key: string, onUpdate: option(Value.t)) =>
|
let update = (t: t, key: string, onUpdate: option(Value.t)) =>
|
||||||
ValueMap.update(t.inputValues, key, onUpdate);
|
ValueMap.update(t.inputValues, key, onUpdate);
|
||||||
|
|
||||||
let toValueArray = (t: t) => {
|
let toValueArray = (t: t): valueArray => {
|
||||||
t.model.inputTypes
|
t.model.inputTypes
|
||||||
|> E.A.fmap((r: TypeWithMetadata.t) =>
|
|> E.A.fmap((r: TypeWithMetadata.t) =>
|
||||||
ValueMap.get(t.inputValues, r.id)
|
t.inputValues->ValueMap.get(r.id)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
157
src/interface/ValueForm.re
Normal file
157
src/interface/ValueForm.re
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
open Prop;
|
||||||
|
let handleChange = (handleChange, event) =>
|
||||||
|
handleChange(ReactEvent.Form.target(event)##value);
|
||||||
|
type onChange = option(Value.t) => unit;
|
||||||
|
|
||||||
|
module ConditionalReducer = {
|
||||||
|
type action =
|
||||||
|
| ADD_OR_UPDATE_CONDITIONAL(Value.conditional)
|
||||||
|
| REMOVE_CONDITIONAL(Value.conditional);
|
||||||
|
|
||||||
|
let reducer = (items: array(Value.conditional), action: action) =>
|
||||||
|
switch (action) {
|
||||||
|
| ADD_OR_UPDATE_CONDITIONAL(conditional) =>
|
||||||
|
items->E.A.hasBy(c => c.name == conditional.name)
|
||||||
|
? items
|
||||||
|
|> E.A.fmap((r: Value.conditional) =>
|
||||||
|
r.name == conditional.name ? conditional : r
|
||||||
|
)
|
||||||
|
: E.A.append(items, [|conditional|])
|
||||||
|
| REMOVE_CONDITIONAL(conditional) =>
|
||||||
|
items
|
||||||
|
|> E.A.filter((c: Value.conditional) => c.name != conditional.name)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
[@react.component]
|
||||||
|
let make =
|
||||||
|
(
|
||||||
|
~type_: TypeWithMetadata.t,
|
||||||
|
~value: option(Value.t),
|
||||||
|
~onChange: onChange,
|
||||||
|
) => {
|
||||||
|
switch (type_.type_, value) {
|
||||||
|
| (Conditionals(l), Some(ConditionalArray(n))) =>
|
||||||
|
<div>
|
||||||
|
{n
|
||||||
|
|> E.A.fmap((r: Value.conditional) =>
|
||||||
|
<div
|
||||||
|
onClick={_ =>
|
||||||
|
onChange(
|
||||||
|
Some(
|
||||||
|
Value.ConditionalArray(
|
||||||
|
ConditionalReducer.reducer(
|
||||||
|
n,
|
||||||
|
REMOVE_CONDITIONAL({name: r.name, truthValue: true}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}>
|
||||||
|
{r.name |> ReasonReact.string}
|
||||||
|
{(r.truthValue ? " = True" : " = False") |> ReasonReact.string}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|> ReasonReact.array}
|
||||||
|
{l.options
|
||||||
|
|> E.A.fmap(r =>
|
||||||
|
<div
|
||||||
|
className="max-w-sm rounded overflow-hidden shadow-sm py-1 px-2 rounded mb-3 bg-gray-200">
|
||||||
|
{r |> ReasonReact.string}
|
||||||
|
<button
|
||||||
|
className="bg-blue-500 hover:bg-blue-700 text-white py-0 px-1 rounded"
|
||||||
|
onClick={e => {
|
||||||
|
ReactEvent.Synthetic.preventDefault(e);
|
||||||
|
onChange(
|
||||||
|
Some(
|
||||||
|
Value.ConditionalArray(
|
||||||
|
ConditionalReducer.reducer(
|
||||||
|
n,
|
||||||
|
ADD_OR_UPDATE_CONDITIONAL({
|
||||||
|
name: r,
|
||||||
|
truthValue: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
();
|
||||||
|
}}>
|
||||||
|
{"=True" |> ReasonReact.string}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="hover:bg-red-700 text-white py-0 px-1 rounded bg-red-500"
|
||||||
|
onClick={e => {
|
||||||
|
ReactEvent.Synthetic.preventDefault(e);
|
||||||
|
onChange(
|
||||||
|
Some(
|
||||||
|
Value.ConditionalArray(
|
||||||
|
ConditionalReducer.reducer(
|
||||||
|
n,
|
||||||
|
ADD_OR_UPDATE_CONDITIONAL({
|
||||||
|
name: r,
|
||||||
|
truthValue: false,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}>
|
||||||
|
{"=False" |> ReasonReact.string}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|> ReasonReact.array}
|
||||||
|
</div>
|
||||||
|
| (Conditionals(l), _) =>
|
||||||
|
l.options |> E.A.fmap(r => r |> ReasonReact.string) |> ReasonReact.array
|
||||||
|
| (Year(_), Some(FloatPoint(r))) =>
|
||||||
|
<input
|
||||||
|
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||||
|
type_="number"
|
||||||
|
value={r |> Js.Float.toString}
|
||||||
|
onChange={handleChange(r =>
|
||||||
|
switch (Js.Float.fromString(r)) {
|
||||||
|
| r => onChange(Some(Value.FloatPoint(r)))
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
| (FloatPoint(_), Some(FloatPoint(r))) =>
|
||||||
|
<input
|
||||||
|
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||||
|
type_="number"
|
||||||
|
value={r |> Js.Float.toString}
|
||||||
|
onChange={handleChange(r =>
|
||||||
|
switch (Js.Float.fromString(r)) {
|
||||||
|
| r => onChange(Some(Value.FloatPoint(r)))
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
| (Year(_), _)
|
||||||
|
| (FloatPoint(_), _) => <input type_="number" value="" />
|
||||||
|
| (SelectSingle(t), Some(SelectSingle(r))) =>
|
||||||
|
<select
|
||||||
|
defaultValue=r
|
||||||
|
className="block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline"
|
||||||
|
onChange={handleChange(l => onChange(Some(Value.SelectSingle(l))))}>
|
||||||
|
{t.options
|
||||||
|
|> E.A.of_list
|
||||||
|
|> E.A.fmap((l: Type.selectOption) =>
|
||||||
|
<option value={l.id} key={l.id}>
|
||||||
|
{l.name |> ReasonReact.string}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
|> ReasonReact.array}
|
||||||
|
</select>
|
||||||
|
| (DateTime(_), Some(DateTime((d: MomentRe.Moment.t)))) =>
|
||||||
|
<input
|
||||||
|
type_="date"
|
||||||
|
value={MomentRe.Moment.format("YYYY-MM-DD", d)}
|
||||||
|
onChange={handleChange(r =>
|
||||||
|
onChange(
|
||||||
|
Some(Value.DateTime(MomentRe.momentWithFormat(r, "YYYY-MM-DD"))),
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
};
|
||||||
|
};
|
34
src/lib/Chart.re
Normal file
34
src/lib/Chart.re
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
module Styles = {
|
||||||
|
open Css;
|
||||||
|
let graph = chartColor =>
|
||||||
|
style([
|
||||||
|
selector(".axis", [fontSize(`px(9))]),
|
||||||
|
selector(".domain", [display(`none)]),
|
||||||
|
selector(".tick line", [display(`none)]),
|
||||||
|
selector(".tick text", [color(`hex("bfcad4"))]),
|
||||||
|
selector(".chart .area-path", [SVG.fill(chartColor)]),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
[@react.component]
|
||||||
|
let make =
|
||||||
|
(
|
||||||
|
~data,
|
||||||
|
~minX=None,
|
||||||
|
~maxX=None,
|
||||||
|
~width=300,
|
||||||
|
~height=50,
|
||||||
|
~color=`hex("7e9db7"),
|
||||||
|
) =>
|
||||||
|
<div className={Styles.graph(color)}>
|
||||||
|
<ForetoldComponents.CdfChart__Base
|
||||||
|
width=0
|
||||||
|
height
|
||||||
|
?minX
|
||||||
|
?maxX
|
||||||
|
marginBottom=20
|
||||||
|
showVerticalLine=false
|
||||||
|
showDistributionLines=false
|
||||||
|
primaryDistribution=data
|
||||||
|
/>
|
||||||
|
</div>;
|
|
@ -211,6 +211,7 @@ module A = {
|
||||||
let unsafe_get = Array.unsafe_get;
|
let unsafe_get = Array.unsafe_get;
|
||||||
let get = Belt.Array.get;
|
let get = Belt.Array.get;
|
||||||
let getBy = Belt.Array.getBy;
|
let getBy = Belt.Array.getBy;
|
||||||
|
let hasBy = (r, fn) => Belt.Array.getBy(r, fn) |> O.isSome;
|
||||||
let fold_left = Array.fold_left;
|
let fold_left = Array.fold_left;
|
||||||
let fold_right = Array.fold_right;
|
let fold_right = Array.fold_right;
|
||||||
let concatMany = Belt.Array.concatMany;
|
let concatMany = Belt.Array.concatMany;
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
let normal = (mean: float, std: float) =>
|
|
||||||
Js.Float.(
|
|
||||||
{
|
|
||||||
let nMean = toPrecisionWithPrecision(mean, ~digits=4);
|
|
||||||
let nStd = toPrecisionWithPrecision(std, ~digits=2);
|
|
||||||
{j|normal($(nMean), $(nStd))|j};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let divide = (str1: string, str2: string) => {j|$(str1)/$(str2)|j};
|
|
21
src/lib/GuesstimatorDist.re
Normal file
21
src/lib/GuesstimatorDist.re
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
let normal = (mean: float, std: float) =>
|
||||||
|
Js.Float.(
|
||||||
|
{
|
||||||
|
let nMean = toPrecisionWithPrecision(mean, ~digits=4);
|
||||||
|
let nStd = toPrecisionWithPrecision(std, ~digits=2);
|
||||||
|
{j|normal($(nMean), $(nStd))|j};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let logNormal = (mean: float, std: float) => {
|
||||||
|
Js.Float.(
|
||||||
|
{
|
||||||
|
let nMean = toPrecisionWithPrecision(Js.Math.log10(mean), ~digits=4);
|
||||||
|
let nStd = toPrecisionWithPrecision(Js.Math.log10(std), ~digits=2);
|
||||||
|
{j|lognormal($(nMean), $(nStd))|j};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let divide = (str1: string, str2: string) => {j|$(str1)/$(str2)|j};
|
||||||
|
let min = (str1: string, str2: string) => {j|min($(str1),$(str2))|j};
|
|
@ -1,93 +0,0 @@
|
||||||
open Prop;
|
|
||||||
let handleChange = (handleChange, event) =>
|
|
||||||
handleChange(ReactEvent.Form.target(event)##value);
|
|
||||||
type onChange = option(Value.t) => unit;
|
|
||||||
|
|
||||||
[@react.component]
|
|
||||||
let make =
|
|
||||||
(
|
|
||||||
~type_: TypeWithMetadata.t,
|
|
||||||
~value: option(Value.t),
|
|
||||||
~onChange: onChange,
|
|
||||||
) => {
|
|
||||||
switch (type_.type_, value) {
|
|
||||||
| (Year(_), Some(FloatPoint(r))) =>
|
|
||||||
<input
|
|
||||||
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
|
||||||
type_="number"
|
|
||||||
value={r |> Js.Float.toString}
|
|
||||||
onChange={handleChange(r =>
|
|
||||||
switch (Js.Float.fromString(r)) {
|
|
||||||
| r => onChange(Some(Value.FloatPoint(r)))
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
| (FloatPoint(_), Some(FloatPoint(r))) =>
|
|
||||||
<input
|
|
||||||
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
|
||||||
type_="number"
|
|
||||||
value={r |> Js.Float.toString}
|
|
||||||
onChange={handleChange(r =>
|
|
||||||
switch (Js.Float.fromString(r)) {
|
|
||||||
| r => onChange(Some(Value.FloatPoint(r)))
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
| (BinaryConditional, Some(BinaryConditional(r))) =>
|
|
||||||
switch (r) {
|
|
||||||
| Unselected =>
|
|
||||||
<div
|
|
||||||
onClick={_ => onChange(Some(BinaryConditional(Selected(true))))}>
|
|
||||||
{"Select" |> ReasonReact.string}
|
|
||||||
</div>
|
|
||||||
| Selected(true) =>
|
|
||||||
<div>
|
|
||||||
{"YES!" |> ReasonReact.string}
|
|
||||||
<div
|
|
||||||
onClick={_ => onChange(Some(BinaryConditional(Selected(false))))}>
|
|
||||||
{"No" |> ReasonReact.string}
|
|
||||||
</div>
|
|
||||||
<div onClick={_ => onChange(Some(BinaryConditional(Unselected)))}>
|
|
||||||
{"Deselect" |> ReasonReact.string}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
| Selected(false) =>
|
|
||||||
<div>
|
|
||||||
{"NO!" |> ReasonReact.string}
|
|
||||||
<div
|
|
||||||
onClick={_ => onChange(Some(BinaryConditional(Selected(true))))}>
|
|
||||||
{"Yes" |> ReasonReact.string}
|
|
||||||
</div>
|
|
||||||
<div onClick={_ => onChange(Some(BinaryConditional(Unselected)))}>
|
|
||||||
{"Deselect" |> ReasonReact.string}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
| (Year(_), _)
|
|
||||||
| (FloatPoint(_), _) => <input type_="number" value="" />
|
|
||||||
| (SelectSingle(t), Some(SelectSingle(r))) =>
|
|
||||||
<select
|
|
||||||
defaultValue=r
|
|
||||||
className="block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline"
|
|
||||||
onChange={handleChange(l => onChange(Some(Value.SelectSingle(l))))}>
|
|
||||||
{t.options
|
|
||||||
|> E.A.of_list
|
|
||||||
|> E.A.fmap((l: Type.selectOption) =>
|
|
||||||
<option value={l.id} key={l.id}>
|
|
||||||
{l.name |> ReasonReact.string}
|
|
||||||
</option>
|
|
||||||
)
|
|
||||||
|> ReasonReact.array}
|
|
||||||
</select>
|
|
||||||
| (DateTime(_), Some(DateTime((d: MomentRe.Moment.t)))) =>
|
|
||||||
<input
|
|
||||||
type_="date"
|
|
||||||
value={MomentRe.Moment.format("YYYY-MM-DD", d)}
|
|
||||||
onChange={handleChange(r =>
|
|
||||||
onChange(
|
|
||||||
Some(Value.DateTime(MomentRe.momentWithFormat(r, "YYYY-MM-DD"))),
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -78,7 +78,10 @@ module Model = {
|
||||||
let yearDiff = MomentRe.diff(dateTime, currentDateTime, `days) /. 365.;
|
let yearDiff = MomentRe.diff(dateTime, currentDateTime, `days) /. 365.;
|
||||||
let meanDiff = Js.Math.pow_float(~base=y.meanDiff, ~exp=yearDiff);
|
let meanDiff = Js.Math.pow_float(~base=y.meanDiff, ~exp=yearDiff);
|
||||||
let stdDevDiff = Js.Math.pow_float(~base=y.meanDiff, ~exp=yearDiff);
|
let stdDevDiff = Js.Math.pow_float(~base=y.meanDiff, ~exp=yearDiff);
|
||||||
FloatCdf.normal(currentValue *. meanDiff, firstYearStdDev *. stdDevDiff);
|
GuesstimatorDist.logNormal(
|
||||||
|
currentValue *. meanDiff,
|
||||||
|
firstYearStdDev *. stdDevDiff,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let rec currentValue = (group: group, output) => {
|
let rec currentValue = (group: group, output) => {
|
||||||
|
@ -100,27 +103,60 @@ module Model = {
|
||||||
| (_, CHANCE_OF_EXISTENCE) => 0.0
|
| (_, CHANCE_OF_EXISTENCE) => 0.0
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let xRisk = conditionals =>
|
||||||
|
Prop.Value.ConditionalArray.get(conditionals, "Global Existential Event");
|
||||||
|
|
||||||
let make =
|
let make =
|
||||||
(
|
(
|
||||||
group: group,
|
group: group,
|
||||||
dateTime: MomentRe.Moment.t,
|
dateTime: MomentRe.Moment.t,
|
||||||
currentDateTime: MomentRe.Moment.t,
|
currentDateTime: MomentRe.Moment.t,
|
||||||
output: output,
|
output: output,
|
||||||
|
conditionals: array(Prop.Value.conditional),
|
||||||
) => {
|
) => {
|
||||||
|
let xRisk = xRisk(conditionals);
|
||||||
switch (output) {
|
switch (output) {
|
||||||
| DONATIONS
|
| DONATIONS
|
||||||
| PAYOUTS =>
|
| PAYOUTS =>
|
||||||
Prop.Value.FloatCdf(
|
let difference =
|
||||||
calculateDifference(
|
calculateDifference(
|
||||||
currentValue(group, output),
|
currentValue(group, output),
|
||||||
dateTime,
|
dateTime,
|
||||||
currentDateTime,
|
currentDateTime,
|
||||||
yearlyMeanGrowthRateIfNotClosed(group),
|
yearlyMeanGrowthRateIfNotClosed(group),
|
||||||
|
);
|
||||||
|
let str =
|
||||||
|
switch (xRisk) {
|
||||||
|
| Some({truthValue: true}) => "0"
|
||||||
|
| Some({truthValue: false}) => difference
|
||||||
|
| None => "uniform(0,1) > .3 ? " ++ difference ++ ": 0"
|
||||||
|
};
|
||||||
|
let genericDistribution =
|
||||||
|
GenericDistribution.make(
|
||||||
|
~generationSource=GuesstimatorString(str),
|
||||||
|
~probabilityType=Cdf,
|
||||||
|
~domain=Complete,
|
||||||
|
~unit=Unspecified,
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
Prop.Value.GenericDistribution(genericDistribution);
|
||||||
|
| CHANCE_OF_EXISTENCE =>
|
||||||
|
Prop.Value.GenericDistribution(
|
||||||
|
GenericDistribution.make(
|
||||||
|
~generationSource=
|
||||||
|
GuesstimatorString(
|
||||||
|
GuesstimatorDist.min(
|
||||||
|
GlobalCatastrophe.guesstimatorString,
|
||||||
|
GuesstimatorDist.logNormal(40., 4.),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
~probabilityType=Cdf,
|
||||||
|
~domain=RightLimited({xPoint: 100., excludingProbabilityMass: 0.3}),
|
||||||
|
~unit=Time({zero: currentDateTime, unit: `years}),
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
| CHANCE_OF_EXISTENCE =>
|
|
||||||
let yearDiff = MomentRe.diff(dateTime, currentDateTime, `days) /. 365.;
|
|
||||||
Prop.Value.Probability((100. -. yearDiff) /. 100.);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -142,14 +178,14 @@ module Interface = {
|
||||||
| _ => PAYOUTS
|
| _ => PAYOUTS
|
||||||
};
|
};
|
||||||
|
|
||||||
let run = (p: Prop.Combo.t) => {
|
let run = (p: array(option(Prop.Value.t))) => {
|
||||||
switch (Prop.Combo.InputValues.toValueArray(p)) {
|
switch (p) {
|
||||||
| [|
|
| [|
|
||||||
Some(SelectSingle(fund)),
|
Some(SelectSingle(fund)),
|
||||||
Some(DateTime(intendedYear)),
|
Some(DateTime(intendedYear)),
|
||||||
Some(DateTime(currentYear)),
|
Some(DateTime(currentYear)),
|
||||||
Some(SelectSingle(output)),
|
Some(SelectSingle(output)),
|
||||||
Some(BinaryConditional(r)),
|
Some(ConditionalArray(conditionals)),
|
||||||
|] =>
|
|] =>
|
||||||
choiceFromString(fund)
|
choiceFromString(fund)
|
||||||
|> E.O.fmap(fund =>
|
|> E.O.fmap(fund =>
|
||||||
|
@ -158,6 +194,7 @@ module Interface = {
|
||||||
intendedYear,
|
intendedYear,
|
||||||
currentYear,
|
currentYear,
|
||||||
outputFromString(output),
|
outputFromString(output),
|
||||||
|
conditionals,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
| _ => None
|
| _ => None
|
||||||
|
@ -166,8 +203,8 @@ module Interface = {
|
||||||
|
|
||||||
let model: Prop.Model.t =
|
let model: Prop.Model.t =
|
||||||
Prop.{
|
Prop.{
|
||||||
name: "EA Funds: Donations & Payouts",
|
name: "CEA Funds: Donations & Payouts",
|
||||||
description: "Calculate the payments and payouts of EA Funds based on existing data.",
|
description: "Calculate the payments and payouts of CEA Funds based on existing data.",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
author: "Ozzie Gooen",
|
author: "Ozzie Gooen",
|
||||||
inputTypes: [|
|
inputTypes: [|
|
||||||
|
@ -220,17 +257,23 @@ module Interface = {
|
||||||
SelectSingle({
|
SelectSingle({
|
||||||
default: Some("Output"),
|
default: Some("Output"),
|
||||||
options: [
|
options: [
|
||||||
{name: "Donations | Exists", id: "donations"},
|
{name: "Donations", id: "donations"},
|
||||||
{name: "Funding | Exists", id: "funding"},
|
{name: "Funding", id: "funding"},
|
||||||
{name: "Exists", id: "exists"},
|
{name: "Closing", id: "exists"},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
(),
|
(),
|
||||||
),
|
),
|
||||||
TypeWithMetadata.make(
|
TypeWithMetadata.make(
|
||||||
~name="Conditional on World Ending",
|
~name="Conditionals",
|
||||||
~id="worldEnd",
|
~id="conditionals",
|
||||||
~type_=BinaryConditional,
|
~type_=
|
||||||
|
Conditionals(
|
||||||
|
Prop.Type.makeConditionals(
|
||||||
|
[||],
|
||||||
|
[|"Global Existential Event"|],
|
||||||
|
),
|
||||||
|
),
|
||||||
(),
|
(),
|
||||||
),
|
),
|
||||||
|],
|
|],
|
||||||
|
|
|
@ -1,17 +1,25 @@
|
||||||
|
let guesstimatorString = GuesstimatorDist.logNormal(20., 3.);
|
||||||
|
|
||||||
module Model = {
|
module Model = {
|
||||||
let make = (dateTime: MomentRe.Moment.t, currentDateTime: MomentRe.Moment.t) => {
|
let make = (currentDateTime: MomentRe.Moment.t) => {
|
||||||
let yearDiff = MomentRe.diff(dateTime, currentDateTime, `days) /. 365.;
|
let genericDistribution =
|
||||||
Prop.Value.Probability(0.001 *. yearDiff);
|
GenericDistribution.make(
|
||||||
|
~generationSource=GuesstimatorString(guesstimatorString),
|
||||||
|
~probabilityType=Cdf,
|
||||||
|
~domain=RightLimited({xPoint: 200., excludingProbabilityMass: 0.3}),
|
||||||
|
~unit=Time({zero: currentDateTime, unit: `years}),
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
Prop.Value.GenericDistribution(genericDistribution);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
module Interface = {
|
module Interface = {
|
||||||
let dayKey = "Day";
|
let dayKey = "Day";
|
||||||
|
|
||||||
let run = (p: Prop.Combo.t) => {
|
let run = (p: array(option(Prop.Value.t))) => {
|
||||||
switch (Prop.Combo.InputValues.toValueArray(p)) {
|
switch (p) {
|
||||||
| [|Some(DateTime(intendedYear)), Some(DateTime(currentYear))|] =>
|
| [|Some(DateTime(currentYear))|] => Some(Model.make(currentYear))
|
||||||
Some(Model.make(intendedYear, currentYear))
|
|
||||||
| _ => None
|
| _ => None
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -22,37 +30,7 @@ module Interface = {
|
||||||
description: "The chances of having at least one catastrophe per year in the future, assuming no other catastrophe until then.",
|
description: "The chances of having at least one catastrophe per year in the future, assuming no other catastrophe until then.",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
author: "Ozzie Gooen",
|
author: "Ozzie Gooen",
|
||||||
inputTypes: [|
|
inputTypes: [|TypeWithMetadata.currentYear|],
|
||||||
TypeWithMetadata.make(
|
|
||||||
~name=dayKey,
|
|
||||||
~type_=
|
|
||||||
DateTime({
|
|
||||||
default:
|
|
||||||
Some(
|
|
||||||
MomentRe.Moment.add(
|
|
||||||
~duration=MomentRe.duration(5., `years),
|
|
||||||
MomentRe.momentNow(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
min:
|
|
||||||
Some(
|
|
||||||
MomentRe.Moment.subtract(
|
|
||||||
~duration=MomentRe.duration(20., `years),
|
|
||||||
MomentRe.momentNow(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
max:
|
|
||||||
Some(
|
|
||||||
MomentRe.Moment.add(
|
|
||||||
~duration=MomentRe.duration(20., `years),
|
|
||||||
MomentRe.momentNow(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
(),
|
|
||||||
),
|
|
||||||
TypeWithMetadata.currentYear,
|
|
||||||
|],
|
|
||||||
outputTypes: [||],
|
outputTypes: [||],
|
||||||
run,
|
run,
|
||||||
};
|
};
|
||||||
|
|
161
src/utility/CdfLibrary.js
Normal file
161
src/utility/CdfLibrary.js
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
const {
|
||||||
|
Cdf,
|
||||||
|
Pdf,
|
||||||
|
ContinuousDistribution,
|
||||||
|
ContinuousDistributionCombination,
|
||||||
|
scoringFunctions,
|
||||||
|
} = require("@foretold/cdf/lib");
|
||||||
|
const _ = require("lodash");
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param xs
|
||||||
|
* @param ys
|
||||||
|
* @returns {{ys: *, xs: *}}
|
||||||
|
*/
|
||||||
|
function cdfToPdf({ xs, ys }) {
|
||||||
|
let cdf = new Cdf(xs, ys);
|
||||||
|
let pdf = cdf.toPdf();
|
||||||
|
return { xs: pdf.xs, ys: pdf.ys };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param xs
|
||||||
|
* @param ys
|
||||||
|
* @returns {{ys: *, xs: *}}
|
||||||
|
*/
|
||||||
|
function pdfToCdf({ xs, ys }) {
|
||||||
|
let cdf = new Pdf(xs, ys);
|
||||||
|
let pdf = cdf.toCdf();
|
||||||
|
return { xs: pdf.xs, ys: pdf.ys };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param sampleCount
|
||||||
|
* @param vars
|
||||||
|
* @returns {{ys: *, xs: *}}
|
||||||
|
*/
|
||||||
|
function mean(sampleCount, vars) {
|
||||||
|
let cdfs = vars.map(r => new Cdf(r.xs, r.ys));
|
||||||
|
let comb = new ContinuousDistributionCombination(cdfs);
|
||||||
|
let newCdf = comb.combineYsWithMean(sampleCount);
|
||||||
|
|
||||||
|
return { xs: newCdf.xs, ys: newCdf.ys };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param sampleCount
|
||||||
|
* @param predictionCdf
|
||||||
|
* @param resolutionCdf
|
||||||
|
*/
|
||||||
|
function scoreNonMarketCdfCdf(sampleCount, predictionCdf, resolutionCdf, resolutionUniformAdditionWeight=0) {
|
||||||
|
let toCdf = (r) => (new Cdf(r.xs, r.ys));
|
||||||
|
let prediction = toCdf(predictionCdf);
|
||||||
|
if (_.isFinite(resolutionUniformAdditionWeight)){
|
||||||
|
prediction = prediction.combineWithUniformOfCdf(
|
||||||
|
{
|
||||||
|
cdf: toCdf(resolutionCdf),
|
||||||
|
uniformWeight: resolutionUniformAdditionWeight,
|
||||||
|
sampleCount
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scoringFunctions.distributionInputDistributionOutputMarketless({
|
||||||
|
predictionCdf: prediction,
|
||||||
|
resultCdf: toCdf(resolutionCdf),
|
||||||
|
sampleCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param sampleCount
|
||||||
|
* @param cdf
|
||||||
|
*/
|
||||||
|
function differentialEntropy(sampleCount, cdf) {
|
||||||
|
let toCdf = (r) => (new Cdf(r.xs, r.ys));
|
||||||
|
|
||||||
|
return scoringFunctions.differentialEntropy({
|
||||||
|
cdf: toCdf(cdf),
|
||||||
|
sampleCount: sampleCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param x
|
||||||
|
* @param xs
|
||||||
|
* @param ys
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function findY(x, { xs, ys }) {
|
||||||
|
let cdf = new Cdf(xs, ys);
|
||||||
|
return cdf.findY(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param y
|
||||||
|
* @param xs
|
||||||
|
* @param ys
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function findX(y, { xs, ys }) {
|
||||||
|
let cdf = new Cdf(xs, ys);
|
||||||
|
return cdf.findX(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param xs
|
||||||
|
* @param ys
|
||||||
|
* @returns {number[]}
|
||||||
|
*/
|
||||||
|
function integral({ xs, ys }) {
|
||||||
|
if (_.includes(ys, NaN)){
|
||||||
|
return NaN;
|
||||||
|
}
|
||||||
|
else if (_.includes(ys, Infinity) && _.includes(ys, -Infinity)){
|
||||||
|
return NaN;
|
||||||
|
}
|
||||||
|
else if (_.includes(ys, Infinity)){
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
else if (_.includes(ys, -Infinity)){
|
||||||
|
return -Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
let integral = 0;
|
||||||
|
for (let i = 1; i < ys.length; i++) {
|
||||||
|
let thisY = ys[i];
|
||||||
|
let lastY = ys[i - 1];
|
||||||
|
let thisX = xs[i];
|
||||||
|
let lastX = xs[i - 1];
|
||||||
|
|
||||||
|
if (
|
||||||
|
_.isFinite(thisY) && _.isFinite(lastY) &&
|
||||||
|
_.isFinite(thisX) && _.isFinite(lastX)
|
||||||
|
) {
|
||||||
|
let sectionInterval = ((thisY + lastY) / 2) * (thisX - lastX);
|
||||||
|
integral = integral + sectionInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return integral;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
cdfToPdf,
|
||||||
|
pdfToCdf,
|
||||||
|
findY,
|
||||||
|
findX,
|
||||||
|
mean,
|
||||||
|
scoreNonMarketCdfCdf,
|
||||||
|
differentialEntropy,
|
||||||
|
integral,
|
||||||
|
};
|
||||||
|
|
49
src/utility/CdfLibrary.re
Normal file
49
src/utility/CdfLibrary.re
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
module JS = {
|
||||||
|
[@bs.deriving abstract]
|
||||||
|
type distJs = {
|
||||||
|
xs: array(float),
|
||||||
|
ys: array(float),
|
||||||
|
};
|
||||||
|
|
||||||
|
let distToJs = (d: DistributionTypes.continuousShape) =>
|
||||||
|
distJs(~xs=d.xs, ~ys=d.ys);
|
||||||
|
|
||||||
|
let jsToDist = (d: distJs): DistributionTypes.continuousShape => {
|
||||||
|
xs: xsGet(d),
|
||||||
|
ys: ysGet(d),
|
||||||
|
};
|
||||||
|
|
||||||
|
let doAsDist = (f, d: DistributionTypes.continuousShape) =>
|
||||||
|
d |> distToJs |> f |> jsToDist;
|
||||||
|
|
||||||
|
[@bs.module "./CdfLibrary.js"]
|
||||||
|
external cdfToPdf: distJs => distJs = "cdfToPdf";
|
||||||
|
|
||||||
|
[@bs.module "./CdfLibrary.js"]
|
||||||
|
external pdfToCdf: distJs => distJs = "pdfToCdf";
|
||||||
|
|
||||||
|
[@bs.module "./CdfLibrary.js"]
|
||||||
|
external findY: (float, distJs) => float = "findY";
|
||||||
|
|
||||||
|
[@bs.module "./CdfLibrary.js"]
|
||||||
|
external findX: (float, distJs) => float = "findX";
|
||||||
|
|
||||||
|
[@bs.module "./CdfLibrary.js"]
|
||||||
|
external integral: distJs => float = "integral";
|
||||||
|
|
||||||
|
[@bs.module "./CdfLibrary.js"]
|
||||||
|
external differentialEntropy: (int, distJs) => distJs =
|
||||||
|
"differentialEntropy";
|
||||||
|
};
|
||||||
|
|
||||||
|
module Distribution = {
|
||||||
|
let toPdf = dist => dist |> JS.doAsDist(JS.cdfToPdf);
|
||||||
|
let toCdf = dist => dist |> JS.doAsDist(JS.pdfToCdf);
|
||||||
|
let findX = (y, dist) => dist |> JS.distToJs |> JS.findX(y);
|
||||||
|
let findY = (x, dist) => dist |> JS.distToJs |> JS.findY(x);
|
||||||
|
let integral = dist => dist |> JS.distToJs |> JS.integral;
|
||||||
|
let differentialEntropy = (maxCalculationLength, dist) =>
|
||||||
|
dist
|
||||||
|
|> JS.doAsDist(JS.differentialEntropy(maxCalculationLength))
|
||||||
|
|> integral;
|
||||||
|
};
|
42
src/utility/Guesstimator.re
Normal file
42
src/utility/Guesstimator.re
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
module Internals = {
|
||||||
|
[@bs.deriving abstract]
|
||||||
|
type discrete = {
|
||||||
|
xs: array(float),
|
||||||
|
ys: array(float),
|
||||||
|
};
|
||||||
|
|
||||||
|
let jsToDistDiscrete = (d: discrete): DistributionTypes.discreteShape => {
|
||||||
|
xs: xsGet(d),
|
||||||
|
ys: ysGet(d),
|
||||||
|
};
|
||||||
|
|
||||||
|
[@bs.deriving abstract]
|
||||||
|
type combined = {
|
||||||
|
continuous: CdfLibrary.JS.distJs,
|
||||||
|
discrete,
|
||||||
|
};
|
||||||
|
|
||||||
|
let toContinous = (r: combined): DistributionTypes.continuousShape =>
|
||||||
|
continuousGet(r) |> CdfLibrary.JS.jsToDist;
|
||||||
|
let toDiscrete = (r: combined): DistributionTypes.discreteShape =>
|
||||||
|
discreteGet(r) |> jsToDistDiscrete;
|
||||||
|
|
||||||
|
[@bs.module "./GuesstimatorLibrary.js"]
|
||||||
|
external toCombinedFormat: (string, int) => combined = "run";
|
||||||
|
|
||||||
|
let toMixedShape = (r: combined): option(DistributionTypes.mixedShape) => {
|
||||||
|
let assumptions: MixedShapeBuilder.assumptions = {
|
||||||
|
continuous: ADDS_TO_1,
|
||||||
|
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
|
discreteProbabilityMass: None,
|
||||||
|
};
|
||||||
|
MixedShapeBuilder.build(
|
||||||
|
~continuous=toContinous(r),
|
||||||
|
~discrete=toDiscrete(r),
|
||||||
|
~assumptions,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let stringToMixedShape = (~string, ~sampleCount=1000, ()) =>
|
||||||
|
Internals.toCombinedFormat(string, sampleCount) |> Internals.toMixedShape;
|
88
src/utility/GuesstimatorLibrary.js
Normal file
88
src/utility/GuesstimatorLibrary.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { Guesstimator } from '@foretold/guesstimator';
|
||||||
|
import { Samples } from '@foretold/cdf';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} minValue
|
||||||
|
* @param {number} maxValue
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
const minMaxRatio = (minValue, maxValue) => {
|
||||||
|
if (minValue === 0 || maxValue === 0) {
|
||||||
|
return 'SMALL';
|
||||||
|
}
|
||||||
|
const ratio = maxValue / minValue;
|
||||||
|
if (ratio < 100000) {
|
||||||
|
return 'SMALL';
|
||||||
|
} else if (ratio < 10000000) {
|
||||||
|
return 'MEDIUM';
|
||||||
|
} else {
|
||||||
|
return 'LARGE';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param samples
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
const ratioSize = samples => {
|
||||||
|
samples.sort();
|
||||||
|
const minValue = samples.getPercentile(2);
|
||||||
|
const maxValue = samples.getPercentile(98);
|
||||||
|
return minMaxRatio(minValue, maxValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const toPdf = (values, sampleCount, min, max) => {
|
||||||
|
let duplicateSamples = _(values).groupBy().pickBy(x => x.length > 1).keys().value();
|
||||||
|
let totalLength = _.size(values);
|
||||||
|
let frequencies = duplicateSamples.map(s => ({value: parseFloat(s), percentage: _(values).filter(x => x ==s).size()/totalLength}));
|
||||||
|
let continuousSamples = _.difference(values, frequencies.map(f => f.value));
|
||||||
|
|
||||||
|
let discrete = {xs: frequencies.map(f => f.value), ys: frequencies.map(f => f.percentage)};
|
||||||
|
let continuous = {ys: [], xs: []};
|
||||||
|
if (continuousSamples.length > 1){
|
||||||
|
const samples = new Samples(continuousSamples);
|
||||||
|
|
||||||
|
const ratioSize$ = ratioSize(samples);
|
||||||
|
const width = ratioSize$ === 'SMALL' ? 20 : 1;
|
||||||
|
|
||||||
|
const cdf = samples.toCdf({ size: sampleCount, width, min, max });
|
||||||
|
continuous = cdf;
|
||||||
|
}
|
||||||
|
return {continuous, discrete};
|
||||||
|
};
|
||||||
|
|
||||||
|
let run = (text, sampleCount, inputs=[], min=false, max=false) => {
|
||||||
|
let [_error, item] = Guesstimator.parse({ text: "=" + text });
|
||||||
|
const { parsedInput } = item;
|
||||||
|
const { guesstimateType } = parsedInput;
|
||||||
|
|
||||||
|
const guesstimator = new Guesstimator({ parsedInput });
|
||||||
|
const value = guesstimator.sample(
|
||||||
|
sampleCount,
|
||||||
|
inputs,
|
||||||
|
);
|
||||||
|
const samplerType = guesstimator.samplerType();
|
||||||
|
|
||||||
|
const values = _.filter(value.values, _.isFinite);
|
||||||
|
|
||||||
|
let update;
|
||||||
|
let blankResponse = {
|
||||||
|
continuous: {ys: [], xs: []},
|
||||||
|
discrete: {ys: [], xs: []}
|
||||||
|
};
|
||||||
|
if (values.length === 0) {
|
||||||
|
update = blankResponse;
|
||||||
|
} else if (values.length === 1) {
|
||||||
|
update = blankResponse;
|
||||||
|
} else {
|
||||||
|
update = toPdf(values, sampleCount, min, max);
|
||||||
|
}
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
run,
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user