Compare commits
1 Commits
develop
...
fix-github
Author | SHA1 | Date | |
---|---|---|---|
|
e6b4f286a1 |
31
.github/CODEOWNERS
vendored
31
.github/CODEOWNERS
vendored
|
@ -1,31 +0,0 @@
|
||||||
# CODEOWNERS file
|
|
||||||
#
|
|
||||||
# This file is used to describe who owns what in this repository.
|
|
||||||
#
|
|
||||||
# For documentation on this file, see https://help.github.com/articles/about-codeowners/
|
|
||||||
# Mentioned users will get code review requests.
|
|
||||||
#
|
|
||||||
# IMPORTANT NOTE: in order to actually get pinged, commit access is required.
|
|
||||||
# This also holds true for GitHub teams.
|
|
||||||
|
|
||||||
# Rescript
|
|
||||||
*.res @berekuk @OAGr
|
|
||||||
*.resi @berekuk @OAGr
|
|
||||||
|
|
||||||
# Typescript
|
|
||||||
*.tsx @Hazelfire @berekuk @OAGr
|
|
||||||
*.ts @Hazelfire @berekuk @OAGr
|
|
||||||
|
|
||||||
# Javascript
|
|
||||||
*.js @Hazelfire @berekuk @OAGr
|
|
||||||
|
|
||||||
# Any opsy files
|
|
||||||
.github/** @quinn-dougherty @berekuk @OAGr
|
|
||||||
*.json @quinn-dougherty @Hazelfire @berekuk @OAGr
|
|
||||||
*.y*ml @quinn-dougherty @berekuk @OAGr
|
|
||||||
*.config.js @Hazelfire @berekuk @OAGr
|
|
||||||
vercel.json @OAGr @berekuk @Hazelfire
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
*.md @quinn-dougherty @OAGr @Hazelfire
|
|
||||||
*.mdx @quinn-dougherty @OAGr @Hazelfire
|
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,5 +0,0 @@
|
||||||
blank_issues_enabled: true
|
|
||||||
contact_links:
|
|
||||||
- name: Ideas and feature requests - Squiggle Discussions on GitHub
|
|
||||||
url: https://github.com/quantified-uncertainty/squiggle/discussions
|
|
||||||
about: Please propose and discuss new features here. Remember to search for your idea before posting a new topic! Where would you like to see Squiggle go over the next few months, several months, or few years?
|
|
13
.github/ISSUE_TEMPLATE/ops-testing.md
vendored
13
.github/ISSUE_TEMPLATE/ops-testing.md
vendored
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
name: Operations and testing
|
|
||||||
about: Have a testing-related task? Developer friction when contributing to squiggle? Etc.
|
|
||||||
labels: "ops & testing"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Description:
|
|
||||||
|
|
||||||
# The OS and version, yarn version, etc. in which this came up
|
|
||||||
|
|
||||||
<!-- delete this section if testing task or otherwise not applicable -->
|
|
||||||
|
|
||||||
# Desired behavior
|
|
13
.github/ISSUE_TEMPLATE/pl.md
vendored
13
.github/ISSUE_TEMPLATE/pl.md
vendored
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
name: Regarding the programming language (the `squiggle-lang` package)
|
|
||||||
about: Anything concerning distributions/numerics, as well as the interpreter, parser, syntax, semantics
|
|
||||||
labels: "programming language"
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- mark one with an x -->
|
|
||||||
|
|
||||||
- \_ Is refactor
|
|
||||||
- \_ Is new feature
|
|
||||||
- \_ Concerns documentation
|
|
||||||
|
|
||||||
# Description of suggestion or shortcoming:
|
|
17
.github/ISSUE_TEMPLATE/user-bug.md
vendored
17
.github/ISSUE_TEMPLATE/user-bug.md
vendored
|
@ -1,17 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug reports for Squiggle users
|
|
||||||
about: Rendering oddly? Is there a mathematical correctness problem?
|
|
||||||
labels: "bug"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Description:
|
|
||||||
|
|
||||||
# Steps to reproduce:
|
|
||||||
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
|
|
||||||
# Expected behavior:
|
|
||||||
|
|
||||||
# What I got instead:
|
|
24
.github/dependabot.yml
vendored
24
.github/dependabot.yml
vendored
|
@ -1,24 +0,0 @@
|
||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "npm" # See documentation for possible values
|
|
||||||
directory: "/" # Location of package manifests
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
commit-message:
|
|
||||||
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
|
|
48
.github/workflows/ci.yml
vendored
48
.github/workflows/ci.yml
vendored
|
@ -1,48 +0,0 @@
|
||||||
name: Squiggle packages checks
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
|
|
||||||
env:
|
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
||||||
TURBO_TEAM: quantified-uncertainty
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-test-lint:
|
|
||||||
name: Build, test, lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Setup Node.js environment
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
cache: 'yarn'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn --frozen-lockfile
|
|
||||||
- name: Turbo run
|
|
||||||
run: npx turbo run build test lint bundle
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
name: Coverage
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
- name: Setup Node.js environment
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
cache: 'yarn'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn
|
|
||||||
- name: Coverage
|
|
||||||
run: npx turbo run coverage
|
|
68
.github/workflows/codeql-analysis.yml
vendored
68
.github/workflows/codeql-analysis.yml
vendored
|
@ -1,68 +0,0 @@
|
||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "42 19 * * 0"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: ["javascript"]
|
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
|
||||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v2
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
||||||
# By default, queries listed here will override any specified in a config file.
|
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v2
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn
|
|
||||||
- name: Build rescript
|
|
||||||
run: cd packages/squiggle-lang && yarn build
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://git.io/JvXDl
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v2
|
|
19
.github/workflows/lang-jest.yml
vendored
Normal file
19
.github/workflows/lang-jest.yml
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
name: Squiggle Lang Jest Tests
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: packages/squiggle-lang
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Packages
|
||||||
|
run: yarn
|
||||||
|
- name: Build rescript
|
||||||
|
run: yarn run build
|
||||||
|
- name: Run tests
|
||||||
|
run: yarn test
|
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
|
|
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -3,13 +3,3 @@ yarn-error.log
|
||||||
.cache
|
.cache
|
||||||
.merlin
|
.merlin
|
||||||
.parcel-cache
|
.parcel-cache
|
||||||
.DS_Store
|
|
||||||
**/.sync.ffs_db
|
|
||||||
.direnv
|
|
||||||
.log
|
|
||||||
|
|
||||||
.vscode
|
|
||||||
todo.txt
|
|
||||||
result
|
|
||||||
shell.nix
|
|
||||||
.turbo
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
.direnv
|
|
||||||
*.bs.js
|
|
||||||
*.gen.tsx
|
|
||||||
packages/components/storybook-static
|
|
||||||
node_modules
|
|
||||||
packages/*/node_modules
|
|
||||||
packages/website/.docusaurus
|
|
||||||
packages/squiggle-lang/lib
|
|
||||||
packages/squiggle-lang/coverage/
|
|
||||||
packages/squiggle-lang/.cache/
|
|
||||||
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.
|
|
144
CONTRIBUTING.md
144
CONTRIBUTING.md
|
@ -1,144 +0,0 @@
|
||||||
_The current document was written quickly and not exhaustively, yet, it's unfinished. [Template here](https://mozillascience.github.io/working-open-workshop/contributing/)_
|
|
||||||
|
|
||||||
# Contributing to Squiggle
|
|
||||||
|
|
||||||
We welcome contributions from developers, especially people in react/typescript, rescript, and interpreters/parsers. We also are keen to hear issues filed by users!
|
|
||||||
|
|
||||||
Squiggle is currently pre-alpha.
|
|
||||||
|
|
||||||
# Quick links
|
|
||||||
|
|
||||||
- [Roadmap to the alpha](https://github.com/orgs/quantified-uncertainty/projects/1)
|
|
||||||
- The team presently communicates via the **EA Forecasting and Epistemics** slack (channels `#squiggle` and `#squiggle-ops`), you can track down an invite by reaching out to Ozzie Gooen
|
|
||||||
- [Squiggle documentation](https://www.squiggle-language.com/docs/Language)
|
|
||||||
- [Rescript documentation](https://rescript-lang.org/docs/manual/latest/introduction)
|
|
||||||
- You can email `quinn@quantifieduncertainty.org` if you need assistance in onboarding or if you have questions
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# Project structure
|
|
||||||
|
|
||||||
Squiggle is a **monorepo** with three **packages**.
|
|
||||||
|
|
||||||
- **components** is where we improve reactive interfacing with Squiggle
|
|
||||||
- **squiggle-lang** is where the magic happens: probability distributions, the interpreter, etc.
|
|
||||||
- **website** is the site `squiggle-language.com`
|
|
||||||
|
|
||||||
# Deployment ops
|
|
||||||
|
|
||||||
We use Vercel, and it should only concern Slava, Sam, and Ozzie.
|
|
||||||
|
|
||||||
# Development environment, building, testing, dev server
|
|
||||||
|
|
||||||
You need `yarn`.
|
|
||||||
|
|
||||||
Being a monorepo, where packages are connected by dependency, it's important to follow `README.md`s closely. Each package has it's own `README.md`, which is where the bulk of information is.
|
|
||||||
|
|
||||||
We aspire for `ci.yaml` and `README.md`s to be in one-to-one correspondence.
|
|
||||||
|
|
||||||
## If you're on NixOS
|
|
||||||
|
|
||||||
You can't run `yarn` outside of a FHS shell. Additionally, you need to `patchelf` some things. A script does everything for you.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./nixos.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Reasons for this are comments in the script. Then, you should be able to do all the package-level `yarn run` commands/scripts.
|
|
||||||
|
|
||||||
# Try not to push directly to develop
|
|
||||||
|
|
||||||
If you absolutely must, please prefix your commit message with `hotfix: `.
|
|
||||||
|
|
||||||
# Pull request protocol
|
|
||||||
|
|
||||||
Please work against `develop` branch. **Do not** work against `master`.
|
|
||||||
|
|
||||||
- For rescript code: Slava 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
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
# Code Quality
|
|
||||||
|
|
||||||
- Aim for at least 8/10\* quality in `/packages/squiggle-lang`, and 7/10 quality in `/packages/components`.
|
|
||||||
- If you submit a PR that is under a 7, for some reason, describe the reasoning for this in the PR.
|
|
||||||
|
|
||||||
* This quality score is subjective.
|
|
||||||
|
|
||||||
# Rescript Style
|
|
||||||
|
|
||||||
**Use `->` instead of `|>`**
|
|
||||||
Note: Our codebase used to use `|>`, so there's a lot of that in the system. We'll gradually change it.
|
|
||||||
|
|
||||||
**Use `x -> y -> z` instead of `let foo = y(x); let bar = z(foo)`**
|
|
||||||
|
|
||||||
**Don't use anonymous functions with over three lines**
|
|
||||||
Bad:
|
|
||||||
|
|
||||||
```rescript
|
|
||||||
foo
|
|
||||||
-> E.O.fmap(r => {
|
|
||||||
let a = 34;
|
|
||||||
let b = 35;
|
|
||||||
let c = 48;
|
|
||||||
r + a + b + c
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Good:
|
|
||||||
|
|
||||||
```rescript
|
|
||||||
let addingFn = (r => {
|
|
||||||
let a = 34;
|
|
||||||
let b = 35;
|
|
||||||
let c = 48;
|
|
||||||
r + a + b + c
|
|
||||||
}
|
|
||||||
foo -> addingFn
|
|
||||||
```
|
|
||||||
|
|
||||||
**Write out types for everything, even if there's an interface file**
|
|
||||||
We'll try this for one month (ending May 5, 2022), then revisit.
|
|
||||||
|
|
||||||
**Use the Rescript optional default syntax**
|
|
||||||
Rescript is clever about function inputs. There's custom syntax for default and optional arguments. In the cases where this applies, use it.
|
|
||||||
|
|
||||||
From https://rescript-lang.org/docs/manual/latest/function:
|
|
||||||
|
|
||||||
```rescript
|
|
||||||
// radius can be omitted
|
|
||||||
let drawCircle = (~color, ~radius=?, ()) => {
|
|
||||||
setColor(color)
|
|
||||||
switch radius {
|
|
||||||
| None => startAt(1, 1)
|
|
||||||
| Some(r_) => startAt(r_, r_)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use named arguments**
|
|
||||||
If a function is called externally (in a different file), and has either:
|
|
||||||
|
|
||||||
1. Two arguments of the same type
|
|
||||||
2. Three paramaters or more.
|
|
||||||
|
|
||||||
**Module naming: Use x_y as module names**
|
|
||||||
For example: `Myname_Myproject_Add.res`. Rescript/Ocaml both require files to have unique names, so long names are needed to keep different parts separate from each other.
|
|
||||||
|
|
||||||
See [this page](https://dev.to/yawaramin/a-modular-ocaml-project-structure-1ikd) for more information. (Though note that they use two underscores, and we do one. We might refactor that later.
|
|
||||||
|
|
||||||
**Module naming: Don't rename modules**
|
|
||||||
We have some of this in the Reducer code, but generally discourage it.
|
|
||||||
|
|
||||||
**Use interface files (.resi) for files with very public interfaces**
|
|
||||||
|
|
||||||
### Recommended Rescript resources
|
|
||||||
|
|
||||||
- https://dev.to/yawaramin/a-modular-ocaml-project-structure-1ikd
|
|
||||||
- https://github.com/avohq/reasonml-code-style-guide
|
|
||||||
- https://cs.brown.edu/courses/cs017/content/docs/reasonml-style.pdf
|
|
||||||
- https://github.com/ostera/reason-design-patterns/
|
|
84
README.md
84
README.md
|
@ -1,76 +1,32 @@
|
||||||
# Squiggle
|
# Squiggle
|
||||||
|
|
||||||
[![Packages check](https://github.com/quantified-uncertainty/squiggle/actions/workflows/ci.yml/badge.svg)](https://github.com/quantified-uncertainty/squiggle/actions/workflows/ci.yml)
|
This is an experiment DSL/language for making probabilistic estimates.
|
||||||
[![npm version - lang](https://badge.fury.io/js/@quri%2Fsquiggle-lang.svg)](https://www.npmjs.com/package/@quri/squiggle-lang)
|
|
||||||
[![npm version - components](https://badge.fury.io/js/@quri%2Fsquiggle-components.svg)](https://www.npmjs.com/package/@quri/squiggle-components)
|
|
||||||
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/quantified-uncertainty/squiggle/blob/develop/LICENSE)
|
|
||||||
[![codecov](https://codecov.io/gh/quantified-uncertainty/squiggle/branch/develop/graph/badge.svg?token=QRLBL5CQ7C)](https://codecov.io/gh/quantified-uncertainty/squiggle)
|
|
||||||
|
|
||||||
_An estimation language_.
|
This monorepo has several packages that can be used for various purposes. All
|
||||||
|
|
||||||
## Get started
|
|
||||||
|
|
||||||
- [Gallery](https://www.squiggle-language.com/docs/Discussions/Gallery)
|
|
||||||
- [Squiggle playground](https://squiggle-language.com/playground)
|
|
||||||
- [Language basics](https://www.squiggle-language.com/docs/Guides/Language)
|
|
||||||
- [Squiggle functions source of truth](https://www.squiggle-language.com/docs/Guides/Functions)
|
|
||||||
- [Known bugs](https://www.squiggle-language.com/docs/Discussions/Bugs)
|
|
||||||
- [Original lesswrong sequence](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3)
|
|
||||||
- [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
|
|
||||||
|
|
||||||
- **website/docs prod**: https://squiggle-language.com
|
|
||||||
- **website/docs staging**: https://preview.squiggle-language.com
|
|
||||||
- **components storybook prod**: https://components.squiggle-language.com
|
|
||||||
- **components storybook staging**: https://preview-components.squiggle-language.com
|
|
||||||
- **legacy (2020) playground**: https://playground.squiggle-language.com
|
|
||||||
|
|
||||||
## Packages
|
|
||||||
|
|
||||||
This monorepo has several packages that can be used for various purposes. All
|
|
||||||
the packages can be found in `packages`.
|
the packages can be found in `packages`.
|
||||||
|
|
||||||
- `@quri/squiggle-lang` in `packages/squiggle-lang` contains the core language, particularly
|
`@squiggle/lang` in `packages/squiggle-lang` contains the core language, particularly
|
||||||
an interface to parse squiggle expressions and return descriptions of distributions
|
an interface to parse squiggle expressions and return descriptions of distributions
|
||||||
or results.
|
or results.
|
||||||
- `@quri/squiggle-components` in `packages/components` contains React components that
|
|
||||||
can be passed squiggle strings as props, and return a presentation of the result
|
|
||||||
of the calculation.
|
|
||||||
- `packages/website` is the main descriptive website for squiggle,
|
|
||||||
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).
|
|
||||||
|
|
||||||
# Develop
|
`@squiggle/components` in `packages/components` contains React components that
|
||||||
|
can be passed squiggle strings as props, and return a presentation of the result
|
||||||
|
of the calculation.
|
||||||
|
|
||||||
For any project in the repo, begin by running `yarn` in the top level
|
`@squiggle/playground` in `packages/playground` contains a website for a playground
|
||||||
|
for squiggle. This website is hosted at `playground.squiggle-language.com`
|
||||||
|
|
||||||
```sh
|
`@squiggle/website` in `packages/website` The main descriptive website for squiggle,
|
||||||
yarn
|
it is hosted at `squiggle-language.com`.
|
||||||
```
|
|
||||||
|
|
||||||
Then use `turbo` to build the specific packages or the entire monorepo:
|
The playground depends on the components library which then depends on the language.
|
||||||
|
This means that if you wish to work on the components library, you will need
|
||||||
|
to package the language, and for the playground to work, you will need to package
|
||||||
|
the components library and the playground.
|
||||||
|
|
||||||
```sh
|
Scripts are available for you in the root directory to do important activities,
|
||||||
turbo run build
|
such as:
|
||||||
```
|
|
||||||
|
|
||||||
Or:
|
`yarn build:lang`. Builds and packages the language
|
||||||
|
`yarn storybook:components`. Hosts the component storybook
|
||||||
|
|
||||||
```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
|
|
||||||
|
|
||||||
See `CONTRIBUTING.md`.
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
# The following code was provided by Nuño Sempere, it comes directly from the post https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3/p/j8o6sgRerE3tqNWdj
|
|
||||||
## Initial setup
|
|
||||||
yearly_probability_max = 0.95
|
|
||||||
yearly_probability_min = 0.66
|
|
||||||
period_probability_function(epsilon, yearly_probability) = 1 - (1 - yearly_probability) ^ (1 / epsilon)
|
|
||||||
probability_decayed(t, time_periods, period_probability) = 1 - (1 - period_probability) ^ (time_periods - t)
|
|
||||||
|
|
||||||
## Monthly decomposition
|
|
||||||
months_in_a_year=12
|
|
||||||
|
|
||||||
monthly_probability_min = period_probability_function(months_in_a_year, yearly_probability_min)
|
|
||||||
monthly_probability_max = period_probability_function(months_in_a_year, yearly_probability_max)
|
|
||||||
|
|
||||||
probability_decayed_monthly_min(t) = probability_decayed(t, months_in_a_year, monthly_probability_min)
|
|
||||||
probability_decayed_monthly_max(t) = probability_decayed(t, months_in_a_year, monthly_probability_max)
|
|
||||||
probability_decayed_monthly(t) = probability_decayed_monthly_min(t) to probability_decayed_monthly_max(t)
|
|
||||||
|
|
||||||
probability_decayed_monthly
|
|
||||||
## probability_decayed_monthly(6)
|
|
||||||
## mean(probability_decayed_monthly(6))
|
|
|
@ -1,38 +0,0 @@
|
||||||
# This is a cost effectiveness analysis of givedirectly, originally done by givewell, and translated into Squiggle by Sam Nolan
|
|
||||||
donation_size = 10000
|
|
||||||
proportion_of_funding_available = beta(10, 2)
|
|
||||||
total_funding_available = donation_size * proportion_of_funding_available
|
|
||||||
household_size = 3.7 to 5.7
|
|
||||||
size_of_transfer = 800 to 1200
|
|
||||||
size_of_transfer_per_person = size_of_transfer / household_size
|
|
||||||
|
|
||||||
portion_invested = 0.3 to 0.5
|
|
||||||
amount_invested = portion_invested * size_of_transfer_per_person
|
|
||||||
amount_consumed = (1 - portion_invested) * size_of_transfer_per_person
|
|
||||||
return_on_investment = 0.08 to 0.12
|
|
||||||
increase_in_consumption_from_investments = return_on_investment * amount_invested
|
|
||||||
baseline_consumption = 200 to 350
|
|
||||||
log_increase_in_consumption = log(amount_consumed + baseline_consumption) + log(baseline_consumption)
|
|
||||||
log_increase_in_consumption_from_investment = log(increase_in_consumption_from_investments + baseline_consumption) + log(baseline_consumption)
|
|
||||||
investment_duration = 8 to 12
|
|
||||||
discount_rate = beta(1.004, 20)
|
|
||||||
|
|
||||||
present_value_excluding_last_year = log_increase_in_consumption_from_investment * (1 - (1 + discount_rate) ^ (-investment_duration)) / (log(1 + discount_rate))
|
|
||||||
|
|
||||||
percent_of_investment_returned = 0.15 to 0.25
|
|
||||||
|
|
||||||
pv_consumption_last_year = (log(baseline_consumption + amount_invested * (return_on_investment + percent_of_investment_returned)) - log(baseline_consumption)) / (1 + discount_rate)^investment_duration
|
|
||||||
|
|
||||||
total_pv_of_cash_transfer = pv_consumption_last_year + present_value_excluding_last_year + log_increase_in_consumption
|
|
||||||
|
|
||||||
discount_negative_spoiler = 0.03 to 0.07
|
|
||||||
|
|
||||||
value_discounting_spoiler = discount_negative_spoiler * total_pv_of_cash_transfer
|
|
||||||
|
|
||||||
consumption_increase_per_household = value_discounting_spoiler * household_size
|
|
||||||
|
|
||||||
amount_of_transfers_made = total_funding_available / size_of_transfer
|
|
||||||
|
|
||||||
total_increase_in_ln_consumption = amount_of_transfers_made * consumption_increase_per_household
|
|
||||||
|
|
||||||
total_increase_in_ln_consumption
|
|
|
@ -1,3 +0,0 @@
|
||||||
xY1 = 99
|
|
||||||
aBa3 = xY1 * 2 + 1
|
|
||||||
aBa3 * xY1 + aBa3
|
|
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";
|
|
||||||
};
|
|
||||||
}
|
|
16
package.json
16
package.json
|
@ -2,18 +2,20 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "squiggle",
|
"name": "squiggle",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"nodeclean": "rm -r node_modules && rm -r packages/*/node_modules"
|
"build:lang": "cd packages/squiggle-lang && yarn && yarn build && yarn package",
|
||||||
},
|
"storybook:components": "cd packages/components && yarn && yarn storybook",
|
||||||
"devDependencies": {
|
"build-storybook:components": "cd packages/components && yarn && yarn build-storybook",
|
||||||
"prettier": "^2.7.1",
|
"build:components": "cd packages/components && yarn && yarn package",
|
||||||
"turbo": "^1.5.5"
|
"build:playground": "cd packages/playground && yarn && yarn parcel-build",
|
||||||
|
"ci:lang": "yarn workspace @squiggle/lang ci",
|
||||||
|
"ci:components": "yarn ci:lang && yarn workspace @squiggle/components ci",
|
||||||
|
"ci:playground": "yarn ci:components && yarn workspace @squiggle/playground ci"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "^18.0.1",
|
"@types/react": "17.0.39"
|
||||||
"react": "^18.0.0"
|
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.17"
|
"packageManager": "yarn@1.22.17"
|
||||||
}
|
}
|
||||||
|
|
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,9 +0,0 @@
|
||||||
node_modules
|
|
||||||
storybook-static
|
|
||||||
public
|
|
||||||
build
|
|
||||||
.storybook
|
|
||||||
.direnv
|
|
||||||
.envrc
|
|
||||||
webpack.config.js
|
|
||||||
index.html
|
|
|
@ -1,4 +0,0 @@
|
||||||
dist/
|
|
||||||
storybook-static
|
|
||||||
src/styles/base.css
|
|
||||||
src/styles/forms.css
|
|
|
@ -1 +0,0 @@
|
||||||
{}
|
|
|
@ -1,37 +1,35 @@
|
||||||
//const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
//const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||||
const custom = require("../webpack.config.js");
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
webpackFinal: async (config) => {
|
/* webpackFinal: async (config) => {
|
||||||
config.resolve.alias = custom.resolve.alias;
|
config.resolve.plugins = [
|
||||||
return {
|
...(config.resolve.plugins || []),
|
||||||
...config,
|
new TsconfigPathsPlugin({
|
||||||
module: {
|
extensions: config.resolve.extensions,
|
||||||
...config.module,
|
}),
|
||||||
rules: config.module.rules.concat(
|
];
|
||||||
custom.module.rules.filter((x) => x.loader === "ts-loader")
|
return config;
|
||||||
),
|
},*/
|
||||||
},
|
"stories": [
|
||||||
};
|
"../src/**/*.stories.mdx",
|
||||||
},
|
"../src/**/*.stories.@(js|jsx|ts|tsx)"
|
||||||
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
],
|
||||||
addons: [
|
"addons": [
|
||||||
"@storybook/addon-links",
|
"@storybook/addon-links",
|
||||||
"@storybook/addon-essentials",
|
"@storybook/addon-essentials",
|
||||||
"@storybook/preset-create-react-app",
|
"@storybook/preset-create-react-app"
|
||||||
],
|
],
|
||||||
framework: "@storybook/react",
|
"framework": "@storybook/react",
|
||||||
core: {
|
"core": {
|
||||||
builder: "webpack5",
|
"builder": "webpack5"
|
||||||
},
|
},
|
||||||
typescript: {
|
typescript: {
|
||||||
check: false,
|
check: false,
|
||||||
checkOptions: {},
|
checkOptions: {},
|
||||||
reactDocgen: "react-docgen-typescript",
|
reactDocgen: 'react-docgen-typescript',
|
||||||
reactDocgenTypescriptOptions: {
|
reactDocgenTypescriptOptions: {
|
||||||
shouldExtractLiteralValuesFromEnum: true,
|
shouldExtractLiteralValuesFromEnum: true,
|
||||||
propFilter: (prop) =>
|
propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
|
||||||
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
|
@ -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: {
|
||||||
|
@ -18,4 +6,4 @@ export const parameters = {
|
||||||
date: /Date$/,
|
date: /Date$/,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
|
@ -1,81 +1,6 @@
|
||||||
[![npm version](https://badge.fury.io/js/@quri%2Fsquiggle-components.svg)](https://www.npmjs.com/package/@quri/squiggle-components)
|
# Squiggle Components
|
||||||
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/quantified-uncertainty/squiggle/blob/develop/LICENSE)
|
|
||||||
|
|
||||||
# Squiggle components
|
This package contains all the components for squiggle. These can be used either
|
||||||
|
as a library or hosted as a [storybook](https://storybook.js.org/).
|
||||||
|
|
||||||
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/).
|
To run the storybook, run `yarn` then `yarn storybook`.
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
For example, in a fresh `create-react-app` project
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn add @quri/squiggle-components
|
|
||||||
```
|
|
||||||
|
|
||||||
Add to `App.js`:
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
import { SquiggleEditor } from "@quri/squiggle-components";
|
|
||||||
<SquiggleEditor
|
|
||||||
defaultCode="x = beta($alpha, 10); x + $shift"
|
|
||||||
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
|
|
||||||
|
|
||||||
We assume that you had run `yarn` at monorepo level, installing dependencies.
|
|
||||||
|
|
||||||
You need to _prepare_ by building and bundling `squiggle-lang`
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd ../squiggle-lang
|
|
||||||
yarn build
|
|
||||||
```
|
|
||||||
|
|
||||||
If you've otherwise done this recently you can skip those.
|
|
||||||
|
|
||||||
Run a development server
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn start
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
||||||
module.exports = {
|
|
||||||
preset: "ts-jest",
|
|
||||||
setupFilesAfterEnv: ["<rootDir>/test/setup.js"],
|
|
||||||
testEnvironment: "jsdom",
|
|
||||||
};
|
|
57037
packages/components/package-lock.json
generated
Normal file
57037
packages/components/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,92 +1,35 @@
|
||||||
{
|
{
|
||||||
"name": "@quri/squiggle-components",
|
"name": "@quri/squiggle-components",
|
||||||
"version": "0.5.0",
|
"version": "0.1.5",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react-dom": "^1.0.0",
|
"@quri/squiggle-lang": "0.2.1",
|
||||||
"@floating-ui/react-dom-interactions": "^0.10.1",
|
"@testing-library/jest-dom": "^5.16.2",
|
||||||
"@headlessui/react": "^1.7.3",
|
"@testing-library/react": "^12.1.2",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@hookform/resolvers": "^2.9.8",
|
"@types/jest": "^27.4.0",
|
||||||
"@quri/squiggle-lang": "^0.5.0",
|
"@types/lodash": "^4.14.178",
|
||||||
"@react-hook/size": "^2.1.2",
|
"@types/node": "^17.0.16",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/react": "^17.0.39",
|
||||||
"clsx": "^1.2.1",
|
"@types/react-dom": "^17.0.11",
|
||||||
"framer-motion": "^7.5.3",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"react": "^18.1.0",
|
|
||||||
"react-ace": "^10.1.0",
|
|
||||||
"react-hook-form": "^7.37.0",
|
|
||||||
"react-use": "^17.4.0",
|
|
||||||
"react-vega": "^7.6.0",
|
|
||||||
"uuid": "^9.0.0",
|
|
||||||
"vega": "^5.22.1",
|
|
||||||
"vega-embed": "^6.21.0",
|
|
||||||
"vega-lite": "^5.5.0",
|
|
||||||
"vscode-uri": "^3.0.6",
|
|
||||||
"yup": "^0.32.11"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.18.6",
|
|
||||||
"@storybook/addon-actions": "^6.5.12",
|
|
||||||
"@storybook/addon-essentials": "^6.5.12",
|
|
||||||
"@storybook/addon-links": "^6.5.12",
|
|
||||||
"@storybook/builder-webpack5": "^6.5.12",
|
|
||||||
"@storybook/manager-webpack5": "^6.5.12",
|
|
||||||
"@storybook/node-logger": "^6.5.9",
|
|
||||||
"@storybook/preset-create-react-app": "^4.1.2",
|
|
||||||
"@storybook/react": "^6.5.12",
|
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
|
||||||
"@testing-library/react": "^13.4.0",
|
|
||||||
"@testing-library/user-event": "^14.4.3",
|
|
||||||
"@types/jest": "^27.5.0",
|
|
||||||
"@types/lodash": "^4.14.186",
|
|
||||||
"@types/node": "^18.8.3",
|
|
||||||
"@types/react": "^18.0.21",
|
|
||||||
"@types/styled-components": "^5.1.26",
|
|
||||||
"@types/uuid": "^8.3.4",
|
|
||||||
"@types/webpack": "^5.28.0",
|
|
||||||
"canvas": "^2.10.1",
|
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"jest": "^29.0.3",
|
"lodash": "^4.17.21",
|
||||||
"jest-environment-jsdom": "^29.1.2",
|
"react": "^17.0.2",
|
||||||
"jsdom": "^20.0.1",
|
"react-dom": "^17.0.2",
|
||||||
"mini-css-extract-plugin": "^2.6.1",
|
"react-scripts": "5.0.0",
|
||||||
"postcss-cli": "^10.0.0",
|
"react-vega": "^7.4.4",
|
||||||
"postcss-import": "^15.0.0",
|
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||||
"postcss-loader": "^7.0.1",
|
"typescript": "^4.5.5",
|
||||||
"postcss-nesting": "^10.2.0",
|
"vega": "^5.21.0",
|
||||||
"react": "^18.1.0",
|
"vega-embed": "^6.20.6",
|
||||||
"react-scripts": "^5.0.1",
|
"vega-lite": "^5.2.0",
|
||||||
"style-loader": "^3.3.1",
|
"web-vitals": "^2.1.4",
|
||||||
"tailwindcss": "^3.1.8",
|
"webpack-cli": "^4.9.2"
|
||||||
"ts-jest": "^29.0.3",
|
|
||||||
"ts-loader": "^9.4.1",
|
|
||||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
|
||||||
"typescript": "^4.8.4",
|
|
||||||
"web-vitals": "^3.0.3",
|
|
||||||
"webpack": "^5.74.0",
|
|
||||||
"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",
|
"storybook": "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-storybook": "build-storybook -s public",
|
||||||
"build:css": "postcss ./src/styles/main.css -o ./dist/main.css",
|
"package": "tsc",
|
||||||
"build:storybook": "build-storybook -s public",
|
"ci": "yarn package"
|
||||||
"build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook",
|
|
||||||
"bundle": "webpack",
|
|
||||||
"all": "yarn bundle && yarn build",
|
|
||||||
"lint": "prettier --check .",
|
|
||||||
"format": "prettier --write .",
|
|
||||||
"prepack": "yarn run build:cjs && yarn run bundle",
|
|
||||||
"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": [
|
||||||
|
@ -116,10 +59,27 @@
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@storybook/addon-actions": "^6.4.18",
|
||||||
|
"@storybook/addon-essentials": "^6.4.18",
|
||||||
|
"@storybook/addon-links": "^6.4.18",
|
||||||
|
"@storybook/builder-webpack5": "^6.4.18",
|
||||||
|
"@storybook/manager-webpack5": "^6.4.18",
|
||||||
|
"@storybook/node-logger": "^6.4.18",
|
||||||
|
"@storybook/preset-create-react-app": "^4.0.0",
|
||||||
|
"@storybook/react": "^6.4.18",
|
||||||
|
"@types/webpack": "^5.28.0",
|
||||||
|
"react-ace": "^9.5.0",
|
||||||
|
"react-codejar": "^1.1.2",
|
||||||
|
"ts-loader": "^9.2.8",
|
||||||
|
"webpack": "^5.70.0",
|
||||||
|
"webpack-cli": "^4.9.2"
|
||||||
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "17.0.43"
|
"@types/react": "17.0.39"
|
||||||
},
|
},
|
||||||
"source": "./src/index.ts",
|
"source": "./src/index.ts",
|
||||||
"main": "./dist/src/index.js",
|
"module": "dist/bundle.js",
|
||||||
"types": "./dist/src/index.d.ts"
|
"main": "dist/bundle.js",
|
||||||
|
"types": "dist/index.d.ts"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,10 @@
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description" content="Squiggle components" />
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Squiggle components"
|
||||||
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<title>Squiggle Components</title>
|
<title>Squiggle Components</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
5
packages/components/shell.nix
Normal file
5
packages/components/shell.nix
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
name = "squiggle-components";
|
||||||
|
buildInputs = with pkgs; [ nodePackages.yarn nodejs ];
|
||||||
|
}
|
1
packages/components/src/SquiggleChart.js.map
Normal file
1
packages/components/src/SquiggleChart.js.map
Normal file
File diff suppressed because one or more lines are too long
355
packages/components/src/SquiggleChart.tsx
Normal file
355
packages/components/src/SquiggleChart.tsx
Normal file
|
@ -0,0 +1,355 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import type { Spec } from 'vega';
|
||||||
|
import { run } from '@squiggle/lang';
|
||||||
|
import type { DistPlus, SamplingInputs } from '@squiggle/lang';
|
||||||
|
import { createClassFromSpec } from 'react-vega';
|
||||||
|
import * as chartSpecification from './spec-distributions.json'
|
||||||
|
import * as percentilesSpec from './spec-pertentiles.json'
|
||||||
|
|
||||||
|
let SquiggleVegaChart = createClassFromSpec({'spec': chartSpecification as Spec});
|
||||||
|
|
||||||
|
let SquigglePercentilesChart = createClassFromSpec({'spec': percentilesSpec as Spec});
|
||||||
|
|
||||||
|
export interface SquiggleChartProps {
|
||||||
|
/** The input string for squiggle */
|
||||||
|
squiggleString : 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
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SquiggleChart : React.FC<SquiggleChartProps> = props => {
|
||||||
|
let samplingInputs : SamplingInputs = {
|
||||||
|
sampleCount : props.sampleCount,
|
||||||
|
outputXYPoints : props.outputXYPoints,
|
||||||
|
kernelWidth : props.kernelWidth,
|
||||||
|
pointDistLength : props.pointDistLength
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let result = run(props.squiggleString, samplingInputs);
|
||||||
|
console.log(result)
|
||||||
|
if (result.tag === "Ok") {
|
||||||
|
let chartResults = result.value.map(chartResult => {
|
||||||
|
console.log(chartResult)
|
||||||
|
if(chartResult["NAME"] === "Float"){
|
||||||
|
return <MakeNumberShower precision={3} number={chartResult["VAL"]} />;
|
||||||
|
}
|
||||||
|
else if(chartResult["NAME"] === "DistPlus"){
|
||||||
|
let shape = chartResult.VAL.pointSetDist;
|
||||||
|
if(shape.tag === "Continuous"){
|
||||||
|
let xyShape = shape.value.xyShape;
|
||||||
|
let totalY = xyShape.ys.reduce((a, b) => a + b);
|
||||||
|
let total = 0;
|
||||||
|
let cdf = xyShape.ys.map(y => {
|
||||||
|
total += y;
|
||||||
|
return total / totalY;
|
||||||
|
})
|
||||||
|
let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x, y ]) => ({cdf: (c * 100).toFixed(2) + "%", x: x, y: y}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SquiggleVegaChart
|
||||||
|
data={{"con": values}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if(shape.tag === "Discrete"){
|
||||||
|
let xyShape = shape.value.xyShape;
|
||||||
|
let totalY = xyShape.ys.reduce((a, b) => a + b);
|
||||||
|
let total = 0;
|
||||||
|
let cdf = xyShape.ys.map(y => {
|
||||||
|
total += y;
|
||||||
|
return total / totalY;
|
||||||
|
})
|
||||||
|
let values = _.zip(cdf, xyShape.xs, xyShape.ys).map(([c, x,y]) => ({cdf: (c * 100).toFixed(2) + "%", x: x, y: y}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SquiggleVegaChart
|
||||||
|
data={{"dis": values}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if(shape.tag === "Mixed"){
|
||||||
|
let discreteShape = shape.value.discrete.xyShape;
|
||||||
|
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
let discretePoints = _.zip(discreteShape.xs, discreteShape.ys);
|
||||||
|
let continuousShape = shape.value.continuous.xyShape;
|
||||||
|
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
|
||||||
|
|
||||||
|
interface labeledPoint {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
type: "discrete" | "continuous"
|
||||||
|
};
|
||||||
|
|
||||||
|
let markedDisPoints : labeledPoint[] = discretePoints.map(([x,y]) => ({x: x, y: y, type: "discrete"}))
|
||||||
|
let markedConPoints : labeledPoint[] = continuousPoints.map(([x,y]) => ({x: x, y: y, type: "continuous"}))
|
||||||
|
|
||||||
|
let sortedPoints = _.sortBy(markedDisPoints.concat(markedConPoints), 'x')
|
||||||
|
|
||||||
|
let totalContinuous = 1 - totalDiscrete;
|
||||||
|
let totalY = continuousShape.ys.reduce((a:number, b:number) => a + b);
|
||||||
|
|
||||||
|
let total = 0;
|
||||||
|
let cdf = sortedPoints.map((point: labeledPoint) => {
|
||||||
|
if(point.type == "discrete") {
|
||||||
|
total += point.y;
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
else if (point.type == "continuous") {
|
||||||
|
total += point.y / totalY * totalContinuous;
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface cdfLabeledPoint {
|
||||||
|
cdf: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
type: "discrete" | "continuous"
|
||||||
|
}
|
||||||
|
let cdfLabeledPoint : cdfLabeledPoint[] = _.zipWith(cdf, sortedPoints, (c: number, point: labeledPoint) => ({...point, cdf: (c * 100).toFixed(2) + "%"}))
|
||||||
|
let continuousValues = cdfLabeledPoint.filter(x => x.type == "continuous")
|
||||||
|
let discreteValues = cdfLabeledPoint.filter(x => x.type == "discrete")
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SquiggleVegaChart
|
||||||
|
data={{"con": continuousValues, "dis": discreteValues}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(chartResult.NAME === "Function"){
|
||||||
|
// We are looking at a function. In this case, we draw a Percentiles chart
|
||||||
|
let start = props.diagramStart ? props.diagramStart : 0
|
||||||
|
let stop = props.diagramStop ? props.diagramStop : 10
|
||||||
|
let count = props.diagramCount ? props.diagramCount : 0.1
|
||||||
|
let step = (stop - start)/ count
|
||||||
|
let data = _.range(start, stop, step).map(x => {
|
||||||
|
if(chartResult.NAME=="Function"){
|
||||||
|
let result = chartResult.VAL(x);
|
||||||
|
if(result.tag == "Ok"){
|
||||||
|
let percentileArray = [
|
||||||
|
0.01,
|
||||||
|
0.05,
|
||||||
|
0.1,
|
||||||
|
0.2,
|
||||||
|
0.3,
|
||||||
|
0.4,
|
||||||
|
0.5,
|
||||||
|
0.6,
|
||||||
|
0.7,
|
||||||
|
0.8,
|
||||||
|
0.9,
|
||||||
|
0.95,
|
||||||
|
0.99
|
||||||
|
]
|
||||||
|
|
||||||
|
let percentiles = getPercentiles(percentileArray, result.value);
|
||||||
|
return {
|
||||||
|
"x": x,
|
||||||
|
"p1": percentiles[0],
|
||||||
|
"p5": percentiles[1],
|
||||||
|
"p10": percentiles[2],
|
||||||
|
"p20": percentiles[3],
|
||||||
|
"p30": percentiles[4],
|
||||||
|
"p40": percentiles[5],
|
||||||
|
"p50": percentiles[6],
|
||||||
|
"p60": percentiles[7],
|
||||||
|
"p70": percentiles[8],
|
||||||
|
"p80": percentiles[9],
|
||||||
|
"p90": percentiles[10],
|
||||||
|
"p95": percentiles[11],
|
||||||
|
"p99": percentiles[12]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
})
|
||||||
|
return <SquigglePercentilesChart data={{"facet": data}} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return <>{chartResults}</>;
|
||||||
|
}
|
||||||
|
else if(result.tag == "Error") {
|
||||||
|
// At this point, we came across an error. What was our error?
|
||||||
|
return (<p>{"Error parsing Squiggle: " + result.value}</p>)
|
||||||
|
|
||||||
|
}
|
||||||
|
return (<p>{"Invalid Response"}</p>)
|
||||||
|
};
|
||||||
|
|
||||||
|
function getPercentiles(percentiles:number[], t : DistPlus) {
|
||||||
|
if(t.pointSetDist.tag == "Discrete") {
|
||||||
|
let total = 0;
|
||||||
|
let maxX = _.max(t.pointSetDist.value.xyShape.xs)
|
||||||
|
let bounds = percentiles.map(_ => maxX);
|
||||||
|
_.zipWith(t.pointSetDist.value.xyShape.xs,t.pointSetDist.value.xyShape.ys, (x,y) => {
|
||||||
|
total += y
|
||||||
|
percentiles.forEach((v, i) => {
|
||||||
|
if(total > v && bounds[i] == maxX){
|
||||||
|
bounds[i] = x
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
else if(t.pointSetDist.tag == "Continuous"){
|
||||||
|
let total = 0;
|
||||||
|
let maxX = _.max(t.pointSetDist.value.xyShape.xs)
|
||||||
|
let totalY = _.sum(t.pointSetDist.value.xyShape.ys)
|
||||||
|
let bounds = percentiles.map(_ => maxX);
|
||||||
|
_.zipWith(t.pointSetDist.value.xyShape.xs,t.pointSetDist.value.xyShape.ys, (x,y) => {
|
||||||
|
total += y / totalY;
|
||||||
|
percentiles.forEach((v, i) => {
|
||||||
|
if(total > v && bounds[i] == maxX){
|
||||||
|
bounds[i] = x
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
else if(t.pointSetDist.tag == "Mixed"){
|
||||||
|
let discreteShape = t.pointSetDist.value.discrete.xyShape;
|
||||||
|
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
let discretePoints = _.zip(discreteShape.xs, discreteShape.ys);
|
||||||
|
let continuousShape = t.pointSetDist.value.continuous.xyShape;
|
||||||
|
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
|
||||||
|
|
||||||
|
interface labeledPoint {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
type: "discrete" | "continuous"
|
||||||
|
};
|
||||||
|
|
||||||
|
let markedDisPoints : labeledPoint[] = discretePoints.map(([x,y]) => ({x: x, y: y, type: "discrete"}))
|
||||||
|
let markedConPoints : labeledPoint[] = continuousPoints.map(([x,y]) => ({x: x, y: y, type: "continuous"}))
|
||||||
|
|
||||||
|
let sortedPoints = _.sortBy(markedDisPoints.concat(markedConPoints), 'x')
|
||||||
|
|
||||||
|
let totalContinuous = 1 - totalDiscrete;
|
||||||
|
let totalY = continuousShape.ys.reduce((a:number, b:number) => a + b);
|
||||||
|
|
||||||
|
let total = 0;
|
||||||
|
let maxX = _.max(sortedPoints.map(x => x.x));
|
||||||
|
let bounds = percentiles.map(_ => maxX);
|
||||||
|
sortedPoints.map((point: labeledPoint) => {
|
||||||
|
if(point.type == "discrete") {
|
||||||
|
total += point.y;
|
||||||
|
}
|
||||||
|
else if (point.type == "continuous") {
|
||||||
|
total += point.y / totalY * totalContinuous;
|
||||||
|
}
|
||||||
|
percentiles.forEach((v,i) => {
|
||||||
|
if(total > v && bounds[i] == maxX){
|
||||||
|
bounds[i] = total;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return total;
|
||||||
|
});
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function MakeNumberShower(props: {number: number, precision :number}){
|
||||||
|
let numberWithPresentation = numberShow(props.number, props.precision);
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{numberWithPresentation.value}
|
||||||
|
{numberWithPresentation.symbol}
|
||||||
|
{numberWithPresentation.power ?
|
||||||
|
<span>
|
||||||
|
{'\u00b710'}
|
||||||
|
<span style={{fontSize: "0.6em", verticalAlign: "super"}}>
|
||||||
|
{numberWithPresentation.power}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
: <></>}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderOfMagnitudeNum = (n:number) => {
|
||||||
|
return Math.pow(10, n);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 105 -> 3
|
||||||
|
const orderOfMagnitude = (n:number) => {
|
||||||
|
return Math.floor(Math.log(n) / Math.LN10 + 0.000000001);
|
||||||
|
};
|
||||||
|
|
||||||
|
function withXSigFigs(number:number, sigFigs:number) {
|
||||||
|
const withPrecision = number.toPrecision(sigFigs);
|
||||||
|
const formatted = Number(withPrecision);
|
||||||
|
return `${formatted}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumberShower {
|
||||||
|
number: number
|
||||||
|
precision: number
|
||||||
|
|
||||||
|
constructor(number:number, precision = 2) {
|
||||||
|
this.number = number;
|
||||||
|
this.precision = precision;
|
||||||
|
}
|
||||||
|
|
||||||
|
convert() {
|
||||||
|
const number = Math.abs(this.number);
|
||||||
|
const response = this.evaluate(number);
|
||||||
|
if (this.number < 0) {
|
||||||
|
response.value = '-' + response.value;
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
metricSystem(number: number, order: number) {
|
||||||
|
const newNumber = number / orderOfMagnitudeNum(order);
|
||||||
|
const precision = this.precision;
|
||||||
|
return `${withXSigFigs(newNumber, precision)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate(number: number) {
|
||||||
|
if (number === 0) {
|
||||||
|
return { value: this.metricSystem(0, 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = orderOfMagnitude(number);
|
||||||
|
if (order < -2) {
|
||||||
|
return { value: this.metricSystem(number, order), power: order };
|
||||||
|
} else if (order < 4) {
|
||||||
|
return { value: this.metricSystem(number, 0) };
|
||||||
|
} else if (order < 6) {
|
||||||
|
return { value: this.metricSystem(number, 3), symbol: 'K' };
|
||||||
|
} else if (order < 9) {
|
||||||
|
return { value: this.metricSystem(number, 6), symbol: 'M' };
|
||||||
|
} else if (order < 12) {
|
||||||
|
return { value: this.metricSystem(number, 9), symbol: 'B' };
|
||||||
|
} else if (order < 15) {
|
||||||
|
return { value: this.metricSystem(number, 12), symbol: 'T' };
|
||||||
|
} else {
|
||||||
|
return { value: this.metricSystem(number, order), power: order };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function numberShow(number: number, precision = 2) {
|
||||||
|
const ns = new NumberShower(number, precision);
|
||||||
|
return ns.convert();
|
||||||
|
}
|
|
@ -1,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,76 +0,0 @@
|
||||||
import _ from "lodash";
|
|
||||||
import React, { FC, useMemo, useRef } from "react";
|
|
||||||
import AceEditor from "react-ace";
|
|
||||||
|
|
||||||
import "ace-builds/src-noconflict/mode-golang";
|
|
||||||
import "ace-builds/src-noconflict/theme-github";
|
|
||||||
|
|
||||||
import { SqLocation } from "@quri/squiggle-lang";
|
|
||||||
|
|
||||||
interface CodeEditorProps {
|
|
||||||
value: string;
|
|
||||||
onChange: (value: string) => void;
|
|
||||||
onSubmit?: () => void;
|
|
||||||
oneLine?: boolean;
|
|
||||||
width?: number;
|
|
||||||
height: number;
|
|
||||||
showGutter?: boolean;
|
|
||||||
errorLocations?: SqLocation[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CodeEditor: FC<CodeEditorProps> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
onSubmit,
|
|
||||||
height,
|
|
||||||
oneLine = false,
|
|
||||||
showGutter = false,
|
|
||||||
errorLocations = [],
|
|
||||||
}) => {
|
|
||||||
const lineCount = value.split("\n").length;
|
|
||||||
const id = useMemo(() => _.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 (
|
|
||||||
<AceEditor
|
|
||||||
ref={editorEl}
|
|
||||||
value={value}
|
|
||||||
mode="golang"
|
|
||||||
theme="github"
|
|
||||||
width="100%"
|
|
||||||
fontSize={14}
|
|
||||||
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={{}}
|
|
||||||
commands={[
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,222 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
SqDistribution,
|
|
||||||
result,
|
|
||||||
SqDistributionError,
|
|
||||||
resultMap,
|
|
||||||
SqRecord,
|
|
||||||
environment,
|
|
||||||
SqDistributionTag,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { Vega } from "react-vega";
|
|
||||||
import { ErrorAlert } from "./Alert";
|
|
||||||
import { useSize } from "react-use";
|
|
||||||
|
|
||||||
import {
|
|
||||||
buildVegaSpec,
|
|
||||||
DistributionChartSpecOptions,
|
|
||||||
} from "../lib/distributionSpecBuilder";
|
|
||||||
import { NumberShower } from "./NumberShower";
|
|
||||||
import { Plot, parsePlot } from "../lib/plotParser";
|
|
||||||
import { flattenResult } from "../lib/utility";
|
|
||||||
import { hasMassBelowZero } from "../lib/distributionUtils";
|
|
||||||
|
|
||||||
export type DistributionPlottingSettings = {
|
|
||||||
/** Whether to show a summary of means, stdev, percentiles etc */
|
|
||||||
showSummary: boolean;
|
|
||||||
actions?: boolean;
|
|
||||||
} & DistributionChartSpecOptions;
|
|
||||||
|
|
||||||
export type DistributionChartProps = {
|
|
||||||
plot: Plot;
|
|
||||||
environment: environment;
|
|
||||||
width?: number;
|
|
||||||
height: number;
|
|
||||||
xAxisType?: "number" | "dateTime";
|
|
||||||
} & DistributionPlottingSettings;
|
|
||||||
|
|
||||||
export function defaultPlot(distribution: SqDistribution): Plot {
|
|
||||||
return { distributions: [{ name: "default", 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,
|
|
||||||
showSummary,
|
|
||||||
width,
|
|
||||||
logX,
|
|
||||||
actions = false,
|
|
||||||
} = props;
|
|
||||||
const [sized] = useSize((size) => {
|
|
||||||
const shapes = flattenResult(
|
|
||||||
plot.distributions.map((x) =>
|
|
||||||
resultMap(x.distribution.pointSet(environment), (pointSet) => ({
|
|
||||||
name: x.name,
|
|
||||||
// color: x.color, // not supported yet
|
|
||||||
...pointSet.asShape(),
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shapes.tag === "Error") {
|
|
||||||
return (
|
|
||||||
<ErrorAlert heading="Distribution Error">
|
|
||||||
{shapes.value.toString()}
|
|
||||||
</ErrorAlert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this is a sample set, include the samples
|
|
||||||
const samples: number[] = [];
|
|
||||||
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
|
|
||||||
spec={spec}
|
|
||||||
data={vegaData}
|
|
||||||
width={widthProp - 10}
|
|
||||||
height={height}
|
|
||||||
actions={actions}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="flex justify-center">
|
|
||||||
{showSummary && plot.distributions.length === 1 && (
|
|
||||||
<SummaryTable
|
|
||||||
distribution={plot.distributions[0].distribution}
|
|
||||||
environment={environment}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return sized;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
|
||||||
children,
|
|
||||||
}) => (
|
|
||||||
<th className="border border-slate-200 bg-slate-50 py-1 px-2 text-slate-500 font-semibold">
|
|
||||||
{children}
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<table className="border border-collapse border-slate-400">
|
|
||||||
<thead className="bg-slate-50">
|
|
||||||
<tr>
|
|
||||||
<TableHeadCell>{"Mean"}</TableHeadCell>
|
|
||||||
{hasResult(stdev) && <TableHeadCell>{"Stdev"}</TableHeadCell>}
|
|
||||||
<TableHeadCell>{"5%"}</TableHeadCell>
|
|
||||||
<TableHeadCell>{"10%"}</TableHeadCell>
|
|
||||||
<TableHeadCell>{"25%"}</TableHeadCell>
|
|
||||||
<TableHeadCell>{"50%"}</TableHeadCell>
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,80 +0,0 @@
|
||||||
import type { LogScale, LinearScale, PowScale } from "vega";
|
|
||||||
export let linearXScale: LinearScale = {
|
|
||||||
name: "xscale",
|
|
||||||
type: "linear",
|
|
||||||
range: "width",
|
|
||||||
zero: false,
|
|
||||||
nice: false,
|
|
||||||
domain: {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
data: "con",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: "dis",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export let linearYScale: LinearScale = {
|
|
||||||
name: "yscale",
|
|
||||||
type: "linear",
|
|
||||||
range: "height",
|
|
||||||
zero: true,
|
|
||||||
domain: {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
data: "con",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: "dis",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export let logXScale: LogScale = {
|
|
||||||
name: "xscale",
|
|
||||||
type: "log",
|
|
||||||
range: "width",
|
|
||||||
zero: false,
|
|
||||||
base: 10,
|
|
||||||
nice: false,
|
|
||||||
domain: {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
data: "con",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: "dis",
|
|
||||||
field: "x",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export let expYScale: PowScale = {
|
|
||||||
name: "yscale",
|
|
||||||
type: "pow",
|
|
||||||
exponent: 0.1,
|
|
||||||
range: "height",
|
|
||||||
zero: true,
|
|
||||||
nice: false,
|
|
||||||
domain: {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
data: "con",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: "dis",
|
|
||||||
field: "y",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,107 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
SqLambda,
|
|
||||||
environment,
|
|
||||||
SqValueTag,
|
|
||||||
SqError,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { FunctionChart1Dist } from "./FunctionChart1Dist";
|
|
||||||
import { FunctionChart1Number } from "./FunctionChart1Number";
|
|
||||||
import { DistributionPlottingSettings } from "./DistributionChart";
|
|
||||||
import { MessageAlert } from "./Alert";
|
|
||||||
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
|
||||||
|
|
||||||
export type FunctionChartSettings = {
|
|
||||||
start: number;
|
|
||||||
stop: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface FunctionChartProps {
|
|
||||||
fn: SqLambda;
|
|
||||||
chartSettings: FunctionChartSettings;
|
|
||||||
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 {
|
|
||||||
return result1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const validResult = getValidResult();
|
|
||||||
|
|
||||||
if (validResult.tag === "Error") {
|
|
||||||
return <FunctionCallErrorAlert error={validResult.value} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (validResult.value.tag) {
|
|
||||||
case SqValueTag.Distribution:
|
|
||||||
return (
|
|
||||||
<FunctionChart1Dist
|
|
||||||
fn={fn}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment}
|
|
||||||
height={height}
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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,95 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
|
|
||||||
const orderOfMagnitudeNum = (n: number) => {
|
|
||||||
return Math.pow(10, n);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 105 -> 3
|
|
||||||
const orderOfMagnitude = (n: number) => {
|
|
||||||
return Math.floor(Math.log(n) / Math.LN10 + 0.000000001);
|
|
||||||
};
|
|
||||||
|
|
||||||
function withXSigFigs(number: number, sigFigs: number) {
|
|
||||||
const withPrecision = number.toPrecision(sigFigs);
|
|
||||||
const formatted = Number(withPrecision);
|
|
||||||
return `${formatted}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
class NumberShowerBuilder {
|
|
||||||
number: number;
|
|
||||||
precision: number;
|
|
||||||
|
|
||||||
constructor(number: number, precision = 2) {
|
|
||||||
this.number = number;
|
|
||||||
this.precision = precision;
|
|
||||||
}
|
|
||||||
|
|
||||||
convert() {
|
|
||||||
const number = Math.abs(this.number);
|
|
||||||
const response = this.evaluate(number);
|
|
||||||
if (this.number < 0) {
|
|
||||||
response.value = "-" + response.value;
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
metricSystem(number: number, order: number) {
|
|
||||||
const newNumber = number / orderOfMagnitudeNum(order);
|
|
||||||
const precision = this.precision;
|
|
||||||
return `${withXSigFigs(newNumber, precision)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluate(number: number) {
|
|
||||||
if (number === 0) {
|
|
||||||
return { value: this.metricSystem(0, 0) };
|
|
||||||
}
|
|
||||||
|
|
||||||
const order = orderOfMagnitude(number);
|
|
||||||
if (order < -2) {
|
|
||||||
return { value: this.metricSystem(number, order), power: order };
|
|
||||||
} else if (order < 4) {
|
|
||||||
return { value: this.metricSystem(number, 0) };
|
|
||||||
} else if (order < 6) {
|
|
||||||
return { value: this.metricSystem(number, 3), symbol: "K" };
|
|
||||||
} else if (order < 9) {
|
|
||||||
return { value: this.metricSystem(number, 6), symbol: "M" };
|
|
||||||
} else if (order < 12) {
|
|
||||||
return { value: this.metricSystem(number, 9), symbol: "B" };
|
|
||||||
} else if (order < 15) {
|
|
||||||
return { value: this.metricSystem(number, 12), symbol: "T" };
|
|
||||||
} else {
|
|
||||||
return { value: this.metricSystem(number, order), power: order };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberShow(number: number, precision = 2) {
|
|
||||||
const ns = new NumberShowerBuilder(number, precision);
|
|
||||||
return ns.convert();
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NumberShowerProps {
|
|
||||||
number: number;
|
|
||||||
precision?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NumberShower: React.FC<NumberShowerProps> = ({
|
|
||||||
number,
|
|
||||||
precision = 2,
|
|
||||||
}) => {
|
|
||||||
const numberWithPresentation = numberShow(number, precision);
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
{numberWithPresentation.value}
|
|
||||||
{numberWithPresentation.symbol}
|
|
||||||
{numberWithPresentation.power ? (
|
|
||||||
<span>
|
|
||||||
{"\u00b7" /* dot symbol */}10
|
|
||||||
<span style={{ fontSize: "0.6em", verticalAlign: "super" }}>
|
|
||||||
{numberWithPresentation.power}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,158 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
SqValue,
|
|
||||||
environment,
|
|
||||||
SqProject,
|
|
||||||
defaultEnvironment,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { useSquiggle } from "../lib/hooks";
|
|
||||||
import { SquiggleViewer } from "./SquiggleViewer";
|
|
||||||
import { JsImports } from "../lib/jsImports";
|
|
||||||
import { getValueToRender } from "../lib/utility";
|
|
||||||
|
|
||||||
export type SquiggleChartProps = {
|
|
||||||
/** The input string for squiggle */
|
|
||||||
code: string;
|
|
||||||
/** Allows to re-run the code if code hasn't changed */
|
|
||||||
executionId?: number;
|
|
||||||
/** If the output requires monte carlo sampling, the amount of samples */
|
|
||||||
sampleCount?: number;
|
|
||||||
/** If the result is a function, where the function domain starts */
|
|
||||||
diagramStart?: number;
|
|
||||||
/** If the result is a function, where the function domain ends */
|
|
||||||
diagramStop?: number;
|
|
||||||
/** If the result is a function, the amount of stops sampled */
|
|
||||||
diagramCount?: number;
|
|
||||||
/** When the squiggle code gets reevaluated */
|
|
||||||
onChange?(expr: SqValue | undefined, sourceName: string): void;
|
|
||||||
/** CSS width of the element */
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
/** JS imported parameters */
|
|
||||||
jsImports?: JsImports;
|
|
||||||
/** Whether to show a summary of the distribution */
|
|
||||||
showSummary?: boolean;
|
|
||||||
/** Set the x scale to be logarithmic by deault */
|
|
||||||
logX?: 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
|
|
||||||
type StandaloneExecutionProps = {
|
|
||||||
project?: undefined;
|
|
||||||
continues?: undefined;
|
|
||||||
/** The amount of points returned to draw the distribution, not needed if using a project */
|
|
||||||
environment?: environment;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Props needed when executing inside a project.
|
|
||||||
type ProjectExecutionProps = {
|
|
||||||
environment?: undefined;
|
|
||||||
/** The project that this execution is part of */
|
|
||||||
project: SqProject;
|
|
||||||
/** What other squiggle sources from the project to continue. Default [] */
|
|
||||||
continues?: string[];
|
|
||||||
};
|
|
||||||
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,
|
|
||||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
|
||||||
executionId = 0,
|
|
||||||
width,
|
|
||||||
height = 200,
|
|
||||||
enableLocalSettings = false,
|
|
||||||
continues,
|
|
||||||
project,
|
|
||||||
environment,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const resultAndBindings = useSquiggle({
|
|
||||||
environment,
|
|
||||||
continues,
|
|
||||||
project,
|
|
||||||
code,
|
|
||||||
jsImports,
|
|
||||||
onChange,
|
|
||||||
executionId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const valueToRender = getValueToRender(resultAndBindings);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SquiggleViewer
|
|
||||||
result={valueToRender}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={
|
|
||||||
project ? project.getEnvironment() : environment ?? defaultEnvironment
|
|
||||||
}
|
|
||||||
enableLocalSettings={enableLocalSettings}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
|
@ -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 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { CodeEditor } from "./CodeEditor";
|
|
||||||
import { SquiggleContainer } from "./SquiggleContainer";
|
|
||||||
import {
|
|
||||||
splitSquiggleChartSettings,
|
|
||||||
SquiggleChartProps,
|
|
||||||
} from "./SquiggleChart";
|
|
||||||
import { useMaybeControlledValue, useSquiggle } from "../lib/hooks";
|
|
||||||
import { JsImports } from "../lib/jsImports";
|
|
||||||
import { defaultEnvironment, SqLocation, SqProject } from "@quri/squiggle-lang";
|
|
||||||
import { SquiggleViewer } from "./SquiggleViewer";
|
|
||||||
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
|
||||||
|
|
||||||
const WrappedCodeEditor: React.FC<{
|
|
||||||
code: string;
|
|
||||||
setCode: (code: string) => void;
|
|
||||||
errorLocations?: SqLocation[];
|
|
||||||
}> = ({ code, setCode, errorLocations }) => (
|
|
||||||
<div className="border border-grey-200 p-2 m-4" data-testid="squiggle-editor">
|
|
||||||
<CodeEditor
|
|
||||||
value={code}
|
|
||||||
onChange={setCode}
|
|
||||||
oneLine={true}
|
|
||||||
showGutter={false}
|
|
||||||
height={20}
|
|
||||||
errorLocations={errorLocations}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export type SquiggleEditorProps = SquiggleChartProps & {
|
|
||||||
defaultCode?: string;
|
|
||||||
onCodeChange?: (code: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultOnChange = () => {};
|
|
||||||
const defaultImports: JsImports = {};
|
|
||||||
|
|
||||||
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
|
||||||
const [code, setCode] = useMaybeControlledValue({
|
|
||||||
value: props.code,
|
|
||||||
defaultValue: props.defaultCode ?? "",
|
|
||||||
onChange: props.onCodeChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { distributionPlotSettings, chartSettings } =
|
|
||||||
splitSquiggleChartSettings(props);
|
|
||||||
|
|
||||||
const {
|
|
||||||
environment,
|
|
||||||
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,
|
|
||||||
executionId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const valueToRender = getValueToRender(resultAndBindings);
|
|
||||||
const errorLocations = getErrorLocations(resultAndBindings.result);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SquiggleContainer>
|
|
||||||
<WrappedCodeEditor
|
|
||||||
code={code}
|
|
||||||
setCode={setCode}
|
|
||||||
errorLocations={errorLocations}
|
|
||||||
/>
|
|
||||||
<SquiggleViewer
|
|
||||||
result={valueToRender}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment ?? defaultEnvironment}
|
|
||||||
enableLocalSettings={enableLocalSettings}
|
|
||||||
/>
|
|
||||||
</SquiggleContainer>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -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 +0,0 @@
|
||||||
import React, {
|
|
||||||
FC,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
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";
|
|
||||||
|
|
||||||
import { SquiggleChartProps } from "./SquiggleChart";
|
|
||||||
import { CodeEditor } from "./CodeEditor";
|
|
||||||
import { JsonEditor } from "./JsonEditor";
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-3 max-w-3xl">
|
|
||||||
<HeadedSection title="Import Variables from JSON">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Text>
|
|
||||||
You can import variables from JSON into your Squiggle code.
|
|
||||||
Variables are accessed with dollar signs. For example, "timeNow"
|
|
||||||
would be accessed as "$timeNow".
|
|
||||||
</Text>
|
|
||||||
<div className="border border-slate-200 mt-6 mb-2">
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
|
|
||||||
const errorLocations = getErrorLocations(resultAndBindings.result);
|
|
||||||
|
|
||||||
const firstTab = vars.showEditor ? (
|
|
||||||
<div className="border border-slate-200" data-testid="squiggle-editor">
|
|
||||||
<CodeEditor
|
|
||||||
errorLocations={errorLocations}
|
|
||||||
value={code}
|
|
||||||
onChange={setCode}
|
|
||||||
onSubmit={run}
|
|
||||||
oneLine={false}
|
|
||||||
showGutter={true}
|
|
||||||
height={height - 1}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
squiggleChart
|
|
||||||
);
|
|
||||||
|
|
||||||
const tabs = (
|
|
||||||
<StyledTab.Panels>
|
|
||||||
<StyledTab.Panel>{firstTab}</StyledTab.Panel>
|
|
||||||
<StyledTab.Panel>
|
|
||||||
<SamplingSettings register={register} />
|
|
||||||
</StyledTab.Panel>
|
|
||||||
<StyledTab.Panel>
|
|
||||||
<ViewSettings
|
|
||||||
register={
|
|
||||||
// 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>
|
|
||||||
<StyledTab.Panel>
|
|
||||||
<InputVariablesSettings
|
|
||||||
initialImports={imports}
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -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 @@
|
||||||
export { SqProject } from "@quri/squiggle-lang/";
|
export { SquiggleChart } from './SquiggleChart';
|
||||||
export { SquiggleChart } from "./components/SquiggleChart";
|
|
||||||
export { SquiggleEditor } from "./components/SquiggleEditor";
|
|
||||||
export { SquigglePlayground } from "./components/SquigglePlayground";
|
|
||||||
export { SquiggleContainer } from "./components/SquiggleContainer";
|
|
||||||
|
|
|
@ -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 [];
|
|
||||||
}
|
|
||||||
}
|
|
122
packages/components/src/spec-distributions.json
Normal file
122
packages/components/src/spec-distributions.json
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
||||||
|
"description": "A basic area chart example.",
|
||||||
|
"width": 500,
|
||||||
|
"height": 200,
|
||||||
|
"padding": 5,
|
||||||
|
"data": [{"name": "con"}, {"name": "dis"}],
|
||||||
|
|
||||||
|
"signals": [
|
||||||
|
{
|
||||||
|
"name": "mousex",
|
||||||
|
"description": "x position of mouse",
|
||||||
|
"update": "0",
|
||||||
|
"on": [{"events": "mousemove", "update": "1-x()/width"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "xscale",
|
||||||
|
"description": "The transform of the x scale",
|
||||||
|
"value": 1.0,
|
||||||
|
"bind": {
|
||||||
|
"input": "range",
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "yscale",
|
||||||
|
"description": "The transform of the y scale",
|
||||||
|
"value": 1.0,
|
||||||
|
"bind": {
|
||||||
|
"input": "range",
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"scales": [{
|
||||||
|
"name": "xscale",
|
||||||
|
"type": "pow",
|
||||||
|
"exponent": {"signal": "xscale"},
|
||||||
|
"range": "width",
|
||||||
|
"zero": false,
|
||||||
|
"nice": false,
|
||||||
|
"domain": {
|
||||||
|
"fields": [
|
||||||
|
{ "data": "con", "field": "x"},
|
||||||
|
{ "data": "dis", "field": "x"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "yscale",
|
||||||
|
"type": "pow",
|
||||||
|
"exponent": {"signal": "yscale"},
|
||||||
|
"range": "height",
|
||||||
|
"nice": true,
|
||||||
|
"zero": true,
|
||||||
|
"domain": {
|
||||||
|
"fields": [
|
||||||
|
{ "data": "con", "field": "y"},
|
||||||
|
{ "data": "dis", "field": "y"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"axes": [
|
||||||
|
{"orient": "bottom", "scale": "xscale", "tickCount": 20},
|
||||||
|
{"orient": "left", "scale": "yscale"}
|
||||||
|
],
|
||||||
|
|
||||||
|
"marks": [
|
||||||
|
{
|
||||||
|
"type": "area",
|
||||||
|
"from": {"data": "con"},
|
||||||
|
"encode": {
|
||||||
|
"enter": {
|
||||||
|
"tooltip": {"signal": "datum.cdf"}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"x": {"scale": "xscale", "field": "x"},
|
||||||
|
"y": {"scale": "yscale", "field": "y"},
|
||||||
|
"y2": {"scale": "yscale", "value": 0},
|
||||||
|
"fill": {
|
||||||
|
"signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'blue'}, {offset: 1.0, color: 'blue'} ] }"
|
||||||
|
},
|
||||||
|
"interpolate": {"value": "monotone"},
|
||||||
|
"fillOpacity": {"value": 1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rect",
|
||||||
|
"from": {"data": "dis"},
|
||||||
|
"encode": {
|
||||||
|
"enter": {
|
||||||
|
"y2": {"scale": "yscale", "value": 0},
|
||||||
|
"width": {"value": 1}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"x": {"scale": "xscale", "field": "x"},
|
||||||
|
"y": {"scale": "yscale", "field": "y"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "symbol",
|
||||||
|
"from": {"data": "dis"},
|
||||||
|
"encode": {
|
||||||
|
"enter": {
|
||||||
|
"shape": {"value": "circle"},
|
||||||
|
"width": {"value": 5},
|
||||||
|
"tooltip": {"signal": "datum.y"}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"x": {"scale": "xscale", "field": "x"},
|
||||||
|
"y": {"scale": "yscale", "field": "y"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
208
packages/components/src/spec-pertentiles.json
Normal file
208
packages/components/src/spec-pertentiles.json
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
||||||
|
"width": 500,
|
||||||
|
"height": 400,
|
||||||
|
"padding": 5,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "facet",
|
||||||
|
"values": [],
|
||||||
|
"format": { "type": "json", "parse": { "timestamp": "date" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "table",
|
||||||
|
"source": "facet",
|
||||||
|
"transform": [
|
||||||
|
{
|
||||||
|
"type": "aggregate",
|
||||||
|
"groupby": ["x"],
|
||||||
|
"ops": [
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean",
|
||||||
|
"mean"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
"p1",
|
||||||
|
"p5",
|
||||||
|
"p10",
|
||||||
|
"p20",
|
||||||
|
"p30",
|
||||||
|
"p40",
|
||||||
|
"p50",
|
||||||
|
"p60",
|
||||||
|
"p70",
|
||||||
|
"p80",
|
||||||
|
"p90",
|
||||||
|
"p95",
|
||||||
|
"p99"
|
||||||
|
],
|
||||||
|
"as": [
|
||||||
|
"p1",
|
||||||
|
"p5",
|
||||||
|
"p10",
|
||||||
|
"p20",
|
||||||
|
"p30",
|
||||||
|
"p40",
|
||||||
|
"p50",
|
||||||
|
"p60",
|
||||||
|
"p70",
|
||||||
|
"p80",
|
||||||
|
"p90",
|
||||||
|
"p95",
|
||||||
|
"p99"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scales": [
|
||||||
|
{
|
||||||
|
"name": "xscale",
|
||||||
|
"type": "linear",
|
||||||
|
"nice": true,
|
||||||
|
"domain": { "data": "facet", "field": "x" },
|
||||||
|
"range": "width"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "yscale",
|
||||||
|
"type": "linear",
|
||||||
|
"range": "height",
|
||||||
|
"nice": true,
|
||||||
|
"zero": true,
|
||||||
|
"domain": { "data": "facet", "field": "p99" }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"axes": [
|
||||||
|
{
|
||||||
|
"orient": "bottom",
|
||||||
|
"scale": "xscale",
|
||||||
|
"grid": false,
|
||||||
|
"tickSize": 2,
|
||||||
|
"encode": {
|
||||||
|
"grid": { "enter": { "stroke": { "value": "#ccc" } } },
|
||||||
|
"ticks": { "enter": { "stroke": { "value": "#ccc" } } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orient": "left",
|
||||||
|
"scale": "yscale",
|
||||||
|
"grid": false,
|
||||||
|
"domain": false,
|
||||||
|
"tickSize": 2,
|
||||||
|
"encode": {
|
||||||
|
"grid": { "enter": { "stroke": { "value": "#ccc" } } },
|
||||||
|
"ticks": { "enter": { "stroke": { "value": "#ccc" } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"marks": [
|
||||||
|
{
|
||||||
|
"type": "area",
|
||||||
|
"from": { "data": "table" },
|
||||||
|
"encode": {
|
||||||
|
"enter": { "fill": { "value": "#4C78A8" } },
|
||||||
|
"update": {
|
||||||
|
"interpolate": { "value": "monotone" },
|
||||||
|
"x": { "scale": "xscale", "field": "x" },
|
||||||
|
"y": { "scale": "yscale", "field": "p1" },
|
||||||
|
"y2": { "scale": "yscale", "field": "p99" },
|
||||||
|
"opacity": { "value": 0.05 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "area",
|
||||||
|
"from": { "data": "table" },
|
||||||
|
"encode": {
|
||||||
|
"enter": { "fill": { "value": "#4C78A8" } },
|
||||||
|
"update": {
|
||||||
|
"interpolate": { "value": "monotone" },
|
||||||
|
"x": { "scale": "xscale", "field": "x" },
|
||||||
|
"y": { "scale": "yscale", "field": "p5" },
|
||||||
|
"y2": { "scale": "yscale", "field": "p95" },
|
||||||
|
"opacity": { "value": 0.1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "area",
|
||||||
|
"from": { "data": "table" },
|
||||||
|
"encode": {
|
||||||
|
"enter": { "fill": { "value": "#4C78A8" } },
|
||||||
|
"update": {
|
||||||
|
"interpolate": { "value": "monotone" },
|
||||||
|
"x": { "scale": "xscale", "field": "x" },
|
||||||
|
"y": { "scale": "yscale", "field": "p10" },
|
||||||
|
"y2": { "scale": "yscale", "field": "p90" },
|
||||||
|
"opacity": { "value": 0.15 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "area",
|
||||||
|
"from": { "data": "table" },
|
||||||
|
"encode": {
|
||||||
|
"enter": { "fill": { "value": "#4C78A8" } },
|
||||||
|
"update": {
|
||||||
|
"interpolate": { "value": "monotone" },
|
||||||
|
"x": { "scale": "xscale", "field": "x" },
|
||||||
|
"y": { "scale": "yscale", "field": "p20" },
|
||||||
|
"y2": { "scale": "yscale", "field": "p80" },
|
||||||
|
"opacity": { "value": 0.2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "area",
|
||||||
|
"from": { "data": "table" },
|
||||||
|
"encode": {
|
||||||
|
"enter": { "fill": { "value": "#4C78A8" } },
|
||||||
|
"update": {
|
||||||
|
"interpolate": { "value": "monotone" },
|
||||||
|
"x": { "scale": "xscale", "field": "x" },
|
||||||
|
"y": { "scale": "yscale", "field": "p30" },
|
||||||
|
"y2": { "scale": "yscale", "field": "p70" },
|
||||||
|
"opacity": { "value": 0.2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "area",
|
||||||
|
"from": { "data": "table" },
|
||||||
|
"encode": {
|
||||||
|
"enter": { "fill": { "value": "#4C78A8" } },
|
||||||
|
"update": {
|
||||||
|
"interpolate": { "value": "monotone" },
|
||||||
|
"x": { "scale": "xscale", "field": "x" },
|
||||||
|
"y": { "scale": "yscale", "field": "p40" },
|
||||||
|
"y2": { "scale": "yscale", "field": "p60" },
|
||||||
|
"opacity": { "value": 0.2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "line",
|
||||||
|
"from": { "data": "table" },
|
||||||
|
"encode": {
|
||||||
|
"update": {
|
||||||
|
"interpolate": { "value": "monotone" },
|
||||||
|
"stroke": { "value": "#4C78A8" },
|
||||||
|
"strokeWidth": { "value": 2 },
|
||||||
|
"opacity": { "value": 0.8 },
|
||||||
|
"x": { "scale": "xscale", "field": "x" },
|
||||||
|
"y": { "scale": "yscale", "field": "p50" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
import { Meta } from "@storybook/addon-docs";
|
import { Meta } from '@storybook/addon-docs';
|
||||||
|
|
||||||
<Meta title="Squiggle/Introduction" />
|
<Meta title="Squiggle/Introduction" />
|
||||||
|
|
||||||
This is the component library for Squiggle. These are React
|
This is the component library for Squiggle. All of these components are react
|
||||||
components, and can be used in any application that you see fit.
|
components, and can be used in any application that you see fit.
|
||||||
|
|
||||||
|
Currently, the only component that is provided is the SquiggleChart component.
|
||||||
|
This component allows you to render the result of a squiggle expression.
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { NumberShower } from "../components/NumberShower";
|
|
||||||
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
|
||||||
|
|
||||||
<Meta title="Squiggle/NumberShower" component={NumberShower} />
|
|
||||||
|
|
||||||
# Number Shower
|
|
||||||
|
|
||||||
The number shower is a simple component to display a number.
|
|
||||||
|
|
||||||
It uses the symbols "K", "M", "B", and "T", to represent thousands, millions, billions, and trillions. Outside of that range, it uses scientific notation.
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Ten Thousand"
|
|
||||||
args={{
|
|
||||||
number: 10000,
|
|
||||||
precision: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(args) => <NumberShower {...args} />}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Ten Billion"
|
|
||||||
args={{
|
|
||||||
number: 10000000000,
|
|
||||||
precision: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(args) => <NumberShower {...args} />}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="1.2*10^15"
|
|
||||||
args={{
|
|
||||||
number: 1200000000000000,
|
|
||||||
precision: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(args) => <NumberShower {...args} />}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="1.35*10^-13"
|
|
||||||
args={{
|
|
||||||
number: 0.000000000000135,
|
|
||||||
precision: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(args) => <NumberShower {...args} />}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
<Props of={NumberShower} />
|
|
|
@ -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"}
|
|
@ -1,14 +1,9 @@
|
||||||
import { SquiggleChart } from "../components/SquiggleChart";
|
import { SquiggleChart } from '../SquiggleChart'
|
||||||
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
import { Canvas, Meta, Story, Props } from '@storybook/addon-docs';
|
||||||
|
|
||||||
<Meta title="Squiggle/SquiggleChart" component={SquiggleChart} />
|
<Meta title="Squiggle/SquiggleChart" component={ SquiggleChart } />
|
||||||
|
|
||||||
export const Template = (props) => <SquiggleChart {...props} />;
|
export const Template = SquiggleChart
|
||||||
/*
|
|
||||||
We have to hardcode a width here, because otherwise some interaction with
|
|
||||||
Storybook creates an infinite loop with the internal width
|
|
||||||
*/
|
|
||||||
const width = 600;
|
|
||||||
|
|
||||||
# Squiggle Chart
|
# Squiggle Chart
|
||||||
|
|
||||||
|
@ -23,230 +18,63 @@ could be continuous, discrete or mixed.
|
||||||
|
|
||||||
## Distributions
|
## Distributions
|
||||||
|
|
||||||
### Continuous Distributions (Symbolic)
|
An example of a normal distribution is:
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Continuous Symbolic"
|
name="Normal"
|
||||||
args={{
|
args={{
|
||||||
code: "normal(5,2)",
|
squiggleString: "normal(5,2)"
|
||||||
width,
|
}}>
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
### Continuous Distributions (PointSet)
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Continuous Pointset"
|
|
||||||
args={{
|
|
||||||
code: "PointSet.fromDist(normal(5,2))",
|
|
||||||
width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
### Continuous Distributions (SampleSet)
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Continuous SampleSet"
|
|
||||||
args={{
|
|
||||||
code: "SampleSet.fromDist(normal(5,2))",
|
|
||||||
width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
### Discrete Distributions
|
|
||||||
|
|
||||||
|
An example of a Discrete distribution is:
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<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: "mm(0, 1, [0.5, 0.5])"
|
||||||
width,
|
}}>
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
### Date Distribution
|
An example of a Mixed distribution is:
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Date Distribution"
|
|
||||||
args={{
|
|
||||||
code: "mx(1661819770311, 1661829770311, 1661839770311)",
|
|
||||||
width,
|
|
||||||
xAxisType: "dateTime",
|
|
||||||
width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
## Mixed distributions
|
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<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: "mm(0, 5 to 10, [0.5, 0.5])"
|
||||||
width,
|
}}>
|
||||||
}}
|
|
||||||
>
|
|
||||||
{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,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
## Constants
|
## Constants
|
||||||
|
|
||||||
A constant is a simple number as a result. This has special formatting rules
|
A constant is a simple number as a result. This has special formatting rules
|
||||||
to allow large and small numbers being printed cleanly.
|
to allow large and small numbers being printed cleanly.
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Constant"
|
name="Constant"
|
||||||
args={{
|
args={{
|
||||||
code: "500000000",
|
squiggleString: "500000 * 5000000"
|
||||||
width,
|
}}>
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
## Arrays
|
## Functions
|
||||||
|
Finally, a function can be returned, and this shows how the distribution changes
|
||||||
|
over the axis between x = 0 and 10.
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Array"
|
name="Function"
|
||||||
args={{
|
args={{
|
||||||
code: "[normal(5,2), normal(10,1), normal(40,2), 400000]",
|
squiggleString: "f(x) = normal(x,x)\nf"
|
||||||
width,
|
}}>
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
## Errors
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Error"
|
|
||||||
args={{
|
|
||||||
code: "f(x) = normal(",
|
|
||||||
width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
## Booleans
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Boolean"
|
|
||||||
args={{
|
|
||||||
code: "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,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
## Records
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Record"
|
|
||||||
args={{
|
|
||||||
code: "{foo: 35 to 50, bar: [1,2,3]}",
|
|
||||||
width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
## Strings
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="String"
|
|
||||||
args={{
|
|
||||||
code: '"Lucky day!"',
|
|
||||||
width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { SquiggleEditor } from "../components/SquiggleEditor";
|
|
||||||
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
|
||||||
|
|
||||||
<Meta title="Squiggle/SquiggleEditor" component={SquiggleEditor} />
|
|
||||||
|
|
||||||
export const Template = (props) => <SquiggleEditor {...props} />;
|
|
||||||
|
|
||||||
# Squiggle Editor
|
|
||||||
|
|
||||||
Squiggle Editor is a Squiggle chart with a text editor included for changing
|
|
||||||
the distribution.
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Normal"
|
|
||||||
args={{
|
|
||||||
defaultCode: "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({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
You can also name variables like so:
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Variables"
|
|
||||||
args={{
|
|
||||||
defaultCode: "x = 2\nnormal(x,2)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { SquigglePlayground } from "../components/SquigglePlayground";
|
|
||||||
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
|
||||||
|
|
||||||
<Meta title="Squiggle/SquigglePlayground" component={SquigglePlayground} />
|
|
||||||
|
|
||||||
export const Template = (props) => <SquigglePlayground {...props} />;
|
|
||||||
|
|
||||||
# Squiggle Playground
|
|
||||||
|
|
||||||
A Squiggle playground is an environment where you can play around with all settings,
|
|
||||||
including sampling settings, in squiggle.
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Normal"
|
|
||||||
args={{
|
|
||||||
defaultCode: "normal(5,2)",
|
|
||||||
height: 800,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="With share button"
|
|
||||||
args={{
|
|
||||||
defaultCode: "normal(5,2)",
|
|
||||||
height: 800,
|
|
||||||
showShareButton: true,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
|
||||||
"description": "A basic area chart example",
|
|
||||||
"width": 500,
|
|
||||||
"height": 100,
|
|
||||||
"padding": 5,
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"name": "con"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dis"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"signals": [],
|
|
||||||
"scales": [],
|
|
||||||
"axes": [
|
|
||||||
{
|
|
||||||
"orient": "bottom",
|
|
||||||
"scale": "xscale",
|
|
||||||
"labelColor": "#727d93",
|
|
||||||
"tickColor": "#fff",
|
|
||||||
"tickOpacity": 0.0,
|
|
||||||
"domainColor": "#fff",
|
|
||||||
"domainOpacity": 0.0,
|
|
||||||
"format": ".9~s",
|
|
||||||
"tickCount": 10
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"marks": [
|
|
||||||
{
|
|
||||||
"type": "area",
|
|
||||||
"from": {
|
|
||||||
"data": "con"
|
|
||||||
},
|
|
||||||
"interpolate": "linear",
|
|
||||||
"encode": {
|
|
||||||
"update": {
|
|
||||||
"x": {
|
|
||||||
"scale": "xscale",
|
|
||||||
"field": "x"
|
|
||||||
},
|
|
||||||
"y": {
|
|
||||||
"scale": "yscale",
|
|
||||||
"field": "y"
|
|
||||||
},
|
|
||||||
"y2": {
|
|
||||||
"scale": "yscale",
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
"fill": {
|
|
||||||
"value": "#739ECC"
|
|
||||||
},
|
|
||||||
"interpolate": {
|
|
||||||
"value": "linear"
|
|
||||||
},
|
|
||||||
"fillOpacity": {
|
|
||||||
"value": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "rect",
|
|
||||||
"from": {
|
|
||||||
"data": "dis"
|
|
||||||
},
|
|
||||||
"encode": {
|
|
||||||
"enter": {
|
|
||||||
"width": {
|
|
||||||
"value": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"update": {
|
|
||||||
"x": {
|
|
||||||
"scale": "xscale",
|
|
||||||
"field": "x"
|
|
||||||
},
|
|
||||||
"y": {
|
|
||||||
"scale": "yscale",
|
|
||||||
"field": "y"
|
|
||||||
},
|
|
||||||
"y2": {
|
|
||||||
"scale": "yscale",
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
"fill": {
|
|
||||||
"value": "#2f65a7"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "symbol",
|
|
||||||
"from": {
|
|
||||||
"data": "dis"
|
|
||||||
},
|
|
||||||
"encode": {
|
|
||||||
"enter": {
|
|
||||||
"shape": {
|
|
||||||
"value": "circle"
|
|
||||||
},
|
|
||||||
"size": [{ "value": 100 }],
|
|
||||||
"tooltip": {
|
|
||||||
"signal": "datum.y"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"update": {
|
|
||||||
"x": {
|
|
||||||
"scale": "xscale",
|
|
||||||
"field": "x"
|
|
||||||
},
|
|
||||||
"y": {
|
|
||||||
"scale": "yscale",
|
|
||||||
"field": "y"
|
|
||||||
},
|
|
||||||
"fill": {
|
|
||||||
"value": "#1e4577"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user