diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 961178ff..af644d29 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -5,18 +5,15 @@ import { distributionError, distributionErrorToString, } from "@quri/squiggle-lang"; -import { Vega, VisualizationSpec } from "react-vega"; -import * as chartSpecification from "../vega-specs/spec-distributions.json"; +import { Vega } from "react-vega"; import { ErrorAlert } from "./Alert"; import { useSize } from "react-use"; import clsx from "clsx"; import { - linearXScale, - logXScale, - linearYScale, - expYScale, -} from "./DistributionVegaScales"; + buildVegaSpec, + DistributionChartSpecOptions, +} from "../lib/distributionSpecBuilder"; import { NumberShower } from "./NumberShower"; export type DistributionPlottingSettings = { @@ -24,27 +21,26 @@ export type DistributionPlottingSettings = { showSummary: boolean; /** Whether to show the user graph controls (scale etc) */ showControls: boolean; - /** Set the x scale to be logarithmic by deault */ - logX: boolean; - /** Set the y scale to be exponential by deault */ - expY: boolean; -}; +} & DistributionChartSpecOptions; export type DistributionChartProps = { distribution: Distribution; width?: number; height: number; + actions?: boolean; } & DistributionPlottingSettings; -export const DistributionChart: React.FC = ({ - distribution, - height, - showSummary, - width, - showControls, - logX, - expY, -}) => { +export const DistributionChart: React.FC = (props) => { + const { + distribution, + height, + showSummary, + width, + showControls, + logX, + expY, + actions = false, + } = props; const [isLogX, setLogX] = React.useState(logX); const [isExpY, setExpY] = React.useState(expY); @@ -64,7 +60,7 @@ export const DistributionChart: React.FC = ({ const massBelow0 = shape.value.continuous.some((x) => x.x <= 0) || shape.value.discrete.some((x) => x.x <= 0); - const spec = buildVegaSpec(isLogX, isExpY); + const spec = buildVegaSpec(props); let widthProp = width ? width : size.width; if (widthProp < 20) { @@ -82,7 +78,7 @@ export const DistributionChart: React.FC = ({ data={{ con: shape.value.continuous, dis: shape.value.discrete }} width={widthProp - 10} height={height} - actions={false} + actions={actions} /> ) : ( @@ -116,16 +112,6 @@ export const DistributionChart: React.FC = ({ return sized; }; -function buildVegaSpec(isLogX: boolean, isExpY: boolean): VisualizationSpec { - return { - ...chartSpecification, - scales: [ - isLogX ? logXScale : linearXScale, - isExpY ? expYScale : linearYScale, - ], - } as VisualizationSpec; -} - interface CheckBoxProps { label: string; onChange: (x: boolean) => void; diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index bfb69a4e..f9f2b368 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -44,6 +44,18 @@ export interface SquiggleChartProps { logX?: boolean; /** Set the y scale to be exponential by deault */ expY?: boolean; + /** How to format numbers on the x axis */ + tickFormat?: string; + /** Title of the graphed distribution */ + title?: string; + /** Color of the graphed distribution */ + color?: string; + /** Specify the lower bound of the x scale */ + minX?: number; + /** Specify the upper bound of the x scale */ + maxX?: number; + /** Whether to show vega actions to the user, so they can copy the chart spec */ + distributionChartActions?: boolean; } const defaultOnChange = () => {}; @@ -65,6 +77,12 @@ export const SquiggleChart: React.FC = React.memo( diagramStart = 0, diagramStop = 10, diagramCount = 100, + tickFormat, + minX, + maxX, + color, + title, + distributionChartActions, }) => { const result = useSquiggle({ code, @@ -83,6 +101,12 @@ export const SquiggleChart: React.FC = React.memo( showSummary, logX, expY, + format: tickFormat, + minX, + maxX, + color, + title, + actions: distributionChartActions, }; let chartSettings = { diff --git a/packages/components/src/components/SquigglePlayground.tsx b/packages/components/src/components/SquigglePlayground.tsx index 301d8892..534be721 100644 --- a/packages/components/src/components/SquigglePlayground.tsx +++ b/packages/components/src/components/SquigglePlayground.tsx @@ -18,7 +18,7 @@ import clsx from "clsx"; import { defaultBindings, environment } from "@quri/squiggle-lang"; -import { SquiggleChart } from "./SquiggleChart"; +import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; import { CodeEditor } from "./CodeEditor"; import { JsonEditor } from "./JsonEditor"; import { ErrorAlert, SuccessAlert } from "./Alert"; @@ -27,28 +27,16 @@ import { Toggle } from "./ui/Toggle"; import { Checkbox } from "./ui/Checkbox"; import { StyledTab } from "./ui/StyledTab"; -interface PlaygroundProps { +type PlaygroundProps = SquiggleChartProps & { /** The initial squiggle string to put in the playground */ defaultCode?: string; /** How many pixels high is the playground */ - height?: number; - /** Whether to show the types of outputs in the playground */ - showTypes?: boolean; - /** Whether to show the log scale controls in the playground */ - showControls?: boolean; - /** Whether to show the summary table in the playground */ - showSummary?: boolean; - /** Whether to log the x coordinate on distribution charts */ - logX?: boolean; - /** Whether to exp the y coordinate on distribution charts */ - expY?: boolean; - /** If code is set, component becomes controlled */ - code?: string; onCodeChange?(expr: string): void; + /* When settings change */ onSettingsChange?(settings: any): void; /** Should we show the editor? */ showEditor?: boolean; -} +}; const schema = yup.object({}).shape({ sampleCount: yup @@ -82,6 +70,12 @@ const schema = yup.object({}).shape({ showEditor: yup.boolean().required(), logX: yup.boolean().required(), expY: yup.boolean().required(), + tickFormat: yup.string().default(".9~s"), + title: yup.string(), + color: yup.string().default("#739ECC").required(), + minX: yup.number(), + maxX: yup.number(), + distributionChartActions: yup.boolean(), showSettingsPage: yup.boolean().default(false), diagramStart: yup.number().required().positive().integer().default(0).min(0), diagramStop: yup.number().required().positive().integer().default(10).min(0), @@ -114,7 +108,7 @@ function InputItem({ }: { name: Path; label: string; - type: "number"; + type: "number" | "text" | "color"; register: UseFormRegister; }) { return ( @@ -122,7 +116,7 @@ function InputItem({
{label}
@@ -202,6 +196,11 @@ const ViewSettings: React.FC<{ register: UseFormRegister }> = ({ name="expY" label="Show y scale exponentially" /> + }> = ({ name="showSummary" label="Show summary statistics" /> + + + + + @@ -385,6 +414,12 @@ export const SquigglePlayground: FC = ({ showSummary = false, logX = false, expY = false, + title, + minX, + maxX, + color = "#739ECC", + tickFormat = ".9~s", + distributionChartActions, code: controlledCode, onCodeChange, onSettingsChange, @@ -408,6 +443,12 @@ export const SquigglePlayground: FC = ({ showControls, logX, expY, + title, + minX, + maxX, + color, + tickFormat, + distributionChartActions, showSummary, showEditor, leftSizePercent: 50, @@ -440,15 +481,7 @@ export const SquigglePlayground: FC = ({ @@ -496,6 +529,7 @@ export const SquigglePlayground: FC = ({ const withoutEditor =
{tabs}
; + console.log(vars); return ( diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts new file mode 100644 index 00000000..4286dbdb --- /dev/null +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -0,0 +1,256 @@ +import { VisualizationSpec } from "react-vega"; +import type { LogScale, LinearScale, PowScale } from "vega"; + +export type DistributionChartSpecOptions = { + /** Set the x scale to be logarithmic by deault */ + logX: boolean; + /** Set the y scale to be exponential by deault */ + expY: boolean; + /** The minimum x coordinate shown on the chart */ + minX?: number; + /** The maximum x coordinate shown on the chart */ + maxX?: number; + /** The color of the chart */ + color?: string; + /** The title of the chart */ + title?: string; + /** The formatting of the ticks */ + format?: string; +}; + +export let linearXScale: LinearScale = { + name: "xscale", + clamp: true, + type: "linear", + range: "width", + zero: false, + nice: false, + domain: { + fields: [ + { + data: "con", + field: "x", + }, + { + data: "dis", + field: "x", + }, + ], + }, +}; +export let linearYScale: LinearScale = { + name: "yscale", + type: "linear", + range: "height", + zero: false, + domain: { + fields: [ + { + data: "con", + field: "y", + }, + { + data: "dis", + field: "y", + }, + ], + }, +}; + +export let logXScale: LogScale = { + name: "xscale", + type: "log", + range: "width", + zero: false, + base: 10, + nice: false, + clamp: true, + domain: { + fields: [ + { + data: "con", + field: "x", + }, + { + data: "dis", + field: "x", + }, + ], + }, +}; + +export let expYScale: PowScale = { + name: "yscale", + type: "pow", + exponent: 0.1, + range: "height", + zero: false, + nice: false, + domain: { + fields: [ + { + data: "con", + field: "y", + }, + { + data: "dis", + field: "y", + }, + ], + }, +}; + +export function buildVegaSpec( + specOptions: DistributionChartSpecOptions +): VisualizationSpec { + let { + format = ".9~s", + color = "#739ECC", + title, + minX, + maxX, + logX, + expY, + } = specOptions; + + let xScale = logX ? logXScale : linearXScale; + if (minX !== undefined && Number.isFinite(minX)) { + xScale = { ...xScale, domainMin: minX }; + } + + if (maxX !== undefined && Number.isFinite(maxX)) { + xScale = { ...xScale, domainMax: maxX }; + } + + let spec: VisualizationSpec = { + $schema: "https://vega.github.io/schema/vega/v5.json", + description: "A basic area chart example", + width: 500, + height: 100, + padding: 5, + data: [ + { + name: "con", + }, + { + name: "dis", + }, + ], + signals: [], + scales: [xScale, expY ? expYScale : linearYScale], + axes: [ + { + orient: "bottom", + scale: "xscale", + labelColor: "#727d93", + tickColor: "#fff", + tickOpacity: 0.0, + domainColor: "#fff", + domainOpacity: 0.0, + format: format, + tickCount: 10, + }, + ], + marks: [ + { + type: "area", + from: { + data: "con", + }, + encode: { + update: { + interpolate: { value: "linear" }, + x: { + scale: "xscale", + field: "x", + }, + y: { + scale: "yscale", + field: "y", + }, + y2: { + scale: "yscale", + value: 0, + }, + fill: { + value: color, + }, + fillOpacity: { + value: 1, + }, + }, + }, + }, + { + type: "rect", + from: { + data: "dis", + }, + encode: { + enter: { + width: { + value: 1, + }, + }, + update: { + x: { + scale: "xscale", + field: "x", + }, + y: { + scale: "yscale", + field: "y", + }, + y2: { + scale: "yscale", + value: 0, + }, + fill: { + value: "#2f65a7", + }, + }, + }, + }, + { + type: "symbol", + from: { + data: "dis", + }, + encode: { + enter: { + shape: { + value: "circle", + }, + size: [{ value: 100 }], + tooltip: { + signal: "datum.y", + }, + }, + update: { + x: { + scale: "xscale", + field: "x", + }, + y: { + scale: "yscale", + field: "y", + }, + fill: { + value: "#1e4577", + }, + }, + }, + }, + ], + }; + if (title) { + spec = { + ...spec, + title: { + text: title, + }, + }; + } + + return spec; +} diff --git a/packages/components/src/stories/SquiggleChart.stories.mdx b/packages/components/src/stories/SquiggleChart.stories.mdx index 2483c985..2febfb6f 100644 --- a/packages/components/src/stories/SquiggleChart.stories.mdx +++ b/packages/components/src/stories/SquiggleChart.stories.mdx @@ -3,7 +3,7 @@ import { Canvas, Meta, Story, Props } from "@storybook/addon-docs"; -export const Template = SquiggleChart; +export const Template = (props) => ; /* We have to hardcode a width here, because otherwise some interaction with Storybook creates an infinite loop with the internal width diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Module/Reducer_Module_defineFFI_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Module/Reducer_Module_test.res similarity index 83% rename from packages/squiggle-lang/__tests__/Reducer/Reducer_Module/Reducer_Module_defineFFI_test.res rename to packages/squiggle-lang/__tests__/Reducer/Reducer_Module/Reducer_Module_test.res index a06c7a57..69fca252 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Module/Reducer_Module_defineFFI_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Module/Reducer_Module_test.res @@ -22,6 +22,13 @@ module FooImplementation = { let makeFoo = (a: string, b: string, _environment): string => `I am ${a}-foo and I am ${b}-foo` let makeBar = (a: float, b: float, _environment): string => `I am ${a->Js.Float.toString}-bar and I am ${b->Js.Float.toString}-bar` + // You can also define functions that has their internal errors + let makeReturningError = (_a: float, _b: float, _environment): result => + if false { + 0.->Ok + } else { + ErrorValue.RETodo("test error")->Error + } } // There is a potential for type modules to define lift functions @@ -38,6 +45,17 @@ module FooFFI = { | [IEvNumber(a), IEvNumber(b)] => FooImplementation.makeBar(a, b, environment)->IEvString->Some | _ => None } + let makeReturningError: ExpressionT.optionFfiFnReturningResult = ( + args: array, + environment, + ) => + switch args { + | [IEvNumber(a), IEvNumber(b)] => + FooImplementation.makeReturningError(a, b, environment) + ->Belt.Result.map(v => v->InternalExpressionValue.IEvNumber) + ->Some + | _ => None + } } let fooModule: Module.t = @@ -47,6 +65,7 @@ let fooModule: Module.t = ->Module.defineBool("fooBool", FooImplementation.fooBool) ->Module.defineFunction("makeFoo", FooFFI.makeFoo) ->Module.defineFunction("makeBar", FooFFI.makeBar) + ->Module.defineFunctionReturningResult("makeReturningError", FooFFI.makeReturningError) let makeBindings = (prevBindings: Bindings.t): Bindings.t => prevBindings->Module.defineModule("Foo", fooModule) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res index dee70c88..bf4d0170 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res @@ -72,6 +72,10 @@ type ffiFn = ( ) => result type optionFfiFn = (array, environment) => option +type optionFfiFnReturningResult = ( + array, + environment, +) => option> type expressionOrFFI = | NotFFI(expression) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module.res index 62f9d8c7..34c304f9 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Module/Reducer_Module.res @@ -126,6 +126,17 @@ let functionNotFoundErrorFFIFn = (functionName: string): ExpressionT.ffiFn => { } } +let convertOptionToFfiFnReturningResult = ( + myFunctionName: string, + myFunction: ExpressionT.optionFfiFnReturningResult, +): ExpressionT.ffiFn => { + (args: array, environment) => { + myFunction(args, environment)->Belt.Option.getWithDefault( + functionNotFoundErrorFFIFn(myFunctionName)(args, environment), + ) + } +} + let convertOptionToFfiFn = ( myFunctionName: string, myFunction: ExpressionT.optionFfiFn, @@ -159,4 +170,15 @@ let defineFunction = (nameSpace: t, identifier: string, value: ExpressionT.optio nameSpace->define(identifier, convertOptionToFfiFn(identifier, value)->eLambdaFFIValue) } -let emptyStdLib: t = emptyModule->defineBool("stdlib", true) +let defineFunctionReturningResult = ( + nameSpace: t, + identifier: string, + value: ExpressionT.optionFfiFnReturningResult, +): t => { + nameSpace->define( + identifier, + convertOptionToFfiFnReturningResult(identifier, value)->eLambdaFFIValue, + ) +} + +let emptyStdLib: t = emptyModule->defineBool("_standardLibrary", true)