+ Added the ability to change the upper and lower boundaries.

+ Made the drawings relative to the canvas, not to the screen.
- Removed the mean line, as it didn't play nice with the ability to change upper and lower boundaries.
This commit is contained in:
Nuno Sempere 2020-05-06 00:15:51 +02:00
parent ff5b26d865
commit 23952af460

View File

@ -1,5 +1,4 @@
module Types = { module Types = {
type rectangle = { type rectangle = {
// Ref: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect // Ref: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
left: int, left: int,
@ -35,19 +34,26 @@ module Types = {
xValues: array(float), xValues: array(float),
}; };
type formElements = { type foretoldFormElements = {
measurableId: string, measurableId: string,
token: string, token: string,
comment: string, comment: string,
}; };
type distributionLimits = {
lower: float,
upper: float,
};
type canvasState = { type canvasState = {
isMouseDown: bool,
lastMousePosition: option(canvasPoint),
canvasShape: option(canvasShape), canvasShape: option(canvasShape),
lastMousePosition: option(canvasPoint),
isMouseDown: bool,
readyToRender: bool, readyToRender: bool,
formElements, hasJustBeenSentToForetold: bool,
hasJustBeenSent: bool, limitsHaveJustBeenUpdated: bool,
foretoldFormElements,
distributionLimits,
}; };
}; };
@ -101,22 +107,25 @@ module CanvasContext = {
module Convert = { module Convert = {
/* /*
- In this module, the fundamental unit for the canvas shape is the distance vector from the (0,0) point at the upper leftmost corner of the screen. - In this module, the fundamental unit for the canvas shape is the distance vector from the (0,0) point at the upper leftmost corner of the screen.
- For some drawing functions, this is instead from the (0,0) point at the upper leftmost corner of the canvas element. This is irrelevant in this module. - For some drawing functions, this is instead from the (0,0) point at the upper leftmost corner of the canvas element. This is irrelevant in this module.
- The fundamental unit for a probability distribution is an x coordinate and its corresponding y probability density - The fundamental unit for a probability distribution is an x coordinate and its corresponding y probability density
*/ */
let xyShapeToCanvasShape = let xyShapeToCanvasShape =
(~xyShape: Types.xyShape, ~canvasElement: Dom.element) => { (~xyShape: Types.xyShape, ~canvasElement: Dom.element) => {
let xs = xyShape.xs; let xs = xyShape.xs;
let ys = xyShape.ys; let ys = xyShape.ys;
let rectangle: Types.rectangle = CanvasContext.getBoundingClientRect(canvasElement); let rectangle: Types.rectangle =
CanvasContext.getBoundingClientRect(canvasElement);
let lengthX = E.A.length(xs); let lengthX = E.A.length(xs);
let minX = xs[0]; let minX = xs[0];
let maxX = xs[lengthX - 1]; let maxX = xs[lengthX - 1];
let ratioXs = let ratioXs =
float_of_int(rectangle.width) *. CanvasContext.paddingRatioX /. (maxX -. minX); float_of_int(rectangle.width)
*. CanvasContext.paddingRatioX
/. (maxX -. minX);
let ws = let ws =
E.A.fmap( E.A.fmap(
x => x =>
@ -131,7 +140,10 @@ module Convert = {
let minY = 0.; let minY = 0.;
let maxY = E.A.reduce(ys, 0., (x, y) => x > y ? x : y); let maxY = E.A.reduce(ys, 0., (x, y) => x > y ? x : y);
let ratioYs = float_of_int(rectangle.height) *. CanvasContext.paddingRatioY /. (maxY -. minY); let ratioYs =
float_of_int(rectangle.height)
*. CanvasContext.paddingRatioY
/. (maxY -. minY);
let hs = let hs =
E.A.fmap( E.A.fmap(
y => y =>
@ -151,11 +163,15 @@ module Convert = {
: Types.continuousShape => { : Types.continuousShape => {
let xs = canvasShape.xValues; let xs = canvasShape.xValues;
let hs = canvasShape.hs; let hs = canvasShape.hs;
let rectangle: Types.rectangle = CanvasContext.getBoundingClientRect(canvasElement); let rectangle: Types.rectangle =
CanvasContext.getBoundingClientRect(canvasElement);
let bottom = float_of_int(rectangle.bottom); let bottom = float_of_int(rectangle.bottom);
let ysRelative = let ysRelative =
E.A.fmap(h => bottom -. h +. CanvasContext.paddingFactorY(rectangle.height), hs); E.A.fmap(
h => bottom -. h +. CanvasContext.paddingFactorY(rectangle.height),
hs,
);
let xyShape: Types.xyShape = {xs, ys: ysRelative}; let xyShape: Types.xyShape = {xs, ys: ysRelative};
let continuousShape: Types.continuousShape = { let continuousShape: Types.continuousShape = {
xyShape, xyShape,
@ -196,7 +212,6 @@ module Convert = {
}; };
module Draw = { module Draw = {
let line = let line =
( (
canvasElement: Dom.element, canvasElement: Dom.element,
@ -218,7 +233,8 @@ module Draw = {
let canvasPlot = let canvasPlot =
(canvasElement: Dom.element, canvasShape: Types.canvasShape) => { (canvasElement: Dom.element, canvasShape: Types.canvasShape) => {
let context = CanvasContext.getContext2d(canvasElement); let context = CanvasContext.getContext2d(canvasElement);
let rectangle: Types.rectangle = CanvasContext.getBoundingClientRect(canvasElement); let rectangle: Types.rectangle =
CanvasContext.getBoundingClientRect(canvasElement);
/* Some useful reference points */ /* Some useful reference points */
let paddingFactorX = CanvasContext.paddingFactorX(rectangle.width); let paddingFactorX = CanvasContext.paddingFactorX(rectangle.width);
@ -252,36 +268,45 @@ module Draw = {
/* Draw a line between every two adjacent points */ /* Draw a line between every two adjacent points */
let length = Array.length(canvasShape.ws); let length = Array.length(canvasShape.ws);
let windowScrollY: float = [%raw "window.scrollY"];
CanvasContext.setStrokeStyle(context, String, "#5680cc"); CanvasContext.setStrokeStyle(context, String, "#5680cc");
CanvasContext.lineWidth(context, 4.); CanvasContext.lineWidth(context, 4.);
for (i in 1 to length - 1) { for (i in 1 to length - 1) {
let point0 = Convert.getPoint(canvasShape, i - 1); let point0 = Convert.getPoint(canvasShape, i - 1);
let point1 = Convert.getPoint(canvasShape, i); let point1 = Convert.getPoint(canvasShape, i);
let point0 = {...point0, h: point0.h -. windowScrollY};
let point1 = {...point1, h: point1.h -. windowScrollY};
line(canvasElement, ~point0, ~point1); line(canvasElement, ~point0, ~point1);
}; };
/* Draws the expected value line */ /* Draws the expected value line */
let continuousShape = // Removed on the grounds that it didn't play nice with changes in limits.
Convert.canvasShapeToContinuousShape(~canvasShape, ~canvasElement); /*
let mean = Distributions.Continuous.T.mean(continuousShape); let continuousShape =
let variance = Distributions.Continuous.T.variance(continuousShape); Convert.canvasShapeToContinuousShape(~canvasShape, ~canvasElement);
let meanLocation = let mean = Distributions.Continuous.T.mean(continuousShape);
Convert.findClosestInOrderedArrayDangerously(mean, canvasShape.xValues); let variance = Distributions.Continuous.T.variance(continuousShape);
let meanLocationCanvasX = canvasShape.ws[meanLocation]; let meanLocation =
let meanLocationCanvasY = canvasShape.hs[meanLocation]; Convert.findClosestInOrderedArrayDangerously(mean, canvasShape.xValues);
CanvasContext.beginPath(context); let meanLocationCanvasX = canvasShape.ws[meanLocation];
CanvasContext.setStrokeStyle(context, String, "#5680cc"); let meanLocationCanvasY = canvasShape.hs[meanLocation];
CanvasContext.setLineDash(context, [|5, 10|]); CanvasContext.beginPath(context);
CanvasContext.setStrokeStyle(context, String, "#5680cc");
line( CanvasContext.setLineDash(context, [|5, 10|]);
canvasElement,
~point0={w: meanLocationCanvasX, h: p00.h},
~point1={w: meanLocationCanvasX, h: meanLocationCanvasY},
);
CanvasContext.stroke(context);
CanvasContext.setLineDash(context, [||]);
line(
canvasElement,
~point0={w: meanLocationCanvasX, h: p00.h},
~point1={
w: meanLocationCanvasX,
h: meanLocationCanvasY -. windowScrollY,
},
);
CanvasContext.stroke(context);
CanvasContext.setLineDash(context, [||]);
*/
/* draws lines parallel to x axis + factors to help w/ precise drawing. */ /* draws lines parallel to x axis + factors to help w/ precise drawing. */
CanvasContext.beginPath(context); CanvasContext.beginPath(context);
CanvasContext.setStrokeStyle(context, String, "#CCC"); CanvasContext.setStrokeStyle(context, String, "#CCC");
@ -318,19 +343,19 @@ module Draw = {
/* draw units along the x axis */ /* draw units along the x axis */
CanvasContext.font(context, "16px Roboto"); CanvasContext.font(context, "16px Roboto");
CanvasContext.lineWidth(context, 2.0); CanvasContext.lineWidth(context, 2.0);
let numUnits = 10; let numIntervals = 10;
let width = float_of_int(rectangle.width); let width = float_of_int(rectangle.width);
let height = float_of_int(rectangle.height); let height = float_of_int(rectangle.height);
let xMin = canvasShape.xValues[0]; let xMin = canvasShape.xValues[0];
let xMax = canvasShape.xValues[length - 1]; let xMax = canvasShape.xValues[length - 1];
let xSpan = (xMax -. xMin) /. float_of_int(numUnits); let xSpan = (xMax -. xMin) /. float_of_int(numIntervals - 1);
for (i in 0 to numUnits - 1) { for (i in 0 to numIntervals - 1) {
let x = let x =
float_of_int(rectangle.left) float_of_int(rectangle.left)
+. width +. width
*. float_of_int(i) *. float_of_int(i)
/. float_of_int(numUnits); /. float_of_int(numIntervals);
let dashValue = xMin +. xSpan *. float_of_int(i); let dashValue = xMin +. xSpan *. float_of_int(i);
CanvasContext.fillText( CanvasContext.fillText(
Js.Float.toFixedWithPrecision(dashValue, ~digits=2), Js.Float.toFixedWithPrecision(dashValue, ~digits=2),
@ -340,7 +365,10 @@ module Draw = {
); );
line( line(
canvasElement, canvasElement,
~point0={w: x +. CanvasContext.paddingFactorX(rectangle.width), h: p00.h}, ~point0={
w: x +. CanvasContext.paddingFactorX(rectangle.width),
h: p00.h,
},
~point1={ ~point1={
w: x +. CanvasContext.paddingFactorX(rectangle.width), w: x +. CanvasContext.paddingFactorX(rectangle.width),
h: p00.h +. 10.0, h: p00.h +. 10.0,
@ -350,9 +378,8 @@ module Draw = {
}; };
let initialDistribution = (canvasElement: Dom.element, setState) => { let initialDistribution = (canvasElement: Dom.element, setState) => {
let mean = 50.0;
let mean = 10.0; let stdev = 20.0;
let stdev = 4.0;
let numSamples = 3000; let numSamples = 3000;
let normal: SymbolicDist.dist = `Normal({mean, stdev}); let normal: SymbolicDist.dist = `Normal({mean, stdev});
@ -365,22 +392,27 @@ module Draw = {
}; };
/* // To use a lognormal instead: /* // To use a lognormal instead:
let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev); let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev);
let lognormalShape = let lognormalShape =
SymbolicDist.GenericSimple.toShape(lognormal, numSamples); SymbolicDist.GenericSimple.toShape(lognormal, numSamples);
let lognormalXYShape: Types.xyShape = let lognormalXYShape: Types.xyShape =
switch (lognormalShape) { switch (lognormalShape) {
| Mixed(_) => {xs: [||], ys: [||]} | Mixed(_) => {xs: [||], ys: [||]}
| Discrete(_) => {xs: [||], ys: [||]} | Discrete(_) => {xs: [||], ys: [||]}
| Continuous(m) => Distributions.Continuous.getShape(m) | Continuous(m) => Distributions.Continuous.getShape(m)
}; };
*/ */
let canvasShape = Convert.xyShapeToCanvasShape(~xyShape, ~canvasElement); let canvasShape = Convert.xyShapeToCanvasShape(~xyShape, ~canvasElement);
/* let continuousShapeBack = /* let continuousShapeBack =
Convert.canvasShapeToContinuousShape(~canvasShape, ~canvasElement); Convert.canvasShapeToContinuousShape(~canvasShape, ~canvasElement);
*/ */
let windowScrollY: float = [%raw "window.scrollY"];
let canvasShape = {
...canvasShape,
hs: E.A.fmap(h => h +. windowScrollY, canvasShape.hs),
};
setState((state: Types.canvasState) => { setState((state: Types.canvasState) => {
{...state, canvasShape: Some(canvasShape)} {...state, canvasShape: Some(canvasShape)}
}); });
@ -443,20 +475,25 @@ module State = {
type t = Types.canvasState; type t = Types.canvasState;
let initialState: t = { let initialState: t = {
isMouseDown: false,
lastMousePosition: None,
canvasShape: None, canvasShape: None,
lastMousePosition: None,
isMouseDown: false,
readyToRender: false, readyToRender: false,
hasJustBeenSent: false, hasJustBeenSentToForetold: false,
formElements: { limitsHaveJustBeenUpdated: false,
foretoldFormElements: {
measurableId: "", measurableId: "",
token: "", token: "",
comment: "", comment: "",
}, },
distributionLimits: {
lower: 0.0,
upper: 1000.0,
},
}; };
let updateMousePosition = (~point: Types.canvasPoint, ~setState) =>{ let updateMousePosition = (~point: Types.canvasPoint, ~setState) => {
setState((state: t) => ({...state, lastMousePosition: Some(point)})); setState((state: t) => {...state, lastMousePosition: Some(point)});
}; };
let onMouseMovement = let onMouseMovement =
@ -466,14 +503,15 @@ module State = {
~state: t, ~state: t,
~setState, ~setState,
) => { ) => {
/* Helper functions and objects*/ /* Helper functions and objects*/
let x = ReactEvent.Mouse.clientX(event); let x = ReactEvent.Mouse.clientX(event);
let y = ReactEvent.Mouse.clientY(event); let y = ReactEvent.Mouse.clientY(event);
let windowScrollY: float = [%raw "window.scrollY"];
let point1: Types.canvasPoint = { let point1: Types.canvasPoint = {
w: float_of_int(x), w: float_of_int(x),
h: float_of_int(y), h: float_of_int(y) +. windowScrollY,
}; };
let pointIsInBetween = let pointIsInBetween =
@ -485,7 +523,6 @@ module State = {
x0 < x2 && x2 < x1 || x1 < x2 && x2 < x0; x0 < x2 && x2 < x1 || x1 < x2 && x2 < x0;
}; };
/* If all conditions are met, update the distribution */ /* If all conditions are met, update the distribution */
let updateDistWithMouseMovement = let updateDistWithMouseMovement =
( (
@ -494,12 +531,12 @@ module State = {
~canvasShape: Types.canvasShape, ~canvasShape: Types.canvasShape,
) => { ) => {
/* /*
The mouse moves across the screen, and we get a series of (x,y) positions. The mouse moves across the screen, and we get a series of (x,y) positions.
We know where the mouse last was We know where the mouse last was
we update everything between the last (x,y) position and the new (x,y), using linear interpolation we update everything between the last (x,y) position and the new (x,y), using linear interpolation
Note that we only want to update & iterate over the parts of the canvas which are changed by the mouse movement Note that we only want to update & iterate over the parts of the canvas which are changed by the mouse movement
(otherwise, things might be too slow) (otherwise, things might be too slow)
*/ */
let slope = (point1.h -. point0.h) /. (point1.w -. point0.w); let slope = (point1.h -. point0.h) /. (point1.w -. point0.w);
let pos0 = let pos0 =
@ -540,19 +577,21 @@ module State = {
(~point: Types.canvasPoint, ~rectangle: Types.rectangle) => { (~point: Types.canvasPoint, ~rectangle: Types.rectangle) => {
switch ( switch (
/* /*
- If we also validate the xs, this produces a jaded user experience around the edges. - If we also validate the xs, this produces a jaded user experience around the edges.
- Instead, we will also update the first and last points in the updateDistWithMouseMovement, with the findClosestInOrderedArrayDangerously function, even when the x is outside the padding zone - Instead, we will also update the first and last points in the updateDistWithMouseMovement, with the findClosestInOrderedArrayDangerously function, even when the x is outside the padding zone
- When we send the distribution to foretold, we'll get rid of the first and last points. - When we send the distribution to foretold, we'll get rid of the first and last points.
*/ */
/* /*
point.w >= float_of_int(rectangle.left) point.w >= float_of_int(rectangle.left)
+. CanvasContext.paddingFactorX(rectangle.width), +. CanvasContext.paddingFactorX(rectangle.width),
point.w <= float_of_int(rectangle.right) point.w <= float_of_int(rectangle.right)
-. CanvasContext.paddingFactorX(rectangle.width), -. CanvasContext.paddingFactorX(rectangle.width),
*/ */
point.h >= float_of_int(rectangle.top) point.h
-. windowScrollY >= float_of_int(rectangle.top)
+. CanvasContext.paddingFactorY(rectangle.height), +. CanvasContext.paddingFactorY(rectangle.height),
point.h <= float_of_int(rectangle.bottom) point.h
-. windowScrollY <= float_of_int(rectangle.bottom)
-. CanvasContext.paddingFactorY(rectangle.height), -. CanvasContext.paddingFactorY(rectangle.height),
) { ) {
| (true, true) => true | (true, true) => true
@ -567,8 +606,8 @@ module State = {
validateYCoordinates(~point=point1, ~rectangle), validateYCoordinates(~point=point1, ~rectangle),
) { ) {
| (true, true) => | (true, true) =>
let newCanvasShape = updateDistWithMouseMovement(~point0, ~point1, ~canvasShape); let newCanvasShape =
state.readyToRender ? Draw.canvasPlot(canvasElement, newCanvasShape) : (); updateDistWithMouseMovement(~point0, ~point1, ~canvasShape);
setState((state: t) => { setState((state: t) => {
{ {
...state, ...state,
@ -577,10 +616,13 @@ module State = {
readyToRender: false, readyToRender: false,
} }
}); });
state.readyToRender
? Draw.canvasPlot(canvasElement, newCanvasShape) : ();
| (false, true) => updateMousePosition(~point=point1, ~setState) | (false, true) => updateMousePosition(~point=point1, ~setState)
| (_, false) => () | (_, false) => ()
}; };
} };
switch ( switch (
potentialCanvas, potentialCanvas,
@ -589,10 +631,11 @@ module State = {
state.lastMousePosition, state.lastMousePosition,
) { ) {
| (Some(canvasElement), Some(canvasShape), true, Some(point0)) => | (Some(canvasElement), Some(canvasShape), true, Some(point0)) =>
decideWithCanvas(~canvasElement, ~canvasShape, ~point0); decideWithCanvas(~canvasElement, ~canvasShape, ~point0)
| (Some(canvasElement), _, true, None) => | (Some(canvasElement), _, true, None) =>
let rectangle = CanvasContext.getBoundingClientRect(canvasElement); let rectangle = CanvasContext.getBoundingClientRect(canvasElement);
validateYCoordinates(~point=point1, ~rectangle) ? updateMousePosition(~point=point1, ~setState) : (); validateYCoordinates(~point=point1, ~rectangle)
? updateMousePosition(~point=point1, ~setState) : ();
| _ => () | _ => ()
}; };
}; };
@ -603,7 +646,7 @@ module State = {
}); });
}; };
let onSubmitForm = let onSubmitForetoldForm =
( (
~state: Types.canvasState, ~state: Types.canvasState,
~potentialCanvasElement: option(Dom.element), ~potentialCanvasElement: option(Dom.element),
@ -615,8 +658,7 @@ module State = {
| (None, _) => () | (None, _) => ()
| (_, None) => () | (_, None) => ()
| (Some(canvasShape), Some(canvasElement)) => | (Some(canvasShape), Some(canvasElement)) =>
let pdf =
let pdf =
Convert.canvasShapeToContinuousShape(~canvasShape, ~canvasElement); Convert.canvasShapeToContinuousShape(~canvasShape, ~canvasElement);
/* create a cdf from a pdf */ /* create a cdf from a pdf */
@ -631,27 +673,81 @@ module State = {
let ys = [||]; let ys = [||];
for (i in 1 to 999) { for (i in 1 to 999) {
/* /*
- see comment in validateYCoordinates as to why this starts at 1. - see comment in validateYCoordinates as to why this starts at 1.
- foretold accepts distributions with up to 1000 points. - foretold accepts distributions with up to 1000 points.
*/ */
let j = i * 3; let j = i * 3;
Js.Array.push(cdf.xyShape.xs[j], xs); Js.Array.push(cdf.xyShape.xs[j], xs);
Js.Array.push(cdf.xyShape.ys[j], ys); Js.Array.push(cdf.xyShape.ys[j], ys);
();
}; };
ForetoldAPI.predict( ForetoldAPI.predict(
~measurableId=state.formElements.measurableId, ~measurableId=state.foretoldFormElements.measurableId,
~token=state.formElements.token, ~token=state.foretoldFormElements.token,
~comment=state.formElements.comment, ~comment=state.foretoldFormElements.comment,
~xs, ~xs,
~ys, ~ys,
); );
setState((state: t) => {...state, hasJustBeenSent: true});
setState((state: t) => {...state, hasJustBeenSentToForetold: true});
Js.Global.setTimeout( Js.Global.setTimeout(
() => { () => {
setState((state: t) => {...state, hasJustBeenSent: false}); setState((state: t) =>
{...state, hasJustBeenSentToForetold: false}
)
}, },
5000, 5000,
); );
();
};
();
};
let onSubmitLimitsForm =
(
~state: Types.canvasState,
~potentialCanvasElement: option(Dom.element),
~setState,
) => {
let potentialCanvasShape = state.canvasShape;
switch (potentialCanvasShape, potentialCanvasElement) {
| (None, _) => ()
| (_, None) => ()
| (Some(canvasShape), Some(canvasElement)) =>
let xValues = canvasShape.xValues;
let length = Array.length(xValues);
let xMin = xValues[0];
let xMax = xValues[length - 1];
let lower = state.distributionLimits.lower;
let upper = state.distributionLimits.upper;
let slope = (upper -. lower) /. (xMax -. xMin);
let delta = lower -. slope *. xMin;
let xValues = E.A.fmap(x => delta +. x *. slope, xValues);
let newCanvasShape = {...canvasShape, xValues};
setState((state: t) =>
{
...state,
canvasShape: Some(newCanvasShape),
limitsHaveJustBeenUpdated: true,
}
);
Draw.canvasPlot(canvasElement, newCanvasShape);
Js.Global.setTimeout(
() => {
setState((state: t) =>
{...state, limitsHaveJustBeenUpdated: false}
)
},
5000,
);
(); ();
}; };
(); ();
@ -666,7 +762,6 @@ module Styles = {
[@react.component] [@react.component]
let make = () => { let make = () => {
let canvasRef: React.Ref.t(option(Dom.element)) = React.useRef(None); // should morally live inside the state, but this is tricky. let canvasRef: React.Ref.t(option(Dom.element)) = React.useRef(None); // should morally live inside the state, but this is tricky.
let (state, setState) = React.useState(() => State.initialState); let (state, setState) = React.useState(() => State.initialState);
@ -684,7 +779,7 @@ let make = () => {
None; None;
}); });
/* Render the current distribution every 40ms, while the mouse is moving and changing it */ /* Render the current distribution every 30ms, while the mouse is moving and changing it */
React.useEffect0(() => { React.useEffect0(() => {
let runningInterval = let runningInterval =
Js.Global.setInterval( Js.Global.setInterval(
@ -693,14 +788,14 @@ let make = () => {
{...state, readyToRender: true} {...state, readyToRender: true}
}) })
}, },
40, 30,
); );
Some(() => Js.Global.clearInterval(runningInterval)); Some(() => Js.Global.clearInterval(runningInterval));
}); });
<Antd.Card title={"Distribution Drawer" |> R.ste}> <Antd.Card title={"Distribution Drawer" |> R.ste}>
<div className=Styles.spacer /> <div className=Styles.spacer />
<p>{"Click to begin drawing, click to stop drawing" |> R.ste}</p> <p> {"Click to begin drawing, click to stop drawing" |> R.ste} </p>
<canvas <canvas
width="1000" width="1000"
height="700" height="700"
@ -722,13 +817,88 @@ let make = () => {
<br /> <br />
<br /> <br />
<br /> <br />
<Antd.Card title={"Update upper and lower limits" |> R.ste}>
<form
id="update-limits"
onSubmit={(e: ReactEvent.Form.t): unit => {
ReactEvent.Form.preventDefault(e);
/* code to run on submit */
State.onSubmitLimitsForm(
~state,
~potentialCanvasElement=React.Ref.current(canvasRef),
~setState,
);
();
}}>
<div>
<label> {"Lower: " |> R.ste} </label>
<input
type_="number"
id="lowerlimit"
name="lowerlimit"
value={Js.Float.toString(state.distributionLimits.lower)}
placeholder="a number. f.ex., 0"
required=true
step=0.001
onChange={event => {
let value = ReactEvent.Form.target(event)##value;
setState((state: Types.canvasState) => {
{
...state,
distributionLimits: {
...state.distributionLimits,
lower: value,
},
}
});
}}
/>
</div>
<br />
<div>
<label> {"Upper: " |> R.ste} </label>
<input
type_="number"
id="upperlimit"
name="upperlimit"
value={Js.Float.toString(state.distributionLimits.upper)}
placeholder="a number. f.ex., 100"
required=true
step=0.001
onChange={event => {
let value = ReactEvent.Form.target(event)##value;
setState((state: Types.canvasState) => {
{
...state,
distributionLimits: {
...state.distributionLimits,
upper: value,
},
}
});
}}
/>
</div>
<br />
<button type_="submit" id="updatelimits">
{"Update limits" |> R.ste}
</button>
<br />
<p hidden={!state.limitsHaveJustBeenUpdated}>
{"Updated!" |> R.ste}
</p>
</form>
</Antd.Card>
<br />
<br />
<br />
<Antd.Card title={"Send to foretold" |> R.ste}> <Antd.Card title={"Send to foretold" |> R.ste}>
<form <form
id="send-to-foretold" id="send-to-foretold"
onSubmit={(e: ReactEvent.Form.t): unit => { onSubmit={(e: ReactEvent.Form.t): unit => {
ReactEvent.Form.preventDefault(e); ReactEvent.Form.preventDefault(e);
/* code to run on submit */ /* code to run on submit */
State.onSubmitForm( State.onSubmitForetoldForm(
~state, ~state,
~potentialCanvasElement=React.Ref.current(canvasRef), ~potentialCanvasElement=React.Ref.current(canvasRef),
~setState, ~setState,
@ -741,7 +911,7 @@ let make = () => {
type_="text" type_="text"
id="measurableId" id="measurableId"
name="measurableId" name="measurableId"
value={state.formElements.measurableId} value={state.foretoldFormElements.measurableId}
placeholder="The last bit in the url, after the m" placeholder="The last bit in the url, after the m"
required=true required=true
onChange={event => { onChange={event => {
@ -749,8 +919,8 @@ let make = () => {
setState((state: Types.canvasState) => { setState((state: Types.canvasState) => {
{ {
...state, ...state,
formElements: { foretoldFormElements: {
...state.formElements, ...state.foretoldFormElements,
measurableId: value, measurableId: value,
}, },
} }
@ -765,7 +935,7 @@ let make = () => {
type_="text" type_="text"
id="foretoldToken" id="foretoldToken"
name="foretoldToken" name="foretoldToken"
value={state.formElements.token} value={state.foretoldFormElements.token}
placeholder="Profile -> Bots -> (New Bot) -> Token" placeholder="Profile -> Bots -> (New Bot) -> Token"
required=true required=true
onChange={event => { onChange={event => {
@ -773,8 +943,8 @@ let make = () => {
setState((state: Types.canvasState) => { setState((state: Types.canvasState) => {
{ {
...state, ...state,
formElements: { foretoldFormElements: {
...state.formElements, ...state.foretoldFormElements,
token: value, token: value,
}, },
} }
@ -794,8 +964,8 @@ let make = () => {
setState((state: Types.canvasState) => { setState((state: Types.canvasState) => {
{ {
...state, ...state,
formElements: { foretoldFormElements: {
...state.formElements, ...state.foretoldFormElements,
comment: value, comment: value,
}, },
} }
@ -807,7 +977,7 @@ let make = () => {
{"Send to foretold" |> R.ste} {"Send to foretold" |> R.ste}
</button> </button>
<br /> <br />
<p hidden={!state.hasJustBeenSent}> {"Sent!" |> R.ste} </p> <p hidden={!state.hasJustBeenSentToForetold}> {"Sent!" |> R.ste} </p>
</form> </form>
</Antd.Card> </Antd.Card>
</Antd.Card>; </Antd.Card>;