diff --git a/packages/components/src/components/FunctionChart.tsx b/packages/components/src/components/FunctionChart.tsx
index d774f3db..5a37f0ca 100644
--- a/packages/components/src/components/FunctionChart.tsx
+++ b/packages/components/src/components/FunctionChart.tsx
@@ -7,34 +7,11 @@ import {
lambdaValue,
environment,
runForeign,
- squiggleExpression,
- errorValue,
- errorValueToString,
} from "@quri/squiggle-lang";
-import { createClassFromSpec } from "react-vega";
-import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
-import { DistributionChart } from "./DistributionChart";
-import { NumberShower } from "./NumberShower";
+import { FunctionChart1Dist } from "./FunctionChart1Dist";
+import { FunctionChart1Number } from "./FunctionChart1Number";
import { ErrorBox } from "./ErrorBox";
-let SquigglePercentilesChart = createClassFromSpec({
- spec: percentilesSpec as Spec,
-});
-
-const _rangeByCount = (start: number, stop: number, count: number) => {
- const step = (stop - start) / (count - 1);
- const items = _.range(start, stop, step);
- const result = items.concat([stop]);
- return result;
-};
-
-function unwrap(x: result): a {
- if (x.tag === "Ok") {
- return x.value;
- } else {
- throw Error("FAILURE TO UNWRAP");
- }
-}
export type FunctionChartSettings = {
start: number;
stop: number;
@@ -48,167 +25,47 @@ interface FunctionChartProps {
height: number;
}
-type percentiles = {
- x: number;
- p1: number;
- p5: number;
- p10: number;
- p20: number;
- p30: number;
- p40: number;
- p50: number;
- p60: number;
- p70: number;
- p80: number;
- p90: number;
- p95: number;
- p99: number;
-}[];
-
-type errors = _.Dictionary<
- {
- x: number;
- value: string;
- }[]
->;
-
-type point = { x: number; value: result };
-
-let getPercentiles = ({ chartSettings, fn, environment }) => {
- let chartPointsToRender = _rangeByCount(
- chartSettings.start,
- chartSettings.stop,
- chartSettings.count
- );
-
- let chartPointsData: point[] = chartPointsToRender.map((x) => {
- let result = runForeign(fn, [x], environment);
- if (result.tag === "Ok") {
- if (result.value.tag == "distribution") {
- return { x, value: { tag: "Ok", value: result.value.value } };
- } else {
- return {
- x,
- value: {
- tag: "Error",
- value:
- "Cannot currently render functions that don't return distributions",
- },
- };
- }
- } else {
- return {
- x,
- value: { tag: "Error", value: errorValueToString(result.value) },
- };
- }
- });
-
- let initialPartition: [
- { x: number; value: Distribution }[],
- { x: number; value: string }[]
- ] = [[], []];
-
- let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
- if (current.value.tag === "Ok") {
- acc[0].push({ x: current.x, value: current.value.value });
- } else {
- acc[1].push({ x: current.x, value: current.value.value });
- }
- return acc;
- }, initialPartition);
-
- let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
-
- let percentiles: percentiles = functionImage.map(({ x, value }) => {
- // We convert it to to a pointSet distribution first, so that in case its a sample set
- // distribution, it doesn't internally convert it to a pointSet distribution for every
- // single inv() call.
- let toPointSet: Distribution = unwrap(value.toPointSet());
- return {
- x: x,
- p1: unwrap(toPointSet.inv(0.01)),
- p5: unwrap(toPointSet.inv(0.05)),
- p10: unwrap(toPointSet.inv(0.1)),
- p20: unwrap(toPointSet.inv(0.2)),
- p30: unwrap(toPointSet.inv(0.3)),
- p40: unwrap(toPointSet.inv(0.4)),
- p50: unwrap(toPointSet.inv(0.5)),
- p60: unwrap(toPointSet.inv(0.6)),
- p70: unwrap(toPointSet.inv(0.7)),
- p80: unwrap(toPointSet.inv(0.8)),
- p90: unwrap(toPointSet.inv(0.9)),
- p95: unwrap(toPointSet.inv(0.95)),
- p99: unwrap(toPointSet.inv(0.99)),
- };
- });
-
- return { percentiles, errors: groupedErrors };
-};
-
export const FunctionChart: React.FC = ({
fn,
chartSettings,
environment,
- height
+ height,
}: FunctionChartProps) => {
- let [mouseOverlay, setMouseOverlay] = React.useState(0);
- function handleHover(_name: string, value: unknown) {
- setMouseOverlay(value as number);
- }
- function handleOut() {
- setMouseOverlay(NaN);
- }
- const signalListeners = { mousemove: handleHover, mouseout: handleOut };
- let mouseItem: result = !!mouseOverlay
- ? runForeign(fn, [mouseOverlay], environment)
- : {
- tag: "Error",
- value: {
- tag: "REExpectedType",
- value: "Hover x-coordinate returned NaN. Expected a number.",
- },
- };
- let showChart =
- mouseItem.tag === "Ok" && mouseItem.value.tag == "distribution" ? (
-
- ) : (
- <>>
- );
+ let result = runForeign(fn, [chartSettings.start], environment);
+ let resultType = result.tag === "Ok" ? result.value.tag : "Error";
- let getPercentilesMemoized = React.useMemo(
- () => getPercentiles({ chartSettings, fn, environment }),
- [environment, fn]
- );
-
- return (
- <>
-
- {showChart}
- {_.entries(getPercentilesMemoized.errors).map(
- ([errorName, errorPoints]) => (
-
- Values:{" "}
- {errorPoints
- .map((r, i) => )
- .reduce((a, b) => (
- <>
- {a}, {b}
- >
- ))}
+ let comp = () => {
+ switch (resultType) {
+ case "distribution":
+ return (
+
+ );
+ case "number":
+ return (
+
+ );
+ case "Error":
+ return (
+ The function failed to be run
+ );
+ default:
+ return (
+
+ There is no function visualization for this type of function
- )
- )}
- >
- );
+ );
+ }
+ };
+
+ return comp();
};
diff --git a/packages/components/src/components/FunctionChart1Dist.tsx b/packages/components/src/components/FunctionChart1Dist.tsx
new file mode 100644
index 00000000..e1d8333c
--- /dev/null
+++ b/packages/components/src/components/FunctionChart1Dist.tsx
@@ -0,0 +1,214 @@
+import * as React from "react";
+import _ from "lodash";
+import type { Spec } from "vega";
+import {
+ Distribution,
+ result,
+ lambdaValue,
+ environment,
+ runForeign,
+ squiggleExpression,
+ errorValue,
+ errorValueToString,
+} from "@quri/squiggle-lang";
+import { createClassFromSpec } from "react-vega";
+import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
+import { DistributionChart } from "./DistributionChart";
+import { NumberShower } from "./NumberShower";
+import { ErrorBox } from "./ErrorBox";
+
+let SquigglePercentilesChart = createClassFromSpec({
+ spec: percentilesSpec as Spec,
+});
+
+const _rangeByCount = (start: number, stop: number, count: number) => {
+ const step = (stop - start) / (count - 1);
+ const items = _.range(start, stop, step);
+ const result = items.concat([stop]);
+ return result;
+};
+
+function unwrap(x: result): a {
+ if (x.tag === "Ok") {
+ return x.value;
+ } else {
+ throw Error("FAILURE TO UNWRAP");
+ }
+}
+export type FunctionChartSettings = {
+ start: number;
+ stop: number;
+ count: number;
+};
+
+interface FunctionChartProps {
+ fn: lambdaValue;
+ chartSettings: FunctionChartSettings;
+ environment: environment;
+ height: number;
+}
+
+type percentiles = {
+ x: number;
+ p1: number;
+ p5: number;
+ p10: number;
+ p20: number;
+ p30: number;
+ p40: number;
+ p50: number;
+ p60: number;
+ p70: number;
+ p80: number;
+ p90: number;
+ p95: number;
+ p99: number;
+}[];
+
+type errors = _.Dictionary<
+ {
+ x: number;
+ value: string;
+ }[]
+>;
+
+type point = { x: number; value: result };
+
+let getPercentiles = ({ chartSettings, fn, environment }) => {
+ let chartPointsToRender = _rangeByCount(
+ chartSettings.start,
+ chartSettings.stop,
+ chartSettings.count
+ );
+
+ let chartPointsData: point[] = chartPointsToRender.map((x) => {
+ let result = runForeign(fn, [x], environment);
+ if (result.tag === "Ok") {
+ if (result.value.tag == "distribution") {
+ return { x, value: { tag: "Ok", value: result.value.value } };
+ } else {
+ return {
+ x,
+ value: {
+ tag: "Error",
+ value:
+ "Cannot currently render functions that don't return distributions",
+ },
+ };
+ }
+ } else {
+ return {
+ x,
+ value: { tag: "Error", value: errorValueToString(result.value) },
+ };
+ }
+ });
+
+ let initialPartition: [
+ { x: number; value: Distribution }[],
+ { x: number; value: string }[]
+ ] = [[], []];
+
+ let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
+ if (current.value.tag === "Ok") {
+ acc[0].push({ x: current.x, value: current.value.value });
+ } else {
+ acc[1].push({ x: current.x, value: current.value.value });
+ }
+ return acc;
+ }, initialPartition);
+
+ let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
+
+ let percentiles: percentiles = functionImage.map(({ x, value }) => {
+ // We convert it to to a pointSet distribution first, so that in case its a sample set
+ // distribution, it doesn't internally convert it to a pointSet distribution for every
+ // single inv() call.
+ let toPointSet: Distribution = unwrap(value.toPointSet());
+ return {
+ x: x,
+ p1: unwrap(toPointSet.inv(0.01)),
+ p5: unwrap(toPointSet.inv(0.05)),
+ p10: unwrap(toPointSet.inv(0.1)),
+ p20: unwrap(toPointSet.inv(0.2)),
+ p30: unwrap(toPointSet.inv(0.3)),
+ p40: unwrap(toPointSet.inv(0.4)),
+ p50: unwrap(toPointSet.inv(0.5)),
+ p60: unwrap(toPointSet.inv(0.6)),
+ p70: unwrap(toPointSet.inv(0.7)),
+ p80: unwrap(toPointSet.inv(0.8)),
+ p90: unwrap(toPointSet.inv(0.9)),
+ p95: unwrap(toPointSet.inv(0.95)),
+ p99: unwrap(toPointSet.inv(0.99)),
+ };
+ });
+
+ return { percentiles, errors: groupedErrors };
+};
+
+export const FunctionChart1Dist: React.FC = ({
+ fn,
+ chartSettings,
+ environment,
+ height
+}: FunctionChartProps) => {
+ let [mouseOverlay, setMouseOverlay] = React.useState(0);
+ function handleHover(_name: string, value: unknown) {
+ setMouseOverlay(value as number);
+ }
+ function handleOut() {
+ setMouseOverlay(NaN);
+ }
+ const signalListeners = { mousemove: handleHover, mouseout: handleOut };
+ let mouseItem: result = !!mouseOverlay
+ ? runForeign(fn, [mouseOverlay], environment)
+ : {
+ tag: "Error",
+ value: {
+ tag: "REExpectedType",
+ value: "Hover x-coordinate returned NaN. Expected a number.",
+ },
+ };
+ let showChart =
+ mouseItem.tag === "Ok" && mouseItem.value.tag == "distribution" ? (
+
+ ) : (
+ <>>
+ );
+
+ let getPercentilesMemoized = React.useMemo(
+ () => getPercentiles({ chartSettings, fn, environment }),
+ [environment, fn]
+ );
+
+ return (
+ <>
+
+ {showChart}
+ {_.entries(getPercentilesMemoized.errors).map(
+ ([errorName, errorPoints]) => (
+
+ Values:{" "}
+ {errorPoints
+ .map((r, i) => )
+ .reduce((a, b) => (
+ <>
+ {a}, {b}
+ >
+ ))}
+
+ )
+ )}
+ >
+ );
+};
diff --git a/packages/components/src/components/FunctionChart1Number.tsx b/packages/components/src/components/FunctionChart1Number.tsx
new file mode 100644
index 00000000..fa72bf71
--- /dev/null
+++ b/packages/components/src/components/FunctionChart1Number.tsx
@@ -0,0 +1,145 @@
+import * as React from "react";
+import _ from "lodash";
+import type { Spec } from "vega";
+import {
+ Distribution,
+ result,
+ lambdaValue,
+ environment,
+ runForeign,
+ squiggleExpression,
+ errorValue,
+ errorValueToString,
+} from "@quri/squiggle-lang";
+import { createClassFromSpec } from "react-vega";
+import * as lineChartSpec from "../vega-specs/spec-line-chart.json";
+import { DistributionChart } from "./DistributionChart";
+import { NumberShower } from "./NumberShower";
+import { ErrorBox } from "./ErrorBox";
+
+let SquiggleLineChart = createClassFromSpec({
+ spec: lineChartSpec as Spec,
+});
+
+const _rangeByCount = (start: number, stop: number, count: number) => {
+ const step = (stop - start) / (count - 1);
+ const items = _.range(start, stop, step);
+ const result = items.concat([stop]);
+ return result;
+};
+
+function unwrap(x: result): a {
+ if (x.tag === "Ok") {
+ return x.value;
+ } else {
+ throw Error("FAILURE TO UNWRAP");
+ }
+}
+export type FunctionChartSettings = {
+ start: number;
+ stop: number;
+ count: number;
+};
+
+interface FunctionChartProps {
+ fn: lambdaValue;
+ chartSettings: FunctionChartSettings;
+ environment: environment;
+ height: number;
+}
+
+type point = { x: number; value: result };
+
+let getFunctionImage = ({ chartSettings, fn, environment }) => {
+ let chartPointsToRender = _rangeByCount(
+ chartSettings.start,
+ chartSettings.stop,
+ chartSettings.count
+ );
+
+ let chartPointsData: point[] = chartPointsToRender.map((x) => {
+ let result = runForeign(fn, [x], environment);
+ if (result.tag === "Ok") {
+ if (result.value.tag == "number") {
+ return { x, value: { tag: "Ok", value: result.value.value } };
+ } else {
+ return {
+ x,
+ value: {
+ tag: "Error",
+ value:
+ "Cannot currently render functions that don't return distributions",
+ },
+ };
+ }
+ } else {
+ return {
+ x,
+ value: { tag: "Error", value: errorValueToString(result.value) },
+ };
+ }
+ });
+
+ let initialPartition: [
+ { x: number; value: number }[],
+ { x: number; value: string }[]
+ ] = [[], []];
+
+ let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
+ if (current.value.tag === "Ok") {
+ acc[0].push({ x: current.x, value: current.value.value });
+ } else {
+ acc[1].push({ x: current.x, value: current.value.value });
+ }
+ return acc;
+ }, initialPartition);
+
+ return { errors, functionImage };
+};
+
+export const FunctionChart1Number: React.FC = ({
+ fn,
+ chartSettings,
+ environment,
+ height,
+}: FunctionChartProps) => {
+ let [mouseOverlay, setMouseOverlay] = React.useState(0);
+ function handleHover(_name: string, value: unknown) {
+ setMouseOverlay(value as number);
+ }
+ function handleOut() {
+ setMouseOverlay(NaN);
+ }
+ const signalListeners = { mousemove: handleHover, mouseout: handleOut };
+ let mouseItem: result = !!mouseOverlay
+ ? runForeign(fn, [mouseOverlay], environment)
+ : {
+ tag: "Error",
+ value: {
+ tag: "REExpectedType",
+ value: "Hover x-coordinate returned NaN. Expected a number.",
+ },
+ };
+
+ let getFunctionImageMemoized = React.useMemo(
+ () => getFunctionImage({ chartSettings, fn, environment }),
+ [environment, fn]
+ );
+
+ let data = getFunctionImageMemoized.functionImage.map(({x, value}) => ({x, y:value}))
+ return (
+ <>
+
+ {getFunctionImageMemoized.errors.map(({ x, value }) => (
+
+ Error at point ${x}
+
+ ))}
+ >
+ );
+};
diff --git a/packages/components/src/components/SquigglePlayground.tsx b/packages/components/src/components/SquigglePlayground.tsx
index 1bbd84a2..c28bf11a 100644
--- a/packages/components/src/components/SquigglePlayground.tsx
+++ b/packages/components/src/components/SquigglePlayground.tsx
@@ -72,9 +72,13 @@ const Display = styled.div`
max-height: ${(props) => props.maxHeight}px;
`;
-const Row = styled.div`
+interface RowProps {
+ readonly leftPercentage: number;
+}
+
+const Row = styled.div`
display: grid;
- grid-template-columns: 50% 50%;
+ grid-template-columns: ${(p) => p.leftPercentage}% ${(p) => 100 - p.leftPercentage}%;
`;
const Col = styled.div``;
@@ -111,6 +115,7 @@ const schema = yup
.min(10)
.max(10000),
chartHeight: yup.number().required().positive().integer().default(350),
+ leftSize: yup.number().required().positive().integer().min(10).max(100).default(50),
showTypes: yup.boolean(),
showControls: yup.boolean(),
showSummary: yup.boolean(),
@@ -152,14 +157,15 @@ let SquigglePlayground: FC = ({
showTypes: showTypes,
showControls: showControls,
showSummary: showSummary,
+ leftSize: 50,
},
});
- const foo = useWatch({
+ const vars = useWatch({
control,
});
let env: environment = {
- sampleCount: Number(foo.sampleCount),
- xyPointLength: Number(foo.xyPointLength),
+ sampleCount: Number(vars.sampleCount),
+ xyPointLength: Number(vars.xyPointLength),
};
let getChangeJson = (r: string) => {
setImportString(r);
@@ -169,17 +175,17 @@ let SquigglePlayground: FC = ({
} catch (e) {
setImportsAreValid(false);
}
- ("");
};
return (
+
-
+
= ({
squiggleString={squiggleString}
environment={env}
chartSettings={chartSettings}
- height={foo.chartHeight}
- showTypes={foo.showTypes}
- showControls={foo.showControls}
+ height={vars.chartHeight}
+ showTypes={vars.showTypes}
+ showControls={vars.showControls}
bindings={defaultBindings}
jsImports={imports}
- showSummary={foo.showSummary}
+ showSummary={vars.showSummary}
/>
diff --git a/packages/components/src/vega-specs/spec-line-chart.json b/packages/components/src/vega-specs/spec-line-chart.json
new file mode 100644
index 00000000..117d9543
--- /dev/null
+++ b/packages/components/src/vega-specs/spec-line-chart.json
@@ -0,0 +1,88 @@
+{
+ "$schema": "https://vega.github.io/schema/vega/v5.json",
+ "width": 500,
+ "height": 200,
+ "padding": 5,
+ "data": [
+ {
+ "name": "facet",
+ "values": [],
+ "format": {
+ "type": "json",
+ "parse": {
+ "timestamp": "date"
+ }
+ }
+ }
+ ],
+ "scales": [
+ {
+ "name": "x",
+ "type": "linear",
+ "nice": true,
+ "domain": {
+ "data": "facet",
+ "field": "x"
+ },
+ "range": "width"
+ },
+ {
+ "name": "y",
+ "type": "linear",
+ "range": "height",
+ "nice": true,
+ "zero": true,
+ "domain": {
+ "data": "facet",
+ "field": "y"
+ }
+ }
+ ],
+ "signals": [
+ {
+ "name": "mousemove",
+ "on": [{ "events": "mousemove", "update": "invert('x', x())" }]
+ },
+ {
+ "name": "mouseout",
+ "on": [{ "events": "mouseout", "update": "invert('x', x())" }]
+ }
+ ],
+ "axes": [
+ {
+ "orient": "bottom",
+ "scale": "x",
+ "grid": false,
+ "labelColor": "#727d93",
+ "tickColor": "#fff",
+ "tickOpacity": 0.0,
+ "domainColor": "#727d93",
+ "domainOpacity": 0.1,
+ "tickCount": 5
+ },
+ {
+ "orient": "left",
+ "scale": "y",
+ "grid": false,
+ "labelColor": "#727d93",
+ "tickColor": "#fff",
+ "tickOpacity": 0.0,
+ "domainColor": "#727d93",
+ "domainOpacity": 0.1,
+ "tickCount": 5
+ }
+ ],
+ "marks": [
+ {
+ "type": "line",
+ "from": { "data": "facet" },
+ "encode": {
+ "enter": {
+ "x": { "scale": "x", "field": "x" },
+ "y": { "scale": "y", "field": "y" },
+ "strokeWidth": { "value": 2 }
+ }
+ }
+ }
+ ]
+}
diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json
index c8d799d5..38d28704 100644
--- a/packages/components/tsconfig.json
+++ b/packages/components/tsconfig.json
@@ -20,7 +20,8 @@
},
"files": [
"src/vega-specs/spec-distributions.json",
- "src/vega-specs/spec-percentiles.json"
+ "src/vega-specs/spec-percentiles.json",
+ "src/vega-specs/spec-line-chart.json"
],
"target": "ES6",
"include": ["src/**/*", "src/*"],