Compare commits
6 Commits
develop
...
plot-funct
Author | SHA1 | Date | |
---|---|---|---|
|
76fe461363 | ||
|
8d612f75f0 | ||
|
c060304161 | ||
|
56913bc95e | ||
|
2dfb57240e | ||
|
62f735efcb |
|
@ -3,8 +3,9 @@ import {
|
||||||
SqDistribution,
|
SqDistribution,
|
||||||
result,
|
result,
|
||||||
SqDistributionError,
|
SqDistributionError,
|
||||||
|
LabeledDistribution,
|
||||||
resultMap,
|
resultMap,
|
||||||
SqRecord,
|
SqPlot,
|
||||||
environment,
|
environment,
|
||||||
SqDistributionTag,
|
SqDistributionTag,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
|
@ -17,7 +18,6 @@ import {
|
||||||
DistributionChartSpecOptions,
|
DistributionChartSpecOptions,
|
||||||
} from "../lib/distributionSpecBuilder";
|
} from "../lib/distributionSpecBuilder";
|
||||||
import { NumberShower } from "./NumberShower";
|
import { NumberShower } from "./NumberShower";
|
||||||
import { Plot, parsePlot } from "../lib/plotParser";
|
|
||||||
import { flattenResult } from "../lib/utility";
|
import { flattenResult } from "../lib/utility";
|
||||||
import { hasMassBelowZero } from "../lib/distributionUtils";
|
import { hasMassBelowZero } from "../lib/distributionUtils";
|
||||||
|
|
||||||
|
@ -28,27 +28,15 @@ export type DistributionPlottingSettings = {
|
||||||
} & DistributionChartSpecOptions;
|
} & DistributionChartSpecOptions;
|
||||||
|
|
||||||
export type DistributionChartProps = {
|
export type DistributionChartProps = {
|
||||||
plot: Plot;
|
|
||||||
environment: environment;
|
environment: environment;
|
||||||
width?: number;
|
width?: number;
|
||||||
height: number;
|
height: number;
|
||||||
xAxisType?: "number" | "dateTime";
|
xAxisType?: "number" | "dateTime";
|
||||||
} & DistributionPlottingSettings;
|
} & DistributionPlottingSettings &
|
||||||
|
({ plot: SqPlot } | { distribution: SqDistribution });
|
||||||
export function defaultPlot(distribution: SqDistribution): Plot {
|
|
||||||
return { distributions: [{ name: "default", distribution }] };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makePlot(record: SqRecord): Plot | void {
|
|
||||||
const plotResult = parsePlot(record);
|
|
||||||
if (plotResult.tag === "Ok") {
|
|
||||||
return plotResult.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
plot,
|
|
||||||
environment,
|
environment,
|
||||||
height,
|
height,
|
||||||
showSummary,
|
showSummary,
|
||||||
|
@ -57,8 +45,14 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
actions = false,
|
actions = false,
|
||||||
} = props;
|
} = props;
|
||||||
const [sized] = useSize((size) => {
|
const [sized] = useSize((size) => {
|
||||||
const shapes = flattenResult(
|
let distributions: LabeledDistribution[];
|
||||||
plot.distributions.map((x) =>
|
if ("plot" in props) {
|
||||||
|
distributions = props.plot.getDistributions();
|
||||||
|
} else {
|
||||||
|
distributions = [{ name: "default", distribution: props.distribution }];
|
||||||
|
}
|
||||||
|
let shapes = flattenResult(
|
||||||
|
distributions.map((x) =>
|
||||||
resultMap(x.distribution.pointSet(environment), (pointSet) => ({
|
resultMap(x.distribution.pointSet(environment), (pointSet) => ({
|
||||||
name: x.name,
|
name: x.name,
|
||||||
// color: x.color, // not supported yet
|
// color: x.color, // not supported yet
|
||||||
|
@ -77,7 +71,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
|
|
||||||
// if this is a sample set, include the samples
|
// if this is a sample set, include the samples
|
||||||
const samples: number[] = [];
|
const samples: number[] = [];
|
||||||
for (const { distribution } of plot?.distributions) {
|
for (const { distribution } of distributions) {
|
||||||
if (distribution.tag === SqDistributionTag.SampleSet) {
|
if (distribution.tag === SqDistributionTag.SampleSet) {
|
||||||
samples.push(...distribution.value());
|
samples.push(...distribution.value());
|
||||||
}
|
}
|
||||||
|
@ -126,9 +120,9 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
{showSummary && plot.distributions.length === 1 && (
|
{showSummary && distributions.length === 1 && (
|
||||||
<SummaryTable
|
<SummaryTable
|
||||||
distribution={plot.distributions[0].distribution}
|
distribution={distributions[0].distribution}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
||||||
import {
|
import {
|
||||||
DistributionChart,
|
DistributionChart,
|
||||||
DistributionPlottingSettings,
|
DistributionPlottingSettings,
|
||||||
defaultPlot,
|
|
||||||
} from "./DistributionChart";
|
} from "./DistributionChart";
|
||||||
import { NumberShower } from "./NumberShower";
|
import { NumberShower } from "./NumberShower";
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
|
@ -184,7 +183,7 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
||||||
mouseItem.tag === "Ok" &&
|
mouseItem.tag === "Ok" &&
|
||||||
mouseItem.value.tag === SqValueTag.Distribution ? (
|
mouseItem.value.tag === SqValueTag.Distribution ? (
|
||||||
<DistributionChart
|
<DistributionChart
|
||||||
plot={defaultPlot(mouseItem.value.value)}
|
distribution={mouseItem.value.value}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
width={400}
|
width={400}
|
||||||
height={50}
|
height={50}
|
||||||
|
@ -194,7 +193,7 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
||||||
|
|
||||||
let getPercentilesMemoized = React.useMemo(
|
let getPercentilesMemoized = React.useMemo(
|
||||||
() => getPercentiles({ chartSettings, fn, environment }),
|
() => getPercentiles({ chartSettings, fn, environment }),
|
||||||
[environment, fn]
|
[chartSettings, environment, fn]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
|
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
|
||||||
import { NumberShower } from "../NumberShower";
|
import { NumberShower } from "../NumberShower";
|
||||||
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
|
import { DistributionChart } from "../DistributionChart";
|
||||||
import { FunctionChart } from "../FunctionChart";
|
import { FunctionChart } from "../FunctionChart";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { VariableBox } from "./VariableBox";
|
import { VariableBox } from "./VariableBox";
|
||||||
|
@ -104,7 +104,7 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||||
{(settings) => {
|
{(settings) => {
|
||||||
return (
|
return (
|
||||||
<DistributionChart
|
<DistributionChart
|
||||||
plot={defaultPlot(value.value)}
|
distribution={value.value}
|
||||||
environment={settings.environment}
|
environment={settings.environment}
|
||||||
{...settings.distributionPlotSettings}
|
{...settings.distributionPlotSettings}
|
||||||
height={settings.height}
|
height={settings.height}
|
||||||
|
@ -219,15 +219,14 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case SqValueTag.Record:
|
case SqValueTag.Plot:
|
||||||
const plot = makePlot(value.value);
|
const plot = value.value;
|
||||||
if (plot) {
|
|
||||||
return (
|
return (
|
||||||
<VariableBox
|
<VariableBox
|
||||||
value={value}
|
value={value}
|
||||||
heading="Plot"
|
heading="Plot"
|
||||||
renderSettingsMenu={({ onChange }) => {
|
renderSettingsMenu={({ onChange }) => {
|
||||||
let disableLogX = plot.distributions.some((x) => {
|
let disableLogX = plot.getDistributions().some((x) => {
|
||||||
let pointSet = x.distribution.pointSet(
|
let pointSet = x.distribution.pointSet(
|
||||||
getMergedSettings(value.location).environment
|
getMergedSettings(value.location).environment
|
||||||
);
|
);
|
||||||
|
@ -259,7 +258,7 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||||
}}
|
}}
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
} else {
|
case SqValueTag.Record:
|
||||||
return (
|
return (
|
||||||
<VariableList value={value} heading="Record">
|
<VariableList value={value} heading="Record">
|
||||||
{(_) =>
|
{(_) =>
|
||||||
|
@ -275,7 +274,6 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||||
}
|
}
|
||||||
</VariableList>
|
</VariableList>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
case SqValueTag.Array:
|
case SqValueTag.Array:
|
||||||
return (
|
return (
|
||||||
<VariableList value={value} heading="Array">
|
<VariableList value={value} heading="Array">
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
import * as yup from "yup";
|
|
||||||
import {
|
|
||||||
SqValue,
|
|
||||||
SqValueTag,
|
|
||||||
SqDistribution,
|
|
||||||
result,
|
|
||||||
SqRecord,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
|
|
||||||
export type LabeledDistribution = {
|
|
||||||
name: string;
|
|
||||||
distribution: SqDistribution;
|
|
||||||
color?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Plot = {
|
|
||||||
distributions: LabeledDistribution[];
|
|
||||||
};
|
|
||||||
|
|
||||||
function error<a, b>(err: b): result<a, b> {
|
|
||||||
return { tag: "Error", value: err };
|
|
||||||
}
|
|
||||||
|
|
||||||
function ok<a, b>(x: a): result<a, b> {
|
|
||||||
return { tag: "Ok", value: x };
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = yup
|
|
||||||
.object()
|
|
||||||
.noUnknown()
|
|
||||||
.strict()
|
|
||||||
.shape({
|
|
||||||
distributions: yup
|
|
||||||
.array()
|
|
||||||
.required()
|
|
||||||
.of(
|
|
||||||
yup.object().required().shape({
|
|
||||||
name: yup.string().required(),
|
|
||||||
distribution: yup.mixed().required(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
type JsonObject =
|
|
||||||
| string
|
|
||||||
| { [key: string]: JsonObject }
|
|
||||||
| JsonObject[]
|
|
||||||
| SqDistribution;
|
|
||||||
|
|
||||||
function toJson(val: SqValue): JsonObject {
|
|
||||||
if (val.tag === SqValueTag.String) {
|
|
||||||
return val.value;
|
|
||||||
} else if (val.tag === SqValueTag.Record) {
|
|
||||||
return toJsonRecord(val.value);
|
|
||||||
} else if (val.tag === SqValueTag.Array) {
|
|
||||||
return val.value.getValues().map(toJson);
|
|
||||||
} else if (val.tag === SqValueTag.Distribution) {
|
|
||||||
return val.value;
|
|
||||||
} else {
|
|
||||||
throw new Error("Could not parse object of type " + val.tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toJsonRecord(val: SqRecord): JsonObject {
|
|
||||||
let recordObject: JsonObject = {};
|
|
||||||
val.entries().forEach(([key, value]) => (recordObject[key] = toJson(value)));
|
|
||||||
return recordObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parsePlot(record: SqRecord): result<Plot, string> {
|
|
||||||
try {
|
|
||||||
const plotRecord = schema.validateSync(toJsonRecord(record));
|
|
||||||
if (plotRecord.distributions) {
|
|
||||||
return ok({ distributions: plotRecord.distributions.map((x) => x) });
|
|
||||||
} else {
|
|
||||||
// I have no idea why yup's typings thinks this is possible
|
|
||||||
return error("no distributions field. Should never get here");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
const message = e instanceof Error ? e.message : "Unknown error";
|
|
||||||
return error(message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -99,7 +99,7 @@ describe("stacktraces", () => {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
error,
|
error,
|
||||||
)->toBe(`Error: There are function matches for add(), but with different arguments: [add(number, number)]; [add(distribution, number)]; [add(number, distribution)]; [add(distribution, distribution)]; [add(date, duration)]; [add(duration, duration)]
|
)->toBe(`Error: There are function matches for add(), but with different arguments: [add(number, number)]; [add(date, duration)]; [add(duration, duration)]; [add(distribution, number)]; [add(number, distribution)]; [add(distribution, distribution)]
|
||||||
Stack trace:
|
Stack trace:
|
||||||
f at line 4, column 5
|
f at line 4, column 5
|
||||||
g at line 6, column 12
|
g at line 6, column 12
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
open Jest
|
||||||
|
open Reducer_TestHelpers
|
||||||
|
|
||||||
|
describe("Plot Library", () => {
|
||||||
|
testEvalToBe(
|
||||||
|
`Plot.dist({
|
||||||
|
show: [{
|
||||||
|
name: "normal",
|
||||||
|
value: normal(0, 1)
|
||||||
|
}, {
|
||||||
|
name: "lognormal",
|
||||||
|
value: 1 to 2
|
||||||
|
}, {
|
||||||
|
name: "constant",
|
||||||
|
value: 3
|
||||||
|
}]
|
||||||
|
})`,
|
||||||
|
"Ok(Plot showing normal,lognormal,constant)",
|
||||||
|
)
|
||||||
|
})
|
25
packages/squiggle-lang/src/js/SqPlot.ts
Normal file
25
packages/squiggle-lang/src/js/SqPlot.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import * as RSPlot from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Plot.gen";
|
||||||
|
import { SqDistribution, wrapDistribution } from "./SqDistribution";
|
||||||
|
import { SqValueLocation } from "./SqValueLocation";
|
||||||
|
|
||||||
|
type T = RSPlot.squiggleValue_Plot;
|
||||||
|
|
||||||
|
export type LabeledDistribution = {
|
||||||
|
name: string;
|
||||||
|
distribution: SqDistribution;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SqPlot {
|
||||||
|
constructor(private _value: T, public location: SqValueLocation) {}
|
||||||
|
|
||||||
|
getDistributions(): LabeledDistribution[] {
|
||||||
|
return this._value.distributions.map((v: RSPlot.labeledDistribution) => ({
|
||||||
|
...v,
|
||||||
|
distribution: wrapDistribution(v.distribution),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return RSPlot.toString(this._value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import { wrapDistribution } from "./SqDistribution";
|
||||||
import { SqLambda } from "./SqLambda";
|
import { SqLambda } from "./SqLambda";
|
||||||
import { SqLambdaDeclaration } from "./SqLambdaDeclaration";
|
import { SqLambdaDeclaration } from "./SqLambdaDeclaration";
|
||||||
import { SqRecord } from "./SqRecord";
|
import { SqRecord } from "./SqRecord";
|
||||||
|
import { SqPlot } from "./SqPlot";
|
||||||
import { SqArray } from "./SqArray";
|
import { SqArray } from "./SqArray";
|
||||||
import { SqValueLocation } from "./SqValueLocation";
|
import { SqValueLocation } from "./SqValueLocation";
|
||||||
|
|
||||||
|
@ -91,6 +92,14 @@ export class SqNumberValue extends SqAbstractValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SqPlotValue extends SqAbstractValue {
|
||||||
|
tag = Tag.Plot as const;
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return new SqPlot(this.valueMethod(RSValue.getPlot), this.location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class SqRecordValue extends SqAbstractValue {
|
export class SqRecordValue extends SqAbstractValue {
|
||||||
tag = Tag.Record as const;
|
tag = Tag.Record as const;
|
||||||
|
|
||||||
|
@ -131,6 +140,7 @@ const tagToClass = {
|
||||||
[Tag.Distribution]: SqDistributionValue,
|
[Tag.Distribution]: SqDistributionValue,
|
||||||
[Tag.Lambda]: SqLambdaValue,
|
[Tag.Lambda]: SqLambdaValue,
|
||||||
[Tag.Number]: SqNumberValue,
|
[Tag.Number]: SqNumberValue,
|
||||||
|
[Tag.Plot]: SqPlotValue,
|
||||||
[Tag.Record]: SqRecordValue,
|
[Tag.Record]: SqRecordValue,
|
||||||
[Tag.String]: SqStringValue,
|
[Tag.String]: SqStringValue,
|
||||||
[Tag.TimeDuration]: SqTimeDurationValue,
|
[Tag.TimeDuration]: SqTimeDurationValue,
|
||||||
|
@ -148,6 +158,7 @@ export type SqValue =
|
||||||
| SqLambdaValue
|
| SqLambdaValue
|
||||||
| SqNumberValue
|
| SqNumberValue
|
||||||
| SqRecordValue
|
| SqRecordValue
|
||||||
|
| SqPlotValue
|
||||||
| SqStringValue
|
| SqStringValue
|
||||||
| SqTimeDurationValue
|
| SqTimeDurationValue
|
||||||
| SqVoidValue;
|
| SqVoidValue;
|
||||||
|
|
|
@ -6,6 +6,7 @@ export { result } from "../rescript/ForTS/ForTS_Result_tag";
|
||||||
export { SqDistribution, SqDistributionTag } from "./SqDistribution";
|
export { SqDistribution, SqDistributionTag } from "./SqDistribution";
|
||||||
export { SqDistributionError } from "./SqDistributionError";
|
export { SqDistributionError } from "./SqDistributionError";
|
||||||
export { SqRecord } from "./SqRecord";
|
export { SqRecord } from "./SqRecord";
|
||||||
|
export { SqPlot, LabeledDistribution } from "./SqPlot";
|
||||||
export { SqLambda } from "./SqLambda";
|
export { SqLambda } from "./SqLambda";
|
||||||
export { SqProject };
|
export { SqProject };
|
||||||
export { SqValue, SqValueTag };
|
export { SqValue, SqValueTag };
|
||||||
|
@ -14,7 +15,7 @@ export {
|
||||||
defaultEnvironment,
|
defaultEnvironment,
|
||||||
} from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen";
|
} from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen";
|
||||||
export { SqError, SqFrame, SqLocation } from "./SqError";
|
export { SqError, SqFrame, SqLocation } from "./SqError";
|
||||||
export { SqShape } from "./SqPointSetDist";
|
export { SqShape, SqPoint } from "./SqPointSetDist";
|
||||||
|
|
||||||
export { resultMap } from "./types";
|
export { resultMap } from "./types";
|
||||||
|
|
||||||
|
|
146
packages/squiggle-lang/src/rescript/FR/FR_Plot.res
Normal file
146
packages/squiggle-lang/src/rescript/FR/FR_Plot.res
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
open FunctionRegistry_Core
|
||||||
|
open FunctionRegistry_Helpers
|
||||||
|
|
||||||
|
let nameSpace = "Plot"
|
||||||
|
|
||||||
|
module FnApp = {
|
||||||
|
type fnApp<'a> = {
|
||||||
|
result: Reducer_T.value => result<'a, SqError.Message.t>,
|
||||||
|
typeRequired: frType,
|
||||||
|
}
|
||||||
|
|
||||||
|
let fmap = (f: 'a => 'b, m: fnApp<'a>): fnApp<'b> => {
|
||||||
|
{
|
||||||
|
result: (a: Reducer_T.value) => E.R.fmap(f, m.result(a)),
|
||||||
|
typeRequired: m.typeRequired,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module Record = {
|
||||||
|
type t<'a> = {
|
||||||
|
result: Reducer_T.map => result<'a, SqError.Message.t>,
|
||||||
|
typesRequired: array<(string, frType)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let getField = (key: string, parser: fnApp<'a>): t<'a> => {
|
||||||
|
let func = (a: Reducer_T.map) =>
|
||||||
|
switch Belt.Map.String.get(a, key) {
|
||||||
|
| Some(x) => parser.result(x)
|
||||||
|
| None => Error(impossibleError)
|
||||||
|
}
|
||||||
|
{result: func, typesRequired: [(key, parser.typeRequired)]}
|
||||||
|
}
|
||||||
|
|
||||||
|
let merge = (m1: t<'a>, m2: t<'b>): t<('a, 'b)> => {
|
||||||
|
{
|
||||||
|
result: (a: Reducer_T.map) => E.R.merge(m1.result(a), m2.result(a)),
|
||||||
|
typesRequired: Belt.Array.concat(m1.typesRequired, m2.typesRequired),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fmap = (f: 'a => 'b, m: t<'a>): t<'b> => {
|
||||||
|
{
|
||||||
|
result: (a: Reducer_T.map) => E.R.fmap(f, m.result(a)),
|
||||||
|
typesRequired: m.typesRequired,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = (m1: t<'a => 'b>, m2: t<'a>): t<'b> => {
|
||||||
|
{
|
||||||
|
result: (a: Reducer_T.map) =>
|
||||||
|
E.R.merge(m1.result(a), m2.result(a))->E.R2.fmap(((f, x)) => f(x)),
|
||||||
|
typesRequired: Belt.Array.concat(m1.typesRequired, m2.typesRequired),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let getString: fnApp<string> = {
|
||||||
|
let func = (a: Reducer_T.value) =>
|
||||||
|
switch a {
|
||||||
|
| IEvString(s) => Ok(s)
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
{result: func, typeRequired: FRTypeString}
|
||||||
|
}
|
||||||
|
|
||||||
|
let getArray = (child: fnApp<'a>): fnApp<array<'a>> => {
|
||||||
|
let func = (a: Reducer_T.value) =>
|
||||||
|
switch a {
|
||||||
|
| IEvArray(x) => x->E.A2.fmap(child.result)->E.A.R.firstErrorOrOpen
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
{result: func, typeRequired: FRTypeArray(child.typeRequired)}
|
||||||
|
}
|
||||||
|
let getRecord = (recMonad: Record.t<'a>): fnApp<'a> => {
|
||||||
|
let func = (a: Reducer_T.value) =>
|
||||||
|
switch a {
|
||||||
|
| IEvRecord(s) => recMonad.result(s)
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
{result: func, typeRequired: FRTypeRecord(recMonad.typesRequired)}
|
||||||
|
}
|
||||||
|
|
||||||
|
let getDistOrNumber: fnApp<GenericDist.t> = {
|
||||||
|
let func = (a: Reducer_T.value) =>
|
||||||
|
switch a {
|
||||||
|
| IEvDistribution(s) => Ok(s)
|
||||||
|
| IEvNumber(s) => Ok(GenericDist.fromFloat(s))
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
{result: func, typeRequired: FRTypeDistOrNumber}
|
||||||
|
}
|
||||||
|
|
||||||
|
let oneArgDef = (
|
||||||
|
name: string,
|
||||||
|
arg1: fnApp<'a>,
|
||||||
|
def: 'a => result<Reducer_T.value, SqError.Message.t>,
|
||||||
|
): FnDefinition.t =>
|
||||||
|
FnDefinition.make(
|
||||||
|
~name,
|
||||||
|
~inputs=[arg1.typeRequired],
|
||||||
|
~run=(inputs, _, _) => {
|
||||||
|
E.R.bind(arg1.result(inputs[0]), def)
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module Internals = {
|
||||||
|
let makeLabeledDistribution = (
|
||||||
|
name: string,
|
||||||
|
distribution: GenericDist.t,
|
||||||
|
): Reducer_T.labeledDistribution => {name: name, distribution: distribution}
|
||||||
|
|
||||||
|
let getLabeledDistribution: FnApp.fnApp<Reducer_T.labeledDistribution> = {
|
||||||
|
makeLabeledDistribution
|
||||||
|
->FnApp.Record.fmap(FnApp.Record.getField("name", FnApp.getString))
|
||||||
|
->FnApp.Record.app(FnApp.Record.getField("value", FnApp.getDistOrNumber))
|
||||||
|
->FnApp.getRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
let makePlot = (show: array<Reducer_T.labeledDistribution>): Reducer_T.plotValue => {
|
||||||
|
distributions: show,
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsePlotValue: FnApp.fnApp<Reducer_T.plotValue> = {
|
||||||
|
makePlot
|
||||||
|
->FnApp.Record.fmap(FnApp.Record.getField("show", FnApp.getArray(getLabeledDistribution)))
|
||||||
|
->FnApp.getRecord
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let library = [
|
||||||
|
Function.make(
|
||||||
|
~name="dist",
|
||||||
|
~nameSpace,
|
||||||
|
~requiresNamespace=true,
|
||||||
|
~output=EvtPlot,
|
||||||
|
~examples=[
|
||||||
|
`Plot.dist({show: [{name: "Control", value: 1 to 2}, {name: "Treatment", value: 1.5 to 2.5}]}) `,
|
||||||
|
],
|
||||||
|
~definitions=[
|
||||||
|
FnApp.oneArgDef("dist", Internals.parsePlotValue, (a: Reducer_T.plotValue) => Ok(IEvPlot(a))),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
]
|
|
@ -6,6 +6,7 @@ type error = SqError.t //use
|
||||||
type squiggleValue_Declaration = ForTS_SquiggleValue_Declaration.squiggleValue_Declaration //use
|
type squiggleValue_Declaration = ForTS_SquiggleValue_Declaration.squiggleValue_Declaration //use
|
||||||
type squiggleValue_Distribution = ForTS_SquiggleValue_Distribution.squiggleValue_Distribution //use
|
type squiggleValue_Distribution = ForTS_SquiggleValue_Distribution.squiggleValue_Distribution //use
|
||||||
type squiggleValue_Lambda = ForTS_SquiggleValue_Lambda.squiggleValue_Lambda //use
|
type squiggleValue_Lambda = ForTS_SquiggleValue_Lambda.squiggleValue_Lambda //use
|
||||||
|
@genType type squiggleValue_Plot = Reducer_T.plotValue //use
|
||||||
|
|
||||||
// Return values are kept as they are if they are JavaScript types.
|
// Return values are kept as they are if they are JavaScript types.
|
||||||
|
|
||||||
|
@ -30,6 +31,9 @@ external svtLambda_: string = "Lambda"
|
||||||
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
|
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
|
||||||
external svtNumber_: string = "Number"
|
external svtNumber_: string = "Number"
|
||||||
|
|
||||||
|
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
|
||||||
|
external svtPlot_: string = "Plot"
|
||||||
|
|
||||||
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
|
@module("./ForTS_SquiggleValue_tag") @scope("squiggleValueTag")
|
||||||
external svtRecord_: string = "Record"
|
external svtRecord_: string = "Record"
|
||||||
|
|
||||||
|
@ -57,6 +61,7 @@ let getTag = (variant: squiggleValue): squiggleValueTag =>
|
||||||
| IEvDistribution(_) => svtDistribution_->castEnum
|
| IEvDistribution(_) => svtDistribution_->castEnum
|
||||||
| IEvLambda(_) => svtLambda_->castEnum
|
| IEvLambda(_) => svtLambda_->castEnum
|
||||||
| IEvNumber(_) => svtNumber_->castEnum
|
| IEvNumber(_) => svtNumber_->castEnum
|
||||||
|
| IEvPlot(_) => svtPlot_->castEnum
|
||||||
| IEvRecord(_) => svtRecord_->castEnum
|
| IEvRecord(_) => svtRecord_->castEnum
|
||||||
| IEvString(_) => svtString_->castEnum
|
| IEvString(_) => svtString_->castEnum
|
||||||
| IEvTimeDuration(_) => svtTimeDuration_->castEnum
|
| IEvTimeDuration(_) => svtTimeDuration_->castEnum
|
||||||
|
@ -122,6 +127,13 @@ let getNumber = (variant: squiggleValue): option<float> =>
|
||||||
| _ => None
|
| _ => None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@genType
|
||||||
|
let getPlot = (variant: squiggleValue): option<squiggleValue_Plot> =>
|
||||||
|
switch variant {
|
||||||
|
| IEvPlot(value) => value->Some
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
|
||||||
@genType
|
@genType
|
||||||
let getRecord = (variant: squiggleValue): option<squiggleValue_Record> =>
|
let getRecord = (variant: squiggleValue): option<squiggleValue_Record> =>
|
||||||
switch variant {
|
switch variant {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
type squiggleValue = ForTS_SquiggleValue.squiggleValue //use
|
||||||
|
@genType type squiggleValue_Plot = ForTS_SquiggleValue.squiggleValue_Plot //re-export recursive type
|
||||||
|
@genType type labeledDistribution = Reducer_T.labeledDistribution // use
|
||||||
|
|
||||||
|
@genType
|
||||||
|
let toString = (v: squiggleValue_Plot) => Reducer_Value.toStringPlot(v)
|
|
@ -6,6 +6,7 @@ export enum squiggleValueTag {
|
||||||
Distribution = "Distribution",
|
Distribution = "Distribution",
|
||||||
Lambda = "Lambda",
|
Lambda = "Lambda",
|
||||||
Number = "Number",
|
Number = "Number",
|
||||||
|
Plot = "Plot",
|
||||||
Record = "Record",
|
Record = "Record",
|
||||||
String = "String",
|
String = "String",
|
||||||
TimeDuration = "TimeDuration",
|
TimeDuration = "TimeDuration",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
type internalExpressionValueType = Reducer_Value.internalExpressionValueType
|
type internalExpressionValueType = Reducer_Value.internalExpressionValueType
|
||||||
|
let valueTypeToString = Reducer_Value.valueTypeToString
|
||||||
type errorMessage = SqError.Message.t
|
type errorMessage = SqError.Message.t
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
let fnList = Belt.Array.concatMany([
|
let fnList = Belt.Array.concatMany([
|
||||||
FR_Builtin.library,
|
FR_Builtin.library,
|
||||||
|
FR_Danger.library,
|
||||||
|
FR_Date.library,
|
||||||
FR_Dict.library,
|
FR_Dict.library,
|
||||||
FR_Dist.library,
|
FR_Dist.library,
|
||||||
FR_Danger.library,
|
|
||||||
FR_Fn.library,
|
FR_Fn.library,
|
||||||
FR_Sampleset.library,
|
|
||||||
FR_List.library,
|
|
||||||
FR_Number.library,
|
|
||||||
FR_Pointset.library,
|
|
||||||
FR_Scoring.library,
|
|
||||||
FR_GenericDist.library,
|
FR_GenericDist.library,
|
||||||
FR_Units.library,
|
FR_List.library,
|
||||||
FR_Date.library,
|
|
||||||
FR_Math.library,
|
FR_Math.library,
|
||||||
|
FR_Number.library,
|
||||||
|
FR_Plot.library,
|
||||||
|
FR_Pointset.library,
|
||||||
|
FR_Sampleset.library,
|
||||||
|
FR_Scoring.library,
|
||||||
|
FR_Units.library,
|
||||||
])
|
])
|
||||||
|
|
||||||
let registry = FunctionRegistry_Core.Registry.make(fnList)
|
let registry = FunctionRegistry_Core.Registry.make(fnList)
|
||||||
|
|
|
@ -9,10 +9,12 @@ type rec value =
|
||||||
| IEvDistribution(DistributionTypes.genericDist)
|
| IEvDistribution(DistributionTypes.genericDist)
|
||||||
| IEvLambda(lambdaValue)
|
| IEvLambda(lambdaValue)
|
||||||
| IEvNumber(float)
|
| IEvNumber(float)
|
||||||
|
| IEvPlot(plotValue)
|
||||||
| IEvRecord(map)
|
| IEvRecord(map)
|
||||||
| IEvString(string)
|
| IEvString(string)
|
||||||
| IEvTimeDuration(float)
|
| IEvTimeDuration(float)
|
||||||
| IEvVoid
|
| IEvVoid
|
||||||
|
|
||||||
@genType.opaque and arrayValue = array<value>
|
@genType.opaque and arrayValue = array<value>
|
||||||
@genType.opaque and map = Belt.Map.String.t<value>
|
@genType.opaque and map = Belt.Map.String.t<value>
|
||||||
and lambdaBody = (array<value>, context, reducerFn) => value
|
and lambdaBody = (array<value>, context, reducerFn) => value
|
||||||
|
@ -66,4 +68,12 @@ and context = {
|
||||||
|
|
||||||
and reducerFn = (expression, context) => (value, context)
|
and reducerFn = (expression, context) => (value, context)
|
||||||
|
|
||||||
|
@genType and plotValue = {distributions: array<labeledDistribution>}
|
||||||
|
|
||||||
|
@genType
|
||||||
|
and labeledDistribution = {
|
||||||
|
name: string,
|
||||||
|
distribution: DistributionTypes.genericDist,
|
||||||
|
}
|
||||||
|
|
||||||
let topFrameName = "<top>"
|
let topFrameName = "<top>"
|
||||||
|
|
|
@ -14,6 +14,7 @@ let rec toString = (aValue: T.value) =>
|
||||||
| IEvDistribution(dist) => toStringDistribution(dist)
|
| IEvDistribution(dist) => toStringDistribution(dist)
|
||||||
| IEvLambda(lambdaValue) => toStringLambda(lambdaValue)
|
| IEvLambda(lambdaValue) => toStringLambda(lambdaValue)
|
||||||
| IEvNumber(aNumber) => toStringNumber(aNumber)
|
| IEvNumber(aNumber) => toStringNumber(aNumber)
|
||||||
|
| IEvPlot(aPlot) => toStringPlot(aPlot)
|
||||||
| IEvRecord(aMap) => aMap->toStringRecord
|
| IEvRecord(aMap) => aMap->toStringRecord
|
||||||
| IEvString(aString) => toStringString(aString)
|
| IEvString(aString) => toStringString(aString)
|
||||||
| IEvTimeDuration(t) => toStringTimeDuration(t)
|
| IEvTimeDuration(t) => toStringTimeDuration(t)
|
||||||
|
@ -35,6 +36,10 @@ and toStringLambda = (lambdaValue: T.lambdaValue) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
and toStringNumber = aNumber => Js.String.make(aNumber)
|
and toStringNumber = aNumber => Js.String.make(aNumber)
|
||||||
|
and toStringPlot = aPlot => {
|
||||||
|
let chartNames = E.A.fmap((x: Reducer_T.labeledDistribution) => x.name, aPlot.distributions)
|
||||||
|
`Plot showing ${Js.Array2.toString(chartNames)}`
|
||||||
|
}
|
||||||
and toStringRecord = aMap => aMap->toStringMap
|
and toStringRecord = aMap => aMap->toStringMap
|
||||||
and toStringString = aString => `'${aString}'`
|
and toStringString = aString => `'${aString}'`
|
||||||
and toStringSymbol = aString => `:${aString}`
|
and toStringSymbol = aString => `:${aString}`
|
||||||
|
@ -59,6 +64,7 @@ let toStringWithType = (aValue: T.value) =>
|
||||||
| IEvDistribution(_) => `Distribution::${toString(aValue)}`
|
| IEvDistribution(_) => `Distribution::${toString(aValue)}`
|
||||||
| IEvLambda(_) => `Lambda::${toString(aValue)}`
|
| IEvLambda(_) => `Lambda::${toString(aValue)}`
|
||||||
| IEvNumber(_) => `Number::${toString(aValue)}`
|
| IEvNumber(_) => `Number::${toString(aValue)}`
|
||||||
|
| IEvPlot(_) => `Plot::${toString(aValue)}`
|
||||||
| IEvRecord(_) => `Record::${toString(aValue)}`
|
| IEvRecord(_) => `Record::${toString(aValue)}`
|
||||||
| IEvString(_) => `String::${toString(aValue)}`
|
| IEvString(_) => `String::${toString(aValue)}`
|
||||||
| IEvTimeDuration(_) => `Date::${toString(aValue)}`
|
| IEvTimeDuration(_) => `Date::${toString(aValue)}`
|
||||||
|
@ -91,6 +97,7 @@ type internalExpressionValueType =
|
||||||
| EvtDistribution
|
| EvtDistribution
|
||||||
| EvtLambda
|
| EvtLambda
|
||||||
| EvtNumber
|
| EvtNumber
|
||||||
|
| EvtPlot
|
||||||
| EvtRecord
|
| EvtRecord
|
||||||
| EvtString
|
| EvtString
|
||||||
| EvtTimeDuration
|
| EvtTimeDuration
|
||||||
|
@ -109,6 +116,7 @@ let valueToValueType = (value: T.value) =>
|
||||||
| IEvDistribution(_) => EvtDistribution
|
| IEvDistribution(_) => EvtDistribution
|
||||||
| IEvLambda(_) => EvtLambda
|
| IEvLambda(_) => EvtLambda
|
||||||
| IEvNumber(_) => EvtNumber
|
| IEvNumber(_) => EvtNumber
|
||||||
|
| IEvPlot(_) => EvtPlot
|
||||||
| IEvRecord(_) => EvtRecord
|
| IEvRecord(_) => EvtRecord
|
||||||
| IEvString(_) => EvtString
|
| IEvString(_) => EvtString
|
||||||
| IEvTimeDuration(_) => EvtTimeDuration
|
| IEvTimeDuration(_) => EvtTimeDuration
|
||||||
|
@ -129,6 +137,7 @@ let valueTypeToString = (valueType: internalExpressionValueType): string =>
|
||||||
| EvtDistribution => `Distribution`
|
| EvtDistribution => `Distribution`
|
||||||
| EvtLambda => `Lambda`
|
| EvtLambda => `Lambda`
|
||||||
| EvtNumber => `Number`
|
| EvtNumber => `Number`
|
||||||
|
| EvtPlot => `Plot`
|
||||||
| EvtRecord => `Record`
|
| EvtRecord => `Record`
|
||||||
| EvtString => `String`
|
| EvtString => `String`
|
||||||
| EvtTimeDuration => `Duration`
|
| EvtTimeDuration => `Duration`
|
||||||
|
|
19
packages/website/docs/Api/Plot.md
Normal file
19
packages/website/docs/Api/Plot.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
sidebar_position: 8
|
||||||
|
title: Plot
|
||||||
|
---
|
||||||
|
|
||||||
|
Plot objects can be created to make plots of different kinds. If you wish to plot
|
||||||
|
multiple distributions simultaneously, you can use `Plot.dist`.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
### dist
|
||||||
|
|
||||||
|
```
|
||||||
|
Plot.dist({show: list({name: string, value: distribution|number})})
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
Plot.dist({show: [{name: "normal", value: normal(0, 1)}, {name: "lognormal", value: 2 to 3}]})
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user