diff --git a/bsconfig.json b/bsconfig.json
index 359646a4..81ef6fe0 100644
--- a/bsconfig.json
+++ b/bsconfig.json
@@ -3,10 +3,16 @@
"reason": {
"react-jsx": 3
},
- "sources": {
- "dir": "src",
- "subdirs": true
- },
+ "sources": [{
+ "dir": "src",
+ "subdirs": true
+ },
+ {
+ "dir": "showcase",
+ "type": "dev",
+ "subdirs": true
+ }
+ ],
"bsc-flags": ["-bs-super-errors", "-bs-no-version-header"],
"package-specs": [{
"module": "commonjs",
diff --git a/package.json b/package.json
index 90305344..59806ab0 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"clean": "bsb -clean-world",
"parcel": "parcel ./src/index.html --public-url / --no-autoinstall -- watch",
"parcel-build": "parcel build ./src/index.html --no-source-maps --no-autoinstall",
+ "showcase": "PORT=12345 parcel showcase/index.html",
"server": "moduleserve ./ --port 8000",
"predeploy": "parcel build ./src/index.html --no-source-maps --no-autoinstall",
"deploy": "gh-pages -d dist",
@@ -31,6 +32,7 @@
"bs-css": "^11.0.0",
"bs-moment": "0.4.4",
"bs-reform": "9.7.1",
+ "d3": "^5.15.0",
"lenses-ppx": "4.0.0",
"less": "^3.10.3",
"lodash": "^4.17.15",
diff --git a/showcase/Entries.re b/showcase/Entries.re
new file mode 100644
index 00000000..ae4cef64
--- /dev/null
+++ b/showcase/Entries.re
@@ -0,0 +1 @@
+let entries = EntryTypes.[Continuous.entry];
\ No newline at end of file
diff --git a/showcase/EntryTypes.re b/showcase/EntryTypes.re
new file mode 100644
index 00000000..49a61a33
--- /dev/null
+++ b/showcase/EntryTypes.re
@@ -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});
+};
diff --git a/showcase/Lib.re b/showcase/Lib.re
new file mode 100644
index 00000000..60c27f79
--- /dev/null
+++ b/showcase/Lib.re
@@ -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) => {
+
+
changeId(f.id)}> f.title->React.string
+
+ {(
+ f.children
+ |> E.L.fmap(e =>
+ switch (e) {
+ | FolderEntry(folder) => buildFolder(folder)
+ | CompEntry(entry) => buildEntry(entry)
+ }
+ )
+ |> E.L.toArray
+ )
+ ->React.array}
+
+
;
+ }
+ and buildEntry = (e: compEntry) => {
+ changeId(e.id)}>
+ e.title->React.string
+
;
+ };
+ (
+ 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 => {e.render()}
+ };
+ };
+
+ [@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;
+
+
+
{buildNav(setRoute)}
+
+ {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) =>
+
+ {renderEntry(c)}
+
+ | _ => React.null
+ }
+ )
+ |> E.L.toArray
+ )
+ ->React.array
+ }
+ | None =>
"Component not found"->React.string
+ };
+ }}
+
+
;
+ };
+};
\ No newline at end of file
diff --git a/showcase/ShowcaseIndex.re b/showcase/ShowcaseIndex.re
new file mode 100644
index 00000000..e3912686
--- /dev/null
+++ b/showcase/ShowcaseIndex.re
@@ -0,0 +1,2 @@
+ReactDOMRe.renderToElementWithId(
, "main");
+ReasonReactRouter.push("");
\ No newline at end of file
diff --git a/showcase/entries/Continuous.re b/showcase/entries/Continuous.re
new file mode 100644
index 00000000..5475fd92
--- /dev/null
+++ b/showcase/entries/Continuous.re
@@ -0,0 +1,19 @@
+open ForetoldComponents.Base;
+
+let data: DistributionTypes.xyShape = {
+ xs: [|0.2, 20., 80., 212., 330.|],
+ ys: [|0.0, 0.3, 0.5, 0.2, 0.1|],
+};
+
+let alerts = () =>
+
+
+
+ Shape.XYShape.integral}
+ color={`hex("333")}
+ />
+
+
;
+
+let entry = EntryTypes.(entry(~title="Pdf", ~render=alerts));
\ No newline at end of file
diff --git a/showcase/index.html b/showcase/index.html
new file mode 100644
index 00000000..7879deec
--- /dev/null
+++ b/showcase/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+ Showcase
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/charts/CdfChart__Base.re b/src/components/charts/CdfChart__Base.re
new file mode 100644
index 00000000..6abb50bc
--- /dev/null
+++ b/src/components/charts/CdfChart__Base.re
@@ -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;
\ No newline at end of file
diff --git a/src/components/charts/CdfChart__Plain.re b/src/components/charts/CdfChart__Plain.re
new file mode 100644
index 00000000..72067a3e
--- /dev/null
+++ b/src/components/charts/CdfChart__Plain.re
@@ -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,
+ ) => {
+
+ Shape.XYShape.toJs}
+ />
+
;
+};
\ No newline at end of file
diff --git a/src/components/charts/ChartSimple.re b/src/components/charts/ChartSimple.re
new file mode 100644
index 00000000..37f8697c
--- /dev/null
+++ b/src/components/charts/ChartSimple.re
@@ -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")) =>
+
+
+
;
\ No newline at end of file
diff --git a/src/components/charts/ChartWithNumber.re b/src/components/charts/ChartWithNumber.re
new file mode 100644
index 00000000..b8ac5bfb
--- /dev/null
+++ b/src/components/charts/ChartWithNumber.re
@@ -0,0 +1,23 @@
+[@react.component]
+let make = (~data, ~color=?) => {
+ let (x, setX) = React.useState(() => 0.);
+ let chart =
+ React.useMemo1(
+ () => setX(_ => r)} />,
+ [|data|],
+ );
+
+ chart
+
{x |> E.Float.toString |> ReasonReact.string}
+
+ {Shape.Continuous.findY(x, data)
+ |> E.Float.toString
+ |> ReasonReact.string}
+
+
+ {Shape.Continuous.findY(x, Shape.XYShape.integral(data))
+ |> E.Float.toString
+ |> ReasonReact.string}
+
+
;
+};
\ No newline at end of file
diff --git a/src/components/charts/cdfChartReact.js b/src/components/charts/cdfChartReact.js
new file mode 100644
index 00000000..f72e5db7
--- /dev/null
+++ b/src/components/charts/cdfChartReact.js
@@ -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;
diff --git a/src/components/charts/cdfChartd3.js b/src/components/charts/cdfChartd3.js
new file mode 100644
index 00000000..a57b8cb1
--- /dev/null
+++ b/src/components/charts/cdfChartd3.js
@@ -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: 5,
+ 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;
diff --git a/src/core/MixedCdf.re b/src/core/MixedCdf.re
index 8ffb7b31..2006df48 100644
--- a/src/core/MixedCdf.re
+++ b/src/core/MixedCdf.re
@@ -8,8 +8,7 @@ type yPdfPoint = {
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),
-// discrete: Shape.Discrete.findY(x, t.discrete),
\ No newline at end of file
+// continuous: Shape.Continuous.findY(x, t.continuous),
\ No newline at end of file
diff --git a/src/core/Shape.re b/src/core/Shape.re
index 507e05a7..7d0ebe06 100644
--- a/src/core/Shape.re
+++ b/src/core/Shape.re
@@ -3,7 +3,7 @@ open DistributionTypes;
let _lastElement = (a: array('a)) =>
switch (Belt.Array.size(a)) {
| 0 => None
- | n => Belt.Array.get(a, n)
+ | n => Belt.Array.get(a, n - 1)
};
module XYShape = {
@@ -28,7 +28,8 @@ module XYShape = {
Belt.Array.zip(p.xs, p.ys)
->Belt.Array.reduce([||], (items, (x, y)) =>
switch (_lastElement(items)) {
- | Some((_, yLast)) => [|(x, fn(y, yLast))|]
+ | Some((_, yLast)) =>
+ Belt.Array.concat(items, [|(x, fn(y, yLast))|])
| None => [|(x, y)|]
}
)
diff --git a/src/utility/CdfLibrary.js b/src/utility/CdfLibrary.js
index 528e2dda..ab9134de 100644
--- a/src/utility/CdfLibrary.js
+++ b/src/utility/CdfLibrary.js
@@ -1,5 +1,6 @@
const {
Cdf,
+ Pdf,
ContinuousDistribution,
ContinuousDistributionCombination,
scoringFunctions,
diff --git a/src/utility/CdfLibrary.re b/src/utility/CdfLibrary.re
index fdc08392..2354331c 100644
--- a/src/utility/CdfLibrary.re
+++ b/src/utility/CdfLibrary.re
@@ -38,7 +38,7 @@ module JS = {
module Distribution = {
let toPdf = dist => dist |> JS.doAsDist(JS.cdfToPdf);
- let toCdf = 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;
diff --git a/yarn.lock b/yarn.lock
index fa7acb48..2e276981 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2967,6 +2967,43 @@ d3@5.9.2:
d3-voronoi "1"
d3-zoom "1"
+d3@^5.15.0:
+ version "5.15.0"
+ resolved "https://registry.yarnpkg.com/d3/-/d3-5.15.0.tgz#ffd44958e6a3cb8a59a84429c45429b8bca5677a"
+ integrity sha512-C+E80SL2nLLtmykZ6klwYj5rPqB5nlfN5LdWEAVdWPppqTD8taoJi2PxLZjPeYT8FFRR2yucXq+kBlOnnvZeLg==
+ dependencies:
+ d3-array "1"
+ d3-axis "1"
+ d3-brush "1"
+ d3-chord "1"
+ d3-collection "1"
+ d3-color "1"
+ d3-contour "1"
+ d3-dispatch "1"
+ d3-drag "1"
+ d3-dsv "1"
+ d3-ease "1"
+ d3-fetch "1"
+ d3-force "1"
+ d3-format "1"
+ d3-geo "1"
+ d3-hierarchy "1"
+ d3-interpolate "1"
+ d3-path "1"
+ d3-polygon "1"
+ d3-quadtree "1"
+ d3-random "1"
+ d3-scale "2"
+ d3-scale-chromatic "1"
+ d3-selection "1"
+ d3-shape "1"
+ d3-time "1"
+ d3-time-format "2"
+ d3-timer "1"
+ d3-transition "1"
+ d3-voronoi "1"
+ d3-zoom "1"
+
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"