Merge pull request #824 from quantified-uncertainty/multiple-charts
Add multiple plotting
This commit is contained in:
commit
514af05fc2
|
@ -4,6 +4,8 @@ import {
|
||||||
result,
|
result,
|
||||||
distributionError,
|
distributionError,
|
||||||
distributionErrorToString,
|
distributionErrorToString,
|
||||||
|
squiggleExpression,
|
||||||
|
resultMap,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { Vega } from "react-vega";
|
import { Vega } from "react-vega";
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
|
@ -14,6 +16,8 @@ import {
|
||||||
DistributionChartSpecOptions,
|
DistributionChartSpecOptions,
|
||||||
} from "../lib/distributionSpecBuilder";
|
} from "../lib/distributionSpecBuilder";
|
||||||
import { NumberShower } from "./NumberShower";
|
import { NumberShower } from "./NumberShower";
|
||||||
|
import { Plot, parsePlot } from "../lib/plotParser";
|
||||||
|
import { flattenResult } from "../lib/utility";
|
||||||
import { hasMassBelowZero } from "../lib/distributionUtils";
|
import { hasMassBelowZero } from "../lib/distributionUtils";
|
||||||
|
|
||||||
export type DistributionPlottingSettings = {
|
export type DistributionPlottingSettings = {
|
||||||
|
@ -23,26 +27,41 @@ export type DistributionPlottingSettings = {
|
||||||
} & DistributionChartSpecOptions;
|
} & DistributionChartSpecOptions;
|
||||||
|
|
||||||
export type DistributionChartProps = {
|
export type DistributionChartProps = {
|
||||||
distribution: Distribution;
|
plot: Plot;
|
||||||
width?: number;
|
width?: number;
|
||||||
height: number;
|
height: number;
|
||||||
} & DistributionPlottingSettings;
|
} & DistributionPlottingSettings;
|
||||||
|
|
||||||
|
export function defaultPlot(distribution: Distribution): Plot {
|
||||||
|
return { distributions: [{ name: "default", distribution }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makePlot(record: {
|
||||||
|
[key: string]: squiggleExpression;
|
||||||
|
}): Plot | void {
|
||||||
|
const plotResult = parsePlot(record);
|
||||||
|
if (plotResult.tag === "Ok") {
|
||||||
|
return plotResult.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
const {
|
const { plot, height, showSummary, width, logX, actions = false } = props;
|
||||||
distribution,
|
|
||||||
height,
|
|
||||||
showSummary,
|
|
||||||
width,
|
|
||||||
logX,
|
|
||||||
actions = false,
|
|
||||||
} = props;
|
|
||||||
const shape = distribution.pointSet();
|
|
||||||
const [sized] = useSize((size) => {
|
const [sized] = useSize((size) => {
|
||||||
if (shape.tag === "Error") {
|
let shapes = flattenResult(
|
||||||
|
plot.distributions.map((x) =>
|
||||||
|
resultMap(x.distribution.pointSet(), (shape) => ({
|
||||||
|
name: x.name,
|
||||||
|
// color: x.color, // not supported yet
|
||||||
|
continuous: shape.continuous,
|
||||||
|
discrete: shape.discrete,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (shapes.tag === "Error") {
|
||||||
return (
|
return (
|
||||||
<ErrorAlert heading="Distribution Error">
|
<ErrorAlert heading="Distribution Error">
|
||||||
{distributionErrorToString(shape.value)}
|
{distributionErrorToString(shapes.value)}
|
||||||
</ErrorAlert>
|
</ErrorAlert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -56,24 +75,29 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
);
|
);
|
||||||
widthProp = 20;
|
widthProp = 20;
|
||||||
}
|
}
|
||||||
|
const domain = shapes.value.flatMap((shape) =>
|
||||||
|
shape.discrete.concat(shape.continuous)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: widthProp }}>
|
<div style={{ width: widthProp }}>
|
||||||
{logX && hasMassBelowZero(shape.value) ? (
|
{logX && shapes.value.some(hasMassBelowZero) ? (
|
||||||
<ErrorAlert heading="Log Domain Error">
|
<ErrorAlert heading="Log Domain Error">
|
||||||
Cannot graph distribution with negative values on logarithmic scale.
|
Cannot graph distribution with negative values on logarithmic scale.
|
||||||
</ErrorAlert>
|
</ErrorAlert>
|
||||||
) : (
|
) : (
|
||||||
<Vega
|
<Vega
|
||||||
spec={spec}
|
spec={spec}
|
||||||
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
|
data={{ data: shapes.value, domain }}
|
||||||
width={widthProp - 10}
|
width={widthProp - 10}
|
||||||
height={height}
|
height={height}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
{showSummary && <SummaryTable distribution={distribution} />}
|
{showSummary && plot.distributions.length === 1 && (
|
||||||
|
<SummaryTable distribution={plot.distributions[0].distribution} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
||||||
import {
|
import {
|
||||||
DistributionChart,
|
DistributionChart,
|
||||||
DistributionPlottingSettings,
|
DistributionPlottingSettings,
|
||||||
|
defaultPlot,
|
||||||
} from "./DistributionChart";
|
} from "./DistributionChart";
|
||||||
import { NumberShower } from "./NumberShower";
|
import { NumberShower } from "./NumberShower";
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
|
@ -179,7 +180,7 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
||||||
let showChart =
|
let showChart =
|
||||||
mouseItem.tag === "Ok" && mouseItem.value.tag === "distribution" ? (
|
mouseItem.tag === "Ok" && mouseItem.value.tag === "distribution" ? (
|
||||||
<DistributionChart
|
<DistributionChart
|
||||||
distribution={mouseItem.value.value}
|
plot={defaultPlot(mouseItem.value.value)}
|
||||||
width={400}
|
width={400}
|
||||||
height={50}
|
height={50}
|
||||||
{...distributionPlotSettings}
|
{...distributionPlotSettings}
|
||||||
|
|
|
@ -37,10 +37,7 @@ 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 { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
||||||
defaultColor,
|
|
||||||
defaultTickFormat,
|
|
||||||
} from "../lib/distributionSpecBuilder";
|
|
||||||
import { Button } from "./ui/Button";
|
import { Button } from "./ui/Button";
|
||||||
|
|
||||||
type PlaygroundProps = SquiggleChartProps & {
|
type PlaygroundProps = SquiggleChartProps & {
|
||||||
|
@ -240,7 +237,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
title,
|
title,
|
||||||
minX,
|
minX,
|
||||||
maxX,
|
maxX,
|
||||||
color = defaultColor,
|
|
||||||
tickFormat = defaultTickFormat,
|
tickFormat = defaultTickFormat,
|
||||||
distributionChartActions,
|
distributionChartActions,
|
||||||
code: controlledCode,
|
code: controlledCode,
|
||||||
|
@ -268,7 +264,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
title,
|
title,
|
||||||
minX,
|
minX,
|
||||||
maxX,
|
maxX,
|
||||||
color,
|
|
||||||
tickFormat,
|
tickFormat,
|
||||||
distributionChartActions,
|
distributionChartActions,
|
||||||
showSummary,
|
showSummary,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
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, defaultPlot, makePlot } from "../DistributionChart";
|
||||||
import { FunctionChart, FunctionChartSettings } from "../FunctionChart";
|
import { FunctionChart, FunctionChartSettings } from "../FunctionChart";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { VariableBox } from "./VariableBox";
|
import { VariableBox } from "./VariableBox";
|
||||||
|
@ -102,7 +102,7 @@ export const ExpressionViewer: React.FC<Props> = ({
|
||||||
{(settings) => {
|
{(settings) => {
|
||||||
return (
|
return (
|
||||||
<DistributionChart
|
<DistributionChart
|
||||||
distribution={expression.value}
|
plot={defaultPlot(expression.value)}
|
||||||
{...settings.distributionPlotSettings}
|
{...settings.distributionPlotSettings}
|
||||||
height={settings.height}
|
height={settings.height}
|
||||||
width={width}
|
width={width}
|
||||||
|
@ -241,9 +241,9 @@ export const ExpressionViewer: React.FC<Props> = ({
|
||||||
case "module": {
|
case "module": {
|
||||||
return (
|
return (
|
||||||
<VariableList path={path} heading="Module">
|
<VariableList path={path} heading="Module">
|
||||||
{(settings) =>
|
{(_) =>
|
||||||
Object.entries(expression.value)
|
Object.entries(expression.value)
|
||||||
.filter(([key, r]) => !key.match(/^(Math|System)\./))
|
.filter(([key, _]) => !key.match(/^(Math|System)\./))
|
||||||
.map(([key, r]) => (
|
.map(([key, r]) => (
|
||||||
<ExpressionViewer
|
<ExpressionViewer
|
||||||
key={key}
|
key={key}
|
||||||
|
@ -257,24 +257,61 @@ export const ExpressionViewer: React.FC<Props> = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "record":
|
case "record":
|
||||||
return (
|
const plot = makePlot(expression.value);
|
||||||
<VariableList path={path} heading="Record">
|
if (plot) {
|
||||||
{(settings) =>
|
return (
|
||||||
Object.entries(expression.value).map(([key, r]) => (
|
<VariableBox
|
||||||
<ExpressionViewer
|
path={path}
|
||||||
key={key}
|
heading={"Plot"}
|
||||||
path={[...path, key]}
|
renderSettingsMenu={({ onChange }) => {
|
||||||
expression={r}
|
let disableLogX = plot.distributions.some((x) => {
|
||||||
width={width !== undefined ? width - 20 : width}
|
let pointSet = x.distribution.pointSet();
|
||||||
/>
|
return (
|
||||||
))
|
pointSet.tag === "Ok" && hasMassBelowZero(pointSet.value)
|
||||||
}
|
);
|
||||||
</VariableList>
|
});
|
||||||
);
|
return (
|
||||||
|
<ItemSettingsMenu
|
||||||
|
path={path}
|
||||||
|
onChange={onChange}
|
||||||
|
disableLogX={disableLogX}
|
||||||
|
withFunctionSettings={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(settings) => {
|
||||||
|
return (
|
||||||
|
<DistributionChart
|
||||||
|
plot={plot}
|
||||||
|
{...settings.distributionPlotSettings}
|
||||||
|
height={settings.height}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<VariableList path={path} heading="Record">
|
||||||
|
{(_) =>
|
||||||
|
Object.entries(expression.value).map(([key, r]) => (
|
||||||
|
<ExpressionViewer
|
||||||
|
key={key}
|
||||||
|
path={[...path, key]}
|
||||||
|
expression={r}
|
||||||
|
width={width !== undefined ? width - 20 : width}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</VariableList>
|
||||||
|
);
|
||||||
|
}
|
||||||
case "array":
|
case "array":
|
||||||
return (
|
return (
|
||||||
<VariableList path={path} heading="Array">
|
<VariableList path={path} heading="Array">
|
||||||
{(settings) =>
|
{(_) =>
|
||||||
expression.value.map((r, i) => (
|
expression.value.map((r, i) => (
|
||||||
<ExpressionViewer
|
<ExpressionViewer
|
||||||
key={i}
|
key={i}
|
||||||
|
|
|
@ -6,10 +6,7 @@ import { Modal } from "../ui/Modal";
|
||||||
import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
|
import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
|
||||||
import { Path, pathAsString } from "./utils";
|
import { Path, pathAsString } from "./utils";
|
||||||
import { ViewerContext } from "./ViewerContext";
|
import { ViewerContext } from "./ViewerContext";
|
||||||
import {
|
import { defaultTickFormat } from "../../lib/distributionSpecBuilder";
|
||||||
defaultColor,
|
|
||||||
defaultTickFormat,
|
|
||||||
} from "../../lib/distributionSpecBuilder";
|
|
||||||
import { PlaygroundContext } from "../SquigglePlayground";
|
import { PlaygroundContext } from "../SquigglePlayground";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -46,7 +43,6 @@ const ItemSettingsModal: React.FC<
|
||||||
tickFormat:
|
tickFormat:
|
||||||
mergedSettings.distributionPlotSettings.format || defaultTickFormat,
|
mergedSettings.distributionPlotSettings.format || defaultTickFormat,
|
||||||
title: mergedSettings.distributionPlotSettings.title,
|
title: mergedSettings.distributionPlotSettings.title,
|
||||||
color: mergedSettings.distributionPlotSettings.color || defaultColor,
|
|
||||||
minX: mergedSettings.distributionPlotSettings.minX,
|
minX: mergedSettings.distributionPlotSettings.minX,
|
||||||
maxX: mergedSettings.distributionPlotSettings.maxX,
|
maxX: mergedSettings.distributionPlotSettings.maxX,
|
||||||
distributionChartActions: mergedSettings.distributionPlotSettings.actions,
|
distributionChartActions: mergedSettings.distributionPlotSettings.actions,
|
||||||
|
@ -66,7 +62,6 @@ const ItemSettingsModal: React.FC<
|
||||||
expY: vars.expY,
|
expY: vars.expY,
|
||||||
format: vars.tickFormat,
|
format: vars.tickFormat,
|
||||||
title: vars.title,
|
title: vars.title,
|
||||||
color: vars.color,
|
|
||||||
minX: vars.minX,
|
minX: vars.minX,
|
||||||
maxX: vars.maxX,
|
maxX: vars.maxX,
|
||||||
actions: vars.distributionChartActions,
|
actions: vars.distributionChartActions,
|
||||||
|
|
|
@ -5,10 +5,7 @@ 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 {
|
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
||||||
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),
|
||||||
|
@ -18,7 +15,6 @@ export const viewSettingsSchema = yup.object({}).shape({
|
||||||
expY: yup.boolean().required(),
|
expY: yup.boolean().required(),
|
||||||
tickFormat: yup.string().default(defaultTickFormat),
|
tickFormat: yup.string().default(defaultTickFormat),
|
||||||
title: yup.string(),
|
title: yup.string(),
|
||||||
color: yup.string().default(defaultColor).required(),
|
|
||||||
minX: yup.number(),
|
minX: yup.number(),
|
||||||
maxX: yup.number(),
|
maxX: yup.number(),
|
||||||
distributionChartActions: yup.boolean(),
|
distributionChartActions: yup.boolean(),
|
||||||
|
@ -114,12 +110,6 @@ export const ViewSettings: React.FC<{
|
||||||
register={register}
|
register={register}
|
||||||
label="Tick Format"
|
label="Tick Format"
|
||||||
/>
|
/>
|
||||||
<InputItem
|
|
||||||
name="color"
|
|
||||||
type="color"
|
|
||||||
register={register}
|
|
||||||
label="Color"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</HeadedSection>
|
</HeadedSection>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,8 +10,6 @@ export type DistributionChartSpecOptions = {
|
||||||
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 */
|
|
||||||
color?: string;
|
|
||||||
/** The title of the chart */
|
/** The title of the chart */
|
||||||
title?: string;
|
title?: string;
|
||||||
/** The formatting of the ticks */
|
/** The formatting of the ticks */
|
||||||
|
@ -25,36 +23,14 @@ export let linearXScale: LinearScale = {
|
||||||
range: "width",
|
range: "width",
|
||||||
zero: false,
|
zero: false,
|
||||||
nice: false,
|
nice: false,
|
||||||
domain: {
|
domain: { data: "domain", field: "x" },
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
data: "con",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: "dis",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
export let linearYScale: LinearScale = {
|
export let linearYScale: LinearScale = {
|
||||||
name: "yscale",
|
name: "yscale",
|
||||||
type: "linear",
|
type: "linear",
|
||||||
range: "height",
|
range: "height",
|
||||||
zero: true,
|
zero: true,
|
||||||
domain: {
|
domain: { data: "domain", field: "y" },
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
data: "con",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: "dis",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export let logXScale: LogScale = {
|
export let logXScale: LogScale = {
|
||||||
|
@ -65,18 +41,7 @@ export let logXScale: LogScale = {
|
||||||
base: 10,
|
base: 10,
|
||||||
nice: false,
|
nice: false,
|
||||||
clamp: true,
|
clamp: true,
|
||||||
domain: {
|
domain: { data: "domain", field: "x" },
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
data: "con",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: "dis",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export let expYScale: PowScale = {
|
export let expYScale: PowScale = {
|
||||||
|
@ -86,29 +51,16 @@ export let expYScale: PowScale = {
|
||||||
range: "height",
|
range: "height",
|
||||||
zero: true,
|
zero: true,
|
||||||
nice: false,
|
nice: false,
|
||||||
domain: {
|
domain: { data: "domain", field: "y" },
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
data: "con",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: "dis",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultTickFormat = ".9~s";
|
export const defaultTickFormat = ".9~s";
|
||||||
export const defaultColor = "#739ECC";
|
|
||||||
|
|
||||||
export function buildVegaSpec(
|
export function buildVegaSpec(
|
||||||
specOptions: DistributionChartSpecOptions
|
specOptions: DistributionChartSpecOptions
|
||||||
): VisualizationSpec {
|
): VisualizationSpec {
|
||||||
let {
|
const {
|
||||||
format = defaultTickFormat,
|
format = defaultTickFormat,
|
||||||
color = defaultColor,
|
|
||||||
title,
|
title,
|
||||||
minX,
|
minX,
|
||||||
maxX,
|
maxX,
|
||||||
|
@ -127,20 +79,32 @@ export function buildVegaSpec(
|
||||||
|
|
||||||
let spec: VisualizationSpec = {
|
let spec: VisualizationSpec = {
|
||||||
$schema: "https://vega.github.io/schema/vega/v5.json",
|
$schema: "https://vega.github.io/schema/vega/v5.json",
|
||||||
description: "A basic area chart example",
|
description: "Squiggle plot chart",
|
||||||
width: 500,
|
width: 500,
|
||||||
height: 100,
|
height: 100,
|
||||||
padding: 5,
|
padding: 5,
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
name: "con",
|
name: "data",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dis",
|
name: "domain",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
signals: [],
|
signals: [],
|
||||||
scales: [xScale, expY ? expYScale : linearYScale],
|
scales: [
|
||||||
|
xScale,
|
||||||
|
expY ? expYScale : linearYScale,
|
||||||
|
{
|
||||||
|
name: "color",
|
||||||
|
type: "ordinal",
|
||||||
|
domain: {
|
||||||
|
data: "data",
|
||||||
|
field: "name",
|
||||||
|
},
|
||||||
|
range: { scheme: "blues" },
|
||||||
|
},
|
||||||
|
],
|
||||||
axes: [
|
axes: [
|
||||||
{
|
{
|
||||||
orient: "bottom",
|
orient: "bottom",
|
||||||
|
@ -152,108 +116,178 @@ export function buildVegaSpec(
|
||||||
domainOpacity: 0.0,
|
domainOpacity: 0.0,
|
||||||
format: format,
|
format: format,
|
||||||
tickCount: 10,
|
tickCount: 10,
|
||||||
|
labelOverlap: "greedy",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
marks: [
|
marks: [
|
||||||
{
|
{
|
||||||
type: "area",
|
name: "all_distributions",
|
||||||
|
type: "group",
|
||||||
from: {
|
from: {
|
||||||
data: "con",
|
facet: {
|
||||||
},
|
name: "distribution_facet",
|
||||||
encode: {
|
data: "data",
|
||||||
update: {
|
groupby: ["name"],
|
||||||
interpolate: { value: "linear" },
|
|
||||||
x: {
|
|
||||||
scale: "xscale",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
scale: "yscale",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
y2: {
|
|
||||||
scale: "yscale",
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
value: color,
|
|
||||||
},
|
|
||||||
fillOpacity: {
|
|
||||||
value: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
name: "continuous_distribution",
|
||||||
|
type: "group",
|
||||||
|
from: {
|
||||||
|
facet: {
|
||||||
|
name: "continuous_facet",
|
||||||
|
data: "distribution_facet",
|
||||||
|
field: "continuous",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
encode: {
|
||||||
|
update: {},
|
||||||
|
},
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
name: "continuous_area",
|
||||||
|
type: "area",
|
||||||
|
from: {
|
||||||
|
data: "continuous_facet",
|
||||||
|
},
|
||||||
|
encode: {
|
||||||
|
update: {
|
||||||
|
interpolate: { value: "linear" },
|
||||||
|
x: {
|
||||||
|
scale: "xscale",
|
||||||
|
field: "x",
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
scale: "yscale",
|
||||||
|
field: "y",
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
scale: "color",
|
||||||
|
field: { parent: "name" },
|
||||||
|
},
|
||||||
|
y2: {
|
||||||
|
scale: "yscale",
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
fillOpacity: {
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discrete_distribution",
|
||||||
|
type: "group",
|
||||||
|
from: {
|
||||||
|
facet: {
|
||||||
|
name: "discrete_facet",
|
||||||
|
data: "distribution_facet",
|
||||||
|
field: "discrete",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
type: "rect",
|
||||||
|
from: {
|
||||||
|
data: "discrete_facet",
|
||||||
|
},
|
||||||
|
encode: {
|
||||||
|
enter: {
|
||||||
|
width: {
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
x: {
|
||||||
|
scale: "xscale",
|
||||||
|
field: "x",
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
scale: "yscale",
|
||||||
|
field: "y",
|
||||||
|
},
|
||||||
|
y2: {
|
||||||
|
scale: "yscale",
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
scale: "color",
|
||||||
|
field: { parent: "name" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "symbol",
|
||||||
|
from: {
|
||||||
|
data: "discrete_facet",
|
||||||
|
},
|
||||||
|
encode: {
|
||||||
|
enter: {
|
||||||
|
shape: {
|
||||||
|
value: "circle",
|
||||||
|
},
|
||||||
|
size: [{ value: 100 }],
|
||||||
|
tooltip: {
|
||||||
|
signal: "{ probability: datum.y, value: datum.x }",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
x: {
|
||||||
|
scale: "xscale",
|
||||||
|
field: "x",
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
scale: "yscale",
|
||||||
|
field: "y",
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
scale: "color",
|
||||||
|
field: { parent: "name" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
legends: [
|
||||||
{
|
{
|
||||||
type: "rect",
|
fill: "color",
|
||||||
from: {
|
orient: "top",
|
||||||
data: "dis",
|
labelFontSize: 12,
|
||||||
},
|
|
||||||
encode: {
|
encode: {
|
||||||
enter: {
|
symbols: {
|
||||||
width: {
|
update: {
|
||||||
value: 1,
|
fill: [
|
||||||
|
{ test: "length(domain('color')) == 1", value: "transparent" },
|
||||||
|
{ scale: "color", field: "value" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: {
|
labels: {
|
||||||
x: {
|
interactive: true,
|
||||||
scale: "xscale",
|
update: {
|
||||||
field: "x",
|
fill: [
|
||||||
},
|
{ test: "length(domain('color')) == 1", value: "transparent" },
|
||||||
y: {
|
{ value: "black" },
|
||||||
scale: "yscale",
|
],
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
y2: {
|
|
||||||
scale: "yscale",
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
value: "#2f65a7",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "symbol",
|
|
||||||
from: {
|
|
||||||
data: "dis",
|
|
||||||
},
|
|
||||||
encode: {
|
|
||||||
enter: {
|
|
||||||
shape: {
|
|
||||||
value: "circle",
|
|
||||||
},
|
|
||||||
size: [{ value: 100 }],
|
|
||||||
tooltip: {
|
|
||||||
signal: "{ probability: datum.y, value: datum.x }",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
x: {
|
|
||||||
scale: "xscale",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
scale: "yscale",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
value: "#1e4577",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
...(title && {
|
||||||
if (title) {
|
|
||||||
spec = {
|
|
||||||
...spec,
|
|
||||||
title: {
|
title: {
|
||||||
text: title,
|
text: title,
|
||||||
},
|
},
|
||||||
};
|
}),
|
||||||
}
|
};
|
||||||
|
|
||||||
return spec;
|
return spec;
|
||||||
}
|
}
|
||||||
|
|
72
packages/components/src/lib/plotParser.ts
Normal file
72
packages/components/src/lib/plotParser.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import * as yup from "yup";
|
||||||
|
import { Distribution, result, squiggleExpression } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
export type LabeledDistribution = {
|
||||||
|
name: string;
|
||||||
|
distribution: Distribution;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Plot = {
|
||||||
|
distributions: LabeledDistribution[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function error<a, b>(err: b): result<a, b> {
|
||||||
|
return { tag: "Error", value: err };
|
||||||
|
}
|
||||||
|
|
||||||
|
function ok<a, b>(x: a): result<a, b> {
|
||||||
|
return { tag: "Ok", value: x };
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = yup
|
||||||
|
.object()
|
||||||
|
.strict()
|
||||||
|
.noUnknown()
|
||||||
|
.shape({
|
||||||
|
distributions: yup.object().shape({
|
||||||
|
tag: yup.mixed().oneOf(["array"]),
|
||||||
|
value: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup.object().shape({
|
||||||
|
tag: yup.mixed().oneOf(["record"]),
|
||||||
|
value: yup.object({
|
||||||
|
name: yup.object().shape({
|
||||||
|
tag: yup.mixed().oneOf(["string"]),
|
||||||
|
value: yup.string().required(),
|
||||||
|
}),
|
||||||
|
// color: yup
|
||||||
|
// .object({
|
||||||
|
// tag: yup.mixed().oneOf(["string"]),
|
||||||
|
// value: yup.string().required(),
|
||||||
|
// })
|
||||||
|
// .default(undefined),
|
||||||
|
distribution: yup.object({
|
||||||
|
tag: yup.mixed().oneOf(["distribution"]),
|
||||||
|
value: yup.mixed(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.required(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function parsePlot(record: {
|
||||||
|
[key: string]: squiggleExpression;
|
||||||
|
}): result<Plot, string> {
|
||||||
|
try {
|
||||||
|
const plotRecord = schema.validateSync(record);
|
||||||
|
return ok({
|
||||||
|
distributions: plotRecord.distributions.value.map((x) => ({
|
||||||
|
name: x.value.name.value,
|
||||||
|
// color: x.value.color?.value, // not supported yet
|
||||||
|
distribution: x.value.distribution.value,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
const message = e instanceof Error ? e.message : "Unknown error";
|
||||||
|
return error(message);
|
||||||
|
}
|
||||||
|
}
|
37
packages/components/src/lib/utility.ts
Normal file
37
packages/components/src/lib/utility.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { result } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
export function flattenResult<a, b>(x: result<a, b>[]): result<a[], b> {
|
||||||
|
if (x.length === 0) {
|
||||||
|
return { tag: "Ok", value: [] };
|
||||||
|
} else {
|
||||||
|
if (x[0].tag === "Error") {
|
||||||
|
return x[0];
|
||||||
|
} else {
|
||||||
|
let rest = flattenResult(x.splice(1));
|
||||||
|
if (rest.tag === "Error") {
|
||||||
|
return rest;
|
||||||
|
} else {
|
||||||
|
return { tag: "Ok", value: [x[0].value].concat(rest.value) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resultBind<a, b, c>(
|
||||||
|
x: result<a, b>,
|
||||||
|
fn: (y: a) => result<c, b>
|
||||||
|
): result<c, b> {
|
||||||
|
if (x.tag === "Ok") {
|
||||||
|
return fn(x.value);
|
||||||
|
} else {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function all(arr: boolean[]): boolean {
|
||||||
|
return arr.reduce((x, y) => x && y, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function some(arr: boolean[]): boolean {
|
||||||
|
return arr.reduce((x, y) => x || y, false);
|
||||||
|
}
|
|
@ -93,6 +93,33 @@ could be continuous, discrete or mixed.
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
## Multiple plots
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Multiple plots"
|
||||||
|
args={{
|
||||||
|
code: `
|
||||||
|
{
|
||||||
|
distributions: [
|
||||||
|
{
|
||||||
|
name: "one",
|
||||||
|
distribution: mx(0.5, normal(0,1))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two",
|
||||||
|
distribution: mx(2, normal(5, 2)),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
width,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
## Constants
|
## Constants
|
||||||
|
|
||||||
A constant is a simple number as a result. This has special formatting rules
|
A constant is a simple number as a result. This has special formatting rules
|
||||||
|
|
Loading…
Reference in New Issue
Block a user