Merge pull request #528 from quantified-uncertainty/function-chart-fix
Function chart fixes
This commit is contained in:
		
						commit
						80fed66efe
					
				| 
						 | 
					@ -7,6 +7,8 @@ import {
 | 
				
			||||||
  lambdaValue,
 | 
					  lambdaValue,
 | 
				
			||||||
  environment,
 | 
					  environment,
 | 
				
			||||||
  runForeign,
 | 
					  runForeign,
 | 
				
			||||||
 | 
					  squiggleExpression,
 | 
				
			||||||
 | 
					  errorValue,
 | 
				
			||||||
  errorValueToString,
 | 
					  errorValueToString,
 | 
				
			||||||
} from "@quri/squiggle-lang";
 | 
					} from "@quri/squiggle-lang";
 | 
				
			||||||
import { createClassFromSpec } from "react-vega";
 | 
					import { createClassFromSpec } from "react-vega";
 | 
				
			||||||
| 
						 | 
					@ -45,40 +47,40 @@ interface FunctionChartProps {
 | 
				
			||||||
  environment: environment;
 | 
					  environment: environment;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const FunctionChart: React.FC<FunctionChartProps> = ({
 | 
					type percentiles = {
 | 
				
			||||||
  fn,
 | 
					  x: number;
 | 
				
			||||||
  chartSettings,
 | 
					  p1: number;
 | 
				
			||||||
  environment,
 | 
					  p5: number;
 | 
				
			||||||
}: FunctionChartProps) => {
 | 
					  p10: number;
 | 
				
			||||||
  let [mouseOverlay, setMouseOverlay] = React.useState(0);
 | 
					  p20: number;
 | 
				
			||||||
  function handleHover(_name: string, value: unknown) {
 | 
					  p30: number;
 | 
				
			||||||
    setMouseOverlay(value as number);
 | 
					  p40: number;
 | 
				
			||||||
  }
 | 
					  p50: number;
 | 
				
			||||||
  function handleOut() {
 | 
					  p60: number;
 | 
				
			||||||
    setMouseOverlay(NaN);
 | 
					  p70: number;
 | 
				
			||||||
  }
 | 
					  p80: number;
 | 
				
			||||||
  const signalListeners = { mousemove: handleHover, mouseout: handleOut };
 | 
					  p90: number;
 | 
				
			||||||
  let mouseItem = runForeign(fn, [mouseOverlay], environment);
 | 
					  p95: number;
 | 
				
			||||||
  let showChart =
 | 
					  p99: number;
 | 
				
			||||||
    mouseItem.tag === "Ok" && mouseItem.value.tag == "distribution" ? (
 | 
					}[];
 | 
				
			||||||
      <DistributionChart
 | 
					
 | 
				
			||||||
        distribution={mouseItem.value.value}
 | 
					type errors = _.Dictionary<
 | 
				
			||||||
        width={400}
 | 
					  {
 | 
				
			||||||
        height={140}
 | 
					    x: number;
 | 
				
			||||||
        showSummary={false}
 | 
					    value: string;
 | 
				
			||||||
      />
 | 
					  }[]
 | 
				
			||||||
    ) : (
 | 
					>;
 | 
				
			||||||
      <></>
 | 
					
 | 
				
			||||||
    );
 | 
					type point = { x: number; value: result<Distribution, string> };
 | 
				
			||||||
  let data1 = _rangeByCount(
 | 
					
 | 
				
			||||||
 | 
					let getPercentiles = ({ chartSettings, fn, environment }) => {
 | 
				
			||||||
 | 
					  let chartPointsToRender = _rangeByCount(
 | 
				
			||||||
    chartSettings.start,
 | 
					    chartSettings.start,
 | 
				
			||||||
    chartSettings.stop,
 | 
					    chartSettings.stop,
 | 
				
			||||||
    chartSettings.count
 | 
					    chartSettings.count
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  type point = { x: number; value: result<Distribution, string> };
 | 
					
 | 
				
			||||||
  let valueData: point[] = React.useMemo(
 | 
					  let chartPointsData: point[] = chartPointsToRender.map((x) => {
 | 
				
			||||||
    () =>
 | 
					 | 
				
			||||||
      data1.map((x) => {
 | 
					 | 
				
			||||||
    let result = runForeign(fn, [x], environment);
 | 
					    let result = runForeign(fn, [x], environment);
 | 
				
			||||||
    if (result.tag === "Ok") {
 | 
					    if (result.tag === "Ok") {
 | 
				
			||||||
      if (result.value.tag == "distribution") {
 | 
					      if (result.value.tag == "distribution") {
 | 
				
			||||||
| 
						 | 
					@ -99,15 +101,14 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
 | 
				
			||||||
        value: { tag: "Error", value: errorValueToString(result.value) },
 | 
					        value: { tag: "Error", value: errorValueToString(result.value) },
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
      }),
 | 
					  });
 | 
				
			||||||
    [environment, fn]
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let initialPartition: [
 | 
					  let initialPartition: [
 | 
				
			||||||
    { x: number; value: Distribution }[],
 | 
					    { x: number; value: Distribution }[],
 | 
				
			||||||
    { x: number; value: string }[]
 | 
					    { x: number; value: string }[]
 | 
				
			||||||
  ] = [[], []];
 | 
					  ] = [[], []];
 | 
				
			||||||
  let [functionImage, errors] = valueData.reduce((acc, current) => {
 | 
					
 | 
				
			||||||
 | 
					  let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
 | 
				
			||||||
    if (current.value.tag === "Ok") {
 | 
					    if (current.value.tag === "Ok") {
 | 
				
			||||||
      acc[0].push({ x: current.x, value: current.value.value });
 | 
					      acc[0].push({ x: current.x, value: current.value.value });
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
| 
						 | 
					@ -116,35 +117,83 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
 | 
				
			||||||
    return acc;
 | 
					    return acc;
 | 
				
			||||||
  }, initialPartition);
 | 
					  }, initialPartition);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let percentiles = functionImage.map(({ x, value }) => {
 | 
					  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 {
 | 
					    return {
 | 
				
			||||||
      x: x,
 | 
					      x: x,
 | 
				
			||||||
      p1: unwrap(value.inv(0.01)),
 | 
					      p1: unwrap(toPointSet.inv(0.01)),
 | 
				
			||||||
      p5: unwrap(value.inv(0.05)),
 | 
					      p5: unwrap(toPointSet.inv(0.05)),
 | 
				
			||||||
      p10: unwrap(value.inv(0.12)),
 | 
					      p10: unwrap(toPointSet.inv(0.1)),
 | 
				
			||||||
      p20: unwrap(value.inv(0.2)),
 | 
					      p20: unwrap(toPointSet.inv(0.2)),
 | 
				
			||||||
      p30: unwrap(value.inv(0.3)),
 | 
					      p30: unwrap(toPointSet.inv(0.3)),
 | 
				
			||||||
      p40: unwrap(value.inv(0.4)),
 | 
					      p40: unwrap(toPointSet.inv(0.4)),
 | 
				
			||||||
      p50: unwrap(value.inv(0.5)),
 | 
					      p50: unwrap(toPointSet.inv(0.5)),
 | 
				
			||||||
      p60: unwrap(value.inv(0.6)),
 | 
					      p60: unwrap(toPointSet.inv(0.6)),
 | 
				
			||||||
      p70: unwrap(value.inv(0.7)),
 | 
					      p70: unwrap(toPointSet.inv(0.7)),
 | 
				
			||||||
      p80: unwrap(value.inv(0.8)),
 | 
					      p80: unwrap(toPointSet.inv(0.8)),
 | 
				
			||||||
      p90: unwrap(value.inv(0.9)),
 | 
					      p90: unwrap(toPointSet.inv(0.9)),
 | 
				
			||||||
      p95: unwrap(value.inv(0.95)),
 | 
					      p95: unwrap(toPointSet.inv(0.95)),
 | 
				
			||||||
      p99: unwrap(value.inv(0.99)),
 | 
					      p99: unwrap(toPointSet.inv(0.99)),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let groupedErrors = _.groupBy(errors, (x) => x.value);
 | 
					  return { percentiles, errors: groupedErrors };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const FunctionChart: React.FC<FunctionChartProps> = ({
 | 
				
			||||||
 | 
					  fn,
 | 
				
			||||||
 | 
					  chartSettings,
 | 
				
			||||||
 | 
					  environment,
 | 
				
			||||||
 | 
					}: 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 getPercentilesMemoized = React.useMemo(
 | 
				
			||||||
 | 
					    () => getPercentiles({ chartSettings, fn, environment }),
 | 
				
			||||||
 | 
					    [environment, fn]
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <SquigglePercentilesChart
 | 
					      <SquigglePercentilesChart
 | 
				
			||||||
        data={{ facet: percentiles }}
 | 
					        data={{ facet: getPercentilesMemoized.percentiles }}
 | 
				
			||||||
        actions={false}
 | 
					        actions={false}
 | 
				
			||||||
        signalListeners={signalListeners}
 | 
					        signalListeners={signalListeners}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      {showChart}
 | 
					      {showChart}
 | 
				
			||||||
      {_.entries(groupedErrors).map(([errorName, errorPoints]) => (
 | 
					      {_.entries(getPercentilesMemoized.errors).map(
 | 
				
			||||||
 | 
					        ([errorName, errorPoints]) => (
 | 
				
			||||||
          <ErrorBox key={errorName} heading={errorName}>
 | 
					          <ErrorBox key={errorName} heading={errorName}>
 | 
				
			||||||
            Values:{" "}
 | 
					            Values:{" "}
 | 
				
			||||||
            {errorPoints
 | 
					            {errorPoints
 | 
				
			||||||
| 
						 | 
					@ -155,7 +204,8 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
 | 
				
			||||||
                </>
 | 
					                </>
 | 
				
			||||||
              ))}
 | 
					              ))}
 | 
				
			||||||
          </ErrorBox>
 | 
					          </ErrorBox>
 | 
				
			||||||
      ))}
 | 
					        )
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -194,7 +194,10 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
 | 
				
			||||||
        <FunctionChart
 | 
					        <FunctionChart
 | 
				
			||||||
          fn={expression.value}
 | 
					          fn={expression.value}
 | 
				
			||||||
          chartSettings={chartSettings}
 | 
					          chartSettings={chartSettings}
 | 
				
			||||||
          environment={environment}
 | 
					          environment={{
 | 
				
			||||||
 | 
					            sampleCount: environment.sampleCount / 10,
 | 
				
			||||||
 | 
					            xyPointLength: environment.xyPointLength / 10,
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -232,7 +235,8 @@ const ChartWrapper = styled.div`
 | 
				
			||||||
    "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
 | 
					    "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let defaultChartSettings = { start: 0, stop: 10, count: 100 };
 | 
					let defaultChartSettings = { start: 0, stop: 10, count: 20 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
 | 
					export const SquiggleChart: React.FC<SquiggleChartProps> = ({
 | 
				
			||||||
  squiggleString = "",
 | 
					  squiggleString = "",
 | 
				
			||||||
  environment,
 | 
					  environment,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,7 +56,7 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
 | 
				
			||||||
  environment,
 | 
					  environment,
 | 
				
			||||||
  diagramStart = 0,
 | 
					  diagramStart = 0,
 | 
				
			||||||
  diagramStop = 10,
 | 
					  diagramStop = 10,
 | 
				
			||||||
  diagramCount = 100,
 | 
					  diagramCount = 20,
 | 
				
			||||||
  onChange,
 | 
					  onChange,
 | 
				
			||||||
  bindings = defaultBindings,
 | 
					  bindings = defaultBindings,
 | 
				
			||||||
  jsImports = defaultImports,
 | 
					  jsImports = defaultImports,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -153,6 +153,20 @@ to allow large and small numbers being printed cleanly.
 | 
				
			||||||
  </Story>
 | 
					  </Story>
 | 
				
			||||||
</Canvas>
 | 
					</Canvas>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Functions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Canvas>
 | 
				
			||||||
 | 
					  <Story
 | 
				
			||||||
 | 
					    name="Function"
 | 
				
			||||||
 | 
					    args={{
 | 
				
			||||||
 | 
					      squiggleString: "foo(t) = normal(t,2)*normal(5,3); foo",
 | 
				
			||||||
 | 
					      width,
 | 
				
			||||||
 | 
					    }}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    {Template.bind({})}
 | 
				
			||||||
 | 
					  </Story>
 | 
				
			||||||
 | 
					</Canvas>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Records
 | 
					## Records
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<Canvas>
 | 
					<Canvas>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user