Merge pull request #53 from QURIresearch/notebook
Add squiggle notebooks
This commit is contained in:
commit
3449c93bcb
5
packages/components/.prettierignore
Normal file
5
packages/components/.prettierignore
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
node_modules
|
||||||
|
storybook-static
|
||||||
|
.storybook
|
1
packages/components/.prettierrc.json
Normal file
1
packages/components/.prettierrc.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
57037
packages/components/package-lock.json
generated
57037
packages/components/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@quri/squiggle-components",
|
"name": "@quri/squiggle-components",
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quri/squiggle-lang": "0.2.2",
|
"@quri/squiggle-lang": "0.2.2",
|
||||||
"@testing-library/jest-dom": "^5.16.2",
|
"@testing-library/jest-dom": "^5.16.2",
|
||||||
|
@ -69,10 +69,12 @@
|
||||||
"@storybook/preset-create-react-app": "^4.0.0",
|
"@storybook/preset-create-react-app": "^4.0.0",
|
||||||
"@storybook/react": "^6.4.18",
|
"@storybook/react": "^6.4.18",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
|
"prettier": "^2.6.0",
|
||||||
"react-codejar": "^1.1.2",
|
"react-codejar": "^1.1.2",
|
||||||
"ts-loader": "^9.2.8",
|
"ts-loader": "^9.2.8",
|
||||||
"webpack": "^5.70.0",
|
"webpack": "^5.70.0",
|
||||||
"webpack-cli": "^4.9.2"
|
"webpack-cli": "^4.9.2",
|
||||||
|
"webpack-dev-server": "^4.7.4"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "17.0.39"
|
"@types/react": "17.0.39"
|
||||||
|
|
|
@ -5,10 +5,7 @@
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta name="description" content="Squiggle components" />
|
||||||
name="description"
|
|
||||||
content="Squiggle components"
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<title>Squiggle Components</title>
|
<title>Squiggle Components</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,86 +1,96 @@
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
import * as _ from 'lodash';
|
import _ from "lodash";
|
||||||
import type { Spec } from 'vega';
|
import type { Spec } from "vega";
|
||||||
import { run } from '@squiggle/lang';
|
import { run } from "@quri/squiggle-lang";
|
||||||
import type { DistPlus, SamplingInputs } from '@squiggle/lang';
|
import type {
|
||||||
import { createClassFromSpec } from 'react-vega';
|
DistPlus,
|
||||||
import * as chartSpecification from './spec-distributions.json'
|
SamplingInputs,
|
||||||
import * as percentilesSpec from './spec-pertentiles.json'
|
exportEnv,
|
||||||
|
exportDistribution,
|
||||||
|
} from "@quri/squiggle-lang";
|
||||||
|
import { createClassFromSpec } from "react-vega";
|
||||||
|
import * as chartSpecification from "./spec-distributions.json";
|
||||||
|
import * as percentilesSpec from "./spec-pertentiles.json";
|
||||||
|
|
||||||
let SquiggleVegaChart = createClassFromSpec({'spec': chartSpecification as Spec});
|
let SquiggleVegaChart = createClassFromSpec({
|
||||||
|
spec: chartSpecification as Spec,
|
||||||
|
});
|
||||||
|
|
||||||
let SquigglePercentilesChart = createClassFromSpec({'spec': percentilesSpec as Spec});
|
let SquigglePercentilesChart = createClassFromSpec({
|
||||||
|
spec: percentilesSpec as Spec,
|
||||||
|
});
|
||||||
|
|
||||||
export interface SquiggleChartProps {
|
export interface SquiggleChartProps {
|
||||||
/** The input string for squiggle */
|
/** The input string for squiggle */
|
||||||
squiggleString : string,
|
squiggleString: string;
|
||||||
|
|
||||||
/** If the output requires monte carlo sampling, the amount of samples */
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
sampleCount? : number,
|
sampleCount?: number;
|
||||||
/** The amount of points returned to draw the distribution */
|
/** The amount of points returned to draw the distribution */
|
||||||
outputXYPoints? : number,
|
outputXYPoints?: number;
|
||||||
kernelWidth? : number,
|
kernelWidth?: number;
|
||||||
pointDistLength? : number,
|
pointDistLength?: number;
|
||||||
/** If the result is a function, where the function starts */
|
/** If the result is a function, where the function starts */
|
||||||
diagramStart? : number,
|
diagramStart?: number;
|
||||||
/** If the result is a function, where the function ends */
|
/** If the result is a function, where the function ends */
|
||||||
diagramStop? : number,
|
diagramStop?: number;
|
||||||
/** If the result is a function, how many points along the function it samples */
|
/** If the result is a function, how many points along the function it samples */
|
||||||
diagramCount? : number
|
diagramCount?: number;
|
||||||
|
/** variables declared before this expression */
|
||||||
|
environment?: exportEnv;
|
||||||
|
/** When the environment changes */
|
||||||
|
onEnvChange?(env: exportEnv): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SquiggleChart : React.FC<SquiggleChartProps> = props => {
|
export const SquiggleChart: React.FC<SquiggleChartProps> = (props) => {
|
||||||
let samplingInputs : SamplingInputs = {
|
let samplingInputs: SamplingInputs = {
|
||||||
sampleCount : props.sampleCount,
|
sampleCount: props.sampleCount,
|
||||||
outputXYPoints : props.outputXYPoints,
|
outputXYPoints: props.outputXYPoints,
|
||||||
kernelWidth : props.kernelWidth,
|
kernelWidth: props.kernelWidth,
|
||||||
pointDistLength : props.pointDistLength
|
pointDistLength: props.pointDistLength,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let result = run(props.squiggleString, samplingInputs, props.environment);
|
||||||
let result = run(props.squiggleString, samplingInputs);
|
|
||||||
console.log(result)
|
|
||||||
if (result.tag === "Ok") {
|
if (result.tag === "Ok") {
|
||||||
let chartResults = result.value.map(chartResult => {
|
let environment = result.value.environment;
|
||||||
console.log(chartResult)
|
let exports = result.value.exports;
|
||||||
if(chartResult["NAME"] === "Float"){
|
if (props.onEnvChange) props.onEnvChange(environment);
|
||||||
|
let chartResults = exports.map((chartResult: exportDistribution) => {
|
||||||
|
if (chartResult["NAME"] === "Float") {
|
||||||
return <MakeNumberShower precision={3} number={chartResult["VAL"]} />;
|
return <MakeNumberShower precision={3} number={chartResult["VAL"]} />;
|
||||||
}
|
} else if (chartResult["NAME"] === "DistPlus") {
|
||||||
else if(chartResult["NAME"] === "DistPlus"){
|
|
||||||
let shape = chartResult.VAL.pointSetDist;
|
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);
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let cdf = xyShape.ys.map(y => {
|
let cdf = xyShape.ys.map((y) => {
|
||||||
total += y;
|
total += y;
|
||||||
return total / totalY;
|
return total / totalY;
|
||||||
})
|
});
|
||||||
let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y ]) => ({cdf: (c * 100).toFixed(2) + "%", x: x, y: y}));
|
let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y]) => ({
|
||||||
|
cdf: (c * 100).toFixed(2) + "%",
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return <SquiggleVegaChart data={{ con: values }} />;
|
||||||
<SquiggleVegaChart
|
} else if (shape.tag === "Discrete") {
|
||||||
data={{"con": values}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if(shape.tag === "Discrete"){
|
|
||||||
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);
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let cdf = xyShape.ys.map(y => {
|
let cdf = xyShape.ys.map((y) => {
|
||||||
total += y;
|
total += y;
|
||||||
return total / totalY;
|
return total / totalY;
|
||||||
})
|
});
|
||||||
let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x,y]) => ({cdf: (c * 100).toFixed(2) + "%", x: x, y: y}));
|
let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y]) => ({
|
||||||
|
cdf: (c * 100).toFixed(2) + "%",
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return <SquiggleVegaChart data={{ dis: values }} />;
|
||||||
<SquiggleVegaChart
|
} else if (shape.tag === "Mixed") {
|
||||||
data={{"dis": values}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if(shape.tag === "Mixed"){
|
|
||||||
let discreteShape = shape.value.discrete.xyShape;
|
let discreteShape = shape.value.discrete.xyShape;
|
||||||
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
@ -89,141 +99,150 @@ export const SquiggleChart : React.FC<SquiggleChartProps> = props => {
|
||||||
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
|
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
|
||||||
|
|
||||||
interface labeledPoint {
|
interface labeledPoint {
|
||||||
x: number,
|
x: number;
|
||||||
y: number,
|
y: number;
|
||||||
type: "discrete" | "continuous"
|
type: "discrete" | "continuous";
|
||||||
};
|
}
|
||||||
|
|
||||||
let markedDisPoints : labeledPoint[] = discretePoints.map(([x,y]) => ({x: x, y: y, type: "discrete"}))
|
let markedDisPoints: labeledPoint[] = discretePoints.map(
|
||||||
let markedConPoints : labeledPoint[] = continuousPoints.map(([x,y]) => ({x: x, y: y, type: "continuous"}))
|
([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 sortedPoints = _.sortBy(
|
||||||
|
markedDisPoints.concat(markedConPoints),
|
||||||
|
"x"
|
||||||
|
);
|
||||||
|
|
||||||
let totalContinuous = 1 - totalDiscrete;
|
let totalContinuous = 1 - totalDiscrete;
|
||||||
let totalY = continuousShape.ys.reduce((a:number, b:number) => a + b);
|
let totalY = continuousShape.ys.reduce(
|
||||||
|
(a: number, b: number) => a + b
|
||||||
|
);
|
||||||
|
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let cdf = sortedPoints.map((point: labeledPoint) => {
|
let cdf = sortedPoints.map((point: labeledPoint) => {
|
||||||
if(point.type == "discrete") {
|
if (point.type == "discrete") {
|
||||||
total += point.y;
|
total += point.y;
|
||||||
return total;
|
return total;
|
||||||
}
|
} else if (point.type == "continuous") {
|
||||||
else if (point.type == "continuous") {
|
total += (point.y / totalY) * totalContinuous;
|
||||||
total += point.y / totalY * totalContinuous;
|
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface cdfLabeledPoint {
|
interface cdfLabeledPoint {
|
||||||
cdf: string,
|
cdf: string;
|
||||||
x: number,
|
x: number;
|
||||||
y: number,
|
y: number;
|
||||||
type: "discrete" | "continuous"
|
type: "discrete" | "continuous";
|
||||||
}
|
}
|
||||||
let cdfLabeledPoint : cdfLabeledPoint[] = _.zipWith(cdf, sortedPoints, (c: number, point: labeledPoint) => ({...point, cdf: (c * 100).toFixed(2) + "%"}))
|
let cdfLabeledPoint: cdfLabeledPoint[] = _.zipWith(
|
||||||
let continuousValues = cdfLabeledPoint.filter(x => x.type == "continuous")
|
cdf,
|
||||||
let discreteValues = cdfLabeledPoint.filter(x => x.type == "discrete")
|
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 (
|
return (
|
||||||
<SquiggleVegaChart
|
<SquiggleVegaChart
|
||||||
data={{"con": continuousValues, "dis": discreteValues}}
|
data={{ con: continuousValues, dis: discreteValues }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(chartResult.NAME === "Function"){
|
|
||||||
// We are looking at a function. In this case, we draw a Percentiles chart
|
|
||||||
let start = props.diagramStart ? props.diagramStart : 0
|
|
||||||
let stop = props.diagramStop ? props.diagramStop : 10
|
|
||||||
let count = props.diagramCount ? props.diagramCount : 0.1
|
|
||||||
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 0;
|
|
||||||
})
|
|
||||||
return <SquigglePercentilesChart data={{"facet": data}} />
|
|
||||||
}
|
}
|
||||||
})
|
} else if (chartResult.NAME === "Function") {
|
||||||
return <>{chartResults}</>;
|
// We are looking at a function. In this case, we draw a Percentiles chart
|
||||||
}
|
let start = props.diagramStart ? props.diagramStart : 0;
|
||||||
else if(result.tag == "Error") {
|
let stop = props.diagramStop ? props.diagramStop : 10;
|
||||||
// At this point, we came across an error. What was our error?
|
let count = props.diagramCount ? props.diagramCount : 0.1;
|
||||||
return (<p>{"Error parsing Squiggle: " + result.value}</p>)
|
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 0;
|
||||||
|
});
|
||||||
|
return <SquigglePercentilesChart data={{ facet: data }} />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return <>{chartResults}</>;
|
||||||
|
} else if (result.tag == "Error") {
|
||||||
|
// At this point, we came across an error. What was our error?
|
||||||
|
return <p>{"Error parsing Squiggle: " + result.value}</p>;
|
||||||
}
|
}
|
||||||
return (<p>{"Invalid Response"}</p>)
|
return <p>{"Invalid Response"}</p>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getPercentiles(percentiles:number[], t : DistPlus) {
|
function getPercentiles(percentiles: number[], t: DistPlus) {
|
||||||
if(t.pointSetDist.tag == "Discrete") {
|
if (t.pointSetDist.tag == "Discrete") {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let maxX = _.max(t.pointSetDist.value.xyShape.xs)
|
let maxX = _.max(t.pointSetDist.value.xyShape.xs);
|
||||||
let bounds = percentiles.map(_ => maxX);
|
let bounds = percentiles.map((_) => maxX);
|
||||||
_.zipWith(t.pointSetDist.value.xyShape.xs,t.pointSetDist.value.xyShape.ys, (x,y) => {
|
_.zipWith(
|
||||||
total += y
|
t.pointSetDist.value.xyShape.xs,
|
||||||
|
t.pointSetDist.value.xyShape.ys,
|
||||||
|
(x, y) => {
|
||||||
|
total += y;
|
||||||
percentiles.forEach((v, i) => {
|
percentiles.forEach((v, i) => {
|
||||||
if(total > v && bounds[i] == maxX){
|
if (total > v && bounds[i] == maxX) {
|
||||||
bounds[i] = x
|
bounds[i] = x;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
});
|
}
|
||||||
return bounds;
|
);
|
||||||
}
|
return bounds;
|
||||||
else if(t.pointSetDist.tag == "Continuous"){
|
} else if (t.pointSetDist.tag == "Continuous") {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let maxX = _.max(t.pointSetDist.value.xyShape.xs)
|
let maxX = _.max(t.pointSetDist.value.xyShape.xs);
|
||||||
let totalY = _.sum(t.pointSetDist.value.xyShape.ys)
|
let totalY = _.sum(t.pointSetDist.value.xyShape.ys);
|
||||||
let bounds = percentiles.map(_ => maxX);
|
let bounds = percentiles.map((_) => maxX);
|
||||||
_.zipWith(t.pointSetDist.value.xyShape.xs,t.pointSetDist.value.xyShape.ys, (x,y) => {
|
_.zipWith(
|
||||||
|
t.pointSetDist.value.xyShape.xs,
|
||||||
|
t.pointSetDist.value.xyShape.ys,
|
||||||
|
(x, y) => {
|
||||||
total += y / totalY;
|
total += y / totalY;
|
||||||
percentiles.forEach((v, i) => {
|
percentiles.forEach((v, i) => {
|
||||||
if(total > v && bounds[i] == maxX){
|
if (total > v && bounds[i] == maxX) {
|
||||||
bounds[i] = x
|
bounds[i] = x;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
});
|
}
|
||||||
return bounds;
|
);
|
||||||
}
|
return bounds;
|
||||||
else if(t.pointSetDist.tag == "Mixed"){
|
} else if (t.pointSetDist.tag == "Mixed") {
|
||||||
let discreteShape = t.pointSetDist.value.discrete.xyShape;
|
let discreteShape = t.pointSetDist.value.discrete.xyShape;
|
||||||
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
@ -232,80 +251,87 @@ function getPercentiles(percentiles:number[], t : DistPlus) {
|
||||||
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
|
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
|
||||||
|
|
||||||
interface labeledPoint {
|
interface labeledPoint {
|
||||||
x: number,
|
x: number;
|
||||||
y: number,
|
y: number;
|
||||||
type: "discrete" | "continuous"
|
type: "discrete" | "continuous";
|
||||||
};
|
}
|
||||||
|
|
||||||
let markedDisPoints : labeledPoint[] = discretePoints.map(([x,y]) => ({x: x, y: y, type: "discrete"}))
|
let markedDisPoints: labeledPoint[] = discretePoints.map(([x, y]) => ({
|
||||||
let markedConPoints : labeledPoint[] = continuousPoints.map(([x,y]) => ({x: x, y: y, type: "continuous"}))
|
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 sortedPoints = _.sortBy(markedDisPoints.concat(markedConPoints), "x");
|
||||||
|
|
||||||
let totalContinuous = 1 - totalDiscrete;
|
let totalContinuous = 1 - totalDiscrete;
|
||||||
let totalY = continuousShape.ys.reduce((a:number, b:number) => a + b);
|
let totalY = continuousShape.ys.reduce((a: number, b: number) => a + b);
|
||||||
|
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let maxX = _.max(sortedPoints.map(x => x.x));
|
let maxX = _.max(sortedPoints.map((x) => x.x));
|
||||||
let bounds = percentiles.map(_ => maxX);
|
let bounds = percentiles.map((_) => maxX);
|
||||||
sortedPoints.map((point: labeledPoint) => {
|
sortedPoints.map((point: labeledPoint) => {
|
||||||
if(point.type == "discrete") {
|
if (point.type == "discrete") {
|
||||||
total += point.y;
|
total += point.y;
|
||||||
|
} else if (point.type == "continuous") {
|
||||||
|
total += (point.y / totalY) * totalContinuous;
|
||||||
}
|
}
|
||||||
else if (point.type == "continuous") {
|
percentiles.forEach((v, i) => {
|
||||||
total += point.y / totalY * totalContinuous;
|
if (total > v && bounds[i] == maxX) {
|
||||||
}
|
|
||||||
percentiles.forEach((v,i) => {
|
|
||||||
if(total > v && bounds[i] == maxX){
|
|
||||||
bounds[i] = total;
|
bounds[i] = total;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return total;
|
return total;
|
||||||
});
|
});
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function MakeNumberShower(props: {number: number, precision :number}){
|
function MakeNumberShower(props: { number: number; precision: number }) {
|
||||||
let numberWithPresentation = numberShow(props.number, props.precision);
|
let numberWithPresentation = numberShow(props.number, props.precision);
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{numberWithPresentation.value}
|
{numberWithPresentation.value}
|
||||||
{numberWithPresentation.symbol}
|
{numberWithPresentation.symbol}
|
||||||
{numberWithPresentation.power ?
|
{numberWithPresentation.power ? (
|
||||||
<span>
|
<span>
|
||||||
{'\u00b710'}
|
{"\u00b710"}
|
||||||
<span style={{fontSize: "0.6em", verticalAlign: "super"}}>
|
<span style={{ fontSize: "0.6em", verticalAlign: "super" }}>
|
||||||
{numberWithPresentation.power}
|
{numberWithPresentation.power}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
) : (
|
||||||
: <></>}
|
<></>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
);
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderOfMagnitudeNum = (n:number) => {
|
const orderOfMagnitudeNum = (n: number) => {
|
||||||
return Math.pow(10, n);
|
return Math.pow(10, n);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 105 -> 3
|
// 105 -> 3
|
||||||
const orderOfMagnitude = (n:number) => {
|
const orderOfMagnitude = (n: number) => {
|
||||||
return Math.floor(Math.log(n) / Math.LN10 + 0.000000001);
|
return Math.floor(Math.log(n) / Math.LN10 + 0.000000001);
|
||||||
};
|
};
|
||||||
|
|
||||||
function withXSigFigs(number:number, sigFigs:number) {
|
function withXSigFigs(number: number, sigFigs: number) {
|
||||||
const withPrecision = number.toPrecision(sigFigs);
|
const withPrecision = number.toPrecision(sigFigs);
|
||||||
const formatted = Number(withPrecision);
|
const formatted = Number(withPrecision);
|
||||||
return `${formatted}`;
|
return `${formatted}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NumberShower {
|
class NumberShower {
|
||||||
number: number
|
number: number;
|
||||||
precision: number
|
precision: number;
|
||||||
|
|
||||||
constructor(number:number, precision = 2) {
|
constructor(number: number, precision = 2) {
|
||||||
this.number = number;
|
this.number = number;
|
||||||
this.precision = precision;
|
this.precision = precision;
|
||||||
}
|
}
|
||||||
|
@ -314,9 +340,9 @@ class NumberShower {
|
||||||
const number = Math.abs(this.number);
|
const number = Math.abs(this.number);
|
||||||
const response = this.evaluate(number);
|
const response = this.evaluate(number);
|
||||||
if (this.number < 0) {
|
if (this.number < 0) {
|
||||||
response.value = '-' + response.value;
|
response.value = "-" + response.value;
|
||||||
}
|
}
|
||||||
return response
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
metricSystem(number: number, order: number) {
|
metricSystem(number: number, order: number) {
|
||||||
|
@ -327,7 +353,7 @@ class NumberShower {
|
||||||
|
|
||||||
evaluate(number: number) {
|
evaluate(number: number) {
|
||||||
if (number === 0) {
|
if (number === 0) {
|
||||||
return { value: this.metricSystem(0, 0) }
|
return { value: this.metricSystem(0, 0) };
|
||||||
}
|
}
|
||||||
|
|
||||||
const order = orderOfMagnitude(number);
|
const order = orderOfMagnitude(number);
|
||||||
|
@ -336,13 +362,13 @@ class NumberShower {
|
||||||
} else if (order < 4) {
|
} else if (order < 4) {
|
||||||
return { value: this.metricSystem(number, 0) };
|
return { value: this.metricSystem(number, 0) };
|
||||||
} else if (order < 6) {
|
} else if (order < 6) {
|
||||||
return { value: this.metricSystem(number, 3), symbol: 'K' };
|
return { value: this.metricSystem(number, 3), symbol: "K" };
|
||||||
} else if (order < 9) {
|
} else if (order < 9) {
|
||||||
return { value: this.metricSystem(number, 6), symbol: 'M' };
|
return { value: this.metricSystem(number, 6), symbol: "M" };
|
||||||
} else if (order < 12) {
|
} else if (order < 12) {
|
||||||
return { value: this.metricSystem(number, 9), symbol: 'B' };
|
return { value: this.metricSystem(number, 9), symbol: "B" };
|
||||||
} else if (order < 15) {
|
} else if (order < 15) {
|
||||||
return { value: this.metricSystem(number, 12), symbol: 'T' };
|
return { value: this.metricSystem(number, 12), symbol: "T" };
|
||||||
} else {
|
} else {
|
||||||
return { value: this.metricSystem(number, order), power: order };
|
return { value: this.metricSystem(number, order), power: order };
|
||||||
}
|
}
|
||||||
|
|
120
packages/components/src/SquiggleEditor.tsx
Normal file
120
packages/components/src/SquiggleEditor.tsx
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import { SquiggleChart } from "./SquiggleChart";
|
||||||
|
import { ReactCodeJar } from "react-codejar";
|
||||||
|
import type { exportEnv } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
export interface SquiggleEditorProps {
|
||||||
|
/** The input string for squiggle */
|
||||||
|
initialSquiggleString?: string;
|
||||||
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
|
sampleCount?: number;
|
||||||
|
/** The amount of points returned to draw the distribution */
|
||||||
|
outputXYPoints?: number;
|
||||||
|
kernelWidth?: number;
|
||||||
|
pointDistLength?: number;
|
||||||
|
/** If the result is a function, where the function starts */
|
||||||
|
diagramStart?: number;
|
||||||
|
/** If the result is a function, where the function ends */
|
||||||
|
diagramStop?: number;
|
||||||
|
/** If the result is a function, how many points along the function it samples */
|
||||||
|
diagramCount?: number;
|
||||||
|
/** The environment, other variables that were already declared */
|
||||||
|
environment?: exportEnv;
|
||||||
|
/** when the environment changes. Used again for notebook magic*/
|
||||||
|
onEnvChange?(env: exportEnv): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const highlight = (editor: HTMLInputElement) => {
|
||||||
|
let code = editor.textContent;
|
||||||
|
code = code.replace(/\((\w+?)(\b)/g, '(<font color="#8a2be2">$1</font>$2');
|
||||||
|
editor.innerHTML = code;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SquiggleEditorState {
|
||||||
|
expression: string;
|
||||||
|
env: exportEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SquiggleEditor extends React.Component<
|
||||||
|
SquiggleEditorProps,
|
||||||
|
SquiggleEditorState
|
||||||
|
> {
|
||||||
|
constructor(props: SquiggleEditorProps) {
|
||||||
|
super(props);
|
||||||
|
let code = props.initialSquiggleString ? props.initialSquiggleString : "";
|
||||||
|
this.state = { expression: code, env: props.environment };
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
let { expression, env } = this.state;
|
||||||
|
let props = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ReactCodeJar
|
||||||
|
code={expression}
|
||||||
|
onUpdate={(e) => {
|
||||||
|
this.setState({ expression: e });
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
borderRadius: "6px",
|
||||||
|
width: "530px",
|
||||||
|
border: "1px solid grey",
|
||||||
|
fontFamily: "'Source Code Pro', monospace",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: "400",
|
||||||
|
letterSpacing: "normal",
|
||||||
|
lineHeight: "20px",
|
||||||
|
padding: "10px",
|
||||||
|
tabSize: "4",
|
||||||
|
}}
|
||||||
|
highlight={highlight}
|
||||||
|
lineNumbers={false}
|
||||||
|
/>
|
||||||
|
<SquiggleChart
|
||||||
|
squiggleString={expression}
|
||||||
|
sampleCount={props.sampleCount}
|
||||||
|
outputXYPoints={props.outputXYPoints}
|
||||||
|
kernelWidth={props.kernelWidth}
|
||||||
|
pointDistLength={props.pointDistLength}
|
||||||
|
diagramStart={props.diagramStart}
|
||||||
|
diagramStop={props.diagramStop}
|
||||||
|
diagramCount={props.diagramCount}
|
||||||
|
environment={env}
|
||||||
|
onEnvChange={props.onEnvChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderSquiggleEditor(props: SquiggleEditorProps) {
|
||||||
|
let parent = document.createElement("div");
|
||||||
|
ReactDOM.render(
|
||||||
|
<SquiggleEditor
|
||||||
|
{...props}
|
||||||
|
onEnvChange={(env) => {
|
||||||
|
// Typescript complains on two levels here.
|
||||||
|
// - Div elements don't have a value property
|
||||||
|
// - Even if it did (like it was an input element), it would have to
|
||||||
|
// be a string
|
||||||
|
//
|
||||||
|
// Which are reasonable in most web contexts.
|
||||||
|
//
|
||||||
|
// However we're using observable, neither of those things have to be
|
||||||
|
// true there. div elements can contain the value property, and can have
|
||||||
|
// the value be any datatype they wish.
|
||||||
|
//
|
||||||
|
// This is here to get the 'viewof' part of:
|
||||||
|
// viewof env = cell('normal(0,1)')
|
||||||
|
// to work
|
||||||
|
// @ts-ignore
|
||||||
|
parent.value = env;
|
||||||
|
|
||||||
|
parent.dispatchEvent(new CustomEvent("input"));
|
||||||
|
if (props.onEnvChange) props.onEnvChange(env);
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
parent
|
||||||
|
);
|
||||||
|
return parent;
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
export { SquiggleChart } from './SquiggleChart';
|
export { SquiggleChart } from "./SquiggleChart";
|
||||||
|
export { SquiggleEditor, renderSquiggleEditor } from "./SquiggleEditor";
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
"width": 500,
|
"width": 500,
|
||||||
"height": 200,
|
"height": 200,
|
||||||
"padding": 5,
|
"padding": 5,
|
||||||
"data": [{"name": "con"}, {"name": "dis"}],
|
"data": [{ "name": "con" }, { "name": "dis" }],
|
||||||
|
|
||||||
"signals": [
|
"signals": [
|
||||||
{
|
{
|
||||||
"name": "mousex",
|
"name": "mousex",
|
||||||
"description": "x position of mouse",
|
"description": "x position of mouse",
|
||||||
"update": "0",
|
"update": "0",
|
||||||
"on": [{"events": "mousemove", "update": "1-x()/width"}]
|
"on": [{ "events": "mousemove", "update": "1-x()/width" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "xscale",
|
"name": "xscale",
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
"input": "range",
|
"input": "range",
|
||||||
"min": 0.1,
|
"min": 0.1,
|
||||||
"max": 1
|
"max": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "yscale",
|
"name": "yscale",
|
||||||
|
@ -31,90 +31,92 @@
|
||||||
"input": "range",
|
"input": "range",
|
||||||
"min": 0.1,
|
"min": 0.1,
|
||||||
"max": 1
|
"max": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"scales": [{
|
"scales": [
|
||||||
"name": "xscale",
|
{
|
||||||
"type": "pow",
|
"name": "xscale",
|
||||||
"exponent": {"signal": "xscale"},
|
"type": "pow",
|
||||||
"range": "width",
|
"exponent": { "signal": "xscale" },
|
||||||
"zero": false,
|
"range": "width",
|
||||||
"nice": false,
|
"zero": false,
|
||||||
"domain": {
|
"nice": false,
|
||||||
"fields": [
|
"domain": {
|
||||||
{ "data": "con", "field": "x"},
|
"fields": [
|
||||||
{ "data": "dis", "field": "x"}
|
{ "data": "con", "field": "x" },
|
||||||
|
{ "data": "dis", "field": "x" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
"name": "yscale",
|
{
|
||||||
"type": "pow",
|
"name": "yscale",
|
||||||
"exponent": {"signal": "yscale"},
|
"type": "pow",
|
||||||
"range": "height",
|
"exponent": { "signal": "yscale" },
|
||||||
"nice": true,
|
"range": "height",
|
||||||
"zero": true,
|
"nice": true,
|
||||||
"domain": {
|
"zero": true,
|
||||||
"fields": [
|
"domain": {
|
||||||
{ "data": "con", "field": "y"},
|
"fields": [
|
||||||
{ "data": "dis", "field": "y"}
|
{ "data": "con", "field": "y" },
|
||||||
|
{ "data": "dis", "field": "y" }
|
||||||
]
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"axes": [
|
"axes": [
|
||||||
{"orient": "bottom", "scale": "xscale", "tickCount": 20},
|
{ "orient": "bottom", "scale": "xscale", "tickCount": 20 },
|
||||||
{"orient": "left", "scale": "yscale"}
|
{ "orient": "left", "scale": "yscale" }
|
||||||
],
|
],
|
||||||
|
|
||||||
"marks": [
|
"marks": [
|
||||||
{
|
{
|
||||||
"type": "area",
|
"type": "area",
|
||||||
"from": {"data": "con"},
|
"from": { "data": "con" },
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": {
|
"enter": {
|
||||||
"tooltip": {"signal": "datum.cdf"}
|
"tooltip": { "signal": "datum.cdf" }
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"x": {"scale": "xscale", "field": "x"},
|
"x": { "scale": "xscale", "field": "x" },
|
||||||
"y": {"scale": "yscale", "field": "y"},
|
"y": { "scale": "yscale", "field": "y" },
|
||||||
"y2": {"scale": "yscale", "value": 0},
|
"y2": { "scale": "yscale", "value": 0 },
|
||||||
"fill": {
|
"fill": {
|
||||||
"signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'blue'}, {offset: 1.0, color: 'blue'} ] }"
|
"signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'blue'}, {offset: 1.0, color: 'blue'} ] }"
|
||||||
},
|
},
|
||||||
"interpolate": {"value": "monotone"},
|
"interpolate": { "value": "monotone" },
|
||||||
"fillOpacity": {"value": 1}
|
"fillOpacity": { "value": 1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "rect",
|
"type": "rect",
|
||||||
"from": {"data": "dis"},
|
"from": { "data": "dis" },
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": {
|
"enter": {
|
||||||
"y2": {"scale": "yscale", "value": 0},
|
"y2": { "scale": "yscale", "value": 0 },
|
||||||
"width": {"value": 1}
|
"width": { "value": 1 }
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"x": {"scale": "xscale", "field": "x"},
|
"x": { "scale": "xscale", "field": "x" },
|
||||||
"y": {"scale": "yscale", "field": "y"}
|
"y": { "scale": "yscale", "field": "y" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "symbol",
|
"type": "symbol",
|
||||||
"from": {"data": "dis"},
|
"from": { "data": "dis" },
|
||||||
"encode": {
|
"encode": {
|
||||||
"enter": {
|
"enter": {
|
||||||
"shape": {"value": "circle"},
|
"shape": { "value": "circle" },
|
||||||
"width": {"value": 5},
|
"width": { "value": 5 },
|
||||||
"tooltip": {"signal": "datum.y"}
|
"tooltip": { "signal": "datum.y" }
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"x": {"scale": "xscale", "field": "x"},
|
"x": { "scale": "xscale", "field": "x" },
|
||||||
"y": {"scale": "yscale", "field": "y"}
|
"y": { "scale": "yscale", "field": "y" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Meta } from '@storybook/addon-docs';
|
import { Meta } from "@storybook/addon-docs";
|
||||||
|
|
||||||
<Meta title="Squiggle/Introduction" />
|
<Meta title="Squiggle/Introduction" />
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { SquiggleChart } from '../SquiggleChart'
|
import { SquiggleChart } from "../SquiggleChart";
|
||||||
import { Canvas, Meta, Story, Props } from '@storybook/addon-docs';
|
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
||||||
|
|
||||||
<Meta title="Squiggle/SquiggleChart" component={ SquiggleChart } />
|
<Meta title="Squiggle/SquiggleChart" component={SquiggleChart} />
|
||||||
|
|
||||||
export const Template = SquiggleChart
|
export const Template = SquiggleChart;
|
||||||
|
|
||||||
# Squiggle Chart
|
# Squiggle Chart
|
||||||
|
|
||||||
|
@ -19,53 +19,62 @@ could be continuous, discrete or mixed.
|
||||||
## Distributions
|
## Distributions
|
||||||
|
|
||||||
An example of a normal distribution is:
|
An example of a normal distribution is:
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Normal"
|
name="Normal"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "normal(5,2)"
|
squiggleString: "normal(5,2)",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
|
||||||
An example of a Discrete distribution is:
|
An example of a Discrete distribution is:
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Discrete"
|
name="Discrete"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "mm(0, 1, [0.5, 0.5])"
|
squiggleString: "mm(0, 1, [0.5, 0.5])",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
An example of a Mixed distribution is:
|
An example of a Mixed distribution is:
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Mixed"
|
name="Mixed"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "mm(0, 5 to 10, [0.5, 0.5])"
|
squiggleString: "mm(0, 5 to 10, [0.5, 0.5])",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
## Constants
|
## Constants
|
||||||
|
|
||||||
A constant is a simple number as a result. This has special formatting rules
|
A constant is a simple number as a result. This has special formatting rules
|
||||||
to allow large and small numbers being printed cleanly.
|
to allow large and small numbers being printed cleanly.
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Constant"
|
name="Constant"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "500000 * 5000000"
|
squiggleString: "500000 * 5000000",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
Finally, a function can be returned, and this shows how the distribution changes
|
Finally, a function can be returned, and this shows how the distribution changes
|
||||||
over the axis between x = 0 and 10.
|
over the axis between x = 0 and 10.
|
||||||
|
|
||||||
|
@ -73,8 +82,9 @@ over the axis between x = 0 and 10.
|
||||||
<Story
|
<Story
|
||||||
name="Function"
|
name="Function"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "f(x) = normal(x,x)\nf"
|
squiggleString: "f(x) = normal(x,x)\nf",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
|
@ -1,26 +1,34 @@
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'production',
|
mode: "production",
|
||||||
entry: './src/index.ts',
|
devtool: "source-map",
|
||||||
|
entry: "./src/index.ts",
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
use: 'ts-loader',
|
use: "ts-loader",
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.tsx', '.ts'],
|
extensions: [".js", ".tsx", ".ts"],
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: 'bundle.js',
|
filename: "bundle.js",
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, "dist"),
|
||||||
library: {
|
library: {
|
||||||
name: 'squiggle_components',
|
name: "squiggle_components",
|
||||||
type: 'umd',
|
type: "umd",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
devServer: {
|
||||||
|
static: {
|
||||||
|
directory: path.join(__dirname, "public"),
|
||||||
|
},
|
||||||
|
compress: true,
|
||||||
|
port: 9000,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,34 @@
|
||||||
import { run } from '../src/js/index';
|
import { run } from '../src/js/index';
|
||||||
|
|
||||||
describe("A simple result", () => {
|
let testRun = (x: string) => {
|
||||||
|
let result = run(x)
|
||||||
|
if(result.tag == 'Ok'){
|
||||||
|
return { tag: 'Ok', value: result.value.exports }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Simple calculations and results", () => {
|
||||||
test("mean(normal(5,2))", () => {
|
test("mean(normal(5,2))", () => {
|
||||||
expect(run("mean(normal(5,2))")).toEqual({ tag: 'Ok', value: [ { NAME: 'Float', VAL: 5 } ] })
|
expect(testRun("mean(normal(5,2))")).toEqual({ tag: 'Ok', value: [ { NAME: 'Float', VAL: 5 } ] })
|
||||||
})
|
})
|
||||||
test("10+10", () => {
|
test("10+10", () => {
|
||||||
let foo = run("10 + 10")
|
let foo = testRun("10 + 10")
|
||||||
expect(foo).toEqual({ tag: 'Ok', value: [ { NAME: 'Float', VAL: 20 } ] })
|
expect(foo).toEqual({ tag: 'Ok', value: [ { NAME: 'Float', VAL: 20 } ] })
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
describe("Log function", () => {
|
||||||
test("log(1) = 0", () => {
|
test("log(1) = 0", () => {
|
||||||
let foo = run("log(1)")
|
let foo = testRun("log(1)")
|
||||||
expect(foo).toEqual({ tag: 'Ok', value: [ { NAME: 'Float', VAL: 0} ]})
|
expect(foo).toEqual({ tag: 'Ok', value: [ { NAME: 'Float', VAL: 0} ]})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Multimodal too many weights error", () => {
|
||||||
test("mm(0,0,[0,0,0])", () => {
|
test("mm(0,0,[0,0,0])", () => {
|
||||||
let foo = run("mm(0,0,[0,0,0])")
|
let foo = testRun("mm(0,0,[0,0,0])")
|
||||||
expect(foo).toEqual({ "tag": "Error", "value": "Function multimodal error: Too many weights provided" })
|
expect(foo).toEqual({ "tag": "Error", "value": "Function multimodal error: Too many weights provided" })
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {runAll} from '../rescript/ProgramEvaluator.gen';
|
import {runAll} from '../rescript/ProgramEvaluator.gen';
|
||||||
import type { Inputs_SamplingInputs_t as SamplingInputs } from '../rescript/ProgramEvaluator.gen';
|
import type { Inputs_SamplingInputs_t as SamplingInputs,exportEnv, exportType, exportDistribution} from '../rescript/ProgramEvaluator.gen';
|
||||||
export type { SamplingInputs }
|
export type { SamplingInputs , exportEnv, exportDistribution }
|
||||||
export type {t as DistPlus} from '../rescript/pointSetDist/DistPlus.gen';
|
export type {t as DistPlus} from '../rescript/pointSetDist/DistPlus.gen';
|
||||||
|
|
||||||
export let defaultSamplingInputs : SamplingInputs = {
|
export let defaultSamplingInputs : SamplingInputs = {
|
||||||
|
@ -9,7 +9,9 @@ export let defaultSamplingInputs : SamplingInputs = {
|
||||||
pointDistLength : 1000
|
pointDistLength : 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
export function run(squiggleString : string, samplingInputs? : SamplingInputs) {
|
export function run(squiggleString : string, samplingInputs? : SamplingInputs, environment?: exportEnv) : { tag: "Ok"; value: exportType }
|
||||||
|
| { tag: "Error"; value: string } {
|
||||||
let si : SamplingInputs = samplingInputs ? samplingInputs : defaultSamplingInputs
|
let si : SamplingInputs = samplingInputs ? samplingInputs : defaultSamplingInputs
|
||||||
return runAll(squiggleString, si)
|
let env : exportEnv = environment ? environment : []
|
||||||
|
return runAll(squiggleString, si, env)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,12 +36,20 @@ module Inputs = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type exportType = [
|
type exportDistribution = [
|
||||||
| #DistPlus(DistPlus.t)
|
| #DistPlus(DistPlus.t)
|
||||||
| #Float(float)
|
| #Float(float)
|
||||||
| #Function((float) => Belt.Result.t<DistPlus.t,string>)
|
| #Function((float) => Belt.Result.t<DistPlus.t,string>)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
type exportEnv = array<(string, ASTTypes.node)>
|
||||||
|
|
||||||
|
type exportType = {
|
||||||
|
environment : exportEnv,
|
||||||
|
exports: array<exportDistribution>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module Internals = {
|
module Internals = {
|
||||||
let addVariable = (
|
let addVariable = (
|
||||||
{samplingInputs, squiggleString, environment}: Inputs.inputs,
|
{samplingInputs, squiggleString, environment}: Inputs.inputs,
|
||||||
|
@ -71,135 +79,125 @@ module Internals = {
|
||||||
let runNode = (inputs, node) =>
|
let runNode = (inputs, node) =>
|
||||||
AST.toLeaf(makeInputs(inputs), inputs.environment, node)
|
AST.toLeaf(makeInputs(inputs), inputs.environment, node)
|
||||||
|
|
||||||
|
let renderIfNeeded = (inputs: Inputs.inputs, node: ASTTypes.node): result<
|
||||||
|
ASTTypes.node,
|
||||||
|
string,
|
||||||
|
> =>
|
||||||
|
node |> (
|
||||||
|
x =>
|
||||||
|
switch x {
|
||||||
|
| #Normalize(_) as n
|
||||||
|
| #SymbolicDist(_) as n =>
|
||||||
|
#Render(n)
|
||||||
|
|> runNode(inputs)
|
||||||
|
|> (
|
||||||
|
x =>
|
||||||
|
switch x {
|
||||||
|
| Ok(#RenderedDist(_)) as r => r
|
||||||
|
| Error(r) => Error(r)
|
||||||
|
| _ => Error("Didn't render, but intended to")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
| n => Ok(n)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let outputToDistPlus = (inputs: Inputs.inputs, pointSetDist: PointSetTypes.pointSetDist) =>
|
||||||
|
DistPlus.make(~pointSetDist, ~squiggleString=Some(inputs.squiggleString), ())
|
||||||
|
|
||||||
|
let rec returnDist = (functionInfo : (array<string>, ASTTypes.node),
|
||||||
|
inputs : Inputs.inputs,
|
||||||
|
env : ASTTypes.environment) => {
|
||||||
|
(input : float) => {
|
||||||
|
let foo: Inputs.inputs = {...inputs, environment: env};
|
||||||
|
evaluateFunction(
|
||||||
|
foo,
|
||||||
|
functionInfo,
|
||||||
|
[#SymbolicDist(#Float(input))],
|
||||||
|
) |> E.R.bind(_, a =>
|
||||||
|
switch a {
|
||||||
|
| #DistPlus(d) => Ok(DistPlus.T.normalize(d))
|
||||||
|
| n =>
|
||||||
|
Js.log2("Error here", n)
|
||||||
|
Error("wrong type")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Consider using ExpressionTypes.ExpressionTree.getFloat or similar in this function
|
||||||
|
and coersionToExportedTypes = (
|
||||||
|
inputs,
|
||||||
|
env: ASTTypes.environment,
|
||||||
|
ex: ASTTypes.node,
|
||||||
|
): result<exportDistribution, string> =>
|
||||||
|
ex
|
||||||
|
|> renderIfNeeded(inputs)
|
||||||
|
|> E.R.bind(_, x =>
|
||||||
|
switch x {
|
||||||
|
| #RenderedDist(Discrete({xyShape: {xs: [x], ys: [1.0]}})) => Ok(#Float(x))
|
||||||
|
| #SymbolicDist(#Float(x)) => Ok(#Float(x))
|
||||||
|
| #RenderedDist(n) => Ok(#DistPlus(outputToDistPlus(inputs, n)))
|
||||||
|
| #Function(n) => Ok(#Function(returnDist(n, inputs, env)))
|
||||||
|
| n => Error("Didn't output a rendered distribution. Format:" ++ AST.toString(n))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
and evaluateFunction = (
|
||||||
|
inputs: Inputs.inputs,
|
||||||
|
fn: (array<string>, ASTTypes.node),
|
||||||
|
fnInputs,
|
||||||
|
) => {
|
||||||
|
let output = AST.runFunction(
|
||||||
|
makeInputs(inputs),
|
||||||
|
inputs.environment,
|
||||||
|
fnInputs,
|
||||||
|
fn,
|
||||||
|
)
|
||||||
|
output |> E.R.bind(_, coersionToExportedTypes(inputs, inputs.environment))
|
||||||
|
}
|
||||||
|
|
||||||
let runProgram = (inputs: Inputs.inputs, p: ASTTypes.program) => {
|
let runProgram = (inputs: Inputs.inputs, p: ASTTypes.program) => {
|
||||||
let ins = ref(inputs)
|
let ins = ref(inputs)
|
||||||
p
|
p
|
||||||
|> E.A.fmap(x =>
|
|> E.A.fmap(x =>
|
||||||
switch x {
|
switch x {
|
||||||
| #Assignment(name, node) =>
|
| #Assignment(name, node) =>
|
||||||
ins := addVariable(ins.contents, name, node)
|
ins := addVariable(ins.contents, name, node)
|
||||||
None
|
None
|
||||||
| #Expression(node) =>
|
| #Expression(node) =>
|
||||||
Some(runNode(ins.contents, node) |> E.R.fmap(r => (ins.contents.environment, r)))
|
Some(runNode(ins.contents, node))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|> E.A.O.concatSomes
|
|> E.A.O.concatSomes
|
||||||
|> E.A.R.firstErrorOrOpen
|
|> E.A.R.firstErrorOrOpen
|
||||||
|
|> E.R.bind(_, d =>
|
||||||
|
d
|
||||||
|
|> E.A.fmap(x => coersionToExportedTypes(inputs, ins.contents.environment, x))
|
||||||
|
|> E.A.R.firstErrorOrOpen
|
||||||
|
)
|
||||||
|
|> E.R.fmap(ex =>
|
||||||
|
{
|
||||||
|
environment: Belt.Map.String.toArray(ins.contents.environment),
|
||||||
|
exports: ex
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputsToLeaf = (inputs: Inputs.inputs) =>
|
let inputsToLeaf = (inputs: Inputs.inputs) =>
|
||||||
Parser.fromString(inputs.squiggleString) |> E.R.bind(_, g => runProgram(inputs, g))
|
Parser.fromString(inputs.squiggleString) |> E.R.bind(_, g => runProgram(inputs, g))
|
||||||
|
|
||||||
let outputToDistPlus = (inputs: Inputs.inputs, pointSetDist: PointSetTypes.pointSetDist) =>
|
|
||||||
DistPlus.make(~pointSetDist, ~squiggleString=Some(inputs.squiggleString), ())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let renderIfNeeded = (inputs: Inputs.inputs, node: ASTTypes.node): result<
|
|
||||||
ASTTypes.node,
|
|
||||||
string,
|
|
||||||
> =>
|
|
||||||
node |> (
|
|
||||||
x =>
|
|
||||||
switch x {
|
|
||||||
| #Normalize(_) as n
|
|
||||||
| #SymbolicDist(_) as n =>
|
|
||||||
#Render(n)
|
|
||||||
|> Internals.runNode(inputs)
|
|
||||||
|> (
|
|
||||||
x =>
|
|
||||||
switch x {
|
|
||||||
| Ok(#RenderedDist(_)) as r => r
|
|
||||||
| Error(r) => Error(r)
|
|
||||||
| _ => Error("Didn't render, but intended to")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
| n => Ok(n)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
let rec returnDist = (functionInfo : (array<string>, ASTTypes.node),
|
|
||||||
inputs : Inputs.inputs,
|
|
||||||
env : ASTTypes.environment) => {
|
|
||||||
(input : float) => {
|
|
||||||
let foo: Inputs.inputs = {...inputs, environment: env};
|
|
||||||
evaluateFunction(
|
|
||||||
foo,
|
|
||||||
functionInfo,
|
|
||||||
[#SymbolicDist(#Float(input))],
|
|
||||||
) |> E.R.bind(_, a =>
|
|
||||||
switch a {
|
|
||||||
| #DistPlus(d) => Ok(DistPlus.T.normalize(d))
|
|
||||||
| n =>
|
|
||||||
Js.log2("Error here", n)
|
|
||||||
Error("wrong type")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: Consider using ExpressionTypes.ExpressionTree.getFloat or similar in this function
|
|
||||||
and coersionToExportedTypes = (
|
|
||||||
inputs,
|
|
||||||
env: ASTTypes.environment,
|
|
||||||
node: ASTTypes.node,
|
|
||||||
): result<exportType, string> =>
|
|
||||||
node
|
|
||||||
|> renderIfNeeded(inputs)
|
|
||||||
|> E.R.bind(_, x =>
|
|
||||||
switch x {
|
|
||||||
| #RenderedDist(Discrete({xyShape: {xs: [x], ys: [1.0]}})) => Ok(#Float(x))
|
|
||||||
| #SymbolicDist(#Float(x)) => Ok(#Float(x))
|
|
||||||
| #RenderedDist(n) => Ok(#DistPlus(Internals.outputToDistPlus(inputs, n)))
|
|
||||||
| #Function(n) => Ok(#Function(returnDist(n, inputs, env)))
|
|
||||||
| n => Error("Didn't output a rendered distribution. Format:" ++ AST.toString(n))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
and evaluateFunction = (
|
|
||||||
inputs: Inputs.inputs,
|
|
||||||
fn: (array<string>, ASTTypes.node),
|
|
||||||
fnInputs,
|
|
||||||
) => {
|
|
||||||
let output = AST.runFunction(
|
|
||||||
Internals.makeInputs(inputs),
|
|
||||||
inputs.environment,
|
|
||||||
fnInputs,
|
|
||||||
fn,
|
|
||||||
)
|
|
||||||
output |> E.R.bind(_, coersionToExportedTypes(inputs, inputs.environment))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let rec mapM = (f, xs) =>
|
|
||||||
switch xs {
|
|
||||||
| [] => Ok([])
|
|
||||||
| arr =>
|
|
||||||
switch f(arr[0]) {
|
|
||||||
| Error(err) => Error(err)
|
|
||||||
| Ok(val) =>
|
|
||||||
switch mapM(f, Belt.Array.sliceToEnd(arr, 1)) {
|
|
||||||
| Error(err) => Error(err)
|
|
||||||
| Ok(restList) => Ok(Belt.Array.concat([val], restList))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let evaluateProgram = (inputs: Inputs.inputs) =>
|
|
||||||
inputs
|
|
||||||
|> Internals.inputsToLeaf
|
|
||||||
|> E.R.bind(_, xs => mapM(((a, b)) => coersionToExportedTypes(inputs, a, b), xs))
|
|
||||||
|
|
||||||
|
|
||||||
@genType
|
@genType
|
||||||
let runAll = (squiggleString: string, samplingInputs: Inputs.SamplingInputs.t) => {
|
let runAll : (string, Inputs.SamplingInputs.t, exportEnv) => result<exportType,string> =
|
||||||
|
(squiggleString, samplingInputs, environment) => {
|
||||||
let inputs = Inputs.make(
|
let inputs = Inputs.make(
|
||||||
~samplingInputs,
|
~samplingInputs,
|
||||||
~squiggleString,
|
~squiggleString,
|
||||||
~environment=[]->Belt.Map.String.fromArray,
|
~environment=Belt.Map.String.fromArray(environment),
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
let response1 = evaluateProgram(inputs);
|
Internals.inputsToLeaf(inputs)
|
||||||
response1
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
@genType
|
||||||
type rec hash = array<(string, node)>
|
type rec hash = array<(string, node)>
|
||||||
and node = [
|
and node = [
|
||||||
| #SymbolicDist(SymbolicDistTypes.symbolicDist)
|
| #SymbolicDist(SymbolicDistTypes.symbolicDist)
|
||||||
|
|
|
@ -31,6 +31,7 @@ type triangular = {
|
||||||
high: float,
|
high: float,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@genType
|
||||||
type symbolicDist = [
|
type symbolicDist = [
|
||||||
| #Normal(normal)
|
| #Normal(normal)
|
||||||
| #Beta(beta)
|
| #Beta(beta)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// This file has no dependencies. It's used outside of the interpreter, but the interpreter depends on it.
|
// This file has no dependencies. It's used outside of the interpreter, but the interpreter depends on it.
|
||||||
|
|
||||||
|
@genType
|
||||||
type algebraicOperation = [
|
type algebraicOperation = [
|
||||||
| #Add
|
| #Add
|
||||||
| #Multiply
|
| #Multiply
|
||||||
|
@ -7,6 +8,7 @@ type algebraicOperation = [
|
||||||
| #Divide
|
| #Divide
|
||||||
| #Exponentiate
|
| #Exponentiate
|
||||||
]
|
]
|
||||||
|
@genType
|
||||||
type pointwiseOperation = [#Add | #Multiply | #Exponentiate]
|
type pointwiseOperation = [#Add | #Multiply | #Exponentiate]
|
||||||
type scaleOperation = [#Multiply | #Exponentiate | #Log]
|
type scaleOperation = [#Multiply | #Exponentiate | #Log]
|
||||||
type distToFloatOperation = [
|
type distToFloatOperation = [
|
||||||
|
|
|
@ -14370,6 +14370,11 @@ prepend-http@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18"
|
||||||
integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==
|
integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==
|
||||||
|
|
||||||
|
prettier@^2.6.0:
|
||||||
|
version "2.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.0.tgz#12f8f504c4d8ddb76475f441337542fa799207d4"
|
||||||
|
integrity sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==
|
||||||
|
|
||||||
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
|
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
||||||
|
@ -18696,7 +18701,7 @@ webpack-dev-middleware@^5.3.1:
|
||||||
range-parser "^1.2.1"
|
range-parser "^1.2.1"
|
||||||
schema-utils "^4.0.0"
|
schema-utils "^4.0.0"
|
||||||
|
|
||||||
webpack-dev-server@^4.6.0, webpack-dev-server@^4.7.1:
|
webpack-dev-server@^4.6.0, webpack-dev-server@^4.7.1, webpack-dev-server@^4.7.4:
|
||||||
version "4.7.4"
|
version "4.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz#d0ef7da78224578384e795ac228d8efb63d5f945"
|
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz#d0ef7da78224578384e795ac228d8efb63d5f945"
|
||||||
integrity sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A==
|
integrity sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A==
|
||||||
|
|
Loading…
Reference in New Issue
Block a user