├── .github
└── workflows
│ ├── chromatic.yml
│ └── deploy-docs.yml
├── .gitignore
├── .npmrc
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── docs
├── .gitignore
├── .vitepress
│ ├── config.mts
│ └── theme
│ │ ├── CustomLayout.vue
│ │ ├── index.ts
│ │ └── tailwind.postcss
├── LICENSE
├── Sandbox.tsx
├── api-reference
│ ├── assets
│ │ └── api-camera-ready.png
│ ├── index.md
│ ├── marks
│ │ ├── circle.md
│ │ ├── ellipse.md
│ │ ├── image.md
│ │ ├── path.md
│ │ ├── rect.md
│ │ └── text.md
│ ├── relations
│ │ ├── align.md
│ │ ├── arrow.md
│ │ ├── background.md
│ │ ├── distribute.md
│ │ ├── group.md
│ │ ├── line.md
│ │ └── stack.md
│ └── special
│ │ ├── bluefish.md
│ │ ├── layout.md
│ │ ├── ref.md
│ │ └── withBluefish.md
├── design-principles.md
├── examples
│ ├── Examples.vue
│ ├── ExamplesItem.vue
│ ├── VPLink.vue
│ └── index.md
├── index.md
├── learn
│ ├── assets
│ │ ├── bordered-mercury-label.png
│ │ ├── circle-stack-with-background.png
│ │ ├── circle-stack.png
│ │ ├── hard-to-read-arrow.png
│ │ ├── label-arrow.png
│ │ ├── label-under-planet.png
│ │ ├── mercury-label-outside-background.png
│ │ ├── mercury-label.png
│ │ └── overlaid-circles.png
│ ├── get-started.md
│ ├── tutorial-1-intro.md
│ ├── tutorial-2-relations.md
│ ├── tutorial-3-framework.md
│ ├── tutorial-4-layouts.md
│ └── what-is-bluefish.md
├── package.json
├── public
│ ├── bluefish-logo.png
│ ├── docs-teaser.png
│ └── planets.png
├── sandboxUtil.ts
├── tailwind.config.js
└── tsconfig.json
├── package.json
├── packages
├── bluefish-js
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── App.template.ts
│ │ ├── index.css
│ │ └── index.html
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ └── vite.config.ts
└── bluefish-solid
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── .storybook
│ ├── main.ts
│ └── preview.ts
│ ├── .vscode
│ └── settings.json
│ ├── CONTRIBUTING.md
│ ├── LICENSE
│ ├── README.md
│ ├── examples
│ ├── continuity.tsx
│ ├── datasets
│ │ └── cars.ts
│ └── topology
│ │ ├── image.tsx
│ │ ├── neighborhood.tsx
│ │ └── space.tsx
│ ├── experiments
│ ├── plot.ts
│ ├── stratified.ts
│ └── theoretical.ts
│ ├── package.json
│ ├── public
│ ├── App.template.tsx
│ ├── index.css
│ ├── index.html
│ └── index.tsx
│ ├── src
│ ├── align.tsx
│ ├── arrow.tsx
│ ├── background.tsx
│ ├── blob.tsx
│ ├── bluefish.tsx
│ ├── chemistry
│ │ ├── atom.tsx
│ │ ├── bond.tsx
│ │ └── molecule.tsx
│ ├── circle.tsx
│ ├── createName.tsx
│ ├── distribute.tsx
│ ├── ellipse.tsx
│ ├── errorContext.ts
│ ├── errors.ts
│ ├── example-gallery
│ │ ├── DFSCQ-log-figure.tsx
│ │ ├── brownie.tsx
│ │ ├── insertion-sort-hyperscript.ts
│ │ ├── insertion-sort.tsx
│ │ ├── ohm-parser.tsx
│ │ ├── path.tsx
│ │ ├── pulley.tsx
│ │ ├── qc-text.tsx
│ │ └── topology.tsx
│ ├── gradient.tsx
│ ├── graphLayered.tsx
│ ├── group.tsx
│ ├── image.tsx
│ ├── index.ts
│ ├── layout.tsx
│ ├── layoutFunction.tsx
│ ├── line.tsx
│ ├── measure-text.tsx
│ ├── path.tsx
│ ├── performance-testing
│ │ ├── README.md
│ │ ├── insertionSort
│ │ │ ├── InsertionSortTest.tsx
│ │ │ ├── generate_insertion_sort.py
│ │ │ └── insertionSortProps.ts
│ │ ├── ohmParser
│ │ │ ├── generate_ohm_expression.py
│ │ │ ├── ohmParserTest.tsx
│ │ │ └── ohmProps.ts
│ │ └── pythonTutor
│ │ │ ├── generate_python_tutor.py
│ │ │ ├── pythonTutorProgram.py
│ │ │ ├── pythonTutorProps.ts
│ │ │ └── pythonTutorTest.tsx
│ ├── plot
│ │ ├── dot.tsx
│ │ └── plot.tsx
│ ├── python-tutor
│ │ ├── elm-tuple.tsx
│ │ ├── global-frame.tsx
│ │ ├── heap-object.tsx
│ │ ├── heap.tsx
│ │ ├── python-tutor.tsx
│ │ ├── stack-slot.tsx
│ │ └── types.tsx
│ ├── rect.tsx
│ ├── ref.tsx
│ ├── scenegraph.ts
│ ├── stack.tsx
│ ├── stackLayout.ts
│ ├── stackh.tsx
│ ├── stackv.tsx
│ ├── stories
│ │ ├── 4.stories.tsx
│ │ ├── 41.stories.tsx
│ │ ├── 44.stories.tsx
│ │ ├── 50.stories.tsx
│ │ ├── 6.stories.tsx
│ │ ├── CarsPlot.stories.tsx
│ │ ├── IdInference.stories.tsx
│ │ ├── JetpackCompose.stories.tsx
│ │ ├── Math3ma.stories.tsx
│ │ ├── Molecule.stories.tsx
│ │ ├── Planets.stories.tsx
│ │ ├── PythonTutor.stories.tsx
│ │ ├── README.md
│ │ ├── SimpleTree.stories.tsx
│ │ ├── ThreePointTopologies.stories.tsx
│ │ ├── UISTExamples.stories.tsx
│ │ ├── VennDiagram.stories.tsx
│ │ └── components
│ │ │ ├── Align.stories.tsx
│ │ │ ├── Arrow.stories.tsx
│ │ │ ├── Background.stories.tsx
│ │ │ ├── Circle.stories.tsx
│ │ │ ├── Distribute.stories.tsx
│ │ │ ├── Ellipse.stories.tsx
│ │ │ ├── GraphLayered.stories.tsx
│ │ │ ├── Image.stories.tsx
│ │ │ ├── Line.stories.tsx
│ │ │ ├── Rect.stories.tsx
│ │ │ ├── Stack.stories.tsx
│ │ │ ├── StackH.stories.tsx
│ │ │ └── StackV.stories.tsx
│ ├── style.ts
│ ├── text.tsx
│ ├── text
│ │ ├── README.md
│ │ ├── getStringDims.ts
│ │ ├── reduce-css-calc.d.ts
│ │ ├── splitAtDelimiters.ts
│ │ ├── textBBox.tsx
│ │ ├── types.ts
│ │ └── useText.tsx
│ ├── util
│ │ ├── bbox.ts
│ │ ├── lca.ts
│ │ └── maybe.ts
│ └── withBluefish.tsx
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── turbo.json
/.github/workflows/chromatic.yml:
--------------------------------------------------------------------------------
1 | name: "Chromatic"
2 |
3 | on:
4 | push:
5 | paths:
6 | - "packages/**"
7 | branches:
8 | - main
9 | pull_request:
10 | paths:
11 | - "packages/**"
12 | types: [opened, synchronize, reopened]
13 |
14 | jobs:
15 | chromatic-deployment:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout repository
19 | uses: actions/checkout@v3
20 | with:
21 | fetch-depth: 0
22 | - name: Setup pnpm
23 | uses: pnpm/action-setup@v4
24 | with:
25 | version: 8.15.6
26 | - name: Install dependencies
27 | run: pnpm install
28 | - name: Publish to Chromatic
29 | uses: chromaui/action@v1
30 | with:
31 | projectToken: chpt_8295300597e1f7e
32 | workingDir: packages/bluefish-solid
33 | exitZeroOnChanges: true
34 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-docs.yml:
--------------------------------------------------------------------------------
1 | # Sample workflow for building and deploying a VitePress site to GitHub Pages
2 | name: Deploy VitePress site to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the `main` branch. Change this to `master` if you're
6 | # using the `master` branch as the default branch.
7 | push:
8 | paths:
9 | - "docs/**"
10 | branches: [main]
11 |
12 | # Allows you to run this workflow manually from the Actions tab
13 | workflow_dispatch:
14 |
15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
16 | permissions:
17 | contents: read
18 | pages: write
19 | id-token: write
20 |
21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
23 | concurrency:
24 | group: pages
25 | cancel-in-progress: false
26 |
27 | jobs:
28 | # Build job
29 | build:
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 | with:
35 | fetch-depth: 0 # Not needed if lastUpdated is not enabled
36 | - uses: pnpm/action-setup@v3
37 | - name: Setup Node
38 | uses: actions/setup-node@v4
39 | with:
40 | node-version: 20
41 | cache: pnpm # or pnpm / yarn
42 | - name: Setup Pages
43 | uses: actions/configure-pages@v4
44 | - name: Install dependencies
45 | run: pnpm install
46 | - name: Build with VitePress
47 | run: pnpm turbo docs:build
48 | - name: Upload artifact
49 | uses: actions/upload-pages-artifact@v3
50 | with:
51 | path: docs/.vitepress/dist
52 |
53 | # Deployment job
54 | deploy:
55 | environment:
56 | name: github-pages
57 | url: ${{ steps.deployment.outputs.page_url }}
58 | needs: build
59 | runs-on: ubuntu-latest
60 | name: Deploy
61 | steps:
62 | - name: Deploy to GitHub Pages
63 | id: deployment
64 | uses: actions/deploy-pages@v4
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # Dependencies
4 | node_modules
5 | .pnp
6 | .pnp.js
7 |
8 | # Local env files
9 | .env
10 | .env.local
11 | .env.development.local
12 | .env.test.local
13 | .env.production.local
14 |
15 | # Testing
16 | coverage
17 |
18 | # Turbo
19 | .turbo
20 |
21 | # Vercel
22 | .vercel
23 |
24 | # Build Outputs
25 | .next/
26 | out/
27 | build
28 | dist
29 |
30 |
31 | # Debug
32 | npm-debug.log*
33 | yarn-debug.log*
34 | yarn-error.log*
35 |
36 | # Misc
37 | .DS_Store
38 | *.pem
39 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/.npmrc
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.workingDirectories": [
3 | {
4 | "mode": "auto"
5 | }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Josh Pollock
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Quickstart Dev Guide
2 |
3 | After cloning this repository, please follow these steps to get started on development with Bluefish.
4 |
5 | 1. Run the following command to globally install Turbo:
6 |
7 | ```sh
8 | npm install turbo --global
9 | ```
10 |
11 | _For more information on how to do this, please reference the [Turbo docs](https://turbo.build/repo/docs/getting-started/installation#global-installation)_
12 |
13 | 2. Run `pnpm install` from the root directory to install the necessary node modules.
14 | 3. Navigate to `packages/bluefish-solid/public` and duplicate the `App.template.tsx` file. Rename the copy to `App.tsx`. This `App.tsx` file serves as a playground for the `bluefish-solid` package.
15 | 4. Navigate to `packages/bluefish-js/public` and duplicate the `App.template.ts` file. Rename the copy to `App.ts`. This `App.ts` file serves as a playground for the `bluefish-js` package.
16 | 5. From the root of this repository, run `turbo build`. The `bluefish-js` package depends on the `bluefish-solid` package--so the latter must be built in order for the former to run properly.
17 | 6. Once the packages have been built, you can run `turbo dev`, and it will render in your browser whatever is in the `App.ts(x)` files.
18 | - If you just want to run one of the two packages, run `pnpm dev` from either the `bluefish-js` or `bluefish-solid` folders.
19 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.vitepress/cache
3 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.mts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitepress";
2 | import container from "markdown-it-container";
3 | import { renderSandbox } from "vitepress-plugin-sandpack";
4 |
5 | // https://vitepress.dev/reference/site-config
6 | export default defineConfig({
7 | title: "Bluefish",
8 | description: "A diagramming library for the web",
9 | // base: "/bluefish-docs/",
10 | themeConfig: {
11 | externalLinkIcon: true,
12 | logo: "/bluefish-logo.png",
13 | // https://vitepress.dev/reference/default-theme-config
14 | nav: [
15 | { text: "Home", link: "/" },
16 | { text: "Get Started", link: "/learn/get-started" },
17 | { text: "Learn", link: "/learn/tutorial-1-intro" },
18 | { text: "Examples", link: "/examples" },
19 | // { text: "API Reference", link: "/api-reference" },
20 | { text: "Playground", link: "https://playground.solidjs.com/anonymous/ed046d3a-261f-4902-842c-b22220f25c1e" },
21 | ],
22 |
23 | search: {
24 | provider: "local",
25 | },
26 |
27 | sidebar: {
28 | "/learn": [
29 | { text: "Get Started", link: "/learn/get-started" },
30 | { text: "What Is Bluefish?", link: "/learn/what-is-bluefish" },
31 | {
32 | text: "Tutorial",
33 | items: [
34 | { text: "1. The Basics", link: "/learn/tutorial-1-intro" },
35 | { text: "2. The Power of Relations", link: "/learn/tutorial-2-relations" },
36 | // { text: "3. Data-Driven Diagrams", link: "/learn/tutorial-3-framework" },
37 | ],
38 | },
39 | ],
40 | "/api-reference": [
41 | {
42 | text: "API Reference",
43 | items: [
44 | { text: "Overview", link: "/api-reference/index" },
45 | {
46 | text: "Marks",
47 | collapsed: true,
48 | items: [
49 | { text: "Circle", link: "/api-reference/marks/circle" },
50 | { text: "Ellipse", link: "/api-reference/marks/ellipse" },
51 | { text: "Image", link: "/api-reference/marks/image" },
52 | { text: "Path", link: "/api-reference/marks/path" },
53 | { text: "Rect", link: "/api-reference/marks/rect" },
54 | { text: "Text", link: "/api-reference/marks/text" },
55 | ],
56 | },
57 | {
58 | text: "Relations",
59 | collapsed: true,
60 | items: [
61 | { text: "Align", link: "/api-reference/relations/align" },
62 | { text: "Arrow", link: "/api-reference/relations/arrow" },
63 | { text: "Background", link: "/api-reference/relations/background" },
64 | { text: "Distribute", link: "/api-reference/relations/distribute" },
65 | { text: "Group", link: "/api-reference/relations/group" },
66 | { text: "Line", link: "/api-reference/relations/line" },
67 | { text: "Stack", link: "/api-reference/relations/stack" },
68 | ],
69 | },
70 | {
71 | text: "Special",
72 | collapsed: true,
73 | items: [
74 | { text: "Bluefish", link: "/api-reference/special/bluefish" },
75 | { text: "Ref", link: "/api-reference/special/ref" },
76 | { text: "withBluefish", link: "/api-reference/special/withBluefish" },
77 | { text: "Layout", link: "/api-reference/special/layout" },
78 | ],
79 | },
80 | ],
81 | },
82 | ],
83 | },
84 |
85 | socialLinks: [
86 | { icon: "github", link: "https://github.com/bluefishjs/bluefish" },
87 | { icon: "discord", link: "https://discord.gg/RTKDYBBybB" },
88 | ],
89 | },
90 | markdown: {
91 | lineNumbers: true,
92 | config(md) {
93 | md
94 | // the second parameter is html tag name
95 | .use(container, "sandbox", {
96 | render(tokens, idx) {
97 | return renderSandbox(tokens, idx, "sandbox");
98 | },
99 | });
100 | },
101 | },
102 | });
103 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
1 | import DefaultTheme from "vitepress/theme";
2 | import { Sandbox } from "../../Sandbox";
3 | // re-enable for some other styling options for the sandbox
4 | // import "vitepress-plugin-sandpack/dist/style.css";
5 | import "./tailwind.postcss";
6 | import { h } from "vue";
7 | import CustomLayout from "./CustomLayout.vue";
8 |
9 | const codeOptions = encodeURIComponent(
10 | JSON.stringify({
11 | "App.tsx": `import { Component } from "solid-js";
12 |
13 | const App: Component = () => {
14 | return
Ahoyyyyyyyyyyy!
15 | };
16 |
17 | export default App;`,
18 | })
19 | );
20 |
21 | export default {
22 | ...DefaultTheme,
23 | enhanceApp(ctx) {
24 | DefaultTheme.enhanceApp(ctx);
25 | +ctx.app.component("Sandbox", Sandbox);
26 | },
27 | Layout: CustomLayout,
28 | };
29 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/tailwind.postcss:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | @tailwind components;
4 |
5 | @tailwind utilities;
6 |
--------------------------------------------------------------------------------
/docs/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Josh Pollock
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/Sandbox.tsx:
--------------------------------------------------------------------------------
1 | import { h, defineComponent, ref, watch, onBeforeMount } from "vue";
2 | // import { Sandbox as DefaultSandbox, sandboxProps } from "vitepress-plugin-sandpack";
3 | import { Sandpack, SandpackProvider, SandpackCodeEditor, SandpackPreview, type SandpackFiles } from "sandpack-vue3";
4 | import { getSandpackFiles } from "./sandboxUtil";
5 |
6 | export const Sandbox = defineComponent({
7 | name: "Sandbox",
8 | props: {
9 | codeOptions: {
10 | type: String,
11 | required: true,
12 | },
13 | },
14 | setup(props, { slots }) {
15 | const files = ref({});
16 |
17 | const resolveFiles = async () => {
18 | files.value = await getSandpackFiles(props, slots);
19 | };
20 |
21 | watch(props, resolveFiles);
22 |
23 | onBeforeMount(resolveFiles);
24 |
25 | return () => {
26 | return (
27 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | };
42 | },
43 | });
44 |
--------------------------------------------------------------------------------
/docs/api-reference/assets/api-camera-ready.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/api-reference/assets/api-camera-ready.png
--------------------------------------------------------------------------------
/docs/api-reference/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | :::warning
6 |
7 | This page is under construction.
8 |
9 | :::
10 |
11 | Bluefish has three kinds of primitives: marks, relations, and special.
12 |
13 | 
14 |
15 | # Marks
16 |
17 | A **mark** is a basic visual element. Bluefish's marks are mostly thin wrappers around SVG
18 | primitives. However, unlike in SVG, in Bluefish a mark's position and size are often omitted,
19 | because they are determined by relations instead.
20 |
21 | - [`Circle`](/api-reference/marks/circle)
22 | - [`Ellipse`](/api-reference/marks/ellipse)
23 | - [`Image`](/api-reference/marks/image)
24 | - [`Path`](/api-reference/marks/path)
25 | - [`Rect`](/api-reference/marks/rect)
26 | - [`Text`](/api-reference/marks/text)
27 |
28 | # Relations
29 |
30 | A **relation** is a visual arrangement of elements. Bluefish's relations are inspired by Gestalt
31 | relations including uniform density, alignment, common region, and connectedness.
32 |
33 | - [`Align`](/api-reference/relations/align)
34 | - [`Arrow`](/api-reference/relations/arrow)
35 | - [`Background`](/api-reference/relations/background)
36 | - [`Distribute`](/api-reference/relations/distribute)
37 | - [`Group`](/api-reference/relations/group)
38 | - [`Line`](/api-reference/relations/line)
39 | - [`Stack`](/api-reference/relations/stack)
40 |
41 | # Special
42 |
43 | Bluefish provides additional primitives for overlapping relations, composing new marks and
44 | relations, and creating new mark and relation primitives.
45 |
46 | - [`Bluefish`](/api-reference/special/bluefish)
47 | - [`Ref`](/api-reference/special/ref)
48 | - [`withBluefish`](/api-reference/special/withBluefish)
49 | - [`Layout`](/api-reference/special/layout)
50 |
51 |
72 |
--------------------------------------------------------------------------------
/docs/api-reference/marks/circle.md:
--------------------------------------------------------------------------------
1 | # Circle
2 |
--------------------------------------------------------------------------------
/docs/api-reference/marks/ellipse.md:
--------------------------------------------------------------------------------
1 | # Ellipse
2 |
--------------------------------------------------------------------------------
/docs/api-reference/marks/image.md:
--------------------------------------------------------------------------------
1 | # Image
2 |
--------------------------------------------------------------------------------
/docs/api-reference/marks/path.md:
--------------------------------------------------------------------------------
1 | # Path
2 |
--------------------------------------------------------------------------------
/docs/api-reference/marks/rect.md:
--------------------------------------------------------------------------------
1 | # Rect
2 |
--------------------------------------------------------------------------------
/docs/api-reference/marks/text.md:
--------------------------------------------------------------------------------
1 | # Text
2 |
--------------------------------------------------------------------------------
/docs/api-reference/relations/align.md:
--------------------------------------------------------------------------------
1 | # Align
2 |
--------------------------------------------------------------------------------
/docs/api-reference/relations/arrow.md:
--------------------------------------------------------------------------------
1 | # Arrow
2 |
--------------------------------------------------------------------------------
/docs/api-reference/relations/background.md:
--------------------------------------------------------------------------------
1 | # Background
2 |
--------------------------------------------------------------------------------
/docs/api-reference/relations/distribute.md:
--------------------------------------------------------------------------------
1 | # Distribute
2 |
--------------------------------------------------------------------------------
/docs/api-reference/relations/group.md:
--------------------------------------------------------------------------------
1 | # Group
2 |
--------------------------------------------------------------------------------
/docs/api-reference/relations/line.md:
--------------------------------------------------------------------------------
1 | # Line
2 |
--------------------------------------------------------------------------------
/docs/api-reference/relations/stack.md:
--------------------------------------------------------------------------------
1 | # Stack
2 |
--------------------------------------------------------------------------------
/docs/api-reference/special/bluefish.md:
--------------------------------------------------------------------------------
1 | # Bluefish
2 |
--------------------------------------------------------------------------------
/docs/api-reference/special/layout.md:
--------------------------------------------------------------------------------
1 | # Layout
2 |
--------------------------------------------------------------------------------
/docs/api-reference/special/ref.md:
--------------------------------------------------------------------------------
1 | # Ref
2 |
3 | ## createName
4 |
--------------------------------------------------------------------------------
/docs/api-reference/special/withBluefish.md:
--------------------------------------------------------------------------------
1 | # withBluefish
2 |
--------------------------------------------------------------------------------
/docs/design-principles.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Bluefish Design Principles
6 |
--------------------------------------------------------------------------------
/docs/examples/Examples.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
33 |
34 |
35 |
74 |
--------------------------------------------------------------------------------
/docs/examples/VPLink.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/docs/examples/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 |
57 |
58 | Take a look at our examples! They're all based on real diagrams.
59 |
60 | - [Baking Recipe](https://playground.solidjs.com/anonymous/8f733a7c-2ed8-4f5f-b98e-f17eb2cf2b13)
61 | - [Pulleys](https://playground.solidjs.com/anonymous/9bdf7c8f-260c-4e6d-bc96-67383a5c6647)
62 | - [Quantum Circuit Equivalence](https://playground.solidjs.com/anonymous/57f3baf1-6af4-4126-9159-d050b88523c3)
63 |
64 |
65 |
66 |
74 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "Bluefish"
7 | text: "The missing diagramming framework"
8 | tagline: "Because your diagrams are worth it"
9 | # image: "/planets.png"
10 | image: "/bluefish-logo.png"
11 | # image: https://github.com/bluefishjs/bluefish/assets/21694516/063a0056-3386-430d-a18b-cfbecf500c0b
12 | # image: /docs-teaser.png
13 | actions:
14 | - theme: brand
15 | text: Get started
16 | link: /learn/get-started
17 | - theme: alt
18 | text: What is Bluefish?
19 | link: /learn/what-is-bluefish
20 | # - theme: alt
21 | # text: Examples
22 | # link: /examples/index.md
23 | features:
24 | - title: "Compose with Relations"
25 | details: Build complex diagrams from simpler building blocks like alignment, spacing, and arrows
26 | - title: "Build Reactive Diagrams"
27 | details: Created interactive and animated diagrams with popular reactive UI primitives
28 | - title: "Use Powerful Custom Layouts"
29 | details: Bluefish provides graph and arrow layouts out of the box, with the ability to add your own
30 | ---
31 |
32 |
33 |
34 | 
35 |
36 |
95 |
--------------------------------------------------------------------------------
/docs/learn/assets/bordered-mercury-label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/learn/assets/bordered-mercury-label.png
--------------------------------------------------------------------------------
/docs/learn/assets/circle-stack-with-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/learn/assets/circle-stack-with-background.png
--------------------------------------------------------------------------------
/docs/learn/assets/circle-stack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/learn/assets/circle-stack.png
--------------------------------------------------------------------------------
/docs/learn/assets/hard-to-read-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/learn/assets/hard-to-read-arrow.png
--------------------------------------------------------------------------------
/docs/learn/assets/label-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/learn/assets/label-arrow.png
--------------------------------------------------------------------------------
/docs/learn/assets/label-under-planet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/learn/assets/label-under-planet.png
--------------------------------------------------------------------------------
/docs/learn/assets/mercury-label-outside-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/learn/assets/mercury-label-outside-background.png
--------------------------------------------------------------------------------
/docs/learn/assets/mercury-label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/learn/assets/mercury-label.png
--------------------------------------------------------------------------------
/docs/learn/assets/overlaid-circles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/learn/assets/overlaid-circles.png
--------------------------------------------------------------------------------
/docs/learn/get-started.md:
--------------------------------------------------------------------------------
1 | # Get Started
2 |
3 | ## Install
4 |
5 | Bluefish comes in two flavors: `bluefish-js` and `bluefish-solid`.
6 |
7 | `bluefish-js` is a pure-JS/TS library that works in any JavaScript environment.
8 | **Use this unless you are using SolidJS.**
9 |
10 | ::: code-group
11 |
12 | ```bash:no-line-numbers [yarn]
13 | yarn add bluefish-js
14 | ```
15 |
16 | ```bash:no-line-numbers [npm]
17 | npm install bluefish-js
18 | ```
19 |
20 | ```bash:no-line-numbers [pnpm]
21 | pnpm install bluefish-js
22 | ```
23 |
24 | ```bash:no-line-numbers [bun]
25 | bun add bluefish-js
26 | ```
27 |
28 | :::
29 |
30 | `bluefish-solid` offers Bluefish as a SolidJS component library using JSX syntax.
31 |
32 | ::: code-group
33 |
34 | ```bash:no-line-numbers [yarn]
35 | yarn add bluefish-solid
36 | ```
37 |
38 | ```bash:no-line-numbers [npm]
39 | npm install bluefish-solid
40 | ```
41 |
42 | ```bash:no-line-numbers [pnpm]
43 | pnpm install bluefish-solid
44 | ```
45 |
46 | ```bash:no-line-numbers [bun]
47 | bun add bluefish-solid
48 | ```
49 |
50 | :::
51 |
52 | ### Starter REPLs
53 |
54 | We have REPLs to get you started in some common environments and frameworks:
55 |
56 | - [Vanilla JS](https://playground.solidjs.com/anonymous/d19113c2-dab6-4867-9d2b-4c14040757b9)
57 | - [Observable](https://observablehq.com/@joshpoll/bluefish-in-observable)
58 | - [Svelte](https://svelte.dev/repl/1fa5bf8713ac4fc2a991560e50564932?version=4.2.1)
59 | - [SolidJS](https://playground.solidjs.com/anonymous/b5ec4207-c725-4bce-9bc8-18e639067514)
60 |
--------------------------------------------------------------------------------
/docs/learn/tutorial-4-layouts.md:
--------------------------------------------------------------------------------
1 | # Tutorial Part 4: Custom Layouts
2 |
3 | The final part of this tutorial is about making custom layouts. When you need to go beyond
4 | Bluefish's standard library, you can lean our custom layout API.
5 |
6 | ## The `Layout` component
7 |
8 | ## Anatomy of a relation
9 |
10 | - marks and relations are the same thing
11 | - everything has a bounding box
12 | - everything has a layout
13 | - everything has a paint
14 |
15 | ## Writing a custom layout
16 |
17 | ## The bounding box model
--------------------------------------------------------------------------------
/docs/learn/what-is-bluefish.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 |
72 |
73 | # What Is Bluefish?
74 |
75 | Bluefish is a diagramming framework for the web. It relaxes the UI component model popularized by
76 | React. The main primitive of Bluefish is the _relation_. Just as components are the
77 | building blocks of user interfaces, relations are the building blocks of diagrams. Unlike
78 | components, relations can share children with other relations, and they don't need to fully specify
79 | their childrens' layouts.
80 |
81 | Bluefish is based on work from the [MIT Visualization Group](https://vis.csail.mit.edu/) and the [Software
82 | is based on work from the [MIT Visualization Group](https://vis.csail.mit.edu/) and the [Software Design Group](https://sdg.csail.mit.edu/). You can read more
83 | about the theory, design, and implementation of Bluefish in our [research
84 | paper](https://arxiv.org/abs/2307.00146).
85 |
86 | # Team
87 |
88 |
89 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "private": true,
4 | "scripts": {
5 | "docs:dev": "vitepress dev .",
6 | "docs:build": "vitepress build .",
7 | "docs:preview": "vitepress preview ."
8 | },
9 | "dependencies": {
10 | "@vue/babel-plugin-jsx": "^1.2.2",
11 | "@vue/babel-preset-jsx": "^1.4.0",
12 | "@wdns/vue-code-block": "^2.3.3",
13 | "autoprefixer": "^10.4.19",
14 | "markdown-it-container": "^4.0.0",
15 | "postcss": "^8.4.38",
16 | "sandpack-vue3": "^3.1.11",
17 | "tailwindcss": "^3.4.4",
18 | "vitepress": "^1.2.3",
19 | "vitepress-plugin-sandpack": "^1.1.4",
20 | "vue": "^3.4.27"
21 | },
22 | "devDependencies": {
23 | "@vue/tsconfig": "^0.5.1"
24 | },
25 | "postcss": {
26 | "plugins": {
27 | "tailwindcss": {},
28 | "autoprefixer": {}
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/docs/public/bluefish-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/public/bluefish-logo.png
--------------------------------------------------------------------------------
/docs/public/docs-teaser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/public/docs-teaser.png
--------------------------------------------------------------------------------
/docs/public/planets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bluefishjs/bluefish/516ec19ed9e3226ec5d14b9dc3fb16c2e6307447/docs/public/planets.png
--------------------------------------------------------------------------------
/docs/sandboxUtil.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/jerrywu001/vitepress-plugin-sandpack/blob/main/src/components/SandpackUtil.ts
2 | import type { Slots, VNode } from "vue";
3 | import { renderToString } from "vue/server-renderer";
4 | import type { SandpackFiles } from "sandpack-vue3";
5 |
6 | const defaultFilePath = "/index.ts";
7 |
8 | const getFileAttributes = (info: string) => {
9 | let path: string | undefined;
10 | const opts = (info || "").split(" ");
11 | const hidden = opts.includes("[hidden]");
12 | const readOnly = opts.includes("[readonly]") || opts.includes("[readOnly]");
13 | const active = opts.includes("[active]");
14 | const filename = opts.find((v) => v.includes("."));
15 | if (filename) {
16 | path = filename;
17 | path = path === "App.vue" ? defaultFilePath : path;
18 | path = path.startsWith("/") ? path : `/${path}`;
19 | }
20 | return { hidden, active, readOnly, path };
21 | };
22 |
23 | export const getSandpackFiles = async (props: { codeOptions: string }, slot: Slots) => {
24 | const items = {} as SandpackFiles;
25 | const content = (slot.default ? slot.default() : []) as VNode[];
26 | let codeItems = content.filter((v) => v.type === "div") || [];
27 | let i = 0;
28 | if (Array.isArray(codeItems)) {
29 | if (!codeItems.length) {
30 | // @ts-ignore
31 | const ctx = content ? content[0]?.ctx : {};
32 | if (ctx && ctx?.slots) {
33 | codeItems = ctx.slots?.default ? ctx.slots.default() : [];
34 | }
35 | }
36 | for await (const v of codeItems) {
37 | let code = "";
38 | let div: HTMLDivElement | null = document.createElement("div");
39 | const children = (v.children || []) as VNode[];
40 | const { active, hidden, readOnly, path } = getFileAttributes(
41 | JSON.parse(decodeURIComponent(props.codeOptions /* as string */))[i]
42 | );
43 | const filename = path || defaultFilePath;
44 | const pre = children.find((c) => c.type === "pre");
45 | v.children = pre ? [pre] : [];
46 | const html = await renderToString(v);
47 | div.insertAdjacentHTML("beforeend", html);
48 | code = div.innerText;
49 | div = null;
50 | items[filename] = {
51 | code,
52 | active,
53 | hidden,
54 | readOnly,
55 | };
56 | i++;
57 | }
58 | }
59 | return items;
60 | };
61 |
--------------------------------------------------------------------------------
/docs/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: {
3 | enabled: process.env.NODE_ENV === "production",
4 | content: ["./**/*.{ js,vue,ts,md}"],
5 | options: {
6 | safelist: ["html", "body"],
7 | },
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "preserve",
5 | "jsxFactory": "h",
6 | "jsxFragmentFactory": "Fragment"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bluefish-monorepo",
3 | "private": true,
4 | "scripts": {
5 | "build": "turbo build",
6 | "dev": "turbo dev",
7 | "lint": "turbo lint",
8 | "format": "prettier --write \"**/*.{ts,tsx,md}\""
9 | },
10 | "devDependencies": {
11 | "prettier": "^3.2.5",
12 | "turbo": "^2.1.2",
13 | "typescript": "^5.4.5"
14 | },
15 | "packageManager": "pnpm@8.15.6",
16 | "engines": {
17 | "node": ">=18"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/bluefish-js/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | public/App.ts
4 |
--------------------------------------------------------------------------------
/packages/bluefish-js/README.md:
--------------------------------------------------------------------------------
1 | # Bluefish JS
2 |
3 |
4 |
5 |
6 |
7 |
Create beautiful, customizable diagrams in JavaScript.
8 |
9 |
10 | ## Installation
11 |
12 | ```bash
13 | npm install bluefish-js
14 | ```
--------------------------------------------------------------------------------
/packages/bluefish-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bluefish-js",
3 | "version": "0.0.37",
4 | "description": "A JS diagramming library",
5 | "type": "module",
6 | "files": [
7 | "dist"
8 | ],
9 | "main": "./dist/index.umd.cjs",
10 | "module": "./dist/index.js",
11 | "types": "./dist/index.d.ts",
12 | "exports": {
13 | ".": {
14 | "import": "./dist/index.js",
15 | "require": "./dist/index.umd.cjs"
16 | }
17 | },
18 | "scripts": {
19 | "build": "vite build",
20 | "dev": "vite",
21 | "serve": "vite preview",
22 | "publish": "pnpm publish"
23 | },
24 | "license": "MIT",
25 | "devDependencies": {
26 | "solid-devtools": "^0.29.2",
27 | "typescript": "^5.3.3",
28 | "vite": "^5.0.11",
29 | "vite-plugin-dts": "^4.2.2",
30 | "vite-plugin-solid": "^2.8.2"
31 | },
32 | "dependencies": {
33 | "bluefish-solid": "workspace:*",
34 | "solid-js": "^1.9.3"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/bluefish-js/public/App.template.ts:
--------------------------------------------------------------------------------
1 | import { Text, render } from "../src";
2 |
3 | function App() {
4 | return Text(
5 | "Duplicate this file and name it `App.ts` to use the dev playground!"
6 | );
7 | }
8 |
9 | render(App, document.getElementById("root")!);
10 |
--------------------------------------------------------------------------------
/packages/bluefish-js/public/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/bluefish-js/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Bluefish-JS
9 |
10 |
11 | You need to enable JavaScript to run this app.
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/bluefish-js/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "exclude": ["node_modules", "dist", "examples", "experiments", "public", "src/stories", "vite.config.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/bluefish-js/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["node_modules", "dist"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleResolution": "node",
8 | "allowSyntheticDefaultImports": true,
9 | "esModuleInterop": true,
10 | "jsx": "preserve",
11 | "jsxImportSource": "solid-js",
12 | "types": ["vite/client"],
13 | "emitDeclarationOnly": true,
14 | "isolatedModules": true,
15 | "declaration": true,
16 | "declarationDir": "dist",
17 | "declarationMap": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/bluefish-js/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { resolve } from "path";
3 | import dts from "vite-plugin-dts";
4 |
5 | export default defineConfig(({ command }) => ({
6 | publicDir: command === "serve" ? "public" : false,
7 | server: {
8 | port: 3000,
9 | open: "/public/index.html",
10 | },
11 | build: {
12 | emptyOutDir: false,
13 | target: "esnext",
14 | lib: {
15 | entry: resolve(__dirname, "src/index.ts"),
16 | formats: ["es", "cjs", "umd"],
17 | name: "bluefish",
18 | fileName: "index",
19 | },
20 | },
21 | plugins: [dts({ tsconfigPath: "./tsconfig.build.json" })],
22 | }));
23 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: [
7 | "eslint:recommended",
8 | "plugin:@typescript-eslint/recommended",
9 | "plugin:solid/typescript",
10 | "plugin:storybook/recommended"
11 | ],
12 | overrides: [
13 | {
14 | env: {
15 | node: true,
16 | },
17 | files: [".eslintrc.{js,cjs}"],
18 | parserOptions: {
19 | sourceType: "script",
20 | },
21 | },
22 | ],
23 | parser: "@typescript-eslint/parser",
24 | parserOptions: {
25 | ecmaVersion: "latest",
26 | sourceType: "module",
27 | },
28 | plugins: ["@typescript-eslint", "solid"],
29 | rules: {
30 | "no-debugger": "off",
31 | "@typescript-eslint/no-unused-vars": [
32 | "warn",
33 | {
34 | varsIgnorePattern: "^_",
35 | argsIgnorePattern: "^_",
36 | },
37 | ],
38 | "@typescript-eslint/no-explicit-any": "warn",
39 | "prefer-const": "warn",
40 | "no-constant-condition": ["error", { checkLoops: false }],
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | /public/App.tsx
4 | */.DS_Store
5 | build-storybook.log
6 | chromatic.log
7 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import { dirname, join } from "path";
2 | import type { StorybookConfig } from "storybook-solidjs-vite";
3 |
4 | const config: StorybookConfig = {
5 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
6 | addons: [
7 | getAbsolutePath("@storybook/addon-links"),
8 | getAbsolutePath("@storybook/addon-essentials"),
9 | getAbsolutePath("@storybook/addon-interactions"),
10 | getAbsolutePath("@storybook/addon-docs"),
11 | getAbsolutePath("@chromatic-com/storybook")
12 | ],
13 | framework: {
14 | name: getAbsolutePath("storybook-solidjs-vite"),
15 | options: {},
16 | },
17 | docs: {},
18 | };
19 | export default config;
20 |
21 | function getAbsolutePath(value: string): any {
22 | return dirname(require.resolve(join(value, "package.json")));
23 | }
24 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/.storybook/preview.ts:
--------------------------------------------------------------------------------
1 | const preview: Preview = {
2 | parameters: {
3 | actions: { argTypesRegex: "^on[A-Z].*" },
4 | controls: {
5 | matchers: {
6 | color: /(background|color)$/i,
7 | date: /Date$/,
8 | },
9 | },
10 | },
11 | };
12 |
13 | export default preview;
14 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "prettier.printWidth": 80,
3 | "typescript.tsdk": "./node_modules/typescript/lib"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Bluefish
2 |
3 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
4 |
5 | ## Setup
6 |
7 | 1. Fork and clone the repo
8 | 2. Run `pnpm install` to install dependencies. (Ensure you have `pnpm` first)
9 | 3. Create a branch for your PR with `git checkout -b your-branch-name`
10 | 4. To keep the `main` branch of your fork pointing to this repo, run
11 |
12 | ```
13 | git remote add upstream https://github.com/bluefishjs/rockfish.git
14 | git fetch upstream
15 | git branch --set-upstream-to=upstream/main main
16 | ```
17 |
18 | 5. If you want to experiment with changes locally, you can duplicate `App.template.tsx` and rename
19 | it to `App.tsx`. This file is ignored by git, so you can make changes to it without affecting the
20 | repo.
21 | 6. If you are adding a bug fix or feature, add a test or example, respectively, to `stories/` and verify that it works with `pnpm storybook`.
22 |
23 | ## Running
24 |
25 | The code can be run with `pnpm start`. This should open a web browser that serves `App.tsx`, typically on `localhost:3000/public/index.html`.
26 | You can use `App.tsx` as a scratchpad for experiments.
27 |
28 | To check examples, run `pnpm storybook`. This will launch storybook, typically on `localhost:6006`.
29 | Changes to examples must be verified by a contributer before a PR can be accepted.
30 |
31 | ## Design Principles and Goals
32 |
33 | Minor bug fixes and features may be accepted readily. For major changes to be accepted into Bluefish, they should adhere to Bluefish's design principles.
34 |
35 | ### End-User Goals
36 |
37 | - **Local Reasoning:** Users should be able to understand a Bluefish program by reasoning locally about individual components rather than globally across the entire program.
38 | - **Gradual Specification:** It should be possible to make small changes to a Bluefish program and get meaningful outputs at each step.
39 |
40 | ### System Interface Goals
41 |
42 | - **Simple Core:** The core of the system should have simple interface. This includes the layout engine and declarative references. Individual components may have complex sets of parameters.
43 | - **Extensibile:** As much as possible no component should be ``privileged'' by the core system. New APIs should be accessible to both standard library and user-defined elements.
44 |
45 | ### System Design Principles
46 |
47 | - **Composition:** To achieve simplicity, aim to design simple pieces that compose in complex ways.
48 | - **Abstraction:** To achieve extensibility, aim to design pieces that can be abstracted into functions without changing their behavior.
49 |
50 | ## Acknowledgements
51 |
52 | This contributing doc is based on [Vega-Lite's](https://github.com/vega/vega-lite/blob/main/CONTRIBUTING.md) and [Excalidraw's](https://docs.excalidraw.com/docs/introduction/contributing).
53 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Joshua M Pollock
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Create beautiful, customizable visualizations in SolidJS.
6 |
7 |
8 | ## Installation Guide
9 |
10 | To start, make sure you have `pnpm` installed. Installation instructions can be found at `https://pnpm.io/installation`.
11 |
12 | After you have installed `pnpm`, open a new terminal window and navigate to project directory. Run the following command to install all necessary packages and dependencies:
13 |
14 | `pnpm install`
15 |
16 | ## Available Scripts
17 |
18 | You can run the following commands inside the project directory:
19 |
20 | ### `pnpm start`
21 |
22 | Runs the app in the development mode.
23 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
24 |
25 | The page will reload if you make edits.
26 |
27 | ### `pnpm storybook`
28 |
29 | Runs storybook
30 |
31 | ## Making an `App.tsx` File
32 |
33 | When you run `pnpm start`, it builds and renders the contents of `App.tsx`. You should create an `App.tsx` file in `/public`. This can be done by duplicating the `App.template.tsx` file in the `/public` folder and renaming it to `App.tsx`.
34 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/examples/continuity.tsx:
--------------------------------------------------------------------------------
1 | // https://en.wikipedia.org/wiki/File:Continuity_topology.svg
2 | /*
3 | A function f : X -> Y
4 | is continuous at a point x in X
5 |
6 | iff
7 |
8 | for every neighborhood V of f(x) in Y
9 | there is a neighborhood U of x in X
10 | s.t. f(U) is a subset of V
11 | */
12 |
13 | /*
14 | - Draw X
15 | - Draw Y
16 | - Draw x
17 | - Draw f(x)
18 | - Draw V
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | */
40 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/examples/topology/image.tsx:
--------------------------------------------------------------------------------
1 | import { ParentProps } from "solid-js";
2 | import { Id, withBluefish } from "../../src";
3 | import Background from "../../src/background";
4 |
5 | export type ImageProps = ParentProps<{
6 | name: Id;
7 | }>;
8 |
9 | export const Image = withBluefish((props: ImageProps) => {
10 | return (
11 |
12 | {props.children}
13 |
14 | );
15 | });
16 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/examples/topology/neighborhood.tsx:
--------------------------------------------------------------------------------
1 | import { ParentProps } from "solid-js";
2 | import { Id, withBluefish } from "../../src";
3 | import Background from "../../src/background";
4 |
5 | export type NeighborhoodProps = ParentProps<{
6 | name: Id;
7 | }>;
8 |
9 | export const Neighborhood = withBluefish((props: NeighborhoodProps) => {
10 | return (
11 |
12 | {props.children}
13 |
14 | );
15 | });
16 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/examples/topology/space.tsx:
--------------------------------------------------------------------------------
1 | import { ParentProps } from "solid-js";
2 | import { Id, withBluefish } from "../../src";
3 | import Background from "../../src/background";
4 |
5 | export type SpaceProps = ParentProps<{
6 | name: Id;
7 | }>;
8 |
9 | export const Space = withBluefish((props: SpaceProps) => {
10 | return (
11 |
12 | {props.children}
13 |
14 | );
15 | });
16 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/experiments/plot.ts:
--------------------------------------------------------------------------------
1 | const alphabet: any = null;
2 |
3 | // Observable Plot
4 | Plot({
5 | x: { percent: true },
6 | marks: [
7 | BarX(alphabet, StackX({ x: "frequency", fillOpacity: 0.3, inset: 0.5 })),
8 | TextX(alphabet, StackX({ x: "frequency", text: "letter", inset: 0.5 })),
9 | ],
10 | });
11 |
12 | // Bluefish Plot StackX(BarX)
13 | Plot(
14 | { data: alphabet, x: { percent: true } },
15 | Align("center", [
16 | StackX({ spacing: 0.5 }, BarX({ x: "frequency", fillOpacity: 0.3 })),
17 | TextX({ text: "letter" }),
18 | ])
19 | );
20 |
21 | // Bluefish Plot BarX(StackX)
22 | Plot(
23 | { data: alphabet, x: { percent: true } },
24 | Align("center", [
25 | BarX({
26 | x: "frequency",
27 | fillOpacity: 0.3,
28 | layout: StackX({ spacing: 0.5 }),
29 | }),
30 | TextX({ text: "letter" }),
31 | ])
32 | );
33 |
34 | function Plot(...arg0: any) {
35 | throw new Error("Function not implemented.");
36 | }
37 |
38 | function Align(...arg0: any[]): any {
39 | throw new Error("Function not implemented.");
40 | }
41 |
42 | function StackX(...BarX: any): any {
43 | throw new Error("Function not implemented.");
44 | }
45 |
46 | function BarX(...args: any): any {
47 | throw new Error("Function not implemented.");
48 | }
49 |
50 | function TextX(...args: any): any {
51 | throw new Error("Function not implemented.");
52 | }
53 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bluefish-solid",
3 | "version": "0.0.37",
4 | "description": "A SolidJS diagramming library",
5 | "type": "module",
6 | "files": [
7 | "dist"
8 | ],
9 | "main": "./dist/index.umd.cjs",
10 | "module": "./dist/index.js",
11 | "types": "./dist/index.d.ts",
12 | "exports": {
13 | ".": {
14 | "import": "./dist/index.js",
15 | "require": "./dist/index.umd.cjs"
16 | }
17 | },
18 | "scripts": {
19 | "start": "vite",
20 | "dev": "vite",
21 | "build": "vite build",
22 | "publish": "pnpm publish",
23 | "tsc": "npx tsc --skipLibCheck -p ./tsconfig.build.json",
24 | "serve": "vite preview",
25 | "storybook": "storybook dev -p 6006",
26 | "storybook-docs": "storybook dev --docs",
27 | "build-storybook": "storybook build",
28 | "test-storybook": "test-storybook",
29 | "chromatic": "pnpm dlx chromatic --project-token=chpt_8295300597e1f7e"
30 | },
31 | "license": "MIT",
32 | "devDependencies": {
33 | "@chromatic-com/storybook": "^3.2.2",
34 | "@solid-devtools/logger": "^0.8.3",
35 | "@storybook/addon-docs": "^8.5.0-alpha.18",
36 | "@storybook/addon-essentials": "^8.5.0-alpha.18",
37 | "@storybook/addon-interactions": "^8.5.0-alpha.18",
38 | "@storybook/addon-links": "^8.5.0-alpha.18",
39 | "@storybook/blocks": "^8.5.0-alpha.18",
40 | "@storybook/docs-tools": "^8.5.0-alpha.18",
41 | "@storybook/test": "^8.5.0-alpha.18",
42 | "@storybook/test-runner": "^0.20.1",
43 | "@types/dagre": "^0.7.51",
44 | "@types/node": "^20.3.3",
45 | "@typescript-eslint/eslint-plugin": "^6.7.5",
46 | "@typescript-eslint/parser": "^6.7.5",
47 | "chromatic": "^7.6.0",
48 | "eslint": "^8.51.0",
49 | "eslint-plugin-solid": "^0.13.0",
50 | "eslint-plugin-storybook": "^0.11.1",
51 | "ohm-js": "^17.1.0",
52 | "playwright": "^1.38.1",
53 | "solid-devtools": "^0.30.1",
54 | "solid-js": "^1.9.3",
55 | "storybook": "^8.5.0-alpha.18",
56 | "storybook-solidjs": "^1.0.0-beta.6",
57 | "storybook-solidjs-vite": "^1.0.0-beta.6",
58 | "typescript": "^5.3.3",
59 | "vite": "^4.3.9",
60 | "vite-plugin-dts": "^4.2.2",
61 | "vite-plugin-solid": "^2.7.0"
62 | },
63 | "dependencies": {
64 | "@types/d3-scale": "^4.0.5",
65 | "@types/lodash": "^4.14.195",
66 | "d3-scale": "^4.0.2",
67 | "dagre": "^0.8.5",
68 | "lodash": "^4.17.21",
69 | "paper": "^0.12.17",
70 | "perfect-arrows": "^0.3.7",
71 | "reduce-css-calc": "^2.1.8",
72 | "smiles-drawer": "^2.1.7",
73 | "solid-toast": "^0.5.0",
74 | "tailwindcss": "^3.4.3"
75 | },
76 | "peerDependencies": {
77 | "solid-js": "^1.9.3"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/public/App.template.tsx:
--------------------------------------------------------------------------------
1 | import { type Component } from "solid-js";
2 | import { Bluefish, Text } from "../src";
3 |
4 | const App: Component = () => {
5 | return (
6 | <>
7 |
8 |
9 | Duplicate this file and name it `App.tsx` to use the dev playground!
10 |
11 |
12 | >
13 | );
14 | };
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/public/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Solid App
9 |
10 |
11 | You need to enable JavaScript to run this app.
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/public/index.tsx:
--------------------------------------------------------------------------------
1 | /* @refresh reload */
2 | import { render } from "solid-js/web";
3 |
4 | import App from "./App";
5 |
6 | const root = document.getElementById("root");
7 |
8 | if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
9 | throw new Error(
10 | "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?"
11 | );
12 | }
13 |
14 | render(() => , root!);
15 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/chemistry/atom.tsx:
--------------------------------------------------------------------------------
1 | import { JSX } from "solid-js/jsx-runtime";
2 | import { Layout } from "../layout";
3 | import { BBox, Id, Transform } from "../scenegraph";
4 | import { splitProps } from "solid-js";
5 | import withBluefish from "../withBluefish";
6 |
7 | type maxBondTypes = {
8 | [key: string]: number;
9 | };
10 |
11 | const maxBonds: maxBondTypes = {
12 | H: 1,
13 | C: 4,
14 | N: 3,
15 | O: 2,
16 | P: 3,
17 | S: 2,
18 | B: 3,
19 | F: 1,
20 | I: 1,
21 | Cl: 1,
22 | Br: 1,
23 | };
24 |
25 | type elementNameTypes = {
26 | [key: string]: string;
27 | };
28 |
29 | const elementName: elementNameTypes = {
30 | H: "Hydrogen",
31 | C: "Carbon",
32 | N: "Nitrogen",
33 | O: "Oxygen",
34 | P: "Phosphorus",
35 | S: "Sulfur",
36 | B: "Boron",
37 | F: "Flourine",
38 | I: "Iodine",
39 | Cl: "Chlorine",
40 | Br: "Bromine",
41 | };
42 |
43 | export type AtomProps = JSX.CircleSVGAttributes & {
44 | content: string;
45 | name: Id;
46 | isTerminal: boolean;
47 | bondCount: number;
48 | ariaHidden: boolean;
49 | r: number;
50 | cx?: number;
51 | cy?: number;
52 | };
53 |
54 | export const Atom = withBluefish((props: AtomProps) => {
55 | const numHydrogens = () => maxBonds[props.content] - props.bondCount;
56 | const hydrogenString = () => "H".repeat(numHydrogens());
57 | const atomContent = () =>
58 | props.content === "C" ? "" : props.content + hydrogenString();
59 | const layout = () => {
60 | return {
61 | bbox: {
62 | left: -props.r,
63 | top: -props.r,
64 | width: props.r * 2,
65 | height: props.r * 2,
66 | },
67 | transform: {
68 | translate: {
69 | x: props.cx,
70 | y: props.cy,
71 | },
72 | },
73 | };
74 | };
75 |
76 | const paint = (paintProps: { bbox: BBox; transform: Transform }) => {
77 | const [_, rest] = splitProps(props, ["name", "cx", "cy", "r"]);
78 |
79 | const r = () => (paintProps.bbox.width ?? 0) / 2;
80 |
81 | return (
82 | <>
83 | {/* if atom content length is greater than 1, then render a rectangle instead */}
84 | {atomContent().length > 1 ? (
85 |
100 | ) : (
101 |
116 | )}
117 |
132 | {atomContent()}
133 |
134 | >
135 | );
136 | };
137 |
138 | return ;
139 | });
140 |
141 | export default Atom;
142 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/circle.tsx:
--------------------------------------------------------------------------------
1 | import { JSX } from "solid-js/jsx-runtime";
2 | import { Layout } from "./layout";
3 | import { BBox, Id, Transform } from "./scenegraph";
4 | import { splitProps } from "solid-js";
5 | import withBluefish from "./withBluefish";
6 |
7 | export type CircleProps = JSX.CircleSVGAttributes & {
8 | name: Id;
9 | cx?: number;
10 | cy?: number;
11 | r: number;
12 | };
13 |
14 | export const Circle = withBluefish(
15 | (props: CircleProps) => {
16 | const layout = () => {
17 | return {
18 | bbox: {
19 | left: -props.r,
20 | top: -props.r,
21 | width: props.r * 2,
22 | height: props.r * 2,
23 | },
24 | transform: {
25 | translate: {
26 | x: props.cx,
27 | y: props.cy,
28 | },
29 | },
30 | };
31 | };
32 |
33 | const paint = (paintProps: { bbox: BBox; transform: Transform }) => {
34 | const [_, rest] = splitProps(props, ["name", "cx", "cy", "r"]);
35 |
36 | const r = () => (paintProps.bbox.width ?? 0) / 2;
37 |
38 | return (
39 |
53 | );
54 | };
55 |
56 | return ;
57 | },
58 | { displayName: "Circle" }
59 | );
60 |
61 | export default Circle;
62 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/createName.tsx:
--------------------------------------------------------------------------------
1 | import { Accessor, createContext, createUniqueId, useContext } from "solid-js";
2 | import { SetStoreFunction, Store } from "solid-js/store";
3 | import { Id } from "./scenegraph";
4 |
5 | export type Name = string;
6 |
7 | /*
8 | TODO: This is wrong!!!
9 | A name should either point to an id of another name or else point to an id of a layout node.
10 |
11 | draw this out. it's too hard to see
12 | */
13 |
14 | export type Scope = {
15 | [key: Id]: {
16 | layoutNode: Id | undefined;
17 | parent: Id; // TODO: not sure if this is used. might be useful for debugging/error reporting, though
18 | children: { [key: Name]: Id };
19 | };
20 | };
21 |
22 | // the scope context is a map from uid to a set of names that map to other uids
23 | export const ScopeContext = createContext<
24 | [get: Store, set: SetStoreFunction]
25 | >([{}, () => {}]);
26 | export const ParentScopeIdContext = createContext>(() => "");
27 |
28 | export const createName = (name: string) => {
29 | const genId = `${name}(${createUniqueId()})`;
30 |
31 | const [scope, setScope] = useContext(ScopeContext);
32 | const parentId = useContext(ParentScopeIdContext);
33 |
34 | // TODO: check if it exists before adding
35 |
36 | // setScope(name, genId);
37 | setScope(genId, {
38 | parent: parentId(),
39 | layoutNode: undefined,
40 | children: {},
41 | }); // inside withBluefish, we first check if we have a props.name as input, which will then become the parent context. otherwise we'll generate a new id for the parent context
42 | setScope(parentId(), "children", name, genId);
43 |
44 | return genId;
45 | };
46 |
47 | export const resolveName = (
48 | layoutNode: Id,
49 | options?: { default?: boolean }
50 | ): Name | undefined => {
51 | options = {
52 | default: true,
53 | ...options,
54 | };
55 | const [scope] = useContext(ScopeContext);
56 | const name = Object.keys(scope).find(
57 | (name) => scope[name].layoutNode === layoutNode
58 | );
59 |
60 | return name ?? (options?.default ? layoutNode : undefined);
61 | };
62 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/ellipse.tsx:
--------------------------------------------------------------------------------
1 | import { JSX } from "solid-js/jsx-runtime";
2 | import { Layout } from "./layout";
3 | import { BBox, Id, Transform } from "./scenegraph";
4 | import { splitProps } from "solid-js";
5 | import withBluefish from "./withBluefish";
6 |
7 | export type EllipseProps = JSX.EllipseSVGAttributes & {
8 | name: Id;
9 | cx?: number;
10 | cy?: number;
11 | rx: number;
12 | ry: number,
13 | };
14 |
15 | export const Ellipse = withBluefish(
16 | (props: EllipseProps) => {
17 | const layout = () => {
18 | return {
19 | bbox: {
20 | left: -props.rx,
21 | top: -props.ry,
22 | width: props.rx * 2,
23 | height: props.ry * 2,
24 | },
25 | transform: {
26 | translate: {
27 | x: props.cx,
28 | y: props.cy,
29 | },
30 | },
31 | };
32 | };
33 |
34 | const paint = (paintProps: { bbox: BBox; transform: Transform }) => {
35 | const [_, rest] = splitProps(props, ["name", "cx", "cy", "rx", "ry"]);
36 |
37 | const rx = () => (paintProps.bbox.width ?? 0) / 2;
38 | const ry = () => (paintProps.bbox.height ?? 0)/2;
39 |
40 | return (
41 |
56 | );
57 | };
58 |
59 | return ;
60 | },
61 | { displayName: "Ellipse" }
62 | );
63 |
64 | export default Ellipse;
65 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/errorContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from "solid-js";
2 | import { BluefishError } from "./errors";
3 | import { createStore } from "solid-js/store";
4 |
5 | export type ErrorContextType = {
6 | errors: BluefishError[];
7 | addError: (error: BluefishError) => void;
8 | };
9 |
10 | export const createErrorContext = (
11 | onError: (error: BluefishError) => void
12 | ): ErrorContextType => {
13 | const [errors, setErrors] = createStore([]);
14 | const addError = (error: BluefishError) => {
15 | setErrors((prev) => [...prev, error]);
16 | onError(error);
17 | };
18 | return { errors, addError };
19 | };
20 |
21 | export const ErrorContext = createContext(null);
22 |
23 | export const useError = () => {
24 | const context = useContext(ErrorContext);
25 |
26 | // if (context === null) {
27 | // throw new Error("useError must be used within an ErrorContext.Provider");
28 | // }
29 |
30 | // return context.addError;
31 | return (error: BluefishError) => {
32 | console.warn(error);
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/example-gallery/path.tsx:
--------------------------------------------------------------------------------
1 | // Path Component extending base Bluefish. Should eventually be merged into Bluefish
2 | import {
3 | JSX,
4 | ParentProps,
5 | Show,
6 | createEffect,
7 | mergeProps,
8 | splitProps,
9 | untrack,
10 | } from "solid-js";
11 | import withBluefish from "../withBluefish";
12 | import { PaperScope } from "paper";
13 | import Layout from "../layout";
14 | // TODO: should be exported by Bluefish
15 | export const maybeSub = (a, b) =>
16 | a !== undefined && b !== undefined ? a - b : undefined;
17 |
18 | // TODO: add support for points as well as d?
19 | // export type PathProps = JSX.PathSVGAttributes & {
20 | // name: Id;
21 | // d: string;
22 | // x?: number;
23 | // y?: number;
24 | // position?: "absolute" | "relative";
25 | // };
26 |
27 | export const Path = withBluefish((props) => {
28 | props = mergeProps(
29 | {
30 | "stroke-width": 3,
31 | stroke: "black",
32 | position: "relative",
33 | fill: "none",
34 | },
35 | props
36 | );
37 |
38 | const canvas = document.createElement("canvas");
39 | const paperScope = new PaperScope();
40 | paperScope.setup(canvas);
41 |
42 | const layout = (childIds) => {
43 | childIds = Array.from(childIds);
44 |
45 | if (props.name.endsWith("DEBUG")) {
46 | debugger;
47 | }
48 |
49 | const path = new paperScope.Path(props.d);
50 | const bounds = path.bounds;
51 |
52 | return {
53 | transform: {
54 | translate: {
55 | x: props.position === "absolute" ? 0 : maybeSub(props.x, bounds.left),
56 | y: props.position === "absolute" ? 0 : maybeSub(props.y, bounds.top),
57 | },
58 | },
59 | bbox: {
60 | left: bounds.left,
61 | top: bounds.top,
62 | width: bounds.width,
63 | height: bounds.height,
64 | },
65 | customData: {
66 | path: path,
67 | },
68 | };
69 | };
70 |
71 | const paint = (paintProps) => {
72 | const [_, rest] = splitProps(props, ["name", "x", "y", "d", "position"]);
73 |
74 | return (
75 | {paintProps.children}}
78 | >
79 |
84 |
85 | {paintProps.children}
86 |
87 |
88 | );
89 | };
90 |
91 | return (
92 |
93 | {props.children}
94 |
95 | );
96 | });
97 |
98 | export default Path;
99 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/example-gallery/qc-text.tsx:
--------------------------------------------------------------------------------
1 | import { Path } from "./path.jsx";
2 | import { Line } from "../line.jsx";
3 | import Background from "../background.jsx";
4 | import Bluefish from "../bluefish.jsx";
5 | import Circle from "../circle.jsx";
6 | import Group from "../group.jsx";
7 | import Rect from "../rect.jsx";
8 | import Ref from "../ref.jsx";
9 | import { StackH } from "../stackh.jsx";
10 | import { StackV } from "../stackv.jsx";
11 | import withBluefish from "../withBluefish.jsx";
12 | import Align from "../align.jsx";
13 | import Text from "../text.jsx";
14 |
15 | const Wire = withBluefish((props) => (
16 |
17 |
18 |
19 |
20 | {props.children}
21 |
22 |
23 | ));
24 |
25 | const WireSymbol = withBluefish((props) => (
26 |
27 |
28 | {props.children}
29 |
30 | ));
31 |
32 | const EmptySpot = withBluefish(() => );
33 |
34 | const BoxedSymbol = withBluefish((props) => {
35 | return (
36 | (
38 |
45 | )}
46 | >
47 |
56 | {props.children}
57 |
58 |
59 | );
60 | });
61 |
62 | const OPlus = withBluefish(() => (
63 |
64 |
65 |
71 |
72 |
73 |
74 |
75 | ));
76 |
77 | const ControlDot = withBluefish(() => );
78 |
79 | export const QCText = () => {
80 | return (
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | Z
91 |
92 |
93 |
94 | ≡
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | H
105 |
106 |
107 |
108 | H
109 |
110 |
111 | This is a controlled-NOT.
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | }
123 | >
124 |
125 |
126 | }
128 | >
129 |
130 |
131 |
132 | );
133 | };
134 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/gradient.tsx:
--------------------------------------------------------------------------------
1 | import { For } from "solid-js";
2 | import withBluefish from "./withBluefish";
3 | import Layout from "./layout";
4 | import { BBox, Id, Transform } from "./scenegraph";
5 |
6 | type ColorOffset = { offset: number; color: string };
7 |
8 | type GradientProps = {
9 | name: Id;
10 | id: string;
11 | colorOffsets: ColorOffset[];
12 | x1?: number;
13 | x2?: number;
14 | y1?: number;
15 | y2?: number;
16 | };
17 |
18 | export const Gradient = withBluefish(
19 | (props: GradientProps) => {
20 | const layout = () => {
21 | return {
22 | bbox: {
23 | centerX: 0,
24 | centerY: 0,
25 | width: 0,
26 | height: 0,
27 | },
28 | transform: {
29 | translate: {},
30 | },
31 | };
32 | };
33 |
34 | const paint = (_paintProps: { bbox: BBox; transform: Transform }) => {
35 | return (
36 |
37 |
44 |
45 | {(colorOffset) => (
46 |
50 | )}
51 |
52 |
53 |
54 | );
55 | };
56 |
57 | return ;
58 | },
59 | { displayName: "Gradient" }
60 | );
61 |
62 | export default Gradient;
63 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/group.tsx:
--------------------------------------------------------------------------------
1 | import { JSX, ParentProps } from "solid-js";
2 | import { BBox, ChildNode, Id, Transform, useScenegraph } from "./scenegraph";
3 | import Layout from "./layout";
4 | import {
5 | maxOfMaybes,
6 | maybeAdd,
7 | maybeMax,
8 | maybeMin,
9 | maybeSub,
10 | minOfMaybes,
11 | } from "./util/maybe";
12 | import { startsWith } from "lodash";
13 | import withBluefish from "./withBluefish";
14 |
15 | export type GroupProps = ParentProps<{
16 | name: Id;
17 | x?: number;
18 | y?: number;
19 | rels?: () => JSX.Element;
20 | }>;
21 |
22 | export const Group = withBluefish(
23 | (props: GroupProps) => {
24 | // NOTE: unlike other layout functions. this one determines its bbox by *skipping* undefined
25 | // values.
26 | // this is to ensure that if some child of the group doesn't know all its dimensions, then we
27 | // ignore that.
28 | // COMBAK: I'm not sure this is the correct behavior in general...
29 | const layout = (childNodes: ChildNode[]) => {
30 | if (props.name.endsWith("DEBUG")) {
31 | debugger;
32 | }
33 |
34 | for (const childNode of childNodes) {
35 | if (!childNode.owned.left) {
36 | childNode.bbox.left = 0;
37 | }
38 |
39 | if (!childNode.owned.top) {
40 | childNode.bbox.top = 0;
41 | }
42 | }
43 |
44 | const bboxes = {
45 | left: childNodes.map((childNode) => childNode.bbox.left),
46 | top: childNodes.map((childNode) => childNode.bbox.top),
47 | width: childNodes.map((childNode) => childNode.bbox.width),
48 | height: childNodes.map((childNode) => childNode.bbox.height),
49 | };
50 |
51 | const left = minOfMaybes(bboxes.left) ?? 0;
52 |
53 | const right = maxOfMaybes(
54 | bboxes.left.map((left, i) => maybeAdd(left, bboxes.width[i]))
55 | );
56 |
57 | const top = minOfMaybes(bboxes.top) ?? 0;
58 |
59 | const bottom = maxOfMaybes(
60 | bboxes.top.map((top, i) => maybeAdd(top, bboxes.height[i]))
61 | );
62 |
63 | const width = maybeSub(right, left);
64 | const height = maybeSub(bottom, top);
65 |
66 | return {
67 | transform: {
68 | translate: {
69 | x: maybeSub(props.x, left),
70 | y: maybeSub(props.y, top),
71 | },
72 | },
73 | bbox: { left, top, width, height },
74 | };
75 | };
76 |
77 | const paint = (paintProps: {
78 | bbox: BBox;
79 | transform: Transform;
80 | children: JSX.Element;
81 | }) => {
82 | return (
83 |
88 | {paintProps.children}
89 |
90 | );
91 | };
92 |
93 | return (
94 |
95 | {props.children}
96 | {props.rels?.() ?? null}
97 |
98 | );
99 | },
100 | { displayName: "Group" }
101 | );
102 |
103 | export default Group;
104 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/image.tsx:
--------------------------------------------------------------------------------
1 | import { JSX } from "solid-js/jsx-runtime";
2 | import { Layout } from "./layout";
3 | import { BBox, Id, Transform } from "./scenegraph";
4 | import { splitProps } from "solid-js";
5 | import withBluefish from "./withBluefish";
6 |
7 | export type ImageProps = JSX.ImageSVGAttributes & {
8 | name: Id;
9 | x?: number;
10 | y?: number;
11 | width?: number;
12 | height?: number;
13 | };
14 |
15 | export const Image = withBluefish(
16 | (props: ImageProps) => {
17 | const layout = () => {
18 | return {
19 | bbox: {
20 | left: 0,
21 | top: 0,
22 | width: props.width,
23 | height: props.height,
24 | },
25 | transform: {
26 | translate: {
27 | x: props.x,
28 | y: props.y,
29 | },
30 | },
31 | };
32 | };
33 |
34 | const paint = (paintProps: { bbox: BBox; transform: Transform }) => {
35 | const [_, rest] = splitProps(props, [
36 | "name",
37 | "x",
38 | "y",
39 | "width",
40 | "height",
41 | ]);
42 |
43 | return (
44 |
56 | );
57 | };
58 |
59 | return ;
60 | },
61 | { displayName: "Image" }
62 | );
63 |
64 | export default Image;
65 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/index.ts:
--------------------------------------------------------------------------------
1 | export { Align } from "./align";
2 | export type { AlignProps, Alignment1D, Alignment2D, AlignmentHorizontal, AlignmentVertical } from "./align";
3 |
4 | export { Arrow } from "./arrow";
5 | export type { ArrowProps } from "./arrow";
6 |
7 | export { Background } from "./background";
8 | export type { BackgroundProps } from "./background";
9 |
10 | export { Blob } from "./blob";
11 | export type { BlobProps } from "./blob";
12 |
13 | export { Bluefish } from "./bluefish";
14 | export type { BluefishProps } from "./bluefish";
15 |
16 | export { Circle } from "./circle";
17 | export type { CircleProps } from "./circle";
18 |
19 | export { StackV } from "./stackv";
20 | export type { StackVProps } from "./stackv";
21 |
22 | export { Distribute } from "./distribute";
23 | export type { DistributeProps } from "./distribute";
24 |
25 | export { Group } from "./group";
26 | export type { GroupProps } from "./group";
27 |
28 | export { Image } from "./image";
29 | export type { ImageProps } from "./image";
30 |
31 | export { Layout } from "./layout";
32 | export type { LayoutProps } from "./layout";
33 |
34 | export { LayoutFunction } from "./layoutFunction";
35 | export type { LayoutFunctionProps } from "./layoutFunction";
36 |
37 | export { Line } from "./line";
38 | export type { LineProps } from "./line";
39 |
40 | export { Rect } from "./rect";
41 | export type { RectProps } from "./rect";
42 |
43 | export { Ref } from "./ref";
44 | export type { RefProps } from "./ref";
45 |
46 | export { StackH } from "./stackh";
47 | export type { StackHProps } from "./stackh";
48 |
49 | export { Text } from "./text";
50 | export type { TextProps } from "./text/types";
51 |
52 | export { Path } from "./path";
53 | export type { PathProps } from "./path";
54 |
55 | export { withBluefish } from "./withBluefish";
56 | export type { WithBluefishProps } from "./withBluefish";
57 |
58 | export { createScenegraph, useScenegraph, UNSAFE_useScenegraph, ScenegraphContext } from "./scenegraph";
59 | export type {
60 | Id,
61 | BBox,
62 | BBoxOwners,
63 | Transform,
64 | TransformOwners,
65 | ScenegraphNode,
66 | Scenegraph,
67 | ScenegraphContextType,
68 | LayoutFn,
69 | } from "./scenegraph";
70 | export { createName, ScopeContext, ParentScopeIdContext } from "./createName";
71 | export type { Name, Scope } from "./createName";
72 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Dynamic } from "solid-js/web";
2 | import {
3 | For,
4 | JSX,
5 | ParentProps,
6 | createRenderEffect,
7 | createSignal,
8 | on,
9 | onCleanup,
10 | useContext,
11 | } from "solid-js";
12 | import {
13 | BBox,
14 | Transform,
15 | UNSAFE_useScenegraph,
16 | LayoutFn,
17 | Id,
18 | ScenegraphElement,
19 | resolveScenegraphElements,
20 | UNSAFE_asNode,
21 | } from "./scenegraph";
22 | import { IdContext } from "./withBluefish";
23 | import { ScopeContext } from "./createName";
24 | import { useError } from "./errorContext";
25 | import { LayoutUIDContext } from "./bluefish";
26 | import { createStore, produce } from "solid-js/store";
27 |
28 | export type LayoutProps = ParentProps<{
29 | name: Id;
30 | bbox?: BBox;
31 | layout: LayoutFn;
32 | paint: (props: {
33 | bbox: BBox;
34 | transform: Transform;
35 | children: JSX.Element;
36 | customData?: any;
37 | }) => JSX.Element;
38 | }>;
39 |
40 | export const Layout = (props: LayoutProps) => {
41 | const [_scope, setScope] = useContext(ScopeContext);
42 | const error = useError();
43 | const layoutUID = useContext(LayoutUIDContext);
44 | const [childLayouts, setChildLayouts] = createSignal<
45 | ((parentId: Id | null) => void)[]
46 | >([]);
47 |
48 | const [scenegraphInfo, setScenegraphInfo] = createStore({
49 | bbox: {},
50 | transform: { translate: {} },
51 | customData: {},
52 | });
53 |
54 | const { scenegraph, createNode, mergeBBoxAndTransform, createChildRepr } =
55 | UNSAFE_useScenegraph();
56 |
57 | // evaluate the child props before running the effect so that children's layout functions are
58 | // called before the parent's layout function
59 | // h/t Erik Demaine
60 | const jsx = (
61 | undefined}>
62 | {(() => {
63 | const childNodes = resolveScenegraphElements(props.children);
64 |
65 | setChildLayouts(() => {
66 | return childNodes.map((child) => {
67 | return child.layout;
68 | });
69 | });
70 |
71 | return (
72 |
73 | {(child) => child.jsx}
74 |
75 | );
76 | })()}
77 |
78 | );
79 |
80 | onCleanup(() => {
81 | // when the Layout node is destroyed, we need to clear any relevant scopes
82 | setScope(
83 | produce((scope) => {
84 | // filter out scopes that have this id as their layoutNode
85 | for (const key of Object.keys(scope) as Array) {
86 | if (scope[key].layoutNode === props.name) {
87 | delete scope[key];
88 | }
89 | }
90 | })
91 | );
92 | });
93 |
94 | createRenderEffect(
95 | on(
96 | () => layoutUID(),
97 | () => {
98 | const node = UNSAFE_asNode(
99 | scenegraph[props.name] ?? {
100 | type: "node",
101 | bbox: {},
102 | transform: { translate: {} },
103 | children: [],
104 | customData: {},
105 | }
106 | );
107 | setScenegraphInfo({
108 | bbox: node.bbox ?? {},
109 | transform: {
110 | translate: {
111 | x: node.transform?.translate?.x ?? 0,
112 | y: node.transform?.translate?.y ?? 0,
113 | },
114 | },
115 | customData: node.customData,
116 | });
117 | }
118 | )
119 | );
120 |
121 | const layout = (parentId: Id | null) => {
122 | createNode(props.name, parentId);
123 |
124 | for (const childLayout of childLayouts()) {
125 | childLayout(props.name);
126 | }
127 |
128 | const node = UNSAFE_asNode(scenegraph[props.name]);
129 |
130 | const { bbox, transform, customData } = props.layout(
131 | (node.children ?? []).map((childId: Id) =>
132 | createChildRepr(props.name, childId)
133 | )
134 | );
135 |
136 | mergeBBoxAndTransform(props.name, props.name, bbox, transform);
137 | node.customData = customData;
138 | };
139 |
140 | return {
141 | jsx,
142 | layout,
143 | } satisfies ScenegraphElement as unknown as JSX.Element;
144 | };
145 |
146 | export default Layout;
147 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/layoutFunction.tsx:
--------------------------------------------------------------------------------
1 | import { JSX, ParentProps, Show, createEffect, mergeProps, untrack } from "solid-js";
2 |
3 | import _, { get, startsWith } from "lodash";
4 | import Layout from "./layout";
5 | import withBluefish from "./withBluefish";
6 | import { Id, ChildNode, BBox } from "./scenegraph";
7 |
8 | export const maybeSub = (a: number, b: number) => (a !== undefined && b !== undefined ? a - b : undefined);
9 | const maybeMin = (a: number, b: number) => (a !== undefined && b !== undefined ? Math.min(a, b) : undefined);
10 | const maybeMax = (a: number, b: number) => (a !== undefined && b !== undefined ? Math.max(a, b) : undefined);
11 |
12 | export type LayoutFunctionProps = ParentProps<{
13 | f: (fromBBox: BBox, toBBox: BBox) => BBox;
14 | x?: number;
15 | y?: number;
16 | }>;
17 |
18 | export const LayoutFunction = withBluefish(
19 | (props: LayoutFunctionProps) => {
20 | props = mergeProps({}, props);
21 |
22 | const layout = (childNodes: ChildNode[]) => {
23 | const fromBBox = childNodes[0].bbox;
24 | const toBBox = childNodes[1].bbox;
25 |
26 | for (const [key, value] of Object.entries(props.f(fromBBox, toBBox))) {
27 | toBBox[key] = value;
28 | }
29 |
30 | return {
31 | transform: {
32 | translate: {
33 | x: props.x,
34 | y: props.y,
35 | },
36 | },
37 | bbox: toBBox,
38 | };
39 | };
40 |
41 | const paint = (paintProps) => {
42 | return {paintProps.children} ;
43 | };
44 |
45 | return (
46 |
47 | {props.children}
48 |
49 | );
50 | },
51 | { displayName: "LayoutFunction" }
52 | );
53 |
54 | export default LayoutFunction;
55 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/measure-text.tsx:
--------------------------------------------------------------------------------
1 | export type TextMeasurement = {
2 | left: number;
3 | right: number;
4 | width: number;
5 | fontTop: number;
6 | fontBottom: number;
7 | fontHeight: number;
8 | // position of text's alphabetic baseline assuming top is the origin
9 | baseline: number;
10 | fontDescent: number;
11 | actualDescent: number;
12 | };
13 |
14 | export function measureText(text: string, font: string): TextMeasurement {
15 | measureText.context.textBaseline = "alphabetic";
16 | measureText.context.font = font;
17 | const measurements = measureText.context.measureText(text);
18 | return {
19 | left: measurements.actualBoundingBoxLeft,
20 | right: measurements.actualBoundingBoxRight,
21 | fontTop: measurements.fontBoundingBoxAscent,
22 | fontBottom: measurements.fontBoundingBoxDescent,
23 | width:
24 | Math.abs(measurements.actualBoundingBoxLeft) +
25 | Math.abs(measurements.actualBoundingBoxRight),
26 | fontHeight:
27 | Math.abs(measurements.fontBoundingBoxAscent) +
28 | Math.abs(measurements.fontBoundingBoxDescent),
29 | baseline: Math.abs(measurements.fontBoundingBoxAscent),
30 | fontDescent: Math.abs(measurements.fontBoundingBoxDescent),
31 | actualDescent: Math.abs(measurements.actualBoundingBoxDescent),
32 | };
33 | }
34 | // static variable
35 | export namespace measureText {
36 | export const element = document.createElement("canvas");
37 | // puts canvas on screen. useful for debugging measurements
38 | /* element.width = 1000;
39 | element.height = 1000;
40 | document.body.appendChild(element); */
41 | export const context = element.getContext("2d")!;
42 | }
43 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/path.tsx:
--------------------------------------------------------------------------------
1 | import { mergeProps, splitProps } from "solid-js";
2 | import Layout from "./layout";
3 | import { BBox, Id, Transform } from "./scenegraph";
4 | import { maybeSub } from "./util/maybe";
5 | import { PaperScope } from "paper/dist/paper-core";
6 | import withBluefish from "./withBluefish";
7 | import { JSX } from "solid-js/jsx-runtime";
8 |
9 | export type PathProps = JSX.PathSVGAttributes & {
10 | name: Id;
11 | d: string;
12 | x?: number;
13 | y?: number;
14 | position?: "absolute" | "relative";
15 | };
16 |
17 | export const Path = withBluefish((rawProps: PathProps) => {
18 | const props = mergeProps(
19 | {
20 | "stroke-width": 3,
21 | stroke: "black",
22 | position: "relative",
23 | fill: "none",
24 | d: "",
25 | },
26 | rawProps
27 | );
28 |
29 | const canvas = document.createElement("canvas");
30 | const paperScope = new PaperScope();
31 | paperScope.setup(canvas);
32 |
33 | const layout = () => {
34 | const path = new paperScope.Path(props.d);
35 | const bounds = path.bounds;
36 |
37 | return {
38 | transform: {
39 | translate: {
40 | x: props.position === "absolute" ? 0 : maybeSub(props.x, bounds.left),
41 | y: props.position === "absolute" ? 0 : maybeSub(props.y, bounds.top),
42 | },
43 | },
44 | bbox: {
45 | left: bounds.left,
46 | top: bounds.top,
47 | width: bounds.width,
48 | height: bounds.height,
49 | },
50 | customData: {
51 | path,
52 | },
53 | };
54 | };
55 |
56 | const paint = (paintProps: {
57 | bbox: BBox;
58 | transform: Transform;
59 | customData?: { path: { pathData: string } };
60 | }) => {
61 | const [_, rest] = splitProps(props, ["name", "x", "y", "d", "position"]);
62 |
63 | return (
64 |
69 |
70 |
71 | );
72 | };
73 |
74 | return ;
75 | });
76 |
77 | export default Path;
78 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/performance-testing/README.md:
--------------------------------------------------------------------------------
1 | # Performance Testing
2 |
3 | Contains scripts and files for generating test data and testing code speed.
4 |
5 | ## File Structure
6 |
7 | For three diagrams (Python Tutor, Ohm Parser, and Insertion Sort), we measured how render time of the diagram scaled with the size/number of sub-elements in the diagram. Performance testing scripts for these 3 diagrams are in separate folders.
8 |
9 | Inside each folder is an associated Python file, prefixed with `generate_`, which is used to generate a `.ts` file containing the requisite props for the diagram (with `Props` suffixed). The code for generating each diagram is in the `tsx` file in the folder, and the diagram component has been configured to take the props generated by the Python file as input.
10 |
11 | ## Running
12 |
13 | In order to performance test the diagrams, there are a few steps to take:
14 |
15 | 1. Create an `App.tsx` file in `/public`. This can be done by duplicating the `App.template.tsx` file in the `/public` folder and renaming it to `App.tsx`.
16 |
17 | 2. Replace the following block of code in `App.tsx`
18 |
19 | ```js
20 |
21 | Duplicate this file and name it `App.tsx` to use the dev playground!
22 |
23 | ```
24 |
25 | with the name of the component that you are trying to test. The names of the components are as follows:
26 |
27 | - Insertion Sort: ` `
28 | - Ohm Parser: ` `
29 | - Python Tutor: ` `
30 |
31 | 3. Generate a props file for the diagram you are trying to test. This is done by downloading and running `python [the relevant script].py`. For example, for the Insertion Sort diagram, you would run download and run `python generate_insertion_sort.py`. Each script also has toggleable size parameters controlling the complexity of the rendered diagram.
32 |
33 | 4. Replace the `.ts` props file in the repository with the `.ts` props file generated by the python script.
34 |
35 | 5. [Optional] To find the number of nodes in the scenegraph of the diagram that you're generating, you should go into the relevant `.tsx` file and change the `debug={false}` parameter in to `debug={true}`. This will cause Bluefish to print out the total length of the scenegraph. After you find this number, to get more accurate performance results, make sure to change it back to `debug={false}`
36 |
37 | 6. Run `pnpm dev` to start a localhost server. Navigate to `http://localhost:3000/public/index.html` to see the rendered diagram. You should then be able to use browser tools or other means to measure the time it takes Bluefish to render the diagram.
38 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/performance-testing/insertionSort/InsertionSortTest.tsx:
--------------------------------------------------------------------------------
1 | import Bluefish from "../../bluefish";
2 | import { InsertionSort } from "../../example-gallery/insertion-sort";
3 | import { sortProps } from "./insertionSortProps";
4 |
5 | export const InsertionSortTest = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/performance-testing/insertionSort/generate_insertion_sort.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | def generate_random_array_and_write_to_ts(size):
4 | # Generate a random array of integers between 0 and 99 inclusive
5 | random_array = [random.randint(0, 99) for _ in range(size)]
6 |
7 | # Write the array to a TypeScript file
8 | with open("insertionSortProps.ts", "w") as file:
9 | file.write(f"export const sortProps = {random_array};\n")
10 |
11 |
12 | # -------------- PARAMETERS --------------
13 | num_elements = 10 # number of elements in array to generate
14 | # ----------------------------------------
15 |
16 | generate_random_array_and_write_to_ts(num_elements)
17 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/performance-testing/insertionSort/insertionSortProps.ts:
--------------------------------------------------------------------------------
1 | export const sortProps = [22, 32, 9, 47, 33, 67, 0, 57, 23, 21];
2 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/performance-testing/ohmParser/generate_ohm_expression.py:
--------------------------------------------------------------------------------
1 | # randomly generate an arithmetic expression with * and + operators (as well as parantheses) that can be used for testing the ohm parser
2 | import random
3 |
4 | def generate_string_expression(num_constants, max_open_parens, open_parens_thresh, close_parens_thresh):
5 | expression = ""
6 | open_parens = 0
7 | for i in range(num_constants):
8 | if open_parens < max_open_parens:
9 | if(random.random() > open_parens_thresh):
10 | # create another open parenthesis
11 | expression += "("
12 | open_parens += 1
13 |
14 | expression += str(random.randint(0, 10))
15 |
16 | if open_parens > 0:
17 | if(random.random() > close_parens_thresh):
18 | # remove an open parenthesis
19 | expression += ")"
20 | open_parens -= 1
21 |
22 | if i != num_constants - 1:
23 | expression += "+" if random.random() > 1 else "*" # randomly add an operator
24 |
25 | for i in range(open_parens):
26 | expression += ")"
27 |
28 | return expression
29 |
30 | # -------------- PARAMETERS --------------
31 | num_constants = 10 # number of constants in expression
32 | max_open_parens = 5 # maximum number of open parentheses at a time
33 | open_parens_thresh = 0.66 # probability that a opening parenthesis will appear before a constant in the expression
34 | close_parens_thresh = 0.66 # probability that a closing parenthesis will appear after a constant in the expression (given there are open parentheses)
35 | # ----------------------------------------
36 |
37 | expression = generate_string_expression(num_constants, max_open_parens, open_parens_thresh, close_parens_thresh)
38 | print("Expression Length:", len(expression))
39 | # Write the generated expression to a TypeScript file
40 | with open("ohm_expression.ts", "w") as ts_file:
41 | ts_file.write(f"export const expression = \"{expression}\";")
42 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/performance-testing/ohmParser/ohmParserTest.tsx:
--------------------------------------------------------------------------------
1 | import Bluefish from "../../bluefish";
2 | import { OhmParser } from "../../example-gallery/ohm-parser";
3 | import { expression } from "./ohmProps";
4 |
5 | export const OhmParserTest = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/performance-testing/ohmParser/ohmProps.ts:
--------------------------------------------------------------------------------
1 | export const expression = "8*3*(7)*1*(8)*0*2*(2*3*(3))";
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/performance-testing/pythonTutor/pythonTutorProgram.py:
--------------------------------------------------------------------------------
1 | var0 = [['20', '98', ['84'], '28'], '22']
2 | var1 = ['7']
3 | var2 = ['24', var0, '81']
4 | var3 = ['76', '64', var0]
5 | var4 = [var2, '79']
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/performance-testing/pythonTutor/pythonTutorProps.ts:
--------------------------------------------------------------------------------
1 | export const pythonProps = {
2 | "stack": [
3 | {
4 | "variable": "var0",
5 | "value": {
6 | "type": "pointer",
7 | "value": 0
8 | }
9 | },
10 | {
11 | "variable": "var1",
12 | "value": {
13 | "type": "pointer",
14 | "value": 3
15 | }
16 | },
17 | {
18 | "variable": "var2",
19 | "value": {
20 | "type": "pointer",
21 | "value": 4
22 | }
23 | },
24 | {
25 | "variable": "var3",
26 | "value": {
27 | "type": "pointer",
28 | "value": 5
29 | }
30 | },
31 | {
32 | "variable": "var4",
33 | "value": {
34 | "type": "pointer",
35 | "value": 6
36 | }
37 | }
38 | ],
39 | "heap": [
40 | {
41 | "type": "tuple",
42 | "values": [
43 | {
44 | "type": "pointer",
45 | "value": 1
46 | },
47 | "22"
48 | ]
49 | },
50 | {
51 | "type": "tuple",
52 | "values": [
53 | "20",
54 | "98",
55 | {
56 | "type": "pointer",
57 | "value": 2
58 | },
59 | "28"
60 | ]
61 | },
62 | {
63 | "type": "tuple",
64 | "values": [
65 | "84"
66 | ]
67 | },
68 | {
69 | "type": "tuple",
70 | "values": [
71 | "7"
72 | ]
73 | },
74 | {
75 | "type": "tuple",
76 | "values": [
77 | "24",
78 | {
79 | "type": "pointer",
80 | "value": 0
81 | },
82 | "81"
83 | ]
84 | },
85 | {
86 | "type": "tuple",
87 | "values": [
88 | "76",
89 | "64",
90 | {
91 | "type": "pointer",
92 | "value": 0
93 | }
94 | ]
95 | },
96 | {
97 | "type": "tuple",
98 | "values": [
99 | {
100 | "type": "pointer",
101 | "value": 4
102 | },
103 | "79"
104 | ]
105 | }
106 | ],
107 | "heapArrangement": [
108 | [
109 | 0,
110 | 1,
111 | 2
112 | ],
113 | [
114 | 3
115 | ],
116 | [
117 | 4
118 | ],
119 | [
120 | 5
121 | ],
122 | [
123 | 6
124 | ]
125 | ]
126 | };
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/performance-testing/pythonTutor/pythonTutorTest.tsx:
--------------------------------------------------------------------------------
1 | import Bluefish from "../../bluefish";
2 | import { PythonTutor } from "../../python-tutor/python-tutor";
3 | import { pythonProps } from "./pythonTutorProps";
4 |
5 | export const PythonTutorTest = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/plot/dot.tsx:
--------------------------------------------------------------------------------
1 | import { For, ParentProps } from "solid-js";
2 | import { JSX } from "solid-js/jsx-runtime";
3 | import withBluefish from "../withBluefish";
4 | import Circle from "../circle";
5 | import { usePlotContext } from "./plot";
6 | import Group from "../group";
7 |
8 | export type DotProps = ParentProps<
9 | Omit<
10 | JSX.CircleSVGAttributes,
11 | "cx" | "cy" | "fill" | "width" | "height" | "label"
12 | > & {
13 | x: keyof T;
14 | y: keyof T;
15 | color?: keyof T;
16 | stroke?: keyof T;
17 | label?:
18 | | keyof T
19 | | {
20 | field: keyof T;
21 | avoid: Symbol[];
22 | };
23 | data?: T[];
24 | }
25 | >;
26 |
27 | export const Dot = withBluefish((props: DotProps) => {
28 | const plotContext = usePlotContext();
29 |
30 | // const resolvedX = () => plotContext.data.map((datum: any) => datum[props.x]);
31 | // const resolvedY = () => plotContext.data.map((datum: any) => datum[props.y]);
32 | // const resolvedColor = () =>
33 | // plotContext.data.map((datum: any) => datum[props.color]);
34 |
35 | // const mappedX = () => resolvedX().map(plotContext.scales.x());
36 | // const mappedY = () => resolvedY().map(plotContext.scales.y());
37 | // const mappedColor = () => resolvedColor()?.map(plotContext.scales.color());
38 |
39 | return (
40 |
41 |
42 | {(datum) => {
43 | return (
44 |
50 | );
51 | }}
52 |
53 |
54 | );
55 | });
56 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/plot/plot.tsx:
--------------------------------------------------------------------------------
1 | import { ParentProps, createContext, splitProps, useContext } from "solid-js";
2 | import { Id } from "../scenegraph";
3 | import withBluefish from "../withBluefish";
4 | import Group from "../group";
5 |
6 | export type Scale = any;
7 |
8 | export type PlotProps = ParentProps<{
9 | name: Id;
10 | width?: number;
11 | height?: number;
12 | x?: Scale;
13 | y?: Scale;
14 | color?: Scale;
15 | data?: any;
16 | }>;
17 |
18 | export type PlotContextValue = {
19 | data?: any;
20 | scales: { [key in string /* Scale */]: any };
21 | };
22 |
23 | const PlotContext = createContext();
24 |
25 | export const usePlotContext = () => {
26 | const context = useContext(PlotContext);
27 |
28 | if (context === undefined) {
29 | throw new Error(
30 | "This component must be used within a containing Plot component."
31 | );
32 | }
33 |
34 | return context;
35 | };
36 |
37 | export const Plot = withBluefish((props: PlotProps) => {
38 | return (
39 |
57 | {props.children}
58 |
59 | );
60 | });
61 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/python-tutor/elm-tuple.tsx:
--------------------------------------------------------------------------------
1 | import { createUniqueId } from "solid-js";
2 | import { Align } from "../align";
3 | import { Group } from "../group";
4 | import { Rect } from "../rect";
5 | import { Ref } from "../ref";
6 | import { Id } from "../scenegraph";
7 | import { Text } from "../text";
8 | import { Value } from "./types";
9 | import { createName } from "../createName";
10 | import withBluefish from "../withBluefish";
11 |
12 | export type ElmTupleProps = {
13 | name?: Id;
14 | tupleIndex: string;
15 | tupleData: { type: string; value: Value };
16 | };
17 |
18 | export const ElmTuple = withBluefish((props: ElmTupleProps) => {
19 | const fontFamily = "verdana, arial, helvetica, sans-serif";
20 |
21 | const boxName = createName("box");
22 | const labelName = createName("label");
23 | const valName = createName("val");
24 |
25 | return (
26 |
27 |
34 |
40 | {props.tupleIndex}
41 |
42 | {props.tupleData.type === "string" ? (
43 |
49 | {props.tupleData.value as string}
50 |
51 | ) : (
52 |
53 | {""}
54 |
55 | )}
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | );
66 | });
67 |
68 | export default ElmTuple;
69 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/python-tutor/global-frame.tsx:
--------------------------------------------------------------------------------
1 | import { For, createUniqueId } from "solid-js";
2 | import { Id } from "../scenegraph";
3 | import Rect from "../rect";
4 | import Group from "../group";
5 | import Align from "../align";
6 | import Ref from "../ref";
7 | import { StackSlot } from "./stack-slot";
8 | import Distribute from "../distribute";
9 | import Text from "../text";
10 | import { Value } from "./types";
11 | import { createName } from "../createName";
12 | import { StackV } from "../stackv";
13 | import withBluefish from "../withBluefish";
14 |
15 | export type GlobalFrameProps = {
16 | name?: Id;
17 | variables: { variable: string; value: Value }[];
18 | };
19 |
20 | export const GlobalFrame = withBluefish((props: GlobalFrameProps) => {
21 | const frameName = createName("frame");
22 | const frameBorderName = createName("frameBorder");
23 | const labelName = createName("label");
24 | const frameVariablesName = createName("frameVariables");
25 | const stackSlotNames = props.variables.map((_, i) =>
26 | createName(`stackSlot-${i}`)
27 | );
28 |
29 | // Font declaration
30 | const fontFamily = "Andale mono, monospace";
31 |
32 | return (
33 | (
35 | <>
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | >
49 | )}
50 | >
51 | {/* Global Frame and relevant text */}
52 |
53 |
54 | {/* TODO: there is a bug where the text is showing up lower than I expect it to... */}
55 |
61 | Global Frame
62 |
63 |
64 |
65 | {(variable, i) => (
66 |
71 | )}
72 |
73 |
74 |
75 | );
76 | });
77 |
78 | export default GlobalFrame;
79 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/python-tutor/heap-object.tsx:
--------------------------------------------------------------------------------
1 | import Group from "../group";
2 | import Text from "../text";
3 | import { Id } from "../scenegraph";
4 | import Distribute from "../distribute";
5 | import Ref from "../ref";
6 | import Align from "../align";
7 | import { For, createUniqueId } from "solid-js";
8 | import ElmTuple from "./elm-tuple";
9 | import withBluefish from "../withBluefish";
10 | import { Value } from "./types";
11 | import { createName } from "../createName";
12 | import { StackH } from "../stackh";
13 | import { StackV } from "../stackv";
14 |
15 | export type ObjectProps = {
16 | name: Id;
17 | objectType: string;
18 | objectValues: {
19 | type: string;
20 | value: Value;
21 | }[];
22 | };
23 |
24 | export const HeapObject = withBluefish((props: ObjectProps) => {
25 | const fontFamily = "verdana, arial, helvetica, sans-serif";
26 |
27 | const objectTypeName = createName("objectType");
28 | const objectRefName = createName("objectRef");
29 |
30 | const elmNames = props.objectValues.map((_, i) => createName(`elm-${i}`));
31 |
32 | return (
33 |
34 |
40 | {props.objectType}
41 |
42 |
43 |
44 | {(elementData, index) => (
45 |
50 | )}
51 |
52 |
53 |
54 | );
55 | });
56 |
57 | export default HeapObject;
58 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/python-tutor/heap.tsx:
--------------------------------------------------------------------------------
1 | import { For } from "solid-js";
2 | import { Align } from "../align";
3 | import { Distribute } from "../distribute";
4 | import { Group } from "../group";
5 | import { Rect } from "../rect";
6 | import { Arrow } from "../arrow";
7 | import { Ref } from "../ref";
8 | import { Id } from "../scenegraph";
9 | import withBluefish from "../withBluefish";
10 | import { HeapObject } from "./heap-object";
11 | import { Address, HeapObject as HeapObjectType, formatValue } from "./types";
12 | import { StackH } from "../stackh";
13 | import { StackV } from "../stackv";
14 | import { createName } from "../createName";
15 |
16 | export type HeapProps = {
17 | name: Id;
18 | heap: HeapObjectType[];
19 | heapArrangement: (Address | null)[][];
20 | };
21 |
22 | export const Heap = withBluefish((props: HeapProps) => {
23 | const addressNames = props.heap.map((_, i) => createName(`address-${i}`));
24 |
25 | return (
26 |
27 |
28 |
29 | {(row, index) => (
30 |
31 |
32 | {(address) =>
33 | address === null ? (
34 |
40 | ) : (
41 | ({
45 | type: typeof value === "string" ? "string" : "pointer",
46 | value: formatValue(value),
47 | }))}
48 | />
49 | )
50 | }
51 |
52 |
53 | )}
54 |
55 |
56 |
57 | {/* Add arrows between heap objects */}
58 |
59 | {(heapObject, address) => (
60 |
61 | {(elmTupleValue, elmTupleIndex) => {
62 | // TODO: probably should just box every value to make this simpler
63 | if (
64 | typeof elmTupleValue === "object" &&
65 | "type" in elmTupleValue &&
66 | elmTupleValue.type === "pointer"
67 | ) {
68 | return (
69 |
76 |
83 |
86 |
87 | );
88 | }
89 | }}
90 |
91 | )}
92 |
93 |
94 | );
95 | });
96 |
97 | export default Heap;
98 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/python-tutor/python-tutor.tsx:
--------------------------------------------------------------------------------
1 | import { For } from "solid-js";
2 | import Arrow from "../arrow";
3 | import { createName } from "../createName";
4 | import Group from "../group";
5 | import Ref from "../ref";
6 | import { StackH } from "../stackh";
7 | import withBluefish, { WithBluefishProps } from "../withBluefish";
8 | import GlobalFrame from "./global-frame";
9 | import Heap from "./heap";
10 | import { StackSlot, HeapObject, Address } from "./types";
11 |
12 | type PythonTutorProps = WithBluefishProps<{
13 | stack: StackSlot[];
14 | heap: HeapObject[];
15 | heapArrangement: (Address | null)[][];
16 | debug?: boolean;
17 | }>;
18 |
19 | export const PythonTutor = withBluefish((props: PythonTutorProps) => {
20 | const globalFrameName = createName("globalFrame");
21 | const heapName = createName("heap");
22 |
23 | // Maps object number to the ID of the corresponding heap object
24 | // This will help generate Arrows between objects
25 | const objectIdToComponentId = new Map();
26 | props.heapArrangement.forEach((row, rowIndex) => {
27 | row.forEach((obj, colIndex) => {
28 | if (obj !== null) {
29 | objectIdToComponentId.set(obj, `row${rowIndex}_col${colIndex}`);
30 | }
31 | });
32 | });
33 |
34 | return (
35 | (
37 | <>
38 | {/* Make arrows from stack slots to heap objects */}
39 |
40 | {(stackSlot, stackSlotIndex) =>
41 | typeof stackSlot.value === "object" &&
42 | "type" in stackSlot.value &&
43 | stackSlot.value.type === "pointer" ? (
44 |
52 |
59 |
66 |
67 | ) : null
68 | }
69 |
70 | >
71 | )}
72 | >
73 |
74 |
75 |
80 |
81 |
82 | );
83 | });
84 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/python-tutor/stack-slot.tsx:
--------------------------------------------------------------------------------
1 | import Group from "../group";
2 | import Rect from "../rect";
3 | import Align from "../align";
4 | import Distribute from "../distribute";
5 | import Ref from "../ref";
6 | import Text from "../text";
7 | import { createUniqueId } from "solid-js";
8 | import { Id } from "../scenegraph";
9 | import { Pointer } from "./types";
10 | import withBluefish from "../withBluefish";
11 | import { createName } from "../createName";
12 |
13 | export type StackSlotProps = {
14 | name?: Id;
15 | variable: string;
16 | value: string | Pointer;
17 | };
18 |
19 | export const StackSlot = withBluefish((props: StackSlotProps) => {
20 | const fontFamily = "verdana, arial, helvetica, sans-serif";
21 |
22 | const boxName = createName("box");
23 | const nameName = createName("name");
24 | const valueName = createName("value");
25 |
26 | return (
27 |
28 |
29 |
30 | {props.variable}
31 |
32 | {/* TODO: if we only align or distribute on a single dimension, then their bounding boxes should be null or something along the unconstrained dimension... */}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {typeof props.value === "string" ? (
51 |
52 |
53 | {props.value}
54 |
55 |
56 |
57 | ) : (
58 |
59 |
60 | {""}
61 |
62 |
63 |
64 | )}
65 |
66 | );
67 | });
68 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/python-tutor/types.tsx:
--------------------------------------------------------------------------------
1 | export type Address = number;
2 |
3 | export type Pointer = { type: "pointer"; value: Address };
4 |
5 | export const pointer = (value: Address): Pointer => ({
6 | type: "pointer",
7 | value,
8 | });
9 |
10 | export type Value = string | number | Pointer;
11 |
12 | export type StackSlot = {
13 | variable: string;
14 | value: Value;
15 | };
16 |
17 | export const formatValue = (value: Value): string | number => {
18 | let formattedValue: string | number;
19 | if (typeof value === "string" || typeof value === "number") {
20 | formattedValue = `${value}`;
21 | } else {
22 | formattedValue = value.value;
23 | }
24 | return formattedValue;
25 | };
26 |
27 | export const stackSlot = (variable: string, value: Value): StackSlot => {
28 | return {
29 | variable,
30 | value: value,
31 | };
32 | };
33 |
34 | export type Tuple = { type: "tuple"; values: Value[] };
35 |
36 | export const tuple = (values: Value[]): Tuple => ({ type: "tuple", values });
37 |
38 | export type HeapObject = Tuple;
39 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/rect.tsx:
--------------------------------------------------------------------------------
1 | import { JSX } from "solid-js/jsx-runtime";
2 | import { Layout } from "./layout";
3 | import { BBox, Id, Transform } from "./scenegraph";
4 | import { splitProps } from "solid-js";
5 | import withBluefish from "./withBluefish";
6 |
7 | export type RectProps = JSX.RectSVGAttributes & {
8 | name: Id;
9 | x?: number;
10 | y?: number;
11 | width?: number;
12 | height?: number;
13 | };
14 |
15 | export const Rect = withBluefish(
16 | (props: RectProps) => {
17 | const layout = () => {
18 | return {
19 | bbox: {
20 | left: 0,
21 | top: 0,
22 | width: props.width,
23 | height: props.height,
24 | },
25 | transform: {
26 | translate: {
27 | x: props.x,
28 | y: props.y,
29 | },
30 | },
31 | };
32 | };
33 |
34 | const paint = (paintProps: { bbox: BBox; transform: Transform }) => {
35 | const [_, rest] = splitProps(props, [
36 | "name",
37 | "x",
38 | "y",
39 | "width",
40 | "height",
41 | ]);
42 |
43 | return (
44 |
56 | );
57 | };
58 |
59 | return ;
60 | },
61 | { displayName: "Rect" }
62 | );
63 |
64 | export default Rect;
65 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/ref.tsx:
--------------------------------------------------------------------------------
1 | import { onCleanup, useContext } from "solid-js";
2 | import type { JSX } from "solid-js";
3 | import { Id, UNSAFE_useScenegraph, ScenegraphElement } from "./scenegraph";
4 | import withBluefish from "./withBluefish";
5 | import { Name, Scope, ScopeContext } from "./createName";
6 | import { useError } from "./errorContext";
7 | import { produce } from "solid-js/store";
8 |
9 | export type Selection = Id | [Id, ...Name[]];
10 | export type NormalizedSelection = [Id, ...Name[]];
11 |
12 | export type RefProps = {
13 | name: Id;
14 | select: Selection;
15 | };
16 |
17 | export const normalizeSelection = (select: Selection): NormalizedSelection => {
18 | if (Array.isArray(select)) {
19 | return select;
20 | } else {
21 | return [select];
22 | }
23 | };
24 |
25 | // TODO: probably scopeIds and layoutIds should have different types...
26 | export const resolveSelection = (
27 | scope: Scope,
28 | select: NormalizedSelection
29 | ): Id => {
30 | const [id, ...names] = select;
31 |
32 | let currId = id;
33 |
34 | if (!(id in scope)) {
35 | throw new Error(
36 | `Could not find ${id}. Available names: ${Object.keys(scope).join(", ")}`
37 | );
38 | }
39 |
40 | for (const name of names) {
41 | const child = scope[currId].children[name];
42 | if (child === undefined) {
43 | console.log(JSON.parse(JSON.stringify(scope)));
44 | throw new Error(
45 | `Could not find ${name} in ${currId}. Available names: ${Object.keys(
46 | scope[currId].children
47 | ).join(", ")}`
48 | );
49 | }
50 | currId = child;
51 | }
52 |
53 | const layoutId = scope[currId].layoutNode;
54 |
55 | if (layoutId === undefined) {
56 | console.log(JSON.parse(JSON.stringify(scope)));
57 | throw new Error(
58 | `Could not find layout node for ${currId}. Available names: ${Object.keys(
59 | scope[currId].children
60 | ).join(", ")}`
61 | );
62 | }
63 |
64 | return layoutId;
65 | };
66 |
67 | export const Ref = withBluefish(
68 | (props: RefProps) => {
69 | const error = useError();
70 | const [scope, setScope] = useContext(ScopeContext);
71 | const { createRef } = UNSAFE_useScenegraph();
72 |
73 | const normalizedSelection = () => normalizeSelection(props.select);
74 |
75 | onCleanup(() => {
76 | // when the Ref node is destroyed, we need to clear any relevant scopes
77 | setScope(
78 | produce((scope) => {
79 | // filter out scopes that have this id as their layoutNode
80 | for (const key of Object.keys(scope) as Array) {
81 | if (scope[key].layoutNode === props.name) {
82 | delete scope[key];
83 | }
84 | }
85 | })
86 | );
87 | });
88 |
89 | // return <>>;
90 | return {
91 | jsx: <>>,
92 | layout: (parentId: Id | null) => {
93 | if (parentId === null) {
94 | throw new Error("Ref must be a child of a Layout");
95 | }
96 |
97 | createRef(
98 | props.name,
99 | resolveSelection(scope, normalizedSelection()),
100 | parentId
101 | );
102 | },
103 | } satisfies ScenegraphElement as unknown as JSX.Element;
104 | },
105 | { displayName: "Ref" }
106 | );
107 |
108 | export default Ref;
109 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stack.tsx:
--------------------------------------------------------------------------------
1 | import { withBluefish } from "./withBluefish";
2 | import { ParentProps, JSX, mergeProps, createMemo } from "solid-js";
3 | import { StackArgs, stackLayout } from "./stackLayout";
4 | import { BBox, Transform, ChildNode } from "./scenegraph";
5 | import Layout from "./layout";
6 |
7 | export type StackProps = ParentProps;
8 |
9 | export const Stack = withBluefish(
10 | (props: StackProps) => {
11 | // if both total and spacing are undefined, default spacing to 10
12 | const spacing = createMemo(() => {
13 | if (props.total === undefined && props.spacing === undefined) return 10;
14 | return props.spacing;
15 | });
16 |
17 | const alignment = createMemo(() => {
18 | if (props.direction === "vertical") return "centerX";
19 | if (props.direction === "horizontal") return "centerY";
20 | return undefined;
21 | });
22 |
23 | props = mergeProps(
24 | {
25 | get spacing() {
26 | return spacing();
27 | },
28 | get alignment() {
29 | return alignment();
30 | },
31 | },
32 | props
33 | );
34 |
35 | const layout = (childNodes: ChildNode[]) => {
36 | return stackLayout(props)(childNodes);
37 | };
38 |
39 | const paint = (paintProps: {
40 | bbox: BBox;
41 | transform: Transform;
42 | children: JSX.Element;
43 | }) => {
44 | return (
45 |
50 | {paintProps.children}
51 |
52 | );
53 | };
54 |
55 | return (
56 |
57 | {props.children}
58 |
59 | );
60 | },
61 | { displayName: "Stack" }
62 | );
63 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stackh.tsx:
--------------------------------------------------------------------------------
1 | import { ParentProps, JSX, mergeProps } from "solid-js";
2 | import { StackArgs, stackLayout } from "./stackLayout";
3 | import withBluefish from "./withBluefish";
4 | import Layout from "./layout";
5 | import { BBox, Transform, ChildNode } from "./scenegraph";
6 | import { AlignmentVertical } from "./align";
7 | import { Stack } from "./stack";
8 |
9 | export type StackHProps = ParentProps<
10 | Omit & { alignment?: AlignmentVertical }
11 | >;
12 |
13 | export const StackH = withBluefish(
14 | (props: StackHProps) => {
15 | const stackProps = mergeProps(
16 | {
17 | direction: "horizontal" as const,
18 | alignment: "centerY" as const,
19 | },
20 | props
21 | );
22 |
23 | return ;
24 | },
25 | { displayName: "StackH" }
26 | );
27 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stackv.tsx:
--------------------------------------------------------------------------------
1 | import { ParentProps, JSX, mergeProps } from "solid-js";
2 | import { StackArgs, stackLayout } from "./stackLayout";
3 | import withBluefish from "./withBluefish";
4 | import Layout from "./layout";
5 | import { BBox, Transform, ChildNode } from "./scenegraph";
6 | import { AlignmentHorizontal } from "./align";
7 | import { Stack } from "./stack";
8 |
9 | export type StackVProps = ParentProps<
10 | Omit & { alignment?: AlignmentHorizontal }
11 | >;
12 |
13 | export const StackV = withBluefish(
14 | (props: StackVProps) => {
15 | const stackProps = mergeProps(
16 | {
17 | direction: "vertical" as const,
18 | alignment: "centerX" as const,
19 | },
20 | props
21 | );
22 |
23 | return ;
24 | },
25 | { displayName: "StackV" }
26 | );
27 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/4.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "storybook-solidjs";
2 | import Bluefish from "../bluefish";
3 | import Group from "../group";
4 | import Rect from "../rect";
5 | import Distribute from "../distribute";
6 | import Align from "../align";
7 | import Ref from "../ref";
8 |
9 | const meta: Meta = {
10 | title: "Regression/#4",
11 | };
12 |
13 | export default meta;
14 | type Story = StoryObj;
15 |
16 | export const App: Story = {
17 | name: "#4",
18 | render: () => {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/41.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "storybook-solidjs";
2 | import Bluefish from "../bluefish";
3 | import Group from "../group";
4 | import Rect from "../rect";
5 | import Distribute from "../distribute";
6 | import Align from "../align";
7 | import Ref from "../ref";
8 | import { createSignal } from "solid-js";
9 | import Background from "../background";
10 | import { StackH } from "../stackh";
11 | import { Text } from "../text";
12 | import Circle from "../circle";
13 | import Arrow from "../arrow";
14 | import { within, userEvent } from "@storybook/test";
15 |
16 | const meta: Meta = {
17 | title: "Regression/#41",
18 | };
19 |
20 | export default meta;
21 | type Story = StoryObj;
22 |
23 | export const App: Story = {
24 | name: "#41",
25 | play: async ({ canvasElement }) => {
26 | const canvas = within(canvasElement);
27 |
28 | await userEvent.click(canvas.getByRole("button"));
29 | await userEvent.click(canvas.getByRole("button"));
30 | await userEvent.click(canvas.getByRole("button"));
31 | await userEvent.click(canvas.getByRole("button"));
32 | await userEvent.click(canvas.getByRole("button"));
33 | },
34 | render: () => {
35 | const [planet, setPlanet] = createSignal("mercury");
36 |
37 | const handler = () => {
38 | if (planet() == "mercury") {
39 | setPlanet("venus");
40 | } else if (planet() == "venus") {
41 | setPlanet("earth");
42 | } else if (planet() == "earth") {
43 | setPlanet("mars");
44 | } else {
45 | setPlanet("mercury");
46 | }
47 | };
48 |
49 | return (
50 | <>
51 | Click Me
52 | Current planet: {planet()}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | {planet()}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | >
78 | );
79 | },
80 | };
81 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/44.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "storybook-solidjs";
2 | import Bluefish from "../bluefish";
3 | import Group from "../group";
4 | import Rect from "../rect";
5 | import Distribute from "../distribute";
6 | import Align from "../align";
7 | import Ref from "../ref";
8 | import { createSignal } from "solid-js";
9 | import Background from "../background";
10 | import { StackH } from "../stackh";
11 | import { Text } from "../text";
12 | import Circle from "../circle";
13 | import Arrow from "../arrow";
14 |
15 | const meta: Meta = {
16 | title: "Regression/#44",
17 | };
18 |
19 | export default meta;
20 | type Story = StoryObj;
21 |
22 | export const App: Story = {
23 | name: "#44",
24 | render: () => {
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | },
45 | };
46 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/50.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "storybook-solidjs";
2 | import Bluefish from "../bluefish";
3 | import Group from "../group";
4 | import Rect from "../rect";
5 | import Distribute from "../distribute";
6 | import Align from "../align";
7 | import Ref from "../ref";
8 | import { For, createSignal } from "solid-js";
9 | import Background from "../background";
10 | import { StackH } from "../stackh";
11 | import { Text } from "../text";
12 | import Circle from "../circle";
13 | import Arrow from "../arrow";
14 | import { within, userEvent } from "@storybook/test";
15 |
16 | const meta: Meta = {
17 | title: "Regression/#50",
18 | };
19 |
20 | export default meta;
21 | type Story = StoryObj;
22 |
23 | export const App: Story = {
24 | name: "#50",
25 | play: async ({ canvasElement }) => {
26 | const canvas = within(canvasElement);
27 |
28 | await userEvent.click(canvas.getByRole("button"));
29 | await userEvent.click(canvas.getByRole("button"));
30 | await userEvent.click(canvas.getByRole("button"));
31 | await userEvent.click(canvas.getByRole("button"));
32 | await userEvent.click(canvas.getByRole("button"));
33 | },
34 | render: () => {
35 | const [count, setCount] = createSignal(0);
36 |
37 | return (
38 | <>
39 | setCount((count) => count + 1)}>
40 | Increment
41 |
42 |
43 |
44 |
45 |
46 | {(variable) => (
47 |
48 | )}
49 |
50 |
51 |
52 |
53 | >
54 | );
55 | },
56 | };
57 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/6.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "storybook-solidjs";
2 | import Bluefish from "../bluefish";
3 | import Group from "../group";
4 | import Rect from "../rect";
5 | import Distribute from "../distribute";
6 | import Text from "../text";
7 | import Align from "../align";
8 | import Ref from "../ref";
9 |
10 | const meta: Meta = {
11 | title: "Regression/#6",
12 | };
13 |
14 | export default meta;
15 | type Story = StoryObj;
16 |
17 | export const A: Story = {
18 | name: "#6/a",
19 | render: () => {
20 | return (
21 |
22 |
23 |
24 | one
25 | two
26 |
27 | three
28 |
29 |
30 | );
31 | },
32 | };
33 |
34 | export const B: Story = {
35 | name: "#6/b",
36 | render: () => {
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | );
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/CarsPlot.stories.tsx:
--------------------------------------------------------------------------------
1 | import { scaleLinear } from "d3-scale";
2 | import { nonNullData } from "../../examples/datasets/cars";
3 | import { Bluefish, withBluefish } from "..";
4 | import { Dot } from "../plot/dot";
5 | import { Plot } from "../plot/plot";
6 | import { Meta, StoryObj } from "storybook-solidjs";
7 |
8 | const meta: Meta = {
9 | title: "Example/Plot/Cars",
10 | };
11 |
12 | export default meta;
13 | type Story = StoryObj;
14 |
15 | export const Cars: Story = {
16 | render: () => {
17 | return (
18 |
19 |
22 | scaleLinear(
23 | [0, Math.max(...nonNullData.map((d) => +d.Horsepower))!],
24 | [0, 1000],
25 | )
26 | }
27 | y={(dims: any) =>
28 | scaleLinear(
29 | [0, Math.max(...nonNullData.map((d) => +d.Miles_per_Gallon))!],
30 | [500, 0],
31 | )
32 | }
33 | color={() => () => "cornflowerblue"}
34 | >
35 |
36 |
37 |
38 | );
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/IdInference.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "storybook-solidjs";
2 | import Bluefish from "../bluefish";
3 | import Group from "../group";
4 | import Rect from "../rect";
5 | import Ref from "../ref";
6 | import withBluefish from "../withBluefish";
7 | import { StackH } from "../stackh";
8 | import { StackV } from "../stackv";
9 |
10 | const meta: Meta = {
11 | title: "Feat/Id Inference",
12 | };
13 |
14 | export default meta;
15 | type Story = StoryObj;
16 |
17 | const CustomComponent = withBluefish(() => {
18 | return ;
19 | });
20 |
21 | export const App: Story = {
22 | name: "Id Inference",
23 | render: () => {
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/Math3ma.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import Align from "../align";
3 | import Arrow from "../arrow";
4 | import Background from "../background";
5 | import Bluefish from "../bluefish";
6 | import Circle from "../circle";
7 | import Distribute from "../distribute";
8 | import Group from "../group";
9 | import Ref from "../ref";
10 | import Text from "../text";
11 | import Rect from "../rect";
12 | import { Blob } from "../blob";
13 | import { PaperScope, Path, Point, Size } from "paper/dist/paper-core";
14 | import { StackV } from "../stackv";
15 | import { StackH } from "../stackh";
16 |
17 | const meta: Meta = {
18 | title: "Example/Math3ma",
19 | };
20 |
21 | export default meta;
22 | type Story = StoryObj;
23 |
24 | const canvas = document.createElement("canvas");
25 | const paperScope = new PaperScope();
26 | paperScope.setup(canvas);
27 | const dims = {
28 | x: 50,
29 | y: 25,
30 | width: 200,
31 | height: 100,
32 | };
33 | const myPath = new Path.Rectangle(new Point(dims.x, dims.y), new Size(dims.width, dims.height));
34 | // const myPath = new Path();
35 | // myPath.add(new Point(50, 75));
36 | // myPath.add(new Point(50, 25));
37 | // myPath.add(new Point(150, 25));
38 | // myPath.add(new Point(150, 75));
39 | myPath.insert(4, new Point(dims.x + dims.width / 2, dims.y + dims.height - (dims.height * 5) / 50));
40 |
41 | const dims2 = {
42 | x: 50,
43 | y: 50,
44 | width: 100,
45 | height: 50,
46 | };
47 | const myPath2 = new Path.Rectangle(new Point(dims2.x, dims2.y), new Size(dims2.width, dims2.height));
48 | myPath2.insert(2, new Point(dims2.x + dims2.width / 2, dims2.y + (dims2.height * 5) / 50));
49 | myPath2.insert(5, new Point(dims2.x + dims2.width / 2, dims2.y + dims2.height - (dims2.height * 5) / 50));
50 |
51 | export const Math3ma: Story = {
52 | name: "Math3ma",
53 | render: () => {
54 | return (
55 |
56 | }
62 | >
63 |
64 | }
66 | >
67 | Borel sets
68 |
69 |
70 | x
71 |
72 |
73 |
74 |
75 |
76 |
77 | {"f^{-1}(N) lives here!"}
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | );
86 | },
87 | };
88 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/Molecule.stories.tsx:
--------------------------------------------------------------------------------
1 | // Integrates with SmilesDrawer: https://pubs.acs.org/doi/10.1021/acs.jcim.7b00425
2 | // SmilesDrawer GitHub Repository: https://github.com/reymond-group/smilesDrawer
3 | // SmilesDrawer Interactive Playground: https://smilesdrawer.surge.sh/
4 |
5 | import type { Meta, StoryObj } from "storybook-solidjs";
6 | import { Bluefish } from "../bluefish";
7 | import { Molecule } from "../chemistry/molecule";
8 |
9 | const meta: Meta = {
10 | title: "Example/Molecule",
11 | };
12 |
13 | export default meta;
14 | type Story = StoryObj;
15 |
16 | export const AspirinMolecule: Story = {
17 | args: {
18 | id: "aspirin",
19 | chemicalFormula: "CC(OC1=C(C(=O)O)C=CC=C1)=O",
20 | ariaLabel: "Aspirin Molecule",
21 | },
22 | render: (props) => (
23 |
24 |
29 |
30 | ),
31 | };
32 |
33 | export const NicotineMolecule: Story = {
34 | args: {
35 | id: "nicotine",
36 | chemicalFormula: "CN1CCCC1C2=CN=CC=C2",
37 | ariaLabel: "Nicotine Molecule",
38 | },
39 | render: (props) => (
40 |
41 |
46 |
47 | ),
48 | };
49 |
50 | export const SucroseMolecule: Story = {
51 | args: {
52 | id: "sucrose",
53 | chemicalFormula: "C(C1C(C(C(C(O1)OC2(C(C(C(O2)CO)O)O)CO)O)O)O)O",
54 | ariaLabel: "Sucrose Molecule",
55 | },
56 | render: (props) => (
57 |
58 |
63 |
64 | ),
65 | };
66 |
67 | export const PenicillinMolecule: Story = {
68 | args: {
69 | id: "Penicillin",
70 | chemicalFormula: "CC1(C(N2C(S1)C(C2=O)NC(=O)CC3=CC=CC=C3)C(=O)O)C",
71 | ariaLabel: "Penicillin Molecule",
72 | },
73 | render: (props) => (
74 |
75 |
80 |
81 | ),
82 | };
83 |
84 | export const DiphenylEtherMolecule: Story = {
85 | args: {
86 | id: "diphenylether",
87 | chemicalFormula: "C1=CC=C(C=C1)OC2=CC=CC=C2",
88 | ariaLabel: "Diphenyl ether Molecule",
89 | },
90 | render: (props) => (
91 |
92 |
97 |
98 | ),
99 | };
100 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/PythonTutor.stories.tsx:
--------------------------------------------------------------------------------
1 | // Examples created based on Python Tutor: https://pythontutor.com/
2 |
3 | import type { Meta, StoryObj } from "storybook-solidjs";
4 | import { Bluefish } from "../bluefish";
5 | import { Address, HeapObject, StackSlot, pointer } from "../python-tutor/types";
6 | import { PythonTutor } from "../python-tutor/python-tutor";
7 |
8 | const meta: Meta = {
9 | title: "Example/PythonTutor",
10 | };
11 |
12 | export default meta;
13 | type Story = StoryObj;
14 |
15 | export const PythonTutorExample: Story = {
16 | args: {
17 | stack: [
18 | { variable: "c", value: pointer(0) },
19 | { variable: "d", value: pointer(1) },
20 | { variable: "x", value: "5" },
21 | ],
22 | heap: [
23 | {
24 | type: "tuple",
25 | values: ["1", pointer(1), pointer(2)],
26 | },
27 | { type: "tuple", values: ["1", "4"] },
28 | { type: "tuple", values: ["3", "10"] },
29 | ],
30 | heapArrangement: [
31 | [0, null, null],
32 | [null, 1, 2],
33 | ],
34 | },
35 | render: (props) => (
36 |
37 |
44 |
45 | ),
46 | };
47 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/README.md:
--------------------------------------------------------------------------------
1 | # Adding a test
2 |
3 | Make a new file called `.stories.tsx`.
4 |
5 | ```tsx
6 | const meta: Meta = {
7 | title: "Example//",
8 | };
9 |
10 | export default meta;
11 | type Story = StoryObj;
12 |
13 | export const Cars: Story = {
14 | render: () => {
15 | return
16 | }
17 | }
18 | ```
19 |
20 | These examples are checked for differences on every commit.
21 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/SimpleTree.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Bluefish } from "../bluefish";
3 | import { For, createUniqueId, mergeProps } from "solid-js";
4 | import withBluefish, { WithBluefishProps } from "../withBluefish";
5 | import { Group } from "../group";
6 | import { Rect } from "../rect";
7 | import { Ref } from "../ref";
8 | import { Text } from "../text";
9 | import { StackV } from "../stackv";
10 | import { StackH } from "../stackh";
11 | import { Arrow } from "../arrow";
12 | import { Align } from "../align";
13 | import { createName } from "../createName";
14 |
15 | const meta: Meta = {
16 | title: "Example/SimpleTree",
17 | };
18 |
19 | export default meta;
20 | type Story = StoryObj;
21 |
22 | // Component to render Tree node
23 | type NodeProps = WithBluefishProps<{
24 | value: string;
25 | }>;
26 |
27 | const Node = withBluefish((props: NodeProps) => {
28 | props = mergeProps({ value: "?" }, props);
29 | const valueName = createName("value");
30 | const outlineName = createName("outline");
31 |
32 | return (
33 |
34 |
43 |
44 | {props.value}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 | });
57 |
58 | // Component to render Tree
59 | type TreeData = {
60 | value?: string;
61 | subtrees?: TreeData[];
62 | };
63 |
64 | type TreeProps = WithBluefishProps;
65 |
66 | const Tree = withBluefish((props: TreeProps) => {
67 | // merge props.data with default
68 | props = mergeProps(
69 | {
70 | subtrees: [] as TreeData[],
71 | nodeId: createUniqueId(),
72 | value: "?",
73 | },
74 | props,
75 | );
76 |
77 | // merge props with default
78 | props = mergeProps({ id: createUniqueId() }, props);
79 |
80 | const nodeName = createName("node");
81 |
82 | // TODO: could probably get even fancier with this by turning objects into nested names somehow
83 | const subtreeNames = (props.subtrees ?? []).map((_, i) =>
84 | createName(`subtree${i}`),
85 | );
86 |
87 | return (
88 |
89 |
90 |
91 | {props.subtrees?.length ? (
92 | <>
93 |
94 |
95 |
96 |
97 | {(child, i) => }
98 |
99 |
100 |
101 |
102 | {(child, i) => (
103 |
104 |
105 |
106 |
107 | )}
108 |
109 | >
110 | ) : null}
111 |
112 | );
113 | });
114 |
115 | export const TwoLevelTree: Story = {
116 | args: {
117 | value: "A",
118 | subtrees: [{ value: "B" }, { value: "C" }],
119 | },
120 | render: (props) => (
121 |
122 |
123 |
124 | ),
125 | };
126 |
127 | export const ThreeLevelTree: Story = {
128 | args: {
129 | value: "A",
130 | subtrees: [
131 | {
132 | value: "B",
133 | subtrees: [{ value: "D" }, { value: "E" }],
134 | },
135 | {
136 | value: "C",
137 | subtrees: [{ value: "F" }, { value: "G" }],
138 | },
139 | ],
140 | },
141 | render: (props) => (
142 |
143 |
144 |
145 | ),
146 | };
147 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/VennDiagram.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Bluefish } from "../bluefish";
3 | import withBluefish, { WithBluefishProps } from "../withBluefish";
4 | import Group from "../group";
5 | import Text from "../text";
6 | import { StackH } from "../stackh";
7 | import Background from "../background";
8 | import Circle from "../circle";
9 | import Ref from "../ref";
10 |
11 | const meta: Meta = {
12 | title: "Example/VennDiagram",
13 | };
14 |
15 | export default meta;
16 | type Story = StoryObj;
17 |
18 | // Adding props here to allow for additions later
19 | type VennDiagramProps = WithBluefishProps<{}>;
20 |
21 | const VennDiagram = withBluefish((props: VennDiagramProps) => {
22 | return (
23 | (
25 | <>
26 | }>
27 |
28 |
29 |
30 | }>
31 |
32 |
33 |
34 | >
35 | )}
36 | >
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | });
45 |
46 | export const VennDiagramExample: Story = {
47 | args: {},
48 | render: (props) => (
49 |
50 |
51 |
52 | ),
53 | };
54 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/Align.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Align } from "../../align";
3 | import { Bluefish } from "../../bluefish";
4 | import { Rect } from "../../rect";
5 | import Group from "../../group";
6 | import Distribute from "../../distribute";
7 | import Ref from "../../ref";
8 | import { StackH } from "../../stackh";
9 | import Background from "../../background";
10 |
11 | /**
12 | * Bluefish's `Align` component contains many different options for aligning its children components. Taking in any number of components as children,
13 | * the alignments can be split into:
14 | * - Alignments that just set Y positions: `top`, `centerY`, `bottom`,
15 | * - Alignments that just set X positions: `left`, `centerX`, `right`,
16 | * - and Alignments that set both X and Y positions: `center`, `topLeft`, `topCenter`, `topRight`, `centerLeft`, `centerRight`, `bottomLeft`, `bottomMiddle`, `bottomRight`
17 | */
18 | const meta: Meta = {
19 | title: "Components/Align",
20 | component: Align,
21 | tags: ["autodocs"],
22 | argTypes: {
23 | alignment1: {
24 | control: "radio",
25 | options: ["top", "centerY", "bottom"],
26 | },
27 | alignment2: {
28 | control: "radio",
29 | options: ["left", "centerX", "right"],
30 | },
31 | alignment3: {
32 | control: "radio",
33 | options: [
34 | "center",
35 | "topLeft",
36 | "topCenter",
37 | "topRight",
38 | "centerLeft",
39 | "centerRight",
40 | "bottomLeft",
41 | "bottomMiddle",
42 | "bottomRight",
43 | ],
44 | },
45 | },
46 | };
47 |
48 | export default meta;
49 | type Story = StoryObj;
50 |
51 | export const AlignComponent: Story = {
52 | args: {
53 | alignment1: "centerY",
54 | alignment2: "right",
55 | alignment3: "center",
56 | },
57 | render: (props) => {
58 | return (
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | );
94 | },
95 | };
96 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/Arrow.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Arrow } from "../../arrow";
3 | import { Bluefish } from "../../bluefish";
4 | import { Rect } from "../../rect";
5 | /**
6 | * Bluefish's `Arrow` component creates an arrow between two components. It uses [perfect-arrows'](https://github.com/steveruizok/perfect-arrows#options) parameters
7 | * in order to do so.
8 | */
9 | const meta: Meta = {
10 | title: "Components/Arrow",
11 | component: Arrow,
12 | tags: ["autodocs"],
13 | argTypes: {
14 | stretch: {
15 | control: {
16 | step: 0.1,
17 | },
18 | },
19 | bow: {
20 | control: {
21 | step: 0.1,
22 | },
23 | },
24 | },
25 | };
26 |
27 | export default meta;
28 | type Story = StoryObj;
29 |
30 | export const ArrowComponent: Story = {
31 | args: {
32 | padStart: 5,
33 | padEnd: 10,
34 | bow: 0,
35 | flip: false,
36 | stretch: 0.1,
37 | },
38 | render: (props) => {
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 | );
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/Background.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Background } from "../../background";
3 | import { Bluefish } from "../../bluefish";
4 | import { Text } from "../../text";
5 | import { StackH } from "../../stackh";
6 | import Rect from "../../rect";
7 |
8 | /**
9 | * Bluefish's `Background` component creates a background around a set of children elements. The background element's sizing will be inferred from that
10 | * of the children components.
11 | *
12 | * It takes in the following params:
13 | * - `background`: What the background should be. Should be of type `(() => JSX.Element) | undefined`. Defaults to a rectangle
14 | * with no fill and black borders
15 | * - `padding`: How much padding should be placed around the children elements
16 | */
17 | const meta: Meta = {
18 | title: "Components/Background",
19 | component: Background,
20 | tags: ["autodocs"],
21 | };
22 |
23 | export default meta;
24 | type Story = StoryObj;
25 |
26 | export const BackgroundComponent: Story = {
27 | args: {
28 | padding: 20,
29 | },
30 | render: (props) => {
31 | return (
32 |
33 | {
36 | return ;
37 | }}
38 | >
39 | I have a background!
40 |
41 |
42 | );
43 | },
44 | };
45 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/Circle.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "storybook-solidjs";
2 | import Bluefish from "../../bluefish";
3 | import Group from "../../group";
4 | import Circle from "../../circle";
5 |
6 | const meta: Meta = {
7 | title: "Components/Circle",
8 | component: Circle,
9 | tags: ["autodocs"],
10 | argTypes: {
11 | fill: { control: "color" },
12 | stroke: { control: "color" },
13 | },
14 | };
15 |
16 | export default meta;
17 | type Story = StoryObj;
18 |
19 | /**
20 | * Creates a circle. Takes [SVG's Circle Element's Attributes](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle#attributes)
21 | * as parameters.
22 | */
23 | export const CircleComponent: Story = {
24 | args: {
25 | r: 15,
26 | cx: 20,
27 | cy: 20,
28 | fill: "red",
29 | "stroke-width": 3,
30 | stroke: "black",
31 | },
32 | render: (props) => {
33 | return (
34 |
35 |
36 |
37 | );
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/Distribute.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Distribute } from "../../distribute";
3 | import { Bluefish } from "../../bluefish";
4 | import Rect from "../../rect";
5 |
6 | /**
7 | * Bluefish's `Distribute` component distributes its children across one axis. Takes parameters:
8 | * - `direction`: either `horizontal` or `vertical`, determines which axis to distribute its children
9 | * - `spacing`: determines the amount of space to put between each child
10 | *
11 | * Each child of a `Distribute` should be positioned in its other axis e.g. with an `Align` or
12 | * another `Distribute`.
13 | */
14 | const meta: Meta = {
15 | title: "Components/Distribute",
16 | component: Distribute,
17 | tags: ["autodocs"],
18 | };
19 |
20 | export default meta;
21 | type Story = StoryObj;
22 |
23 | /**
24 | * Using `direction` = `horizontal`
25 | */
26 | export const HorizontalDistribution: Story = {
27 | args: {
28 | spacing: 20,
29 | },
30 | render: (props) => {
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | },
41 | };
42 |
43 | /**
44 | * Using `direction` = `vertical`
45 | */
46 | export const VerticalDistribution: Story = {
47 | args: {
48 | spacing: 20,
49 | },
50 | render: (props) => {
51 | return (
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | );
60 | },
61 | };
62 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/Ellipse.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "storybook-solidjs";
2 | import Bluefish from "../../bluefish";
3 | import Ellipse from "../../ellipse";
4 |
5 | const meta: Meta = {
6 | title: "Components/Ellipse",
7 | component: Ellipse,
8 | tags: ["autodocs"],
9 | argTypes: {
10 | fill: { control: "color" },
11 | stroke: { control: "color" },
12 | },
13 | };
14 |
15 | export default meta;
16 | type Story = StoryObj;
17 |
18 | /**
19 | * Creates a Ellipse. Takes [SVG's Ellipse Element Attributes](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse#attributes)
20 | * as parameters.
21 | */
22 | export const EllipseComponent: Story = {
23 | args: {
24 | rx: 15,
25 | ry: 20,
26 | cx: 20,
27 | cy: 20,
28 | fill: "red",
29 | "stroke-width": 3,
30 | stroke: "black",
31 | },
32 | render: (props) => {
33 | return (
34 |
35 |
36 |
37 | );
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/GraphLayered.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Rect } from "../../rect";
3 | import { Bluefish } from "../../bluefish";
4 | import Arrow from "../../arrow";
5 | import Ref from "../../ref";
6 | import { GraphLayered, Node, Edge } from "../../graphLayered";
7 |
8 | /**
9 | * Creates a layered graph using [dagre](https://github.com/dagrejs/dagre). The direction of the
10 | * graph can be specified as `left-right`, `right-left`, `top-bottom`, or `bottom-top`. This
11 | * component is best used for directed acyclic graphs (DAGs).
12 | *
13 | * Edges are specified using the `edges` prop. The names used in this prop are read off the children.
14 | */
15 | const meta: Meta = {
16 | title: "Components/GraphLayered",
17 | component: GraphLayered,
18 | tags: ["autodocs"],
19 | argTypes: {
20 | direction: {
21 | control: "radio",
22 | options: ["left-right", "right-left", "top-bottom", "bottom-top"],
23 | },
24 | },
25 | };
26 |
27 | export default meta;
28 | type Story = StoryObj;
29 |
30 | export const FirstStory: Story = {
31 | args: {
32 | direction: "left-right",
33 | },
34 | render: (props) => (
35 |
36 |
37 |
38 |
45 |
46 |
47 |
54 |
55 |
56 |
63 |
64 |
65 |
72 |
73 |
74 |
81 |
82 |
83 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | ),
119 | };
120 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/Image.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Image } from "../../image";
3 | import { Bluefish } from "../../bluefish";
4 |
5 | /**
6 | * Creates an image. Takes [SVGImageElement attributes](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image#attributes)
7 | * as parameters. Currently the image's width and height must be specified either by the component
8 | * or through a `Ref` component. They cannot be inferred from the intrinsic dimensions of the image.
9 | */
10 | const meta: Meta = {
11 | title: "Components/Image",
12 | component: Image,
13 | tags: ["autodocs"],
14 | argTypes: {},
15 | };
16 |
17 | export default meta;
18 | type Story = StoryObj;
19 |
20 | export const FirstStory: Story = {
21 | args: {
22 | width: 100,
23 | height: 100,
24 | href: "https://bluefishjs.org/bluefish-logo.png",
25 | },
26 | render: (props) => (
27 |
28 |
29 |
30 | ),
31 | };
32 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/Line.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Line } from "../../line";
3 | import { Bluefish } from "../../bluefish";
4 | import { Rect } from "../../rect";
5 | import { Ref } from "../../ref";
6 | /**
7 | * Bluefish's `Line` relation creates a line between two components. The first child component of the `Line` is the **source**,
8 | * which dictates the start point of the line. The second child component of the `Line` is the **target**, which dictates the end point of the line.
9 | */
10 | const meta: Meta = {
11 | title: "Components/Line",
12 | component: Line,
13 | tags: ["autodocs"],
14 | argTypes: {
15 | "stroke-dasharray": {
16 | description:
17 | "The specification of dashes and gaps for the line. See [here](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray) for more details.",
18 | },
19 | "stroke-width": {
20 | description: "The width of the line.",
21 | control: {
22 | type: "number",
23 | step: 1,
24 | },
25 | },
26 | stroke: {
27 | description: "The color of the line.",
28 | control: {
29 | type: "text",
30 | },
31 | },
32 |
33 | source: {
34 | description:
35 | "Either an array of two numbers, `[a, b]`, between 0 and 1, or `undefined`. If an array is specified, the start point of the line's x-coordinate \
36 | is a linear interpolation between the left and right edges of the source box. For example, if `a` is 0.25, then the \
37 | x-coordinate is a quarter of the way from the left edge. Similarly, its y-coordinate is a linear interpolation between \
38 | the top and bottom edges of the source box. For example, if `b` is 0.25, then the y-coordinate is a quarter of the way from the top edge.\
39 | If `undefined`, the start point of the line is inferred.",
40 | control: {
41 | type: "array" || "undefined",
42 | },
43 | },
44 | target: {
45 | description:
46 | "Either an array of two numbers, `[a, b]`, between 0 and 1, or `undefined`. If an array is specified, the end point of the line's x-coordinate \
47 | is a linear interpolation between the left and right edges of the source box. For example, if `a` is 0.25, then the \
48 | x-coordinate is a quarter of the way from the left edge. Similarly, its y-coordinate is a linear interpolation between \
49 | the top and bottom edges of the source box. For example, if `b` is 0.25, then the y-coordinate is a quarter of the way from the top edge.\
50 | If `undefined`, the end point of the line is inferred.",
51 | control: {
52 | type: "array" || "undefined",
53 | },
54 | },
55 | },
56 | };
57 |
58 | export default meta;
59 | type Story = StoryObj;
60 |
61 | export const LineComponent: Story = {
62 | args: {
63 | "stroke-width": 3,
64 | "stroke-dasharray": "0",
65 | stroke: "black",
66 | source: [1, 0.5],
67 | target: [0, 0.5],
68 | },
69 | render: (props) => {
70 | return (
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | );
80 | },
81 | };
82 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/Rect.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Rect } from "../../rect";
3 | import { Bluefish } from "../../bluefish";
4 |
5 | /**
6 | * Creates a rectangle. Takes [SVGRectElement attributes](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect#attributes)
7 | * as parameters.
8 | */
9 | const meta: Meta = {
10 | title: "Components/Rect",
11 | component: Rect,
12 | tags: ["autodocs"],
13 | argTypes: {
14 | fill: { control: "color" },
15 | },
16 | };
17 |
18 | export default meta;
19 | type Story = StoryObj;
20 |
21 | export const FirstStory: Story = {
22 | args: {
23 | width: 300,
24 | height: 200,
25 | x: 30,
26 | y: 30,
27 | fill: "red",
28 | },
29 | render: (props) => (
30 |
31 |
32 |
33 | ),
34 | };
35 |
36 | export const SecondStory: Story = {
37 | args: {
38 | width: 100,
39 | height: 100,
40 | x: 30,
41 | y: 30,
42 | fill: "steelblue",
43 | },
44 | render: (props) => (
45 |
46 |
47 |
48 | ),
49 | };
50 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/Stack.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { Stack } from "../../stack";
3 | import { Bluefish } from "../../bluefish";
4 | import Rect from "../../rect";
5 |
6 | /**
7 | * `Stack` combines `StackH` and `StackV` into a single component. It takes parameters:
8 | * - `direction`: either `horizontal` or `vertical`, determines which axis to stack its children
9 | * - `alignment`: determines how to align the children in the other axis. This should be a 1D align
10 | * like in `Align`.
11 | * - `spacing`: determines the amount of space to put between each child
12 | * - `total`: determines the total size of the stack in the stacking axis
13 | *
14 | * Spacing/Total Behavior:
15 | * - If only `spacing` is specified, each child will be placed `spacing` apart.
16 | * - If only `total` is specified, this spacing will be divided evenly among the children.
17 | * - If both `spacing` and `total` are specified, the children will be resized to fit the total
18 | * size with `spacing` between them.
19 | */
20 | const meta: Meta = {
21 | title: "Components/Stack",
22 | component: Stack,
23 | tags: ["autodocs"],
24 | argTypes: {
25 | alignment: { control: "radio", options: ["left", "centerX", "right"] },
26 | },
27 | };
28 |
29 | export default meta;
30 | type Story = StoryObj;
31 |
32 | export const Horizontal: Story = {
33 | args: {
34 | spacing: 20,
35 | alignment: "centerY",
36 | direction: "horizontal",
37 | },
38 | render: (props) => {
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 | },
49 | };
50 |
51 | export const Vertical: Story = {
52 | args: {
53 | spacing: 20,
54 | alignment: "centerX",
55 | direction: "vertical",
56 | },
57 | render: (props) => {
58 | return (
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | );
67 | },
68 | };
69 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/StackH.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { StackH } from "../../stackh";
3 | import { Bluefish } from "../../bluefish";
4 | import Rect from "../../rect";
5 |
6 | /**
7 | * `StackH` stacks its children horizontally. It takes parameters:
8 | * - `alignment`: determines how to align the children vertically. This should be a 1D vertical
9 | * alignment: `top`, `centerY`, or `bottom`.
10 | * - `spacing`: determines the amount of space to put between each child
11 | * - `total`: determines the total size of the stack in the stacking axis
12 | *
13 | * Spacing/Total Behavior:
14 | * - If only `spacing` is specified, each child will be placed `spacing` apart.
15 | * - If only `total` is specified, this spacing will be divided evenly among the children.
16 | * - If both `spacing` and `total` are specified, the children will be resized to fit the total
17 | * size with `spacing` between them.
18 | */
19 | const meta: Meta = {
20 | title: "Components/StackH",
21 | component: StackH,
22 | tags: ["autodocs"],
23 | argTypes: {
24 | alignment: { control: "radio", options: ["top", "centerY", "bottom"] },
25 | },
26 | };
27 |
28 | export default meta;
29 | type Story = StoryObj;
30 |
31 | export const DefinedSpacing: Story = {
32 | args: {
33 | spacing: 20,
34 | alignment: "centerY",
35 | },
36 | render: (props) => {
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | },
47 | };
48 |
49 | export const TotalSpacing: Story = {
50 | args: {
51 | total: 110,
52 | alignment: "centerY",
53 | },
54 | render: (props) => {
55 | return (
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | );
64 | },
65 | };
66 |
67 | export const TotalAndSpacing: Story = {
68 | args: {
69 | spacing: 20,
70 | total: 500,
71 | alignment: "centerY",
72 | },
73 | render: (props) => {
74 | return (
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | );
83 | },
84 | };
85 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/stories/components/StackV.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "storybook-solidjs";
2 | import { StackV } from "../../stackv";
3 | import { Bluefish } from "../../bluefish";
4 | import Rect from "../../rect";
5 |
6 | /**
7 | * `StackV` stacks its children vertically. It takes parameters:
8 | * - `alignment`: determines how to align the children horizontally. This should be a 1D horizontal
9 | * alignment: `left`, `centerX`, or `right`.
10 | * - `spacing`: determines the amount of space to put between each child
11 | * - `total`: determines the total size of the stack in the stacking axis
12 | *
13 | * Spacing/Total Behavior:
14 | * - If only `spacing` is specified, each child will be placed `spacing` apart.
15 | * - If only `total` is specified, this spacing will be divided evenly among the children.
16 | * - If both `spacing` and `total` are specified, the children will be resized to fit the total
17 | * size with `spacing` between them.
18 | */
19 | const meta: Meta = {
20 | title: "Components/StackV",
21 | component: StackV,
22 | tags: ["autodocs"],
23 | argTypes: {
24 | alignment: { control: "radio", options: ["left", "centerX", "right"] },
25 | },
26 | };
27 |
28 | export default meta;
29 | type Story = StoryObj;
30 |
31 | export const DefinedSpacing: Story = {
32 | args: {
33 | spacing: 20,
34 | alignment: "centerX",
35 | },
36 | render: (props) => {
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | },
47 | };
48 |
49 | export const TotalSpacing: Story = {
50 | args: {
51 | total: 110,
52 | alignment: "centerX",
53 | },
54 | render: (props) => {
55 | return (
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | );
64 | },
65 | };
66 |
67 | export const TotalAndSpacing: Story = {
68 | args: {
69 | spacing: 20,
70 | total: 100,
71 | alignment: "centerX",
72 | },
73 | render: (props) => {
74 | return (
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | );
83 | },
84 | };
85 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/text.tsx:
--------------------------------------------------------------------------------
1 | import { For, Ref, mergeProps, splitProps } from "solid-js";
2 | import { TextProps } from "./text/types";
3 | import useText from "./text/useText";
4 | import { BBox, Transform } from "./scenegraph";
5 | import Layout from "./layout";
6 | import computeBoundingBox from "./text/textBBox";
7 | import withBluefish from "./withBluefish";
8 | import splitAtDelimiters from "./text/splitAtDelimiters";
9 |
10 | const SVG_STYLE = { overflow: "visible" };
11 |
12 | export const Text = withBluefish(
13 | (props: TextProps) => {
14 | props = mergeProps(
15 | {
16 | // dx: 0,
17 | // dy: 0,
18 | "text-anchor": "start" as const,
19 | "vertical-anchor": "end" as const,
20 | "line-height": "1em",
21 | "cap-height": "0.71em", // Magic number from d3
22 | "font-family": "Alegreya Sans, sans-serif",
23 | "font-weight": 700,
24 | "font-size": "14",
25 | x: 0,
26 | y: 0,
27 | delimiters: [
28 | { left: "$$", right: "$$", display: true },
29 | { left: "\\(", right: "\\)", display: false },
30 | { left: "$", right: "$", display: false },
31 | { left: "\\[", right: "\\]", display: true },
32 | ],
33 | },
34 | props
35 | );
36 |
37 | const [_, textProps] = splitProps(props, [
38 | "name",
39 | "dx",
40 | "dy",
41 | "innerRef",
42 | "innerTextRef",
43 | "vertical-anchor",
44 | "angle",
45 | "line-height",
46 | "scaleToFit",
47 | "cap-height",
48 | "width",
49 | "delimiters",
50 | ]);
51 |
52 | // const textAndMathRegions = () =>
53 | // splitAtDelimiters(
54 | // props.children !== undefined ? `${props.children}` : "",
55 | // props.delimiters!
56 | // );
57 |
58 | const { wordsByLines, startDy, transform } = useText(props);
59 |
60 | const mergedProps = mergeProps(props, {
61 | get wordsByLines() {
62 | return wordsByLines();
63 | },
64 | get startDy() {
65 | return startDy();
66 | },
67 | get transform() {
68 | return transform();
69 | },
70 | });
71 |
72 | const layout = () => {
73 | const bbox = computeBoundingBox(mergedProps);
74 |
75 | return {
76 | bbox: {
77 | left: bbox.x,
78 | top: bbox.y,
79 | width: bbox.width,
80 | height: bbox.height,
81 | },
82 | transform: {
83 | translate: {
84 | x: props.dx !== undefined ? parseFloat(`${props.dx}`) : undefined,
85 | y: props.dy !== undefined ? parseFloat(`${props.dy}`) : undefined,
86 | },
87 | },
88 | };
89 | };
90 |
91 | const paint = (paintProps: { bbox: BBox; transform: Transform }) => {
92 | return (
93 |
98 | {wordsByLines().length > 0 ? (
99 |
104 |
105 | {(line, index) => (
106 |
111 | {line.words.join(" ")}
112 |
113 | )}
114 |
115 |
116 | ) : null}
117 |
118 | );
119 | };
120 |
121 | return ;
122 | },
123 | { displayName: "Text" }
124 | );
125 |
126 | export default Text;
127 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/text/README.md:
--------------------------------------------------------------------------------
1 | This text component is based very closely on visx's text component.
2 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/text/getStringDims.ts:
--------------------------------------------------------------------------------
1 | import memoize from "lodash/memoize";
2 | import { TextProps } from "./types";
3 |
4 | const MEASUREMENT_ELEMENT_ID = "__react_svg_text_measurement_id";
5 |
6 | function getStringDims(str: string, style?: TextProps) {
7 | try {
8 | // Calculate length of each word to be used to determine number of words per line
9 | let textEl = document.getElementById(
10 | MEASUREMENT_ELEMENT_ID
11 | ) as SVGTextElement | null;
12 | if (!textEl) {
13 | const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
14 | svg.setAttribute("aria-hidden", "true");
15 | svg.style.width = "0";
16 | svg.style.height = "0";
17 | svg.style.position = "absolute";
18 | svg.style.top = "-100%";
19 | svg.style.left = "-100%";
20 | svg.style.padding = "0";
21 | svg.style.margin = "0";
22 | textEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
23 | textEl.setAttribute("id", MEASUREMENT_ELEMENT_ID);
24 | svg.appendChild(textEl);
25 | document.body.appendChild(svg);
26 | }
27 |
28 | Object.assign(textEl.style, style);
29 | textEl.textContent = str;
30 | const width = textEl.getComputedTextLength();
31 |
32 | return {
33 | width,
34 | height: style !== undefined ? parseFloat(`${style["font-size"]}`) : 0,
35 | };
36 | } catch (e) {
37 | console.error(e);
38 | return null;
39 | }
40 | }
41 |
42 | export default memoize(getStringDims, (str, style) => {
43 | if (!style) return `${str}_`;
44 | const { children, name, ...rest } = style;
45 | return `${str}_${JSON.stringify(rest)}`;
46 | });
47 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/text/reduce-css-calc.d.ts:
--------------------------------------------------------------------------------
1 | declare module "reduce-css-calc" {
2 | export default function (_: any): string;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/text/splitAtDelimiters.ts:
--------------------------------------------------------------------------------
1 | /* Adapted from https://github.com/harunurhan/react-latex-next/blob/master/src/splitAtDelimiters.ts,
2 | which was in turn adapted from /contrib/auto-render/splitAtDelimiters.js at github.com/Khan/KaTeX */
3 |
4 | export type MathJaxData = {
5 | data: string;
6 | type: string;
7 | rawData?: string;
8 | display?: boolean;
9 | };
10 |
11 | export type Delimiter = {
12 | right: string;
13 | left: string;
14 | display: boolean;
15 | };
16 |
17 | function findEndOfMath(
18 | delimiterValue: string,
19 | text: string,
20 | startIndex: number
21 | ): number {
22 | let index = startIndex;
23 | let braceLevel = 0;
24 |
25 | const delimLength = delimiterValue.length;
26 |
27 | while (index < text.length) {
28 | const character = text[index];
29 |
30 | if (
31 | braceLevel <= 0 &&
32 | text.slice(index, index + delimLength) === delimiterValue
33 | ) {
34 | return index;
35 | } else if (character === "\\") {
36 | index++;
37 | } else if (character === "{") {
38 | braceLevel++;
39 | } else if (character === "}") {
40 | braceLevel--;
41 | }
42 |
43 | index++;
44 | }
45 |
46 | return -1;
47 | }
48 |
49 | function escapeRegex(text: string): string {
50 | return text.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
51 | }
52 |
53 | const amsRegex = /^\\begin{/;
54 |
55 | export default function splitAtDelimiters(
56 | text: string,
57 | delimiters: Delimiter[]
58 | ): MathJaxData[] {
59 | let index;
60 | const data = [];
61 |
62 | const regexLeft = new RegExp(
63 | "(" + delimiters.map((x) => escapeRegex(x.left)).join("|") + ")"
64 | );
65 |
66 | while (true) {
67 | index = text.search(regexLeft);
68 | if (index === -1) {
69 | break;
70 | }
71 | if (index > 0) {
72 | data.push({
73 | type: "text",
74 | data: text.slice(0, index),
75 | });
76 | text = text.slice(index); // now text starts with delimiter
77 | }
78 | // ... so this always succeeds:
79 | const i = delimiters.findIndex((delim) => text.startsWith(delim.left));
80 | index = findEndOfMath(delimiters[i].right, text, delimiters[i].left.length);
81 | if (index === -1) {
82 | break;
83 | }
84 | const rawData = text.slice(0, index + delimiters[i].right.length);
85 | const math = amsRegex.test(rawData)
86 | ? rawData
87 | : text.slice(delimiters[i].left.length, index);
88 | data.push({
89 | type: "math",
90 | data: math,
91 | rawData,
92 | display: delimiters[i].display,
93 | });
94 | text = text.slice(index + delimiters[i].right.length);
95 | }
96 |
97 | if (text !== "") {
98 | data.push({
99 | type: "text",
100 | data: text,
101 | });
102 | }
103 |
104 | return data;
105 | }
106 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/text/textBBox.tsx:
--------------------------------------------------------------------------------
1 | import { createEffect } from "solid-js";
2 | import { TextProps, WordsWithDims } from "./types";
3 | import useText from "./useText";
4 |
5 | type FnProps = TextProps & {
6 | wordsByLines: WordsWithDims[];
7 | startDy: string;
8 | transform: string;
9 | };
10 |
11 | function computeBoundingBox(props: FnProps): {
12 | x: number;
13 | y: number;
14 | width: number;
15 | height: number;
16 | } {
17 | // Compute width. This depends on the props.width, actual text width, and the scaleToFit prop.
18 | let width = () => {
19 | let width = props.width || 0;
20 | if (props.wordsByLines.length > 0) {
21 | const maxWidthLine = Math.max(
22 | ...props.wordsByLines.map((line) => line.width || 0)
23 | );
24 | if (props.scaleToFit) {
25 | width = Math.min(maxWidthLine, props.width || maxWidthLine);
26 | } else {
27 | width = maxWidthLine;
28 | }
29 | }
30 |
31 | // Adjust x and y for any transformations.
32 | // Note: This implementation only considers scaling transformations.
33 | // If other transformations are added (like skewing), this needs to be updated.
34 | if (props.transform) {
35 | const scaleMatch = () =>
36 | /matrix\(([^,]+),[^,]+,[^,]+,[^,]+,[^,]+,[^,]+\)/.exec(props.transform);
37 | if (scaleMatch()) {
38 | const scale = () => parseFloat(scaleMatch()![1]);
39 | width *= scale();
40 | // If height needs to be scaled similarly, add height *= scale;
41 | }
42 | }
43 |
44 | return width;
45 | };
46 |
47 | // height is the sum of each line's height, plus the space between each line.
48 | const height = () =>
49 | props.wordsByLines.reduce(
50 | (height, line, index) => height + (line.height || 0),
51 | 0
52 | );
53 |
54 | let x = () => (props.dx !== undefined ? parseFloat(`${props.dx}`) : 0);
55 | let y = () => {
56 | let y = props.dy !== undefined ? parseFloat(`${props.dy}`) : 0;
57 | // Adjust x and y based on vertical-anchor prop.
58 | switch (props["vertical-anchor"]) {
59 | case "start":
60 | y += parseFloat(props.startDy);
61 | break;
62 | case "middle":
63 | y += height() / 2;
64 | break;
65 | case "end":
66 | y -= height();
67 | break;
68 | default:
69 | break;
70 | }
71 | return y;
72 | };
73 |
74 | return {
75 | get x() {
76 | return x();
77 | },
78 | get y() {
79 | return y();
80 | },
81 | get width() {
82 | return width();
83 | },
84 | get height() {
85 | return height();
86 | },
87 | };
88 | }
89 |
90 | export default computeBoundingBox;
91 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/text/types.ts:
--------------------------------------------------------------------------------
1 | import { Ref } from "solid-js";
2 | import { JSX } from "solid-js/jsx-runtime";
3 | import { Id } from "../scenegraph";
4 | import { Delimiter } from "./splitAtDelimiters";
5 |
6 | type SVGTSpanProps = JSX.TSpanSVGAttributes;
7 | type SVGTextProps = JSX.TextSVGAttributes;
8 |
9 | type OwnProps = {
10 | /** className to apply to the SVGText element. */
11 | className?: string;
12 | /** Whether to scale the fontSize to accommodate the specified width. */
13 | scaleToFit?: boolean | "shrink-only";
14 | /** Rotational angle of the text. */
15 | angle?: number;
16 | /** Horizontal text anchor. */
17 | // "text-anchor"?: "start" | "middle" | "end" | "inherit";
18 | /** Vertical text anchor. */
19 | "vertical-anchor"?: "start" | "middle" | "end";
20 | /** Styles to be applied to the text (and used in computation of its size). */
21 | // style?: JSX.CSSProperties;
22 | /** Ref passed to the Text SVG element. */
23 | innerRef?: Ref;
24 | /** Ref passed to the Text text element */
25 | innerTextRef?: Ref;
26 | /** x position of the text. */
27 | x?: string | number;
28 | /** y position of the text. */
29 | y?: string | number;
30 | /** dx offset of the text. */
31 | dx?: string | number;
32 | /** dy offset of the text. */
33 | dy?: string | number;
34 | /** Desired "line height" of the text, implemented as y offsets. */
35 | "line-height"?: SVGTSpanProps["dy"];
36 | /** Cap height of the text. */
37 | "cap-height"?: string | number | undefined;
38 | /** Font size of text. */
39 | // "font-size"?: string;
40 | /** Font family of text. */
41 | // "font-family"?: string;
42 | /** Fill color of text. */
43 | // fill?: string;
44 | /** Maximum width to occupy (approximate as words are not split). */
45 | width?: number;
46 | /** String (or number coercible to one) to be styled and positioned. */
47 | children?: string | number;
48 |
49 | delimiters?: Delimiter[];
50 | };
51 |
52 | export type TextProps = OwnProps &
53 | Omit & { name: Id };
54 |
55 | export type compareFunction = (prev: T | undefined, next: T) => boolean;
56 |
57 | export interface WordsWithDims {
58 | words: string[];
59 | width?: number;
60 | height?: number;
61 | }
62 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/util/lca.ts:
--------------------------------------------------------------------------------
1 | export type Tree = { [key: string]: { parent: string | null } };
2 |
3 | export const getAncestorChain = (scenegraph: Tree, id: string): string[] => {
4 | const chain = [];
5 | let node = scenegraph[id];
6 | if (node === undefined) {
7 | throw new Error(`node ${id} doesn't exist`);
8 | }
9 | while (node.parent !== null) {
10 | chain.push(node.parent);
11 | node = scenegraph[node.parent];
12 | }
13 | return chain.reverse();
14 | };
15 |
16 | export const getLCAChain = (
17 | scenegraph: Tree,
18 | id1: string,
19 | id2: string
20 | ): string[] => {
21 | const chain1 = getAncestorChain(scenegraph, id1);
22 | const chain2 = getAncestorChain(scenegraph, id2);
23 |
24 | const lcaChain = [];
25 | for (let i = 0; i < Math.min(chain1.length, chain2.length); i++) {
26 | if (chain1[i] === chain2[i]) {
27 | lcaChain.push(chain1[i]);
28 | } else {
29 | break;
30 | }
31 | }
32 |
33 | return lcaChain;
34 | };
35 |
36 | // like getLCAChain, but returns the suffixes of the chains instead
37 | export const getLCAChainSuffixes = (
38 | scenegraph: Tree,
39 | id1: string,
40 | id2: string
41 | ): [string[], string[]] => {
42 | const chain1 = getAncestorChain(scenegraph, id1);
43 | const chain2 = getAncestorChain(scenegraph, id2);
44 |
45 | const lcaChain = [];
46 | for (let i = 0; i < Math.min(chain1.length, chain2.length); i++) {
47 | if (chain1[i] === chain2[i]) {
48 | lcaChain.push(chain1[i]);
49 | } else {
50 | break;
51 | }
52 | }
53 |
54 | return [chain1.slice(lcaChain.length), chain2.slice(lcaChain.length)];
55 | };
56 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/util/maybe.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Monadic functions for dealing with undefined values.
3 | Saves us a lot of boilerplate with undefined checks that make the code
4 | harder to read.
5 | */
6 |
7 | // TODO: merge these two functions once I'm confident they're the same
8 | export const maybeAdd = (a: number | undefined, b: number | undefined) =>
9 | a !== undefined && b !== undefined ? a + b : undefined;
10 |
11 | export const maybeAddAll = (...xs: (number | undefined)[]) => {
12 | if (xs.every((x) => x !== undefined)) {
13 | let sum = 0;
14 | for (const x of xs) {
15 | sum += x!;
16 | }
17 | return sum;
18 | } else {
19 | return undefined;
20 | }
21 | };
22 |
23 | export const maybeSub = (a: number | undefined, b: number | undefined) =>
24 | a !== undefined && b !== undefined ? a - b : undefined;
25 |
26 | export const maybeMul = (a: number | undefined, b: number | undefined) =>
27 | a !== undefined && b !== undefined ? a * b : undefined;
28 |
29 | export const maybeDiv = (a: number | undefined, b: number | undefined) =>
30 | a !== undefined && b !== undefined ? a / b : undefined;
31 |
32 | export const maybeMax = (xs: (number | undefined)[]) =>
33 | xs.every((x) => x !== undefined) ? Math.max(...(xs as number[])) : undefined;
34 |
35 | // only returns undefined if all values are undefined, otherwise skips undefined values
36 | export const maxOfMaybes = (xs: (number | undefined)[]) =>
37 | xs.every((x) => x === undefined)
38 | ? undefined
39 | : Math.max(...(xs.filter((x) => x !== undefined) as number[]));
40 |
41 | export const maybeMin = (xs: (number | undefined)[]) =>
42 | xs.every((x) => x !== undefined) ? Math.min(...(xs as number[])) : undefined;
43 |
44 | export const minOfMaybes = (xs: (number | undefined)[]) =>
45 | xs.every((x) => x === undefined)
46 | ? undefined
47 | : Math.min(...(xs.filter((x) => x !== undefined) as number[]));
48 |
49 | export const maybe = (x: T | undefined, f: (x: T) => U): U | undefined =>
50 | x !== undefined ? f(x) : undefined;
51 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/src/withBluefish.tsx:
--------------------------------------------------------------------------------
1 | import { Accessor, Component, createContext, createSignal, createUniqueId, useContext } from "solid-js";
2 | import type { JSX } from "solid-js";
3 | import { Id, ScenegraphElement, resolveScenegraphElements } from "./scenegraph";
4 | import { ParentScopeIdContext, ScopeContext } from "./createName";
5 | import { Dynamic } from "solid-js/web";
6 |
7 | export type WithBluefishProps = T & {
8 | name?: Id;
9 | };
10 |
11 | export const IdContext = createContext>(() => undefined);
12 |
13 | export function withBluefish(
14 | WrappedComponent: Component>,
15 | options?: { displayName?: string }
16 | ) {
17 | return (props: Omit & { name?: Id }) => {
18 | // scenegraph id
19 | const contextId = useContext(IdContext);
20 | const parentScopeId = useContext(ParentScopeIdContext);
21 | const displayNamePrefix = options?.displayName !== undefined ? `${options?.displayName}(` : "";
22 | const displayNameSuffix = options?.displayName !== undefined ? ")" : "";
23 | const genId = `${displayNamePrefix}${createUniqueId()}${displayNameSuffix}`;
24 | const genScopeId = `${displayNamePrefix}${createUniqueId()}${displayNameSuffix}`;
25 | // const id = () => props.name ?? contextId() ?? genId;
26 | const id = () => contextId() ?? genId;
27 | const scopeId = () => props.name ?? genScopeId;
28 |
29 | // component scope id
30 | const [scope, setScope] = useContext(ScopeContext);
31 | const [layout, setLayout] = createSignal<(parentId: Id | null) => void>(() => {});
32 |
33 | // TODO: might have to initialize the scope in the store if the scope id was auto-generated
34 |
35 | if (scope[scopeId()] === undefined) {
36 | setScope(scopeId(), {
37 | parent: parentScopeId(),
38 | layoutNode: undefined,
39 | children: {},
40 | });
41 | }
42 | setScope(scopeId(), "layoutNode", id());
43 |
44 | const jsx = (
45 |
46 |
47 | {(() => {
48 | const layoutNode = resolveScenegraphElements(
49 | )} name={id()} />
50 | );
51 |
52 | setLayout(() => layoutNode[0].layout);
53 |
54 | return layoutNode[0].jsx;
55 | })()}
56 |
57 |
58 | );
59 |
60 | return {
61 | jsx,
62 | layout: (parentId) => layout()(parentId),
63 | } satisfies ScenegraphElement as unknown as JSX.Element;
64 | };
65 | }
66 |
67 | export default withBluefish;
68 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "exclude": ["node_modules", "dist", "examples", "experiments", "public", "src/stories", "vite.config.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["node_modules", "dist"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleResolution": "node",
8 | "allowSyntheticDefaultImports": true,
9 | "esModuleInterop": true,
10 | "jsx": "preserve",
11 | "jsxImportSource": "solid-js",
12 | "types": ["vite/client"],
13 | "emitDeclarationOnly": true,
14 | "isolatedModules": true,
15 | "declaration": true,
16 | "declarationDir": "dist",
17 | "declarationMap": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/bluefish-solid/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { resolve } from "path";
3 | import solidPlugin from "vite-plugin-solid";
4 | import dts from "vite-plugin-dts";
5 |
6 | export default defineConfig(({ command }) => ({
7 | publicDir: command === "serve" ? "public" : false,
8 | plugins: [
9 | solidPlugin(),
10 | dts({ tsconfigPath: "./tsconfig.build.json" }),
11 | ],
12 | server: {
13 | port: 3000,
14 | open: "/public/index.html",
15 | },
16 | build: {
17 | lib: {
18 | entry: resolve(__dirname, "src/index.ts"),
19 | formats: ["es", "cjs", "umd"],
20 | name: "bluefish",
21 | fileName: "index",
22 | },
23 | emptyOutDir: false,
24 | target: "esnext",
25 | rollupOptions: {
26 | external: ["solid-js", "solid-js/web", "solid-js/store"],
27 | output: {
28 | globals: {
29 | "solid-js": "solidJs",
30 | "solid-js/web": "web",
31 | "solid-js/store": "store",
32 | },
33 | },
34 | },
35 | },
36 | }));
37 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "docs"
3 | - "packages/*"
4 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "ui": "tui",
4 | "tasks": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "inputs": ["$TURBO_DEFAULT$", ".env*"],
8 | "outputs": [".next/**", "!.next/cache/**", "dist/**"]
9 | },
10 | "publish": {
11 | "dependsOn": ["build"]
12 | },
13 | "docs:build": {
14 | "dependsOn": ["^docs:build"],
15 | "inputs": ["$TURBO_DEFAULT$"],
16 | "outputs": [".vitepress/dist/**"]
17 | },
18 | "lint": {
19 | "dependsOn": ["^lint"]
20 | },
21 | "dev": {
22 | "cache": false,
23 | "persistent": true
24 | },
25 | "docs:dev": {
26 | "cache": false,
27 | "persistent": true
28 | },
29 | "storybook": {
30 | "cache": false,
31 | "persistent": true
32 | },
33 | "storybook-docs": {
34 | "cache": false,
35 | "persistent": true
36 | },
37 | "build-storybook": {
38 | "dependsOn": ["^build-storybook"],
39 | "inputs": ["$TURBO_DEFAULT$"],
40 | "outputs": ["storybook-static/**"]
41 | },
42 | "test-storybook": {
43 | "dependsOn": ["build-storybook"]
44 | },
45 | "chromatic": {
46 | "dependsOn": ["build-storybook"],
47 | "inputs": ["$TURBO_DEFAULT$"]
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------