Merge pull request #53 from QURIresearch/notebook

Add squiggle notebooks
This commit is contained in:
Ozzie Gooen 2022-03-24 11:10:12 -04:00 committed by GitHub
commit 3449c93bcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 608 additions and 57449 deletions

View File

@ -0,0 +1,5 @@
dist
build
node_modules
storybook-static
.storybook

View File

@ -0,0 +1 @@
{}

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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>

View File

@ -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); let result = run(props.squiggleString, samplingInputs, props.environment);
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 };
} }

View 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;
}

View File

@ -1 +1,2 @@
export { SquiggleChart } from './SquiggleChart'; export { SquiggleChart } from "./SquiggleChart";
export { SquiggleEditor, renderSquiggleEditor } from "./SquiggleEditor";

View File

@ -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" }
} }
} }
} }

View File

@ -1,8 +1,8 @@
import { Meta } from '@storybook/addon-docs'; import { Meta } from "@storybook/addon-docs";
<Meta title="Squiggle/Introduction" /> <Meta title="Squiggle/Introduction" />
This is the component library for Squiggle. All of these components are react This is the component library for Squiggle. All of these components are react
components, and can be used in any application that you see fit. components, and can be used in any application that you see fit.
Currently, the only component that is provided is the SquiggleChart component. Currently, the only component that is provided is the SquiggleChart component.

View File

@ -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>

View File

@ -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,
},
}; };

View File

@ -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" })
}) })
}); });

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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)
@ -229,4 +230,4 @@ module SamplingDistribution = {
pointSetDist |> E.R.fmap(r => #Normalize(#RenderedDist(r))) pointSetDist |> E.R.fmap(r => #Normalize(#RenderedDist(r)))
}) })
} }
} }

View File

@ -31,6 +31,7 @@ type triangular = {
high: float, high: float,
} }
@genType
type symbolicDist = [ type symbolicDist = [
| #Normal(normal) | #Normal(normal)
| #Beta(beta) | #Beta(beta)

View File

@ -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 = [
@ -109,4 +111,4 @@ module Truncate = {
let right = right |> E.O.dimap(Js.Float.toString, () => "inf") let right = right |> E.O.dimap(Js.Float.toString, () => "inf")
j`truncate($nodeToString, $left, $right)` j`truncate($nodeToString, $left, $right)`
} }
} }

View File

@ -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==