Merge pull request #258 from quantified-uncertainty/handle-arrays-in-components

Playground improvements with new types
This commit is contained in:
Ozzie Gooen 2022-04-15 19:18:49 -04:00 committed by GitHub
commit cf9c12f786
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 332 additions and 115 deletions

View File

@ -1,6 +1,6 @@
---
name: Bug reports for Squiggle users
about: Rendering oddly? Is there a mathematical correctness problem?
about: Rendering oddly? Is there a mathematical correctness problem?
labels: "bug"
---

View File

@ -3,6 +3,7 @@
"version": "0.1.8",
"dependencies": {
"@quri/squiggle-lang": "0.2.2",
"@react-hook/size": "^2.1.2",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.0.1",
"@testing-library/user-event": "^14.0.4",

View File

@ -10,12 +10,16 @@ interface CodeEditorProps {
onChange: (value: string) => void;
oneLine?: boolean;
width?: number;
height: number;
showGutter?: boolean;
}
export let CodeEditor: FC<CodeEditorProps> = ({
value,
onChange,
oneLine = false,
showGutter = false,
height,
}: CodeEditorProps) => {
let lineCount = value.split("\n").length;
let id = _.uniqueId();
@ -25,9 +29,10 @@ export let CodeEditor: FC<CodeEditorProps> = ({
mode="golang"
theme="github"
width={"100%"}
minLines={oneLine ? lineCount : 15}
maxLines={oneLine ? lineCount : 15}
showGutter={false}
height={String(height) + "px"}
minLines={oneLine ? lineCount : undefined}
maxLines={oneLine ? lineCount : undefined}
showGutter={showGutter}
highlightActiveLine={false}
showPrintMargin={false}
onChange={onChange}

View File

@ -27,7 +27,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
return (
<SquiggleVegaChart
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
width={width}
width={width - 20}
height={height}
actions={false}
/>

View File

@ -1,10 +1,117 @@
import * as React from "react";
import _ from "lodash";
import { run, errorValueToString } from "@quri/squiggle-lang";
import styled from "styled-components";
import {
run,
errorValueToString,
squiggleExpression,
} from "@quri/squiggle-lang";
import type { samplingParams, exportEnv } from "@quri/squiggle-lang";
import { NumberShower } from "./NumberShower";
import { DistributionChart } from "./DistributionChart";
import { ErrorBox } from "./ErrorBox";
import useSize from "@react-hook/size";
const variableBox = {
Component: styled.div`
background: white;
border: 1px solid #eee;
border-radius: 2px;
margin-bottom: 0.4em;
`,
Heading: styled.div`
border-bottom: 1px solid #eee;
padding-left: 0.8em;
padding-right: 0.8em;
padding-top: 0.1em;
`,
Body: styled.div`
padding: 0.4em 0.8em;
`,
};
export const VariableBox: React.FC<{
heading: string;
children: React.ReactNode;
}> = ({ heading = "Error", children }) => {
return (
<variableBox.Component>
<variableBox.Heading>
<h3>{heading}</h3>
</variableBox.Heading>
<variableBox.Body>{children}</variableBox.Body>
</variableBox.Component>
);
};
export interface SquiggleItemProps {
/** The input string for squiggle */
expression: squiggleExpression;
width: number;
height: number;
}
const SquiggleItem: React.FC<SquiggleItemProps> = ({
expression,
width,
height,
}: SquiggleItemProps) => {
switch (expression.tag) {
case "number":
return (
<VariableBox heading="Number">
<NumberShower precision={3} number={expression.value} />
</VariableBox>
);
case "distribution": {
let distType = expression.value.type();
return (
<VariableBox heading={`Distribution (${distType})`}>
{distType === "Symbolic" ? (
<>
<div>{expression.value.toString()}</div>
</>
) : (
<></>
)}
<DistributionChart
distribution={expression.value}
height={height}
width={width}
/>
</VariableBox>
);
}
case "string":
return (
<VariableBox heading="String">{`"${expression.value}"`}</VariableBox>
);
case "boolean":
return (
<VariableBox heading="Boolean">
{expression.value.toString()}
</VariableBox>
);
case "symbol":
return <VariableBox heading="Symbol">{expression.value}</VariableBox>;
case "call":
return <VariableBox heading="Call">{expression.value}</VariableBox>;
case "array":
return (
<VariableBox heading="Array">
{expression.value.map((r) => (
<SquiggleItem expression={r} width={width - 20} height={50} />
))}
</VariableBox>
);
default:
return (
<ErrorBox heading="No Viewer">
{"We don't currently have a working viewer for record types."}
</ErrorBox>
);
}
};
export interface SquiggleChartProps {
/** The input string for squiggle */
@ -36,41 +143,33 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
outputXYPoints = 1000,
environment = [],
onEnvChange = () => {},
width = 500,
height = 60,
width = NaN,
}: SquiggleChartProps) => {
const target = React.useRef(null);
const [componentWidth] = useSize(target);
// I would have wanted to just use componentWidth, but this created infinite loops with SquiggleChart.stories.
//So you can manually add a width, as an escape hatch.
let _width = width || componentWidth;
let samplingInputs: samplingParams = {
sampleCount: sampleCount,
xyPointLength: outputXYPoints,
};
let expressionResult = run(squiggleString, samplingInputs, environment);
let internal: JSX.Element;
if (expressionResult.tag === "Ok") {
onEnvChange(environment);
let expression = expressionResult.value;
if (expression.tag === "number") {
return <NumberShower precision={3} number={expression.value} />;
} else if (expression.tag === "distribution") {
return (
<DistributionChart
distribution={expression.value}
height={height}
width={width}
/>
);
} else {
return (
<ErrorBox heading="No Viewer">
{"We don't currently have a viewer for this type: " + expression.tag}
</ErrorBox>
);
}
internal = (
<SquiggleItem expression={expression} width={_width} height={height} />
);
} else {
// At this point, we came across an error. What was our error?
return (
internal = (
<ErrorBox heading={"Parse Error"}>
{errorValueToString(expressionResult.value)}
</ErrorBox>
);
}
return <div ref={target}>{internal}</div>;
};

View File

@ -55,6 +55,8 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
value={expression}
onChange={setExpression}
oneLine={true}
showGutter={false}
height={20}
/>
</Input>
<SquiggleChart

View File

@ -3,7 +3,8 @@ import React, { FC, useState } from "react";
import ReactDOM from "react-dom";
import { SquiggleChart } from "./SquiggleChart";
import CodeEditor from "./CodeEditor";
import { Form, Input, Card, Row, Col } from "antd";
import { Form, Input, Row, Col } from "antd";
import styled from "styled-components";
import "antd/dist/antd.css";
interface FieldFloatProps {
@ -34,9 +35,40 @@ function FieldFloat(Props: FieldFloatProps) {
interface Props {
initialSquiggleString?: string;
height?: number;
}
let SquigglePlayground: FC<Props> = ({ initialSquiggleString = "" }: Props) => {
interface Props2 {
height: number;
}
const ShowBox = styled.div<Props2>`
border: 1px solid #eee;
border-radius: 2px;
height: ${(props) => props.height};
`;
const MyComponent = styled.div`
color: ${(props) => props.theme.colors.main};
`;
interface TitleProps {
readonly maxHeight: number;
}
const Display = styled.div<TitleProps>`
background: #f6f6f6;
border-left: 1px solid #eee;
height: 100vh;
padding: 3px;
overflow-y: auto;
max-height: ${(props) => props.maxHeight}px;
`;
let SquigglePlayground: FC<Props> = ({
initialSquiggleString = "",
height = 300,
}: Props) => {
let [squiggleString, setSquiggleString] = useState(initialSquiggleString);
let [sampleCount, setSampleCount] = useState(1000);
let [outputXYPoints, setOutputXYPoints] = useState(1000);
@ -44,81 +76,34 @@ let SquigglePlayground: FC<Props> = ({ initialSquiggleString = "" }: Props) => {
let [diagramStart, setDiagramStart] = useState(0);
let [diagramStop, setDiagramStop] = useState(10);
let [diagramCount, setDiagramCount] = useState(20);
var demoDist = (
<SquiggleChart
squiggleString={squiggleString}
sampleCount={sampleCount}
outputXYPoints={outputXYPoints}
diagramStart={diagramStart}
diagramStop={diagramStop}
diagramCount={diagramCount}
pointDistLength={pointDistLength}
height={150}
/>
);
return (
<Row>
<Col span={12}>
<Card title="Distribution Form">
<Form>
<Row gutter={16}>
<Col span={24}>
<CodeEditor
value={squiggleString}
onChange={setSquiggleString}
oneLine={false}
/>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<FieldFloat
value={sampleCount}
label="Sample Count"
onChange={setSampleCount}
/>
</Col>
<Col span={12}>
<FieldFloat
value={outputXYPoints}
onChange={setOutputXYPoints}
label="Output XY-points"
/>
</Col>
<Col span={12}>
<FieldFloat
value={pointDistLength}
onChange={setPointDistLength}
label="Downsample To"
/>
</Col>
<Col span={12}>
<FieldFloat
value={diagramStart}
onChange={setDiagramStart}
label="Diagram Start"
/>
</Col>
<Col span={12}>
<FieldFloat
value={diagramStop}
onChange={setDiagramStop}
label="Diagram Stop"
/>
</Col>
<Col span={12}>
<FieldFloat
value={diagramCount}
onChange={setDiagramCount}
label="Diagram Count"
/>
</Col>
</Row>
</Form>
</Card>
</Col>
<Col span={12}>{demoDist}</Col>
</Row>
<ShowBox height={height}>
<Row>
<Col span={12}>
<CodeEditor
value={squiggleString}
onChange={setSquiggleString}
oneLine={false}
showGutter={true}
height={height - 3}
/>
</Col>
<Col span={12}>
<Display maxHeight={height - 3}>
<SquiggleChart
squiggleString={squiggleString}
sampleCount={sampleCount}
outputXYPoints={outputXYPoints}
diagramStart={diagramStart}
diagramStop={diagramStop}
diagramCount={diagramCount}
pointDistLength={pointDistLength}
height={150}
/>
</Display>
</Col>
</Row>
</ShowBox>
);
};
export default SquigglePlayground;

View File

@ -4,6 +4,11 @@ import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
<Meta title="Squiggle/SquiggleChart" component={SquiggleChart} />
export const Template = SquiggleChart;
/*
We have to hardcode a width here, because otherwise some interaction with
Storybook creates an infinite loop with the internal width
*/
const width = 600;
# Squiggle Chart
@ -18,13 +23,42 @@ could be continuous, discrete or mixed.
## Distributions
### Continuous Distributions
### Continuous Distributions (Symbolic)
<Canvas>
<Story
name="Normal"
name="Continuous Symbolic"
args={{
squiggleString: "normal(5,2)",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
### Continuous Distributions (PointSet)
<Canvas>
<Story
name="Continuous Pointset"
args={{
squiggleString: "toPointSet(normal(5,2))",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
### Continuous Distributions (SampleSet)
<Canvas>
<Story
name="Continuous SampleSet"
args={{
squiggleString: "toSampleSet(normal(5,2), 1000)",
width,
}}
>
{Template.bind({})}
@ -38,6 +72,7 @@ could be continuous, discrete or mixed.
name="Discrete"
args={{
squiggleString: "mx(0, 1, 3, 5, 8, 10, [0.1, 0.8, 0.5, 0.3, 0.2, 0.1])",
width,
}}
>
{Template.bind({})}
@ -52,6 +87,7 @@ could be continuous, discrete or mixed.
args={{
squiggleString:
"mx(0, 1, 3, 5, 8, normal(8, 1), [0.1, 0.3, 0.4, 0.35, 0.2, 0.8])",
width,
}}
>
{Template.bind({})}
@ -68,23 +104,21 @@ to allow large and small numbers being printed cleanly.
name="Constant"
args={{
squiggleString: "500000000",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Functions
Full functions can be returned. These plot out the results of distributions between a set of x-coordinates.
The default is show 10 points between 0 and 10.
## Arrays
<Canvas>
<Story
name="Function"
name="Array"
args={{
squiggleString: "f(x) = normal(x^2,(x+.1)^1.8)\nf",
squiggleString: "[normal(5,2), normal(10,1), normal(40,2), 400000]",
width,
}}
>
{Template.bind({})}
@ -98,6 +132,49 @@ The default is show 10 points between 0 and 10.
name="Error"
args={{
squiggleString: "f(x) = normal(",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Booleans
<Canvas>
<Story
name="Boolean"
args={{
squiggleString: "3 == 3",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Records
<Canvas>
<Story
name="Record"
args={{
squiggleString: "{foo: 35 to 50, bar: [1,2,3]}",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Strings
<Canvas>
<Story
name="String"
args={{
squiggleString: '"Lucky day!"',
width,
}}
>
{Template.bind({})}

View File

@ -1,5 +1,6 @@
import SquigglePlayground from "../components/SquigglePlayground";
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
import styled from "styled-components";
<Meta title="Squiggle/SquigglePlayground" component={SquigglePlayground} />
@ -15,6 +16,7 @@ including sampling settings, in squiggle.
name="Normal"
args={{
initialSquiggleString: "normal(5,2)",
height: 500,
}}
>
{Template.bind({})}

View File

@ -48,6 +48,7 @@ import {
Constructors_pointwiseLogarithm,
Constructors_pointwisePower,
} from "../rescript/Distributions/DistributionOperation/DistributionOperation.gen";
import { pointSetDistFn } from "../rescript/OldInterpreter/DistPlus.bs";
export type { samplingParams, errorValue };
export let defaultSamplingInputs: samplingParams = {
@ -200,6 +201,10 @@ export class Distribution {
);
}
type() {
return this.t.tag;
}
pointSet(): result<shape, distributionError> {
let pointSet = toPointSet(
this.t,

View File

@ -8,12 +8,9 @@ export default function PlaygroundPage() {
<div
style={{
maxWidth: 2000,
paddingTop: "3em",
margin: "0 auto",
}}
>
<h2> Squiggle Playground </h2>
<SquigglePlayground initialSquiggleString="normal(0,1)" />
<SquigglePlayground initialSquiggleString="normal(0,1)" height={500} />
</div>
</Layout>
);

View File

@ -2202,6 +2202,11 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@juggle/resize-observer@^3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0"
integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==
"@leichtgewicht/ip-codec@^2.0.1":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0"
@ -2326,6 +2331,35 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==
"@react-hook/latest@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@react-hook/latest/-/latest-1.0.3.tgz#c2d1d0b0af8b69ec6e2b3a2412ba0768ac82db80"
integrity sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==
"@react-hook/passive-layout-effect@^1.2.0":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz#c06dac2d011f36d61259aa1c6df4f0d5e28bc55e"
integrity sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==
"@react-hook/resize-observer@^1.2.1":
version "1.2.5"
resolved "https://registry.yarnpkg.com/@react-hook/resize-observer/-/resize-observer-1.2.5.tgz#b59e2300de98bc6ddc6946942f21243cde10f984"
integrity sha512-qa0pPvRxq5VbdI8mMK2apPFsZOckhQ6D3Jc9yLuyHMNhui8yEih4qyFCZBDzzK3ymZS6LAltVSVg3l1Dg9vA0w==
dependencies:
"@juggle/resize-observer" "^3.3.1"
"@react-hook/latest" "^1.0.2"
"@react-hook/passive-layout-effect" "^1.2.0"
"@types/raf-schd" "^4.0.0"
raf-schd "^4.0.2"
"@react-hook/size@^2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@react-hook/size/-/size-2.1.2.tgz#87ed634ffb200f65d3e823501e5559aa3d584451"
integrity sha512-BmE5asyRDxSuQ9p14FUKJ0iBRgV9cROjqNG9jT/EjCM+xHha1HVqbPoT+14FQg1K7xIydabClCibUY4+1tw/iw==
dependencies:
"@react-hook/passive-layout-effect" "^1.2.0"
"@react-hook/resize-observer" "^1.2.1"
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@ -3990,6 +4024,11 @@
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
"@types/raf-schd@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/raf-schd/-/raf-schd-4.0.1.tgz#1f9e03736f277fe9c7b82102bf18570a6ee19f82"
integrity sha512-Ha+EnKHFIh9EKW0/XZJPUd3EGDFisEvauaBd4VVCRPKeOqUxNEc9TodiY2Zhk33XCgzJucoFEcaoNcBAPHTQ2A==
"@types/range-parser@*":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
@ -13583,6 +13622,11 @@ quick-lru@^5.1.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
raf-schd@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
raf@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"