Merge pull request #596 from quantified-uncertainty/playground-toggles

First attempt at playground configurable toggles
This commit is contained in:
Ozzie Gooen 2022-05-27 07:33:05 -04:00 committed by GitHub
commit f61dc08f03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 770 additions and 235 deletions

View File

@ -3,18 +3,21 @@
"version": "0.2.20",
"license": "MIT",
"dependencies": {
"@hookform/resolvers": "^2.8.10",
"@quri/squiggle-lang": "^0.2.8",
"@react-hook/size": "^2.1.2",
"lodash": "^4.17.21",
"react": "^18.1.0",
"react-ace": "^10.1.0",
"react-dom": "^18.1.0",
"react-hook-form": "^7.31.2",
"react-use": "^17.4.0",
"react-vega": "^7.5.1",
"styled-components": "^5.3.5",
"vega": "^5.22.1",
"vega-embed": "^6.20.6",
"vega-lite": "^5.2.0"
"vega-lite": "^5.2.0",
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.17.12",

View File

@ -1,40 +1,9 @@
import * as React from "react";
import _ from "lodash";
import type { Spec } from "vega";
import {
Distribution,
result,
lambdaValue,
environment,
runForeign,
squiggleExpression,
errorValue,
errorValueToString,
} from "@quri/squiggle-lang";
import { createClassFromSpec } from "react-vega";
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
import { DistributionChart } from "./DistributionChart";
import { NumberShower } from "./NumberShower";
import { lambdaValue, environment, runForeign } from "@quri/squiggle-lang";
import { FunctionChart1Dist } from "./FunctionChart1Dist";
import { FunctionChart1Number } from "./FunctionChart1Number";
import { ErrorBox } from "./ErrorBox";
let SquigglePercentilesChart = createClassFromSpec({
spec: percentilesSpec as Spec,
});
const _rangeByCount = (start: number, stop: number, count: number) => {
const step = (stop - start) / (count - 1);
const items = _.range(start, stop, step);
const result = items.concat([stop]);
return result;
};
function unwrap<a, b>(x: result<a, b>): a {
if (x.tag === "Ok") {
return x.value;
} else {
throw Error("FAILURE TO UNWRAP");
}
}
export type FunctionChartSettings = {
start: number;
stop: number;
@ -45,167 +14,61 @@ interface FunctionChartProps {
fn: lambdaValue;
chartSettings: FunctionChartSettings;
environment: environment;
height: number;
}
type percentiles = {
x: number;
p1: number;
p5: number;
p10: number;
p20: number;
p30: number;
p40: number;
p50: number;
p60: number;
p70: number;
p80: number;
p90: number;
p95: number;
p99: number;
}[];
type errors = _.Dictionary<
{
x: number;
value: string;
}[]
>;
type point = { x: number; value: result<Distribution, string> };
let getPercentiles = ({ chartSettings, fn, environment }) => {
let chartPointsToRender = _rangeByCount(
chartSettings.start,
chartSettings.stop,
chartSettings.count
);
let chartPointsData: point[] = chartPointsToRender.map((x) => {
let result = runForeign(fn, [x], environment);
if (result.tag === "Ok") {
if (result.value.tag == "distribution") {
return { x, value: { tag: "Ok", value: result.value.value } };
} else {
return {
x,
value: {
tag: "Error",
value:
"Cannot currently render functions that don't return distributions",
},
};
}
} else {
return {
x,
value: { tag: "Error", value: errorValueToString(result.value) },
};
}
});
let initialPartition: [
{ x: number; value: Distribution }[],
{ x: number; value: string }[]
] = [[], []];
let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
if (current.value.tag === "Ok") {
acc[0].push({ x: current.x, value: current.value.value });
} else {
acc[1].push({ x: current.x, value: current.value.value });
}
return acc;
}, initialPartition);
let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
let percentiles: percentiles = functionImage.map(({ x, value }) => {
// We convert it to to a pointSet distribution first, so that in case its a sample set
// distribution, it doesn't internally convert it to a pointSet distribution for every
// single inv() call.
let toPointSet: Distribution = unwrap(value.toPointSet());
return {
x: x,
p1: unwrap(toPointSet.inv(0.01)),
p5: unwrap(toPointSet.inv(0.05)),
p10: unwrap(toPointSet.inv(0.1)),
p20: unwrap(toPointSet.inv(0.2)),
p30: unwrap(toPointSet.inv(0.3)),
p40: unwrap(toPointSet.inv(0.4)),
p50: unwrap(toPointSet.inv(0.5)),
p60: unwrap(toPointSet.inv(0.6)),
p70: unwrap(toPointSet.inv(0.7)),
p80: unwrap(toPointSet.inv(0.8)),
p90: unwrap(toPointSet.inv(0.9)),
p95: unwrap(toPointSet.inv(0.95)),
p99: unwrap(toPointSet.inv(0.99)),
};
});
return { percentiles, errors: groupedErrors };
};
export const FunctionChart: React.FC<FunctionChartProps> = ({
fn,
chartSettings,
environment,
height,
}: FunctionChartProps) => {
let [mouseOverlay, setMouseOverlay] = React.useState(0);
function handleHover(_name: string, value: unknown) {
setMouseOverlay(value as number);
}
function handleOut() {
setMouseOverlay(NaN);
}
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay
? runForeign(fn, [mouseOverlay], environment)
: {
tag: "Error",
value: {
tag: "REExpectedType",
value: "Hover x-coordinate returned NaN. Expected a number.",
},
};
let showChart =
mouseItem.tag === "Ok" && mouseItem.value.tag == "distribution" ? (
<DistributionChart
distribution={mouseItem.value.value}
width={400}
height={140}
showSummary={false}
/>
) : (
<></>
);
let result1 = runForeign(fn, [chartSettings.start], environment);
let result2 = runForeign(fn, [chartSettings.stop], environment);
let getValidResult = () => {
if (result1.tag === "Ok") {
return result1;
} else if (result2.tag === "Ok") {
return result2;
} else {
return result1;
}
};
let validResult = getValidResult();
let resultType = validResult.tag === "Ok" ? validResult.value.tag : "Error";
let getPercentilesMemoized = React.useMemo(
() => getPercentiles({ chartSettings, fn, environment }),
[environment, fn]
);
return (
<>
<SquigglePercentilesChart
data={{ facet: getPercentilesMemoized.percentiles }}
actions={false}
signalListeners={signalListeners}
/>
{showChart}
{_.entries(getPercentilesMemoized.errors).map(
([errorName, errorPoints]) => (
<ErrorBox key={errorName} heading={errorName}>
Values:{" "}
{errorPoints
.map((r, i) => <NumberShower key={i} number={r.x} />)
.reduce((a, b) => (
<>
{a}, {b}
</>
))}
let component = () => {
switch (resultType) {
case "distribution":
return (
<FunctionChart1Dist
fn={fn}
chartSettings={chartSettings}
environment={environment}
height={height}
/>
);
case "number":
return (
<FunctionChart1Number
fn={fn}
chartSettings={chartSettings}
environment={environment}
height={height}
/>
);
case "Error":
return (
<ErrorBox heading="Error">The function failed to be run</ErrorBox>
);
default:
return (
<ErrorBox heading="No Viewer">
There is no function visualization for this type of function
</ErrorBox>
)
)}
</>
);
);
}
};
return component();
};

