Add plot function
This commit is contained in:
		
							parent
							
								
									eaa7d38428
								
							
						
					
					
						commit
						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,63 +219,61 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | ||||||
|         </VariableBox> |         </VariableBox> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |     case SqValueTag.Plot: | ||||||
|  |       const plot = value.value; | ||||||
|  |       return ( | ||||||
|  |         <VariableBox | ||||||
|  |           value={value} | ||||||
|  |           heading="Plot" | ||||||
|  |           renderSettingsMenu={({ onChange }) => { | ||||||
|  |             let disableLogX = plot.getDistributions().some((x) => { | ||||||
|  |               let pointSet = x.distribution.pointSet( | ||||||
|  |                 getMergedSettings(value.location).environment | ||||||
|  |               ); | ||||||
|  |               return ( | ||||||
|  |                 pointSet.tag === "Ok" && | ||||||
|  |                 hasMassBelowZero(pointSet.value.asShape()) | ||||||
|  |               ); | ||||||
|  |             }); | ||||||
|  |             return ( | ||||||
|  |               <ItemSettingsMenu | ||||||
|  |                 value={value} | ||||||
|  |                 onChange={onChange} | ||||||
|  |                 disableLogX={disableLogX} | ||||||
|  |                 withFunctionSettings={false} | ||||||
|  |               /> | ||||||
|  |             ); | ||||||
|  |           }} | ||||||
|  |         > | ||||||
|  |           {(settings) => { | ||||||
|  |             return ( | ||||||
|  |               <DistributionChart | ||||||
|  |                 plot={plot} | ||||||
|  |                 environment={settings.environment} | ||||||
|  |                 {...settings.distributionPlotSettings} | ||||||
|  |                 height={settings.height} | ||||||
|  |                 width={width} | ||||||
|  |               /> | ||||||
|  |             ); | ||||||
|  |           }} | ||||||
|  |         </VariableBox> | ||||||
|  |       ); | ||||||
|     case SqValueTag.Record: |     case SqValueTag.Record: | ||||||
|       const plot = makePlot(value.value); |       return ( | ||||||
|       if (plot) { |         <VariableList value={value} heading="Record"> | ||||||
|         return ( |           {(_) => | ||||||
|           <VariableBox |             value.value | ||||||
|             value={value} |               .entries() | ||||||
|             heading="Plot" |               .map(([key, r]) => ( | ||||||
|             renderSettingsMenu={({ onChange }) => { |                 <ExpressionViewer | ||||||
|               let disableLogX = plot.distributions.some((x) => { |                   key={key} | ||||||
|                 let pointSet = x.distribution.pointSet( |                   value={r} | ||||||
|                   getMergedSettings(value.location).environment |                   width={width !== undefined ? width - 20 : width} | ||||||
|                 ); |  | ||||||
|                 return ( |  | ||||||
|                   pointSet.tag === "Ok" && |  | ||||||
|                   hasMassBelowZero(pointSet.value.asShape()) |  | ||||||
|                 ); |  | ||||||
|               }); |  | ||||||
|               return ( |  | ||||||
|                 <ItemSettingsMenu |  | ||||||
|                   value={value} |  | ||||||
|                   onChange={onChange} |  | ||||||
|                   disableLogX={disableLogX} |  | ||||||
|                   withFunctionSettings={false} |  | ||||||
|                 /> |                 /> | ||||||
|               ); |               )) | ||||||
|             }} |           } | ||||||
|           > |         </VariableList> | ||||||
|             {(settings) => { |       ); | ||||||
|               return ( |  | ||||||
|                 <DistributionChart |  | ||||||
|                   plot={plot} |  | ||||||
|                   environment={settings.environment} |  | ||||||
|                   {...settings.distributionPlotSettings} |  | ||||||
|                   height={settings.height} |  | ||||||
|                   width={width} |  | ||||||
|                 /> |  | ||||||
|               ); |  | ||||||
|             }} |  | ||||||
|           </VariableBox> |  | ||||||
|         ); |  | ||||||
|       } else { |  | ||||||
|         return ( |  | ||||||
|           <VariableList value={value} heading="Record"> |  | ||||||
|             {(_) => |  | ||||||
|               value.value |  | ||||||
|                 .entries() |  | ||||||
|                 .map(([key, r]) => ( |  | ||||||
|                   <ExpressionViewer |  | ||||||
|                     key={key} |  | ||||||
|                     value={r} |  | ||||||
|                     width={width !== undefined ? width - 20 : width} |  | ||||||
|                   /> |  | ||||||
|                 )) |  | ||||||
|             } |  | ||||||
|           </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); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										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"; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										103
									
								
								packages/squiggle-lang/src/rescript/FR/FR_Plot.res
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								packages/squiggle-lang/src/rescript/FR/FR_Plot.res
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,103 @@ | ||||||
|  | open FunctionRegistry_Core | ||||||
|  | open FunctionRegistry_Helpers | ||||||
|  | 
 | ||||||
|  | let nameSpace = "Plot" | ||||||
|  | 
 | ||||||
|  | module Internals = { | ||||||
|  |   let parseString = (a: Reducer_T.value): result<string, SqError.Message.t> => { | ||||||
|  |     switch a { | ||||||
|  |     | IEvString(s) => Ok(s) | ||||||
|  |     | _ => Error(SqError.Message.REOther("Expected to be a string")) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let parseDistributionOrNumber = (a: Reducer_T.value): result< | ||||||
|  |     GenericDist.t, | ||||||
|  |     SqError.Message.t, | ||||||
|  |   > => { | ||||||
|  |     switch a { | ||||||
|  |     | IEvDistribution(s) => Ok(s) | ||||||
|  |     | IEvNumber(s) => Ok(GenericDist.fromFloat(s)) | ||||||
|  |     | _ => Error(SqError.Message.REOther("Expected to be a distribution")) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let parseArray = ( | ||||||
|  |     parser: Reducer_T.value => result<'a, SqError.Message.t>, | ||||||
|  |     a: Reducer_T.value, | ||||||
|  |   ): result<array<'a>, SqError.Message.t> => { | ||||||
|  |     switch a { | ||||||
|  |     | IEvArray(x) => x->E.A2.fmap(parser)->E.A.R.firstErrorOrOpen | ||||||
|  |     | _ => Error(SqError.Message.REOther("Expected to be an array")) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let parseRecord = ( | ||||||
|  |     parser: Reducer_T.map => result<'b, SqError.Message.t>, | ||||||
|  |     a: Reducer_T.value, | ||||||
|  |   ): result<'b, SqError.Message.t> => { | ||||||
|  |     switch a { | ||||||
|  |     | IEvRecord(x) => parser(x) | ||||||
|  |     | _ => Error(SqError.Message.REOther("Expected to be an array")) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let parseField = ( | ||||||
|  |     a: Reducer_T.map, | ||||||
|  |     key: string, | ||||||
|  |     parser: Reducer_T.value => result<'a, SqError.Message.t>, | ||||||
|  |   ): result<'a, SqError.Message.t> => { | ||||||
|  |     switch Belt.Map.String.get(a, key) { | ||||||
|  |     | Some(x) => parser(x) | ||||||
|  |     | None => Error(SqError.Message.REOther("expected field " ++ key ++ " in plot dictionary.")) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let parseLabeledDistribution = (a: Reducer_T.map): result< | ||||||
|  |     Reducer_T.labeledDistribution, | ||||||
|  |     SqError.Message.t, | ||||||
|  |   > => { | ||||||
|  |     let name = parseField(a, "name", parseString) | ||||||
|  |     let distribution = parseField(a, "value", parseDistributionOrNumber) | ||||||
|  |     switch E.R.merge(name, distribution) { | ||||||
|  |     | Ok(name, distribution) => Ok({name: name, distribution: distribution}) | ||||||
|  |     | Error(err) => Error(err) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let parsePlotValue = (a: Reducer_T.map): result<Reducer_T.plotValue, SqError.Message.t> => { | ||||||
|  |     parseField(a, "show", parseArray(parseRecord(parseLabeledDistribution)))->E.R2.fmap(dists => { | ||||||
|  |       let plot: Reducer_T.plotValue = {distributions: dists} | ||||||
|  |       plot | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let dist = (a: Reducer_T.map): result<Reducer_T.value, SqError.Message.t> => | ||||||
|  |     E.R2.fmap(parsePlotValue(a), x => Reducer_T.IEvPlot(x)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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=[ | ||||||
|  |       FnDefinition.make( | ||||||
|  |         ~name="dist", | ||||||
|  |         ~inputs=[FRTypeDict(FRTypeAny)], | ||||||
|  |         ~run=(inputs, _, _) => { | ||||||
|  |           switch inputs { | ||||||
|  |           | [IEvRecord(plot)] => Internals.dist(plot) | ||||||
|  |           | _ => impossibleError->Error | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         (), | ||||||
|  |       ), | ||||||
|  |     ], | ||||||
|  |     (), | ||||||
|  |   ), | ||||||
|  | ] | ||||||
|  | @ -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,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` | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user