Merge pull request #1 from foretold-app/improvements/1080
Improvements/1080
This commit is contained in:
commit
00301ec7d3
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,3 +13,4 @@ yarn-error.log
|
||||||
*.bs.js
|
*.bs.js
|
||||||
# Local Netlify folder
|
# Local Netlify folder
|
||||||
.netlify
|
.netlify
|
||||||
|
.idea
|
51
package.json
51
package.json
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "probtest",
|
"name": "estiband",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"homepage": "https://foretold-app.github.io/estiband/",
|
"homepage": "https://foretold-app.github.io/estiband/",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -26,38 +26,35 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@foretold/cdf": "^1.0.15",
|
"@foretold/cdf": "1.0.15",
|
||||||
"@foretold/components": "^0.0.3",
|
"@foretold/components": "0.0.3",
|
||||||
"@foretold/guesstimator": "^1.0.10",
|
"@foretold/guesstimator": "1.0.10",
|
||||||
"@glennsl/bs-jest": "^0.4.9",
|
|
||||||
"antd": "3.17.0",
|
"antd": "3.17.0",
|
||||||
"autoprefixer": "^9.7.4",
|
"autoprefixer": "9.7.4",
|
||||||
"babel-jest": "^25.1.0",
|
"babel-jest": "25.1.0",
|
||||||
"bs-ant-design-alt": "2.0.0-alpha.31",
|
"bs-ant-design-alt": "2.0.0-alpha.31",
|
||||||
"bs-css": "^11.0.0",
|
"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",
|
"d3": "5.15.0",
|
||||||
|
"lenses-ppx": "5.1.0",
|
||||||
"jest": "^25.1.0",
|
"jest": "^25.1.0",
|
||||||
"lenses-ppx": "4.0.0",
|
"less": "3.10.3",
|
||||||
"less": "^3.10.3",
|
"lodash": "4.17.15",
|
||||||
"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",
|
"postcss-cli": "7.1.0",
|
||||||
"postcss-cli": "^7.1.0",
|
"rationale": "0.2.0",
|
||||||
"rationale": "^0.2.0",
|
"react": "16.12.0",
|
||||||
"react": "^16.8.1",
|
"react-dom": "16.12.0",
|
||||||
"react-dom": "^16.8.1",
|
|
||||||
"reason-react": ">=0.7.0",
|
"reason-react": ">=0.7.0",
|
||||||
"reschema": "^1.3.0"
|
"reschema": "1.3.0",
|
||||||
},
|
"bs-platform": "7.0.1",
|
||||||
"devDependencies": {
|
"bsb-js": "1.1.7",
|
||||||
"bs-platform": "5.2.1",
|
"gh-pages": "2.2.0",
|
||||||
"bsb-js": "^1.1.7",
|
"moduleserve": "0.9.1",
|
||||||
"gh-pages": "^2.2.0",
|
"tailwindcss": "1.2.0"
|
||||||
"moduleserve": "^0.9.0",
|
|
||||||
"tailwindcss": "^1.2.0"
|
|
||||||
},
|
},
|
||||||
"alias": {
|
"alias": {
|
||||||
"react": "./node_modules/react",
|
"react": "./node_modules/react",
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
open ForetoldComponents.Base;
|
|
||||||
|
|
||||||
let data: DistributionTypes.xyShape = {
|
let data: DistributionTypes.xyShape = {
|
||||||
xs: [|1., 10., 10., 200., 250., 292., 330.|],
|
xs: [|1., 10., 10., 200., 250., 292., 330.|],
|
||||||
ys: [|0.0, 0.0, 0.1, 0.3, 0.5, 0.2, 0.1|],
|
ys: [|0.0, 0.0, 0.1, 0.3, 0.5, 0.2, 0.1|],
|
||||||
|
@ -13,7 +11,7 @@ let mixedDist =
|
||||||
),
|
),
|
||||||
~probabilityType=Pdf,
|
~probabilityType=Pdf,
|
||||||
~domain=Complete,
|
~domain=Complete,
|
||||||
~unit=Unspecified,
|
~unit=UnspecifiedDistribution,
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
|> GenericDistribution.renderIfNeeded(~sampleCount=1000);
|
|> GenericDistribution.renderIfNeeded(~sampleCount=1000);
|
||||||
|
@ -23,7 +21,7 @@ let timeDist =
|
||||||
~generationSource=GuesstimatorString("mm(3, normal(5,1), [.5,.5])"),
|
~generationSource=GuesstimatorString("mm(3, normal(5,1), [.5,.5])"),
|
||||||
~probabilityType=Pdf,
|
~probabilityType=Pdf,
|
||||||
~domain=Complete,
|
~domain=Complete,
|
||||||
~unit=Time({zero: MomentRe.momentNow(), unit: `years}),
|
~unit=TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
|> GenericDistribution.renderIfNeeded(~sampleCount=1000);
|
|> GenericDistribution.renderIfNeeded(~sampleCount=1000);
|
||||||
|
@ -33,7 +31,7 @@ let domainLimitedDist =
|
||||||
~generationSource=GuesstimatorString("mm(3, normal(5,1), [.5,.5])"),
|
~generationSource=GuesstimatorString("mm(3, normal(5,1), [.5,.5])"),
|
||||||
~probabilityType=Pdf,
|
~probabilityType=Pdf,
|
||||||
~domain=RightLimited({xPoint: 6.0, excludingProbabilityMass: 0.3}),
|
~domain=RightLimited({xPoint: 6.0, excludingProbabilityMass: 0.3}),
|
||||||
~unit=Unspecified,
|
~unit=UnspecifiedDistribution,
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
|> GenericDistribution.renderIfNeeded(~sampleCount=1000);
|
|> GenericDistribution.renderIfNeeded(~sampleCount=1000);
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
// 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) =*/
|
|
|
@ -1,50 +0,0 @@
|
||||||
// open EAFunds_Data;
|
|
||||||
// let handleChange = (handleChange, event) =>
|
|
||||||
// handleChange(ReactEvent.Form.target(event)##value);
|
|
||||||
// [@react.component]
|
|
||||||
// let make = () => {
|
|
||||||
// let (year, setYear) = React.useState(() => 2021.);
|
|
||||||
// <>
|
|
||||||
// <h1> {"EA Funds Forecasting Model 0.1" |> ReasonReact.string} </h1>
|
|
||||||
// <input
|
|
||||||
// type_="number"
|
|
||||||
// value={year |> Js.Float.toString}
|
|
||||||
// onChange={handleChange(r =>
|
|
||||||
// switch (Js.Float.fromString(r)) {
|
|
||||||
// | r when r >= 2020.0 && r <= 2050.0 => setYear(_ => r)
|
|
||||||
// | _ => ()
|
|
||||||
// }
|
|
||||||
// )}
|
|
||||||
// />
|
|
||||||
// <table className="table-auto">
|
|
||||||
// <thead>
|
|
||||||
// <tr>
|
|
||||||
// <th className="px-4 py-2"> {"Fund Name" |> ReasonReact.string} </th>
|
|
||||||
// <th className="px-4 py-2"> {"Donations" |> ReasonReact.string} </th>
|
|
||||||
// <th className="px-4 py-2"> {"Payouts" |> ReasonReact.string} </th>
|
|
||||||
// </tr>
|
|
||||||
// </thead>
|
|
||||||
// <tbody>
|
|
||||||
// {funds
|
|
||||||
// |> Belt.Array.map(_, r =>
|
|
||||||
// <tr>
|
|
||||||
// <th className="px-4 py-2 border ">
|
|
||||||
// {r.name |> ReasonReact.string}
|
|
||||||
// </th>
|
|
||||||
// <th className="px-4 py-2 border font-normal">
|
|
||||||
// {EAFunds_Model.go(r.group, year, DONATIONS)
|
|
||||||
// |> Model.InputTypes.to_string
|
|
||||||
// |> ReasonReact.string}
|
|
||||||
// </th>
|
|
||||||
// <th className="px-4 py-2 border font-normal">
|
|
||||||
// {EAFunds_Model.go(r.group, year, PAYOUTS)
|
|
||||||
// |> Model.InputTypes.to_string
|
|
||||||
// |> ReasonReact.string}
|
|
||||||
// </th>
|
|
||||||
// </tr>
|
|
||||||
// )
|
|
||||||
// |> ReasonReact.array}
|
|
||||||
// </tbody>
|
|
||||||
// </table>
|
|
||||||
// </>;
|
|
||||||
/* }*/
|
|
|
@ -1,56 +0,0 @@
|
||||||
type senate =
|
|
||||||
| DEMOCRAT_VICTORY;
|
|
||||||
|
|
||||||
type house =
|
|
||||||
| DEMOCRAT_VICTORY;
|
|
||||||
|
|
||||||
type statement =
|
|
||||||
| Senate(senate)
|
|
||||||
| House(house);
|
|
||||||
|
|
||||||
type outcome =
|
|
||||||
| Bool(bool)
|
|
||||||
| Option(string);
|
|
||||||
|
|
||||||
type fullStatement = {
|
|
||||||
statement,
|
|
||||||
outcome,
|
|
||||||
};
|
|
||||||
|
|
||||||
type jointStatement = {
|
|
||||||
statements: array(fullStatement),
|
|
||||||
probability: option(float),
|
|
||||||
};
|
|
||||||
|
|
||||||
let foo = (joint: jointStatement) => {
|
|
||||||
[|
|
|
||||||
{
|
|
||||||
statements: [|
|
|
||||||
{statement: Senate(DEMOCRAT_VICTORY), outcome: Bool(true)},
|
|
||||||
{statement: House(DEMOCRAT_VICTORY), outcome: Bool(true)},
|
|
||||||
|],
|
|
||||||
probability: Some(0.2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
statements: [|
|
|
||||||
{statement: Senate(DEMOCRAT_VICTORY), outcome: Bool(true)},
|
|
||||||
{statement: House(DEMOCRAT_VICTORY), outcome: Bool(false)},
|
|
||||||
|],
|
|
||||||
probability: Some(0.2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
statements: [|
|
|
||||||
{statement: Senate(DEMOCRAT_VICTORY), outcome: Bool(false)},
|
|
||||||
{statement: House(DEMOCRAT_VICTORY), outcome: Bool(true)},
|
|
||||||
|],
|
|
||||||
probability: Some(0.5),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
statements: [|
|
|
||||||
{statement: Senate(DEMOCRAT_VICTORY), outcome: Bool(false)},
|
|
||||||
{statement: House(DEMOCRAT_VICTORY), outcome: Bool(false)},
|
|
||||||
|],
|
|
||||||
probability: Some(0.1),
|
|
||||||
},
|
|
||||||
|];
|
|
||||||
};
|
|
|
@ -1,60 +0,0 @@
|
||||||
module Math = {
|
|
||||||
let normal = (mean: float, std: float) =>
|
|
||||||
Js.Float.(
|
|
||||||
{
|
|
||||||
let nMean = toString(mean);
|
|
||||||
let nStd = toString(std);
|
|
||||||
{j|normal($(nMean), $(nStd))|j};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let divide = (str1: string, str2: string) => {j|$(str1)/$(str2)|j};
|
|
||||||
};
|
|
||||||
|
|
||||||
type param =
|
|
||||||
| SHARE_PRICE
|
|
||||||
| SHARES_OUTSTANDING
|
|
||||||
| MARKET_CAP;
|
|
||||||
|
|
||||||
type company = {
|
|
||||||
name: string,
|
|
||||||
currentPrice: option(float),
|
|
||||||
marketCap: option(float),
|
|
||||||
};
|
|
||||||
|
|
||||||
type otherSettings = {currentYear: int};
|
|
||||||
|
|
||||||
let sharesOutstanding = (price, marketCap) =>
|
|
||||||
switch (price, marketCap) {
|
|
||||||
| (Some(price), Some(marketCap)) =>
|
|
||||||
Some(GuesstimatorDist.divide(marketCap, price))
|
|
||||||
| _ => None
|
|
||||||
};
|
|
||||||
|
|
||||||
let rec run =
|
|
||||||
(
|
|
||||||
company: company,
|
|
||||||
year: int,
|
|
||||||
param: param,
|
|
||||||
otherSettings: otherSettings,
|
|
||||||
) => {
|
|
||||||
switch (param, year, company.currentPrice, company.marketCap) {
|
|
||||||
| (SHARE_PRICE, year, Some(price), _) when year > 2019 && year < 2030 =>
|
|
||||||
let diffYears = year - otherSettings.currentYear;
|
|
||||||
let diffPerYear = 0.1;
|
|
||||||
Some(
|
|
||||||
GuesstimatorDist.normal(price, float_of_int(diffYears) *. diffPerYear),
|
|
||||||
);
|
|
||||||
| (MARKET_CAP, year, _, Some(price)) when year > 2019 && year < 2030 =>
|
|
||||||
let diffYears = year - otherSettings.currentYear;
|
|
||||||
let diffPerYear = 0.1;
|
|
||||||
Some(
|
|
||||||
GuesstimatorDist.normal(price, float_of_int(diffYears) *. diffPerYear),
|
|
||||||
);
|
|
||||||
| (SHARES_OUTSTANDING, year, _, _) when year > 2019 && year < 2030 =>
|
|
||||||
let price = run(company, year, SHARE_PRICE, otherSettings);
|
|
||||||
let marketCap = run(company, year, MARKET_CAP, otherSettings);
|
|
||||||
sharesOutstanding(price, marketCap);
|
|
||||||
| _ => None
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,38 +0,0 @@
|
||||||
// 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*/
|
|
|
@ -11,15 +11,17 @@ type primaryDistribution = {
|
||||||
let make =
|
let make =
|
||||||
(
|
(
|
||||||
~height=?,
|
~height=?,
|
||||||
~verticalLine=?,
|
|
||||||
~showVerticalLine=?,
|
|
||||||
~marginBottom=?,
|
~marginBottom=?,
|
||||||
~marginTop=?,
|
~marginTop=?,
|
||||||
~showDistributionLines=?,
|
|
||||||
~maxX=?,
|
~maxX=?,
|
||||||
~minX=?,
|
~minX=?,
|
||||||
~onHover=(f: float) => (),
|
~onHover=(f: float) => (),
|
||||||
~primaryDistribution=?,
|
~primaryDistribution=?,
|
||||||
|
~scale=?,
|
||||||
|
~showDistributionLines=?,
|
||||||
|
~showVerticalLine=?,
|
||||||
|
~timeScale=?,
|
||||||
|
~verticalLine=?,
|
||||||
~children=[||],
|
~children=[||],
|
||||||
) =>
|
) =>
|
||||||
ReasonReact.wrapJsForReason(
|
ReasonReact.wrapJsForReason(
|
||||||
|
@ -27,15 +29,17 @@ let make =
|
||||||
~props=
|
~props=
|
||||||
makeProps(
|
makeProps(
|
||||||
~height?,
|
~height?,
|
||||||
~verticalLine?,
|
|
||||||
~marginBottom?,
|
~marginBottom?,
|
||||||
~marginTop?,
|
~marginTop?,
|
||||||
~onHover,
|
|
||||||
~showVerticalLine?,
|
|
||||||
~showDistributionLines?,
|
|
||||||
~maxX?,
|
~maxX?,
|
||||||
~minX?,
|
~minX?,
|
||||||
|
~onHover,
|
||||||
~primaryDistribution?,
|
~primaryDistribution?,
|
||||||
|
~scale?,
|
||||||
|
~showDistributionLines?,
|
||||||
|
~showVerticalLine?,
|
||||||
|
~timeScale?,
|
||||||
|
~verticalLine?,
|
||||||
(),
|
(),
|
||||||
),
|
),
|
||||||
children,
|
children,
|
||||||
|
|
|
@ -18,24 +18,28 @@ module Styles = {
|
||||||
[@react.component]
|
[@react.component]
|
||||||
let make =
|
let make =
|
||||||
(
|
(
|
||||||
~data,
|
|
||||||
~minX=?,
|
|
||||||
~maxX=?,
|
|
||||||
~height=200,
|
|
||||||
~color=`hex("111"),
|
~color=`hex("111"),
|
||||||
|
~data,
|
||||||
|
~height=200,
|
||||||
|
~maxX=?,
|
||||||
|
~minX=?,
|
||||||
~onHover: float => unit,
|
~onHover: float => unit,
|
||||||
|
~scale=?,
|
||||||
|
~timeScale=?,
|
||||||
) => {
|
) => {
|
||||||
<div className={Styles.graph(color)}>
|
<div className={Styles.graph(color)}>
|
||||||
<CdfChart__Base
|
<CdfChart__Base
|
||||||
height
|
|
||||||
?minX
|
|
||||||
?maxX
|
?maxX
|
||||||
|
?minX
|
||||||
|
?scale
|
||||||
|
?timeScale
|
||||||
|
height
|
||||||
marginBottom=50
|
marginBottom=50
|
||||||
marginTop=0
|
marginTop=0
|
||||||
onHover
|
onHover
|
||||||
showVerticalLine=false
|
|
||||||
showDistributionLines=false
|
|
||||||
primaryDistribution={data |> Shape.XYShape.toJs}
|
primaryDistribution={data |> Shape.XYShape.toJs}
|
||||||
|
showDistributionLines=false
|
||||||
|
showVerticalLine=false
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
|
@ -1,17 +1,22 @@
|
||||||
module Continuous = {
|
module Continuous = {
|
||||||
[@react.component]
|
[@react.component]
|
||||||
let make = (~data) => {
|
let make = (~data, ~
|
||||||
|
|
||||||
|
) => {
|
||||||
let (x, setX) = React.useState(() => 0.);
|
let (x, setX) = React.useState(() => 0.);
|
||||||
|
let timeScale = unit |> DistributionTypes.DistributionUnit.toJson;
|
||||||
let chart =
|
let chart =
|
||||||
React.useMemo1(
|
React.useMemo1(
|
||||||
() =>
|
() =>
|
||||||
<CdfChart__Plain
|
<CdfChart__Plain
|
||||||
data
|
data
|
||||||
|
timeScale
|
||||||
color={`hex("333")}
|
color={`hex("333")}
|
||||||
onHover={r => setX(_ => r)}
|
onHover={r => setX(_ => r)}
|
||||||
/>,
|
/>,
|
||||||
[|data|],
|
[|data|],
|
||||||
);
|
);
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
chart
|
chart
|
||||||
<table className="table-auto">
|
<table className="table-auto">
|
||||||
|
@ -100,6 +105,7 @@ module Mixed = {
|
||||||
let make = (~dist) => {
|
let make = (~dist) => {
|
||||||
switch ((dist: option(DistributionTypes.genericDistribution))) {
|
switch ((dist: option(DistributionTypes.genericDistribution))) {
|
||||||
| Some({
|
| Some({
|
||||||
|
unit,
|
||||||
generationSource:
|
generationSource:
|
||||||
Shape(
|
Shape(
|
||||||
Mixed({
|
Mixed({
|
||||||
|
@ -110,8 +116,9 @@ let make = (~dist) => {
|
||||||
),
|
),
|
||||||
}) =>
|
}) =>
|
||||||
<div>
|
<div>
|
||||||
<Continuous data=n />
|
<Continuous data={n |> Shape.Continuous.toPdf} unit />
|
||||||
<Continuous
|
<Continuous
|
||||||
|
unit
|
||||||
data={
|
data={
|
||||||
n
|
n
|
||||||
|> Shape.XYShape.Range.integrateWithTriangles
|
|> Shape.XYShape.Range.integrateWithTriangles
|
||||||
|
@ -133,6 +140,7 @@ let make = (~dist) => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Continuous
|
<Continuous
|
||||||
|
unit
|
||||||
data={
|
data={
|
||||||
n
|
n
|
||||||
|> Shape.XYShape.Range.integrateWithTriangles
|
|> Shape.XYShape.Range.integrateWithTriangles
|
||||||
|
|
407
src/components/charts/cdfChartD3.js
Normal file
407
src/components/charts/cdfChartD3.js
Normal file
|
@ -0,0 +1,407 @@
|
||||||
|
const _ = require('lodash');
|
||||||
|
const d3 = require('d3');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
export class CdfChartD3 {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.attrs = {
|
||||||
|
svgWidth: 400,
|
||||||
|
svgHeight: 400,
|
||||||
|
|
||||||
|
marginTop: 5,
|
||||||
|
marginBottom: 5,
|
||||||
|
marginRight: 50,
|
||||||
|
marginLeft: 5,
|
||||||
|
|
||||||
|
container: null,
|
||||||
|
minX: false,
|
||||||
|
maxX: false,
|
||||||
|
scale: 'linear',
|
||||||
|
timeScale: null,
|
||||||
|
showDistributionLines: true,
|
||||||
|
areaColors: ['#E1E5EC', '#E1E5EC'],
|
||||||
|
logBase: 10,
|
||||||
|
verticalLine: 110,
|
||||||
|
showVerticalLine: true,
|
||||||
|
data: null,
|
||||||
|
onHover: (e) => {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.hoverLine = null;
|
||||||
|
this.xScale = null;
|
||||||
|
this.dataPoints = null;
|
||||||
|
this.mouseover = this.mouseover.bind(this);
|
||||||
|
this.mouseout = this.mouseout.bind(this);
|
||||||
|
this.formatDates = this.formatDates.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
svgWidth(svgWidth) {
|
||||||
|
this.attrs.svgWidth = svgWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
svgHeight(height) {
|
||||||
|
this.attrs.svgHeight = height;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxX(maxX) {
|
||||||
|
this.attrs.maxX = maxX;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
minX(minX) {
|
||||||
|
this.attrs.minX = minX;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
scale(scale) {
|
||||||
|
this.attrs.scale = scale;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeScale(timeScale) {
|
||||||
|
this.attrs.timeScale = timeScale;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
onHover(onHover) {
|
||||||
|
this.attrs.onHover = onHover;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
marginBottom(marginBottom) {
|
||||||
|
this.attrs.marginBottom = marginBottom;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
marginLeft(marginLeft) {
|
||||||
|
this.attrs.marginLeft = marginLeft;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
marginRight(marginRight) {
|
||||||
|
this.attrs.marginRight = marginRight;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
marginTop(marginTop) {
|
||||||
|
this.attrs.marginTop = marginTop;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
showDistributionLines(showDistributionLines) {
|
||||||
|
this.attrs.showDistributionLines = showDistributionLines;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
verticalLine(verticalLine) {
|
||||||
|
this.attrs.verticalLine = verticalLine;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
showVerticalLine(showVerticalLine) {
|
||||||
|
this.attrs.showVerticalLine = showVerticalLine;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
container(container) {
|
||||||
|
this.attrs.container = container;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
data(data) {
|
||||||
|
this.attrs.data = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param key
|
||||||
|
* @returns {[]}
|
||||||
|
*/
|
||||||
|
getDataPoints(key) {
|
||||||
|
const dt = [];
|
||||||
|
const data = this.attrs.data[key];
|
||||||
|
const len = data.xs.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
dt.push({ x: data.xs[i], y: data.ys[i] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const attrs = this.attrs;
|
||||||
|
const container = d3.select(attrs.container);
|
||||||
|
if (container.node() === null) {
|
||||||
|
console.error('Container for D3 is not defined.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the width from the DOM element.
|
||||||
|
const containerRect = container.node().getBoundingClientRect();
|
||||||
|
if (containerRect.width > 0) {
|
||||||
|
attrs.svgWidth = containerRect.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculated properties.
|
||||||
|
const calc = {};
|
||||||
|
calc.chartLeftMargin = attrs.marginLeft;
|
||||||
|
calc.chartTopMargin = attrs.marginTop;
|
||||||
|
calc.chartWidth = attrs.svgWidth - attrs.marginRight - attrs.marginLeft;
|
||||||
|
calc.chartHeight = attrs.svgHeight - attrs.marginBottom - attrs.marginTop;
|
||||||
|
|
||||||
|
const areaColorRange = d3.scaleOrdinal().range(attrs.areaColors);
|
||||||
|
this.dataPoints = [this.getDataPoints('primary')];
|
||||||
|
|
||||||
|
// Scales.
|
||||||
|
const xMin = d3.min(attrs.data.primary.xs);
|
||||||
|
const xMax = d3.max(attrs.data.primary.xs);
|
||||||
|
|
||||||
|
if (attrs.scale === 'linear') {
|
||||||
|
this.xScale = d3.scaleLinear()
|
||||||
|
.domain([attrs.minX || xMin, attrs.maxX || xMax])
|
||||||
|
.range([0, calc.chartWidth]);
|
||||||
|
} else {
|
||||||
|
this.xScale = d3.scaleLog()
|
||||||
|
.base(attrs.logBase)
|
||||||
|
.domain([attrs.minX, attrs.maxX])
|
||||||
|
.range([0, calc.chartWidth]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const yMin = d3.min(attrs.data.primary.ys);
|
||||||
|
const yMax = d3.max(attrs.data.primary.ys);
|
||||||
|
|
||||||
|
this.yScale = d3.scaleLinear()
|
||||||
|
.domain([yMin, yMax])
|
||||||
|
.range([calc.chartHeight, 0]);
|
||||||
|
|
||||||
|
// Axis generator.
|
||||||
|
if (!!this.attrs.timeScale) {
|
||||||
|
const zero = _.get(this.attrs.timeScale, 'zero', moment());
|
||||||
|
const unit = _.get(this.attrs.timeScale, 'unit', 'years');
|
||||||
|
const diff = Math.abs(xMax - xMin);
|
||||||
|
const left = zero.clone().add(xMin, unit);
|
||||||
|
const right = left.clone().add(diff, unit);
|
||||||
|
|
||||||
|
const xScaleTime = d3.scaleTime()
|
||||||
|
.domain([left.toDate(), right.toDate()])
|
||||||
|
.nice()
|
||||||
|
.range([0, calc.chartWidth]);
|
||||||
|
|
||||||
|
this.xAxis = d3.axisBottom()
|
||||||
|
.scale(xScaleTime)
|
||||||
|
.ticks(this.getTimeTicksByStr(unit))
|
||||||
|
.tickFormat(this.formatDates);
|
||||||
|
} else {
|
||||||
|
this.xAxis = d3.axisBottom(this.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 {
|
||||||
|
const prefix = d3.formatPrefix(".0", d);
|
||||||
|
return prefix(d).replace("G", "B");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Objects.
|
||||||
|
const line = d3.line()
|
||||||
|
.x(d => this.xScale(d.x))
|
||||||
|
.y(d => this.yScale(d.y));
|
||||||
|
|
||||||
|
const area = d3.area()
|
||||||
|
.x(d => this.xScale(d.x))
|
||||||
|
.y1(d => this.yScale(d.y))
|
||||||
|
.y0(calc.chartHeight);
|
||||||
|
|
||||||
|
// Add svg.
|
||||||
|
const svg = container
|
||||||
|
.createObject({ tag: 'svg', selector: 'svg-chart-container' })
|
||||||
|
.attr('width', "100%")
|
||||||
|
.attr('height', attrs.svgHeight)
|
||||||
|
.attr('pointer-events', 'none');
|
||||||
|
|
||||||
|
// Add container g element.
|
||||||
|
this.chart = svg
|
||||||
|
.createObject({ tag: 'g', selector: 'chart' })
|
||||||
|
.attr(
|
||||||
|
'transform',
|
||||||
|
'translate(' + calc.chartLeftMargin + ',' + calc.chartTopMargin + ')',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add axis.
|
||||||
|
this.chart.createObject({ tag: 'g', selector: 'axis' })
|
||||||
|
.attr('transform', 'translate(' + 0 + ',' + calc.chartHeight + ')')
|
||||||
|
.call(this.xAxis);
|
||||||
|
|
||||||
|
// Draw area.
|
||||||
|
this.chart
|
||||||
|
.createObjectsWithData({
|
||||||
|
tag: 'path',
|
||||||
|
selector: 'area-path',
|
||||||
|
data: this.dataPoints,
|
||||||
|
})
|
||||||
|
.attr('d', area)
|
||||||
|
.attr('fill', (d, i) => areaColorRange(i))
|
||||||
|
.attr('opacity', (d, i) => i === 0 ? 0.7 : 0.5);
|
||||||
|
|
||||||
|
// Draw line.
|
||||||
|
if (attrs.showDistributionLines) {
|
||||||
|
this.chart
|
||||||
|
.createObjectsWithData({
|
||||||
|
tag: 'path',
|
||||||
|
selector: 'line-path',
|
||||||
|
data: this.dataPoints,
|
||||||
|
})
|
||||||
|
.attr('d', line)
|
||||||
|
.attr('id', (d, i) => 'line-' + (i + 1))
|
||||||
|
.attr('opacity', (d, i) => i === 0 ? 0.7 : 1)
|
||||||
|
.attr('fill', 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrs.showVerticalLine) {
|
||||||
|
this.chart
|
||||||
|
.createObject({ tag: 'line', selector: 'v-line' })
|
||||||
|
.attr('x1', this.xScale(attrs.verticalLine))
|
||||||
|
.attr('x2', this.xScale(attrs.verticalLine))
|
||||||
|
.attr('y1', 0)
|
||||||
|
.attr('y2', calc.chartHeight)
|
||||||
|
.attr('stroke-width', 1.5)
|
||||||
|
.attr('stroke-dasharray', '6 6')
|
||||||
|
.attr('stroke', 'steelblue');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hoverLine = this.chart
|
||||||
|
.createObject({ 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.
|
||||||
|
const thi$ = this;
|
||||||
|
this.chart
|
||||||
|
.createObject({ tag: 'rect', selector: 'mouse-rect' })
|
||||||
|
.attr('width', calc.chartWidth)
|
||||||
|
.attr('height', calc.chartHeight)
|
||||||
|
.attr('fill', 'transparent')
|
||||||
|
.attr('pointer-events', 'all')
|
||||||
|
.on('mouseover', function () {
|
||||||
|
thi$.mouseover(this);
|
||||||
|
})
|
||||||
|
.on('mousemove', function () {
|
||||||
|
thi$.mouseover(this);
|
||||||
|
})
|
||||||
|
.on('mouseout', this.mouseout);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseover(constructor) {
|
||||||
|
const mouse = d3.mouse(constructor);
|
||||||
|
this.hoverLine.attr('opacity', 1)
|
||||||
|
.attr('x1', mouse[0])
|
||||||
|
.attr('x2', mouse[0]);
|
||||||
|
|
||||||
|
const xValue = this.xScale.invert(mouse[0]).toFixed(2);
|
||||||
|
|
||||||
|
const range = [
|
||||||
|
this.xScale(this.dataPoints[this.dataPoints.length - 1][0].x),
|
||||||
|
this.xScale(
|
||||||
|
this.dataPoints
|
||||||
|
[this.dataPoints.length - 1]
|
||||||
|
[this.dataPoints[this.dataPoints.length - 1].length - 1].x,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (mouse[0] > range[0] && mouse[0] < range[1]) {
|
||||||
|
this.attrs.onHover(xValue);
|
||||||
|
} else {
|
||||||
|
this.attrs.onHover(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseout() {
|
||||||
|
this.hoverLine.attr('opacity', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDates(ts) {
|
||||||
|
return moment(ts).format("MMMM Do YYYY");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} unit
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
getTimeTicksByStr(unit) {
|
||||||
|
switch (unit) {
|
||||||
|
case "months":
|
||||||
|
return d3.timeMonth.every(1);
|
||||||
|
case "quarters":
|
||||||
|
return d3.timeMonth.every(3);
|
||||||
|
case "hours":
|
||||||
|
return d3.timeHour.every(1);
|
||||||
|
case "days":
|
||||||
|
return d3.timeDay.every(1);
|
||||||
|
case "seconds":
|
||||||
|
return d3.timeSecond.every(1);
|
||||||
|
case "years":
|
||||||
|
return d3.timeYear.every(1);
|
||||||
|
case "minutes":
|
||||||
|
return d3.timeMinute.every(1);
|
||||||
|
case "weeks":
|
||||||
|
return d3.timeWeek.every(1);
|
||||||
|
case "milliseconds":
|
||||||
|
return d3.timeMillisecond.every(1);
|
||||||
|
default:
|
||||||
|
return d3.timeYear.every(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @docs: https://github.com/d3/d3-selection
|
||||||
|
* @param params
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
d3.selection.prototype.createObject = function createObject(params) {
|
||||||
|
const selector = params.selector;
|
||||||
|
const tag = params.tag;
|
||||||
|
return this.insert(tag).attr('class', selector);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @example:
|
||||||
|
* This call example
|
||||||
|
* createObjectsByData({
|
||||||
|
* tag: 'path',
|
||||||
|
* selector: 'line-path',
|
||||||
|
* data: this.dataPoints,
|
||||||
|
* })
|
||||||
|
* will create a new tag "<path class="line-path">1,2,3</path>".
|
||||||
|
* @docs: https://github.com/d3/d3-selection
|
||||||
|
* @param params
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
d3.selection.prototype.createObjectsWithData = function createObjectsWithData(params) {
|
||||||
|
const selector = params.selector;
|
||||||
|
const tag = params.tag;
|
||||||
|
const data = params.data;
|
||||||
|
|
||||||
|
return this.selectAll('.' + selector)
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.insert(tag)
|
||||||
|
.attr('class', selector);
|
||||||
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useSize } from 'react-use';
|
import { useSize } from 'react-use';
|
||||||
|
|
||||||
import chart from './cdfChartd3';
|
import { CdfChartD3 } from './cdfChartD3';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param min
|
* @param min
|
||||||
|
@ -15,14 +15,16 @@ function getRandomInt(min, max) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example input:
|
* @param props
|
||||||
* {
|
* @returns {*}
|
||||||
* xs: [50,100,300,400,500,600],
|
* @constructor
|
||||||
* ys: [0.1, 0.4, 0.6, 0.7,0.8, 0.9]}
|
|
||||||
* }
|
|
||||||
*/
|
*/
|
||||||
function CdfChart(props) {
|
function CdfChartReact(props) {
|
||||||
const id = "chart-" + getRandomInt(0, 100000);
|
const containerRef = React.createRef();
|
||||||
|
const key = "cdf-chart-react-" + getRandomInt(0, 1000);
|
||||||
|
const scale = props.scale || 'linear';
|
||||||
|
const style = !!props.width ? { width: props.width + "px" } : {};
|
||||||
|
|
||||||
const [sized, { width }] = useSize(() => {
|
const [sized, { width }] = useSize(() => {
|
||||||
return React.createElement("div", {
|
return React.createElement("div", {
|
||||||
key: "resizable-div",
|
key: "resizable-div",
|
||||||
|
@ -32,7 +34,7 @@ function CdfChart(props) {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
chart()
|
new CdfChartD3()
|
||||||
.svgWidth(width)
|
.svgWidth(width)
|
||||||
.svgHeight(props.height)
|
.svgHeight(props.height)
|
||||||
.maxX(props.maxX)
|
.maxX(props.maxX)
|
||||||
|
@ -45,13 +47,13 @@ function CdfChart(props) {
|
||||||
.showDistributionLines(props.showDistributionLines)
|
.showDistributionLines(props.showDistributionLines)
|
||||||
.verticalLine(props.verticalLine)
|
.verticalLine(props.verticalLine)
|
||||||
.showVerticalLine(props.showVerticalLine)
|
.showVerticalLine(props.showVerticalLine)
|
||||||
.container("#" + id)
|
.container(containerRef.current)
|
||||||
.data({ primary: props.primaryDistribution }).render();
|
.data({ primary: props.primaryDistribution })
|
||||||
|
.scale(scale)
|
||||||
|
.timeScale(props.timeScale)
|
||||||
|
.render();
|
||||||
});
|
});
|
||||||
|
|
||||||
const style = !!props.width ? { width: props.width + "px" } : {};
|
|
||||||
const key = id;
|
|
||||||
|
|
||||||
return React.createElement("div", {
|
return React.createElement("div", {
|
||||||
style: {
|
style: {
|
||||||
paddingLeft: "10px",
|
paddingLeft: "10px",
|
||||||
|
@ -59,8 +61,12 @@ function CdfChart(props) {
|
||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
sized,
|
sized,
|
||||||
React.createElement("div", { id, style, key }),
|
React.createElement("div", {
|
||||||
|
key,
|
||||||
|
style,
|
||||||
|
ref: containerRef,
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CdfChart;
|
export default CdfChartReact;
|
||||||
|
|
|
@ -1,302 +0,0 @@
|
||||||
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;
|
|
|
@ -33,8 +33,8 @@ type generationSource =
|
||||||
| Shape(pointsType);
|
| Shape(pointsType);
|
||||||
|
|
||||||
type distributionUnit =
|
type distributionUnit =
|
||||||
| Unspecified
|
| UnspecifiedDistribution
|
||||||
| Time(TimeTypes.timeVector);
|
| TimeDistribution(TimeTypes.timeVector);
|
||||||
|
|
||||||
type probabilityType =
|
type probabilityType =
|
||||||
| Cdf
|
| Cdf
|
||||||
|
@ -47,3 +47,14 @@ type genericDistribution = {
|
||||||
domain,
|
domain,
|
||||||
unit: distributionUnit,
|
unit: distributionUnit,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module DistributionUnit = {
|
||||||
|
let toJson = (distributionUnit: distributionUnit) =>
|
||||||
|
switch (distributionUnit) {
|
||||||
|
| TimeDistribution({zero, unit}) =>
|
||||||
|
Js.Null.fromOption(
|
||||||
|
Some({"zero": zero, "unit": unit |> TimeTypes.TimeUnit.toString}),
|
||||||
|
)
|
||||||
|
| _ => Js.Null.fromOption(None)
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,10 +1,11 @@
|
||||||
open DistributionTypes;
|
open DistributionTypes;
|
||||||
|
|
||||||
let make =
|
let make =
|
||||||
(
|
(
|
||||||
~generationSource,
|
~generationSource,
|
||||||
~probabilityType=Pdf,
|
~probabilityType=Pdf,
|
||||||
~domain=Complete,
|
~domain=Complete,
|
||||||
~unit=Unspecified,
|
~unit=UnspecifiedDistribution,
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
: genericDistribution => {
|
: genericDistribution => {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
type assumption =
|
type assumption =
|
||||||
| ADDS_TO_1
|
| ADDS_TO_1
|
||||||
| ADDS_TO_CORRECT_PROBABILITY;
|
| ADDS_TO_CORRECT_PROBABILITY;
|
||||||
|
|
||||||
type assumptions = {
|
type assumptions = {
|
||||||
continuous: assumption,
|
continuous: assumption,
|
||||||
discrete: assumption,
|
discrete: assumption,
|
||||||
discreteProbabilityMass: option(float),
|
discreteProbabilityMass: option(float),
|
||||||
};
|
};
|
||||||
|
|
||||||
let build = (~continuous, ~discrete, ~assumptions) =>
|
let build = (~continuous, ~discrete, ~assumptions) =>
|
||||||
switch (assumptions) {
|
switch (assumptions) {
|
||||||
| {
|
| {
|
||||||
|
@ -21,6 +23,7 @@ let build = (~continuous, ~discrete, ~assumptions) =>
|
||||||
~discreteProbabilityMassFraction=r,
|
~discreteProbabilityMassFraction=r,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
| {
|
| {
|
||||||
continuous: ADDS_TO_1,
|
continuous: ADDS_TO_1,
|
||||||
discrete: ADDS_TO_1,
|
discrete: ADDS_TO_1,
|
||||||
|
@ -33,18 +36,21 @@ let build = (~continuous, ~discrete, ~assumptions) =>
|
||||||
~discreteProbabilityMassFraction=r,
|
~discreteProbabilityMassFraction=r,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
| {
|
| {
|
||||||
continuous: ADDS_TO_1,
|
continuous: ADDS_TO_1,
|
||||||
discrete: ADDS_TO_1,
|
discrete: ADDS_TO_1,
|
||||||
discreteProbabilityMass: None,
|
discreteProbabilityMass: None,
|
||||||
} =>
|
} =>
|
||||||
None
|
None
|
||||||
|
|
||||||
| {
|
| {
|
||||||
continuous: ADDS_TO_CORRECT_PROBABILITY,
|
continuous: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
discrete: ADDS_TO_1,
|
discrete: ADDS_TO_1,
|
||||||
discreteProbabilityMass: None,
|
discreteProbabilityMass: None,
|
||||||
} =>
|
} =>
|
||||||
None
|
None
|
||||||
|
|
||||||
| {
|
| {
|
||||||
continuous: ADDS_TO_1,
|
continuous: ADDS_TO_1,
|
||||||
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
|
|
|
@ -20,6 +20,21 @@ type timePoint = {
|
||||||
value: float,
|
value: float,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module TimeUnit = {
|
||||||
|
let toString = (timeUnit: timeUnit) =>
|
||||||
|
switch (timeUnit) {
|
||||||
|
| `days => "days"
|
||||||
|
| `hours => "hours"
|
||||||
|
| `milliseconds => "milliseconds"
|
||||||
|
| `minutes => "minutes"
|
||||||
|
| `months => "months"
|
||||||
|
| `quarters => "quarters"
|
||||||
|
| `seconds => "seconds"
|
||||||
|
| `weeks => "weeks"
|
||||||
|
| `years => "years"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
module TimePoint = {
|
module TimePoint = {
|
||||||
let fromTimeVector = (timeVector, value): timePoint => {timeVector, value};
|
let fromTimeVector = (timeVector, value): timePoint => {timeVector, value};
|
||||||
|
|
||||||
|
|
|
@ -139,10 +139,11 @@ module Model = {
|
||||||
~generationSource=GuesstimatorString(str),
|
~generationSource=GuesstimatorString(str),
|
||||||
~probabilityType=Cdf,
|
~probabilityType=Cdf,
|
||||||
~domain=Complete,
|
~domain=Complete,
|
||||||
~unit=Unspecified,
|
~unit=UnspecifiedDistribution,
|
||||||
(),
|
(),
|
||||||
);
|
);
|
||||||
Prop.Value.GenericDistribution(genericDistribution);
|
Prop.Value.GenericDistribution(genericDistribution);
|
||||||
|
|
||||||
| CHANCE_OF_EXISTENCE =>
|
| CHANCE_OF_EXISTENCE =>
|
||||||
Prop.Value.GenericDistribution(
|
Prop.Value.GenericDistribution(
|
||||||
GenericDistribution.make(
|
GenericDistribution.make(
|
||||||
|
@ -155,7 +156,7 @@ module Model = {
|
||||||
),
|
),
|
||||||
~probabilityType=Cdf,
|
~probabilityType=Cdf,
|
||||||
~domain=RightLimited({xPoint: 100., excludingProbabilityMass: 0.3}),
|
~domain=RightLimited({xPoint: 100., excludingProbabilityMass: 0.3}),
|
||||||
~unit=Time({zero: currentDateTime, unit: `years}),
|
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
|
||||||
(),
|
(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ module Model = {
|
||||||
~generationSource=GuesstimatorString(guesstimatorString),
|
~generationSource=GuesstimatorString(guesstimatorString),
|
||||||
~probabilityType=Cdf,
|
~probabilityType=Cdf,
|
||||||
~domain=RightLimited({xPoint: 200., excludingProbabilityMass: 0.3}),
|
~domain=RightLimited({xPoint: 200., excludingProbabilityMass: 0.3}),
|
||||||
~unit=Time({zero: currentDateTime, unit: `years}),
|
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
|
||||||
(),
|
(),
|
||||||
);
|
);
|
||||||
Prop.Value.GenericDistribution(genericDistribution);
|
Prop.Value.GenericDistribution(genericDistribution);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user