diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 79a69597525..3f80e250105 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,6 +23,7 @@ jobs:
outputs:
stackgl_modules: ${{ steps.check.outputs.stackgl_modules }}
topojson: ${{ steps.check.outputs.topojson }}
+ regl_codegen: ${{ steps.check.outputs.regl_codegen }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
@@ -42,6 +43,12 @@ jobs:
if git diff --name-only "$BASE"...HEAD -- stackgl_modules/ | grep -q .; then
echo "stackgl_modules=true" >> "$GITHUB_OUTPUT"
fi
+ # If any of the directories listed below have changed, we need to re-run the regl-codegen step
+ # stackgl_modules/ is listed here too because the regl-* shader libs live there;
+ # changes can alter shader output and require regenerating the precompiled shaders.
+ if git diff --name-only "$BASE"...HEAD -- src/traces/scattergl/ src/traces/scatterpolargl/ src/traces/splom/ src/traces/parcoords/ src/lib/prepare_regl.js stackgl_modules/ devtools/regl_codegen/ | grep -q .; then
+ echo "regl_codegen=true" >> "$GITHUB_OUTPUT"
+ fi
# ============================================================
# Root build job - all dependent jobs fan out from here
@@ -575,123 +582,6 @@ jobs:
# ============================================================
# Standalone jobs (no dependencies on install-and-cibuild)
# ============================================================
- publish-dist:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- with:
- fetch-depth: 0
- fetch-tags: true
-
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
- with:
- node-version: ${{ env.NODE_VERSION }}
- cache: 'npm'
-
- - name: Set up build environment
- run: .github/scripts/env_build.sh
-
- - name: Preview CHANGELOG for next release (only on master)
- if: github.ref == 'refs/heads/master'
- run: npm run use-draftlogs && git --no-pager diff --color-words CHANGELOG.md || true
-
- - name: Set draft version in package.json
- run: |
- node --eval "var fs = require('fs'); var inOut = './package.json'; var data = JSON.parse(fs.readFileSync(inOut)); var a = process.argv; data.version = a[a.length - 1].replace('v', ''); fs.writeFileSync(inOut, JSON.stringify(data, null, 2) + '\n');" $(git describe)
-
- - name: View package.json diff between previous and next releases
- run: git --no-pager diff --color-words tags/$(git describe --tags --abbrev=0) package.json || true
-
- - name: Build dist/
- run: npm run build
-
- # Upload library uncompressed to allow for testing in REPLs
- - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
- name: Upload uncompressed plotly.js built from PR, using Node 22
- with:
- retention-days: 30
- archive: false
- path: dist/plotly.js
-
- - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
- name: Upload Node 18 archive of plotly.js build folder
- with:
- name: dist-node18
- retention-days: 7
- path: dist/
-
- - name: View dist/README.md diff between previous and next releases
- run: git --no-pager diff --color-words tags/$(git describe --tags --abbrev=0) dist/README.md || true
-
- - name: Preview plot-schema diff (only on master)
- if: github.ref == 'refs/heads/master'
- run: git --no-pager diff tags/$(git describe --tags --abbrev=0) dist/plot-schema.json || true
-
- - name: Test plot-schema.json diff
- run: diff --unified --color dist/plot-schema.json test/plot-schema.json
-
- publish-dist-node-v22:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- with:
- fetch-depth: 0
- fetch-tags: true
-
- - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
- with:
- node-version: '22.14.0'
- cache: 'npm'
-
- - name: Set up build environment
- run: .github/scripts/env_build.sh
-
- - name: Preview CHANGELOG for next release (only on master)
- if: github.ref == 'refs/heads/master'
- run: npm run use-draftlogs && git --no-pager diff --color-words CHANGELOG.md || true
-
- - name: Set draft version in package.json
- run: |
- node --eval "var fs = require('fs'); var inOut = './package.json'; var data = JSON.parse(fs.readFileSync(inOut)); var a = process.argv; data.version = a[a.length - 1].replace('v', ''); fs.writeFileSync(inOut, JSON.stringify(data, null, 2) + '\n');" $(git describe)
-
- - name: View package.json diff between previous and next releases
- run: git --no-pager diff --color-words tags/$(git describe --tags --abbrev=0) package.json || true
-
- - name: Build dist/
- run: npm run build
-
- # This is necessary to avoid a naming collision with the upload from the Node 18 build
- - name: Copy library for upload
- run: cp dist/plotly.js dist/plotly.node22.js
-
- # Upload library uncompressed to allow for testing in REPLs
- - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
- name: Upload uncompressed plotly.js built from PR, using Node 22
- with:
- retention-days: 30
- archive: false
- path: dist/plotly.node22.js
-
- - name: Remove copy of library
- run: rm dist/plotly.node22.js
-
- - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
- name: Upload Node 22 archive of plotly.js build folder
- with:
- name: dist-node22
- retention-days: 7
- path: dist/
-
- - name: View dist/README.md diff between previous and next releases
- run: git --no-pager diff --color-words tags/$(git describe --tags --abbrev=0) dist/README.md || true
-
- - name: Preview plot-schema diff (only on master)
- if: github.ref == 'refs/heads/master'
- run: git --no-pager diff tags/$(git describe --tags --abbrev=0) dist/plot-schema.json || true
-
- - name: Test plot-schema.json diff
- run: diff --unified --color dist/plot-schema.json test/plot-schema.json
-
test-stackgl-bundle:
needs: detect-changes
if: >-
@@ -753,3 +643,54 @@ jobs:
name: topojson-dist
retention-days: 7
path: topojson/dist/
+
+ check-regl-codegen:
+ needs: [detect-changes, install-and-cibuild]
+ if: >-
+ (github.event_name == 'push' && github.ref_name == github.event.repository.default_branch) ||
+ needs.detect-changes.outputs.regl_codegen == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ - uses: ./.github/actions/setup-workspace
+ - uses: ./.github/actions/setup-chrome
+
+ - name: Run regl codegen
+ run: |
+ # Wipe the codegen output dir so orphaned shader files surface as deletions in the diff below.
+ rm -rf src/generated/regl-codegen/*
+ node devtools/regl_codegen/server.mjs &
+ SERVER_PID=$!
+ until curl -sf -o /dev/null http://localhost:3000/build/regl_codegen-bundle.js 2>/dev/null; do
+ sleep 1
+ done
+ $CHROME_BIN --headless=new --no-sandbox --enable-unsafe-swiftshader --ignore-gpu-blocklist http://localhost:3000/devtools/regl_codegen/index.html &
+ wait $SERVER_PID
+
+ - name: Check regl precompiled shaders are up to date
+ run: |
+ # git add -N so newly-generated (untracked) shader files show up in git diff
+ git add -N src/generated/regl-codegen/
+ if ! git diff --exit-code \
+ src/generated/regl-codegen/ \
+ src/traces/scattergl/regl_precompiled.js \
+ src/traces/scatterpolargl/regl_precompiled.js \
+ src/traces/splom/regl_precompiled.js \
+ src/traces/parcoords/regl_precompiled.js; then
+ echo "::error::Regl precompiled shaders are out of date. Download the 'regl-codegen' artifact from this workflow run, extract it into src/, and commit the updated files to this pull request."
+ exit 1
+ fi
+
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ name: Upload updated regl codegen files
+ if: failure()
+ with:
+ name: regl-codegen
+ retention-days: 7
+ path: |
+ src/generated/regl-codegen/
+ src/traces/scattergl/regl_precompiled.js
+ src/traces/scatterpolargl/regl_precompiled.js
+ src/traces/splom/regl_precompiled.js
+ src/traces/parcoords/regl_precompiled.js
diff --git a/.github/workflows/publish-dist.yml b/.github/workflows/publish-dist.yml
new file mode 100644
index 00000000000..eded04faf93
--- /dev/null
+++ b/.github/workflows/publish-dist.yml
@@ -0,0 +1,51 @@
+name: Publish Dist
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ types:
+ - opened
+ - reopened
+ - synchronize
+ workflow_dispatch:
+
+concurrency:
+ group: publish-dist-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ publish-dist:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+
+ - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
+ with:
+ node-version: '22.14.0'
+ cache: 'npm'
+
+ - name: Set up build environment
+ run: .github/scripts/env_build.sh
+
+ - name: Set draft version in package.json
+ run: |
+ node --eval "var fs = require('fs'); var inOut = './package.json'; var data = JSON.parse(fs.readFileSync(inOut)); var a = process.argv; data.version = a[a.length - 1].replace('v', ''); fs.writeFileSync(inOut, JSON.stringify(data, null, 2) + '\n');" $(git describe)
+ # View package.json diff from last release (should show that the version has been changed to the draft version)
+ git --no-pager diff --color-words tags/$(git describe --tags --abbrev=0) package.json || true
+
+ - name: Build dist/
+ run: npm run build
+
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ name: Upload archive of plotly.js build artifacts (contents of dist/)
+ with:
+ name: dist
+ retention-days: 7
+ path: dist/
+
+ - name: Test plot-schema.json diff
+ run: diff --unified --color dist/plot-schema.json test/plot-schema.json
diff --git a/.github/workflows/upload-dev-build.yml b/.github/workflows/upload-dev-build.yml
new file mode 100644
index 00000000000..6d2a0b7f975
--- /dev/null
+++ b/.github/workflows/upload-dev-build.yml
@@ -0,0 +1,184 @@
+name: Upload dev build from PR
+
+# See https://github.com/plotly/plotly.js/blob/master/CONTRIBUTING.md#live-links-to-dev-builds
+# for documentation on the usage of this workflow.
+
+on:
+ workflow_run:
+ workflows: ["Publish Dist"] # publish-dist.yml
+ types:
+ - completed
+ workflow_dispatch:
+ inputs:
+ pr_number:
+ description: 'PR Number to deploy'
+ required: false
+
+env:
+ ARTIFACT_UPLOAD_WORKFLOW_NAME: "Publish Dist"
+ UPLOAD_DIR_NAME: "upload"
+
+jobs:
+ upload:
+ runs-on: ubuntu-latest
+ # Only run on manual dispatch,
+ # OR if the parent run succeeded and was triggered by a PR from
+ # a branch in the main repo (not a fork)
+ if: |
+ github.event_name == 'workflow_dispatch' ||
+ (
+ github.event_name == 'workflow_run' &&
+ github.event.workflow_run.event == 'pull_request' &&
+ github.event.workflow_run.conclusion == 'success' &&
+ github.event.workflow_run.head_repository.full_name == github.repository
+ )
+ steps:
+ - name: Get required metadata (PR number, commit SHA, workflow run ID containing artifacts)
+ id: get-metadata
+ env:
+ GH_TOKEN: ${{ github.token }}
+ GH_EVENT_NAME: ${{ github.event_name }}
+ GH_REPO: ${{ github.repository }}
+ SHA: ${{ github.event.workflow_run.head_sha }}
+ RUN_ID: ${{ github.event.workflow_run.id }}
+ PR_NUM: ${{ github.event.workflow_run.pull_requests[0].number || inputs.pr_number }}
+ run: |
+ # Get SHA from manually-provided PR number if triggered by workflow_dispatch
+ if [ "${GH_EVENT_NAME}" == "workflow_dispatch" ]; then
+ if [ -n "${PR_NUM}" ]; then
+ SHA=$(gh pr view "${PR_NUM}" --repo "${GH_REPO}" --json headRefOid --template '{{.headRefOid}}')
+ fi
+ fi
+
+ # At this point, SHA should be defined. If not, fail the workflow
+ if [ -z "${SHA}" ]; then
+ echo "Failed to get commit SHA, exiting"
+ exit 1
+ fi
+
+ # If PR_NUM is empty, get PR number using SHA
+ if [ -z "${PR_NUM}" ]; then
+ PR_NUM=$(gh pr list --search "sha:${SHA}" --state open --json number --jq '.[0].number')
+ fi
+
+ # Validate that we have a valid PR number
+ if [ -z "${PR_NUM}" ] || [[ ! "${PR_NUM}" =~ ^[1-9][0-9]{0,4}$ ]]; then
+ echo "Failed to get PR number, exiting (PR_NUM=${PR_NUM})"
+ exit 1
+ fi
+
+ # If RUN_ID is empty, use the gh CLI to get the most recent run ID for SHA
+ if [ -z "${RUN_ID}" ]; then
+ RUN_ID=$(gh run list \
+ --workflow "${ARTIFACT_UPLOAD_WORKFLOW_NAME}" \
+ --commit "${SHA}" \
+ --limit 1 \
+ --json databaseId \
+ --jq '.[0].databaseId')
+ fi
+
+ # At this point, RUN_ID should be defined. If not, fail the workflow
+ if [ -z "${RUN_ID}" ]; then
+ echo "Failed to get workflow run ID, exiting"
+ exit 1
+ fi
+
+ # Save PR number, commit SHA, short SHA, and run ID to output
+ echo "PR_NUM=${PR_NUM}" >> $GITHUB_OUTPUT
+ echo "SHA=${SHA}" >> $GITHUB_OUTPUT
+ echo "SHORT_SHA=${SHA:0:7}" >> $GITHUB_OUTPUT
+ echo "RUN_ID=${RUN_ID}" >> $GITHUB_OUTPUT
+
+ - name: Download build artifact
+ id: download-artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+ with:
+ name: dist # uploaded by publish-dist.yml > publish-dist
+ run-id: ${{ steps.get-metadata.outputs.RUN_ID }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ path: temp-dist
+
+ - name: Prepare folders
+ id: setup-metadata
+ env:
+ GH_TOKEN: ${{ github.token }}
+ PR_NUM: ${{ steps.get-metadata.outputs.PR_NUM }}
+ SHA: ${{ steps.get-metadata.outputs.SHA }}
+ SHORT_SHA: ${{ steps.get-metadata.outputs.SHORT_SHA }}
+ run: |
+ echo "SHA: ${SHA}"
+ echo "Short SHA: ${SHORT_SHA}"
+ echo "PR number: ${PR_NUM}"
+ mkdir -p "${UPLOAD_DIR_NAME}/pr-${PR_NUM}/latest"
+ mkdir -p "${UPLOAD_DIR_NAME}/pr-${PR_NUM}/${SHORT_SHA}"
+ # Copy all 3 artifacts (plotly.js, plotly.min.js, plot-schema.json) to /latest/
+ cp temp-dist/plotly.js "${UPLOAD_DIR_NAME}/pr-${PR_NUM}/latest/plotly.js"
+ cp temp-dist/plotly.min.js "${UPLOAD_DIR_NAME}/pr-${PR_NUM}/latest/plotly.min.js"
+ cp temp-dist/plot-schema.json "${UPLOAD_DIR_NAME}/pr-${PR_NUM}/latest/plot-schema.json"
+ # Copy only plotly.min.js to /$SHORT_SHA/
+ cp temp-dist/plotly.min.js "${UPLOAD_DIR_NAME}/pr-${PR_NUM}/${SHORT_SHA}/plotly.min.js"
+
+ UPLOAD_DIR_FULL_PATH=$(pwd)/${UPLOAD_DIR_NAME}/
+ echo "Created directory ${UPLOAD_DIR_FULL_PATH} with the following contents:"
+ echo "$(ls -lR ${UPLOAD_DIR_FULL_PATH})"
+
+ - name: Generate GitHub App token
+ id: generate-token
+ uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 #v3.1.1
+ with:
+ client-id: ${{ vars.DEV_DEPLOY_APP_ID }}
+ private-key: ${{ secrets.DEV_DEPLOY_APP_PRIVATE_KEY }}
+ owner: plotly
+ repositories: plotly.js-dev-builds
+
+ - name: Check out plotly.js-dev-builds repo
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ with:
+ repository: plotly/plotly.js-dev-builds
+ token: ${{ steps.generate-token.outputs.token }} # token from previous step
+ path: plotly.js-dev-builds
+
+ - name: Commit and push files
+ id: commit-and-push
+ env:
+ PR_NUM: ${{ steps.get-metadata.outputs.PR_NUM }}
+ SHORT_SHA: ${{ steps.get-metadata.outputs.SHORT_SHA }}
+ run: |
+ # Move 'pr-NNNN/' directory into upload directory inside repo and cd into repo root
+ TARGET_DIR="${UPLOAD_DIR_NAME}/pr-${PR_NUM}"
+ mkdir -p "plotly.js-dev-builds/${UPLOAD_DIR_NAME}"
+ cp -r "${TARGET_DIR}" "plotly.js-dev-builds/${UPLOAD_DIR_NAME}"
+ cd plotly.js-dev-builds
+
+ # Configure git
+ git config user.name "plotly.js-pr-upload"
+ git config user.email "<>"
+
+ # Add files
+ git add "${TARGET_DIR}/"
+
+ # Ensure that only files in upload/pr-NNNN/ are staged
+ if git diff --name-only --cached | grep -qv "^${TARGET_DIR}/"; then
+ echo "Error: Changes detected outside ${TARGET_DIR}/"
+ exit 1
+ fi
+
+ # Only commit if there are changes
+ if git diff --staged --quiet; then
+ echo "No changes to commit"
+ else
+ git commit -m "Deploy build for PR #${PR_NUM} (commit ${SHORT_SHA})"
+ git push origin main
+ fi
+
+ - name: Generate summary
+ env:
+ PR_NUM: ${{ steps.get-metadata.outputs.PR_NUM }}
+ SHORT_SHA: ${{ steps.get-metadata.outputs.SHORT_SHA }}
+ run: |
+ BASE_URL="https://plotly.github.io/plotly.js-dev-builds/${UPLOAD_DIR_NAME}/pr-${PR_NUM}"
+ echo "### PR Build Uploaded" >> $GITHUB_STEP_SUMMARY
+ echo "Builds for PR #${PR_NUM} can be accessed at:" >> $GITHUB_STEP_SUMMARY
+ echo "- Latest build for this PR: [${BASE_URL}/latest/plotly.min.js](${BASE_URL}/latest/plotly.min.js)" >> $GITHUB_STEP_SUMMARY
+ echo "- Build for this commit: [${BASE_URL}/${SHORT_SHA}/plotly.min.js](${BASE_URL}/${SHORT_SHA}/plotly.min.js)" >> $GITHUB_STEP_SUMMARY
+ echo "The above links should start working a minute or two after this job completes." >> $GITHUB_STEP_SUMMARY
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40c38459472..dc2299687ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,20 @@ To see all merged commits on the master branch that will be part of the next plo
where X.Y.Z is the semver of most recent plotly.js release.
+## [3.6.0] -- 2026-06-01
+
+### Added
+- Add support for arrays for the pie property `legendrank`, so that it can be configured per slice [[#7723](https://github.com/plotly/plotly.js/pull/7723)], with thanks to @my-tien for the contribution!
+- Add `hoversort` layout attribute to sort unified hover label items by value [[#7734](https://github.com/plotly/plotly.js/pull/7734)], with thanks to @kimsehwan96 for the contribution!
+
+### Fixed
+- Fix unexpected `ticklabelindex` behavior when minor ticks are not shown [[#7735](https://github.com/plotly/plotly.js/pull/7735)], with thanks to @my-tien for the contribution!
+- Fix issue where `hoveranywhere` / `clickanywhere` would not emit hover and click events over editable shapes [[#7788](https://github.com/plotly/plotly.js/pull/7788)]
+- Handle 'pixel' size mode for shape labels [[#7790](https://github.com/plotly/plotly.js/pull/7790)]
+- Update box plot defaults to fix issue with calling `Plotly.react` to switch from box to violin plot [[#7811](https://github.com/plotly/plotly.js/pull/7811)]
+- Include shapes with `legendgroup` specified when handling legend visibility toggling [[#7813](https://github.com/plotly/plotly.js/pull/7813)]
+
+
## [3.5.1] -- 2026-05-01
### Changed
diff --git a/CITATION.cff b/CITATION.cff
index 38d294398c5..b75d6c50aa5 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -9,7 +9,7 @@ authors:
- family-names: "Samimi"
given-names: "Mojtaba"
title: "Open source Plotly charting library"
-version: 3.5.1
+version: 3.6.0
doi: 10.5281/zenodo.13964707
-date-released: 2026-05-01
+date-released: 2026-06-01
url: "https://github.com/plotly/plotly.js"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d7cfa2bd96c..43dc2d31f8c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -163,19 +163,54 @@ npm run schema
#### Step 9: REGL - Review & commit potential changes to precompiled regl shaders
If you are implementing a new feature that involves regl shaders, or if you are
-making changes that affect the usage of regl shaders, you would need to run
+making changes that affect the usage of regl shaders, you will need to regenerate the precompiled regl shader code. In practice, that means edits under:
-```bash
-npm run regl-codegen
-```
+- `src/traces/{scattergl,scatterpolargl,splom,parcoords}/`
+- `src/lib/prepare_regl.js`
+- `stackgl_modules/` (where the `regl-*` shader libs live)
+- `devtools/regl_codegen/`
-to regenerate the regl code. This will prompt you to open a browser window. This will then run through all
-traces with 'regl' in the tags, and store the captured code into
-[src/generated/regl-codegen](https://github.com/plotly/plotly.js/blob/master/src/generated/regl-codegen). If no updates are necessary, it will be a no-op, but if there are changes, you will need to commit them.
+CI's `check-regl-codegen` job uses this same list as its trigger — see [`.github/workflows/ci.yml`](.github/workflows/ci.yml) for the authoritative version.
This is needed because regl performs codegen in runtime which breaks CSP
compliance, and so for strict builds we pre-generate regl shader code here.
+The CI pipeline will automatically detect when regl-related files have changed and
+run the codegen process. If the precompiled shaders are out of date, the
+`check-regl-codegen` job will fail and upload a `regl-codegen` artifact containing
+the updated files. The artifact represents the full desired state of the codegen
+output (CI wipes the output directory before regenerating, so any orphaned shader
+files are pruned). To fix this:
+
+1. Download the `regl-codegen` artifact from the failed workflow run
+2. Delete `src/generated/regl-codegen/` in your working tree, then unzip the
+ artifact into `src/` so it replaces (rather than merges into) the existing
+ directory. Note that `actions/upload-artifact` strips the longest common
+ parent path from the uploaded paths, so the zip's root contains
+ `generated/regl-codegen/` and `traces/{scattergl,scatterpolargl,splom,parcoords}/regl_precompiled.js`
+ directly — extracting into `src/` restores the correct layout and
+ overwrites the four `regl_precompiled.js` files in one step.
+3. Commit and push the changes to your pull request
+
+Alternatively, you can regenerate the code locally:
+
+```bash
+rm -rf src/generated/regl-codegen/*
+npm run regl-codegen
+```
+
+The `rm -rf` step is needed to clean up any orphaned shader files left over from
+previous changes. `npm run regl-codegen` will prompt you to open
+a browser window, run through the mocks for each regl-using trace
+(`parcoords`, `scattergl`, `scatterpolargl`, `splom`), and store the captured
+shader code into
+[src/generated/regl-codegen](https://github.com/plotly/plotly.js/blob/master/src/generated/regl-codegen).
+The four `src/traces/{parcoords,scattergl,scatterpolargl,splom}/regl_precompiled.js`
+files are rewritten in the same pass so their imports point at the freshly
+generated shader files. Commit any changes that result — both the
+`src/generated/regl-codegen/` updates and any modified `regl_precompiled.js`
+files.
+
#### Other npm scripts that may be of interest in development
- `npm run preprocess`: pre-processes the css and svg source file in js. This
@@ -326,6 +361,17 @@ This will produce the following plot, and say you want to simulate a selection p
+### Live links to dev builds
+
+The [Upload dev build from PR](.github/workflows/upload-dev-build.yml) workflow can be used to upload a dev build for a PR to the plotly.js-dev-builds repo, creating a live link to the plotly.js dev build which can be used in online coding environments to test and demo the PR.
+
+It is triggered in one of two ways:
+- Automatically on completion of the Publish Dist workflow, if triggered by a PR from a branch in the main repo (not a fork).
+- Manually via the Actions tab in the GitHub UI, by entering a PR number in the workflow inputs
+ - Only users with write access to the plotly.js repo can trigger workflows manually
+
+If you would like a link to the dev build for your PR but don't have permission to trigger the workflow, tag a maintainer to request a run for your PR.
+
## Repo organization
- Distributed files are in `dist/`
diff --git a/README.md b/README.md
index 59b737594be..1778b7b1b57 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ You may also consider using [`plotly.js-dist`](https://www.npmjs.com/package/plo
```html
p?1:c>=p?0:NaN}e.descending=function(c,p){return p =p.length)return S?S.call(c,N):A?N.sort(A):N;for(var X=-1,j=N.length,ee=p[G++],he,we,te,se=new b,ue;++X oe)E=E.L;else if(R=p-mN(E,x),R>oe){if(!E.R){A=E;break}E=E.R}else{L>-oe?(A=E.P,S=E):R>-oe?(A=E,S=E.N):A=S=E;break}var N=o5(c);if(hs.insert(A,N),!(!A&&!S)){if(A===S){ps(A),S=o5(A.site),hs.insert(N,S),N.edge=S.edge=Hu(A.site,N.site),ds(A),ds(S);return}if(!S){N.edge=Hu(A.site,N.site);return}ps(A),ps(S);var G=A.site,X=G.x,j=G.y,ee=c.x-X,he=c.y-j,we=S.site,te=we.x-X,se=we.y-j,ue=2*(ee*se-he*te),_e=ee*ee+he*he,Te=te*te+se*se,ce={x:(se*_e-he*Te)/ue+X,y:(ee*Te-te*_e)/ue+j};jc(S.edge,G,we,ce),N.edge=Hu(G,c,null,ce),S.edge=Hu(c,we,null,ce),ds(A),ds(S)}}function s5(c,p){var x=c.site,A=x.x,S=x.y,L=S-p;if(!L)return A;var R=c.P;if(!R)return-1/0;x=R.site;var E=x.x,N=x.y,G=N-p;if(!G)return E;var X=E-A,j=1/L-1/G,ee=X/G;return j?(-ee+Math.sqrt(ee*ee-2*j*(X*X/(-2*G)-N+G/2+S-L/2)))/j+A:(A+E)/2}function mN(c,p){var x=c.N;if(x)return s5(x,p);var A=c.site;return A.y===p?A.x:1/0}function u5(c){this.site=c,this.edges=[]}u5.prototype.prepare=function(){for(var c=this.edges,p=c.length,x;p--;)x=c[p].edge,(!x.b||!x.a)&&c.splice(p,1);return c.sort(f5),c.length};function yN(c){for(var p=c[0][0],x=c[1][0],A=c[0][1],S=c[1][1],L,R,E,N,G=jl,X=G.length,j,ee,he,we,te,se;X--;)if(j=G[X],!(!j||!j.prepare()))for(he=j.edges,we=he.length,ee=0;ee>>1;c(p[L],x)<0?A=L+1:S=L}return A},right:function(p,x,A,S){for(arguments.length<3&&(A=0),arguments.length<4&&(S=p.length);A>>1;c(p[L],x)>0?S=L:A=L+1}return A}}}var y=m(v);e.bisectLeft=y.left,e.bisect=e.bisectRight=y.right,e.bisector=function(c){return m(c.length===1?function(p,x){return v(c(p),x)}:c)},e.shuffle=function(c,p,x){(A=arguments.length)<3&&(x=c.length,A<2&&(p=0));for(var A=x-p,S,L;A;)L=Math.random()*A--|0,S=c[A+p],c[A+p]=c[L+p],c[L+p]=S;return c},e.permute=function(c,p){for(var x=p.length,A=new Array(x);x--;)A[x]=c[p[x]];return A},e.pairs=function(c){for(var p=0,x=c.length-1,A,S=c[0],L=new Array(x<0?0:x);p0?1:c<0?-1:0}function lt(c,p,x){return(p[0]-c[0])*(x[1]-c[1])-(p[1]-c[1])*(x[0]-c[0])}function _t(c){return c>1?0:c<-1?Ee:Math.acos(c)}function gt(c){return c>1?sr:c<-1?-sr:Math.asin(c)}function Bt(c){return((c=Math.exp(c))-1/c)/2}function zt(c){return((c=Math.exp(c))+1/c)/2}function Oa(c){return((c=Math.exp(2*c))-1)/(c+1)}function ca(c){return(c=Math.sin(c/2))*c}var aa=Math.SQRT2,Ba=2,na=4;e.interpolateZoom=function(c,p){var x=c[0],A=c[1],S=c[2],L=p[0],R=p[1],E=p[2],N=L-x,G=R-A,X=N*N+G*G,j,ee;if(X>4,A=A>>4|A,S=N&240,S=S>>4|S,L=N&15,L=L<<4|L):c.length===7&&(A=(N&16711680)>>16,S=(N&65280)>>8,L=N&255)),p(A,S,L))}function $r(c,p,x){var A=Math.min(c/=255,p/=255,x/=255),S=Math.max(c,p,x),L=S-A,R,E,N=(S+A)/2;return L?(E=N<.5?L/(S+A):L/(2-S-A),c==S?R=(p-x)/L+(p=X&&ue.x<=ee&&ue.y>=j&&ue.y<=he?[[X,he],[ee,he],[ee,j],[X,j]]:[];_e.point=N[te]}),G}function E(N){return N.map(function(G,X){return{x:Math.round(A(G,X)/oe)*oe,y:Math.round(S(G,X)/oe)*oe,i:X}})}return R.links=function(N){return k1(E(N)).edges.filter(function(G){return G.l&&G.r}).map(function(G){return{source:N[G.l.i],target:N[G.r.i]}})},R.triangles=function(N){var G=[];return k1(E(N)).cells.forEach(function(X,j){for(var ee=X.site,he=X.edges.sort(f5),we=-1,te=he.length,se,ue,_e=he[te-1].edge,Te=_e.l===ee?_e.r:_e.l;++we0&&(P=0);break}return P>0?k.slice(0,P)+k.slice(z+1):k}var f;function v(k,q){var D=t(k,q);if(!D)return k+"";var P=D[0],z=D[1],F=z-(f=Math.max(-8,Math.min(8,Math.floor(z/3)))*3)+1,O=P.length;return F===O?P:F>O?P+new Array(F-O+1).join("0"):F>0?P.slice(0,F)+"."+P.slice(F):"0."+new Array(1-F).join("0")+t(k,Math.max(0,q+F-1))[0]}function h(k,q){var D=t(k,q);if(!D)return k+"";var P=D[0],z=D[1];return z<0?"0."+new Array(-z).join("0")+P:P.length>z+1?P.slice(0,z+1)+"."+P.slice(z+1):P+new Array(z-P.length+2).join("0")}var d={"%":function(k,q){return(k*100).toFixed(q)},b:function(k){return Math.round(k).toString(2)},c:function(k){return k+""},d:r,e:function(k,q){return k.toExponential(q)},f:function(k,q){return k.toFixed(q)},g:function(k,q){return k.toPrecision(q)},o:function(k){return Math.round(k).toString(8)},p:function(k,q){return h(k*100,q)},r:h,s:v,X:function(k){return Math.round(k).toString(16).toUpperCase()},x:function(k){return Math.round(k).toString(16)}};function m(k){return k}var y=Array.prototype.map,g=["y","z","a","f","p","n","\xB5","m","","k","M","G","T","P","E","Z","Y"];function _(k){var q=k.grouping===void 0||k.thousands===void 0?m:n(y.call(k.grouping,Number),k.thousands+""),D=k.currency===void 0?"":k.currency[0]+"",P=k.currency===void 0?"":k.currency[1]+"",z=k.decimal===void 0?".":k.decimal+"",F=k.numerals===void 0?m:i(y.call(k.numerals,String)),O=k.percent===void 0?"%":k.percent+"",I=k.minus===void 0?"-":k.minus+"",W=k.nan===void 0?"NaN":k.nan+"";function Z(Y){Y=o(Y);var H=Y.fill,Q=Y.align,K=Y.sign,ne=Y.symbol,pe=Y.zero,ye=Y.width,Se=Y.comma,re=Y.precision,Ce=Y.trim,de=Y.type;de==="n"?(Se=!0,de="g"):d[de]||(re===void 0&&(re=12),Ce=!0,de="g"),(pe||H==="0"&&Q==="=")&&(pe=!0,H="0",Q="=");var be=ne==="$"?D:ne==="#"&&/[boxX]/.test(de)?"0"+de.toLowerCase():"",ge=ne==="$"?P:/[%p]/.test(de)?O:"",ke=d[de],B=/[defgprs%]/.test(de);re=re===void 0?6:/[gprs]/.test(de)?Math.max(1,Math.min(21,re)):Math.max(0,Math.min(20,re));function $(U){var le=be,ve=ge,me,De,Re;if(de==="c")ve=ke(U)+ve,U="";else{U=+U;var Le=U<0||1/U<0;if(U=isNaN(U)?W:ke(Math.abs(U),re),Ce&&(U=u(U)),Le&&+U==0&&K!=="+"&&(Le=!1),le=(Le?K==="("?K:I:K==="-"||K==="("?"":K)+le,ve=(de==="s"?g[8+f/3]:"")+ve+(Le&&K==="("?")":""),B){for(me=-1,De=U.length;++me
>4,g[f++]=(h&15)<<4|d>>2,g[f++]=(d&3)<<6|m&63;return y};e.decode=i,e.encode=n,Object.defineProperty(e,"__esModule",{value:!0})})});var cl=J((Pie,Cb)=>{"use strict";Cb.exports=function(r){return window&&window.process&&window.process.versions?Object.prototype.toString.call(r)==="[object Object]":Object.prototype.toString.call(r)==="[object Object]"&&Object.getPrototypeOf(r).hasOwnProperty("hasOwnProperty")}});var on=J(Xn=>{"use strict";var mI=K1().decode,yI=cl(),Q1=Array.isArray,gI=ArrayBuffer,bI=DataView;function Lb(e){return gI.isView(e)&&!(e instanceof bI)}Xn.isTypedArray=Lb;function Mv(e){return Q1(e)||Lb(e)}Xn.isArrayOrTypedArray=Mv;function xI(e){return!Mv(e[0])}Xn.isArray1D=xI;Xn.ensureArray=function(e,r){return Q1(e)||(e=[]),e.length=r,e};var sa={u1c:typeof Uint8ClampedArray=="undefined"?void 0:Uint8ClampedArray,i1:typeof Int8Array=="undefined"?void 0:Int8Array,u1:typeof Uint8Array=="undefined"?void 0:Uint8Array,i2:typeof Int16Array=="undefined"?void 0:Int16Array,u2:typeof Uint16Array=="undefined"?void 0:Uint16Array,i4:typeof Int32Array=="undefined"?void 0:Int32Array,u4:typeof Uint32Array=="undefined"?void 0:Uint32Array,f4:typeof Float32Array=="undefined"?void 0:Float32Array,f8:typeof Float64Array=="undefined"?void 0:Float64Array};sa.uint8c=sa.u1c;sa.uint8=sa.u1;sa.int8=sa.i1;sa.uint16=sa.u2;sa.int16=sa.i2;sa.uint32=sa.u4;sa.int32=sa.i4;sa.float32=sa.f4;sa.float64=sa.f8;function $1(e){return e.constructor===ArrayBuffer}Xn.isArrayBuffer=$1;Xn.decodeTypedArraySpec=function(e){var r=[],t=_I(e),a=t.dtype,n=sa[a];if(!n)throw new Error('Error in dtype: "'+a+'"');var i=n.BYTES_PER_ELEMENT,l=t.bdata;$1(l)||(l=mI(l));var o=t.shape===void 0?[l.byteLength/i]:(""+t.shape).split(",");o.reverse();var s=o.length,u,f,v=+o[0],h=i*v,d=0;if(s===1)r=new n(l);else if(s===2)for(u=+o[1],f=0;f{"use strict";var Db=Or(),ep=on().isArrayOrTypedArray;zb.exports=function(r,t){if(Db(t))t=String(t);else if(typeof t!="string"||t.slice(-4)==="[-1]")throw"bad property string";var a=t.split("."),n,i,l,o;for(o=0;o
/g),h=0;h
/i;An.BR_TAG_ALL=/
/gi;var o7=/(^|[\s"'])style\s*=\s*("([^"]*);?"|'([^']*);?')/i,s7=/(^|[\s"'])href\s*=\s*("([^"]*)"|'([^']*)')/i,u7=/(^|[\s"'])target\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i,qY=/(^|[\s"'])popup\s*=\s*("([\w=,]*)"|'([\w=,]*)')/i;function _o(e,r){if(!e)return null;var t=e.match(r),a=t&&(t[3]||t[4]);return a&&f0(a)}var DY=/(^|;)\s*color:/;An.plainText=function(e,r){r=r||{};for(var t=r.len!==void 0&&r.len!==-1?r.len:1/0,a=r.allowedTags!==void 0?r.allowedTags:["br"],n="...",i=n.length,l=e.split(Xp),o=[],s="",u=0,f=0;f=k&&b
=re.min&&(ne
=D:I<=D;I=Ye.tickIncrement(I,V,f,n)){if(b&&W++,C.rangebreaks&&!f){if(I
=h)break}if(g.length>d||I===O)break;O=I;var Y={value:I};b?(z&&I!==(I|0)&&(Y.simpleLabel=!0),i>1&&W%i&&(Y.skipLabel=!0),g.push(Y)):(Y.minor=!0,_.push(Y))}}if(!_||_.length<2)s=!1;else{var H=(_[1].value-_[0].value)*(o?-1:1);gG(H,r.tickformat)||(s=!1)}if(!s)w=g;else{var Q=g.concat(_);l&&g.length&&(Q=Q.slice(1)),Q=Q.sort(function(Fe,Oe){return Fe.value-Oe.value}).filter(function(Fe,Oe,Ge){return Oe===0||Fe.value!==Ge[Oe-1].value});var K=Q.map(function(Fe,Oe){return Fe.minor===void 0&&!Fe.skipLabel?Oe:null}).filter(function(Fe){return Fe!==null});K.forEach(function(Fe){s.map(function(Oe){var Ge=Fe+Oe;Ge>=0&&Ge
")}r.text=l}function QV(e,r,t,a,n){var i=e.dtick,l=r.x,o=e.tickformat,s=typeof i=="string"&&i.charAt(0);if(n==="never"&&(n=""),a&&s!=="L"&&(i="L3",s="L"),o||s==="L")r.text=kf(Math.pow(10,l),e,n,a);else if(Ht(i)||s==="D"&&(e.minorloglabels==="complete"||gr.mod(l+.01,1)<.1)){var u;e.minorloglabels==="complete"&&!(gr.mod(l+.01,1)<.1)&&(u=!0,r.fontSize*=.75);var f=Math.pow(10,l).toExponential(0),v=f.split("e"),h=+v[1],d=Math.abs(h),m=e.exponentformat;m==="power"||Ks(m)&&m!=="SI extended"&&$m(h)||Ks(m)&&m==="SI extended"&&jm(h)?(r.text=v[0],d>0&&(r.text+="x10"),r.text==="1x10"&&(r.text="10"),h!==0&&h!==1&&(r.text+=""+(h>0?"":Co)+d+""),r.fontSize*=1.25):(m==="e"||m==="E")&&d>2?r.text=v[0]+m+(h>0?"+":Co)+d:(r.text=kf(Math.pow(10,l),e,"","fakehover"),i==="D1"&&e._id.charAt(0)==="y"&&(r.dy-=r.fontSize/6))}else if(s==="D")r.text=e.minorloglabels==="none"?"":String(Math.round(Math.pow(10,gr.mod(l,1)))),r.fontSize*=.75;else throw"unrecognized dtick "+String(i);if(e.dtick==="D1"){var y=String(r.text).charAt(0);(y==="0"||y==="1")&&(e._id.charAt(0)==="y"?r.dx-=r.fontSize/4:(r.dy+=r.fontSize/2,r.dx+=(e.range[1]>e.range[0]?1:-1)*r.fontSize*(l<0?.5:.25)))}}function $V(e,r){var t=e._categories[Math.round(r.x)];t===void 0&&(t=""),r.text=String(t)}function jV(e,r,t){var a=Math.round(r.x),n=e._categories[a]||[],i=n[1]===void 0?"":String(n[1]),l=n[0]===void 0?"":String(n[0]);t?r.text=l+" - "+i:(r.text=i,r.text2=l)}function eG(e,r,t,a,n){n==="never"?n="":e.showexponent==="all"&&Math.abs(r.x/e.dtick)<1e-6&&(n="hide"),r.text=kf(r.x,e,n,a)}function rG(e,r,t,a,n){if(e.thetaunit==="radians"&&!t){var i=r.x/180;if(i===0)r.text="0";else{var l=tG(i);if(l[1]>=100)r.text=kf(gr.deg2rad(r.x),e,n,a);else{var o=r.x<0;l[1]===1?l[0]===1?r.text="\u03C0":r.text=l[0]+"\u03C0":r.text=["",l[0],"","\u2044","",l[1],"","\u03C0"].join(""),o&&(r.text=Co+r.text)}}}else r.text=kf(r.x,e,n,a)}function tG(e){function r(o,s){return Math.abs(o-s)<=1e-6}function t(o,s){return r(s,0)?o:t(s,o%s)}function a(o){for(var s=1;!r(Math.round(o*s)/s,o);)s*=10;return s}var n=a(e),i=e*n,l=Math.abs(t(i,n));return[Math.round(i/l),Math.round(n/l)]}var z8=["f","p","n","\u03BC","m","","k","M","G","T"],aG=["q","r","y","z","a",...z8,"P","E","Z","Y","R","Q"],Ks=e=>["SI","SI extended","B"].includes(e);function $m(e){return e>14||e<-15}function jm(e){return e>32||e<-30}function nG(e,r){return Ks(r)?!!(r==="SI extended"&&jm(e)||r!=="SI extended"&&$m(e)):!1}function kf(e,r,t,a){var n=e<0,i=r._tickround,l=t||r.exponentformat||"B",o=r._tickexponent,s=Ye.getTickFormat(r),u=r.separatethousands;if(a){var f={exponentformat:l,minexponent:r.minexponent,dtick:r.showexponent==="none"?r.dtick:Ht(e)&&Math.abs(e)||1,range:r.showexponent==="none"?r.range.map(r.r2d):[0,e||1]};P8(f),i=(Number(f._tickround)||0)+4,o=f._tickexponent,r.hoverformat&&(s=r.hoverformat)}if(s)return r._numFormat(s)(e).replace(/-/g,Co);var v=Math.pow(10,-i)/2;if(l==="none"&&(o=0),e=Math.abs(e),e
"),e.yLabel!==void 0&&(o+="y: "+e.yLabel+"
"),e.trace.type!=="choropleth"&&e.trace.type!=="choroplethmapbox"&&e.trace.type!=="choroplethmap"&&(o+=(o?"z: ":"")+e.zLabel)):r&&e[s+"Label"]===n?o=e[u+"Label"]||"":e.xLabel===void 0?e.yLabel!==void 0&&e.trace.type!=="scattercarpet"&&(o=e.yLabel):e.yLabel===void 0?o=e.xLabel:o="("+e.xLabel+", "+e.yLabel+")",(e.text||e.text===0)&&!Array.isArray(e.text)&&(o+=(o?"
":"")+e.text),e.extraText!==void 0&&(o+=(o?"
":"")+e.extraText),i&&o===""&&!e.hovertemplate&&(l===""&&i.remove(),o=l),(h=(v=e.trace)==null?void 0:v.hoverlabel)!=null&&h.split&&(e.hovertemplate="");let{hovertemplate:f=!1}=e;if(f){let d=e.hovertemplateLabels||e;e[s+"Label"]!==n&&(d[s+"other"]=d[s+"Val"],d[s+"otherLabel"]=d[s+"Label"]),o=Tt.hovertemplateString({data:[e.eventData[0]||{},e.trace._meta],fallback:e.trace.hovertemplatefallback,labels:d,locale:a._d3locale,template:f}),o=o.replace(yW,(m,y)=>(l=Bw(y,e.nameLength),""))}return[o,l]}function gW(e,r,t,a){var n=r?"xa":"ya",i=r?"ya":"xa",l=0,o=1,s=e.size(),u=new Array(s),f=0,v=a.minX,h=a.maxX,d=a.minY,m=a.maxY,y=function(Z){return Z*t._invScaleX},g=function(Z){return Z*t._invScaleY};e.each(function(Z){var V=Z[n],Y=Z[i],H=V._id.charAt(0)==="x",Q=V.range;f===0&&Q&&Q[0]>Q[1]!==H&&(o=-1);var K=0,ne=H?t.width:t.height;if(t.hovermode==="x"||t.hovermode==="y"){var pe=Ww(Z,r),ye=Z.anchor,Se=ye==="end"?-1:1,re,Ce;if(ye==="middle")re=Z.crossPos+(H?g(pe.y-Z.by/2):y(Z.bx/2+Z.tx2width/2)),Ce=re+(H?g(Z.by):y(Z.bx));else if(H)re=Z.crossPos+g(mt+pe.y)-g(Z.by/2-mt),Ce=re+g(Z.by);else{var de=y(Se*mt+pe.x),be=de+y(Se*Z.bx);re=Z.crossPos+Math.min(de,be),Ce=Z.crossPos+Math.max(de,be)}H?d!==void 0&&m!==void 0&&Math.min(Ce,m)-Math.max(re,d)>1&&(Y.side==="left"?(K=Y._mainLinePosition,ne=t.width):ne=Y._mainLinePosition):v!==void 0&&h!==void 0&&Math.min(Ce,h)-Math.max(re,v)>1&&(Y.side==="top"?(K=Y._mainLinePosition,ne=t.height):ne=Y._mainLinePosition)}u[f++]=[{datum:Z,traceIndex:Z.trace.index,dp:0,pos:Z.pos,posref:Z.posref,size:Z.by*(H?vW:1)/2,pmin:K,pmax:ne}]}),u.sort(function(Z,V){return Z[0].posref-V[0].posref||o*(V[0].traceIndex-Z[0].traceIndex)});var _,w,T,b,M,C,k;function q(Z){var V=Z[0],Y=Z[Z.length-1];if(w=V.pmin-V.pos-V.dp+V.size,T=Y.pos+Y.dp+Y.size-V.pmax,w>.01){for(M=Z.length-1;M>=0;M--)Z[M].dp+=w;_=!1}if(!(T<.01)){if(w<-.01){for(M=Z.length-1;M>=0;M--)Z[M].dp-=T;_=!1}if(_){var H=0;for(b=0;b