View File

@ -0,0 +1,214 @@
import * as React from "react";
import _ from "lodash";
import type { Spec } from "vega";
import {
Distribution,
result,
lambdaValue,
environment,
runForeign,
squiggleExpression,
errorValue,
errorValueToString,
} from "@quri/squiggle-lang";
import { createClassFromSpec } from "react-vega";
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
import { DistributionChart } from "./DistributionChart";
import { NumberShower } from "./NumberShower";
import { ErrorBox } from "./ErrorBox";
let SquigglePercentilesChart = createClassFromSpec({
spec: percentilesSpec as Spec,
});
const _rangeByCount = (start: number, stop: number, count: number) => {
const step = (stop - start) / (count - 1);
const items = _.range(start, stop, step);
const result = items.concat([stop]);
return result;
};
function unwrap<a, b>(x: result<a, b>): a {
if (x.tag === "Ok") {
return x.value;
} else {
throw Error("FAILURE TO UNWRAP");
}
}
export type FunctionChartSettings = {
start: number;
stop: number;
count: number;
};
interface FunctionChart1DistProps {
fn: lambdaValue;
chartSettings: FunctionChartSettings;
environment: environment;
height: number;
}
type percentiles = {
x: number;
p1: number;
p5: number;
p10: number;
p20: number;
p30: number;
p40: number;
p50: number;
p60: number;
p70: number;
p80: number;
p90: number;
p95: number;
p99: number;
}[];
type errors = _.Dictionary<
{
x: number;
value: string;
}[]
>;
type point = { x: number; value: result<Distribution, string> };
let getPercentiles = ({ chartSettings, fn, environment }) => {
let chartPointsToRender = _rangeByCount(
chartSettings.start,
chartSettings.stop,
chartSettings.count
);
let chartPointsData: point[] = chartPointsToRender.map((x) => {
let result = runForeign(fn, [x], environment);
if (result.tag === "Ok") {
if (result.value.tag == "distribution") {
return { x, value: { tag: "Ok", value: result.value.value } };
} else {
return {
x,
value: {
tag: "Error",
value:
"Cannot currently render functions that don't return distributions",
},
};
}
} else {
return {
x,
value: { tag: "Error", value: errorValueToString(result.value) },
};
}
});
let initialPartition: [
{ x: number; value: Distribution }[],
{ x: number; value: string }[]
] = [[], []];
let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
if (current.value.tag === "Ok") {
acc[0].push({ x: current.x, value: current.value.value });
} else {
acc[1].push({ x: current.x, value: current.value.value });
}
return acc;
}, initialPartition);
let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
let percentiles: percentiles = functionImage.map(({ x, value }) => {
// We convert it to to a pointSet distribution first, so that in case its a sample set
// distribution, it doesn't internally convert it to a pointSet distribution for every
// single inv() call.
let toPointSet: Distribution = unwrap(value.toPointSet());
return {
x: x,
p1: unwrap(toPointSet.inv(0.01)),
p5: unwrap(toPointSet.inv(0.05)),
p10: unwrap(toPointSet.inv(0.1)),
p20: unwrap(toPointSet.inv(0.2)),
p30: unwrap(toPointSet.inv(0.3)),
p40: unwrap(toPointSet.inv(0.4)),
p50: unwrap(toPointSet.inv(0.5)),
p60: unwrap(toPointSet.inv(0.6)),
p70: unwrap(toPointSet.inv(0.7)),
p80: unwrap(toPointSet.inv(0.8)),
p90: unwrap(toPointSet.inv(0.9)),
p95: unwrap(toPointSet.inv(0.95)),
p99: unwrap(toPointSet.inv(0.99)),
};
});
return { percentiles, errors: groupedErrors };
};
export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
fn,
chartSettings,
environment,
height,
}: FunctionChart1DistProps) => {
let [mouseOverlay, setMouseOverlay] = React.useState(0);
function handleHover(_name: string, value: unknown) {
setMouseOverlay(value as number);
}
function handleOut() {
setMouseOverlay(NaN);
}
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay
? runForeign(fn, [mouseOverlay], environment)
: {
tag: "Error",
value: {
tag: "REExpectedType",
value: "Hover x-coordinate returned NaN. Expected a number.",
},
};
let showChart =
mouseItem.tag === "Ok" && mouseItem.value.tag == "distribution" ? (
<DistributionChart
distribution={mouseItem.value.value}
width={400}
height={50}
showSummary={false}
/>
) : (
<></>
);
let getPercentilesMemoized = React.useMemo(
() => getPercentiles({ chartSettings, fn, environment }),
[environment, fn]
);
return (
<>
<SquigglePercentilesChart
data={{ facet: getPercentilesMemoized.percentiles }}
height={height}
actions={false}
signalListeners={signalListeners}
/>
{showChart}
{_.entries(getPercentilesMemoized.errors).map(
([errorName, errorPoints]) => (
<ErrorBox key={errorName} heading={errorName}>
Values:{" "}
{errorPoints
.map((r, i) => <NumberShower key={i} number={r.x} />)
.reduce((a, b) => (
<>
{a}, {b}
</>
))}
</ErrorBox>
)
)}
</>
);
};

