parent
1ea3c975d5
commit
56c34de18a
|
@ -49,10 +49,10 @@ export function makePlot(record: SqRecord): Plot | void {
|
|||
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||
const {
|
||||
plot,
|
||||
environment,
|
||||
height,
|
||||
showSummary,
|
||||
width,
|
||||
environment,
|
||||
logX,
|
||||
actions = false,
|
||||
} = props;
|
||||
|
@ -89,8 +89,12 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
|
||||
const spec = buildVegaSpec({
|
||||
...props,
|
||||
minX: props.minX ?? Math.min(...domain.map((x) => x.x)),
|
||||
maxX: props.minX ?? Math.max(...domain.map((x) => x.x)),
|
||||
minX: Number.isFinite(props.minX)
|
||||
? props.minX
|
||||
: 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)),
|
||||
});
|
||||
|
||||
|
|
|
@ -1,161 +1,22 @@
|
|||
import * as React from "react";
|
||||
import { SqValue, environment, SqProject } from "@quri/squiggle-lang";
|
||||
import { useSquiggle } from "../lib/hooks";
|
||||
import { SquiggleViewer } from "./SquiggleViewer";
|
||||
import { JsImports } from "../lib/jsImports";
|
||||
import { useSquiggle, SquiggleArgs } from "../lib/hooks/useSquiggle";
|
||||
import {
|
||||
SquiggleViewer,
|
||||
FlattenedViewSettings,
|
||||
createViewSettings,
|
||||
} from "./SquiggleViewer";
|
||||
import { getValueToRender } from "../lib/utility";
|
||||
|
||||
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 = {};
|
||||
const defaultContinues: string[] = [];
|
||||
|
||||
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 type SquiggleChartProps = SquiggleArgs & FlattenedViewSettings;
|
||||
|
||||
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||
(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 = defaultContinues,
|
||||
} = props;
|
||||
|
||||
const p = React.useMemo(() => {
|
||||
if (props.project) {
|
||||
return props.project;
|
||||
} else {
|
||||
const p = SqProject.create();
|
||||
if (props.environment) {
|
||||
p.setEnvironment(props.environment);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}, [props.project, props.environment]);
|
||||
|
||||
const resultAndBindings = useSquiggle({
|
||||
continues,
|
||||
project: p,
|
||||
code,
|
||||
jsImports,
|
||||
onChange,
|
||||
executionId,
|
||||
});
|
||||
const resultAndBindings = useSquiggle(props);
|
||||
|
||||
const valueToRender = getValueToRender(resultAndBindings);
|
||||
|
||||
return (
|
||||
<SquiggleViewer
|
||||
result={valueToRender}
|
||||
width={width}
|
||||
height={height}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
chartSettings={chartSettings}
|
||||
environment={p.getEnvironment()}
|
||||
enableLocalSettings={enableLocalSettings}
|
||||
/>
|
||||
<SquiggleViewer {...createViewSettings(props)} result={valueToRender} />
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React from "react";
|
||||
import { CodeEditor } from "./CodeEditor";
|
||||
import { SquiggleContainer } from "./SquiggleContainer";
|
||||
import { useMaybeControlledValue } from "../lib/hooks";
|
||||
import { useSquiggle, SquiggleArgs } from "../lib/hooks/useSquiggle";
|
||||
import { SqLocation } from "@quri/squiggle-lang";
|
||||
import {
|
||||
splitSquiggleChartSettings,
|
||||
SquiggleChartProps,
|
||||
} from "./SquiggleChart";
|
||||
import { useMaybeControlledValue, useSquiggle } from "../lib/hooks";
|
||||
import { JsImports } from "../lib/jsImports";
|
||||
import { defaultEnvironment, SqLocation, SqProject } from "@quri/squiggle-lang";
|
||||
import { SquiggleViewer } from "./SquiggleViewer";
|
||||
SquiggleViewer,
|
||||
createViewSettings,
|
||||
FlattenedViewSettings,
|
||||
} from "./SquiggleViewer";
|
||||
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
||||
|
||||
const WrappedCodeEditor: React.FC<{
|
||||
|
@ -28,13 +28,11 @@ const WrappedCodeEditor: React.FC<{
|
|||
</div>
|
||||
);
|
||||
|
||||
export type SquiggleEditorProps = SquiggleChartProps & {
|
||||
export type SquiggleEditorProps = SquiggleArgs &
|
||||
FlattenedViewSettings & {
|
||||
defaultCode?: string;
|
||||
onCodeChange?: (code: string) => void;
|
||||
};
|
||||
|
||||
const defaultOnChange = () => {};
|
||||
const defaultImports: JsImports = {};
|
||||
};
|
||||
|
||||
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
||||
const [code, setCode] = useMaybeControlledValue({
|
||||
|
@ -43,34 +41,7 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
|||
onChange: props.onCodeChange,
|
||||
});
|
||||
|
||||
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,
|
||||
} = props;
|
||||
|
||||
const project = React.useMemo(() => {
|
||||
const p = SqProject.create();
|
||||
if (environment) {
|
||||
p.setEnvironment(environment);
|
||||
}
|
||||
return p;
|
||||
}, [environment]);
|
||||
|
||||
const resultAndBindings = useSquiggle({
|
||||
code,
|
||||
project,
|
||||
jsImports,
|
||||
onChange,
|
||||
executionId,
|
||||
});
|
||||
const resultAndBindings = useSquiggle({ ...props, code });
|
||||
|
||||
const valueToRender = getValueToRender(resultAndBindings);
|
||||
const errorLocations = getErrorLocations(resultAndBindings.result);
|
||||
|
@ -82,15 +53,7 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
|||
setCode={setCode}
|
||||
errorLocations={errorLocations}
|
||||
/>
|
||||
<SquiggleViewer
|
||||
result={valueToRender}
|
||||
width={width}
|
||||
height={height}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment ?? defaultEnvironment}
|
||||
enableLocalSettings={enableLocalSettings}
|
||||
/>
|
||||
<SquiggleViewer result={valueToRender} {...createViewSettings(props)} />
|
||||
</SquiggleContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
useRunnerState,
|
||||
useSquiggle,
|
||||
} from "../lib/hooks";
|
||||
import { SquiggleArgs } from "../lib/hooks/useSquiggle";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import {
|
||||
ChartSquareBarIcon,
|
||||
|
@ -28,9 +29,8 @@ import {
|
|||
} from "@heroicons/react/solid";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { environment, SqProject } from "@quri/squiggle-lang";
|
||||
import { environment } from "@quri/squiggle-lang";
|
||||
|
||||
import { SquiggleChartProps } from "./SquiggleChart";
|
||||
import { CodeEditor } from "./CodeEditor";
|
||||
import { JsonEditor } from "./JsonEditor";
|
||||
import { ErrorAlert, SuccessAlert } from "./Alert";
|
||||
|
@ -41,13 +41,17 @@ import { InputItem } from "./ui/InputItem";
|
|||
import { Text } from "./ui/Text";
|
||||
import { ViewSettings, viewSettingsSchema } from "./ViewSettings";
|
||||
import { HeadedSection } from "./ui/HeadedSection";
|
||||
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
||||
import { Button } from "./ui/Button";
|
||||
import { JsImports } from "../lib/jsImports";
|
||||
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
||||
import { SquiggleViewer } from "./SquiggleViewer";
|
||||
import {
|
||||
SquiggleViewer,
|
||||
FlattenedViewSettings,
|
||||
createViewSettings,
|
||||
} from "./SquiggleViewer";
|
||||
|
||||
type PlaygroundProps = SquiggleChartProps & {
|
||||
type PlaygroundProps = SquiggleArgs &
|
||||
FlattenedViewSettings & {
|
||||
/** The initial squiggle string to put in the playground */
|
||||
defaultCode?: string;
|
||||
onCodeChange?(expr: string): void;
|
||||
|
@ -57,7 +61,7 @@ type PlaygroundProps = SquiggleChartProps & {
|
|||
showEditor?: boolean;
|
||||
/** Useful for playground on squiggle website, where we update the anchor link based on current code and settings */
|
||||
showShareButton?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const schema = yup
|
||||
.object({})
|
||||
|
@ -78,6 +82,7 @@ const schema = yup
|
|||
.default(1000)
|
||||
.min(10)
|
||||
.max(10000),
|
||||
showEditor: yup.boolean().required().default(true),
|
||||
})
|
||||
.concat(viewSettingsSchema);
|
||||
|
||||
|
@ -235,23 +240,14 @@ export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
|
|||
getLeftPanelElement: () => undefined,
|
||||
});
|
||||
|
||||
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||
export const SquigglePlayground: FC<PlaygroundProps> = (props) => {
|
||||
const {
|
||||
defaultCode = "",
|
||||
height = 500,
|
||||
showSummary = true,
|
||||
logX = false,
|
||||
expY = false,
|
||||
title,
|
||||
minX,
|
||||
maxX,
|
||||
tickFormat = defaultTickFormat,
|
||||
distributionChartActions,
|
||||
code: controlledCode,
|
||||
onCodeChange,
|
||||
onSettingsChange,
|
||||
showEditor = true,
|
||||
showShareButton = false,
|
||||
}) => {
|
||||
} = props;
|
||||
const [code, setCode] = useMaybeControlledValue({
|
||||
value: controlledCode,
|
||||
defaultValue: defaultCode,
|
||||
|
@ -260,29 +256,19 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
|
||||
const [imports, setImports] = useState<JsImports>({});
|
||||
|
||||
let defaultValues: FormFields = {
|
||||
...schema.getDefault(),
|
||||
...props,
|
||||
};
|
||||
|
||||
const { register, control } = useForm({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
sampleCount: 1000,
|
||||
xyPointLength: 1000,
|
||||
chartHeight: 150,
|
||||
logX,
|
||||
expY,
|
||||
title,
|
||||
minX,
|
||||
maxX,
|
||||
tickFormat,
|
||||
distributionChartActions,
|
||||
showSummary,
|
||||
showEditor,
|
||||
diagramStart: 0,
|
||||
diagramStop: 10,
|
||||
diagramCount: 20,
|
||||
},
|
||||
defaultValues: defaultValues,
|
||||
});
|
||||
const vars = useWatch({
|
||||
const rawVars = useWatch({
|
||||
control,
|
||||
});
|
||||
let vars = useMemo(() => ({ ...schema.getDefault(), ...rawVars }), [rawVars]);
|
||||
|
||||
useEffect(() => {
|
||||
onSettingsChange?.(vars);
|
||||
|
@ -305,20 +291,12 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
executionId,
|
||||
} = useRunnerState(code);
|
||||
|
||||
const project = React.useMemo(() => {
|
||||
const p = SqProject.create();
|
||||
if (environment) {
|
||||
p.setEnvironment(environment);
|
||||
let args: SquiggleArgs = props;
|
||||
args = { ...args, code, jsImports: imports, executionId };
|
||||
if (!args.project) {
|
||||
args = { ...args, environment };
|
||||
}
|
||||
return p;
|
||||
}, [environment]);
|
||||
|
||||
const resultAndBindings = useSquiggle({
|
||||
code,
|
||||
project,
|
||||
jsImports: imports,
|
||||
executionId,
|
||||
});
|
||||
const resultAndBindings = useSquiggle(args);
|
||||
|
||||
const valueToRender = getValueToRender(resultAndBindings);
|
||||
|
||||
|
@ -328,27 +306,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
{isRunning ? (
|
||||
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
|
||||
) : null}
|
||||
<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}
|
||||
/>
|
||||
<SquiggleViewer {...createViewSettings(vars)} result={valueToRender} />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -363,7 +321,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
onSubmit={run}
|
||||
oneLine={false}
|
||||
showGutter={true}
|
||||
height={height - 1}
|
||||
height={(props.chartHeight ?? 200) - 1}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
@ -402,7 +360,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
<div className="flex mt-2">
|
||||
<div
|
||||
className="w-1/2 relative"
|
||||
style={{ minHeight: height }}
|
||||
style={{ minHeight: props.chartHeight }}
|
||||
ref={leftPanelRef}
|
||||
>
|
||||
{tabs}
|
||||
|
|
|
@ -105,9 +105,9 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
|||
return (
|
||||
<DistributionChart
|
||||
plot={defaultPlot(value.value)}
|
||||
environment={settings.environment}
|
||||
{...settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
height={settings.chartHeight}
|
||||
environment={settings.environment}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
|
@ -178,7 +178,7 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
|||
fn={value.value}
|
||||
chartSettings={settings.chartSettings}
|
||||
distributionPlotSettings={settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
height={settings.chartHeight}
|
||||
environment={{
|
||||
sampleCount: settings.environment.sampleCount / 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>
|
||||
// <FunctionChart
|
||||
// fn={expression.value.fn}
|
||||
|
@ -252,7 +252,7 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
|||
plot={plot}
|
||||
environment={settings.environment}
|
||||
{...settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
height={settings.chartHeight}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -3,9 +3,13 @@ import React, { useContext, useRef, useState, useEffect } from "react";
|
|||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Modal } from "../ui/Modal";
|
||||
import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
|
||||
import {
|
||||
ViewSettings,
|
||||
viewSettingsSchema,
|
||||
mergedToViewSettings,
|
||||
viewSettingsToLocal,
|
||||
} from "../ViewSettings";
|
||||
import { ViewerContext } from "./ViewerContext";
|
||||
import { defaultTickFormat } from "../../lib/distributionSpecBuilder";
|
||||
import { PlaygroundContext } from "../SquigglePlayground";
|
||||
import { SqValue } from "@quri/squiggle-lang";
|
||||
import { locationAsString } from "./utils";
|
||||
|
@ -34,44 +38,14 @@ const ItemSettingsModal: React.FC<
|
|||
|
||||
const { register, watch } = useForm({
|
||||
resolver: yupResolver(viewSettingsSchema),
|
||||
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,
|
||||
},
|
||||
defaultValues: mergedToViewSettings(mergedSettings),
|
||||
});
|
||||
useEffect(() => {
|
||||
const subscription = watch((vars) => {
|
||||
const settings = getSettings(value.location); // get the latest version
|
||||
setSettings(value.location, {
|
||||
...settings,
|
||||
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,
|
||||
},
|
||||
...viewSettingsToLocal(vars),
|
||||
});
|
||||
onChange();
|
||||
});
|
||||
|
@ -102,7 +76,6 @@ const ItemSettingsModal: React.FC<
|
|||
<Modal.Body>
|
||||
<ViewSettings
|
||||
register={register}
|
||||
withShowEditorSetting={false}
|
||||
withFunctionSettings={withFunctionSettings}
|
||||
disableLogXSetting={disableLogX}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang";
|
||||
import React from "react";
|
||||
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
||||
import { viewSettingsSchema, viewSettingsToMerged } from "../ViewSettings";
|
||||
|
||||
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).
|
||||
|
@ -16,19 +17,8 @@ export const ViewerContext = React.createContext<ViewerContextShape>({
|
|||
getSettings: () => ({ collapsed: false }),
|
||||
getMergedSettings: () => ({
|
||||
collapsed: false,
|
||||
// copy-pasted from SquiggleChart
|
||||
chartSettings: {
|
||||
start: 0,
|
||||
stop: 10,
|
||||
count: 100,
|
||||
},
|
||||
distributionPlotSettings: {
|
||||
showSummary: false,
|
||||
logX: false,
|
||||
expY: false,
|
||||
},
|
||||
environment: defaultEnvironment,
|
||||
height: 150,
|
||||
...viewSettingsToMerged(viewSettingsSchema.getDefault()),
|
||||
}),
|
||||
setSettings() {},
|
||||
enableLocalSettings: false,
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import React, { useCallback, useRef } from "react";
|
||||
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
||||
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||
import { FunctionChartSettings } from "../FunctionChart";
|
||||
import { SqValueLocation } from "@quri/squiggle-lang";
|
||||
import { ExpressionViewer } from "./ExpressionViewer";
|
||||
import { ViewerContext } from "./ViewerContext";
|
||||
import {
|
||||
|
@ -10,20 +8,40 @@ import {
|
|||
MergedItemSettings,
|
||||
} from "./utils";
|
||||
import { useSquiggle } from "../../lib/hooks";
|
||||
import {
|
||||
EditableViewSettings,
|
||||
viewSettingsSchema,
|
||||
viewSettingsToMerged,
|
||||
} from "../ViewSettings";
|
||||
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 = {
|
||||
/** The output of squiggle's run */
|
||||
result: ReturnType<typeof useSquiggle>["result"];
|
||||
width?: number;
|
||||
height: number;
|
||||
distributionPlotSettings: DistributionPlottingSettings;
|
||||
/** Settings for displaying functions */
|
||||
chartSettings: FunctionChartSettings;
|
||||
/** Environment for further function executions */
|
||||
environment: environment;
|
||||
enableLocalSettings?: boolean;
|
||||
};
|
||||
} & ViewSettings;
|
||||
|
||||
type Settings = {
|
||||
[k: string]: LocalItemSettings;
|
||||
|
@ -34,10 +52,9 @@ const defaultSettings: LocalItemSettings = { collapsed: false };
|
|||
export const SquiggleViewer: React.FC<Props> = ({
|
||||
result,
|
||||
width,
|
||||
height,
|
||||
chartHeight,
|
||||
distributionPlotSettings,
|
||||
chartSettings,
|
||||
environment,
|
||||
enableLocalSettings = false,
|
||||
}) => {
|
||||
// can't store settings in the state because we don't want to rerender the entire tree on every change
|
||||
|
@ -59,6 +76,7 @@ export const SquiggleViewer: React.FC<Props> = ({
|
|||
|
||||
const getMergedSettings = useCallback(
|
||||
(location: SqValueLocation) => {
|
||||
const env = location.project.getEnvironment();
|
||||
const localSettings = getSettings(location);
|
||||
const result: MergedItemSettings = {
|
||||
distributionPlotSettings: {
|
||||
|
@ -70,14 +88,14 @@ export const SquiggleViewer: React.FC<Props> = ({
|
|||
...(localSettings.chartSettings || {}),
|
||||
},
|
||||
environment: {
|
||||
...environment,
|
||||
...(localSettings.environment || {}),
|
||||
...env,
|
||||
...localSettings.environment,
|
||||
},
|
||||
height: localSettings.height || height,
|
||||
chartHeight: localSettings.chartHeight || chartHeight,
|
||||
};
|
||||
return result;
|
||||
},
|
||||
[distributionPlotSettings, chartSettings, environment, height, getSettings]
|
||||
[distributionPlotSettings, chartSettings, chartHeight, getSettings]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||
import { FunctionChartSettings } from "../FunctionChart";
|
||||
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
||||
import { SqValueLocation, environment } from "@quri/squiggle-lang";
|
||||
|
||||
export type LocalItemSettings = {
|
||||
collapsed: boolean;
|
||||
distributionPlotSettings?: Partial<DistributionPlottingSettings>;
|
||||
chartSettings?: Partial<FunctionChartSettings>;
|
||||
height?: number;
|
||||
environment?: Partial<environment>;
|
||||
chartHeight?: number;
|
||||
environment?: environment;
|
||||
};
|
||||
|
||||
export type MergedItemSettings = {
|
||||
distributionPlotSettings: DistributionPlottingSettings;
|
||||
chartSettings: FunctionChartSettings;
|
||||
height: number;
|
||||
chartHeight: number;
|
||||
environment: environment;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,49 +5,137 @@ import { InputItem } from "./ui/InputItem";
|
|||
import { Checkbox } from "./ui/Checkbox";
|
||||
import { HeadedSection } from "./ui/HeadedSection";
|
||||
import { Text } from "./ui/Text";
|
||||
import { MergedItemSettings, LocalItemSettings } from "./SquiggleViewer/utils";
|
||||
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
||||
|
||||
export const viewSettingsSchema = yup.object({}).shape({
|
||||
chartHeight: yup.number().required().positive().integer().default(350),
|
||||
showSummary: yup.boolean().required(),
|
||||
showEditor: yup.boolean().required(),
|
||||
logX: yup.boolean().required(),
|
||||
expY: yup.boolean().required(),
|
||||
tickFormat: yup.string().default(defaultTickFormat),
|
||||
showSummary: yup.boolean().required().default(false),
|
||||
logX: yup.boolean().required().default(false),
|
||||
expY: yup.boolean().required().default(false),
|
||||
tickFormat: yup.string().required().default(defaultTickFormat),
|
||||
title: yup.string(),
|
||||
minX: yup.number(),
|
||||
maxX: yup.number(),
|
||||
xAxisType: yup
|
||||
.mixed<"number" | "dateTime">()
|
||||
.oneOf(["number", "dateTime"])
|
||||
.default("number"),
|
||||
distributionChartActions: yup.boolean(),
|
||||
diagramStart: yup.number().required().positive().integer().default(0).min(0),
|
||||
diagramStop: yup.number().required().positive().integer().default(10).min(0),
|
||||
diagramCount: yup.number().required().positive().integer().default(20).min(2),
|
||||
});
|
||||
|
||||
type FormFields = yup.InferType<typeof viewSettingsSchema>;
|
||||
export type EditableViewSettings = 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<{
|
||||
withShowEditorSetting?: boolean;
|
||||
withFunctionSettings?: boolean;
|
||||
disableLogXSetting?: boolean;
|
||||
register: UseFormRegister<FormFields>;
|
||||
}> = ({
|
||||
withShowEditorSetting = true,
|
||||
withFunctionSettings = true,
|
||||
disableLogXSetting,
|
||||
register,
|
||||
}) => {
|
||||
register: UseFormRegister<EditableViewSettings>;
|
||||
}> = ({ withFunctionSettings = true, disableLogXSetting, register }) => {
|
||||
return (
|
||||
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
||||
<HeadedSection title="General Display Settings">
|
||||
<div className="space-y-4">
|
||||
{withShowEditorSetting ? (
|
||||
<Checkbox
|
||||
name="showEditor"
|
||||
register={register}
|
||||
label="Show code editor on left"
|
||||
/>
|
||||
) : null}
|
||||
<InputItem
|
||||
name="chartHeight"
|
||||
type="number"
|
||||
|
@ -57,6 +145,23 @@ export const ViewSettings: React.FC<{
|
|||
</div>
|
||||
</HeadedSection>
|
||||
|
||||
<DistributionViewSettings
|
||||
disableLogXSetting={disableLogXSetting}
|
||||
register={register}
|
||||
/>
|
||||
|
||||
{withFunctionSettings ? (
|
||||
<FunctionViewSettings register={register} />
|
||||
) : null}
|
||||
</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">
|
||||
|
@ -113,16 +218,20 @@ export const ViewSettings: React.FC<{
|
|||
</div>
|
||||
</HeadedSection>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
{withFunctionSettings ? (
|
||||
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.
|
||||
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
|
||||
|
@ -147,7 +256,4 @@ export const ViewSettings: React.FC<{
|
|||
</div>
|
||||
</HeadedSection>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
|
|
@ -13,9 +13,9 @@ export type DistributionChartSpecOptions = {
|
|||
/** The title of the chart */
|
||||
title?: string;
|
||||
/** The formatting of the ticks */
|
||||
format?: string;
|
||||
format: string;
|
||||
/** Whether the x-axis should be dates or numbers */
|
||||
xAxisType?: "number" | "dateTime";
|
||||
xAxisType: "number" | "dateTime";
|
||||
};
|
||||
|
||||
/** X Scales */
|
||||
|
@ -70,15 +70,7 @@ const width = 500;
|
|||
export function buildVegaSpec(
|
||||
specOptions: DistributionChartSpecOptions & { maxY: number }
|
||||
): VisualizationSpec {
|
||||
const {
|
||||
title,
|
||||
minX,
|
||||
maxX,
|
||||
logX,
|
||||
expY,
|
||||
xAxisType = "number",
|
||||
maxY,
|
||||
} = specOptions;
|
||||
const { title, minX, maxX, logX, expY, xAxisType, maxY } = specOptions;
|
||||
|
||||
const dateTime = xAxisType === "dateTime";
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
result,
|
||||
SqError,
|
||||
environment,
|
||||
SqProject,
|
||||
SqRecord,
|
||||
SqValue,
|
||||
|
@ -9,13 +10,31 @@ import { useEffect, useMemo } from "react";
|
|||
import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
type SquiggleArgs = {
|
||||
export type SquiggleArgs = {
|
||||
code: string;
|
||||
executionId?: number;
|
||||
jsImports?: JsImports;
|
||||
onChange?: (
|
||||
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[];
|
||||
onChange?: (expr: SqValue | undefined, sourceName: string) => void;
|
||||
};
|
||||
|
||||
export type ResultAndBindings = {
|
||||
|
@ -29,12 +48,24 @@ const defaultContinues = [];
|
|||
export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
|
||||
const sourceName = useMemo(() => uuid.v4(), []);
|
||||
|
||||
const env = args.project.getEnvironment();
|
||||
const p = useMemo(() => {
|
||||
if (args.project) {
|
||||
return args.project;
|
||||
} else {
|
||||
const p = SqProject.create();
|
||||
if (args.environment) {
|
||||
p.setEnvironment(args.environment);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}, [args.project, args.environment]);
|
||||
|
||||
const env = p.getEnvironment();
|
||||
const continues = args.continues || defaultContinues;
|
||||
|
||||
const result = useMemo(
|
||||
() => {
|
||||
const project = args.project;
|
||||
const project = p;
|
||||
|
||||
project.setSource(sourceName, args.code);
|
||||
let fullContinues = continues;
|
||||
|
@ -61,25 +92,23 @@ export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
|
|||
continues,
|
||||
args.project,
|
||||
env,
|
||||
p,
|
||||
]
|
||||
);
|
||||
|
||||
const { onChange } = args;
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.(
|
||||
result.result.tag === "Ok" ? result.result.value : undefined,
|
||||
sourceName
|
||||
);
|
||||
onChange?.(result.result, sourceName);
|
||||
}, [result, onChange, sourceName]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
args.project.removeSource(sourceName);
|
||||
if (args.project.getSource(importSourceName(sourceName)))
|
||||
args.project.removeSource(importSourceName(sourceName));
|
||||
p.removeSource(sourceName);
|
||||
if (p.getSource(importSourceName(sourceName)))
|
||||
p.removeSource(importSourceName(sourceName));
|
||||
};
|
||||
}, [args.project, sourceName]);
|
||||
}, [p, sourceName]);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -250,5 +250,3 @@ to allow large and small numbers being printed cleanly.
|
|||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
<Props of={SquiggleChart} />
|
||||
|
|
12
packages/components/test/viewProps.test.tsx
Normal file
12
packages/components/test/viewProps.test.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
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