Merge branch 'develop' into function-charts

This commit is contained in:
Sam Nolan 2022-05-10 15:57:59 +00:00
commit 07b93e5080
8 changed files with 155 additions and 30 deletions

View File

@ -43,7 +43,7 @@
"tsconfig-paths-webpack-plugin": "^3.5.2", "tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.6.3", "typescript": "^4.6.3",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",
"webpack": "^5.72.0", "webpack": "^5.72.1",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0" "webpack-dev-server": "^4.9.0"
}, },

View File

@ -1,7 +1,11 @@
import * as React from "react"; import * as React from "react";
import _ from "lodash"; import _ from "lodash";
import type { Distribution } from "@quri/squiggle-lang"; import {
import { distributionErrorToString } from "@quri/squiggle-lang"; Distribution,
result,
distributionError,
distributionErrorToString,
} from "@quri/squiggle-lang";
import { Vega, VisualizationSpec } from "react-vega"; import { Vega, VisualizationSpec } from "react-vega";
import * as chartSpecification from "../vega-specs/spec-distributions.json"; import * as chartSpecification from "../vega-specs/spec-distributions.json";
import { ErrorBox } from "./ErrorBox"; import { ErrorBox } from "./ErrorBox";
@ -13,11 +17,14 @@ import {
expYScale, expYScale,
} from "./DistributionVegaScales"; } from "./DistributionVegaScales";
import styled from "styled-components"; import styled from "styled-components";
import { NumberShower } from "./NumberShower";
type DistributionChartProps = { type DistributionChartProps = {
distribution: Distribution; distribution: Distribution;
width?: number; width?: number;
height: number; height: number;
/** Whether to show a summary of means, stdev, percentiles etc */
showSummary: boolean;
/** Whether to show the user graph controls (scale etc) */ /** Whether to show the user graph controls (scale etc) */
showControls?: boolean; showControls?: boolean;
}; };
@ -25,6 +32,7 @@ type DistributionChartProps = {
export const DistributionChart: React.FC<DistributionChartProps> = ({ export const DistributionChart: React.FC<DistributionChartProps> = ({
distribution, distribution,
height, height,
showSummary,
width, width,
showControls = false, showControls = false,
}: DistributionChartProps) => { }: DistributionChartProps) => {
@ -37,7 +45,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
shape.value.continuous.some((x) => x.x <= 0) || shape.value.continuous.some((x) => x.x <= 0) ||
shape.value.discrete.some((x) => x.x <= 0); shape.value.discrete.some((x) => x.x <= 0);
let spec = buildVegaSpec(isLogX, isExpY); let spec = buildVegaSpec(isLogX, isExpY);
let widthProp = width ? width - 20 : size.width - 10; let widthProp = width ? width : size.width;
// Check whether we should disable the checkbox // Check whether we should disable the checkbox
var logCheckbox = ( var logCheckbox = (
@ -58,21 +66,22 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
} }
var result = ( var result = (
<div> <ChartContainer width={widthProp + "px"}>
<Vega <Vega
spec={spec} spec={spec}
data={{ con: shape.value.continuous, dis: shape.value.discrete }} data={{ con: shape.value.continuous, dis: shape.value.discrete }}
width={widthProp} width={widthProp - 10}
height={height} height={height}
actions={false} actions={false}
/> />
{showSummary && <SummaryTable distribution={distribution} />}
{showControls && ( {showControls && (
<div> <div>
{logCheckbox} {logCheckbox}
<CheckBox label="Exp Y scale" value={isExpY} onChange={setExpY} /> <CheckBox label="Exp Y scale" value={isExpY} onChange={setExpY} />
</div> </div>
)} )}
</div> </ChartContainer>
); );
} else { } else {
var result = ( var result = (
@ -87,6 +96,12 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
return sized; return sized;
}; };
type ChartContainerProps = { width: string };
let ChartContainer = styled.div<ChartContainerProps>`
width: ${(props) => props.width};
`;
function buildVegaSpec(isLogX: boolean, isExpY: boolean): VisualizationSpec { function buildVegaSpec(isLogX: boolean, isExpY: boolean): VisualizationSpec {
return { return {
...chartSpecification, ...chartSpecification,
@ -128,3 +143,90 @@ export const CheckBox = ({
</span> </span>
); );
}; };
type SummaryTableProps = {
distribution: Distribution;
};
const Table = styled.table`
margin-left: auto;
margin-right: auto;
border-collapse: collapse;
text-align: center;
border-style: hidden;
`;
const TableHead = styled.thead`
border-bottom: 1px solid rgb(141 149 167);
`;
const TableHeadCell = styled.th`
border-right: 1px solid rgb(141 149 167);
border-left: 1px solid rgb(141 149 167);
padding: 0.3em;
`;
const TableBody = styled.tbody``;
const Row = styled.tr``;
const Cell = styled.td`
padding: 0.3em;
border-right: 1px solid rgb(141 149 167);
border-left: 1px solid rgb(141 149 167);
`;
const SummaryTable: React.FC<SummaryTableProps> = ({
distribution,
}: SummaryTableProps) => {
let mean = distribution.mean();
let p5 = distribution.inv(0.05);
let p10 = distribution.inv(0.1);
let p25 = distribution.inv(0.25);
let p50 = distribution.inv(0.5);
let p75 = distribution.inv(0.75);
let p90 = distribution.inv(0.9);
let p95 = distribution.inv(0.95);
let unwrapResult = (
x: result<number, distributionError>
): React.ReactNode => {
if (x.tag === "Ok") {
return <NumberShower number={x.value} />;
} else {
return (
<ErrorBox heading="Distribution Error">
{distributionErrorToString(x.value)}
</ErrorBox>
);
}
};
return (
<Table>
<TableHead>
<Row>
<TableHeadCell>{"Mean"}</TableHeadCell>
<TableHeadCell>{"5%"}</TableHeadCell>
<TableHeadCell>{"10%"}</TableHeadCell>
<TableHeadCell>{"25%"}</TableHeadCell>
<TableHeadCell>{"50%"}</TableHeadCell>
<TableHeadCell>{"75%"}</TableHeadCell>
<TableHeadCell>{"90%"}</TableHeadCell>
<TableHeadCell>{"95%"}</TableHeadCell>
</Row>
</TableHead>
<TableBody>
<Row>
<Cell>{unwrapResult(mean)}</Cell>
<Cell>{unwrapResult(p5)}</Cell>
<Cell>{unwrapResult(p10)}</Cell>
<Cell>{unwrapResult(p25)}</Cell>
<Cell>{unwrapResult(p50)}</Cell>
<Cell>{unwrapResult(p75)}</Cell>
<Cell>{unwrapResult(p90)}</Cell>
<Cell>{unwrapResult(p95)}</Cell>
</Row>
</TableBody>
</Table>
);
};

View File

@ -65,6 +65,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
distribution={mouseItem.value.value} distribution={mouseItem.value.value}
width={400} width={400}
height={140} height={140}
showSummary={false}
/> />
) : ( ) : (
<></> <></>

View File

@ -66,6 +66,8 @@ export interface SquiggleItemProps {
expression: squiggleExpression; expression: squiggleExpression;
width?: number; width?: number;
height: number; height: number;
/** Whether to show a summary of statistics for distributions */
showSummary: boolean;
/** Whether to show type information */ /** Whether to show type information */
showTypes: boolean; showTypes: boolean;
/** Whether to show users graph controls (scale etc) */ /** Whether to show users graph controls (scale etc) */
@ -80,6 +82,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
expression, expression,
width, width,
height, height,
showSummary,
showTypes = false, showTypes = false,
showControls = false, showControls = false,
chartSettings, chartSettings,
@ -110,6 +113,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
distribution={expression.value} distribution={expression.value}
height={height} height={height}
width={width} width={width}
showSummary={showSummary}
showControls={showControls} showControls={showControls}
/> />
</VariableBox> </VariableBox>
@ -152,6 +156,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
showControls={showControls} showControls={showControls}
chartSettings={chartSettings} chartSettings={chartSettings}
environment={environment} environment={environment}
showSummary={showSummary}
/> />
))} ))}
</VariableBox> </VariableBox>
@ -167,6 +172,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
width={width !== undefined ? width - 20 : width} width={width !== undefined ? width - 20 : width}
height={50} height={50}
showTypes={showTypes} showTypes={showTypes}
showSummary={showSummary}
showControls={showControls} showControls={showControls}
chartSettings={chartSettings} chartSettings={chartSettings}
environment={environment} environment={environment}
@ -209,7 +215,9 @@ export interface SquiggleChartProps {
/** Bindings of previous variables declared */ /** Bindings of previous variables declared */
bindings: bindings; bindings: bindings;
/** JS imported parameters */ /** JS imported parameters */
jsImports: jsImports; jsImports?: jsImports;
/** Whether to show a summary of the distirbution */
showSummary?: boolean;
/** Whether to show type information about returns, default false */ /** Whether to show type information about returns, default false */
showTypes: boolean; showTypes: boolean;
/** Whether to show graph controls (scale etc)*/ /** Whether to show graph controls (scale etc)*/
@ -230,6 +238,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
height = 60, height = 60,
bindings = defaultBindings, bindings = defaultBindings,
jsImports = defaultImports, jsImports = defaultImports,
showSummary = false,
width, width,
showTypes = false, showTypes = false,
showControls = false, showControls = false,
@ -245,6 +254,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
expression={expression} expression={expression}
width={width} width={width}
height={height} height={height}
showSummary={showSummary}
showTypes={showTypes} showTypes={showTypes}
showControls={showControls} showControls={showControls}
chartSettings={chartSettings} chartSettings={chartSettings}

View File

@ -41,7 +41,9 @@ export interface SquiggleEditorProps {
/** Whether to show detail about types of the returns, default false */ /** Whether to show detail about types of the returns, default false */
showTypes?: boolean; showTypes?: boolean;
/** Whether to give users access to graph controls */ /** Whether to give users access to graph controls */
showControls: boolean; showControls?: boolean;
/** Whether to show a summary table */
showSummary?: boolean;
} }
const Input = styled.div` const Input = styled.div`
@ -63,6 +65,7 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
jsImports = defaultImports, jsImports = defaultImports,
showTypes = false, showTypes = false,
showControls = false, showControls = false,
showSummary = false,
}: SquiggleEditorProps) => { }: SquiggleEditorProps) => {
let [expression, setExpression] = React.useState(initialSquiggleString); let [expression, setExpression] = React.useState(initialSquiggleString);
let chartSettings = { let chartSettings = {
@ -96,6 +99,7 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
jsImports={jsImports} jsImports={jsImports}
showTypes={showTypes} showTypes={showTypes}
showControls={showControls} showControls={showControls}
showSummary={showSummary}
/> />
</div> </div>
); );

View File

@ -45,18 +45,11 @@ function FieldFloat(Props: FieldFloatProps) {
); );
} }
interface Props { interface ShowBoxProps {
initialSquiggleString?: string;
height?: number;
showTypes?: boolean;
showControls?: boolean;
}
interface Props2 {
height: number; height: number;
} }
const ShowBox = styled.div<Props2>` const ShowBox = styled.div<ShowBoxProps>`
border: 1px solid #eee; border: 1px solid #eee;
border-radius: 2px; border-radius: 2px;
height: ${(props) => props.height}; height: ${(props) => props.height};
@ -81,12 +74,26 @@ const Row = styled.div`
`; `;
const Col = styled.div``; const Col = styled.div``;
let SquigglePlayground: FC<Props> = ({ interface PlaygroundProps {
/** The initial squiggle string to put in the playground */
initialSquiggleString?: 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;
}
let SquigglePlayground: FC<PlaygroundProps> = ({
initialSquiggleString = "", initialSquiggleString = "",
height = 300, height = 300,
showTypes = false, showTypes = false,
showControls = false, showControls = false,
}: Props) => { showSummary = false,
}: PlaygroundProps) => {
let [squiggleString, setSquiggleString] = useState(initialSquiggleString); let [squiggleString, setSquiggleString] = useState(initialSquiggleString);
let [sampleCount, setSampleCount] = useState(1000); let [sampleCount, setSampleCount] = useState(1000);
let [outputXYPoints, setOutputXYPoints] = useState(1000); let [outputXYPoints, setOutputXYPoints] = useState(1000);
@ -126,6 +133,7 @@ let SquigglePlayground: FC<Props> = ({
showControls={showControls} showControls={showControls}
bindings={defaultBindings} bindings={defaultBindings}
jsImports={defaultImports} jsImports={defaultImports}
showSummary={showSummary}
/> />
</Display> </Display>
</Col> </Col>
@ -134,7 +142,7 @@ let SquigglePlayground: FC<Props> = ({
); );
}; };
export default SquigglePlayground; export default SquigglePlayground;
export function renderSquigglePlaygroundToDom(props: Props) { export function renderSquigglePlaygroundToDom(props: PlaygroundProps) {
let parent = document.createElement("div"); let parent = document.createElement("div");
ReactDOM.render(<SquigglePlayground {...props} />, parent); ReactDOM.render(<SquigglePlayground {...props} />, parent);
return parent; return parent;

View File

@ -60,7 +60,7 @@
"ts-loader": "^9.3.0", "ts-loader": "^9.3.0",
"ts-node": "^10.7.0", "ts-node": "^10.7.0",
"typescript": "^4.6.3", "typescript": "^4.6.3",
"webpack": "^5.72.0", "webpack": "^5.72.1",
"webpack-cli": "^4.9.2" "webpack-cli": "^4.9.2"
}, },
"source": "./src/js/index.ts", "source": "./src/js/index.ts",

View File

@ -8017,7 +8017,7 @@ enhanced-resolve@^4.5.0:
memory-fs "^0.5.0" memory-fs "^0.5.0"
tapable "^1.0.0" tapable "^1.0.0"
enhanced-resolve@^5.0.0, enhanced-resolve@^5.7.0, enhanced-resolve@^5.9.2: enhanced-resolve@^5.0.0, enhanced-resolve@^5.7.0, enhanced-resolve@^5.9.3:
version "5.9.3" version "5.9.3"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88"
integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow== integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==
@ -11301,7 +11301,7 @@ json-parse-better-errors@^1.0.2:
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
json-parse-even-better-errors@^2.3.0: json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
@ -18228,10 +18228,10 @@ webpack@4:
watchpack "^1.7.4" watchpack "^1.7.4"
webpack-sources "^1.4.1" webpack-sources "^1.4.1"
webpack@^5, webpack@^5.64.4, webpack@^5.72.0, webpack@^5.9.0: webpack@^5, webpack@^5.64.4, webpack@^5.72.0, webpack@^5.72.1, webpack@^5.9.0:
version "5.72.0" version "5.72.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.0.tgz#f8bc40d9c6bb489a4b7a8a685101d6022b8b6e28" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.1.tgz#3500fc834b4e9ba573b9f430b2c0a61e1bb57d13"
integrity sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w== integrity sha512-dXG5zXCLspQR4krZVR6QgajnZOjW2K/djHvdcRaDQvsjV9z9vaW6+ja5dZOYbqBBjF6kGXka/2ZyxNdc+8Jung==
dependencies: dependencies:
"@types/eslint-scope" "^3.7.3" "@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51" "@types/estree" "^0.0.51"
@ -18242,13 +18242,13 @@ webpack@^5, webpack@^5.64.4, webpack@^5.72.0, webpack@^5.9.0:
acorn-import-assertions "^1.7.6" acorn-import-assertions "^1.7.6"
browserslist "^4.14.5" browserslist "^4.14.5"
chrome-trace-event "^1.0.2" chrome-trace-event "^1.0.2"
enhanced-resolve "^5.9.2" enhanced-resolve "^5.9.3"
es-module-lexer "^0.9.0" es-module-lexer "^0.9.0"
eslint-scope "5.1.1" eslint-scope "5.1.1"
events "^3.2.0" events "^3.2.0"
glob-to-regexp "^0.4.1" glob-to-regexp "^0.4.1"
graceful-fs "^4.2.9" graceful-fs "^4.2.9"
json-parse-better-errors "^1.0.2" json-parse-even-better-errors "^2.3.1"
loader-runner "^4.2.0" loader-runner "^4.2.0"
mime-types "^2.1.27" mime-types "^2.1.27"
neo-async "^2.6.2" neo-async "^2.6.2"