Simple implementation of function hover working

This commit is contained in:
Ozzie Gooen 2022-04-07 09:53:45 -04:00
parent 624e788094
commit f63c775cb6
3 changed files with 224 additions and 154 deletions

View File

@ -13,6 +13,7 @@ import * as chartSpecification from "./spec-distributions.json";
import * as percentilesSpec from "./spec-percentiles.json"; import * as percentilesSpec from "./spec-percentiles.json";
import { NumberShower } from "./NumberShower"; import { NumberShower } from "./NumberShower";
import styled from "styled-components"; import styled from "styled-components";
import { CONTINUOUS_TO_DISCRETE_SCALES } from "vega-lite/build/src/scale";
let SquiggleVegaChart = createClassFromSpec({ let SquiggleVegaChart = createClassFromSpec({
spec: chartSpecification as Spec, spec: chartSpecification as Spec,
@ -65,37 +66,12 @@ const ShowError: React.FC<{ heading: string; children: React.ReactNode }> = ({
); );
}; };
export const SquiggleChart: React.FC<SquiggleChartProps> = ({ export const DistPlusChart: React.FC<{
squiggleString = "", distPlus: DistPlus;
sampleCount = 1000, width: number;
outputXYPoints = 1000, height: number;
kernelWidth, }> = ({ distPlus, width, height }) => {
pointDistLength = 1000, let shape = distPlus.pointSetDist;
diagramStart = 0,
diagramStop = 10,
diagramCount = 20,
environment = [],
onEnvChange = () => {},
width = 500,
height = 60,
}: SquiggleChartProps) => {
let samplingInputs: SamplingInputs = {
sampleCount: sampleCount,
outputXYPoints: outputXYPoints,
kernelWidth: kernelWidth,
pointDistLength: pointDistLength,
};
let result = run(squiggleString, samplingInputs, environment);
if (result.tag === "Ok") {
let environment = result.value.environment;
let exports = result.value.exports;
onEnvChange(environment);
let chartResults = exports.map((chartResult: exportDistribution) => {
if (chartResult["NAME"] === "Float") {
return <NumberShower precision={3} number={chartResult["VAL"]} />;
} else if (chartResult["NAME"] === "DistPlus") {
let shape = chartResult.VAL.pointSetDist;
if (shape.tag === "Continuous") { if (shape.tag === "Continuous") {
let xyShape = shape.value.xyShape; let xyShape = shape.value.xyShape;
let totalY = xyShape.ys.reduce((a, b) => a + b); let totalY = xyShape.ys.reduce((a, b) => a + b);
@ -147,22 +123,21 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
type: "discrete" | "continuous"; type: "discrete" | "continuous";
} }
let markedDisPoints: labeledPoint[] = discretePoints.map( let markedDisPoints: labeledPoint[] = discretePoints.map(([x, y]) => ({
([x, y]) => ({ x: x, y: y, type: "discrete" }) x: x,
); y: y,
let markedConPoints: labeledPoint[] = continuousPoints.map( type: "discrete",
([x, y]) => ({ x: x, y: y, type: "continuous" }) }));
); let markedConPoints: labeledPoint[] = continuousPoints.map(([x, y]) => ({
x: x,
y: y,
type: "continuous",
}));
let sortedPoints = _.sortBy( let sortedPoints = _.sortBy(markedDisPoints.concat(markedConPoints), "x");
markedDisPoints.concat(markedConPoints),
"x"
);
let totalContinuous = 1 - totalDiscrete; let totalContinuous = 1 - totalDiscrete;
let totalY = continuousShape.ys.reduce( let totalY = continuousShape.ys.reduce((a: number, b: number) => a + b);
(a: number, b: number) => a + b
);
let total = 0; let total = 0;
let cdf = sortedPoints.map((point: labeledPoint) => { let cdf = sortedPoints.map((point: labeledPoint) => {
@ -192,9 +167,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
let continuousValues = cdfLabeledPoint.filter( let continuousValues = cdfLabeledPoint.filter(
(x) => x.type === "continuous" (x) => x.type === "continuous"
); );
let discreteValues = cdfLabeledPoint.filter( let discreteValues = cdfLabeledPoint.filter((x) => x.type === "discrete");
(x) => x.type === "discrete"
);
return ( return (
<SquiggleVegaChart <SquiggleVegaChart
@ -203,21 +176,50 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
/> />
); );
} }
} else if (chartResult.NAME === "Function") { };
// We are looking at a function. In this case, we draw a Percentiles chart
let start = diagramStart;
let stop = diagramStop;
let count = diagramCount;
let step = (stop - start) / count;
let data = _.range(start, stop, step).map((x) => {
if (chartResult.NAME === "Function") {
let result = chartResult.VAL(x);
if (result.tag === "Ok") {
let percentileArray = [
0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95,
0.99,
];
const _rangeByCount = (start, stop, count) => {
const step = (stop - start) / (count - 1);
const items = _.range(start, stop, step);
const result = items.concat([stop]);
return result;
};
type distPlusFn = (
a: number
) => { tag: "Ok"; value: DistPlus } | { tag: "Error"; value: string };
// This could really use a line in the location of the signal. I couldn't get it to work.
// https://vega.github.io/vega/docs/signals/#handlers
export const FunctionChart: React.FC<{
distPlusFn: distPlusFn;
diagramStart: number;
diagramStop: number;
diagramCount: number;
}> = ({ distPlusFn, diagramStart, diagramStop, diagramCount }) => {
let [mouseOverlay, setMouseOverlay] = React.useState(NaN);
function handleHover(...args) {
setMouseOverlay(args[1]);
}
function handleOut(...args) {
setMouseOverlay(NaN);
}
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
let percentileArray = [
0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99,
];
let mouseItem = distPlusFn(mouseOverlay);
let showChart =
mouseItem.tag === "Ok" ? (
<DistPlusChart distPlus={mouseItem.value} width={400} height={140} />
) : (
<></>
);
let data = _rangeByCount(diagramStart, diagramStop, diagramCount)
.map((x) => {
let result = distPlusFn(x);
if (result.tag === "Ok") {
let percentiles = getPercentiles(percentileArray, result.value); let percentiles = getPercentiles(percentileArray, result.value);
return { return {
x: x, x: x,
@ -235,14 +237,68 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
p95: percentiles[11], p95: percentiles[11],
p99: percentiles[12], p99: percentiles[12],
}; };
} } else {
console.log("Error", x, result);
return null; return null;
} }
}); })
.filter((x) => x !== null);
return ( return (
<>
<SquigglePercentilesChart <SquigglePercentilesChart
data={{ facet: data.filter((x) => x !== null) }} data={{ facet: data }}
actions={false} actions={false}
signalListeners={signalListeners}
/>
{showChart}
</>
);
};
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
squiggleString = "",
sampleCount = 1000,
outputXYPoints = 1000,
kernelWidth,
pointDistLength = 1000,
diagramStart = 0,
diagramStop = 10,
diagramCount = 20,
environment = [],
onEnvChange = () => {},
width = 500,
height = 60,
}: SquiggleChartProps) => {
let samplingInputs: SamplingInputs = {
sampleCount: sampleCount,
outputXYPoints: outputXYPoints,
kernelWidth: kernelWidth,
pointDistLength: pointDistLength,
};
let result = run(squiggleString, samplingInputs, environment);
if (result.tag === "Ok") {
let environment = result.value.environment;
let exports = result.value.exports;
onEnvChange(environment);
let chartResults = exports.map((chartResult: exportDistribution) => {
if (chartResult["NAME"] === "Float") {
return <NumberShower precision={3} number={chartResult["VAL"]} />;
} else if (chartResult["NAME"] === "DistPlus") {
return (
<DistPlusChart
distPlus={chartResult.VAL}
height={height}
width={width}
/>
);
} else if (chartResult.NAME === "Function") {
return (
<FunctionChart
distPlusFn={chartResult.VAL}
diagramStart={diagramStart}
diagramStop={diagramStop}
diagramCount={diagramCount}
/> />
); );
} }
@ -250,11 +306,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
return <>{chartResults}</>; return <>{chartResults}</>;
} else if (result.tag === "Error") { } else if (result.tag === "Error") {
// At this point, we came across an error. What was our error? // At this point, we came across an error. What was our error?
return ( return <ShowError heading={"Parse Error"}>{result.value}</ShowError>;
<ShowError heading={"Parse Error"}>
{result.value}
</ShowError>
);
} }
return <p>{"Invalid Response"}</p>; return <p>{"Invalid Response"}</p>;
}; };

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://vega.github.io/schema/vega/v5.json", "$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 500, "width": 500,
"height": 400, "height": 200,
"padding": 5, "padding": 5,
"data": [ "data": [
{ {
@ -93,6 +93,16 @@
} }
} }
], ],
"signals": [
{
"name": "mousemove",
"on": [{"events": "mousemove", "update": "invert('xscale', x())"}]
},
{
"name": "mouseout",
"on": [{"events": "mouseout", "update": "invert('xscale', x())"}]
}
],
"axes": [ "axes": [
{ {
"orient": "bottom", "orient": "bottom",
@ -118,6 +128,14 @@
} }
], ],
"marks": [ "marks": [
{
"type": "rule",
"encode": {
"update": {
"xscale": {"scale": "xscale", "signal": "mousemove"}
}
}
},
{ {
"type": "area", "type": "area",
"from": { "from": {

View File

@ -83,7 +83,7 @@ The default is show 10 points between 0 and 10.
<Story <Story
name="Function" name="Function"
args={{ args={{
squiggleString: "f(x) = normal(x^2,x^1.8)\nf", squiggleString: "f(x) = normal(x^2,(x+.1)^1.8)\nf",
}} }}
> >
{Template.bind({})} {Template.bind({})}