diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 3f1b8567..de488d8f 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -49,10 +49,10 @@ export function makePlot(record: SqRecord): Plot | void { export const DistributionChart: React.FC = (props) => { const { plot, - environment, height, showSummary, width, + environment, logX, actions = false, } = props; @@ -89,8 +89,12 @@ export const DistributionChart: React.FC = (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)), }); diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 215031a7..f2a228f6 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -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 = 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 ( - + ); } ); diff --git a/packages/components/src/components/SquiggleEditor.tsx b/packages/components/src/components/SquiggleEditor.tsx index 23765f38..49810b39 100644 --- a/packages/components/src/components/SquiggleEditor.tsx +++ b/packages/components/src/components/SquiggleEditor.tsx @@ -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<{ ); -export type SquiggleEditorProps = SquiggleChartProps & { - defaultCode?: string; - onCodeChange?: (code: string) => void; -}; - -const defaultOnChange = () => {}; -const defaultImports: JsImports = {}; +export type SquiggleEditorProps = SquiggleArgs & + FlattenedViewSettings & { + defaultCode?: string; + onCodeChange?: (code: string) => void; + }; export const SquiggleEditor: React.FC = (props) => { const [code, setCode] = useMaybeControlledValue({ @@ -43,34 +41,7 @@ export const SquiggleEditor: React.FC = (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 = (props) => { setCode={setCode} errorLocations={errorLocations} /> - + ); }; diff --git a/packages/components/src/components/SquigglePlayground.tsx b/packages/components/src/components/SquigglePlayground.tsx index 59898d6b..a1f8bc05 100644 --- a/packages/components/src/components/SquigglePlayground.tsx +++ b/packages/components/src/components/SquigglePlayground.tsx @@ -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,23 +41,27 @@ 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 & { - /** The initial squiggle string to put in the playground */ - defaultCode?: string; - onCodeChange?(expr: string): void; - /* When settings change */ - onSettingsChange?(settings: any): void; - /** Should we show the editor? */ - showEditor?: boolean; - /** Useful for playground on squiggle website, where we update the anchor link based on current code and settings */ - showShareButton?: boolean; -}; +type PlaygroundProps = SquiggleArgs & + FlattenedViewSettings & { + /** The initial squiggle string to put in the playground */ + defaultCode?: string; + onCodeChange?(expr: string): void; + /* When settings change */ + onSettingsChange?(settings: any): void; + /** Should we show the editor? */ + 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({ getLeftPanelElement: () => undefined, }); -export const SquigglePlayground: FC = ({ - defaultCode = "", - height = 500, - showSummary = true, - logX = false, - expY = false, - title, - minX, - maxX, - tickFormat = defaultTickFormat, - distributionChartActions, - code: controlledCode, - onCodeChange, - onSettingsChange, - showEditor = true, - showShareButton = false, -}) => { +export const SquigglePlayground: FC = (props) => { + const { + defaultCode = "", + code: controlledCode, + onCodeChange, + onSettingsChange, + showShareButton = false, + } = props; const [code, setCode] = useMaybeControlledValue({ value: controlledCode, defaultValue: defaultCode, @@ -260,29 +256,19 @@ export const SquigglePlayground: FC = ({ const [imports, setImports] = useState({}); + 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 = ({ executionId, } = useRunnerState(code); - const project = React.useMemo(() => { - const p = SqProject.create(); - if (environment) { - p.setEnvironment(environment); - } - return p; - }, [environment]); - - const resultAndBindings = useSquiggle({ - code, - project, - jsImports: imports, - executionId, - }); + let args: SquiggleArgs = props; + args = { ...args, code, jsImports: imports, executionId }; + if (!args.project) { + args = { ...args, environment }; + } + const resultAndBindings = useSquiggle(args); const valueToRender = getValueToRender(resultAndBindings); @@ -328,27 +306,7 @@ export const SquigglePlayground: FC = ({ {isRunning ? (
) : null} - +
); @@ -363,7 +321,7 @@ export const SquigglePlayground: FC = ({ onSubmit={run} oneLine={false} showGutter={true} - height={height - 1} + height={(props.chartHeight ?? 200) - 1} /> ) : ( @@ -402,7 +360,7 @@ export const SquigglePlayground: FC = ({
{tabs} diff --git a/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx b/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx index 1c041dbf..6da9c786 100644 --- a/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx +++ b/packages/components/src/components/SquiggleViewer/ExpressionViewer.tsx @@ -105,9 +105,9 @@ export const ExpressionViewer: React.FC = ({ value, width }) => { return ( ); @@ -178,7 +178,7 @@ export const ExpressionViewer: React.FC = ({ 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 = ({ value, width }) => { ); }} > - {(settings) => ( + {(_) => (
NOT IMPLEMENTED IN 0.4 YET
// = ({ value, width }) => { plot={plot} environment={settings.environment} {...settings.distributionPlotSettings} - height={settings.height} + height={settings.chartHeight} width={width} /> ); diff --git a/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx b/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx index 50f8e5ba..3f6b2312 100644 --- a/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx +++ b/packages/components/src/components/SquiggleViewer/ItemSettingsMenu.tsx @@ -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< diff --git a/packages/components/src/components/SquiggleViewer/ViewerContext.ts b/packages/components/src/components/SquiggleViewer/ViewerContext.ts index 71b44126..6d4ec66f 100644 --- a/packages/components/src/components/SquiggleViewer/ViewerContext.ts +++ b/packages/components/src/components/SquiggleViewer/ViewerContext.ts @@ -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({ 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, diff --git a/packages/components/src/components/SquiggleViewer/index.tsx b/packages/components/src/components/SquiggleViewer/index.tsx index 17e9fdee..e297c7f2 100644 --- a/packages/components/src/components/SquiggleViewer/index.tsx +++ b/packages/components/src/components/SquiggleViewer/index.tsx @@ -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; + +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["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 = ({ 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 = ({ 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 = ({ ...(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 ( diff --git a/packages/components/src/components/SquiggleViewer/utils.ts b/packages/components/src/components/SquiggleViewer/utils.ts index f23de043..96e7c496 100644 --- a/packages/components/src/components/SquiggleViewer/utils.ts +++ b/packages/components/src/components/SquiggleViewer/utils.ts @@ -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; chartSettings?: Partial; - height?: number; - environment?: Partial; + chartHeight?: number; + environment?: environment; }; export type MergedItemSettings = { distributionPlotSettings: DistributionPlottingSettings; chartSettings: FunctionChartSettings; - height: number; + chartHeight: number; environment: environment; }; diff --git a/packages/components/src/components/ViewSettings.tsx b/packages/components/src/components/ViewSettings.tsx index 7d70bfc8..a00f867a 100644 --- a/packages/components/src/components/ViewSettings.tsx +++ b/packages/components/src/components/ViewSettings.tsx @@ -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; +export type EditableViewSettings = yup.InferType; + +export const viewSettingsToMerged = ( + settings: EditableViewSettings +): Omit => { + 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 +): Omit => { + 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; -}> = ({ - withShowEditorSetting = true, - withFunctionSettings = true, - disableLogXSetting, - register, -}) => { + register: UseFormRegister; +}> = ({ withFunctionSettings = true, disableLogXSetting, register }) => { return (
- {withShowEditorSetting ? ( - - ) : null} -
- -
- - - - - - - - -
-
-
+ {withFunctionSettings ? ( -
- -
- - 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. - -
- - - -
-
-
-
+ ) : null}
); }; + +export const DistributionViewSettings: React.FC<{ + disableLogXSetting?: boolean; + register: UseFormRegister; +}> = ({ disableLogXSetting, register }) => { + return ( +
+ +
+ + + + + + + + +
+
+
+ ); +}; + +export const FunctionViewSettings: React.FC<{ + register: UseFormRegister; +}> = ({ register }) => ( +
+ +
+ + 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. + +
+ + + +
+
+
+
+); diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index a97248b7..c0b232ed 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -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"; diff --git a/packages/components/src/lib/hooks/useSquiggle.ts b/packages/components/src/lib/hooks/useSquiggle.ts index 63fa3839..57f223db 100644 --- a/packages/components/src/lib/hooks/useSquiggle.ts +++ b/packages/components/src/lib/hooks/useSquiggle.ts @@ -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 | 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; }; diff --git a/packages/components/src/stories/SquiggleChart.stories.mdx b/packages/components/src/stories/SquiggleChart.stories.mdx index 45fec12e..93a0591e 100644 --- a/packages/components/src/stories/SquiggleChart.stories.mdx +++ b/packages/components/src/stories/SquiggleChart.stories.mdx @@ -250,5 +250,3 @@ to allow large and small numbers being printed cleanly. {Template.bind({})} - - diff --git a/packages/components/test/viewProps.test.tsx b/packages/components/test/viewProps.test.tsx new file mode 100644 index 00000000..5d78ff07 --- /dev/null +++ b/packages/components/test/viewProps.test.tsx @@ -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( + + ); + expect(container).toHaveTextContent("Mean"); + expect(container).toHaveTextContent("5"); +});