diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 9c373616b26..00000000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,606 +0,0 @@
-version: 2.1
-orbs:
- browser-tools: circleci/browser-tools@2.4.0
-
-# Inspired by:
-# https://github.com/CircleCI-Public/circleci-demo-workflows/blob/workspace-forwarding/.circleci/config.yml
-# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
-#
-# For list of official CircleCI node.js images, go to:
-# https://hub.docker.com/r/cimg/node/tags/
-
-jobs:
- install-and-cibuild:
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - checkout
- - run:
- name: Set up build environment
- command: .circleci/env_build.sh
- - run:
- name: Pretest
- command: npm run pretest
- - run:
- name: CI-Build
- command: npm run cibuild
- - run:
- name: Delete git
- command: rm -rf .git
- - persist_to_workspace:
- root: /home/circleci
- paths:
- - plotly.js
-
- timezone-jasmine:
- docker:
- # need '-browsers' version to test in real (xvfb-wrapped) browsers
- - image: cimg/node:18.20.4-browsers
- working_directory: ~/plotly.js
- steps:
- - run: sudo apt-get update
- - browser-tools/install_browser_tools:
- install_firefox: false
- install_geckodriver: false
- install_chrome: true
- chrome_version: "136.0.7103.113"
- - attach_workspace:
- at: ~/
- - run:
- name: Run hover_label test in UTC timezone
- environment:
- TZ: "UTC"
- command: date && npm run test-jasmine hover_label
- - run:
- name: Run hover_label test in Europe/Berlin timezone
- environment:
- TZ: "Europe/Berlin"
- command: date && npm run test-jasmine hover_label
- - run:
- name: Run hover_label test in Asia/Tokyo timezone
- environment:
- TZ: "Asia/Tokyo"
- command: date && npm run test-jasmine hover_label
- - run:
- name: Run hover_label test in America/Toronto timezone
- environment:
- TZ: "America/Toronto"
- command: date && npm run test-jasmine hover_label
-
- no-gl-jasmine:
- docker:
- # need '-browsers' version to test in real (xvfb-wrapped) browsers
- - image: cimg/node:18.20.4-browsers
- environment:
- # Alaska time (arbitrary timezone to test date logic)
- TZ: "America/Anchorage"
- parallelism: 4
- working_directory: ~/plotly.js
- steps:
- - run: sudo apt-get update
- - browser-tools/install_browser_tools:
- install_firefox: false
- install_geckodriver: false
- install_chrome: true
- chrome_version: "136.0.7103.113"
- - attach_workspace:
- at: ~/
- - run:
- name: Run jasmine tests (part A)
- command: .circleci/test.sh no-gl-jasmine
-
- webgl-jasmine:
- docker:
- # need '-browsers' version to test in real (xvfb-wrapped) browsers
- - image: cimg/node:18.20.4-browsers
- environment:
- # Alaska time (arbitrary timezone to test date logic)
- TZ: "America/Anchorage"
- parallelism: 8
- working_directory: ~/plotly.js
- steps:
- - run: sudo apt-get update
- - browser-tools/install_browser_tools:
- install_firefox: false
- install_geckodriver: false
- install_chrome: true
- chrome_version: "136.0.7103.113"
- - attach_workspace:
- at: ~/
- - run:
- name: Run jasmine tests (part B)
- command: .circleci/test.sh webgl-jasmine
-
- virtual-webgl-jasmine:
- docker:
- # need '-browsers' version to test in real (xvfb-wrapped) browsers
- - image: cimg/node:18.20.4-browsers
- environment:
- # Alaska time (arbitrary timezone to test date logic)
- TZ: "America/Anchorage"
- parallelism: 8
- working_directory: ~/plotly.js
- steps:
- - run: sudo apt-get update
- - browser-tools/install_browser_tools:
- install_firefox: false
- install_geckodriver: false
- install_chrome: true
- chrome_version: "136.0.7103.113"
- - attach_workspace:
- at: ~/
- - run:
- name: Run jasmine tests (part B)
- command: .circleci/test.sh virtual-webgl-jasmine
-
- webgl-jasmine-chromeLatest:
- docker:
- # need '-browsers' version to test in real (xvfb-wrapped) browsers
- - image: cimg/node:18.20.4-browsers
- environment:
- # Alaska time (arbitrary timezone to test date logic)
- TZ: "America/Anchorage"
- parallelism: 8
- working_directory: ~/plotly.js
- steps:
- - browser-tools/install_browser_tools: &browser-versions
- install_firefox: false
- install_geckodriver: false
- install_chrome: true
- chrome_version: "143.0.7499.192" # TEMPORARY pin until WebGL issues with 144 are resolved
- - attach_workspace:
- at: ~/
- - run:
- name: Run jasmine tests (part B)
- command: .circleci/test.sh webgl-jasmine
-
- flaky-no-gl-jasmine:
- docker:
- # need '-browsers' version to test in real (xvfb-wrapped) browsers
- - image: cimg/node:18.20.4-browsers
- environment:
- # Alaska time (arbitrary timezone to test date logic)
- TZ: "America/Anchorage"
- working_directory: ~/plotly.js
- steps:
- - run: sudo apt-get update
- - browser-tools/install_browser_tools:
- install_firefox: false
- install_geckodriver: false
- install_chrome: true
- chrome_version: "136.0.7103.113"
- - attach_workspace:
- at: ~/
- - run:
- name: Run jasmine tests (part C)
- command: .circleci/test.sh flaky-no-gl-jasmine
-
- bundle-jasmine:
- docker:
- # need '-browsers' version to test in real (xvfb-wrapped) browsers
- - image: cimg/node:18.20.4-browsers
- environment:
- # Alaska time (arbitrary timezone to test date logic)
- TZ: "America/Anchorage"
- working_directory: ~/plotly.js
- steps:
- - run: sudo apt-get update
- - browser-tools/install_browser_tools:
- install_firefox: false
- install_geckodriver: false
- install_chrome: true
- chrome_version: "136.0.7103.113"
- - attach_workspace:
- at: ~/
- - run:
- name: Run jasmine tests (part D)
- command: .circleci/test.sh bundle-jasmine
-
- mathjax-firefoxLatest:
- docker:
- # need '-browsers' version to test in real (xvfb-wrapped) browsers
- - image: cimg/node:18.20.4-browsers
- environment:
- # Alaska time (arbitrary timezone to test date logic)
- TZ: "America/Anchorage"
- working_directory: ~/plotly.js
- steps:
- - browser-tools/install_browser_tools:
- install_chrome: false
- install_chromedriver: false
- - attach_workspace:
- at: ~/
- - run:
- name: Test MathJax on firefox-latest
- command: .circleci/test.sh mathjax-firefox
-
- make-baselines-virtual-webgl:
- parallelism: 8
- docker:
- - image: cimg/python:3.12.11
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run: sudo apt-get update
- - run:
- name: Install kaleido, plotly.io and required fonts
- command: .circleci/env_image.sh
- - run:
- name: Create png files using virtual-webgl & WebGL v1
- command: .circleci/test.sh make-baselines-virtual-webgl
- - persist_to_workspace:
- root: ~/
- paths:
- - plotly.js
-
- make-baselines-mathjax3:
- docker:
- - image: cimg/python:3.12.11
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run: sudo apt-get update
- - run:
- name: Install kaleido, plotly.io and required fonts
- command: .circleci/env_image.sh
- - run:
- name: Create mathjax v3 png files
- command: .circleci/test.sh make-baselines-mathjax3
- - persist_to_workspace:
- root: ~/
- paths:
- - plotly.js
-
- make-baselines:
- parallelism: 12
- docker:
- - image: cimg/python:3.12.11
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run: sudo apt-get update
- - run:
- name: Install kaleido, plotly.io and required fonts
- command: .circleci/env_image.sh
- - run:
- name: Create all png files
- command: .circleci/test.sh make-baselines
- - persist_to_workspace:
- root: ~/
- paths:
- - plotly.js
-
- make-baselines-b64:
- parallelism: 12
- docker:
- - image: cimg/python:3.12.11
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run: sudo apt-get update
- - run:
- name: Install kaleido, plotly.io and required fonts
- command: .circleci/env_image.sh
- - run:
- name: Create all png files
- command: .circleci/test.sh make-baselines-b64
- - persist_to_workspace:
- root: ~/
- paths:
- - plotly.js
-
- test-baselines:
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run:
- name: Compare pixels
- command: .circleci/test.sh test-image ; find build -maxdepth 1 -type f -delete
- - image-diff-message
- - store_artifacts:
- path: build
- destination: /
-
- test-baselines-virtual-webgl:
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run:
- name: Compare pixels
- command: .circleci/test.sh test-image-virtual-webgl ; find build -maxdepth 1 -type f -delete
- - image-diff-message
- - store_artifacts:
- path: build
- destination: /
-
- test-baselines-b64:
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run:
- name: Compare pixels
- command: .circleci/test.sh test-image ; find build -maxdepth 1 -type f -delete
- - image-diff-message
- - store_artifacts:
- path: build
- destination: /
-
- test-baselines-mathjax3:
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run:
- name: Compare pixels of mathjax v3 baselines
- command: .circleci/test.sh test-image-mathjax3
- - image-diff-message
- - store_artifacts:
- path: build
- destination: /
-
- make-exports:
- docker:
- - image: cimg/python:3.12.11
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run: sudo apt-get update
- - run:
- name: Install kaleido, plotly.io and required fonts
- command: .circleci/env_image.sh
- - run:
- name: Install poppler-utils to have pdftops for exporting eps
- command: |
- sudo apt-get update --allow-releaseinfo-change
- sudo apt-get install poppler-utils
- - run:
- name: Create svg, jpg, jpeg, webp, pdf and eps files
- command: sudo python3 test/image/make_exports.py
- - persist_to_workspace:
- root: ~/
- paths:
- - plotly.js
-
- test-exports:
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run:
- name: Test export sizes
- command: node test/image/export_test.js ; find build -maxdepth 1 -type f -delete
- - store_artifacts:
- path: build
- destination: /
-
- mock-validation:
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run:
- name: Test validation using node.js and jsdom
- command: npm run test-plain-obj
- - run:
- name: Validate mocks
- command: npm run test-mock
-
- source-syntax:
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - attach_workspace:
- at: ~/
- - run:
- name: Run syntax tests on source files
- command: .circleci/test.sh source-syntax
-
- publish-dist: &publish-dist
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - checkout
- - run:
- name: Set up build environment
- command: .circleci/env_build.sh
- - run:
- name: Preview CHANGELOG for next release (only on master)
- command: |
- if [ $CIRCLE_BRANCH == "master" ]
- then npm run use-draftlogs && git --no-pager diff --color-words CHANGELOG.md || true
- fi
- - run:
- name: Set draft version in package.json
- command: |
- 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`
- - run:
- name: View package.json diff between previous and next releases (including above draft version change)
- command: git --no-pager diff --color-words tags/$(git describe --tags --abbrev=0) package.json || true
- - run:
- name: Build dist/
- command: npm run build
- - store_artifacts:
- path: dist
- destination: dist
- - run:
- name: View dist/README.md diff between previous and next releases (including new bundle sizes)
- command: git --no-pager diff --color-words tags/$(git describe --tags --abbrev=0) dist/README.md || true
- - run:
- name: Preview plot-schema diff between previous and next releases (only on master)
- command: |
- if [ $CIRCLE_BRANCH == "master" ]
- then git --no-pager diff tags/$(git describe --tags --abbrev=0) dist/plot-schema.json || true
- fi
- - run:
- name: Pack tarball
- command: |
- npm pack
- version=$(node --eval "console.log(require('./package.json').version)")
- mv plotly.js-$version.tgz plotly.js.tgz
- - store_artifacts:
- path: plotly.js.tgz
- destination: /plotly.js.tgz
- - run:
- name: Show URLs to build files
- command: |
- PROJECT_NUM=45646037
- echo https://$CIRCLE_BUILD_NUM-$PROJECT_NUM-gh.circle-artifacts.com/0/plotly.js.tgz
- echo https://$CIRCLE_BUILD_NUM-$PROJECT_NUM-gh.circle-artifacts.com/0/dist/plotly.js
- echo https://$CIRCLE_BUILD_NUM-$PROJECT_NUM-gh.circle-artifacts.com/0/dist/plotly.min.js
- echo https://$CIRCLE_BUILD_NUM-$PROJECT_NUM-gh.circle-artifacts.com/0/dist/plot-schema.json
- - run:
- name: Test plot-schema.json diff - If failed, after (npm start) you could run (npm run schema && git add test/plot-schema.json && git commit -m "update plot-schema diff")
- command: diff --unified --color dist/plot-schema.json test/plot-schema.json
-
- publish-dist-node-v22:
- <<: *publish-dist
- docker:
- - image: cimg/node:22.14.0
-
- test-stackgl-bundle:
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - checkout
- - run:
- name: Set up build environment
- command: cd stackgl_modules && npm ci
- - run:
- name: Bundle bundle-stackgl/index.js
- command: cd stackgl_modules && cp index.js INDEX.js && npm run bundle-stackgl
- - run:
- name: Test stackgl_modules/index.js diff - If failed please remember this file in auto generated and you should not modify it directly until a dependency change. To suggest changes please submit pull request to the relevant dependency.
- command: diff --unified --color stackgl_modules/INDEX.js stackgl_modules/index.js
- - store_artifacts:
- path: stackgl_modules/index.js
- destination: stackgl_modules/index.js
-
- test-topojson-build:
- docker:
- - image: cimg/node:18.20.4
- working_directory: ~/plotly.js
- steps:
- - checkout
- - run:
- name: Set up build environment
- command: cd topojson && npm ci
- - run:
- name: Build topojson
- command: cd topojson && mv dist dist_backup && npm run build
- - run:
- name: Compare existing files with newly built files. Any difference is a failure. A failure might mean that the source data changed.
- command: diff -qr topojson/dist topojson/dist_backup
- - run:
- name: Compress artifacts
- command: tar -cvzf topojson.tar topojson/dist
- - store_artifacts:
- path: topojson.tar
-
-commands:
- image-diff-message:
- steps:
- - run:
- name: IMAGE DIFF DETECTED - SEE NOTE BELOW
- when: on_fail
- command: |
- echo "Image Diff Detected: baseline images may need to be updated. Run 'tasks/circleci_image_artifact_download.sh' to download the baseline images generated by this job."
- echo "Add the new images to 'test/image/baselines/' and commit them to this pull request."
-
-workflows:
- version: 2
- build-and-test:
- max_auto_reruns: 1
- jobs:
- - install-and-cibuild
- - timezone-jasmine:
- requires:
- - install-and-cibuild
- - bundle-jasmine:
- requires:
- - install-and-cibuild
- - mathjax-firefoxLatest:
- requires:
- - install-and-cibuild
- - no-gl-jasmine:
- requires:
- - install-and-cibuild
- - webgl-jasmine:
- requires:
- - install-and-cibuild
- - virtual-webgl-jasmine:
- requires:
- - install-and-cibuild
- - webgl-jasmine-chromeLatest:
- requires:
- - install-and-cibuild
- - flaky-no-gl-jasmine:
- requires:
- - install-and-cibuild
- - make-baselines-virtual-webgl:
- requires:
- - install-and-cibuild
- - test-baselines-virtual-webgl:
- requires:
- - make-baselines-virtual-webgl
- - make-baselines-mathjax3:
- requires:
- - install-and-cibuild
- - test-baselines-mathjax3:
- requires:
- - make-baselines-mathjax3
- - make-baselines-b64:
- requires:
- - install-and-cibuild
- - test-baselines-b64:
- requires:
- - make-baselines-b64
- - make-baselines:
- requires:
- - install-and-cibuild
- - test-baselines:
- requires:
- - make-baselines
- - make-exports:
- requires:
- - install-and-cibuild
- - test-exports:
- requires:
- - make-exports
- - mock-validation:
- requires:
- - install-and-cibuild
- - source-syntax:
- requires:
- - install-and-cibuild
-
- - publish-dist
-
- - publish-dist-node-v22
-
- - test-stackgl-bundle
-
- - test-topojson-build
diff --git a/.circleci/download_google_fonts.py b/.circleci/download_google_fonts.py
deleted file mode 100644
index 8dc9dd7daa6..00000000000
--- a/.circleci/download_google_fonts.py
+++ /dev/null
@@ -1,82 +0,0 @@
-import os
-
-import requests
-
-dir_out = ".circleci/fonts/truetype/googleFonts/"
-
-
-def download(repo, family, types, overwrite=True):
- for t in types:
- name = family + t + ".ttf"
- url = repo + name + "?raw=true"
- out_file = dir_out + name
- print("Getting: ", url)
- if os.path.exists(out_file) and not overwrite:
- print(" => Already exists: ", out_file)
- continue
- req = requests.get(url, allow_redirects=False)
- if req.status_code != 200:
- # If we get a redirect, print an error so that we know to update the URL
- if req.status_code == 302 or req.status_code == 301:
- new_url = req.headers.get("Location")
- print(f" => Redirected -- please update URL to: {new_url}")
- raise RuntimeError(f"""
-Download failed.
-Status code: {req.status_code}
-Message: {req.reason}
-""")
- open(out_file, "wb").write(req.content)
-
-
-download(
- "https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansMono/hinted/ttf/",
- "NotoSansMono",
- ["-Regular", "-Bold"],
-)
-
-download(
- "https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSans/hinted/ttf/",
- "NotoSans",
- ["-Regular", "-Italic", "-Bold"],
-)
-
-download(
- "https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSerif/hinted/ttf/",
- "NotoSerif",
- [
- "-Regular",
- "-Italic",
- "-Bold",
- "-BoldItalic",
- ],
-)
-
-download(
- "https://raw.githubusercontent.com/google/fonts/refs/heads/main/ofl/oldstandardtt/",
- "OldStandard",
- ["-Regular", "-Italic", "-Bold"],
-)
-
-download(
- "https://raw.githubusercontent.com/google/fonts/refs/heads/main/ofl/ptsansnarrow/",
- "PT_Sans-Narrow-Web",
- ["-Regular", "-Bold"],
-)
-
-download(
- "https://raw.githubusercontent.com/impallari/Raleway/refs/heads/master/fonts/v3.000%20Fontlab/TTF/",
- "Raleway",
- ["-Regular", "-Regular-Italic", "-Bold", "-Bold-Italic"],
-)
-
-download(
- "https://raw.githubusercontent.com/googlefonts/roboto-2/refs/heads/main/src/hinted/",
- "Roboto",
- ["-Regular", "-Italic", "-Bold", "-BoldItalic"],
-)
-
-download(
- "https://raw.githubusercontent.com/expo/google-fonts/refs/heads/main/font-packages/gravitas-one/400Regular/",
- "GravitasOne",
- ["_400Regular"],
-)
diff --git a/.circleci/env_image.sh b/.circleci/env_image.sh
deleted file mode 100755
index 17e2f5bfa26..00000000000
--- a/.circleci/env_image.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/sh
-set -e
-# install required fonts
-sudo apt-get install fonts-liberation2 fonts-open-sans fonts-noto-cjk fonts-noto-color-emoji
-
-# install pip
-sudo curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
-sudo python3 get-pip.py
-
-# install additional fonts
-sudo python3 -m pip install requests
-sudo python3 .circleci/download_google_fonts.py
-sudo cp -r .circleci/fonts/ /usr/share/
-sudo apt install fontconfig
-sudo fc-cache -f
-
-# install kaleido & plotly
-sudo python3 -m pip install kaleido==0.2.1 plotly==6.2.0 --progress-bar off
-
-# install numpy i.e. to convert arrays to typed arrays
-sudo python3 -m pip install numpy==1.24.2
diff --git a/.github/actions/run-xvfb/action.yml b/.github/actions/run-xvfb/action.yml
new file mode 100644
index 00000000000..36b2d96fcfc
--- /dev/null
+++ b/.github/actions/run-xvfb/action.yml
@@ -0,0 +1,13 @@
+name: 'Run with Xvfb'
+description: 'Run a command under Xvfb with a preconfigured screen size'
+
+inputs:
+ run:
+ description: 'Command to execute'
+ required: true
+
+runs:
+ using: 'composite'
+ steps:
+ - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x1024x24" ${{ inputs.run }}
+ shell: bash
diff --git a/.github/actions/setup-chrome/action.yml b/.github/actions/setup-chrome/action.yml
new file mode 100644
index 00000000000..fe6bc48700b
--- /dev/null
+++ b/.github/actions/setup-chrome/action.yml
@@ -0,0 +1,25 @@
+name: 'Setup Chrome'
+description: 'Install Chrome and set CHROME_BIN environment variable'
+
+inputs:
+ chrome-version:
+ description: 'Chrome version to install'
+ default: '136.0.7103.113'
+
+runs:
+ using: 'composite'
+ steps:
+ - uses: browser-actions/setup-chrome@v2
+ id: setup-chrome
+ with:
+ chrome-version: ${{ inputs.chrome-version }}
+
+ - name: Set Chrome binary path
+ run: echo "CHROME_BIN=${{ steps.setup-chrome.outputs.chrome-path }}" >> $GITHUB_ENV
+ shell: bash
+
+ - name: Verify Chrome version
+ run: |
+ echo "Chrome path: $CHROME_BIN"
+ $CHROME_BIN --version
+ shell: bash
diff --git a/.github/actions/setup-image-env/action.yml b/.github/actions/setup-image-env/action.yml
new file mode 100644
index 00000000000..86a84869a0a
--- /dev/null
+++ b/.github/actions/setup-image-env/action.yml
@@ -0,0 +1,35 @@
+name: 'Setup Image Environment'
+description: 'Setup Python, uv, and install Kaleido/plotly with required fonts'
+
+inputs:
+ python-version:
+ description: 'Python version to use'
+ default: '3.12'
+ uv-version:
+ description: 'uv version to use'
+ default: '0.11.2'
+
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-python@v6
+ with:
+ python-version: ${{ inputs.python-version }}
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
+ with:
+ version: ${{ inputs.uv-version }}
+
+ - name: Cache apt font packages
+ uses: actions/cache@v4
+ id: apt-cache
+ with:
+ path: ~/.cache/apt-fonts
+ key: apt-fonts-${{ runner.os }}-${{ hashFiles('.github/scripts/env_image.sh') }}
+
+ - name: Install Kaleido, plotly.io and required fonts
+ run: .github/scripts/env_image.sh
+ shell: bash
+ env:
+ APT_CACHE_HIT: ${{ steps.apt-cache.outputs.cache-hit }}
diff --git a/.github/actions/setup-workspace/action.yml b/.github/actions/setup-workspace/action.yml
new file mode 100644
index 00000000000..b21fc046a46
--- /dev/null
+++ b/.github/actions/setup-workspace/action.yml
@@ -0,0 +1,26 @@
+name: 'Setup Workspace'
+description: 'Setup Node.js, install dependencies, and download build artifacts'
+
+inputs:
+ node-version:
+ description: 'Node.js version to use'
+ default: '18.20.4'
+
+runs:
+ using: 'composite'
+ steps:
+ - uses: actions/setup-node@v6
+ with:
+ node-version: ${{ inputs.node-version }}
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+ shell: bash
+ env:
+ NODE_OPTIONS: '--max-old-space-size=4096'
+
+ - uses: actions/download-artifact@v8
+ with:
+ name: build-output
+ path: .
diff --git a/.circleci/fonts/truetype/googleFonts/GravitasOne_400Regular.ttf b/.github/fonts/truetype/googleFonts/GravitasOne_400Regular.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/GravitasOne_400Regular.ttf
rename to .github/fonts/truetype/googleFonts/GravitasOne_400Regular.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/NotoSans-Bold.ttf b/.github/fonts/truetype/googleFonts/NotoSans-Bold.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/NotoSans-Bold.ttf
rename to .github/fonts/truetype/googleFonts/NotoSans-Bold.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/NotoSans-Italic.ttf b/.github/fonts/truetype/googleFonts/NotoSans-Italic.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/NotoSans-Italic.ttf
rename to .github/fonts/truetype/googleFonts/NotoSans-Italic.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/NotoSans-Regular.ttf b/.github/fonts/truetype/googleFonts/NotoSans-Regular.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/NotoSans-Regular.ttf
rename to .github/fonts/truetype/googleFonts/NotoSans-Regular.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/NotoSansMono-Bold.ttf b/.github/fonts/truetype/googleFonts/NotoSansMono-Bold.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/NotoSansMono-Bold.ttf
rename to .github/fonts/truetype/googleFonts/NotoSansMono-Bold.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/NotoSansMono-Regular.ttf b/.github/fonts/truetype/googleFonts/NotoSansMono-Regular.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/NotoSansMono-Regular.ttf
rename to .github/fonts/truetype/googleFonts/NotoSansMono-Regular.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/NotoSerif-Bold.ttf b/.github/fonts/truetype/googleFonts/NotoSerif-Bold.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/NotoSerif-Bold.ttf
rename to .github/fonts/truetype/googleFonts/NotoSerif-Bold.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/NotoSerif-BoldItalic.ttf b/.github/fonts/truetype/googleFonts/NotoSerif-BoldItalic.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/NotoSerif-BoldItalic.ttf
rename to .github/fonts/truetype/googleFonts/NotoSerif-BoldItalic.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/NotoSerif-Italic.ttf b/.github/fonts/truetype/googleFonts/NotoSerif-Italic.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/NotoSerif-Italic.ttf
rename to .github/fonts/truetype/googleFonts/NotoSerif-Italic.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/NotoSerif-Regular.ttf b/.github/fonts/truetype/googleFonts/NotoSerif-Regular.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/NotoSerif-Regular.ttf
rename to .github/fonts/truetype/googleFonts/NotoSerif-Regular.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/OldStandard-Bold.ttf b/.github/fonts/truetype/googleFonts/OldStandard-Bold.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/OldStandard-Bold.ttf
rename to .github/fonts/truetype/googleFonts/OldStandard-Bold.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/OldStandard-Italic.ttf b/.github/fonts/truetype/googleFonts/OldStandard-Italic.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/OldStandard-Italic.ttf
rename to .github/fonts/truetype/googleFonts/OldStandard-Italic.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/OldStandard-Regular.ttf b/.github/fonts/truetype/googleFonts/OldStandard-Regular.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/OldStandard-Regular.ttf
rename to .github/fonts/truetype/googleFonts/OldStandard-Regular.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/PT_Sans-Narrow-Web-Bold.ttf b/.github/fonts/truetype/googleFonts/PT_Sans-Narrow-Web-Bold.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/PT_Sans-Narrow-Web-Bold.ttf
rename to .github/fonts/truetype/googleFonts/PT_Sans-Narrow-Web-Bold.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/PT_Sans-Narrow-Web-Regular.ttf b/.github/fonts/truetype/googleFonts/PT_Sans-Narrow-Web-Regular.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/PT_Sans-Narrow-Web-Regular.ttf
rename to .github/fonts/truetype/googleFonts/PT_Sans-Narrow-Web-Regular.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/Raleway-Bold-Italic.ttf b/.github/fonts/truetype/googleFonts/Raleway-Bold-Italic.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/Raleway-Bold-Italic.ttf
rename to .github/fonts/truetype/googleFonts/Raleway-Bold-Italic.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/Raleway-Bold.ttf b/.github/fonts/truetype/googleFonts/Raleway-Bold.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/Raleway-Bold.ttf
rename to .github/fonts/truetype/googleFonts/Raleway-Bold.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/Raleway-Regular-Italic.ttf b/.github/fonts/truetype/googleFonts/Raleway-Regular-Italic.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/Raleway-Regular-Italic.ttf
rename to .github/fonts/truetype/googleFonts/Raleway-Regular-Italic.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/Raleway-Regular.ttf b/.github/fonts/truetype/googleFonts/Raleway-Regular.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/Raleway-Regular.ttf
rename to .github/fonts/truetype/googleFonts/Raleway-Regular.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/Roboto-Bold.ttf b/.github/fonts/truetype/googleFonts/Roboto-Bold.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/Roboto-Bold.ttf
rename to .github/fonts/truetype/googleFonts/Roboto-Bold.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/Roboto-BoldItalic.ttf b/.github/fonts/truetype/googleFonts/Roboto-BoldItalic.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/Roboto-BoldItalic.ttf
rename to .github/fonts/truetype/googleFonts/Roboto-BoldItalic.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/Roboto-Italic.ttf b/.github/fonts/truetype/googleFonts/Roboto-Italic.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/Roboto-Italic.ttf
rename to .github/fonts/truetype/googleFonts/Roboto-Italic.ttf
diff --git a/.circleci/fonts/truetype/googleFonts/Roboto-Regular.ttf b/.github/fonts/truetype/googleFonts/Roboto-Regular.ttf
similarity index 100%
rename from .circleci/fonts/truetype/googleFonts/Roboto-Regular.ttf
rename to .github/fonts/truetype/googleFonts/Roboto-Regular.ttf
diff --git a/.circleci/env_build.sh b/.github/scripts/env_build.sh
similarity index 100%
rename from .circleci/env_build.sh
rename to .github/scripts/env_build.sh
diff --git a/.github/scripts/env_image.sh b/.github/scripts/env_image.sh
new file mode 100755
index 00000000000..a27204a3c1e
--- /dev/null
+++ b/.github/scripts/env_image.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+set -e
+
+APT_PACKAGES="fonts-liberation2 fonts-open-sans fonts-noto-cjk fonts-noto-color-emoji fontconfig"
+APT_CACHE_DIR="${HOME}/.cache/apt-fonts"
+
+if [ "$APT_CACHE_HIT" = "true" ] && [ -d "$APT_CACHE_DIR" ]; then
+ echo "Installing font packages from cache..."
+ sudo dpkg -i "$APT_CACHE_DIR"/*.deb 2>/dev/null || sudo apt-get install -yf
+else
+ echo "Downloading and installing font packages..."
+ sudo apt-get update -q
+ sudo apt-get install -y --no-install-recommends $APT_PACKAGES
+ # Save debs for future cache
+ mkdir -p "$APT_CACHE_DIR"
+ for pkg in $APT_PACKAGES; do
+ cp /var/cache/apt/archives/${pkg}_*.deb "$APT_CACHE_DIR/" 2>/dev/null || true
+ done
+fi
+
+# Rebuild font cache
+sudo fc-cache -f
+
+# Install additional fonts (committed in .github/fonts/)
+sudo cp -r .github/fonts/ /usr/share/
+sudo fc-cache -f
+
+# Install Kaleido & Plotly
+uv pip install --system kaleido==1.2 plotly==6.6.0 --no-progress
+
+# Install numpy i.e. to convert arrays to typed arrays
+uv pip install --system numpy==2.4.3
+
+# Verify version of python and versions of installed python packages
+python --version
+uv pip freeze --system
diff --git a/.github/scripts/split_files.mjs b/.github/scripts/split_files.mjs
new file mode 100755
index 00000000000..8841ab33ea8
--- /dev/null
+++ b/.github/scripts/split_files.mjs
@@ -0,0 +1,20 @@
+#!/usr/bin/env node
+
+// Replacement for `circleci tests split`
+// Reads lines from stdin and emits only those where index % SHARD_TOTAL == SHARD_INDEX
+// Environment variables SHARD_INDEX and SHARD_TOTAL must be set.
+
+import { readFileSync } from 'fs';
+
+const lines = readFileSync('/dev/stdin', 'utf8').trim().split('\n').filter(Boolean);
+const index = parseInt(process.env.SHARD_INDEX);
+const total = parseInt(process.env.SHARD_TOTAL);
+
+if (isNaN(index) || isNaN(total) || total <= 0) {
+ console.error('SHARD_INDEX and SHARD_TOTAL environment variables must be set to valid integers');
+ process.exit(1);
+}
+
+lines.forEach((line, i) => {
+ if (i % total === index) console.log(line);
+});
diff --git a/.circleci/test.sh b/.github/scripts/test.sh
similarity index 71%
rename from .circleci/test.sh
rename to .github/scripts/test.sh
index 4fc876018b8..7bd42ff6feb 100755
--- a/.circleci/test.sh
+++ b/.github/scripts/test.sh
@@ -1,10 +1,13 @@
#!/bin/bash
-# override CircleCi's default run settings
+# GitHub Actions version of .circleci/test.sh
+# Replaces `circleci tests split` with split_files.mjs
+
set +e
set +o pipefail
-ROOT=$(dirname $0)/..
+ROOT=$(dirname $0)/../..
+SPLIT="$ROOT/.github/scripts/split_files.mjs"
EXIT_STATE=0
MAX_AUTO_RETRY=0
@@ -33,10 +36,13 @@ retry () {
fi
}
+# Ensure output directories exist (not present in fresh GHA checkout)
+mkdir -p build/test_images
+
case $1 in
no-gl-jasmine)
- SUITE=$(circleci tests glob "$ROOT/test/jasmine/tests/*" | circleci tests split)
+ SUITE=$(ls -1 $ROOT/test/jasmine/tests/* | sort | node "$SPLIT")
MAX_AUTO_RETRY=2
retry npm run test-jasmine -- $SUITE --skip-tags=gl,noCI,flaky || EXIT_STATE=$?
@@ -44,7 +50,7 @@ case $1 in
;;
webgl-jasmine)
- SHARDS=($(node $ROOT/tasks/shard_jasmine_tests.js --limit=5 --tag=gl | circleci tests split))
+ SHARDS=($(node $ROOT/tasks/shard_jasmine_tests.js --limit=5 --tag=gl | node "$SPLIT"))
for s in ${SHARDS[@]}; do
MAX_AUTO_RETRY=2
retry npm run test-jasmine -- "$s" --tags=gl --skip-tags=noCI --doNotFailOnEmptyTestSuite
@@ -54,7 +60,7 @@ case $1 in
;;
virtual-webgl-jasmine)
- SHARDS=($(node $ROOT/tasks/shard_jasmine_tests.js --limit=5 --tag=gl | circleci tests split))
+ SHARDS=($(node $ROOT/tasks/shard_jasmine_tests.js --limit=5 --tag=gl | node "$SPLIT"))
for s in ${SHARDS[@]}; do
MAX_AUTO_RETRY=2
retry ./node_modules/karma/bin/karma start test/jasmine/karma.conf.js --virtualWebgl --tags=gl --skip-tags=noCI,noVirtualWebgl --doNotFailOnEmptyTestSuite -- "$s"
@@ -64,7 +70,7 @@ case $1 in
;;
flaky-no-gl-jasmine)
- SHARDS=($(node $ROOT/tasks/shard_jasmine_tests.js --limit=1 --tag=flaky | circleci tests split))
+ SHARDS=($(node $ROOT/tasks/shard_jasmine_tests.js --limit=1 --tag=flaky | node "$SPLIT"))
for s in ${SHARDS[@]}; do
MAX_AUTO_RETRY=5
@@ -96,40 +102,46 @@ case $1 in
SUITE=$({\
find $ROOT/test/image/mocks/gl* -type f -printf "%f\n"; \
find $ROOT/test/image/mocks/map* -type f -printf "%f\n"; \
- } | sed 's/\.json$//1' | circleci tests split)
- sudo python3 test/image/make_baseline.py virtual-webgl $SUITE || EXIT_STATE=$?
+ } | sed 's/\.json$//1' | sort | node "$SPLIT")
+ python test/image/make_baseline.py virtual-webgl $SUITE || EXIT_STATE=$?
exit $EXIT_STATE
;;
make-baselines-mathjax3)
- sudo python3 test/image/make_baseline.py mathjax3 legend_mathjax_title_and_items mathjax parcats_grid_subplots table_latex_multitrace_scatter table_plain_birds table_wrapped_birds ternary-mathjax ternary-mathjax-title-place-subtitle || EXIT_STATE=$?
+ MATHJAX3_MOCKS=$(jq -r '.compare_mathjax3 | join(" ")' test/image/compare_pixels_collections.json)
+ python test/image/make_baseline.py mathjax3 $MATHJAX3_MOCKS || EXIT_STATE=$?
exit $EXIT_STATE
;;
make-baselines-b64)
- SUITE=$(find $ROOT/test/image/mocks/ -type f -printf "%f\n" | sed 's/\.json$//1' | circleci tests split)
- sudo python3 test/image/make_baseline.py b64 $SUITE || EXIT_STATE=$?
+ SUITE=$(find $ROOT/test/image/mocks/ -type f -printf "%f\n" | sed 's/\.json$//1' | sort | node "$SPLIT")
+ python test/image/make_baseline.py b64 $SUITE || EXIT_STATE=$?
exit $EXIT_STATE
;;
make-baselines)
- SUITE=$(find $ROOT/test/image/mocks/ -type f -printf "%f\n" | sed 's/\.json$//1' | circleci tests split)
- sudo python3 test/image/make_baseline.py $SUITE || EXIT_STATE=$?
+ SUITE=$(find $ROOT/test/image/mocks/ -type f -printf "%f\n" | sed 's/\.json$//1' | sort | node "$SPLIT")
+ python test/image/make_baseline.py $SUITE || EXIT_STATE=$?
+ exit $EXIT_STATE
+ ;;
+
+ make-exports)
+ python test/image/make_exports.py || EXIT_STATE=$?
exit $EXIT_STATE
;;
test-image)
- node test/image/compare_pixels_test.js || { tar -cvf build/baselines.tar build/test_images/*.png ; exit 1 ; } || EXIT_STATE=$?
+ node test/image/compare_pixels_test.mjs || EXIT_STATE=$?
exit $EXIT_STATE
;;
test-image-mathjax3)
- node test/image/compare_pixels_test.js mathjax3 || { tar -cvf build/baselines.tar build/test_images/*.png ; exit 1 ; } || EXIT_STATE=$?
+ node test/image/compare_pixels_test.mjs mathjax3 || EXIT_STATE=$?
exit $EXIT_STATE
;;
test-image-virtual-webgl)
- node test/image/compare_pixels_test.js virtual-webgl || { tar -cvf build/baselines.tar build/test_images/*.png ; exit 1 ; } || EXIT_STATE=$?
+ node test/image/compare_pixels_test.mjs virtual-webgl || EXIT_STATE=$?
exit $EXIT_STATE
;;
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000000..e9d52b8bd5f
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,712 @@
+name: CI
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: ci-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+env:
+ # TODO: Update this version to match library
+ NODE_VERSION: '18.20.4'
+
+jobs:
+ # ============================================================
+ # Root build job - all dependent jobs fan out from here
+ # ============================================================
+ install-and-cibuild:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+ env:
+ NODE_OPTIONS: '--max-old-space-size=4096'
+
+ - name: Pretest
+ run: npm run pretest
+
+ - name: CI-Build
+ run: npm run cibuild
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: build-output
+ retention-days: 3
+ path: |
+ build/
+ lib/
+ src/version.js
+
+ # ============================================================
+ # Jasmine browser tests
+ # ============================================================
+ timezone-jasmine:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+ - uses: ./.github/actions/setup-chrome
+
+ - name: Run hover_label test in UTC timezone
+ uses: ./.github/actions/run-xvfb
+ env:
+ TZ: 'UTC'
+ with:
+ run: npm run test-jasmine hover_label
+
+ - name: Run hover_label test in Europe/Berlin timezone
+ uses: ./.github/actions/run-xvfb
+ env:
+ TZ: 'Europe/Berlin'
+ with:
+ run: npm run test-jasmine hover_label
+
+ - name: Run hover_label test in Asia/Tokyo timezone
+ uses: ./.github/actions/run-xvfb
+ env:
+ TZ: 'Asia/Tokyo'
+ with:
+ run: npm run test-jasmine hover_label
+
+ - name: Run hover_label test in America/Toronto timezone
+ uses: ./.github/actions/run-xvfb
+ env:
+ TZ: 'America/Toronto'
+ with:
+ run: npm run test-jasmine hover_label
+
+ no-gl-jasmine:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ shard: [0, 1, 2, 3]
+ env:
+ TZ: 'America/Anchorage'
+ SHARD_INDEX: ${{ matrix.shard }}
+ SHARD_TOTAL: 4
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+ - uses: ./.github/actions/setup-chrome
+
+ - name: Run jasmine tests (no-gl, shard ${{ matrix.shard }})
+ uses: ./.github/actions/run-xvfb
+ with:
+ run: .github/scripts/test.sh no-gl-jasmine
+
+ webgl-jasmine:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ shard: [0, 1, 2, 3, 4, 5, 6, 7]
+ env:
+ TZ: 'America/Anchorage'
+ SHARD_INDEX: ${{ matrix.shard }}
+ SHARD_TOTAL: 8
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+ - uses: ./.github/actions/setup-chrome
+
+ - name: Run jasmine tests (webgl, shard ${{ matrix.shard }})
+ uses: ./.github/actions/run-xvfb
+ with:
+ run: .github/scripts/test.sh webgl-jasmine
+
+ virtual-webgl-jasmine:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ shard: [0, 1, 2, 3, 4, 5, 6, 7]
+ env:
+ TZ: 'America/Anchorage'
+ SHARD_INDEX: ${{ matrix.shard }}
+ SHARD_TOTAL: 8
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+ - uses: ./.github/actions/setup-chrome
+
+ - name: Run jasmine tests (virtual-webgl, shard ${{ matrix.shard }})
+ uses: ./.github/actions/run-xvfb
+ with:
+ run: .github/scripts/test.sh virtual-webgl-jasmine
+
+ webgl-jasmine-chromeLatest:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ shard: [0, 1, 2, 3, 4, 5, 6, 7]
+ env:
+ TZ: 'America/Anchorage'
+ SHARD_INDEX: ${{ matrix.shard }}
+ SHARD_TOTAL: 8
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+ - uses: ./.github/actions/setup-chrome
+ with:
+ chrome-version: '143.0.7499.192'
+
+ - name: Run jasmine tests (webgl chromeLatest, shard ${{ matrix.shard }})
+ uses: ./.github/actions/run-xvfb
+ with:
+ run: .github/scripts/test.sh webgl-jasmine
+
+ flaky-no-gl-jasmine:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ env:
+ TZ: 'America/Anchorage'
+ SHARD_INDEX: 0
+ SHARD_TOTAL: 1
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+ - uses: ./.github/actions/setup-chrome
+
+ - name: Run flaky jasmine tests
+ uses: ./.github/actions/run-xvfb
+ with:
+ run: .github/scripts/test.sh flaky-no-gl-jasmine
+
+ bundle-jasmine:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ env:
+ TZ: 'America/Anchorage'
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+ - uses: ./.github/actions/setup-chrome
+
+ - name: Run bundle jasmine tests
+ uses: ./.github/actions/run-xvfb
+ with:
+ run: .github/scripts/test.sh bundle-jasmine
+
+ mathjax-firefoxLatest:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ env:
+ TZ: 'America/Anchorage'
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: browser-actions/setup-firefox@v1
+
+ - name: Test MathJax on firefox-latest
+ uses: ./.github/actions/run-xvfb
+ with:
+ run: .github/scripts/test.sh mathjax-firefox
+
+ # ============================================================
+ # noCI tests
+ # ============================================================
+ noci-jasmine:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+ - uses: ./.github/actions/setup-chrome
+ with:
+ chrome-version: 'stable'
+
+ - name: Run noCI tests
+ uses: coactions/setup-xvfb@v1
+ with:
+ run: ./tasks/noci_test.sh jasmine
+
+ # ============================================================
+ # Image baseline generation and comparison
+ # ============================================================
+ make-baselines:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ shard: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
+ env:
+ SHARD_INDEX: ${{ matrix.shard }}
+ SHARD_TOTAL: 12
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: ./.github/actions/setup-image-env
+
+ - name: Create all png files
+ run: .github/scripts/test.sh make-baselines
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: baselines-default-${{ matrix.shard }}
+ retention-days: 3
+ path: build/test_images/
+
+ test-baselines:
+ needs: make-baselines
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: actions/download-artifact@v8
+ with:
+ pattern: baselines-default-*
+ path: build/test_images/
+ merge-multiple: true
+
+ - name: Compare pixels
+ run: .github/scripts/test.sh test-image
+ - name: List diff folder contents
+ if: failure()
+ run: |
+ echo "=== build/test_images/ ==="
+ ls -la build/test_images/ 2>/dev/null || echo "(empty or missing)"
+ echo "=== build/test_images_diff/ ==="
+ ls -la build/test_images_diff/ 2>/dev/null || echo "(empty or missing)"
+ - name: IMAGE DIFF DETECTED
+ if: failure()
+ run: |
+ echo "::warning::Image Diff Detected: baseline images may need to be updated."
+ echo "Download the baseline images from the artifacts of this workflow run."
+ echo "Add the new images to 'test/image/baselines/' and commit them to this pull request."
+
+ - uses: actions/upload-artifact@v7
+ if: failure()
+ with:
+ name: baselines-default-diff
+ retention-days: 7
+ path: |
+ build/test_images/
+ build/test_images_diff/
+
+ make-baselines-b64:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ shard: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
+ env:
+ SHARD_INDEX: ${{ matrix.shard }}
+ SHARD_TOTAL: 12
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: ./.github/actions/setup-image-env
+
+ - name: Create all png files (b64)
+ run: .github/scripts/test.sh make-baselines-b64
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: baselines-b64-${{ matrix.shard }}
+ retention-days: 3
+ path: build/test_images/
+
+ test-baselines-b64:
+ needs: make-baselines-b64
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: actions/download-artifact@v8
+ with:
+ pattern: baselines-b64-*
+ path: build/test_images/
+ merge-multiple: true
+
+ - name: Compare pixels
+ run: .github/scripts/test.sh test-image
+ - name: IMAGE DIFF DETECTED
+ if: failure()
+ run: |
+ echo "::warning::Image Diff Detected: baseline images may need to be updated."
+ echo "Download the baseline images from the artifacts of this workflow run."
+ echo "Add the new images to 'test/image/baselines/' and commit them to this pull request."
+
+ - uses: actions/upload-artifact@v7
+ if: failure()
+ with:
+ name: baselines-b64-diff
+ retention-days: 7
+ path: |
+ build/test_images/
+ build/test_images_diff/
+
+ make-baselines-virtual-webgl:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ shard: [0, 1, 2, 3, 4, 5, 6, 7]
+ env:
+ SHARD_INDEX: ${{ matrix.shard }}
+ SHARD_TOTAL: 8
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: ./.github/actions/setup-image-env
+
+ - name: Create png files (virtual-webgl)
+ run: .github/scripts/test.sh make-baselines-virtual-webgl
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: baselines-virtual-webgl-${{ matrix.shard }}
+ retention-days: 3
+ path: build/test_images/
+
+ test-baselines-virtual-webgl:
+ needs: make-baselines-virtual-webgl
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: actions/download-artifact@v8
+ with:
+ pattern: baselines-virtual-webgl-*
+ path: build/test_images/
+ merge-multiple: true
+
+ - name: Compare pixels
+ run: .github/scripts/test.sh test-image-virtual-webgl
+ - name: IMAGE DIFF DETECTED
+ if: failure()
+ run: |
+ echo "::warning::Image Diff Detected: baseline images may need to be updated."
+ echo "Download the baseline images from the artifacts of this workflow run."
+ echo "Add the new images to 'test/image/baselines/' and commit them to this pull request."
+
+ - uses: actions/upload-artifact@v7
+ if: failure()
+ with:
+ name: baselines-virtual-webgl-diff
+ retention-days: 7
+ path: |
+ build/test_images/
+ build/test_images_diff/
+
+ make-baselines-mathjax3:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: ./.github/actions/setup-image-env
+
+ - name: Create mathjax v3 png files
+ run: .github/scripts/test.sh make-baselines-mathjax3
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: baselines-mathjax3
+ retention-days: 3
+ path: build/test_images/
+
+ test-baselines-mathjax3:
+ needs: make-baselines-mathjax3
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: actions/download-artifact@v8
+ with:
+ name: baselines-mathjax3
+ path: build/test_images/
+
+ - name: Compare pixels of mathjax v3 baselines
+ run: .github/scripts/test.sh test-image-mathjax3
+
+ - name: IMAGE DIFF DETECTED
+ if: failure()
+ run: |
+ echo "::warning::Image Diff Detected: baseline images may need to be updated."
+ echo "Download the baseline images from the artifacts of this workflow run."
+ echo "Add the new images to 'test/image/baselines/' and commit them to this pull request."
+
+ - uses: actions/upload-artifact@v7
+ if: failure()
+ with:
+ name: baselines-mathjax3-diff
+ retention-days: 7
+ path: |
+ build/test_images/
+ build/test_images_diff/
+
+ # ============================================================
+ # Export generation and testing
+ # ============================================================
+ make-exports:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: ./.github/actions/setup-image-env
+
+ - name: Install poppler-utils for eps export
+ run: |
+ sudo apt-get update --allow-releaseinfo-change
+ sudo apt-get install poppler-utils
+
+ - name: Create svg, jpg, jpeg, webp, pdf and eps files
+ run: .github/scripts/test.sh make-exports
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: exports
+ retention-days: 3
+ path: build/
+
+ test-exports:
+ needs: make-exports
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - uses: actions/download-artifact@v8
+ with:
+ name: exports
+ path: build/
+
+ - name: Test export sizes
+ run: node test/image/export_test.js
+ - uses: actions/upload-artifact@v7
+ if: failure()
+ with:
+ name: exports-diff
+ retention-days: 7
+ path: build/
+
+ # ============================================================
+ # Validation jobs
+ # ============================================================
+ mock-validation:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - name: Test validation using node.js and jsdom
+ run: npm run test-plain-obj
+
+ - name: Validate mocks
+ run: npm run test-mock
+
+ source-syntax:
+ needs: install-and-cibuild
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: ./.github/actions/setup-workspace
+
+ - name: Run syntax tests on source files
+ run: .github/scripts/test.sh source-syntax
+
+ # ============================================================
+ # Standalone jobs (no dependencies on install-and-cibuild)
+ # ============================================================
+ publish-dist:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+
+ - uses: actions/setup-node@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
+
+ - uses: actions/upload-artifact@v7
+ 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: Pack tarball
+ run: |
+ npm pack
+ version=$(node --eval "console.log(require('./package.json').version)")
+ mv plotly.js-$version.tgz plotly.js.tgz
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: tarball-node18
+ retention-days: 7
+ path: plotly.js.tgz
+
+ - 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@v6
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+
+ - uses: actions/setup-node@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
+
+ - uses: actions/upload-artifact@v7
+ 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: Pack tarball
+ run: |
+ npm pack
+ version=$(node --eval "console.log(require('./package.json').version)")
+ mv plotly.js-$version.tgz plotly.js.tgz
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: tarball-node22
+ retention-days: 7
+ path: plotly.js.tgz
+
+ - name: Test plot-schema.json diff
+ run: diff --unified --color dist/plot-schema.json test/plot-schema.json
+
+ test-stackgl-bundle:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'npm'
+
+ - name: Set up build environment
+ run: cd stackgl_modules && npm ci
+
+ - name: Bundle bundle-stackgl/index.js
+ run: cd stackgl_modules && cp index.js INDEX.js && npm run bundle-stackgl
+
+ - name: Test stackgl_modules/index.js diff
+ run: diff --unified --color stackgl_modules/INDEX.js stackgl_modules/index.js
+
+ - uses: actions/upload-artifact@v7
+ if: failure()
+ with:
+ name: stackgl-bundle
+ retention-days: 7
+ path: stackgl_modules/index.js
+
+ test-topojson-build:
+ if: github.event_name == 'push' && github.ref_name == github.event.repository.default_branch
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'npm'
+
+ - name: Set up build environment
+ run: cd topojson && npm ci
+
+ - name: Build topojson
+ run: cd topojson && mv dist dist_backup && npm run build
+
+ - name: Compare existing files with newly built files
+ run: diff -qr topojson/dist topojson/dist_backup
+
+ - uses: actions/upload-artifact@v7
+ if: failure()
+ with:
+ name: topojson-dist
+ retention-days: 7
+ path: topojson/dist/
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index 93caad4a454..00000000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
-
-name: No CI Test
-
-on: push
-
-jobs:
- build:
-
- runs-on: ubuntu-latest
-
- strategy:
- matrix:
- node-version: [18.x]
-
- steps:
- - uses: browser-actions/setup-chrome@v2
- id: setup-chrome
- - uses: actions/checkout@v4
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4
- with:
- node-version: ${{ matrix.node-version }}
- cache: 'npm'
- - name: Set Chrome binary path
- run: echo "CHROME_BIN=${{ steps.setup-chrome.outputs.chrome-path }}" >> $GITHUB_ENV
- - name: Verify Chrome version
- run: |
- echo "Chrome path: $CHROME_BIN"
- $CHROME_BIN --version
- - run: ls
- - run: npm run pretest
- - run: npm ci
- - run: npm run cibuild
- - name: Run noCI tests
- uses: coactions/setup-xvfb@v1
- with:
- run: ./tasks/noci_test.sh jasmine
diff --git a/biome.json b/biome.json
index 24635690072..b176cb37767 100644
--- a/biome.json
+++ b/biome.json
@@ -11,6 +11,7 @@
"**/tasks/**",
"**/devtools/**",
"**/topojson/**",
+ "**/.github/**",
"!**/test/plot-schema.json",
"!**/dist",
"!**/stackgl_modules",
diff --git a/package-lock.json b/package-lock.json
index 02fc4416104..dcb50905f38 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -104,7 +104,7 @@
"minify-stream": "^2.1.0",
"npm-link-check": "^5.0.1",
"open": "^8.4.2",
- "pixelmatch": "^5.3.0",
+ "pixelmatch": "^7.1.0",
"prepend-file": "^2.0.1",
"prettysize": "^2.0.0",
"raw-loader": "^4.0.2",
@@ -8208,12 +8208,13 @@
}
},
"node_modules/pixelmatch": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz",
- "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz",
+ "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==",
"dev": true,
+ "license": "ISC",
"dependencies": {
- "pngjs": "^6.0.0"
+ "pngjs": "^7.0.0"
},
"bin": {
"pixelmatch": "bin/pixelmatch"
@@ -8229,12 +8230,13 @@
}
},
"node_modules/pngjs": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
- "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
+ "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=12.13.0"
+ "node": ">=14.19.0"
}
},
"node_modules/point-in-polygon": {
diff --git a/package.json b/package.json
index 04f32177e6e..1c8e1ed6fab 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
"pretest": "node tasks/pretest.js",
"test-jasmine": "karma start test/jasmine/karma.conf.js",
"test-mock": "node tasks/test_mock.mjs",
- "test-image": "node test/image/compare_pixels_test.js",
+ "test-image": "node test/image/compare_pixels_test.mjs",
"test-export": "node test/image/export_test.js",
"test-syntax": "node tasks/test_syntax.js && npm run find-strings -- --no-output",
"test-bundle": "node tasks/test_bundle.js",
@@ -162,7 +162,7 @@
"minify-stream": "^2.1.0",
"npm-link-check": "^5.0.1",
"open": "^8.4.2",
- "pixelmatch": "^5.3.0",
+ "pixelmatch": "^7.1.0",
"prepend-file": "^2.0.1",
"prettysize": "^2.0.0",
"raw-loader": "^4.0.2",
diff --git a/tasks/test_mock.mjs b/tasks/test_mock.mjs
index ae9bef8fcdd..2a69eaf7276 100644
--- a/tasks/test_mock.mjs
+++ b/tasks/test_mock.mjs
@@ -1,240 +1,246 @@
+import fs from 'fs';
import minimist from 'minimist';
+import os from 'os';
import path from 'path';
-import fs from 'fs';
+import { fileURLToPath } from 'url';
+import { isMainThread, parentPort, Worker, workerData } from 'worker_threads';
-import plotlyNode from './util/plotly_node.mjs';
-var Plotly = plotlyNode('build/plotly.js');
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const pathToRoot = path.join(__dirname, '..');
+const pathToMocks = path.join(pathToRoot, 'test', 'image', 'mocks');
-import { fileURLToPath } from 'url';
-var __dirname = path.dirname(fileURLToPath(import.meta.url));
+const disallowList = new Set([
+ // has contourcarpet See https://github.com/plotly/plotly.js/issues/5669
+ 'airfoil',
+ 'h-colorbar_airfoil',
+ 'cheater',
+ 'cheater_constraint_greater_than',
+ 'cheater_constraint_greater_than_with_hill',
+ 'cheater_constraint_greater_than_with_valley',
+ 'cheater_constraint_inner_range',
+ 'cheater_constraint_inner_range_hi_top',
+ 'cheater_constraint_inner_range_hi_top_with_hill',
+ 'cheater_constraint_inner_range_hi_top_with_valley',
+ 'cheater_constraint_inner_range_lo_top',
+ 'cheater_constraint_inner_range_lo_top_with_hill',
+ 'cheater_constraint_inner_range_lo_top_with_valley',
+ 'cheater_constraint_inner_range_with_hill',
+ 'cheater_constraint_inner_range_with_valley',
+ 'cheater_constraint_less_than',
+ 'cheater_constraint_less_than_with_hill',
+ 'cheater_constraint_less_than_with_valley',
+ 'cheater_constraint_outer_range',
+ 'cheater_constraint_outer_range_hi_top',
+ 'cheater_constraint_outer_range_hi_top_with_hill',
+ 'cheater_constraint_outer_range_hi_top_with_valley',
+ 'cheater_constraint_outer_range_lo_top',
+ 'cheater_constraint_outer_range_lo_top_with_hill',
+ 'cheater_constraint_outer_range_lo_top_with_valley',
+ 'cheater_constraint_outer_range_with_hill',
+ 'cheater_constraint_outer_range_with_valley',
+ 'cheater_constraints',
+ 'cheater_contour',
+ 'cheater_fully_filled',
+ 'cheater_smooth',
-var pathToRoot = path.join(__dirname, '..');
-var pathToMocks = path.join(pathToRoot, 'test', 'image', 'mocks');
+ // other
+ '1',
+ '11',
+ '12',
+ '13',
+ '14',
+ '15',
+ '16',
+ '17',
+ '18',
+ '19',
+ '21',
+ '22',
+ '23',
+ '24',
+ '25',
+ '26',
+ '27',
+ '28',
+ '29',
+ '30',
+ '31',
+ '32',
+ 'annotations',
+ 'annotations-autorange',
+ 'axes_labels',
+ 'candlestick_double-y-axis',
+ 'candlestick_rangeslider_thai',
+ 'category-autorange',
+ 'contour_match_edges',
+ 'dendrogram',
+ 'error_bar_style',
+ 'fake_violins',
+ 'fonts',
+ 'font-variant-bar',
+ 'font-weight-bar',
+ 'geo_africa-insets',
+ 'gl2d_10',
+ 'gl2d_12',
+ 'gl2d_14',
+ 'gl2d_17',
+ 'gl2d_annotations',
+ 'gl2d_axes_labels',
+ 'gl2d_fill_trace_tozero_order',
+ 'gl2d_fonts',
+ 'gl2d_layout_image',
+ 'gl2d_marker_coloraxis',
+ 'gl2d_rgb_dont_accept_alpha_scattergl',
+ 'gl2d_scatter-marker-line-colorscales',
+ 'gl2d_scatter-subplot-panel',
+ 'gl2d_shape_line',
+ 'gl2d_text_chart_basic',
+ 'gl2d_text_chart_single-string',
+ 'gl2d_text_chart_styling',
+ 'gl2d_texttemplate',
+ 'gl3d_bunny',
+ 'gl3d_bunny-hull',
+ 'gl3d_coloraxes',
+ 'gl3d_contour-lines',
+ 'gl3d_contour-lines2',
+ 'gl3d_convex-hull',
+ 'gl3d_cufflinks',
+ 'gl3d_directions-volume1',
+ 'gl3d_ibm-plot',
+ 'gl3d_line-colorscale-with-markers',
+ 'gl3d_opacity-surface',
+ 'gl3d_scatter-colorscale-marker',
+ 'gl3d_scatter3d-align-texts',
+ 'gl3d_surface_opacity-and-opacityscale',
+ 'gl3d_surface_opacityscale_contour',
+ 'gl3d_surface-heatmap-treemap_transparent-colorscale',
+ 'gl3d_surface-lighting',
+ 'gl3d_traces-with-legend',
+ 'gl3d_traces-with-opacity',
+ 'gl3d_volume_multiple-traces',
+ 'gl3d_z-range',
+ 'glpolar_scatter',
+ 'heatmap_small_aspect-ratio',
+ 'histogram_errorbars_inherit_color',
+ 'indicator_attrs',
+ 'indicator_bignumber',
+ 'indicator_bullet',
+ 'indicator_datacard',
+ 'indicator_datacard2',
+ 'indicator_gauge',
+ 'indicator_scatter',
+ 'japanese',
+ 'layout_image',
+ 'layout_metatext',
+ 'legend_horizontal',
+ 'legend_horizontal_autowrap',
+ 'legend-constant-itemsizing',
+ 'matching-missing-axes',
+ 'mathjax',
+ 'ohlc_first',
+ 'pattern_bars',
+ 'pattern_fgcolor_overlay_fillmode',
+ 'pattern_with_colorscale',
+ 'petrophysics',
+ 'plot_types',
+ 'polar_blank',
+ 'polar_dates',
+ 'range_slider_box',
+ 'shapes_fixed_size',
+ 'splom_ragged-via-axes',
+ 'stacked_area_duplicates',
+ 'table_plain_birds',
+ 'table_wrapped_birds',
+ 'text_chart_basic',
+ 'text_chart_single-string',
+ 'text_chart_styling',
+ 'texttemplate',
+ 'texttemplate_scatter',
+ 'titles-avoid-labels',
+ 'trace_metatext',
+ 'updatemenus',
+ 'violin_non-linear',
+ 'violin_ridgeplot',
+ 'violin_style',
+ 'waterfall_funnel_texttemplate_date',
+ 'yaxis-over-yaxis2',
+ 'yignbu_heatmap',
+ 'yiorrd_heatmap'
+]);
-var list = [];
+function validate(Plotly, name) {
+ const filename = path.join(pathToMocks, `${name}.json`);
+ const fig = JSON.parse(fs.readFileSync(filename));
+ const out = Plotly.validate(fig.data, fig.layout);
-// command line options
-var args = minimist(process.argv.slice(2), {});
-if(args._.length) {
- // test listed mock(s)
- list = args._;
-} else {
- // no mock listed, test all excluding the black list
- list = fs.readdirSync(pathToMocks)
- .filter(function(e) { return e.indexOf('.json') !== -1; })
- .map(function(e) { return e.replace('.json', ''); })
- .filter(notBlackListed);
+ if (!out) {
+ if (out !== undefined) {
+ console.error(`Expected ${out} to be undefined`);
+ return false;
+ }
+ return true;
+ }
+
+ for (const e of out) {
+ if (!['dynamic', 'invisible'].includes(e.code) && e.path.at(-1) !== 'coloraxis') {
+ console.error('Expected false to be true');
+ console.log('file:', name);
+ console.log(JSON.stringify(out, null, 2));
+ return false;
+ }
+ }
+ return true;
}
-var fail;
-var failedMocks = [];
+if (isMainThread) {
+ const args = minimist(process.argv.slice(2), {});
+ const list = args._.length
+ ? args._
+ : fs
+ .readdirSync(pathToMocks)
+ .filter((e) => e.endsWith('.json'))
+ .map((e) => e.replace('.json', ''))
+ .filter((e) => !disallowList.has(e));
-for(var i = 0; i < list.length; i++) {
- var name = list[i];
- console.log('validating ' + name);
+ const numWorkers = Math.min(args.workers || os.cpus().length, list.length);
+ const chunkSize = Math.ceil(list.length / numWorkers);
+ const promises = [];
- var filename = path.join(pathToMocks, name + '.json');
- var fig = JSON.parse(fs.readFileSync(filename));
- var out = Plotly.validate(fig.data, fig.layout);
+ console.log(`Validating ${list.length} mocks across ${numWorkers} workers...`);
- fail = false;
- assert(name, out);
- if(fail) failedMocks.push(name);
-}
+ for (let i = 0; i < numWorkers; i++) {
+ const chunk = list.slice(i * chunkSize, (i + 1) * chunkSize);
+ if (chunk.length === 0) continue;
-if(failedMocks.length) {
- var error = 'Failed at ' + JSON.stringify({mocks: failedMocks}, null, 2);
- throw error;
-}
+ promises.push(
+ new Promise((resolve, reject) => {
+ const worker = new Worker(new URL(import.meta.url), {
+ workerData: { mocks: chunk }
+ });
+ worker.on('message', resolve);
+ worker.on('error', reject);
+ })
+ );
+ }
+
+ const results = await Promise.all(promises);
+ const failedMocks = results.flat();
-function expectToBe(actual, expected) {
- if(actual !== expected) {
- console.error('Expected ' + actual + ' to be ' + expected);
- fail = true;
+ if (failedMocks.length) {
+ throw `Failed at ${JSON.stringify({ mocks: failedMocks }, null, 2)}`;
}
-}
-function assert(name, v) {
- var success = true;
- if(!v) {
- expectToBe(v, undefined);
- if(v !== undefined) success = false;
- } else {
- v.forEach(function(e) {
- var condition = (
- e.code === 'invisible' ||
- e.code === 'dynamic' ||
- e.path[e.path.length - 1] === 'coloraxis'
- );
- expectToBe(condition, true); // we accept invisible, dynamic and coloraxis for now
- if(!condition) {
- console.log('file:', name);
- console.log(JSON.stringify(v, null, 2));
- success = false;
- return success;
- }
- });
+ console.log('All mocks validated successfully.');
+} else {
+ const { default: plotlyNode } = await import('./util/plotly_node.mjs');
+ const Plotly = plotlyNode('build/plotly.js');
+ const { mocks } = workerData;
+ const failedMocks = [];
+
+ for (const name of mocks) {
+ console.log(`validating ${name}`);
+ if (!validate(Plotly, name)) failedMocks.push(name);
}
- return success;
-}
-function notBlackListed(name) {
- return [
- // has contourcarpet See https://github.com/plotly/plotly.js/issues/5669
- 'airfoil',
- 'h-colorbar_airfoil',
- 'cheater',
- 'cheater_constraint_greater_than',
- 'cheater_constraint_greater_than_with_hill',
- 'cheater_constraint_greater_than_with_valley',
- 'cheater_constraint_inner_range',
- 'cheater_constraint_inner_range_hi_top',
- 'cheater_constraint_inner_range_hi_top_with_hill',
- 'cheater_constraint_inner_range_hi_top_with_valley',
- 'cheater_constraint_inner_range_lo_top',
- 'cheater_constraint_inner_range_lo_top_with_hill',
- 'cheater_constraint_inner_range_lo_top_with_valley',
- 'cheater_constraint_inner_range_with_hill',
- 'cheater_constraint_inner_range_with_valley',
- 'cheater_constraint_less_than',
- 'cheater_constraint_less_than_with_hill',
- 'cheater_constraint_less_than_with_valley',
- 'cheater_constraint_outer_range',
- 'cheater_constraint_outer_range_hi_top',
- 'cheater_constraint_outer_range_hi_top_with_hill',
- 'cheater_constraint_outer_range_hi_top_with_valley',
- 'cheater_constraint_outer_range_lo_top',
- 'cheater_constraint_outer_range_lo_top_with_hill',
- 'cheater_constraint_outer_range_lo_top_with_valley',
- 'cheater_constraint_outer_range_with_hill',
- 'cheater_constraint_outer_range_with_valley',
- 'cheater_constraints',
- 'cheater_contour',
- 'cheater_fully_filled',
- 'cheater_smooth',
-
- // other
- '1',
- '11',
- '12',
- '13',
- '14',
- '15',
- '16',
- '17',
- '18',
- '19',
- '21',
- '22',
- '23',
- '24',
- '25',
- '26',
- '27',
- '28',
- '29',
- '30',
- '31',
- '32',
- 'annotations',
- 'annotations-autorange',
- 'axes_labels',
- 'candlestick_double-y-axis',
- 'candlestick_rangeslider_thai',
- 'category-autorange',
- 'contour_match_edges',
- 'dendrogram',
- 'error_bar_style',
- 'fake_violins',
- 'fonts',
- 'font-variant-bar',
- 'font-weight-bar',
- 'geo_africa-insets',
- 'gl2d_10',
- 'gl2d_12',
- 'gl2d_14',
- 'gl2d_17',
- 'gl2d_annotations',
- 'gl2d_axes_labels',
- 'gl2d_fill_trace_tozero_order',
- 'gl2d_fonts',
- 'gl2d_layout_image',
- 'gl2d_marker_coloraxis',
- 'gl2d_rgb_dont_accept_alpha_scattergl',
- 'gl2d_scatter-marker-line-colorscales',
- 'gl2d_scatter-subplot-panel',
- 'gl2d_shape_line',
- 'gl2d_text_chart_basic',
- 'gl2d_text_chart_single-string',
- 'gl2d_text_chart_styling',
- 'gl2d_texttemplate',
- 'gl3d_bunny',
- 'gl3d_bunny-hull',
- 'gl3d_coloraxes',
- 'gl3d_contour-lines',
- 'gl3d_contour-lines2',
- 'gl3d_convex-hull',
- 'gl3d_cufflinks',
- 'gl3d_directions-volume1',
- 'gl3d_ibm-plot',
- 'gl3d_line-colorscale-with-markers',
- 'gl3d_opacity-surface',
- 'gl3d_scatter-colorscale-marker',
- 'gl3d_scatter3d-align-texts',
- 'gl3d_surface_opacity-and-opacityscale',
- 'gl3d_surface_opacityscale_contour',
- 'gl3d_surface-heatmap-treemap_transparent-colorscale',
- 'gl3d_surface-lighting',
- 'gl3d_traces-with-legend',
- 'gl3d_traces-with-opacity',
- 'gl3d_volume_multiple-traces',
- 'gl3d_z-range',
- 'glpolar_scatter',
- 'heatmap_small_aspect-ratio',
- 'histogram_errorbars_inherit_color',
- 'indicator_attrs',
- 'indicator_bignumber',
- 'indicator_bullet',
- 'indicator_datacard',
- 'indicator_datacard2',
- 'indicator_gauge',
- 'indicator_scatter',
- 'japanese',
- 'layout_image',
- 'layout_metatext',
- 'legend_horizontal',
- 'legend_horizontal_autowrap',
- 'legend-constant-itemsizing',
- 'matching-missing-axes',
- 'mathjax',
- 'ohlc_first',
- 'pattern_bars',
- 'pattern_fgcolor_overlay_fillmode',
- 'pattern_with_colorscale',
- 'petrophysics',
- 'plot_types',
- 'polar_blank',
- 'polar_dates',
- 'range_slider_box',
- 'shapes_fixed_size',
- 'splom_ragged-via-axes',
- 'stacked_area_duplicates',
- 'table_plain_birds',
- 'table_wrapped_birds',
- 'text_chart_basic',
- 'text_chart_single-string',
- 'text_chart_styling',
- 'texttemplate',
- 'texttemplate_scatter',
- 'titles-avoid-labels',
- 'trace_metatext',
- 'updatemenus',
- 'violin_non-linear',
- 'violin_ridgeplot',
- 'violin_style',
- 'waterfall_funnel_texttemplate_date',
- 'yaxis-over-yaxis2',
- 'yignbu_heatmap',
- 'yiorrd_heatmap'
- ].indexOf(name) === -1;
+ parentPort.postMessage(failedMocks);
}
diff --git a/test/image/baselines/zz-geo_choropleth-egypt-sudan.png b/test/image/baselines/geo_choropleth-egypt-sudan.png
similarity index 100%
rename from test/image/baselines/zz-geo_choropleth-egypt-sudan.png
rename to test/image/baselines/geo_choropleth-egypt-sudan.png
diff --git a/test/image/baselines/geo_choropleth-usa-location-names.png b/test/image/baselines/geo_choropleth-usa-location-names.png
new file mode 100644
index 00000000000..eeeadd84555
Binary files /dev/null and b/test/image/baselines/geo_choropleth-usa-location-names.png differ
diff --git a/test/image/baselines/zz-label-spacing.png b/test/image/baselines/label-spacing.png
similarity index 100%
rename from test/image/baselines/zz-label-spacing.png
rename to test/image/baselines/label-spacing.png
diff --git a/test/image/baselines/zz-legend-vertical-maxheight.png b/test/image/baselines/legend-vertical-maxheight.png
similarity index 100%
rename from test/image/baselines/zz-legend-vertical-maxheight.png
rename to test/image/baselines/legend-vertical-maxheight.png
diff --git a/test/image/baselines/zz-minorloglabels.png b/test/image/baselines/minorloglabels.png
similarity index 100%
rename from test/image/baselines/zz-minorloglabels.png
rename to test/image/baselines/minorloglabels.png
diff --git a/test/image/baselines/zz-pattern_bars-path.png b/test/image/baselines/pattern_bars-path.png
similarity index 100%
rename from test/image/baselines/zz-pattern_bars-path.png
rename to test/image/baselines/pattern_bars-path.png
diff --git a/test/image/baselines/zz-pie-slice-legend.png b/test/image/baselines/pie-slice-legend.png
similarity index 100%
rename from test/image/baselines/zz-pie-slice-legend.png
rename to test/image/baselines/pie-slice-legend.png
diff --git a/test/image/baselines/zz-pie-slice-legend2.png b/test/image/baselines/pie-slice-legend2.png
similarity index 100%
rename from test/image/baselines/zz-pie-slice-legend2.png
rename to test/image/baselines/pie-slice-legend2.png
diff --git a/test/image/baselines/zz-pie-slice-legend3.png b/test/image/baselines/pie-slice-legend3.png
similarity index 100%
rename from test/image/baselines/zz-pie-slice-legend3.png
rename to test/image/baselines/pie-slice-legend3.png
diff --git a/test/image/baselines/zz-scatter_marker_line_dash.png b/test/image/baselines/scatter_marker_line_dash.png
similarity index 100%
rename from test/image/baselines/zz-scatter_marker_line_dash.png
rename to test/image/baselines/scatter_marker_line_dash.png
diff --git a/test/image/baselines/zz_shapes_clipping_double_digit_subplots.png b/test/image/baselines/shapes_clipping_double_digit_subplots.png
similarity index 100%
rename from test/image/baselines/zz_shapes_clipping_double_digit_subplots.png
rename to test/image/baselines/shapes_clipping_double_digit_subplots.png
diff --git a/test/image/baselines/zz-tickson_boundaries_ticklabelposition.png b/test/image/baselines/tickson_boundaries_ticklabelposition.png
similarity index 100%
rename from test/image/baselines/zz-tickson_boundaries_ticklabelposition.png
rename to test/image/baselines/tickson_boundaries_ticklabelposition.png
diff --git a/test/image/baselines/zzz_zerolinelayer_above.png b/test/image/baselines/zerolinelayer_above.png
similarity index 100%
rename from test/image/baselines/zzz_zerolinelayer_above.png
rename to test/image/baselines/zerolinelayer_above.png
diff --git a/test/image/baselines/zzz_zerolinelayer_below.png b/test/image/baselines/zerolinelayer_below.png
similarity index 100%
rename from test/image/baselines/zzz_zerolinelayer_below.png
rename to test/image/baselines/zerolinelayer_below.png
diff --git a/test/image/compare_pixels_collections.json b/test/image/compare_pixels_collections.json
new file mode 100644
index 00000000000..234eb781609
--- /dev/null
+++ b/test/image/compare_pixels_collections.json
@@ -0,0 +1,21 @@
+{
+ "compare_disallow": [
+ "map_angles",
+ "map_fonts-supported-open-sans-weight",
+ "map_fonts-supported-open-sans",
+ "map_layers",
+ "map_predefined-styles2",
+ "map_scattercluster",
+ "map_stamen-style"
+ ],
+ "compare_mathjax3": [
+ "legend_mathjax_title_and_items",
+ "mathjax",
+ "parcats_grid_subplots",
+ "table_latex_multitrace_scatter",
+ "table_plain_birds",
+ "table_wrapped_birds",
+ "ternary-mathjax",
+ "ternary-mathjax-title-place-subtitle"
+ ]
+}
diff --git a/test/image/compare_pixels_test.js b/test/image/compare_pixels_test.js
deleted file mode 100644
index 574e5edf51f..00000000000
--- a/test/image/compare_pixels_test.js
+++ /dev/null
@@ -1,204 +0,0 @@
-var minimist = require('minimist');
-var pixelmatch = require('pixelmatch');
-var PNG = require('pngjs').PNG;
-var fs = require('fs');
-
-var common = require('../../tasks/util/common');
-var getMockList = require('./assets/get_mock_list');
-var getImagePaths = require('./assets/get_image_paths');
-
-/**
- * Image pixel comparison test script.
- *
- * Called by `tasks/test_image.sh in `npm run test-image`.
- *
- * CLI arguments:
- *
- * 1. 'pattern' : glob determining which mock(s) are to be tested
- *
- * Examples:
- *
- * Run all tests:
- *
- * npm run test-image
- *
- * Run the 'contour_nolines' test:
- *
- * npm run test-image -- contour_nolines
- *
- * Run all gl3d image test
- *
- * npm run test-image -- gl3d_*
- *
- */
-
-var argv = minimist(process.argv.slice(2), {});
-
-// If no pattern is provided, all mocks are compared
-if(argv._.length === 0) {
- argv._.push('');
-}
-
-// Build list of mocks to compare
-var allMockList = [];
-var mathjax3;
-var virtualWebgl = false;
-argv._.forEach(function(pattern) {
- if(pattern === 'mathjax3') {
- mathjax3 = true;
- } else if(pattern === 'virtual-webgl') {
- virtualWebgl = true;
- allMockList = getMockList('');
- } else {
- var mockList = getMockList(pattern);
-
- if(mockList.length === 0) {
- throw 'No mocks found with pattern ' + pattern;
- }
-
- allMockList = allMockList.concat(mockList);
- }
-});
-
-var blacklist = [
- 'map_angles',
- 'map_stamen-style',
- 'map_predefined-styles2',
- 'map_scattercluster',
- 'map_fonts-supported-open-sans',
- 'map_fonts-supported-open-sans-weight',
- 'map_layers',
-];
-
-if(virtualWebgl) {
- allMockList = allMockList.filter(function(a) {
- return a.slice(0, 2) === 'gl';
- });
-}
-
-if(mathjax3) {
- allMockList = [
- 'legend_mathjax_title_and_items',
- 'mathjax',
- 'parcats_grid_subplots',
- 'table_latex_multitrace_scatter',
- 'table_plain_birds',
- 'table_wrapped_birds',
- 'ternary-mathjax',
- 'ternary-mathjax-title-place-subtitle',
- ];
-}
-
-// To get rid of duplicates
-function unique(value, index, self) {
- return self.indexOf(value) === index;
-}
-allMockList = allMockList.filter(unique);
-
-var skipped = [];
-var failed = [];
-var fail = function(mockName) {
- if(failed.indexOf(mockName) === -1) {
- failed.push(mockName);
- }
-};
-
-for(var i = 0; i < allMockList.length; i++) {
- var mockName = allMockList[i];
-
- // skip blacklist
- if(blacklist.indexOf(mockName) !== -1) continue;
-
- var flakyMap = [
- // more flaky
- 'map_density0-legend',
- 'map_osm-style',
- 'map_predefined-styles1',
- 'map_predefined-styles2',
- ].indexOf(mockName) !== -1;
-
- var otherFlaky = [
- // list flaky mocks other than maps:
- 'gl3d_bunny-hull'
- ].indexOf(mockName) !== -1;
-
- var threshold =
- flakyMap ? 1 :
- otherFlaky ? 0.15 :
- 0;
-
- if(mathjax3) mockName = 'mathjax3___' + mockName;
-
- var imagePaths = getImagePaths(mockName);
- var base = imagePaths.baseline;
- var test = imagePaths.test;
-
- if(!common.doesFileExist(test) && !mathjax3) {
- console.log('- skip:', mockName);
- skipped.push(mockName);
- continue;
- }
- console.log('+ test:', mockName);
-
- var img0 = PNG.sync.read(fs.readFileSync(base));
- var img1 = PNG.sync.read(fs.readFileSync(test));
- var s0, s1, key;
-
- key = 'width';
- s0 = img0[key];
- s1 = img0[key];
- if(s0 !== s1) {
- console.error(key + 's do not match: ' + s0 + ' vs ' + s1);
- fail(mockName);
- }
-
- key = 'height';
- s0 = img0[key];
- s1 = img0[key];
- if(s0 !== s1) {
- console.error(key + 's do not match: ' + s0 + ' vs ' + s1);
- fail(mockName);
- }
-
- var width = img0.width;
- var height = img0.height;
-
- var diff = new PNG({
- width: width,
- height: height
- });
-
- if(virtualWebgl) {
- threshold = Math.max(0.4, threshold);
- if([
- 'gl3d_ibm-plot',
- 'gl3d_isosurface_2surfaces-checker_spaceframe',
- 'gl3d_opacity-scaling-spikes',
- 'gl3d_cone-wind',
- 'gl3d_isosurface_math',
- 'gl3d_scatter3d-blank-text',
- 'gl3d_mesh3d_surface3d_scatter3d_line3d_error3d_log_reversed_ranges'
- ].indexOf(mockName) !== -1) threshold = 0.7;
- }
-
- var numDiffPixels = pixelmatch(img0.data, img1.data, diff.data, width, height, {
- threshold: threshold
- });
-
- if(numDiffPixels) {
- fs.writeFileSync(imagePaths.diff, PNG.sync.write(diff));
-
- console.error('pixels do not match: ' + numDiffPixels);
- fail(mockName);
- } else {
- // remove when identical
- fs.unlinkSync(imagePaths.test);
- }
-}
-
-if(failed.length || skipped.length) {
- throw JSON.stringify({
- failed: failed,
- skipped: skipped
- }, null, 2);
-}
diff --git a/test/image/compare_pixels_test.mjs b/test/image/compare_pixels_test.mjs
new file mode 100644
index 00000000000..d23d64458b8
--- /dev/null
+++ b/test/image/compare_pixels_test.mjs
@@ -0,0 +1,173 @@
+import fs from 'fs';
+import minimist from 'minimist';
+import path from 'path';
+import pixelmatch from 'pixelmatch';
+import { PNG } from 'pngjs';
+import { fileURLToPath } from 'url';
+import common from '../../tasks/util/common.js';
+import constants from '../../tasks/util/constants.js';
+import getImagePaths from './assets/get_image_paths.js';
+import getMockList from './assets/get_mock_list.js';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+fs.mkdirSync(constants.pathToTestImagesDiff, { recursive: true });
+
+/**
+ * Image pixel comparison test script.
+ *
+ * Called by `tasks/test_image.sh in `npm run test-image`.
+ *
+ * CLI arguments:
+ *
+ * 1. 'pattern' : glob determining which mock(s) are to be tested
+ *
+ * Examples:
+ *
+ * Run all tests:
+ *
+ * npm run test-image
+ *
+ * Run the 'contour_nolines' test:
+ *
+ * npm run test-image -- contour_nolines
+ *
+ * Run all gl3d image test
+ *
+ * npm run test-image -- gl3d_*
+ *
+ */
+
+const argv = minimist(process.argv.slice(2), {});
+
+// If no pattern is provided, all mocks are compared
+if (argv._.length === 0) argv._.push('');
+
+// Build list of mocks to compare
+let allMockList = [];
+let mathjax3 = false;
+let virtualWebgl = false;
+argv._.forEach((pattern) => {
+ if (pattern === 'mathjax3') {
+ mathjax3 = true;
+ } else if (pattern === 'virtual-webgl') {
+ virtualWebgl = true;
+ allMockList = getMockList('');
+ } else {
+ const mockList = getMockList(pattern);
+ if (mockList.length === 0) throw 'No mocks found with pattern ' + pattern;
+
+ allMockList = allMockList.concat(mockList);
+ }
+});
+
+const skipped = new Set();
+const failed = new Set();
+// TODO: In Node 20+, replace with: import collections from './compare_pixels_collections.json' with { type: 'json' };
+const collectionsPath = path.join(__dirname, 'compare_pixels_collections.json');
+const collections = JSON.parse(fs.readFileSync(collectionsPath));
+const disallowList = new Set(collections.compare_disallow);
+const flakyList = new Set(['gl3d_bunny-hull']);
+const flakyListMaps = new Set([
+ // more flaky
+ 'map_density0-legend',
+ 'map_osm-style',
+ 'map_predefined-styles1',
+ 'map_predefined-styles2'
+]);
+if (virtualWebgl) {
+ allMockList = allMockList.filter((a) => a.startsWith('gl') || a.startsWith('map'));
+}
+
+if (mathjax3) {
+ allMockList = collections.compare_mathjax3;
+}
+allMockList = new Set(allMockList);
+
+for (let mockName of allMockList) {
+ if (disallowList.has(mockName)) continue;
+
+ let threshold;
+ if (flakyListMaps.has(mockName)) threshold = 1;
+ else if (flakyList.has(mockName)) threshold = 0.15;
+ else threshold = 0;
+
+ if (mathjax3) mockName = 'mathjax3___' + mockName;
+ if (virtualWebgl) mockName = 'virtual-webgl___' + mockName;
+
+ const { baseline: base, test, diff } = getImagePaths(mockName);
+
+ if (!common.doesFileExist(test) && !mathjax3) {
+ console.log('- skip:', mockName);
+ skipped.add(mockName);
+ continue;
+ }
+ console.log('+ test:', mockName);
+
+ try {
+ if (!common.doesFileExist(base)) {
+ console.error('baseline image missing');
+ fs.copyFileSync(test, diff);
+ failed.add(mockName);
+ continue;
+ }
+
+ const img0 = PNG.sync.read(fs.readFileSync(base));
+ const img1 = PNG.sync.read(fs.readFileSync(test));
+ let dimensionMismatch = false;
+ for (const key of ['height', 'width']) {
+ const length0 = img0[key];
+ const length1 = img1[key];
+ if (length0 !== length1) {
+ console.error(key + 's do not match: ' + length0 + ' vs ' + length1);
+ dimensionMismatch = true;
+ }
+ }
+
+ if (dimensionMismatch) {
+ fs.copyFileSync(test, diff);
+ failed.add(mockName);
+ continue;
+ }
+
+ const { height, width } = img0;
+ const imageDiff = new PNG({ width, height });
+ const numDiffPixels = pixelmatch(img0.data, img1.data, imageDiff.data, width, height, { threshold });
+
+ if (numDiffPixels) {
+ fs.writeFileSync(diff, PNG.sync.write(imageDiff));
+ console.error('pixels do not match: ' + numDiffPixels);
+ failed.add(mockName);
+ } else {
+ // remove when identical
+ fs.unlinkSync(test);
+ }
+ } catch (e) {
+ console.error('error comparing ' + mockName + ':', e);
+ failed.add(mockName);
+ }
+}
+
+// Debug: list contents of diff folder
+console.log('\n=== build/test_images/ ===');
+try {
+ const testFiles = fs.readdirSync(constants.pathToTestImages);
+ console.log(testFiles.length + ' files:', testFiles.join(', '));
+} catch { console.log('(empty or missing)'); }
+
+console.log('=== build/test_images_diff/ ===');
+try {
+ const diffFiles = fs.readdirSync(constants.pathToTestImagesDiff);
+ console.log(diffFiles.length + ' files:', diffFiles.join(', '));
+} catch { console.log('(empty or missing)'); }
+
+if (failed.size || skipped.size) {
+ throw JSON.stringify(
+ {
+ failed: Array.from(failed),
+ skipped: Array.from(skipped)
+ },
+ null,
+ 2
+ );
+}
diff --git a/test/image/make_baseline.py b/test/image/make_baseline.py
index 01dc836da76..ff0b600df09 100644
--- a/test/image/make_baseline.py
+++ b/test/image/make_baseline.py
@@ -1,134 +1,160 @@
+import asyncio
+import json
+import kaleido
import os
import sys
-import json
-import plotly.io as pio
from convert_b64 import arraysToB64
args = []
-if len(sys.argv) == 2 :
+if len(sys.argv) == 2:
args = sys.argv[1].split()
-elif len(sys.argv) > 1 :
+elif len(sys.argv) > 1:
args = sys.argv
root = os.getcwd()
-virtual_webgl = os.path.join(root, 'node_modules', 'virtual-webgl', 'src', 'virtual-webgl.js')
-plotlyjs = os.path.join(root, 'build', 'plotly.js')
-plotlyjs_with_virtual_webgl = os.path.join(root, 'build', 'plotly_with_virtual-webgl.js')
+virtual_webgl = os.path.join(
+ root, "node_modules", "virtual-webgl", "src", "virtual-webgl.js"
+)
+plotlyjs = os.path.join(root, "build", "plotly.js")
+plotlyjs_with_virtual_webgl = os.path.join(
+ root, "build", "plotly_with_virtual-webgl.js"
+)
-dirIn = os.path.join(root, 'test', 'image', 'mocks')
-dirOut = os.path.join(root, 'build', 'test_images')
+topojson = "file://" + os.path.join(root, "topojson", "dist")
+dirIn = os.path.join(root, "test", "image", "mocks")
+dirOut = os.path.join(root, "build", "test_images")
-# N.B. equal is the falg to write to baselines not test_images
+# N.B. equal is the flag to write to baselines not test_images
-if '=' in args :
- args = args[args.index('=') + 1:]
- dirOut = os.path.join(root, 'test', 'image', 'baselines')
+if "=" in args:
+ args = args[args.index("=") + 1 :]
+ dirOut = os.path.join(root, "test", "image", "baselines")
-if 'mathjax3=' in sys.argv :
- dirOut = os.path.join(root, 'test', 'image', 'baselines')
+if "mathjax3=" in sys.argv:
+ dirOut = os.path.join(root, "test", "image", "baselines")
-print('output to', dirOut)
+print("output to", dirOut)
mathjax_version = 2
-if 'mathjax3' in sys.argv or 'mathjax3=' in sys.argv :
+mathjax = None
+if "mathjax3" in sys.argv or "mathjax3=" in sys.argv:
# until https://github.com/plotly/Kaleido/issues/124 is addressed
# we are uanble to use local mathjax v3 installed in node_modules
# for now let's download it from the internet:
- pio.kaleido.scope.mathjax = 'https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-svg.js'
+ mathjax = "https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-svg.js"
+
mathjax_version = 3
- print('Kaleido using MathJax v3')
+ print("Kaleido using MathJax v3")
-virtual_webgl_version = 0 # i.e. virtual-webgl is not used
-if 'virtual-webgl' in sys.argv or 'virtual-webgl=' in sys.argv :
+virtual_webgl_version = 0 # i.e. virtual-webgl is not used
+if "virtual-webgl" in sys.argv or "virtual-webgl=" in sys.argv:
virtual_webgl_version = 1
- print('using virtual-webgl for WebGL v1')
+ print("using virtual-webgl for WebGL v1")
- with open(plotlyjs_with_virtual_webgl, 'w') as fileOut:
+ with open(plotlyjs_with_virtual_webgl, "w") as fileOut:
for filename in [virtual_webgl, plotlyjs]:
- with open(filename, 'r') as fileIn:
+ with open(filename, "r") as fileIn:
for line in fileIn:
fileOut.write(line)
plotlyjs = plotlyjs_with_virtual_webgl
-pio.kaleido.scope.plotlyjs = plotlyjs
-pio.kaleido.scope.topojson = "file://" + os.path.join(root, 'topojson', 'dist')
-pio.templates.default = 'none'
-
-ALL_MOCKS = [os.path.splitext(a)[0] for a in os.listdir(dirIn) if a.endswith('.json')]
+ALL_MOCKS = [os.path.splitext(a)[0] for a in os.listdir(dirIn) if a.endswith(".json")]
ALL_MOCKS.sort()
-if len(args) > 0 :
+if len(args) > 0:
allNames = [a for a in args if a in ALL_MOCKS]
-else :
+else:
allNames = ALL_MOCKS
-# unable to generate baselines for the following mocks
-blacklist = [
- 'map_stamen-style',
- 'map_predefined-styles2',
- 'map_scattercluster',
- 'map_fonts-supported-open-sans',
- 'map_fonts-supported-open-sans-weight',
- 'map_layers',
-]
-allNames = [a for a in allNames if a not in blacklist]
-
-if len(allNames) == 0 :
- print('error: Nothing to create!')
+with open(os.path.join(root, "test", "image", "compare_pixels_collections.json"), "r") as f:
+ # unable to generate baselines for the following mocks
+ disallowList = set(json.load(f)["compare_disallow"])
+allNames = [a for a in allNames if a not in disallowList]
+
+if len(allNames) == 0:
+ print("error: Nothing to create!")
sys.exit(1)
failed = []
-for name in allNames :
- outName = name
- if mathjax_version == 3 :
- outName = 'mathjax3___' + name
-
- print(outName)
-
- created = False
-
- MAX_RETRY = 2 # 1 means retry once
- for attempt in range(0, MAX_RETRY + 1) :
- with open(os.path.join(dirIn, name + '.json'), 'r') as _in :
- fig = json.load(_in)
-
- width = 700
- height = 500
- if 'layout' in fig :
- layout = fig['layout']
- if 'autosize' not in layout or layout['autosize'] != True :
- if 'width' in layout :
- width = layout['width']
- if 'height' in layout :
- height = layout['height']
-
- if 'b64' in sys.argv or 'b64=' in sys.argv or 'b64-json' in sys.argv :
- newFig = dict()
- arraysToB64(fig, newFig)
- fig = newFig
- if 'b64-json' in sys.argv and attempt == 0 : print(json.dumps(fig, indent = 2))
-
- try :
- pio.write_image(
- fig=fig,
- file=os.path.join(dirOut, outName + '.png'),
- width=width,
- height=height,
- validate=False
- )
- created = True
- except Exception as e :
- print(e)
- if attempt < MAX_RETRY :
- print('retry', attempt + 1, '/', MAX_RETRY)
- else :
- failed.append(outName)
-
- if(created) : break
-
-if len(failed) > 0 :
- print('Failed at :')
- print(failed)
- sys.exit(1)
+
+
+async def make_baselines_async():
+
+ kopts = dict(
+ plotlyjs=plotlyjs,
+ )
+ if mathjax is not None:
+ kopts["mathjax"] = mathjax
+
+ async with kaleido.Kaleido(n=1, **kopts) as k:
+ for name in allNames:
+ outName = name
+ if mathjax_version == 3:
+ outName = "mathjax3___" + name
+ if virtual_webgl_version == 1:
+ outName = "virtual-webgl___" + outName
+
+ print(outName)
+
+ created = False
+
+ MAX_RETRY = 2 # 1 means retry once
+ for attempt in range(0, MAX_RETRY + 1):
+ with open(os.path.join(dirIn, name + ".json"), "r") as _in:
+ fig = json.load(_in)
+
+ width = 700
+ height = 500
+ if "layout" in fig:
+ layout = fig["layout"]
+ if "autosize" not in layout or layout["autosize"] != True:
+ if "width" in layout:
+ width = layout["width"]
+ if "height" in layout:
+ height = layout["height"]
+
+ if (
+ "b64" in sys.argv
+ or "b64=" in sys.argv
+ or "b64-json" in sys.argv
+ ):
+ newFig = dict()
+ arraysToB64(fig, newFig)
+ fig = newFig
+ if "b64-json" in sys.argv and attempt == 0:
+ print(json.dumps(fig, indent=2))
+
+ try:
+ bytes = await k.calc_fig(
+ fig,
+ opts=dict(
+ format="png",
+ width=width,
+ height=height,
+ ),
+ topojson=topojson,
+ )
+ filename = os.path.join(dirOut, outName + ".png")
+ with open(filename, "wb") as f:
+ f.write(bytes)
+ created = True
+ except Exception as e:
+ print(e)
+ if attempt < MAX_RETRY:
+ print("retry", attempt + 1, "/", MAX_RETRY)
+ else:
+ failed.append(outName)
+
+ if created:
+ break
+
+ if len(failed) > 0:
+ print("Failed at :")
+ print(failed)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ asyncio.run(make_baselines_async())
diff --git a/test/image/make_exports.py b/test/image/make_exports.py
index 279439af1e4..42774ed9ff0 100644
--- a/test/image/make_exports.py
+++ b/test/image/make_exports.py
@@ -1,65 +1,82 @@
+import asyncio
+import json
+import kaleido
import os
import sys
-import json
-import plotly.io as pio
root = os.getcwd()
-dirIn = os.path.join(root, 'test', 'image', 'mocks')
-dirOut = os.path.join(root, 'build', 'test_images')
+dirIn = os.path.join(root, "test", "image", "mocks")
+dirOut = os.path.join(root, "build", "test_images")
+os.makedirs(dirOut, exist_ok=True)
-pio.templates.default = 'none'
-pio.kaleido.scope.plotlyjs = os.path.join(root, 'build', 'plotly.js')
+plotlyjs = os.path.join(root, "build", "plotly.js")
+topojson = "file://" + os.path.join(root, "topojson", "dist")
-allFormats = ['svg', 'jpg', 'jpeg', 'webp', 'pdf']
+allFormats = ["svg", "jpg", "jpeg", "webp", "pdf"]
# 'png' is tested by image-test
allNames = [
- 'plot_types',
- 'annotations',
- 'shapes',
- 'range_slider',
- 'contour_legend-colorscale',
- 'layout_image',
- 'image_astronaut_source',
- 'gl2d_no-clustering2',
- 'gl3d_surface-heatmap-treemap_transparent-colorscale',
- 'map_density-multiple_legend',
- 'smith_modes',
- 'zsmooth_methods',
- 'fonts',
- 'worldcup',
- 'mathjax'
+ "annotations",
+ "contour_legend-colorscale",
+ "fonts",
+ "gl2d_no-clustering2",
+ "gl3d_surface-heatmap-treemap_transparent-colorscale",
+ "image_astronaut_source",
+ "layout_image",
+ "map_density-multiple_legend",
+ "mathjax",
+ "plot_types",
+ "range_slider",
+ "shapes",
+ "smith_modes",
+ "worldcup",
+ "zsmooth_methods",
]
failed = 0
-for name in allNames :
- for fmt in allFormats :
- print(name + ' --> ' + fmt)
- with open(os.path.join(dirIn, name + '.json'), 'r') as _in :
- fig = json.load(_in)
- width = 700
- height = 500
- if 'layout' in fig :
- layout = fig['layout']
- if 'autosize' not in layout or layout['autosize'] != True :
- if 'width' in layout :
- width = layout['width']
- if 'height' in layout :
- height = layout['height']
+async def make_exports_async():
+ global failed
+
+ async with kaleido.Kaleido(n=1, plotlyjs=plotlyjs) as k:
+ for name in allNames:
+ with open(os.path.join(dirIn, name + ".json"), "r") as _in:
+ fig = json.load(_in)
+
+ width = 700
+ height = 500
+ if "layout" in fig:
+ layout = fig["layout"]
+ if "autosize" not in layout or layout["autosize"] != True:
+ if "width" in layout:
+ width = layout["width"]
+ if "height" in layout:
+ height = layout["height"]
+
+ for fmt in allFormats:
+ print(name + " --> " + fmt)
+
+ try:
+ data = await k.calc_fig(
+ fig,
+ opts=dict(
+ format=fmt,
+ width=width,
+ height=height,
+ ),
+ topojson=topojson,
+ )
+ filename = os.path.join(dirOut, name + "." + fmt)
+ with open(filename, "wb") as f:
+ f.write(data)
- try :
- pio.write_image(
- fig=fig,
- file=os.path.join(dirOut, name + '.' + fmt),
- width=width,
- height=height,
- validate=False
- )
+ except Exception as e:
+ print(e)
+ failed += 1
- except Exception as e :
- print(e)
- failed += 1
-if failed > 0 : sys.exit(1)
+if __name__ == "__main__":
+ asyncio.run(make_exports_async())
+ if failed > 0:
+ sys.exit(1)
diff --git a/test/image/mocks/zz-geo_choropleth-egypt-sudan.json b/test/image/mocks/geo_choropleth-egypt-sudan.json
similarity index 100%
rename from test/image/mocks/zz-geo_choropleth-egypt-sudan.json
rename to test/image/mocks/geo_choropleth-egypt-sudan.json
diff --git a/test/image/mocks/geo_choropleth-usa-location-names.json b/test/image/mocks/geo_choropleth-usa-location-names.json
new file mode 100644
index 00000000000..59495991023
--- /dev/null
+++ b/test/image/mocks/geo_choropleth-usa-location-names.json
@@ -0,0 +1,152 @@
+{
+ "data": [
+ {
+ "autocolorscale": false,
+ "zmax": 16472.88,
+ "colorscale": [
+ [0, "rgb(242,240,247)"],
+ [0.2, "rgb(218,218,235)"],
+ [0.4, "rgb(188,189,220)"],
+ [0.6, "rgb(158,154,200)"],
+ [0.8, "rgb(117,107,177)"],
+ [1, "rgb(84,39,143)"]
+ ],
+ "text": [
+ "Alabama
--
Beef: 34.4
Pork: 10.6
Poultry: 481.0
Dairy: 4.06
--
Fruits: 25.11
Veggies: 14.33
--
Wheat: 70.0
Corn: 34.9
Cotton: 317.61",
+ "Alaska
--
Beef: 0.2
Pork: 0.1
Poultry: 0.0
Dairy: 0.19
--
Fruits: 0.0
Veggies: 1.56
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
+ "Arizona
--
Beef: 71.3
Pork: 17.9
Poultry: 0.0
Dairy: 105.48
--
Fruits: 60.27
Veggies: 386.91
--
Wheat: 48.7
Corn: 7.3
Cotton: 423.95",
+ "Arkansas
--
Beef: 53.2
Pork: 29.4
Poultry: 562.9
Dairy: 3.53
--
Fruits: 6.88
Veggies: 11.45
--
Wheat: 114.5
Corn: 69.5
Cotton: 665.44",
+ " California
--
Beef: 228.7
Pork: 11.1
Poultry: 225.4
Dairy: 929.95
--
Fruits: 8736.4
Veggies: 2106.79
--
Wheat: 249.3
Corn: 34.6
Cotton: 1064.95",
+ "Colorado
--
Beef: 261.4
Pork: 66.0
Poultry: 14.0
Dairy: 71.94
--
Fruits: 17.99
Veggies: 118.27
--
Wheat: 400.5
Corn: 183.2
Cotton: 0.0",
+ "Connecticut
--
Beef: 1.1
Pork: 0.1
Poultry: 6.9
Dairy: 9.49
--
Fruits: 13.1
Veggies: 11.16
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
+ "Delaware
--
Beef: 0.4
Pork: 0.6
Poultry: 114.7
Dairy: 2.3
--
Fruits: 1.53
Veggies: 20.03
--
Wheat: 22.9
Corn: 26.9
Cotton: 0.0",
+ "Florida
--
Beef: 42.6
Pork: 0.9
Poultry: 56.9
Dairy: 66.31
--
Fruits: 1371.36
Veggies: 450.86
--
Wheat: 1.8
Corn: 3.5
Cotton: 78.24",
+ "Georgia
--
Beef: 31.0
Pork: 18.9
Poultry: 630.4
Dairy: 38.38
--
Fruits: 233.51
Veggies: 154.77
--
Wheat: 65.4
Corn: 57.8
Cotton: 1154.07",
+ "Hawaii
--
Beef: 4.0
Pork: 0.7
Poultry: 1.3
Dairy: 1.16
--
Fruits: 55.51
Veggies: 24.83
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
+ "Idaho
--
Beef: 119.8
Pork: 0.0
Poultry: 2.4
Dairy: 294.6
--
Fruits: 21.64
Veggies: 319.19
--
Wheat: 568.2
Corn: 24.0
Cotton: 0.0",
+ "Illinois
--
Beef: 53.7
Pork: 394.0
Poultry: 14.0
Dairy: 45.82
--
Fruits: 12.53
Veggies: 39.95
--
Wheat: 223.8
Corn: 2228.5
Cotton: 0.0",
+ "Indiana
--
Beef: 21.9
Pork: 341.9
Poultry: 165.6
Dairy: 89.7
--
Fruits: 12.98
Veggies: 37.89
--
Wheat: 114.0
Corn: 1123.2
Cotton: 0.0",
+ "Iowa
--
Beef: 289.8
Pork: 1895.6
Poultry: 155.6
Dairy: 107.0
--
Fruits: 3.24
Veggies: 7.1
--
Wheat: 3.1
Corn: 2529.8
Cotton: 0.0",
+ "Kansas
--
Beef: 659.3
Pork: 179.4
Poultry: 6.4
Dairy: 65.45
--
Fruits: 3.11
Veggies: 9.32
--
Wheat: 1426.5
Corn: 457.3
Cotton: 43.98",
+ "Kentucky
--
Beef: 54.8
Pork: 34.2
Poultry: 151.3
Dairy: 28.27
--
Fruits: 6.6
Veggies: 0.0
--
Wheat: 149.3
Corn: 179.1
Cotton: 0.0",
+ "Louisiana
--
Beef: 19.8
Pork: 0.8
Poultry: 77.2
Dairy: 6.02
--
Fruits: 17.83
Veggies: 17.25
--
Wheat: 78.7
Corn: 91.4
Cotton: 280.42",
+ "Maine
--
Beef: 1.4
Pork: 0.5
Poultry: 10.4
Dairy: 16.18
--
Fruits: 52.01
Veggies: 62.9
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
+ "Maryland
--
Beef: 5.6
Pork: 3.1
Poultry: 127.0
Dairy: 24.81
--
Fruits: 12.9
Veggies: 20.43
--
Wheat: 55.8
Corn: 54.1
Cotton: 0.0",
+ "Massachusetts
--
Beef: 0.6
Pork: 0.5
Poultry: 0.6
Dairy: 5.81
--
Fruits: 80.83
Veggies: 21.13
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
+ "Michigan
--
Beef: 37.7
Pork: 118.1
Poultry: 32.6
Dairy: 214.82
--
Fruits: 257.69
Veggies: 189.96
--
Wheat: 247.0
Corn: 381.5
Cotton: 0.0",
+ "Minnesota
--
Beef: 112.3
Pork: 740.4
Poultry: 189.2
Dairy: 218.05
--
Fruits: 7.91
Veggies: 120.37
--
Wheat: 538.1
Corn: 1264.3
Cotton: 0.0",
+ "Mississippi
--
Beef: 12.8
Pork: 30.4
Poultry: 370.8
Dairy: 5.45
--
Fruits: 17.04
Veggies: 27.87
--
Wheat: 102.2
Corn: 110.0
Cotton: 494.75",
+ "Missouri
--
Beef: 137.2
Pork: 277.3
Poultry: 196.1
Dairy: 34.26
--
Fruits: 13.18
Veggies: 17.9
--
Wheat: 161.7
Corn: 428.8
Cotton: 345.29",
+ "Montana
--
Beef: 105.0
Pork: 16.7
Poultry: 1.7
Dairy: 6.82
--
Fruits: 3.3
Veggies: 45.27
--
Wheat: 1198.1
Corn: 5.4
Cotton: 0.0",
+ "Nebraska
--
Beef: 762.2
Pork: 262.5
Poultry: 31.4
Dairy: 30.07
--
Fruits: 2.16
Veggies: 53.5
--
Wheat: 292.3
Corn: 1735.9
Cotton: 0.0",
+ "Nevada
--
Beef: 21.8
Pork: 0.2
Poultry: 0.0
Dairy: 16.57
--
Fruits: 1.19
Veggies: 27.93
--
Wheat: 5.4
Corn: 0.0
Cotton: 0.0",
+ "New Hampshire
--
Beef: 0.6
Pork: 0.2
Poultry: 0.8
Dairy: 7.46
--
Fruits: 7.98
Veggies: 4.5
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
+ "New Jersey
--
Beef: 0.8
Pork: 0.4
Poultry: 4.6
Dairy: 3.37
--
Fruits: 109.45
Veggies: 56.54
--
Wheat: 6.7
Corn: 10.1
Cotton: 0.0",
+ "New Mexico
--
Beef: 117.2
Pork: 0.1
Poultry: 0.3
Dairy: 191.01
--
Fruits: 101.9
Veggies: 43.88
--
Wheat: 13.9
Corn: 11.2
Cotton: 72.62",
+ "New York
--
Beef: 22.2
Pork: 5.8
Poultry: 17.7
Dairy: 331.8
--
Fruits: 202.56
Veggies: 143.37
--
Wheat: 29.9
Corn: 106.1
Cotton: 0.0",
+ "North Carolina
--
Beef: 24.8
Pork: 702.8
Poultry: 598.4
Dairy: 24.9
--
Fruits: 74.47
Veggies: 150.45
--
Wheat: 200.3
Corn: 92.2
Cotton: 470.86",
+ "North Dakota
--
Beef: 78.5
Pork: 16.1
Poultry: 0.5
Dairy: 8.14
--
Fruits: 0.25
Veggies: 130.79
--
Wheat: 1664.5
Corn: 236.1
Cotton: 0.0",
+ "Ohio
--
Beef: 36.2
Pork: 199.1
Poultry: 129.9
Dairy: 134.57
--
Fruits: 27.21
Veggies: 53.53
--
Wheat: 207.4
Corn: 535.1
Cotton: 0.0",
+ "Oklahoma
--
Beef: 337.6
Pork: 265.3
Poultry: 131.1
Dairy: 24.35
--
Fruits: 9.24
Veggies: 8.9
--
Wheat: 324.8
Corn: 27.5
Cotton: 110.54",
+ "Oregon
--
Beef: 58.8
Pork: 1.4
Poultry: 14.2
Dairy: 63.66
--
Fruits: 315.04
Veggies: 126.5
--
Wheat: 320.3
Corn: 11.7
Cotton: 0.0",
+ "Pennsylvania
--
Beef: 50.9
Pork: 91.3
Poultry: 169.8
Dairy: 280.87
--
Fruits: 89.48
Veggies: 38.26
--
Wheat: 41.0
Corn: 112.1
Cotton: 0.0",
+ "Rhode Island
--
Beef: 0.1
Pork: 0.1
Poultry: 0.2
Dairy: 0.52
--
Fruits: 2.83
Veggies: 3.02
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
+ "South Carolina
--
Beef: 15.2
Pork: 10.9
Poultry: 186.5
Dairy: 7.62
--
Fruits: 53.45
Veggies: 42.66
--
Wheat: 55.3
Corn: 32.1
Cotton: 206.1",
+ "South Dakota
--
Beef: 193.5
Pork: 160.2
Poultry: 29.3
Dairy: 46.77
--
Fruits: 0.8
Veggies: 4.06
--
Wheat: 704.5
Corn: 643.6
Cotton: 0.0",
+ "Tennessee
--
Beef: 51.1
Pork: 17.6
Poultry: 82.4
Dairy: 21.18
--
Fruits: 6.23
Veggies: 24.67
--
Wheat: 100.0
Corn: 88.8
Cotton: 363.83",
+ "Texas
--
Beef: 961.0
Pork: 42.7
Poultry: 339.2
Dairy: 240.55
--
Fruits: 99.9
Veggies: 115.23
--
Wheat: 309.7
Corn: 167.2
Cotton: 2308.76",
+ "Utah
--
Beef: 27.9
Pork: 59.0
Poultry: 23.1
Dairy: 48.6
--
Fruits: 12.34
Veggies: 6.6
--
Wheat: 42.8
Corn: 5.3
Cotton: 0.0",
+ "Vermont
--
Beef: 6.2
Pork: 0.2
Poultry: 0.9
Dairy: 65.98
--
Fruits: 8.01
Veggies: 4.05
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
+ "Virginia
--
Beef: 39.5
Pork: 16.9
Poultry: 164.7
Dairy: 47.85
--
Fruits: 36.48
Veggies: 27.25
--
Wheat: 77.5
Corn: 39.5
Cotton: 64.84",
+ "Washington
--
Beef: 59.2
Pork: 0.0
Poultry: 35.6
Dairy: 154.18
--
Fruits: 1738.57
Veggies: 363.79
--
Wheat: 786.3
Corn: 29.5
Cotton: 0.0",
+ "West Virginia
--
Beef: 12.0
Pork: 0.3
Poultry: 45.4
Dairy: 3.9
--
Fruits: 11.54
Veggies: 0.0
--
Wheat: 1.6
Corn: 3.5
Cotton: 0.0",
+ "Wisconsin
--
Beef: 107.3
Pork: 38.6
Poultry: 34.5
Dairy: 633.6
--
Fruits: 133.8
Veggies: 148.99
--
Wheat: 96.7
Corn: 460.5
Cotton: 0.0",
+ "Wyoming
--
Beef: 75.1
Pork: 33.2
Poultry: 0.1
Dairy: 2.89
--
Fruits: 0.17
Veggies: 10.23
--
Wheat: 20.7
Corn: 9.0
Cotton: 0.0"
+ ],
+ "zmin": 13.31,
+ "locations": [
+ "AL",
+ "AK",
+ "AZ",
+ "AR",
+ "CA",
+ "CO",
+ "CT",
+ "DE",
+ "FL",
+ "GA",
+ "HI",
+ "ID",
+ "IL",
+ "IN",
+ "IA",
+ "KS",
+ "KY",
+ "LA",
+ "ME",
+ "MD",
+ "MA",
+ "MI",
+ "MN",
+ "MS",
+ "MO",
+ "MT",
+ "NE",
+ "NV",
+ "NH",
+ "NJ",
+ "NM",
+ "NY",
+ "NC",
+ "ND",
+ "OH",
+ "OK",
+ "OR",
+ "PA",
+ "RI",
+ "SC",
+ "SD",
+ "TN",
+ "TX",
+ "UT",
+ "VT",
+ "VA",
+ "WA",
+ "WV",
+ "WI",
+ "WY"
+ ],
+ "colorbar": {
+ "title": { "text": "Millions USD" }
+ },
+ "type": "choropleth",
+ "z": [
+ 1390.63, 13.31, 1463.17, 3586.02, 16472.88, 1851.33, 259.62, 282.19,
+ 3764.09, 2860.84, 401.84, 2078.89, 8709.48, 5050.23, 11273.76, 4589.01,
+ 1889.15, 1914.23, 278.37, 692.75, 248.65, 3164.16, 7192.33, 2170.8,
+ 3933.42, 1718, 7114.13, 139.89, 73.06, 500.4, 751.58, 1488.9, 3806.05,
+ 3761.96, 3979.79, 1646.41, 1794.57, 1969.87, 31.59, 929.93, 3770.19,
+ 1535.13, 6648.22, 453.39, 180.14, 1146.48, 3894.81, 138.89, 3090.23,
+ 349.69
+ ],
+ "locationmode": "USA-states"
+ }
+ ],
+ "layout": {
+ "autosize": true,
+ "title": {
+ "text": "2011 US Agriculture Exports by State - Location Names
(Hover for breakdown)"
+ },
+ "showlegend": false,
+ "height": 598,
+ "width": 870,
+ "geo": {
+ "showlakes": true,
+ "scope": "usa",
+ "projection": {
+ "type": "albers usa"
+ },
+ "lakecolor": "rgb(255, 255, 255)"
+ }
+ }
+}
diff --git a/test/image/mocks/geo_choropleth-usa.json b/test/image/mocks/geo_choropleth-usa.json
index 88329cbb828..b3a2255a730 100644
--- a/test/image/mocks/geo_choropleth-usa.json
+++ b/test/image/mocks/geo_choropleth-usa.json
@@ -2,13 +2,6 @@
"data": [
{
"autocolorscale": false,
- "colorbar": {
- "title": { "text": "Millions USD" }
- },
- "locationmode": "USA-states",
- "name": "",
- "type": "choropleth",
- "zmin": 13.31,
"zmax": 16472.88,
"colorscale": [
[0, "rgb(242,240,247)"],
@@ -70,6 +63,7 @@
"Wisconsin
--
Beef: 107.3
Pork: 38.6
Poultry: 34.5
Dairy: 633.6
--
Fruits: 133.8
Veggies: 148.99
--
Wheat: 96.7
Corn: 460.5
Cotton: 0.0",
"Wyoming
--
Beef: 75.1
Pork: 33.2
Poultry: 0.1
Dairy: 2.89
--
Fruits: 0.17
Veggies: 10.23
--
Wheat: 20.7
Corn: 9.0
Cotton: 0.0"
],
+ "zmin": 13.31,
"locations": [
"AL",
"AK",
@@ -122,137 +116,10 @@
"WI",
"WY"
],
- "z": [
- 1390.63, 13.31, 1463.17, 3586.02, 16472.88, 1851.33, 259.62, 282.19,
- 3764.09, 2860.84, 401.84, 2078.89, 8709.48, 5050.23, 11273.76, 4589.01,
- 1889.15, 1914.23, 278.37, 692.75, 248.65, 3164.16, 7192.33, 2170.8,
- 3933.42, 1718, 7114.13, 139.89, 73.06, 500.4, 751.58, 1488.9, 3806.05,
- 3761.96, 3979.79, 1646.41, 1794.57, 1969.87, 31.59, 929.93, 3770.19,
- 1535.13, 6648.22, 453.39, 180.14, 1146.48, 3894.81, 138.89, 3090.23,
- 349.69
- ]
- },
- {
- "autocolorscale": false,
- "geo": "geo2",
- "locationmode": "USA-states",
- "name": "",
- "showscale": false,
+ "colorbar": {
+ "title": { "text": "Millions USD" }
+ },
"type": "choropleth",
- "zmax": 16472.88,
- "zmin": 13.31,
- "colorscale": [
- [0, "rgb(242,240,247)"],
- [0.2, "rgb(218,218,235)"],
- [0.4, "rgb(188,189,220)"],
- [0.6, "rgb(158,154,200)"],
- [0.8, "rgb(117,107,177)"],
- [1, "rgb(84,39,143)"]
- ],
- "text": [
- "Alabama
--
Beef: 34.4
Pork: 10.6
Poultry: 481.0
Dairy: 4.06
--
Fruits: 25.11
Veggies: 14.33
--
Wheat: 70.0
Corn: 34.9
Cotton: 317.61",
- "Alaska
--
Beef: 0.2
Pork: 0.1
Poultry: 0.0
Dairy: 0.19
--
Fruits: 0.0
Veggies: 1.56
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
- "Arizona
--
Beef: 71.3
Pork: 17.9
Poultry: 0.0
Dairy: 105.48
--
Fruits: 60.27
Veggies: 386.91
--
Wheat: 48.7
Corn: 7.3
Cotton: 423.95",
- "Arkansas
--
Beef: 53.2
Pork: 29.4
Poultry: 562.9
Dairy: 3.53
--
Fruits: 6.88
Veggies: 11.45
--
Wheat: 114.5
Corn: 69.5
Cotton: 665.44",
- " California
--
Beef: 228.7
Pork: 11.1
Poultry: 225.4
Dairy: 929.95
--
Fruits: 8736.4
Veggies: 2106.79
--
Wheat: 249.3
Corn: 34.6
Cotton: 1064.95",
- "Colorado
--
Beef: 261.4
Pork: 66.0
Poultry: 14.0
Dairy: 71.94
--
Fruits: 17.99
Veggies: 118.27
--
Wheat: 400.5
Corn: 183.2
Cotton: 0.0",
- "Connecticut
--
Beef: 1.1
Pork: 0.1
Poultry: 6.9
Dairy: 9.49
--
Fruits: 13.1
Veggies: 11.16
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
- "Delaware
--
Beef: 0.4
Pork: 0.6
Poultry: 114.7
Dairy: 2.3
--
Fruits: 1.53
Veggies: 20.03
--
Wheat: 22.9
Corn: 26.9
Cotton: 0.0",
- "Florida
--
Beef: 42.6
Pork: 0.9
Poultry: 56.9
Dairy: 66.31
--
Fruits: 1371.36
Veggies: 450.86
--
Wheat: 1.8
Corn: 3.5
Cotton: 78.24",
- "Georgia
--
Beef: 31.0
Pork: 18.9
Poultry: 630.4
Dairy: 38.38
--
Fruits: 233.51
Veggies: 154.77
--
Wheat: 65.4
Corn: 57.8
Cotton: 1154.07",
- "Hawaii
--
Beef: 4.0
Pork: 0.7
Poultry: 1.3
Dairy: 1.16
--
Fruits: 55.51
Veggies: 24.83
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
- "Idaho
--
Beef: 119.8
Pork: 0.0
Poultry: 2.4
Dairy: 294.6
--
Fruits: 21.64
Veggies: 319.19
--
Wheat: 568.2
Corn: 24.0
Cotton: 0.0",
- "Illinois
--
Beef: 53.7
Pork: 394.0
Poultry: 14.0
Dairy: 45.82
--
Fruits: 12.53
Veggies: 39.95
--
Wheat: 223.8
Corn: 2228.5
Cotton: 0.0",
- "Indiana
--
Beef: 21.9
Pork: 341.9
Poultry: 165.6
Dairy: 89.7
--
Fruits: 12.98
Veggies: 37.89
--
Wheat: 114.0
Corn: 1123.2
Cotton: 0.0",
- "Iowa
--
Beef: 289.8
Pork: 1895.6
Poultry: 155.6
Dairy: 107.0
--
Fruits: 3.24
Veggies: 7.1
--
Wheat: 3.1
Corn: 2529.8
Cotton: 0.0",
- "Kansas
--
Beef: 659.3
Pork: 179.4
Poultry: 6.4
Dairy: 65.45
--
Fruits: 3.11
Veggies: 9.32
--
Wheat: 1426.5
Corn: 457.3
Cotton: 43.98",
- "Kentucky
--
Beef: 54.8
Pork: 34.2
Poultry: 151.3
Dairy: 28.27
--
Fruits: 6.6
Veggies: 0.0
--
Wheat: 149.3
Corn: 179.1
Cotton: 0.0",
- "Louisiana
--
Beef: 19.8
Pork: 0.8
Poultry: 77.2
Dairy: 6.02
--
Fruits: 17.83
Veggies: 17.25
--
Wheat: 78.7
Corn: 91.4
Cotton: 280.42",
- "Maine
--
Beef: 1.4
Pork: 0.5
Poultry: 10.4
Dairy: 16.18
--
Fruits: 52.01
Veggies: 62.9
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
- "Maryland
--
Beef: 5.6
Pork: 3.1
Poultry: 127.0
Dairy: 24.81
--
Fruits: 12.9
Veggies: 20.43
--
Wheat: 55.8
Corn: 54.1
Cotton: 0.0",
- "Massachusetts
--
Beef: 0.6
Pork: 0.5
Poultry: 0.6
Dairy: 5.81
--
Fruits: 80.83
Veggies: 21.13
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
- "Michigan
--
Beef: 37.7
Pork: 118.1
Poultry: 32.6
Dairy: 214.82
--
Fruits: 257.69
Veggies: 189.96
--
Wheat: 247.0
Corn: 381.5
Cotton: 0.0",
- "Minnesota
--
Beef: 112.3
Pork: 740.4
Poultry: 189.2
Dairy: 218.05
--
Fruits: 7.91
Veggies: 120.37
--
Wheat: 538.1
Corn: 1264.3
Cotton: 0.0",
- "Mississippi
--
Beef: 12.8
Pork: 30.4
Poultry: 370.8
Dairy: 5.45
--
Fruits: 17.04
Veggies: 27.87
--
Wheat: 102.2
Corn: 110.0
Cotton: 494.75",
- "Missouri
--
Beef: 137.2
Pork: 277.3
Poultry: 196.1
Dairy: 34.26
--
Fruits: 13.18
Veggies: 17.9
--
Wheat: 161.7
Corn: 428.8
Cotton: 345.29",
- "Montana
--
Beef: 105.0
Pork: 16.7
Poultry: 1.7
Dairy: 6.82
--
Fruits: 3.3
Veggies: 45.27
--
Wheat: 1198.1
Corn: 5.4
Cotton: 0.0",
- "Nebraska
--
Beef: 762.2
Pork: 262.5
Poultry: 31.4
Dairy: 30.07
--
Fruits: 2.16
Veggies: 53.5
--
Wheat: 292.3
Corn: 1735.9
Cotton: 0.0",
- "Nevada
--
Beef: 21.8
Pork: 0.2
Poultry: 0.0
Dairy: 16.57
--
Fruits: 1.19
Veggies: 27.93
--
Wheat: 5.4
Corn: 0.0
Cotton: 0.0",
- "New Hampshire
--
Beef: 0.6
Pork: 0.2
Poultry: 0.8
Dairy: 7.46
--
Fruits: 7.98
Veggies: 4.5
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
- "New Jersey
--
Beef: 0.8
Pork: 0.4
Poultry: 4.6
Dairy: 3.37
--
Fruits: 109.45
Veggies: 56.54
--
Wheat: 6.7
Corn: 10.1
Cotton: 0.0",
- "New Mexico
--
Beef: 117.2
Pork: 0.1
Poultry: 0.3
Dairy: 191.01
--
Fruits: 101.9
Veggies: 43.88
--
Wheat: 13.9
Corn: 11.2
Cotton: 72.62",
- "New York
--
Beef: 22.2
Pork: 5.8
Poultry: 17.7
Dairy: 331.8
--
Fruits: 202.56
Veggies: 143.37
--
Wheat: 29.9
Corn: 106.1
Cotton: 0.0",
- "North Carolina
--
Beef: 24.8
Pork: 702.8
Poultry: 598.4
Dairy: 24.9
--
Fruits: 74.47
Veggies: 150.45
--
Wheat: 200.3
Corn: 92.2
Cotton: 470.86",
- "North Dakota
--
Beef: 78.5
Pork: 16.1
Poultry: 0.5
Dairy: 8.14
--
Fruits: 0.25
Veggies: 130.79
--
Wheat: 1664.5
Corn: 236.1
Cotton: 0.0",
- "Ohio
--
Beef: 36.2
Pork: 199.1
Poultry: 129.9
Dairy: 134.57
--
Fruits: 27.21
Veggies: 53.53
--
Wheat: 207.4
Corn: 535.1
Cotton: 0.0",
- "Oklahoma
--
Beef: 337.6
Pork: 265.3
Poultry: 131.1
Dairy: 24.35
--
Fruits: 9.24
Veggies: 8.9
--
Wheat: 324.8
Corn: 27.5
Cotton: 110.54",
- "Oregon
--
Beef: 58.8
Pork: 1.4
Poultry: 14.2
Dairy: 63.66
--
Fruits: 315.04
Veggies: 126.5
--
Wheat: 320.3
Corn: 11.7
Cotton: 0.0",
- "Pennsylvania
--
Beef: 50.9
Pork: 91.3
Poultry: 169.8
Dairy: 280.87
--
Fruits: 89.48
Veggies: 38.26
--
Wheat: 41.0
Corn: 112.1
Cotton: 0.0",
- "Rhode Island
--
Beef: 0.1
Pork: 0.1
Poultry: 0.2
Dairy: 0.52
--
Fruits: 2.83
Veggies: 3.02
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
- "South Carolina
--
Beef: 15.2
Pork: 10.9
Poultry: 186.5
Dairy: 7.62
--
Fruits: 53.45
Veggies: 42.66
--
Wheat: 55.3
Corn: 32.1
Cotton: 206.1",
- "South Dakota
--
Beef: 193.5
Pork: 160.2
Poultry: 29.3
Dairy: 46.77
--
Fruits: 0.8
Veggies: 4.06
--
Wheat: 704.5
Corn: 643.6
Cotton: 0.0",
- "Tennessee
--
Beef: 51.1
Pork: 17.6
Poultry: 82.4
Dairy: 21.18
--
Fruits: 6.23
Veggies: 24.67
--
Wheat: 100.0
Corn: 88.8
Cotton: 363.83",
- "Texas
--
Beef: 961.0
Pork: 42.7
Poultry: 339.2
Dairy: 240.55
--
Fruits: 99.9
Veggies: 115.23
--
Wheat: 309.7
Corn: 167.2
Cotton: 2308.76",
- "Utah
--
Beef: 27.9
Pork: 59.0
Poultry: 23.1
Dairy: 48.6
--
Fruits: 12.34
Veggies: 6.6
--
Wheat: 42.8
Corn: 5.3
Cotton: 0.0",
- "Vermont
--
Beef: 6.2
Pork: 0.2
Poultry: 0.9
Dairy: 65.98
--
Fruits: 8.01
Veggies: 4.05
--
Wheat: 0.0
Corn: 0.0
Cotton: 0.0",
- "Virginia
--
Beef: 39.5
Pork: 16.9
Poultry: 164.7
Dairy: 47.85
--
Fruits: 36.48
Veggies: 27.25
--
Wheat: 77.5
Corn: 39.5
Cotton: 64.84",
- "Washington
--
Beef: 59.2
Pork: 0.0
Poultry: 35.6
Dairy: 154.18
--
Fruits: 1738.57
Veggies: 363.79
--
Wheat: 786.3
Corn: 29.5
Cotton: 0.0",
- "West Virginia
--
Beef: 12.0
Pork: 0.3
Poultry: 45.4
Dairy: 3.9
--
Fruits: 11.54
Veggies: 0.0
--
Wheat: 1.6
Corn: 3.5
Cotton: 0.0",
- "Wisconsin
--
Beef: 107.3
Pork: 38.6
Poultry: 34.5
Dairy: 633.6
--
Fruits: 133.8
Veggies: 148.99
--
Wheat: 96.7
Corn: 460.5
Cotton: 0.0",
- "Wyoming
--
Beef: 75.1
Pork: 33.2
Poultry: 0.1
Dairy: 2.89
--
Fruits: 0.17
Veggies: 10.23
--
Wheat: 20.7
Corn: 9.0
Cotton: 0.0"
- ],
- "locations": [
- "Alabama",
- "Alaska",
- "Arizona",
- "Arkansas",
- "California",
- "Colorado",
- "Connecticut",
- "Delaware",
- "Florida",
- "Georgia",
- "Hawaii",
- "Idaho",
- "Illinois",
- "Indiana",
- "Iowa",
- "Kansas",
- "Kentucky",
- "Louisiana",
- "Maine",
- "Maryland",
- "Massachusetts",
- "Michigan",
- "Minnesota",
- "Mississippi",
- "Missouri",
- "Montana",
- "Nebraska",
- "Nevada",
- "New Hampshire",
- "New Jersey",
- "New Mexico",
- "New York",
- "North Carolina",
- "North Dakota",
- "Ohio",
- "Oklahoma",
- "Oregon",
- "Pennsylvania",
- "Rhode Island",
- "South Carolina",
- "South Dakota",
- "Tennessee",
- "Texas",
- "Utah",
- "Vermont",
- "Virginia",
- "Washington",
- "West Virginia",
- "Wisconsin",
- "Wyoming"
- ],
"z": [
1390.63, 13.31, 1463.17, 3586.02, 16472.88, 1851.33, 259.62, 282.19,
3764.09, 2860.84, 401.84, 2078.89, 8709.48, 5050.23, 11273.76, 4589.01,
@@ -261,16 +128,17 @@
3761.96, 3979.79, 1646.41, 1794.57, 1969.87, 31.59, 929.93, 3770.19,
1535.13, 6648.22, 453.39, 180.14, 1146.48, 3894.81, 138.89, 3090.23,
349.69
- ]
+ ],
+ "locationmode": "USA-states"
}
],
"layout": {
"autosize": true,
"title": {
- "text": "2011 US Agriculture Exports by State
(Hover for breakdown)"
+ "text": "2011 US Agriculture Exports by State - Two Letter Abbreviations
(Hover for breakdown)"
},
"showlegend": false,
- "height": 1200,
+ "height": 598,
"width": 870,
"geo": {
"showlakes": true,
@@ -278,37 +146,7 @@
"projection": {
"type": "albers usa"
},
- "lakecolor": "rgb(255, 255, 255)",
- "domain": { "y": [0.55, 1] }
- },
- "geo2": {
- "showlakes": true,
- "scope": "usa",
- "projection": {
- "type": "albers usa"
- },
- "lakecolor": "rgb(255, 255, 255)",
- "domain": { "y": [0, 0.45] }
- },
- "annotations": [
- {
- "text": "Lookup by two letter location abbreviation",
- "x": 0.5,
- "y": 0.53,
- "xref": "paper",
- "yref": "paper",
- "showarrow": false,
- "font": { "size": 16 }
- },
- {
- "text": "Lookup by full location name",
- "x": 0.5,
- "y": -0.02,
- "xref": "paper",
- "yref": "paper",
- "showarrow": false,
- "font": { "size": 16 }
- }
- ]
+ "lakecolor": "rgb(255, 255, 255)"
+ }
}
}
diff --git a/test/image/mocks/zz-label-spacing.json b/test/image/mocks/label-spacing.json
similarity index 100%
rename from test/image/mocks/zz-label-spacing.json
rename to test/image/mocks/label-spacing.json
diff --git a/test/image/mocks/zz-legend-vertical-maxheight.json b/test/image/mocks/legend-vertical-maxheight.json
similarity index 100%
rename from test/image/mocks/zz-legend-vertical-maxheight.json
rename to test/image/mocks/legend-vertical-maxheight.json
diff --git a/test/image/mocks/zz-minorloglabels.json b/test/image/mocks/minorloglabels.json
similarity index 100%
rename from test/image/mocks/zz-minorloglabels.json
rename to test/image/mocks/minorloglabels.json
diff --git a/test/image/mocks/zz-pattern_bars-path.json b/test/image/mocks/pattern_bars-path.json
similarity index 100%
rename from test/image/mocks/zz-pattern_bars-path.json
rename to test/image/mocks/pattern_bars-path.json
diff --git a/test/image/mocks/zz-pie-slice-legend.json b/test/image/mocks/pie-slice-legend.json
similarity index 100%
rename from test/image/mocks/zz-pie-slice-legend.json
rename to test/image/mocks/pie-slice-legend.json
diff --git a/test/image/mocks/zz-pie-slice-legend2.json b/test/image/mocks/pie-slice-legend2.json
similarity index 100%
rename from test/image/mocks/zz-pie-slice-legend2.json
rename to test/image/mocks/pie-slice-legend2.json
diff --git a/test/image/mocks/zz-pie-slice-legend3.json b/test/image/mocks/pie-slice-legend3.json
similarity index 100%
rename from test/image/mocks/zz-pie-slice-legend3.json
rename to test/image/mocks/pie-slice-legend3.json
diff --git a/test/image/mocks/zz-scatter_marker_line_dash.json b/test/image/mocks/scatter_marker_line_dash.json
similarity index 100%
rename from test/image/mocks/zz-scatter_marker_line_dash.json
rename to test/image/mocks/scatter_marker_line_dash.json
diff --git a/test/image/mocks/zz_shapes_clipping_double_digit_subplots.json b/test/image/mocks/shapes_clipping_double_digit_subplots.json
similarity index 100%
rename from test/image/mocks/zz_shapes_clipping_double_digit_subplots.json
rename to test/image/mocks/shapes_clipping_double_digit_subplots.json
diff --git a/test/image/mocks/zz-tickson_boundaries_ticklabelposition.json b/test/image/mocks/tickson_boundaries_ticklabelposition.json
similarity index 100%
rename from test/image/mocks/zz-tickson_boundaries_ticklabelposition.json
rename to test/image/mocks/tickson_boundaries_ticklabelposition.json
diff --git a/test/image/mocks/zzz_zerolinelayer_above.json b/test/image/mocks/zerolinelayer_above.json
similarity index 100%
rename from test/image/mocks/zzz_zerolinelayer_above.json
rename to test/image/mocks/zerolinelayer_above.json
diff --git a/test/image/mocks/zzz_zerolinelayer_below.json b/test/image/mocks/zerolinelayer_below.json
similarity index 100%
rename from test/image/mocks/zzz_zerolinelayer_below.json
rename to test/image/mocks/zerolinelayer_below.json
diff --git a/test/jasmine/karma.conf.js b/test/jasmine/karma.conf.js
index 2c7f075c3ee..fb412599a9b 100644
--- a/test/jasmine/karma.conf.js
+++ b/test/jasmine/karma.conf.js
@@ -38,7 +38,7 @@ var argv = minimist(process.argv.slice(4), {
failFast: false,
doNotFailOnEmptyTestSuite: false,
width: '1035',
- height: '617',
+ height: '800',
verbose: false,
showSkipped: isCI
}
@@ -261,12 +261,6 @@ func.defaultConfig = {
'--touch-events',
'--window-size=' + argv.width + ',' + argv.height,
isCI ? '--ignore-gpu-blocklist' : '',
- // The following two flags are needed only for the "NoCI" tests which run in GitHub Actions.
- // The first is needed because the GPU is not available to those runners,
- // and therefore we need to use SwiftShader instead (which is disabled by default as of Jan 2026,
- // hence the need for a flag to manually enable it).
- // The second flag is needed because the Chrome browser installed by the CI job runner
- // fails without it.
isCI && process.env.GITHUB_ACTIONS ? '--enable-unsafe-swiftshader' : '',
isCI && process.env.GITHUB_ACTIONS ? '--no-sandbox' : '',
isBundleTest && basename(testFileGlob) === 'no_webgl' ? '--disable-webgl' : ''
diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js
index 4d85a14fc37..6b336cb0c5c 100644
--- a/test/jasmine/tests/axes_test.js
+++ b/test/jasmine/tests/axes_test.js
@@ -1722,13 +1722,13 @@ describe('Test axes', function() {
width: 600,
height: 600
}).then(function() {
- expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.110, 2]);
+ expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.12, 2]);
return Plotly.relayout(gd, {
'xaxis.insiderange': [1, 3]
});
}).then(function() {
- expect(gd._fullLayout.xaxis.range).toBeCloseToArray([0.889, 3]);
+ expect(gd._fullLayout.xaxis.range).toBeCloseToArray([0.879, 3]);
}).then(done, done.fail);
});
});
@@ -8152,11 +8152,11 @@ describe('more react tests', function() {
Plotly.newPlot(gd, fig1)
.then(function() {
- expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.110, 2]);
+ expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.12, 2]);
return Plotly.react(gd, fig2);
}).then(function() {
- expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.164, 2]);
+ expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.173, 2]);
}).then(done, done.fail);
});
});
diff --git a/test/jasmine/tests/geo_test.js b/test/jasmine/tests/geo_test.js
index 83d0f92a4c2..6faddf3c630 100644
--- a/test/jasmine/tests/geo_test.js
+++ b/test/jasmine/tests/geo_test.js
@@ -1404,7 +1404,7 @@ describe('Test geo interactions', function() {
py -= 2;
mouseEvent('mousemove', px, py);
- if(py > 175) {
+ if(py > 176) {
_assert('- py ' + py, 1);
expect(cnt).toBe(0, 'no plotly_unhover event so far');
} else {
@@ -2608,7 +2608,7 @@ describe('Test geo zoom/pan/drag interactions:', function() {
_assert('base', [
[-96.6, 38.7], 1,
], [
- [410, 329], 738.5
+ [410, 309], 738.5
], undefined);
return drag({path: [[250, 250], [200, 200]], noCover: true});
})
@@ -2616,7 +2616,7 @@ describe('Test geo zoom/pan/drag interactions:', function() {
_assert('after NW-SE drag', [
[-91.8, 34.8], 1,
], [
- [366, 279], 738.5
+ [366, 259], 738.5
], [
'geo.center.lon', 'geo.center.lon'
]);
@@ -2626,7 +2626,7 @@ describe('Test geo zoom/pan/drag interactions:', function() {
_assert('after scroll', [
[-94.5, 35.0], 1.3
], [
- [380, 273], 974.4
+ [380, 245.9], 974.4
], [
'geo.center.lon', 'geo.center.lon', 'geo.projection.scale'
]);
@@ -2637,7 +2637,7 @@ describe('Test geo zoom/pan/drag interactions:', function() {
[-94.5, 35.0], 1.3
], [
// new center values are reflected in translate()
- [380, 273], 974.4
+ [380, 245.9], 974.4
], [
'geo.showlakes'
]);
@@ -2647,7 +2647,7 @@ describe('Test geo zoom/pan/drag interactions:', function() {
_assert('after double click', [
[-96.6, 38.7], 1,
], [
- [416, 329], 738.5
+ [416, 309], 738.5
], 'dblclick');
})
.then(done, done.fail);
@@ -2759,10 +2759,10 @@ describe('Test geo interactions update marker angles:', function() {
})
.then(function() {
newPath = getPath();
- expect(newPath).toEqual('M0,0L18.224184922503092,8.238876374264322L19.586365339190138,-4.046516131164082Z');
+ expect(newPath).toEqual('M0,0L18.27769005891461,8.119485581627321L19.559475756661865,-4.174554841483899Z');
expect(newPath).not.toEqual(initialPath);
- expect(newPath).toEqual('M0,0L18.224184922503092,8.238876374264322L19.586365339190138,-4.046516131164082Z');
+ expect(newPath).toEqual('M0,0L18.27769005891461,8.119485581627321L19.559475756661865,-4.174554841483899Z');
expect(initialPath).toEqual('M0,0L-1.5094067529528923,19.942960945008643L10.501042615957648,17.021401351764233Z');
})
.then(done, done.fail);
diff --git a/test/jasmine/tests/sankey_test.js b/test/jasmine/tests/sankey_test.js
index 76fa0e9f27c..69120330795 100644
--- a/test/jasmine/tests/sankey_test.js
+++ b/test/jasmine/tests/sankey_test.js
@@ -1144,10 +1144,17 @@ describe('sankey tests', function() {
return function(elType) {
return new Promise(function(resolve, reject) {
- gd.once(eventType, function(d) {
+ const handler = (d) => {
Lib.clearThrottle();
+ const isNode = d.points[0].hasOwnProperty('sourceLinks');
+ const isExpectedType = (elType === 'node') ? isNode : !isNode;
+ if (!isExpectedType) {
+ gd.once(eventType, handler);
+ return;
+ }
resolve(d);
- });
+ }
+ gd.once(eventType, handler);
mouseFn(posByElementType[elType]);
setTimeout(function() {
@@ -1460,8 +1467,8 @@ describe('sankey tests', function() {
nodes = document.getElementsByClassName('sankey-node');
node = Array.prototype.slice.call(nodes).find(function(n) { return n.textContent === '0';});
var newPosition = getNodeCoords(node);
- expect(newPosition.x).toBeCloseTo(positionAfterDrag[0], 2, 'final x position is off');
- expect(newPosition.y).toBeCloseTo(positionAfterDrag[1], 2, 'final y position is off');
+ expect(newPosition.x).toBeCloseTo(positionAfterDrag[0], -1, 'final x position is off');
+ expect(newPosition.y).toBeCloseTo(positionAfterDrag[1], -1, 'final y position is off');
// Change color of nodes
var mockCopy = Lib.extendDeep({}, mockCircularFreeform);
@@ -1485,8 +1492,8 @@ describe('sankey tests', function() {
pos = positionBeforeDrag;
msg = 'should go back to its default because uirevision changed';
}
- expect(newPosition.x).toBeCloseTo(pos[0], 2, 'x position ' + msg);
- expect(newPosition.y).toBeCloseTo(pos[1], 2, 'y position ' + msg);
+ expect(newPosition.x).toBeCloseTo(pos[0], -1, 'x position ' + msg);
+ expect(newPosition.y).toBeCloseTo(pos[1], -1, 'y position ' + msg);
})
.then(done, done.fail);
});
diff --git a/test/jasmine/tests/scatter3d_test.js b/test/jasmine/tests/scatter3d_test.js
index ea6916ae435..a5f1a7f8b4d 100644
--- a/test/jasmine/tests/scatter3d_test.js
+++ b/test/jasmine/tests/scatter3d_test.js
@@ -204,7 +204,6 @@ describe('Test scatter3d interactions:', function() {
}
Plotly.newPlot(gd, _mock)
- .then(delay(20))
.then(function() {
assertObjects(order0);
diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js
index 194c91cdc69..41a9a3d2211 100644
--- a/test/jasmine/tests/select_test.js
+++ b/test/jasmine/tests/select_test.js
@@ -1876,8 +1876,18 @@ describe('Test select box and lasso in general:', function() {
}).then(function() {
return Plotly.relayout(gd, 'dragmode', 'select');
}).then(function() {
+ var plots = gd._fullLayout._plots;
+ Object.keys(plots).forEach(function(id) {
+ var p = plots[id];
+ var xa = p.xaxis, ya = p.yaxis;
+ if(xa && ya) {
+ console.log('[subplot ' + id + '] x: ' + xa._offset + '-' + (xa._offset + xa._length) +
+ ', y: ' + ya._offset + '-' + (ya._offset + ya._length));
+ }
+ });
return drag([[150, 450], [650, 350]]);
}).then(function() {
+ console.log('[after drag 1] selectedpoints:', gd.data.map(function(t) { return t.selectedpoints; }));
expect(gd.data[0].selectedpoints).toBe(undefined);
expect(gd.data[1].selectedpoints).toEqual([1, 2]);
expect(gd.data[2].selectedpoints).toBe(undefined);
@@ -1885,6 +1895,7 @@ describe('Test select box and lasso in general:', function() {
}).then(function() {
return drag([[150, 100], [600, 200]]);
}).then(function() {
+ console.log('[after drag 2] selectedpoints:', gd.data.map(function(t) { return t.selectedpoints; }));
expect(gd.data[0].selectedpoints).toEqual([1, 2]);
expect(gd.data[1].selectedpoints).toEqual([1, 2]);
expect(gd.data[2].selectedpoints).toEqual([1]);
@@ -1892,6 +1903,7 @@ describe('Test select box and lasso in general:', function() {
}).then(function() {
return drag([[600, 150], [650, 150]]); // Extend existing selection
}).then(function() {
+ console.log('[after drag 3] selectedpoints:', gd.data.map(function(t) { return t.selectedpoints; }));
expect(gd.data[0].selectedpoints).toEqual([1, 2]);
expect(gd.data[1].selectedpoints).toEqual([1, 2]);
expect(gd.data[2].selectedpoints).toEqual([1, 2]);
@@ -2299,7 +2311,7 @@ describe('Test select box and lasso per trace:', function() {
[[150, 150], [300, 300]],
function() {
assertPoints([['NY', 10]]);
- assertRanges([[-83.38, 46.13], [-74.06, 39.29]]);
+ assertRanges([[-83.35, 46.13], [-74.03, 39.29]]);
assertSelectedPoints({0: [0]});
},
null, BOXEVENTS, 'choroplethmap select'
@@ -2315,8 +2327,8 @@ describe('Test select box and lasso per trace:', function() {
assertPoints([['MA', 20]]);
assertSelectedPoints({0: [1]});
assertLassoPoints([
- [-74.06, 43.936], [-74.06, 39.293], [-67.84, 39.293],
- [-67.84, 43.936], [-74.06, 43.936]
+ [-74.03, 43.936], [-74.03, 39.293], [-67.81, 39.293],
+ [-67.81, 43.936], [-74.03, 43.936]
]);
},
null, LASSOEVENTS, 'choroplethmap lasso'
@@ -2656,12 +2668,14 @@ describe('Test select box and lasso per trace:', function() {
assertPoints([
[0, 281, 'Purchases'],
[0, 269, 'Material expenses'],
+ [0, 191, 'Personnel expenses'],
+ [0, 179, 'Other expenses'],
]);
assertSelectedPoints({
- 0: [5, 6]
+ 0: [5, 6, 7, 8]
});
},
- null, [3, 2, 1], 'waterfall lasso'
+ null, [4, 2, 1], 'waterfall lasso'
);
})
.then(function() {
@@ -2705,13 +2719,14 @@ describe('Test select box and lasso per trace:', function() {
assertPoints([
[0, 331.5, 'Author: etpinard'],
[1, 53.5, 'Pull requests'],
+ [1, 15.5, 'Author: etpinard'],
]);
assertSelectedPoints({
0: [2],
- 1: [1]
+ 1: [1, 2]
});
},
- null, [3, 2, 1], 'funnel lasso'
+ null, [4, 2, 1], 'funnel lasso'
);
})
.then(done, done.fail);
@@ -3336,7 +3351,7 @@ describe('Test select box and lasso per trace:', function() {
[[150, 150], [300, 300]],
function() {
assertPoints([['NY', 10]]);
- assertRanges([[-83.38, 46.13], [-74.06, 39.29]]);
+ assertRanges([[-83.35, 46.13], [-74.03, 39.29]]);
assertSelectedPoints({0: [0], 3: []});
},
null, BOXEVENTS, 'choroplethmap select'
@@ -3352,8 +3367,8 @@ describe('Test select box and lasso per trace:', function() {
assertPoints([['MA', 20], []]);
assertSelectedPoints({0: [1], 3: [0]});
assertLassoPoints([
- [-74.06, 43.936], [-74.06, 39.293], [-67.84, 39.293],
- [-67.84, 43.936], [-74.06, 43.936]
+ [-74.03, 43.936], [-74.03, 39.293], [-67.81, 39.293],
+ [-67.81, 43.936], [-74.03, 43.936]
]);
},
null, LASSOEVENTS, 'choroplethmap lasso'