View File

@ -0,0 +1,119 @@
import * as React from "react";
import _ from "lodash";
import type { Spec } from "vega";
import {
result,
lambdaValue,
environment,
runForeign,
errorValueToString,
} from "@quri/squiggle-lang";
import { createClassFromSpec } from "react-vega";
import * as lineChartSpec from "../vega-specs/spec-line-chart.json";
import { ErrorBox } from "./ErrorBox";
let SquiggleLineChart = createClassFromSpec({
spec: lineChartSpec as Spec,
});
const _rangeByCount = (start: number, stop: number, count: number) => {
const step = (stop - start) / (count - 1);
const items = _.range(start, stop, step);
const result = items.concat([stop]);
return result;
};
export type FunctionChartSettings = {
start: number;
stop: number;
count: number;
};
interface FunctionChart1NumberProps {
fn: lambdaValue;
chartSettings: FunctionChartSettings;
environment: environment;
height: number;
}
type point = { x: number; value: result<number, string> };
let getFunctionImage = ({ chartSettings, fn, environment }) => {
//We adjust the count, because the count is made for distributions, which are much more expensive to estimate
let adjustedCount = chartSettings.count * 20;
let chartPointsToRender = _rangeByCount(
chartSettings.start,
chartSettings.stop,
adjustedCount
);
let chartPointsData: point[] = chartPointsToRender.map((x) => {
let result = runForeign(fn, [x], environment);
if (result.tag === "Ok") {
if (result.value.tag == "number") {
return { x, value: { tag: "Ok", value: result.value.value } };
} else {
return {
x,
value: {
tag: "Error",
value: "This component expected number outputs",
},
};
}
} else {
return {
x,
value: { tag: "Error", value: errorValueToString(result.value) },
};
}
});
let initialPartition: [
{ x: number; value: number }[],
{ x: number; value: string }[]
] = [[], []];
let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
if (current.value.tag === "Ok") {
acc[0].push({ x: current.x, value: current.value.value });
} else {
acc[1].push({ x: current.x, value: current.value.value });
}
return acc;
}, initialPartition);
return { errors, functionImage };
};
export const FunctionChart1Number: React.FC<FunctionChart1NumberProps> = ({
fn,
chartSettings,
environment,
height,
}: FunctionChart1NumberProps) => {
let getFunctionImageMemoized = React.useMemo(
() => getFunctionImage({ chartSettings, fn, environment }),
[environment, fn]
);
let data = getFunctionImageMemoized.functionImage.map(({ x, value }) => ({
x,
y: value,
}));
return (
<>
<SquiggleLineChart
data={{ facet: data }}
height={height}
actions={false}
/>
{getFunctionImageMemoized.errors.map(({ x, value }) => (
<ErrorBox key={x} heading={value}>
Error at point ${x}
</ErrorBox>
))}
</>
);
};

