Simple line chart for Functions
This commit is contained in:
parent
c9a54d4c14
commit
f393cfda9f
|
@ -7,34 +7,11 @@ import {
|
||||||
lambdaValue,
|
lambdaValue,
|
||||||
environment,
|
environment,
|
||||||
runForeign,
|
runForeign,
|
||||||
squiggleExpression,
|
|
||||||
errorValue,
|
|
||||||
errorValueToString,
|
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { createClassFromSpec } from "react-vega";
|
import { FunctionChart1Dist } from "./FunctionChart1Dist";
|
||||||
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
import { FunctionChart1Number } from "./FunctionChart1Number";
|
||||||
import { DistributionChart } from "./DistributionChart";
|
|
||||||
import { NumberShower } from "./NumberShower";
|
|
||||||
import { ErrorBox } from "./ErrorBox";
|
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 = {
|
export type FunctionChartSettings = {
|
||||||
start: number;
|
start: number;
|
||||||
stop: number;
|
stop: number;
|
||||||
|
@ -48,167 +25,47 @@ interface FunctionChartProps {
|
||||||
height: number;
|
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> = ({
|
export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
fn,
|
fn,
|
||||||
chartSettings,
|
chartSettings,
|
||||||
environment,
|
environment,
|
||||||
height
|
height,
|
||||||
}: FunctionChartProps) => {
|
}: FunctionChartProps) => {
|
||||||
let [mouseOverlay, setMouseOverlay] = React.useState(0);
|
let result = runForeign(fn, [chartSettings.start], environment);
|
||||||
function handleHover(_name: string, value: unknown) {
|
let resultType = result.tag === "Ok" ? result.value.tag : "Error";
|
||||||
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(
|
let comp = () => {
|
||||||
() => getPercentiles({ chartSettings, fn, environment }),
|
switch (resultType) {
|
||||||
[environment, fn]
|
case "distribution":
|
||||||
);
|
return (
|
||||||
|
<FunctionChart1Dist
|
||||||
return (
|
fn={fn}
|
||||||
<>
|
chartSettings={chartSettings}
|
||||||
<SquigglePercentilesChart
|
environment={environment}
|
||||||
data={{ facet: getPercentilesMemoized.percentiles }}
|
height={height}
|
||||||
height={height}
|
/>
|
||||||
actions={false}
|
);
|
||||||
signalListeners={signalListeners}
|
case "number":
|
||||||
/>
|
return (
|
||||||
{showChart}
|
<FunctionChart1Number
|
||||||
{_.entries(getPercentilesMemoized.errors).map(
|
fn={fn}
|
||||||
([errorName, errorPoints]) => (
|
chartSettings={chartSettings}
|
||||||
<ErrorBox key={errorName} heading={errorName}>
|
environment={environment}
|
||||||
Values:{" "}
|
height={height}
|
||||||
{errorPoints
|
/>
|
||||||
.map((r, i) => <NumberShower key={i} number={r.x} />)
|
);
|
||||||
.reduce((a, b) => (
|
case "Error":
|
||||||
<>
|
return (
|
||||||
{a}, {b}
|
<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>
|
</ErrorBox>
|
||||||
)
|
);
|
||||||
)}
|
}
|
||||||
</>
|
};
|
||||||
);
|
|
||||||
|
return comp();
|
||||||
};
|
};
|
||||||
|
|
214
packages/components/src/components/FunctionChart1Dist.tsx
Normal file
214
packages/components/src/components/FunctionChart1Dist.tsx
Normal 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 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 FunctionChart1Dist: 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={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>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
145
packages/components/src/components/FunctionChart1Number.tsx
Normal file
145
packages/components/src/components/FunctionChart1Number.tsx
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
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 lineChartSpec from "../vega-specs/spec-line-chart.json";
|
||||||
|
import { DistributionChart } from "./DistributionChart";
|
||||||
|
import { NumberShower } from "./NumberShower";
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 FunctionChartProps {
|
||||||
|
fn: lambdaValue;
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
environment: environment;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type point = { x: number; value: result<number, string> };
|
||||||
|
|
||||||
|
let getFunctionImage = ({ 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 == "number") {
|
||||||
|
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: 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<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 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}
|
||||||
|
signalListeners={signalListeners}
|
||||||
|
/>
|
||||||
|
{getFunctionImageMemoized.errors.map(({ x, value }) => (
|
||||||
|
<ErrorBox key={x} heading={value}>
|
||||||
|
Error at point ${x}
|
||||||
|
</ErrorBox>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -72,9 +72,13 @@ const Display = styled.div<TitleProps>`
|
||||||
max-height: ${(props) => props.maxHeight}px;
|
max-height: ${(props) => props.maxHeight}px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Row = styled.div`
|
interface RowProps {
|
||||||
|
readonly leftPercentage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Row = styled.div<RowProps>`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 50% 50%;
|
grid-template-columns: ${(p) => p.leftPercentage}% ${(p) => 100 - p.leftPercentage}%;
|
||||||
`;
|
`;
|
||||||
const Col = styled.div``;
|
const Col = styled.div``;
|
||||||
|
|
||||||
|
@ -111,6 +115,7 @@ const schema = yup
|
||||||
.min(10)
|
.min(10)
|
||||||
.max(10000),
|
.max(10000),
|
||||||
chartHeight: yup.number().required().positive().integer().default(350),
|
chartHeight: yup.number().required().positive().integer().default(350),
|
||||||
|
leftSize: yup.number().required().positive().integer().min(10).max(100).default(50),
|
||||||
showTypes: yup.boolean(),
|
showTypes: yup.boolean(),
|
||||||
showControls: yup.boolean(),
|
showControls: yup.boolean(),
|
||||||
showSummary: yup.boolean(),
|
showSummary: yup.boolean(),
|
||||||
|
@ -152,14 +157,15 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
showTypes: showTypes,
|
showTypes: showTypes,
|
||||||
showControls: showControls,
|
showControls: showControls,
|
||||||
showSummary: showSummary,
|
showSummary: showSummary,
|
||||||
|
leftSize: 50,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const foo = useWatch({
|
const vars = useWatch({
|
||||||
control,
|
control,
|
||||||
});
|
});
|
||||||
let env: environment = {
|
let env: environment = {
|
||||||
sampleCount: Number(foo.sampleCount),
|
sampleCount: Number(vars.sampleCount),
|
||||||
xyPointLength: Number(foo.xyPointLength),
|
xyPointLength: Number(vars.xyPointLength),
|
||||||
};
|
};
|
||||||
let getChangeJson = (r: string) => {
|
let getChangeJson = (r: string) => {
|
||||||
setImportString(r);
|
setImportString(r);
|
||||||
|
@ -169,17 +175,17 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setImportsAreValid(false);
|
setImportsAreValid(false);
|
||||||
}
|
}
|
||||||
("");
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ShowBox height={height}>
|
<ShowBox height={height}>
|
||||||
<input type="number" {...register("sampleCount")} />
|
<input type="number" {...register("sampleCount")} />
|
||||||
<input type="number" {...register("xyPointLength")} />
|
<input type="number" {...register("xyPointLength")} />
|
||||||
<input type="number" {...register("chartHeight")} />
|
<input type="number" {...register("chartHeight")} />
|
||||||
|
<input type="number" {...register("leftSize")} />
|
||||||
<input type="checkbox" {...register("showTypes")} />
|
<input type="checkbox" {...register("showTypes")} />
|
||||||
<input type="checkbox" {...register("showControls")} />
|
<input type="checkbox" {...register("showControls")} />
|
||||||
<input type="checkbox" {...register("showSummary")} />
|
<input type="checkbox" {...register("showSummary")} />
|
||||||
<Row>
|
<Row leftPercentage={vars.leftSize || 50}>
|
||||||
<Col>
|
<Col>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={squiggleString}
|
value={squiggleString}
|
||||||
|
@ -203,12 +209,12 @@ let SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
squiggleString={squiggleString}
|
squiggleString={squiggleString}
|
||||||
environment={env}
|
environment={env}
|
||||||
chartSettings={chartSettings}
|
chartSettings={chartSettings}
|
||||||
height={foo.chartHeight}
|
height={vars.chartHeight}
|
||||||
showTypes={foo.showTypes}
|
showTypes={vars.showTypes}
|
||||||
showControls={foo.showControls}
|
showControls={vars.showControls}
|
||||||
bindings={defaultBindings}
|
bindings={defaultBindings}
|
||||||
jsImports={imports}
|
jsImports={imports}
|
||||||
showSummary={foo.showSummary}
|
showSummary={vars.showSummary}
|
||||||
/>
|
/>
|
||||||
</Display>
|
</Display>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
88
packages/components/src/vega-specs/spec-line-chart.json
Normal file
88
packages/components/src/vega-specs/spec-line-chart.json
Normal 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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -20,7 +20,8 @@
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src/vega-specs/spec-distributions.json",
|
"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",
|
"target": "ES6",
|
||||||
"include": ["src/**/*", "src/*"],
|
"include": ["src/**/*", "src/*"],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user