simplify settings objects (WIP)

This commit is contained in:
Vyacheslav Matyukhin 2022-07-29 02:52:32 +04:00
parent 4cdf9a6008
commit a3187b0a15
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
12 changed files with 211 additions and 285 deletions

View File

@ -16,27 +16,43 @@ import {
import { NumberShower } from "./NumberShower"; import { NumberShower } from "./NumberShower";
import { hasMassBelowZero } from "../lib/distributionUtils"; import { hasMassBelowZero } from "../lib/distributionUtils";
export type DistributionPlottingSettings = { export type PlotSettings = {
/** Whether to show a summary of means, stdev, percentiles etc */ /** Whether to show a summary of means, stdev, percentiles etc */
showSummary: boolean; showSummary: boolean;
actions?: boolean; /** Whether to show vega actions to the user, so they can copy the chart spec */
actions: boolean;
} & DistributionChartSpecOptions; } & DistributionChartSpecOptions;
export const plotSettingsFromPartial = (
partial: Partial<PlotSettings>
): PlotSettings => {
return {
showSummary: false,
logX: false,
expY: false,
color: "#739ECC",
tickFormat: ".9~s",
title: "",
actions: false,
...partial,
};
};
export type DistributionChartProps = { export type DistributionChartProps = {
distribution: Distribution; distribution: Distribution;
width?: number; width?: number;
height: number; height: number;
} & DistributionPlottingSettings; settings: PlotSettings;
};
export const DistributionChart: React.FC<DistributionChartProps> = (props) => { export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
const { const {
distribution, distribution,
height, height,
showSummary,
width, width,
logX, settings: { showSummary, logX, actions },
actions = false,
} = props; } = props;
const shape = distribution.pointSet(); const shape = distribution.pointSet();
const [sized] = useSize((size) => { const [sized] = useSize((size) => {
if (shape.tag === "Error") { if (shape.tag === "Error") {
@ -47,7 +63,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
); );
} }
const spec = buildVegaSpec(props); const spec = buildVegaSpec(props.settings);
let widthProp = width ? width : size.width; let widthProp = width ? width : size.width;
if (widthProp < 20) { if (widthProp < 20) {
@ -131,15 +147,15 @@ const SummaryTable: React.FC<SummaryTableProps> = ({ distribution }) => {
<table className="border border-collapse border-slate-400"> <table className="border border-collapse border-slate-400">
<thead className="bg-slate-50"> <thead className="bg-slate-50">
<tr> <tr>
<TableHeadCell>{"Mean"}</TableHeadCell> <TableHeadCell>Mean</TableHeadCell>
{hasResult(stdev) && <TableHeadCell>{"Stdev"}</TableHeadCell>} {hasResult(stdev) && <TableHeadCell>Stdev</TableHeadCell>}
<TableHeadCell>{"5%"}</TableHeadCell> <TableHeadCell>5%</TableHeadCell>
<TableHeadCell>{"10%"}</TableHeadCell> <TableHeadCell>10%</TableHeadCell>
<TableHeadCell>{"25%"}</TableHeadCell> <TableHeadCell>25%</TableHeadCell>
<TableHeadCell>{"50%"}</TableHeadCell> <TableHeadCell>50%</TableHeadCell>
<TableHeadCell>{"75%"}</TableHeadCell> <TableHeadCell>75%</TableHeadCell>
<TableHeadCell>{"90%"}</TableHeadCell> <TableHeadCell>90%</TableHeadCell>
<TableHeadCell>{"95%"}</TableHeadCell> <TableHeadCell>95%</TableHeadCell>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -7,28 +7,42 @@ import {
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { FunctionChart1Dist } from "./FunctionChart1Dist"; import { FunctionChart1Dist } from "./FunctionChart1Dist";
import { FunctionChart1Number } from "./FunctionChart1Number"; import { FunctionChart1Number } from "./FunctionChart1Number";
import { DistributionPlottingSettings } from "./DistributionChart"; import { PlotSettings } from "./DistributionChart";
import { ErrorAlert, MessageAlert } from "./Alert"; import { ErrorAlert, MessageAlert } from "./Alert";
export type FunctionChartSettings = { export type FunctionSettings = {
/** Where the function domain starts */
start: number; start: number;
/** Where the function domain ends */
stop: number; stop: number;
/** The amount of stops sampled */
count: number; count: number;
}; };
interface FunctionChartProps { interface FunctionChartProps {
fn: lambdaValue; fn: lambdaValue;
chartSettings: FunctionChartSettings; functionSettings: FunctionSettings;
distributionPlotSettings: DistributionPlottingSettings; plotSettings: PlotSettings;
environment: environment; environment: environment;
height: number; height: number;
} }
export const functionSettingsFromPartial = (
partial: Partial<FunctionSettings>
): FunctionSettings => {
return {
start: 0,
stop: 10,
count: 20,
...partial,
};
};
export const FunctionChart: React.FC<FunctionChartProps> = ({ export const FunctionChart: React.FC<FunctionChartProps> = ({
fn, fn,
chartSettings, functionSettings,
environment, environment,
distributionPlotSettings, plotSettings,
height, height,
}) => { }) => {
if (fn.parameters.length > 1) { if (fn.parameters.length > 1) {
@ -38,8 +52,8 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
</MessageAlert> </MessageAlert>
); );
} }
const result1 = runForeign(fn, [chartSettings.start], environment); const result1 = runForeign(fn, [functionSettings.start], environment);
const result2 = runForeign(fn, [chartSettings.stop], environment); const result2 = runForeign(fn, [functionSettings.stop], environment);
const getValidResult = () => { const getValidResult = () => {
if (result1.tag === "Ok") { if (result1.tag === "Ok") {
return result1; return result1;
@ -64,17 +78,17 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
return ( return (
<FunctionChart1Dist <FunctionChart1Dist
fn={fn} fn={fn}
chartSettings={chartSettings} chartSettings={functionSettings}
environment={environment} environment={environment}
height={height} height={height}
distributionPlotSettings={distributionPlotSettings} distributionPlotSettings={plotSettings}
/> />
); );
case "number": case "number":
return ( return (
<FunctionChart1Number <FunctionChart1Number
fn={fn} fn={fn}
chartSettings={chartSettings} chartSettings={functionSettings}
environment={environment} environment={environment}
height={height} height={height}
/> />

View File

@ -13,12 +13,10 @@ import {
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { createClassFromSpec } from "react-vega"; import { createClassFromSpec } from "react-vega";
import * as percentilesSpec from "../vega-specs/spec-percentiles.json"; import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
import { import { DistributionChart, PlotSettings } from "./DistributionChart";
DistributionChart,
DistributionPlottingSettings,
} from "./DistributionChart";
import { NumberShower } from "./NumberShower"; import { NumberShower } from "./NumberShower";
import { ErrorAlert } from "./Alert"; import { ErrorAlert } from "./Alert";
import { FunctionSettings } from "./FunctionChart";
let SquigglePercentilesChart = createClassFromSpec({ let SquigglePercentilesChart = createClassFromSpec({
spec: percentilesSpec as Spec, spec: percentilesSpec as Spec,
@ -38,16 +36,11 @@ function unwrap<a, b>(x: result<a, b>): a {
throw Error("FAILURE TO UNWRAP"); throw Error("FAILURE TO UNWRAP");
} }
} }
export type FunctionChartSettings = {
start: number;
stop: number;
count: number;
};
interface FunctionChart1DistProps { interface FunctionChart1DistProps {
fn: lambdaValue; fn: lambdaValue;
chartSettings: FunctionChartSettings; chartSettings: FunctionSettings;
distributionPlotSettings: DistributionPlottingSettings; distributionPlotSettings: PlotSettings;
environment: environment; environment: environment;
height: number; height: number;
} }
@ -182,7 +175,7 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
distribution={mouseItem.value.value} distribution={mouseItem.value.value}
width={400} width={400}
height={50} height={50}
{...distributionPlotSettings} settings={distributionPlotSettings}
/> />
) : null; ) : null;

View File

@ -10,6 +10,8 @@ import {
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { useSquiggle } from "../lib/hooks"; import { useSquiggle } from "../lib/hooks";
import { SquiggleViewer } from "./SquiggleViewer"; import { SquiggleViewer } from "./SquiggleViewer";
import { FunctionSettings, functionSettingsFromPartial } from "./FunctionChart";
import { PlotSettings, plotSettingsFromPartial } from "./DistributionChart";
export interface SquiggleChartProps { export interface SquiggleChartProps {
/** The input string for squiggle */ /** The input string for squiggle */
@ -20,12 +22,8 @@ export interface SquiggleChartProps {
sampleCount?: number; sampleCount?: number;
/** The amount of points returned to draw the distribution */ /** The amount of points returned to draw the distribution */
environment?: environment; environment?: environment;
/** If the result is a function, where the function domain starts */ plotSettings?: PlotSettings;
diagramStart?: number; functionSettings?: FunctionSettings;
/** 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 */ /** When the squiggle code gets reevaluated */
onChange?(expr: squiggleExpression | undefined): void; onChange?(expr: squiggleExpression | undefined): void;
/** CSS width of the element */ /** CSS width of the element */
@ -35,24 +33,6 @@ export interface SquiggleChartProps {
bindings?: bindings; bindings?: bindings;
/** JS imported parameters */ /** JS imported parameters */
jsImports?: jsImports; 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 to show vega actions to the user, so they can copy the chart spec */
distributionChartActions?: boolean;
enableLocalSettings?: boolean; enableLocalSettings?: boolean;
} }
@ -67,19 +47,9 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
height = 200, height = 200,
bindings = defaultBindings, bindings = defaultBindings,
jsImports = defaultImports, jsImports = defaultImports,
showSummary = false,
width, width,
logX = false, functionSettings,
expY = false, plotSettings,
diagramStart = 0,
diagramStop = 10,
diagramCount = 100,
tickFormat,
minX,
maxX,
color,
title,
distributionChartActions,
enableLocalSettings = false, enableLocalSettings = false,
}) => { }) => {
const result = useSquiggle({ const result = useSquiggle({
@ -91,31 +61,13 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
executionId, executionId,
}); });
const distributionPlotSettings = {
showSummary,
logX,
expY,
format: tickFormat,
minX,
maxX,
color,
title,
actions: distributionChartActions,
};
const chartSettings = {
start: diagramStart,
stop: diagramStop,
count: diagramCount,
};
return ( return (
<SquiggleViewer <SquiggleViewer
result={result} result={result}
width={width} width={width}
height={height} height={height}
distributionPlotSettings={distributionPlotSettings} plotSettings={plotSettingsFromPartial(plotSettings || {})}
chartSettings={chartSettings} functionSettings={functionSettingsFromPartial(functionSettings || {})}
environment={environment ?? defaultEnvironment} environment={environment ?? defaultEnvironment}
enableLocalSettings={enableLocalSettings} enableLocalSettings={enableLocalSettings}
/> />

View File

@ -6,7 +6,7 @@ import React, {
useRef, useRef,
useCallback, useCallback,
} from "react"; } from "react";
import { useForm, UseFormRegister, useWatch } from "react-hook-form"; import { useForm, UseFormRegister } from "react-hook-form";
import * as yup from "yup"; import * as yup from "yup";
import { useMaybeControlledValue, useRunnerState } from "../lib/hooks"; import { useMaybeControlledValue, useRunnerState } from "../lib/hooks";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
@ -36,10 +36,8 @@ 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 { import { plotSettingsFromPartial } from "./DistributionChart";
defaultColor, import { functionSettingsFromPartial } from "./FunctionChart";
defaultTickFormat,
} from "../lib/distributionSpecBuilder";
type PlaygroundProps = SquiggleChartProps & { type PlaygroundProps = SquiggleChartProps & {
/** The initial squiggle string to put in the playground */ /** The initial squiggle string to put in the playground */
@ -52,8 +50,8 @@ type PlaygroundProps = SquiggleChartProps & {
}; };
const schema = yup const schema = yup
.object({}) .object({
.shape({ sampleSettings: yup.object({
sampleCount: yup sampleCount: yup
.number() .number()
.required() .required()
@ -70,6 +68,7 @@ const schema = yup
.default(1000) .default(1000)
.min(10) .min(10)
.max(10000), .max(10000),
}),
}) })
.concat(viewSettingsSchema); .concat(viewSettingsSchema);
@ -81,7 +80,7 @@ const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
<div className="space-y-6 p-3 max-w-xl"> <div className="space-y-6 p-3 max-w-xl">
<div> <div>
<InputItem <InputItem
name="sampleCount" name="sampleSettings.sampleCount"
type="number" type="number"
label="Sample Count" label="Sample Count"
register={register} register={register}
@ -95,7 +94,7 @@ const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
</div> </div>
<div> <div>
<InputItem <InputItem
name="xyPointLength" name="sampleSettings.xyPointLength"
type="number" type="number"
register={register} register={register}
label="Coordinate Count (For PointSet Shapes)" label="Coordinate Count (For PointSet Shapes)"
@ -207,15 +206,8 @@ export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
export const SquigglePlayground: FC<PlaygroundProps> = ({ export const SquigglePlayground: FC<PlaygroundProps> = ({
defaultCode = "", defaultCode = "",
height = 500, height = 500,
showSummary = false, plotSettings: initialPlotSettings,
logX = false, functionSettings: initialFunctionSettings,
expY = false,
title,
minX,
maxX,
color = defaultColor,
tickFormat = defaultTickFormat,
distributionChartActions,
code: controlledCode, code: controlledCode,
onCodeChange, onCodeChange,
onSettingsChange, onSettingsChange,
@ -229,41 +221,40 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
const [imports, setImports] = useState({}); const [imports, setImports] = useState({});
const { register, control } = useForm({ const defaultValues = {
resolver: yupResolver(schema), chartHeight: 150,
defaultValues: { showEditor,
sampleSettings: {
sampleCount: 1000, sampleCount: 1000,
xyPointLength: 1000, xyPointLength: 1000,
chartHeight: 150,
logX,
expY,
title,
minX,
maxX,
color,
tickFormat,
distributionChartActions,
showSummary,
showEditor,
diagramStart: 0,
diagramStop: 10,
diagramCount: 20,
}, },
}); plotSettings: plotSettingsFromPartial(initialPlotSettings || {}),
const vars = useWatch({ functionSettings: functionSettingsFromPartial(
control, initialFunctionSettings || {}
}); ),
};
const { register, watch, getValues } = useForm<FormFields>({
resolver: yupResolver(schema),
defaultValues,
});
watch();
const [settings, setSettings] = useState(() => getValues());
useEffect(() => { useEffect(() => {
onSettingsChange?.(vars); const subscription = watch(() => {
}, [vars, onSettingsChange]); setSettings(getValues());
onSettingsChange?.(getValues());
});
return () => subscription.unsubscribe();
}, [onSettingsChange, getValues, watch]);
const env: environment = useMemo( const env: environment = useMemo(
() => ({ () => ({
sampleCount: Number(vars.sampleCount), sampleCount: Number(settings.sampleSettings.sampleCount),
xyPointLength: Number(vars.xyPointLength), xyPointLength: Number(settings.sampleSettings.xyPointLength),
}), }),
[vars.sampleCount, vars.xyPointLength] [settings.sampleSettings.sampleCount, settings.sampleSettings.xyPointLength]
); );
const { const {
@ -285,7 +276,8 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
code={renderedCode} code={renderedCode}
executionId={executionId} executionId={executionId}
environment={env} environment={env}
{...vars} plotSettings={settings.plotSettings}
functionSettings={settings.functionSettings}
bindings={defaultBindings} bindings={defaultBindings}
jsImports={imports} jsImports={imports}
enableLocalSettings={true} enableLocalSettings={true}
@ -293,7 +285,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
</div> </div>
); );
const firstTab = vars.showEditor ? ( const firstTab = settings.showEditor ? (
<div className="border border-slate-200"> <div className="border border-slate-200">
<CodeEditor <CodeEditor
value={code} value={code}
@ -363,8 +355,8 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<StyledTab.List> <StyledTab.List>
<StyledTab <StyledTab
name={vars.showEditor ? "Code" : "Display"} name={settings.showEditor ? "Code" : "Display"}
icon={vars.showEditor ? CodeIcon : EyeIcon} icon={settings.showEditor ? CodeIcon : EyeIcon}
/> />
<StyledTab name="Sampling Settings" icon={CogIcon} /> <StyledTab name="Sampling Settings" icon={CogIcon} />
<StyledTab name="View Settings" icon={ChartSquareBarIcon} /> <StyledTab name="View Settings" icon={ChartSquareBarIcon} />
@ -378,7 +370,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
onAutorunModeChange={setAutorunMode} onAutorunModeChange={setAutorunMode}
/> />
</div> </div>
{vars.showEditor ? withEditor : withoutEditor} {settings.showEditor ? withEditor : withoutEditor}
</div> </div>
</StyledTab.Group> </StyledTab.Group>
</PlaygroundContext.Provider> </PlaygroundContext.Provider>

View File

@ -2,7 +2,7 @@ import React from "react";
import { squiggleExpression, declaration } from "@quri/squiggle-lang"; import { squiggleExpression, declaration } from "@quri/squiggle-lang";
import { NumberShower } from "../NumberShower"; import { NumberShower } from "../NumberShower";
import { DistributionChart } from "../DistributionChart"; import { DistributionChart } from "../DistributionChart";
import { FunctionChart, FunctionChartSettings } from "../FunctionChart"; import { FunctionChart, FunctionSettings } from "../FunctionChart";
import clsx from "clsx"; import clsx from "clsx";
import { VariableBox } from "./VariableBox"; import { VariableBox } from "./VariableBox";
import { ItemSettingsMenu } from "./ItemSettingsMenu"; import { ItemSettingsMenu } from "./ItemSettingsMenu";
@ -21,7 +21,7 @@ function getRange<a>(x: declaration<a>) {
} }
} }
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings { function getFunctionSettings<a>(x: declaration<a>): FunctionSettings {
const range = getRange(x); const range = getRange(x);
const min = range.floats ? range.floats.min : 0; const min = range.floats ? range.floats.min : 0;
const max = range.floats ? range.floats.max : 10; const max = range.floats ? range.floats.max : 10;
@ -96,7 +96,7 @@ export const ExpressionViewer: React.FC<Props> = ({
return ( return (
<DistributionChart <DistributionChart
distribution={expression.value} distribution={expression.value}
{...settings.distributionPlotSettings} settings={settings.plotSettings}
height={settings.height} height={settings.height}
width={width} width={width}
/> />
@ -189,8 +189,8 @@ export const ExpressionViewer: React.FC<Props> = ({
)})`}</div> )})`}</div>
<FunctionChart <FunctionChart
fn={expression.value} fn={expression.value}
chartSettings={settings.chartSettings} functionSettings={settings.functionSettings}
distributionPlotSettings={settings.distributionPlotSettings} plotSettings={settings.plotSettings}
height={settings.height} height={settings.height}
environment={{ environment={{
sampleCount: settings.environment.sampleCount / 10, sampleCount: settings.environment.sampleCount / 10,
@ -219,8 +219,8 @@ export const ExpressionViewer: React.FC<Props> = ({
{(settings) => ( {(settings) => (
<FunctionChart <FunctionChart
fn={expression.value.fn} fn={expression.value.fn}
chartSettings={getChartSettings(expression.value)} functionSettings={getFunctionSettings(expression.value)}
distributionPlotSettings={settings.distributionPlotSettings} plotSettings={settings.plotSettings}
height={settings.height} height={settings.height}
environment={{ environment={{
sampleCount: settings.environment.sampleCount / 10, sampleCount: settings.environment.sampleCount / 10,

View File

@ -3,13 +3,13 @@ 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 { ViewSettings, viewSettingsSchema } from "../ViewSettings"; import {
ViewSettings,
ViewSettingsFormFields,
viewSettingsSchema,
} from "../ViewSettings";
import { Path, pathAsString } from "./utils"; import { Path, pathAsString } from "./utils";
import { ViewerContext } from "./ViewerContext"; import { ViewerContext } from "./ViewerContext";
import {
defaultColor,
defaultTickFormat,
} from "../../lib/distributionSpecBuilder";
import { PlaygroundContext } from "../SquigglePlayground"; import { PlaygroundContext } from "../SquigglePlayground";
type Props = { type Props = {
@ -34,48 +34,25 @@ const ItemSettingsModal: React.FC<
const mergedSettings = getMergedSettings(path); const mergedSettings = getMergedSettings(path);
const { register, watch } = useForm({ const { register, watch } = useForm<ViewSettingsFormFields>({
resolver: yupResolver(viewSettingsSchema), resolver: yupResolver(viewSettingsSchema),
defaultValues: { defaultValues: {
// this is a mess and should be fixed
showEditor: true, // doesn't matter showEditor: true, // doesn't matter
chartHeight: mergedSettings.height, chartHeight: mergedSettings.height,
showSummary: mergedSettings.distributionPlotSettings.showSummary, plotSettings: mergedSettings.plotSettings,
logX: mergedSettings.distributionPlotSettings.logX, functionSettings: mergedSettings.functionSettings,
expY: mergedSettings.distributionPlotSettings.expY,
tickFormat:
mergedSettings.distributionPlotSettings.format || defaultTickFormat,
title: mergedSettings.distributionPlotSettings.title,
color: mergedSettings.distributionPlotSettings.color || defaultColor,
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(path); // get the latest version const settings = getSettings(path); // get the latest version
setSettings(path, { setSettings(path, {
...settings, ...settings,
distributionPlotSettings: { // vars should be defined and react-hook-form is too conservative with its types so these `?.` are ok... hopefully.
showSummary: vars.showSummary, // This might cause a bug in the future. I don't really understand why react-hook-form needs to return partials here.
logX: vars.logX, plotSettings: vars?.plotSettings,
expY: vars.expY, functionSettings: vars?.functionSettings,
format: vars.tickFormat,
title: vars.title,
color: vars.color,
minX: vars.minX,
maxX: vars.maxX,
actions: vars.distributionChartActions,
},
chartSettings: {
start: vars.diagramStart,
stop: vars.diagramStop,
count: vars.diagramCount,
},
}); });
onChange(); onChange();
}); });
@ -141,13 +118,13 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => {
className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500" className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500"
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
/> />
{settings.distributionPlotSettings || settings.chartSettings ? ( {settings.plotSettings || settings.functionSettings ? (
<button <button
onClick={() => { onClick={() => {
setSettings(props.path, { setSettings(props.path, {
...settings, ...settings,
distributionPlotSettings: undefined, plotSettings: undefined,
chartSettings: undefined, functionSettings: undefined,
}); });
props.onChange(); props.onChange();
}} }}

View File

@ -1,5 +1,7 @@
import { defaultEnvironment } from "@quri/squiggle-lang"; import { defaultEnvironment } from "@quri/squiggle-lang";
import React from "react"; import React from "react";
import { plotSettingsFromPartial } from "../DistributionChart";
import { functionSettingsFromPartial } from "../FunctionChart";
import { LocalItemSettings, MergedItemSettings, Path } from "./utils"; import { LocalItemSettings, MergedItemSettings, Path } from "./utils";
type ViewerContextShape = { type ViewerContextShape = {
@ -17,16 +19,8 @@ export const ViewerContext = React.createContext<ViewerContextShape>({
getMergedSettings: () => ({ getMergedSettings: () => ({
collapsed: false, collapsed: false,
// copy-pasted from SquiggleChart // copy-pasted from SquiggleChart
chartSettings: { plotSettings: plotSettingsFromPartial({}),
start: 0, functionSettings: functionSettingsFromPartial({}),
stop: 10,
count: 100,
},
distributionPlotSettings: {
showSummary: false,
logX: false,
expY: false,
},
environment: defaultEnvironment, environment: defaultEnvironment,
height: 150, height: 150,
}), }),

View File

@ -1,7 +1,7 @@
import React, { useCallback, useRef } from "react"; import React, { useCallback, useRef } from "react";
import { environment } from "@quri/squiggle-lang"; import { environment } from "@quri/squiggle-lang";
import { DistributionPlottingSettings } from "../DistributionChart"; import { PlotSettings } from "../DistributionChart";
import { FunctionChartSettings } from "../FunctionChart"; import { FunctionSettings } from "../FunctionChart";
import { ExpressionViewer } from "./ExpressionViewer"; import { ExpressionViewer } from "./ExpressionViewer";
import { ViewerContext } from "./ViewerContext"; import { ViewerContext } from "./ViewerContext";
import { import {
@ -18,9 +18,8 @@ type Props = {
result: ReturnType<typeof useSquiggle>; result: ReturnType<typeof useSquiggle>;
width?: number; width?: number;
height: number; height: number;
distributionPlotSettings: DistributionPlottingSettings; plotSettings: PlotSettings;
/** Settings for displaying functions */ functionSettings: FunctionSettings;
chartSettings: FunctionChartSettings;
/** Environment for further function executions */ /** Environment for further function executions */
environment: environment; environment: environment;
enableLocalSettings?: boolean; enableLocalSettings?: boolean;
@ -36,8 +35,8 @@ export const SquiggleViewer: React.FC<Props> = ({
result, result,
width, width,
height, height,
distributionPlotSettings, plotSettings,
chartSettings, functionSettings,
environment, environment,
enableLocalSettings = false, enableLocalSettings = false,
}) => { }) => {
@ -62,13 +61,13 @@ export const SquiggleViewer: React.FC<Props> = ({
(path: Path) => { (path: Path) => {
const localSettings = getSettings(path); const localSettings = getSettings(path);
const result: MergedItemSettings = { const result: MergedItemSettings = {
distributionPlotSettings: { plotSettings: {
...distributionPlotSettings, ...plotSettings,
...(localSettings.distributionPlotSettings || {}), ...(localSettings.plotSettings || {}),
}, },
chartSettings: { functionSettings: {
...chartSettings, ...functionSettings,
...(localSettings.chartSettings || {}), ...(localSettings.functionSettings || {}),
}, },
environment: { environment: {
...environment, ...environment,
@ -78,7 +77,7 @@ export const SquiggleViewer: React.FC<Props> = ({
}; };
return result; return result;
}, },
[distributionPlotSettings, chartSettings, environment, height, getSettings] [plotSettings, functionSettings, environment, height, getSettings]
); );
return ( return (

View File

@ -1,18 +1,18 @@
import { DistributionPlottingSettings } from "../DistributionChart"; import { PlotSettings } from "../DistributionChart";
import { FunctionChartSettings } from "../FunctionChart"; import { FunctionSettings } from "../FunctionChart";
import { environment } from "@quri/squiggle-lang"; import { environment } from "@quri/squiggle-lang";
export type LocalItemSettings = { export type LocalItemSettings = {
collapsed: boolean; collapsed: boolean;
distributionPlotSettings?: Partial<DistributionPlottingSettings>; plotSettings?: Partial<PlotSettings>;
chartSettings?: Partial<FunctionChartSettings>; functionSettings?: Partial<FunctionSettings>;
height?: number; height?: number;
environment?: Partial<environment>; environment?: Partial<environment>;
}; };
export type MergedItemSettings = { export type MergedItemSettings = {
distributionPlotSettings: DistributionPlottingSettings; plotSettings: PlotSettings;
chartSettings: FunctionChartSettings; functionSettings: FunctionSettings;
height: number; height: number;
environment: environment; environment: environment;
}; };

View File

@ -5,36 +5,36 @@ 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 {
defaultColor,
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(),
showEditor: yup.boolean().required(), showEditor: yup.boolean().required(),
plotSettings: yup.object({
showSummary: yup.boolean().required(),
logX: yup.boolean().required(), logX: yup.boolean().required(),
expY: yup.boolean().required(), expY: yup.boolean().required(),
tickFormat: yup.string().default(defaultTickFormat), tickFormat: yup.string().required(),
title: yup.string(), title: yup.string().default(""),
color: yup.string().default(defaultColor).required(), color: yup.string().required(),
minX: yup.number(), minX: yup.number(),
maxX: yup.number(), maxX: yup.number(),
distributionChartActions: yup.boolean(), actions: yup.boolean().required(),
diagramStart: yup.number().required().positive().integer().default(0).min(0), }),
diagramStop: yup.number().required().positive().integer().default(10).min(0), functionSettings: yup.object({
diagramCount: yup.number().required().positive().integer().default(20).min(2), start: yup.number().required().positive().integer().default(0).min(0),
stop: yup.number().required().positive().integer().default(10).min(0),
count: yup.number().required().positive().integer().default(20).min(2),
}),
}); });
type FormFields = yup.InferType<typeof viewSettingsSchema>; export type ViewSettingsFormFields = yup.InferType<typeof viewSettingsSchema>;
// This component is used in two places: for global settings in SquigglePlayground, and for item-specific settings in modal dialogs. // 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; withShowEditorSetting?: boolean;
withFunctionSettings?: boolean; withFunctionSettings?: boolean;
disableLogXSetting?: boolean; disableLogXSetting?: boolean;
register: UseFormRegister<FormFields>; register: UseFormRegister<ViewSettingsFormFields>;
}> = ({ }> = ({
withShowEditorSetting = true, withShowEditorSetting = true,
withFunctionSettings = true, withFunctionSettings = true,
@ -66,7 +66,7 @@ export const ViewSettings: React.FC<{
<div className="space-y-2"> <div className="space-y-2">
<Checkbox <Checkbox
register={register} register={register}
name="logX" name="plotSettings.logX"
label="Show x scale logarithmically" label="Show x scale logarithmically"
disabled={disableLogXSetting} disabled={disableLogXSetting}
tooltip={ tooltip={
@ -77,45 +77,45 @@ export const ViewSettings: React.FC<{
/> />
<Checkbox <Checkbox
register={register} register={register}
name="expY" name="plotSettings.expY"
label="Show y scale exponentially" label="Show y scale exponentially"
/> />
<Checkbox <Checkbox
register={register} register={register}
name="distributionChartActions" name="plotSettings.actions"
label="Show vega chart controls" label="Show vega chart controls"
/> />
<Checkbox <Checkbox
register={register} register={register}
name="showSummary" name="plotSettings.showSummary"
label="Show summary statistics" label="Show summary statistics"
/> />
<InputItem <InputItem
name="minX" name="plotSettings.minX"
type="number" type="number"
register={register} register={register}
label="Min X Value" label="Min X Value"
/> />
<InputItem <InputItem
name="maxX" name="plotSettings.maxX"
type="number" type="number"
register={register} register={register}
label="Max X Value" label="Max X Value"
/> />
<InputItem <InputItem
name="title" name="plotSettings.title"
type="text" type="text"
register={register} register={register}
label="Title" label="Title"
/> />
<InputItem <InputItem
name="tickFormat" name="plotSettings.tickFormat"
type="text" type="text"
register={register} register={register}
label="Tick Format" label="Tick Format"
/> />
<InputItem <InputItem
name="color" name="plotSettings.color"
type="color" type="color"
register={register} register={register}
label="Color" label="Color"
@ -137,19 +137,19 @@ export const ViewSettings: React.FC<{
<div className="space-y-4"> <div className="space-y-4">
<InputItem <InputItem
type="number" type="number"
name="diagramStart" name="functionSettings.start"
register={register} register={register}
label="Min X Value" label="Min X Value"
/> />
<InputItem <InputItem
type="number" type="number"
name="diagramStop" name="functionSettings.stop"
register={register} register={register}
label="Max X Value" label="Max X Value"
/> />
<InputItem <InputItem
type="number" type="number"
name="diagramCount" name="functionSettings.count"
register={register} register={register}
label="Points between X min and X max to sample" label="Points between X min and X max to sample"
/> />

View File

@ -2,20 +2,20 @@ import { VisualizationSpec } from "react-vega";
import type { LogScale, LinearScale, PowScale } from "vega"; import type { LogScale, LinearScale, PowScale } from "vega";
export type DistributionChartSpecOptions = { export type DistributionChartSpecOptions = {
/** Set the x scale to be logarithmic by deault */ /** Set the x scale to be logarithmic by default */
logX: boolean; logX: boolean;
/** Set the y scale to be exponential by deault */ /** Set the y scale to be exponential by default */
expY: boolean; expY: boolean;
/** The minimum x coordinate shown on the chart */ /** The minimum x coordinate shown on the chart */
minX?: number; minX?: number;
/** The maximum x coordinate shown on the chart */ /** The maximum x coordinate shown on the chart */
maxX?: number; maxX?: number;
/** The color of the chart */ /** The color of the chart */
color?: string; color: string;
/** The title of the chart */ /** The title of the chart */
title?: string; title: string;
/** The formatting of the ticks */ /** How to format numbers on the x axis */
format?: string; tickFormat: string;
}; };
export let linearXScale: LinearScale = { export let linearXScale: LinearScale = {
@ -100,21 +100,10 @@ export let expYScale: PowScale = {
}, },
}; };
export const defaultTickFormat = ".9~s";
export const defaultColor = "#739ECC";
export function buildVegaSpec( export function buildVegaSpec(
specOptions: DistributionChartSpecOptions specOptions: DistributionChartSpecOptions
): VisualizationSpec { ): VisualizationSpec {
let { let { tickFormat, color, title, minX, maxX, logX, expY } = specOptions;
format = defaultTickFormat,
color = defaultColor,
title,
minX,
maxX,
logX,
expY,
} = specOptions;
let xScale = logX ? logXScale : linearXScale; let xScale = logX ? logXScale : linearXScale;
if (minX !== undefined && Number.isFinite(minX)) { if (minX !== undefined && Number.isFinite(minX)) {
@ -150,7 +139,7 @@ export function buildVegaSpec(
tickOpacity: 0.0, tickOpacity: 0.0,
domainColor: "#fff", domainColor: "#fff",
domainOpacity: 0.0, domainOpacity: 0.0,
format: format, format: tickFormat,
tickCount: 10, tickCount: 10,
}, },
], ],