diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 6d4e7c55..7556da26 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -17,6 +17,8 @@ import { DistributionChartSpecOptions, } from "../lib/distributionSpecBuilder"; import { NumberShower } from "./NumberShower"; +import { Plot, parsePlot } from "../lib/plotting"; +import { flattenResult, all } from "../lib/utility"; export type DistributionPlottingSettings = { /** Whether to show a summary of means, stdev, percentiles etc */ @@ -25,12 +27,6 @@ export type DistributionPlottingSettings = { showControls: boolean; } & DistributionChartSpecOptions; -export type LabeledDistribution = { name: string; distribution: Distribution }; - -export type Plot = { - distributions: LabeledDistribution[]; -}; - export type DistributionChartProps = { plot: Plot; width?: number; @@ -42,99 +38,6 @@ export function defaultPlot(distribution: Distribution): Plot { return { distributions: [{ name: "default", distribution }] }; } -function error(err: b): result { - return { tag: "Error", value: err }; -} - -function ok(x: a): result { - return { tag: "Ok", value: x }; -} - -function parseString(expr: squiggleExpression): result { - if (expr.tag === "string") { - return ok(expr.value); - } else { - return error("Expression was not string"); - } -} - -function parseRecord( - expr: squiggleExpression -): result<{ [key: string]: squiggleExpression }, string> { - if (expr.tag === "record") { - return ok(expr.value); - } else { - return error("Expression was not a record"); - } -} - -function parseDistribution( - expr: squiggleExpression -): result { - if (expr.tag === "distribution") { - return ok(expr.value); - } else { - return error("Expression was not a distribution"); - } -} - -function parseArray( - expr: squiggleExpression -): result { - if (expr.tag === "array") { - return ok(expr.value); - } else { - return error("Expression was not a distribution"); - } -} - -function parseField( - record: { [key: string]: squiggleExpression }, - field: string, - parser: (expr: squiggleExpression) => result -): result { - if (record[field]) { - return parser(record[field]); - } else { - return error("record does not have field " + field); - } -} - -function resultBind( - x: result, - fn: (y: a) => result -): result { - if (x.tag === "Ok") { - return fn(x.value); - } else { - return x; - } -} - -function parseLabeledDistribution( - x: squiggleExpression -): result { - return resultBind(parseRecord(x), (record) => - resultBind(parseField(record, "name", parseString), (name) => - resultBind( - parseField(record, "distribution", parseDistribution), - (distribution) => ok({ name, distribution }) - ) - ) - ); -} - -function parsePlot(record: { - [key: string]: squiggleExpression; -}): result { - return resultBind(parseField(record, "distributions", parseArray), (array) => - resultBind( - flattenResult(array.map(parseLabeledDistribution)), - (distributions) => ok({ distributions }) - ) - ); -} - export function makePlot(record: { [key: string]: squiggleExpression; }): Plot | void { @@ -143,26 +46,6 @@ export function makePlot(record: { return plotResult.value; } } -function all(arr: boolean[]): boolean { - return arr.reduce((x, y) => x && y, true); -} - -function flattenResult(x: result[]): result { - if (x.length === 0) { - return { tag: "Ok", value: [] }; - } else { - if (x[0].tag === "Error") { - return x[0]; - } else { - let rest = flattenResult(x.splice(1)); - if (rest.tag === "Error") { - return rest; - } else { - return { tag: "Ok", value: [x[0].value].concat(rest.value) }; - } - } - } -} export const DistributionChart: React.FC = (props) => { const { diff --git a/packages/components/src/lib/plotting.ts b/packages/components/src/lib/plotting.ts new file mode 100644 index 00000000..5b7ca31d --- /dev/null +++ b/packages/components/src/lib/plotting.ts @@ -0,0 +1,90 @@ +import { Distribution, result, squiggleExpression } from "@quri/squiggle-lang"; +import { flattenResult, resultBind } from "./utility"; + +export type LabeledDistribution = { name: string; distribution: Distribution }; + +export type Plot = { + distributions: LabeledDistribution[]; +}; + +function error(err: b): result { + return { tag: "Error", value: err }; +} + +function ok(x: a): result { + return { tag: "Ok", value: x }; +} + +function parseString(expr: squiggleExpression): result { + if (expr.tag === "string") { + return ok(expr.value); + } else { + return error("Expression was not string"); + } +} + +function parseRecord( + expr: squiggleExpression +): result<{ [key: string]: squiggleExpression }, string> { + if (expr.tag === "record") { + return ok(expr.value); + } else { + return error("Expression was not a record"); + } +} + +function parseDistribution( + expr: squiggleExpression +): result { + if (expr.tag === "distribution") { + return ok(expr.value); + } else { + return error("Expression was not a distribution"); + } +} + +function parseArray( + expr: squiggleExpression +): result { + if (expr.tag === "array") { + return ok(expr.value); + } else { + return error("Expression was not a distribution"); + } +} + +function parseField( + record: { [key: string]: squiggleExpression }, + field: string, + parser: (expr: squiggleExpression) => result +): result { + if (record[field]) { + return parser(record[field]); + } else { + return error("record does not have field " + field); + } +} + +function parseLabeledDistribution( + x: squiggleExpression +): result { + return resultBind(parseRecord(x), (record) => + resultBind(parseField(record, "name", parseString), (name) => + resultBind( + parseField(record, "distribution", parseDistribution), + (distribution) => ok({ name, distribution }) + ) + ) + ); +} + +export function parsePlot(record: { + [key: string]: squiggleExpression; +}): result { + return resultBind(parseField(record, "distributions", parseArray), (array) => + resultBind( + flattenResult(array.map(parseLabeledDistribution)), + (distributions) => ok({ distributions }) + ) + ); +} diff --git a/packages/components/src/lib/utility.ts b/packages/components/src/lib/utility.ts new file mode 100644 index 00000000..4a5ecc6b --- /dev/null +++ b/packages/components/src/lib/utility.ts @@ -0,0 +1,33 @@ +import { result } from "@quri/squiggle-lang"; + +export function flattenResult(x: result[]): result { + if (x.length === 0) { + return { tag: "Ok", value: [] }; + } else { + if (x[0].tag === "Error") { + return x[0]; + } else { + let rest = flattenResult(x.splice(1)); + if (rest.tag === "Error") { + return rest; + } else { + return { tag: "Ok", value: [x[0].value].concat(rest.value) }; + } + } + } +} + +export function resultBind( + x: result, + fn: (y: a) => result +): result { + if (x.tag === "Ok") { + return fn(x.value); + } else { + return x; + } +} + +export function all(arr: boolean[]): boolean { + return arr.reduce((x, y) => x && y, true); +}