34
README.md
|
@ -2,13 +2,31 @@
|
|||
|
||||
This is an experiment DSL/language for making probabilistic estimates.
|
||||
|
||||
## DistPlus
|
||||
We have a custom library called DistPlus to handle distributions with additional metadata. This helps handle mixed distributions (continuous + discrete), a cache for a cdf, possible unit types (specific times are supported), and limited domains.
|
||||
This monorepo has several packages that can be used for various purposes. All
|
||||
the packages can be found in `packages`.
|
||||
|
||||
## Running packages in the monorepo
|
||||
This application uses `lerna` to manage dependencies between packages. To install
|
||||
dependencies of all packages, run:
|
||||
`@squiggle/lang` in `packages/squiggle-lang` contains the core language, particularly
|
||||
an interface to parse squiggle expressions and return descriptions of distributions
|
||||
or results.
|
||||
|
||||
`@squiggle/components` in `packages/components` contains React components that
|
||||
can be passed squiggle strings as props, and return a presentation of the result
|
||||
of the calculation.
|
||||
|
||||
`@squiggle/playground` in `packages/playground` contains a website for a playground
|
||||
for squiggle. This website is hosted at `playground.squiggle-language.com`
|
||||
|
||||
`@squiggle/website` in `packages/website` The main descriptive website for squiggle,
|
||||
it is hosted at `squiggle-language.com`.
|
||||
|
||||
The playground depends on the components library which then depends on the language.
|
||||
This means that if you wish to work on the components library, you will need
|
||||
to package the language, and for the playground to work, you will need to package
|
||||
the components library and the playground.
|
||||
|
||||
Scripts are available for you in the root directory to do important activities,
|
||||
such as:
|
||||
|
||||
`yarn build:lang`. Builds and packages the language
|
||||
`yarn storybook:components`. Hosts the component storybook
|
||||
|
||||
```
|
||||
lerna bootstrap
|
||||
```
|
||||
|
|
|
@ -4,5 +4,11 @@
|
|||
"lerna": "^4.0.0"
|
||||
},
|
||||
"name": "squiggle",
|
||||
"scripts": {
|
||||
"build:lang": "cd packages/squiggle-lang && yarn && yarn build && yarn package",
|
||||
"storybook:components": "cd packages/components && yarn && yarn storybook",
|
||||
"build-storybook:components": "cd packages/components && yarn && yarn build-storybook",
|
||||
"build:components": "cd packages/components && yarn && yarn package"
|
||||
},
|
||||
"workspaces": ["packages/*"]
|
||||
}
|
||||
|
|
25
packages/components/.gitignore
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
storybook-static
|
||||
dist
|
26
packages/components/.storybook/main.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
//const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
/* webpackFinal: async (config) => {
|
||||
config.resolve.plugins = [
|
||||
...(config.resolve.plugins || []),
|
||||
new TsconfigPathsPlugin({
|
||||
extensions: config.resolve.extensions,
|
||||
}),
|
||||
];
|
||||
return config;
|
||||
},*/
|
||||
"stories": [
|
||||
"../src/**/*.stories.mdx",
|
||||
"../src/**/*.stories.@(js|jsx|ts|tsx)"
|
||||
],
|
||||
"addons": [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/preset-create-react-app"
|
||||
],
|
||||
"framework": "@storybook/react",
|
||||
"core": {
|
||||
"builder": "webpack5"
|
||||
}
|
||||
}
|
9
packages/components/.storybook/preview.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
export const parameters = {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
}
|
6
packages/components/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Squiggle Components
|
||||
|
||||
This package contains all the components for squiggle. These can be used either
|
||||
as a library or hosted as a [storybook](https://storybook.js.org/).
|
||||
|
||||
To run the storybook, run `yarn` then `yarn storybook`.
|
57037
packages/components/package-lock.json
generated
Normal file
73
packages/components/package.json
Normal file
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"name": "@squiggle/components",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@squiggle/lang": "^0.1.9",
|
||||
"@testing-library/jest-dom": "^5.16.2",
|
||||
"@testing-library/react": "^12.1.2",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@types/lodash": "^4.14.178",
|
||||
"@types/node": "^17.0.16",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"cross-env": "^7.0.3",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "5.0.0",
|
||||
"react-vega": "^7.4.4",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||
"typescript": "^4.5.5",
|
||||
"vega-embed": "^6.20.6",
|
||||
"web-vitals": "^2.1.4",
|
||||
"webpack-cli": "^4.9.2"
|
||||
},
|
||||
"scripts": {
|
||||
"storybook": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
||||
"build-storybook": "build-storybook -s public",
|
||||
"package": "tsc"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"**/*.stories.*"
|
||||
],
|
||||
"rules": {
|
||||
"import/no-anonymous-default-export": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-actions": "^6.4.18",
|
||||
"@storybook/addon-essentials": "^6.4.18",
|
||||
"@storybook/addon-links": "^6.4.18",
|
||||
"@storybook/builder-webpack5": "^6.4.18",
|
||||
"@storybook/manager-webpack5": "^6.4.18",
|
||||
"@storybook/node-logger": "^6.4.18",
|
||||
"@storybook/preset-create-react-app": "^4.0.0",
|
||||
"@storybook/react": "^6.4.18",
|
||||
"webpack": "^5.68.0"
|
||||
},
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts"
|
||||
}
|
BIN
packages/components/public/favicon.ico
Normal file
After Width: | Height: | Size: 13 KiB |
19
packages/components/public/index.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Squiggle components"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<title>Squiggle Components</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
BIN
packages/components/public/logo16.png
Normal file
After Width: | Height: | Size: 327 B |
BIN
packages/components/public/logo192.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
packages/components/public/logo32.png
Normal file
After Width: | Height: | Size: 697 B |
BIN
packages/components/public/logo42.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
packages/components/public/logo512.png
Normal file
After Width: | Height: | Size: 31 KiB |
25
packages/components/public/manifest.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
packages/components/public/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
676
packages/components/public/squiggle.svg
Normal file
After Width: | Height: | Size: 107 KiB |
5
packages/components/shell.nix
Normal file
|
@ -0,0 +1,5 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
pkgs.mkShell {
|
||||
name = "squiggle-components";
|
||||
buildInputs = with pkgs; [ nodePackages.yarn nodejs ];
|
||||
}
|
1
packages/components/src/SquiggleChart.js.map
Normal file
344
packages/components/src/SquiggleChart.tsx
Normal file
|
@ -0,0 +1,344 @@
|
|||
import * as React from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import * as _ from 'lodash';
|
||||
import type { Spec } from 'vega';
|
||||
import { run } from '@squiggle/lang';
|
||||
import type { DistPlus } from '@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 SquigglePercentilesChart = createClassFromSpec({'spec': percentilesSpec as Spec});
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const SquiggleChart = ({ squiggleString }: { squiggleString: string}) => {
|
||||
|
||||
let result = run(squiggleString);
|
||||
console.log(result)
|
||||
if (result.tag === "Ok") {
|
||||
let chartResults = result.value.map(chartResult => {
|
||||
console.log(chartResult)
|
||||
if(chartResult["NAME"] === "Float"){
|
||||
return <MakeNumberShower precision={3} number={chartResult["VAL"]} />;
|
||||
}
|
||||
else if(chartResult["NAME"] === "DistPlus"){
|
||||
let shape = chartResult.VAL.pointSetDist;
|
||||
if(shape.tag === "Continuous"){
|
||||
let xyShape = shape.value.xyShape;
|
||||
let totalY = xyShape.ys.reduce((a, b) => a + b);
|
||||
let total = 0;
|
||||
let cdf = xyShape.ys.map(y => {
|
||||
total += y;
|
||||
return total / totalY;
|
||||
})
|
||||
let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y ]) => ({cdf: (c * 100).toFixed(2) + "%", x: x, y: y}));
|
||||
|
||||
return (
|
||||
<SquiggleVegaChart
|
||||
data={{"con": values}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
else if(shape.tag === "Discrete"){
|
||||
let xyShape = shape.value.xyShape;
|
||||
let totalY = xyShape.ys.reduce((a, b) => a + b);
|
||||
let total = 0;
|
||||
let cdf = xyShape.ys.map(y => {
|
||||
total += y;
|
||||
return total / totalY;
|
||||
})
|
||||
let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x,y]) => ({cdf: (c * 100).toFixed(2) + "%", x: x, y: y}));
|
||||
|
||||
return (
|
||||
<SquiggleVegaChart
|
||||
data={{"dis": values}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
else if(shape.tag === "Mixed"){
|
||||
let discreteShape = shape.value.discrete.xyShape;
|
||||
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
||||
|
||||
let discretePoints = _.zip(discreteShape.xs, discreteShape.ys);
|
||||
let continuousShape = shape.value.continuous.xyShape;
|
||||
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
|
||||
|
||||
interface labeledPoint {
|
||||
x: number,
|
||||
y: number,
|
||||
type: "discrete" | "continuous"
|
||||
};
|
||||
|
||||
let markedDisPoints : labeledPoint[] = discretePoints.map(([x,y]) => ({x: x, y: y, type: "discrete"}))
|
||||
let markedConPoints : labeledPoint[] = continuousPoints.map(([x,y]) => ({x: x, y: y, type: "continuous"}))
|
||||
|
||||
let sortedPoints = _.sortBy(markedDisPoints.concat(markedConPoints), 'x')
|
||||
|
||||
let totalContinuous = 1 - totalDiscrete;
|
||||
let totalY = continuousShape.ys.reduce((a:number, b:number) => a + b);
|
||||
|
||||
let total = 0;
|
||||
let cdf = sortedPoints.map((point: labeledPoint) => {
|
||||
if(point.type == "discrete") {
|
||||
total += point.y;
|
||||
return total;
|
||||
}
|
||||
else if (point.type == "continuous") {
|
||||
total += point.y / totalY * totalContinuous;
|
||||
return total;
|
||||
}
|
||||
});
|
||||
|
||||
interface cdfLabeledPoint {
|
||||
cdf: string,
|
||||
x: number,
|
||||
y: number,
|
||||
type: "discrete" | "continuous"
|
||||
}
|
||||
let cdfLabeledPoint : cdfLabeledPoint[] = _.zipWith(cdf, sortedPoints, (c: number, point: labeledPoint) => ({...point, cdf: (c * 100).toFixed(2) + "%"}))
|
||||
let continuousValues = cdfLabeledPoint.filter(x => x.type == "continuous")
|
||||
let discreteValues = cdfLabeledPoint.filter(x => x.type == "discrete")
|
||||
|
||||
return (
|
||||
<SquiggleVegaChart
|
||||
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 data = _.range(0,10,0.1).map((_,i) => {
|
||||
let x = i /10;
|
||||
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>)
|
||||
};
|
||||
|
||||
function getPercentiles(percentiles:number[], t : DistPlus) {
|
||||
if(t.pointSetDist.tag == "Discrete") {
|
||||
let total = 0;
|
||||
let maxX = _.max(t.pointSetDist.value.xyShape.xs)
|
||||
let bounds = percentiles.map(_ => maxX);
|
||||
_.zipWith(t.pointSetDist.value.xyShape.xs,t.pointSetDist.value.xyShape.ys, (x,y) => {
|
||||
total += y
|
||||
percentiles.forEach((v, i) => {
|
||||
if(total > v && bounds[i] == maxX){
|
||||
bounds[i] = x
|
||||
}
|
||||
})
|
||||
});
|
||||
return bounds;
|
||||
}
|
||||
else if(t.pointSetDist.tag == "Continuous"){
|
||||
let total = 0;
|
||||
let maxX = _.max(t.pointSetDist.value.xyShape.xs)
|
||||
let totalY = _.sum(t.pointSetDist.value.xyShape.ys)
|
||||
let bounds = percentiles.map(_ => maxX);
|
||||
_.zipWith(t.pointSetDist.value.xyShape.xs,t.pointSetDist.value.xyShape.ys, (x,y) => {
|
||||
total += y / totalY;
|
||||
percentiles.forEach((v, i) => {
|
||||
if(total > v && bounds[i] == maxX){
|
||||
bounds[i] = x
|
||||
}
|
||||
})
|
||||
});
|
||||
return bounds;
|
||||
}
|
||||
else if(t.pointSetDist.tag == "Mixed"){
|
||||
let discreteShape = t.pointSetDist.value.discrete.xyShape;
|
||||
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
||||
|
||||
let discretePoints = _.zip(discreteShape.xs, discreteShape.ys);
|
||||
let continuousShape = t.pointSetDist.value.continuous.xyShape;
|
||||
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
|
||||
|
||||
interface labeledPoint {
|
||||
x: number,
|
||||
y: number,
|
||||
type: "discrete" | "continuous"
|
||||
};
|
||||
|
||||
let markedDisPoints : labeledPoint[] = discretePoints.map(([x,y]) => ({x: x, y: y, type: "discrete"}))
|
||||
let markedConPoints : labeledPoint[] = continuousPoints.map(([x,y]) => ({x: x, y: y, type: "continuous"}))
|
||||
|
||||
let sortedPoints = _.sortBy(markedDisPoints.concat(markedConPoints), 'x')
|
||||
|
||||
let totalContinuous = 1 - totalDiscrete;
|
||||
let totalY = continuousShape.ys.reduce((a:number, b:number) => a + b);
|
||||
|
||||
let total = 0;
|
||||
let maxX = _.max(sortedPoints.map(x => x.x));
|
||||
let bounds = percentiles.map(_ => maxX);
|
||||
sortedPoints.map((point: labeledPoint) => {
|
||||
if(point.type == "discrete") {
|
||||
total += point.y;
|
||||
}
|
||||
else if (point.type == "continuous") {
|
||||
total += point.y / totalY * totalContinuous;
|
||||
}
|
||||
percentiles.forEach((v,i) => {
|
||||
if(total > v && bounds[i] == maxX){
|
||||
bounds[i] = total;
|
||||
}
|
||||
})
|
||||
return total;
|
||||
});
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
SquiggleChart.propTypes = {
|
||||
/**
|
||||
* Squiggle String
|
||||
*/
|
||||
squiggleString : PropTypes.string
|
||||
};
|
||||
|
||||
SquiggleChart.defaultProps = {
|
||||
squggleString: "normal(5, 2)"
|
||||
|
||||
};
|
||||
|
||||
|
||||
function MakeNumberShower(props: {number: number, precision :number}){
|
||||
let numberWithPresentation = numberShow(props.number, props.precision);
|
||||
return (
|
||||
<span>
|
||||
{numberWithPresentation.value}
|
||||
{numberWithPresentation.symbol}
|
||||
{numberWithPresentation.power ?
|
||||
<span>
|
||||
{'\u00b710'}
|
||||
<span style={{fontSize: "0.6em", verticalAlign: "super"}}>
|
||||
{numberWithPresentation.power}
|
||||
</span>
|
||||
</span>
|
||||
: <></>}
|
||||
</span>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
const orderOfMagnitudeNum = (n:number) => {
|
||||
return Math.pow(10, n);
|
||||
};
|
||||
|
||||
// 105 -> 3
|
||||
const orderOfMagnitude = (n:number) => {
|
||||
return Math.floor(Math.log(n) / Math.LN10 + 0.000000001);
|
||||
};
|
||||
|
||||
function withXSigFigs(number:number, sigFigs:number) {
|
||||
const withPrecision = number.toPrecision(sigFigs);
|
||||
const formatted = Number(withPrecision);
|
||||
return `${formatted}`;
|
||||
}
|
||||
|
||||
class NumberShower {
|
||||
number: number
|
||||
precision: number
|
||||
|
||||
constructor(number:number, precision = 2) {
|
||||
this.number = number;
|
||||
this.precision = precision;
|
||||
}
|
||||
|
||||
convert() {
|
||||
const number = Math.abs(this.number);
|
||||
const response = this.evaluate(number);
|
||||
if (this.number < 0) {
|
||||
response.value = '-' + response.value;
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
metricSystem(number: number, order: number) {
|
||||
const newNumber = number / orderOfMagnitudeNum(order);
|
||||
const precision = this.precision;
|
||||
return `${withXSigFigs(newNumber, precision)}`;
|
||||
}
|
||||
|
||||
evaluate(number: number) {
|
||||
if (number === 0) {
|
||||
return { value: this.metricSystem(0, 0) }
|
||||
}
|
||||
|
||||
const order = orderOfMagnitude(number);
|
||||
if (order < -2) {
|
||||
return { value: this.metricSystem(number, order), power: order };
|
||||
} else if (order < 4) {
|
||||
return { value: this.metricSystem(number, 0) };
|
||||
} else if (order < 6) {
|
||||
return { value: this.metricSystem(number, 3), symbol: 'K' };
|
||||
} else if (order < 9) {
|
||||
return { value: this.metricSystem(number, 6), symbol: 'M' };
|
||||
} else if (order < 12) {
|
||||
return { value: this.metricSystem(number, 9), symbol: 'B' };
|
||||
} else if (order < 15) {
|
||||
return { value: this.metricSystem(number, 12), symbol: 'T' };
|
||||
} else {
|
||||
return { value: this.metricSystem(number, order), power: order };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function numberShow(number: number, precision = 2) {
|
||||
const ns = new NumberShower(number, precision);
|
||||
return ns.convert();
|
||||
}
|
1
packages/components/src/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { SquiggleChart } from './SquiggleChart';
|
122
packages/components/src/spec-distributions.json
Normal file
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
||||
"description": "A basic area chart example.",
|
||||
"width": 500,
|
||||
"height": 200,
|
||||
"padding": 5,
|
||||
"data": [{"name": "con"}, {"name": "dis"}],
|
||||
|
||||
"signals": [
|
||||
{
|
||||
"name": "mousex",
|
||||
"description": "x position of mouse",
|
||||
"update": "0",
|
||||
"on": [{"events": "mousemove", "update": "1-x()/width"}]
|
||||
},
|
||||
{
|
||||
"name": "xscale",
|
||||
"description": "The transform of the x scale",
|
||||
"value": 1.0,
|
||||
"bind": {
|
||||
"input": "range",
|
||||
"min": 0.1,
|
||||
"max": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "yscale",
|
||||
"description": "The transform of the y scale",
|
||||
"value": 1.0,
|
||||
"bind": {
|
||||
"input": "range",
|
||||
"min": 0.1,
|
||||
"max": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"scales": [{
|
||||
"name": "xscale",
|
||||
"type": "pow",
|
||||
"exponent": {"signal": "xscale"},
|
||||
"range": "width",
|
||||
"zero": false,
|
||||
"nice": false,
|
||||
"domain": {
|
||||
"fields": [
|
||||
{ "data": "con", "field": "x"},
|
||||
{ "data": "dis", "field": "x"}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "yscale",
|
||||
"type": "pow",
|
||||
"exponent": {"signal": "yscale"},
|
||||
"range": "height",
|
||||
"nice": true,
|
||||
"zero": true,
|
||||
"domain": {
|
||||
"fields": [
|
||||
{ "data": "con", "field": "y"},
|
||||
{ "data": "dis", "field": "y"}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"axes": [
|
||||
{"orient": "bottom", "scale": "xscale", "tickCount": 20},
|
||||
{"orient": "left", "scale": "yscale"}
|
||||
],
|
||||
|
||||
"marks": [
|
||||
{
|
||||
"type": "area",
|
||||
"from": {"data": "con"},
|
||||
"encode": {
|
||||
"enter": {
|
||||
"tooltip": {"signal": "datum.cdf"}
|
||||
},
|
||||
"update": {
|
||||
"x": {"scale": "xscale", "field": "x"},
|
||||
"y": {"scale": "yscale", "field": "y"},
|
||||
"y2": {"scale": "yscale", "value": 0},
|
||||
"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'} ] }"
|
||||
},
|
||||
"interpolate": {"value": "monotone"},
|
||||
"fillOpacity": {"value": 1}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "rect",
|
||||
"from": {"data": "dis"},
|
||||
"encode": {
|
||||
"enter": {
|
||||
"y2": {"scale": "yscale", "value": 0},
|
||||
"width": {"value": 1}
|
||||
},
|
||||
"update": {
|
||||
"x": {"scale": "xscale", "field": "x"},
|
||||
"y": {"scale": "yscale", "field": "y"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "symbol",
|
||||
"from": {"data": "dis"},
|
||||
"encode": {
|
||||
"enter": {
|
||||
"shape": {"value": "circle"},
|
||||
"width": {"value": 5},
|
||||
"tooltip": {"signal": "datum.y"}
|
||||
},
|
||||
"update": {
|
||||
"x": {"scale": "xscale", "field": "x"},
|
||||
"y": {"scale": "yscale", "field": "y"}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
208
packages/components/src/spec-pertentiles.json
Normal file
|
@ -0,0 +1,208 @@
|
|||
{
|
||||
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
||||
"width": 500,
|
||||
"height": 400,
|
||||
"padding": 5,
|
||||
"data": [
|
||||
{
|
||||
"name": "facet",
|
||||
"values": [],
|
||||
"format": { "type": "json", "parse": { "timestamp": "date" } }
|
||||
},
|
||||
{
|
||||
"name": "table",
|
||||
"source": "facet",
|
||||
"transform": [
|
||||
{
|
||||
"type": "aggregate",
|
||||
"groupby": ["x"],
|
||||
"ops": [
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean"
|
||||
],
|
||||
"fields": [
|
||||
"p1",
|
||||
"p5",
|
||||
"p10",
|
||||
"p20",
|
||||
"p30",
|
||||
"p40",
|
||||
"p50",
|
||||
"p60",
|
||||
"p70",
|
||||
"p80",
|
||||
"p90",
|
||||
"p95",
|
||||
"p99"
|
||||
],
|
||||
"as": [
|
||||
"p1",
|
||||
"p5",
|
||||
"p10",
|
||||
"p20",
|
||||
"p30",
|
||||
"p40",
|
||||
"p50",
|
||||
"p60",
|
||||
"p70",
|
||||
"p80",
|
||||
"p90",
|
||||
"p95",
|
||||
"p99"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"scales": [
|
||||
{
|
||||
"name": "xscale",
|
||||
"type": "linear",
|
||||
"nice": true,
|
||||
"domain": { "data": "facet", "field": "x" },
|
||||
"range": "width"
|
||||
},
|
||||
{
|
||||
"name": "yscale",
|
||||
"type": "linear",
|
||||
"range": "height",
|
||||
"nice": true,
|
||||
"zero": true,
|
||||
"domain": { "data": "facet", "field": "p99" }
|
||||
}
|
||||
],
|
||||
"axes": [
|
||||
{
|
||||
"orient": "bottom",
|
||||
"scale": "xscale",
|
||||
"grid": false,
|
||||
"tickSize": 2,
|
||||
"encode": {
|
||||
"grid": { "enter": { "stroke": { "value": "#ccc" } } },
|
||||
"ticks": { "enter": { "stroke": { "value": "#ccc" } } }
|
||||
}
|
||||
},
|
||||
{
|
||||
"orient": "left",
|
||||
"scale": "yscale",
|
||||
"grid": false,
|
||||
"domain": false,
|
||||
"tickSize": 2,
|
||||
"encode": {
|
||||
"grid": { "enter": { "stroke": { "value": "#ccc" } } },
|
||||
"ticks": { "enter": { "stroke": { "value": "#ccc" } } }
|
||||
}
|
||||
}
|
||||
],
|
||||
"marks": [
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"enter": { "fill": { "value": "#4C78A8" } },
|
||||
"update": {
|
||||
"interpolate": { "value": "monotone" },
|
||||
"x": { "scale": "xscale", "field": "x" },
|
||||
"y": { "scale": "yscale", "field": "p1" },
|
||||
"y2": { "scale": "yscale", "field": "p99" },
|
||||
"opacity": { "value": 0.05 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"enter": { "fill": { "value": "#4C78A8" } },
|
||||
"update": {
|
||||
"interpolate": { "value": "monotone" },
|
||||
"x": { "scale": "xscale", "field": "x" },
|
||||
"y": { "scale": "yscale", "field": "p5" },
|
||||
"y2": { "scale": "yscale", "field": "p95" },
|
||||
"opacity": { "value": 0.1 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"enter": { "fill": { "value": "#4C78A8" } },
|
||||
"update": {
|
||||
"interpolate": { "value": "monotone" },
|
||||
"x": { "scale": "xscale", "field": "x" },
|
||||
"y": { "scale": "yscale", "field": "p10" },
|
||||
"y2": { "scale": "yscale", "field": "p90" },
|
||||
"opacity": { "value": 0.15 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"enter": { "fill": { "value": "#4C78A8" } },
|
||||
"update": {
|
||||
"interpolate": { "value": "monotone" },
|
||||
"x": { "scale": "xscale", "field": "x" },
|
||||
"y": { "scale": "yscale", "field": "p20" },
|
||||
"y2": { "scale": "yscale", "field": "p80" },
|
||||
"opacity": { "value": 0.2 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"enter": { "fill": { "value": "#4C78A8" } },
|
||||
"update": {
|
||||
"interpolate": { "value": "monotone" },
|
||||
"x": { "scale": "xscale", "field": "x" },
|
||||
"y": { "scale": "yscale", "field": "p30" },
|
||||
"y2": { "scale": "yscale", "field": "p70" },
|
||||
"opacity": { "value": 0.2 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"enter": { "fill": { "value": "#4C78A8" } },
|
||||
"update": {
|
||||
"interpolate": { "value": "monotone" },
|
||||
"x": { "scale": "xscale", "field": "x" },
|
||||
"y": { "scale": "yscale", "field": "p40" },
|
||||
"y2": { "scale": "yscale", "field": "p60" },
|
||||
"opacity": { "value": 0.2 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"update": {
|
||||
"interpolate": { "value": "monotone" },
|
||||
"stroke": { "value": "#4C78A8" },
|
||||
"strokeWidth": { "value": 2 },
|
||||
"opacity": { "value": 0.8 },
|
||||
"x": { "scale": "xscale", "field": "x" },
|
||||
"y": { "scale": "yscale", "field": "p50" }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
9
packages/components/src/stories/Introduction.stories.mdx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Squiggle/Introduction" />
|
||||
|
||||
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.
|
||||
|
||||
Currently, the only component that is provided is the SquiggleChart component.
|
||||
This component allows you to render the result of a squiggle expression.
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"SquiggleChart.stories.js","sourceRoot":"","sources":["SquiggleChart.stories.tsx"],"names":[],"mappings":";;;AAAA,6BAA8B;AAC9B,iDAA+C;AAG/C,qBAAe;IACb,KAAK,EAAE,uBAAuB;IAC9B,SAAS,EAAE,6BAAa;CACzB,CAAA;AAED,IAAM,QAAQ,GAAG,UAAC,EAAgB;QAAf,cAAc,oBAAA;IAAM,OAAA,oBAAC,6BAAa,IAAC,cAAc,EAAE,cAAc,GAAI;AAAjD,CAAiD,CAAA;AAE3E,QAAA,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACxC,eAAO,CAAC,IAAI,GAAG;IACb,cAAc,EAAE,cAAc;CAC/B,CAAC"}
|
81
packages/components/src/stories/SquiggleChart.stories.mdx
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { SquiggleChart } from '../SquiggleChart'
|
||||
import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Squiggle/SquiggleChart" component={ SquiggleChart } />
|
||||
|
||||
export const Template = ({squiggleString}) => <SquiggleChart squiggleString={squiggleString} />
|
||||
|
||||
# Squiggle Chart
|
||||
|
||||
Squiggle chart evaluates squiggle expressions, and then returns a graph representing
|
||||
the result of a squiggle expression.
|
||||
|
||||
A squiggle expression can have three different types of returns. A distribution,
|
||||
a constant, and a function.
|
||||
|
||||
A distribution means that the result forms a probability distribution. This
|
||||
could be continuous, discrete or mixed.
|
||||
|
||||
## Distributions
|
||||
|
||||
An example of a normal distribution is:
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Normal"
|
||||
args={{
|
||||
squiggleString: "normal(5,2)"
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
|
||||
An example of a Discrete distribution is:
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Discrete"
|
||||
args={{
|
||||
squiggleString: "mm(0, 1, [0.5, 0.5])"
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
An example of a Mixed distribution is:
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Mixed"
|
||||
args={{
|
||||
squiggleString: "mm(0, 5 to 10, [0.5, 0.5])"
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Constants
|
||||
A constant is a simple number as a result. This has special formatting rules
|
||||
to allow large and small numbers being printed cleanly.
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Constant"
|
||||
args={{
|
||||
squiggleString: "500000 * 5000000"
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Functions
|
||||
Finally, a function can be returned, and this shows how the distribution changes
|
||||
over the axis between x = 0 and 10.
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Function"
|
||||
args={{
|
||||
squiggleString: "f(x) = normal(x,x)\nf"
|
||||
}}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
17
packages/components/tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"jsx": "react",
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"resolveJsonModule": true,
|
||||
"outDir": "./dist",
|
||||
"declarationDir": "./dist",
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"target": "ES6",
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "**/*.spec.ts"]
|
||||
}
|