View File

@ -0,0 +1,51 @@
import _ from "lodash";
import React, { FC } from "react";
import AceEditor from "react-ace";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-github";
interface CodeEditorProps {
value: string;
onChange: (value: string) => void;
oneLine?: boolean;
width?: number;
height: number;
showGutter?: boolean;
}
export let JsonEditor: FC<CodeEditorProps> = ({
value,
onChange,
oneLine = false,
showGutter = false,
height,
}: CodeEditorProps) => {
let lineCount = value.split("\n").length;
let id = _.uniqueId();
return (
<AceEditor
value={value}
mode="json"
theme="github"
width={"100%"}
height={String(height) + "px"}
minLines={oneLine ? lineCount : undefined}
maxLines={oneLine ? lineCount : undefined}
showGutter={showGutter}
highlightActiveLine={false}
showPrintMargin={false}
onChange={onChange}
name={id}
editorProps={{
$blockScrolling: true,
}}
setOptions={{
enableBasicAutocompletion: false,
enableLiveAutocompletion: false,
}}
/>
);
};
export default JsonEditor;

View File

@ -172,7 +172,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
<SquiggleItem
expression={r}
width={width !== undefined ? width - 20 : width}
height={50}
height={height / 3}
showTypes={showTypes}
showSummary={showSummary}
showControls={showControls}
@ -204,14 +204,17 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
}
case "lambda":
return (
<FunctionChart
fn={expression.value}
chartSettings={chartSettings}
environment={{
sampleCount: environment.sampleCount / 10,
xyPointLength: environment.xyPointLength / 10,
}}
/>
<VariableBox heading="Function" showTypes={showTypes}>
<FunctionChart
fn={expression.value}
chartSettings={chartSettings}
height={height}
environment={{
sampleCount: environment.sampleCount / 10,
xyPointLength: environment.xyPointLength / 10,
}}
/>
</VariableBox>
);
}
};
@ -254,7 +257,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
squiggleString = "",
environment,
onChange = () => {},
height = 60,
height = 200,
bindings = defaultBindings,
jsImports = defaultImports,
showSummary = false,

