Compare commits
No commits in common. "develop" and "documentation-refactors-april" have entirely different histories.
develop
...
documentat
20
.github/CODEOWNERS
vendored
20
.github/CODEOWNERS
vendored
|
@ -9,22 +9,22 @@
|
||||||
# This also holds true for GitHub teams.
|
# This also holds true for GitHub teams.
|
||||||
|
|
||||||
# Rescript
|
# Rescript
|
||||||
*.res @berekuk @OAGr
|
*.res @OAGr @quinn-dougherty
|
||||||
*.resi @berekuk @OAGr
|
*.resi @OAGr @quinn-dougherty
|
||||||
|
|
||||||
# Typescript
|
# Typescript
|
||||||
*.tsx @Hazelfire @berekuk @OAGr
|
*.tsx @Hazelfire @OAGr
|
||||||
*.ts @Hazelfire @berekuk @OAGr
|
*.ts @Hazelfire @OAGr
|
||||||
|
|
||||||
# Javascript
|
# Javascript
|
||||||
*.js @Hazelfire @berekuk @OAGr
|
*.js @Hazelfire @OAGr
|
||||||
|
|
||||||
# Any opsy files
|
# Any opsy files
|
||||||
.github/** @quinn-dougherty @berekuk @OAGr
|
.github/** @quinn-dougherty @OAGr
|
||||||
*.json @quinn-dougherty @Hazelfire @berekuk @OAGr
|
*.json @quinn-dougherty @Hazelfire @OAGr
|
||||||
*.y*ml @quinn-dougherty @berekuk @OAGr
|
*.y*ml @quinn-dougherty @OAGr
|
||||||
*.config.js @Hazelfire @berekuk @OAGr
|
*.config.js @Hazelfire @OAGr
|
||||||
vercel.json @OAGr @berekuk @Hazelfire
|
netlify.toml @quinn-dougherty @OAGr @Hazelfire
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
*.md @quinn-dougherty @OAGr @Hazelfire
|
*.md @quinn-dougherty @OAGr @Hazelfire
|
||||||
|
|
13
.github/dependabot.yml
vendored
13
.github/dependabot.yml
vendored
|
@ -8,17 +8,6 @@ updates:
|
||||||
- package-ecosystem: "npm" # See documentation for possible values
|
- package-ecosystem: "npm" # See documentation for possible values
|
||||||
directory: "/" # Location of package manifests
|
directory: "/" # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "daily"
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "⬆️"
|
prefix: "⬆️"
|
||||||
open-pull-requests-limit: 100
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
commit-message:
|
|
||||||
prefix: "⬆️"
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
|
|
87
.github/workflows/ci-cachix.yml
vendored
87
.github/workflows/ci-cachix.yml
vendored
|
@ -1,87 +0,0 @@
|
||||||
name: Nix build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
- reducer-dev
|
|
||||||
- epic-reducer-project
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
flake-lints:
|
|
||||||
name: All lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Install nix
|
|
||||||
uses: cachix/install-nix-action@v17
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-22.05
|
|
||||||
- name: Use cachix
|
|
||||||
uses: cachix/cachix-action@v10
|
|
||||||
with:
|
|
||||||
name: quantified-uncertainty
|
|
||||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
|
||||||
|
|
||||||
- name: Check that lang lints
|
|
||||||
run: nix build .#lang-lint
|
|
||||||
- name: Check that components lints
|
|
||||||
run: nix build .#components-lint
|
|
||||||
- name: Check that website lints
|
|
||||||
run: nix build .#docusaurus-lint
|
|
||||||
- name: Check that vscode extension lints
|
|
||||||
run: nix build .#vscode-lint
|
|
||||||
- name: Check that cli lints
|
|
||||||
run: nix build .#cli-lint
|
|
||||||
|
|
||||||
flake-packages:
|
|
||||||
name: Builds, tests, and bundles
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: flake-lints
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Install nix
|
|
||||||
uses: cachix/install-nix-action@v17
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-22.05
|
|
||||||
- name: Use cachix
|
|
||||||
uses: cachix/cachix-action@v10
|
|
||||||
with:
|
|
||||||
name: quantified-uncertainty
|
|
||||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
|
||||||
|
|
||||||
- name: Check all lang tests
|
|
||||||
run: nix build .#lang-test
|
|
||||||
- name: Check that lang bundles
|
|
||||||
run: nix build .#lang-bundle
|
|
||||||
- name: Check that components builds
|
|
||||||
run: nix build .#components
|
|
||||||
- name: Check that components bundles
|
|
||||||
run: nix build .#components-bundle
|
|
||||||
|
|
||||||
flake-devshells:
|
|
||||||
name: Development shell environment
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Install nix
|
|
||||||
uses: cachix/install-nix-action@v17
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-22.05
|
|
||||||
- name: Use cachix
|
|
||||||
uses: cachix/cachix-action@v10
|
|
||||||
with:
|
|
||||||
name: quantified-uncertainty
|
|
||||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
|
||||||
- name: Build js devshell
|
|
||||||
run: nix develop .#js --profile just-js
|
|
||||||
- name: Build js & wasm devshell
|
|
||||||
run: nix develop --profile full-shell
|
|
164
.github/workflows/ci.yml
vendored
164
.github/workflows/ci.yml
vendored
|
@ -1,4 +1,4 @@
|
||||||
name: Squiggle packages checks
|
name: Squiggle packages check
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
@ -9,40 +9,148 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
|
- reducer-dev
|
||||||
env:
|
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
||||||
TURBO_TEAM: quantified-uncertainty
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-test-lint:
|
pre_check:
|
||||||
name: Build, test, lint
|
name: Precheck for skipping redundant jobs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
should_skip_lang: ${{ steps.skip_lang_check.outputs.should_skip }}
|
||||||
|
should_skip_components: ${{ steps.skip_components_check.outputs.should_skip }}
|
||||||
|
should_skip_website: ${{ steps.skip_website_check.outputs.should_skip }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- id: skip_lang_check
|
||||||
- name: Setup Node.js environment
|
name: Check if the changes are about squiggle-lang src files
|
||||||
uses: actions/setup-node@v3
|
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
paths: '["packages/squiggle-lang/**"]'
|
||||||
cache: 'yarn'
|
- id: skip_components_check
|
||||||
- name: Install dependencies
|
name: Check if the changes are about components src files
|
||||||
run: yarn --frozen-lockfile
|
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||||
- name: Turbo run
|
with:
|
||||||
run: npx turbo run build test lint bundle
|
paths: '["packages/components/**"]'
|
||||||
|
- id: skip_website_check
|
||||||
|
name: Check if the changes are about website src files
|
||||||
|
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||||
|
with:
|
||||||
|
paths: '["packages/website/**"]'
|
||||||
|
|
||||||
coverage:
|
lang-lint:
|
||||||
name: Coverage
|
name: Language lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: packages/squiggle-lang
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: cd ../../ && yarn
|
||||||
|
- name: Check rescript lint
|
||||||
|
run: yarn lint:rescript
|
||||||
|
- name: Check javascript, typescript, and markdown lint
|
||||||
|
uses: creyD/prettier_action@v4.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
dry: true
|
||||||
- name: Setup Node.js environment
|
prettier_options: --check packages/squiggle-lang
|
||||||
uses: actions/setup-node@v2
|
|
||||||
|
lang-build-test-bundle:
|
||||||
|
name: Language build, test, and bundle
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: packages/squiggle-lang
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install dependencies from monorepo level
|
||||||
|
run: cd ../../ && yarn
|
||||||
|
- name: Build rescript codebase
|
||||||
|
run: yarn build
|
||||||
|
- name: Run rescript tests
|
||||||
|
run: yarn test:rescript
|
||||||
|
- name: Run typescript tests
|
||||||
|
run: yarn test:ts
|
||||||
|
- name: Run webpack
|
||||||
|
run: yarn bundle
|
||||||
|
- name: Upload rescript coverage report
|
||||||
|
run: yarn coverage:rescript:ci
|
||||||
|
- name: Upload typescript coverage report
|
||||||
|
run: yarn coverage:ts:ci
|
||||||
|
|
||||||
|
components-lint:
|
||||||
|
name: Components lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: packages/components
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Check javascript, typescript, and markdown lint
|
||||||
|
uses: creyD/prettier_action@v4.2
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
dry: true
|
||||||
cache: 'yarn'
|
prettier_options: --check packages/components
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn
|
components-bundle-build:
|
||||||
- name: Coverage
|
name: Components bundle and build
|
||||||
run: npx turbo run coverage
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: packages/components
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install dependencies from monorepo level
|
||||||
|
run: cd ../../ && yarn
|
||||||
|
- name: Build rescript codebase in squiggle-lang
|
||||||
|
run: cd ../squiggle-lang && yarn build
|
||||||
|
- name: Run webpack
|
||||||
|
run: yarn bundle
|
||||||
|
- name: Build storybook
|
||||||
|
run: yarn build
|
||||||
|
|
||||||
|
website-lint:
|
||||||
|
name: Website lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: packages/website
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Check javascript, typescript, and markdown lint
|
||||||
|
uses: creyD/prettier_action@v4.2
|
||||||
|
with:
|
||||||
|
dry: true
|
||||||
|
prettier_options: --check packages/website
|
||||||
|
|
||||||
|
website-build:
|
||||||
|
name: Website build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ (needs.pre_check.outputs.should_skip_website != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') || (needs.pre_check.outputs.should_skip_components != 'true') }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: packages/website
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install dependencies from monorepo level
|
||||||
|
run: cd ../../ && yarn
|
||||||
|
- name: Build rescript in squiggle-lang
|
||||||
|
run: cd ../squiggle-lang && yarn build
|
||||||
|
- name: Build website assets
|
||||||
|
run: yarn build
|
||||||
|
|
14
.github/workflows/codeql-analysis.yml
vendored
14
.github/workflows/codeql-analysis.yml
vendored
|
@ -12,6 +12,12 @@
|
||||||
name: "CodeQL"
|
name: "CodeQL"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- production
|
||||||
|
- staging
|
||||||
|
- develop
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "42 19 * * 0"
|
- cron: "42 19 * * 0"
|
||||||
|
|
||||||
|
@ -33,11 +39,11 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v1
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
@ -48,7 +54,7 @@ jobs:
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v1
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
- name: Build rescript
|
- name: Build rescript
|
||||||
|
@ -65,4 +71,4 @@ jobs:
|
||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v1
|
||||||
|
|
141
.github/workflows/release-please.yml
vendored
141
.github/workflows/release-please.yml
vendored
|
@ -1,141 +0,0 @@
|
||||||
name: Run Release Please
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pre_check:
|
|
||||||
name: Precheck for skipping redundant jobs
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
should_skip_lang: ${{ steps.skip_lang_check.outputs.should_skip }}
|
|
||||||
should_skip_components: ${{ steps.skip_components_check.outputs.should_skip }}
|
|
||||||
should_skip_website: ${{ steps.skip_website_check.outputs.should_skip }}
|
|
||||||
should_skip_vscodeext: ${{ steps.skip_vscodeext_check.outputs.should_skip }}
|
|
||||||
should_skip_cli: ${{ steps.skip_cli_check.outputs.should_skip }}
|
|
||||||
steps:
|
|
||||||
- id: skip_lang_check
|
|
||||||
name: Check if the changes are about squiggle-lang src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v5.2.0
|
|
||||||
with:
|
|
||||||
paths: '["packages/squiggle-lang/**"]'
|
|
||||||
- id: skip_components_check
|
|
||||||
name: Check if the changes are about components src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v5.2.0
|
|
||||||
with:
|
|
||||||
paths: '["packages/components/**"]'
|
|
||||||
- id: skip_website_check
|
|
||||||
name: Check if the changes are about website src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v5.2.0
|
|
||||||
with:
|
|
||||||
paths: '["packages/website/**"]'
|
|
||||||
- id: skip_vscodeext_check
|
|
||||||
name: Check if the changes are about vscode extension src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v5.2.0
|
|
||||||
with:
|
|
||||||
paths: '["packages/vscode-ext/**"]'
|
|
||||||
- id: skip_cli_check
|
|
||||||
name: Check if the changes are about cli src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v5.2.0
|
|
||||||
with:
|
|
||||||
paths: '["packages/cli/**"]'
|
|
||||||
|
|
||||||
relplz-lang:
|
|
||||||
name: for squiggle-lang
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
|
|
||||||
steps:
|
|
||||||
- name: Release please (squiggle-lang)
|
|
||||||
uses: google-github-actions/release-please-action@v3
|
|
||||||
id: release
|
|
||||||
with:
|
|
||||||
token: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
command: manifest-pr
|
|
||||||
path: packages/squiggle-lang
|
|
||||||
# bump-patch-for-minor-pre-major: true
|
|
||||||
skip-github-release: true
|
|
||||||
- name: Publish- Checkout source
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
# these if statements ensure that a publication only occurs when
|
|
||||||
# a new release is created:
|
|
||||||
if: ${{ steps.release.outputs.release_created }}
|
|
||||||
- name: Publish- Install dependencies
|
|
||||||
run: yarn
|
|
||||||
if: ${{ steps.release.outputs.release_created }}
|
|
||||||
- name: Publish
|
|
||||||
run: cd packages/squiggle-lang && yarn publish
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
||||||
if: ${{ steps.release.outputs.release_created }}
|
|
||||||
|
|
||||||
relplz-components:
|
|
||||||
name: for components
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }}
|
|
||||||
steps:
|
|
||||||
- name: Release please (components)
|
|
||||||
uses: google-github-actions/release-please-action@v3
|
|
||||||
with:
|
|
||||||
token: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
command: manifest-pr
|
|
||||||
path: packages/components
|
|
||||||
# bump-patch-for-minor-pre-major: true
|
|
||||||
skip-github-release: true
|
|
||||||
- name: Publish- Checkout source
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
# these if statements ensure that a publication only occurs when
|
|
||||||
# a new release is created:
|
|
||||||
if: ${{ steps.release.outputs.release_created }}
|
|
||||||
- name: Publish- Install dependencies
|
|
||||||
run: yarn
|
|
||||||
if: ${{ steps.release.outputs.release_created }}
|
|
||||||
- name: Publish
|
|
||||||
run: cd packages/components && yarn publish
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
||||||
relplz-website:
|
|
||||||
name: for website
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }}
|
|
||||||
steps:
|
|
||||||
- name: Release please (website)
|
|
||||||
uses: google-github-actions/release-please-action@v3
|
|
||||||
with:
|
|
||||||
token: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
command: manifest-pr
|
|
||||||
path: packages/website
|
|
||||||
# bump-patch-for-minor-pre-major: true
|
|
||||||
skip-github-release: true
|
|
||||||
relplz-vscodeext:
|
|
||||||
name: for vscode-ext
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }}
|
|
||||||
steps:
|
|
||||||
- name: Release please (vscode-ext)
|
|
||||||
uses: google-github-actions/release-please-action@v3
|
|
||||||
with:
|
|
||||||
token: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
command: manifest-pr
|
|
||||||
path: packages/vscode-ext
|
|
||||||
# bump-patch-for-minor-pre-major: true
|
|
||||||
skip-github-release: true
|
|
||||||
relplz-cl:
|
|
||||||
name: for cli
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }}
|
|
||||||
steps:
|
|
||||||
- name: Release please (cli)
|
|
||||||
uses: google-github-actions/release-please-action@v3
|
|
||||||
with:
|
|
||||||
token: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
command: manifest-pr
|
|
||||||
path: packages/cli
|
|
||||||
bump-patch-for-minor-pre-major: true
|
|
||||||
skip-github-release: true
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -7,9 +7,3 @@ yarn-error.log
|
||||||
**/.sync.ffs_db
|
**/.sync.ffs_db
|
||||||
.direnv
|
.direnv
|
||||||
.log
|
.log
|
||||||
|
|
||||||
.vscode
|
|
||||||
todo.txt
|
|
||||||
result
|
|
||||||
shell.nix
|
|
||||||
.turbo
|
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
.direnv
|
.direnv
|
||||||
*.bs.js
|
*.bs.js
|
||||||
*.gen.tsx
|
*.gen.tsx
|
||||||
|
packages/*/dist
|
||||||
packages/components/storybook-static
|
packages/components/storybook-static
|
||||||
node_modules
|
node_modules
|
||||||
packages/*/node_modules
|
packages/*/node_modules
|
||||||
packages/website/.docusaurus
|
packages/website/.docusaurus
|
||||||
packages/squiggle-lang/lib
|
packages/squiggle-lang/lib
|
||||||
|
packages/squiggle-lang/.nyc_output/
|
||||||
packages/squiggle-lang/coverage/
|
packages/squiggle-lang/coverage/
|
||||||
packages/squiggle-lang/.cache/
|
packages/squiggle-lang/.cache/
|
||||||
packages/website/build/
|
packages/website/build/
|
||||||
packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
|
||||||
packages/vscode-ext/media/vendor/
|
|
||||||
packages/squiggle-lang/.nyc_output/
|
|
||||||
packages/*/dist
|
|
||||||
result
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"packages/cli": "0.0.3",
|
|
||||||
"packages/components": "0.4.1",
|
|
||||||
"packages/squiggle-lang": "0.4.1",
|
|
||||||
"packages/vscode-ext": "0.4.1",
|
|
||||||
"packages/website": "0.0.0"
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
See the [Changelog.mdx page](./packages/website/docs/Changelog.mdx) for the changelog.
|
|
|
@ -16,7 +16,7 @@ Squiggle is currently pre-alpha.
|
||||||
|
|
||||||
# Bug reports
|
# Bug reports
|
||||||
|
|
||||||
Anyone (with a github account) can file an issue at any time. Please allow Slava, Sam, and Ozzie to triage, but otherwise just follow the suggestions in the issue templates.
|
Anyone (with a github account) can file an issue at any time. Please allow Quinn, Sam, and Ozzie to triage, but otherwise just follow the suggestions in the issue templates.
|
||||||
|
|
||||||
# Project structure
|
# Project structure
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ Squiggle is a **monorepo** with three **packages**.
|
||||||
|
|
||||||
# Deployment ops
|
# Deployment ops
|
||||||
|
|
||||||
We use Vercel, and it should only concern Slava, Sam, and Ozzie.
|
We use netlify, and it should only concern Quinn, Sam, and Ozzie.
|
||||||
|
|
||||||
# Development environment, building, testing, dev server
|
# Development environment, building, testing, dev server
|
||||||
|
|
||||||
|
@ -56,9 +56,9 @@ If you absolutely must, please prefix your commit message with `hotfix: `.
|
||||||
|
|
||||||
Please work against `develop` branch. **Do not** work against `master`.
|
Please work against `develop` branch. **Do not** work against `master`.
|
||||||
|
|
||||||
- For rescript code: Slava and Ozzie are reviewers
|
- For rescript code: Quinn and Ozzie are reviewers
|
||||||
- For js or typescript code: Sam and Ozzie are reviewers
|
- For js or typescript code: Sam and Ozzie are reviewers
|
||||||
- For ops code (i.e. yaml, package.json): Slava and Sam are reviewers
|
- For ops code (i.e. yaml, package.json): Quinn and Sam are reviewers
|
||||||
|
|
||||||
Autopings are set up: if you are not autopinged, you are welcome to comment, but please do not use the formal review feature, send approvals, rejections, or merges.
|
Autopings are set up: if you are not autopinged, you are welcome to comment, but please do not use the formal review feature, send approvals, rejections, or merges.
|
||||||
|
|
||||||
|
|
37
README.md
37
README.md
|
@ -12,19 +12,18 @@ _An estimation language_.
|
||||||
|
|
||||||
- [Gallery](https://www.squiggle-language.com/docs/Discussions/Gallery)
|
- [Gallery](https://www.squiggle-language.com/docs/Discussions/Gallery)
|
||||||
- [Squiggle playground](https://squiggle-language.com/playground)
|
- [Squiggle playground](https://squiggle-language.com/playground)
|
||||||
- [Language basics](https://www.squiggle-language.com/docs/Guides/Language)
|
- [Language basics](https://www.squiggle-language.com/docs/Features/Language)
|
||||||
- [Squiggle functions source of truth](https://www.squiggle-language.com/docs/Guides/Functions)
|
- [Squiggle functions source of truth](https://www.squiggle-language.com/docs/Features/Functions)
|
||||||
- [Known bugs](https://www.squiggle-language.com/docs/Discussions/Bugs)
|
- [Known bugs](https://www.squiggle-language.com/docs/Discussions/Bugs)
|
||||||
- [Original lesswrong sequence](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3)
|
- [Original lesswrong sequence](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3)
|
||||||
- [Author your squiggle models as Observable notebooks](https://observablehq.com/@hazelfire/squiggle)
|
- [Author your squiggle models as Observable notebooks](https://observablehq.com/@hazelfire/squiggle)
|
||||||
- [Use squiggle in VS Code](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-squiggle)
|
|
||||||
|
|
||||||
## Our deployments
|
## Our deployments
|
||||||
|
|
||||||
- **website/docs prod**: https://squiggle-language.com
|
- **website/docs prod**: https://squiggle-language.com [![Netlify Status](https://api.netlify.com/api/v1/badges/2139af5c-671d-473d-a9f6-66c96077d8a1/deploy-status)](https://app.netlify.com/sites/squiggle-documentation/deploys)
|
||||||
- **website/docs staging**: https://preview.squiggle-language.com
|
- **website/docs staging**: https://develop--squiggle-documentation.netlify.app/
|
||||||
- **components storybook prod**: https://components.squiggle-language.com
|
- **components storybook prod**: https://squiggle-components.netlify.app/ [![Netlify Status](https://api.netlify.com/api/v1/badges/b7f724aa-6b20-4d0e-bf86-3fcd1a3e9a70/deploy-status)](https://app.netlify.com/sites/squiggle-components/deploys)
|
||||||
- **components storybook staging**: https://preview-components.squiggle-language.com
|
- **components storybook staging**: https://develop--squiggle-components.netlify.app/
|
||||||
- **legacy (2020) playground**: https://playground.squiggle-language.com
|
- **legacy (2020) playground**: https://playground.squiggle-language.com
|
||||||
|
|
||||||
## Packages
|
## Packages
|
||||||
|
@ -40,8 +39,8 @@ the packages can be found in `packages`.
|
||||||
of the calculation.
|
of the calculation.
|
||||||
- `packages/website` is the main descriptive website for squiggle,
|
- `packages/website` is the main descriptive website for squiggle,
|
||||||
it is hosted at `squiggle-language.com`.
|
it is hosted at `squiggle-language.com`.
|
||||||
- `packages/vscode-ext` is the VS Code extension for writing estimation functions.
|
|
||||||
- `packages/cli` is an experimental way of using imports in squiggle, which is also on [npm](https://www.npmjs.com/package/squiggle-cli-experimental).
|
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 build (no need to bundle) the language, and as of this writing playground doesn't really work.
|
||||||
|
|
||||||
# Develop
|
# Develop
|
||||||
|
|
||||||
|
@ -51,25 +50,7 @@ For any project in the repo, begin by running `yarn` in the top level
|
||||||
yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
Then use `turbo` to build the specific packages or the entire monorepo:
|
See `packages/*/README.md` to work with whatever project you're interested in.
|
||||||
|
|
||||||
```sh
|
|
||||||
turbo run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Or:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
turbo run build --filter=@quri/squiggle-components
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also run specific npm scripts for the package you're working on. See `packages/*/README.md` for the details.
|
|
||||||
|
|
||||||
# NixOS users
|
|
||||||
|
|
||||||
This repository requires the use of bundled binaries from node_modules, which
|
|
||||||
are not linked statically. The easiest way to get them working is to enable
|
|
||||||
[nix-ld](https://github.com/Mic92/nix-ld).
|
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|
79
flake.lock
79
flake.lock
|
@ -1,79 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"flake-utils": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1659877975,
|
|
||||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1659877975,
|
|
||||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gentype": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils_2",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1661855866,
|
|
||||||
"narHash": "sha256-+q0OOTyaq8eOn9BOWdPOCtSDOISW4A59v3mq3JOZyug=",
|
|
||||||
"owner": "rescript-association",
|
|
||||||
"repo": "genType",
|
|
||||||
"rev": "6b5f164b4f6ced456019b7579a0ab7e0a86518ad",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "rescript-association",
|
|
||||||
"repo": "genType",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1661617163,
|
|
||||||
"narHash": "sha256-NN9Ky47j8ohgPhA9JZyfkYIbbAo6RJkGz+7h8/exVpE=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "0ba2543f8c855d7be8e90ef6c8dc89c1617e8a08",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "nixpkgs",
|
|
||||||
"ref": "nixos-22.05",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"gentype": "gentype",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
99
flake.nix
99
flake.nix
|
@ -1,99 +0,0 @@
|
||||||
{
|
|
||||||
description = "Squiggle packages";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "nixpkgs/nixos-22.05";
|
|
||||||
gentype = {
|
|
||||||
url = "github:rescript-association/genType";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, gentype, flake-utils }:
|
|
||||||
let
|
|
||||||
version = builtins.substring 0 8 self.lastModifiedDate;
|
|
||||||
overlays = [
|
|
||||||
(final: prev: {
|
|
||||||
# set the node version here
|
|
||||||
nodejs = prev.nodejs-18_x;
|
|
||||||
# The override is the only way to get it into mkYarnModules
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
commonFn = pkgs: {
|
|
||||||
buildInputs = with pkgs; [ nodejs yarn ];
|
|
||||||
prettier = with pkgs.nodePackages; [ prettier ];
|
|
||||||
which = [ pkgs.which ];
|
|
||||||
};
|
|
||||||
gentypeOutputFn = pkgs: gentype.outputs.packages.${pkgs.system}.default;
|
|
||||||
langFn = { pkgs, ... }:
|
|
||||||
# Probably doesn't work on i686-linux
|
|
||||||
import ./nix/squiggle-lang.nix {
|
|
||||||
inherit pkgs commonFn gentypeOutputFn;
|
|
||||||
};
|
|
||||||
componentsFn = { pkgs, ... }:
|
|
||||||
import ./nix/squiggle-components.nix { inherit pkgs commonFn langFn; };
|
|
||||||
websiteFn = { pkgs, ... }:
|
|
||||||
import ./nix/squiggle-website.nix {
|
|
||||||
inherit pkgs commonFn langFn componentsFn;
|
|
||||||
};
|
|
||||||
vscodeextFn = { pkgs, ... }:
|
|
||||||
import ./nix/squiggle-vscode.nix {
|
|
||||||
inherit pkgs commonFn langFn componentsFn;
|
|
||||||
};
|
|
||||||
cliFn = { pkgs, ... }:
|
|
||||||
import ./nix/squiggle-cli.nix {
|
|
||||||
inherit pkgs commonFn;
|
|
||||||
};
|
|
||||||
|
|
||||||
# local machines
|
|
||||||
localFlakeOutputs = { pkgs, ... }:
|
|
||||||
let
|
|
||||||
lang = langFn pkgs;
|
|
||||||
components = componentsFn pkgs;
|
|
||||||
website = websiteFn pkgs;
|
|
||||||
vscodeext = vscodeextFn pkgs;
|
|
||||||
cli = cliFn pkgs;
|
|
||||||
in {
|
|
||||||
# validating
|
|
||||||
checks = flake-utils.lib.flattenTree {
|
|
||||||
lang-lint = lang.lint;
|
|
||||||
lang-test = lang.test;
|
|
||||||
components-lint = components.lint;
|
|
||||||
docusaurus-lint = website.lint;
|
|
||||||
cli-lint = cli.lint;
|
|
||||||
};
|
|
||||||
# building
|
|
||||||
packages = flake-utils.lib.flattenTree {
|
|
||||||
default = components.build;
|
|
||||||
lang = lang.build;
|
|
||||||
lang-bundle = lang.bundle;
|
|
||||||
lang-test = lang.test;
|
|
||||||
components = components.build;
|
|
||||||
components-bundle = components.bundle;
|
|
||||||
|
|
||||||
# Lint
|
|
||||||
lang-lint = lang.lint;
|
|
||||||
components-lint = components.lint;
|
|
||||||
docusaurus-lint = website.lint;
|
|
||||||
vscode-lint = vscodeext.lint;
|
|
||||||
cli-lint = cli.lint;
|
|
||||||
};
|
|
||||||
|
|
||||||
# developing
|
|
||||||
devShells = let shellNix = import ./nix/shell.nix { inherit pkgs; };
|
|
||||||
in flake-utils.lib.flattenTree {
|
|
||||||
default = shellNix.all;
|
|
||||||
js = shellNix.just-js;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in flake-utils.lib.eachDefaultSystem (system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs {
|
|
||||||
inherit system;
|
|
||||||
overlays = overlays;
|
|
||||||
};
|
|
||||||
|
|
||||||
in localFlakeOutputs pkgs);
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Visit `quantified-uncertainty.cachix.org` for information about how to add our binary cache to your local dev environment.
|
|
|
@ -1,25 +0,0 @@
|
||||||
{ pkgs }:
|
|
||||||
with pkgs;
|
|
||||||
let
|
|
||||||
js = [ yarn nodejs nodePackages.ts-node ];
|
|
||||||
rust = [
|
|
||||||
wasm-pack
|
|
||||||
cargo
|
|
||||||
rustup
|
|
||||||
pkg-config
|
|
||||||
libressl
|
|
||||||
rustfmt
|
|
||||||
wasmtime
|
|
||||||
binaryen
|
|
||||||
wasm-bindgen-cli
|
|
||||||
];
|
|
||||||
in {
|
|
||||||
all = mkShell {
|
|
||||||
name = "squiggle_yarn-wasm-devshell";
|
|
||||||
buildInputs = builtins.concatLists [ js rust [ nixfmt ] ];
|
|
||||||
};
|
|
||||||
just-js = mkShell {
|
|
||||||
name = "squiggle_yarn-devshell";
|
|
||||||
buildInputs = js ++ [ nixfmt ];
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{ pkgs, commonFn }:
|
|
||||||
|
|
||||||
rec {
|
|
||||||
common = commonFn pkgs;
|
|
||||||
|
|
||||||
lint = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "squiggle-cli-lint";
|
|
||||||
buildInputs = common.buildInputs ++ common.prettier;
|
|
||||||
src = ../packages/cli;
|
|
||||||
buildPhase = "prettier --check .";
|
|
||||||
installPhase = "mkdir -p $out";
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
{ pkgs, commonFn, langFn }:
|
|
||||||
|
|
||||||
rec {
|
|
||||||
common = commonFn pkgs;
|
|
||||||
lang = langFn pkgs;
|
|
||||||
componentsPackageJson = let
|
|
||||||
raw = pkgs.lib.importJSON ../packages/components/package.json;
|
|
||||||
modified =
|
|
||||||
pkgs.lib.recursiveUpdate raw { dependencies.react-dom = "^18.2.0"; };
|
|
||||||
packageJsonString = builtins.toJSON modified;
|
|
||||||
in pkgs.writeText "packages/components/patched-package.json"
|
|
||||||
packageJsonString;
|
|
||||||
yarn-source = pkgs.mkYarnPackage {
|
|
||||||
name = "squiggle-components_yarnsource";
|
|
||||||
buildInputs = common.buildInputs;
|
|
||||||
src = ../packages/components;
|
|
||||||
packageJSON = componentsPackageJson;
|
|
||||||
yarnLock = ../yarn.lock;
|
|
||||||
packageResolutions."@quri/squiggle-lang" = lang.build;
|
|
||||||
};
|
|
||||||
lint = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "squiggle-components-lint";
|
|
||||||
src = ../packages/components;
|
|
||||||
buildInputs = common.buildInputs ++ common.prettier;
|
|
||||||
buildPhase = "yarn lint";
|
|
||||||
installPhase = "mkdir -p $out";
|
|
||||||
};
|
|
||||||
build = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "squiggle-components-build";
|
|
||||||
src = yarn-source + "/libexec/@quri/squiggle-components";
|
|
||||||
buildInputs = common.buildInputs;
|
|
||||||
buildPhase = ''
|
|
||||||
cp -r node_modules/@quri/squiggle-lang deps/@quri
|
|
||||||
pushd deps/@quri/squiggle-components
|
|
||||||
|
|
||||||
yarn --offline build:cjs
|
|
||||||
yarn --offline build:css
|
|
||||||
popd
|
|
||||||
'';
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
|
|
||||||
# annoying hack because permissions on transitive dependencies later on
|
|
||||||
mv deps/@quri/squiggle-components/node_modules deps/@quri/squiggle-components/NODE_MODULES
|
|
||||||
mv node_modules deps/@quri/squiggle-components
|
|
||||||
|
|
||||||
# patching .gitignore so flake keeps build artefacts
|
|
||||||
sed -i /dist/d deps/@quri/squiggle-components/.gitignore
|
|
||||||
cp -r deps/@quri/squiggle-components/. $out
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
bundle = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "squiggle-components-bundle";
|
|
||||||
src = yarn-source + "/libexec/@quri/squiggle-components";
|
|
||||||
buildInputs = common.buildInputs;
|
|
||||||
buildPhase = ''
|
|
||||||
cp -r node_modules/@quri/squiggle-lang deps/@quri
|
|
||||||
pushd deps/@quri/squiggle-components
|
|
||||||
|
|
||||||
yarn --offline bundle
|
|
||||||
popd
|
|
||||||
'';
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
|
|
||||||
# annoying hack because permissions on transitive dependencies later on
|
|
||||||
mv deps/@quri/squiggle-components/node_modules deps/@quri/squiggle-components/NODE_MODULES
|
|
||||||
mv node_modules deps/@quri/squiggle-components
|
|
||||||
|
|
||||||
# patching .gitignore so flake keeps build artefacts
|
|
||||||
sed -i /dist/d deps/@quri/squiggle-components/.gitignore
|
|
||||||
cp -r deps/@quri/squiggle-components/. $out
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
{ pkgs, commonFn, gentypeOutputFn }:
|
|
||||||
|
|
||||||
rec {
|
|
||||||
common = commonFn pkgs;
|
|
||||||
langPackageJson = let
|
|
||||||
raw = pkgs.lib.importJSON ../packages/squiggle-lang/package.json;
|
|
||||||
modified = pkgs.lib.recursiveUpdate raw {
|
|
||||||
devDependencies."@types/lodash" = "^4.14.167";
|
|
||||||
};
|
|
||||||
packageJsonString = builtins.toJSON modified;
|
|
||||||
in pkgs.writeText "packages/squiggle-lang/patched-package.json"
|
|
||||||
packageJsonString;
|
|
||||||
yarn-source = pkgs.mkYarnPackage {
|
|
||||||
name = "squiggle-lang_yarnsource";
|
|
||||||
src = ../packages/squiggle-lang;
|
|
||||||
packageJSON = langPackageJson;
|
|
||||||
yarnLock = ../yarn.lock;
|
|
||||||
pkgConfig = {
|
|
||||||
rescript = {
|
|
||||||
buildInputs = common.which
|
|
||||||
++ (if pkgs.system != "i686-linux" then [ pkgs.gcc_multi ] else [ ]);
|
|
||||||
postInstall = ''
|
|
||||||
echo "PATCHELF'ING RESCRIPT EXECUTABLES (INCL NINJA)"
|
|
||||||
# Patching interpreter for linux/*.exe's
|
|
||||||
THE_LD=$(patchelf --print-interpreter $(which mkdir))
|
|
||||||
patchelf --set-interpreter $THE_LD linux/*.exe && echo "- patched interpreter for linux/*.exe's"
|
|
||||||
|
|
||||||
# Replacing needed shared library for linux/ninja.exe
|
|
||||||
THE_SO=$(find /nix/store/*/lib64 -name libstdc++.so.6 | head -n 1)
|
|
||||||
patchelf --replace-needed libstdc++.so.6 $THE_SO linux/ninja.exe && echo "- replaced needed for linux/ninja.exe"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
gentype = {
|
|
||||||
postInstall = ''
|
|
||||||
mv gentype.exe ELFLESS-gentype.exe
|
|
||||||
cp ${gentypeOutputFn pkgs}/src/GenType.exe gentype.exe
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
lint = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "squiggle-lang-lint";
|
|
||||||
src = yarn-source + "/libexec/@quri/squiggle-lang/deps/@quri/squiggle-lang";
|
|
||||||
buildInputs = common.buildInputs ++ common.prettier;
|
|
||||||
buildPhase = ''
|
|
||||||
yarn lint:prettier
|
|
||||||
yarn lint:rescript
|
|
||||||
'';
|
|
||||||
installPhase = "mkdir -p $out";
|
|
||||||
};
|
|
||||||
build = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "squiggle-lang-build";
|
|
||||||
# `peggy` is in the `node_modules` that's adjacent to `deps`.
|
|
||||||
src = yarn-source + "/libexec/@quri/squiggle-lang";
|
|
||||||
buildInputs = common.buildInputs;
|
|
||||||
buildPhase = ''
|
|
||||||
# so that the path to ppx doesn't need to be patched.
|
|
||||||
mv node_modules deps
|
|
||||||
|
|
||||||
pushd deps/@quri/squiggle-lang
|
|
||||||
yarn --offline build:peggy
|
|
||||||
yarn --offline build:rescript
|
|
||||||
yarn --offline build:typescript
|
|
||||||
|
|
||||||
# custom gitignore so that the flake keeps build artefacts
|
|
||||||
mv .gitignore GITIGNORE
|
|
||||||
sed -i /Reducer_Peggy_GeneratedParser.js/d GITIGNORE
|
|
||||||
sed -i /ReducerProject_IncludeParser.js/d GITIGNORE
|
|
||||||
sed -i /\*.bs.js/d GITIGNORE
|
|
||||||
sed -i /\*.gen.ts/d GITIGNORE
|
|
||||||
sed -i /\*.gen.tsx/d GITIGNORE
|
|
||||||
sed -i /\*.gen.js/d GITIGNORE
|
|
||||||
sed -i /helpers.js/d GITIGNORE
|
|
||||||
|
|
||||||
popd
|
|
||||||
'';
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
# mkdir -p $out/node_modules
|
|
||||||
mv deps/@quri/squiggle-lang/GITIGNORE deps/@quri/squiggle-lang/.gitignore
|
|
||||||
|
|
||||||
# annoying hack because permissions on transitive dependencies later on
|
|
||||||
mv deps/@quri/squiggle-lang/node_modules deps/@quri/squiggle-lang/NODE_MODULES
|
|
||||||
mv deps/node_modules deps/@quri/squiggle-lang
|
|
||||||
|
|
||||||
# the proper install phase
|
|
||||||
cp -r deps/@quri/squiggle-lang/. $out
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
test = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "squiggle-lang-test";
|
|
||||||
src = build;
|
|
||||||
buildInputs = common.buildInputs;
|
|
||||||
buildPhase = ''
|
|
||||||
yarn --offline test
|
|
||||||
'';
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
cp -r . $out
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
bundle = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "squiggle-lang-bundle";
|
|
||||||
src = test;
|
|
||||||
buildInputs = common.buildInputs;
|
|
||||||
buildPhase = ''
|
|
||||||
yarn --offline bundle
|
|
||||||
'';
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out
|
|
||||||
cp -r dist $out
|
|
||||||
cp *.json $out/dist
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
{ pkgs, commonFn, langFn, componentsFn }:
|
|
||||||
|
|
||||||
rec {
|
|
||||||
common = commonFn pkgs;
|
|
||||||
lang = langFn pkgs;
|
|
||||||
components = componentsFn pkgs;
|
|
||||||
|
|
||||||
yarn-source = pkgs.mkYarnPackage {
|
|
||||||
name = "squiggle-vscodeext_yarnsource";
|
|
||||||
src = ../packages/vscode-ext;
|
|
||||||
packageJson = ../packages/vscode-ext/package.json;
|
|
||||||
yarnLock = ../yarn.lock;
|
|
||||||
packageResolutions."@quri/squiggle-lang" = lang.build;
|
|
||||||
packageResolutions."@quri/squiggle-components" = components.build;
|
|
||||||
};
|
|
||||||
lint = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "squiggle-vscode-lint";
|
|
||||||
buildInputs = common.buildInputs ++ common.prettier;
|
|
||||||
src =
|
|
||||||
../packages/vscode-ext; # yarn-source + "/libexec/vscode-squiggle/deps/vscode-squiggle";
|
|
||||||
buildPhase = "prettier --check .";
|
|
||||||
installPhase = "mkdir -p $out";
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
{ pkgs, commonFn, langFn, componentsFn }:
|
|
||||||
|
|
||||||
rec {
|
|
||||||
common = commonFn pkgs;
|
|
||||||
lang = langFn pkgs;
|
|
||||||
components = componentsFn pkgs;
|
|
||||||
websitePackageJson = let
|
|
||||||
raw = pkgs.lib.importJSON ../packages/website/package.json;
|
|
||||||
modified = pkgs.lib.recursiveUpdate raw {
|
|
||||||
dependencies.postcss-import = "^14.1.0";
|
|
||||||
dependencies.tailwindcss = "^3.1.8";
|
|
||||||
};
|
|
||||||
packageJsonString = builtins.toJSON modified;
|
|
||||||
in pkgs.writeText "packages/website/patched-package.json" packageJsonString;
|
|
||||||
yarn-source = pkgs.mkYarnPackage {
|
|
||||||
name = "squiggle-website_yarnsource";
|
|
||||||
src = ../packages/website;
|
|
||||||
packageJSON = websitePackageJson;
|
|
||||||
yarnLock = ../yarn.lock;
|
|
||||||
packageResolutions."@quri/squiggle-lang" = lang.build;
|
|
||||||
packageResolutions."@quri/squiggle-components" = components.build;
|
|
||||||
};
|
|
||||||
lint = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "squiggle-website-lint";
|
|
||||||
buildInputs = common.buildInputs ++ common.prettier;
|
|
||||||
src = ../packages/website;
|
|
||||||
buildPhase = "yarn lint";
|
|
||||||
installPhase = "mkdir -p $out";
|
|
||||||
};
|
|
||||||
}
|
|
18
nixos.sh
Executable file
18
nixos.sh
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# This script is only relevant if you're rolling nixos.
|
||||||
|
|
||||||
|
# Esy (a bisect_ppx dependency/build tool) is borked on nixos without using an FHS shell. https://github.com/esy/esy/issues/858
|
||||||
|
# We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375
|
||||||
|
set -x
|
||||||
|
|
||||||
|
fhsShellName="squiggle-development"
|
||||||
|
fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn]; runScript = \"yarn\"; }).env"
|
||||||
|
nix-shell - <<<"$fhsShellDotNix"
|
||||||
|
|
||||||
|
theLd=$(patchelf --print-interpreter $(which mkdir))
|
||||||
|
patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe
|
||||||
|
patchelf --set-interpreter $theLd ./node_modules/rescript/linux/*.exe
|
||||||
|
patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/ppx
|
||||||
|
patchelf --set-interpreter $theLd ./node_moduels/bisect_ppx/bisect-ppx-report
|
||||||
|
theSo=$(find /nix/store/*$fhsShellName*/lib64 -name libstdc++.so.6 | grep $fhsShellName | head -n 1)
|
||||||
|
patchelf --replace-needed libstdc++.so.6 $theSo ./node_modules/rescript/linux/ninja.exe
|
|
@ -2,11 +2,12 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "squiggle",
|
"name": "squiggle",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"nodeclean": "rm -r node_modules && rm -r packages/*/node_modules"
|
"nodeclean": "rm -r node_modules && rm -r packages/*/node_modules",
|
||||||
|
"format:all": "prettier --write . && cd packages/squiggle-lang && yarn format",
|
||||||
|
"lint:all": "prettier --check . && cd packages/squiggle-lang && yarn lint:rescript"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.6.2"
|
||||||
"turbo": "^1.5.5"
|
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
4
packages/cli/.gitignore
vendored
4
packages/cli/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
||||||
## Artifacts
|
|
||||||
*.swp
|
|
||||||
/node_modules/
|
|
||||||
yarn-error.log
|
|
|
@ -1,49 +0,0 @@
|
||||||
## Squiggle CLI
|
|
||||||
|
|
||||||
This package can be used to incorporate a very simple `import` system into Squiggle.
|
|
||||||
|
|
||||||
To use, write special files with a `.squiggleU` file type. In these files, you can write lines like,
|
|
||||||
|
|
||||||
```
|
|
||||||
@import(models/gdp_over_time.squiggle, gdpOverTime)
|
|
||||||
gdpOverTime(2.5)
|
|
||||||
```
|
|
||||||
|
|
||||||
The imports will be replaced with the contents of the file in `models/gdp_over_time.squiggle` upon compilation. The `.squiggleU` file will be converted into a `.squiggle` file with the `import` statement having this replacement.
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
### `npx squiggle-cli-experimental compile`
|
|
||||||
|
|
||||||
Runs compilation in the current directory and all of its subdirectories.
|
|
||||||
|
|
||||||
### `npx squiggle-cli-experimental watch`
|
|
||||||
|
|
||||||
Watches `.squiggleU` files in the current directory (and subdirectories) and rebuilds them when they are saved. Note that this will _not_ rebuild files when their dependencies are changed, just when they are changed directly.
|
|
||||||
|
|
||||||
## Further instructions
|
|
||||||
|
|
||||||
The above requires having node, npm and npx. To install the first two, see [here](https://nodejs.org/en/), to install npx, run:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install -g npx
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can run the following without the need for npx:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install squiggle-cli-experimental
|
|
||||||
node node_modules/squiggle-cli-experimental/index.js compile
|
|
||||||
```
|
|
||||||
|
|
||||||
or you can add a script to your `package.json`, like:
|
|
||||||
|
|
||||||
```
|
|
||||||
...
|
|
||||||
scripts: {
|
|
||||||
"compile": "squiggle-cli-experimental compile"
|
|
||||||
}
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
This can be run with `npm run compile`. `npm` knows how to reach into the node_modules directly, so it's not necessary to specify that.
|
|
|
@ -1,96 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import indentString from "indent-string";
|
|
||||||
import chokidar from "chokidar";
|
|
||||||
import chalk from "chalk";
|
|
||||||
import { Command } from "commander";
|
|
||||||
import glob from "glob";
|
|
||||||
|
|
||||||
const processFile = (fileName, seen = []) => {
|
|
||||||
const normalizedFileName = path.resolve(fileName);
|
|
||||||
if (seen.includes(normalizedFileName)) {
|
|
||||||
throw new Error(`Recursive dependency for file ${fileName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileContents = fs.readFileSync(fileName, "utf-8");
|
|
||||||
if (!fileName.endsWith(".squiggleU")) {
|
|
||||||
return fileContents;
|
|
||||||
}
|
|
||||||
|
|
||||||
const regex = /\@import\(\s*([^)]+?)\s*\)/g;
|
|
||||||
const matches = Array.from(fileContents.matchAll(regex)).map((r) =>
|
|
||||||
r[1].split(/\s*,\s*/)
|
|
||||||
);
|
|
||||||
const newContent = fileContents.replaceAll(regex, "");
|
|
||||||
const appendings = [];
|
|
||||||
|
|
||||||
matches.forEach((r) => {
|
|
||||||
const importFileName = r[0];
|
|
||||||
const rename = r[1];
|
|
||||||
const item = fs.statSync(importFileName);
|
|
||||||
if (item.isFile()) {
|
|
||||||
const data = processFile(importFileName, [...seen, normalizedFileName]);
|
|
||||||
if (data) {
|
|
||||||
const importString = `${rename} = {\n${indentString(data, 2)}\n}\n`;
|
|
||||||
appendings.push(importString);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
chalk.red(`Import Error`) +
|
|
||||||
`: ` +
|
|
||||||
chalk.cyan(importFileName) +
|
|
||||||
` not found in file ` +
|
|
||||||
chalk.cyan(fileName) +
|
|
||||||
`. Make sure the @import file names all exist in this repo.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const imports = appendings.join("\n");
|
|
||||||
|
|
||||||
const newerContent = imports.concat(newContent);
|
|
||||||
return newerContent;
|
|
||||||
};
|
|
||||||
|
|
||||||
const run = (fileName) => {
|
|
||||||
const content = processFile(fileName);
|
|
||||||
const parsedPath = path.parse(path.resolve(fileName));
|
|
||||||
const newFilename = `${parsedPath.dir}/${parsedPath.name}.squiggle`;
|
|
||||||
fs.writeFileSync(newFilename, content);
|
|
||||||
console.log(chalk.cyan(`Updated ${fileName} -> ${newFilename}`));
|
|
||||||
};
|
|
||||||
|
|
||||||
const compile = () => {
|
|
||||||
glob("**/*.squiggleU", (_err, files) => {
|
|
||||||
files.forEach(run);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const watch = () => {
|
|
||||||
chokidar
|
|
||||||
.watch("**.squiggleU")
|
|
||||||
.on("ready", () => console.log(chalk.green("Ready!")))
|
|
||||||
.on("change", (event, _) => {
|
|
||||||
run(event);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const program = new Command();
|
|
||||||
|
|
||||||
program
|
|
||||||
.name("squiggle-utils")
|
|
||||||
.description("CLI to transform squiggle files with @imports")
|
|
||||||
.version("0.0.1");
|
|
||||||
|
|
||||||
program
|
|
||||||
.command("watch")
|
|
||||||
.description("watch files and compile on the fly")
|
|
||||||
.action(watch);
|
|
||||||
|
|
||||||
program
|
|
||||||
.command("compile")
|
|
||||||
.description("compile all .squiggleU files into .squiggle files")
|
|
||||||
.action(compile);
|
|
||||||
|
|
||||||
program.parse();
|
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"name": "squiggle-cli-experimental",
|
|
||||||
"version": "0.0.3",
|
|
||||||
"main": "index.js",
|
|
||||||
"homepage": "https://squiggle-language.com",
|
|
||||||
"author": "Quantified Uncertainty Research Institute",
|
|
||||||
"bin": "index.js",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"start": "node .",
|
|
||||||
"lint": "prettier --check .",
|
|
||||||
"format": "prettier --write ."
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"chalk": "^5.1.0",
|
|
||||||
"chokidar": "^3.5.3",
|
|
||||||
"commander": "^9.4.1",
|
|
||||||
"fs": "^0.0.1-security",
|
|
||||||
"glob": "^8.0.3",
|
|
||||||
"indent-string": "^5.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,2 @@
|
||||||
dist/
|
dist/
|
||||||
storybook-static
|
storybook-static
|
||||||
src/styles/base.css
|
|
||||||
src/styles/forms.css
|
|
||||||
|
|
|
@ -1,15 +1,3 @@
|
||||||
import "../src/styles/main.css";
|
|
||||||
import "!style-loader!css-loader!postcss-loader!../src/styles/main.css";
|
|
||||||
import { SquiggleContainer } from "../src/components/SquiggleContainer";
|
|
||||||
|
|
||||||
export const decorators = [
|
|
||||||
(Story) => (
|
|
||||||
<SquiggleContainer>
|
|
||||||
<Story />
|
|
||||||
</SquiggleContainer>
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||||
controls: {
|
controls: {
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
This package contains the react components for squiggle. These can be used either as a library or hosted as a [storybook](https://storybook.js.org/).
|
This package contains the react components for squiggle. These can be used either as a library or hosted as a [storybook](https://storybook.js.org/).
|
||||||
|
|
||||||
The `@quri/squiggle-components` package offers several components and utilities for people who want to embed Squiggle components into websites.
|
|
||||||
|
|
||||||
# Usage in a `react` project
|
# Usage in a `react` project
|
||||||
|
|
||||||
For example, in a fresh `create-react-app` project
|
For example, in a fresh `create-react-app` project
|
||||||
|
@ -20,47 +18,11 @@ Add to `App.js`:
|
||||||
```jsx
|
```jsx
|
||||||
import { SquiggleEditor } from "@quri/squiggle-components";
|
import { SquiggleEditor } from "@quri/squiggle-components";
|
||||||
<SquiggleEditor
|
<SquiggleEditor
|
||||||
defaultCode="x = beta($alpha, 10); x + $shift"
|
initialSquiggleString="x = beta($alpha, 10); x + $shift"
|
||||||
jsImports={{ alpha: 3, shift: 20 }}
|
jsImports={{ alpha: 3, shift: 20 }}
|
||||||
/>;
|
/>;
|
||||||
```
|
```
|
||||||
|
|
||||||
# Usage in a Nextjs project
|
|
||||||
|
|
||||||
For now, `squiggle-components` requires the `window` property, so using the package in nextjs requires dynamic loading:
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { SquiggleChart } from "@quri/squiggle-components";
|
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
|
|
||||||
const SquiggleChart = dynamic(
|
|
||||||
() => import("@quri/squiggle-components").then((mod) => mod.SquiggleChart),
|
|
||||||
{
|
|
||||||
loading: () => <p>Loading...</p>,
|
|
||||||
ssr: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export function DynamicSquiggleChart({ squiggleString }) {
|
|
||||||
if (squiggleString == "") {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<SquiggleChart
|
|
||||||
defaultCode={squiggleString}
|
|
||||||
width={445}
|
|
||||||
height={200}
|
|
||||||
showSummary={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Build storybook for development
|
# Build storybook for development
|
||||||
|
|
||||||
We assume that you had run `yarn` at monorepo level, installing dependencies.
|
We assume that you had run `yarn` at monorepo level, installing dependencies.
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
||||||
module.exports = {
|
|
||||||
preset: "ts-jest",
|
|
||||||
setupFilesAfterEnv: ["<rootDir>/test/setup.js"],
|
|
||||||
testEnvironment: "jsdom",
|
|
||||||
};
|
|
8
packages/components/netlify.toml
Normal file
8
packages/components/netlify.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[build]
|
||||||
|
base = "packages/components/"
|
||||||
|
command = "cd ../squiggle-lang && yarn build && cd ../components && yarn build"
|
||||||
|
publish = "storybook-static/"
|
||||||
|
ignore = "node -e 'process.exitCode = process.env.BRANCH.includes(\"dependabot\") ? 0 : 1' && git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../squiggle-lang"
|
||||||
|
|
||||||
|
[build.environment]
|
||||||
|
NETLIFY_USE_YARN = "true"
|
|
@ -1,92 +1,60 @@
|
||||||
{
|
{
|
||||||
"name": "@quri/squiggle-components",
|
"name": "@quri/squiggle-components",
|
||||||
"version": "0.5.0",
|
"version": "0.2.17",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react-dom": "^1.0.0",
|
"@quri/squiggle-lang": "^0.2.8",
|
||||||
"@floating-ui/react-dom-interactions": "^0.10.1",
|
|
||||||
"@headlessui/react": "^1.7.3",
|
|
||||||
"@heroicons/react": "^1.0.6",
|
|
||||||
"@hookform/resolvers": "^2.9.8",
|
|
||||||
"@quri/squiggle-lang": "^0.5.0",
|
|
||||||
"@react-hook/size": "^2.1.2",
|
"@react-hook/size": "^2.1.2",
|
||||||
"@types/uuid": "^8.3.4",
|
|
||||||
"clsx": "^1.2.1",
|
|
||||||
"framer-motion": "^7.5.3",
|
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-ace": "^10.1.0",
|
"react-ace": "^10.1.0",
|
||||||
"react-hook-form": "^7.37.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.3.2",
|
||||||
"react-vega": "^7.6.0",
|
"react-vega": "^7.5.0",
|
||||||
"uuid": "^9.0.0",
|
"styled-components": "^5.3.5",
|
||||||
"vega": "^5.22.1",
|
"vega": "^5.22.1",
|
||||||
"vega-embed": "^6.21.0",
|
"vega-embed": "^6.20.6",
|
||||||
"vega-lite": "^5.5.0",
|
"vega-lite": "^5.2.0"
|
||||||
"vscode-uri": "^3.0.6",
|
|
||||||
"yup": "^0.32.11"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.18.6",
|
"@babel/plugin-proposal-private-property-in-object": "^7.16.7",
|
||||||
"@storybook/addon-actions": "^6.5.12",
|
"@storybook/addon-actions": "^6.4.22",
|
||||||
"@storybook/addon-essentials": "^6.5.12",
|
"@storybook/addon-essentials": "^6.4.22",
|
||||||
"@storybook/addon-links": "^6.5.12",
|
"@storybook/addon-links": "^6.4.22",
|
||||||
"@storybook/builder-webpack5": "^6.5.12",
|
"@storybook/builder-webpack5": "^6.4.22",
|
||||||
"@storybook/manager-webpack5": "^6.5.12",
|
"@storybook/manager-webpack5": "^6.4.22",
|
||||||
"@storybook/node-logger": "^6.5.9",
|
"@storybook/node-logger": "^6.4.22",
|
||||||
"@storybook/preset-create-react-app": "^4.1.2",
|
"@storybook/preset-create-react-app": "^4.1.0",
|
||||||
"@storybook/react": "^6.5.12",
|
"@storybook/react": "^6.4.22",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.1.1",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.1.1",
|
||||||
"@types/jest": "^27.5.0",
|
"@types/jest": "^27.4.0",
|
||||||
"@types/lodash": "^4.14.186",
|
"@types/lodash": "^4.14.182",
|
||||||
"@types/node": "^18.8.3",
|
"@types/node": "^17.0.31",
|
||||||
"@types/react": "^18.0.21",
|
"@types/react": "^18.0.3",
|
||||||
"@types/styled-components": "^5.1.26",
|
"@types/react-dom": "^18.0.2",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/styled-components": "^5.1.24",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
"canvas": "^2.10.1",
|
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"jest": "^29.0.3",
|
|
||||||
"jest-environment-jsdom": "^29.1.2",
|
|
||||||
"jsdom": "^20.0.1",
|
|
||||||
"mini-css-extract-plugin": "^2.6.1",
|
|
||||||
"postcss-cli": "^10.0.0",
|
|
||||||
"postcss-import": "^15.0.0",
|
|
||||||
"postcss-loader": "^7.0.1",
|
|
||||||
"postcss-nesting": "^10.2.0",
|
|
||||||
"react": "^18.1.0",
|
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
"tailwindcss": "^3.1.8",
|
"ts-loader": "^9.3.0",
|
||||||
"ts-jest": "^29.0.3",
|
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||||
"ts-loader": "^9.4.1",
|
"typescript": "^4.6.3",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
"web-vitals": "^2.1.4",
|
||||||
"typescript": "^4.8.4",
|
"webpack": "^5.72.0",
|
||||||
"web-vitals": "^3.0.3",
|
"webpack-cli": "^4.9.2",
|
||||||
"webpack": "^5.74.0",
|
"webpack-dev-server": "^4.8.1"
|
||||||
"webpack-cli": "^4.10.0",
|
|
||||||
"webpack-dev-server": "^4.11.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8.0 || ^17 || ^18",
|
|
||||||
"react-dom": "^16.8.0 || ^17 || ^18"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
||||||
"build:cjs": "rm -rf dist/src && rm -f dist/tsconfig.tsbuildinfo && tsc -b",
|
"build": "tsc -b && build-storybook -s public",
|
||||||
"build:css": "postcss ./src/styles/main.css -o ./dist/main.css",
|
|
||||||
"build:storybook": "build-storybook -s public",
|
|
||||||
"build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook",
|
|
||||||
"bundle": "webpack",
|
"bundle": "webpack",
|
||||||
"all": "yarn bundle && yarn build",
|
"all": "yarn bundle && yarn build",
|
||||||
"lint": "prettier --check .",
|
"lint": "prettier --check .",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"prepack": "yarn run build:cjs && yarn run bundle",
|
"prepack": "yarn bundle && tsc -b"
|
||||||
"test": "jest",
|
|
||||||
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
|
|
||||||
"test:profile": "node --cpu-prof node_modules/.bin/jest --runInBand"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
"postcss-import": {},
|
|
||||||
"tailwindcss/nesting": {},
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
cssnano: {},
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,86 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
XCircleIcon,
|
|
||||||
InformationCircleIcon,
|
|
||||||
CheckCircleIcon,
|
|
||||||
} from "@heroicons/react/solid";
|
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
export const Alert: React.FC<{
|
|
||||||
heading: string;
|
|
||||||
backgroundColor: string;
|
|
||||||
headingColor: string;
|
|
||||||
bodyColor: string;
|
|
||||||
icon: (props: React.ComponentProps<"svg">) => JSX.Element;
|
|
||||||
iconColor: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}> = ({
|
|
||||||
heading = "Error",
|
|
||||||
backgroundColor,
|
|
||||||
headingColor,
|
|
||||||
bodyColor,
|
|
||||||
icon: Icon,
|
|
||||||
iconColor,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className={clsx("rounded-md p-4", backgroundColor)} role="status">
|
|
||||||
<div className="flex">
|
|
||||||
<Icon
|
|
||||||
className={clsx("h-5 w-5 flex-shrink-0", iconColor)}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<div className="ml-3 grow">
|
|
||||||
<header className={clsx("text-sm font-medium", headingColor)}>
|
|
||||||
{heading}
|
|
||||||
</header>
|
|
||||||
{children && React.Children.count(children) ? (
|
|
||||||
<div className={clsx("mt-2 text-sm", bodyColor)}>{children}</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ErrorAlert: React.FC<{
|
|
||||||
heading: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}> = (props) => (
|
|
||||||
<Alert
|
|
||||||
{...props}
|
|
||||||
backgroundColor="bg-red-100"
|
|
||||||
headingColor="text-red-800"
|
|
||||||
bodyColor="text-red-700"
|
|
||||||
icon={XCircleIcon}
|
|
||||||
iconColor="text-red-400"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const MessageAlert: React.FC<{
|
|
||||||
heading: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}> = (props) => (
|
|
||||||
<Alert
|
|
||||||
{...props}
|
|
||||||
backgroundColor="bg-slate-100"
|
|
||||||
headingColor="text-slate-700"
|
|
||||||
bodyColor="text-slate-700"
|
|
||||||
icon={InformationCircleIcon}
|
|
||||||
iconColor="text-slate-400"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const SuccessAlert: React.FC<{
|
|
||||||
heading: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}> = (props) => (
|
|
||||||
<Alert
|
|
||||||
{...props}
|
|
||||||
backgroundColor="bg-green-50"
|
|
||||||
headingColor="text-green-800"
|
|
||||||
bodyColor="text-green-700"
|
|
||||||
icon={CheckCircleIcon}
|
|
||||||
iconColor="text-green-400"
|
|
||||||
/>
|
|
||||||
);
|
|
|
@ -1,49 +1,34 @@
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { FC, useMemo, useRef } from "react";
|
import React, { FC } from "react";
|
||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
|
|
||||||
import "ace-builds/src-noconflict/mode-golang";
|
import "ace-builds/src-noconflict/mode-golang";
|
||||||
import "ace-builds/src-noconflict/theme-github";
|
import "ace-builds/src-noconflict/theme-github";
|
||||||
|
|
||||||
import { SqLocation } from "@quri/squiggle-lang";
|
|
||||||
|
|
||||||
interface CodeEditorProps {
|
interface CodeEditorProps {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
onSubmit?: () => void;
|
|
||||||
oneLine?: boolean;
|
oneLine?: boolean;
|
||||||
width?: number;
|
width?: number;
|
||||||
height: number;
|
height: number;
|
||||||
showGutter?: boolean;
|
showGutter?: boolean;
|
||||||
errorLocations?: SqLocation[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeEditor: FC<CodeEditorProps> = ({
|
export let CodeEditor: FC<CodeEditorProps> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
onSubmit,
|
|
||||||
height,
|
|
||||||
oneLine = false,
|
oneLine = false,
|
||||||
showGutter = false,
|
showGutter = false,
|
||||||
errorLocations = [],
|
height,
|
||||||
}) => {
|
}: CodeEditorProps) => {
|
||||||
const lineCount = value.split("\n").length;
|
let lineCount = value.split("\n").length;
|
||||||
const id = useMemo(() => _.uniqueId(), []);
|
let id = _.uniqueId();
|
||||||
|
|
||||||
// this is necessary because AceEditor binds commands on mount, see https://github.com/securingsincity/react-ace/issues/684
|
|
||||||
const onSubmitRef = useRef<typeof onSubmit | null>(null);
|
|
||||||
onSubmitRef.current = onSubmit;
|
|
||||||
|
|
||||||
const editorEl = useRef<AceEditor | null>(null);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AceEditor
|
<AceEditor
|
||||||
ref={editorEl}
|
|
||||||
value={value}
|
value={value}
|
||||||
mode="golang"
|
mode="golang"
|
||||||
theme="github"
|
theme="github"
|
||||||
width="100%"
|
width={"100%"}
|
||||||
fontSize={14}
|
|
||||||
height={String(height) + "px"}
|
height={String(height) + "px"}
|
||||||
minLines={oneLine ? lineCount : undefined}
|
minLines={oneLine ? lineCount : undefined}
|
||||||
maxLines={oneLine ? lineCount : undefined}
|
maxLines={oneLine ? lineCount : undefined}
|
||||||
|
@ -55,22 +40,11 @@ export const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
editorProps={{
|
editorProps={{
|
||||||
$blockScrolling: true,
|
$blockScrolling: true,
|
||||||
}}
|
}}
|
||||||
setOptions={{}}
|
setOptions={{
|
||||||
commands={[
|
enableBasicAutocompletion: false,
|
||||||
{
|
enableLiveAutocompletion: false,
|
||||||
name: "submit",
|
}}
|
||||||
bindKey: { mac: "Cmd-Enter", win: "Ctrl-Enter" },
|
|
||||||
exec: () => onSubmitRef.current?.(),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
markers={errorLocations?.map((location) => ({
|
|
||||||
startRow: location.start.line - 1,
|
|
||||||
startCol: location.start.column - 1,
|
|
||||||
endRow: location.end.line - 1,
|
|
||||||
endCol: location.end.column - 1,
|
|
||||||
className: "ace-error-marker",
|
|
||||||
type: "text",
|
|
||||||
}))}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export default CodeEditor;
|
||||||
|
|
|
@ -1,222 +1,130 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import _ from "lodash";
|
||||||
SqDistribution,
|
import type { Distribution } from "@quri/squiggle-lang";
|
||||||
result,
|
import { distributionErrorToString } from "@quri/squiggle-lang";
|
||||||
SqDistributionError,
|
import { Vega, VisualizationSpec } from "react-vega";
|
||||||
resultMap,
|
import * as chartSpecification from "../vega-specs/spec-distributions.json";
|
||||||
SqRecord,
|
import { ErrorBox } from "./ErrorBox";
|
||||||
environment,
|
|
||||||
SqDistributionTag,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { Vega } from "react-vega";
|
|
||||||
import { ErrorAlert } from "./Alert";
|
|
||||||
import { useSize } from "react-use";
|
import { useSize } from "react-use";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
buildVegaSpec,
|
linearXScale,
|
||||||
DistributionChartSpecOptions,
|
logXScale,
|
||||||
} from "../lib/distributionSpecBuilder";
|
linearYScale,
|
||||||
import { NumberShower } from "./NumberShower";
|
expYScale,
|
||||||
import { Plot, parsePlot } from "../lib/plotParser";
|
} from "./DistributionVegaScales";
|
||||||
import { flattenResult } from "../lib/utility";
|
import styled from "styled-components";
|
||||||
import { hasMassBelowZero } from "../lib/distributionUtils";
|
|
||||||
|
|
||||||
export type DistributionPlottingSettings = {
|
type DistributionChartProps = {
|
||||||
/** Whether to show a summary of means, stdev, percentiles etc */
|
distribution: Distribution;
|
||||||
showSummary: boolean;
|
|
||||||
actions?: boolean;
|
|
||||||
} & DistributionChartSpecOptions;
|
|
||||||
|
|
||||||
export type DistributionChartProps = {
|
|
||||||
plot: Plot;
|
|
||||||
environment: environment;
|
|
||||||
width?: number;
|
width?: number;
|
||||||
height: number;
|
height: number;
|
||||||
xAxisType?: "number" | "dateTime";
|
/** Whether to show the user graph controls (scale etc) */
|
||||||
} & DistributionPlottingSettings;
|
showControls?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export function defaultPlot(distribution: SqDistribution): Plot {
|
export const DistributionChart: React.FC<DistributionChartProps> = ({
|
||||||
return { distributions: [{ name: "default", distribution }] };
|
distribution,
|
||||||
}
|
|
||||||
|
|
||||||
export function makePlot(record: SqRecord): Plot | void {
|
|
||||||
const plotResult = parsePlot(record);
|
|
||||||
if (plotResult.tag === "Ok") {
|
|
||||||
return plotResult.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|
||||||
const {
|
|
||||||
plot,
|
|
||||||
environment,
|
|
||||||
height,
|
height,
|
||||||
showSummary,
|
|
||||||
width,
|
width,
|
||||||
logX,
|
showControls = false,
|
||||||
actions = false,
|
}: DistributionChartProps) => {
|
||||||
} = props;
|
let [isLogX, setLogX] = React.useState(false);
|
||||||
const [sized] = useSize((size) => {
|
let [isExpY, setExpY] = React.useState(false);
|
||||||
const shapes = flattenResult(
|
let shape = distribution.pointSet();
|
||||||
plot.distributions.map((x) =>
|
const [sized, _] = useSize((size) => {
|
||||||
resultMap(x.distribution.pointSet(environment), (pointSet) => ({
|
if (shape.tag === "Ok") {
|
||||||
name: x.name,
|
let massBelow0 =
|
||||||
// color: x.color, // not supported yet
|
shape.value.continuous.some((x) => x.x <= 0) ||
|
||||||
...pointSet.asShape(),
|
shape.value.discrete.some((x) => x.x <= 0);
|
||||||
}))
|
let spec = buildVegaSpec(isLogX, isExpY);
|
||||||
)
|
let widthProp = width ? width - 20 : size.width - 10;
|
||||||
);
|
|
||||||
|
|
||||||
if (shapes.tag === "Error") {
|
// Check whether we should disable the checkbox
|
||||||
return (
|
var logCheckbox = (
|
||||||
<ErrorAlert heading="Distribution Error">
|
<CheckBox label="Log X scale" value={isLogX} onChange={setLogX} />
|
||||||
{shapes.value.toString()}
|
);
|
||||||
</ErrorAlert>
|
if (massBelow0) {
|
||||||
|
logCheckbox = (
|
||||||
|
<CheckBox
|
||||||
|
label="Log X scale"
|
||||||
|
value={isLogX}
|
||||||
|
onChange={setLogX}
|
||||||
|
disabled={true}
|
||||||
|
tooltip={
|
||||||
|
"Your distribution has mass lower than or equal to 0. Log only works on strictly positive values."
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is a sample set, include the samples
|
var result = (
|
||||||
const samples: number[] = [];
|
<div>
|
||||||
for (const { distribution } of plot?.distributions) {
|
|
||||||
if (distribution.tag === SqDistributionTag.SampleSet) {
|
|
||||||
samples.push(...distribution.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const domain = shapes.value.flatMap((shape) =>
|
|
||||||
shape.discrete.concat(shape.continuous)
|
|
||||||
);
|
|
||||||
|
|
||||||
const spec = buildVegaSpec({
|
|
||||||
...props,
|
|
||||||
minX: props.minX ?? Math.min(...domain.map((x) => x.x)),
|
|
||||||
maxX: props.minX ?? Math.max(...domain.map((x) => x.x)),
|
|
||||||
maxY: Math.max(...domain.map((x) => x.y)),
|
|
||||||
});
|
|
||||||
|
|
||||||
// I think size.width is sometimes not finite due to the component not being in a visible context
|
|
||||||
// This occurs during testing
|
|
||||||
let widthProp = width
|
|
||||||
? width
|
|
||||||
: Number.isFinite(size.width)
|
|
||||||
? size.width
|
|
||||||
: 400;
|
|
||||||
if (widthProp < 20) {
|
|
||||||
console.warn(
|
|
||||||
`Width of Distribution is set to ${widthProp}, which is too small`
|
|
||||||
);
|
|
||||||
widthProp = 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vegaData = { data: shapes.value, samples };
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ width: widthProp }}>
|
|
||||||
{logX && shapes.value.some(hasMassBelowZero) ? (
|
|
||||||
<ErrorAlert heading="Log Domain Error">
|
|
||||||
Cannot graph distribution with negative values on logarithmic scale.
|
|
||||||
</ErrorAlert>
|
|
||||||
) : (
|
|
||||||
<Vega
|
<Vega
|
||||||
spec={spec}
|
spec={spec}
|
||||||
data={vegaData}
|
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
|
||||||
width={widthProp - 10}
|
width={widthProp}
|
||||||
height={height}
|
height={height}
|
||||||
actions={actions}
|
actions={false}
|
||||||
/>
|
/>
|
||||||
)}
|
{showControls && (
|
||||||
<div className="flex justify-center">
|
<div>
|
||||||
{showSummary && plot.distributions.length === 1 && (
|
{logCheckbox}
|
||||||
<SummaryTable
|
<CheckBox label="Exp Y scale" value={isExpY} onChange={setExpY} />
|
||||||
distribution={plot.distributions[0].distribution}
|
|
||||||
environment={environment}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
var result = (
|
||||||
|
<ErrorBox heading="Distribution Error">
|
||||||
|
{distributionErrorToString(shape.value)}
|
||||||
|
</ErrorBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
return sized;
|
return sized;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
function buildVegaSpec(isLogX: boolean, isExpY: boolean): VisualizationSpec {
|
||||||
children,
|
return {
|
||||||
}) => (
|
...chartSpecification,
|
||||||
<th className="border border-slate-200 bg-slate-50 py-1 px-2 text-slate-500 font-semibold">
|
scales: [
|
||||||
{children}
|
isLogX ? logXScale : linearXScale,
|
||||||
</th>
|
isExpY ? expYScale : linearYScale,
|
||||||
);
|
],
|
||||||
|
} as VisualizationSpec;
|
||||||
const Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
||||||
<td className="border border-slate-200 py-1 px-2 text-slate-900">
|
|
||||||
{children}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
|
|
||||||
type SummaryTableProps = {
|
|
||||||
distribution: SqDistribution;
|
|
||||||
environment: environment;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SummaryTable: React.FC<SummaryTableProps> = ({
|
|
||||||
distribution,
|
|
||||||
environment,
|
|
||||||
}) => {
|
|
||||||
const mean = distribution.mean(environment);
|
|
||||||
const stdev = distribution.stdev(environment);
|
|
||||||
const p5 = distribution.inv(environment, 0.05);
|
|
||||||
const p10 = distribution.inv(environment, 0.1);
|
|
||||||
const p25 = distribution.inv(environment, 0.25);
|
|
||||||
const p50 = distribution.inv(environment, 0.5);
|
|
||||||
const p75 = distribution.inv(environment, 0.75);
|
|
||||||
const p90 = distribution.inv(environment, 0.9);
|
|
||||||
const p95 = distribution.inv(environment, 0.95);
|
|
||||||
|
|
||||||
const hasResult = (x: result<number, SqDistributionError>): boolean =>
|
|
||||||
x.tag === "Ok";
|
|
||||||
|
|
||||||
const unwrapResult = (
|
|
||||||
x: result<number, SqDistributionError>
|
|
||||||
): React.ReactNode => {
|
|
||||||
if (x.tag === "Ok") {
|
|
||||||
return <NumberShower number={x.value} />;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<ErrorAlert heading="Distribution Error">
|
|
||||||
{x.value.toString()}
|
|
||||||
</ErrorAlert>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
interface CheckBoxProps {
|
||||||
|
label: string;
|
||||||
|
onChange: (x: boolean) => void;
|
||||||
|
value: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
tooltip?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Label = styled.label<{ disabled: boolean }>`
|
||||||
|
${(props) => props.disabled && "color: #999;"}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CheckBox = ({
|
||||||
|
label,
|
||||||
|
onChange,
|
||||||
|
value,
|
||||||
|
disabled = false,
|
||||||
|
tooltip,
|
||||||
|
}: CheckBoxProps) => {
|
||||||
return (
|
return (
|
||||||
<table className="border border-collapse border-slate-400">
|
<span title={tooltip}>
|
||||||
<thead className="bg-slate-50">
|
<input
|
||||||
<tr>
|
type="checkbox"
|
||||||
<TableHeadCell>{"Mean"}</TableHeadCell>
|
value={value + ""}
|
||||||
{hasResult(stdev) && <TableHeadCell>{"Stdev"}</TableHeadCell>}
|
onChange={() => onChange(!value)}
|
||||||
<TableHeadCell>{"5%"}</TableHeadCell>
|
disabled={disabled}
|
||||||
<TableHeadCell>{"10%"}</TableHeadCell>
|
/>
|
||||||
<TableHeadCell>{"25%"}</TableHeadCell>
|
<Label disabled={disabled}>{label}</Label>
|
||||||
<TableHeadCell>{"50%"}</TableHeadCell>
|
</span>
|
||||||
<TableHeadCell>{"75%"}</TableHeadCell>
|
|
||||||
<TableHeadCell>{"90%"}</TableHeadCell>
|
|
||||||
<TableHeadCell>{"95%"}</TableHeadCell>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<Cell>{unwrapResult(mean)}</Cell>
|
|
||||||
{hasResult(stdev) && <Cell>{unwrapResult(stdev)}</Cell>}
|
|
||||||
<Cell>{unwrapResult(p5)}</Cell>
|
|
||||||
<Cell>{unwrapResult(p10)}</Cell>
|
|
||||||
<Cell>{unwrapResult(p25)}</Cell>
|
|
||||||
<Cell>{unwrapResult(p50)}</Cell>
|
|
||||||
<Cell>{unwrapResult(p75)}</Cell>
|
|
||||||
<Cell>{unwrapResult(p90)}</Cell>
|
|
||||||
<Cell>{unwrapResult(p95)}</Cell>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
20
packages/components/src/components/ErrorBox.tsx
Normal file
20
packages/components/src/components/ErrorBox.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const ShowError = styled.div`
|
||||||
|
border: 1px solid #792e2e;
|
||||||
|
background: #eee2e2;
|
||||||
|
padding: 0.4em 0.8em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ErrorBox: React.FC<{
|
||||||
|
heading: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}> = ({ heading = "Error", children }) => {
|
||||||
|
return (
|
||||||
|
<ShowError>
|
||||||
|
<h3>{heading}</h3>
|
||||||
|
{children}
|
||||||
|
</ShowError>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,107 +1,115 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import _ from "lodash";
|
||||||
SqLambda,
|
import type { Spec } from "vega";
|
||||||
environment,
|
import type { Distribution, errorValue, result } from "@quri/squiggle-lang";
|
||||||
SqValueTag,
|
import { createClassFromSpec } from "react-vega";
|
||||||
SqError,
|
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
||||||
} from "@quri/squiggle-lang";
|
import { DistributionChart } from "./DistributionChart";
|
||||||
import { FunctionChart1Dist } from "./FunctionChart1Dist";
|
import { ErrorBox } from "./ErrorBox";
|
||||||
import { FunctionChart1Number } from "./FunctionChart1Number";
|
|
||||||
import { DistributionPlottingSettings } from "./DistributionChart";
|
|
||||||
import { MessageAlert } from "./Alert";
|
|
||||||
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
|
||||||
|
|
||||||
export type FunctionChartSettings = {
|
let SquigglePercentilesChart = createClassFromSpec({
|
||||||
start: number;
|
spec: percentilesSpec as Spec,
|
||||||
stop: number;
|
});
|
||||||
count: number;
|
|
||||||
|
type distPlusFn = (a: number) => result<Distribution, errorValue>;
|
||||||
|
|
||||||
|
const _rangeByCount = (start: number, stop: number, count: number) => {
|
||||||
|
const step = (stop - start) / (count - 1);
|
||||||
|
const items = _.range(start, stop, step);
|
||||||
|
const result = items.concat([stop]);
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FunctionChartProps {
|
function unwrap<a, b>(x: result<a, b>): a {
|
||||||
fn: SqLambda;
|
if (x.tag === "Ok") {
|
||||||
chartSettings: FunctionChartSettings;
|
return x.value;
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
|
||||||
environment: environment;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FunctionCallErrorAlert = ({ error }: { error: SqError }) => {
|
|
||||||
const [expanded, setExpanded] = React.useState(false);
|
|
||||||
if (expanded) {
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<MessageAlert heading="Function Display Failed">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<span
|
|
||||||
className="underline decoration-dashed cursor-pointer"
|
|
||||||
onClick={() => setExpanded(!expanded)}
|
|
||||||
>
|
|
||||||
{expanded ? "Hide" : "Show"} error details
|
|
||||||
</span>
|
|
||||||
{expanded ? <SquiggleErrorAlert error={error} /> : null}
|
|
||||||
</div>
|
|
||||||
</MessageAlert>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FunctionChart: React.FC<FunctionChartProps> = ({
|
|
||||||
fn,
|
|
||||||
chartSettings,
|
|
||||||
environment,
|
|
||||||
distributionPlotSettings,
|
|
||||||
height,
|
|
||||||
}) => {
|
|
||||||
console.log(fn.parameters().length);
|
|
||||||
if (fn.parameters().length !== 1) {
|
|
||||||
return (
|
|
||||||
<MessageAlert heading="Function Display Not Supported">
|
|
||||||
Only functions with one parameter are displayed.
|
|
||||||
</MessageAlert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const result1 = fn.call([chartSettings.start]);
|
|
||||||
const result2 = fn.call([chartSettings.stop]);
|
|
||||||
const getValidResult = () => {
|
|
||||||
if (result1.tag === "Ok") {
|
|
||||||
return result1;
|
|
||||||
} else if (result2.tag === "Ok") {
|
|
||||||
return result2;
|
|
||||||
} else {
|
} else {
|
||||||
return result1;
|
throw Error("FAILURE TO UNWRAP");
|
||||||
}
|
}
|
||||||
};
|
|
||||||
const validResult = getValidResult();
|
|
||||||
|
|
||||||
if (validResult.tag === "Error") {
|
|
||||||
return <FunctionCallErrorAlert error={validResult.value} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (validResult.value.tag) {
|
function mapFilter<a, b>(xs: a[], f: (x: a) => b | undefined): b[] {
|
||||||
case SqValueTag.Distribution:
|
let initial: b[] = [];
|
||||||
return (
|
return xs.reduce((previous, current) => {
|
||||||
<FunctionChart1Dist
|
let value: b | undefined = f(current);
|
||||||
fn={fn}
|
if (value !== undefined) {
|
||||||
chartSettings={chartSettings}
|
return previous.concat([value]);
|
||||||
environment={environment}
|
} else {
|
||||||
height={height}
|
return previous;
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case SqValueTag.Number:
|
|
||||||
return (
|
|
||||||
<FunctionChart1Number
|
|
||||||
fn={fn}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment}
|
|
||||||
height={height}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<MessageAlert heading="Function Display Not Supported">
|
|
||||||
There is no function visualization for this type of output:{" "}
|
|
||||||
<span className="font-bold">{validResult.value.tag}</span>
|
|
||||||
</MessageAlert>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}, initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FunctionChart: React.FC<{
|
||||||
|
distPlusFn: distPlusFn;
|
||||||
|
diagramStart: number;
|
||||||
|
diagramStop: number;
|
||||||
|
diagramCount: number;
|
||||||
|
}> = ({ distPlusFn, diagramStart, diagramStop, diagramCount }) => {
|
||||||
|
let [mouseOverlay, setMouseOverlay] = React.useState(0);
|
||||||
|
function handleHover(...args) {
|
||||||
|
setMouseOverlay(args[1]);
|
||||||
|
}
|
||||||
|
function handleOut() {
|
||||||
|
setMouseOverlay(NaN);
|
||||||
|
}
|
||||||
|
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
|
||||||
|
let mouseItem = distPlusFn(mouseOverlay);
|
||||||
|
let showChart =
|
||||||
|
mouseItem.tag === "Ok" ? (
|
||||||
|
<DistributionChart
|
||||||
|
distribution={mouseItem.value}
|
||||||
|
width={400}
|
||||||
|
height={140}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
|
let data1 = _rangeByCount(diagramStart, diagramStop, diagramCount);
|
||||||
|
let valueData = mapFilter(data1, (x) => {
|
||||||
|
let result = distPlusFn(x);
|
||||||
|
if (result.tag === "Ok") {
|
||||||
|
return { x: x, value: result.value };
|
||||||
|
}
|
||||||
|
}).map(({ x, value }) => {
|
||||||
|
return {
|
||||||
|
x: x,
|
||||||
|
p1: unwrap(value.inv(0.01)),
|
||||||
|
p5: unwrap(value.inv(0.05)),
|
||||||
|
p10: unwrap(value.inv(0.12)),
|
||||||
|
p20: unwrap(value.inv(0.2)),
|
||||||
|
p30: unwrap(value.inv(0.3)),
|
||||||
|
p40: unwrap(value.inv(0.4)),
|
||||||
|
p50: unwrap(value.inv(0.5)),
|
||||||
|
p60: unwrap(value.inv(0.6)),
|
||||||
|
p70: unwrap(value.inv(0.7)),
|
||||||
|
p80: unwrap(value.inv(0.8)),
|
||||||
|
p90: unwrap(value.inv(0.9)),
|
||||||
|
p95: unwrap(value.inv(0.95)),
|
||||||
|
p99: unwrap(value.inv(0.99)),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let errorData = mapFilter(data1, (x) => {
|
||||||
|
let result = distPlusFn(x);
|
||||||
|
if (result.tag === "Error") {
|
||||||
|
return { x: x, error: result.value };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let error2 = _.groupBy(errorData, (x) => x.error);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SquigglePercentilesChart
|
||||||
|
data={{ facet: valueData }}
|
||||||
|
actions={false}
|
||||||
|
signalListeners={signalListeners}
|
||||||
|
/>
|
||||||
|
{showChart}
|
||||||
|
{_.keysIn(error2).map((k) => (
|
||||||
|
<ErrorBox heading={k}>
|
||||||
|
{`Values: [${error2[k].map((r) => r.x.toFixed(2)).join(",")}]`}
|
||||||
|
</ErrorBox>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,225 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import _ from "lodash";
|
|
||||||
import type { Spec } from "vega";
|
|
||||||
import {
|
|
||||||
SqDistribution,
|
|
||||||
result,
|
|
||||||
SqLambda,
|
|
||||||
environment,
|
|
||||||
SqError,
|
|
||||||
SqValue,
|
|
||||||
SqValueTag,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { createClassFromSpec } from "react-vega";
|
|
||||||
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
|
||||||
import {
|
|
||||||
DistributionChart,
|
|
||||||
DistributionPlottingSettings,
|
|
||||||
defaultPlot,
|
|
||||||
} from "./DistributionChart";
|
|
||||||
import { NumberShower } from "./NumberShower";
|
|
||||||
import { ErrorAlert } from "./Alert";
|
|
||||||
|
|
||||||
let SquigglePercentilesChart = createClassFromSpec({
|
|
||||||
spec: percentilesSpec as Spec,
|
|
||||||
});
|
|
||||||
|
|
||||||
const _rangeByCount = (start: number, stop: number, count: number) => {
|
|
||||||
const step = (stop - start) / (count - 1);
|
|
||||||
const items = _.range(start, stop, step);
|
|
||||||
const result = items.concat([stop]);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
function unwrap<a, b>(x: result<a, b>): a {
|
|
||||||
if (x.tag === "Ok") {
|
|
||||||
return x.value;
|
|
||||||
} else {
|
|
||||||
throw Error("FAILURE TO UNWRAP");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export type FunctionChartSettings = {
|
|
||||||
start: number;
|
|
||||||
stop: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface FunctionChart1DistProps {
|
|
||||||
fn: SqLambda;
|
|
||||||
chartSettings: FunctionChartSettings;
|
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
|
||||||
environment: environment;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type percentiles = {
|
|
||||||
x: number;
|
|
||||||
p1: number;
|
|
||||||
p5: number;
|
|
||||||
p10: number;
|
|
||||||
p20: number;
|
|
||||||
p30: number;
|
|
||||||
p40: number;
|
|
||||||
p50: number;
|
|
||||||
p60: number;
|
|
||||||
p70: number;
|
|
||||||
p80: number;
|
|
||||||
p90: number;
|
|
||||||
p95: number;
|
|
||||||
p99: number;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
type errors = _.Dictionary<
|
|
||||||
{
|
|
||||||
x: number;
|
|
||||||
value: string;
|
|
||||||
}[]
|
|
||||||
>;
|
|
||||||
|
|
||||||
type point = { x: number; value: result<SqDistribution, string> };
|
|
||||||
|
|
||||||
let getPercentiles = ({
|
|
||||||
chartSettings,
|
|
||||||
fn,
|
|
||||||
environment,
|
|
||||||
}: {
|
|
||||||
chartSettings: FunctionChartSettings;
|
|
||||||
fn: SqLambda;
|
|
||||||
environment: environment;
|
|
||||||
}) => {
|
|
||||||
let chartPointsToRender = _rangeByCount(
|
|
||||||
chartSettings.start,
|
|
||||||
chartSettings.stop,
|
|
||||||
chartSettings.count
|
|
||||||
);
|
|
||||||
|
|
||||||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
|
||||||
let result = fn.call([x]);
|
|
||||||
if (result.tag === "Ok") {
|
|
||||||
if (result.value.tag === SqValueTag.Distribution) {
|
|
||||||
return { x, value: { tag: "Ok", value: result.value.value } };
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
value: {
|
|
||||||
tag: "Error",
|
|
||||||
value:
|
|
||||||
"Cannot currently render functions that don't return distributions",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
value: { tag: "Error", value: result.value.toString() },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let initialPartition: [
|
|
||||||
{ x: number; value: SqDistribution }[],
|
|
||||||
{ x: number; value: string }[]
|
|
||||||
] = [[], []];
|
|
||||||
|
|
||||||
let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
|
|
||||||
if (current.value.tag === "Ok") {
|
|
||||||
acc[0].push({ x: current.x, value: current.value.value });
|
|
||||||
} else {
|
|
||||||
acc[1].push({ x: current.x, value: current.value.value });
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, initialPartition);
|
|
||||||
|
|
||||||
let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
|
|
||||||
|
|
||||||
let percentiles: percentiles = functionImage.map(({ x, value }) => {
|
|
||||||
const res = {
|
|
||||||
x: x,
|
|
||||||
p1: unwrap(value.inv(environment, 0.01)),
|
|
||||||
p5: unwrap(value.inv(environment, 0.05)),
|
|
||||||
p10: unwrap(value.inv(environment, 0.1)),
|
|
||||||
p20: unwrap(value.inv(environment, 0.2)),
|
|
||||||
p30: unwrap(value.inv(environment, 0.3)),
|
|
||||||
p40: unwrap(value.inv(environment, 0.4)),
|
|
||||||
p50: unwrap(value.inv(environment, 0.5)),
|
|
||||||
p60: unwrap(value.inv(environment, 0.6)),
|
|
||||||
p70: unwrap(value.inv(environment, 0.7)),
|
|
||||||
p80: unwrap(value.inv(environment, 0.8)),
|
|
||||||
p90: unwrap(value.inv(environment, 0.9)),
|
|
||||||
p95: unwrap(value.inv(environment, 0.95)),
|
|
||||||
p99: unwrap(value.inv(environment, 0.99)),
|
|
||||||
};
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
|
|
||||||
return { percentiles, errors: groupedErrors };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
|
||||||
fn,
|
|
||||||
chartSettings,
|
|
||||||
environment,
|
|
||||||
distributionPlotSettings,
|
|
||||||
height,
|
|
||||||
}) => {
|
|
||||||
let [mouseOverlay, setMouseOverlay] = React.useState(0);
|
|
||||||
function handleHover(_name: string, value: unknown) {
|
|
||||||
setMouseOverlay(value as number);
|
|
||||||
}
|
|
||||||
function handleOut() {
|
|
||||||
setMouseOverlay(NaN);
|
|
||||||
}
|
|
||||||
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
|
|
||||||
|
|
||||||
//TODO: This custom error handling is a bit hacky and should be improved.
|
|
||||||
let mouseItem: result<SqValue, SqError> = !!mouseOverlay
|
|
||||||
? fn.call([mouseOverlay])
|
|
||||||
: {
|
|
||||||
tag: "Error",
|
|
||||||
value: SqError.createOtherError(
|
|
||||||
"Hover x-coordinate returned NaN. Expected a number."
|
|
||||||
),
|
|
||||||
};
|
|
||||||
let showChart =
|
|
||||||
mouseItem.tag === "Ok" &&
|
|
||||||
mouseItem.value.tag === SqValueTag.Distribution ? (
|
|
||||||
<DistributionChart
|
|
||||||
plot={defaultPlot(mouseItem.value.value)}
|
|
||||||
environment={environment}
|
|
||||||
width={400}
|
|
||||||
height={50}
|
|
||||||
{...distributionPlotSettings}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
let getPercentilesMemoized = React.useMemo(
|
|
||||||
() => getPercentiles({ chartSettings, fn, environment }),
|
|
||||||
[environment, fn]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SquigglePercentilesChart
|
|
||||||
data={{ facet: getPercentilesMemoized.percentiles }}
|
|
||||||
height={height}
|
|
||||||
actions={false}
|
|
||||||
signalListeners={signalListeners}
|
|
||||||
/>
|
|
||||||
{showChart}
|
|
||||||
{_.entries(getPercentilesMemoized.errors).map(
|
|
||||||
([errorName, errorPoints]) => (
|
|
||||||
<ErrorAlert key={errorName} heading={errorName}>
|
|
||||||
Values:{" "}
|
|
||||||
{errorPoints
|
|
||||||
.map((r, i) => <NumberShower key={i} number={r.x} />)
|
|
||||||
.reduce((a, b) => (
|
|
||||||
<>
|
|
||||||
{a}, {b}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</ErrorAlert>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,119 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import _ from "lodash";
|
|
||||||
import type { Spec } from "vega";
|
|
||||||
import { result, SqLambda, environment, SqValueTag } from "@quri/squiggle-lang";
|
|
||||||
import { createClassFromSpec } from "react-vega";
|
|
||||||
import * as lineChartSpec from "../vega-specs/spec-line-chart.json";
|
|
||||||
import { ErrorAlert } from "./Alert";
|
|
||||||
import { squiggleValueTag } from "@quri/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_tag";
|
|
||||||
|
|
||||||
let SquiggleLineChart = createClassFromSpec({
|
|
||||||
spec: lineChartSpec as Spec,
|
|
||||||
});
|
|
||||||
|
|
||||||
const _rangeByCount = (start: number, stop: number, count: number) => {
|
|
||||||
const step = (stop - start) / (count - 1);
|
|
||||||
const items = _.range(start, stop, step);
|
|
||||||
const result = items.concat([stop]);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FunctionChartSettings = {
|
|
||||||
start: number;
|
|
||||||
stop: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface FunctionChart1NumberProps {
|
|
||||||
fn: SqLambda;
|
|
||||||
chartSettings: FunctionChartSettings;
|
|
||||||
environment: environment;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type point = { x: number; value: result<number, string> };
|
|
||||||
|
|
||||||
let getFunctionImage = ({
|
|
||||||
chartSettings,
|
|
||||||
fn,
|
|
||||||
environment,
|
|
||||||
}: {
|
|
||||||
chartSettings: FunctionChartSettings;
|
|
||||||
fn: SqLambda;
|
|
||||||
environment: environment;
|
|
||||||
}) => {
|
|
||||||
let chartPointsToRender = _rangeByCount(
|
|
||||||
chartSettings.start,
|
|
||||||
chartSettings.stop,
|
|
||||||
chartSettings.count
|
|
||||||
);
|
|
||||||
|
|
||||||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
|
||||||
let result = fn.call([x]);
|
|
||||||
if (result.tag === "Ok") {
|
|
||||||
if (result.value.tag === SqValueTag.Number) {
|
|
||||||
return { x, value: { tag: "Ok", value: result.value.value } };
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
value: {
|
|
||||||
tag: "Error",
|
|
||||||
value: "This component expected number outputs",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
value: { tag: "Error", value: result.value.toString() },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let initialPartition: [
|
|
||||||
{ x: number; value: number }[],
|
|
||||||
{ x: number; value: string }[]
|
|
||||||
] = [[], []];
|
|
||||||
|
|
||||||
let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
|
|
||||||
if (current.value.tag === "Ok") {
|
|
||||||
acc[0].push({ x: current.x, value: current.value.value });
|
|
||||||
} else {
|
|
||||||
acc[1].push({ x: current.x, value: current.value.value });
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, initialPartition);
|
|
||||||
|
|
||||||
return { errors, functionImage };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FunctionChart1Number: React.FC<FunctionChart1NumberProps> = ({
|
|
||||||
fn,
|
|
||||||
chartSettings,
|
|
||||||
environment,
|
|
||||||
height,
|
|
||||||
}: FunctionChart1NumberProps) => {
|
|
||||||
let getFunctionImageMemoized = React.useMemo(
|
|
||||||
() => getFunctionImage({ chartSettings, fn, environment }),
|
|
||||||
[environment, fn]
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = getFunctionImageMemoized.functionImage.map(({ x, value }) => ({
|
|
||||||
x,
|
|
||||||
y: value,
|
|
||||||
}));
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SquiggleLineChart
|
|
||||||
data={{ facet: data }}
|
|
||||||
height={height}
|
|
||||||
actions={false}
|
|
||||||
/>
|
|
||||||
{getFunctionImageMemoized.errors.map(({ x, value }) => (
|
|
||||||
<ErrorAlert key={x} heading={value}>
|
|
||||||
Error at point ${x}
|
|
||||||
</ErrorAlert>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,49 +0,0 @@
|
||||||
import _ from "lodash";
|
|
||||||
import React, { FC } from "react";
|
|
||||||
import AceEditor from "react-ace";
|
|
||||||
|
|
||||||
import "ace-builds/src-noconflict/mode-json";
|
|
||||||
import "ace-builds/src-noconflict/theme-github";
|
|
||||||
|
|
||||||
interface CodeEditorProps {
|
|
||||||
value: string;
|
|
||||||
onChange: (value: string) => void;
|
|
||||||
oneLine?: boolean;
|
|
||||||
width?: number;
|
|
||||||
height: number;
|
|
||||||
showGutter?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const JsonEditor: FC<CodeEditorProps> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
oneLine = false,
|
|
||||||
showGutter = false,
|
|
||||||
height,
|
|
||||||
}) => {
|
|
||||||
const lineCount = value.split("\n").length;
|
|
||||||
const id = _.uniqueId();
|
|
||||||
return (
|
|
||||||
<AceEditor
|
|
||||||
value={value}
|
|
||||||
mode="json"
|
|
||||||
theme="github"
|
|
||||||
width={"100%"}
|
|
||||||
height={String(height) + "px"}
|
|
||||||
minLines={oneLine ? lineCount : undefined}
|
|
||||||
maxLines={oneLine ? lineCount : undefined}
|
|
||||||
showGutter={showGutter}
|
|
||||||
highlightActiveLine={false}
|
|
||||||
showPrintMargin={false}
|
|
||||||
onChange={onChange}
|
|
||||||
name={id}
|
|
||||||
editorProps={{
|
|
||||||
$blockScrolling: true,
|
|
||||||
}}
|
|
||||||
setOptions={{
|
|
||||||
enableBasicAutocompletion: false,
|
|
||||||
enableLiveAutocompletion: false,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
const orderOfMagnitudeNum = (n: number) => {
|
const orderOfMagnitudeNum = (n: number) => {
|
||||||
return Math.pow(10, n);
|
return Math.pow(10, n);
|
||||||
|
@ -73,23 +74,25 @@ export interface NumberShowerProps {
|
||||||
precision?: number;
|
precision?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NumberShower: React.FC<NumberShowerProps> = ({
|
export let NumberShower: React.FC<NumberShowerProps> = ({
|
||||||
number,
|
number,
|
||||||
precision = 2,
|
precision = 2,
|
||||||
}) => {
|
}: NumberShowerProps) => {
|
||||||
const numberWithPresentation = numberShow(number, precision);
|
let numberWithPresentation = numberShow(number, precision);
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{numberWithPresentation.value}
|
{numberWithPresentation.value}
|
||||||
{numberWithPresentation.symbol}
|
{numberWithPresentation.symbol}
|
||||||
{numberWithPresentation.power ? (
|
{numberWithPresentation.power ? (
|
||||||
<span>
|
<span>
|
||||||
{"\u00b7" /* dot symbol */}10
|
{"\u00b710"}
|
||||||
<span style={{ fontSize: "0.6em", verticalAlign: "super" }}>
|
<span style={{ fontSize: "0.6em", verticalAlign: "super" }}>
|
||||||
{numberWithPresentation.power}
|
{numberWithPresentation.power}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,158 +1,261 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import _ from "lodash";
|
||||||
|
import styled from "styled-components";
|
||||||
import {
|
import {
|
||||||
SqValue,
|
run,
|
||||||
environment,
|
errorValueToString,
|
||||||
SqProject,
|
squiggleExpression,
|
||||||
defaultEnvironment,
|
bindings,
|
||||||
|
samplingParams,
|
||||||
|
jsImports,
|
||||||
|
defaultImports,
|
||||||
|
defaultBindings,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { useSquiggle } from "../lib/hooks";
|
import { NumberShower } from "./NumberShower";
|
||||||
import { SquiggleViewer } from "./SquiggleViewer";
|
import { DistributionChart } from "./DistributionChart";
|
||||||
import { JsImports } from "../lib/jsImports";
|
import { ErrorBox } from "./ErrorBox";
|
||||||
import { getValueToRender } from "../lib/utility";
|
|
||||||
|
|
||||||
export type SquiggleChartProps = {
|
const variableBox = {
|
||||||
|
Component: styled.div`
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-bottom: 0.4em;
|
||||||
|
`,
|
||||||
|
Heading: styled.div`
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-left: 0.8em;
|
||||||
|
padding-right: 0.8em;
|
||||||
|
padding-top: 0.1em;
|
||||||
|
`,
|
||||||
|
Body: styled.div`
|
||||||
|
padding: 0.4em 0.8em;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface VariableBoxProps {
|
||||||
|
heading: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
showTypes?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||||
|
heading = "Error",
|
||||||
|
children,
|
||||||
|
showTypes = false,
|
||||||
|
}: VariableBoxProps) => {
|
||||||
|
if (showTypes) {
|
||||||
|
return (
|
||||||
|
<variableBox.Component>
|
||||||
|
<variableBox.Heading>
|
||||||
|
<h3>{heading}</h3>
|
||||||
|
</variableBox.Heading>
|
||||||
|
<variableBox.Body>{children}</variableBox.Body>
|
||||||
|
</variableBox.Component>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let RecordKeyHeader = styled.h3``;
|
||||||
|
|
||||||
|
export interface SquiggleItemProps {
|
||||||
/** The input string for squiggle */
|
/** The input string for squiggle */
|
||||||
code: string;
|
expression: squiggleExpression;
|
||||||
/** Allows to re-run the code if code hasn't changed */
|
width?: number;
|
||||||
executionId?: number;
|
height: number;
|
||||||
|
/** Whether to show type information */
|
||||||
|
showTypes?: boolean;
|
||||||
|
/** Whether to show users graph controls (scale etc) */
|
||||||
|
showControls?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
||||||
|
expression,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
showTypes = false,
|
||||||
|
showControls = false,
|
||||||
|
}: SquiggleItemProps) => {
|
||||||
|
switch (expression.tag) {
|
||||||
|
case "number":
|
||||||
|
return (
|
||||||
|
<VariableBox heading="Number" showTypes={showTypes}>
|
||||||
|
<NumberShower precision={3} number={expression.value} />
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "distribution": {
|
||||||
|
let distType = expression.value.type();
|
||||||
|
return (
|
||||||
|
<VariableBox
|
||||||
|
heading={`Distribution (${distType})`}
|
||||||
|
showTypes={showTypes}
|
||||||
|
>
|
||||||
|
{distType === "Symbolic" && showTypes ? (
|
||||||
|
<>
|
||||||
|
<div>{expression.value.toString()}</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
<DistributionChart
|
||||||
|
distribution={expression.value}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
showControls={showControls}
|
||||||
|
/>
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "string":
|
||||||
|
return (
|
||||||
|
<VariableBox
|
||||||
|
heading="String"
|
||||||
|
showTypes={showTypes}
|
||||||
|
>{`"${expression.value}"`}</VariableBox>
|
||||||
|
);
|
||||||
|
case "boolean":
|
||||||
|
return (
|
||||||
|
<VariableBox heading="Boolean" showTypes={showTypes}>
|
||||||
|
{expression.value.toString()}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "symbol":
|
||||||
|
return (
|
||||||
|
<VariableBox heading="Symbol" showTypes={showTypes}>
|
||||||
|
{expression.value}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "call":
|
||||||
|
return (
|
||||||
|
<VariableBox heading="Call" showTypes={showTypes}>
|
||||||
|
{expression.value}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "array":
|
||||||
|
return (
|
||||||
|
<VariableBox heading="Array" showTypes={showTypes}>
|
||||||
|
{expression.value.map((r) => (
|
||||||
|
<SquiggleItem
|
||||||
|
expression={r}
|
||||||
|
width={width !== undefined ? width - 20 : width}
|
||||||
|
height={50}
|
||||||
|
showTypes={showTypes}
|
||||||
|
showControls={showControls}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "record":
|
||||||
|
return (
|
||||||
|
<VariableBox heading="Record" showTypes={showTypes}>
|
||||||
|
{Object.entries(expression.value).map(([key, r]) => (
|
||||||
|
<>
|
||||||
|
<RecordKeyHeader>{key}</RecordKeyHeader>
|
||||||
|
<SquiggleItem
|
||||||
|
expression={r}
|
||||||
|
width={width !== undefined ? width - 20 : width}
|
||||||
|
height={50}
|
||||||
|
showTypes={showTypes}
|
||||||
|
showControls={showControls}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "arraystring":
|
||||||
|
return (
|
||||||
|
<VariableBox heading="Array String" showTypes={showTypes}>
|
||||||
|
{expression.value.map((r) => `"${r}"`)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "lambda":
|
||||||
|
return (
|
||||||
|
<ErrorBox heading="No Viewer">
|
||||||
|
There is no viewer currently available for function types.
|
||||||
|
</ErrorBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SquiggleChartProps {
|
||||||
|
/** The input string for squiggle */
|
||||||
|
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;
|
||||||
/** If the result is a function, where the function domain starts */
|
/** 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;
|
diagramStart?: number;
|
||||||
/** If the result is a function, where the function domain ends */
|
/** If the result is a function, where the function ends */
|
||||||
diagramStop?: number;
|
diagramStop?: number;
|
||||||
/** If the result is a function, the amount of stops sampled */
|
/** If the result is a function, how many points along the function it samples */
|
||||||
diagramCount?: number;
|
diagramCount?: number;
|
||||||
/** When the squiggle code gets reevaluated */
|
/** When the environment changes */
|
||||||
onChange?(expr: SqValue | undefined, sourceName: string): void;
|
onChange?(expr: squiggleExpression): void;
|
||||||
/** CSS width of the element */
|
/** CSS width of the element */
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
/** Bindings of previous variables declared */
|
||||||
|
bindings?: bindings;
|
||||||
/** JS imported parameters */
|
/** JS imported parameters */
|
||||||
jsImports?: JsImports;
|
jsImports?: jsImports;
|
||||||
/** Whether to show a summary of the distribution */
|
/** Whether to show type information about returns, default false */
|
||||||
showSummary?: boolean;
|
showTypes?: boolean;
|
||||||
/** Set the x scale to be logarithmic by deault */
|
/** Whether to show graph controls (scale etc)*/
|
||||||
logX?: boolean;
|
showControls?: boolean;
|
||||||
/** Set the y scale to be exponential by deault */
|
}
|
||||||
expY?: boolean;
|
|
||||||
/** How to format numbers on the x axis */
|
|
||||||
tickFormat?: string;
|
|
||||||
/** Title of the graphed distribution */
|
|
||||||
title?: string;
|
|
||||||
/** Color of the graphed distribution */
|
|
||||||
color?: string;
|
|
||||||
/** Specify the lower bound of the x scale */
|
|
||||||
minX?: number;
|
|
||||||
/** Specify the upper bound of the x scale */
|
|
||||||
maxX?: number;
|
|
||||||
/** Whether the x-axis should be dates or numbers */
|
|
||||||
xAxisType?: "number" | "dateTime";
|
|
||||||
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
|
||||||
distributionChartActions?: boolean;
|
|
||||||
enableLocalSettings?: boolean;
|
|
||||||
} & (StandaloneExecutionProps | ProjectExecutionProps);
|
|
||||||
|
|
||||||
// Props needed for a standalone execution
|
const ChartWrapper = styled.div`
|
||||||
type StandaloneExecutionProps = {
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
project?: undefined;
|
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
|
||||||
continues?: undefined;
|
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
/** The amount of points returned to draw the distribution, not needed if using a project */
|
`;
|
||||||
environment?: environment;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Props needed when executing inside a project.
|
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
||||||
type ProjectExecutionProps = {
|
squiggleString = "",
|
||||||
environment?: undefined;
|
sampleCount = 1000,
|
||||||
/** The project that this execution is part of */
|
outputXYPoints = 1000,
|
||||||
project: SqProject;
|
onChange = () => {},
|
||||||
/** What other squiggle sources from the project to continue. Default [] */
|
height = 60,
|
||||||
continues?: string[];
|
bindings = defaultBindings,
|
||||||
};
|
|
||||||
const defaultOnChange = () => {};
|
|
||||||
const defaultImports: JsImports = {};
|
|
||||||
|
|
||||||
export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
|
|
||||||
const {
|
|
||||||
showSummary = false,
|
|
||||||
logX = false,
|
|
||||||
expY = false,
|
|
||||||
diagramStart = 0,
|
|
||||||
diagramStop = 10,
|
|
||||||
diagramCount = 20,
|
|
||||||
tickFormat,
|
|
||||||
minX,
|
|
||||||
maxX,
|
|
||||||
color,
|
|
||||||
title,
|
|
||||||
xAxisType = "number",
|
|
||||||
distributionChartActions,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const distributionPlotSettings = {
|
|
||||||
showSummary,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
format: tickFormat,
|
|
||||||
minX,
|
|
||||||
maxX,
|
|
||||||
color,
|
|
||||||
title,
|
|
||||||
xAxisType,
|
|
||||||
actions: distributionChartActions,
|
|
||||||
};
|
|
||||||
|
|
||||||
const chartSettings = {
|
|
||||||
start: diagramStart,
|
|
||||||
stop: diagramStop,
|
|
||||||
count: diagramCount,
|
|
||||||
};
|
|
||||||
|
|
||||||
return { distributionPlotSettings, chartSettings };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
|
||||||
(props) => {
|
|
||||||
const { distributionPlotSettings, chartSettings } =
|
|
||||||
splitSquiggleChartSettings(props);
|
|
||||||
|
|
||||||
const {
|
|
||||||
code,
|
|
||||||
jsImports = defaultImports,
|
jsImports = defaultImports,
|
||||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
|
||||||
executionId = 0,
|
|
||||||
width,
|
width,
|
||||||
height = 200,
|
showTypes = false,
|
||||||
enableLocalSettings = false,
|
showControls = false,
|
||||||
continues,
|
}: SquiggleChartProps) => {
|
||||||
project,
|
let samplingInputs: samplingParams = {
|
||||||
environment,
|
sampleCount: sampleCount,
|
||||||
} = props;
|
xyPointLength: outputXYPoints,
|
||||||
|
};
|
||||||
const resultAndBindings = useSquiggle({
|
let expressionResult = run(
|
||||||
environment,
|
squiggleString,
|
||||||
continues,
|
bindings,
|
||||||
project,
|
samplingInputs,
|
||||||
code,
|
jsImports
|
||||||
jsImports,
|
);
|
||||||
onChange,
|
let internal: JSX.Element;
|
||||||
executionId,
|
if (expressionResult.tag === "Ok") {
|
||||||
});
|
let expression = expressionResult.value;
|
||||||
|
onChange(expression);
|
||||||
const valueToRender = getValueToRender(resultAndBindings);
|
internal = (
|
||||||
|
<SquiggleItem
|
||||||
return (
|
expression={expression}
|
||||||
<SquiggleViewer
|
|
||||||
result={valueToRender}
|
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
showTypes={showTypes}
|
||||||
chartSettings={chartSettings}
|
showControls={showControls}
|
||||||
environment={
|
|
||||||
project ? project.getEnvironment() : environment ?? defaultEnvironment
|
|
||||||
}
|
|
||||||
enableLocalSettings={enableLocalSettings}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
|
internal = (
|
||||||
|
<ErrorBox heading={"Parse Error"}>
|
||||||
|
{errorValueToString(expressionResult.value)}
|
||||||
|
</ErrorBox>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
return <ChartWrapper>{internal}</ChartWrapper>;
|
||||||
|
};
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import React, { useContext } from "react";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
type SquiggleContextShape = {
|
|
||||||
containerized: boolean;
|
|
||||||
};
|
|
||||||
const SquiggleContext = React.createContext<SquiggleContextShape>({
|
|
||||||
containerized: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SquiggleContainer: React.FC<Props> = ({ children }) => {
|
|
||||||
const context = useContext(SquiggleContext);
|
|
||||||
|
|
||||||
if (context.containerized) {
|
|
||||||
return <>{children}</>;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<SquiggleContext.Provider value={{ containerized: true }}>
|
|
||||||
<div className="squiggle">{children}</div>
|
|
||||||
</SquiggleContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,92 +1,224 @@
|
||||||
import React from "react";
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import { SquiggleChart } from "./SquiggleChart";
|
||||||
import { CodeEditor } from "./CodeEditor";
|
import { CodeEditor } from "./CodeEditor";
|
||||||
import { SquiggleContainer } from "./SquiggleContainer";
|
import styled from "styled-components";
|
||||||
|
import type {
|
||||||
|
squiggleExpression,
|
||||||
|
samplingParams,
|
||||||
|
bindings,
|
||||||
|
jsImports,
|
||||||
|
} from "@quri/squiggle-lang";
|
||||||
import {
|
import {
|
||||||
splitSquiggleChartSettings,
|
runPartial,
|
||||||
SquiggleChartProps,
|
errorValueToString,
|
||||||
} from "./SquiggleChart";
|
defaultImports,
|
||||||
import { useMaybeControlledValue, useSquiggle } from "../lib/hooks";
|
defaultBindings,
|
||||||
import { JsImports } from "../lib/jsImports";
|
} from "@quri/squiggle-lang";
|
||||||
import { defaultEnvironment, SqLocation, SqProject } from "@quri/squiggle-lang";
|
import { ErrorBox } from "./ErrorBox";
|
||||||
import { SquiggleViewer } from "./SquiggleViewer";
|
|
||||||
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
|
||||||
|
|
||||||
const WrappedCodeEditor: React.FC<{
|
export interface SquiggleEditorProps {
|
||||||
code: string;
|
/** The input string for squiggle */
|
||||||
setCode: (code: string) => void;
|
initialSquiggleString?: string;
|
||||||
errorLocations?: SqLocation[];
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
}> = ({ code, setCode, errorLocations }) => (
|
sampleCount?: number;
|
||||||
<div className="border border-grey-200 p-2 m-4" data-testid="squiggle-editor">
|
/** 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;
|
||||||
|
/** when the environment changes. Used again for notebook magic*/
|
||||||
|
onChange?(expr: squiggleExpression): void;
|
||||||
|
/** The width of the element */
|
||||||
|
width?: number;
|
||||||
|
/** Previous variable declarations */
|
||||||
|
bindings?: bindings;
|
||||||
|
/** JS Imports */
|
||||||
|
jsImports?: jsImports;
|
||||||
|
/** Whether to show detail about types of the returns, default false */
|
||||||
|
showTypes?: boolean;
|
||||||
|
/** Whether to give users access to graph controls */
|
||||||
|
showControls: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Input = styled.div`
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 0.3em 0.3em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
|
||||||
|
initialSquiggleString = "",
|
||||||
|
width,
|
||||||
|
sampleCount,
|
||||||
|
outputXYPoints,
|
||||||
|
kernelWidth,
|
||||||
|
pointDistLength,
|
||||||
|
diagramStart,
|
||||||
|
diagramStop,
|
||||||
|
diagramCount,
|
||||||
|
onChange,
|
||||||
|
bindings = defaultBindings,
|
||||||
|
jsImports = defaultImports,
|
||||||
|
showTypes = false,
|
||||||
|
showControls = false,
|
||||||
|
}: SquiggleEditorProps) => {
|
||||||
|
let [expression, setExpression] = React.useState(initialSquiggleString);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Input>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={code}
|
value={expression}
|
||||||
onChange={setCode}
|
onChange={setExpression}
|
||||||
oneLine={true}
|
oneLine={true}
|
||||||
showGutter={false}
|
showGutter={false}
|
||||||
height={20}
|
height={20}
|
||||||
errorLocations={errorLocations}
|
/>
|
||||||
|
</Input>
|
||||||
|
<SquiggleChart
|
||||||
|
width={width}
|
||||||
|
squiggleString={expression}
|
||||||
|
sampleCount={sampleCount}
|
||||||
|
outputXYPoints={outputXYPoints}
|
||||||
|
kernelWidth={kernelWidth}
|
||||||
|
pointDistLength={pointDistLength}
|
||||||
|
diagramStart={diagramStart}
|
||||||
|
diagramStop={diagramStop}
|
||||||
|
diagramCount={diagramCount}
|
||||||
|
onChange={onChange}
|
||||||
|
bindings={bindings}
|
||||||
|
jsImports={jsImports}
|
||||||
|
showTypes={showTypes}
|
||||||
|
showControls={showControls}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type SquiggleEditorProps = SquiggleChartProps & {
|
|
||||||
defaultCode?: string;
|
|
||||||
onCodeChange?: (code: string) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultOnChange = () => {};
|
export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
|
||||||
const defaultImports: JsImports = {};
|
let parent = document.createElement("div");
|
||||||
|
ReactDOM.render(
|
||||||
|
<SquiggleEditor
|
||||||
|
{...props}
|
||||||
|
onChange={(expr) => {
|
||||||
|
// 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 = expr;
|
||||||
|
|
||||||
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
parent.dispatchEvent(new CustomEvent("input"));
|
||||||
const [code, setCode] = useMaybeControlledValue({
|
if (props.onChange) props.onChange(expr);
|
||||||
value: props.code,
|
}}
|
||||||
defaultValue: props.defaultCode ?? "",
|
/>,
|
||||||
onChange: props.onCodeChange,
|
parent
|
||||||
});
|
);
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
const { distributionPlotSettings, chartSettings } =
|
export interface SquigglePartialProps {
|
||||||
splitSquiggleChartSettings(props);
|
/** 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;
|
||||||
|
/** when the environment changes. Used again for notebook magic*/
|
||||||
|
onChange?(expr: bindings): void;
|
||||||
|
/** Previously declared variables */
|
||||||
|
bindings?: bindings;
|
||||||
|
/** Variables imported from js */
|
||||||
|
jsImports?: jsImports;
|
||||||
|
/** Whether to give users access to graph controls */
|
||||||
|
showControls?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
export let SquigglePartial: React.FC<SquigglePartialProps> = ({
|
||||||
environment,
|
initialSquiggleString = "",
|
||||||
jsImports = defaultImports,
|
|
||||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
|
||||||
executionId = 0,
|
|
||||||
width,
|
|
||||||
height = 200,
|
|
||||||
enableLocalSettings = false,
|
|
||||||
continues,
|
|
||||||
project,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const resultAndBindings = useSquiggle({
|
|
||||||
environment,
|
|
||||||
continues,
|
|
||||||
code,
|
|
||||||
project,
|
|
||||||
jsImports,
|
|
||||||
onChange,
|
onChange,
|
||||||
executionId,
|
bindings = defaultBindings,
|
||||||
});
|
sampleCount = 1000,
|
||||||
|
outputXYPoints = 1000,
|
||||||
|
jsImports = defaultImports,
|
||||||
|
}: SquigglePartialProps) => {
|
||||||
|
let samplingInputs: samplingParams = {
|
||||||
|
sampleCount: sampleCount,
|
||||||
|
xyPointLength: outputXYPoints,
|
||||||
|
};
|
||||||
|
let [expression, setExpression] = React.useState(initialSquiggleString);
|
||||||
|
let [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
const valueToRender = getValueToRender(resultAndBindings);
|
let runSquiggleAndUpdateBindings = () => {
|
||||||
const errorLocations = getErrorLocations(resultAndBindings.result);
|
let squiggleResult = runPartial(
|
||||||
|
expression,
|
||||||
|
bindings,
|
||||||
|
samplingInputs,
|
||||||
|
jsImports
|
||||||
|
);
|
||||||
|
if (squiggleResult.tag == "Ok") {
|
||||||
|
if (onChange) onChange(squiggleResult.value);
|
||||||
|
setError(null);
|
||||||
|
} else {
|
||||||
|
setError(errorValueToString(squiggleResult.value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(runSquiggleAndUpdateBindings, [expression]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SquiggleContainer>
|
<div>
|
||||||
<WrappedCodeEditor
|
<Input>
|
||||||
code={code}
|
<CodeEditor
|
||||||
setCode={setCode}
|
value={expression}
|
||||||
errorLocations={errorLocations}
|
onChange={setExpression}
|
||||||
|
oneLine={true}
|
||||||
|
showGutter={false}
|
||||||
|
height={20}
|
||||||
/>
|
/>
|
||||||
<SquiggleViewer
|
</Input>
|
||||||
result={valueToRender}
|
{error !== null ? <ErrorBox heading="Error">{error}</ErrorBox> : <></>}
|
||||||
width={width}
|
</div>
|
||||||
height={height}
|
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment ?? defaultEnvironment}
|
|
||||||
enableLocalSettings={enableLocalSettings}
|
|
||||||
/>
|
|
||||||
</SquiggleContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function renderSquigglePartialToDom(props: SquigglePartialProps) {
|
||||||
|
let parent = document.createElement("div");
|
||||||
|
ReactDOM.render(
|
||||||
|
<SquigglePartial
|
||||||
|
{...props}
|
||||||
|
onChange={(bindings) => {
|
||||||
|
// @ts-ignore
|
||||||
|
parent.value = bindings;
|
||||||
|
|
||||||
|
parent.dispatchEvent(new CustomEvent("input"));
|
||||||
|
if (props.onChange) props.onChange(bindings);
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
parent
|
||||||
|
);
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { SqError, SqFrame } from "@quri/squiggle-lang";
|
|
||||||
import React from "react";
|
|
||||||
import { ErrorAlert } from "./Alert";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
error: SqError;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StackTraceFrame: React.FC<{ frame: SqFrame }> = ({ frame }) => {
|
|
||||||
const location = frame.location();
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{frame.name()}
|
|
||||||
{location
|
|
||||||
? ` at line ${location.start.line}, column ${location.start.column}`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const StackTrace: React.FC<Props> = ({ error }) => {
|
|
||||||
const frames = error.getFrameArray();
|
|
||||||
return frames.length ? (
|
|
||||||
<div>
|
|
||||||
<div className="font-medium">Stack trace:</div>
|
|
||||||
<div className="ml-4">
|
|
||||||
{frames.map((frame, i) => (
|
|
||||||
<StackTraceFrame frame={frame} key={i} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => {
|
|
||||||
return (
|
|
||||||
<ErrorAlert heading="Error">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>{error.toString()}</div>
|
|
||||||
<StackTrace error={error} />
|
|
||||||
</div>
|
|
||||||
</ErrorAlert>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,450 +1,129 @@
|
||||||
import React, {
|
import _ from "lodash";
|
||||||
FC,
|
import React, { FC, ReactElement, useState } from "react";
|
||||||
useState,
|
import ReactDOM from "react-dom";
|
||||||
useEffect,
|
import { SquiggleChart } from "./SquiggleChart";
|
||||||
useMemo,
|
import CodeEditor from "./CodeEditor";
|
||||||
useRef,
|
import styled from "styled-components";
|
||||||
useCallback,
|
|
||||||
} from "react";
|
|
||||||
import { useForm, UseFormRegister, useWatch } from "react-hook-form";
|
|
||||||
import * as yup from "yup";
|
|
||||||
import {
|
|
||||||
useMaybeControlledValue,
|
|
||||||
useRunnerState,
|
|
||||||
useSquiggle,
|
|
||||||
} from "../lib/hooks";
|
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
|
||||||
import {
|
|
||||||
ChartSquareBarIcon,
|
|
||||||
CheckCircleIcon,
|
|
||||||
ClipboardCopyIcon,
|
|
||||||
CodeIcon,
|
|
||||||
CogIcon,
|
|
||||||
CurrencyDollarIcon,
|
|
||||||
EyeIcon,
|
|
||||||
PauseIcon,
|
|
||||||
PlayIcon,
|
|
||||||
RefreshIcon,
|
|
||||||
} from "@heroicons/react/solid";
|
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
import { environment, SqProject } from "@quri/squiggle-lang";
|
interface FieldFloatProps {
|
||||||
|
label: string;
|
||||||
import { SquiggleChartProps } from "./SquiggleChart";
|
className?: string;
|
||||||
import { CodeEditor } from "./CodeEditor";
|
value: number;
|
||||||
import { JsonEditor } from "./JsonEditor";
|
onChange: (value: number) => void;
|
||||||
import { ErrorAlert, SuccessAlert } from "./Alert";
|
|
||||||
import { SquiggleContainer } from "./SquiggleContainer";
|
|
||||||
import { Toggle } from "./ui/Toggle";
|
|
||||||
import { StyledTab } from "./ui/StyledTab";
|
|
||||||
import { InputItem } from "./ui/InputItem";
|
|
||||||
import { Text } from "./ui/Text";
|
|
||||||
import { ViewSettings, viewSettingsSchema } from "./ViewSettings";
|
|
||||||
import { HeadedSection } from "./ui/HeadedSection";
|
|
||||||
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
|
||||||
import { Button } from "./ui/Button";
|
|
||||||
import { JsImports } from "../lib/jsImports";
|
|
||||||
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
|
||||||
import { SquiggleViewer } from "./SquiggleViewer";
|
|
||||||
|
|
||||||
type PlaygroundProps = SquiggleChartProps & {
|
|
||||||
/** The initial squiggle string to put in the playground */
|
|
||||||
defaultCode?: string;
|
|
||||||
onCodeChange?(expr: string): void;
|
|
||||||
/* When settings change */
|
|
||||||
onSettingsChange?(settings: any): void;
|
|
||||||
/** Should we show the editor? */
|
|
||||||
showEditor?: boolean;
|
|
||||||
/** Useful for playground on squiggle website, where we update the anchor link based on current code and settings */
|
|
||||||
showShareButton?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const schema = yup
|
|
||||||
.object({})
|
|
||||||
.shape({
|
|
||||||
sampleCount: yup
|
|
||||||
.number()
|
|
||||||
.required()
|
|
||||||
.positive()
|
|
||||||
.integer()
|
|
||||||
.default(1000)
|
|
||||||
.min(10)
|
|
||||||
.max(1000000),
|
|
||||||
xyPointLength: yup
|
|
||||||
.number()
|
|
||||||
.required()
|
|
||||||
.positive()
|
|
||||||
.integer()
|
|
||||||
.default(1000)
|
|
||||||
.min(10)
|
|
||||||
.max(10000),
|
|
||||||
})
|
|
||||||
.concat(viewSettingsSchema);
|
|
||||||
|
|
||||||
type FormFields = yup.InferType<typeof schema>;
|
|
||||||
|
|
||||||
const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
|
||||||
register,
|
|
||||||
}) => (
|
|
||||||
<div className="space-y-6 p-3 max-w-xl">
|
|
||||||
<div>
|
|
||||||
<InputItem
|
|
||||||
name="sampleCount"
|
|
||||||
type="number"
|
|
||||||
label="Sample Count"
|
|
||||||
register={register}
|
|
||||||
/>
|
|
||||||
<div className="mt-2">
|
|
||||||
<Text>
|
|
||||||
How many samples to use for Monte Carlo simulations. This can
|
|
||||||
occasionally be overridden by specific Squiggle programs.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<InputItem
|
|
||||||
name="xyPointLength"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Coordinate Count (For PointSet Shapes)"
|
|
||||||
/>
|
|
||||||
<div className="mt-2">
|
|
||||||
<Text>
|
|
||||||
When distributions are converted into PointSet shapes, we need to know
|
|
||||||
how many coordinates to use.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const InputVariablesSettings: React.FC<{
|
|
||||||
initialImports: JsImports;
|
|
||||||
setImports: (imports: JsImports) => void;
|
|
||||||
}> = ({ initialImports, setImports }) => {
|
|
||||||
const [importString, setImportString] = useState(() =>
|
|
||||||
JSON.stringify(initialImports)
|
|
||||||
);
|
|
||||||
const [importsAreValid, setImportsAreValid] = useState(true);
|
|
||||||
|
|
||||||
const onChange = (value: string) => {
|
|
||||||
setImportString(value);
|
|
||||||
let imports = {};
|
|
||||||
try {
|
|
||||||
imports = JSON.parse(value);
|
|
||||||
setImportsAreValid(true);
|
|
||||||
} catch (e) {
|
|
||||||
setImportsAreValid(false);
|
|
||||||
}
|
}
|
||||||
setImports(imports);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const Input = styled.input``;
|
||||||
|
|
||||||
|
const FormItem = (props: { label: string; children: ReactElement }) => (
|
||||||
|
<div>
|
||||||
|
<label>{props.label}</label>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
function FieldFloat(Props: FieldFloatProps) {
|
||||||
|
let [contents, setContents] = useState(Props.value + "");
|
||||||
return (
|
return (
|
||||||
<div className="p-3 max-w-3xl">
|
<FormItem label={Props.label}>
|
||||||
<HeadedSection title="Import Variables from JSON">
|
<Input
|
||||||
<div className="space-y-6">
|
value={contents}
|
||||||
<Text>
|
className={Props.className ? Props.className : ""}
|
||||||
You can import variables from JSON into your Squiggle code.
|
onChange={(e) => {
|
||||||
Variables are accessed with dollar signs. For example, "timeNow"
|
setContents(e.target.value);
|
||||||
would be accessed as "$timeNow".
|
let result = parseFloat(contents);
|
||||||
</Text>
|
if (_.isFinite(result)) {
|
||||||
<div className="border border-slate-200 mt-6 mb-2">
|
Props.onChange(result);
|
||||||
<JsonEditor
|
}
|
||||||
value={importString}
|
|
||||||
onChange={onChange}
|
|
||||||
oneLine={false}
|
|
||||||
showGutter={true}
|
|
||||||
height={150}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="p-1 pt-2">
|
|
||||||
{importsAreValid ? (
|
|
||||||
<SuccessAlert heading="Valid JSON" />
|
|
||||||
) : (
|
|
||||||
<ErrorAlert heading="Invalid JSON">
|
|
||||||
You must use valid JSON in this editor.
|
|
||||||
</ErrorAlert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const RunControls: React.FC<{
|
|
||||||
autorunMode: boolean;
|
|
||||||
isRunning: boolean;
|
|
||||||
isStale: boolean;
|
|
||||||
onAutorunModeChange: (value: boolean) => void;
|
|
||||||
run: () => void;
|
|
||||||
}> = ({ autorunMode, isRunning, isStale, onAutorunModeChange, run }) => {
|
|
||||||
const CurrentPlayIcon = isRunning ? RefreshIcon : PlayIcon;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex space-x-1 items-center" data-testid="autorun-controls">
|
|
||||||
{autorunMode ? null : (
|
|
||||||
<button onClick={run}>
|
|
||||||
<CurrentPlayIcon
|
|
||||||
className={clsx(
|
|
||||||
"w-8 h-8",
|
|
||||||
isRunning && "animate-spin",
|
|
||||||
isStale ? "text-indigo-500" : "text-gray-400"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<Toggle
|
|
||||||
texts={["Autorun", "Paused"]}
|
|
||||||
icons={[CheckCircleIcon, PauseIcon]}
|
|
||||||
status={autorunMode}
|
|
||||||
onChange={onAutorunModeChange}
|
|
||||||
spinIcon={autorunMode && isRunning}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ShareButton: React.FC = () => {
|
|
||||||
const [isCopied, setIsCopied] = useState(false);
|
|
||||||
const copy = () => {
|
|
||||||
navigator.clipboard.writeText((window.top || window).location.href);
|
|
||||||
setIsCopied(true);
|
|
||||||
setTimeout(() => setIsCopied(false), 1000);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div className="w-36">
|
|
||||||
<Button onClick={copy} wide>
|
|
||||||
{isCopied ? (
|
|
||||||
"Copied to clipboard!"
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center space-x-1">
|
|
||||||
<ClipboardCopyIcon className="w-4 h-4" />
|
|
||||||
<span>Copy share link</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type PlaygroundContextShape = {
|
|
||||||
getLeftPanelElement: () => HTMLDivElement | undefined;
|
|
||||||
};
|
|
||||||
export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
|
|
||||||
getLeftPanelElement: () => undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|
||||||
defaultCode = "",
|
|
||||||
height = 500,
|
|
||||||
showSummary = true,
|
|
||||||
logX = false,
|
|
||||||
expY = false,
|
|
||||||
title,
|
|
||||||
minX,
|
|
||||||
maxX,
|
|
||||||
tickFormat = defaultTickFormat,
|
|
||||||
distributionChartActions,
|
|
||||||
code: controlledCode,
|
|
||||||
onCodeChange,
|
|
||||||
onSettingsChange,
|
|
||||||
showEditor = true,
|
|
||||||
showShareButton = false,
|
|
||||||
continues,
|
|
||||||
project,
|
|
||||||
}) => {
|
|
||||||
const [code, setCode] = useMaybeControlledValue({
|
|
||||||
value: controlledCode,
|
|
||||||
defaultValue: defaultCode,
|
|
||||||
onChange: onCodeChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [imports, setImports] = useState<JsImports>({});
|
|
||||||
|
|
||||||
const { register, control } = useForm({
|
|
||||||
resolver: yupResolver(schema),
|
|
||||||
defaultValues: {
|
|
||||||
sampleCount: 1000,
|
|
||||||
xyPointLength: 1000,
|
|
||||||
chartHeight: 150,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
title,
|
|
||||||
minX,
|
|
||||||
maxX,
|
|
||||||
tickFormat,
|
|
||||||
distributionChartActions,
|
|
||||||
showSummary,
|
|
||||||
showEditor,
|
|
||||||
diagramStart: 0,
|
|
||||||
diagramStop: 10,
|
|
||||||
diagramCount: 20,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const vars = useWatch({
|
|
||||||
control,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onSettingsChange?.(vars);
|
|
||||||
}, [vars, onSettingsChange]);
|
|
||||||
|
|
||||||
const environment: environment = useMemo(
|
|
||||||
() => ({
|
|
||||||
sampleCount: Number(vars.sampleCount),
|
|
||||||
xyPointLength: Number(vars.xyPointLength),
|
|
||||||
}),
|
|
||||||
[vars.sampleCount, vars.xyPointLength]
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
run,
|
|
||||||
autorunMode,
|
|
||||||
setAutorunMode,
|
|
||||||
isRunning,
|
|
||||||
renderedCode,
|
|
||||||
executionId,
|
|
||||||
} = useRunnerState(code);
|
|
||||||
|
|
||||||
const resultAndBindings = useSquiggle({
|
|
||||||
environment,
|
|
||||||
continues,
|
|
||||||
code: renderedCode,
|
|
||||||
project,
|
|
||||||
jsImports: imports,
|
|
||||||
executionId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const valueToRender = getValueToRender(resultAndBindings);
|
|
||||||
|
|
||||||
const squiggleChart =
|
|
||||||
renderedCode === "" ? null : (
|
|
||||||
<div className="relative">
|
|
||||||
{isRunning ? (
|
|
||||||
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
|
|
||||||
) : null}
|
|
||||||
<SquiggleViewer
|
|
||||||
result={valueToRender}
|
|
||||||
environment={environment}
|
|
||||||
height={vars.chartHeight || 150}
|
|
||||||
distributionPlotSettings={{
|
|
||||||
showSummary: vars.showSummary ?? false,
|
|
||||||
logX: vars.logX ?? false,
|
|
||||||
expY: vars.expY ?? false,
|
|
||||||
format: vars.tickFormat,
|
|
||||||
minX: vars.minX,
|
|
||||||
maxX: vars.maxX,
|
|
||||||
title: vars.title,
|
|
||||||
actions: vars.distributionChartActions,
|
|
||||||
}}
|
}}
|
||||||
chartSettings={{
|
|
||||||
start: vars.diagramStart ?? 0,
|
|
||||||
stop: vars.diagramStop ?? 10,
|
|
||||||
count: vars.diagramCount ?? 20,
|
|
||||||
}}
|
|
||||||
enableLocalSettings={true}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</FormItem>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const errorLocations = getErrorLocations(resultAndBindings.result);
|
interface Props {
|
||||||
|
initialSquiggleString?: string;
|
||||||
|
height?: number;
|
||||||
|
showTypes?: boolean;
|
||||||
|
showControls?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const firstTab = vars.showEditor ? (
|
interface Props2 {
|
||||||
<div className="border border-slate-200" data-testid="squiggle-editor">
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ShowBox = styled.div<Props2>`
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 2px;
|
||||||
|
height: ${(props) => props.height};
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface TitleProps {
|
||||||
|
readonly maxHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Display = styled.div<TitleProps>`
|
||||||
|
background: #f6f6f6;
|
||||||
|
border-left: 1px solid #eee;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 3px;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: ${(props) => props.maxHeight}px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Row = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 50% 50%;
|
||||||
|
`;
|
||||||
|
const Col = styled.div``;
|
||||||
|
|
||||||
|
let SquigglePlayground: FC<Props> = ({
|
||||||
|
initialSquiggleString = "",
|
||||||
|
height = 300,
|
||||||
|
showTypes = false,
|
||||||
|
showControls = false,
|
||||||
|
}: Props) => {
|
||||||
|
let [squiggleString, setSquiggleString] = useState(initialSquiggleString);
|
||||||
|
let [sampleCount, setSampleCount] = useState(1000);
|
||||||
|
let [outputXYPoints, setOutputXYPoints] = useState(1000);
|
||||||
|
let [pointDistLength, setPointDistLength] = useState(1000);
|
||||||
|
let [diagramStart, setDiagramStart] = useState(0);
|
||||||
|
let [diagramStop, setDiagramStop] = useState(10);
|
||||||
|
let [diagramCount, setDiagramCount] = useState(20);
|
||||||
|
return (
|
||||||
|
<ShowBox height={height}>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
errorLocations={errorLocations}
|
value={squiggleString}
|
||||||
value={code}
|
onChange={setSquiggleString}
|
||||||
onChange={setCode}
|
|
||||||
onSubmit={run}
|
|
||||||
oneLine={false}
|
oneLine={false}
|
||||||
showGutter={true}
|
showGutter={true}
|
||||||
height={height - 1}
|
height={height - 3}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Col>
|
||||||
) : (
|
<Col>
|
||||||
squiggleChart
|
<Display maxHeight={height - 3}>
|
||||||
);
|
<SquiggleChart
|
||||||
|
squiggleString={squiggleString}
|
||||||
const tabs = (
|
sampleCount={sampleCount}
|
||||||
<StyledTab.Panels>
|
outputXYPoints={outputXYPoints}
|
||||||
<StyledTab.Panel>{firstTab}</StyledTab.Panel>
|
diagramStart={diagramStart}
|
||||||
<StyledTab.Panel>
|
diagramStop={diagramStop}
|
||||||
<SamplingSettings register={register} />
|
diagramCount={diagramCount}
|
||||||
</StyledTab.Panel>
|
pointDistLength={pointDistLength}
|
||||||
<StyledTab.Panel>
|
height={150}
|
||||||
<ViewSettings
|
showTypes={showTypes}
|
||||||
register={
|
showControls={showControls}
|
||||||
// This is dangerous, but doesn't cause any problems.
|
|
||||||
// I tried to make `ViewSettings` generic (to allow it to accept any extension of a settings schema), but it didn't work.
|
|
||||||
register as unknown as UseFormRegister<
|
|
||||||
yup.InferType<typeof viewSettingsSchema>
|
|
||||||
>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</StyledTab.Panel>
|
</Display>
|
||||||
<StyledTab.Panel>
|
</Col>
|
||||||
<InputVariablesSettings
|
</Row>
|
||||||
initialImports={imports}
|
</ShowBox>
|
||||||
setImports={setImports}
|
|
||||||
/>
|
|
||||||
</StyledTab.Panel>
|
|
||||||
</StyledTab.Panels>
|
|
||||||
);
|
|
||||||
|
|
||||||
const leftPanelRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
const withEditor = (
|
|
||||||
<div className="flex mt-2">
|
|
||||||
<div
|
|
||||||
className="w-1/2 relative"
|
|
||||||
style={{ minHeight: height }}
|
|
||||||
ref={leftPanelRef}
|
|
||||||
>
|
|
||||||
{tabs}
|
|
||||||
</div>
|
|
||||||
<div className="w-1/2 p-2 pl-4" data-testid="playground-result">
|
|
||||||
{squiggleChart}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const withoutEditor = <div className="mt-3">{tabs}</div>;
|
|
||||||
|
|
||||||
const getLeftPanelElement = useCallback(() => {
|
|
||||||
return leftPanelRef.current ?? undefined;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SquiggleContainer>
|
|
||||||
<PlaygroundContext.Provider value={{ getLeftPanelElement }}>
|
|
||||||
<StyledTab.Group>
|
|
||||||
<div className="pb-4">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<StyledTab.List>
|
|
||||||
<StyledTab
|
|
||||||
name={vars.showEditor ? "Code" : "Display"}
|
|
||||||
icon={vars.showEditor ? CodeIcon : EyeIcon}
|
|
||||||
/>
|
|
||||||
<StyledTab name="Sampling Settings" icon={CogIcon} />
|
|
||||||
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
|
|
||||||
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
|
|
||||||
</StyledTab.List>
|
|
||||||
<div className="flex space-x-2 items-center">
|
|
||||||
<RunControls
|
|
||||||
autorunMode={autorunMode}
|
|
||||||
isStale={renderedCode !== code}
|
|
||||||
run={run}
|
|
||||||
isRunning={isRunning}
|
|
||||||
onAutorunModeChange={setAutorunMode}
|
|
||||||
/>
|
|
||||||
{showShareButton && <ShareButton />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{vars.showEditor ? withEditor : withoutEditor}
|
|
||||||
</div>
|
|
||||||
</StyledTab.Group>
|
|
||||||
</PlaygroundContext.Provider>
|
|
||||||
</SquiggleContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export default SquigglePlayground;
|
||||||
|
export function renderSquigglePlaygroundToDom(props: Props) {
|
||||||
|
let parent = document.createElement("div");
|
||||||
|
ReactDOM.render(<SquigglePlayground {...props} />, parent);
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
|
@ -1,310 +0,0 @@
|
||||||
import React, { useContext } from "react";
|
|
||||||
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
|
|
||||||
import { NumberShower } from "../NumberShower";
|
|
||||||
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
|
|
||||||
import { FunctionChart } from "../FunctionChart";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import { VariableBox } from "./VariableBox";
|
|
||||||
import { ItemSettingsMenu } from "./ItemSettingsMenu";
|
|
||||||
import { hasMassBelowZero } from "../../lib/distributionUtils";
|
|
||||||
import { MergedItemSettings } from "./utils";
|
|
||||||
import { ViewerContext } from "./ViewerContext";
|
|
||||||
|
|
||||||
/*
|
|
||||||
// DISABLED FOR 0.4 branch, for now
|
|
||||||
function getRange<a>(x: declaration<a>) {
|
|
||||||
const first = x.args[0];
|
|
||||||
switch (first.tag) {
|
|
||||||
case "Float": {
|
|
||||||
return { floats: { min: first.value.min, max: first.value.max } };
|
|
||||||
}
|
|
||||||
case "Date": {
|
|
||||||
return { time: { min: first.value.min, max: first.value.max } };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
|
|
||||||
const range = getRange(x);
|
|
||||||
const min = range.floats ? range.floats.min : 0;
|
|
||||||
const max = range.floats ? range.floats.max : 10;
|
|
||||||
return {
|
|
||||||
start: min,
|
|
||||||
stop: max,
|
|
||||||
count: 20,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const VariableList: React.FC<{
|
|
||||||
value: SqValue;
|
|
||||||
heading: string;
|
|
||||||
children: (settings: MergedItemSettings) => React.ReactNode;
|
|
||||||
}> = ({ value, heading, children }) => (
|
|
||||||
<VariableBox value={value} heading={heading}>
|
|
||||||
{(settings) => (
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
"space-y-3",
|
|
||||||
value.location.path.items.length ? "pt-1 mt-1" : null
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children(settings)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
/** The output of squiggle's run */
|
|
||||||
value: SqValue;
|
|
||||||
width?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
|
||||||
const { getMergedSettings } = useContext(ViewerContext);
|
|
||||||
|
|
||||||
switch (value.tag) {
|
|
||||||
case SqValueTag.Number:
|
|
||||||
return (
|
|
||||||
<VariableBox value={value} heading="Number">
|
|
||||||
{() => (
|
|
||||||
<div className="font-semibold text-slate-600">
|
|
||||||
<NumberShower precision={3} number={value.value} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case SqValueTag.Distribution: {
|
|
||||||
const distType = value.value.tag;
|
|
||||||
return (
|
|
||||||
<VariableBox
|
|
||||||
value={value}
|
|
||||||
heading={`Distribution (${distType})\n${
|
|
||||||
distType === SqDistributionTag.Symbolic
|
|
||||||
? value.value.toString()
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
renderSettingsMenu={({ onChange }) => {
|
|
||||||
const shape = value.value.pointSet(
|
|
||||||
getMergedSettings(value.location).environment
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<ItemSettingsMenu
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
disableLogX={
|
|
||||||
shape.tag === "Ok" && hasMassBelowZero(shape.value.asShape())
|
|
||||||
}
|
|
||||||
withFunctionSettings={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(settings) => {
|
|
||||||
return (
|
|
||||||
<DistributionChart
|
|
||||||
plot={defaultPlot(value.value)}
|
|
||||||
environment={settings.environment}
|
|
||||||
{...settings.distributionPlotSettings}
|
|
||||||
height={settings.height}
|
|
||||||
width={width}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case SqValueTag.String:
|
|
||||||
return (
|
|
||||||
<VariableBox value={value} heading="String">
|
|
||||||
{() => (
|
|
||||||
<>
|
|
||||||
<span className="text-slate-400">"</span>
|
|
||||||
<span className="text-slate-600 font-semibold font-mono">
|
|
||||||
{value.value}
|
|
||||||
</span>
|
|
||||||
<span className="text-slate-400">"</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case SqValueTag.Bool:
|
|
||||||
return (
|
|
||||||
<VariableBox value={value} heading="Boolean">
|
|
||||||
{() => value.value.toString()}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case SqValueTag.Date:
|
|
||||||
return (
|
|
||||||
<VariableBox value={value} heading="Date">
|
|
||||||
{() => value.value.toDateString()}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case SqValueTag.Void:
|
|
||||||
return (
|
|
||||||
<VariableBox value={value} heading="Void">
|
|
||||||
{() => "Void"}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case SqValueTag.TimeDuration: {
|
|
||||||
return (
|
|
||||||
<VariableBox value={value} heading="Time Duration">
|
|
||||||
{() => <NumberShower precision={3} number={value.value} />}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case SqValueTag.Lambda:
|
|
||||||
return (
|
|
||||||
<VariableBox
|
|
||||||
value={value}
|
|
||||||
heading="Function"
|
|
||||||
renderSettingsMenu={({ onChange }) => {
|
|
||||||
return (
|
|
||||||
<ItemSettingsMenu
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
withFunctionSettings={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(settings) => (
|
|
||||||
<>
|
|
||||||
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${value.value
|
|
||||||
.parameters()
|
|
||||||
.join(",")})`}</div>
|
|
||||||
<FunctionChart
|
|
||||||
fn={value.value}
|
|
||||||
chartSettings={settings.chartSettings}
|
|
||||||
distributionPlotSettings={settings.distributionPlotSettings}
|
|
||||||
height={settings.height}
|
|
||||||
environment={{
|
|
||||||
sampleCount: settings.environment.sampleCount / 10,
|
|
||||||
xyPointLength: settings.environment.xyPointLength / 10,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case SqValueTag.Declaration: {
|
|
||||||
return (
|
|
||||||
<VariableBox
|
|
||||||
value={value}
|
|
||||||
heading="Function Declaration"
|
|
||||||
renderSettingsMenu={({ onChange }) => {
|
|
||||||
return (
|
|
||||||
<ItemSettingsMenu
|
|
||||||
onChange={onChange}
|
|
||||||
value={value}
|
|
||||||
withFunctionSettings={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(settings) => (
|
|
||||||
<div>NOT IMPLEMENTED IN 0.4 YET</div>
|
|
||||||
// <FunctionChart
|
|
||||||
// fn={expression.value.fn}
|
|
||||||
// chartSettings={getChartSettings(expression.value)}
|
|
||||||
// distributionPlotSettings={settings.distributionPlotSettings}
|
|
||||||
// height={settings.height}
|
|
||||||
// environment={{
|
|
||||||
// sampleCount: settings.environment.sampleCount / 10,
|
|
||||||
// xyPointLength: settings.environment.xyPointLength / 10,
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
)}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case SqValueTag.Record:
|
|
||||||
const plot = makePlot(value.value);
|
|
||||||
if (plot) {
|
|
||||||
return (
|
|
||||||
<VariableBox
|
|
||||||
value={value}
|
|
||||||
heading="Plot"
|
|
||||||
renderSettingsMenu={({ onChange }) => {
|
|
||||||
let disableLogX = plot.distributions.some((x) => {
|
|
||||||
let pointSet = x.distribution.pointSet(
|
|
||||||
getMergedSettings(value.location).environment
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
pointSet.tag === "Ok" &&
|
|
||||||
hasMassBelowZero(pointSet.value.asShape())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<ItemSettingsMenu
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
disableLogX={disableLogX}
|
|
||||||
withFunctionSettings={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(settings) => {
|
|
||||||
return (
|
|
||||||
<DistributionChart
|
|
||||||
plot={plot}
|
|
||||||
environment={settings.environment}
|
|
||||||
{...settings.distributionPlotSettings}
|
|
||||||
height={settings.height}
|
|
||||||
width={width}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<VariableList value={value} heading="Record">
|
|
||||||
{(_) =>
|
|
||||||
value.value
|
|
||||||
.entries()
|
|
||||||
.map(([key, r]) => (
|
|
||||||
<ExpressionViewer
|
|
||||||
key={key}
|
|
||||||
value={r}
|
|
||||||
width={width !== undefined ? width - 20 : width}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</VariableList>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case SqValueTag.Array:
|
|
||||||
return (
|
|
||||||
<VariableList value={value} heading="Array">
|
|
||||||
{(_) =>
|
|
||||||
value.value
|
|
||||||
.getValues()
|
|
||||||
.map((r, i) => (
|
|
||||||
<ExpressionViewer
|
|
||||||
key={i}
|
|
||||||
value={r}
|
|
||||||
width={width !== undefined ? width - 20 : width}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</VariableList>
|
|
||||||
);
|
|
||||||
default: {
|
|
||||||
return (
|
|
||||||
<VariableList value={value} heading="Error">
|
|
||||||
{() => (
|
|
||||||
<div>
|
|
||||||
<span>No display for type: </span>{" "}
|
|
||||||
<span className="font-semibold text-slate-600">
|
|
||||||
{(value as { tag: string }).tag}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</VariableList>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,164 +0,0 @@
|
||||||
import { CogIcon } from "@heroicons/react/solid";
|
|
||||||
import React, { useContext, useRef, useState, useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
|
||||||
import { Modal } from "../ui/Modal";
|
|
||||||
import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
|
|
||||||
import { ViewerContext } from "./ViewerContext";
|
|
||||||
import { defaultTickFormat } from "../../lib/distributionSpecBuilder";
|
|
||||||
import { PlaygroundContext } from "../SquigglePlayground";
|
|
||||||
import { SqValue } from "@quri/squiggle-lang";
|
|
||||||
import { locationAsString } from "./utils";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
value: SqValue;
|
|
||||||
onChange: () => void;
|
|
||||||
disableLogX?: boolean;
|
|
||||||
withFunctionSettings: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ItemSettingsModal: React.FC<
|
|
||||||
Props & { close: () => void; resetScroll: () => void }
|
|
||||||
> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
disableLogX,
|
|
||||||
withFunctionSettings,
|
|
||||||
close,
|
|
||||||
resetScroll,
|
|
||||||
}) => {
|
|
||||||
const { setSettings, getSettings, getMergedSettings } =
|
|
||||||
useContext(ViewerContext);
|
|
||||||
|
|
||||||
const mergedSettings = getMergedSettings(value.location);
|
|
||||||
|
|
||||||
const { register, watch } = useForm({
|
|
||||||
resolver: yupResolver(viewSettingsSchema),
|
|
||||||
defaultValues: {
|
|
||||||
// this is a mess and should be fixed
|
|
||||||
showEditor: true, // doesn't matter
|
|
||||||
chartHeight: mergedSettings.height,
|
|
||||||
showSummary: mergedSettings.distributionPlotSettings.showSummary,
|
|
||||||
logX: mergedSettings.distributionPlotSettings.logX,
|
|
||||||
expY: mergedSettings.distributionPlotSettings.expY,
|
|
||||||
tickFormat:
|
|
||||||
mergedSettings.distributionPlotSettings.format || defaultTickFormat,
|
|
||||||
title: mergedSettings.distributionPlotSettings.title,
|
|
||||||
minX: mergedSettings.distributionPlotSettings.minX,
|
|
||||||
maxX: mergedSettings.distributionPlotSettings.maxX,
|
|
||||||
distributionChartActions: mergedSettings.distributionPlotSettings.actions,
|
|
||||||
diagramStart: mergedSettings.chartSettings.start,
|
|
||||||
diagramStop: mergedSettings.chartSettings.stop,
|
|
||||||
diagramCount: mergedSettings.chartSettings.count,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
|
||||||
const subscription = watch((vars) => {
|
|
||||||
const settings = getSettings(value.location); // get the latest version
|
|
||||||
setSettings(value.location, {
|
|
||||||
...settings,
|
|
||||||
distributionPlotSettings: {
|
|
||||||
showSummary: vars.showSummary,
|
|
||||||
logX: vars.logX,
|
|
||||||
expY: vars.expY,
|
|
||||||
format: vars.tickFormat,
|
|
||||||
title: vars.title,
|
|
||||||
minX: vars.minX,
|
|
||||||
maxX: vars.maxX,
|
|
||||||
actions: vars.distributionChartActions,
|
|
||||||
},
|
|
||||||
chartSettings: {
|
|
||||||
start: vars.diagramStart,
|
|
||||||
stop: vars.diagramStop,
|
|
||||||
count: vars.diagramCount,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
onChange();
|
|
||||||
});
|
|
||||||
return () => subscription.unsubscribe();
|
|
||||||
}, [getSettings, setSettings, onChange, value.location, watch]);
|
|
||||||
|
|
||||||
const { getLeftPanelElement } = useContext(PlaygroundContext);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal container={getLeftPanelElement()} close={close}>
|
|
||||||
<Modal.Header>
|
|
||||||
Chart settings
|
|
||||||
{value.location.path.items.length ? (
|
|
||||||
<>
|
|
||||||
{" for "}
|
|
||||||
<span
|
|
||||||
title="Scroll to item"
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={resetScroll}
|
|
||||||
>
|
|
||||||
{locationAsString(value.location)}
|
|
||||||
</span>{" "}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Body>
|
|
||||||
<ViewSettings
|
|
||||||
register={register}
|
|
||||||
withShowEditorSetting={false}
|
|
||||||
withFunctionSettings={withFunctionSettings}
|
|
||||||
disableLogXSetting={disableLogX}
|
|
||||||
/>
|
|
||||||
</Modal.Body>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const { enableLocalSettings, setSettings, getSettings } =
|
|
||||||
useContext(ViewerContext);
|
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
if (!enableLocalSettings) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const settings = getSettings(props.value.location);
|
|
||||||
|
|
||||||
const resetScroll = () => {
|
|
||||||
if (!ref.current) return;
|
|
||||||
window.scroll({
|
|
||||||
top: ref.current.getBoundingClientRect().y + window.scrollY,
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex gap-2" ref={ref}>
|
|
||||||
<CogIcon
|
|
||||||
className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500"
|
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
|
||||||
/>
|
|
||||||
{settings.distributionPlotSettings || settings.chartSettings ? (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setSettings(props.value.location, {
|
|
||||||
...settings,
|
|
||||||
distributionPlotSettings: undefined,
|
|
||||||
chartSettings: undefined,
|
|
||||||
});
|
|
||||||
props.onChange();
|
|
||||||
}}
|
|
||||||
className="text-xs px-1 py-0.5 rounded bg-slate-300"
|
|
||||||
>
|
|
||||||
Reset settings
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
{isOpen ? (
|
|
||||||
<ItemSettingsModal
|
|
||||||
{...props}
|
|
||||||
close={() => setIsOpen(false)}
|
|
||||||
resetScroll={resetScroll}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,82 +0,0 @@
|
||||||
import { SqValue } from "@quri/squiggle-lang";
|
|
||||||
import React, { useContext, useReducer } from "react";
|
|
||||||
import { Tooltip } from "../ui/Tooltip";
|
|
||||||
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
|
||||||
import { ViewerContext } from "./ViewerContext";
|
|
||||||
|
|
||||||
type SettingsMenuParams = {
|
|
||||||
onChange: () => void; // used to notify VariableBox that settings have changed, so that VariableBox could re-render itself
|
|
||||||
};
|
|
||||||
|
|
||||||
type VariableBoxProps = {
|
|
||||||
value: SqValue;
|
|
||||||
heading: string;
|
|
||||||
renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode;
|
|
||||||
children: (settings: MergedItemSettings) => React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const VariableBox: React.FC<VariableBoxProps> = ({
|
|
||||||
value: { location },
|
|
||||||
heading = "Error",
|
|
||||||
renderSettingsMenu,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const { setSettings, getSettings, getMergedSettings } =
|
|
||||||
useContext(ViewerContext);
|
|
||||||
|
|
||||||
// Since ViewerContext doesn't keep the actual settings, VariableBox won't rerender when setSettings is called.
|
|
||||||
// So we use `forceUpdate` to force rerendering.
|
|
||||||
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
||||||
|
|
||||||
const settings = getSettings(location);
|
|
||||||
|
|
||||||
const setSettingsAndUpdate = (newSettings: LocalItemSettings) => {
|
|
||||||
setSettings(location, newSettings);
|
|
||||||
forceUpdate();
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleCollapsed = () => {
|
|
||||||
setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed });
|
|
||||||
};
|
|
||||||
|
|
||||||
const isTopLevel = location.path.items.length === 0;
|
|
||||||
const name = isTopLevel
|
|
||||||
? { result: "Result", bindings: "Bindings" }[location.path.root]
|
|
||||||
: location.path.items[location.path.items.length - 1];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div role={isTopLevel ? "status" : undefined}>
|
|
||||||
<header className="inline-flex space-x-1">
|
|
||||||
<Tooltip text={heading}>
|
|
||||||
<span
|
|
||||||
className="text-slate-500 font-mono text-sm cursor-pointer"
|
|
||||||
onClick={toggleCollapsed}
|
|
||||||
>
|
|
||||||
{name}:
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
{settings.collapsed ? (
|
|
||||||
<span
|
|
||||||
className="rounded p-0.5 bg-slate-200 text-slate-500 font-mono text-xs cursor-pointer"
|
|
||||||
onClick={toggleCollapsed}
|
|
||||||
>
|
|
||||||
...
|
|
||||||
</span>
|
|
||||||
) : renderSettingsMenu ? (
|
|
||||||
renderSettingsMenu({ onChange: forceUpdate })
|
|
||||||
) : null}
|
|
||||||
</header>
|
|
||||||
{settings.collapsed ? null : (
|
|
||||||
<div className="flex w-full">
|
|
||||||
{location.path.items.length ? (
|
|
||||||
<div
|
|
||||||
className="shrink-0 border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
|
|
||||||
onClick={toggleCollapsed}
|
|
||||||
></div>
|
|
||||||
) : null}
|
|
||||||
<div className="grow">{children(getMergedSettings(location))}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang";
|
|
||||||
import React from "react";
|
|
||||||
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
|
||||||
|
|
||||||
type ViewerContextShape = {
|
|
||||||
// Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
|
|
||||||
// Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch.
|
|
||||||
// See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
|
|
||||||
getSettings(location: SqValueLocation): LocalItemSettings;
|
|
||||||
getMergedSettings(location: SqValueLocation): MergedItemSettings;
|
|
||||||
setSettings(location: SqValueLocation, value: LocalItemSettings): void;
|
|
||||||
enableLocalSettings: boolean; // show local settings icon in the UI
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ViewerContext = React.createContext<ViewerContextShape>({
|
|
||||||
getSettings: () => ({ collapsed: false }),
|
|
||||||
getMergedSettings: () => ({
|
|
||||||
collapsed: false,
|
|
||||||
// copy-pasted from SquiggleChart
|
|
||||||
chartSettings: {
|
|
||||||
start: 0,
|
|
||||||
stop: 10,
|
|
||||||
count: 100,
|
|
||||||
},
|
|
||||||
distributionPlotSettings: {
|
|
||||||
showSummary: false,
|
|
||||||
logX: false,
|
|
||||||
expY: false,
|
|
||||||
},
|
|
||||||
environment: defaultEnvironment,
|
|
||||||
height: 150,
|
|
||||||
}),
|
|
||||||
setSettings() {},
|
|
||||||
enableLocalSettings: false,
|
|
||||||
});
|
|
|
@ -1,99 +0,0 @@
|
||||||
import React, { useCallback, useRef } from "react";
|
|
||||||
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
|
||||||
import { DistributionPlottingSettings } from "../DistributionChart";
|
|
||||||
import { FunctionChartSettings } from "../FunctionChart";
|
|
||||||
import { ExpressionViewer } from "./ExpressionViewer";
|
|
||||||
import { ViewerContext } from "./ViewerContext";
|
|
||||||
import {
|
|
||||||
LocalItemSettings,
|
|
||||||
locationAsString,
|
|
||||||
MergedItemSettings,
|
|
||||||
} from "./utils";
|
|
||||||
import { useSquiggle } from "../../lib/hooks";
|
|
||||||
import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
/** The output of squiggle's run */
|
|
||||||
result: ReturnType<typeof useSquiggle>["result"];
|
|
||||||
width?: number;
|
|
||||||
height: number;
|
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
|
||||||
/** Settings for displaying functions */
|
|
||||||
chartSettings: FunctionChartSettings;
|
|
||||||
/** Environment for further function executions */
|
|
||||||
environment: environment;
|
|
||||||
enableLocalSettings?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Settings = {
|
|
||||||
[k: string]: LocalItemSettings;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultSettings: LocalItemSettings = { collapsed: false };
|
|
||||||
|
|
||||||
export const SquiggleViewer: React.FC<Props> = ({
|
|
||||||
result,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
distributionPlotSettings,
|
|
||||||
chartSettings,
|
|
||||||
environment,
|
|
||||||
enableLocalSettings = false,
|
|
||||||
}) => {
|
|
||||||
// can't store settings in the state because we don't want to rerender the entire tree on every change
|
|
||||||
const settingsRef = useRef<Settings>({});
|
|
||||||
|
|
||||||
const getSettings = useCallback(
|
|
||||||
(location: SqValueLocation) => {
|
|
||||||
return settingsRef.current[locationAsString(location)] || defaultSettings;
|
|
||||||
},
|
|
||||||
[settingsRef]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setSettings = useCallback(
|
|
||||||
(location: SqValueLocation, value: LocalItemSettings) => {
|
|
||||||
settingsRef.current[locationAsString(location)] = value;
|
|
||||||
},
|
|
||||||
[settingsRef]
|
|
||||||
);
|
|
||||||
|
|
||||||
const getMergedSettings = useCallback(
|
|
||||||
(location: SqValueLocation) => {
|
|
||||||
const localSettings = getSettings(location);
|
|
||||||
const result: MergedItemSettings = {
|
|
||||||
distributionPlotSettings: {
|
|
||||||
...distributionPlotSettings,
|
|
||||||
...(localSettings.distributionPlotSettings || {}),
|
|
||||||
},
|
|
||||||
chartSettings: {
|
|
||||||
...chartSettings,
|
|
||||||
...(localSettings.chartSettings || {}),
|
|
||||||
},
|
|
||||||
environment: {
|
|
||||||
...environment,
|
|
||||||
...(localSettings.environment || {}),
|
|
||||||
},
|
|
||||||
height: localSettings.height || height,
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
[distributionPlotSettings, chartSettings, environment, height, getSettings]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ViewerContext.Provider
|
|
||||||
value={{
|
|
||||||
getSettings,
|
|
||||||
setSettings,
|
|
||||||
getMergedSettings,
|
|
||||||
enableLocalSettings,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{result.tag === "Ok" ? (
|
|
||||||
<ExpressionViewer value={result.value} width={width} />
|
|
||||||
) : (
|
|
||||||
<SquiggleErrorAlert error={result.value} />
|
|
||||||
)}
|
|
||||||
</ViewerContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { DistributionPlottingSettings } from "../DistributionChart";
|
|
||||||
import { FunctionChartSettings } from "../FunctionChart";
|
|
||||||
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
|
||||||
|
|
||||||
export type LocalItemSettings = {
|
|
||||||
collapsed: boolean;
|
|
||||||
distributionPlotSettings?: Partial<DistributionPlottingSettings>;
|
|
||||||
chartSettings?: Partial<FunctionChartSettings>;
|
|
||||||
height?: number;
|
|
||||||
environment?: Partial<environment>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MergedItemSettings = {
|
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
|
||||||
chartSettings: FunctionChartSettings;
|
|
||||||
height: number;
|
|
||||||
environment: environment;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const locationAsString = (location: SqValueLocation) =>
|
|
||||||
location.path.items.join(".");
|
|
|
@ -1,153 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import * as yup from "yup";
|
|
||||||
import { UseFormRegister } from "react-hook-form";
|
|
||||||
import { InputItem } from "./ui/InputItem";
|
|
||||||
import { Checkbox } from "./ui/Checkbox";
|
|
||||||
import { HeadedSection } from "./ui/HeadedSection";
|
|
||||||
import { Text } from "./ui/Text";
|
|
||||||
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
|
||||||
|
|
||||||
export const viewSettingsSchema = yup.object({}).shape({
|
|
||||||
chartHeight: yup.number().required().positive().integer().default(350),
|
|
||||||
showSummary: yup.boolean().required(),
|
|
||||||
showEditor: yup.boolean().required(),
|
|
||||||
logX: yup.boolean().required(),
|
|
||||||
expY: yup.boolean().required(),
|
|
||||||
tickFormat: yup.string().default(defaultTickFormat),
|
|
||||||
title: yup.string(),
|
|
||||||
minX: yup.number(),
|
|
||||||
maxX: yup.number(),
|
|
||||||
distributionChartActions: yup.boolean(),
|
|
||||||
diagramStart: yup.number().required().positive().integer().default(0).min(0),
|
|
||||||
diagramStop: yup.number().required().positive().integer().default(10).min(0),
|
|
||||||
diagramCount: yup.number().required().positive().integer().default(20).min(2),
|
|
||||||
});
|
|
||||||
|
|
||||||
type FormFields = yup.InferType<typeof viewSettingsSchema>;
|
|
||||||
|
|
||||||
// This component is used in two places: for global settings in SquigglePlayground, and for item-specific settings in modal dialogs.
|
|
||||||
export const ViewSettings: React.FC<{
|
|
||||||
withShowEditorSetting?: boolean;
|
|
||||||
withFunctionSettings?: boolean;
|
|
||||||
disableLogXSetting?: boolean;
|
|
||||||
register: UseFormRegister<FormFields>;
|
|
||||||
}> = ({
|
|
||||||
withShowEditorSetting = true,
|
|
||||||
withFunctionSettings = true,
|
|
||||||
disableLogXSetting,
|
|
||||||
register,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
|
||||||
<HeadedSection title="General Display Settings">
|
|
||||||
<div className="space-y-4">
|
|
||||||
{withShowEditorSetting ? (
|
|
||||||
<Checkbox
|
|
||||||
name="showEditor"
|
|
||||||
register={register}
|
|
||||||
label="Show code editor on left"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<InputItem
|
|
||||||
name="chartHeight"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Chart Height (in pixels)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
|
|
||||||
<div className="pt-8">
|
|
||||||
<HeadedSection title="Distribution Display Settings">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="logX"
|
|
||||||
label="Show x scale logarithmically"
|
|
||||||
disabled={disableLogXSetting}
|
|
||||||
tooltip={
|
|
||||||
disableLogXSetting
|
|
||||||
? "Your distribution has mass lower than or equal to 0. Log only works on strictly positive values."
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="expY"
|
|
||||||
label="Show y scale exponentially"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="distributionChartActions"
|
|
||||||
label="Show vega chart controls"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="showSummary"
|
|
||||||
label="Show summary statistics"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="minX"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Min X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="maxX"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Max X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="title"
|
|
||||||
type="text"
|
|
||||||
register={register}
|
|
||||||
label="Title"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="tickFormat"
|
|
||||||
type="text"
|
|
||||||
register={register}
|
|
||||||
label="Tick Format"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{withFunctionSettings ? (
|
|
||||||
<div className="pt-8">
|
|
||||||
<HeadedSection title="Function Display Settings">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Text>
|
|
||||||
When displaying functions of single variables that return
|
|
||||||
numbers or distributions, we need to use defaults for the
|
|
||||||
x-axis. We need to select a minimum and maximum value of x to
|
|
||||||
sample, and a number n of the number of points to sample.
|
|
||||||
</Text>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramStart"
|
|
||||||
register={register}
|
|
||||||
label="Min X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramStop"
|
|
||||||
register={register}
|
|
||||||
label="Max X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramCount"
|
|
||||||
register={register}
|
|
||||||
label="Points between X min and X max to sample"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,22 +0,0 @@
|
||||||
import clsx from "clsx";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
onClick: () => void;
|
|
||||||
children: React.ReactNode;
|
|
||||||
wide?: boolean; // stretch the button horizontally
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Button: React.FC<Props> = ({ onClick, wide, children }) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={clsx(
|
|
||||||
"rounded-md py-1.5 px-2 bg-slate-500 text-white text-xs font-semibold flex items-center justify-center space-x-1",
|
|
||||||
wide && "w-full"
|
|
||||||
)}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,37 +0,0 @@
|
||||||
import clsx from "clsx";
|
|
||||||
import React from "react";
|
|
||||||
import { Path, UseFormRegister, FieldValues } from "react-hook-form";
|
|
||||||
|
|
||||||
export function Checkbox<T extends FieldValues>({
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
register,
|
|
||||||
disabled,
|
|
||||||
tooltip,
|
|
||||||
}: {
|
|
||||||
name: Path<T>;
|
|
||||||
label: string;
|
|
||||||
register: UseFormRegister<T>;
|
|
||||||
disabled?: boolean;
|
|
||||||
tooltip?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<label className="flex items-center" title={tooltip}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
disabled={disabled}
|
|
||||||
{...register(name)}
|
|
||||||
className="form-checkbox focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
|
||||||
/>
|
|
||||||
{/* Clicking on the div makes the checkbox lose focus while mouse button is pressed, leading to annoying blinking; I couldn't figure out how to fix this. */}
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
"ml-3 text-sm font-medium",
|
|
||||||
disabled ? "text-gray-400" : "text-gray-700"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export const HeadedSection: React.FC<{
|
|
||||||
title: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}> = ({ title, children }) => (
|
|
||||||
<div>
|
|
||||||
<header className="text-lg leading-6 font-medium text-gray-900">
|
|
||||||
{title}
|
|
||||||
</header>
|
|
||||||
<div className="mt-4">{children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
|
@ -1,25 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { Path, UseFormRegister, FieldValues } from "react-hook-form";
|
|
||||||
|
|
||||||
export function InputItem<T extends FieldValues>({
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
type,
|
|
||||||
register,
|
|
||||||
}: {
|
|
||||||
name: Path<T>;
|
|
||||||
label: string;
|
|
||||||
type: "number" | "text" | "color";
|
|
||||||
register: UseFormRegister<T>;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<label className="block">
|
|
||||||
<div className="text-sm font-medium text-gray-600 mb-1">{label}</div>
|
|
||||||
<input
|
|
||||||
type={type}
|
|
||||||
{...register(name, { valueAsNumber: type === "number" })}
|
|
||||||
className="form-input max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,184 +0,0 @@
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import React, { useContext } from "react";
|
|
||||||
import * as ReactDOM from "react-dom";
|
|
||||||
import { XIcon } from "@heroicons/react/solid";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import { useWindowScroll, useWindowSize } from "react-use";
|
|
||||||
|
|
||||||
type ModalContextShape = {
|
|
||||||
close: () => void;
|
|
||||||
};
|
|
||||||
const ModalContext = React.createContext<ModalContextShape>({
|
|
||||||
close: () => undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Overlay: React.FC = () => {
|
|
||||||
const { close } = useContext(ModalContext);
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
className="absolute inset-0 -z-10 bg-black"
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 0.1 }}
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ModalHeader: React.FC<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}> = ({ children }) => {
|
|
||||||
const { close } = useContext(ModalContext);
|
|
||||||
return (
|
|
||||||
<header className="px-5 py-3 border-b border-gray-200 font-bold flex items-center justify-between">
|
|
||||||
<div>{children}</div>
|
|
||||||
<button
|
|
||||||
className="px-1 bg-transparent cursor-pointer text-gray-700 hover:text-accent-500"
|
|
||||||
type="button"
|
|
||||||
onClick={close}
|
|
||||||
>
|
|
||||||
<XIcon className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500" />
|
|
||||||
</button>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO - get rid of forwardRef, support `focus` and `{...hotkeys}` via smart props
|
|
||||||
const ModalBody = React.forwardRef<
|
|
||||||
HTMLDivElement,
|
|
||||||
JSX.IntrinsicElements["div"]
|
|
||||||
>(function ModalBody(props, ref) {
|
|
||||||
return <div ref={ref} className="px-5 py-3 overflow-auto" {...props} />;
|
|
||||||
});
|
|
||||||
|
|
||||||
const ModalFooter: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
||||||
<div className="px-5 py-3 border-t border-gray-200">{children}</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const ModalWindow: React.FC<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
container?: HTMLElement;
|
|
||||||
}> = ({ children, container }) => {
|
|
||||||
// This component works in two possible modes:
|
|
||||||
// 1. container mode - the modal is rendered inside a container element
|
|
||||||
// 2. centered mode - the modal is rendered in the middle of the screen
|
|
||||||
// The mode is determined by the presence of the `container` prop and by whether the available space is large enough to fit the modal.
|
|
||||||
|
|
||||||
// Necessary for container mode - need to reposition the modal on scroll and resize events.
|
|
||||||
useWindowSize();
|
|
||||||
useWindowScroll();
|
|
||||||
|
|
||||||
let position:
|
|
||||||
| {
|
|
||||||
left: number;
|
|
||||||
top: number;
|
|
||||||
maxWidth: number;
|
|
||||||
maxHeight: number;
|
|
||||||
transform: string;
|
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
// If available space in `visibleRect` is smaller than these, fallback to positioning in the middle of the screen.
|
|
||||||
const minWidth = 384;
|
|
||||||
const minHeight = 300;
|
|
||||||
const offset = 8;
|
|
||||||
const naturalWidth = 576; // maximum possible width; modal tries to take this much space, but can be smaller
|
|
||||||
|
|
||||||
if (container) {
|
|
||||||
const { clientWidth: screenWidth, clientHeight: screenHeight } =
|
|
||||||
document.documentElement;
|
|
||||||
const rect = container?.getBoundingClientRect();
|
|
||||||
|
|
||||||
const visibleRect = {
|
|
||||||
left: Math.max(rect.left, 0),
|
|
||||||
right: Math.min(rect.right, screenWidth),
|
|
||||||
top: Math.max(rect.top, 0),
|
|
||||||
bottom: Math.min(rect.bottom, screenHeight),
|
|
||||||
};
|
|
||||||
const maxWidth = visibleRect.right - visibleRect.left - 2 * offset;
|
|
||||||
const maxHeight = visibleRect.bottom - visibleRect.top - 2 * offset;
|
|
||||||
|
|
||||||
const center = {
|
|
||||||
left: visibleRect.left + (visibleRect.right - visibleRect.left) / 2,
|
|
||||||
top: visibleRect.top + (visibleRect.bottom - visibleRect.top) / 2,
|
|
||||||
};
|
|
||||||
position = {
|
|
||||||
left: center.left,
|
|
||||||
top: center.top,
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
maxWidth,
|
|
||||||
maxHeight,
|
|
||||||
};
|
|
||||||
if (maxWidth < minWidth || maxHeight < minHeight) {
|
|
||||||
position = undefined; // modal is hard to fit in the container, fallback to positioning it in the middle of the screen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
"bg-white rounded-md shadow-toast flex flex-col overflow-auto border",
|
|
||||||
position ? "fixed" : null
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
width: naturalWidth,
|
|
||||||
...(position ?? {
|
|
||||||
maxHeight: "calc(100% - 20px)",
|
|
||||||
maxWidth: "calc(100% - 20px)",
|
|
||||||
width: naturalWidth,
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type ModalType = React.FC<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
container?: HTMLElement; // if specified, modal will be positioned over the visible part of the container, if it's not too small
|
|
||||||
close: () => void;
|
|
||||||
}> & {
|
|
||||||
Body: typeof ModalBody;
|
|
||||||
Footer: typeof ModalFooter;
|
|
||||||
Header: typeof ModalHeader;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Modal: ModalType = ({ children, container, close }) => {
|
|
||||||
const [el] = React.useState(() => document.createElement("div"));
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
document.body.appendChild(el);
|
|
||||||
return () => {
|
|
||||||
document.body.removeChild(el);
|
|
||||||
};
|
|
||||||
}, [el]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener("keydown", handleEscape);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("keydown", handleEscape);
|
|
||||||
};
|
|
||||||
}, [close]);
|
|
||||||
|
|
||||||
const modal = (
|
|
||||||
<ModalContext.Provider value={{ close }}>
|
|
||||||
<div className="squiggle">
|
|
||||||
<div className="fixed inset-0 z-40 flex justify-center items-center">
|
|
||||||
<Overlay />
|
|
||||||
<ModalWindow container={container}>{children}</ModalWindow>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
return ReactDOM.createPortal(modal, container || el);
|
|
||||||
};
|
|
||||||
|
|
||||||
Modal.Body = ModalBody;
|
|
||||||
Modal.Footer = ModalFooter;
|
|
||||||
Modal.Header = ModalHeader;
|
|
|
@ -1,60 +0,0 @@
|
||||||
import React, { Fragment } from "react";
|
|
||||||
import { Tab } from "@headlessui/react";
|
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
type StyledTabProps = {
|
|
||||||
name: string;
|
|
||||||
icon: (props: React.ComponentProps<"svg">) => JSX.Element;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StyledTabType = React.FC<StyledTabProps> & {
|
|
||||||
List: React.FC<{ children: React.ReactNode }>;
|
|
||||||
Group: typeof Tab.Group;
|
|
||||||
Panels: typeof Tab.Panels;
|
|
||||||
Panel: typeof Tab.Panel;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StyledTab: StyledTabType = ({ name, icon: Icon }) => {
|
|
||||||
return (
|
|
||||||
<Tab as={Fragment}>
|
|
||||||
{({ selected }) => (
|
|
||||||
<button className="group flex rounded-md focus:outline-none focus-visible:ring-offset-gray-100">
|
|
||||||
<span
|
|
||||||
className={clsx(
|
|
||||||
"p-1 pl-2.5 pr-3.5 rounded-md flex items-center text-sm font-medium",
|
|
||||||
selected && "bg-white shadow-sm ring-1 ring-black ring-opacity-5"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className={clsx(
|
|
||||||
"-ml-0.5 mr-2 h-4 w-4",
|
|
||||||
selected
|
|
||||||
? "text-slate-500"
|
|
||||||
: "text-gray-400 group-hover:text-gray-900"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className={clsx(
|
|
||||||
selected
|
|
||||||
? "text-gray-900"
|
|
||||||
: "text-gray-600 group-hover:text-gray-900"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Tab>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
StyledTab.List = ({ children }) => (
|
|
||||||
<Tab.List className="flex w-fit p-0.5 rounded-md bg-slate-100 hover:bg-slate-200">
|
|
||||||
{children}
|
|
||||||
</Tab.List>
|
|
||||||
);
|
|
||||||
|
|
||||||
StyledTab.Group = Tab.Group;
|
|
||||||
StyledTab.Panels = Tab.Panels;
|
|
||||||
StyledTab.Panel = Tab.Panel;
|
|
|
@ -1,5 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export const Text: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
||||||
<p className="text-sm text-gray-500">{children}</p>
|
|
||||||
);
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { RefreshIcon } from "@heroicons/react/solid";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
type IconType = (props: React.ComponentProps<"svg">) => JSX.Element;
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
status: boolean;
|
|
||||||
onChange: (status: boolean) => void;
|
|
||||||
texts: [string, string];
|
|
||||||
icons: [IconType, IconType];
|
|
||||||
spinIcon?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Toggle: React.FC<Props> = ({
|
|
||||||
status,
|
|
||||||
onChange,
|
|
||||||
texts: [onText, offText],
|
|
||||||
icons: [OnIcon, OffIcon],
|
|
||||||
spinIcon,
|
|
||||||
}) => {
|
|
||||||
const CurrentIcon = status ? OnIcon : OffIcon;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={clsx(
|
|
||||||
"rounded-md py-0.5 bg-slate-500 text-white text-xs font-semibold flex items-center space-x-1",
|
|
||||||
status ? "bg-slate-500" : "bg-gray-400",
|
|
||||||
status ? "pl-1 pr-3" : "pl-3 pr-1",
|
|
||||||
!status && "flex-row-reverse space-x-reverse"
|
|
||||||
)}
|
|
||||||
onClick={() => onChange(!status)}
|
|
||||||
>
|
|
||||||
<div className="relative w-6 h-6" key={String(spinIcon)}>
|
|
||||||
<CurrentIcon
|
|
||||||
className={clsx(
|
|
||||||
"w-6 h-6 absolute opacity-100",
|
|
||||||
spinIcon && "animate-hide"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{spinIcon && (
|
|
||||||
<RefreshIcon className="w-6 h-6 absolute opacity-0 animate-appear-and-spin" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<span>{status ? onText : offText}</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,64 +0,0 @@
|
||||||
import React, { cloneElement, useState } from "react";
|
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
|
||||||
import {
|
|
||||||
flip,
|
|
||||||
shift,
|
|
||||||
useDismiss,
|
|
||||||
useFloating,
|
|
||||||
useHover,
|
|
||||||
useInteractions,
|
|
||||||
useRole,
|
|
||||||
} from "@floating-ui/react-dom-interactions";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
text: string;
|
|
||||||
children: JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Tooltip: React.FC<Props> = ({ text, children }) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
const { x, y, reference, floating, strategy, context } = useFloating({
|
|
||||||
placement: "top",
|
|
||||||
open: isOpen,
|
|
||||||
onOpenChange: setIsOpen,
|
|
||||||
middleware: [shift(), flip()],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { getReferenceProps, getFloatingProps } = useInteractions([
|
|
||||||
useHover(context),
|
|
||||||
useRole(context, { role: "tooltip" }),
|
|
||||||
useDismiss(context),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{cloneElement(
|
|
||||||
children,
|
|
||||||
getReferenceProps({ ref: reference, ...children.props })
|
|
||||||
)}
|
|
||||||
<AnimatePresence>
|
|
||||||
{isOpen && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
transition={{ duration: 0.15 }}
|
|
||||||
{...getFloatingProps({
|
|
||||||
ref: floating,
|
|
||||||
className:
|
|
||||||
"text-xs p-2 border border-gray-300 rounded bg-white z-10",
|
|
||||||
style: {
|
|
||||||
position: strategy,
|
|
||||||
top: y ?? 0,
|
|
||||||
left: x ?? 0,
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className="font-mono whitespace-pre">{text}</div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,5 +1,11 @@
|
||||||
export { SqProject } from "@quri/squiggle-lang/";
|
|
||||||
export { SquiggleChart } from "./components/SquiggleChart";
|
export { SquiggleChart } from "./components/SquiggleChart";
|
||||||
export { SquiggleEditor } from "./components/SquiggleEditor";
|
export {
|
||||||
export { SquigglePlayground } from "./components/SquigglePlayground";
|
SquiggleEditor,
|
||||||
export { SquiggleContainer } from "./components/SquiggleContainer";
|
SquigglePartial,
|
||||||
|
renderSquiggleEditorToDom,
|
||||||
|
renderSquigglePartialToDom,
|
||||||
|
} from "./components/SquiggleEditor";
|
||||||
|
import SquigglePlayground, {
|
||||||
|
renderSquigglePlaygroundToDom,
|
||||||
|
} from "./components/SquigglePlayground";
|
||||||
|
export { SquigglePlayground, renderSquigglePlaygroundToDom };
|
||||||
|
|
|
@ -1,398 +0,0 @@
|
||||||
import { VisualizationSpec } from "react-vega";
|
|
||||||
import type { LogScale, LinearScale, PowScale, TimeScale } from "vega";
|
|
||||||
|
|
||||||
export type DistributionChartSpecOptions = {
|
|
||||||
/** Set the x scale to be logarithmic by deault */
|
|
||||||
logX: boolean;
|
|
||||||
/** Set the y scale to be exponential by deault */
|
|
||||||
expY: boolean;
|
|
||||||
/** The minimum x coordinate shown on the chart */
|
|
||||||
minX?: number;
|
|
||||||
/** The maximum x coordinate shown on the chart */
|
|
||||||
maxX?: number;
|
|
||||||
/** The title of the chart */
|
|
||||||
title?: string;
|
|
||||||
/** The formatting of the ticks */
|
|
||||||
format?: string;
|
|
||||||
/** Whether the x-axis should be dates or numbers */
|
|
||||||
xAxisType?: "number" | "dateTime";
|
|
||||||
};
|
|
||||||
|
|
||||||
/** X Scales */
|
|
||||||
export const linearXScale: LinearScale = {
|
|
||||||
name: "xscale",
|
|
||||||
clamp: true,
|
|
||||||
type: "linear",
|
|
||||||
range: "width",
|
|
||||||
zero: false,
|
|
||||||
nice: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const logXScale: LogScale = {
|
|
||||||
name: "xscale",
|
|
||||||
type: "log",
|
|
||||||
range: "width",
|
|
||||||
zero: false,
|
|
||||||
base: 10,
|
|
||||||
nice: false,
|
|
||||||
clamp: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const timeXScale: TimeScale = {
|
|
||||||
name: "xscale",
|
|
||||||
clamp: true,
|
|
||||||
type: "time",
|
|
||||||
range: "width",
|
|
||||||
nice: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Y Scales */
|
|
||||||
export const linearYScale: LinearScale = {
|
|
||||||
name: "yscale",
|
|
||||||
type: "linear",
|
|
||||||
range: "height",
|
|
||||||
zero: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const expYScale: PowScale = {
|
|
||||||
name: "yscale",
|
|
||||||
type: "pow",
|
|
||||||
exponent: 0.1,
|
|
||||||
range: "height",
|
|
||||||
zero: true,
|
|
||||||
nice: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultTickFormat = ".9~s";
|
|
||||||
export const timeTickFormat = "%b %d, %Y %H:%M";
|
|
||||||
const width = 500;
|
|
||||||
|
|
||||||
export function buildVegaSpec(
|
|
||||||
specOptions: DistributionChartSpecOptions & { maxY: number }
|
|
||||||
): VisualizationSpec {
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
minX,
|
|
||||||
maxX,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
xAxisType = "number",
|
|
||||||
maxY,
|
|
||||||
} = specOptions;
|
|
||||||
|
|
||||||
const dateTime = xAxisType === "dateTime";
|
|
||||||
|
|
||||||
// some fallbacks
|
|
||||||
const format = specOptions?.format
|
|
||||||
? specOptions.format
|
|
||||||
: dateTime
|
|
||||||
? timeTickFormat
|
|
||||||
: defaultTickFormat;
|
|
||||||
|
|
||||||
let xScale = dateTime ? timeXScale : logX ? logXScale : linearXScale;
|
|
||||||
|
|
||||||
xScale = {
|
|
||||||
...xScale,
|
|
||||||
domain: [minX ?? 0, maxX ?? 1],
|
|
||||||
domainMin: minX,
|
|
||||||
domainMax: maxX,
|
|
||||||
};
|
|
||||||
|
|
||||||
let yScale = expY ? expYScale : linearYScale;
|
|
||||||
yScale = { ...yScale, domain: [0, maxY ?? 1], domainMin: 0, domainMax: maxY };
|
|
||||||
|
|
||||||
const spec: VisualizationSpec = {
|
|
||||||
$schema: "https://vega.github.io/schema/vega/v5.json",
|
|
||||||
description: "Squiggle plot chart",
|
|
||||||
width: width,
|
|
||||||
height: 100,
|
|
||||||
padding: 5,
|
|
||||||
data: [{ name: "data" }, { name: "domain" }, { name: "samples" }],
|
|
||||||
signals: [
|
|
||||||
{
|
|
||||||
name: "hover",
|
|
||||||
value: null,
|
|
||||||
on: [
|
|
||||||
{ events: "mouseover", update: "datum" },
|
|
||||||
{ events: "mouseout", update: "null" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "position",
|
|
||||||
value: "[0, 0]",
|
|
||||||
on: [
|
|
||||||
{ events: "mousemove", update: "xy() " },
|
|
||||||
{ events: "mouseout", update: "null" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "position_scaled",
|
|
||||||
value: null,
|
|
||||||
update: "isArray(position) ? invert('xscale', position[0]) : ''",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
scales: [
|
|
||||||
xScale,
|
|
||||||
yScale,
|
|
||||||
{
|
|
||||||
name: "color",
|
|
||||||
type: "ordinal",
|
|
||||||
domain: {
|
|
||||||
data: "data",
|
|
||||||
field: "name",
|
|
||||||
},
|
|
||||||
range: { scheme: "blues" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
axes: [
|
|
||||||
{
|
|
||||||
orient: "bottom",
|
|
||||||
scale: "xscale",
|
|
||||||
labelColor: "#727d93",
|
|
||||||
tickColor: "#fff",
|
|
||||||
tickOpacity: 0.0,
|
|
||||||
domainColor: "#fff",
|
|
||||||
domainOpacity: 0.0,
|
|
||||||
format: format,
|
|
||||||
tickCount: dateTime ? 3 : 10,
|
|
||||||
labelOverlap: "greedy",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
marks: [
|
|
||||||
{
|
|
||||||
name: "all_distributions",
|
|
||||||
type: "group",
|
|
||||||
from: {
|
|
||||||
facet: {
|
|
||||||
name: "distribution_facet",
|
|
||||||
data: "data",
|
|
||||||
groupby: ["name"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
marks: [
|
|
||||||
{
|
|
||||||
name: "continuous_distribution",
|
|
||||||
type: "group",
|
|
||||||
from: {
|
|
||||||
facet: {
|
|
||||||
name: "continuous_facet",
|
|
||||||
data: "distribution_facet",
|
|
||||||
field: "continuous",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
encode: {
|
|
||||||
update: {},
|
|
||||||
},
|
|
||||||
marks: [
|
|
||||||
{
|
|
||||||
name: "continuous_area",
|
|
||||||
type: "area",
|
|
||||||
from: {
|
|
||||||
data: "continuous_facet",
|
|
||||||
},
|
|
||||||
encode: {
|
|
||||||
update: {
|
|
||||||
interpolate: { value: "linear" },
|
|
||||||
x: {
|
|
||||||
scale: "xscale",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
scale: "yscale",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
scale: "color",
|
|
||||||
field: { parent: "name" },
|
|
||||||
},
|
|
||||||
y2: {
|
|
||||||
scale: "yscale",
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
fillOpacity: {
|
|
||||||
value: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "discrete_distribution",
|
|
||||||
type: "group",
|
|
||||||
from: {
|
|
||||||
facet: {
|
|
||||||
name: "discrete_facet",
|
|
||||||
data: "distribution_facet",
|
|
||||||
field: "discrete",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
marks: [
|
|
||||||
{
|
|
||||||
type: "rect",
|
|
||||||
from: {
|
|
||||||
data: "discrete_facet",
|
|
||||||
},
|
|
||||||
encode: {
|
|
||||||
enter: {
|
|
||||||
width: {
|
|
||||||
value: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
x: {
|
|
||||||
scale: "xscale",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
scale: "yscale",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
y2: {
|
|
||||||
scale: "yscale",
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
scale: "color",
|
|
||||||
field: { parent: "name" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "symbol",
|
|
||||||
from: {
|
|
||||||
data: "discrete_facet",
|
|
||||||
},
|
|
||||||
encode: {
|
|
||||||
enter: {
|
|
||||||
shape: {
|
|
||||||
value: "circle",
|
|
||||||
},
|
|
||||||
size: [{ value: 100 }],
|
|
||||||
tooltip: {
|
|
||||||
signal: dateTime
|
|
||||||
? "{ probability: datum.y, value: datetime(datum.x) }"
|
|
||||||
: "{ probability: datum.y, value: datum.x }",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
x: {
|
|
||||||
scale: "xscale",
|
|
||||||
field: "x",
|
|
||||||
offset: 0.5, // if this is not included, the circles are slightly left of center.
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
scale: "yscale",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
scale: "color",
|
|
||||||
field: { parent: "name" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "sampleset",
|
|
||||||
type: "rect",
|
|
||||||
from: { data: "samples" },
|
|
||||||
encode: {
|
|
||||||
enter: {
|
|
||||||
x: { scale: "xscale", field: "data" },
|
|
||||||
width: { value: 0.1 },
|
|
||||||
|
|
||||||
y: { value: 25, offset: { signal: "height" } },
|
|
||||||
height: { value: 5 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
name: "announcer",
|
|
||||||
interactive: false,
|
|
||||||
encode: {
|
|
||||||
enter: {
|
|
||||||
x: { signal: String(width), offset: 1 }, // vega would prefer its internal ` "width" ` variable, but that breaks the squiggle playground. Just setting it to the same var as used elsewhere in the spec achieves the same result.
|
|
||||||
fill: { value: "black" },
|
|
||||||
fontSize: { value: 20 },
|
|
||||||
align: { value: "right" },
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
text: {
|
|
||||||
signal: dateTime
|
|
||||||
? "position_scaled ? utcyear(position_scaled) + '-' + utcmonth(position_scaled) + '-' + utcdate(position_scaled) + 'T' + utchours(position_scaled)+':' +utcminutes(position_scaled) : ''"
|
|
||||||
: "position_scaled ? format(position_scaled, ',.4r') : ''",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "rule",
|
|
||||||
interactive: false,
|
|
||||||
encode: {
|
|
||||||
enter: {
|
|
||||||
x: { value: 0 },
|
|
||||||
y: { scale: "yscale", value: 0 },
|
|
||||||
|
|
||||||
y2: {
|
|
||||||
signal: "height",
|
|
||||||
offset: 2,
|
|
||||||
},
|
|
||||||
strokeDash: { value: [5, 5] },
|
|
||||||
},
|
|
||||||
|
|
||||||
update: {
|
|
||||||
x: {
|
|
||||||
signal:
|
|
||||||
"position ? position[0] < 0 ? null : position[0] > width ? null : position[0]: null",
|
|
||||||
},
|
|
||||||
|
|
||||||
opacity: {
|
|
||||||
signal:
|
|
||||||
"position ? position[0] < 0 ? 0 : position[0] > width ? 0 : 1 : 0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
legends: [
|
|
||||||
{
|
|
||||||
fill: "color",
|
|
||||||
orient: "top",
|
|
||||||
labelFontSize: 12,
|
|
||||||
encode: {
|
|
||||||
symbols: {
|
|
||||||
update: {
|
|
||||||
fill: [
|
|
||||||
{ test: "length(domain('color')) == 1", value: "transparent" },
|
|
||||||
{ scale: "color", field: "value" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
labels: {
|
|
||||||
interactive: true,
|
|
||||||
update: {
|
|
||||||
fill: [
|
|
||||||
{ test: "length(domain('color')) == 1", value: "transparent" },
|
|
||||||
{ value: "black" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
...(title && {
|
|
||||||
title: {
|
|
||||||
text: title,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
return spec;
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { SqShape } from "@quri/squiggle-lang";
|
|
||||||
|
|
||||||
export const hasMassBelowZero = (shape: SqShape) =>
|
|
||||||
shape.continuous.some((x) => x.x <= 0) ||
|
|
||||||
shape.discrete.some((x) => x.x <= 0);
|
|
|
@ -1,3 +0,0 @@
|
||||||
export { useMaybeControlledValue } from "./useMaybeControlledValue";
|
|
||||||
export { useSquiggle } from "./useSquiggle";
|
|
||||||
export { useRunnerState } from "./useRunnerState";
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
type ControlledValueArgs<T> = {
|
|
||||||
value?: T;
|
|
||||||
defaultValue: T;
|
|
||||||
onChange?: (x: T) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useMaybeControlledValue<T>(
|
|
||||||
args: ControlledValueArgs<T>
|
|
||||||
): [T, (x: T) => void] {
|
|
||||||
let [uncontrolledValue, setUncontrolledValue] = useState(args.defaultValue);
|
|
||||||
let value = args.value ?? uncontrolledValue;
|
|
||||||
let onChange = (newValue: T) => {
|
|
||||||
if (args.value === undefined) {
|
|
||||||
// uncontrolled mode
|
|
||||||
setUncontrolledValue(newValue);
|
|
||||||
}
|
|
||||||
args.onChange?.(newValue);
|
|
||||||
};
|
|
||||||
return [value, onChange];
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
import { useLayoutEffect, useReducer } from "react";
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
autorunMode: boolean;
|
|
||||||
renderedCode: string;
|
|
||||||
// "prepared" is for rendering a spinner; "run" for executing squiggle code; then it gets back to "none" on the next render
|
|
||||||
runningState: "none" | "prepared" | "run";
|
|
||||||
executionId: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildInitialState = (code: string): State => ({
|
|
||||||
autorunMode: true,
|
|
||||||
renderedCode: "",
|
|
||||||
runningState: "none",
|
|
||||||
executionId: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
type Action =
|
|
||||||
| {
|
|
||||||
type: "SET_AUTORUN_MODE";
|
|
||||||
value: boolean;
|
|
||||||
code: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "PREPARE_RUN";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "RUN";
|
|
||||||
code: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "STOP_RUN";
|
|
||||||
};
|
|
||||||
|
|
||||||
const reducer = (state: State, action: Action): State => {
|
|
||||||
switch (action.type) {
|
|
||||||
case "SET_AUTORUN_MODE":
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
autorunMode: action.value,
|
|
||||||
};
|
|
||||||
case "PREPARE_RUN":
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
runningState: "prepared",
|
|
||||||
};
|
|
||||||
case "RUN":
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
runningState: "run",
|
|
||||||
renderedCode: action.code,
|
|
||||||
executionId: state.executionId + 1,
|
|
||||||
};
|
|
||||||
case "STOP_RUN":
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
runningState: "none",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useRunnerState = (code: string) => {
|
|
||||||
const [state, dispatch] = useReducer(reducer, buildInitialState(code));
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (state.runningState === "prepared") {
|
|
||||||
// this is necessary for async playground loading - otherwise it executes the code synchronously on the initial load
|
|
||||||
// (it's surprising that this is necessary, but empirically it _is_ necessary, both with `useEffect` and `useLayoutEffect`)
|
|
||||||
setTimeout(() => {
|
|
||||||
dispatch({ type: "RUN", code });
|
|
||||||
}, 0);
|
|
||||||
} else if (state.runningState === "run") {
|
|
||||||
dispatch({ type: "STOP_RUN" });
|
|
||||||
}
|
|
||||||
}, [state.runningState, code]);
|
|
||||||
|
|
||||||
const run = () => {
|
|
||||||
// The rest will be handled by dispatches above on following renders, but we need to update the spinner first.
|
|
||||||
dispatch({ type: "PREPARE_RUN" });
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
state.autorunMode &&
|
|
||||||
state.renderedCode !== code &&
|
|
||||||
state.runningState === "none"
|
|
||||||
) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
run,
|
|
||||||
autorunMode: state.autorunMode,
|
|
||||||
renderedCode: state.renderedCode,
|
|
||||||
isRunning: state.runningState !== "none",
|
|
||||||
executionId: state.executionId,
|
|
||||||
setAutorunMode: (newValue: boolean) => {
|
|
||||||
dispatch({ type: "SET_AUTORUN_MODE", value: newValue, code });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,97 +0,0 @@
|
||||||
import {
|
|
||||||
result,
|
|
||||||
SqError,
|
|
||||||
SqProject,
|
|
||||||
SqRecord,
|
|
||||||
SqValue,
|
|
||||||
environment,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
|
|
||||||
import * as uuid from "uuid";
|
|
||||||
|
|
||||||
type SquiggleArgs = {
|
|
||||||
environment?: environment;
|
|
||||||
code: string;
|
|
||||||
executionId?: number;
|
|
||||||
jsImports?: JsImports;
|
|
||||||
project?: SqProject;
|
|
||||||
continues?: string[];
|
|
||||||
onChange?: (expr: SqValue | undefined, sourceName: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ResultAndBindings = {
|
|
||||||
result: result<SqValue, SqError>;
|
|
||||||
bindings: SqRecord;
|
|
||||||
};
|
|
||||||
|
|
||||||
const importSourceName = (sourceName: string) => "imports-" + sourceName;
|
|
||||||
const defaultContinues = [];
|
|
||||||
|
|
||||||
export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
|
|
||||||
const project = useMemo(() => {
|
|
||||||
if (args.project) {
|
|
||||||
return args.project;
|
|
||||||
} else {
|
|
||||||
const p = SqProject.create();
|
|
||||||
if (args.environment) {
|
|
||||||
p.setEnvironment(args.environment);
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
}, [args.project, args.environment]);
|
|
||||||
|
|
||||||
const sourceName = useMemo(() => uuid.v4(), []);
|
|
||||||
|
|
||||||
const env = project.getEnvironment();
|
|
||||||
const continues = args.continues || defaultContinues;
|
|
||||||
|
|
||||||
const result = useMemo(
|
|
||||||
() => {
|
|
||||||
project.setSource(sourceName, args.code);
|
|
||||||
let fullContinues = continues;
|
|
||||||
if (args.jsImports && Object.keys(args.jsImports).length) {
|
|
||||||
const importsSource = jsImportsToSquiggleCode(args.jsImports);
|
|
||||||
project.setSource(importSourceName(sourceName), importsSource);
|
|
||||||
fullContinues = continues.concat(importSourceName(sourceName));
|
|
||||||
}
|
|
||||||
project.setContinues(sourceName, fullContinues);
|
|
||||||
project.run(sourceName);
|
|
||||||
const result = project.getResult(sourceName);
|
|
||||||
const bindings = project.getBindings(sourceName);
|
|
||||||
return { result, bindings };
|
|
||||||
},
|
|
||||||
// This complains about executionId not being used inside the function body.
|
|
||||||
// This is on purpose, as executionId simply allows you to run the squiggle
|
|
||||||
// code again
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[
|
|
||||||
args.code,
|
|
||||||
args.jsImports,
|
|
||||||
args.executionId,
|
|
||||||
sourceName,
|
|
||||||
continues,
|
|
||||||
project,
|
|
||||||
env,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { onChange } = args;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onChange?.(
|
|
||||||
result.result.tag === "Ok" ? result.result.value : undefined,
|
|
||||||
sourceName
|
|
||||||
);
|
|
||||||
}, [result, onChange, sourceName]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
project.removeSource(sourceName);
|
|
||||||
if (project.getSource(importSourceName(sourceName)))
|
|
||||||
project.removeSource(importSourceName(sourceName));
|
|
||||||
};
|
|
||||||
}, [project, sourceName]);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
|
@ -1,51 +0,0 @@
|
||||||
type JsImportsValue =
|
|
||||||
| number
|
|
||||||
| string
|
|
||||||
| JsImportsValue[]
|
|
||||||
| {
|
|
||||||
[k: string]: JsImportsValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type JsImports = {
|
|
||||||
[k: string]: JsImportsValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
const quote = (arg: string) => `"${arg.replace(new RegExp('"', "g"), '\\"')}"`;
|
|
||||||
|
|
||||||
const jsImportsValueToSquiggleCode = (v: JsImportsValue): string => {
|
|
||||||
if (typeof v === "number") {
|
|
||||||
return String(v);
|
|
||||||
} else if (typeof v === "string") {
|
|
||||||
return quote(v);
|
|
||||||
} else if (v instanceof Array) {
|
|
||||||
return "[" + v.map((x) => jsImportsValueToSquiggleCode(x)) + "]";
|
|
||||||
} else {
|
|
||||||
if (Object.keys(v).length) {
|
|
||||||
return (
|
|
||||||
"{" +
|
|
||||||
Object.entries(v)
|
|
||||||
.map(([k, v]) => `${quote(k)}:${jsImportsValueToSquiggleCode(v)},`)
|
|
||||||
.join("") +
|
|
||||||
"}"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return "0"; // squiggle doesn't support empty `{}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const jsImportsToSquiggleCode = (v: JsImports) => {
|
|
||||||
const validId = new RegExp("[a-zA-Z][[a-zA-Z0-9]*");
|
|
||||||
let result = Object.entries(v)
|
|
||||||
.map(([k, v]) => {
|
|
||||||
if (!k.match(validId)) {
|
|
||||||
return ""; // skipping without warnings; can be improved
|
|
||||||
}
|
|
||||||
return `$${k} = ${jsImportsValueToSquiggleCode(v)}\n`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
if (!result) {
|
|
||||||
result = "$__no_valid_imports__ = 1"; // without this generated squiggle code can be invalid
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
|
@ -1,83 +0,0 @@
|
||||||
import * as yup from "yup";
|
|
||||||
import {
|
|
||||||
SqValue,
|
|
||||||
SqValueTag,
|
|
||||||
SqDistribution,
|
|
||||||
result,
|
|
||||||
SqRecord,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
|
|
||||||
export type LabeledDistribution = {
|
|
||||||
name: string;
|
|
||||||
distribution: SqDistribution;
|
|
||||||
color?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Plot = {
|
|
||||||
distributions: LabeledDistribution[];
|
|
||||||
};
|
|
||||||
|
|
||||||
function error<a, b>(err: b): result<a, b> {
|
|
||||||
return { tag: "Error", value: err };
|
|
||||||
}
|
|
||||||
|
|
||||||
function ok<a, b>(x: a): result<a, b> {
|
|
||||||
return { tag: "Ok", value: x };
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = yup
|
|
||||||
.object()
|
|
||||||
.noUnknown()
|
|
||||||
.strict()
|
|
||||||
.shape({
|
|
||||||
distributions: yup
|
|
||||||
.array()
|
|
||||||
.required()
|
|
||||||
.of(
|
|
||||||
yup.object().required().shape({
|
|
||||||
name: yup.string().required(),
|
|
||||||
distribution: yup.mixed().required(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
type JsonObject =
|
|
||||||
| string
|
|
||||||
| { [key: string]: JsonObject }
|
|
||||||
| JsonObject[]
|
|
||||||
| SqDistribution;
|
|
||||||
|
|
||||||
function toJson(val: SqValue): JsonObject {
|
|
||||||
if (val.tag === SqValueTag.String) {
|
|
||||||
return val.value;
|
|
||||||
} else if (val.tag === SqValueTag.Record) {
|
|
||||||
return toJsonRecord(val.value);
|
|
||||||
} else if (val.tag === SqValueTag.Array) {
|
|
||||||
return val.value.getValues().map(toJson);
|
|
||||||
} else if (val.tag === SqValueTag.Distribution) {
|
|
||||||
return val.value;
|
|
||||||
} else {
|
|
||||||
throw new Error("Could not parse object of type " + val.tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toJsonRecord(val: SqRecord): JsonObject {
|
|
||||||
let recordObject: JsonObject = {};
|
|
||||||
val.entries().forEach(([key, value]) => (recordObject[key] = toJson(value)));
|
|
||||||
return recordObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parsePlot(record: SqRecord): result<Plot, string> {
|
|
||||||
try {
|
|
||||||
const plotRecord = schema.validateSync(toJsonRecord(record));
|
|
||||||
if (plotRecord.distributions) {
|
|
||||||
return ok({ distributions: plotRecord.distributions.map((x) => x) });
|
|
||||||
} else {
|
|
||||||
// I have no idea why yup's typings thinks this is possible
|
|
||||||
return error("no distributions field. Should never get here");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
const message = e instanceof Error ? e.message : "Unknown error";
|
|
||||||
return error(message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { result, resultMap, SqValueTag } from "@quri/squiggle-lang";
|
|
||||||
import { ResultAndBindings } from "./hooks/useSquiggle";
|
|
||||||
|
|
||||||
export function flattenResult<a, b>(x: result<a, b>[]): result<a[], b> {
|
|
||||||
if (x.length === 0) {
|
|
||||||
return { tag: "Ok", value: [] };
|
|
||||||
} else {
|
|
||||||
if (x[0].tag === "Error") {
|
|
||||||
return x[0];
|
|
||||||
} else {
|
|
||||||
let rest = flattenResult(x.splice(1));
|
|
||||||
if (rest.tag === "Error") {
|
|
||||||
return rest;
|
|
||||||
} else {
|
|
||||||
return { tag: "Ok", value: [x[0].value].concat(rest.value) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resultBind<a, b, c>(
|
|
||||||
x: result<a, b>,
|
|
||||||
fn: (y: a) => result<c, b>
|
|
||||||
): result<c, b> {
|
|
||||||
if (x.tag === "Ok") {
|
|
||||||
return fn(x.value);
|
|
||||||
} else {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function all(arr: boolean[]): boolean {
|
|
||||||
return arr.reduce((x, y) => x && y, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function some(arr: boolean[]): boolean {
|
|
||||||
return arr.reduce((x, y) => x || y, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getValueToRender({ result, bindings }: ResultAndBindings) {
|
|
||||||
return resultMap(result, (value) =>
|
|
||||||
value.tag === SqValueTag.Void ? bindings.asValue() : value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getErrorLocations(result: ResultAndBindings["result"]) {
|
|
||||||
if (result.tag === "Error") {
|
|
||||||
const location = result.value.location();
|
|
||||||
return location ? [location] : [];
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"}
|
|
@ -3,7 +3,7 @@ import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
||||||
|
|
||||||
<Meta title="Squiggle/SquiggleChart" component={SquiggleChart} />
|
<Meta title="Squiggle/SquiggleChart" component={SquiggleChart} />
|
||||||
|
|
||||||
export const Template = (props) => <SquiggleChart {...props} />;
|
export const Template = SquiggleChart;
|
||||||
/*
|
/*
|
||||||
We have to hardcode a width here, because otherwise some interaction with
|
We have to hardcode a width here, because otherwise some interaction with
|
||||||
Storybook creates an infinite loop with the internal width
|
Storybook creates an infinite loop with the internal width
|
||||||
|
@ -29,7 +29,7 @@ could be continuous, discrete or mixed.
|
||||||
<Story
|
<Story
|
||||||
name="Continuous Symbolic"
|
name="Continuous Symbolic"
|
||||||
args={{
|
args={{
|
||||||
code: "normal(5,2)",
|
squiggleString: "normal(5,2)",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -43,7 +43,7 @@ could be continuous, discrete or mixed.
|
||||||
<Story
|
<Story
|
||||||
name="Continuous Pointset"
|
name="Continuous Pointset"
|
||||||
args={{
|
args={{
|
||||||
code: "PointSet.fromDist(normal(5,2))",
|
squiggleString: "toPointSet(normal(5,2))",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -57,7 +57,7 @@ could be continuous, discrete or mixed.
|
||||||
<Story
|
<Story
|
||||||
name="Continuous SampleSet"
|
name="Continuous SampleSet"
|
||||||
args={{
|
args={{
|
||||||
code: "SampleSet.fromDist(normal(5,2))",
|
squiggleString: "toSampleSet(normal(5,2), 1000)",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -71,23 +71,7 @@ could be continuous, discrete or mixed.
|
||||||
<Story
|
<Story
|
||||||
name="Discrete"
|
name="Discrete"
|
||||||
args={{
|
args={{
|
||||||
code: "mx(0, 1, 3, 5, 8, 10, [0.1, 0.8, 0.5, 0.3, 0.2, 0.1])",
|
squiggleString: "mx(0, 1, 3, 5, 8, 10, [0.1, 0.8, 0.5, 0.3, 0.2, 0.1])",
|
||||||
width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
### Date Distribution
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Date Distribution"
|
|
||||||
args={{
|
|
||||||
code: "mx(1661819770311, 1661829770311, 1661839770311)",
|
|
||||||
width,
|
|
||||||
xAxisType: "dateTime",
|
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -101,34 +85,8 @@ could be continuous, discrete or mixed.
|
||||||
<Story
|
<Story
|
||||||
name="Mixed"
|
name="Mixed"
|
||||||
args={{
|
args={{
|
||||||
code: "mx(0, 1, 3, 5, 8, normal(8, 1), [0.1, 0.3, 0.4, 0.35, 0.2, 0.8])",
|
squiggleString:
|
||||||
width,
|
"mx(0, 1, 3, 5, 8, normal(8, 1), [0.1, 0.3, 0.4, 0.35, 0.2, 0.8])",
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
## Multiple plots
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Multiple plots"
|
|
||||||
args={{
|
|
||||||
code: `
|
|
||||||
{
|
|
||||||
distributions: [
|
|
||||||
{
|
|
||||||
name: "one",
|
|
||||||
distribution: mx(0.5, normal(0,1))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "two",
|
|
||||||
distribution: mx(2, normal(5, 2)),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -145,7 +103,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Constant"
|
name="Constant"
|
||||||
args={{
|
args={{
|
||||||
code: "500000000",
|
squiggleString: "500000000",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -159,7 +117,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Array"
|
name="Array"
|
||||||
args={{
|
args={{
|
||||||
code: "[normal(5,2), normal(10,1), normal(40,2), 400000]",
|
squiggleString: "[normal(5,2), normal(10,1), normal(40,2), 400000]",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -173,7 +131,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Error"
|
name="Error"
|
||||||
args={{
|
args={{
|
||||||
code: "f(x) = normal(",
|
squiggleString: "f(x) = normal(",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -187,35 +145,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Boolean"
|
name="Boolean"
|
||||||
args={{
|
args={{
|
||||||
code: "3 == 3",
|
squiggleString: "3 == 3",
|
||||||
width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
## Functions (Distribution Output)
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Function to Distribution"
|
|
||||||
args={{
|
|
||||||
code: "foo(t) = normal(t,2)*normal(5,3); foo",
|
|
||||||
width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
## Functions (Number Output)
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Function to Number"
|
|
||||||
args={{
|
|
||||||
code: "foo(t) = t^2; foo",
|
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -229,7 +159,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Record"
|
name="Record"
|
||||||
args={{
|
args={{
|
||||||
code: "{foo: 35 to 50, bar: [1,2,3]}",
|
squiggleString: "{foo: 35 to 50, bar: [1,2,3]}",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -243,7 +173,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="String"
|
name="String"
|
||||||
args={{
|
args={{
|
||||||
code: '"Lucky day!"',
|
squiggleString: '"Lucky day!"',
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -14,20 +14,7 @@ the distribution.
|
||||||
<Story
|
<Story
|
||||||
name="Normal"
|
name="Normal"
|
||||||
args={{
|
args={{
|
||||||
defaultCode: "normal(5,2)",
|
initialSquiggleString: "normal(5,2)",
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
It's also possible to create a controlled version of the same component
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Controlled"
|
|
||||||
args={{
|
|
||||||
code: "normal(5,2)",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
|
@ -40,7 +27,7 @@ You can also name variables like so:
|
||||||
<Story
|
<Story
|
||||||
name="Variables"
|
name="Variables"
|
||||||
args={{
|
args={{
|
||||||
defaultCode: "x = 2\nnormal(x,2)",
|
initialSquiggleString: "x = 2\nnormal(x,2)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
|
|
51
packages/components/src/stories/SquigglePartial.stories.mdx
Normal file
51
packages/components/src/stories/SquigglePartial.stories.mdx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import { SquigglePartial, SquiggleEditor } from "../components/SquiggleEditor";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
<Meta title="Squiggle/SquigglePartial" component={SquigglePartial} />
|
||||||
|
|
||||||
|
export const Template = (props) => <SquigglePartial {...props} />;
|
||||||
|
|
||||||
|
# Squiggle Partial
|
||||||
|
|
||||||
|
A Squiggle Partial is an editor that does not return a graph to the user, but
|
||||||
|
instead returns bindings that can be used by further Squiggle Editors.
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Standalone"
|
||||||
|
args={{
|
||||||
|
initialSquiggleString: "x = normal(5,2)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="With Editor"
|
||||||
|
args={{
|
||||||
|
initialPartialString: "x = normal(5,2)",
|
||||||
|
initialEditorString: "x",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(props) => {
|
||||||
|
let [bindings, setBindings] = useState({});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SquigglePartial
|
||||||
|
{...props}
|
||||||
|
initialSquiggleString={props.initialPartialString}
|
||||||
|
onChange={setBindings}
|
||||||
|
/>
|
||||||
|
<SquiggleEditor
|
||||||
|
{...props}
|
||||||
|
initialSquiggleString={props.initialEditorString}
|
||||||
|
bindings={bindings}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
|
@ -1,5 +1,6 @@
|
||||||
import { SquigglePlayground } from "../components/SquigglePlayground";
|
import SquigglePlayground from "../components/SquigglePlayground";
|
||||||
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
<Meta title="Squiggle/SquigglePlayground" component={SquigglePlayground} />
|
<Meta title="Squiggle/SquigglePlayground" component={SquigglePlayground} />
|
||||||
|
|
||||||
|
@ -14,21 +15,8 @@ including sampling settings, in squiggle.
|
||||||
<Story
|
<Story
|
||||||
name="Normal"
|
name="Normal"
|
||||||
args={{
|
args={{
|
||||||
defaultCode: "normal(5,2)",
|
initialSquiggleString: "normal(5,2)",
|
||||||
height: 800,
|
height: 500,
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="With share button"
|
|
||||||
args={{
|
|
||||||
defaultCode: "normal(5,2)",
|
|
||||||
height: 800,
|
|
||||||
showShareButton: true,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
|
|
|
@ -1,396 +0,0 @@
|
||||||
.squiggle {
|
|
||||||
/*
|
|
||||||
This file contains:
|
|
||||||
1) Base Tailwind preflight styles
|
|
||||||
2) Base https://github.com/tailwindlabs/tailwindcss-forms styles
|
|
||||||
|
|
||||||
(Both are wrapped in .squiggle)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use a consistent sensible line-height in all browsers.
|
|
||||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
|
||||||
3. Use a more readable tab size.
|
|
||||||
4. Use the user's configured `sans` font-family by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* html { */
|
|
||||||
line-height: 1.5; /* 1 */
|
|
||||||
-webkit-text-size-adjust: 100%; /* 2 */
|
|
||||||
-moz-tab-size: 4; /* 3 */
|
|
||||||
tab-size: 4; /* 3 */
|
|
||||||
font-family: theme(
|
|
||||||
"fontFamily.sans",
|
|
||||||
ui-sans-serif,
|
|
||||||
system-ui,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
"Segoe UI",
|
|
||||||
Roboto,
|
|
||||||
"Helvetica Neue",
|
|
||||||
Arial,
|
|
||||||
"Noto Sans",
|
|
||||||
sans-serif,
|
|
||||||
"Apple Color Emoji",
|
|
||||||
"Segoe UI Emoji",
|
|
||||||
"Segoe UI Symbol",
|
|
||||||
"Noto Color Emoji"
|
|
||||||
); /* 4 */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove the margin in all browsers.
|
|
||||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* body { */
|
|
||||||
margin: 0; /* 1 */
|
|
||||||
line-height: inherit; /* 2 */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
|
||||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
|
||||||
*/
|
|
||||||
|
|
||||||
*,
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
box-sizing: border-box; /* 1 */
|
|
||||||
border-width: 0; /* 2 */
|
|
||||||
border-style: solid; /* 2 */
|
|
||||||
border-color: theme("borderColor.DEFAULT", currentColor); /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
--tw-content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Add the correct height in Firefox.
|
|
||||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
|
||||||
3. Ensure horizontal rules are visible by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
hr {
|
|
||||||
height: 0; /* 1 */
|
|
||||||
color: inherit; /* 2 */
|
|
||||||
border-top-width: 1px; /* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
abbr:where([title]) {
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the default font size and weight for headings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-size: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reset links to optimize for opt-in styling instead of opt-out.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font weight in Edge and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use the user's configured `mono` font family by default.
|
|
||||||
2. Correct the odd `em` font sizing in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp,
|
|
||||||
pre {
|
|
||||||
font-family: theme(
|
|
||||||
"fontFamily.mono",
|
|
||||||
ui-monospace,
|
|
||||||
SFMono-Regular,
|
|
||||||
Menlo,
|
|
||||||
Monaco,
|
|
||||||
Consolas,
|
|
||||||
"Liberation Mono",
|
|
||||||
"Courier New",
|
|
||||||
monospace
|
|
||||||
); /* 1 */
|
|
||||||
font-size: 1em; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font size in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 0;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
|
||||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
|
||||||
3. Remove gaps between table borders by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
table {
|
|
||||||
text-indent: 0; /* 1 */
|
|
||||||
border-color: inherit; /* 2 */
|
|
||||||
border-collapse: collapse; /* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Change the font styles in all browsers.
|
|
||||||
2. Remove the margin in Firefox and Safari.
|
|
||||||
3. Remove default padding in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
optgroup,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-family: inherit; /* 1 */
|
|
||||||
font-size: 100%; /* 1 */
|
|
||||||
font-weight: inherit; /* 1 */
|
|
||||||
line-height: inherit; /* 1 */
|
|
||||||
color: inherit; /* 1 */
|
|
||||||
margin: 0; /* 2 */
|
|
||||||
padding: 0; /* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inheritance of text transform in Edge and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Remove default button styles.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
[type="button"],
|
|
||||||
[type="reset"],
|
|
||||||
[type="submit"] {
|
|
||||||
-webkit-appearance: button; /* 1 */
|
|
||||||
background-color: transparent; /* 2 */
|
|
||||||
background-image: none; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Use the modern Firefox focus style for all focusable elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-focusring {
|
|
||||||
outline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-ui-invalid {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct vertical alignment in Chrome and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Correct the cursor style of increment and decrement buttons in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-inner-spin-button,
|
|
||||||
::-webkit-outer-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the odd appearance in Chrome and Safari.
|
|
||||||
2. Correct the outline style in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[type="search"] {
|
|
||||||
-webkit-appearance: textfield; /* 1 */
|
|
||||||
outline-offset: -2px; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inner padding in Chrome and Safari on macOS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Change font properties to `inherit` in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
-webkit-appearance: button; /* 1 */
|
|
||||||
font: inherit; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct display in Chrome and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Removes the default spacing and border for appropriate elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
blockquote,
|
|
||||||
dl,
|
|
||||||
dd,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
hr,
|
|
||||||
figure,
|
|
||||||
p,
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
menu {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent resizing textareas horizontally by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
|
||||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
|
||||||
*/
|
|
||||||
|
|
||||||
input::placeholder,
|
|
||||||
textarea::placeholder {
|
|
||||||
opacity: 1; /* 1 */
|
|
||||||
color: theme("colors.gray.400", #9ca3af); /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the default cursor for buttons.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
[role="button"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Make sure disabled buttons don't get the pointer cursor.
|
|
||||||
*/
|
|
||||||
:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
|
||||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg,
|
|
||||||
video,
|
|
||||||
canvas,
|
|
||||||
audio,
|
|
||||||
iframe,
|
|
||||||
embed,
|
|
||||||
object {
|
|
||||||
display: block; /* 1 */
|
|
||||||
vertical-align: middle; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
video {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
/* Fork of https://github.com/tailwindlabs/tailwindcss-forms styles, see the comment in main.css for details. */
|
|
||||||
.squiggle {
|
|
||||||
.form-input,
|
|
||||||
.form-textarea,
|
|
||||||
.form-select,
|
|
||||||
.form-multiselect {
|
|
||||||
appearance: none;
|
|
||||||
background-color: #fff;
|
|
||||||
border-color: #6b7280;
|
|
||||||
border-width: 1px;
|
|
||||||
border-radius: 0px;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
padding-right: 0.75rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
padding-left: 0.75rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
}
|
|
||||||
.form-input:focus,
|
|
||||||
.form-textarea:focus,
|
|
||||||
.form-select:focus,
|
|
||||||
.form-multiselect:focus {
|
|
||||||
outline: 2px solid transparent;
|
|
||||||
outline-offset: 2px;
|
|
||||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: #2563eb;
|
|
||||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0
|
|
||||||
var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
||||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0
|
|
||||||
calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
||||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
|
|
||||||
var(--tw-shadow);
|
|
||||||
border-color: #2563eb;
|
|
||||||
}
|
|
||||||
.form-input::placeholder,
|
|
||||||
.form-textarea::placeholder {
|
|
||||||
color: #6b7280;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.form-input::-webkit-datetime-edit-fields-wrapper {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.form-input::-webkit-date-and-time-value {
|
|
||||||
min-height: 1.5em;
|
|
||||||
}
|
|
||||||
.form-input::-webkit-datetime-edit,
|
|
||||||
.form-input::-webkit-datetime-edit-year-field,
|
|
||||||
.form-input::-webkit-datetime-edit-month-field,
|
|
||||||
.form-input::-webkit-datetime-edit-day-field,
|
|
||||||
.form-input::-webkit-datetime-edit-hour-field,
|
|
||||||
.form-input::-webkit-datetime-edit-minute-field,
|
|
||||||
.form-input::-webkit-datetime-edit-second-field,
|
|
||||||
.form-input::-webkit-datetime-edit-millisecond-field,
|
|
||||||
.form-input::-webkit-datetime-edit-meridiem-field {
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
.form-checkbox,
|
|
||||||
.form-radio {
|
|
||||||
appearance: none;
|
|
||||||
padding: 0;
|
|
||||||
-webkit-print-color-adjust: exact;
|
|
||||||
print-color-adjust: exact;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
background-origin: border-box;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
flex-shrink: 0;
|
|
||||||
height: 1rem;
|
|
||||||
width: 1rem;
|
|
||||||
color: #2563eb;
|
|
||||||
background-color: #fff;
|
|
||||||
border-color: #6b7280;
|
|
||||||
border-width: 1px;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
}
|
|
||||||
.form-checkbox {
|
|
||||||
border-radius: 0px;
|
|
||||||
}
|
|
||||||
.form-checkbox:focus,
|
|
||||||
.form-radio:focus {
|
|
||||||
outline: 2px solid transparent;
|
|
||||||
outline-offset: 2px;
|
|
||||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
|
||||||
--tw-ring-offset-width: 2px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: #2563eb;
|
|
||||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0
|
|
||||||
var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
||||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0
|
|
||||||
calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
||||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
|
|
||||||
var(--tw-shadow);
|
|
||||||
}
|
|
||||||
.form-checkbox:checked,
|
|
||||||
.form-radio:checked {
|
|
||||||
border-color: transparent;
|
|
||||||
background-color: currentColor;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
.form-checkbox:checked {
|
|
||||||
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
|
|
||||||
}
|
|
||||||
.form-checkbox:checked:hover,
|
|
||||||
.form-checkbox:checked:focus,
|
|
||||||
.form-radio:checked:hover,
|
|
||||||
.form-radio:checked:focus {
|
|
||||||
border-color: transparent;
|
|
||||||
background-color: currentColor;
|
|
||||||
}
|
|
||||||
.form-checkbox:indeterminate {
|
|
||||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");
|
|
||||||
border-color: transparent;
|
|
||||||
background-color: currentColor;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
.form-checkbox:indeterminate:hover,
|
|
||||||
.form-checkbox:indeterminate:focus {
|
|
||||||
border-color: transparent;
|
|
||||||
background-color: currentColor;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
Fork of tailwind's preflight with `.squiggle` scoping.
|
|
||||||
*/
|
|
||||||
@import "./base.css";
|
|
||||||
/*
|
|
||||||
Fork of https://github.com/tailwindlabs/tailwindcss-forms styles (with strategy: "class"), but with `.squiggle` scoping.
|
|
||||||
This is necessary because tailwindcss-forms's css specificity is lower than preflight's specificity (`padding: 0` and so on),
|
|
||||||
so unscoped forms css with scoped preflight css, and there's no setting for scoping.
|
|
||||||
*/
|
|
||||||
@import "./forms.css";
|
|
||||||
|
|
||||||
/*
|
|
||||||
This doesn't include tailwind's original preflight (it's disabled in tailwind.config.js),
|
|
||||||
but this line is still necessary for proper initialization of `--tw-*` variables.
|
|
||||||
*/
|
|
||||||
@tailwind base;
|
|
||||||
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
/* Necessary hack because scoped preflight in ./base.css has higher specificity. */
|
|
||||||
.ace_cursor {
|
|
||||||
border-left: 2px solid !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ace-error-marker {
|
|
||||||
position: absolute;
|
|
||||||
border-bottom: 1px solid red;
|
|
||||||
}
|
|
|
@ -23,7 +23,7 @@
|
||||||
"tickOpacity": 0.0,
|
"tickOpacity": 0.0,
|
||||||
"domainColor": "#fff",
|
"domainColor": "#fff",
|
||||||
"domainOpacity": 0.0,
|
"domainOpacity": 0.0,
|
||||||
"format": ".9~s",
|
"format": "~g",
|
||||||
"tickCount": 10
|
"tickCount": 10
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -33,7 +33,6 @@
|
||||||
"from": {
|
"from": {
|
||||||
"data": "con"
|
"data": "con"
|
||||||
},
|
},
|
||||||
"interpolate": "linear",
|
|
||||||
"encode": {
|
"encode": {
|
||||||
"update": {
|
"update": {
|
||||||
"x": {
|
"x": {
|
||||||
|
@ -49,10 +48,10 @@
|
||||||
"value": 0
|
"value": 0
|
||||||
},
|
},
|
||||||
"fill": {
|
"fill": {
|
||||||
"value": "#739ECC"
|
"signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: '#4C78A8'}] }"
|
||||||
},
|
},
|
||||||
"interpolate": {
|
"interpolate": {
|
||||||
"value": "linear"
|
"value": "monotone"
|
||||||
},
|
},
|
||||||
"fillOpacity": {
|
"fillOpacity": {
|
||||||
"value": 1
|
"value": 1
|
||||||
|
@ -83,9 +82,6 @@
|
||||||
"y2": {
|
"y2": {
|
||||||
"scale": "yscale",
|
"scale": "yscale",
|
||||||
"value": 0
|
"value": 0
|
||||||
},
|
|
||||||
"fill": {
|
|
||||||
"value": "#2f65a7"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
|
||||||
"width": 500,
|
|
||||||
"height": 200,
|
|
||||||
"padding": 5,
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"name": "facet",
|
|
||||||
"values": [],
|
|
||||||
"format": {
|
|
||||||
"type": "json",
|
|
||||||
"parse": {
|
|
||||||
"timestamp": "date"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"scales": [
|
|
||||||
{
|
|
||||||
"name": "x",
|
|
||||||
"type": "linear",
|
|
||||||
"nice": true,
|
|
||||||
"zero": false,
|
|
||||||
"domain": {
|
|
||||||
"data": "facet",
|
|
||||||
"field": "x"
|
|
||||||
},
|
|
||||||
"range": "width"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "y",
|
|
||||||
"type": "linear",
|
|
||||||
"range": "height",
|
|
||||||
"nice": true,
|
|
||||||
"zero": false,
|
|
||||||
"domain": {
|
|
||||||
"data": "facet",
|
|
||||||
"field": "y"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"signals": [
|
|
||||||
{
|
|
||||||
"name": "mousemove",
|
|
||||||
"on": [{ "events": "mousemove", "update": "invert('x', x())" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mouseout",
|
|
||||||
"on": [{ "events": "mouseout", "update": "invert('x', x())" }]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"axes": [
|
|
||||||
{
|
|
||||||
"orient": "bottom",
|
|
||||||
"scale": "x",
|
|
||||||
"grid": false,
|
|
||||||
"labelColor": "#727d93",
|
|
||||||
"tickColor": "#fff",
|
|
||||||
"tickOpacity": 0.0,
|
|
||||||
"domainColor": "#727d93",
|
|
||||||
"domainOpacity": 0.1,
|
|
||||||
"format": ".9~s",
|
|
||||||
"tickCount": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"orient": "left",
|
|
||||||
"scale": "y",
|
|
||||||
"grid": false,
|
|
||||||
"labelColor": "#727d93",
|
|
||||||
"tickColor": "#fff",
|
|
||||||
"tickOpacity": 0.0,
|
|
||||||
"domainColor": "#727d93",
|
|
||||||
"domainOpacity": 0.1,
|
|
||||||
"format": ".9~s",
|
|
||||||
"tickCount": 5
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"marks": [
|
|
||||||
{
|
|
||||||
"type": "line",
|
|
||||||
"from": { "data": "facet" },
|
|
||||||
"encode": {
|
|
||||||
"enter": {
|
|
||||||
"x": { "scale": "x", "field": "x" },
|
|
||||||
"y": { "scale": "y", "field": "y" },
|
|
||||||
"strokeWidth": { "value": 2 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -75,7 +75,6 @@
|
||||||
"name": "xscale",
|
"name": "xscale",
|
||||||
"type": "linear",
|
"type": "linear",
|
||||||
"nice": true,
|
"nice": true,
|
||||||
"zero": false,
|
|
||||||
"domain": {
|
"domain": {
|
||||||
"data": "facet",
|
"data": "facet",
|
||||||
"field": "x"
|
"field": "x"
|
||||||
|
@ -87,10 +86,10 @@
|
||||||
"type": "linear",
|
"type": "linear",
|
||||||
"range": "height",
|
"range": "height",
|
||||||
"nice": true,
|
"nice": true,
|
||||||
"zero": false,
|
"zero": true,
|
||||||
"domain": {
|
"domain": {
|
||||||
"data": "facet",
|
"data": "facet",
|
||||||
"fields": ["p1", "p99"]
|
"field": "p99"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -114,14 +113,12 @@
|
||||||
"tickOpacity": 0.0,
|
"tickOpacity": 0.0,
|
||||||
"domainColor": "#727d93",
|
"domainColor": "#727d93",
|
||||||
"domainOpacity": 0.1,
|
"domainOpacity": 0.1,
|
||||||
"format": ".9~s",
|
|
||||||
"tickCount": 5
|
"tickCount": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"orient": "left",
|
"orient": "left",
|
||||||
"scale": "yscale",
|
"scale": "yscale",
|
||||||
"grid": false,
|
"grid": false,
|
||||||
"format": ".9~s",
|
|
||||||
"labelColor": "#727d93",
|
"labelColor": "#727d93",
|
||||||
"tickColor": "#fff",
|
"tickColor": "#fff",
|
||||||
"tickOpacity": 0.0,
|
"tickOpacity": 0.0,
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
content: ["./src/**/*.{html,tsx,ts,js,jsx}"],
|
|
||||||
corePlugins: {
|
|
||||||
preflight: false,
|
|
||||||
},
|
|
||||||
important: ".squiggle",
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
animation: {
|
|
||||||
"appear-and-spin":
|
|
||||||
"spin 1s linear infinite, squiggle-appear 0.2s forwards",
|
|
||||||
"semi-appear": "squiggle-semi-appear 0.2s forwards",
|
|
||||||
hide: "squiggle-hide 0.2s forwards",
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
"squiggle-appear": {
|
|
||||||
from: { opacity: 0 },
|
|
||||||
to: { opacity: 1 },
|
|
||||||
},
|
|
||||||
"squiggle-semi-appear": {
|
|
||||||
from: { opacity: 0 },
|
|
||||||
to: { opacity: 0.5 },
|
|
||||||
},
|
|
||||||
"squiggle-hide": {
|
|
||||||
from: { opacity: 1 },
|
|
||||||
to: { opacity: 0 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,55 +0,0 @@
|
||||||
import { render, screen, waitFor, within } from "@testing-library/react";
|
|
||||||
import userEvent from "@testing-library/user-event";
|
|
||||||
import * as React from "react";
|
|
||||||
import "@testing-library/jest-dom";
|
|
||||||
import { SquigglePlayground } from "../src/index";
|
|
||||||
|
|
||||||
test("Autorun is default", async () => {
|
|
||||||
render(<SquigglePlayground code="70*30" />);
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("playground-result")).toHaveTextContent("2100")
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Autorun can be switched off", async () => {
|
|
||||||
const user = userEvent.setup();
|
|
||||||
render(<SquigglePlayground code="70*30" />);
|
|
||||||
|
|
||||||
expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Autorun");
|
|
||||||
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("playground-result")).toHaveTextContent("2100")
|
|
||||||
);
|
|
||||||
|
|
||||||
await user.click(screen.getByText("Autorun")); // disable
|
|
||||||
expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Paused");
|
|
||||||
expect(screen.getByTestId("autorun-controls")).not.toHaveTextContent(
|
|
||||||
"Autorun"
|
|
||||||
);
|
|
||||||
|
|
||||||
await user.click(screen.getByText("Paused")); // enable autorun again
|
|
||||||
expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Autorun");
|
|
||||||
|
|
||||||
// we should replace the code here, but it's hard to update react-ace state via user events: https://github.com/securingsincity/react-ace/issues/923
|
|
||||||
// ...or replace react-ace with something else
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
|
|
||||||
/*
|
|
||||||
const editor = screen
|
|
||||||
.getByTestId("squiggle-editor")
|
|
||||||
.querySelector(".ace_editor") as HTMLElement;
|
|
||||||
editor.focus();
|
|
||||||
// await user.clear(editor);
|
|
||||||
await userEvent.paste("40*40"); // https://github.com/securingsincity/react-ace/issues/923#issuecomment-755502696
|
|
||||||
screen.debug(editor);
|
|
||||||
|
|
||||||
// this makes the tests slower, but it's hard to test otherwise that the code _didn't_ execute
|
|
||||||
await new Promise((r) => setTimeout(r, 300));
|
|
||||||
expect(screen.getByTestId("playground-result")).toHaveTextContent("2100"); // still the old value
|
|
||||||
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("playground-result")).toHaveTextContent("1600")
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
});
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { render, screen } from "@testing-library/react";
|
|
||||||
import React from "react";
|
|
||||||
import "@testing-library/jest-dom";
|
|
||||||
import {
|
|
||||||
SquiggleChart,
|
|
||||||
SquiggleEditor,
|
|
||||||
SquigglePlayground,
|
|
||||||
} from "../src/index";
|
|
||||||
import { SqProject } from "@quri/squiggle-lang";
|
|
||||||
|
|
||||||
test("Chart logs nothing on render", async () => {
|
|
||||||
const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />);
|
|
||||||
unmount();
|
|
||||||
|
|
||||||
expect(console.log).not.toBeCalled();
|
|
||||||
expect(console.warn).not.toBeCalled();
|
|
||||||
expect(console.error).not.toBeCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Editor logs nothing on render", async () => {
|
|
||||||
const { unmount } = render(<SquiggleEditor code={"normal(0, 1)"} />);
|
|
||||||
unmount();
|
|
||||||
|
|
||||||
expect(console.log).not.toBeCalled();
|
|
||||||
expect(console.warn).not.toBeCalled();
|
|
||||||
expect(console.error).not.toBeCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Project dependencies work in editors", async () => {
|
|
||||||
const project = SqProject.create();
|
|
||||||
|
|
||||||
render(<SquiggleEditor code={"x = 1"} project={project} />);
|
|
||||||
const source = project.getSourceIds()[0];
|
|
||||||
const { container } = render(
|
|
||||||
<SquiggleEditor code={"x + 1"} project={project} continues={[source]} />
|
|
||||||
);
|
|
||||||
expect(container).toHaveTextContent("2");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Project dependencies work in playgrounds", async () => {
|
|
||||||
const project = SqProject.create();
|
|
||||||
project.setSource("depend", "x = 1");
|
|
||||||
|
|
||||||
render(
|
|
||||||
<SquigglePlayground
|
|
||||||
code={"x + 1"}
|
|
||||||
project={project}
|
|
||||||
continues={["depend"]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
// We must await here because SquigglePlayground loads results asynchronously
|
|
||||||
expect(await screen.findByRole("status")).toHaveTextContent("2");
|
|
||||||
});
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { render } from "@testing-library/react";
|
|
||||||
import React from "react";
|
|
||||||
import "@testing-library/jest-dom";
|
|
||||||
import { SquiggleChart } from "../src/index";
|
|
||||||
import { SqProject } from "@quri/squiggle-lang";
|
|
||||||
|
|
||||||
test("Creates and cleans up source", async () => {
|
|
||||||
const project = SqProject.create();
|
|
||||||
|
|
||||||
const { unmount } = render(
|
|
||||||
<SquiggleChart code={"normal(0, 1)"} project={project} />
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(project.getSourceIds().length).toBe(1);
|
|
||||||
|
|
||||||
const sourceId = project.getSourceIds()[0];
|
|
||||||
expect(project.getSource(sourceId)).toBe("normal(0, 1)");
|
|
||||||
|
|
||||||
unmount();
|
|
||||||
expect(project.getSourceIds().length).toBe(0);
|
|
||||||
expect(project.getSource(sourceId)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Creates and cleans up source and imports", async () => {
|
|
||||||
const project = SqProject.create();
|
|
||||||
|
|
||||||
const { unmount } = render(
|
|
||||||
<SquiggleChart
|
|
||||||
code={"normal($x, 1)"}
|
|
||||||
project={project}
|
|
||||||
jsImports={{ x: 3 }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(project.getSourceIds().length).toBe(2);
|
|
||||||
|
|
||||||
unmount();
|
|
||||||
expect(project.getSourceIds()).toStrictEqual([]);
|
|
||||||
});
|
|
|
@ -1,8 +0,0 @@
|
||||||
global.console = {
|
|
||||||
...console,
|
|
||||||
log: jest.fn(console.log),
|
|
||||||
debug: jest.fn(console.debug),
|
|
||||||
info: jest.fn(console.info),
|
|
||||||
warn: jest.fn(console.warn),
|
|
||||||
error: jest.fn(console.error),
|
|
||||||
};
|
|
|
@ -20,8 +20,7 @@
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src/vega-specs/spec-distributions.json",
|
"src/vega-specs/spec-distributions.json",
|
||||||
"src/vega-specs/spec-percentiles.json",
|
"src/vega-specs/spec-percentiles.json"
|
||||||
"src/vega-specs/spec-line-chart.json"
|
|
||||||
],
|
],
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"include": ["src/**/*", "src/*"],
|
"include": ["src/**/*", "src/*"],
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"buildCommand": "cd ../.. && npx turbo run build --filter=@quri/squiggle-components",
|
|
||||||
"outputDirectory": "storybook-static"
|
|
||||||
}
|
|
|
@ -10,9 +10,13 @@ module.exports = {
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
loader: "ts-loader",
|
loader: "ts-loader",
|
||||||
options: { projectReferences: true },
|
options: { projectReferences: true, transpileOnly: true },
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: ["style-loader", "css-loader"],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
@ -36,18 +40,4 @@ module.exports = {
|
||||||
compress: true,
|
compress: true,
|
||||||
port: 9000,
|
port: 9000,
|
||||||
},
|
},
|
||||||
externals: {
|
|
||||||
react: {
|
|
||||||
commonjs: "react",
|
|
||||||
commonjs2: "react",
|
|
||||||
amd: "react",
|
|
||||||
root: "React",
|
|
||||||
},
|
|
||||||
"react-dom": {
|
|
||||||
commonjs: "react-dom",
|
|
||||||
commonjs2: "react-dom",
|
|
||||||
amd: "react-dom",
|
|
||||||
root: "ReactDOM",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
3
packages/squiggle-lang/.gitignore
vendored
3
packages/squiggle-lang/.gitignore
vendored
|
@ -21,6 +21,3 @@ dist
|
||||||
_coverage
|
_coverage
|
||||||
coverage
|
coverage
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
|
||||||
src/rescript/Reducer/Reducer_Peggy/helpers.js
|
|
||||||
src/rescript/ReducerProject/ReducerProject_IncludeParser.js
|
|
||||||
|
|
|
@ -5,6 +5,3 @@ lib
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
coverage/
|
coverage/
|
||||||
.cache/
|
.cache/
|
||||||
Reducer_Peggy_GeneratedParser.js
|
|
||||||
ReducerProject_IncludeParser.js
|
|
||||||
src/rescript/Reducer/Reducer_Peggy/helpers.js
|
|
||||||
|
|
|
@ -13,29 +13,15 @@ For instance, in a javascript project, you can
|
||||||
yarn add @quri/squiggle-lang
|
yarn add @quri/squiggle-lang
|
||||||
```
|
```
|
||||||
|
|
||||||
The `@quri/squiggle-lang` package exports a single function, `run`, which given
|
|
||||||
a string of Squiggle code, will execute the code and return any exports and the
|
|
||||||
environment created from the squiggle code.
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { run } from "@quri/squiggle-lang";
|
import { run } from "@quri/squiggle-lang";
|
||||||
run(
|
run(
|
||||||
"normal(0, 1) * SampleSet.fromList([-3, 2,-1,1,2,3,3,3,4,9])"
|
"normal(0, 1) * fromSamples([-3,-2,-1,1,2,3,3,3,4,9]"
|
||||||
).value.value.toSparkline().value;
|
).value.value.toSparkline().value;
|
||||||
```
|
```
|
||||||
|
|
||||||
**However, for most use cases you'll prefer to use our [library of react components](https://www.npmjs.com/package/@quri/squiggle-components)**, and let your app transitively depend on `@quri/squiggle-lang`.
|
**However, for most use cases you'll prefer to use our [library of react components](https://www.npmjs.com/package/@quri/squiggle-components)**, and let your app transitively depend on `@quri/squiggle-lang`.
|
||||||
|
|
||||||
`run` has two optional arguments. The first optional argument allows you to set
|
|
||||||
sampling settings for Squiggle when representing distributions. The second optional
|
|
||||||
argument allows you to pass an environment previously created by another `run`
|
|
||||||
call. Passing this environment will mean that all previously declared variables
|
|
||||||
in the previous environment will be made available.
|
|
||||||
|
|
||||||
The return type of `run` is a bit complicated, and comes from auto generated `js`
|
|
||||||
code that comes from rescript. We highly recommend using typescript when using
|
|
||||||
this library to help navigate the return type.
|
|
||||||
|
|
||||||
# Build for development
|
# Build for development
|
||||||
|
|
||||||
We assume that you ran `yarn` at the monorepo level.
|
We assume that you ran `yarn` at the monorepo level.
|
||||||
|
|
|
@ -14,16 +14,4 @@ describe("Combining Continuous and Discrete Distributions", () => {
|
||||||
), // Multiply distribution by -1
|
), // Multiply distribution by -1
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
makeTest(
|
|
||||||
"keep order of xs when first number is discrete and adding",
|
|
||||||
AlgebraicShapeCombination.isOrdered(
|
|
||||||
AlgebraicShapeCombination.combineShapesContinuousDiscrete(
|
|
||||||
#Add,
|
|
||||||
{xs: [0., 1.], ys: [1., 1.]},
|
|
||||||
{xs: [1.], ys: [1.]},
|
|
||||||
~discretePosition=First,
|
|
||||||
),
|
|
||||||
), // 1 + distribution
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
open Jest
|
open Jest
|
||||||
open Expect
|
open Expect
|
||||||
|
|
||||||
let env: GenericDist.env = {
|
let env: DistributionOperation.env = {
|
||||||
sampleCount: 100,
|
sampleCount: 100,
|
||||||
xyPointLength: 100,
|
xyPointLength: 100,
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ describe("sparkline", () => {
|
||||||
expected: DistributionOperation.outputType,
|
expected: DistributionOperation.outputType,
|
||||||
) => {
|
) => {
|
||||||
test(name, () => {
|
test(name, () => {
|
||||||
let result = DistributionOperation.run(~env, FromDist(#ToString(ToSparkline(20)), dist))
|
let result = DistributionOperation.run(~env, FromDist(ToString(ToSparkline(20)), dist))
|
||||||
expect(result)->toEqual(expected)
|
expect(result)->toEqual(expected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -81,8 +81,8 @@ describe("sparkline", () => {
|
||||||
describe("toPointSet", () => {
|
describe("toPointSet", () => {
|
||||||
test("on symbolic normal distribution", () => {
|
test("on symbolic normal distribution", () => {
|
||||||
let result =
|
let result =
|
||||||
run(FromDist(#ToDist(ToPointSet), normalDist5))
|
run(FromDist(ToDist(ToPointSet), normalDist5))
|
||||||
->outputMap(FromDist(#ToFloat(#Mean)))
|
->outputMap(FromDist(ToFloat(#Mean)))
|
||||||
->toFloat
|
->toFloat
|
||||||
->toExt
|
->toExt
|
||||||
expect(result)->toBeSoCloseTo(5.0, ~digits=0)
|
expect(result)->toBeSoCloseTo(5.0, ~digits=0)
|
||||||
|
@ -90,10 +90,10 @@ describe("toPointSet", () => {
|
||||||
|
|
||||||
test("on sample set", () => {
|
test("on sample set", () => {
|
||||||
let result =
|
let result =
|
||||||
run(FromDist(#ToDist(ToPointSet), normalDist5))
|
run(FromDist(ToDist(ToPointSet), normalDist5))
|
||||||
->outputMap(FromDist(#ToDist(ToSampleSet(1000))))
|
->outputMap(FromDist(ToDist(ToSampleSet(1000))))
|
||||||
->outputMap(FromDist(#ToDist(ToPointSet)))
|
->outputMap(FromDist(ToDist(ToPointSet)))
|
||||||
->outputMap(FromDist(#ToFloat(#Mean)))
|
->outputMap(FromDist(ToFloat(#Mean)))
|
||||||
->toFloat
|
->toFloat
|
||||||
->toExt
|
->toExt
|
||||||
expect(result)->toBeSoCloseTo(5.0, ~digits=-1)
|
expect(result)->toBeSoCloseTo(5.0, ~digits=-1)
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
open TestHelpers
|
|
||||||
open FastCheck
|
|
||||||
open Arbitrary
|
|
||||||
open Property.Sync
|
|
||||||
|
|
||||||
describe("dotSubtract", () => {
|
|
||||||
test("mean of normal minus exponential (unit)", () => {
|
|
||||||
let mean = 0.0
|
|
||||||
let rate = 10.0
|
|
||||||
exception MeanFailed
|
|
||||||
let dotDifference = DistributionOperation.Constructors.pointwiseSubtract(
|
|
||||||
~env,
|
|
||||||
mkNormal(mean, 1.0),
|
|
||||||
mkExponential(rate),
|
|
||||||
)
|
|
||||||
let meanResult = E.R2.bind(DistributionOperation.Constructors.mean(~env), dotDifference)
|
|
||||||
let meanAnalytical =
|
|
||||||
mean -.
|
|
||||||
SymbolicDist.Exponential.mean({rate: rate})->E.R2.toExn(
|
|
||||||
"On trusted input this should never happen",
|
|
||||||
)
|
|
||||||
switch meanResult {
|
|
||||||
| Ok(meanValue) => meanValue->expect->toBeCloseTo(meanAnalytical)
|
|
||||||
| Error(_) => raise(MeanFailed)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
/*
|
|
||||||
It seems like this test should work, and it's plausible that
|
|
||||||
there's some bug in `pointwiseSubtract`
|
|
||||||
*/
|
|
||||||
Skip.test("mean of normal minus exponential (property)", () => {
|
|
||||||
assert_(
|
|
||||||
property2(
|
|
||||||
float_(),
|
|
||||||
floatRange(1e-5, 1e5),
|
|
||||||
(mean, rate) => {
|
|
||||||
// We limit ourselves to stdev=1 so that the integral is trivial
|
|
||||||
let dotDifference = DistributionOperation.Constructors.pointwiseSubtract(
|
|
||||||
~env,
|
|
||||||
mkNormal(mean, 1.0),
|
|
||||||
mkExponential(rate),
|
|
||||||
)
|
|
||||||
let meanResult = E.R2.bind(DistributionOperation.Constructors.mean(~env), dotDifference)
|
|
||||||
// according to algebra or random variables,
|
|
||||||
let meanAnalytical =
|
|
||||||
mean -.
|
|
||||||
SymbolicDist.Exponential.mean({rate: rate})->E.R2.toExn(
|
|
||||||
"On trusted input this should never happen",
|
|
||||||
)
|
|
||||||
switch meanResult {
|
|
||||||
| Ok(meanValue) => abs_float(meanValue -. meanAnalytical) /. abs_float(meanValue) < 1e-2 // 1% relative error
|
|
||||||
| Error(err) => err === DistributionTypes.OperationError(DivisionByZeroError)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
pass
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -11,14 +11,4 @@ let triangularDist: DistributionTypes.genericDist = Symbolic(
|
||||||
)
|
)
|
||||||
let exponentialDist: DistributionTypes.genericDist = Symbolic(#Exponential({rate: 2.0}))
|
let exponentialDist: DistributionTypes.genericDist = Symbolic(#Exponential({rate: 2.0}))
|
||||||
let uniformDist: DistributionTypes.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0}))
|
let uniformDist: DistributionTypes.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0}))
|
||||||
let uniformDist2: DistributionTypes.genericDist = Symbolic(#Uniform({low: 8.0, high: 11.0}))
|
|
||||||
let floatDist: DistributionTypes.genericDist = Symbolic(#Float(1e1))
|
let floatDist: DistributionTypes.genericDist = Symbolic(#Float(1e1))
|
||||||
|
|
||||||
exception KlFailed
|
|
||||||
exception MixtureFailed
|
|
||||||
let float1 = 1.0
|
|
||||||
let float2 = 2.0
|
|
||||||
let float3 = 3.0
|
|
||||||
let point1 = TestHelpers.mkDelta(float1)
|
|
||||||
let point2 = TestHelpers.mkDelta(float2)
|
|
||||||
let point3 = TestHelpers.mkDelta(float3)
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user