diff --git a/.prettierignore b/.prettierignore
index 8cc94b7af4..d62a7667df 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -9,6 +9,8 @@ __pycache__/
.vscode-test-web/
.venv/
/allocator/
+/circuit_vis/lib/
+/circuit_vis/dist/
/compiler/
/jupyterlab/lib/
/jupyterlab/qsharp-jupyterlab/labextension/
diff --git a/build.py b/build.py
index 5a748a10f4..aefa575b6b 100755
--- a/build.py
+++ b/build.py
@@ -69,10 +69,10 @@
)
parser.add_argument(
- "--ci-bench",
- action=argparse.BooleanOptionalAction,
- default=False,
- help="Run the benchmarking script that is run in CI (default is --no-ci-bench)",
+ "--ci-bench",
+ action=argparse.BooleanOptionalAction,
+ default=False,
+ help="Run the benchmarking script that is run in CI (default is --no-ci-bench)",
)
args = parser.parse_args()
@@ -117,6 +117,7 @@
wasm_bld = os.path.join(root_dir, "target", "wasm32", build_type)
samples_src = os.path.join(root_dir, "samples")
npm_src = os.path.join(root_dir, "npm", "qsharp")
+circuit_vis_src = os.path.join(root_dir, "circuit_vis")
play_src = os.path.join(root_dir, "playground")
pip_src = os.path.join(root_dir, "pip")
widgets_src = os.path.join(root_dir, "widgets")
@@ -303,25 +304,46 @@ def run_python_integration_tests(cwd, interpreter):
def run_ci_historic_benchmark():
branch = "main"
output = subprocess.check_output(
- ["git", "rev-list", "--since=1 week ago", "--pretty=format:%ad__%h", "--date=short", branch]
+ [
+ "git",
+ "rev-list",
+ "--since=1 week ago",
+ "--pretty=format:%ad__%h",
+ "--date=short",
+ branch,
+ ]
).decode("utf-8")
- print('\n'.join([line for i, line in enumerate(output.split('\n')) if i % 2 == 1]))
+ print("\n".join([line for i, line in enumerate(output.split("\n")) if i % 2 == 1]))
output = subprocess.check_output(
- ["git", "rev-list", "--since=1 week ago", "--pretty=format:%ad__%h", "--date=short", branch]
+ [
+ "git",
+ "rev-list",
+ "--since=1 week ago",
+ "--pretty=format:%ad__%h",
+ "--date=short",
+ branch,
+ ]
).decode("utf-8")
- date_and_commits = [line for i, line in enumerate(output.split('\n')) if i % 2 == 1]
+ date_and_commits = [line for i, line in enumerate(output.split("\n")) if i % 2 == 1]
for date_and_commit in date_and_commits:
print("benching commit", date_and_commit)
result = subprocess.run(
- ["cargo", "criterion", "--message-format=json", "--history-id", date_and_commit],
+ [
+ "cargo",
+ "criterion",
+ "--message-format=json",
+ "--history-id",
+ date_and_commit,
+ ],
capture_output=True,
- text=True
+ text=True,
)
with open(f"{date_and_commit}.json", "w") as f:
f.write(result.stdout)
+
if build_pip:
step_start("Building the pip package")
@@ -357,23 +379,6 @@ def run_ci_historic_benchmark():
step_end()
-if build_widgets:
- step_start("Building the Python widgets")
-
- widgets_build_args = [
- sys.executable,
- "-m",
- "pip",
- "wheel",
- "--no-deps",
- "--wheel-dir",
- wheels_dir,
- widgets_src,
- ]
- subprocess.run(widgets_build_args, check=True, text=True, cwd=widgets_src)
-
- step_end()
-
if build_wasm:
step_start("Building the wasm crate")
# wasm-pack can't build for web and node in the same build, so need to run twice.
@@ -394,6 +399,16 @@ def run_ci_historic_benchmark():
)
step_end()
+if build_npm:
+ step_start("Building the circuit_vis package")
+
+ npm_args = [npm_cmd, "install"]
+ subprocess.run(npm_args, check=True, text=True, cwd=circuit_vis_src)
+
+ npm_args = [npm_cmd, "run", "build:prod"]
+ subprocess.run(npm_args, check=True, text=True, cwd=circuit_vis_src)
+ step_end()
+
if build_npm:
step_start("Building the npm package")
# Copy the wasm build files over for web and node targets
@@ -422,6 +437,23 @@ def run_ci_historic_benchmark():
subprocess.run(npm_test_args, check=True, text=True, cwd=npm_src)
step_end()
+if build_widgets:
+ step_start("Building the Python widgets")
+
+ widgets_build_args = [
+ sys.executable,
+ "-m",
+ "pip",
+ "wheel",
+ "--no-deps",
+ "--wheel-dir",
+ wheels_dir,
+ widgets_src,
+ ]
+ subprocess.run(widgets_build_args, check=True, text=True, cwd=widgets_src)
+
+ step_end()
+
if build_play:
step_start("Building the playground")
play_args = [npm_cmd, "run", "build"]
diff --git a/circuit_vis/.gitignore b/circuit_vis/.gitignore
new file mode 100644
index 0000000000..34a3ea2900
--- /dev/null
+++ b/circuit_vis/.gitignore
@@ -0,0 +1,156 @@
+## Ignore Node.js temporary files, build results, and
+## files generated by popular Node.js add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/Node.gitignore
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env.test
+.*[v]env/
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+
+## Project-specific files ##
+
+# NPM's package-lock.json
+package-lock.json
+
+# VSCode configuration
+.vscode
+
+# IntelliJ
+.idea
+
+# macOS metadata folder
+.DS_Store
+
+# Compiled files
+lib
+.tgz
+
+# Python
+.mypy_cache/
+*/.coverage
+*/.coverage.*
+*/.nox/
+*/.python-version
+*/.pytype/
+/dist/
+*/docs/_build/
+/src/*.egg-info/
+__pycache__/
+*/.cookiecutter.json
+*-checkpoint.ipynb
+*/*.xml
+*.xml
diff --git a/circuit_vis/.prettierrc.js b/circuit_vis/.prettierrc.js
new file mode 100644
index 0000000000..7780e14a1e
--- /dev/null
+++ b/circuit_vis/.prettierrc.js
@@ -0,0 +1,17 @@
+module.exports = {
+ semi: true,
+ trailingComma: "all",
+ singleQuote: true,
+ endOfLine: "lf",
+ printWidth: 120,
+ tabWidth: 4,
+ parser: "typescript",
+ overrides: [
+ {
+ files: ["*.md", "*.json", "*.yml", "*.yaml"],
+ options: {
+ tabWidth: 2,
+ },
+ },
+ ],
+};
diff --git a/circuit_vis/README.md b/circuit_vis/README.md
new file mode 100644
index 0000000000..9a0d5dd57c
--- /dev/null
+++ b/circuit_vis/README.md
@@ -0,0 +1,107 @@
+# quantum-viz.js
+
+[![Licensed under the MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.txt)
+[![PR's Welcome](https://img.shields.io/badge/PRs%20-welcome-brightgreen.svg)](CONTRIBUTING.md)
+[![Build Status](https://github.com/microsoft/quantum-viz.js/actions/workflows/main.yml/badge.svg)](https://github.com/microsoft/quantum-viz.js/actions)
+[![npm version](https://badge.fury.io/js/%40microsoft%2Fquantum-viz.js.svg)](https://www.npmjs.com/package/@microsoft/quantum-viz.js)
+
+
+
+
+
+**quantum-viz.js** (or **qviz**) is a configurable tool for rendering quantum circuits. With the increasing demand for quantum libraries and educational tools, quantum circuits provide an intuitive way to visualize and understand quantum algorithms. quantum-viz.js is a lightweight library that can be easily integrated into any project. It aims to be easily configurable while allowing complex user interactions, such as toggling between different measurement outcomes.
+
+## Getting Started
+
+### Installation
+
+Include `quantum-viz.js` in your HTML page by using it directly from CDN:
+
+```html
+
+```
+
+or import it in into your TypeScript package:
+
+```bash
+npm i @microsoft/quantum-viz.js
+```
+
+### Usage
+
+1. Create a `Circuit` JavaScript object (the `Circuit` schema is documented [here](https://github.com/microsoft/quantum-viz.js/wiki/API-schema-reference)):
+
+```js
+const sampleCircuit = {
+ qubits: [
+ // ...
+ ],
+ operations: [
+ // ...
+ ],
+};
+```
+
+2. Draw it in a `div`:
+
+```js
+const sampleDiv = document.getElementById("sample");
+qviz.draw(sampleCircuit, sampleDiv, qviz.STYLES["Default"]);
+```
+
+Refer to the [`example`](./example) folder for an example on how to use quantum-viz.js. Notice that in order to open the contents of this folder in a browser you will need first to install from source (see [below](#running-from-source)).
+
+## Python usage
+
+To use this package with Python, use [quantum-viz](/quantum-viz).
+
+## Running from source
+
+### Installing
+
+To build and install this project from source, run the following commands from the root folder of this repository:
+
+```bash
+# Install dependencies
+> npm install
+# Build
+> npm run build:prod
+```
+
+### Running tests
+
+To run tests for this project, run the following commands:
+
+```bash
+# Installs dependencies (run this step if you haven't)
+> npm install
+# Starts Jest tests
+> npm run test
+```
+
+## Contributing
+
+Check out our [contributing guidelines](CONTRIBUTING.md) to find out how you can contribute to quantum-viz.js!
+
+## Feedback
+
+If you have feedback about this library, please let us know by filing a [new issue](https://github.com/microsoft/quantum-viz.js/issues/new/choose)!
+
+## Reporting Security Issues
+
+Security issues and bugs should be reported privately, via email, to the Microsoft Security
+Response Center (MSRC) at [secure@microsoft.com](mailto:secure@microsoft.com). You should
+receive a response within 24 hours. If for some reason you do not, please follow up via
+email to ensure we received your original message. Further information, including the
+[MSRC PGP](https://technet.microsoft.com/en-us/security/dn606155) key, can be found in
+the [Security TechCenter](https://technet.microsoft.com/en-us/security/default).
+
+## Trademarks
+
+This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.
+
+## Code of Conduct
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
+contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
diff --git a/circuit_vis/SECURITY.md b/circuit_vis/SECURITY.md
new file mode 100644
index 0000000000..1488eb5dc9
--- /dev/null
+++ b/circuit_vis/SECURITY.md
@@ -0,0 +1,41 @@
+
+
+## Security
+
+Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
+
+If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
+
+## Reporting Security Issues
+
+**Please do not report security vulnerabilities through public GitHub issues.**
+
+Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
+
+If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
+
+You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
+
+Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
+
+- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
+- Full paths of source file(s) related to the manifestation of the issue
+- The location of the affected source code (tag/branch/commit or direct URL)
+- Any special configuration required to reproduce the issue
+- Step-by-step instructions to reproduce the issue
+- Proof-of-concept or exploit code (if possible)
+- Impact of the issue, including how an attacker might exploit the issue
+
+This information will help us triage your report more quickly.
+
+If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
+
+## Preferred Languages
+
+We prefer all communications to be in English.
+
+## Policy
+
+Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
+
+
diff --git a/circuit_vis/__tests__/__snapshots__/gateFormatter.test.ts.snap b/circuit_vis/__tests__/__snapshots__/gateFormatter.test.ts.snap
new file mode 100644
index 0000000000..e75b6f54d1
--- /dev/null
+++ b/circuit_vis/__tests__/__snapshots__/gateFormatter.test.ts.snap
@@ -0,0 +1,2991 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Testing _classicalControlled No htmlClass 1`] = `
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+
+ ?
+
+
+
+
+`;
+
+exports[`Testing _classicalControlled change padding 1`] = `
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+
+ ?
+
+
+
+
+`;
+
+exports[`Testing _classicalControlled multiple 'zero'/'one' children 1`] = `
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+ Z
+
+
+
+
+
+
+
+
+
+
+
+ H
+
+
+
+
+
+
+
+
+
+
+ ?
+
+
+
+
+`;
+
+exports[`Testing _classicalControlled nested children 1`] = `
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ?
+
+
+
+
+
+
+
+
+
+
+ ?
+
+
+
+
+`;
+
+exports[`Testing _classicalControlled one 'one' child 1`] = `
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+
+ ?
+
+
+
+
+`;
+
+exports[`Testing _classicalControlled one 'zero' child 1`] = `
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+
+
+ ?
+
+
+
+
+`;
+
+exports[`Testing _classicalControlled one 'zero'/'one' child 1`] = `
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+
+
+ H
+
+
+
+
+
+
+
+
+
+
+ ?
+
+
+
+
+`;
+
+exports[`Testing _controlledGate CNOT gate 1`] = `
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _controlledGate CNOT gate 2`] = `
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with 1 control + 1 target 1`] = `
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with 1 control + 1 target 2`] = `
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with 1 control + 2 targets 1`] = `
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with 1 control + 2 targets 2`] = `
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with 1 control + 2 targets 3`] = `
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with 2 controls + 2 targets 1`] = `
+
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with 2 controls + 2 targets 2`] = `
+
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with 2 controls + 2 targets 3`] = `
+
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with 2 controls + 2 targets 4`] = `
+
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with multiple controls + 1 target 1`] = `
+
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with multiple controls + 1 target 2`] = `
+
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate Controlled U gate with multiple controls + 1 target 3`] = `
+
+
+
+
+
+
+
+
+ Foo
+
+
+
+
+`;
+
+exports[`Testing _controlledGate SWAP gate 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _controlledGate SWAP gate 2`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _controlledGate SWAP gate 3`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _createGate Expanded gate 1`] = `
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _formatGate CNOT gate 1`] = `
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _formatGate classically controlled gate 1`] = `
+
+
+
+
+
+
+ ?
+
+
+
+
+`;
+
+exports[`Testing _formatGate controlled swap gate 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _formatGate controlled unitary gate 1`] = `
+
+
+
+
+
+
+
+ U
+
+
+
+
+`;
+
+exports[`Testing _formatGate controlled unitary gate with arguments 1`] = `
+
+
+
+
+
+
+
+ U
+
+
+ ('foo', 'bar')
+
+
+
+
+`;
+
+exports[`Testing _formatGate gate with metadata 1`] = `
+
+
+
+
+
+ H
+
+
+
+
+`;
+
+exports[`Testing _formatGate measure gate 1`] = `
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _formatGate multi-qubit unitary gate 1`] = `
+
+
+
+
+
+ U
+
+
+
+
+`;
+
+exports[`Testing _formatGate multi-qubit unitary gate with arguments 1`] = `
+
+
+
+
+
+
+ U
+
+
+ ('foo', 'bar')
+
+
+
+
+`;
+
+exports[`Testing _formatGate single-qubit unitary gate 1`] = `
+
+
+
+
+
+ H
+
+
+
+
+`;
+
+exports[`Testing _formatGate single-qubit unitary gate with arguments 1`] = `
+
+
+
+
+
+ Ry
+
+
+ (0.25)
+
+
+
+
+`;
+
+exports[`Testing _formatGate swap gate 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _groupedOperations children on consecutive registers 1`] = `
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+ H
+
+
+
+
+
+
+`;
+
+exports[`Testing _groupedOperations children on non-consecutive registers 1`] = `
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+ H
+
+
+
+
+
+
+`;
+
+exports[`Testing _groupedOperations children on same register 1`] = `
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+ Z
+
+
+
+
+
+
+`;
+
+exports[`Testing _groupedOperations multiple children 1`] = `
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+ Z
+
+
+
+
+
+
+
+
+
+ H
+
+
+
+
+
+
+`;
+
+exports[`Testing _groupedOperations nested children 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+ Z
+
+
+
+
+
+
+
+
+
+
+
+ H
+
+
+
+
+
+
+`;
+
+exports[`Testing _groupedOperations one child 1`] = `
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+`;
+
+exports[`Testing _measure 1 qubit + 1 classical registers 1`] = `
+
+
+
+
+
+`;
+
+exports[`Testing _measure 2 qubit + 1 classical registers 1`] = `
+
+
+
+
+
+`;
+
+exports[`Testing _measure 2 qubit + 2 classical registers 1`] = `
+
+
+
+
+
+`;
+
+exports[`Testing _measure 2 qubit + 2 classical registers 2`] = `
+
+
+
+
+
+`;
+
+exports[`Testing _swap Adjacent swap 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _swap Adjacent swap 2`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _swap Non-adjacent swap 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _swap Non-adjacent swap 2`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing _unitary Multiqubit unitary on consecutive registers 1`] = `
+
+
+
+
+ ZZ
+
+
+
+`;
+
+exports[`Testing _unitary Multiqubit unitary on consecutive registers 2`] = `
+
+
+
+
+ ZZZ
+
+
+
+`;
+
+exports[`Testing _unitary Multiqubit unitary on non-consecutive registers 1`] = `
+
+
+
+
+
+ ZZ
+
+
+
+
+
+ ZZ
+
+
+
+`;
+
+exports[`Testing _unitary Multiqubit unitary on non-consecutive registers 2`] = `
+
+
+
+
+
+ ZZZ
+
+
+
+
+
+ ZZZ
+
+
+
+`;
+
+exports[`Testing _unitary Multiqubit unitary on non-consecutive registers 3`] = `
+
+
+
+
+ ZZ
+
+
+
+
+
+ ZZ
+
+
+
+`;
+
+exports[`Testing _unitary Multiqubit unitary on non-consecutive registers 4`] = `
+
+
+
+
+ ZZZ
+
+
+
+
+
+ ZZZ
+
+
+
+`;
+
+exports[`Testing _unitary Single qubit unitary 1`] = `
+
+
+
+
+ H
+
+
+
+`;
+
+exports[`Testing _zoomButton Expanded gate 1`] = `null`;
+
+exports[`Testing _zoomButton Expanded with children gate 1`] = `null`;
+
+exports[`Testing _zoomButton Non-expanded with children gate 1`] = `null`;
+
+exports[`Testing _zoomButton Non-expanded with no children gate 1`] = `null`;
+
+exports[`Testing formatGates Multiple gates 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing formatGates Single gate 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/circuit_vis/__tests__/__snapshots__/inputFormatter.test.ts.snap b/circuit_vis/__tests__/__snapshots__/inputFormatter.test.ts.snap
new file mode 100644
index 0000000000..fc1914accb
--- /dev/null
+++ b/circuit_vis/__tests__/__snapshots__/inputFormatter.test.ts.snap
@@ -0,0 +1,170 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Testing _qubitInput classical register 1`] = `
+
+ |0⟩
+
+`;
+
+exports[`Testing _qubitInput classical register 2`] = `
+
+ |0⟩
+
+`;
+
+exports[`Testing _qubitInput classical register 3`] = `
+
+ |0⟩
+
+`;
+
+exports[`Testing formatInputs 1 quantum register 1`] = `
+
+
+ |0⟩
+
+
+`;
+
+exports[`Testing formatInputs Multiple quantum registers 1`] = `
+
+
+ |0⟩
+
+
+ |0⟩
+
+
+ |0⟩
+
+
+`;
+
+exports[`Testing formatInputs Quantum and classical registers 1`] = `
+
+
+ |0⟩
+
+
+ |0⟩
+
+
+ |0⟩
+
+
+`;
+
+exports[`Testing formatInputs Quantum and classical registers 2`] = `
+
+
+ |0⟩
+
+
+ |0⟩
+
+
+ |0⟩
+
+
+`;
+
+exports[`Testing formatInputs Skip quantum registers 1`] = `
+
+
+ |0⟩
+
+
+ |0⟩
+
+
+`;
diff --git a/circuit_vis/__tests__/__snapshots__/registerFormatter.test.ts.snap b/circuit_vis/__tests__/__snapshots__/registerFormatter.test.ts.snap
new file mode 100644
index 0000000000..3487917ad4
--- /dev/null
+++ b/circuit_vis/__tests__/__snapshots__/registerFormatter.test.ts.snap
@@ -0,0 +1,1786 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Testing _classicalRegister register with label offset 1`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with label offset 2`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with label offset 3`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with large width 1`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with large width 2`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with large width 3`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with normal width 1`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with normal width 2`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with normal width 3`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with small width 1`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with small width 2`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _classicalRegister register with small width 3`] = `
+
+
+
+
+
+
+`;
+
+exports[`Testing _qubitRegister register with label offset 1`] = `
+
+
+
+ q0
+
+
+`;
+
+exports[`Testing _qubitRegister register with label offset 2`] = `
+
+
+
+ q1
+
+
+`;
+
+exports[`Testing _qubitRegister register with label offset 3`] = `
+
+
+
+ q2
+
+
+`;
+
+exports[`Testing _qubitRegister register with large width 1`] = `
+
+
+
+ q0
+
+
+`;
+
+exports[`Testing _qubitRegister register with large width 2`] = `
+
+
+
+ q1
+
+
+`;
+
+exports[`Testing _qubitRegister register with large width 3`] = `
+
+
+
+ q2
+
+
+`;
+
+exports[`Testing _qubitRegister register with normal width 1`] = `
+
+
+
+ q0
+
+
+`;
+
+exports[`Testing _qubitRegister register with normal width 2`] = `
+
+
+
+ q1
+
+
+`;
+
+exports[`Testing _qubitRegister register with normal width 3`] = `
+
+
+
+ q2
+
+
+`;
+
+exports[`Testing _qubitRegister register with small width 1`] = `
+
+
+
+ q0
+
+
+`;
+
+exports[`Testing _qubitRegister register with small width 2`] = `
+
+
+
+ q1
+
+
+`;
+
+exports[`Testing _qubitRegister register with small width 3`] = `
+
+
+
+ q2
+
+
+`;
+
+exports[`Testing formatRegisters 1 quantum register 1`] = `
+
+
+
+
+ q0
+
+
+
+`;
+
+exports[`Testing formatRegisters 1 quantum register 2`] = `
+
+
+
+
+ q0
+
+
+
+`;
+
+exports[`Testing formatRegisters 1 quantum register 3`] = `
+
+
+
+
+ q0
+
+
+
+`;
+
+exports[`Testing formatRegisters Multiple quantum registers 1`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q1
+
+
+
+
+
+ q2
+
+
+
+
+
+ q3
+
+
+
+`;
+
+exports[`Testing formatRegisters Multiple quantum registers 2`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q1
+
+
+
+
+
+ q2
+
+
+
+
+
+ q3
+
+
+
+`;
+
+exports[`Testing formatRegisters Multiple quantum registers 3`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q1
+
+
+
+
+
+ q2
+
+
+
+
+
+ q3
+
+
+
+`;
+
+exports[`Testing formatRegisters Quantum and classical registers 1`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q1
+
+
+
+
+
+ q2
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing formatRegisters Quantum and classical registers 2`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q1
+
+
+
+
+
+ q2
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing formatRegisters Quantum and classical registers 3`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q1
+
+
+
+
+
+ q2
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing formatRegisters Quantum and classical registers 4`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q1
+
+
+
+
+
+ q2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing formatRegisters Quantum and classical registers 5`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q1
+
+
+
+
+
+ q2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing formatRegisters Quantum and classical registers 6`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q1
+
+
+
+
+
+ q2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Testing formatRegisters Skipped quantum registers 1`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q2
+
+
+
+
+
+ q3
+
+
+
+`;
+
+exports[`Testing formatRegisters Skipped quantum registers 2`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q2
+
+
+
+
+
+ q3
+
+
+
+`;
+
+exports[`Testing formatRegisters Skipped quantum registers 3`] = `
+
+
+
+
+ q0
+
+
+
+
+
+ q2
+
+
+
+
+
+ q3
+
+
+
+`;
diff --git a/circuit_vis/__tests__/gateFormatter.test.ts b/circuit_vis/__tests__/gateFormatter.test.ts
new file mode 100644
index 0000000000..d0448c39f0
--- /dev/null
+++ b/circuit_vis/__tests__/gateFormatter.test.ts
@@ -0,0 +1,1061 @@
+import {
+ formatGates,
+ _formatGate,
+ _createGate,
+ _measure,
+ _unitary,
+ _swap,
+ _controlledGate,
+ _groupedOperations,
+ _classicalControlled,
+ _zoomButton,
+} from "../src/formatters/gateFormatter";
+import { createSvgElement } from "../src/formatters/formatUtils";
+import { Metadata, GateType } from "../src/metadata";
+import {
+ startX,
+ startY,
+ registerHeight,
+ minGateWidth,
+ gatePadding,
+ classicalRegHeight,
+ controlBtnOffset,
+ groupBoxPadding,
+} from "../src/constants";
+
+describe("Testing _classicalControlled", () => {
+ test("one 'zero' child", () => {
+ const metadata: Metadata = {
+ type: GateType.ClassicalControlled,
+ x: startX,
+ controlsY: [startY + classicalRegHeight],
+ targetsY: [startY],
+ width: minGateWidth + controlBtnOffset + groupBoxPadding * 2,
+ label: "",
+ children: [
+ [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + controlBtnOffset + groupBoxPadding,
+ label: "X",
+ controlsY: [],
+ targetsY: [[startY]],
+ width: minGateWidth,
+ },
+ ],
+ [],
+ ],
+ };
+ expect(_classicalControlled(metadata)).toMatchSnapshot();
+ });
+ test("one 'one' child", () => {
+ const metadata: Metadata = {
+ type: GateType.ClassicalControlled,
+ x: startX,
+ controlsY: [startY + classicalRegHeight],
+ targetsY: [startY],
+ width: minGateWidth + controlBtnOffset + groupBoxPadding * 2,
+ label: "",
+ children: [
+ [],
+ [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + controlBtnOffset + groupBoxPadding,
+ label: "X",
+ controlsY: [],
+ targetsY: [[startY]],
+ width: minGateWidth,
+ },
+ ],
+ ],
+ };
+ expect(_classicalControlled(metadata)).toMatchSnapshot();
+ });
+ test("one 'zero'/'one' child", () => {
+ const metadata: Metadata = {
+ type: GateType.ClassicalControlled,
+ x: startX,
+ controlsY: [startY + classicalRegHeight],
+ targetsY: [startY, startY + classicalRegHeight * 2],
+ label: "",
+ width: minGateWidth + controlBtnOffset + groupBoxPadding * 2,
+ children: [
+ [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + controlBtnOffset + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "X",
+ width: minGateWidth,
+ },
+ ],
+ [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + controlBtnOffset + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY + classicalRegHeight * 2]],
+ label: "H",
+ width: minGateWidth,
+ },
+ ],
+ ],
+ };
+ expect(_classicalControlled(metadata)).toMatchSnapshot();
+ });
+ test("multiple 'zero'/'one' children", () => {
+ const metadata: Metadata = {
+ type: GateType.ClassicalControlled,
+ x: startX,
+ controlsY: [startY + classicalRegHeight],
+ targetsY: [startY, startY + classicalRegHeight * 2],
+ label: "",
+ width:
+ minGateWidth * 2 +
+ gatePadding * 2 +
+ controlBtnOffset +
+ groupBoxPadding * 2,
+ children: [
+ [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + controlBtnOffset + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Unitary,
+ x:
+ startX +
+ minGateWidth +
+ minGateWidth / 2 +
+ gatePadding * 2 +
+ controlBtnOffset +
+ groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "Z",
+ width: minGateWidth,
+ },
+ ],
+ [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + controlBtnOffset + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY + classicalRegHeight * 2]],
+ label: "H",
+ width: minGateWidth,
+ },
+ ],
+ ],
+ };
+ expect(_classicalControlled(metadata)).toMatchSnapshot();
+ });
+ test("nested children", () => {
+ const metadata: Metadata = {
+ type: GateType.ClassicalControlled,
+ x: startX,
+ controlsY: [startY + registerHeight * 2],
+ targetsY: [startY, startY + registerHeight],
+ width: minGateWidth * 2 + gatePadding * 6,
+ label: "if",
+ children: [
+ [],
+ [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + gatePadding,
+ label: "X",
+ controlsY: [],
+ targetsY: [[startY]],
+ width: minGateWidth,
+ },
+ {
+ type: GateType.ClassicalControlled,
+ x: startX + minGateWidth + gatePadding * 3,
+ controlsY: [startY + registerHeight * 3],
+ targetsY: [startY, startY + registerHeight],
+ width: minGateWidth + gatePadding * 2,
+ label: "if",
+ children: [
+ [],
+ [
+ {
+ type: GateType.Cnot,
+ x: startX + minGateWidth + gatePadding * 4 + minGateWidth / 2,
+ label: "X",
+ controlsY: [startY + registerHeight],
+ targetsY: [startY],
+ width: minGateWidth,
+ },
+ ],
+ ],
+ },
+ ],
+ ],
+ };
+ expect(_classicalControlled(metadata)).toMatchSnapshot();
+ });
+ test("No htmlClass", () => {
+ const metadata: Metadata = {
+ type: GateType.ClassicalControlled,
+ x: startX,
+ controlsY: [startY + registerHeight * 2],
+ targetsY: [startY, startY + registerHeight],
+ width: minGateWidth * 2 + gatePadding * 4,
+ label: "if",
+ children: [
+ [],
+ [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + gatePadding,
+ label: "X",
+ controlsY: [],
+ targetsY: [[startY]],
+ width: minGateWidth,
+ },
+ ],
+ ],
+ };
+ expect(_classicalControlled(metadata)).toMatchSnapshot();
+ });
+ test("change padding", () => {
+ const metadata: Metadata = {
+ type: GateType.ClassicalControlled,
+ x: startX,
+ controlsY: [startY + registerHeight * 2],
+ targetsY: [startY, startY + registerHeight],
+ width: minGateWidth * 2 + gatePadding * 4,
+ label: "if",
+ children: [
+ [],
+ [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + gatePadding,
+ label: "X",
+ controlsY: [],
+ targetsY: [[startY]],
+ width: minGateWidth,
+ },
+ ],
+ ],
+ };
+ expect(_classicalControlled(metadata, 20)).toMatchSnapshot();
+ });
+});
+
+describe("Testing _groupedOperations", () => {
+ test("one child", () => {
+ const metadata: Metadata = {
+ type: GateType.Group,
+ x: startX,
+ controlsY: [],
+ targetsY: [startY],
+ label: "",
+ width: minGateWidth + groupBoxPadding * 2,
+ children: [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "X",
+ width: minGateWidth,
+ },
+ ],
+ };
+ expect(_groupedOperations(metadata, 0)).toMatchSnapshot();
+ });
+ test("children on consecutive registers", () => {
+ const metadata: Metadata = {
+ type: GateType.Group,
+ x: startX,
+ controlsY: [],
+ targetsY: [startY, startY + registerHeight],
+ label: "",
+ width: minGateWidth + groupBoxPadding * 2,
+ children: [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY + registerHeight]],
+ label: "H",
+ width: minGateWidth,
+ },
+ ],
+ };
+ expect(_groupedOperations(metadata, 0)).toMatchSnapshot();
+ });
+ test("children on non-consecutive registers", () => {
+ const metadata: Metadata = {
+ type: GateType.Group,
+ x: startX,
+ controlsY: [],
+ targetsY: [startY, startY + registerHeight * 2],
+ label: "",
+ width: minGateWidth + groupBoxPadding * 2,
+ children: [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY + registerHeight * 2]],
+ label: "H",
+ width: minGateWidth,
+ },
+ ],
+ };
+ expect(_groupedOperations(metadata, 0)).toMatchSnapshot();
+ });
+ test("children on same register", () => {
+ const metadata: Metadata = {
+ type: GateType.Group,
+ x: startX,
+ controlsY: [],
+ targetsY: [startY],
+ label: "",
+ width: minGateWidth * 2 + gatePadding * 2 + groupBoxPadding * 2,
+ children: [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Unitary,
+ x:
+ startX +
+ minGateWidth +
+ minGateWidth / 2 +
+ gatePadding * 2 +
+ groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "Z",
+ width: minGateWidth,
+ },
+ ],
+ };
+ expect(_groupedOperations(metadata, 0)).toMatchSnapshot();
+ });
+ test("multiple children", () => {
+ const metadata: Metadata = {
+ type: GateType.Group,
+ x: startX,
+ controlsY: [],
+ targetsY: [startY, startY + registerHeight],
+ label: "",
+ width: minGateWidth * 2 + gatePadding * 2 + groupBoxPadding * 2,
+ children: [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Unitary,
+ x:
+ startX +
+ minGateWidth +
+ minGateWidth / 2 +
+ gatePadding * 2 +
+ groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "Z",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY + registerHeight]],
+ label: "H",
+ width: minGateWidth,
+ },
+ ],
+ };
+ expect(_groupedOperations(metadata, 0)).toMatchSnapshot();
+ });
+ test("nested children", () => {
+ const metadata: Metadata = {
+ type: GateType.Group,
+ x: startX,
+ controlsY: [],
+ targetsY: [startY, startY + registerHeight],
+ label: "",
+ width: minGateWidth * 2 + gatePadding * 2 + groupBoxPadding * 4,
+ children: [
+ {
+ type: GateType.Group,
+ x: startX + gatePadding,
+ controlsY: [],
+ targetsY: [startY],
+ label: "",
+ width: minGateWidth * 2 + gatePadding * 2 + groupBoxPadding * 2,
+ children: [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + groupBoxPadding * 2,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Unitary,
+ x:
+ startX +
+ minGateWidth +
+ minGateWidth / 2 +
+ gatePadding * 2 +
+ groupBoxPadding * 2,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "Z",
+ width: minGateWidth,
+ },
+ ],
+ },
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth + gatePadding + groupBoxPadding * 2,
+ controlsY: [],
+ targetsY: [[startY + registerHeight]],
+ label: "H",
+ width: minGateWidth,
+ },
+ ],
+ };
+ expect(_groupedOperations(metadata, 0)).toMatchSnapshot();
+ });
+});
+
+describe("Testing _controlledGate", () => {
+ test("CNOT gate", () => {
+ const metadata: Metadata = {
+ type: GateType.Cnot,
+ label: "X",
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + registerHeight],
+ width: minGateWidth,
+ };
+ let svg: SVGElement = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Flip target and control
+ metadata.controlsY = [startY + registerHeight];
+ metadata.targetsY = [startY];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+ });
+ test("SWAP gate", () => {
+ const metadata: Metadata = {
+ type: GateType.Swap,
+ label: "",
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + registerHeight, startY + registerHeight * 2],
+ width: minGateWidth,
+ };
+ // Control on top
+ let svg: SVGElement = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Control on bottom
+ metadata.controlsY = [startY + registerHeight * 2];
+ metadata.targetsY = [startY, startY + registerHeight];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Control in middle
+ metadata.controlsY = [startY + registerHeight];
+ metadata.targetsY = [startY, startY + registerHeight * 2];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+ });
+ test("Controlled U gate with 1 control + 1 target", () => {
+ const metadata: Metadata = {
+ type: GateType.ControlledUnitary,
+ label: "Foo",
+ x: startX,
+ controlsY: [startY],
+ targetsY: [[startY + registerHeight]],
+ width: 45,
+ };
+ let svg: SVGElement = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Flip target and control
+ metadata.controlsY = [startY + registerHeight];
+ metadata.targetsY = [[startY]];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+ });
+ test("Controlled U gate with multiple controls + 1 target", () => {
+ const metadata: Metadata = {
+ type: GateType.ControlledUnitary,
+ label: "Foo",
+ x: startX,
+ controlsY: [startY, startY + registerHeight],
+ targetsY: [[startY + registerHeight * 2]],
+ width: 45,
+ };
+ // Target on bottom
+ let svg: SVGElement = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Target on top
+ metadata.controlsY = [startY + registerHeight, startY + registerHeight * 2];
+ metadata.targetsY = [[startY]];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Target in middle
+ metadata.controlsY = [startY, startY + registerHeight * 2];
+ metadata.targetsY = [[startY + registerHeight]];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+ });
+ test("Controlled U gate with 1 control + 2 targets", () => {
+ const metadata: Metadata = {
+ type: GateType.ControlledUnitary,
+ label: "Foo",
+ x: startX,
+ controlsY: [startY + registerHeight * 2],
+ targetsY: [[startY, startY + registerHeight]],
+ width: 45,
+ };
+ // Control on bottom
+ let svg: SVGElement = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Control on top
+ metadata.controlsY = [startY];
+ metadata.targetsY = [
+ [startY + registerHeight, startY + registerHeight * 2],
+ ];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Control in middle
+ metadata.controlsY = [startY + registerHeight];
+ metadata.targetsY = [[startY], [startY + registerHeight * 2]];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+ });
+ test("Controlled U gate with 2 controls + 2 targets", () => {
+ const metadata: Metadata = {
+ type: GateType.ControlledUnitary,
+ label: "Foo",
+ x: startX,
+ controlsY: [startY + registerHeight * 2, startY + registerHeight * 3],
+ targetsY: [[startY, startY + registerHeight]],
+ width: 45,
+ };
+ // Controls on bottom
+ let svg: SVGElement = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Controls on top
+ metadata.controlsY = [startY, startY + registerHeight];
+ metadata.targetsY = [
+ [startY + registerHeight * 2, startY + registerHeight * 3],
+ ];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Controls in middle
+ metadata.controlsY = [startY + registerHeight, startY + registerHeight * 2];
+ metadata.targetsY = [[startY], [startY + registerHeight * 3]];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+
+ // Interleaved controls/targets
+ metadata.controlsY = [startY + registerHeight, startY + registerHeight * 3];
+ metadata.targetsY = [[startY], [startY + registerHeight * 2]];
+ svg = _controlledGate(metadata, 0);
+ expect(svg).toMatchSnapshot();
+ });
+ test("Invalid gate", () => {
+ const metadata: Metadata = {
+ type: GateType.Measure,
+ label: "X",
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + registerHeight],
+ width: minGateWidth,
+ };
+ expect(() => _controlledGate(metadata, 0)).toThrowError(
+ `ERROR: Unrecognized gate: X of type ${GateType.Measure}`,
+ );
+ });
+});
+
+describe("Testing _swap", () => {
+ const metadata: Metadata = {
+ type: GateType.Measure,
+ label: "SWAP",
+ x: startX,
+ controlsY: [],
+ targetsY: [startY + registerHeight],
+ width: minGateWidth,
+ };
+
+ test("Adjacent swap", () => {
+ metadata.targetsY = [startY, startY + registerHeight];
+ let svg: SVGElement = _swap(metadata, 0);
+ expect(svg).toMatchSnapshot();
+ // Flip target and control
+ metadata.targetsY = [startY + registerHeight, startY];
+ svg = _swap(metadata, 0);
+ expect(svg).toMatchSnapshot();
+ });
+ test("Non-adjacent swap", () => {
+ metadata.targetsY = [startY, startY + registerHeight * 2];
+ let svg: SVGElement = _swap(metadata, 0);
+ expect(svg).toMatchSnapshot();
+ // Flip target and control
+ metadata.targetsY = [startY + registerHeight * 2, startY];
+ svg = _swap(metadata, 0);
+ expect(svg).toMatchSnapshot();
+ });
+});
+
+describe("Testing _unitary", () => {
+ test("Single qubit unitary", () => {
+ expect(_unitary("H", startX, [[startY]], minGateWidth)).toMatchSnapshot();
+ });
+ test("Multiqubit unitary on consecutive registers", () => {
+ let svg: SVGElement = _unitary(
+ "ZZ",
+ startX,
+ [[startY, startY + registerHeight]],
+ minGateWidth,
+ );
+ expect(svg).toMatchSnapshot();
+ svg = _unitary(
+ "ZZZ",
+ startX,
+ [[startY, startY + registerHeight, startY + registerHeight * 2]],
+ minGateWidth,
+ );
+ expect(svg).toMatchSnapshot();
+ });
+ test("Multiqubit unitary on non-consecutive registers", () => {
+ // Dashed line between unitaries
+ let svg: SVGElement = _unitary(
+ "ZZ",
+ startX,
+ [[startY], [startY + registerHeight * 2]],
+ minGateWidth,
+ );
+ expect(svg).toMatchSnapshot();
+ svg = _unitary(
+ "ZZZ",
+ startX,
+ [[startY], [startY + registerHeight * 2, startY + registerHeight * 3]],
+ minGateWidth,
+ );
+ expect(svg).toMatchSnapshot();
+ // Solid line
+ svg = _unitary(
+ "ZZ",
+ startX,
+ [[startY], [startY + registerHeight * 2]],
+ minGateWidth,
+ undefined,
+ false,
+ );
+ expect(svg).toMatchSnapshot();
+ svg = _unitary(
+ "ZZZ",
+ startX,
+ [[startY], [startY + registerHeight * 2, startY + registerHeight * 3]],
+ minGateWidth,
+ undefined,
+ false,
+ );
+ expect(svg).toMatchSnapshot();
+ });
+ test("No y coords", () => {
+ expect(() => _unitary("ZZ", startX, [], minGateWidth)).toThrowError(
+ "Failed to render unitary gate (ZZ): has no y-values",
+ );
+ });
+});
+
+describe("Testing _measure", () => {
+ test("1 qubit + 1 classical registers", () => {
+ expect(_measure(startX, startY)).toMatchSnapshot();
+ });
+ test("2 qubit + 1 classical registers", () => {
+ expect(_measure(startX, startY)).toMatchSnapshot();
+ });
+ test("2 qubit + 2 classical registers", () => {
+ expect(_measure(startX, startY)).toMatchSnapshot();
+ expect(_measure(startX, startY + registerHeight)).toMatchSnapshot();
+ });
+});
+
+describe("Testing _createGate", () => {
+ const metadata: Metadata = {
+ type: GateType.Invalid,
+ x: 0,
+ controlsY: [],
+ targetsY: [],
+ label: "",
+ width: -1,
+ dataAttributes: { a: "1", b: "2" },
+ };
+ const line: SVGElement = createSvgElement("line");
+ test("Empty gate", () => {
+ expect(_createGate([line], metadata, 0).outerHTML).toEqual(
+ '',
+ );
+ });
+ test("Expanded gate", () => {
+ if (metadata.dataAttributes) metadata.dataAttributes["expanded"] = "true";
+ expect(_createGate([line], metadata, 0)).toMatchSnapshot();
+ });
+});
+
+describe("Testing _zoomButton", () => {
+ const metadata: Metadata = {
+ type: GateType.Group,
+ x: startX,
+ controlsY: [],
+ targetsY: [startY],
+ label: "",
+ width: minGateWidth + groupBoxPadding * 2,
+ children: [
+ {
+ type: GateType.Unitary,
+ x: startX + minGateWidth / 2 + groupBoxPadding,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "X",
+ width: minGateWidth,
+ },
+ ],
+ };
+
+ test("Expanded gate", () => {
+ if (metadata.dataAttributes) {
+ metadata.dataAttributes["expanded"] = "true";
+ metadata.dataAttributes["zoom-in"] = "true";
+ }
+ expect(_zoomButton(metadata, 0)).toMatchSnapshot();
+ });
+ test("Non-expanded with no children gate", () => {
+ if (metadata.dataAttributes) {
+ delete metadata.dataAttributes["expanded"];
+ delete metadata.dataAttributes["zoom-in"];
+ }
+ expect(_zoomButton(metadata, 0)).toMatchSnapshot();
+ });
+ test("Non-expanded with children gate", () => {
+ if (metadata.dataAttributes) {
+ delete metadata.dataAttributes["expanded"];
+ metadata.dataAttributes["zoom-in"] = "true";
+ }
+ expect(_zoomButton(metadata, 0)).toMatchSnapshot();
+ });
+ test("Expanded with children gate", () => {
+ if (metadata.dataAttributes) {
+ metadata.dataAttributes["expanded"] = "true";
+ metadata.dataAttributes["zoom-in"] = "true";
+ }
+ expect(_zoomButton(metadata, 0)).toMatchSnapshot();
+ });
+});
+
+describe("Testing _formatGate", () => {
+ test("measure gate", () => {
+ const metadata: Metadata = {
+ type: GateType.Measure,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + registerHeight],
+ label: "",
+ width: minGateWidth,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("single-qubit unitary gate", () => {
+ const metadata: Metadata = {
+ type: GateType.Unitary,
+ x: startX,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "H",
+ width: minGateWidth,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("single-qubit unitary gate with arguments", () => {
+ const metadata: Metadata = {
+ type: GateType.Unitary,
+ x: startX,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "Ry",
+ displayArgs: "(0.25)",
+ width: 52,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("multi-qubit unitary gate", () => {
+ const metadata: Metadata = {
+ type: GateType.Unitary,
+ x: startX,
+ controlsY: [],
+ targetsY: [[startY, startY + registerHeight]],
+ label: "U",
+ width: minGateWidth,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("multi-qubit unitary gate with arguments", () => {
+ const metadata: Metadata = {
+ type: GateType.ControlledUnitary,
+ x: startX,
+ controlsY: [],
+ targetsY: [[startY, startY + registerHeight]],
+ label: "U",
+ displayArgs: "('foo', 'bar')",
+ width: 77,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("swap gate", () => {
+ const metadata: Metadata = {
+ type: GateType.Swap,
+ x: startX,
+ controlsY: [],
+ targetsY: [startY, startY + registerHeight],
+ label: "",
+ width: minGateWidth,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("controlled swap gate", () => {
+ const metadata: Metadata = {
+ type: GateType.Swap,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + registerHeight, startY + registerHeight * 2],
+ label: "",
+ width: minGateWidth,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("CNOT gate", () => {
+ const metadata: Metadata = {
+ type: GateType.Cnot,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + registerHeight],
+ label: "X",
+ width: minGateWidth,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("controlled unitary gate", () => {
+ const metadata: Metadata = {
+ type: GateType.ControlledUnitary,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [[startY + registerHeight]],
+ label: "U",
+ width: minGateWidth,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("controlled unitary gate with arguments", () => {
+ const metadata: Metadata = {
+ type: GateType.ControlledUnitary,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [[startY + registerHeight]],
+ label: "U",
+ displayArgs: "('foo', 'bar')",
+ width: 77,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("classically controlled gate", () => {
+ const metadata: Metadata = {
+ type: GateType.ClassicalControlled,
+ x: startX,
+ controlsY: [startY + registerHeight * 2],
+ targetsY: [startY, startY + registerHeight],
+ label: "",
+ width: minGateWidth,
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("gate with metadata", () => {
+ const metadata: Metadata = {
+ type: GateType.Unitary,
+ x: startX,
+ controlsY: [],
+ targetsY: [[startY]],
+ label: "H",
+ width: minGateWidth,
+ dataAttributes: { a: "1", b: "2" },
+ };
+ expect(_formatGate(metadata)).toMatchSnapshot();
+ });
+ test("invalid gate", () => {
+ const metadata: Metadata = {
+ type: GateType.Invalid,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + registerHeight],
+ label: "Foo",
+ width: 48,
+ };
+ expect(() => _formatGate(metadata)).toThrowError(
+ `ERROR: unknown gate (Foo) of type ${GateType.Invalid}.`,
+ );
+ });
+});
+
+describe("Testing formatGates", () => {
+ test("Single gate", () => {
+ const gates: Metadata[] = [
+ {
+ type: GateType.Cnot,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + registerHeight],
+ label: "X",
+ width: minGateWidth,
+ },
+ ];
+ expect(formatGates(gates)).toMatchSnapshot();
+ });
+ test("Single null gate", () => {
+ const gates: Metadata[] = [
+ {
+ type: GateType.Invalid,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + registerHeight],
+ label: "",
+ width: minGateWidth,
+ },
+ ];
+ expect(() => formatGates(gates)).toThrowError(
+ `ERROR: unknown gate () of type ${GateType.Invalid}.`,
+ );
+ });
+ test("Multiple gates", () => {
+ const gates: Metadata[] = [
+ {
+ type: GateType.Cnot,
+ x: startX,
+ controlsY: [startY + registerHeight],
+ targetsY: [startY],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.ControlledUnitary,
+ x: startX,
+ controlsY: [startY + registerHeight],
+ targetsY: [[startY + registerHeight * 2]],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Unitary,
+ x: startX,
+ controlsY: [],
+ targetsY: [[startY + registerHeight * 2]],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Measure,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + registerHeight * 3],
+ label: "X",
+ width: minGateWidth,
+ },
+ ];
+ expect(formatGates(gates)).toMatchSnapshot();
+ });
+ test("Multiple gates with invalid gate", () => {
+ const gates: Metadata[] = [
+ {
+ type: GateType.Unitary,
+ x: startX,
+ controlsY: [],
+ targetsY: [[startY + registerHeight * 2]],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Cnot,
+ x: startX,
+ controlsY: [startY + registerHeight],
+ targetsY: [startY],
+ label: "X",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Invalid,
+ x: startX,
+ controlsY: [],
+ targetsY: [startY + registerHeight * 2],
+ label: "",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Invalid,
+ x: startX,
+ controlsY: [],
+ targetsY: [],
+ label: "",
+ width: minGateWidth,
+ },
+ ];
+ expect(() => formatGates(gates)).toThrowError(
+ `ERROR: unknown gate () of type ${GateType.Invalid}.`,
+ );
+ });
+});
diff --git a/circuit_vis/__tests__/inputFormatter.test.ts b/circuit_vis/__tests__/inputFormatter.test.ts
new file mode 100644
index 0000000000..da37a1b8f5
--- /dev/null
+++ b/circuit_vis/__tests__/inputFormatter.test.ts
@@ -0,0 +1,102 @@
+import { Qubit } from "../src/circuit";
+import { RegisterMap, RegisterType } from "../src/register";
+import { formatInputs, _qubitInput } from "../src/formatters/inputFormatter";
+import { startY, registerHeight, classicalRegHeight } from "../src/constants";
+
+describe("Testing _qubitInput", () => {
+ test("classical register", () => {
+ expect(_qubitInput(20)).toMatchSnapshot();
+ expect(_qubitInput(50)).toMatchSnapshot();
+ expect(_qubitInput(0)).toMatchSnapshot();
+ });
+});
+
+describe("Testing formatInputs", () => {
+ test("1 quantum register", () => {
+ const inputs: Qubit[] = [{ id: 0 }];
+ const expectedRegs: RegisterMap = {
+ 0: { type: RegisterType.Qubit, y: startY },
+ };
+ const { qubitWires, registers } = formatInputs(inputs);
+ expect(qubitWires).toMatchSnapshot();
+ expect(registers).toEqual(expectedRegs);
+ expect(registers).toEqual(expectedRegs);
+ });
+ test("Multiple quantum registers", () => {
+ const inputs: Qubit[] = [{ id: 0 }, { id: 1 }, { id: 2 }];
+ const expectedRegs: RegisterMap = {
+ 0: { type: RegisterType.Qubit, y: startY },
+ 1: { type: RegisterType.Qubit, y: startY + registerHeight },
+ 2: { type: RegisterType.Qubit, y: startY + registerHeight * 2 },
+ };
+ const { qubitWires, registers } = formatInputs(inputs);
+ expect(qubitWires).toMatchSnapshot();
+ expect(registers).toEqual(expectedRegs);
+ expect(registers).toEqual(expectedRegs);
+ });
+ test("Quantum and classical registers", () => {
+ let inputs: Qubit[] = [{ id: 0, numChildren: 1 }, { id: 1 }, { id: 2 }];
+ let expectedRegs: RegisterMap = {
+ 0: {
+ type: RegisterType.Qubit,
+ y: startY,
+ children: [
+ { type: RegisterType.Classical, y: startY + classicalRegHeight },
+ ],
+ },
+ 1: { type: RegisterType.Qubit, y: startY + classicalRegHeight * 2 },
+ 2: {
+ type: RegisterType.Qubit,
+ y: startY + registerHeight + classicalRegHeight * 2,
+ },
+ };
+ let { qubitWires, registers } = formatInputs(inputs);
+ expect(qubitWires).toMatchSnapshot();
+ expect(registers).toEqual(expectedRegs);
+ expect(registers).toEqual(expectedRegs);
+
+ inputs = [{ id: 0 }, { id: 1, numChildren: 2 }, { id: 2, numChildren: 1 }];
+ expectedRegs = {
+ 0: { type: RegisterType.Qubit, y: startY },
+ 1: {
+ type: RegisterType.Qubit,
+ y: startY + registerHeight,
+ children: [
+ {
+ type: RegisterType.Classical,
+ y: startY + registerHeight + classicalRegHeight,
+ },
+ {
+ type: RegisterType.Classical,
+ y: startY + registerHeight + classicalRegHeight * 2,
+ },
+ ],
+ },
+ 2: {
+ type: RegisterType.Qubit,
+ y: startY + registerHeight + classicalRegHeight * 3,
+ children: [
+ {
+ type: RegisterType.Classical,
+ y: startY + registerHeight + classicalRegHeight * 4,
+ },
+ ],
+ },
+ };
+ ({ qubitWires, registers } = formatInputs(inputs));
+ expect(qubitWires).toMatchSnapshot();
+ expect(registers).toEqual(expectedRegs);
+ expect(registers).toEqual(expectedRegs);
+ });
+ test("Skip quantum registers", () => {
+ const inputs: Qubit[] = [{ id: 0 }, { id: 2 }];
+ const expectedRegs: RegisterMap = {
+ 0: { type: RegisterType.Qubit, y: startY },
+ 2: { type: RegisterType.Qubit, y: startY + registerHeight },
+ };
+ const { qubitWires, registers } = formatInputs(inputs);
+ expect(qubitWires).toMatchSnapshot();
+ expect(registers).toEqual(expectedRegs);
+ expect(registers).toEqual(expectedRegs);
+ });
+});
diff --git a/circuit_vis/__tests__/registerFormatter.test.ts b/circuit_vis/__tests__/registerFormatter.test.ts
new file mode 100644
index 0000000000..1fec69e603
--- /dev/null
+++ b/circuit_vis/__tests__/registerFormatter.test.ts
@@ -0,0 +1,205 @@
+import {
+ formatRegisters,
+ _classicalRegister,
+ _qubitRegister,
+} from "../src/formatters/registerFormatter";
+import { RegisterMap, RegisterType } from "../src/register";
+import { Metadata, GateType } from "../src/metadata";
+import {
+ startY,
+ registerHeight,
+ classicalRegHeight,
+ startX,
+ minGateWidth,
+} from "../src/constants";
+
+describe("Testing _classicalRegister", () => {
+ test("register with normal width", () => {
+ expect(_classicalRegister(10, 10, 100, 20)).toMatchSnapshot();
+ expect(_classicalRegister(10, 10, 100, 20)).toMatchSnapshot();
+ expect(_classicalRegister(10, 10, 100, 20)).toMatchSnapshot();
+ });
+ test("register with small width", () => {
+ expect(_classicalRegister(10, 10, 5, 20)).toMatchSnapshot();
+ expect(_classicalRegister(10, 10, 5, 20)).toMatchSnapshot();
+ expect(_classicalRegister(10, 10, 5, 20)).toMatchSnapshot();
+ });
+ test("register with large width", () => {
+ expect(_classicalRegister(10, 10, 500, 20)).toMatchSnapshot();
+ expect(_classicalRegister(10, 10, 500, 20)).toMatchSnapshot();
+ expect(_classicalRegister(10, 10, 500, 20)).toMatchSnapshot();
+ });
+ test("register with label offset", () => {
+ expect(_classicalRegister(10, 10, 100, 20)).toMatchSnapshot();
+ expect(_classicalRegister(10, 10, 100, 20)).toMatchSnapshot();
+ expect(_classicalRegister(10, 10, 100, 20)).toMatchSnapshot();
+ });
+});
+
+describe("Testing _qubitRegister", () => {
+ test("register with normal width", () => {
+ expect(_qubitRegister(0, 100, 20)).toMatchSnapshot();
+ expect(_qubitRegister(1, 100, 20)).toMatchSnapshot();
+ expect(_qubitRegister(2, 100, 20)).toMatchSnapshot();
+ });
+ test("register with small width", () => {
+ expect(_qubitRegister(0, 5, 20)).toMatchSnapshot();
+ expect(_qubitRegister(1, 5, 20)).toMatchSnapshot();
+ expect(_qubitRegister(2, 5, 20)).toMatchSnapshot();
+ });
+ test("register with large width", () => {
+ expect(_qubitRegister(0, 500, 20)).toMatchSnapshot();
+ expect(_qubitRegister(1, 500, 20)).toMatchSnapshot();
+ expect(_qubitRegister(2, 500, 20)).toMatchSnapshot();
+ });
+ test("register with label offset", () => {
+ expect(_qubitRegister(0, 100, 20, 0)).toMatchSnapshot();
+ expect(_qubitRegister(1, 100, 20, 5)).toMatchSnapshot();
+ expect(_qubitRegister(2, 100, 20, 50)).toMatchSnapshot();
+ });
+});
+
+describe("Testing formatRegisters", () => {
+ test("1 quantum register", () => {
+ const registers: RegisterMap = {
+ 0: { type: RegisterType.Qubit, y: startY },
+ };
+ // Normal width
+ expect(formatRegisters(registers, [], startX + 100)).toMatchSnapshot();
+ // Small width
+ expect(formatRegisters(registers, [], startX + 5)).toMatchSnapshot();
+ // Large width
+ expect(formatRegisters(registers, [], startX + 500)).toMatchSnapshot();
+ });
+ test("Multiple quantum registers", () => {
+ const registers: RegisterMap = {
+ 0: { type: RegisterType.Qubit, y: startY },
+ 1: { type: RegisterType.Qubit, y: startY + registerHeight },
+ 2: { type: RegisterType.Qubit, y: startY + registerHeight * 2 },
+ 3: { type: RegisterType.Qubit, y: startY + registerHeight * 3 },
+ };
+ // Normal width
+ expect(formatRegisters(registers, [], startX + 100)).toMatchSnapshot();
+ // Small width
+ expect(formatRegisters(registers, [], startX + 5)).toMatchSnapshot();
+ // Large width
+ expect(formatRegisters(registers, [], startX + 500)).toMatchSnapshot();
+ });
+ test("Skipped quantum registers", () => {
+ const registers: RegisterMap = {
+ 0: { type: RegisterType.Qubit, y: startY },
+ 2: { type: RegisterType.Qubit, y: startY + registerHeight * 2 },
+ 3: { type: RegisterType.Qubit, y: startY + registerHeight * 3 },
+ };
+ // Normal width
+ expect(formatRegisters(registers, [], startX + 100)).toMatchSnapshot();
+ // Small width
+ expect(formatRegisters(registers, [], startX + 5)).toMatchSnapshot();
+ // Large width
+ expect(formatRegisters(registers, [], startX + 500)).toMatchSnapshot();
+ });
+ test("Quantum and classical registers", () => {
+ let registers: RegisterMap = {
+ 0: {
+ type: RegisterType.Qubit,
+ y: startY,
+ children: [
+ { type: RegisterType.Classical, y: startY + classicalRegHeight },
+ ],
+ },
+ 1: { type: RegisterType.Qubit, y: startY + classicalRegHeight * 2 },
+ 2: {
+ type: RegisterType.Qubit,
+ y: startY + registerHeight + classicalRegHeight * 2,
+ },
+ };
+ let measureGates: Metadata[] = [
+ {
+ type: GateType.Measure,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + classicalRegHeight],
+ label: "",
+ width: minGateWidth,
+ },
+ ];
+ // Normal width
+ expect(
+ formatRegisters(registers, measureGates, startX + 100),
+ ).toMatchSnapshot();
+ // Small width
+ expect(
+ formatRegisters(registers, measureGates, startX + 5),
+ ).toMatchSnapshot();
+ // Large width
+ expect(
+ formatRegisters(registers, measureGates, startX + 500),
+ ).toMatchSnapshot();
+
+ registers = {
+ 0: { type: RegisterType.Qubit, y: startY },
+ 1: {
+ type: RegisterType.Qubit,
+ y: startY + registerHeight,
+ children: [
+ {
+ type: RegisterType.Classical,
+ y: startY + registerHeight + classicalRegHeight,
+ },
+ {
+ type: RegisterType.Classical,
+ y: startY + registerHeight + classicalRegHeight * 2,
+ },
+ ],
+ },
+ 2: {
+ type: RegisterType.Qubit,
+ y: startY + registerHeight + classicalRegHeight * 3,
+ children: [
+ {
+ type: RegisterType.Classical,
+ y: startY + registerHeight + classicalRegHeight * 4,
+ },
+ ],
+ },
+ };
+ measureGates = [
+ {
+ type: GateType.Measure,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + classicalRegHeight],
+ label: "",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Measure,
+ x: startX,
+ controlsY: [startY],
+ targetsY: [startY + classicalRegHeight * 2],
+ label: "",
+ width: minGateWidth,
+ },
+ {
+ type: GateType.Measure,
+ x: startX,
+ controlsY: [startY + classicalRegHeight * 3],
+ targetsY: [startY + classicalRegHeight * 4],
+ label: "",
+ width: minGateWidth,
+ },
+ ];
+ // Normal width
+ expect(
+ formatRegisters(registers, measureGates, startX + 100),
+ ).toMatchSnapshot();
+ // Small width
+ expect(
+ formatRegisters(registers, measureGates, startX + 5),
+ ).toMatchSnapshot();
+ // Large width
+ expect(
+ formatRegisters(registers, measureGates, startX + 500),
+ ).toMatchSnapshot();
+ });
+});
diff --git a/circuit_vis/jest.config.js b/circuit_vis/jest.config.js
new file mode 100644
index 0000000000..49bd84ae2d
--- /dev/null
+++ b/circuit_vis/jest.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ roots: [""],
+ transform: {
+ "^.+\\.tsx?$": "ts-jest",
+ },
+ testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
+ moduleFileExtensions: ["ts", "js"],
+};
diff --git a/circuit_vis/package.json b/circuit_vis/package.json
new file mode 100644
index 0000000000..392e933e94
--- /dev/null
+++ b/circuit_vis/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@microsoft/quantum-viz.js",
+ "version": "1.0.6",
+ "description": "quantum-viz.js is a configurable tool for visualizing quantum circuits.",
+ "main": "dist/qviz.min.js",
+ "scripts": {
+ "build": "tsc",
+ "build:prod": "tsc && webpack",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "prepublishOnly": "npm run lint && npm run test && npm run build:prod",
+ "start": "tsc -w"
+ },
+ "files": [
+ "README.md",
+ "LICENSE.txt",
+ "lib",
+ "dist"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/microsoft/quantum-viz.js"
+ },
+ "keywords": [
+ "quantum",
+ "circuits",
+ "visualization"
+ ],
+ "author": "Microsoft Quantum",
+ "license": "MIT",
+ "homepage": "https://github.com/microsoft/quantum-viz.js#readme"
+}
diff --git a/circuit_vis/src/circuit.ts b/circuit_vis/src/circuit.ts
new file mode 100644
index 0000000000..584069a785
--- /dev/null
+++ b/circuit_vis/src/circuit.ts
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { Register } from "./register";
+
+/**
+ * Circuit to be visualized.
+ */
+export interface Circuit {
+ /** Array of qubit resources. */
+ qubits: Qubit[];
+ operations: Operation[];
+}
+
+/**
+ * Represents a unique qubit resource bit.
+ */
+export interface Qubit {
+ /** Qubit ID. */
+ id: number;
+ /** Number of classical registers attached to quantum register. */
+ numChildren?: number;
+}
+
+/**
+ * Conditions on when to render the given operation.
+ */
+export enum ConditionalRender {
+ /** Always rendered. */
+ Always,
+ /** Render classically-controlled operation when measurement is a zero. */
+ OnZero,
+ /** Render classically-controlled operation when measurement is a one. */
+ OnOne,
+ /** Render operation as a group of its nested operations. */
+ AsGroup,
+}
+
+/**
+ * Custom data attributes (e.g. data-{attr}="{val}")
+ */
+export interface DataAttributes {
+ [attr: string]: string;
+}
+
+/**
+ * Represents an operation and the registers it acts on.
+ */
+export interface Operation {
+ /** Gate label. */
+ gate: string;
+ /** Formatted gate arguments to be displayed. */
+ displayArgs?: string;
+ /** Nested operations within this operation. */
+ children?: Operation[];
+ /** Whether gate is a measurement operation. */
+ isMeasurement: boolean;
+ /** Whether gate is a conditional operation. */
+ isConditional: boolean;
+ /** Whether gate is a controlled operation. */
+ isControlled: boolean;
+ /** Whether gate is an adjoint operation. */
+ isAdjoint: boolean;
+ /** Control registers the gate acts on. */
+ controls?: Register[];
+ /** Target registers the gate acts on. */
+ targets: Register[];
+ /** Specify conditions on when to render operation. */
+ conditionalRender?: ConditionalRender;
+ /** Custom data attributes to attach to gate element. */
+ dataAttributes?: DataAttributes;
+}
diff --git a/circuit_vis/src/constants.ts b/circuit_vis/src/constants.ts
new file mode 100644
index 0000000000..fe2763319c
--- /dev/null
+++ b/circuit_vis/src/constants.ts
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+// SVG Namespace
+export const svgNS = "http://www.w3.org/2000/svg";
+
+// Display attributes
+/** Left padding of SVG. */
+export const leftPadding = 20;
+/** x coordinate for first operation on each register. */
+export const startX = 80;
+/** y coordinate of first register. */
+export const startY = 40;
+/** Minimum width of each gate. */
+export const minGateWidth = 40;
+/** Height of each gate. */
+export const gateHeight = 40;
+/** Padding on each side of gate. */
+export const gatePadding = 10;
+/** Padding on each side of gate label. */
+export const labelPadding = 10;
+/** Height between each qubit register. */
+export const registerHeight: number = gateHeight + gatePadding * 2;
+/** Height between classical registers. */
+export const classicalRegHeight: number = gateHeight;
+/** Group box inner padding. */
+export const groupBoxPadding = gatePadding;
+/** Padding between nested groups. */
+export const nestedGroupPadding = 2;
+/** Additional offset for control button. */
+export const controlBtnOffset = 40;
+/** Control button radius. */
+export const controlBtnRadius = 15;
+/** Default font size for gate labels. */
+export const labelFontSize = 14;
+/** Default font size for gate arguments. */
+export const argsFontSize = 12;
+/** Starting x coord for each register wire. */
+export const regLineStart = 40;
diff --git a/circuit_vis/src/formatters/formatUtils.ts b/circuit_vis/src/formatters/formatUtils.ts
new file mode 100644
index 0000000000..c3bccd1beb
--- /dev/null
+++ b/circuit_vis/src/formatters/formatUtils.ts
@@ -0,0 +1,220 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { labelFontSize, svgNS } from "../constants";
+
+// Helper functions for basic SVG components
+
+/**
+ * Create an SVG element.
+ *
+ * @param type The type of element to be created.
+ * @param attributes The attributes that define the element.
+ *
+ * @returns SVG element.
+ */
+export const createSvgElement = (
+ type: string,
+ attributes: { [attr: string]: string } = {},
+): SVGElement => {
+ const el: SVGElement = document.createElementNS(svgNS, type);
+ Object.entries(attributes).forEach(([attrName, attrVal]) =>
+ el.setAttribute(attrName, attrVal),
+ );
+ return el;
+};
+
+/**
+ * Given an array of SVG elements, group them as an SVG group using the `` tag.
+ *
+ * @param svgElems Array of SVG elements.
+ * @param attributes Key-value pairs of attributes and they values.
+ *
+ * @returns SVG element for grouped elements.
+ */
+export const group = (
+ svgElems: SVGElement[],
+ attributes: { [attr: string]: string } = {},
+): SVGElement => {
+ const el: SVGElement = createSvgElement("g", attributes);
+ svgElems.forEach((child: SVGElement) => el.appendChild(child));
+ return el;
+};
+
+/**
+ * Generate an SVG line.
+ *
+ * @param x1 x coord of starting point of line.
+ * @param y1 y coord of starting point of line.
+ * @param x2 x coord of ending point of line.
+ * @param y2 y coord fo ending point of line.
+ * @param className Class name of element.
+ *
+ * @returns SVG element for line.
+ */
+export const line = (
+ x1: number,
+ y1: number,
+ x2: number,
+ y2: number,
+ className?: string,
+): SVGElement => {
+ const attrs: { [attr: string]: string } = {
+ x1: x1.toString(),
+ x2: x2.toString(),
+ y1: y1.toString(),
+ y2: y2.toString(),
+ };
+ if (className != null) attrs["class"] = className;
+ return createSvgElement("line", attrs);
+};
+
+/**
+ * Generate an SVG circle.
+ *
+ * @param x x coord of circle.
+ * @param y y coord of circle.
+ * @param radius Radius of circle.
+ *
+ * @returns SVG element for circle.
+ */
+export const circle = (
+ x: number,
+ y: number,
+ radius: number,
+ className?: string,
+): SVGElement => {
+ const attrs: { [attr: string]: string } = {
+ cx: x.toString(),
+ cy: y.toString(),
+ r: radius.toString(),
+ };
+ if (className != null) attrs["class"] = className;
+ return createSvgElement("circle", attrs);
+};
+
+/**
+ * Generate the SVG representation of a control dot used for controlled operations.
+ *
+ * @param x x coord of circle.
+ * @param y y coord of circle.
+ * @param radius Radius of circle.
+ *
+ * @returns SVG element for control dot.
+ */
+export const controlDot = (x: number, y: number, radius = 5): SVGElement =>
+ circle(x, y, radius, "control-dot");
+
+/**
+ * Generate the SVG representation of a unitary box that represents an arbitrary unitary operation.
+ *
+ * @param x x coord of box.
+ * @param y y coord of box.
+ * @param width Width of box.
+ * @param height Height of box.
+ * @param className Class name of element.
+ *
+ * @returns SVG element for unitary box.
+ */
+export const box = (
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ className = "gate-unitary",
+): SVGElement =>
+ createSvgElement("rect", {
+ class: className,
+ x: x.toString(),
+ y: y.toString(),
+ width: width.toString(),
+ height: height.toString(),
+ });
+
+/**
+ * Generate the SVG text element from a given text string.
+ *
+ * @param text String to render as SVG text.
+ * @param x Middle x coord of text.
+ * @param y Middle y coord of text.
+ * @param fs Font size of text.
+ *
+ * @returns SVG element for text.
+ */
+export const text = (
+ text: string,
+ x: number,
+ y: number,
+ fs: number = labelFontSize,
+): SVGElement => {
+ const el: SVGElement = createSvgElement("text", {
+ "font-size": fs.toString(),
+ x: x.toString(),
+ y: y.toString(),
+ });
+ el.textContent = text;
+ return el;
+};
+
+/**
+ * Generate the SVG representation of the arc used in the measurement box.
+ *
+ * @param x x coord of arc.
+ * @param y y coord of arc.
+ * @param rx x radius of arc.
+ * @param ry y radius of arc.
+ *
+ * @returns SVG element for arc.
+ */
+export const arc = (x: number, y: number, rx: number, ry: number): SVGElement =>
+ createSvgElement("path", {
+ class: "arc-measure",
+ d: `M ${x + 2 * rx} ${y} A ${rx} ${ry} 0 0 0 ${x} ${y}`,
+ });
+
+/**
+ * Generate a dashed SVG line.
+ *
+ * @param x1 x coord of starting point of line.
+ * @param y1 y coord of starting point of line.
+ * @param x2 x coord of ending point of line.
+ * @param y2 y coord fo ending point of line.
+ * @param className Class name of element.
+ *
+ * @returns SVG element for dashed line.
+ */
+export const dashedLine = (
+ x1: number,
+ y1: number,
+ x2: number,
+ y2: number,
+ className?: string,
+): SVGElement => {
+ const el: SVGElement = line(x1, y1, x2, y2, className);
+ el.setAttribute("stroke-dasharray", "8, 8");
+ return el;
+};
+
+/**
+ * Generate the SVG representation of the dashed box used for enclosing groups of operations controlled on a classical register.
+ *
+ * @param x x coord of box.
+ * @param y y coord of box.
+ * @param width Width of box.
+ * @param height Height of box.
+ * @param className Class name of element.
+ *
+ * @returns SVG element for dashed box.
+ */
+export const dashedBox = (
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ className?: string,
+): SVGElement => {
+ const el: SVGElement = box(x, y, width, height, className);
+ el.setAttribute("fill-opacity", "0");
+ el.setAttribute("stroke-dasharray", "8, 8");
+ return el;
+};
diff --git a/circuit_vis/src/formatters/gateFormatter.ts b/circuit_vis/src/formatters/gateFormatter.ts
new file mode 100644
index 0000000000..f4be3626df
--- /dev/null
+++ b/circuit_vis/src/formatters/gateFormatter.ts
@@ -0,0 +1,557 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { Metadata, GateType } from "../metadata";
+import {
+ minGateWidth,
+ gateHeight,
+ labelFontSize,
+ argsFontSize,
+ controlBtnRadius,
+ controlBtnOffset,
+ groupBoxPadding,
+ classicalRegHeight,
+ nestedGroupPadding,
+} from "../constants";
+import {
+ createSvgElement,
+ group,
+ line,
+ circle,
+ controlDot,
+ box,
+ text,
+ arc,
+ dashedLine,
+ dashedBox,
+} from "./formatUtils";
+
+/**
+ * Given an array of operations (in metadata format), return the SVG representation.
+ *
+ * @param opsMetadata Array of Metadata representation of operations.
+ * @param nestedDepth Depth of nested operations (used in classically controlled and grouped operations).
+ *
+ * @returns SVG representation of operations.
+ */
+const formatGates = (opsMetadata: Metadata[], nestedDepth = 0): SVGElement => {
+ const formattedGates: SVGElement[] = opsMetadata.map((metadata) =>
+ _formatGate(metadata, nestedDepth),
+ );
+ return group(formattedGates);
+};
+
+/**
+ * Takes in an operation's metadata and formats it into SVG.
+ *
+ * @param metadata Metadata object representation of gate.
+ * @param nestedDepth Depth of nested operations (used in classically controlled and grouped operations).
+ *
+ * @returns SVG representation of gate.
+ */
+const _formatGate = (metadata: Metadata, nestedDepth = 0): SVGElement => {
+ const { type, x, controlsY, targetsY, label, displayArgs, width } = metadata;
+ switch (type) {
+ case GateType.Measure:
+ return _createGate([_measure(x, controlsY[0])], metadata, nestedDepth);
+ case GateType.Unitary:
+ return _createGate(
+ [_unitary(label, x, targetsY as number[][], width, displayArgs)],
+ metadata,
+ nestedDepth,
+ );
+ case GateType.X:
+ return _createGate([_x(metadata, nestedDepth)], metadata, nestedDepth);
+ case GateType.Swap:
+ return controlsY.length > 0
+ ? _controlledGate(metadata, nestedDepth)
+ : _createGate([_swap(metadata, nestedDepth)], metadata, nestedDepth);
+ case GateType.Cnot:
+ case GateType.ControlledUnitary:
+ return _controlledGate(metadata, nestedDepth);
+ case GateType.Group:
+ return _groupedOperations(metadata, nestedDepth);
+ case GateType.ClassicalControlled:
+ return _classicalControlled(metadata);
+ default:
+ throw new Error(`ERROR: unknown gate (${label}) of type ${type}.`);
+ }
+};
+
+/**
+ * Groups SVG elements into a gate SVG group.
+ *
+ * @param svgElems Array of SVG elements.
+ * @param dataAttributes Custom data attributes to be attached to SVG group.
+ *
+ * @returns SVG representation of a gate.
+ */
+const _createGate = (
+ svgElems: SVGElement[],
+ metadata: Metadata,
+ nestedDepth: number,
+): SVGElement => {
+ const { dataAttributes } = metadata || {};
+ const attributes: { [attr: string]: string } = { class: "gate" };
+ Object.entries(dataAttributes || {}).forEach(
+ ([attr, val]) => (attributes[`data-${attr}`] = val),
+ );
+
+ const zoomBtn: SVGElement | null = _zoomButton(metadata, nestedDepth);
+ if (zoomBtn != null) svgElems = svgElems.concat([zoomBtn]);
+ return group(svgElems, attributes);
+};
+
+/**
+ * Returns the expand/collapse button for an operation if it can be zoomed-in or zoomed-out,
+ * respectively. If neither are allowed, return `null`.
+ *
+ * @param metadata Operation metadata.
+ * @param nestedDepth Depth of nested operation.
+ *
+ * @returns SVG element for expand/collapse button if needed, or null otherwise.
+ */
+const _zoomButton = (
+ metadata: Metadata,
+ nestedDepth: number,
+): SVGElement | null => {
+ if (metadata == undefined) return null;
+
+ const [x1, y1] = _gatePosition(metadata, nestedDepth);
+ let { dataAttributes } = metadata;
+ dataAttributes = dataAttributes || {};
+
+ const expanded = "expanded" in dataAttributes;
+
+ const x = x1 + 2;
+ const y = y1 + 2;
+ const circleBorder: SVGElement = circle(x, y, 10);
+
+ if (expanded) {
+ // Create collapse button if expanded
+ const minusSign: SVGElement = createSvgElement("path", {
+ d: `M${x - 7},${y} h14`,
+ });
+ const elements: SVGElement[] = [circleBorder, minusSign];
+ return group(elements, { class: "gate-control gate-collapse" });
+ } else if (dataAttributes["zoom-in"] == "true") {
+ // Create expand button if operation can be zoomed in
+ const plusSign: SVGElement = createSvgElement("path", {
+ d: `M${x},${y - 7} v14 M${x - 7},${y} h14`,
+ });
+ const elements: SVGElement[] = [circleBorder, plusSign];
+ return group(elements, { class: "gate-control gate-expand" });
+ }
+
+ return null;
+};
+
+/**
+ * Calculate position of gate.
+ *
+ * @param metadata Operation metadata.
+ * @param nestedDepth Depth of nested operations.
+ *
+ * @returns Coordinates of gate: [x1, y1, x2, y2].
+ */
+const _gatePosition = (
+ metadata: Metadata,
+ nestedDepth: number,
+): [number, number, number, number] => {
+ const { x, width, type, targetsY } = metadata;
+
+ const ys = targetsY?.flatMap((y) => y as number[]) || [];
+ const maxY = Math.max(...ys);
+ const minY = Math.min(...ys);
+
+ let x1: number, y1: number, x2: number, y2: number;
+
+ switch (type) {
+ case GateType.Group: {
+ const padding = groupBoxPadding - nestedDepth * nestedGroupPadding;
+
+ x1 = x - 2 * padding;
+ y1 = minY - gateHeight / 2 - padding;
+ x2 = width + 2 * padding;
+ y2 = maxY + +gateHeight / 2 + padding - (minY - gateHeight / 2 - padding);
+
+ return [x1, y1, x2, y2];
+ }
+
+ default:
+ x1 = x - width / 2;
+ y1 = minY - gateHeight / 2;
+ x2 = x + width;
+ y2 = maxY + gateHeight / 2;
+ }
+
+ return [x1, y1, x2, y2];
+};
+
+/**
+ * Creates a measurement gate at position (x, y).
+ *
+ * @param x x coord of measurement gate.
+ * @param y y coord of measurement gate.
+ *
+ * @returns SVG representation of measurement gate.
+ */
+const _measure = (x: number, y: number): SVGElement => {
+ x -= minGateWidth / 2;
+ const width: number = minGateWidth,
+ height = gateHeight;
+ // Draw measurement box
+ const mBox: SVGElement = box(
+ x,
+ y - height / 2,
+ width,
+ height,
+ "gate-measure",
+ );
+ const mArc: SVGElement = arc(x + 5, y + 2, width / 2 - 5, height / 2 - 8);
+ const meter: SVGElement = line(
+ x + width / 2,
+ y + 8,
+ x + width - 8,
+ y - height / 2 + 8,
+ );
+ return group([mBox, mArc, meter]);
+};
+
+/**
+ * Creates the SVG for a unitary gate on an arbitrary number of qubits.
+ *
+ * @param label Gate label.
+ * @param x x coord of gate.
+ * @param y Array of y coords of registers acted upon by gate.
+ * @param width Width of gate.
+ * @param displayArgs Arguments passed in to gate.
+ * @param renderDashedLine If true, draw dashed lines between non-adjacent unitaries.
+ *
+ * @returns SVG representation of unitary gate.
+ */
+const _unitary = (
+ label: string,
+ x: number,
+ y: number[][],
+ width: number,
+ displayArgs?: string,
+ renderDashedLine = true,
+): SVGElement => {
+ if (y.length === 0)
+ throw new Error(
+ `Failed to render unitary gate (${label}): has no y-values`,
+ );
+
+ // Render each group as a separate unitary boxes
+ const unitaryBoxes: SVGElement[] = y.map((group: number[]) => {
+ const maxY: number = group[group.length - 1],
+ minY: number = group[0];
+ const height: number = maxY - minY + gateHeight;
+ return _unitaryBox(label, x, minY, width, height, displayArgs);
+ });
+
+ // Draw dashed line between disconnected unitaries
+ if (renderDashedLine && unitaryBoxes.length > 1) {
+ const lastBox: number[] = y[y.length - 1];
+ const firstBox: number[] = y[0];
+ const maxY: number = lastBox[lastBox.length - 1],
+ minY: number = firstBox[0];
+ const vertLine: SVGElement = dashedLine(x, minY, x, maxY);
+ return group([vertLine, ...unitaryBoxes]);
+ }
+
+ return group(unitaryBoxes);
+};
+
+/**
+ * Generates SVG representation of the boxed unitary gate symbol.
+ *
+ * @param label Label for unitary operation.
+ * @param x x coord of gate.
+ * @param y y coord of gate.
+ * @param width Width of gate.
+ * @param height Height of gate.
+ * @param displayArgs Arguments passed in to gate.
+ *
+ * @returns SVG representation of unitary box.
+ */
+const _unitaryBox = (
+ label: string,
+ x: number,
+ y: number,
+ width: number,
+ height: number = gateHeight,
+ displayArgs?: string,
+): SVGElement => {
+ y -= gateHeight / 2;
+ const uBox: SVGElement = box(x - width / 2, y, width, height);
+ const labelY = y + height / 2 - (displayArgs == null ? 0 : 7);
+ const labelText: SVGElement = text(label, x, labelY);
+ const elems = [uBox, labelText];
+ if (displayArgs != null) {
+ const argStrY = y + height / 2 + 8;
+ const argText: SVGElement = text(displayArgs, x, argStrY, argsFontSize);
+ elems.push(argText);
+ }
+ return group(elems);
+};
+
+/**
+ * Creates the SVG for a SWAP gate on y coords given by targetsY.
+ *
+ * @param x Centre x coord of SWAP gate.
+ * @param targetsY y coords of target registers.
+ *
+ * @returns SVG representation of SWAP gate.
+ */
+const _swap = (metadata: Metadata, nestedDepth: number): SVGElement => {
+ const { x, targetsY } = metadata;
+
+ // Get SVGs of crosses
+ const [x1, y1, x2, y2] = _gatePosition(metadata, nestedDepth);
+ const ys = targetsY?.flatMap((y) => y as number[]) || [];
+
+ const bg: SVGElement = box(x1, y1, x2, y2, "gate-swap");
+ const crosses: SVGElement[] = ys.map((y) => _cross(x, y));
+ const vertLine: SVGElement = line(x, ys[0], x, ys[1]);
+ return group([bg, ...crosses, vertLine]);
+};
+/**
+ * Creates the SVG for an X gate
+ *
+ * @returns SVG representation of X gate.
+ */
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const _x = (metadata: Metadata, _: number): SVGElement => {
+ const { x, targetsY } = metadata;
+ const ys = targetsY.flatMap((y) => y as number[]);
+ return _oplus(x, ys[0]);
+};
+/**
+ * Generates cross for display in SWAP gate.
+ *
+ * @param x x coord of gate.
+ * @param y y coord of gate.
+ *
+ * @returns SVG representation for cross.
+ */
+const _cross = (x: number, y: number): SVGElement => {
+ const radius = 8;
+ const line1: SVGElement = line(
+ x - radius,
+ y - radius,
+ x + radius,
+ y + radius,
+ );
+ const line2: SVGElement = line(
+ x - radius,
+ y + radius,
+ x + radius,
+ y - radius,
+ );
+ return group([line1, line2]);
+};
+
+/**
+ * Produces the SVG representation of a controlled gate on multiple qubits.
+ *
+ * @param metadata Metadata of controlled gate.
+ *
+ * @returns SVG representation of controlled gate.
+ */
+const _controlledGate = (
+ metadata: Metadata,
+ nestedDepth: number,
+): SVGElement => {
+ const targetGateSvgs: SVGElement[] = [];
+ const { type, x, controlsY, label, displayArgs, width } = metadata;
+ let { targetsY } = metadata;
+
+ // Get SVG for target gates
+ switch (type) {
+ case GateType.Cnot:
+ (targetsY as number[]).forEach((y) => targetGateSvgs.push(_oplus(x, y)));
+ break;
+ case GateType.Swap:
+ (targetsY as number[]).forEach((y) => targetGateSvgs.push(_cross(x, y)));
+ break;
+ case GateType.ControlledUnitary:
+ {
+ const groupedTargetsY: number[][] = targetsY as number[][];
+ targetGateSvgs.push(
+ _unitary(label, x, groupedTargetsY, width, displayArgs, false),
+ );
+ targetsY = targetsY.flat();
+ }
+ break;
+ default:
+ throw new Error(`ERROR: Unrecognized gate: ${label} of type ${type}`);
+ }
+ // Get SVGs for control dots
+ const controlledDotsSvg: SVGElement[] = controlsY.map((y) =>
+ controlDot(x, y),
+ );
+ // Create control lines
+ const maxY: number = Math.max(...controlsY, ...(targetsY as number[]));
+ const minY: number = Math.min(...controlsY, ...(targetsY as number[]));
+ const vertLine: SVGElement = line(x, minY, x, maxY);
+ const svg: SVGElement = _createGate(
+ [vertLine, ...controlledDotsSvg, ...targetGateSvgs],
+ metadata,
+ nestedDepth,
+ );
+ return svg;
+};
+
+/**
+ * Generates $\oplus$ symbol for display in CNOT gate.
+ *
+ * @param x x coordinate of gate.
+ * @param y y coordinate of gate.
+ * @param r radius of circle.
+ *
+ * @returns SVG representation of $\oplus$ symbol.
+ */
+const _oplus = (x: number, y: number, r = 15): SVGElement => {
+ const circleBorder: SVGElement = circle(x, y, r);
+ const vertLine: SVGElement = line(x, y - r, x, y + r);
+ const horLine: SVGElement = line(x - r, y, x + r, y);
+ return group([circleBorder, vertLine, horLine], { class: "oplus" });
+};
+
+/**
+ * Generates the SVG for a group of nested operations.
+ *
+ * @param metadata Metadata representation of gate.
+ * @param nestedDepth Depth of nested operations (used in classically controlled and grouped operations).
+ *
+ * @returns SVG representation of gate.
+ */
+const _groupedOperations = (
+ metadata: Metadata,
+ nestedDepth: number,
+): SVGElement => {
+ const { children } = metadata;
+ const [x1, y1, x2, y2] = _gatePosition(metadata, nestedDepth);
+
+ // Draw dashed box around children gates
+ const box: SVGElement = dashedBox(x1, y1, x2, y2);
+ const elems: SVGElement[] = [box];
+ if (children != null)
+ elems.push(formatGates(children as Metadata[], nestedDepth + 1));
+ return _createGate(elems, metadata, nestedDepth);
+};
+
+/**
+ * Generates the SVG for a classically controlled group of operations.
+ *
+ * @param metadata Metadata representation of gate.
+ * @param padding Padding within dashed box.
+ *
+ * @returns SVG representation of gate.
+ */
+const _classicalControlled = (
+ metadata: Metadata,
+ padding: number = groupBoxPadding,
+): SVGElement => {
+ const { controlsY, dataAttributes } = metadata;
+ const targetsY: number[] = metadata.targetsY as number[];
+ const children: Metadata[][] = metadata.children as Metadata[][];
+ let { x, width } = metadata;
+
+ const controlY = controlsY[0];
+
+ const elems: SVGElement[] = [];
+
+ if (children != null) {
+ if (children.length !== 2)
+ throw new Error(
+ `Invalid number of children found for classically-controlled gate: ${children.length}`,
+ );
+
+ // Get SVG for gates controlled on 0
+ const childrenZero: SVGElement = formatGates(children[0]);
+ childrenZero.setAttribute("class", "gates-zero");
+ elems.push(childrenZero);
+
+ // Get SVG for gates controlled on 1
+ const childrenOne: SVGElement = formatGates(children[1]);
+ childrenOne.setAttribute("class", "gates-one");
+ elems.push(childrenOne);
+ }
+
+ // Draw control button and attached dashed line to dashed box
+ const controlCircleX: number = x + controlBtnRadius;
+ const controlCircle: SVGElement = _controlCircle(controlCircleX, controlY);
+ const lineY1: number = controlY + controlBtnRadius,
+ lineY2: number = controlY + classicalRegHeight / 2;
+ const vertLine: SVGElement = dashedLine(
+ controlCircleX,
+ lineY1,
+ controlCircleX,
+ lineY2,
+ "classical-line",
+ );
+ x += controlBtnOffset;
+ const horLine: SVGElement = dashedLine(
+ controlCircleX,
+ lineY2,
+ x,
+ lineY2,
+ "classical-line",
+ );
+
+ width = width - controlBtnOffset + (padding - groupBoxPadding) * 2;
+ x += groupBoxPadding - padding;
+ const y: number = targetsY[0] - gateHeight / 2 - padding;
+ const height: number = targetsY[1] - targetsY[0] + gateHeight + padding * 2;
+
+ // Draw dashed box around children gates
+ const box: SVGElement = dashedBox(x, y, width, height, "classical-container");
+
+ elems.push(...[horLine, vertLine, controlCircle, box]);
+
+ // Display controlled operation in initial "unknown" state
+ const attributes: { [attr: string]: string } = {
+ class: `classically-controlled-group classically-controlled-unknown`,
+ };
+ if (dataAttributes != null)
+ Object.entries(dataAttributes).forEach(
+ ([attr, val]) => (attributes[`data-${attr}`] = val),
+ );
+
+ return group(elems, attributes);
+};
+
+/**
+ * Generates the SVG representation of the control circle on a classical register with interactivity support
+ * for toggling between bit values (unknown, 1, and 0).
+ *
+ * @param x x coord.
+ * @param y y coord.
+ * @param r Radius of circle.
+ *
+ * @returns SVG representation of control circle.
+ */
+const _controlCircle = (
+ x: number,
+ y: number,
+ r: number = controlBtnRadius,
+): SVGElement =>
+ group([circle(x, y, r), text("?", x, y, labelFontSize)], {
+ class: "classically-controlled-btn",
+ });
+
+export {
+ formatGates,
+ _formatGate,
+ _createGate,
+ _zoomButton,
+ _measure,
+ _unitary,
+ _swap,
+ _controlledGate,
+ _groupedOperations,
+ _classicalControlled,
+};
diff --git a/circuit_vis/src/formatters/inputFormatter.ts b/circuit_vis/src/formatters/inputFormatter.ts
new file mode 100644
index 0000000000..7464de4c33
--- /dev/null
+++ b/circuit_vis/src/formatters/inputFormatter.ts
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { Qubit } from "../circuit";
+import { RegisterType, RegisterMap, RegisterMetadata } from "../register";
+import {
+ leftPadding,
+ startY,
+ registerHeight,
+ classicalRegHeight,
+} from "../constants";
+import { group, text } from "./formatUtils";
+
+/**
+ * `formatInputs` takes in an array of Qubits and outputs the SVG string of formatted
+ * qubit wires and a mapping from register IDs to register metadata (for rendering).
+ *
+ * @param qubits List of declared qubits.
+ *
+ * @returns returns the SVG string of formatted qubit wires, a mapping from registers
+ * to y coord and total SVG height.
+ */
+const formatInputs = (
+ qubits: Qubit[],
+): { qubitWires: SVGElement; registers: RegisterMap; svgHeight: number } => {
+ const qubitWires: SVGElement[] = [];
+ const registers: RegisterMap = {};
+
+ let currY: number = startY;
+ qubits.forEach(({ id, numChildren }) => {
+ // Add qubit wire to list of qubit wires
+ qubitWires.push(_qubitInput(currY));
+
+ // Create qubit register
+ registers[id] = { type: RegisterType.Qubit, y: currY };
+
+ // If there are no attached classical registers, increment y by fixed register height
+ if (numChildren == null || numChildren === 0) {
+ currY += registerHeight;
+ return;
+ }
+
+ // Increment current height by classical register height for attached classical registers
+ currY += classicalRegHeight;
+
+ // Add classical wires
+ registers[id].children = Array.from(Array(numChildren), () => {
+ const clsReg: RegisterMetadata = {
+ type: RegisterType.Classical,
+ y: currY,
+ };
+ currY += classicalRegHeight;
+ return clsReg;
+ });
+ });
+
+ return {
+ qubitWires: group(qubitWires),
+ registers,
+ svgHeight: currY,
+ };
+};
+
+/**
+ * Generate the SVG text component for the input qubit register.
+ *
+ * @param y y coord of input wire to render in SVG.
+ *
+ * @returns SVG text component for the input register.
+ */
+const _qubitInput = (y: number): SVGElement => {
+ const el: SVGElement = text("|0⟩", leftPadding, y, 16);
+ el.setAttribute("text-anchor", "start");
+ el.setAttribute("dominant-baseline", "middle");
+ return el;
+};
+
+export { formatInputs, _qubitInput };
diff --git a/circuit_vis/src/formatters/registerFormatter.ts b/circuit_vis/src/formatters/registerFormatter.ts
new file mode 100644
index 0000000000..aa858e9278
--- /dev/null
+++ b/circuit_vis/src/formatters/registerFormatter.ts
@@ -0,0 +1,118 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { RegisterMap } from "../register";
+import { regLineStart } from "../constants";
+import { Metadata, GateType } from "../metadata";
+import { group, line, text } from "./formatUtils";
+
+/**
+ * Generate the SVG representation of the qubit register wires in `registers` and the classical wires
+ * stemming from each measurement gate.
+ *
+ * @param registers Map from register IDs to register metadata.
+ * @param measureGates Array of measurement gates metadata.
+ * @param endX End x coord.
+ *
+ * @returns SVG representation of register wires.
+ */
+const formatRegisters = (
+ registers: RegisterMap,
+ measureGates: Metadata[],
+ endX: number,
+): SVGElement => {
+ const formattedRegs: SVGElement[] = [];
+ // Render qubit wires
+ for (const qId in registers) {
+ formattedRegs.push(_qubitRegister(Number(qId), endX, registers[qId].y));
+ }
+ // Render classical wires
+ measureGates.forEach(({ type, x, targetsY, controlsY }) => {
+ if (type !== GateType.Measure) return;
+ const gateY: number = controlsY[0];
+ (targetsY as number[]).forEach((y) => {
+ formattedRegs.push(_classicalRegister(x, gateY, endX, y));
+ });
+ });
+ return group(formattedRegs);
+};
+
+/**
+ * Generates the SVG representation of a classical register.
+ *
+ * @param startX Start x coord.
+ * @param gateY y coord of measurement gate.
+ * @param endX End x coord.
+ * @param wireY y coord of wire.
+ *
+ * @returns SVG representation of the given classical register.
+ */
+const _classicalRegister = (
+ startX: number,
+ gateY: number,
+ endX: number,
+ wireY: number,
+): SVGElement => {
+ const wirePadding = 1;
+ // Draw vertical lines
+ const vLine1: SVGElement = line(
+ startX + wirePadding,
+ gateY,
+ startX + wirePadding,
+ wireY - wirePadding,
+ "register-classical",
+ );
+ const vLine2: SVGElement = line(
+ startX - wirePadding,
+ gateY,
+ startX - wirePadding,
+ wireY + wirePadding,
+ "register-classical",
+ );
+
+ // Draw horizontal lines
+ const hLine1: SVGElement = line(
+ startX + wirePadding,
+ wireY - wirePadding,
+ endX,
+ wireY - wirePadding,
+ "register-classical",
+ );
+ const hLine2: SVGElement = line(
+ startX - wirePadding,
+ wireY + wirePadding,
+ endX,
+ wireY + wirePadding,
+ "register-classical",
+ );
+
+ return group([vLine1, vLine2, hLine1, hLine2]);
+};
+
+/**
+ * Generates the SVG representation of a qubit register.
+ *
+ * @param qId Qubit register index.
+ * @param endX End x coord.
+ * @param y y coord of wire.
+ * @param labelOffset y offset for wire label.
+ *
+ * @returns SVG representation of the given qubit register.
+ */
+const _qubitRegister = (
+ qId: number,
+ endX: number,
+ y: number,
+ labelOffset = 16,
+): SVGElement => {
+ const wire: SVGElement = line(regLineStart, y, endX, y);
+
+ const label: SVGElement = text(`q${qId}`, regLineStart, y - labelOffset);
+ label.setAttribute("dominant-baseline", "hanging");
+ label.setAttribute("text-anchor", "start");
+ label.setAttribute("font-size", "75%");
+
+ return group([wire, label]);
+};
+
+export { formatRegisters, _classicalRegister, _qubitRegister };
diff --git a/circuit_vis/src/index.ts b/circuit_vis/src/index.ts
new file mode 100644
index 0000000000..5e37c199f8
--- /dev/null
+++ b/circuit_vis/src/index.ts
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { Sqore } from "./sqore";
+import { Circuit } from "./circuit";
+import { StyleConfig } from "./styles";
+
+/**
+ * Render `circuit` into `container` at the specified layer depth.
+ *
+ * @param circuit Circuit to be visualized.
+ * @param container HTML element for rendering visualization into.
+ * @param style Custom visualization style.
+ * @param renderDepth Initial layer depth at which to render gates.
+ */
+export const draw = (
+ circuit: Circuit,
+ container: HTMLElement,
+ style: StyleConfig | string = {},
+ renderDepth = 0,
+): void => {
+ const sqore = new Sqore(circuit, style);
+ sqore.draw(container, renderDepth);
+};
+
+export { STYLES } from "./styles";
+
+// Export types
+export type { StyleConfig } from "./styles";
+export type { Circuit, Qubit, Operation } from "./circuit";
diff --git a/circuit_vis/src/metadata.ts b/circuit_vis/src/metadata.ts
new file mode 100644
index 0000000000..4c5c265289
--- /dev/null
+++ b/circuit_vis/src/metadata.ts
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { DataAttributes } from "./circuit";
+
+/**
+ * Enum for the various gate operations handled.
+ */
+export enum GateType {
+ /** Measurement gate. */
+ Measure,
+ /** CNOT gate. */
+ Cnot,
+ /** SWAP gate. */
+ Swap,
+ /** X gate. */
+ X,
+ /** Single/multi qubit unitary gate. */
+ Unitary,
+ /** Single/multi controlled unitary gate. */
+ ControlledUnitary,
+ /** Nested group of classically-controlled gates. */
+ ClassicalControlled,
+ /** Group of nested gates */
+ Group,
+ /** Invalid gate. */
+ Invalid,
+}
+
+/**
+ * Metadata used to store information pertaining to a given
+ * operation for rendering its corresponding SVG.
+ */
+export interface Metadata {
+ /** Gate type. */
+ type: GateType;
+ /** Centre x coord for gate position. */
+ x: number;
+ /** Array of y coords of control registers. */
+ controlsY: number[];
+ /** Array of y coords of target registers.
+ * For `GateType.Unitary` or `GateType.ControlledUnitary`, this is an array of groups of
+ * y coords, where each group represents a unitary box to be rendered separately.
+ */
+ targetsY: (number | number[])[];
+ /** Gate label. */
+ label: string;
+ /** Gate arguments as string. */
+ displayArgs?: string;
+ /** Gate width. */
+ width: number;
+ /** Children operations as part of group. */
+ children?: (Metadata | Metadata[])[];
+ /** Custom data attributes to attach to gate element. */
+ dataAttributes?: DataAttributes;
+}
diff --git a/circuit_vis/src/process.ts b/circuit_vis/src/process.ts
new file mode 100644
index 0000000000..f18d7be1cf
--- /dev/null
+++ b/circuit_vis/src/process.ts
@@ -0,0 +1,536 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import {
+ minGateWidth,
+ startX,
+ gatePadding,
+ controlBtnOffset,
+ groupBoxPadding,
+} from "./constants";
+import { Operation, ConditionalRender } from "./circuit";
+import { Metadata, GateType } from "./metadata";
+import { Register, RegisterMap, RegisterType } from "./register";
+import { getGateWidth } from "./utils";
+
+/**
+ * Takes in a list of operations and maps them to `metadata` objects which
+ * contains information for formatting the corresponding SVG.
+ *
+ * @param operations Array of operations.
+ * @param registers Mapping from qubit IDs to register metadata.
+ *
+ * @returns An object containing `metadataList` (Array of Metadata objects) and
+ * `svgWidth` which is the width of the entire SVG.
+ */
+const processOperations = (
+ operations: Operation[],
+ registers: RegisterMap,
+): { metadataList: Metadata[]; svgWidth: number } => {
+ if (operations.length === 0) return { metadataList: [], svgWidth: startX };
+
+ // Group operations based on registers
+ const groupedOps: number[][] = _groupOperations(operations, registers);
+
+ // Align operations on multiple registers
+ const alignedOps: (number | null)[][] = _alignOps(groupedOps);
+
+ // Maintain widths of each column to account for variable-sized gates
+ const numColumns: number = Math.max(
+ 0,
+ ...alignedOps.map((ops) => ops.length),
+ );
+ const columnsWidths: number[] = new Array(numColumns).fill(minGateWidth);
+
+ // Get classical registers and their starting column index
+ const classicalRegs: [number, Register][] = _getClassicalRegStart(
+ operations,
+ alignedOps,
+ );
+
+ // Keep track of which ops are already seen to avoid duplicate rendering
+ const visited: { [opIdx: number]: boolean } = {};
+
+ // Map operation index to gate metadata for formatting later
+ const opsMetadata: Metadata[][] = alignedOps.map((regOps) =>
+ regOps.map((opIdx, col) => {
+ let op: Operation | null = null;
+
+ // eslint-disable-next-line no-prototype-builtins
+ if (opIdx != null && !visited.hasOwnProperty(opIdx)) {
+ op = operations[opIdx];
+ visited[opIdx] = true;
+ }
+
+ const metadata: Metadata = _opToMetadata(op, registers);
+
+ if (
+ op != null &&
+ [GateType.Unitary, GateType.ControlledUnitary].includes(metadata.type)
+ ) {
+ // If gate is a unitary type, split targetsY into groups if there
+ // is a classical register between them for rendering
+
+ // Get y coordinates of classical registers in the same column as this operation
+ const classicalRegY: number[] = classicalRegs
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ .filter(([regCol, _]) => regCol <= col)
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ .map(([_, reg]) => {
+ if (reg.cId == null)
+ throw new Error("Could not find cId for classical register.");
+ const { children } = registers[reg.qId];
+ if (children == null)
+ throw new Error(
+ `Failed to find classical registers for qubit ID ${reg.qId}.`,
+ );
+ return children[reg.cId].y;
+ });
+
+ metadata.targetsY = _splitTargetsY(
+ op.targets,
+ classicalRegY,
+ registers,
+ );
+ }
+
+ // Expand column size, if needed
+ if (metadata.width > columnsWidths[col]) {
+ columnsWidths[col] = metadata.width;
+ }
+
+ return metadata;
+ }),
+ );
+
+ // Fill in x coord of each gate
+ const endX: number = _fillMetadataX(opsMetadata, columnsWidths);
+
+ // Flatten operations and filter out invalid gates
+ const metadataList: Metadata[] = opsMetadata
+ .flat()
+ .filter(({ type }) => type != GateType.Invalid);
+
+ return { metadataList, svgWidth: endX };
+};
+
+/**
+ * Group gates provided by operations into their respective registers.
+ *
+ * @param operations Array of operations.
+ * @param numRegs Total number of registers.
+ *
+ * @returns 2D array of indices where `groupedOps[i][j]` is the index of the operations
+ * at register `i` and column `j` (not yet aligned/padded).
+ */
+const _groupOperations = (
+ operations: Operation[],
+ registers: RegisterMap,
+): number[][] => {
+ // NOTE: We get the max ID instead of just number of keys because there can be a qubit ID that
+ // isn't acted upon and thus does not show up as a key in registers.
+ const numRegs: number =
+ Math.max(-1, ...Object.keys(registers).map(Number)) + 1;
+ const groupedOps: number[][] = Array.from(Array(numRegs), () => new Array(0));
+ operations.forEach(({ targets, controls }, instrIdx) => {
+ const ctrls: Register[] = controls || [];
+ const qRegs: Register[] = [...ctrls, ...targets].filter(
+ ({ type }) => (type || RegisterType.Qubit) === RegisterType.Qubit,
+ );
+ const qRegIdxList: number[] = qRegs.map(({ qId }) => qId);
+ const clsControls: Register[] = ctrls.filter(
+ ({ type }) => (type || RegisterType.Qubit) === RegisterType.Classical,
+ );
+ const isClassicallyControlled: boolean = clsControls.length > 0;
+ if (!isClassicallyControlled && qRegs.length === 0) return;
+ // If operation is classically-controlled, pad all qubit registers. Otherwise, only pad
+ // the contiguous range of registers that it covers.
+ const minRegIdx: number = isClassicallyControlled
+ ? 0
+ : Math.min(...qRegIdxList);
+ const maxRegIdx: number = isClassicallyControlled
+ ? numRegs - 1
+ : Math.max(...qRegIdxList);
+ // Add operation also to registers that are in-between target registers
+ // so that other gates won't render in the middle.
+ for (let i = minRegIdx; i <= maxRegIdx; i++) {
+ groupedOps[i].push(instrIdx);
+ }
+ });
+ return groupedOps;
+};
+
+/**
+ * Aligns operations by padding registers with `null`s to make sure that multiqubit
+ * gates are in the same column.
+ * e.g. ---[x]---[x]--
+ * ----------|---
+ *
+ * @param ops 2D array of operations. Each row represents a register
+ * and the operations acting on it (in-order).
+ *
+ * @returns 2D array of aligned operations padded with `null`s.
+ */
+const _alignOps = (ops: number[][]): (number | null)[][] => {
+ let maxNumOps: number = Math.max(0, ...ops.map((regOps) => regOps.length));
+ let col = 0;
+ // Deep copy ops to be returned as paddedOps
+ const paddedOps: (number | null)[][] = JSON.parse(JSON.stringify(ops));
+ while (col < maxNumOps) {
+ for (let regIdx = 0; regIdx < paddedOps.length; regIdx++) {
+ const reg: (number | null)[] = paddedOps[regIdx];
+ if (reg.length <= col) continue;
+
+ // Should never be null (nulls are only padded to previous columns)
+ const opIdx: number | null = reg[col];
+
+ // Get position of gate
+ const targetsPos: number[] = paddedOps.map((regOps) =>
+ regOps.indexOf(opIdx),
+ );
+ const gatePos: number = Math.max(-1, ...targetsPos);
+
+ // If current column is not desired gate position, pad with null
+ if (col < gatePos) {
+ paddedOps[regIdx].splice(col, 0, null);
+ maxNumOps = Math.max(maxNumOps, paddedOps[regIdx].length);
+ }
+ }
+ col++;
+ }
+ return paddedOps;
+};
+
+/**
+ * Retrieves the starting index of each classical register.
+ *
+ * @param ops Array of operations.
+ * @param idxList 2D array of aligned operation indices.
+ *
+ * @returns Array of classical register and their starting column indices in the form [[column, register]].
+ */
+const _getClassicalRegStart = (
+ ops: Operation[],
+ idxList: (number | null)[][],
+): [number, Register][] => {
+ const clsRegs: [number, Register][] = [];
+ idxList.forEach((reg) => {
+ for (let col = 0; col < reg.length; col++) {
+ const opIdx: number | null = reg[col];
+ if (opIdx != null && ops[opIdx].isMeasurement) {
+ const targetClsRegs: Register[] = ops[opIdx].targets.filter(
+ (reg) => reg.type === RegisterType.Classical,
+ );
+ targetClsRegs.forEach((reg) => clsRegs.push([col, reg]));
+ }
+ }
+ });
+ return clsRegs;
+};
+
+/**
+ * Maps operation to metadata (e.g. gate type, position, dimensions, text)
+ * required to render the image.
+ *
+ * @param op Operation to be mapped into metadata format.
+ * @param registers Array of registers.
+ *
+ * @returns Metadata representation of given operation.
+ */
+const _opToMetadata = (
+ op: Operation | null,
+ registers: RegisterMap,
+): Metadata => {
+ const metadata: Metadata = {
+ type: GateType.Invalid,
+ x: 0,
+ controlsY: [],
+ targetsY: [],
+ label: "",
+ width: -1,
+ };
+
+ if (op == null) return metadata;
+
+ const {
+ gate,
+ dataAttributes,
+ displayArgs,
+ isMeasurement,
+ isConditional,
+ isControlled,
+ isAdjoint,
+ controls,
+ targets,
+ children,
+ conditionalRender,
+ } = op;
+
+ // Set y coords
+ metadata.controlsY = controls?.map((reg) => _getRegY(reg, registers)) || [];
+ metadata.targetsY = targets.map((reg) => _getRegY(reg, registers));
+
+ if (isConditional) {
+ // Classically-controlled operations
+ if (children == null || children.length == 0)
+ throw new Error(
+ "No children operations found for classically-controlled operation.",
+ );
+
+ // Gates to display when classical bit is 0.
+ const onZeroOps: Operation[] = children.filter(
+ (op) => op.conditionalRender !== ConditionalRender.OnOne,
+ );
+ let childrenInstrs = processOperations(onZeroOps, registers);
+ const zeroGates: Metadata[] = childrenInstrs.metadataList;
+ const zeroChildWidth: number = childrenInstrs.svgWidth;
+
+ // Gates to display when classical bit is 1.
+ const onOneOps: Operation[] = children.filter(
+ (op) => op.conditionalRender !== ConditionalRender.OnZero,
+ );
+ childrenInstrs = processOperations(onOneOps, registers);
+ const oneGates: Metadata[] = childrenInstrs.metadataList;
+ const oneChildWidth: number = childrenInstrs.svgWidth;
+
+ // Subtract startX (left-side) and 2*gatePadding (right-side) from nested child gates width
+ const width: number =
+ Math.max(zeroChildWidth, oneChildWidth) - startX - gatePadding * 2;
+
+ metadata.type = GateType.ClassicalControlled;
+ metadata.children = [zeroGates, oneGates];
+ // Add additional width from control button and inner box padding for dashed box
+ metadata.width = width + controlBtnOffset + groupBoxPadding * 2;
+
+ // Set targets to first and last quantum registers so we can render the surrounding box
+ // around all quantum registers.
+ const qubitsY: number[] = Object.values(registers).map(({ y }) => y);
+ if (qubitsY.length > 0)
+ metadata.targetsY = [Math.min(...qubitsY), Math.max(...qubitsY)];
+ } else if (
+ conditionalRender == ConditionalRender.AsGroup &&
+ (children?.length || 0) > 0
+ ) {
+ const childrenInstrs = processOperations(
+ children as Operation[],
+ registers,
+ );
+ metadata.type = GateType.Group;
+ metadata.children = childrenInstrs.metadataList;
+ // _zoomButton function in gateFormatter.ts relies on
+ // 'expanded' attribute to render zoom button
+ metadata.dataAttributes = { expanded: "true" };
+ // Subtract startX (left-side) and add inner box padding (minus nested gate padding) for dashed box
+ metadata.width =
+ childrenInstrs.svgWidth - startX + (groupBoxPadding - gatePadding) * 2;
+ } else if (isMeasurement) {
+ metadata.type = GateType.Measure;
+ } else if (gate === "SWAP") {
+ metadata.type = GateType.Swap;
+ } else if (isControlled) {
+ metadata.type = gate === "X" ? GateType.Cnot : GateType.ControlledUnitary;
+ metadata.label = gate;
+ } else if (gate === "X") {
+ metadata.type = GateType.X;
+ metadata.label = gate;
+ } else {
+ // Any other gate treated as a simple unitary gate
+ metadata.type = GateType.Unitary;
+ metadata.label = gate;
+ }
+
+ // If adjoint, add ' to the end of gate label
+ if (isAdjoint && metadata.label.length > 0) metadata.label += "'";
+
+ // If gate has extra arguments, display them
+ if (displayArgs != null) metadata.displayArgs = displayArgs;
+
+ // Set gate width
+ metadata.width = getGateWidth(metadata);
+
+ // Extend existing data attributes with user-provided data attributes
+ if (dataAttributes != null)
+ metadata.dataAttributes = { ...metadata.dataAttributes, ...dataAttributes };
+
+ return metadata;
+};
+
+/**
+ * Compute the y coord of a given register.
+ *
+ * @param reg Register to compute y coord of.
+ * @param registers Map of qubit IDs to RegisterMetadata.
+ *
+ * @returns The y coord of give register.
+ */
+const _getRegY = (reg: Register, registers: RegisterMap): number => {
+ const { type, qId, cId } = reg;
+ if (!Object.prototype.hasOwnProperty.call(registers, qId))
+ throw new Error(`ERROR: Qubit register with ID ${qId} not found.`);
+ const { y, children } = registers[qId];
+ switch (type) {
+ case undefined:
+ case RegisterType.Qubit:
+ return y;
+ case RegisterType.Classical:
+ if (children == null)
+ throw new Error(
+ `ERROR: No classical registers found for qubit ID ${qId}.`,
+ );
+ if (cId == null)
+ throw new Error(
+ `ERROR: No ID defined for classical register associated with qubit ID ${qId}.`,
+ );
+ if (children.length <= cId)
+ throw new Error(
+ `ERROR: Classical register ID ${cId} invalid for qubit ID ${qId} with ${children.length} classical register(s).`,
+ );
+ return children[cId].y;
+ default:
+ throw new Error(`ERROR: Unknown register type ${type}.`);
+ }
+};
+
+/**
+ * Splits `targets` if non-adjacent or intersected by classical registers.
+ *
+ * @param targets Target qubit registers.
+ * @param classicalRegY y coords of classical registers overlapping current column.
+ * @param registers Mapping from register qubit IDs to register metadata.
+ *
+ * @returns Groups of target qubit y coords.
+ */
+const _splitTargetsY = (
+ targets: Register[],
+ classicalRegY: number[],
+ registers: RegisterMap,
+): number[][] => {
+ if (targets.length === 0) return [];
+
+ // Get qIds sorted by ascending y value
+ const orderedQIds: number[] = Object.keys(registers).map(Number);
+ orderedQIds.sort((a, b) => registers[a].y - registers[b].y);
+ const qIdPosition: { [qId: number]: number } = {};
+ orderedQIds.forEach((qId, i) => (qIdPosition[qId] = i));
+
+ // Sort targets and classicalRegY by ascending y value
+ targets = targets.slice();
+ targets.sort((a, b) => {
+ const posDiff: number = qIdPosition[a.qId] - qIdPosition[b.qId];
+ if (posDiff === 0 && a.cId != null && b.cId != null) return a.cId - b.cId;
+ else return posDiff;
+ });
+ classicalRegY = classicalRegY.slice();
+ classicalRegY.sort((a, b) => a - b);
+
+ let prevPos = 0;
+ let prevY = 0;
+
+ return targets.reduce((groups: number[][], target: Register) => {
+ const y = _getRegY(target, registers);
+ const pos = qIdPosition[target.qId];
+
+ // Split into new group if one of the following holds:
+ // 1. First target register
+ // 2. Non-adjacent qubit registers
+ // 3. There is a classical register between current and previous register
+ if (
+ groups.length === 0 ||
+ pos > prevPos + 1 ||
+ (classicalRegY[0] > prevY && classicalRegY[0] < y)
+ )
+ groups.push([y]);
+ else groups[groups.length - 1].push(y);
+
+ prevPos = pos;
+ prevY = y;
+
+ // Remove classical registers that are higher than current y
+ while (classicalRegY.length > 0 && classicalRegY[0] <= y)
+ classicalRegY.shift();
+
+ return groups;
+ }, []);
+};
+
+/**
+ * Updates the x coord of each metadata in the given 2D array of metadata and returns rightmost x coord.
+ *
+ * @param opsMetadata 2D array of metadata.
+ * @param columnWidths Array of column widths.
+ *
+ * @returns Rightmost x coord.
+ */
+const _fillMetadataX = (
+ opsMetadata: Metadata[][],
+ columnWidths: number[],
+): number => {
+ let currX: number = startX;
+
+ const colStartX: number[] = columnWidths.map((width) => {
+ const x: number = currX;
+ currX += width + gatePadding * 2;
+ return x;
+ });
+
+ const endX: number = currX;
+
+ opsMetadata.forEach((regOps) =>
+ regOps.forEach((metadata, col) => {
+ const x = colStartX[col];
+ switch (metadata.type) {
+ case GateType.ClassicalControlled:
+ case GateType.Group:
+ {
+ // Subtract startX offset from nested gates and add offset and padding
+ let offset: number = x - startX + groupBoxPadding;
+ if (metadata.type === GateType.ClassicalControlled)
+ offset += controlBtnOffset;
+
+ // Offset each x coord in children gates
+ _offsetChildrenX(metadata.children, offset);
+
+ // We don't use the centre x coord because we only care about the rightmost x for
+ // rendering the box around the group of nested gates
+ metadata.x = x;
+ }
+ break;
+
+ default:
+ metadata.x = x + columnWidths[col] / 2;
+ break;
+ }
+ }),
+ );
+
+ return endX;
+};
+
+/**
+ * Offset x coords of nested children operations.
+ *
+ * @param children 2D array of children metadata.
+ * @param offset x coord offset.
+ */
+const _offsetChildrenX = (
+ children: (Metadata | Metadata[])[] | undefined,
+ offset: number,
+): void => {
+ if (children == null) return;
+ children.flat().forEach((child) => {
+ child.x += offset;
+ _offsetChildrenX(child.children, offset);
+ });
+};
+
+export {
+ processOperations,
+ _groupOperations,
+ _alignOps,
+ _getClassicalRegStart,
+ _opToMetadata,
+ _getRegY,
+ _splitTargetsY,
+ _fillMetadataX,
+ _offsetChildrenX,
+};
diff --git a/circuit_vis/src/register.ts b/circuit_vis/src/register.ts
new file mode 100644
index 0000000000..e2fb0588fa
--- /dev/null
+++ b/circuit_vis/src/register.ts
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+/**
+ * Type of register.
+ */
+export enum RegisterType {
+ Qubit,
+ Classical,
+}
+
+/**
+ * Represents a register resource.
+ */
+export interface Register {
+ /** Type of register. If missing defaults to Qubit. */
+ type?: RegisterType;
+ /** Qubit register ID. */
+ qId: number;
+ /** Classical register ID (if classical register). */
+ cId?: number;
+}
+
+/**
+ * Metadata for qubit register.
+ */
+export interface RegisterMetadata {
+ /** Type of register. */
+ type: RegisterType;
+ /** y coord of register */
+ y: number;
+ /** Nested classical registers attached to quantum register. */
+ children?: RegisterMetadata[];
+}
+
+/**
+ * Mapping from qubit IDs to their register metadata.
+ */
+export interface RegisterMap {
+ [id: number]: RegisterMetadata;
+}
diff --git a/circuit_vis/src/sqore.ts b/circuit_vis/src/sqore.ts
new file mode 100644
index 0000000000..2c3263265f
--- /dev/null
+++ b/circuit_vis/src/sqore.ts
@@ -0,0 +1,376 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { formatInputs } from "./formatters/inputFormatter";
+import { formatGates } from "./formatters/gateFormatter";
+import { formatRegisters } from "./formatters/registerFormatter";
+import { processOperations } from "./process";
+import { ConditionalRender, Circuit, Operation } from "./circuit";
+import { Metadata, GateType } from "./metadata";
+import { StyleConfig, style, STYLES } from "./styles";
+import { createUUID } from "./utils";
+import { svgNS } from "./constants";
+
+/**
+ * Contains metadata for visualization.
+ */
+interface ComposedSqore {
+ /** Width of visualization. */
+ width: number;
+ /** Height of visualization. */
+ height: number;
+ /** SVG elements the make up the visualization. */
+ elements: SVGElement[];
+}
+
+/**
+ * Defines the mapping of unique ID to each operation. Used for enabling
+ * interactivity.
+ */
+type GateRegistry = {
+ [id: string]: Operation;
+};
+
+/**
+ * Entrypoint class for rendering circuit visualizations.
+ */
+export class Sqore {
+ circuit: Circuit;
+ style: StyleConfig = {};
+ gateRegistry: GateRegistry = {};
+
+ /**
+ * Initializes Sqore object with custom styles.
+ *
+ * @param circuit Circuit to be visualized.
+ * @param style Custom visualization style.
+ */
+ constructor(circuit: Circuit, style: StyleConfig | string = {}) {
+ this.circuit = circuit;
+ this.style = this.getStyle(style);
+ }
+
+ /**
+ * Render circuit into `container` at the specified layer depth.
+ *
+ * @param container HTML element for rendering visualization into.
+ * @param renderDepth Initial layer depth at which to render gates.
+ */
+ draw(container: HTMLElement, renderDepth = 0): void {
+ // Inject into container
+ if (container == null) throw new Error(`Container not provided.`);
+
+ // Create copy of circuit to prevent mutation
+ const circuit: Circuit = JSON.parse(JSON.stringify(this.circuit));
+
+ // Assign unique IDs to each operation
+ circuit.operations.forEach((op, i) =>
+ this.fillGateRegistry(op, i.toString()),
+ );
+
+ // Render operations at starting at given depth
+ circuit.operations = this.selectOpsAtDepth(circuit.operations, renderDepth);
+
+ // If only one top-level operation, expand automatically:
+ if (
+ circuit.operations.length == 1 &&
+ circuit.operations[0].dataAttributes != null &&
+ // eslint-disable-next-line no-prototype-builtins
+ circuit.operations[0].dataAttributes.hasOwnProperty("id")
+ ) {
+ const id: string = circuit.operations[0].dataAttributes["id"];
+ this.expandOperation(circuit.operations, id);
+ }
+
+ this.renderCircuit(container, circuit);
+ }
+
+ /**
+ * Retrieve style for visualization.
+ *
+ * @param style Custom style or style name.
+ *
+ * @returns Custom style.
+ */
+ private getStyle(style: StyleConfig | string = {}): StyleConfig {
+ if (typeof style === "string" || style instanceof String) {
+ const styleName: string = style as string;
+ // eslint-disable-next-line no-prototype-builtins
+ if (!STYLES.hasOwnProperty(styleName)) {
+ console.error(`No style ${styleName} found in STYLES.`);
+ return {};
+ }
+ style = STYLES[styleName];
+ }
+ return style;
+ }
+
+ /**
+ * Render circuit into `container`.
+ *
+ * @param container HTML element for rendering visualization into.
+ * @param circuit Circuit object to be rendered.
+ */
+ private renderCircuit(container: HTMLElement, circuit: Circuit): void {
+ // Create visualization components
+ const composedSqore: ComposedSqore = this.compose(circuit);
+ const svg: SVGElement = this.generateSvg(composedSqore);
+ container.innerHTML = "";
+ container.appendChild(svg);
+ this.addGateClickHandlers(container, circuit);
+ }
+
+ /**
+ * Generates the components required for visualization.
+ *
+ * @param circuit Circuit to be visualized.
+ *
+ * @returns `ComposedSqore` object containing metadata for visualization.
+ */
+ private compose(circuit: Circuit): ComposedSqore {
+ const add = (acc: Metadata[], gate: Metadata | Metadata[]): void => {
+ if (Array.isArray(gate)) {
+ gate.forEach((g) => add(acc, g));
+ } else {
+ acc.push(gate);
+ gate.children?.forEach((g) => add(acc, g));
+ }
+ };
+
+ const flatten = (gates: Metadata[]): Metadata[] => {
+ const result: Metadata[] = [];
+ add(result, gates);
+ return result;
+ };
+
+ const { qubits, operations } = circuit;
+ const { qubitWires, registers, svgHeight } = formatInputs(qubits);
+ const { metadataList, svgWidth } = processOperations(operations, registers);
+ const formattedGates: SVGElement = formatGates(metadataList);
+ const measureGates: Metadata[] = flatten(metadataList).filter(
+ ({ type }) => type === GateType.Measure,
+ );
+ const formattedRegs: SVGElement = formatRegisters(
+ registers,
+ measureGates,
+ svgWidth,
+ );
+
+ const composedSqore: ComposedSqore = {
+ width: svgWidth,
+ height: svgHeight,
+ elements: [qubitWires, formattedRegs, formattedGates],
+ };
+ return composedSqore;
+ }
+
+ /**
+ * Generates visualization of `composedSqore` as an SVG.
+ *
+ * @param composedSqore ComposedSqore to be visualized.
+ *
+ * @returns SVG representation of circuit visualization.
+ */
+ private generateSvg(composedSqore: ComposedSqore): SVGElement {
+ const { width, height, elements } = composedSqore;
+ const uuid: string = createUUID();
+
+ const svg: SVGElement = document.createElementNS(svgNS, "svg");
+ svg.setAttribute("id", uuid);
+ svg.setAttribute("class", "qviz");
+ svg.setAttribute("width", width.toString());
+ svg.setAttribute("height", height.toString());
+ svg.style.setProperty("max-width", "fit-content");
+
+ // Add styles
+ const css = document.createElement("style");
+ css.innerHTML = style(this.style);
+ svg.appendChild(css);
+
+ // Add body elements
+ elements.forEach((element: SVGElement) => svg.appendChild(element));
+
+ return svg;
+ }
+
+ /**
+ * Depth-first traversal to assign unique ID to `operation`.
+ * The operation is assigned the id `id` and its `i`th child is recursively given
+ * the id `${id}-${i}`.
+ *
+ * @param operation Operation to be assigned.
+ * @param id: ID to assign to `operation`.
+ *
+ */
+ private fillGateRegistry(operation: Operation, id: string): void {
+ if (operation.dataAttributes == null) operation.dataAttributes = {};
+ operation.dataAttributes["id"] = id;
+ // By default, operations cannot be zoomed-out
+ operation.dataAttributes["zoom-out"] = "false";
+ this.gateRegistry[id] = operation;
+ operation.children?.forEach((childOp, i) => {
+ this.fillGateRegistry(childOp, `${id}-${i}`);
+ if (childOp.dataAttributes == null) childOp.dataAttributes = {};
+ // Children operations can be zoomed out
+ childOp.dataAttributes["zoom-out"] = "true";
+ });
+ // Composite operations can be zoomed in
+ operation.dataAttributes["zoom-in"] = (
+ operation.children != null
+ ).toString();
+ }
+
+ /**
+ * Pick out operations that are at or below `renderDepth`.
+ *
+ * @param operations List of circuit operations.
+ * @param renderDepth Initial layer depth at which to render gates.
+ *
+ * @returns List of operations at or below specifed depth.
+ */
+ private selectOpsAtDepth(
+ operations: Operation[],
+ renderDepth: number,
+ ): Operation[] {
+ if (renderDepth < 0)
+ throw new Error(
+ `Invalid renderDepth of ${renderDepth}. Needs to be >= 0.`,
+ );
+ if (renderDepth === 0) return operations;
+ return operations
+ .map((op) =>
+ op.children != null
+ ? this.selectOpsAtDepth(op.children, renderDepth - 1)
+ : op,
+ )
+ .flat();
+ }
+
+ /**
+ * Add interactive click handlers to circuit HTML elements.
+ *
+ * @param container HTML element containing visualized circuit.
+ * @param circuit Circuit to be visualized.
+ *
+ */
+ private addGateClickHandlers(container: HTMLElement, circuit: Circuit): void {
+ this.addClassicalControlHandlers(container);
+ this.addZoomHandlers(container, circuit);
+ }
+
+ /**
+ * Add interactive click handlers for classically-controlled operations.
+ *
+ * @param container HTML element containing visualized circuit.
+ *
+ */
+ private addClassicalControlHandlers(container: HTMLElement): void {
+ container.querySelectorAll(".classically-controlled-btn").forEach((btn) => {
+ // Zoom in on clicked gate
+ btn.addEventListener("click", (evt: Event) => {
+ const textSvg = btn.querySelector("text");
+ const group = btn.parentElement;
+ if (textSvg == null || group == null) return;
+
+ const currValue = textSvg.firstChild?.nodeValue;
+ const zeroGates = group?.querySelector(".gates-zero");
+ const oneGates = group?.querySelector(".gates-one");
+ switch (currValue) {
+ case "?":
+ textSvg.childNodes[0].nodeValue = "1";
+ group.classList.remove("classically-controlled-unknown");
+ group.classList.remove("classically-controlled-zero");
+ group.classList.add("classically-controlled-one");
+ zeroGates?.classList.add("hidden");
+ oneGates?.classList.remove("hidden");
+ break;
+ case "1":
+ textSvg.childNodes[0].nodeValue = "0";
+ group.classList.remove("classically-controlled-unknown");
+ group.classList.add("classically-controlled-zero");
+ group.classList.remove("classically-controlled-one");
+ zeroGates?.classList.remove("hidden");
+ oneGates?.classList.add("hidden");
+ break;
+ case "0":
+ textSvg.childNodes[0].nodeValue = "?";
+ group.classList.add("classically-controlled-unknown");
+ group.classList.remove("classically-controlled-zero");
+ group.classList.remove("classically-controlled-one");
+ zeroGates?.classList.remove("hidden");
+ oneGates?.classList.remove("hidden");
+ break;
+ }
+ evt.stopPropagation();
+ });
+ });
+ }
+
+ /**
+ * Add interactive click handlers for zoom-in/out functionality.
+ *
+ * @param container HTML element containing visualized circuit.
+ * @param circuit Circuit to be visualized.
+ *
+ */
+ private addZoomHandlers(container: HTMLElement, circuit: Circuit): void {
+ container.querySelectorAll(".gate .gate-control").forEach((ctrl) => {
+ // Zoom in on clicked gate
+ ctrl.addEventListener("click", (ev: Event) => {
+ const gateId: string | null | undefined =
+ ctrl.parentElement?.getAttribute("data-id");
+ if (typeof gateId == "string") {
+ if (ctrl.classList.contains("gate-collapse")) {
+ this.collapseOperation(circuit.operations, gateId);
+ } else if (ctrl.classList.contains("gate-expand")) {
+ this.expandOperation(circuit.operations, gateId);
+ }
+ this.renderCircuit(container, circuit);
+
+ ev.stopPropagation();
+ }
+ });
+ });
+ }
+
+ /**
+ * Expand selected operation for zoom-in interaction.
+ *
+ * @param operations List of circuit operations.
+ * @param id ID of operation to expand.
+ *
+ */
+ private expandOperation(operations: Operation[], id: string): void {
+ operations.forEach((op) => {
+ if (op.conditionalRender === ConditionalRender.AsGroup)
+ this.expandOperation(op.children || [], id);
+ if (op.dataAttributes == null) return op;
+ const opId: string = op.dataAttributes["id"];
+ if (opId === id && op.children != null) {
+ op.conditionalRender = ConditionalRender.AsGroup;
+ op.dataAttributes["expanded"] = "true";
+ }
+ });
+ }
+
+ /**
+ * Collapse selected operation for zoom-out interaction.
+ *
+ * @param operations List of circuit operations.
+ * @param id ID of operation to collapse.
+ *
+ */
+ private collapseOperation(operations: Operation[], parentId: string): void {
+ operations.forEach((op) => {
+ if (op.conditionalRender === ConditionalRender.AsGroup)
+ this.collapseOperation(op.children || [], parentId);
+ if (op.dataAttributes == null) return op;
+ const opId: string = op.dataAttributes["id"];
+ // Collapse parent gate and its children
+ if (opId.startsWith(parentId)) {
+ op.conditionalRender = ConditionalRender.Always;
+ delete op.dataAttributes["expanded"];
+ }
+ });
+ }
+}
diff --git a/circuit_vis/src/styles.ts b/circuit_vis/src/styles.ts
new file mode 100644
index 0000000000..ad22c45b3d
--- /dev/null
+++ b/circuit_vis/src/styles.ts
@@ -0,0 +1,236 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+/**
+ * Provides configuration for CSS styles of visualization.
+ */
+export interface StyleConfig {
+ /** Line stroke style. */
+ lineStroke?: string;
+ /** Line width. */
+ lineWidth?: number;
+ /** Text colour. */
+ textColour?: string;
+ /** Single qubit unitary fill colour. */
+ unitary?: string;
+ /** Oplus circle fill colour. */
+ oplus?: string;
+ /** Measurement gate fill colour. */
+ measure?: string;
+ /** Measurement unknown primary colour. */
+ classicalUnknown?: string;
+ /** Measurement zero primary colour. */
+ classicalZero?: string;
+ /** Measurement one primary colour. */
+ classicalOne?: string;
+ /** Measurement zero text colour */
+ classicalZeroText?: string;
+ /** Measurement one text colour */
+ classicalOneText?: string;
+}
+
+const defaultStyle: StyleConfig = {
+ lineStroke: "#000000",
+ lineWidth: 1,
+ textColour: "#000000",
+ unitary: "#D9F1FA",
+ oplus: "#FFFFFF",
+ measure: "#FFDE86",
+ classicalUnknown: "#E5E5E5",
+ classicalZero: "#C40000",
+ classicalOne: "#4059BD",
+ classicalZeroText: "#FFFFFF",
+ classicalOneText: "#FFFFFF",
+};
+
+const blackAndWhiteStyle: StyleConfig = {
+ lineStroke: "#000000",
+ lineWidth: 1,
+ textColour: "#000000",
+ unitary: "#FFFFFF",
+ oplus: "#FFFFFF",
+ measure: "#FFFFFF",
+ classicalUnknown: "#FFFFFF",
+ classicalZero: "#000000",
+ classicalOne: "#000000",
+ classicalZeroText: "#FFFFFF",
+ classicalOneText: "#FFFFFF",
+};
+
+const invertedStyle: StyleConfig = {
+ lineStroke: "#FFFFFF",
+ lineWidth: 1,
+ textColour: "#FFFFFF",
+ unitary: "#000000",
+ oplus: "#000000",
+ measure: "#000000",
+ classicalUnknown: "#000000",
+ classicalZero: "#FFFFFF",
+ classicalOne: "#FFFFFF",
+ classicalZeroText: "#000000",
+ classicalOneText: "#000000",
+};
+
+/**
+ * Set of default styles.
+ */
+export const STYLES: { [name: string]: StyleConfig } = {
+ /** Default style with coloured gates. */
+ Default: defaultStyle,
+ /** Black and white style. */
+ BlackAndWhite: blackAndWhiteStyle,
+ /** Inverted black and white style (for black backgrounds). */
+ Inverted: invertedStyle,
+};
+
+/**
+ * CSS style script to be injected into visualization SVG.
+ *
+ * @param customStyle Custom style configuration.
+ *
+ * @returns String containing CSS style script.
+ */
+export const style = (customStyle: StyleConfig = {}): string => {
+ const styleConfig = { ...defaultStyle, ...customStyle };
+
+ return `${_defaultGates(styleConfig)}
+ ${_classicallyControlledGates(styleConfig)}
+ ${_expandCollapse}`;
+};
+
+const _defaultGates = (styleConfig: StyleConfig): string => `
+ line,
+ circle,
+ rect {
+ stroke: ${styleConfig.lineStroke};
+ stroke-width: ${styleConfig.lineWidth};
+ }
+ text {
+ fill: ${styleConfig.textColour};
+ dominant-baseline: middle;
+ text-anchor: middle;
+ font-family: Arial;
+ }
+ .control-dot {
+ fill: ${styleConfig.lineStroke};
+ }
+ .oplus line, .oplus circle {
+ fill: ${styleConfig.oplus};
+ stroke-width: 2;
+ }
+ .gate-unitary {
+ fill: ${styleConfig.unitary};
+ }
+ .gate-measure {
+ fill: ${styleConfig.measure};
+ }
+ rect.gate-swap {
+ fill: transparent;
+ stroke: transparent;
+ }
+ .arc-measure {
+ stroke: ${styleConfig.lineStroke};
+ fill: none;
+ stroke-width: ${styleConfig.lineWidth};
+ }
+ .register-classical {
+ stroke-width: ${(styleConfig.lineWidth || 0) / 2};
+ }`;
+
+const _classicallyControlledGates = (styleConfig: StyleConfig): string => {
+ const gateOutline = `
+ .classically-controlled-one .classical-container,
+ .classically-controlled-one .classical-line {
+ stroke: ${styleConfig.classicalOne};
+ stroke-width: ${(styleConfig.lineWidth || 0) + 0.3};
+ fill: ${styleConfig.classicalOne};
+ fill-opacity: 0.1;
+ }
+ .classically-controlled-zero .classical-container,
+ .classically-controlled-zero .classical-line {
+ stroke: ${styleConfig.classicalZero};
+ stroke-width: ${(styleConfig.lineWidth || 0) + 0.3};
+ fill: ${styleConfig.classicalZero};
+ fill-opacity: 0.1;
+ }`;
+ const controlBtn = `
+ .classically-controlled-btn {
+ cursor: pointer;
+ }
+ .classically-controlled-unknown .classically-controlled-btn {
+ fill: ${styleConfig.classicalUnknown};
+ }
+ .classically-controlled-one .classically-controlled-btn {
+ fill: ${styleConfig.classicalOne};
+ }
+ .classically-controlled-zero .classically-controlled-btn {
+ fill: ${styleConfig.classicalZero};
+ }`;
+
+ const controlBtnText = `
+ .classically-controlled-btn text {
+ dominant-baseline: middle;
+ text-anchor: middle;
+ stroke: none;
+ font-family: Arial;
+ }
+ .classically-controlled-unknown .classically-controlled-btn text {
+ fill: ${styleConfig.textColour};
+ }
+ .classically-controlled-one .classically-controlled-btn text {
+ fill: ${styleConfig.classicalOneText};
+ }
+ .classically-controlled-zero .classically-controlled-btn text {
+ fill: ${styleConfig.classicalZeroText};
+ }`;
+
+ return `
+ .hidden {
+ display: none;
+ }
+ .classically-controlled-unknown {
+ opacity: 0.25;
+ }
+
+ ${gateOutline}
+ ${controlBtn}
+ ${controlBtnText}`;
+};
+
+const _expandCollapse = `
+ .qviz .gate-collapse,
+ .qviz .gate-expand {
+ opacity: 0;
+ transition: opacity 1s;
+ }
+
+ .qviz:hover .gate-collapse,
+ .qviz:hover .gate-expand {
+ visibility: visible;
+ opacity: 0.2;
+ transition: visibility 1s;
+ transition: opacity 1s;
+ }
+
+ .gate-expand, .gate-collapse {
+ cursor: pointer;
+ }
+
+ .gate-collapse circle,
+ .gate-expand circle {
+ fill: white;
+ stroke-width: 2px;
+ stroke: black;
+ }
+ .gate-collapse path,
+ .gate-expand path {
+ stroke-width: 4px;
+ stroke: black;
+ }
+
+ .gate:hover > .gate-collapse,
+ .gate:hover > .gate-expand {
+ visibility: visible;
+ opacity: 1;
+ transition: opacity 1s;
+ }`;
diff --git a/circuit_vis/src/utils.ts b/circuit_vis/src/utils.ts
new file mode 100644
index 0000000000..8ce9f9d4eb
--- /dev/null
+++ b/circuit_vis/src/utils.ts
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { Metadata, GateType } from "./metadata";
+import {
+ minGateWidth,
+ labelPadding,
+ labelFontSize,
+ argsFontSize,
+} from "./constants";
+
+/**
+ * Generate a UUID using `Math.random`.
+ * Note: this implementation came from https://stackoverflow.com/questions/105034/how-to-create-guid-uuid
+ * and is not cryptographically secure but works for our use case.
+ *
+ * @returns UUID string.
+ */
+const createUUID = (): string =>
+ "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
+ const r = (Math.random() * 16) | 0,
+ v = c == "x" ? r : (r & 0x3) | 0x8;
+ return v.toString(16);
+ });
+
+/**
+ * Calculate the width of a gate, given its metadata.
+ *
+ * @param metadata Metadata of a given gate.
+ *
+ * @returns Width of given gate (in pixels).
+ */
+const getGateWidth = ({
+ type,
+ label,
+ displayArgs,
+ width,
+}: Metadata): number => {
+ if (width > 0) return width;
+
+ switch (type) {
+ case GateType.Measure:
+ case GateType.Cnot:
+ case GateType.Swap:
+ return minGateWidth;
+ default: {
+ const labelWidth = _getStringWidth(label);
+ const argsWidth =
+ displayArgs != null ? _getStringWidth(displayArgs, argsFontSize) : 0;
+ const textWidth = Math.max(labelWidth, argsWidth) + labelPadding * 2;
+ return Math.max(minGateWidth, textWidth);
+ }
+ }
+};
+
+/**
+ * Get the width of a string with font-size `fontSize` and font-family Arial.
+ *
+ * @param text Input string.
+ * @param fontSize Font size of `text`.
+ *
+ * @returns Pixel width of given string.
+ */
+const _getStringWidth = (
+ text: string,
+ fontSize: number = labelFontSize,
+): number => {
+ const canvas: HTMLCanvasElement = document.createElement("canvas");
+ const context: CanvasRenderingContext2D | null = canvas.getContext("2d");
+ if (context == null) throw new Error("Null canvas");
+
+ context.font = `${fontSize}px Arial`;
+ const metrics: TextMetrics = context.measureText(text);
+ return metrics.width;
+};
+
+export { createUUID, getGateWidth, _getStringWidth };
diff --git a/circuit_vis/tsconfig.json b/circuit_vis/tsconfig.json
new file mode 100644
index 0000000000..85efcd2f95
--- /dev/null
+++ b/circuit_vis/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "commonjs",
+ "lib": ["DOM", "ES2019"],
+ "declaration": true,
+ "strict": true,
+ "outDir": "lib",
+ "skipLibCheck": true
+ },
+ "include": ["src/**/*"]
+}
diff --git a/circuit_vis/webpack.config.js b/circuit_vis/webpack.config.js
new file mode 100644
index 0000000000..fafaa93ab8
--- /dev/null
+++ b/circuit_vis/webpack.config.js
@@ -0,0 +1,71 @@
+const path = require("path");
+const TerserPlugin = require("terser-webpack-plugin");
+
+const PATHS = {
+ entryPoint: path.resolve(__dirname, "src/index.ts"),
+ bundles: path.resolve(__dirname, "dist"),
+};
+
+const config = {
+ // Enables production mode built-in optimizations
+ mode: "production",
+ // These are the entry point of our library. We tell webpack to use
+ // the name we assign later, when creating the bundle. We also use
+ // the name to filter the second entry point for applying code
+ // minification via UglifyJS
+ entry: {
+ qviz: [PATHS.entryPoint],
+ "qviz.min": [PATHS.entryPoint],
+ },
+ // The output defines how and where we want the bundles. The special
+ // value `[name]` in `filename` tell Webpack to use the name we defined above.
+ // The bundled script will be available as a global variable `qviz`.
+ output: {
+ path: PATHS.bundles,
+ filename: "[name].js",
+ library: "qviz",
+ libraryTarget: "umd",
+ },
+ // Add resolve for `tsx` and `ts` files, otherwise Webpack would
+ // only look for common JavaScript file extension (.js)
+ resolve: {
+ extensions: [".ts", ".tsx", ".js"],
+ },
+ // Activate source maps for the bundles in order to preserve the original
+ // source when the user debugs the application
+ devtool: "source-map",
+ optimization: {
+ // Apply minification only on the second bundle by
+ // using a RegEx on the name, which must end with `.min.js`
+ minimize: true,
+ minimizer: [
+ new TerserPlugin({
+ // sourceMap: true,
+ include: /\.min\.js$/,
+ }),
+ ],
+ },
+ module: {
+ // Webpack doesn't understand TypeScript files and a loader is needed.
+ // `node_modules` folder is excluded in order to prevent problems with
+ // the library dependencies, as well as `__tests__` folders that
+ // contain the tests for the library
+ rules: [
+ {
+ test: /\.tsx?$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: "ts-loader",
+ options: {
+ // Speeds up compilation and does not build *.d.ts files
+ transpileOnly: true,
+ },
+ },
+ ],
+ },
+ ],
+ },
+};
+
+module.exports = config;
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 96c46336f9..e32ba008c8 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -12,6 +12,10 @@ export default tseslint.config(
},
{
ignores: [
+ "circuit_vis/lib/",
+ "circuit_vis/coverage/",
+ "circuit_vis/dist/",
+ "circuit_vis/*.config.js",
"target/",
"playground/public/",
"npm/qsharp/dist/",
diff --git a/package-lock.json b/package-lock.json
index e82464764d..75384129aa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,12 +9,12 @@
"workspaces": [
"./npm/qsharp",
"./playground",
- "./vscode"
+ "./vscode",
+ "./circuit_vis"
],
"devDependencies": {
"@anywidget/types": "^0.1.4",
"@eslint/js": "^9.9.1",
- "@microsoft/quantum-viz.js": "^1.0.5",
"@parcel/watcher": "^2.4.1",
"@types/chai": "^4.3.8",
"@types/markdown-it": "^14.1.1",
@@ -40,16 +40,25 @@
"preact": "^10.20.0",
"prettier": "^3.3.3",
"punycode": "^2.3.1",
+ "terser-webpack-plugin": "^5.3.11",
+ "ts-loader": "^9.5.2",
"typescript": "^5.5.4",
"typescript-eslint": "^8.19.1",
"url": "^0.11.3",
"user-agent-data-types": "^0.4.2",
- "vscode-uri": "^3.0.8"
+ "vscode-uri": "^3.0.8",
+ "webpack": "^5.97.1",
+ "webpack-cli": "^6.0.1"
},
"engines": {
"node": ">=18.17.1"
}
},
+ "circuit_vis": {
+ "name": "@microsoft/quantum-viz.js",
+ "version": "1.0.6",
+ "license": "MIT"
+ },
"node_modules/@aashutoshrathi/word-wrap": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
@@ -232,6 +241,15 @@
"node": ">=14.0.0"
}
},
+ "node_modules/@discoveryjs/json-ext": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz",
+ "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.17.0"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
@@ -954,6 +972,64 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
+ "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
"node_modules/@jupyter-widgets/base": {
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/@jupyter-widgets/base/-/base-6.0.7.tgz",
@@ -1452,10 +1528,8 @@
"dev": true
},
"node_modules/@microsoft/quantum-viz.js": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@microsoft/quantum-viz.js/-/quantum-viz.js-1.0.5.tgz",
- "integrity": "sha512-U2atugL4j+p/yiioPR/TfqqR534iP6kEa10S+YNUW68UyvMe8eXNDFlb8GG4Ro9WSGobyLa8tP7iRiR2YMZO1w==",
- "dev": true
+ "resolved": "circuit_vis",
+ "link": true
},
"node_modules/@nevware21/ts-async": {
"version": "0.5.0",
@@ -1922,6 +1996,26 @@
"integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==",
"dev": true
},
+ "node_modules/@types/eslint": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
+ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "node_modules/@types/eslint-scope": {
+ "version": "3.7.7",
+ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
+ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
+ "dev": true,
+ "dependencies": {
+ "@types/eslint": "*",
+ "@types/estree": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@@ -2309,6 +2403,208 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/@webassemblyjs/ast": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
+ "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/helper-numbers": "1.13.2",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
+ "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-api-error": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
+ "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-buffer": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
+ "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-numbers": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
+ "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/floating-point-hex-parser": "1.13.2",
+ "@webassemblyjs/helper-api-error": "1.13.2",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
+ "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-wasm-section": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
+ "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/wasm-gen": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/ieee754": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
+ "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
+ "dev": true,
+ "dependencies": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "node_modules/@webassemblyjs/leb128": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
+ "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
+ "dev": true,
+ "dependencies": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/utf8": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
+ "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/wasm-edit": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
+ "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/helper-wasm-section": "1.14.1",
+ "@webassemblyjs/wasm-gen": "1.14.1",
+ "@webassemblyjs/wasm-opt": "1.14.1",
+ "@webassemblyjs/wasm-parser": "1.14.1",
+ "@webassemblyjs/wast-printer": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-gen": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
+ "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/ieee754": "1.13.2",
+ "@webassemblyjs/leb128": "1.13.2",
+ "@webassemblyjs/utf8": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-opt": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
+ "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/wasm-gen": "1.14.1",
+ "@webassemblyjs/wasm-parser": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-parser": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
+ "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-api-error": "1.13.2",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/ieee754": "1.13.2",
+ "@webassemblyjs/leb128": "1.13.2",
+ "@webassemblyjs/utf8": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/wast-printer": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
+ "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webpack-cli/configtest": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz",
+ "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "webpack": "^5.82.0",
+ "webpack-cli": "6.x.x"
+ }
+ },
+ "node_modules/@webpack-cli/info": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz",
+ "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "webpack": "^5.82.0",
+ "webpack-cli": "6.x.x"
+ }
+ },
+ "node_modules/@webpack-cli/serve": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz",
+ "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "webpack": "^5.82.0",
+ "webpack-cli": "6.x.x"
+ },
+ "peerDependenciesMeta": {
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true
+ },
+ "node_modules/@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true
+ },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -2380,6 +2676,35 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv-keywords": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
+ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3"
+ },
+ "peerDependencies": {
+ "ajv": "^8.8.2"
+ }
+ },
"node_modules/ansi-colors": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
@@ -2646,6 +2971,38 @@
"pako": "~0.2.0"
}
},
+ "node_modules/browserslist": {
+ "version": "4.24.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
+ "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001688",
+ "electron-to-chromium": "^1.5.73",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -2705,6 +3062,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001692",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz",
+ "integrity": "sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
"node_modules/chai": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz",
@@ -2790,6 +3167,15 @@
"node": ">= 6"
}
},
+ "node_modules/chrome-trace-event": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
+ "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
"node_modules/cjs-module-lexer": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz",
@@ -2807,6 +3193,20 @@
"wrap-ansi": "^7.0.0"
}
},
+ "node_modules/clone-deep": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+ "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+ "dev": true,
+ "dependencies": {
+ "is-plain-object": "^2.0.4",
+ "kind-of": "^6.0.2",
+ "shallow-clone": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/cls-hooked": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz",
@@ -2858,6 +3258,12 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "dev": true
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -3140,6 +3546,12 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"dev": true
},
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.82",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.82.tgz",
+ "integrity": "sha512-Zq16uk1hfQhyGx5GpwPAYDwddJuSGhtRhgOA2mCxANYaDT79nAeGnaXogMGng4KqLaJUVnOnuL0+TDop9nLOiA==",
+ "dev": true
+ },
"node_modules/emitter-listener": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz",
@@ -3173,6 +3585,19 @@
"once": "^1.4.0"
}
},
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
+ "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -3185,6 +3610,18 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/envinfo": {
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz",
+ "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==",
+ "dev": true,
+ "bin": {
+ "envinfo": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
@@ -3206,6 +3643,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
+ "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
+ "dev": true
+ },
"node_modules/esbuild": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
@@ -3246,9 +3689,9 @@
}
},
"node_modules/escalade": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
- "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"engines": {
"node": ">=6"
@@ -3547,6 +3990,15 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
+ "node_modules/fastest-levenshtein": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
+ "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.9.1"
+ }
+ },
"node_modules/fastq": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
@@ -3772,6 +4224,12 @@
"node": ">=10.13.0"
}
},
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "dev": true
+ },
"node_modules/glob/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
@@ -3808,6 +4266,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -4038,6 +4502,25 @@
"module-details-from-path": "^1.0.3"
}
},
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -4063,6 +4546,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
+ "node_modules/interpret": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
+ "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -4165,6 +4657,18 @@
"node": ">=8"
}
},
+ "node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@@ -4189,6 +4693,15 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
+ "node_modules/isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/isomorphic.js": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
@@ -4214,11 +4727,40 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/jquery": {
- "version": "3.7.1",
- "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
- "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
- "dev": true
+ "node_modules/jest-worker": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
+ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/jquery": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
+ "dev": true
},
"node_modules/js-tokens": {
"version": "4.0.0",
@@ -4245,6 +4787,12 @@
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true
},
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
"node_modules/json-schema-compare": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz",
@@ -4338,6 +4886,15 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/koa": {
"version": "2.15.3",
"resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz",
@@ -4560,6 +5117,15 @@
"uc.micro": "^2.0.0"
}
},
+ "node_modules/loader-runner": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
+ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.11.5"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -4675,6 +5241,12 @@
"node": ">= 0.6"
}
},
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -4907,6 +5479,12 @@
"node": ">= 0.6"
}
},
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true
+ },
"node_modules/node-addon-api": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz",
@@ -4916,6 +5494,12 @@
"node": "^16 || ^18 || >= 20"
}
},
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -5017,6 +5601,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -5140,6 +5733,12 @@
"through2": "^2.0.3"
}
},
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true
+ },
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@@ -5152,6 +5751,70 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/playwright": {
"version": "1.47.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz",
@@ -5395,6 +6058,18 @@
"node": ">=8.10.0"
}
},
+ "node_modules/rechoir": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
+ "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==",
+ "dev": true,
+ "dependencies": {
+ "resolve": "^1.20.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -5450,6 +6125,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -5570,6 +6266,55 @@
}
]
},
+ "node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/schema-utils/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/schema-utils/node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true,
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/schema-utils/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
"node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
@@ -5617,6 +6362,18 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"dev": true
},
+ "node_modules/shallow-clone": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+ "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+ "dev": true,
+ "dependencies": {
+ "kind-of": "^6.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -5674,6 +6431,25 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
"node_modules/stack-chain": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz",
@@ -5814,6 +6590,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/tar-fs": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz",
@@ -5849,6 +6634,92 @@
"streamx": "^2.15.0"
}
},
+ "node_modules/terser": {
+ "version": "5.37.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
+ "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.8.2",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/terser-webpack-plugin": {
+ "version": "5.3.11",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz",
+ "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jest-worker": "^27.4.5",
+ "schema-utils": "^4.3.0",
+ "serialize-javascript": "^6.0.2",
+ "terser": "^5.31.1"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "uglify-js": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
+ "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.9",
+ "ajv": "^8.9.0",
+ "ajv-formats": "^2.1.1",
+ "ajv-keywords": "^5.1.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/terser/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
"node_modules/text-decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz",
@@ -5901,6 +6772,35 @@
"typescript": ">=4.8.4"
}
},
+ "node_modules/ts-loader": {
+ "version": "9.5.2",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz",
+ "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "enhanced-resolve": "^5.0.0",
+ "micromatch": "^4.0.0",
+ "semver": "^7.3.4",
+ "source-map": "^0.7.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "typescript": "*",
+ "webpack": "^5.0.0"
+ }
+ },
+ "node_modules/ts-loader/node_modules/source-map": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
+ "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
@@ -6003,6 +6903,36 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
+ "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -6111,6 +7041,161 @@
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
"dev": true
},
+ "node_modules/watchpack": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
+ "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
+ "dev": true,
+ "dependencies": {
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.1.2"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/webpack": {
+ "version": "5.97.1",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
+ "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
+ "dev": true,
+ "dependencies": {
+ "@types/eslint-scope": "^3.7.7",
+ "@types/estree": "^1.0.6",
+ "@webassemblyjs/ast": "^1.14.1",
+ "@webassemblyjs/wasm-edit": "^1.14.1",
+ "@webassemblyjs/wasm-parser": "^1.14.1",
+ "acorn": "^8.14.0",
+ "browserslist": "^4.24.0",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^5.17.1",
+ "es-module-lexer": "^1.2.1",
+ "eslint-scope": "5.1.1",
+ "events": "^3.2.0",
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.2.11",
+ "json-parse-even-better-errors": "^2.3.1",
+ "loader-runner": "^4.2.0",
+ "mime-types": "^2.1.27",
+ "neo-async": "^2.6.2",
+ "schema-utils": "^3.2.0",
+ "tapable": "^2.1.1",
+ "terser-webpack-plugin": "^5.3.10",
+ "watchpack": "^2.4.1",
+ "webpack-sources": "^3.2.3"
+ },
+ "bin": {
+ "webpack": "bin/webpack.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependenciesMeta": {
+ "webpack-cli": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-cli": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz",
+ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==",
+ "dev": true,
+ "dependencies": {
+ "@discoveryjs/json-ext": "^0.6.1",
+ "@webpack-cli/configtest": "^3.0.1",
+ "@webpack-cli/info": "^3.0.1",
+ "@webpack-cli/serve": "^3.0.1",
+ "colorette": "^2.0.14",
+ "commander": "^12.1.0",
+ "cross-spawn": "^7.0.3",
+ "envinfo": "^7.14.0",
+ "fastest-levenshtein": "^1.0.12",
+ "import-local": "^3.0.2",
+ "interpret": "^3.1.1",
+ "rechoir": "^0.8.0",
+ "webpack-merge": "^6.0.1"
+ },
+ "bin": {
+ "webpack-cli": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.82.0"
+ },
+ "peerDependenciesMeta": {
+ "webpack-bundle-analyzer": {
+ "optional": true
+ },
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-cli/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webpack-merge": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz",
+ "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==",
+ "dev": true,
+ "dependencies": {
+ "clone-deep": "^4.0.1",
+ "flat": "^5.0.2",
+ "wildcard": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/webpack/node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/webpack/node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/webpack/node_modules/webpack-sources": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
+ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -6126,6 +7211,12 @@
"node": ">= 8"
}
},
+ "node_modules/wildcard": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
+ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
+ "dev": true
+ },
"node_modules/workerpool": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
diff --git a/package.json b/package.json
index 59bd26d448..778066cd41 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,8 @@
"workspaces": [
"./npm/qsharp",
"./playground",
- "./vscode"
+ "./vscode",
+ "./circuit_vis"
],
"license": "MIT",
"engines": {
@@ -19,7 +20,6 @@
"devDependencies": {
"@anywidget/types": "^0.1.4",
"@eslint/js": "^9.9.1",
- "@microsoft/quantum-viz.js": "^1.0.5",
"@parcel/watcher": "^2.4.1",
"@types/chai": "^4.3.8",
"@types/markdown-it": "^14.1.1",
@@ -45,10 +45,14 @@
"preact": "^10.20.0",
"prettier": "^3.3.3",
"punycode": "^2.3.1",
+ "terser-webpack-plugin": "^5.3.11",
+ "ts-loader": "^9.5.2",
"typescript": "^5.5.4",
"typescript-eslint": "^8.19.1",
"url": "^0.11.3",
"user-agent-data-types": "^0.4.2",
- "vscode-uri": "^3.0.8"
+ "vscode-uri": "^3.0.8",
+ "webpack": "^5.97.1",
+ "webpack-cli": "^6.0.1"
}
}