diff --git a/packages/components/src/SquiggleChart.tsx b/packages/components/src/SquiggleChart.tsx
index e381127c..ce978706 100644
--- a/packages/components/src/SquiggleChart.tsx
+++ b/packages/components/src/SquiggleChart.tsx
@@ -13,6 +13,7 @@ import * as chartSpecification from "./spec-distributions.json";
import * as percentilesSpec from "./spec-percentiles.json";
import { NumberShower } from "./NumberShower";
import styled from "styled-components";
+import { CONTINUOUS_TO_DISCRETE_SCALES } from "vega-lite/build/src/scale";
let SquiggleVegaChart = createClassFromSpec({
spec: chartSpecification as Spec,
@@ -65,6 +66,195 @@ const ShowError: React.FC<{ heading: string; children: React.ReactNode }> = ({
);
};
+export const DistPlusChart: React.FC<{
+ distPlus: DistPlus;
+ width: number;
+ height: number;
+}> = ({ distPlus, width, height }) => {
+ let shape = distPlus.pointSetDist;
+ if (shape.tag === "Continuous") {
+ let xyShape = shape.value.xyShape;
+ let totalY = xyShape.ys.reduce((a, b) => a + b);
+ let total = 0;
+ let cdf = xyShape.ys.map((y) => {
+ total += y;
+ return total / totalY;
+ });
+ let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y]) => ({
+ cdf: (c * 100).toFixed(2) + "%",
+ x: x,
+ y: y,
+ }));
+
+ return (
+
+ );
+ } else if (shape.tag === "Discrete") {
+ let xyShape = shape.value.xyShape;
+ let totalY = xyShape.ys.reduce((a, b) => a + b);
+ let total = 0;
+ let cdf = xyShape.ys.map((y) => {
+ total += y;
+ return total / totalY;
+ });
+ let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y]) => ({
+ cdf: (c * 100).toFixed(2) + "%",
+ x: x,
+ y: y,
+ }));
+
+ return ;
+ } else if (shape.tag === "Mixed") {
+ let discreteShape = shape.value.discrete.xyShape;
+ let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
+
+ let discretePoints = _.zip(discreteShape.xs, discreteShape.ys);
+ let continuousShape = shape.value.continuous.xyShape;
+ let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
+
+ interface labeledPoint {
+ x: number;
+ y: number;
+ type: "discrete" | "continuous";
+ }
+
+ let markedDisPoints: labeledPoint[] = discretePoints.map(([x, y]) => ({
+ x: x,
+ y: y,
+ type: "discrete",
+ }));
+ let markedConPoints: labeledPoint[] = continuousPoints.map(([x, y]) => ({
+ x: x,
+ y: y,
+ type: "continuous",
+ }));
+
+ let sortedPoints = _.sortBy(markedDisPoints.concat(markedConPoints), "x");
+
+ let totalContinuous = 1 - totalDiscrete;
+ let totalY = continuousShape.ys.reduce((a: number, b: number) => a + b);
+
+ let total = 0;
+ let cdf = sortedPoints.map((point: labeledPoint) => {
+ if (point.type === "discrete") {
+ total += point.y;
+ return total;
+ } else if (point.type === "continuous") {
+ total += (point.y / totalY) * totalContinuous;
+ return total;
+ }
+ });
+
+ interface cdfLabeledPoint {
+ cdf: string;
+ x: number;
+ y: number;
+ type: "discrete" | "continuous";
+ }
+ let cdfLabeledPoint: cdfLabeledPoint[] = _.zipWith(
+ cdf,
+ sortedPoints,
+ (c: number, point: labeledPoint) => ({
+ ...point,
+ cdf: (c * 100).toFixed(2) + "%",
+ })
+ );
+ let continuousValues = cdfLabeledPoint.filter(
+ (x) => x.type === "continuous"
+ );
+ let discreteValues = cdfLabeledPoint.filter((x) => x.type === "discrete");
+
+ return (
+
+ );
+ }
+};
+
+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" ? (
+
+ ) : (
+ <>>
+ );
+ let data = _rangeByCount(diagramStart, diagramStop, diagramCount)
+ .map((x) => {
+ let result = distPlusFn(x);
+ if (result.tag === "Ok") {
+ let percentiles = getPercentiles(percentileArray, result.value);
+ return {
+ x: x,
+ p1: percentiles[0],
+ p5: percentiles[1],
+ p10: percentiles[2],
+ p20: percentiles[3],
+ p30: percentiles[4],
+ p40: percentiles[5],
+ p50: percentiles[6],
+ p60: percentiles[7],
+ p70: percentiles[8],
+ p80: percentiles[9],
+ p90: percentiles[10],
+ p95: percentiles[11],
+ p99: percentiles[12],
+ };
+ } else {
+ console.log("Error", x, result);
+ return null;
+ }
+ })
+ .filter((x) => x !== null);
+ return (
+ <>
+
+ {showChart}
+ >
+ );
+};
+
export const SquiggleChart: React.FC = ({
squiggleString = "",
sampleCount = 1000,
@@ -95,154 +285,20 @@ export const SquiggleChart: React.FC = ({
if (chartResult["NAME"] === "Float") {
return ;
} else if (chartResult["NAME"] === "DistPlus") {
- let shape = chartResult.VAL.pointSetDist;
- if (shape.tag === "Continuous") {
- let xyShape = shape.value.xyShape;
- let totalY = xyShape.ys.reduce((a, b) => a + b);
- let total = 0;
- let cdf = xyShape.ys.map((y) => {
- total += y;
- return total / totalY;
- });
- let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y]) => ({
- cdf: (c * 100).toFixed(2) + "%",
- x: x,
- y: y,
- }));
-
- return (
-
- );
- } else if (shape.tag === "Discrete") {
- let xyShape = shape.value.xyShape;
- let totalY = xyShape.ys.reduce((a, b) => a + b);
- let total = 0;
- let cdf = xyShape.ys.map((y) => {
- total += y;
- return total / totalY;
- });
- let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y]) => ({
- cdf: (c * 100).toFixed(2) + "%",
- x: x,
- y: y,
- }));
-
- return ;
- } else if (shape.tag === "Mixed") {
- let discreteShape = shape.value.discrete.xyShape;
- let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
-
- let discretePoints = _.zip(discreteShape.xs, discreteShape.ys);
- let continuousShape = shape.value.continuous.xyShape;
- let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
-
- interface labeledPoint {
- x: number;
- y: number;
- type: "discrete" | "continuous";
- }
-
- let markedDisPoints: labeledPoint[] = discretePoints.map(
- ([x, y]) => ({ x: x, y: y, type: "discrete" })
- );
- let markedConPoints: labeledPoint[] = continuousPoints.map(
- ([x, y]) => ({ x: x, y: y, type: "continuous" })
- );
-
- let sortedPoints = _.sortBy(
- markedDisPoints.concat(markedConPoints),
- "x"
- );
-
- let totalContinuous = 1 - totalDiscrete;
- let totalY = continuousShape.ys.reduce(
- (a: number, b: number) => a + b
- );
-
- let total = 0;
- let cdf = sortedPoints.map((point: labeledPoint) => {
- if (point.type === "discrete") {
- total += point.y;
- return total;
- } else if (point.type === "continuous") {
- total += (point.y / totalY) * totalContinuous;
- return total;
- }
- });
-
- interface cdfLabeledPoint {
- cdf: string;
- x: number;
- y: number;
- type: "discrete" | "continuous";
- }
- let cdfLabeledPoint: cdfLabeledPoint[] = _.zipWith(
- cdf,
- sortedPoints,
- (c: number, point: labeledPoint) => ({
- ...point,
- cdf: (c * 100).toFixed(2) + "%",
- })
- );
- let continuousValues = cdfLabeledPoint.filter(
- (x) => x.type === "continuous"
- );
- let discreteValues = cdfLabeledPoint.filter(
- (x) => x.type === "discrete"
- );
-
- return (
-
- );
- }
- } 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,
- ];
-
- let percentiles = getPercentiles(percentileArray, result.value);
- return {
- x: x,
- p1: percentiles[0],
- p5: percentiles[1],
- p10: percentiles[2],
- p20: percentiles[3],
- p30: percentiles[4],
- p40: percentiles[5],
- p50: percentiles[6],
- p60: percentiles[7],
- p70: percentiles[8],
- p80: percentiles[9],
- p90: percentiles[10],
- p95: percentiles[11],
- p99: percentiles[12],
- };
- }
- return null;
- }
- });
return (
- x !== null) }}
- actions={false}
+
+ );
+ } else if (chartResult.NAME === "Function") {
+ return (
+
);
}
@@ -250,11 +306,7 @@ export const SquiggleChart: React.FC = ({
return <>{chartResults}>;
} else if (result.tag === "Error") {
// At this point, we came across an error. What was our error?
- return (
-
- {result.value}
-
- );
+ return {result.value};
}
return {"Invalid Response"}
;
};
diff --git a/packages/components/src/spec-percentiles.json b/packages/components/src/spec-percentiles.json
index 5751f924..a9fc08d4 100644
--- a/packages/components/src/spec-percentiles.json
+++ b/packages/components/src/spec-percentiles.json
@@ -1,7 +1,7 @@
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 500,
- "height": 400,
+ "height": 200,
"padding": 5,
"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": [
{
"orient": "bottom",
@@ -118,6 +128,14 @@
}
],
"marks": [
+ {
+ "type": "rule",
+ "encode": {
+ "update": {
+ "xscale": {"scale": "xscale", "signal": "mousemove"}
+ }
+ }
+ },
{
"type": "area",
"from": {
diff --git a/packages/components/src/stories/SquiggleChart.stories.mdx b/packages/components/src/stories/SquiggleChart.stories.mdx
index 76c40228..94273232 100644
--- a/packages/components/src/stories/SquiggleChart.stories.mdx
+++ b/packages/components/src/stories/SquiggleChart.stories.mdx
@@ -83,7 +83,7 @@ The default is show 10 points between 0 and 10.
{Template.bind({})}