View File

@ -3,12 +3,12 @@ import React, { FC, ReactElement, useState } from "react";
import ReactDOM from "react-dom";
import { SquiggleChart } from "./SquiggleChart";
import CodeEditor from "./CodeEditor";
import JsonEditor from "./JsonEditor";
import styled from "styled-components";
import {
defaultBindings,
environment,
defaultImports,
} from "@quri/squiggle-lang";
import { useForm, useWatch } from "react-hook-form";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import { defaultBindings, environment } from "@quri/squiggle-lang";
interface FieldFloatProps {
label: string;
@ -68,9 +68,14 @@ const Display = styled.div<TitleProps>`
max-height: ${(props) => props.maxHeight}px;
`;
const Row = styled.div`
interface RowProps {
readonly leftPercentage: number;
}
const Row = styled.div<RowProps>`
display: grid;
grid-template-columns: 50% 50%;
grid-template-columns: ${(p) => p.leftPercentage}% ${(p) =>
100 - p.leftPercentage}%;
`;
const Col = styled.div``;
@ -87,17 +92,64 @@ interface PlaygroundProps {
showSummary?: boolean;
}
const schema = yup
.object()
.shape({
sampleCount: yup
.number()
.required()
.positive()
.integer()
.default(1000)
.min(10)
.max(1000000),
xyPointLength: yup
.number()
.required()
.positive()
.integer()
.default(1000)
.min(10)
.max(10000),
chartHeight: yup.number().required().positive().integer().default(350),
leftSizePercent: yup
.number()
.required()
.positive()
.integer()
.min(10)
.max(100)
.default(50),
showTypes: yup.boolean(),
showControls: yup.boolean(),
showSummary: yup.boolean(),
showSettingsPage: yup.boolean().default(false),
})
.required();
type InputProps = {
label: string;
children: ReactElement;
};
const InputItem: React.FC<InputProps> = ({ label, children }) => (
<div>
<label>{label}</label>
{children}
</div>
);
let SquigglePlayground: FC<PlaygroundProps> = ({
initialSquiggleString = "",
height = 300,
height = 500,
showTypes = false,
showControls = false,
showSummary = false,
}: PlaygroundProps) => {
let [squiggleString, setSquiggleString] = useState(initialSquiggleString);
let [sampleCount, setSampleCount] = useState(1000);
let [outputXYPoints, setOutputXYPoints] = useState(1000);
let [pointDistLength, setPointDistLength] = useState(1000);
let [importString, setImportString] = useState("{}");
let [imports, setImports] = useState({});
let [importsAreValid, setImportsAreValid] = useState(true);
let [diagramStart, setDiagramStart] = useState(0);
let [diagramStop, setDiagramStop] = useState(10);
let [diagramCount, setDiagramCount] = useState(20);
@ -106,21 +158,95 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
stop: diagramStop,
count: diagramCount,
};
const {
register,
formState: { errors },
control,
} = useForm({
resolver: yupResolver(schema),
defaultValues: {
sampleCount: 1000,
xyPointLength: 1000,
chartHeight: 150,
showTypes: showTypes,
showControls: showControls,
showSummary: showSummary,
leftSizePercent: 50,
showSettingsPage: false,
},
});
const vars = useWatch({
control,
});
let env: environment = {
sampleCount: sampleCount,
xyPointLength: outputXYPoints,
sampleCount: Number(vars.sampleCount),
xyPointLength: Number(vars.xyPointLength),
};
let getChangeJson = (r: string) => {
setImportString(r);
try {
setImports(JSON.parse(r));
setImportsAreValid(true);
} catch (e) {
setImportsAreValid(false);
}
};
return (
<ShowBox height={height}>
<Row>
<input type="checkbox" {...register("showSettingsPage")} />
<Row leftPercentage={vars.leftSizePercent || 50}>
<Col>
<CodeEditor
value={squiggleString}
onChange={setSquiggleString}
oneLine={false}
showGutter={true}
height={height - 3}
/>
{vars.showSettingsPage ? (
<>
<InputItem label="Sample Count">
<input type="number" {...register("sampleCount")} />
</InputItem>
<InputItem label="XYPointLength Count">
<input type="number" {...register("xyPointLength")} />
</InputItem>
<InputItem label="Chart Height (in Pixels)">
<input type="number" {...register("chartHeight")} />
</InputItem>
<InputItem label="Show Types">
<input type="checkbox" {...register("showTypes")} />
</InputItem>
<InputItem label="Show Controls">
<input type="checkbox" {...register("showControls")} />
</InputItem>
<InputItem label="Show Summary Statistics">
<input type="checkbox" {...register("showSummary")} />
</InputItem>
<InputItem label="Editor Width">
<input
type="range"
min="1"
max="100"
className="slider"
{...register("leftSizePercent")}
/>
</InputItem>
<InputItem label="Json Editor for imports">
<>
<JsonEditor
value={importString}
onChange={getChangeJson}
oneLine={false}
showGutter={true}
height={100}
/>
{importsAreValid ? "Valid" : "Invalid"}
</>
</InputItem>
</>
) : (
<CodeEditor
value={squiggleString}
onChange={setSquiggleString}
oneLine={false}
showGutter={true}
height={height - 3}
/>
)}
</Col>
<Col>
<Display maxHeight={height - 3}>
@ -128,12 +254,12 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
squiggleString={squiggleString}
environment={env}
chartSettings={chartSettings}
height={150}
showTypes={showTypes}
showControls={showControls}
height={vars.chartHeight}
showTypes={vars.showTypes}
showControls={vars.showControls}
bindings={defaultBindings}
jsImports={defaultImports}
showSummary={showSummary}
jsImports={imports}
showSummary={vars.showSummary}
/>
</Display>
</Col>

View File

@ -153,11 +153,11 @@ to allow large and small numbers being printed cleanly.
</Story>
</Canvas>
## Functions
## Functions (Distribution Output)
<Canvas>
<Story
name="Function"
name="Function to Distribution"
args={{
squiggleString: "foo(t) = normal(t,2)*normal(5,3); foo",
width,
@ -167,6 +167,20 @@ to allow large and small numbers being printed cleanly.
</Story>
</Canvas>
## Functions (Number Output)
<Canvas>
<Story
name="Function to Number"
args={{
squiggleString: "foo(t) = t^2; foo",
width,
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Records
<Canvas>

View File

@ -16,7 +16,7 @@ including sampling settings, in squiggle.
name="Normal"
args={{
initialSquiggleString: "normal(5,2)",
height: 500,
height: 800,
}}
>
{Template.bind({})}

View File

@ -82,6 +82,9 @@
"y2": {
"scale": "yscale",
"value": 0
},
"fill": {
"value": "#2f65a7"
}
}
}

View File

@ -0,0 +1,88 @@
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 500,
"height": 200,
"padding": 5,
"data": [
{
"name": "facet",
"values": [],
"format": {
"type": "json",
"parse": {
"timestamp": "date"
}
}
}
],
"scales": [
{
"name": "x",
"type": "linear",
"nice": true,
"domain": {
"data": "facet",
"field": "x"
},
"range": "width"
},
{
"name": "y",
"type": "linear",
"range": "height",
"nice": true,
"zero": true,
"domain": {
"data": "facet",
"field": "y"
}
}
],
"signals": [
{
"name": "mousemove",
"on": [{ "events": "mousemove", "update": "invert('x', x())" }]
},
{
"name": "mouseout",
"on": [{ "events": "mouseout", "update": "invert('x', x())" }]
}
],
"axes": [
{
"orient": "bottom",
"scale": "x",
"grid": false,
"labelColor": "#727d93",
"tickColor": "#fff",
"tickOpacity": 0.0,
"domainColor": "#727d93",
"domainOpacity": 0.1,
"tickCount": 5
},
{
"orient": "left",
"scale": "y",
"grid": false,
"labelColor": "#727d93",
"tickColor": "#fff",
"tickOpacity": 0.0,
"domainColor": "#727d93",
"domainOpacity": 0.1,
"tickCount": 5
}
],
"marks": [
{
"type": "line",
"from": { "data": "facet" },
"encode": {
"enter": {
"x": { "scale": "x", "field": "x" },
"y": { "scale": "y", "field": "y" },
"strokeWidth": { "value": 2 }
}
}
}
]
}

View File

@ -20,7 +20,8 @@
},
"files": [
"src/vega-specs/spec-distributions.json",
"src/vega-specs/spec-percentiles.json"
"src/vega-specs/spec-percentiles.json",
"src/vega-specs/spec-line-chart.json"
],
"target": "ES6",
"include": ["src/**/*", "src/*"],

View File

@ -1245,6 +1245,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.15.4":
version "7.18.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4"
integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.12.7", "@babel/template@^7.16.7", "@babel/template@^7.3.3":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
@ -1839,6 +1846,11 @@
dependencies:
"@hapi/hoek" "^9.0.0"
"@hookform/resolvers@^2.8.10":
version "2.8.10"
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-2.8.10.tgz#b66d7a7848b1b1dd5b976a73fff36bb366666e7d"
integrity sha512-DDFtNlugsbwAhCJHYp3NcN5LvJrwSsCLPi41Wo5O8UAIbUFnBfY/jW+zKnlX57BZ4jE0j/g6R9rB3JlO89ad0g==
"@humanwhocodes/config-array@^0.9.2":
version "0.9.5"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7"
@ -4163,7 +4175,7 @@
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.11.1.tgz#34de04477dcf79e2ef6c8d23b41a3d81f9ebeaf5"
integrity sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==
"@types/lodash@^4.14.167", "@types/lodash@^4.14.182":
"@types/lodash@^4.14.167", "@types/lodash@^4.14.175", "@types/lodash@^4.14.182":
version "4.14.182"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
@ -4298,10 +4310,10 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.0.1", "@types/react@^18.0.9":
version "18.0.9"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.9.tgz#d6712a38bd6cd83469603e7359511126f122e878"
integrity sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==
"@types/react@*", "@types/react@17.0.43", "@types/react@^18.0.9":
version "17.0.43"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55"
integrity sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
@ -11601,6 +11613,11 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash.assignin@^4.0.9:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2"
@ -12290,6 +12307,11 @@ nano-css@^5.3.1:
stacktrace-js "^2.0.2"
stylis "^4.0.6"
nanoclone@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4"
integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==
nanoid@^3.3.1, nanoid@^3.3.3:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
@ -13998,6 +14020,11 @@ prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
property-expr@^2.0.4:
version "2.0.5"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4"
integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==
property-information@^5.0.0, property-information@^5.3.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69"
@ -14328,6 +14355,11 @@ react-helmet-async@*, react-helmet-async@^1.3.0:
react-fast-compare "^3.2.0"
shallowequal "^1.1.0"
react-hook-form@^7.31.2:
version "7.31.2"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.31.2.tgz#efb7ac469810954488b7cf40be4e5017122c6e5e"
integrity sha512-oPudn3YuyzWg//IsT9z2cMEjWocAgHWX/bmueDT8cmsYQnGY5h7/njjvMDfLVv3mbdhYBjslTRnII2MIT7eNCA==
react-inspector@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8"
@ -14525,7 +14557,7 @@ react-vega@^7.5.1:
prop-types "^15.8.1"
vega-embed "^6.5.1"
react@^18.0.0, react@^18.1.0:
react@^18.1.0:
version "18.1.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890"
integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==
@ -16559,6 +16591,11 @@ topojson-client@^3.1.0:
dependencies:
commander "2"
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
totalist@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
@ -18440,6 +18477,19 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
yup@^0.32.11:
version "0.32.11"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5"
integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==
dependencies:
"@babel/runtime" "^7.15.4"
"@types/lodash" "^4.14.175"
lodash "^4.17.21"
lodash-es "^4.17.21"
nanoclone "^0.2.1"
property-expr "^2.0.4"
toposort "^2.0.2"
zwitch@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"