Compare commits
4 Commits
refactor-c
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
9e2eace05e | ||
|
a0000cd179 | ||
|
98454a87b5 | ||
|
0f8e7ce6b6 |
|
@ -24,7 +24,7 @@ export const Alert: React.FC<{
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={clsx("rounded-md p-4", backgroundColor)}>
|
<div className={clsx("rounded-md p-4", backgroundColor)} role="status">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Icon
|
<Icon
|
||||||
className={clsx("h-5 w-5 flex-shrink-0", iconColor)}
|
className={clsx("h-5 w-5 flex-shrink-0", iconColor)}
|
||||||
|
|
|
@ -55,10 +55,7 @@ export const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
editorProps={{
|
editorProps={{
|
||||||
$blockScrolling: true,
|
$blockScrolling: true,
|
||||||
}}
|
}}
|
||||||
setOptions={{
|
setOptions={{}}
|
||||||
enableBasicAutocompletion: false,
|
|
||||||
enableLiveAutocompletion: false,
|
|
||||||
}}
|
|
||||||
commands={[
|
commands={[
|
||||||
{
|
{
|
||||||
name: "submit",
|
name: "submit",
|
||||||
|
|
|
@ -49,10 +49,10 @@ export function makePlot(record: SqRecord): Plot | void {
|
||||||
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
plot,
|
plot,
|
||||||
|
environment,
|
||||||
height,
|
height,
|
||||||
showSummary,
|
showSummary,
|
||||||
width,
|
width,
|
||||||
environment,
|
|
||||||
logX,
|
logX,
|
||||||
actions = false,
|
actions = false,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -89,12 +89,8 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
|
|
||||||
const spec = buildVegaSpec({
|
const spec = buildVegaSpec({
|
||||||
...props,
|
...props,
|
||||||
minX: Number.isFinite(props.minX)
|
minX: props.minX ?? Math.min(...domain.map((x) => x.x)),
|
||||||
? props.minX
|
maxX: props.minX ?? Math.max(...domain.map((x) => x.x)),
|
||||||
: Math.min(...domain.map((x) => x.x)),
|
|
||||||
maxX: Number.isFinite(props.maxX)
|
|
||||||
? props.maxX
|
|
||||||
: Math.max(...domain.map((x) => x.x)),
|
|
||||||
maxY: Math.max(...domain.map((x) => x.y)),
|
maxY: Math.max(...domain.map((x) => x.y)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,158 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useSquiggle, SquiggleArgs } from "../lib/hooks/useSquiggle";
|
|
||||||
import {
|
import {
|
||||||
SquiggleViewer,
|
SqValue,
|
||||||
FlattenedViewSettings,
|
environment,
|
||||||
createViewSettings,
|
SqProject,
|
||||||
} from "./SquiggleViewer";
|
defaultEnvironment,
|
||||||
|
} from "@quri/squiggle-lang";
|
||||||
|
import { useSquiggle } from "../lib/hooks";
|
||||||
|
import { SquiggleViewer } from "./SquiggleViewer";
|
||||||
|
import { JsImports } from "../lib/jsImports";
|
||||||
import { getValueToRender } from "../lib/utility";
|
import { getValueToRender } from "../lib/utility";
|
||||||
|
|
||||||
export type SquiggleChartProps = SquiggleArgs & FlattenedViewSettings;
|
export type SquiggleChartProps = {
|
||||||
|
/** The input string for squiggle */
|
||||||
|
code: string;
|
||||||
|
/** Allows to re-run the code if code hasn't changed */
|
||||||
|
executionId?: number;
|
||||||
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
|
sampleCount?: number;
|
||||||
|
/** If the result is a function, where the function domain starts */
|
||||||
|
diagramStart?: number;
|
||||||
|
/** If the result is a function, where the function domain ends */
|
||||||
|
diagramStop?: number;
|
||||||
|
/** If the result is a function, the amount of stops sampled */
|
||||||
|
diagramCount?: number;
|
||||||
|
/** When the squiggle code gets reevaluated */
|
||||||
|
onChange?(expr: SqValue | undefined, sourceName: string): void;
|
||||||
|
/** CSS width of the element */
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
/** JS imported parameters */
|
||||||
|
jsImports?: JsImports;
|
||||||
|
/** Whether to show a summary of the distribution */
|
||||||
|
showSummary?: boolean;
|
||||||
|
/** Set the x scale to be logarithmic by deault */
|
||||||
|
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 the x-axis should be dates or numbers */
|
||||||
|
xAxisType?: "number" | "dateTime";
|
||||||
|
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
||||||
|
distributionChartActions?: boolean;
|
||||||
|
enableLocalSettings?: boolean;
|
||||||
|
} & (StandaloneExecutionProps | ProjectExecutionProps);
|
||||||
|
|
||||||
|
// Props needed for a standalone execution
|
||||||
|
type StandaloneExecutionProps = {
|
||||||
|
project?: undefined;
|
||||||
|
continues?: undefined;
|
||||||
|
/** The amount of points returned to draw the distribution, not needed if using a project */
|
||||||
|
environment?: environment;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Props needed when executing inside a project.
|
||||||
|
type ProjectExecutionProps = {
|
||||||
|
environment?: undefined;
|
||||||
|
/** The project that this execution is part of */
|
||||||
|
project: SqProject;
|
||||||
|
/** What other squiggle sources from the project to continue. Default [] */
|
||||||
|
continues?: string[];
|
||||||
|
};
|
||||||
|
const defaultOnChange = () => {};
|
||||||
|
const defaultImports: JsImports = {};
|
||||||
|
|
||||||
|
export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
|
||||||
|
const {
|
||||||
|
showSummary = false,
|
||||||
|
logX = false,
|
||||||
|
expY = false,
|
||||||
|
diagramStart = 0,
|
||||||
|
diagramStop = 10,
|
||||||
|
diagramCount = 20,
|
||||||
|
tickFormat,
|
||||||
|
minX,
|
||||||
|
maxX,
|
||||||
|
color,
|
||||||
|
title,
|
||||||
|
xAxisType = "number",
|
||||||
|
distributionChartActions,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const distributionPlotSettings = {
|
||||||
|
showSummary,
|
||||||
|
logX,
|
||||||
|
expY,
|
||||||
|
format: tickFormat,
|
||||||
|
minX,
|
||||||
|
maxX,
|
||||||
|
color,
|
||||||
|
title,
|
||||||
|
xAxisType,
|
||||||
|
actions: distributionChartActions,
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartSettings = {
|
||||||
|
start: diagramStart,
|
||||||
|
stop: diagramStop,
|
||||||
|
count: diagramCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { distributionPlotSettings, chartSettings };
|
||||||
|
};
|
||||||
|
|
||||||
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
const resultAndBindings = useSquiggle(props);
|
const { distributionPlotSettings, chartSettings } =
|
||||||
|
splitSquiggleChartSettings(props);
|
||||||
|
|
||||||
|
const {
|
||||||
|
code,
|
||||||
|
jsImports = defaultImports,
|
||||||
|
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||||
|
executionId = 0,
|
||||||
|
width,
|
||||||
|
height = 200,
|
||||||
|
enableLocalSettings = false,
|
||||||
|
continues,
|
||||||
|
project,
|
||||||
|
environment,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const resultAndBindings = useSquiggle({
|
||||||
|
environment,
|
||||||
|
continues,
|
||||||
|
project,
|
||||||
|
code,
|
||||||
|
jsImports,
|
||||||
|
onChange,
|
||||||
|
executionId,
|
||||||
|
});
|
||||||
|
|
||||||
const valueToRender = getValueToRender(resultAndBindings);
|
const valueToRender = getValueToRender(resultAndBindings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SquiggleViewer {...createViewSettings(props)} result={valueToRender} />
|
<SquiggleViewer
|
||||||
|
result={valueToRender}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
|
chartSettings={chartSettings}
|
||||||
|
environment={
|
||||||
|
project ? project.getEnvironment() : environment ?? defaultEnvironment
|
||||||
|
}
|
||||||
|
enableLocalSettings={enableLocalSettings}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { CodeEditor } from "./CodeEditor";
|
import { CodeEditor } from "./CodeEditor";
|
||||||
import { SquiggleContainer } from "./SquiggleContainer";
|
import { SquiggleContainer } from "./SquiggleContainer";
|
||||||
import { useMaybeControlledValue } from "../lib/hooks";
|
|
||||||
import { useSquiggle, SquiggleArgs } from "../lib/hooks/useSquiggle";
|
|
||||||
import { SqLocation } from "@quri/squiggle-lang";
|
|
||||||
import {
|
import {
|
||||||
SquiggleViewer,
|
splitSquiggleChartSettings,
|
||||||
createViewSettings,
|
SquiggleChartProps,
|
||||||
FlattenedViewSettings,
|
} from "./SquiggleChart";
|
||||||
} from "./SquiggleViewer";
|
import { useMaybeControlledValue, useSquiggle } from "../lib/hooks";
|
||||||
|
import { JsImports } from "../lib/jsImports";
|
||||||
|
import { defaultEnvironment, SqLocation, SqProject } from "@quri/squiggle-lang";
|
||||||
|
import { SquiggleViewer } from "./SquiggleViewer";
|
||||||
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
||||||
|
|
||||||
const WrappedCodeEditor: React.FC<{
|
const WrappedCodeEditor: React.FC<{
|
||||||
|
@ -28,11 +28,13 @@ const WrappedCodeEditor: React.FC<{
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type SquiggleEditorProps = SquiggleArgs &
|
export type SquiggleEditorProps = SquiggleChartProps & {
|
||||||
FlattenedViewSettings & {
|
defaultCode?: string;
|
||||||
defaultCode?: string;
|
onCodeChange?: (code: string) => void;
|
||||||
onCodeChange?: (code: string) => void;
|
};
|
||||||
};
|
|
||||||
|
const defaultOnChange = () => {};
|
||||||
|
const defaultImports: JsImports = {};
|
||||||
|
|
||||||
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
||||||
const [code, setCode] = useMaybeControlledValue({
|
const [code, setCode] = useMaybeControlledValue({
|
||||||
|
@ -41,7 +43,30 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
||||||
onChange: props.onCodeChange,
|
onChange: props.onCodeChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
const resultAndBindings = useSquiggle({ ...props, code });
|
const { distributionPlotSettings, chartSettings } =
|
||||||
|
splitSquiggleChartSettings(props);
|
||||||
|
|
||||||
|
const {
|
||||||
|
environment,
|
||||||
|
jsImports = defaultImports,
|
||||||
|
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||||
|
executionId = 0,
|
||||||
|
width,
|
||||||
|
height = 200,
|
||||||
|
enableLocalSettings = false,
|
||||||
|
continues,
|
||||||
|
project,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const resultAndBindings = useSquiggle({
|
||||||
|
environment,
|
||||||
|
continues,
|
||||||
|
code,
|
||||||
|
project,
|
||||||
|
jsImports,
|
||||||
|
onChange,
|
||||||
|
executionId,
|
||||||
|
});
|
||||||
|
|
||||||
const valueToRender = getValueToRender(resultAndBindings);
|
const valueToRender = getValueToRender(resultAndBindings);
|
||||||
const errorLocations = getErrorLocations(resultAndBindings.result);
|
const errorLocations = getErrorLocations(resultAndBindings.result);
|
||||||
|
@ -53,7 +78,15 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
||||||
setCode={setCode}
|
setCode={setCode}
|
||||||
errorLocations={errorLocations}
|
errorLocations={errorLocations}
|
||||||
/>
|
/>
|
||||||
<SquiggleViewer result={valueToRender} {...createViewSettings(props)} />
|
<SquiggleViewer
|
||||||
|
result={valueToRender}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
|
chartSettings={chartSettings}
|
||||||
|
environment={environment ?? defaultEnvironment}
|
||||||
|
enableLocalSettings={enableLocalSettings}
|
||||||
|
/>
|
||||||
</SquiggleContainer>
|
</SquiggleContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
useRunnerState,
|
useRunnerState,
|
||||||
useSquiggle,
|
useSquiggle,
|
||||||
} from "../lib/hooks";
|
} from "../lib/hooks";
|
||||||
import { SquiggleArgs } from "../lib/hooks/useSquiggle";
|
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import {
|
import {
|
||||||
ChartSquareBarIcon,
|
ChartSquareBarIcon,
|
||||||
|
@ -29,8 +28,9 @@ import {
|
||||||
} from "@heroicons/react/solid";
|
} from "@heroicons/react/solid";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import { environment } from "@quri/squiggle-lang";
|
import { environment, SqProject } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
import { SquiggleChartProps } from "./SquiggleChart";
|
||||||
import { CodeEditor } from "./CodeEditor";
|
import { CodeEditor } from "./CodeEditor";
|
||||||
import { JsonEditor } from "./JsonEditor";
|
import { JsonEditor } from "./JsonEditor";
|
||||||
import { ErrorAlert, SuccessAlert } from "./Alert";
|
import { ErrorAlert, SuccessAlert } from "./Alert";
|
||||||
|
@ -41,27 +41,23 @@ import { InputItem } from "./ui/InputItem";
|
||||||
import { Text } from "./ui/Text";
|
import { Text } from "./ui/Text";
|
||||||
import { ViewSettings, viewSettingsSchema } from "./ViewSettings";
|
import { ViewSettings, viewSettingsSchema } from "./ViewSettings";
|
||||||
import { HeadedSection } from "./ui/HeadedSection";
|
import { HeadedSection } from "./ui/HeadedSection";
|
||||||
|
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
||||||
import { Button } from "./ui/Button";
|
import { Button } from "./ui/Button";
|
||||||
import { JsImports } from "../lib/jsImports";
|
import { JsImports } from "../lib/jsImports";
|
||||||
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
||||||
import {
|
import { SquiggleViewer } from "./SquiggleViewer";
|
||||||
SquiggleViewer,
|
|
||||||
FlattenedViewSettings,
|
|
||||||
createViewSettings,
|
|
||||||
} from "./SquiggleViewer";
|
|
||||||
|
|
||||||
type PlaygroundProps = SquiggleArgs &
|
type PlaygroundProps = SquiggleChartProps & {
|
||||||
FlattenedViewSettings & {
|
/** The initial squiggle string to put in the playground */
|
||||||
/** The initial squiggle string to put in the playground */
|
defaultCode?: string;
|
||||||
defaultCode?: string;
|
onCodeChange?(expr: string): void;
|
||||||
onCodeChange?(expr: string): void;
|
/* When settings change */
|
||||||
/* When settings change */
|
onSettingsChange?(settings: any): void;
|
||||||
onSettingsChange?(settings: any): void;
|
/** Should we show the editor? */
|
||||||
/** Should we show the editor? */
|
showEditor?: boolean;
|
||||||
showEditor?: boolean;
|
/** Useful for playground on squiggle website, where we update the anchor link based on current code and settings */
|
||||||
/** Useful for playground on squiggle website, where we update the anchor link based on current code and settings */
|
showShareButton?: boolean;
|
||||||
showShareButton?: boolean;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const schema = yup
|
const schema = yup
|
||||||
.object({})
|
.object({})
|
||||||
|
@ -82,7 +78,6 @@ const schema = yup
|
||||||
.default(1000)
|
.default(1000)
|
||||||
.min(10)
|
.min(10)
|
||||||
.max(10000),
|
.max(10000),
|
||||||
showEditor: yup.boolean().required().default(true),
|
|
||||||
})
|
})
|
||||||
.concat(viewSettingsSchema);
|
.concat(viewSettingsSchema);
|
||||||
|
|
||||||
|
@ -240,14 +235,25 @@ export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
|
||||||
getLeftPanelElement: () => undefined,
|
getLeftPanelElement: () => undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SquigglePlayground: FC<PlaygroundProps> = (props) => {
|
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
const {
|
defaultCode = "",
|
||||||
defaultCode = "",
|
height = 500,
|
||||||
code: controlledCode,
|
showSummary = true,
|
||||||
onCodeChange,
|
logX = false,
|
||||||
onSettingsChange,
|
expY = false,
|
||||||
showShareButton = false,
|
title,
|
||||||
} = props;
|
minX,
|
||||||
|
maxX,
|
||||||
|
tickFormat = defaultTickFormat,
|
||||||
|
distributionChartActions,
|
||||||
|
code: controlledCode,
|
||||||
|
onCodeChange,
|
||||||
|
onSettingsChange,
|
||||||
|
showEditor = true,
|
||||||
|
showShareButton = false,
|
||||||
|
continues,
|
||||||
|
project,
|
||||||
|
}) => {
|
||||||
const [code, setCode] = useMaybeControlledValue({
|
const [code, setCode] = useMaybeControlledValue({
|
||||||
value: controlledCode,
|
value: controlledCode,
|
||||||
defaultValue: defaultCode,
|
defaultValue: defaultCode,
|
||||||
|
@ -256,19 +262,29 @@ export const SquigglePlayground: FC<PlaygroundProps> = (props) => {
|
||||||
|
|
||||||
const [imports, setImports] = useState<JsImports>({});
|
const [imports, setImports] = useState<JsImports>({});
|
||||||
|
|
||||||
let defaultValues: FormFields = {
|
|
||||||
...schema.getDefault(),
|
|
||||||
...props,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { register, control } = useForm({
|
const { register, control } = useForm({
|
||||||
resolver: yupResolver(schema),
|
resolver: yupResolver(schema),
|
||||||
defaultValues: defaultValues,
|
defaultValues: {
|
||||||
|
sampleCount: 1000,
|
||||||
|
xyPointLength: 1000,
|
||||||
|
chartHeight: 150,
|
||||||
|
logX,
|
||||||
|
expY,
|
||||||
|
title,
|
||||||
|
minX,
|
||||||
|
maxX,
|
||||||
|
tickFormat,
|
||||||
|
distributionChartActions,
|
||||||
|
showSummary,
|
||||||
|
showEditor,
|
||||||
|
diagramStart: 0,
|
||||||
|
diagramStop: 10,
|
||||||
|
diagramCount: 20,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const rawVars = useWatch({
|
const vars = useWatch({
|
||||||
control,
|
control,
|
||||||
});
|
});
|
||||||
let vars = useMemo(() => ({ ...schema.getDefault(), ...rawVars }), [rawVars]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onSettingsChange?.(vars);
|
onSettingsChange?.(vars);
|
||||||
|
@ -291,12 +307,14 @@ export const SquigglePlayground: FC<PlaygroundProps> = (props) => {
|
||||||
executionId,
|
executionId,
|
||||||
} = useRunnerState(code);
|
} = useRunnerState(code);
|
||||||
|
|
||||||
let args: SquiggleArgs = props;
|
const resultAndBindings = useSquiggle({
|
||||||
args = { ...args, code, jsImports: imports, executionId };
|
environment,
|
||||||
if (!args.project) {
|
continues,
|
||||||
args = { ...args, environment };
|
code: renderedCode,
|
||||||
}
|
project,
|
||||||
const resultAndBindings = useSquiggle(args);
|
jsImports: imports,
|
||||||
|
executionId,
|
||||||
|
});
|
||||||
|
|
||||||
const valueToRender = getValueToRender(resultAndBindings);
|
const valueToRender = getValueToRender(resultAndBindings);
|
||||||
|
|
||||||
|
@ -306,7 +324,27 @@ export const SquigglePlayground: FC<PlaygroundProps> = (props) => {
|
||||||
{isRunning ? (
|
{isRunning ? (
|
||||||
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
|
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
|
||||||
) : null}
|
) : null}
|
||||||
<SquiggleViewer {...createViewSettings(vars)} result={valueToRender} />
|
<SquiggleViewer
|
||||||
|
result={valueToRender}
|
||||||
|
environment={environment}
|
||||||
|
height={vars.chartHeight || 150}
|
||||||
|
distributionPlotSettings={{
|
||||||
|
showSummary: vars.showSummary ?? false,
|
||||||
|
logX: vars.logX ?? false,
|
||||||
|
expY: vars.expY ?? false,
|
||||||
|
format: vars.tickFormat,
|
||||||
|
minX: vars.minX,
|
||||||
|
maxX: vars.maxX,
|
||||||
|
title: vars.title,
|
||||||
|
actions: vars.distributionChartActions,
|
||||||
|
}}
|
||||||
|
chartSettings={{
|
||||||
|
start: vars.diagramStart ?? 0,
|
||||||
|
stop: vars.diagramStop ?? 10,
|
||||||
|
count: vars.diagramCount ?? 20,
|
||||||
|
}}
|
||||||
|
enableLocalSettings={true}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -321,7 +359,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = (props) => {
|
||||||
onSubmit={run}
|
onSubmit={run}
|
||||||
oneLine={false}
|
oneLine={false}
|
||||||
showGutter={true}
|
showGutter={true}
|
||||||
height={(props.chartHeight ?? 200) - 1}
|
height={height - 1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -360,7 +398,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = (props) => {
|
||||||
<div className="flex mt-2">
|
<div className="flex mt-2">
|
||||||
<div
|
<div
|
||||||
className="w-1/2 relative"
|
className="w-1/2 relative"
|
||||||
style={{ minHeight: props.chartHeight }}
|
style={{ minHeight: height }}
|
||||||
ref={leftPanelRef}
|
ref={leftPanelRef}
|
||||||
>
|
>
|
||||||
{tabs}
|
{tabs}
|
||||||
|
|
|
@ -105,9 +105,9 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||||
return (
|
return (
|
||||||
<DistributionChart
|
<DistributionChart
|
||||||
plot={defaultPlot(value.value)}
|
plot={defaultPlot(value.value)}
|
||||||
{...settings.distributionPlotSettings}
|
|
||||||
height={settings.chartHeight}
|
|
||||||
environment={settings.environment}
|
environment={settings.environment}
|
||||||
|
{...settings.distributionPlotSettings}
|
||||||
|
height={settings.height}
|
||||||
width={width}
|
width={width}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -178,7 +178,7 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||||
fn={value.value}
|
fn={value.value}
|
||||||
chartSettings={settings.chartSettings}
|
chartSettings={settings.chartSettings}
|
||||||
distributionPlotSettings={settings.distributionPlotSettings}
|
distributionPlotSettings={settings.distributionPlotSettings}
|
||||||
height={settings.chartHeight}
|
height={settings.height}
|
||||||
environment={{
|
environment={{
|
||||||
sampleCount: settings.environment.sampleCount / 10,
|
sampleCount: settings.environment.sampleCount / 10,
|
||||||
xyPointLength: settings.environment.xyPointLength / 10,
|
xyPointLength: settings.environment.xyPointLength / 10,
|
||||||
|
@ -203,7 +203,7 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(_) => (
|
{(settings) => (
|
||||||
<div>NOT IMPLEMENTED IN 0.4 YET</div>
|
<div>NOT IMPLEMENTED IN 0.4 YET</div>
|
||||||
// <FunctionChart
|
// <FunctionChart
|
||||||
// fn={expression.value.fn}
|
// fn={expression.value.fn}
|
||||||
|
@ -252,7 +252,7 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||||
plot={plot}
|
plot={plot}
|
||||||
environment={settings.environment}
|
environment={settings.environment}
|
||||||
{...settings.distributionPlotSettings}
|
{...settings.distributionPlotSettings}
|
||||||
height={settings.chartHeight}
|
height={settings.height}
|
||||||
width={width}
|
width={width}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,13 +3,9 @@ import React, { useContext, useRef, useState, useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { Modal } from "../ui/Modal";
|
import { Modal } from "../ui/Modal";
|
||||||
import {
|
import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
|
||||||
ViewSettings,
|
|
||||||
viewSettingsSchema,
|
|
||||||
mergedToViewSettings,
|
|
||||||
viewSettingsToLocal,
|
|
||||||
} from "../ViewSettings";
|
|
||||||
import { ViewerContext } from "./ViewerContext";
|
import { ViewerContext } from "./ViewerContext";
|
||||||
|
import { defaultTickFormat } from "../../lib/distributionSpecBuilder";
|
||||||
import { PlaygroundContext } from "../SquigglePlayground";
|
import { PlaygroundContext } from "../SquigglePlayground";
|
||||||
import { SqValue } from "@quri/squiggle-lang";
|
import { SqValue } from "@quri/squiggle-lang";
|
||||||
import { locationAsString } from "./utils";
|
import { locationAsString } from "./utils";
|
||||||
|
@ -38,14 +34,44 @@ const ItemSettingsModal: React.FC<
|
||||||
|
|
||||||
const { register, watch } = useForm({
|
const { register, watch } = useForm({
|
||||||
resolver: yupResolver(viewSettingsSchema),
|
resolver: yupResolver(viewSettingsSchema),
|
||||||
defaultValues: mergedToViewSettings(mergedSettings),
|
defaultValues: {
|
||||||
|
// this is a mess and should be fixed
|
||||||
|
showEditor: true, // doesn't matter
|
||||||
|
chartHeight: mergedSettings.height,
|
||||||
|
showSummary: mergedSettings.distributionPlotSettings.showSummary,
|
||||||
|
logX: mergedSettings.distributionPlotSettings.logX,
|
||||||
|
expY: mergedSettings.distributionPlotSettings.expY,
|
||||||
|
tickFormat:
|
||||||
|
mergedSettings.distributionPlotSettings.format || defaultTickFormat,
|
||||||
|
title: mergedSettings.distributionPlotSettings.title,
|
||||||
|
minX: mergedSettings.distributionPlotSettings.minX,
|
||||||
|
maxX: mergedSettings.distributionPlotSettings.maxX,
|
||||||
|
distributionChartActions: mergedSettings.distributionPlotSettings.actions,
|
||||||
|
diagramStart: mergedSettings.chartSettings.start,
|
||||||
|
diagramStop: mergedSettings.chartSettings.stop,
|
||||||
|
diagramCount: mergedSettings.chartSettings.count,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = watch((vars) => {
|
const subscription = watch((vars) => {
|
||||||
const settings = getSettings(value.location); // get the latest version
|
const settings = getSettings(value.location); // get the latest version
|
||||||
setSettings(value.location, {
|
setSettings(value.location, {
|
||||||
...settings,
|
...settings,
|
||||||
...viewSettingsToLocal(vars),
|
distributionPlotSettings: {
|
||||||
|
showSummary: vars.showSummary,
|
||||||
|
logX: vars.logX,
|
||||||
|
expY: vars.expY,
|
||||||
|
format: vars.tickFormat,
|
||||||
|
title: vars.title,
|
||||||
|
minX: vars.minX,
|
||||||
|
maxX: vars.maxX,
|
||||||
|
actions: vars.distributionChartActions,
|
||||||
|
},
|
||||||
|
chartSettings: {
|
||||||
|
start: vars.diagramStart,
|
||||||
|
stop: vars.diagramStop,
|
||||||
|
count: vars.diagramCount,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
onChange();
|
onChange();
|
||||||
});
|
});
|
||||||
|
@ -76,6 +102,7 @@ const ItemSettingsModal: React.FC<
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<ViewSettings
|
<ViewSettings
|
||||||
register={register}
|
register={register}
|
||||||
|
withShowEditorSetting={false}
|
||||||
withFunctionSettings={withFunctionSettings}
|
withFunctionSettings={withFunctionSettings}
|
||||||
disableLogXSetting={disableLogX}
|
disableLogXSetting={disableLogX}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -45,7 +45,7 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||||
: location.path.items[location.path.items.length - 1];
|
: location.path.items[location.path.items.length - 1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div role={isTopLevel ? "status" : undefined}>
|
||||||
<header className="inline-flex space-x-1">
|
<header className="inline-flex space-x-1">
|
||||||
<Tooltip text={heading}>
|
<Tooltip text={heading}>
|
||||||
<span
|
<span
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang";
|
import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
||||||
import { viewSettingsSchema, viewSettingsToMerged } from "../ViewSettings";
|
|
||||||
|
|
||||||
type ViewerContextShape = {
|
type ViewerContextShape = {
|
||||||
// Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
|
// Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
|
||||||
|
@ -17,8 +16,19 @@ export const ViewerContext = React.createContext<ViewerContextShape>({
|
||||||
getSettings: () => ({ collapsed: false }),
|
getSettings: () => ({ collapsed: false }),
|
||||||
getMergedSettings: () => ({
|
getMergedSettings: () => ({
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
|
// copy-pasted from SquiggleChart
|
||||||
|
chartSettings: {
|
||||||
|
start: 0,
|
||||||
|
stop: 10,
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
|
distributionPlotSettings: {
|
||||||
|
showSummary: false,
|
||||||
|
logX: false,
|
||||||
|
expY: false,
|
||||||
|
},
|
||||||
environment: defaultEnvironment,
|
environment: defaultEnvironment,
|
||||||
...viewSettingsToMerged(viewSettingsSchema.getDefault()),
|
height: 150,
|
||||||
}),
|
}),
|
||||||
setSettings() {},
|
setSettings() {},
|
||||||
enableLocalSettings: false,
|
enableLocalSettings: false,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React, { useCallback, useRef } from "react";
|
import React, { useCallback, useRef } from "react";
|
||||||
import { SqValueLocation } from "@quri/squiggle-lang";
|
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
||||||
|
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||||
|
import { FunctionChartSettings } from "../FunctionChart";
|
||||||
import { ExpressionViewer } from "./ExpressionViewer";
|
import { ExpressionViewer } from "./ExpressionViewer";
|
||||||
import { ViewerContext } from "./ViewerContext";
|
import { ViewerContext } from "./ViewerContext";
|
||||||
import {
|
import {
|
||||||
|
@ -8,40 +10,20 @@ import {
|
||||||
MergedItemSettings,
|
MergedItemSettings,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import { useSquiggle } from "../../lib/hooks";
|
import { useSquiggle } from "../../lib/hooks";
|
||||||
import {
|
|
||||||
EditableViewSettings,
|
|
||||||
viewSettingsSchema,
|
|
||||||
viewSettingsToMerged,
|
|
||||||
} from "../ViewSettings";
|
|
||||||
import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
|
import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
|
||||||
|
|
||||||
// Flattened view settings, gets turned into props for SquiggleChart and SquiggleEditor
|
|
||||||
export type FlattenedViewSettings = Partial<
|
|
||||||
EditableViewSettings & {
|
|
||||||
width?: number;
|
|
||||||
enableLocalSettings?: boolean;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
type ViewSettings = {
|
|
||||||
width?: number;
|
|
||||||
enableLocalSettings?: boolean;
|
|
||||||
} & Omit<MergedItemSettings, "environment">;
|
|
||||||
|
|
||||||
export const createViewSettings = (
|
|
||||||
props: FlattenedViewSettings
|
|
||||||
): ViewSettings => {
|
|
||||||
const propsWithDefaults = { ...viewSettingsSchema.getDefault(), ...props };
|
|
||||||
let merged = viewSettingsToMerged(propsWithDefaults);
|
|
||||||
const { width, enableLocalSettings } = propsWithDefaults;
|
|
||||||
|
|
||||||
return { ...merged, width, enableLocalSettings };
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
/** The output of squiggle's run */
|
/** The output of squiggle's run */
|
||||||
result: ReturnType<typeof useSquiggle>["result"];
|
result: ReturnType<typeof useSquiggle>["result"];
|
||||||
} & ViewSettings;
|
width?: number;
|
||||||
|
height: number;
|
||||||
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
|
/** Settings for displaying functions */
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
/** Environment for further function executions */
|
||||||
|
environment: environment;
|
||||||
|
enableLocalSettings?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type Settings = {
|
type Settings = {
|
||||||
[k: string]: LocalItemSettings;
|
[k: string]: LocalItemSettings;
|
||||||
|
@ -52,9 +34,10 @@ const defaultSettings: LocalItemSettings = { collapsed: false };
|
||||||
export const SquiggleViewer: React.FC<Props> = ({
|
export const SquiggleViewer: React.FC<Props> = ({
|
||||||
result,
|
result,
|
||||||
width,
|
width,
|
||||||
chartHeight,
|
height,
|
||||||
distributionPlotSettings,
|
distributionPlotSettings,
|
||||||
chartSettings,
|
chartSettings,
|
||||||
|
environment,
|
||||||
enableLocalSettings = false,
|
enableLocalSettings = false,
|
||||||
}) => {
|
}) => {
|
||||||
// can't store settings in the state because we don't want to rerender the entire tree on every change
|
// can't store settings in the state because we don't want to rerender the entire tree on every change
|
||||||
|
@ -76,7 +59,6 @@ export const SquiggleViewer: React.FC<Props> = ({
|
||||||
|
|
||||||
const getMergedSettings = useCallback(
|
const getMergedSettings = useCallback(
|
||||||
(location: SqValueLocation) => {
|
(location: SqValueLocation) => {
|
||||||
const env = location.project.getEnvironment();
|
|
||||||
const localSettings = getSettings(location);
|
const localSettings = getSettings(location);
|
||||||
const result: MergedItemSettings = {
|
const result: MergedItemSettings = {
|
||||||
distributionPlotSettings: {
|
distributionPlotSettings: {
|
||||||
|
@ -88,14 +70,14 @@ export const SquiggleViewer: React.FC<Props> = ({
|
||||||
...(localSettings.chartSettings || {}),
|
...(localSettings.chartSettings || {}),
|
||||||
},
|
},
|
||||||
environment: {
|
environment: {
|
||||||
...env,
|
...environment,
|
||||||
...localSettings.environment,
|
...(localSettings.environment || {}),
|
||||||
},
|
},
|
||||||
chartHeight: localSettings.chartHeight || chartHeight,
|
height: localSettings.height || height,
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
[distributionPlotSettings, chartSettings, chartHeight, getSettings]
|
[distributionPlotSettings, chartSettings, environment, height, getSettings]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import { DistributionPlottingSettings } from "../DistributionChart";
|
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||||
import { FunctionChartSettings } from "../FunctionChart";
|
import { FunctionChartSettings } from "../FunctionChart";
|
||||||
import { SqValueLocation, environment } from "@quri/squiggle-lang";
|
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
export type LocalItemSettings = {
|
export type LocalItemSettings = {
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
distributionPlotSettings?: Partial<DistributionPlottingSettings>;
|
distributionPlotSettings?: Partial<DistributionPlottingSettings>;
|
||||||
chartSettings?: Partial<FunctionChartSettings>;
|
chartSettings?: Partial<FunctionChartSettings>;
|
||||||
chartHeight?: number;
|
height?: number;
|
||||||
environment?: environment;
|
environment?: Partial<environment>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MergedItemSettings = {
|
export type MergedItemSettings = {
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
chartSettings: FunctionChartSettings;
|
chartSettings: FunctionChartSettings;
|
||||||
chartHeight: number;
|
height: number;
|
||||||
environment: environment;
|
environment: environment;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,137 +5,49 @@ import { InputItem } from "./ui/InputItem";
|
||||||
import { Checkbox } from "./ui/Checkbox";
|
import { Checkbox } from "./ui/Checkbox";
|
||||||
import { HeadedSection } from "./ui/HeadedSection";
|
import { HeadedSection } from "./ui/HeadedSection";
|
||||||
import { Text } from "./ui/Text";
|
import { Text } from "./ui/Text";
|
||||||
import { MergedItemSettings, LocalItemSettings } from "./SquiggleViewer/utils";
|
|
||||||
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
||||||
|
|
||||||
export const viewSettingsSchema = yup.object({}).shape({
|
export const viewSettingsSchema = yup.object({}).shape({
|
||||||
chartHeight: yup.number().required().positive().integer().default(350),
|
chartHeight: yup.number().required().positive().integer().default(350),
|
||||||
showSummary: yup.boolean().required().default(false),
|
showSummary: yup.boolean().required(),
|
||||||
logX: yup.boolean().required().default(false),
|
showEditor: yup.boolean().required(),
|
||||||
expY: yup.boolean().required().default(false),
|
logX: yup.boolean().required(),
|
||||||
tickFormat: yup.string().required().default(defaultTickFormat),
|
expY: yup.boolean().required(),
|
||||||
|
tickFormat: yup.string().default(defaultTickFormat),
|
||||||
title: yup.string(),
|
title: yup.string(),
|
||||||
minX: yup.number(),
|
minX: yup.number(),
|
||||||
maxX: yup.number(),
|
maxX: yup.number(),
|
||||||
xAxisType: yup
|
|
||||||
.mixed<"number" | "dateTime">()
|
|
||||||
.oneOf(["number", "dateTime"])
|
|
||||||
.default("number"),
|
|
||||||
distributionChartActions: yup.boolean(),
|
distributionChartActions: yup.boolean(),
|
||||||
diagramStart: yup.number().required().positive().integer().default(0).min(0),
|
diagramStart: yup.number().required().positive().integer().default(0).min(0),
|
||||||
diagramStop: yup.number().required().positive().integer().default(10).min(0),
|
diagramStop: yup.number().required().positive().integer().default(10).min(0),
|
||||||
diagramCount: yup.number().required().positive().integer().default(20).min(2),
|
diagramCount: yup.number().required().positive().integer().default(20).min(2),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type EditableViewSettings = yup.InferType<typeof viewSettingsSchema>;
|
type FormFields = yup.InferType<typeof viewSettingsSchema>;
|
||||||
|
|
||||||
export const viewSettingsToMerged = (
|
|
||||||
settings: EditableViewSettings
|
|
||||||
): Omit<MergedItemSettings, "environment"> => {
|
|
||||||
const {
|
|
||||||
showSummary,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
diagramStart,
|
|
||||||
diagramStop,
|
|
||||||
diagramCount,
|
|
||||||
tickFormat,
|
|
||||||
minX,
|
|
||||||
maxX,
|
|
||||||
title,
|
|
||||||
xAxisType,
|
|
||||||
distributionChartActions,
|
|
||||||
chartHeight,
|
|
||||||
} = settings;
|
|
||||||
|
|
||||||
const distributionPlotSettings = {
|
|
||||||
showSummary,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
format: tickFormat,
|
|
||||||
minX,
|
|
||||||
maxX,
|
|
||||||
title,
|
|
||||||
xAxisType,
|
|
||||||
actions: distributionChartActions,
|
|
||||||
};
|
|
||||||
|
|
||||||
const chartSettings = {
|
|
||||||
start: diagramStart,
|
|
||||||
stop: diagramStop,
|
|
||||||
count: diagramCount,
|
|
||||||
};
|
|
||||||
|
|
||||||
return { distributionPlotSettings, chartSettings, chartHeight };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const viewSettingsToLocal = (
|
|
||||||
settings: Partial<EditableViewSettings>
|
|
||||||
): Omit<LocalItemSettings, "collapsed" | "environment"> => {
|
|
||||||
const {
|
|
||||||
showSummary,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
diagramStart,
|
|
||||||
diagramStop,
|
|
||||||
diagramCount,
|
|
||||||
tickFormat,
|
|
||||||
minX,
|
|
||||||
maxX,
|
|
||||||
title,
|
|
||||||
xAxisType,
|
|
||||||
distributionChartActions,
|
|
||||||
chartHeight,
|
|
||||||
} = settings;
|
|
||||||
|
|
||||||
const distributionPlotSettings = {
|
|
||||||
showSummary,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
format: tickFormat,
|
|
||||||
minX,
|
|
||||||
maxX,
|
|
||||||
title,
|
|
||||||
xAxisType,
|
|
||||||
actions: distributionChartActions,
|
|
||||||
};
|
|
||||||
|
|
||||||
const chartSettings = {
|
|
||||||
start: diagramStart,
|
|
||||||
stop: diagramStop,
|
|
||||||
count: diagramCount,
|
|
||||||
};
|
|
||||||
|
|
||||||
return { distributionPlotSettings, chartSettings, chartHeight };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const mergedToViewSettings = (
|
|
||||||
mergedSettings: MergedItemSettings
|
|
||||||
): EditableViewSettings => ({
|
|
||||||
chartHeight: mergedSettings.chartHeight,
|
|
||||||
showSummary: mergedSettings.distributionPlotSettings.showSummary,
|
|
||||||
logX: mergedSettings.distributionPlotSettings.logX,
|
|
||||||
expY: mergedSettings.distributionPlotSettings.expY,
|
|
||||||
tickFormat: mergedSettings.distributionPlotSettings.format,
|
|
||||||
title: mergedSettings.distributionPlotSettings.title,
|
|
||||||
minX: mergedSettings.distributionPlotSettings.minX,
|
|
||||||
maxX: mergedSettings.distributionPlotSettings.maxX,
|
|
||||||
distributionChartActions: mergedSettings.distributionPlotSettings.actions,
|
|
||||||
xAxisType: mergedSettings.distributionPlotSettings.xAxisType,
|
|
||||||
diagramStart: mergedSettings.chartSettings.start,
|
|
||||||
diagramStop: mergedSettings.chartSettings.stop,
|
|
||||||
diagramCount: mergedSettings.chartSettings.count,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// This component is used in two places: for global settings in SquigglePlayground, and for item-specific settings in modal dialogs.
|
||||||
export const ViewSettings: React.FC<{
|
export const ViewSettings: React.FC<{
|
||||||
|
withShowEditorSetting?: boolean;
|
||||||
withFunctionSettings?: boolean;
|
withFunctionSettings?: boolean;
|
||||||
disableLogXSetting?: boolean;
|
disableLogXSetting?: boolean;
|
||||||
register: UseFormRegister<EditableViewSettings>;
|
register: UseFormRegister<FormFields>;
|
||||||
}> = ({ withFunctionSettings = true, disableLogXSetting, register }) => {
|
}> = ({
|
||||||
|
withShowEditorSetting = true,
|
||||||
|
withFunctionSettings = true,
|
||||||
|
disableLogXSetting,
|
||||||
|
register,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
||||||
<HeadedSection title="General Display Settings">
|
<HeadedSection title="General Display Settings">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{withShowEditorSetting ? (
|
||||||
|
<Checkbox
|
||||||
|
name="showEditor"
|
||||||
|
register={register}
|
||||||
|
label="Show code editor on left"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<InputItem
|
<InputItem
|
||||||
name="chartHeight"
|
name="chartHeight"
|
||||||
type="number"
|
type="number"
|
||||||
|
@ -145,115 +57,97 @@ export const ViewSettings: React.FC<{
|
||||||
</div>
|
</div>
|
||||||
</HeadedSection>
|
</HeadedSection>
|
||||||
|
|
||||||
<DistributionViewSettings
|
<div className="pt-8">
|
||||||
disableLogXSetting={disableLogXSetting}
|
<HeadedSection title="Distribution Display Settings">
|
||||||
register={register}
|
<div className="space-y-2">
|
||||||
/>
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="logX"
|
||||||
|
label="Show x scale logarithmically"
|
||||||
|
disabled={disableLogXSetting}
|
||||||
|
tooltip={
|
||||||
|
disableLogXSetting
|
||||||
|
? "Your distribution has mass lower than or equal to 0. Log only works on strictly positive values."
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="expY"
|
||||||
|
label="Show y scale exponentially"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="distributionChartActions"
|
||||||
|
label="Show vega chart controls"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="showSummary"
|
||||||
|
label="Show summary statistics"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="minX"
|
||||||
|
type="number"
|
||||||
|
register={register}
|
||||||
|
label="Min X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="maxX"
|
||||||
|
type="number"
|
||||||
|
register={register}
|
||||||
|
label="Max X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="title"
|
||||||
|
type="text"
|
||||||
|
register={register}
|
||||||
|
label="Title"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="tickFormat"
|
||||||
|
type="text"
|
||||||
|
register={register}
|
||||||
|
label="Tick Format"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
</div>
|
||||||
|
|
||||||
{withFunctionSettings ? (
|
{withFunctionSettings ? (
|
||||||
<FunctionViewSettings register={register} />
|
<div className="pt-8">
|
||||||
|
<HeadedSection title="Function Display Settings">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Text>
|
||||||
|
When displaying functions of single variables that return
|
||||||
|
numbers or distributions, we need to use defaults for the
|
||||||
|
x-axis. We need to select a minimum and maximum value of x to
|
||||||
|
sample, and a number n of the number of points to sample.
|
||||||
|
</Text>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramStart"
|
||||||
|
register={register}
|
||||||
|
label="Min X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramStop"
|
||||||
|
register={register}
|
||||||
|
label="Max X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramCount"
|
||||||
|
register={register}
|
||||||
|
label="Points between X min and X max to sample"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DistributionViewSettings: React.FC<{
|
|
||||||
disableLogXSetting?: boolean;
|
|
||||||
register: UseFormRegister<EditableViewSettings>;
|
|
||||||
}> = ({ disableLogXSetting, register }) => {
|
|
||||||
return (
|
|
||||||
<div className="pt-8">
|
|
||||||
<HeadedSection title="Distribution Display Settings">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="logX"
|
|
||||||
label="Show x scale logarithmically"
|
|
||||||
disabled={disableLogXSetting}
|
|
||||||
tooltip={
|
|
||||||
disableLogXSetting
|
|
||||||
? "Your distribution has mass lower than or equal to 0. Log only works on strictly positive values."
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="expY"
|
|
||||||
label="Show y scale exponentially"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="distributionChartActions"
|
|
||||||
label="Show vega chart controls"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="showSummary"
|
|
||||||
label="Show summary statistics"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="minX"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Min X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="maxX"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Max X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="title"
|
|
||||||
type="text"
|
|
||||||
register={register}
|
|
||||||
label="Title"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="tickFormat"
|
|
||||||
type="text"
|
|
||||||
register={register}
|
|
||||||
label="Tick Format"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FunctionViewSettings: React.FC<{
|
|
||||||
register: UseFormRegister<EditableViewSettings>;
|
|
||||||
}> = ({ register }) => (
|
|
||||||
<div className="pt-8">
|
|
||||||
<HeadedSection title="Function Display Settings">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Text>
|
|
||||||
When displaying functions of single variables that return numbers or
|
|
||||||
distributions, we need to use defaults for the x-axis. We need to
|
|
||||||
select a minimum and maximum value of x to sample, and a number n of
|
|
||||||
the number of points to sample.
|
|
||||||
</Text>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramStart"
|
|
||||||
register={register}
|
|
||||||
label="Min X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramStop"
|
|
||||||
register={register}
|
|
||||||
label="Max X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramCount"
|
|
||||||
register={register}
|
|
||||||
label="Points between X min and X max to sample"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
|
@ -13,9 +13,9 @@ export type DistributionChartSpecOptions = {
|
||||||
/** The title of the chart */
|
/** The title of the chart */
|
||||||
title?: string;
|
title?: string;
|
||||||
/** The formatting of the ticks */
|
/** The formatting of the ticks */
|
||||||
format: string;
|
format?: string;
|
||||||
/** Whether the x-axis should be dates or numbers */
|
/** Whether the x-axis should be dates or numbers */
|
||||||
xAxisType: "number" | "dateTime";
|
xAxisType?: "number" | "dateTime";
|
||||||
};
|
};
|
||||||
|
|
||||||
/** X Scales */
|
/** X Scales */
|
||||||
|
@ -70,7 +70,15 @@ const width = 500;
|
||||||
export function buildVegaSpec(
|
export function buildVegaSpec(
|
||||||
specOptions: DistributionChartSpecOptions & { maxY: number }
|
specOptions: DistributionChartSpecOptions & { maxY: number }
|
||||||
): VisualizationSpec {
|
): VisualizationSpec {
|
||||||
const { title, minX, maxX, logX, expY, xAxisType, maxY } = specOptions;
|
const {
|
||||||
|
title,
|
||||||
|
minX,
|
||||||
|
maxX,
|
||||||
|
logX,
|
||||||
|
expY,
|
||||||
|
xAxisType = "number",
|
||||||
|
maxY,
|
||||||
|
} = specOptions;
|
||||||
|
|
||||||
const dateTime = xAxisType === "dateTime";
|
const dateTime = xAxisType === "dateTime";
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,23 @@
|
||||||
import {
|
import {
|
||||||
result,
|
result,
|
||||||
SqError,
|
SqError,
|
||||||
environment,
|
|
||||||
SqProject,
|
SqProject,
|
||||||
SqRecord,
|
SqRecord,
|
||||||
SqValue,
|
SqValue,
|
||||||
|
environment,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
|
import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
|
|
||||||
export type SquiggleArgs = {
|
type SquiggleArgs = {
|
||||||
|
environment?: environment;
|
||||||
code: string;
|
code: string;
|
||||||
executionId?: number;
|
executionId?: number;
|
||||||
jsImports?: JsImports;
|
jsImports?: JsImports;
|
||||||
onChange?: (
|
project?: SqProject;
|
||||||
expr: result<SqValue, SqError> | undefined,
|
|
||||||
sourceName: string
|
|
||||||
) => void;
|
|
||||||
} & (StandaloneExecutionProps | ProjectExecutionProps);
|
|
||||||
|
|
||||||
// Props needed for a standalone execution
|
|
||||||
type StandaloneExecutionProps = {
|
|
||||||
project?: undefined;
|
|
||||||
continues?: undefined;
|
|
||||||
/** The amount of points returned to draw the distribution, not needed if using a project */
|
|
||||||
environment?: environment;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Props needed when executing inside a project.
|
|
||||||
type ProjectExecutionProps = {
|
|
||||||
environment?: undefined;
|
|
||||||
/** The project that this execution is part of */
|
|
||||||
project: SqProject;
|
|
||||||
/** What other squiggle sources from the project to continue. Default [] */
|
|
||||||
continues?: string[];
|
continues?: string[];
|
||||||
|
onChange?: (expr: SqValue | undefined, sourceName: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResultAndBindings = {
|
export type ResultAndBindings = {
|
||||||
|
@ -46,9 +29,7 @@ const importSourceName = (sourceName: string) => "imports-" + sourceName;
|
||||||
const defaultContinues = [];
|
const defaultContinues = [];
|
||||||
|
|
||||||
export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
|
export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
|
||||||
const sourceName = useMemo(() => uuid.v4(), []);
|
const project = useMemo(() => {
|
||||||
|
|
||||||
const p = useMemo(() => {
|
|
||||||
if (args.project) {
|
if (args.project) {
|
||||||
return args.project;
|
return args.project;
|
||||||
} else {
|
} else {
|
||||||
|
@ -60,13 +41,13 @@ export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
|
||||||
}
|
}
|
||||||
}, [args.project, args.environment]);
|
}, [args.project, args.environment]);
|
||||||
|
|
||||||
const env = p.getEnvironment();
|
const sourceName = useMemo(() => uuid.v4(), []);
|
||||||
|
|
||||||
|
const env = project.getEnvironment();
|
||||||
const continues = args.continues || defaultContinues;
|
const continues = args.continues || defaultContinues;
|
||||||
|
|
||||||
const result = useMemo(
|
const result = useMemo(
|
||||||
() => {
|
() => {
|
||||||
const project = p;
|
|
||||||
|
|
||||||
project.setSource(sourceName, args.code);
|
project.setSource(sourceName, args.code);
|
||||||
let fullContinues = continues;
|
let fullContinues = continues;
|
||||||
if (args.jsImports && Object.keys(args.jsImports).length) {
|
if (args.jsImports && Object.keys(args.jsImports).length) {
|
||||||
|
@ -90,25 +71,27 @@ export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
|
||||||
args.executionId,
|
args.executionId,
|
||||||
sourceName,
|
sourceName,
|
||||||
continues,
|
continues,
|
||||||
args.project,
|
project,
|
||||||
env,
|
env,
|
||||||
p,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { onChange } = args;
|
const { onChange } = args;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange?.(result.result, sourceName);
|
onChange?.(
|
||||||
|
result.result.tag === "Ok" ? result.result.value : undefined,
|
||||||
|
sourceName
|
||||||
|
);
|
||||||
}, [result, onChange, sourceName]);
|
}, [result, onChange, sourceName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
p.removeSource(sourceName);
|
project.removeSource(sourceName);
|
||||||
if (p.getSource(importSourceName(sourceName)))
|
if (project.getSource(importSourceName(sourceName)))
|
||||||
p.removeSource(importSourceName(sourceName));
|
project.removeSource(importSourceName(sourceName));
|
||||||
};
|
};
|
||||||
}, [p, sourceName]);
|
}, [project, sourceName]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
|
@ -250,3 +250,5 @@ to allow large and small numbers being printed cleanly.
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
<Props of={SquiggleChart} />
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import { render } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import { SquiggleChart } from "../src/index";
|
import {
|
||||||
|
SquiggleChart,
|
||||||
|
SquiggleEditor,
|
||||||
|
SquigglePlayground,
|
||||||
|
} from "../src/index";
|
||||||
|
import { SqProject } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
test("Logs nothing on render", async () => {
|
test("Chart logs nothing on render", async () => {
|
||||||
const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />);
|
const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />);
|
||||||
unmount();
|
unmount();
|
||||||
|
|
||||||
|
@ -11,3 +16,38 @@ test("Logs nothing on render", async () => {
|
||||||
expect(console.warn).not.toBeCalled();
|
expect(console.warn).not.toBeCalled();
|
||||||
expect(console.error).not.toBeCalled();
|
expect(console.error).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Editor logs nothing on render", async () => {
|
||||||
|
const { unmount } = render(<SquiggleEditor code={"normal(0, 1)"} />);
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
expect(console.log).not.toBeCalled();
|
||||||
|
expect(console.warn).not.toBeCalled();
|
||||||
|
expect(console.error).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Project dependencies work in editors", async () => {
|
||||||
|
const project = SqProject.create();
|
||||||
|
|
||||||
|
render(<SquiggleEditor code={"x = 1"} project={project} />);
|
||||||
|
const source = project.getSourceIds()[0];
|
||||||
|
const { container } = render(
|
||||||
|
<SquiggleEditor code={"x + 1"} project={project} continues={[source]} />
|
||||||
|
);
|
||||||
|
expect(container).toHaveTextContent("2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Project dependencies work in playgrounds", async () => {
|
||||||
|
const project = SqProject.create();
|
||||||
|
project.setSource("depend", "x = 1");
|
||||||
|
|
||||||
|
render(
|
||||||
|
<SquigglePlayground
|
||||||
|
code={"x + 1"}
|
||||||
|
project={project}
|
||||||
|
continues={["depend"]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
// We must await here because SquigglePlayground loads results asynchronously
|
||||||
|
expect(await screen.findByRole("status")).toHaveTextContent("2");
|
||||||
|
});
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { render } from "@testing-library/react";
|
|
||||||
import React from "react";
|
|
||||||
import "@testing-library/jest-dom";
|
|
||||||
import { SquiggleChart } from "../src/index";
|
|
||||||
|
|
||||||
test("showSummary prop shows table", async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<SquiggleChart code={"normal(5, 1)"} showSummary={true} />
|
|
||||||
);
|
|
||||||
expect(container).toHaveTextContent("Mean");
|
|
||||||
expect(container).toHaveTextContent("5");
|
|
||||||
});
|
|
Loading…
Reference in New Issue
Block a user