diff --git a/packages/components/package.json b/packages/components/package.json index d770758b..b5f1ab7c 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -43,7 +43,7 @@ "tsconfig-paths-webpack-plugin": "^3.5.2", "typescript": "^4.6.3", "web-vitals": "^2.1.4", - "webpack": "^5.72.0", + "webpack": "^5.72.1", "webpack-cli": "^4.9.2", "webpack-dev-server": "^4.9.0" }, diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 5eafecf9..27dbf4b8 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -1,7 +1,11 @@ import * as React from "react"; import _ from "lodash"; -import type { Distribution } from "@quri/squiggle-lang"; -import { distributionErrorToString } from "@quri/squiggle-lang"; +import { + Distribution, + result, + distributionError, + distributionErrorToString, +} from "@quri/squiggle-lang"; import { Vega, VisualizationSpec } from "react-vega"; import * as chartSpecification from "../vega-specs/spec-distributions.json"; import { ErrorBox } from "./ErrorBox"; @@ -13,11 +17,14 @@ import { expYScale, } from "./DistributionVegaScales"; import styled from "styled-components"; +import { NumberShower } from "./NumberShower"; type DistributionChartProps = { distribution: Distribution; width?: number; height: number; + /** Whether to show a summary of means, stdev, percentiles etc */ + showSummary: boolean; /** Whether to show the user graph controls (scale etc) */ showControls?: boolean; }; @@ -25,6 +32,7 @@ type DistributionChartProps = { export const DistributionChart: React.FC = ({ distribution, height, + showSummary, width, showControls = false, }: DistributionChartProps) => { @@ -37,7 +45,7 @@ export const DistributionChart: React.FC = ({ shape.value.continuous.some((x) => x.x <= 0) || shape.value.discrete.some((x) => x.x <= 0); 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 var logCheckbox = ( @@ -58,21 +66,22 @@ export const DistributionChart: React.FC = ({ } var result = ( -
+ + {showSummary && } {showControls && (
{logCheckbox}
)} -
+ ); } else { var result = ( @@ -87,6 +96,12 @@ export const DistributionChart: React.FC = ({ return sized; }; +type ChartContainerProps = { width: string }; + +let ChartContainer = styled.div` + width: ${(props) => props.width}; +`; + function buildVegaSpec(isLogX: boolean, isExpY: boolean): VisualizationSpec { return { ...chartSpecification, @@ -128,3 +143,90 @@ export const CheckBox = ({ ); }; + +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 = ({ + 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 + ): React.ReactNode => { + if (x.tag === "Ok") { + return ; + } else { + return ( + + {distributionErrorToString(x.value)} + + ); + } + }; + + return ( + + + + {"Mean"} + {"5%"} + {"10%"} + {"25%"} + {"50%"} + {"75%"} + {"90%"} + {"95%"} + + + + + {unwrapResult(mean)} + {unwrapResult(p5)} + {unwrapResult(p10)} + {unwrapResult(p25)} + {unwrapResult(p50)} + {unwrapResult(p75)} + {unwrapResult(p90)} + {unwrapResult(p95)} + + +
+ ); +}; diff --git a/packages/components/src/components/FunctionChart.tsx b/packages/components/src/components/FunctionChart.tsx index 8e5abf32..2ee71721 100644 --- a/packages/components/src/components/FunctionChart.tsx +++ b/packages/components/src/components/FunctionChart.tsx @@ -65,6 +65,7 @@ export const FunctionChart: React.FC = ({ distribution={mouseItem.value.value} width={400} height={140} + showSummary={false} /> ) : ( <> diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index c833f9c3..e1407be5 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -66,6 +66,8 @@ export interface SquiggleItemProps { expression: squiggleExpression; width?: number; height: number; + /** Whether to show a summary of statistics for distributions */ + showSummary: boolean; /** Whether to show type information */ showTypes: boolean; /** Whether to show users graph controls (scale etc) */ @@ -80,6 +82,7 @@ const SquiggleItem: React.FC = ({ expression, width, height, + showSummary, showTypes = false, showControls = false, chartSettings, @@ -110,6 +113,7 @@ const SquiggleItem: React.FC = ({ distribution={expression.value} height={height} width={width} + showSummary={showSummary} showControls={showControls} /> @@ -152,6 +156,7 @@ const SquiggleItem: React.FC = ({ showControls={showControls} chartSettings={chartSettings} environment={environment} + showSummary={showSummary} /> ))} @@ -167,6 +172,7 @@ const SquiggleItem: React.FC = ({ width={width !== undefined ? width - 20 : width} height={50} showTypes={showTypes} + showSummary={showSummary} showControls={showControls} chartSettings={chartSettings} environment={environment} @@ -209,7 +215,9 @@ export interface SquiggleChartProps { /** Bindings of previous variables declared */ bindings: bindings; /** 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 */ showTypes: boolean; /** Whether to show graph controls (scale etc)*/ @@ -230,6 +238,7 @@ export const SquiggleChart: React.FC = ({ height = 60, bindings = defaultBindings, jsImports = defaultImports, + showSummary = false, width, showTypes = false, showControls = false, @@ -245,6 +254,7 @@ export const SquiggleChart: React.FC = ({ expression={expression} width={width} height={height} + showSummary={showSummary} showTypes={showTypes} showControls={showControls} chartSettings={chartSettings} diff --git a/packages/components/src/components/SquiggleEditor.tsx b/packages/components/src/components/SquiggleEditor.tsx index 8e99182d..4b17b585 100644 --- a/packages/components/src/components/SquiggleEditor.tsx +++ b/packages/components/src/components/SquiggleEditor.tsx @@ -41,7 +41,9 @@ export interface SquiggleEditorProps { /** Whether to show detail about types of the returns, default false */ showTypes?: boolean; /** Whether to give users access to graph controls */ - showControls: boolean; + showControls?: boolean; + /** Whether to show a summary table */ + showSummary?: boolean; } const Input = styled.div` @@ -63,6 +65,7 @@ export let SquiggleEditor: React.FC = ({ jsImports = defaultImports, showTypes = false, showControls = false, + showSummary = false, }: SquiggleEditorProps) => { let [expression, setExpression] = React.useState(initialSquiggleString); let chartSettings = { @@ -96,6 +99,7 @@ export let SquiggleEditor: React.FC = ({ jsImports={jsImports} showTypes={showTypes} showControls={showControls} + showSummary={showSummary} /> ); diff --git a/packages/components/src/components/SquigglePlayground.tsx b/packages/components/src/components/SquigglePlayground.tsx index 44fc4803..f403a4ce 100644 --- a/packages/components/src/components/SquigglePlayground.tsx +++ b/packages/components/src/components/SquigglePlayground.tsx @@ -45,18 +45,11 @@ function FieldFloat(Props: FieldFloatProps) { ); } -interface Props { - initialSquiggleString?: string; - height?: number; - showTypes?: boolean; - showControls?: boolean; -} - -interface Props2 { +interface ShowBoxProps { height: number; } -const ShowBox = styled.div` +const ShowBox = styled.div` border: 1px solid #eee; border-radius: 2px; height: ${(props) => props.height}; @@ -81,12 +74,26 @@ const Row = styled.div` `; const Col = styled.div``; -let SquigglePlayground: FC = ({ +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 = ({ initialSquiggleString = "", height = 300, showTypes = false, showControls = false, -}: Props) => { + showSummary = false, +}: PlaygroundProps) => { let [squiggleString, setSquiggleString] = useState(initialSquiggleString); let [sampleCount, setSampleCount] = useState(1000); let [outputXYPoints, setOutputXYPoints] = useState(1000); @@ -126,6 +133,7 @@ let SquigglePlayground: FC = ({ showControls={showControls} bindings={defaultBindings} jsImports={defaultImports} + showSummary={showSummary} /> @@ -134,7 +142,7 @@ let SquigglePlayground: FC = ({ ); }; export default SquigglePlayground; -export function renderSquigglePlaygroundToDom(props: Props) { +export function renderSquigglePlaygroundToDom(props: PlaygroundProps) { let parent = document.createElement("div"); ReactDOM.render(, parent); return parent; diff --git a/packages/squiggle-lang/package.json b/packages/squiggle-lang/package.json index 6cbb4906..49dad58c 100644 --- a/packages/squiggle-lang/package.json +++ b/packages/squiggle-lang/package.json @@ -60,7 +60,7 @@ "ts-loader": "^9.3.0", "ts-node": "^10.7.0", "typescript": "^4.6.3", - "webpack": "^5.72.0", + "webpack": "^5.72.1", "webpack-cli": "^4.9.2" }, "source": "./src/js/index.ts", diff --git a/yarn.lock b/yarn.lock index 123a0842..a8181cd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8017,7 +8017,7 @@ enhanced-resolve@^4.5.0: memory-fs "^0.5.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" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88" 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" 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" 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== @@ -18228,10 +18228,10 @@ webpack@4: watchpack "^1.7.4" webpack-sources "^1.4.1" -webpack@^5, webpack@^5.64.4, webpack@^5.72.0, webpack@^5.9.0: - version "5.72.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.0.tgz#f8bc40d9c6bb489a4b7a8a685101d6022b8b6e28" - integrity sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w== +webpack@^5, webpack@^5.64.4, webpack@^5.72.0, webpack@^5.72.1, webpack@^5.9.0: + version "5.72.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.1.tgz#3500fc834b4e9ba573b9f430b2c0a61e1bb57d13" + integrity sha512-dXG5zXCLspQR4krZVR6QgajnZOjW2K/djHvdcRaDQvsjV9z9vaW6+ja5dZOYbqBBjF6kGXka/2ZyxNdc+8Jung== dependencies: "@types/eslint-scope" "^3.7.3" "@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" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.2" + enhanced-resolve "^5.9.3" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" 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" mime-types "^2.1.27" neo-async "^2.6.2"