diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..b08f2d6
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,46 @@
+name: CI
+
+on:
+ push:
+ branches: [main, v3]
+ pull_request:
+
+jobs:
+ backend:
+ runs-on: ubuntu-latest
+ defaults:
+ run: { working-directory: backend }
+ steps:
+ - uses: actions/checkout@v4
+ - uses: astral-sh/setup-uv@v5
+ - run: uv sync
+ - run: uv run ruff check .
+ - run: uv run ruff format --check .
+ - run: uv run mypy app
+ - run: uv run pytest
+
+ frontend:
+ runs-on: ubuntu-latest
+ defaults:
+ run: { working-directory: frontend }
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with: { node-version: 22, cache: npm, cache-dependency-path: frontend/package-lock.json }
+ - run: npm ci
+ - run: npm run lint
+ - run: npx prettier --check src
+ - run: npx tsc -b --noEmit || npx tsc --noEmit
+ - run: npm test -- --run
+
+ e2e:
+ runs-on: ubuntu-latest
+ needs: [backend, frontend]
+ steps:
+ - uses: actions/checkout@v4
+ - uses: astral-sh/setup-uv@v5
+ - uses: actions/setup-node@v4
+ with: { node-version: 22, cache: npm, cache-dependency-path: frontend/package-lock.json }
+ - run: cd backend && uv sync && (uv run uvicorn app.main:app --port 8000 &)
+ - run: cd frontend && npm ci && npx playwright install --with-deps chromium
+ - run: cd frontend && npm run test:e2e
diff --git a/.gitignore b/.gitignore
index ba31e72..af9b419 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,3 +38,18 @@ vignettes/*.pdf
# R Environment Variables
.Renviron
Arena3Dweb.Rproj
+
+# Python
+backend/.venv/
+__pycache__/
+*.pyc
+.mypy_cache/
+.ruff_cache/
+
+# Node
+frontend/node_modules/
+frontend/dist/
+
+# Editors
+.DS_Store
+.playwright-cli/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..44d95eb
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,24 @@
+repos:
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.15.0
+ hooks:
+ - id: ruff-check
+ args: [--fix]
+ files: ^backend/
+ - id: ruff-format
+ files: ^backend/
+
+ - repo: local
+ hooks:
+ - id: eslint
+ name: eslint
+ language: system
+ entry: bash -c 'cd frontend && npx eslint src'
+ files: ^frontend/src/.*\.ts$
+ pass_filenames: false
+ - id: prettier
+ name: prettier
+ language: system
+ entry: bash -c 'cd frontend && npx prettier --check src'
+ files: ^frontend/src/
+ pass_filenames: false
diff --git a/.serena/.gitignore b/.serena/.gitignore
new file mode 100644
index 0000000..2e510af
--- /dev/null
+++ b/.serena/.gitignore
@@ -0,0 +1,2 @@
+/cache
+/project.local.yml
diff --git a/.serena/project.yml b/.serena/project.yml
new file mode 100644
index 0000000..acca853
--- /dev/null
+++ b/.serena/project.yml
@@ -0,0 +1,137 @@
+# the name by which the project can be referenced within Serena
+project_name: "Arena3Dweb"
+
+
+# list of languages for which language servers are started; choose from:
+# al angular ansible bash clojure
+# cpp cpp_ccls crystal csharp csharp_omnisharp
+# dart elixir elm erlang fortran
+# fsharp go groovy haskell haxe
+# hlsl html java json julia
+# kotlin lean4 lua luau markdown
+# matlab msl nix ocaml pascal
+# perl php php_phpactor powershell python
+# python_jedi python_ty r rego ruby
+# ruby_solargraph rust scala scss solidity
+# svelte swift systemverilog terraform toml
+# typescript typescript_vts vue yaml zig
+# (This list may be outdated. For the current list, see values of Language enum here:
+# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
+# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
+# Note:
+# - For C, use cpp
+# - For JavaScript, use typescript
+# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
+# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
+# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
+# - For Free Pascal/Lazarus, use pascal
+# Special requirements:
+# Some languages require additional setup/installations.
+# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
+# When using multiple languages, the first language server that supports a given file will be used for that file.
+# The first language is the default language and the respective language server will be used as a fallback.
+# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
+# R dropped: legacy code being deleted through the v3 migration, and its LSP
+# (R `languageserver` package) isn't installed — its failure aborted the whole
+# language-server manager. Active dev is TS (frontend) + Python (backend).
+languages:
+- typescript
+- python
+
+# the encoding used by text files in the project
+# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
+encoding: "utf-8"
+
+# line ending convention to use when writing source files.
+# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
+# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
+line_ending:
+
+# The language backend to use for this project.
+# If not set, the global setting from serena_config.yml is used.
+# Valid values: LSP, JetBrains
+# Note: the backend is fixed at startup. If a project with a different backend
+# is activated post-init, an error will be returned.
+language_backend:
+
+# whether to use project's .gitignore files to ignore files
+ignore_all_files_in_gitignore: true
+
+# advanced configuration option allowing to configure language server-specific options.
+# Maps the language key to the options.
+# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
+# No documentation on options means no options are available.
+ls_specific_settings: {}
+
+# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos).
+# Paths can be absolute or relative to the project root.
+# Each folder is registered as an LSP workspace folder, enabling language servers to discover
+# symbols and references across package boundaries.
+# Currently supported for: TypeScript.
+# Example:
+# additional_workspace_folders:
+# - ../sibling-package
+# - ../shared-lib
+additional_workspace_folders: []
+
+# list of additional paths to ignore in this project.
+# Same syntax as gitignore, so you can use * and **.
+# Note: global ignored_paths from serena_config.yml are also applied additively.
+ignored_paths: []
+
+# whether the project is in read-only mode
+# If set to true, all editing tools will be disabled and attempts to use them will result in an error
+# Added on 2025-04-18
+read_only: false
+
+# list of tool names to exclude.
+# This extends the existing exclusions (e.g. from the global configuration)
+# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
+excluded_tools: []
+
+# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
+# This extends the existing inclusions (e.g. from the global configuration).
+# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
+included_optional_tools: []
+
+# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
+# This cannot be combined with non-empty excluded_tools or included_optional_tools.
+# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
+fixed_tools: []
+
+# list of mode names that are to be activated by default, overriding the setting in the global configuration.
+# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
+# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
+# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
+# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
+# for this project.
+# This setting can, in turn, be overridden by CLI parameters (--mode).
+# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
+default_modes:
+
+# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
+# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
+# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
+added_modes:
+
+# initial prompt for the project. It will always be given to the LLM upon activating the project
+# (contrary to the memories, which are loaded on demand).
+initial_prompt: ""
+
+# time budget (seconds) per tool call for the retrieval of additional symbol information
+# such as docstrings or parameter information.
+# This overrides the corresponding setting in the global configuration; see the documentation there.
+# If null or missing, use the setting from the global configuration.
+symbol_info_budget:
+
+# list of regex patterns which, when matched, mark a memory entry as read‑only.
+# Extends the list from the global configuration, merging the two lists.
+read_only_memory_patterns: []
+
+# list of regex patterns for memories to completely ignore.
+# Matching memories will not appear in list_memories or activate_project output
+# and cannot be accessed via read_memory or write_memory.
+# To access ignored memory files, use the read_file tool on the raw file path.
+# Extends the list from the global configuration, merging the two lists.
+# Example: ["_archive/.*", "_episodes/.*"]
+ignored_memory_patterns: []
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..1366145
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,85 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+Arena3D is a web application for interactive 3D visualization of multilayered networks: **FastAPI backend** (Python, `uv`) + **Vite / TypeScript / Three.js frontend** (npm).
+
+The app was migrated from R/Shiny to this stack. All R/Shiny source is gone; the migration history lives in:
+- **`SPEC.md`** — architecture decisions, chosen stack, design patterns, API contract, and rationale.
+- **`PLAN.md`** — phased implementation checklist (essentially complete).
+- **`MIGRATION.md`** — old R/Shiny file → new equivalent map (all rows done).
+
+## Rules for Agents
+
+- **Never push to remote.** Commit only when explicitly asked, one commit per feature.
+- **Always use the `token-saviour` skill** — and the tools/skills it routes to across its layers — wherever it makes sense.
+- **When a plan is active** (e.g. a `PLAN*.md` file): one feature per commit, tick the checkboxes as you go, and verify each feature at runtime before moving to the next.
+- **Before every commit**: run the build, lint, typecheck, and the relevant test suite (see commands below). Verify UI/UX changes at runtime with Playwright.
+- **Keep responses concise** — summarize rather than dumping full files, to stay within output token limits.
+
+## Running the App
+
+**Backend** (package management via `uv` — no manual venv/pip):
+```bash
+cd backend
+uv sync # installs deps + dev group into .venv
+uv run uvicorn app.main:app --reload # http://localhost:8000
+```
+
+**Frontend:**
+```bash
+cd frontend
+npm install
+npm run dev # http://localhost:5173 — /api proxied to localhost:8000
+```
+
+**Both together (Docker):**
+```bash
+docker-compose up
+```
+
+**Backend tests:**
+```bash
+cd backend && uv run pytest
+```
+
+**Frontend tests:**
+```bash
+cd frontend && npm test # Vitest unit tests
+cd frontend && npm run test:e2e # Playwright E2E
+```
+
+**Lint / format / typecheck:**
+```bash
+cd backend && uv run ruff check . && uv run ruff format . && uv run mypy app
+cd frontend && npm run lint && npm run format && npx tsc --noEmit
+```
+
+## Architecture Overview
+
+### Backend (`backend/app/`)
+Stateless FastAPI — the frontend holds all scene state; the server validates input and runs the graph algorithms.
+
+- `main.py` — app + router registration; `config.py` — constants (limits, palettes, scale targets) served at `GET /api/config`.
+- `models/` — Pydantic request/response models (`network`, `layout`, `topology`, `session`, `attributes`).
+- `routers/` — one per endpoint: `config`, `network` (TSV upload), `layout`, `topology`, `session` (import/export), `external` (token-shared sessions), `attributes` (node/edge attribute files).
+- `services/` — logic: `parser` (TSV parse/validate), `graph` (igraph construction + scopes), `layouts` (11 layout algos), `clustering` (4 community algos, optional layout step), `topology` (Degree / Clustering Coefficient / Betweenness), `session`, `attributes`.
+- Algorithms use **python-igraph** — same C core as R's igraph, so layouts/clustering/topology port 1:1.
+
+### Frontend (`frontend/src/`)
+- `main.ts` — entry point: fetch config → set up Three.js → mount canvas → wire panels + listeners → `animate()`. Exposes `window.__arena = { ctx, history }` as a Playwright test hook (the WebGL canvas is opaque to the a11y tree).
+- `three/` — `Scene`, `Layer`, `Node`, `Edge` classes on npm `three` r170; `runtime.ts` holds the shared mutable `ctx` (replaces v2 ambient globals); `constants.ts` static geometry/palette constants.
+- `actions/` — one module per domain (`network`, `layout`, `layer`, `node`, `edge`, `labels`, `themes`, `screen`, `canvas_controls`, `nav_controls`, `drag_controls`, `right_click_menu`, `session`). These mutate the object model + `ctx`.
+- `commands/` — `Command` interface + `CommandHistory` (undo/redo); `scene.ts` holds the concrete commands. Every scene mutation that should be undoable routes through a command.
+- `ui/` — one module per navbar panel (`home`, `file`, `layouts`, `scene`, `layer`, `node`, `edge`, `data`, `fps`, `help`), each filling its `#panel-*` pane with Bootstrap DOM and wiring controls to `actions`/`commands`.
+- `bus/` — typed `EventBus` singleton (returns unsubscribe fns); `store/` — typed `AppState` store. Together they replace the old Shiny input/output sync.
+- `api/client.ts` — hand-written typed client mirroring the Pydantic models.
+
+### Communication
+- **Frontend → backend**: `api.*` calls to `/api/*` (network parse, layout, topology, session, attributes).
+- **Within frontend**: components emit/subscribe on the `EventBus` and read/write the `store`; the render loop reacts to `ctx` flags (`renderInterLayerEdgesFlag`, label flags, etc.).
+
+### Network Data Model
+- Networks upload as TSV with mandatory columns `SourceNode`, `SourceLayer`, `TargetNode`, `TargetLayer` (optional: `Weight`, `Channel`, edge color columns).
+- Node/edge attribute files add per-node color/size/url/description and per-edge (optionally per-channel) color.
+- Sessions export/import as JSON with full node/edge/layer/scene state. `POST /api/external` returns a token URL so another app can hand off a session.
diff --git a/Arena3Dweb.Rproj b/Arena3Dweb.Rproj
deleted file mode 100644
index 8e3c2eb..0000000
--- a/Arena3Dweb.Rproj
+++ /dev/null
@@ -1,13 +0,0 @@
-Version: 1.0
-
-RestoreWorkspace: Default
-SaveWorkspace: Default
-AlwaysSaveHistory: Default
-
-EnableCodeIndexing: Yes
-UseSpacesForTab: Yes
-NumSpacesForTab: 2
-Encoding: UTF-8
-
-RnwWeave: Sweave
-LaTeX: pdfLaTeX
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..f416cf2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,34 @@
+# Changelog
+
+## [3.0.0] - 2026-07-08
+
+Full rewrite: migrated from R/Shiny to a **FastAPI (Python) backend + Vite / TypeScript / Three.js frontend**. Algorithms (layouts, clustering, topology) ported 1:1 via python-igraph.
+
+### Added
+
+- **Satellite view** — minimap overview of the scene (hidden layers excluded).
+- **Command-pattern undo/redo** — granular history for scene mutations.
+- **Token-shared sessions** — hand off a session to another app via `?session=` URL (`POST /api/external`).
+- **Export Image** — button to export the scene as PNG.
+- Reproducible node scatter (seeded), 60 FPS render loop as default, redesigned UI (side-drawer panels).
+
+### Changed
+
+- **Zoom to cursor** — mouse-wheel zoom anchors at the pointer instead of the scene center.
+
+### Fixed
+
+- **Undraggable layers when zoomed in** — orthographic drag ray rejected planes sitting behind its origin; layers froze at high zoom despite hover working.
+
+## [2.0.0] - 2023-04-13
+
+Initial public release — Arena3D, an **R/Shiny + Three.js** web app for interactive 3D visualization of multilayered networks.
+
+- Interactive 3D rendering of multilayer networks (Three.js via R/Shiny).
+- TSV network upload; layer / node / edge styling.
+- Layout algorithms, community clustering, and topology metrics on the graph.
+- Session import / export.
+
+## [1.0.0] - 2008-11-28
+
+The now retired legacy Arena3D standalone desktop application written in Java.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..709f46d
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,6 @@
+# CLAUDE.md
+
+See **[AGENTS.md](AGENTS.md)**.
+
+Guidance for coding agents working in this repository lives there, so that every agent — Claude Code,
+Codex, and others — reads the same file.
diff --git a/Dockerfile b/Dockerfile
index 80a142c..21d0b32 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,23 +1,20 @@
-FROM r-base
-#Description of the image
-LABEL description="Arena3Dweb docker image"
-LABEL maintainer="Fotis Baltoumas"
-LABEL version="2.0"
-#set the timezone
-ENV TZ Europe/Athens
-#copy the Rprofile.site file and containing the shiny port and CRAN repo url
-COPY Rprofile.site /usr/lib/R/etc/
+# Production image: nginx serves static Vite build, proxies /api to uvicorn.
+FROM node:22-slim AS frontend-build
+WORKDIR /build
+COPY frontend/package.json frontend/package-lock.json ./
+RUN npm ci
+COPY frontend/ ./
+RUN npm run build
-# install required R packages
-RUN R -e 'install.packages(c("shiny","shinyjs","shinythemes","igraph","RColorBrewer","jsonlite","tidyr"))'
+FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
+WORKDIR /app
+RUN apt-get update && apt-get install -y --no-install-recommends nginx && rm -rf /var/lib/apt/lists/*
+COPY backend/pyproject.toml backend/uv.lock ./
+RUN uv sync --frozen --no-group dev
+COPY backend/app ./app
+COPY nginx/nginx.conf /etc/nginx/nginx.conf
+COPY --from=frontend-build /build/dist /usr/share/nginx/html
-# copy the Arena3Dweb directory to the VM
-COPY ./Arena3Dweb/ /root/Arena3DWeb/
-
-RUN R -e 'install.packages(c("DT", "fst"))'
-
-#expose the shiny port
-EXPOSE 3838
-
-#set the default command to run, ie "R"
-CMD ["R", "-e", "shiny::runApp('/root/Arena3DWeb/')"]
+EXPOSE 8080
+# ponytail: sh -c instead of a supervisor; two processes, container dies if nginx dies
+CMD ["sh", "-c", "uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 & exec nginx -g 'daemon off;'"]
diff --git a/README.md b/README.md
index 149c937..d7720aa 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
-[](https://hub.docker.com/r/pavlopouloslab/arena3dweb)
-[](https://www.arena3d.org)
-[](https://github.com/PavlopoulosLab/Arena3Dweb)
+[](https://hub.docker.com/r/pavlopouloslab/arena3d)
+[](https://www.arena3d.org)
+[](https://github.com/pavlopoulos-lab/arena3d)
-# Arena3Dweb
+# Arena3D
-> Fully interactive, dependency-free 3D visualization of multilayered networks.
+> Interactive 3D visualization of multilayered networks.
---
@@ -15,31 +15,31 @@
1. [Overview](#overview)
2. [Key Features](#key-features)
3. [Getting Started](#getting-started)
-
* [Online Demo](#online-demo)
* [Local Installation](#local-installation)
4. [Example Data](#example-data)
5. [Usage](#usage)
-6. [Citing Arena3Dweb ](#citing-arena3dweb)
+6. [Citing Arena3D](#citing-arena3d)
7. [License](#license)
---
## 📝 Overview
-Arena3Dweb is a web application built with R/Shiny and JavaScript for visualizing multilayered graphs in 3D space, without external dependencies. Integrate multiple networks into a single scene, explore intra- and inter-layer connections, and manipulate the view in real time, including VR mode.
+Arena3D is a web application for visualizing multilayered graphs in 3D space. It pairs a **FastAPI** backend (Python + python-igraph for layouts, clustering, and topology metrics) with a **Vite / TypeScript / Three.js** frontend. Use it to integrate multiple networks into a single scene, explore intra- and inter-layer connections, and manipulate the view in real time.
---
## 🚀 Key Features
-* **Multi-layer integration**: Load and combine multiple network layers with cross-layer edges.
-* **3D Interactivity**: Translate, rotate, and scale the scene or individual layers; VR mode supported.
-* **Rich layouts & clustering**: Apply and customize layouts (force-directed, circular, grid) and clustering algorithms on selected layers.
-* **Dynamic styling**: Adjust node size, color, and edge colors on-the-fly to highlight important paths or topological features.
-* **Themes & export**: Choose from three premade themes; export/import sessions in JSON; download high-resolution snapshots.
+* **Multilayer integration**: Load and combine multiple network layers with cross-layer edges.
+* **3D interactivity**: Translate, rotate, and scale the scene or individual layers.
+* **Rich layouts & clustering**: Apply and customize 11 layouts (force-directed, circular, grid, …) and 4 clustering algorithms on selected layers.
+* **Dynamic styling**: Adjust node size, color, and edge colors on the fly to highlight important paths or topological features; upload node/edge attribute files.
+* **Themes & export**: Choose from predefined themes; export/import sessions in JSON.
+* **Undo/redo**: Every scene mutation is undoable.
* **Graph support**: Handle weighted/unweighted, directed/undirected, and multi-channel graphs up to 10,000 edges (online); unlimited locally.
-* **API access**: Open networks directly from external applications via REST endpoint.
+* **API access**: Open networks directly from external applications via the REST API.
---
@@ -54,44 +54,42 @@ Access the live app at: [https://www.arena3d.org](https://www.arena3d.org)
#### Docker (Recommended)
```bash
-# Pull the Docker image
-docker pull pavlopouloslab/arena3dweb
-# Run the container (port 3838)
-docker run -p 3838:3838 pavlopouloslab/arena3dweb
+git clone https://github.com/pavlopoulos-lab/arena3d.git
+cd Arena3D
+docker-compose up # builds + runs backend (8000) and frontend (5173)
```
-#### From Source
+For a single production image (nginx serving the built frontend + Uvicorn):
+
+```bash
+docker build -t arena3d .
+docker run -p 8080:8080 arena3d # http://localhost:8080
+```
-1. Clone the repo:
+#### From Source
- ```bash
- ```
+**Backend** (Python, managed with [`uv`](https://docs.astral.sh/uv/)):
-git clone [https://github.com/PavlopoulosLab/Arena3Dweb.git](https://github.com/PavlopoulosLab/Arena3Dweb.git)
-cd Arena3Dweb
+```bash
+cd backend
+uv sync
+uv run uvicorn app.main:app --reload # http://localhost:8000
+```
-````
-2. Install R (>=4.0) and RStudio.
-3. Install required R packages:
- ```r
-install.packages(c(
- "shiny", "shinyjs", "shinythemes",
- "igraph", "RColorBrewer",
- "jsonlite", "tidyr"
-))
-````
+**Frontend** (Node + npm):
-4. Open **Arena3Dweb.Rproj** in RStudio.
-5. Open **server.R**, select **Run External**, then click **Run App**.
+```bash
+cd frontend
+npm install
+npm run dev # http://localhost:5173 (/api proxied to :8000)
+```
---
## 📂 Example Data
-Find downloadable example files in the `www/data/` folder:
-
-* **TSV** files for "Upload Network" format
-* **JSON** files for "Load Session" format
+* One of the bundled example networks (`frontend/public/data/figure2A_data.tsv`) loads via the **Load Example** button in the File panel.
+* Backend test fixtures live in `backend/tests/fixtures/` (TSV networks + JSON sessions).
---
@@ -99,22 +97,24 @@ Find downloadable example files in the `www/data/` folder:
1. **Upload** network files or load a saved session.
2. **Select** layers to apply layouts or clustering.
-3. **Interact** with the 3D scene: pan, zoom, rotate, enter VR.
-4. **Customize** node/edge styling and themes.
-5. **Export** snapshots or session JSON for later reuse.
+3. **Interact** with the 3D scene: pan, zoom, rotate, drag layers.
+4. **Customize** node/edge styling and themes; undo/redo any change.
+5. **Export** session JSON for later reuse.
---
-## 📚 Citing Arena3Dweb
+## 📚 Citing Arena3D
+
+The following publications refer to Arena3D under its former name, Arena3Dweb.
-* **Arena3Dweb : interactive 3D visualization of multilayered networks**
- Karatzas E., Baltoumas F.A., Panayiotou N.A., Schneider R., Pavlopoulos G.A.
- *Nucleic Acids Research*, 2021;49(W1)\:W36–W45.
+* **Arena3Dweb : interactive 3D visualization of multilayered networks**
+ Karatzas E., Baltoumas F.A., Panayiotou N.A., Schneider R., Pavlopoulos G.A.
+ *Nucleic Acids Research*, 2021;49(W1):W36-W45.
doi: [10.1093/nar/gkab278](https://doi.org/10.1093/nar/gkab278)
-* **Arena3Dweb : interactive 3D visualization of multilayered networks supporting multiple directional information channels, clustering analysis and application integration**
- Kokoli M., Karatzas E., Baltoumas F.A., Schneider R., Pafilis E., Paragkamian S., Doncheva N.T., Jensen L.J., Pavlopoulos G.A.
- *NAR Genomics and Bioinformatics*, 2022;5(2)\:lqad053.
+* **Arena3Dweb : interactive 3D visualization of multilayered networks supporting multiple directional information channels, clustering analysis and application integration**
+ Kokoli M., Karatzas E., Baltoumas F.A., Schneider R., Pafilis E., Paragkamian S., Doncheva N.T., Jensen L.J., Pavlopoulos G.A.
+ *NAR Genomics and Bioinformatics*, 2022;5(2):lqad053.
doi: [10.1093/nargab/lqad053](https://doi.org/10.1093/nargab/lqad053)
---
diff --git a/Rprofile.site b/Rprofile.site
deleted file mode 100644
index fc51453..0000000
--- a/Rprofile.site
+++ /dev/null
@@ -1,8 +0,0 @@
-local({
-options(shiny.port = 3838, shiny.host = "0.0.0.0")
-})
-
-local({r <- getOption("repos")
- r["CRAN"] <- "https://cran.r-project.org"
- options(repos=r)
-})
diff --git a/backend/app/__init__.py b/backend/app/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/app/config.py b/backend/app/config.py
new file mode 100644
index 0000000..28051e8
--- /dev/null
+++ b/backend/app/config.py
@@ -0,0 +1,78 @@
+"""Application constants — ports config/server_variables.R + global_variables.R.
+
+RColorBrewer palettes are inlined as hex (Set3 / Set1), matching v2 output exactly.
+"""
+
+import os
+
+# Input validation
+MANDATORY_NETWORK_COLUMNS = ["SourceNode", "SourceLayer", "TargetNode", "TargetLayer"]
+OPTIONAL_NETWORK_COLUMNS = ["Channel", "Weight"]
+MANDATORY_JSON_OBJECTS = ["layers", "nodes", "edges"]
+OPTIONAL_JSON_OBJECTS = ["scene", "universalLabelColor", "direction", "edgeOpacityByWeight"]
+MANDATORY_JSON_NODE_COLUMNS = ["name", "layer"]
+MANDATORY_JSON_EDGE_COLUMNS = ["src", "trg"]
+# DoS fix: MAX_EDGES/MAX_NODES used to be enforced only by the TSV-upload
+# parser, leaving /api/layout, /api/topology, /api/session/import and
+# /api/external free to accept unbounded graphs. Env-overridable so
+# deployments can size limits to their hardware without a code change
+# (docker-compose.yml sets these for local dev/prod).
+MAX_EDGES = int(os.environ.get("ARENA_MAX_EDGES", "10000"))
+MAX_NODES = int(os.environ.get("ARENA_MAX_NODES", "20000"))
+MAX_CHANNELS = 9
+MAX_LAYERS = 18
+# DoS fix: caps request-body size for TSV/JSON uploads (bare uvicorn has no
+# built-in limit; nginx's client_max_body_size only covers the prod Docker
+# image, not local/dev deployments).
+MAX_UPLOAD_BYTES = int(os.environ.get("ARENA_MAX_UPLOAD_BYTES", str(20 * 1024 * 1024)))
+
+# Topology scaling
+DEFAULT_MAP_VALUE = 0.3
+TARGET_NODE_SCALE_MIN = 0.5
+TARGET_NODE_SCALE_MAX = 2.5
+TOPOLOGY_METRICS = ["Degree", "Clustering Coefficient", "Betweenness Centrality"]
+
+# Layouts
+NO_EDGE_LAYOUTS = ["Circle", "Grid", "Random"]
+
+# Themes — RColorBrewer::brewer.pal(12, "Set3")
+_SET3 = [
+ "#8DD3C7",
+ "#FFFFB3",
+ "#BEBADA",
+ "#FB8072",
+ "#80B1D3",
+ "#FDB462",
+ "#B3DE69",
+ "#FCCDE5",
+ "#D9D9D9",
+ "#BC80BD",
+ "#CCEBC5",
+ "#FFED6F",
+]
+# brewer.pal(9, "Set1")
+_SET1 = [
+ "#E41A1C",
+ "#377EB8",
+ "#4DAF4A",
+ "#984EA3",
+ "#FF7F00",
+ "#FFFF33",
+ "#A65628",
+ "#F781BF",
+ "#999999",
+]
+NODE_COLORS = _SET3 + _SET3[:6] # 18 layers
+EDGE_DEFAULT_COLOR = "#CFCFCF"
+CHANNEL_COLORS_LIGHT = _SET3[:MAX_CHANNELS]
+CHANNEL_COLORS_DARK = _SET1
+
+# UI defaults
+FLOOR_DEFAULT_COLOR = "#777777"
+FLOOR_DEFAULT_WIDTH = 1000
+
+# VR
+VR_DOWNSCALE_FACTOR = 300
+
+# Ephemeral storage for external-API sessions + VR output (SPEC §5)
+TMP_PATH = "tmp/"
diff --git a/backend/app/main.py b/backend/app/main.py
new file mode 100644
index 0000000..62bf356
--- /dev/null
+++ b/backend/app/main.py
@@ -0,0 +1,46 @@
+from fastapi import FastAPI, Request
+from fastapi.responses import JSONResponse
+
+from app import config
+from app.routers import attributes as attributes_router
+from app.routers import config as config_router
+from app.routers import external as external_router
+from app.routers import layout as layout_router
+from app.routers import network as network_router
+from app.routers import session as session_router
+from app.routers import topology as topology_router
+
+app = FastAPI(title="Arena3D API", version="3.0.0")
+app.include_router(config_router.router)
+app.include_router(network_router.router)
+app.include_router(layout_router.router)
+app.include_router(topology_router.router)
+app.include_router(session_router.router)
+app.include_router(external_router.router)
+app.include_router(attributes_router.router)
+
+
+# Defense-in-depth: JSON body endpoints (layout/topology/external) validate
+# node/edge counts only *after* Starlette buffers + Pydantic parses the full
+# body, so an oversized payload is fully read into memory before rejection.
+# Reject early using the declared Content-Length. This is a backstop for
+# direct/non-nginx exposure (e.g. docker-compose dev) — the prod deploy's
+# nginx client_max_body_size already enforces this at the edge. A request
+# without Content-Length (chunked transfer) isn't caught here; it still hits
+# the per-endpoint UploadFile checks for the file-upload routes.
+@app.middleware("http")
+async def limit_body_size(request: Request, call_next): # type: ignore[no-untyped-def]
+ content_length = request.headers.get("content-length")
+ if content_length is not None:
+ try:
+ too_large = int(content_length) > config.MAX_UPLOAD_BYTES
+ except ValueError:
+ too_large = False # malformed header — let normal parsing reject it
+ if too_large:
+ return JSONResponse(status_code=413, content={"detail": "Payload too large."})
+ return await call_next(request)
+
+
+@app.get("/api/health")
+async def health() -> dict[str, str]:
+ return {"status": "ok"}
diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/app/models/attributes.py b/backend/app/models/attributes.py
new file mode 100644
index 0000000..7522628
--- /dev/null
+++ b/backend/app/models/attributes.py
@@ -0,0 +1,17 @@
+"""Node/edge attribute-file rows — ports functions/input.R attribute uploads."""
+
+from pydantic import BaseModel
+
+
+class NodeAttributeRow(BaseModel):
+ node_layer: str # "Node_Layer" key, as v2 nodeAttributes$NodeLayer
+ color: str | None = None
+ size: float | None = None
+ url: str | None = None
+ description: str | None = None
+
+
+class EdgeAttributeRow(BaseModel):
+ edge_pair: str # "Source_Layer---Target_Layer", as v2 edgeAttributes$EdgePair
+ color: str
+ channel: str | None = None
diff --git a/backend/app/models/layout.py b/backend/app/models/layout.py
new file mode 100644
index 0000000..c757c7d
--- /dev/null
+++ b/backend/app/models/layout.py
@@ -0,0 +1,38 @@
+"""Request/response schemas for POST /api/layout."""
+
+from typing import Literal
+
+from pydantic import BaseModel, Field
+
+from app import config
+from app.models.network import EdgeModel, NodeModel
+
+Scope = Literal["perLayer", "allLayers", "nodesPerLayers"]
+
+
+class ClusteringOptions(BaseModel):
+ algorithm: str # Louvain | Walktrap | Fast Greedy | Label Propagation
+ local_layout: str # layout name for members within a cluster
+
+
+class LayoutRequest(BaseModel):
+ # Unauthenticated DoS fix: this endpoint is reachable directly (not just via
+ # the TSV-upload flow), so the MAX_EDGES/MAX_NODES cap must be enforced here
+ # too, or a caller can force an O(V*E) layout algorithm over an arbitrarily
+ # large graph.
+ nodes: list[NodeModel] = Field(max_length=config.MAX_NODES)
+ edges: list[EdgeModel] = Field(max_length=config.MAX_EDGES)
+ algorithm: str
+ scope: Scope = "perLayer"
+ selected_layers: list[str]
+ selected_nodes: list[str] | None = None # Node_Layer ids, for nodesPerLayers
+ selected_channels: list[str] | None = None
+ clustering: ClusteringOptions | None = None
+ seed: int = 123 # v2 uses set.seed(123)
+
+
+class LayoutResponse(BaseModel):
+ # Node_Layer id -> [y, z] within the layer plane (x is fixed by the layer)
+ positions: dict[str, tuple[float, float]]
+ # Node_Layer id -> cluster id, only when clustering was requested
+ clusters: dict[str, int] | None = None
diff --git a/backend/app/models/network.py b/backend/app/models/network.py
new file mode 100644
index 0000000..4fc27ed
--- /dev/null
+++ b/backend/app/models/network.py
@@ -0,0 +1,33 @@
+"""Pydantic schemas for a parsed network — response of POST /api/network."""
+
+from pydantic import BaseModel
+
+
+class NodeModel(BaseModel):
+ id: str # "Node_Layer"
+ label: str
+ layer: str
+
+
+class EdgeModel(BaseModel):
+ src: str # "SourceNode_Layer"
+ trg: str # "TargetNode_Layer"
+ source_node: str
+ source_layer: str
+ target_node: str
+ target_layer: str
+ # Non-positive/non-finite scaled_weight is sanitized in build_graph rather
+ # than rejected here: a Field(allow_inf_nan=False) constraint makes FastAPI's
+ # 422 handler try to echo the inf input and itself 500. The TSV path already
+ # rejects non-finite weights at parse time (services/parser.py).
+ weight: float
+ scaled_weight: float
+ channel: str | None = None
+
+
+class NetworkModel(BaseModel):
+ nodes: list[NodeModel]
+ edges: list[EdgeModel]
+ layers: list[str]
+ channels: list[str]
+ warnings: list[str] = []
diff --git a/backend/app/models/session.py b/backend/app/models/session.py
new file mode 100644
index 0000000..d3a400d
--- /dev/null
+++ b/backend/app/models/session.py
@@ -0,0 +1,25 @@
+"""Session schemas. JSON is loose/stringly-typed (ports v2 export format), so the
+import model is permissive — validation + defaulting lives in services/session.py."""
+
+from typing import Any
+
+from pydantic import BaseModel
+
+
+class SessionImportResponse(BaseModel):
+ """Normalized session returned to the frontend (ports parseUploadedJSON output)."""
+
+ scene: dict[str, Any]
+ layers: list[dict[str, Any]]
+ nodes: list[dict[str, Any]]
+ edges: list[dict[str, Any]]
+ universalLabelColor: str
+ direction: bool
+ edgeOpacityByWeight: bool
+ scramble_nodes: bool
+ warnings: list[str] = []
+
+
+class ExternalCreateResponse(BaseModel):
+ token: str
+ url: str
diff --git a/backend/app/models/topology.py b/backend/app/models/topology.py
new file mode 100644
index 0000000..a1f82bb
--- /dev/null
+++ b/backend/app/models/topology.py
@@ -0,0 +1,30 @@
+"""Request/response schemas for POST /api/topology."""
+
+from typing import Literal
+
+from pydantic import BaseModel, Field
+
+from app import config
+from app.models.network import EdgeModel, NodeModel
+
+Scope = Literal["perLayer", "allLayers", "nodesPerLayers"]
+
+
+class TopologyRequest(BaseModel):
+ # Same unauthenticated-DoS fix as LayoutRequest — Betweenness Centrality is
+ # O(V*E) and this endpoint had no size limit of its own.
+ nodes: list[NodeModel] = Field(max_length=config.MAX_NODES)
+ edges: list[EdgeModel] = Field(max_length=config.MAX_EDGES)
+ metric: str # Degree | Clustering Coefficient | Betweenness Centrality
+ scope: Scope = "perLayer"
+ selected_layers: list[str]
+ selected_nodes: list[str] | None = None
+ selected_channels: list[str] | None = None
+ directed: bool = False # v2 edgeDirectionToggle (affects betweenness)
+
+
+class TopologyResponse(BaseModel):
+ # Node_Layer id -> node scale mapped into [0.5, 2.5]
+ scales: dict[str, float]
+ # Node_Layer id -> raw metric value (for the View Data table)
+ raw: dict[str, float]
diff --git a/backend/app/routers/__init__.py b/backend/app/routers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/app/routers/attributes.py b/backend/app/routers/attributes.py
new file mode 100644
index 0000000..a289758
--- /dev/null
+++ b/backend/app/routers/attributes.py
@@ -0,0 +1,40 @@
+"""POST /api/attributes/{nodes,edges} — attribute-file TSV uploads.
+
+Replaces handleInput{Node,Edge}AttributeFileUpload() (functions/input.R).
+"""
+
+from fastapi import APIRouter, HTTPException, UploadFile
+
+from app import config
+from app.models.attributes import EdgeAttributeRow, NodeAttributeRow
+from app.services.attributes import parse_edge_attributes_tsv, parse_node_attributes_tsv
+from app.services.parser import NetworkValidationError
+
+router = APIRouter()
+
+
+async def _read_capped(file: UploadFile) -> str:
+ """DoS fix: bound upload size in-app, since bare uvicorn (no nginx in
+ front, e.g. local/dev) enforces no request-body limit of its own."""
+ raw_bytes = await file.read()
+ if len(raw_bytes) > config.MAX_UPLOAD_BYTES:
+ raise HTTPException(status_code=413, detail="File too large.")
+ return raw_bytes.decode("utf-8", errors="replace")
+
+
+@router.post("/api/attributes/nodes", response_model=list[NodeAttributeRow])
+async def upload_node_attributes(file: UploadFile) -> list[NodeAttributeRow]:
+ raw = await _read_capped(file)
+ try:
+ return parse_node_attributes_tsv(raw)
+ except NetworkValidationError as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
+
+
+@router.post("/api/attributes/edges", response_model=list[EdgeAttributeRow])
+async def upload_edge_attributes(file: UploadFile) -> list[EdgeAttributeRow]:
+ raw = await _read_capped(file)
+ try:
+ return parse_edge_attributes_tsv(raw)
+ except NetworkValidationError as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
diff --git a/backend/app/routers/config.py b/backend/app/routers/config.py
new file mode 100644
index 0000000..11fe128
--- /dev/null
+++ b/backend/app/routers/config.py
@@ -0,0 +1,26 @@
+"""GET /api/config — replaces handler_initializeGlobals push (functions/init.R)."""
+
+from typing import Any
+
+from fastapi import APIRouter
+
+from app import config
+
+router = APIRouter()
+
+
+@router.get("/api/config")
+async def get_config() -> dict[str, Any]:
+ return {
+ "max_edges": config.MAX_EDGES,
+ "max_channels": config.MAX_CHANNELS,
+ "max_layers": config.MAX_LAYERS,
+ "edge_default_color": config.EDGE_DEFAULT_COLOR,
+ "channel_colors_dark": config.CHANNEL_COLORS_DARK,
+ "channel_colors_light": config.CHANNEL_COLORS_LIGHT,
+ "node_colors": config.NODE_COLORS,
+ "floor_default_color": config.FLOOR_DEFAULT_COLOR,
+ "floor_default_width": config.FLOOR_DEFAULT_WIDTH,
+ "topology_metrics": config.TOPOLOGY_METRICS,
+ "no_edge_layouts": config.NO_EDGE_LAYOUTS,
+ }
diff --git a/backend/app/routers/external.py b/backend/app/routers/external.py
new file mode 100644
index 0000000..0a163e0
--- /dev/null
+++ b/backend/app/routers/external.py
@@ -0,0 +1,110 @@
+"""External API — ports resolveAPI(). A caller POSTs a session; we store it under a
+random token in tmp/ (TTL-swept) and hand back a self-contained URL. GET resolves the
+token back to a normalized session, same as a file import.
+
+Token-in-file (not base64-in-URL) because MAX_EDGES=10k sessions blow past URL limits
+(SPEC §5). These tmp files are the one deliberate exception to the stateless server.
+"""
+
+import json
+import os
+import secrets
+import time
+from typing import Any
+
+from fastapi import APIRouter, HTTPException
+
+from app import config
+from app.models.session import ExternalCreateResponse, SessionImportResponse
+from app.services.session import SessionValidationError, normalize_session
+
+router = APIRouter()
+
+# Handoff tokens are consumed within minutes of creation, so a short TTL keeps
+# steady-state storage tiny. MAX_TOKENS is the hard disk-fill DoS guard (H2):
+# combined with the per-request MAX_UPLOAD_BYTES cap it bounds tmp/ to at most
+# MAX_TOKENS * MAX_UPLOAD_BYTES regardless of request volume.
+TTL_SECONDS = 60 * 60
+MAX_TOKENS = 1000
+
+
+def _session_files() -> list[str]:
+ if not os.path.isdir(config.TMP_PATH):
+ return []
+ return [
+ os.path.join(config.TMP_PATH, n) for n in os.listdir(config.TMP_PATH) if n.endswith(".json")
+ ]
+
+
+def _sweep() -> None:
+ """Delete stored sessions older than the TTL (ports the tmp cleanup intent)."""
+ cutoff = time.time() - TTL_SECONDS
+ for path in _session_files():
+ # A concurrent sweep/resolve may remove or stat the file between the
+ # listdir and here; skip rather than 500 the request.
+ try:
+ if os.path.getmtime(path) < cutoff:
+ os.remove(path)
+ except OSError:
+ continue
+
+
+def _enforce_cap() -> None:
+ """DoS guard (H2): before a new write, evict oldest sessions so at most
+ MAX_TOKENS-1 remain. Evict (not reject) so an attacker can't deny the
+ feature to legitimate callers by keeping the store full.
+
+ ponytail: TOCTOU under concurrent creates can briefly exceed the cap by the
+ concurrency count; the bound is MAX_TOKENS + in-flight, which is fine.
+ """
+ paths = _session_files()
+ if len(paths) < MAX_TOKENS:
+ return
+ dated: list[tuple[float, str]] = []
+ for path in paths:
+ try:
+ dated.append((os.path.getmtime(path), path))
+ except OSError:
+ continue
+ dated.sort() # oldest first
+ for _, path in dated[: len(dated) - MAX_TOKENS + 1]:
+ try:
+ os.remove(path)
+ except OSError:
+ continue
+
+
+@router.post("/api/external", response_model=ExternalCreateResponse)
+async def create_external(session: dict[str, Any]) -> ExternalCreateResponse:
+ # DoS fix: validate (incl. MAX_NODES/MAX_EDGES) before anything touches
+ # disk — this endpoint used to write any payload straight to disk
+ # unvalidated, letting a caller fill server storage with oversized sessions.
+ try:
+ normalize_session(session)
+ except SessionValidationError as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
+ os.makedirs(config.TMP_PATH, exist_ok=True)
+ _sweep()
+ _enforce_cap()
+ token = secrets.token_urlsafe(16)
+ with open(os.path.join(config.TMP_PATH, f"{token}.json"), "w") as fh:
+ json.dump(session, fh)
+ # read per request so redeploys / tests can change it without a restart
+ base_url = os.environ.get("ARENA_PUBLIC_URL", "")
+ return ExternalCreateResponse(token=token, url=f"{base_url}/?session={token}")
+
+
+@router.get("/api/external/{token}", response_model=SessionImportResponse)
+async def resolve_external(token: str) -> SessionImportResponse:
+ # guard against path traversal — tokens are urlsafe base64, never contain / or .
+ if not token.isalnum() and not all(c.isalnum() or c in "-_" for c in token):
+ raise HTTPException(status_code=400, detail="Invalid token.")
+ path = os.path.join(config.TMP_PATH, f"{token}.json")
+ if not os.path.isfile(path):
+ raise HTTPException(status_code=404, detail="Session not found or expired.")
+ with open(path) as fh:
+ data = json.load(fh)
+ try:
+ return SessionImportResponse(**normalize_session(data))
+ except SessionValidationError as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
diff --git a/backend/app/routers/layout.py b/backend/app/routers/layout.py
new file mode 100644
index 0000000..8d8a782
--- /dev/null
+++ b/backend/app/routers/layout.py
@@ -0,0 +1,18 @@
+"""POST /api/layout — replaces functions/igraph/layout.R."""
+
+from fastapi import APIRouter, HTTPException
+
+from app.models.layout import LayoutRequest, LayoutResponse
+from app.services.clustering import ClusteringError
+from app.services.layouts import LayoutError, compute_layout
+
+router = APIRouter()
+
+
+@router.post("/api/layout", response_model=LayoutResponse)
+async def run_layout(req: LayoutRequest) -> LayoutResponse:
+ try:
+ positions, clusters = compute_layout(req)
+ except (LayoutError, ClusteringError) as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
+ return LayoutResponse(positions=positions, clusters=clusters)
diff --git a/backend/app/routers/network.py b/backend/app/routers/network.py
new file mode 100644
index 0000000..d447771
--- /dev/null
+++ b/backend/app/routers/network.py
@@ -0,0 +1,23 @@
+"""POST /api/network — TSV upload. Replaces handleUploadNetwork() (functions/input.R)."""
+
+from fastapi import APIRouter, HTTPException, UploadFile
+
+from app import config
+from app.models.network import NetworkModel
+from app.services.parser import NetworkValidationError, parse_network_tsv
+
+router = APIRouter()
+
+
+@router.post("/api/network", response_model=NetworkModel)
+async def upload_network(file: UploadFile) -> NetworkModel:
+ raw_bytes = await file.read()
+ # DoS fix: reject oversized uploads before pandas parses the whole file
+ # into memory — MAX_EDGES was only checked *after* the parse completed.
+ if len(raw_bytes) > config.MAX_UPLOAD_BYTES:
+ raise HTTPException(status_code=413, detail="File too large.")
+ raw = raw_bytes.decode("utf-8", errors="replace")
+ try:
+ return parse_network_tsv(raw)
+ except NetworkValidationError as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
diff --git a/backend/app/routers/session.py b/backend/app/routers/session.py
new file mode 100644
index 0000000..a7945c5
--- /dev/null
+++ b/backend/app/routers/session.py
@@ -0,0 +1,42 @@
+"""Session import/export — ports loadNetworkFromJSONFilepath() and
+convertSessionToJSON() from functions/input.R.
+
+Export is a thin packaging endpoint: in the stateless design the frontend already
+holds the full scene state, so the server just returns it as a JSON download.
+"""
+
+import json
+from typing import Any
+
+from fastapi import APIRouter, HTTPException, UploadFile
+from fastapi.responses import JSONResponse
+
+from app import config
+from app.models.session import SessionImportResponse
+from app.services.session import SessionValidationError, normalize_session
+
+router = APIRouter()
+
+
+@router.post("/api/session/import", response_model=SessionImportResponse)
+async def import_session(file: UploadFile) -> SessionImportResponse:
+ raw_bytes = await file.read()
+ # DoS fix: same upload-size cap as /api/network — see routers/attributes.py.
+ if len(raw_bytes) > config.MAX_UPLOAD_BYTES:
+ raise HTTPException(status_code=413, detail="File too large.")
+ try:
+ data = json.loads(raw_bytes.decode("utf-8", errors="replace"))
+ except json.JSONDecodeError as e:
+ raise HTTPException(status_code=400, detail="Bad imported network file format.") from e
+ try:
+ return SessionImportResponse(**normalize_session(data))
+ except SessionValidationError as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
+
+
+@router.post("/api/session/export")
+async def export_session(session: dict[str, Any]) -> JSONResponse:
+ return JSONResponse(
+ content=session,
+ headers={"Content-Disposition": 'attachment; filename="network.json"'},
+ )
diff --git a/backend/app/routers/topology.py b/backend/app/routers/topology.py
new file mode 100644
index 0000000..1316d83
--- /dev/null
+++ b/backend/app/routers/topology.py
@@ -0,0 +1,17 @@
+"""POST /api/topology — replaces functions/igraph/topology.R."""
+
+from fastapi import APIRouter, HTTPException
+
+from app.models.topology import TopologyRequest, TopologyResponse
+from app.services.topology import TopologyError, compute_topology
+
+router = APIRouter()
+
+
+@router.post("/api/topology", response_model=TopologyResponse)
+async def run_topology(req: TopologyRequest) -> TopologyResponse:
+ try:
+ scales, raw = compute_topology(req)
+ except TopologyError as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
+ return TopologyResponse(scales=scales, raw=raw)
diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/app/services/attributes.py b/backend/app/services/attributes.py
new file mode 100644
index 0000000..5fb2a9c
--- /dev/null
+++ b/backend/app/services/attributes.py
@@ -0,0 +1,74 @@
+"""Attribute-file TSV parsing — ports functions/input.R
+handleInputNodeAttributeFileUpload() / handleInputEdgeAttributeFileUpload().
+"""
+
+from io import StringIO
+
+import pandas as pd
+
+from app.models.attributes import EdgeAttributeRow, NodeAttributeRow
+from app.services.parser import NetworkValidationError
+
+
+def _read(text: str) -> pd.DataFrame:
+ try:
+ df = pd.read_csv(StringIO(text), sep="\t", dtype=str)
+ except Exception as e:
+ raise NetworkValidationError("Bad attributes file format.") from e
+ return df
+
+
+def _cell(row: pd.Series, column: str) -> str | None:
+ value = str(row[column]).strip() if column in row.index and pd.notna(row[column]) else ""
+ return value or None
+
+
+def parse_node_attributes_tsv(text: str) -> list[NodeAttributeRow]:
+ df = _read(text)
+ if not {"Node", "Layer"}.issubset(df.columns):
+ raise NetworkValidationError(
+ "Your node attribute file must contain at least two columns: Node and Layer"
+ )
+ rows = []
+ for _, row in df.iterrows():
+ size = _cell(row, "Size")
+ try:
+ size_value = float(size) if size is not None else None
+ except ValueError as e:
+ raise NetworkValidationError("Make sure all Size values are numeric.") from e
+ rows.append(
+ NodeAttributeRow(
+ node_layer=f"{str(row['Node']).strip()}_{str(row['Layer']).strip()}",
+ color=_cell(row, "Color"),
+ size=size_value,
+ url=_cell(row, "Url"),
+ description=_cell(row, "Description"),
+ )
+ )
+ return rows
+
+
+def parse_edge_attributes_tsv(text: str) -> list[EdgeAttributeRow]:
+ df = _read(text)
+ mandatory = {"SourceNode", "SourceLayer", "TargetNode", "TargetLayer", "Color"}
+ if not mandatory.issubset(df.columns):
+ raise NetworkValidationError(
+ "Your edge attribute file must contain at least these five columns: "
+ "SourceNode, SourceLayer, TargetNode, TargetLayer and Color, "
+ "and optionally a Channel name."
+ )
+ rows = []
+ for _, row in df.iterrows():
+ source = f"{str(row['SourceNode']).strip()}_{str(row['SourceLayer']).strip()}"
+ target = f"{str(row['TargetNode']).strip()}_{str(row['TargetLayer']).strip()}"
+ color = _cell(row, "Color")
+ if color is None:
+ raise NetworkValidationError("Make sure all edge attribute rows have a Color.")
+ rows.append(
+ EdgeAttributeRow(
+ edge_pair=f"{source}---{target}",
+ color=color,
+ channel=_cell(row, "Channel"),
+ )
+ )
+ return rows
diff --git a/backend/app/services/clustering.py b/backend/app/services/clustering.py
new file mode 100644
index 0000000..59c8902
--- /dev/null
+++ b/backend/app/services/clustering.py
@@ -0,0 +1,148 @@
+"""Clustering + clustered layout — ports functions/igraph/cluster.R.
+
+Clustering is an optional step of a layout run (v2 calculateClusteredLayout):
+detect communities, lay the community super-nodes out with the global algorithm
+(repelled from the origin), lay each community's members out locally with the
+local algorithm, then translate members into their super-node's position.
+"""
+
+from collections.abc import Callable
+
+import igraph as ig
+
+REPELLING_FORCE = 3.0
+
+# DoS guard: the local layout pads a community with a tiny edge between every
+# pair of members to keep disconnected members spread out. That is O(n^2) edges
+# — for a large community (node/edge caps don't bound community size) it builds
+# hundreds of millions of tuples and can OOM the worker on a single crafted
+# /api/layout request. Above this size we pad with an O(n) virtual hub instead,
+# which still keeps members connected without the quadratic cost. Below it the
+# exact all-pairs behavior (v2 output) is preserved.
+MAX_ALL_PAIRS_MEMBERS = 1000
+
+CLUSTERING: dict[str, Callable[[ig.Graph], ig.VertexClustering]] = {
+ "Louvain": lambda gr: gr.community_multilevel(),
+ "Walktrap": lambda gr: gr.community_walktrap().as_clustering(),
+ "Fast Greedy": lambda gr: gr.community_fastgreedy().as_clustering(),
+ "Label Propagation": lambda gr: gr.community_label_propagation(),
+}
+
+
+class ClusteringError(ValueError):
+ """Unknown clustering algorithm."""
+
+
+def clustered_layout(
+ graph: ig.Graph,
+ cluster_algo: str,
+ global_layout: Callable[[ig.Graph], ig.Layout],
+ local_layout: Callable[[ig.Graph], ig.Layout],
+ is_kamada_kawai: bool,
+) -> tuple[dict[str, tuple[float, float]], dict[str, int]]:
+ """Return ({name: (y, z)}, {name: cluster_id}) for one subgraph.
+
+ Ports execute_strategy3_superNodes_strictPartitioning.
+ """
+ if cluster_algo not in CLUSTERING:
+ raise ClusteringError(f"Unknown clustering algorithm: {cluster_algo}")
+
+ names = graph.vs["name"]
+ membership = CLUSTERING[cluster_algo](graph).membership
+ groups: dict[int, list[str]] = {}
+ for name, cid in zip(names, membership, strict=True):
+ groups.setdefault(cid, []).append(name)
+
+ super_coords = _super_node_coords(graph, membership, groups, global_layout)
+
+ weights = graph.es["weight"] if graph.ecount() else [1.0]
+ tiny = min(weights) / 100
+ tiny_w = tiny * REPELLING_FORCE if is_kamada_kawai else tiny / REPELLING_FORCE
+
+ positions: dict[str, tuple[float, float]] = {}
+ for cid, members in groups.items():
+ local = _local_group_coords(graph, membership, cid, members, local_layout, tiny_w)
+ gx, gy = super_coords[cid]
+ for name, (x, y) in local.items():
+ positions[name] = (x + gx, y + gy)
+
+ clusters = dict(zip(names, membership, strict=True))
+ return positions, clusters
+
+
+def _super_node_coords(
+ graph: ig.Graph,
+ membership: list[int],
+ groups: dict[int, list[str]],
+ global_layout: Callable[[ig.Graph], ig.Layout],
+) -> dict[int, tuple[float, float]]:
+ """Lay out the community graph, then repel every super-node from the origin."""
+ cluster_ids = sorted(groups)
+ idx = {c: i for i, c in enumerate(cluster_ids)}
+ edges = [(idx[membership[e.source]], idx[membership[e.target]]) for e in graph.es]
+ sg = ig.Graph(n=len(cluster_ids), edges=edges)
+ if graph.ecount():
+ sg.es["weight"] = graph.es["weight"]
+ sg.simplify(multiple=True, loops=False, combine_edges={"weight": "mean"})
+ else:
+ sg.simplify(multiple=True, loops=False)
+
+ coords = global_layout(sg)
+ result: dict[int, tuple[float, float]] = {}
+ for c in cluster_ids:
+ x, y = float(coords[idx[c]][0]), float(coords[idx[c]][1])
+ # Push the super-node radially away from the origin. The old slope-based
+ # form (x*F, (y/x)*x*F) equals this for x != 0 but collapsed to (0, 0)
+ # when x == 0 (Circle/Grid layouts routinely place nodes there).
+ result[c] = (x * REPELLING_FORCE, y * REPELLING_FORCE)
+ return result
+
+
+def _local_group_coords(
+ graph: ig.Graph,
+ membership: list[int],
+ cid: int,
+ members: list[str],
+ local_layout: Callable[[ig.Graph], ig.Layout],
+ tiny_w: float,
+) -> dict[str, tuple[float, float]]:
+ """Lay out one community's members: real intra edges + tiny all-pairs edges.
+
+ The tiny complete-graph edges (ports calculateGroupNetworkWithAllEdges) keep
+ disconnected members spread out; real edges win under simplify(max).
+ """
+ index = {name: i for i, name in enumerate(members)}
+ edge_pairs: list[tuple[int, int]] = []
+ weights: list[float] = []
+
+ # real intra-community edges
+ for e in graph.es:
+ if membership[e.source] == cid and membership[e.target] == cid:
+ u, v = graph.vs[e.source]["name"], graph.vs[e.target]["name"]
+ edge_pairs.append((index[u], index[v]))
+ weights.append(e["weight"])
+
+ n = len(members)
+ if n <= MAX_ALL_PAIRS_MEMBERS:
+ # tiny edges between every pair of members (exact v2 padding)
+ for i in range(n):
+ for j in range(i + 1, n):
+ edge_pairs.append((i, j))
+ weights.append(tiny_w)
+ vertex_count = n
+ else:
+ # DoS fallback: connect every member to one virtual hub (index n).
+ # O(n) edges, keeps members from collapsing; the hub is dropped below.
+ for i in range(n):
+ edge_pairs.append((i, n))
+ weights.append(tiny_w)
+ vertex_count = n + 1
+
+ lg = ig.Graph(n=vertex_count, edges=edge_pairs)
+ if edge_pairs:
+ lg.es["weight"] = weights
+ lg.simplify(multiple=True, loops=False, combine_edges={"weight": "max"})
+
+ coords = local_layout(lg)
+ # iterate real members only — this naturally drops the virtual hub (index n)
+ return {members[i]: (float(coords[i][0]), float(coords[i][1])) for i in range(n)}
diff --git a/backend/app/services/graph.py b/backend/app/services/graph.py
new file mode 100644
index 0000000..096f93b
--- /dev/null
+++ b/backend/app/services/graph.py
@@ -0,0 +1,80 @@
+"""ig.Graph construction from a parsed network — ports functions/igraph/general.R.
+
+Handles channel filtering, the three subgraph scopes (perLayer / allLayers /
+nodesPerLayers) and the v2 simplify rules (sum parallel-edge weights, keep loops).
+"""
+
+import math
+
+import igraph as ig
+
+from app.models.network import EdgeModel
+
+# igraph's weighted betweenness (topology) 500s on non-positive weights, and
+# inf/nan weights propagate garbage into responses. The TSV path always scales
+# into [0.1, 1.0], but direct /api/layout|topology callers can send arbitrary
+# scaled_weight — clamp anything not strictly-positive-finite to this floor.
+MIN_EDGE_WEIGHT = 1e-9
+
+
+def filter_edges(
+ edges: list[EdgeModel],
+ selected_channels: list[str] | None,
+) -> list[EdgeModel]:
+ """Drop edges whose channel is not selected (ports filterSelectedChannels).
+
+ Only applies when the network has channels; otherwise all edges pass.
+ """
+ has_channels = any(e.channel is not None for e in edges)
+ if not has_channels or selected_channels is None:
+ return list(edges)
+ chosen = set(selected_channels)
+ return [e for e in edges if e.channel in chosen]
+
+
+def intra_layer_edges(edges: list[EdgeModel], layer: str) -> list[EdgeModel]:
+ """Edges with both endpoints in `layer` (ports filterPerLayer)."""
+ return [e for e in edges if e.source_layer == layer and e.target_layer == layer]
+
+
+def within_layers_edges(edges: list[EdgeModel], layers: set[str]) -> list[EdgeModel]:
+ """Edges with both endpoints among `layers` (ports filterAllSelectedLayers)."""
+ return [e for e in edges if e.source_layer in layers and e.target_layer in layers]
+
+
+def build_graph(
+ edges: list[EdgeModel],
+ *,
+ remove_multiple: bool,
+ extra_vertices: list[str] | None = None,
+) -> ig.Graph:
+ """Build a simplified weighted undirected graph from edge rows.
+
+ Ports createGraph(): edge weight = ScaledWeight, simplify with
+ combine_edges="sum", loops kept. `extra_vertices` adds isolated nodes
+ (used by no-edge layouts so every layer node is placed).
+ """
+ names: list[str] = []
+ seen: set[str] = set()
+ for e in edges:
+ for name in (e.src, e.trg):
+ if name not in seen:
+ seen.add(name)
+ names.append(name)
+ for name in extra_vertices or []:
+ if name not in seen:
+ seen.add(name)
+ names.append(name)
+
+ index = {name: i for i, name in enumerate(names)}
+ edge_list = [(index[e.src], index[e.trg]) for e in edges]
+ weights = [
+ w if (w := e.scaled_weight) > 0 and math.isfinite(w) else MIN_EDGE_WEIGHT for e in edges
+ ]
+
+ graph = ig.Graph(n=len(names), edges=edge_list)
+ graph.vs["name"] = names
+ graph.es["weight"] = weights
+ # remove_loops=False matches v2 (remove.loops = F); sum aggregates parallel edges
+ graph.simplify(multiple=remove_multiple, loops=False, combine_edges={"weight": "sum"})
+ return graph
diff --git a/backend/app/services/layouts.py b/backend/app/services/layouts.py
new file mode 100644
index 0000000..7741ca1
--- /dev/null
+++ b/backend/app/services/layouts.py
@@ -0,0 +1,112 @@
+"""Layout registry + orchestration — ports functions/igraph/layout.R.
+
+Maps the 11 v2 UI layout names to python-igraph Graph.layout_* calls, runs
+the chosen scope, and returns 2D [y, z] coordinates keyed by Node_Layer id.
+No weights are passed to layout functions — matches v2 (calculateLayout calls
+the layout with defaults even though the graph carries weights).
+"""
+
+import random
+from collections.abc import Callable
+
+import igraph as ig
+
+from app import config
+from app.models.layout import LayoutRequest
+from app.models.network import EdgeModel
+from app.services import graph as g
+from app.services.clustering import clustered_layout
+
+LayoutFn = Callable[[ig.Graph], ig.Layout]
+
+LAYOUTS: dict[str, LayoutFn] = {
+ "Fruchterman-Reingold": lambda gr: gr.layout_fruchterman_reingold(),
+ "Reingold-Tilford": lambda gr: gr.layout_reingold_tilford(),
+ "Circle": lambda gr: gr.layout_circle(),
+ "Grid": lambda gr: gr.layout_grid(),
+ "Random": lambda gr: gr.layout_random(),
+ "DrL": lambda gr: gr.layout_drl(),
+ "Graphopt": lambda gr: gr.layout_graphopt(),
+ "Kamada-Kawai": lambda gr: gr.layout_kamada_kawai(),
+ "Large Graph Layout": lambda gr: gr.layout_lgl(),
+ "Multidimensional Scaling": lambda gr: gr.layout_mds(),
+ "Sugiyama": lambda gr: gr.layout_sugiyama(),
+}
+
+
+class LayoutError(ValueError):
+ """Unknown algorithm or a scope with no computable subgraph."""
+
+
+def compute_layout(
+ req: LayoutRequest,
+) -> tuple[dict[str, tuple[float, float]], dict[str, int] | None]:
+ if req.algorithm not in LAYOUTS:
+ raise LayoutError(f"Unknown layout algorithm: {req.algorithm}")
+ if req.clustering and req.clustering.local_layout not in LAYOUTS:
+ raise LayoutError(f"Unknown local layout: {req.clustering.local_layout}")
+
+ random.seed(req.seed) # v2 set.seed(123)
+ edges = g.filter_edges(req.edges, req.selected_channels)
+ selected = set(req.selected_layers)
+ no_edge = req.algorithm in config.NO_EDGE_LAYOUTS
+
+ # nodesPerLayers narrows to selected nodes first (ports runLocalLayout)
+ if req.scope == "nodesPerLayers":
+ chosen_nodes = set(req.selected_nodes or [])
+ edges = [e for e in edges if e.src in chosen_nodes and e.trg in chosen_nodes]
+
+ positions: dict[str, tuple[float, float]] = {}
+ clusters: dict[str, int] = {}
+ scopes = [selected] if req.scope == "allLayers" else [{layer} for layer in req.selected_layers]
+ for layers in scopes:
+ if req.scope == "allLayers":
+ layer_edges = g.within_layers_edges(edges, layers)
+ else:
+ layer_edges = g.intra_layer_edges(edges, next(iter(layers)))
+ extra = _layer_nodes(req, layers) if no_edge else None
+ pos, clu = _layout_subgraph(layer_edges, req, no_edge, extra)
+ positions.update(pos)
+ clusters.update(clu)
+
+ return positions, (clusters if req.clustering else None)
+
+
+def _layer_nodes(req: LayoutRequest, layers: set[str]) -> list[str]:
+ """All node ids belonging to `layers` (so no-edge layouts place isolated nodes)."""
+ ids = [n.id for n in req.nodes if n.layer in layers]
+ if req.scope == "nodesPerLayers" and req.selected_nodes is not None:
+ chosen = set(req.selected_nodes)
+ ids = [i for i in ids if i in chosen]
+ return ids
+
+
+def _layout_subgraph(
+ edges: list[EdgeModel],
+ req: LayoutRequest,
+ no_edge: bool,
+ extra_vertices: list[str] | None,
+) -> tuple[dict[str, tuple[float, float]], dict[str, int]]:
+ if not edges and not extra_vertices:
+ return {}, {} # nothing to place (v2: "cannot form a graph")
+ # no channels here → remove parallel edges (edge-based scopes rebuild per layer)
+ graph = g.build_graph(edges, remove_multiple=True, extra_vertices=extra_vertices)
+ if graph.vcount() == 0:
+ return {}, {}
+
+ if req.clustering:
+ return clustered_layout(
+ graph,
+ req.clustering.algorithm,
+ LAYOUTS[req.algorithm],
+ LAYOUTS[req.clustering.local_layout],
+ is_kamada_kawai=req.algorithm == "Kamada-Kawai",
+ )
+
+ coords = LAYOUTS[req.algorithm](graph)
+ names = graph.vs["name"]
+ # Sugiyama appends dummy vertices for edge bends — keep only real vertices
+ positions = {
+ names[i]: (float(coords[i][0]), float(coords[i][1])) for i in range(graph.vcount())
+ }
+ return positions, {}
diff --git a/backend/app/services/parser.py b/backend/app/services/parser.py
new file mode 100644
index 0000000..2fbd241
--- /dev/null
+++ b/backend/app/services/parser.py
@@ -0,0 +1,145 @@
+"""TSV network parsing + validation — ports functions/input.R (upload path).
+
+Chain mirrors parseUploadedNetwork(): subset legit columns, trim, dedup,
+scale weights, append Node_Layer keys. Validation mirrors isNetworkFormatValid().
+Blocking failures raise NetworkValidationError → HTTP 400 in the router.
+"""
+
+from io import StringIO
+
+import numpy as np
+import pandas as pd
+
+from app import config
+from app.models.network import EdgeModel, NetworkModel, NodeModel
+
+
+class NetworkValidationError(ValueError):
+ """A blocking validation failure — bad file format."""
+
+
+def _mapper(values: pd.Series, out_min: float, out_max: float) -> pd.Series:
+ """Linear-rescale into [out_min, out_max]; constant input → DEFAULT_MAP_VALUE.
+
+ Ports functions/general.R mapper() (used with 0.1..1 for scaled weights).
+ """
+ in_min, in_max = values.min(), values.max()
+ if in_max - in_min == 0:
+ return pd.Series([config.DEFAULT_MAP_VALUE] * len(values), index=values.index)
+ scaled = (values - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
+ return pd.Series(scaled, index=values.index)
+
+
+def _validate(df: pd.DataFrame) -> None:
+ if not set(config.MANDATORY_NETWORK_COLUMNS).issubset(df.columns):
+ raise NetworkValidationError(
+ "Your network file must contain at least these four columns: "
+ "SourceNode, SourceLayer, TargetNode, TargetLayer"
+ )
+ # Reject rows with an empty/whitespace mandatory cell — a blank becomes NaN
+ # and would crash EdgeModel construction with a 500 instead of a clean 400.
+ mandatory = df[config.MANDATORY_NETWORK_COLUMNS].apply(lambda c: c.str.strip())
+ if mandatory.isna().to_numpy().any() or (mandatory == "").to_numpy().any():
+ raise NetworkValidationError(
+ "Every edge must have a non-empty SourceNode, SourceLayer, TargetNode and TargetLayer."
+ )
+ if (
+ "Weight" in df.columns
+ and not np.isfinite(pd.to_numeric(df["Weight"], errors="coerce")).all()
+ ):
+ # non-numeric coerces to NaN; inf is rejected too (it would map to a NaN
+ # scaled_weight, which the finite-only EdgeModel then refuses -> 500)
+ raise NetworkValidationError("Make sure all input weights are finite numeric values.")
+ if "Channel" in df.columns and (df["Channel"].fillna("").astype(str).str.strip() == "").any():
+ raise NetworkValidationError(
+ "At least one edge has no channel name. "
+ "Please reupload the network file with all edge channels."
+ )
+
+
+def parse_network_tsv(text: str) -> NetworkModel:
+ """Parse a raw TSV string into a validated NetworkModel."""
+ try:
+ df = pd.read_csv(StringIO(text), sep="\t", dtype=str)
+ except pd.errors.EmptyDataError as e:
+ raise NetworkValidationError("The network file is empty or has no columns.") from e
+ _validate(df)
+
+ # subset legit columns (mandatory + optional Channel/Weight, in fixed order)
+ legit = [
+ c
+ for c in config.MANDATORY_NETWORK_COLUMNS + config.OPTIONAL_NETWORK_COLUMNS
+ if c in df.columns
+ ]
+ df = df[legit].copy()
+
+ # trim whitespace on every column
+ for col in df.columns:
+ df[col] = df[col].astype(str).str.strip()
+
+ # dedup — include Channel in the key when present
+ subset = list(config.MANDATORY_NETWORK_COLUMNS)
+ if "Channel" in df.columns:
+ subset.append("Channel")
+ df = df.drop_duplicates(subset=subset).reset_index(drop=True)
+
+ # weights: numeric or default 1; scaled into 0.1..1
+ weight = (
+ pd.to_numeric(df["Weight"], errors="coerce")
+ if "Weight" in df.columns
+ else pd.Series([1.0] * len(df))
+ )
+ df["Weight"] = weight
+ df["ScaledWeight"] = _mapper(weight, 0.1, 1.0)
+
+ df["SourceNode_Layer"] = df["SourceNode"] + "_" + df["SourceLayer"]
+ df["TargetNode_Layer"] = df["TargetNode"] + "_" + df["TargetLayer"]
+
+ layers = pd.unique(pd.concat([df["SourceLayer"], df["TargetLayer"]]).dropna()).tolist()
+ if len(layers) > config.MAX_LAYERS:
+ raise NetworkValidationError(
+ f"The network must contain no more than {config.MAX_LAYERS} layers."
+ ) # noqa: E501
+ if len(df) > config.MAX_EDGES:
+ raise NetworkValidationError(
+ f"The network must contain no more than {config.MAX_EDGES} edges."
+ ) # noqa: E501
+
+ return NetworkModel(
+ nodes=_build_nodes(df),
+ edges=_build_edges(df),
+ layers=layers,
+ channels=sorted(df["Channel"].unique().tolist()) if "Channel" in df.columns else [],
+ )
+
+
+def _build_nodes(df: pd.DataFrame) -> list[NodeModel]:
+ src = df[["SourceNode_Layer", "SourceNode", "SourceLayer"]].rename(
+ columns={"SourceNode_Layer": "id", "SourceNode": "label", "SourceLayer": "layer"}
+ )
+ trg = df[["TargetNode_Layer", "TargetNode", "TargetLayer"]].rename(
+ columns={"TargetNode_Layer": "id", "TargetNode": "label", "TargetLayer": "layer"}
+ )
+ nodes = pd.concat([src, trg]).drop_duplicates(subset="id")
+ return [
+ NodeModel(id=str(r["id"]), label=str(r["label"]), layer=str(r["layer"]))
+ for r in nodes.to_dict("records")
+ ]
+
+
+def _build_edges(df: pd.DataFrame) -> list[EdgeModel]:
+ has_channel = "Channel" in df.columns
+ return [
+ EdgeModel(
+ src=r["SourceNode_Layer"],
+ trg=r["TargetNode_Layer"],
+ source_node=r["SourceNode"],
+ source_layer=r["SourceLayer"],
+ target_node=r["TargetNode"],
+ target_layer=r["TargetLayer"],
+ weight=r["Weight"],
+ scaled_weight=r["ScaledWeight"],
+ channel=r["Channel"] if has_channel else None,
+ )
+ for r in df.to_dict("records")
+ ]
diff --git a/backend/app/services/session.py b/backend/app/services/session.py
new file mode 100644
index 0000000..5f25fa4
--- /dev/null
+++ b/backend/app/services/session.py
@@ -0,0 +1,173 @@
+"""Session JSON import normalization — ports the JSON path of functions/input.R
+(loadNetworkFromJSONFilepath → parseUploadedJSON + isJSONValid).
+
+Fills defaults for scene/layers/nodes/edges, handles channels, dedups edges,
+and decides the scramble/generate-coordinate flags — so a partial JSON (e.g. an
+external-API payload with only src/trg) renders like v2.
+"""
+
+from typing import Any
+
+from app import config
+
+
+class SessionValidationError(ValueError):
+ """A blocking JSON-format failure."""
+
+
+def _empty(v: Any) -> bool:
+ return v is None or v == "" or (isinstance(v, str) and v.strip() == "")
+
+
+def _default(value: Any, default: Any = "0") -> Any:
+ """Ports keepValuesOrDefault: fill None/empty with default, coerce TRUE/FALSE."""
+ if value is None:
+ return default
+ if value == "TRUE":
+ return True
+ if value == "FALSE":
+ return False
+ if isinstance(value, list):
+ return [default if _empty(x) else x for x in value]
+ return default if _empty(value) else value
+
+
+def _validate(data: dict[str, Any]) -> None:
+ if not set(config.MANDATORY_JSON_OBJECTS).issubset(data):
+ raise SessionValidationError(
+ "Your JSON file must contain at least these objects: layers, nodes, edges"
+ )
+ # Each must be a list of objects — otherwise the .get()/subscript calls below
+ # raise AttributeError/TypeError and 500 instead of a clean 400. Reachable
+ # from /api/session/import and /api/external (arbitrary caller JSON).
+ for key in config.MANDATORY_JSON_OBJECTS:
+ value = data[key]
+ if not isinstance(value, list) or not all(isinstance(x, dict) for x in value):
+ raise SessionValidationError(f"JSON '{key}' must be a list of objects.")
+ layers = data["layers"]
+ if not layers or any(_empty(row.get("name")) for row in layers):
+ raise SessionValidationError("JSON layers must each have a non-empty name.")
+ if len({row["name"] for row in layers}) != len(layers):
+ # Duplicate names collapse layerGroups on the frontend, orphaning the
+ # earlier layer's nodes onto the wrong plane.
+ raise SessionValidationError("JSON layer names must be unique.")
+ if len({row["name"] for row in layers}) > config.MAX_LAYERS:
+ raise SessionValidationError(
+ f"The network must contain no more than {config.MAX_LAYERS} layers."
+ )
+ nodes = data["nodes"]
+ # DoS fix: unlike the TSV upload path (parser.py), JSON session import and
+ # the /api/external hand-off had no node/edge count limit at all.
+ if len(nodes) > config.MAX_NODES:
+ raise SessionValidationError(
+ f"The network must contain no more than {config.MAX_NODES} nodes."
+ )
+ if any(_empty(n.get("name")) or _empty(n.get("layer")) for n in nodes):
+ raise SessionValidationError("JSON nodes must each have a non-empty name and layer.")
+ # Referential integrity: the frontend parents every node to layerGroups[layer]
+ # and resolves edge endpoints against the node registry, so a dangling
+ # reference crashes the scene build. Reject it here with a 400 instead.
+ layer_names = {row["name"] for row in layers}
+ bad_layer = next((n["layer"] for n in nodes if n["layer"] not in layer_names), None)
+ if bad_layer is not None:
+ raise SessionValidationError(f"JSON node references unknown layer '{bad_layer}'.")
+ node_ids = {f"{n['name']}_{n['layer']}" for n in nodes}
+ edges = data["edges"]
+ # Same DoS fix as the node-count check above, for edges.
+ if len(edges) > config.MAX_EDGES:
+ raise SessionValidationError(
+ f"The network must contain no more than {config.MAX_EDGES} edges."
+ )
+ if any(_empty(e.get("src")) or _empty(e.get("trg")) for e in edges):
+ raise SessionValidationError("JSON edges must each have a non-empty src and trg.")
+ bad_edge = next(
+ (e for e in edges if e["src"] not in node_ids or e["trg"] not in node_ids),
+ None,
+ )
+ if bad_edge is not None:
+ raise SessionValidationError(
+ f"JSON edge references unknown node ({bad_edge['src']} -> {bad_edge['trg']})."
+ )
+
+
+def _handle_channels(edges: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[str]]:
+ """Drop channels entirely if any edge lacks one (ports handleJSONChannels)."""
+ warnings: list[str] = []
+ has_channel = any("channel" in e for e in edges)
+ if has_channel and any(_empty(e.get("channel")) for e in edges):
+ warnings.append("At least one edge has no channel name. Removing channels completely.")
+ for e in edges:
+ e.pop("channel", None)
+ return edges, warnings
+
+
+def _dedup_edges(edges: list[dict[str, Any]]) -> list[dict[str, Any]]:
+ seen: set[tuple[str, ...]] = set()
+ out: list[dict[str, Any]] = []
+ has_channel = any("channel" in e for e in edges)
+ for e in edges:
+ key = (e["src"], e["trg"], e.get("channel", "")) if has_channel else (e["src"], e["trg"])
+ if key not in seen:
+ seen.add(key)
+ out.append(e)
+ return out
+
+
+def normalize_session(data: dict[str, Any]) -> dict[str, Any]:
+ """Validate + fill defaults, returning the canonical session dict."""
+ _validate(data)
+
+ scene = dict(data.get("scene") or {})
+ scene["position_x"] = _default(scene.get("position_x"))
+ scene["position_y"] = _default(scene.get("position_y"))
+ scene["scale"] = _default(scene.get("scale"), "0.9")
+ scene["color"] = _default(scene.get("color"), "#000000")
+ for axis in ("rotation_x", "rotation_y", "rotation_z"):
+ scene[axis] = _default(scene.get(axis), "0.261799388")
+
+ layers = [dict(row) for row in data["layers"]]
+ for row in layers:
+ has_pos = all(not _empty(row.get(f"position_{a}")) for a in "xyz")
+ row["generate_coordinates"] = not has_pos
+ for a in "xyz":
+ row[f"position_{a}"] = _default(row.get(f"position_{a}"))
+ row[f"rotation_{a}"] = _default(row.get(f"rotation_{a}"))
+ row["last_layer_scale"] = _default(row.get("last_layer_scale"), "1")
+ row["floor_current_color"] = _default(
+ row.get("floor_current_color"), config.FLOOR_DEFAULT_COLOR
+ )
+ row["geometry_parameters_width"] = _default(
+ row.get("geometry_parameters_width"), config.FLOOR_DEFAULT_WIDTH
+ )
+
+ nodes = [dict(n) for n in data["nodes"]]
+ scramble = all(_empty(n.get("position_x")) for n in nodes) if nodes else True
+ layer_names = [row["name"] for row in layers]
+ for n in nodes:
+ for a in "xyz":
+ n[f"position_{a}"] = _default(n.get(f"position_{a}"))
+ n["scale"] = _default(n.get("scale"), 1)
+ if _empty(n.get("color")):
+ i = layer_names.index(n["layer"]) if n["layer"] in layer_names else 0
+ n["color"] = config.NODE_COLORS[i % len(config.NODE_COLORS)]
+ n["url"] = _default(n.get("url"), "")
+ n["descr"] = _default(n.get("descr"), "")
+
+ edges = [dict(e) for e in data["edges"]]
+ edges, warnings = _handle_channels(edges)
+ edges = _dedup_edges(edges)
+ for e in edges:
+ e["opacity"] = _default(e.get("opacity"), 1)
+ e["color"] = _default(e.get("color"), config.EDGE_DEFAULT_COLOR)
+
+ return {
+ "scene": scene,
+ "layers": layers,
+ "nodes": nodes,
+ "edges": edges,
+ "universalLabelColor": _default(data.get("universalLabelColor"), "#FFFFFF"),
+ "direction": _default(data.get("direction"), False),
+ "edgeOpacityByWeight": _default(data.get("edgeOpacityByWeight"), True),
+ "scramble_nodes": scramble,
+ "warnings": warnings,
+ }
diff --git a/backend/app/services/topology.py b/backend/app/services/topology.py
new file mode 100644
index 0000000..6f2c2e9
--- /dev/null
+++ b/backend/app/services/topology.py
@@ -0,0 +1,86 @@
+"""Topology metrics + node scaling — ports functions/igraph/topology.R.
+
+Three metrics (raw igraph values, not normalized centralities), then per-subgraph
+mapping into [TARGET_NODE_SCALE_MIN, TARGET_NODE_SCALE_MAX] as v2's scaleTopology does.
+"""
+
+import igraph as ig
+
+from app import config
+from app.models.topology import TopologyRequest
+from app.services import graph as g
+
+
+class TopologyError(ValueError):
+ """Unknown metric."""
+
+
+def _degree(graph: ig.Graph, directed: bool) -> list[float]:
+ # v2: degree(mode="all", loops=T, normalized=F) — raw degree, self-loops counted
+ return [float(d) for d in graph.degree(loops=True)]
+
+
+def _clustering_coefficient(graph: ig.Graph, directed: bool) -> list[float]:
+ # v2: transitivity(type="weighted", isolates="zero")
+ vals = graph.transitivity_local_undirected(weights="weight", mode="zero")
+ return [float(v) for v in vals]
+
+
+def _betweenness(graph: ig.Graph, directed: bool) -> list[float]:
+ # v2: betweenness(directed=edgeDirectionToggle, weights=E$weight, normalized=F)
+ weights = graph.es["weight"] if graph.ecount() else None
+ return [float(b) for b in graph.betweenness(directed=directed, weights=weights)]
+
+
+METRICS = {
+ "Degree": _degree,
+ "Clustering Coefficient": _clustering_coefficient,
+ "Betweenness Centrality": _betweenness,
+}
+
+
+def _mapper(values: list[float], out_min: float, out_max: float, default: float) -> list[float]:
+ """Linear-rescale into [out_min, out_max]; constant input → default.
+
+ Ports functions/general.R mapper() (topology uses default 1).
+ """
+ lo, hi = min(values), max(values)
+ if hi - lo == 0:
+ return [default] * len(values)
+ return [(v - lo) * (out_max - out_min) / (hi - lo) + out_min for v in values]
+
+
+def compute_topology(req: TopologyRequest) -> tuple[dict[str, float], dict[str, float]]:
+ if req.metric not in METRICS:
+ raise TopologyError(f"Unknown topology metric: {req.metric}")
+
+ edges = g.filter_edges(req.edges, req.selected_channels)
+ if req.scope == "nodesPerLayers":
+ chosen = set(req.selected_nodes or [])
+ edges = [e for e in edges if e.src in chosen and e.trg in chosen]
+
+ selected = set(req.selected_layers)
+ if req.scope == "allLayers":
+ scopes = [selected]
+ else:
+ scopes = [{layer} for layer in req.selected_layers]
+
+ scales: dict[str, float] = {}
+ raw: dict[str, float] = {}
+ for layers in scopes:
+ if req.scope == "allLayers":
+ layer_edges = g.within_layers_edges(edges, layers)
+ else:
+ layer_edges = g.intra_layer_edges(edges, next(iter(layers)))
+ if not layer_edges:
+ continue # no edges in this scope → nothing to score
+ graph = g.build_graph(layer_edges, remove_multiple=True)
+ names = graph.vs["name"]
+ values = METRICS[req.metric](graph, req.directed)
+ mapped = _mapper(
+ values, config.TARGET_NODE_SCALE_MIN, config.TARGET_NODE_SCALE_MAX, default=1.0
+ )
+ for name, r, m in zip(names, values, mapped, strict=True):
+ raw[name] = r
+ scales[name] = m
+ return scales, raw
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
new file mode 100644
index 0000000..db47338
--- /dev/null
+++ b/backend/pyproject.toml
@@ -0,0 +1,42 @@
+[project]
+name = "arena3d"
+version = "3.0.0"
+requires-python = ">=3.11"
+dependencies = [
+ "fastapi>=0.115.0",
+ "uvicorn[standard]>=0.30.0",
+ "igraph>=0.11.0",
+ "pandas>=2.2.0",
+ "pydantic>=2.8.0",
+ "python-multipart>=0.0.9",
+]
+
+[dependency-groups]
+dev = [
+ "pytest>=8.3.0",
+ "pytest-asyncio>=0.24.0",
+ "mypy>=1.11.0",
+ "ruff>=0.6.0",
+ "pandas-stubs>=3.0.3.260530",
+ "httpx2>=2.5.0",
+]
+
+[tool.ruff]
+line-length = 100
+target-version = "py311"
+# ruff config lives in backend/; legacy R-side scripts live above it (../www) — never lint those
+extend-exclude = ["../www", "../functions", "../views", "../config"]
+
+[tool.ruff.lint]
+select = ["E", "F", "I", "UP", "B"]
+
+[tool.mypy]
+python_version = "3.12" # numpy/pandas stubs require 3.12+ type syntax
+strict = true
+
+[[tool.mypy.overrides]]
+module = "igraph.*"
+ignore_missing_imports = true
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"
diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/tests/fixtures/.gitkeep b/backend/tests/fixtures/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/www/data/Arena3DwebApp_aspirin.json b/backend/tests/fixtures/Arena3DApp_aspirin.json
similarity index 100%
rename from www/data/Arena3DwebApp_aspirin.json
rename to backend/tests/fixtures/Arena3DApp_aspirin.json
diff --git a/www/data/aspirin_3channels.tsv b/backend/tests/fixtures/aspirin_3channels.tsv
similarity index 100%
rename from www/data/aspirin_3channels.tsv
rename to backend/tests/fixtures/aspirin_3channels.tsv
diff --git a/www/data/figure1_export.json b/backend/tests/fixtures/figure1_export.json
similarity index 100%
rename from www/data/figure1_export.json
rename to backend/tests/fixtures/figure1_export.json
diff --git a/backend/tests/test_attributes.py b/backend/tests/test_attributes.py
new file mode 100644
index 0000000..714e83d
--- /dev/null
+++ b/backend/tests/test_attributes.py
@@ -0,0 +1,75 @@
+from fastapi.testclient import TestClient
+
+from app.main import app
+
+client = TestClient(app)
+
+
+def _post(kind: str, text: str):
+ return client.post(
+ f"/api/attributes/{kind}",
+ files={"file": ("attrs.tsv", text, "text/tab-separated-values")},
+ )
+
+
+def test_node_attributes_full_row() -> None:
+ tsv = (
+ "Node\tLayer\tColor\tSize\tUrl\tDescription\n"
+ " aspirin \t drugs \t#ff0000\t2.5\thttp://x\thello\n"
+ )
+ r = _post("nodes", tsv)
+ assert r.status_code == 200
+ assert r.json() == [
+ {
+ "node_layer": "aspirin_drugs",
+ "color": "#ff0000",
+ "size": 2.5,
+ "url": "http://x",
+ "description": "hello",
+ }
+ ]
+
+
+def test_node_attributes_optional_columns_missing() -> None:
+ r = _post("nodes", "Node\tLayer\nA\tL1\n")
+ assert r.status_code == 200
+ assert r.json()[0] == {
+ "node_layer": "A_L1",
+ "color": None,
+ "size": None,
+ "url": None,
+ "description": None,
+ }
+
+
+def test_node_attributes_missing_mandatory_column() -> None:
+ r = _post("nodes", "Node\tColor\nA\t#fff\n")
+ assert r.status_code == 400
+ assert "Node and Layer" in r.json()["detail"]
+
+
+def test_node_attributes_non_numeric_size() -> None:
+ r = _post("nodes", "Node\tLayer\tSize\nA\tL1\tbig\n")
+ assert r.status_code == 400
+
+
+def test_edge_attributes_with_channel() -> None:
+ tsv = (
+ "SourceNode\tSourceLayer\tTargetNode\tTargetLayer\tColor\tChannel\n"
+ "A\tL1\tB\tL2\t #00ff00 \t ppi \n"
+ )
+ r = _post("edges", tsv)
+ assert r.status_code == 200
+ assert r.json() == [{"edge_pair": "A_L1---B_L2", "color": "#00ff00", "channel": "ppi"}]
+
+
+def test_edge_attributes_without_channel() -> None:
+ tsv = "SourceNode\tSourceLayer\tTargetNode\tTargetLayer\tColor\nA\tL1\tB\tL2\t#000\n"
+ r = _post("edges", tsv)
+ assert r.status_code == 200
+ assert r.json()[0]["channel"] is None
+
+
+def test_edge_attributes_missing_color_column() -> None:
+ r = _post("edges", "SourceNode\tSourceLayer\tTargetNode\tTargetLayer\nA\tL1\tB\tL2\n")
+ assert r.status_code == 400
diff --git a/backend/tests/test_cluster.py b/backend/tests/test_cluster.py
new file mode 100644
index 0000000..2e0cd71
--- /dev/null
+++ b/backend/tests/test_cluster.py
@@ -0,0 +1,138 @@
+import igraph as ig
+import pytest
+from fastapi.testclient import TestClient
+
+from app.main import app
+from app.models.layout import ClusteringOptions, LayoutRequest
+from app.services.clustering import (
+ CLUSTERING,
+ MAX_ALL_PAIRS_MEMBERS,
+ _local_group_coords,
+)
+from app.services.layouts import compute_layout
+
+client = TestClient(app)
+
+
+def _two_cluster_net():
+ # two 3-cliques joined by a single bridge edge — clear community structure
+ names = ["A", "B", "C", "D", "E", "F"]
+ nodes = [{"id": f"{n}_L1", "label": n, "layer": "L1"} for n in names]
+
+ def edge(a, b):
+ return {
+ "src": f"{a}_L1",
+ "trg": f"{b}_L1",
+ "source_node": a,
+ "source_layer": "L1",
+ "target_node": b,
+ "target_layer": "L1",
+ "weight": 1.0,
+ "scaled_weight": 0.5,
+ "channel": None,
+ }
+
+ edges = [
+ edge("A", "B"),
+ edge("B", "C"),
+ edge("A", "C"),
+ edge("D", "E"),
+ edge("E", "F"),
+ edge("D", "F"),
+ edge("C", "D"), # bridge
+ ]
+ return nodes, edges
+
+
+def _req(algo="Louvain", local="Circle"):
+ nodes, edges = _two_cluster_net()
+ return LayoutRequest(
+ nodes=nodes,
+ edges=edges,
+ algorithm="Fruchterman-Reingold",
+ scope="perLayer",
+ selected_layers=["L1"],
+ clustering=ClusteringOptions(algorithm=algo, local_layout=local),
+ )
+
+
+@pytest.mark.parametrize("algo", list(CLUSTERING))
+def test_every_clustering_algo_runs(algo: str) -> None:
+ positions, clusters = compute_layout(_req(algo=algo))
+ assert clusters is not None
+ assert set(positions) == {f"{n}_L1" for n in "ABCDEF"}
+ assert set(clusters) == set(positions)
+
+
+def test_finds_two_communities() -> None:
+ _, clusters = compute_layout(_req(algo="Louvain"))
+ assert clusters is not None
+ # A,B,C in one community; D,E,F in another
+ assert clusters["A_L1"] == clusters["B_L1"] == clusters["C_L1"]
+ assert clusters["D_L1"] == clusters["E_L1"] == clusters["F_L1"]
+ assert clusters["A_L1"] != clusters["D_L1"]
+
+
+def test_no_clustering_returns_none() -> None:
+ nodes, edges = _two_cluster_net()
+ _, clusters = compute_layout(
+ LayoutRequest(
+ nodes=nodes,
+ edges=edges,
+ algorithm="Circle",
+ scope="perLayer",
+ selected_layers=["L1"],
+ )
+ )
+ assert clusters is None
+
+
+def test_unknown_clustering_400() -> None:
+ nodes, edges = _two_cluster_net()
+ resp = client.post(
+ "/api/layout",
+ json={
+ "nodes": nodes,
+ "edges": edges,
+ "algorithm": "Fruchterman-Reingold",
+ "scope": "perLayer",
+ "selected_layers": ["L1"],
+ "clustering": {"algorithm": "Nope", "local_layout": "Circle"},
+ },
+ )
+ assert resp.status_code == 400
+
+
+def test_large_community_uses_hub_not_all_pairs() -> None:
+ # DoS guard (H1): a community above MAX_ALL_PAIRS_MEMBERS must not build the
+ # O(n^2) all-pairs padding. Verify it still lays out every member (and drops
+ # the virtual hub — exactly n coords, no stray hub key).
+ n = MAX_ALL_PAIRS_MEMBERS + 100
+ members = [f"m{i}" for i in range(n)]
+ graph = ig.Graph(n=n, edges=[(0, 1), (1, 2)])
+ graph.vs["name"] = members
+ graph.es["weight"] = [1.0, 1.0]
+ membership = [0] * n
+
+ coords = _local_group_coords(
+ graph, membership, 0, members, lambda g: g.layout_circle(), tiny_w=0.01
+ )
+ assert set(coords) == set(members) # all members placed, no hub artifact
+ assert len(coords) == n
+
+
+def test_endpoint_returns_clusters() -> None:
+ nodes, edges = _two_cluster_net()
+ resp = client.post(
+ "/api/layout",
+ json={
+ "nodes": nodes,
+ "edges": edges,
+ "algorithm": "Fruchterman-Reingold",
+ "scope": "perLayer",
+ "selected_layers": ["L1"],
+ "clustering": {"algorithm": "Louvain", "local_layout": "Grid"},
+ },
+ )
+ assert resp.status_code == 200
+ assert resp.json()["clusters"]
diff --git a/backend/tests/test_config.py b/backend/tests/test_config.py
new file mode 100644
index 0000000..3091c3c
--- /dev/null
+++ b/backend/tests/test_config.py
@@ -0,0 +1,25 @@
+from fastapi.testclient import TestClient
+
+from app.main import app
+
+client = TestClient(app)
+
+
+def test_config_matches_v2_constants() -> None:
+ body = client.get("/api/config").json()
+ assert body["max_edges"] == 10_000
+ assert body["max_layers"] == 18
+ assert body["max_channels"] == 9
+ assert body["edge_default_color"] == "#CFCFCF"
+ assert len(body["node_colors"]) == 18
+ assert len(body["channel_colors_light"]) == 9
+ assert len(body["channel_colors_dark"]) == 9
+ assert body["channel_colors_dark"][0] == "#E41A1C" # brewer Set1[1]
+ assert body["node_colors"][0] == "#8DD3C7" # brewer Set3[1]
+ assert body["no_edge_layouts"] == ["Circle", "Grid", "Random"]
+ expected_metrics = ["Degree", "Clustering Coefficient", "Betweenness Centrality"]
+ assert body["topology_metrics"] == expected_metrics
+
+
+def test_health() -> None:
+ assert client.get("/api/health").json() == {"status": "ok"}
diff --git a/backend/tests/test_layout.py b/backend/tests/test_layout.py
new file mode 100644
index 0000000..f5971e4
--- /dev/null
+++ b/backend/tests/test_layout.py
@@ -0,0 +1,160 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from app.main import app
+from app.models.layout import LayoutRequest
+from app.services.layouts import LAYOUTS, compute_layout
+
+client = TestClient(app)
+
+
+def _net():
+ # two layers, a small clique each, one inter-layer edge
+ nodes = [
+ {"id": "A_L1", "label": "A", "layer": "L1"},
+ {"id": "B_L1", "label": "B", "layer": "L1"},
+ {"id": "C_L1", "label": "C", "layer": "L1"},
+ {"id": "D_L2", "label": "D", "layer": "L2"},
+ {"id": "E_L2", "label": "E", "layer": "L2"},
+ ]
+
+ def edge(src, trg, sl, tl):
+ s, t = src.split("_")[0], trg.split("_")[0]
+ return {
+ "src": src,
+ "trg": trg,
+ "source_node": s,
+ "source_layer": sl,
+ "target_node": t,
+ "target_layer": tl,
+ "weight": 1.0,
+ "scaled_weight": 0.5,
+ "channel": None,
+ }
+
+ edges = [
+ edge("A_L1", "B_L1", "L1", "L1"),
+ edge("B_L1", "C_L1", "L1", "L1"),
+ edge("A_L1", "C_L1", "L1", "L1"),
+ edge("D_L2", "E_L2", "L2", "L2"),
+ edge("C_L1", "D_L2", "L1", "L2"), # inter-layer
+ ]
+ return nodes, edges
+
+
+def _req(**kw):
+ nodes, edges = _net()
+ base = dict(
+ nodes=nodes,
+ edges=edges,
+ algorithm="Fruchterman-Reingold",
+ scope="perLayer",
+ selected_layers=["L1", "L2"],
+ )
+ base.update(kw)
+ return LayoutRequest(**base)
+
+
+def test_all_registered_algorithms_run() -> None:
+ for algo in LAYOUTS:
+ pos, _ = compute_layout(_req(algorithm=algo))
+ assert pos, f"{algo} produced no positions"
+ for coord in pos.values():
+ assert len(coord) == 2
+
+
+def test_per_layer_excludes_inter_layer_edges() -> None:
+ # perLayer L1 should place only L1 nodes reachable via intra-layer edges
+ pos, _ = compute_layout(_req(selected_layers=["L1"]))
+ assert set(pos) == {"A_L1", "B_L1", "C_L1"}
+
+
+def test_all_layers_scope_places_both_layers() -> None:
+ pos, _ = compute_layout(_req(scope="allLayers"))
+ assert {"A_L1", "D_L2"} <= set(pos)
+
+
+def test_no_edge_layout_places_isolated_nodes() -> None:
+ # add a node with no intra-layer edge; Grid must still place it
+ nodes, edges = _net()
+ nodes.append({"id": "Z_L1", "label": "Z", "layer": "L1"})
+ req = LayoutRequest(
+ nodes=nodes,
+ edges=edges,
+ algorithm="Grid",
+ scope="perLayer",
+ selected_layers=["L1"],
+ )
+ pos, _ = compute_layout(req)
+ assert "Z_L1" in pos
+
+
+def test_seed_is_deterministic() -> None:
+ a, _ = compute_layout(_req(algorithm="Fruchterman-Reingold", seed=7))
+ b, _ = compute_layout(_req(algorithm="Fruchterman-Reingold", seed=7))
+ assert a == b
+
+
+def test_channel_filter_drops_unselected() -> None:
+ nodes, edges = _net()
+ for e in edges:
+ e["channel"] = "keep"
+ edges[0]["channel"] = "drop" # A_L1-B_L1
+ req = LayoutRequest(
+ nodes=nodes,
+ edges=edges,
+ algorithm="Circle",
+ scope="perLayer",
+ selected_layers=["L1"],
+ selected_channels=["keep"],
+ )
+ pos, _ = compute_layout(req)
+ # A-B edge dropped but A,B still reachable via C's edges → all three present
+ assert set(pos) == {"A_L1", "B_L1", "C_L1"}
+
+
+def test_unknown_algorithm_400() -> None:
+ resp = client.post(
+ "/api/layout",
+ json={
+ "nodes": _net()[0],
+ "edges": _net()[1],
+ "algorithm": "Nope",
+ "scope": "perLayer",
+ "selected_layers": ["L1"],
+ },
+ )
+ assert resp.status_code == 400
+
+
+def test_endpoint_returns_positions() -> None:
+ nodes, edges = _net()
+ resp = client.post(
+ "/api/layout",
+ json={
+ "nodes": nodes,
+ "edges": edges,
+ "algorithm": "Kamada-Kawai",
+ "scope": "allLayers",
+ "selected_layers": ["L1", "L2"],
+ },
+ )
+ assert resp.status_code == 200
+ assert resp.json()["positions"]
+
+
+def test_nodes_per_layer_scope() -> None:
+ pos, _ = compute_layout(
+ _req(
+ scope="nodesPerLayers",
+ selected_layers=["L1"],
+ selected_nodes=["A_L1", "B_L1", "C_L1"],
+ algorithm="Circle",
+ )
+ )
+ assert set(pos) <= {"A_L1", "B_L1", "C_L1"}
+
+
+@pytest.mark.parametrize("algo", ["Circle", "Grid", "Random"])
+def test_no_edge_layouts_registered(algo: str) -> None:
+ assert algo in LAYOUTS
diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py
new file mode 100644
index 0000000..0e62ce6
--- /dev/null
+++ b/backend/tests/test_main.py
@@ -0,0 +1,24 @@
+from fastapi.testclient import TestClient
+
+from app import config
+from app.main import app
+
+client = TestClient(app)
+
+
+def test_oversized_content_length_rejected_before_parsing() -> None:
+ headers = {"content-length": str(config.MAX_UPLOAD_BYTES + 1)}
+ resp = client.post("/api/layout", headers=headers, content=b"")
+ assert resp.status_code == 413
+
+
+def test_undersized_content_length_reaches_normal_validation() -> None:
+ # No node/edge lists -> 422 from Pydantic, proving the middleware let it
+ # past the size gate rather than blocking every request.
+ resp = client.post("/api/layout", json={})
+ assert resp.status_code == 422
+
+
+def test_malformed_content_length_is_ignored() -> None:
+ resp = client.post("/api/layout", headers={"content-length": "not-a-number"}, json={})
+ assert resp.status_code == 422
diff --git a/backend/tests/test_network.py b/backend/tests/test_network.py
new file mode 100644
index 0000000..7467827
--- /dev/null
+++ b/backend/tests/test_network.py
@@ -0,0 +1,102 @@
+from pathlib import Path
+
+import pytest
+from fastapi.testclient import TestClient
+
+from app.main import app
+from app.services.parser import NetworkValidationError, parse_network_tsv
+
+client = TestClient(app)
+DATA = Path(__file__).resolve().parent / "fixtures"
+HDR = "SourceNode\tSourceLayer\tTargetNode\tTargetLayer"
+
+
+def _post(text: str):
+ return client.post(
+ "/api/network", files={"file": ("net.tsv", text, "text/tab-separated-values")}
+ )
+
+
+def test_parses_real_multichannel_fixture() -> None:
+ fixture = DATA / "aspirin_3channels.tsv"
+ if not fixture.exists():
+ pytest.skip("fixture missing")
+ body = _post(fixture.read_text()).json()
+ assert len(body["nodes"]) > 0
+ assert len(body["edges"]) > 0
+ assert "Drug" in body["layers"]
+ assert body["channels"] # multichannel file → non-empty
+
+
+def test_scaled_weight_in_range() -> None:
+ tsv = f"{HDR}\tWeight\nA\tL1\tB\tL2\t5\nC\tL1\tD\tL2\t10\n"
+ edges = _post(tsv).json()["edges"]
+ scaled = [e["scaled_weight"] for e in edges]
+ assert min(scaled) == pytest.approx(0.1)
+ assert max(scaled) == pytest.approx(1.0)
+
+
+def test_constant_weight_uses_default_map_value() -> None:
+ tsv = f"{HDR}\tWeight\nA\tL1\tB\tL2\t3\nC\tL1\tD\tL2\t3\n"
+ edges = _post(tsv).json()["edges"]
+ assert all(e["scaled_weight"] == pytest.approx(0.3) for e in edges)
+
+
+def test_missing_weight_column_defaults_to_one() -> None:
+ tsv = f"{HDR}\nA\tL1\tB\tL2\n"
+ edges = _post(tsv).json()["edges"]
+ assert edges[0]["weight"] == 1.0
+
+
+def test_missing_mandatory_column_400() -> None:
+ tsv = "SourceNode\tSourceLayer\tTargetNode\nA\tL1\tB\n"
+ resp = _post(tsv)
+ assert resp.status_code == 400
+ assert "four columns" in resp.json()["detail"]
+
+
+def test_non_numeric_weight_400() -> None:
+ tsv = f"{HDR}\tWeight\nA\tL1\tB\tL2\theavy\n"
+ assert _post(tsv).status_code == 400
+
+
+def test_infinite_weight_400() -> None:
+ # #4: inf weight would map to a NaN scaled_weight — reject at parse time
+ tsv = f"{HDR}\tWeight\nA\tL1\tB\tL2\tinf\n"
+ assert _post(tsv).status_code == 400
+
+
+def test_empty_channel_400() -> None:
+ tsv = f"{HDR}\tChannel\nA\tL1\tB\tL2\t\n"
+ assert _post(tsv).status_code == 400
+
+
+def test_duplicate_rows_collapsed() -> None:
+ tsv = f"{HDR}\nA\tL1\tB\tL2\nA\tL1\tB\tL2\n"
+ assert len(_post(tsv).json()["edges"]) == 1
+
+
+def test_whitespace_trimmed() -> None:
+ tsv = f"{HDR}\n A \tL1\tB\tL2\n"
+ node_ids = [n["id"] for n in _post(tsv).json()["nodes"]]
+ assert "A_L1" in node_ids
+
+
+def test_empty_file_400() -> None:
+ # #1a: empty upload must be a clean 400, not a pandas EmptyDataError 500
+ assert _post("").status_code == 400
+
+
+def test_empty_mandatory_cell_400() -> None:
+ # #1b: a blank SourceLayer became NaN and 500'd EdgeModel construction
+ tsv = f"{HDR}\nA\t\tB\tL2\n"
+ resp = _post(tsv)
+ assert resp.status_code == 400
+ assert "non-empty" in resp.json()["detail"]
+
+
+def test_too_many_layers_rejected() -> None:
+ rows = "\n".join(f"A\tL{i}\tB\tL{i + 1}" for i in range(0, 40, 2))
+ tsv = f"{HDR}\n" + rows + "\n"
+ with pytest.raises(NetworkValidationError):
+ parse_network_tsv(tsv)
diff --git a/backend/tests/test_session.py b/backend/tests/test_session.py
new file mode 100644
index 0000000..496ae26
--- /dev/null
+++ b/backend/tests/test_session.py
@@ -0,0 +1,165 @@
+import json
+from pathlib import Path
+
+import pytest
+from fastapi.testclient import TestClient
+
+from app.main import app
+from app.services.session import SessionValidationError, normalize_session
+
+client = TestClient(app)
+DATA = Path(__file__).resolve().parent / "fixtures"
+
+
+def _minimal():
+ return {
+ "layers": [{"name": "L1"}],
+ "nodes": [{"name": "A", "layer": "L1"}, {"name": "B", "layer": "L1"}],
+ "edges": [{"src": "A_L1", "trg": "B_L1"}],
+ }
+
+
+def test_import_real_api_fixture() -> None:
+ fixture = DATA / "Arena3DApp_aspirin.json"
+ if not fixture.exists():
+ pytest.skip("fixture missing")
+ resp = client.post(
+ "/api/session/import",
+ files={"file": ("net.json", fixture.read_text(), "application/json")},
+ )
+ assert resp.status_code == 200
+ body = resp.json()
+ assert body["nodes"] and body["edges"] and body["layers"]
+
+
+def test_defaults_filled_for_minimal_json() -> None:
+ s = normalize_session(_minimal())
+ assert s["scene"]["scale"] == "0.9"
+ assert s["scene"]["color"] == "#000000"
+ assert s["universalLabelColor"] == "#FFFFFF"
+ assert s["scramble_nodes"] is True # no node positions given
+ assert s["nodes"][0]["color"] # palette-assigned
+ assert s["layers"][0]["generate_coordinates"] is True
+
+
+def test_missing_mandatory_object_raises() -> None:
+ with pytest.raises(SessionValidationError):
+ normalize_session({"layers": [{"name": "L1"}], "nodes": []})
+
+
+@pytest.mark.parametrize(
+ "bad",
+ [
+ {"layers": ["x"], "nodes": [], "edges": []},
+ {"layers": "abc", "nodes": [], "edges": []},
+ {"layers": [{"name": "L"}], "nodes": ["n"], "edges": []},
+ {"layers": [{"name": "L"}], "nodes": [], "edges": ["e"]},
+ ],
+)
+def test_non_object_list_members_raise(bad) -> None:
+ # #2: non-dict members used to 500 with AttributeError instead of a 400
+ with pytest.raises(SessionValidationError):
+ normalize_session(bad)
+
+
+def test_empty_layer_name_raises() -> None:
+ bad = _minimal()
+ bad["layers"] = [{"name": ""}]
+ with pytest.raises(SessionValidationError):
+ normalize_session(bad)
+
+
+def test_duplicate_layer_names_raise() -> None:
+ bad = _minimal()
+ bad["layers"] = [{"name": "L1"}, {"name": "L1"}]
+ with pytest.raises(SessionValidationError):
+ normalize_session(bad)
+
+
+def test_node_referencing_unknown_layer_raises() -> None:
+ bad = _minimal()
+ bad["nodes"] = [{"name": "A", "layer": "L1"}, {"name": "B", "layer": "MISSING"}]
+ with pytest.raises(SessionValidationError):
+ normalize_session(bad)
+
+
+def test_edge_referencing_unknown_node_raises() -> None:
+ bad = _minimal()
+ bad["edges"] = [{"src": "A_L1", "trg": "GHOST_L1"}]
+ with pytest.raises(SessionValidationError):
+ normalize_session(bad)
+
+
+def test_channels_dropped_when_partial() -> None:
+ s = _minimal()
+ s["edges"] = [
+ {"src": "A_L1", "trg": "B_L1", "channel": "x"},
+ {"src": "B_L1", "trg": "A_L1", "channel": ""},
+ ]
+ out = normalize_session(s)
+ assert all("channel" not in e for e in out["edges"])
+ assert any("channel" in w.lower() for w in out["warnings"])
+
+
+def test_duplicate_edges_collapsed() -> None:
+ s = _minimal()
+ s["edges"] = [{"src": "A_L1", "trg": "B_L1"}, {"src": "A_L1", "trg": "B_L1"}]
+ assert len(normalize_session(s)["edges"]) == 1
+
+
+def test_export_is_download() -> None:
+ resp = client.post("/api/session/export", json=_minimal())
+ assert resp.status_code == 200
+ assert "attachment" in resp.headers["content-disposition"]
+ assert resp.json()["layers"][0]["name"] == "L1"
+
+
+def test_external_create_and_resolve(tmp_path, monkeypatch) -> None:
+ monkeypatch.setattr("app.config.TMP_PATH", str(tmp_path) + "/")
+ monkeypatch.setattr("app.routers.external.config.TMP_PATH", str(tmp_path) + "/")
+ created = client.post("/api/external", json=_minimal())
+ assert created.status_code == 200
+ token = created.json()["token"]
+ assert token in created.json()["url"]
+
+ resolved = client.get(f"/api/external/{token}")
+ assert resolved.status_code == 200
+ assert resolved.json()["layers"][0]["name"] == "L1"
+
+
+def test_external_storage_capped(tmp_path, monkeypatch) -> None:
+ # DoS guard (H2): stored sessions never exceed MAX_TOKENS — oldest are
+ # evicted so an attacker can't fill disk with unbounded token files.
+ monkeypatch.setattr("app.config.TMP_PATH", str(tmp_path) + "/")
+ monkeypatch.setattr("app.routers.external.config.TMP_PATH", str(tmp_path) + "/")
+ monkeypatch.setattr("app.routers.external.MAX_TOKENS", 5)
+
+ for _ in range(20):
+ assert client.post("/api/external", json=_minimal()).status_code == 200
+
+ files = list(tmp_path.glob("*.json"))
+ assert len(files) <= 5
+
+
+def test_external_missing_token_404(tmp_path, monkeypatch) -> None:
+ monkeypatch.setattr("app.routers.external.config.TMP_PATH", str(tmp_path) + "/")
+ assert client.get("/api/external/doesnotexist").status_code == 404
+
+
+def test_external_rejects_path_traversal(tmp_path, monkeypatch) -> None:
+ monkeypatch.setattr("app.routers.external.config.TMP_PATH", str(tmp_path) + "/")
+ resp = client.get("/api/external/..%2f..%2fetc%2fpasswd")
+ assert resp.status_code in (400, 404)
+
+
+def test_export_fixture_round_trips_through_import(tmp_path) -> None:
+ fixture = DATA / "figure1_export.json"
+ if not fixture.exists():
+ pytest.skip("fixture missing")
+ data = json.loads(fixture.read_text())
+ # export format uses scene_pan; import treats scene as optional and fills defaults
+ resp = client.post(
+ "/api/session/import",
+ files={"file": ("net.json", json.dumps(data), "application/json")},
+ )
+ assert resp.status_code == 200
diff --git a/backend/tests/test_topology.py b/backend/tests/test_topology.py
new file mode 100644
index 0000000..0704230
--- /dev/null
+++ b/backend/tests/test_topology.py
@@ -0,0 +1,156 @@
+import math
+
+import pytest
+from fastapi.testclient import TestClient
+
+from app.main import app
+from app.models.topology import TopologyRequest
+from app.services.topology import METRICS, compute_topology
+
+client = TestClient(app)
+
+
+def _star_net():
+ # star: hub H connected to A,B,C in L1 → H has degree 3, leaves degree 1
+ nodes = [{"id": f"{n}_L1", "label": n, "layer": "L1"} for n in ("H", "A", "B", "C")]
+
+ def edge(a, b):
+ return {
+ "src": f"{a}_L1",
+ "trg": f"{b}_L1",
+ "source_node": a,
+ "source_layer": "L1",
+ "target_node": b,
+ "target_layer": "L1",
+ "weight": 1.0,
+ "scaled_weight": 0.5,
+ "channel": None,
+ }
+
+ edges = [edge("H", "A"), edge("H", "B"), edge("H", "C")]
+ return nodes, edges
+
+
+def _req(metric="Degree", **kw):
+ nodes, edges = _star_net()
+ base = dict(nodes=nodes, edges=edges, metric=metric, scope="perLayer", selected_layers=["L1"])
+ base.update(kw)
+ return TopologyRequest(**base)
+
+
+@pytest.mark.parametrize("metric", list(METRICS))
+def test_every_metric_runs(metric: str) -> None:
+ scales, raw = compute_topology(_req(metric=metric))
+ assert set(scales) == {"H_L1", "A_L1", "B_L1", "C_L1"}
+ assert set(raw) == set(scales)
+
+
+def test_degree_is_raw_not_normalized() -> None:
+ _, raw = compute_topology(_req(metric="Degree"))
+ assert raw["H_L1"] == 3.0 # raw degree, not centrality (which would be 1.0)
+ assert raw["A_L1"] == 1.0
+
+
+def test_scales_mapped_into_target_range() -> None:
+ scales, _ = compute_topology(_req(metric="Degree"))
+ assert min(scales.values()) == pytest.approx(0.5)
+ assert max(scales.values()) == pytest.approx(2.5)
+
+
+def test_constant_metric_defaults_to_one() -> None:
+ # a triangle: every node has degree 2 → constant → scale defaults to 1
+ nodes = [{"id": f"{n}_L1", "label": n, "layer": "L1"} for n in ("A", "B", "C")]
+
+ def edge(a, b):
+ return {
+ "src": f"{a}_L1",
+ "trg": f"{b}_L1",
+ "source_node": a,
+ "source_layer": "L1",
+ "target_node": b,
+ "target_layer": "L1",
+ "weight": 1.0,
+ "scaled_weight": 0.5,
+ "channel": None,
+ }
+
+ edges = [edge("A", "B"), edge("B", "C"), edge("A", "C")]
+ scales, _ = compute_topology(
+ TopologyRequest(
+ nodes=nodes,
+ edges=edges,
+ metric="Degree",
+ scope="perLayer",
+ selected_layers=["L1"],
+ )
+ )
+ assert all(v == pytest.approx(1.0) for v in scales.values())
+
+
+def test_single_edge_layer_is_scored() -> None:
+ # one edge forms a valid 2-node graph; both endpoints get the default scale
+ nodes = [{"id": f"{n}_L1", "label": n, "layer": "L1"} for n in ("A", "B")]
+ edges = [
+ {
+ "src": "A_L1",
+ "trg": "B_L1",
+ "source_node": "A",
+ "source_layer": "L1",
+ "target_node": "B",
+ "target_layer": "L1",
+ "weight": 1.0,
+ "scaled_weight": 0.5,
+ "channel": None,
+ }
+ ]
+ scales, _ = compute_topology(
+ TopologyRequest(
+ nodes=nodes, edges=edges, metric="Degree", scope="perLayer", selected_layers=["L1"]
+ )
+ )
+ assert set(scales) == {"A_L1", "B_L1"}
+
+
+@pytest.mark.parametrize("bad", [-1.0, 0.0, float("inf"), float("nan")])
+def test_nonpositive_or_nonfinite_weight_clamped_not_500(bad: float) -> None:
+ # #3/#4: negative/zero crashed igraph betweenness (500) and inf/nan poisoned
+ # responses; build_graph now clamps to a positive floor so metrics compute
+ # and every scale stays finite.
+ nodes, edges = _star_net()
+ for e in edges:
+ e["scaled_weight"] = bad
+ scales, raw = compute_topology(_req(metric="Betweenness Centrality"))
+ assert set(scales) == {"H_L1", "A_L1", "B_L1", "C_L1"}
+ assert all(math.isfinite(v) for v in {**scales, **raw}.values())
+
+
+def test_unknown_metric_400() -> None:
+ nodes, edges = _star_net()
+ resp = client.post(
+ "/api/topology",
+ json={
+ "nodes": nodes,
+ "edges": edges,
+ "metric": "Nope",
+ "scope": "perLayer",
+ "selected_layers": ["L1"],
+ },
+ )
+ assert resp.status_code == 400
+
+
+def test_endpoint_returns_scales_and_raw() -> None:
+ nodes, edges = _star_net()
+ resp = client.post(
+ "/api/topology",
+ json={
+ "nodes": nodes,
+ "edges": edges,
+ "metric": "Betweenness Centrality",
+ "scope": "perLayer",
+ "selected_layers": ["L1"],
+ },
+ )
+ assert resp.status_code == 200
+ body = resp.json()
+ assert body["scales"] and body["raw"]
diff --git a/backend/uv.lock b/backend/uv.lock
new file mode 100644
index 0000000..f5aec3f
--- /dev/null
+++ b/backend/uv.lock
@@ -0,0 +1,1235 @@
+version = 1
+revision = 3
+requires-python = ">=3.11"
+resolution-markers = [
+ "python_full_version >= '3.15' and sys_platform == 'win32'",
+ "python_full_version == '3.14.*' and sys_platform == 'win32'",
+ "python_full_version >= '3.15' and sys_platform == 'emscripten'",
+ "python_full_version == '3.14.*' and sys_platform == 'emscripten'",
+ "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'",
+ "python_full_version < '3.12' and sys_platform == 'win32'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'",
+ "python_full_version < '3.12' and sys_platform == 'emscripten'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+]
+
+[[package]]
+name = "annotated-doc"
+version = "0.0.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.14.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" },
+]
+
+[[package]]
+name = "arena3d"
+version = "3.0.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "fastapi" },
+ { name = "igraph" },
+ { name = "pandas" },
+ { name = "pydantic" },
+ { name = "python-multipart" },
+ { name = "uvicorn", extra = ["standard"] },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "httpx2" },
+ { name = "mypy" },
+ { name = "pandas-stubs" },
+ { name = "pytest" },
+ { name = "pytest-asyncio" },
+ { name = "ruff" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "fastapi", specifier = ">=0.115.0" },
+ { name = "igraph", specifier = ">=0.11.0" },
+ { name = "pandas", specifier = ">=2.2.0" },
+ { name = "pydantic", specifier = ">=2.8.0" },
+ { name = "python-multipart", specifier = ">=0.0.9" },
+ { name = "uvicorn", extras = ["standard"], specifier = ">=0.30.0" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "httpx2", specifier = ">=2.5.0" },
+ { name = "mypy", specifier = ">=1.11.0" },
+ { name = "pandas-stubs", specifier = ">=3.0.3.260530" },
+ { name = "pytest", specifier = ">=8.3.0" },
+ { name = "pytest-asyncio", specifier = ">=0.24.0" },
+ { name = "ruff", specifier = ">=0.6.0" },
+]
+
+[[package]]
+name = "ast-serialize"
+version = "0.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/58/ad/0d70a3a2d6e01968d985415259e8ec7ad3f777903f9b1c1f3c8c44642c60/ast_serialize-0.6.0.tar.gz", hash = "sha256:aadd3ffcf4858c9726bf3515f7b199c7eadbe504f96028e4a87172c0da65a8fe", size = 61489, upload-time = "2026-06-30T20:02:55.555Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/12/3e5f575f156555547c250a8b0d1347517a3a20fc7f4492e9703a69d4f45e/ast_serialize-0.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a7520b672827885bafeae7501f684d14d47d17e5f45256f9df547686cca52264", size = 1177640, upload-time = "2026-06-30T20:02:06.708Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/a4/921a9e27951627983b0f368859ea00f8330a551dc0bf4c2fdcb11855a98b/ast_serialize-0.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a14191beec7e0c078d2fc1f6edc0aee88bcd4db9f18e1bc9f8052b559c22dddc", size = 1168111, upload-time = "2026-06-30T20:02:08.366Z" },
+ { url = "https://files.pythonhosted.org/packages/00/69/950cf404de7b8782cf95e5c1237e25e2aa46177b287f39f9eeddf481fd6f/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32ef62ec34cf6be20ad77d4799556638fbdf187f3ae10698dfb20ef9f2c89516", size = 1227656, upload-time = "2026-06-30T20:02:09.843Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/a8/46f8f6a6479d9d2273980957bb091a506c55f5b95d3c029ee58518a78407/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13b7769970a39983b0adf2f38917b1cd3b8946f76df045756c3d741bc689f089", size = 1227706, upload-time = "2026-06-30T20:02:11.367Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/b9/9ac415bda0a40e49eab8fea3b2741c19c98bb84d57d62c4cfc6230eb67be/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f7a408601bb3edaefb3bc67a4c01f5235e3253653b6a5729a2ee2382b35341c", size = 1431705, upload-time = "2026-06-30T20:02:12.737Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/06/8807115d441444879f7561b5eede5ac18fc80392f11826d61ccf31f503b1/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8670bfa51208a2c0c8d138928e40e998fab158f9200d53bb80c088b5b8eda7b8", size = 1249533, upload-time = "2026-06-30T20:02:14.571Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/c0/c2ba82ef9618650357d9421a1fdb27ffec862a7f57e8e2de82a3ccd11e12/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4826809eb8597a8cd59fd924b6d7c285b8969a1e0007e2cb652cab62376270f", size = 1252619, upload-time = "2026-06-30T20:02:16.219Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/a7/fa31d52dd4102cede29fb9634e98d214129b2783b4f95528c6dc6a8f6587/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:577a6c189068686869f5f1ddc38363f3ae1808a4753b577266f9202071a7bb66", size = 1242983, upload-time = "2026-06-30T20:02:17.813Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/20/ddf742b5ad3c4bafd3466f2265037cfd99bc1b9a5ee46a5d58c90d523242/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085de7f62dc9cc247eb01e965a362707d1d90b1d89a82c5bf78301a60a3c417b", size = 1296148, upload-time = "2026-06-30T20:02:19.146Z" },
+ { url = "https://files.pythonhosted.org/packages/24/cb/9f6f217cce8b3b632c5568b478d195a35e79dce4dbe309438cb89ba6ea4f/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9f8a8b78b13173de6a9ec22111d9be674874cd5bdccda04f14ae5ebc2bef403a", size = 1403826, upload-time = "2026-06-30T20:02:20.696Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/f8/9d16d4f0107a183924425cc0e7618d8bf76f96b45afa9ff19f924ed1ad57/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f2ff3baffc3a29c1f15bc9098aa0c09763410262d5e6cef42116f7356c184554", size = 1502943, upload-time = "2026-06-30T20:02:22.034Z" },
+ { url = "https://files.pythonhosted.org/packages/80/dd/bbc1c38756350dddf7e24acae1c9482ef42051c267417e019aecc1ed4075/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0067b25fce104eaae5b88383de9ab803faeb671831e14ca698b771b356e2600f", size = 1497632, upload-time = "2026-06-30T20:02:23.517Z" },
+ { url = "https://files.pythonhosted.org/packages/42/7e/9daffefcf5b97e6bb4c3e0b3c024c1aee9722f23d3cf7cd2ff80d6fb4a40/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c617417f9cbb0cb144f6283c3cbe0d2e0f01beaf9f608f662b21191058a626ec", size = 1448858, upload-time = "2026-06-30T20:02:24.889Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/1f/f9baaab81a677ea0af7d2458cac2f94ebcc85958f8a3c15ba9d9e5dab653/ast_serialize-0.6.0-cp314-cp314t-win32.whl", hash = "sha256:5337cb256dcea3df9288205213d1601581536526b8f4da44b6974f1180f3252a", size = 1052600, upload-time = "2026-06-30T20:02:26.263Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/1f/41b535866519512d8cf6669cb2cff7823b7672bb6279c0333b4ff89d7d9f/ast_serialize-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d947e45cafc4b09bd7528917fa84c517654a43de173c79785574b7b3068ac24", size = 1095570, upload-time = "2026-06-30T20:02:27.639Z" },
+ { url = "https://files.pythonhosted.org/packages/50/64/e472fe3e3a2d33d874b987e8518aedf24562919e3b6161a4fa1797e89c0f/ast_serialize-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6e15ec740436e1a0d62de848641abe5f3a2f89a7f94907d534795ac91bbacf14", size = 1067267, upload-time = "2026-06-30T20:02:28.949Z" },
+ { url = "https://files.pythonhosted.org/packages/52/19/ac8348ae8711c9b5ae834634f635780cab62a0f5e6f988882e048b89c2ae/ast_serialize-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:093cb8bb91b720d8523580498d031791bb1bbaa048599c3d21085d380e11a596", size = 1185367, upload-time = "2026-06-30T20:02:30.427Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/f6/ec7ec652c51db77c2f61d8573338e13e4704303265ccc658cb4031d9f354/ast_serialize-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e61580a69faf47e3689795367ed211f2a10fd741478cc0f36a0f128793360aad", size = 1178657, upload-time = "2026-06-30T20:02:31.964Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305802f2ce2a7c4e87835078ea85c58b586ddda8095b92fe2ead9364ae19c80a", size = 1238620, upload-time = "2026-06-30T20:02:33.664Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/21/087957bba486242afc52f49b2d9e21c9dad00289356cf9efe67084015a9d/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7b8b8f0c42f752ea00b2b7d7c090b3f80d9c1c5c75cadf16423790a0cc74081", size = 1236075, upload-time = "2026-06-30T20:02:34.936Z" },
+ { url = "https://files.pythonhosted.org/packages/82/04/78128bbb170071c2c72a210a181f1c00e11cc1cec60a8beef747b07f9201/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd5b91b9e6f2356ace3a556963b0cd783b395fbbb0bb17b4defc283415466e77", size = 1441348, upload-time = "2026-06-30T20:02:36.245Z" },
+ { url = "https://files.pythonhosted.org/packages/64/64/62fb99d6faf199b4c3e5b08a07136e9a0d7664bb249c6de3670e5b63e9b6/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d6ef91590258ada18909b9caea344dac4de2013906b035473cd674a43f4b790", size = 1258580, upload-time = "2026-06-30T20:02:37.53Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/87/b4d6c38e0ccd5e85dc54cecdf933a152c60b28fe5d993a6d8a72fa6d5896/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcbed41e9386059fc0261d602445ede0976c2ecec2939688bcbcb9ed0b6f28b7", size = 1261693, upload-time = "2026-06-30T20:02:39.123Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/4b/3676ca2191f39bafb75f93f99b2f429ec464586158fece2165f3572805dc/ast_serialize-0.6.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:cdc4e6f930b9090c2f92c9036ad12ffb8e6e44d4a5ba06f1458a05d60f203f7b", size = 1252517, upload-time = "2026-06-30T20:02:40.511Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/58/494ef8c4b4acb2f4a265ac934caf45f792a08fe27d6b853de35ad991941a/ast_serialize-0.6.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:897ac47b5637be41c0c07061c8a912fafa967ef1dc73fa115e4bfa70882a093b", size = 1304843, upload-time = "2026-06-30T20:02:41.961Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/f2/13736d920ab3d49bbee80ef1a277dd7b7aaf3b3545efd9d2a8114fe05525/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c4af9a1386166e40ed01464991806f89038a2d89782576c7774876fa77034e32", size = 1413698, upload-time = "2026-06-30T20:02:44.179Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/5a/e046f3899e2acba4677d7427b76431443a1aa1a0e583dfb05b55b69d55cf/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c901adbd750029b9ac4ad3d6aa56853e0ad4875119fbf52b7b8298afc223828b", size = 1512209, upload-time = "2026-06-30T20:02:45.584Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/c7/e42aaca7bb2d22a7c06d5a8c7930086c5a334e93d716e6fa5e6647a4515f/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae22a366b752ab4496191525b78b097b5b72d531752e3c1dd7e383a8f2c8a1a", size = 1508464, upload-time = "2026-06-30T20:02:46.942Z" },
+ { url = "https://files.pythonhosted.org/packages/95/93/5524a3dc6c3f593de3228ed9cbef73afa047625b7000ec21b7f58e6eb4d4/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4ed29121da8b3fdc291002801a1de0f76248fa07dce89157a5f277842cf6126e", size = 1457164, upload-time = "2026-06-30T20:02:48.294Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/c0/36a6ffb4d653cf621427b4c4928671f53ad800c453474de2b82564a44ad9/ast_serialize-0.6.0-cp39-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:b1dac4e09d341c1300ba69cdcbe62867b32a8c75d90db9bf4d083bec3b039f0b", size = 863014, upload-time = "2026-06-30T20:02:49.742Z" },
+ { url = "https://files.pythonhosted.org/packages/09/c7/7d5ad8b49e1278e1c2a1e0274bd7850560b3f09313aa00c13bc8d5544792/ast_serialize-0.6.0-cp39-abi3-win32.whl", hash = "sha256:82c312a7844d2fdeb4d5c48bd3d215bf940dafd4704e1a9bcf252a99010a99b1", size = 1063165, upload-time = "2026-06-30T20:02:50.98Z" },
+ { url = "https://files.pythonhosted.org/packages/47/ae/6710c14ecb276031cf10249f6adf5a59e2d3fdb3b5183bd59f70524067ee/ast_serialize-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:113b58346f9ceb664352032770caca817d4a3c86f611c6088e6ef65ddaa70f0e", size = 1101444, upload-time = "2026-06-30T20:02:52.554Z" },
+ { url = "https://files.pythonhosted.org/packages/66/40/c53deb2cd0c9b0fb636d24d9f40924cf2e65028e6b20b10cd5c1eeb2c730/ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", size = 1072965, upload-time = "2026-06-30T20:02:54.097Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "fastapi"
+version = "0.139.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-doc" },
+ { name = "pydantic" },
+ { name = "starlette" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d3/af/a5f50ccfa659ec1802cb4ca842c23f06d906a8cc9aef6016a2caeea3d4ed/fastapi-0.139.0.tar.gz", hash = "sha256:99ab7b2d92223c76d6cf10757ab3f89d45b38267fc20b2a136cf02f6beac3145", size = 423016, upload-time = "2026-07-01T16:35:33.436Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/7c/8e3c6ad324ea5cb36604fc3f968554887891c316d9dfde57761611d907ad/fastapi-0.139.0-py3-none-any.whl", hash = "sha256:cf15e1e9e667ddb0ad63811e60bd11390d1aac838ca4a7a23f421807b2308189", size = 130339, upload-time = "2026-07-01T16:35:32.19Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "httpcore2"
+version = "2.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "h11" },
+ { name = "truststore" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/47/06/5c12df521b5322fb1114a83d46911b2fbcb8855ddb3a635f11c01a214af5/httpcore2-2.5.0.tar.gz", hash = "sha256:88aa170137c17328d5ac44234f9fd10706466d5fb347f3edac4d39b91137b09d", size = 64808, upload-time = "2026-06-25T14:16:56.472Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c9/a1/7564199d1a8728fe737b0a72e5b3f8d92dfe085a74ddf7cdd83bce5f206d/httpcore2-2.5.0-py3-none-any.whl", hash = "sha256:5ce35188de461d31e8d000bfb8ef8bf22c6c16587a211e5571deaa5e9bdf842a", size = 80330, upload-time = "2026-06-25T14:16:53.634Z" },
+]
+
+[[package]]
+name = "httptools"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" },
+ { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" },
+ { url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" },
+ { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" },
+ { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" },
+ { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" },
+ { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" },
+ { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" },
+ { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" },
+ { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" },
+ { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" },
+]
+
+[[package]]
+name = "httpx2"
+version = "2.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "httpcore2" },
+ { name = "idna" },
+ { name = "truststore" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d0/e2/b5dedc0cf35aa65de5f541ccd30d2bc1fd7f1d43c9ab09f8ed9a7342317b/httpx2-2.5.0.tar.gz", hash = "sha256:e2df9cb4611021527ff8a675b1c320b610a2ec397acc8d6fe6e91df2d9b33c29", size = 83121, upload-time = "2026-06-25T14:16:57.491Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/31/22/859d8252dad9bc9adee34b52e62cde621ece07b042ccb2ab4da1be46695f/httpx2-2.5.0-py3-none-any.whl", hash = "sha256:3d2d4d9cf4b61f1a1f46a95947cfdb47e80cb56a2f91c6256ac8f58e4891df41", size = 76652, upload-time = "2026-06-25T14:16:55.23Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.18"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
+]
+
+[[package]]
+name = "igraph"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "texttable" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/23/be/56bef1919005b4caf1f71522b300d359f7faeb7ae93a3b0baa9b4f146a87/igraph-1.0.0.tar.gz", hash = "sha256:2414d0be2e4d77ee5357807d100974b40f6082bb1bb71988ec46cfb6728651ee", size = 5077105, upload-time = "2025-10-23T12:22:50.127Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a5/03/3278ad0ceb3ea0e84d8ae3a85bdded4d0e57853aeb802a200feb43847b93/igraph-1.0.0-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:c2cbc415e02523e5a241eecee82319080bf928a70b1ba299f3b3e25bf029b6d4", size = 2257415, upload-time = "2025-10-23T12:22:27.246Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/bc/6281ec7f9baaf71ee57c3b1748da2d3148d15d253e1a03006f204aa68ca5/igraph-1.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a27753cd80680a8f676c2d5a467aaa4a95e510b30748398ec4e4aeb982130e8", size = 2048555, upload-time = "2025-10-23T12:22:29.49Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/38/3cd6428a4ed4c09a56df05998438e7774fd1d799ee4fb8fc481674f5f7fc/igraph-1.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a55dc3a2a4e3fc3eba42479910c1511bfc3ecb33cdf5f0406891fd85f14b5aee", size = 5314141, upload-time = "2025-10-23T12:22:31.023Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/da/dd2867c25adbb41563720f14b5fc895c98bf88be682a3faff4f7b3118d2a/igraph-1.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2d04c2c76f686fb1f554ee35dfd3085f5e73b7965ba6b4cf06d53e66b1955522", size = 5683134, upload-time = "2025-10-23T12:22:32.423Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/40/243c118d34ab80382d7009c4dcb99b887384c3d2ce84d29eeac19e2a007a/igraph-1.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f2b52dc1757fff0fed29a9f7a276d971a11db4211569ed78b9eab36288dfcc9d", size = 6211583, upload-time = "2025-10-23T12:22:34.238Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/b7/88f433819c54b496cb0315fce28e658970cb20ff5dbd52a5a605ce2888de/igraph-1.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:05c79a2a8fca695b2f217a6fa7f2549f896f757d4db41be32a055400cb19cc30", size = 6594509, upload-time = "2025-10-23T12:22:35.831Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5d/8f7f6f619d374e959aa3664ebc4b24c10abc90c2e8efbed97f2623fadaf5/igraph-1.0.0-cp39-abi3-win32.whl", hash = "sha256:c2bce3cd472fec3dd9c4d8a3ea5b6b9be65fb30edf760beb4850760dd4f2d479", size = 2725406, upload-time = "2025-10-23T12:22:37.588Z" },
+ { url = "https://files.pythonhosted.org/packages/af/77/a85b3745cf40a0572bae2de8cd9c2a2a8af78e5cf3e880fc0a249114e609/igraph-1.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:faeff8ede0cf15eb4ded44b0fcea6e1886740146e60504c24ad2da14e0939563", size = 3221663, upload-time = "2025-10-23T12:22:39.404Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/7e/5df541c37bdf6493035e89c22bd53f30d99b291bcda6c78e9a8afeecec2b/igraph-1.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:b607cafc24b10a615e713ee96e58208ef27e0764af80140c7cc45d4724a3f2df", size = 2785701, upload-time = "2025-10-23T12:22:41.03Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/73/bf1d4dbbc9123435b3ca14bb608b243a50a4f158ecea564bf196715248d9/igraph-1.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3189c1a8e8a8f58009f3f729040eb3701254d074ed37245691d529869ec940c5", size = 2246636, upload-time = "2025-10-23T12:22:42.314Z" },
+ { url = "https://files.pythonhosted.org/packages/59/ac/28482f2af45cc0a0ca88a69d17a6ea694f58bdbd22cc876e7273a0379282/igraph-1.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ebe9502689b946301584b3cfacdbc70c58c4d664d804e39b6daa31be5c20bf46", size = 2036101, upload-time = "2025-10-23T12:22:43.957Z" },
+ { url = "https://files.pythonhosted.org/packages/56/80/806a093df1d1ddc3b30d0418b1ee56388ae7018f8ae288677ee2b3a1abaf/igraph-1.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f117683108c54330d6dc67a708e3724c13c9989885122a29781296872989a222", size = 3053403, upload-time = "2025-10-23T12:22:45.573Z" },
+ { url = "https://files.pythonhosted.org/packages/56/bf/cf7aeff230a4368c0b8bc6b02f3ea27db41db33714b51e1e8a7c1458f31b/igraph-1.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:077dbff0edb8b4ce0f9fefdf325200346d9d5db02de31872b41743de08e67a16", size = 3262472, upload-time = "2025-10-23T12:22:47.248Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/ca/dbc06072d5eea402a6dc81f387afb1b7e0c415f1d8a75232943fc4d1bfdb/igraph-1.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fe7c693b2a84a4e03ca31e65aa05a2ecd8728137fa9909ccbf6453b4200b856d", size = 3218861, upload-time = "2025-10-23T12:22:48.46Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "librt"
+version = "0.12.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c6/e0/dbd0f2a68a1c1a1991eb7921ff6014465d56608cdc9a9fb468a616210a37/librt-0.12.0.tar.gz", hash = "sha256:cb26faedbd09c6130e9c1b64d8000efec5076ffd18d606c6cd1cf02730e6d8b0", size = 203841, upload-time = "2026-06-30T16:14:29.671Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/ab/628490f42d1eba82f3c7e5821aa62013e6df7f525b7a9e92c048f8d1cc1c/librt-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f13c1e8563102c2b17581cf37fcb2c6dae7ad485ccea93ae46258998c25f9a1", size = 143821, upload-time = "2026-06-30T16:12:23.248Z" },
+ { url = "https://files.pythonhosted.org/packages/38/5f/793e8b6f4b6ac16e7d7198478c0af3670606fbb535c768d5f3e954781423/librt-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1ddff067610a122387024c4df527493b909d41e54a6e5b2d0e6c1041d6dfa09", size = 148442, upload-time = "2026-06-30T16:12:24.582Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/92/c780fe37a9e0982f3bd8fd9a631d6b95d09a5a7201c6c50366ce843b7e42/librt-0.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8dc7ebb5f3eec062398e9d0ef1938acd21b589e74286c4a8906d0183318d91b", size = 478276, upload-time = "2026-06-30T16:12:26.101Z" },
+ { url = "https://files.pythonhosted.org/packages/41/bb/226d444bc20d7dff4a19ec6c1ff2c13a76385eebddb59c9c00c923b67536/librt-0.12.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:198de569ea9d5f6f33808f1c00cc3db9de62bf4d6deafa3b052bd08255083038", size = 472337, upload-time = "2026-06-30T16:12:27.83Z" },
+ { url = "https://files.pythonhosted.org/packages/12/79/98ac0840ee90a75d4e1155c79062860b12ccca508587ff2119fc086965f2/librt-0.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e958678a8bca56016aedc891b391c0e0813ea382a874b54a2c1b313c1d232720", size = 502087, upload-time = "2026-06-30T16:12:29.443Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/72/a6b1a0d080606a7f5f646b79a1496f21d709f8563877759ace9ce5adad73/librt-0.12.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575a6eca68c8437ed4a8e0f534e31d74b562ba1049a0ee4b5f09e114bcc21be1", size = 493202, upload-time = "2026-06-30T16:12:31.077Z" },
+ { url = "https://files.pythonhosted.org/packages/69/cf/e1b036b45f2fc272205ee18bf272b47e8d684bf1a75af26db440c7504359/librt-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:86f241c50dc9e9a3f0db6dbb37a607c8205aa87b920802dabbd50b70d40f6939", size = 514139, upload-time = "2026-06-30T16:12:33.032Z" },
+ { url = "https://files.pythonhosted.org/packages/40/34/b193b3e6985469a2f8afa86c90012329c86480b6ff4f2e4bd7b5b937e134/librt-0.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:113417b934fbf38220a9c7fe94578cefbe7dbb047adcb75aa197905af2b13724", size = 519486, upload-time = "2026-06-30T16:12:34.996Z" },
+ { url = "https://files.pythonhosted.org/packages/31/9e/7de4947b1695f247c813f833e3c1e7b77b52e52a7dba2c35411cf806b58e/librt-0.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:762f17c0eb6b5d74e269126996cea8a89e35ab6464c5151619163abcd8623ae2", size = 499609, upload-time = "2026-06-30T16:12:36.663Z" },
+ { url = "https://files.pythonhosted.org/packages/59/11/f3730e04e758b1fbf215359062ad2d5b6bd0b0ab5ac46b1c140628795be7/librt-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa93b3bd7f7588c628f6e9bf66485d3467fd9a1ccdb8975b770178f39f35697", size = 542205, upload-time = "2026-06-30T16:12:38.56Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/8f/710453617eabe20e18433864f335534c8aff63fbc68d8cd9dbc70a3d08f6/librt-0.12.0-cp311-cp311-win32.whl", hash = "sha256:aaa04b44d4fe86d824616b1f9c13e34c7c01ec0c96dd2abc4f59423696f788e2", size = 98067, upload-time = "2026-06-30T16:12:40.102Z" },
+ { url = "https://files.pythonhosted.org/packages/42/53/401bff50a56e95daf151d911c99adf5732af2190e8f4d11886c9a229103c/librt-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:9aaeeddb8e7e4ae3bb9f944e0e618418cb91c0071d5ddbfcc3584b3cf59d39f0", size = 118346, upload-time = "2026-06-30T16:12:41.388Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/9a/a3a9078fe88bfc2d2d99dcf1c18593938ae830089cf84c3b2532a6c49d63/librt-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:18a2402fa3123ab76ecca670e6fb33038fde7c1e91181b885226ec4d30af2c2c", size = 104760, upload-time = "2026-06-30T16:12:43.112Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/1a/5bec493821b0e85b91de4f234912b50133d1aedb875048eef27938ec3f96/librt-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9bce19aa7c05f91c989f9da7b567f81d21d57a2e6501e2b811aa0f3f79614c1a", size = 146756, upload-time = "2026-06-30T16:12:44.395Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/d0/cc04b48a57c1f275387f5578847214c4a6c21bfb24c6c8c8d6ba753fe403/librt-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0ace09f5bf4d982fe726015f102fb856658b41580597104e301e630ed1d8d86", size = 145537, upload-time = "2026-06-30T16:12:45.95Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/10/c02325556beb2aa158c9e549ddade8cc9a23b36cdad14756dbed730c1ff1/librt-0.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d007efe9243ede81ce75990ad7aa172da1e2024144b3eff17ba46a5fff1fff3c", size = 488637, upload-time = "2026-06-30T16:12:47.658Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/9e/7b49ca1c30baa9c8df96024aa09a97c35a97455e36004c9b5311703c56f3/librt-0.12.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:ad324a5e4858388a4864915b90a42efc8b374376393f14b9940f2454e791912b", size = 483651, upload-time = "2026-06-30T16:12:49.283Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/71/03c8c8cec39645fda451132ff9d6d662fc5aea42a1a188a77a4fddb35906/librt-0.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10a40cf74cdd97b6f8f905056db73f5d459783de2ca04c6ebd1bf47652818e7e", size = 518359, upload-time = "2026-06-30T16:12:50.999Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/ec/a9f357f94bbcba92277d22af22cff42ef706ae5d9d6d58b69bebf3a67954/librt-0.12.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:92e61c09de95217ae02a9d17f4f66cf073253cdc51bcfdc0f15c62c9a70baa85", size = 509510, upload-time = "2026-06-30T16:12:52.631Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/34/717055325d028743aa01a7691ad59a63352a26a8ff2e7eeb0c9249514150/librt-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0461344061d6fc3718940f5855d95647831cef6d03a6c7506897f98222784ad4", size = 527302, upload-time = "2026-06-30T16:12:54.244Z" },
+ { url = "https://files.pythonhosted.org/packages/95/f8/7612eeedb3395d92f7c6a84dca5f15e282d650483a4dc01aa5b9cffdfda3/librt-0.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e6dfe89074732c9287b3c0f5a6af575c9ede380a788013876cc7b14fe0da0361", size = 532568, upload-time = "2026-06-30T16:12:55.74Z" },
+ { url = "https://files.pythonhosted.org/packages/79/1e/a9afe85d5bb8b65dc27be3809ed1d69082079e1e9717fd2c66aa9939600c/librt-0.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9efed79d51ad1383bba0855f613cca7aa91c943e709af2413ac7f4bb9936ce08", size = 521579, upload-time = "2026-06-30T16:12:57.884Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/1e/93aebb219d52c37ea578f83b0588cd7b040974e464d4e435086a48b4dc4d/librt-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1eac6cc0e23e448fb3c1446ed85ff796afb616eed5897c978d35dbec030b7c7c", size = 558743, upload-time = "2026-06-30T16:12:59.577Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/85/1680c0ec332f238e3145c5608d313ab0a43281e210a5dd87e3bc3cc25631/librt-0.12.0-cp312-cp312-win32.whl", hash = "sha256:0ab8ee0210047ae86ca023ccfbfe3df82077fd1c9bc021aebbf37d993ef64af0", size = 99200, upload-time = "2026-06-30T16:13:01.015Z" },
+ { url = "https://files.pythonhosted.org/packages/30/0e/abca12d8904875aa2ad66327390a3f7b1b75ebc43c0a00fc763cecf32ea5/librt-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:51c8bfa12632c81b94401c101bcedd0c56c3a1f8fa3273ca3472b28cd2f54003", size = 119390, upload-time = "2026-06-30T16:13:02.493Z" },
+ { url = "https://files.pythonhosted.org/packages/32/a5/4203481b6d3a3bb348c82ac71abf1fcb4cb3ae8422a24a8dee4cd3ac5bd7/librt-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:5eebd451f5def089369ba6d8ff0291303d035e8154f9f26f7633835c5b029ade", size = 105117, upload-time = "2026-06-30T16:13:03.952Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/87/568d948c8079c9ff3c9e8110cf85f1eb70218e1209af29d0b7b89aa4a60c/librt-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8d9a55760a34ae5ce70434aabb6a6c61c6c44a0ec58ca1cfd9cd86e4745d417d", size = 146808, upload-time = "2026-06-30T16:13:05.417Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/1d/bea471ecea210088847bb5f3c4b4b424d596518934c06679b78ca85d6e63/librt-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff0b197e338b4cf432873e0d6ef025213fdea85311ec4d87d2ea88c28adf2409", size = 145503, upload-time = "2026-06-30T16:13:07.023Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/9e/984ad422b56de95fdce158f06b051655373784ebea0aba9a7fcbc41614d1/librt-0.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e69f120a20b69e2539d603bbd4d62db38399b10f8bf73a1cf445038a621e8af", size = 488421, upload-time = "2026-06-30T16:13:08.492Z" },
+ { url = "https://files.pythonhosted.org/packages/50/03/1a2f94009b07ea71f8e1a4cfe53370565b56da9caa341b89e0699325e9f5/librt-0.12.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:fde3cde595e947fc8e755b0a21f919a1622483d07c662d00496e040773d22591", size = 483488, upload-time = "2026-06-30T16:13:10.169Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/3b/084bdc295823fbb6ab91670047adf8f420787f9e8794bf2d140b66dc196b/librt-0.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d977447315fa09ea4e8c7ae9b4e22f7659b5128161c1fd55ff786b5349f73503", size = 518428, upload-time = "2026-06-30T16:13:11.681Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/22/5a307390b93a115ffbecd95c64eecb4e56269680e45e9415ada7285f2cf4/librt-0.12.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ffac8a67e4143cea9a549d4822b93bc0bbaad73fc25aa0ab0ba5ec27d178677", size = 509744, upload-time = "2026-06-30T16:13:13.217Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/90/83f3cb6184f5d669660717b4b2e317c9ddaccf7ca5bb97f2196deac1a3b7/librt-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:94af1ed773ff104ef08ef3d669a0ba9d3a5916c609eb698cffe5d5476d66ff9b", size = 527749, upload-time = "2026-06-30T16:13:15.277Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/3b/f162be5cc88d47378e3a20776fe425fa1c2bece755da15e2783ebf06d3d6/librt-0.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:548199d21d22fb26398dfbbe0ba953a52465c66f3a49f38e6fddce1b127faf53", size = 532582, upload-time = "2026-06-30T16:13:17.074Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/28/6c5d2f6b7232fd24f284fc4cab37a459fe69a9096a09942f44cc5c55e073/librt-0.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c8f1f413b966a9dd3ecf80cd337b0ad7bb3de2474a4ff448ed3ebabfc3f803fc", size = 522235, upload-time = "2026-06-30T16:13:18.823Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/1c/bd115360587fdc22c8ae8fac14c040a556b442e2965d4370d2cf274c8b95/librt-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55f13f95b629be5b6ab38918e439bf14169d6f9a8deaae55e0c14e12fb0c74b9", size = 559055, upload-time = "2026-06-30T16:13:20.509Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/5a/c26f49f576437014825a86faea3cec60c1ed17f976abd567b6c12b8e35a7/librt-0.12.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:8b2dc079dfe29e77a47a19073d2040fa4879aa3656501f1650f8402ddce0313c", size = 79809, upload-time = "2026-06-30T16:13:22.401Z" },
+ { url = "https://files.pythonhosted.org/packages/69/0b/a55244261d9ad7375ac039b8af06d42602722e2e8b8d8d6b86e4a3888c02/librt-0.12.0-cp313-cp313-win32.whl", hash = "sha256:da58944be8270f2bfee628a9a2a60c1cf6a12c8bea8e2c9b6edf3e5414ca7793", size = 99308, upload-time = "2026-06-30T16:13:23.661Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/bf/ed9465e58d44c5a5637795547d0841c8934aab905ea452cac1adf14672cf/librt-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:1db4be3037e4ce065a071fa7deee93e78ebc25f448340a02a6c1c0b82c37e383", size = 119438, upload-time = "2026-06-30T16:13:25.188Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/44/3cad652aeb892e6e8ffe48d0fafa2bc652f28ec7ed3f4403fcbb1be4f948/librt-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:05fd2542892ad770b5dd45003fd080477cf220b611d3ee59b0792097eb0873a9", size = 105118, upload-time = "2026-06-30T16:13:26.533Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/51/3a0e05618c12423b6fc5141b590ec02a6efb645833edc8736a6c7b46d1ec/librt-0.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b37ee42e09722284a6d9288fe44a191f7276060a3195939bb77c6502058dbb34", size = 145579, upload-time = "2026-06-30T16:13:27.909Z" },
+ { url = "https://files.pythonhosted.org/packages/77/9e/fd399d099dfb4020f3f7c34e7e6210c389fa89f7d79ca92f5afb0395f278/librt-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ade11988728b3e4768dadc5696e82c60e9b35fc95335a9b4d1f5d69e753ccec7", size = 150139, upload-time = "2026-06-30T16:13:29.357Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/ee/610239fbd8c4b005443664c5d4c3bc1717daedd8c71369bf45011aa87194/librt-0.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f351ed425380e39bd86df382578aa5b8c5b98e2e265112de7379e7d030258150", size = 480457, upload-time = "2026-06-30T16:13:30.78Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/10/ceddc9010f26c541444be36e1153a79b64626694db2d33a524c719fa3e46/librt-0.12.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:857d2163e088c868967717ace8e980017fd868a735f3de010412af02bdc30319", size = 479002, upload-time = "2026-06-30T16:13:32.398Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/f1/b1523d9718e8192e5403e6b41a02742e17ba554369f0729b9f30ab590e2d/librt-0.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2befc80aa5f2f5b93f28abaaf11feff6677931dd548320e44c52deaa9399744", size = 510527, upload-time = "2026-06-30T16:13:34.615Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/0e/0f3ff43befb18a531615736791e52fb67eaa71ff7b89e6e5f7004b64cc6e/librt-0.12.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:be3694dcfa97c6715dd19ac73d3e1b21a805514a5785663e57fecacd3ff64e5a", size = 500988, upload-time = "2026-06-30T16:13:36.408Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/1a/0278ea4a9e599dc507c43839a87f2c764ad04bf69418e2d763d58659e55f/librt-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2d5f67e86f45638843d025b0828f2e9e55fc45ff9180d2618ccdeaf72a796050", size = 519318, upload-time = "2026-06-30T16:13:37.883Z" },
+ { url = "https://files.pythonhosted.org/packages/59/55/090e10e62be2f35265e41601337f83ac9f83be9aca1bf92692e3a82effdd/librt-0.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:64572c85e4ab7d572c9b72cd76b5f90b21181b1459fa6b1aac6f8958c4fcff31", size = 527127, upload-time = "2026-06-30T16:13:39.682Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/34/8052c9ec678be6ba751279947831f089aa69b009000b985ce91d1979669a/librt-0.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8b961912b0e688c1eb4658a46bdb0606b31918d65597fbe7356ca83aa653ffcc", size = 509766, upload-time = "2026-06-30T16:13:41.266Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/f8/8761b36189e9ec8dc20b49fa84cef22852c6c41fcda56f760f7fc1360da5/librt-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:722375903e3f079436a7a33da51ce73931536dd041f9feb01536f05d8e010c96", size = 552043, upload-time = "2026-06-30T16:13:43.197Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/98/7283971ef6b70269938b49c7b25f670ec6325d252265fbcc996f9b364379/librt-0.12.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:a5a96a8f536b65ef1bf910c09e7e71647edde5111f6e1b51f413c6fba5bfe71b", size = 79472, upload-time = "2026-06-30T16:13:44.64Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/5e/b30940dea935e8ac5bd0e0abb1985f5274590d557ac3a252ca0d5392ce52/librt-0.12.0-cp314-cp314-win32.whl", hash = "sha256:8ffc99c356f1777c506e1b69dc303879153ae2640ba15b8f3d4448bc87139149", size = 94246, upload-time = "2026-06-30T16:13:45.962Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/4e/0af9fe63f35fa304da3b05688f30ff6a329bcc59581b1cc51dc87fd30141/librt-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:1e68fb20798f455cda41d20a306a23c901218883f17a4bab1ed6e1331b265fb7", size = 114951, upload-time = "2026-06-30T16:13:47.279Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/8e/843c495d7db35e13b84cd533898fa89145c40dc255da0bc316d53d631464/librt-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:2df534f97916cf38ec9b1ddafeb68ae1a4cd4a54775ff26a797026774c0517cf", size = 100562, upload-time = "2026-06-30T16:13:48.699Z" },
+ { url = "https://files.pythonhosted.org/packages/75/30/c686d0f978d5fd6867c5bbad96b015c9445746764d1c228e16a2d30d9382/librt-0.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c09e581b1c2b8a62b809d4f4bd101ca3de93791e5b0ed1a14085d911be3dee3f", size = 153897, upload-time = "2026-06-30T16:13:50.017Z" },
+ { url = "https://files.pythonhosted.org/packages/40/46/f6f2d77ce46628b48fb5280709013b5109cf3a2c46a2472093cdfc03519d/librt-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:976888d0d831402086e641018bcc3208e0a38f0835789da91f72894b2cb4161f", size = 156391, upload-time = "2026-06-30T16:13:51.462Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/46/cd790c7e19e460779471530ffab454541d6ea4a3b7d338cad7f16ff96995/librt-0.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:563c37cdb41d08fe1e3f08b201abac0e317ca18e88b91285466ee0a585797520", size = 564151, upload-time = "2026-06-30T16:13:53.146Z" },
+ { url = "https://files.pythonhosted.org/packages/54/12/724559a15fb023cbdef7aee1e81fbfbc3ee22fd09009baa816cea63e3a60/librt-0.12.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:b97eb1a3140e279cc76f85b0fb92b7eb3dfbe0471260ee878bc9dc4bf9a0d649", size = 546002, upload-time = "2026-06-30T16:13:54.665Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/7e/f9d8c257ab4909f101c7c13734367749e782fd8625545f0343502c2f09f1/librt-0.12.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06e0623351ab9904cf628245f99c714586f4dd23dc740b88c8bc670d8401a847", size = 584204, upload-time = "2026-06-30T16:13:56.301Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/33/64665810575ac23b6cb6ef364de51309b7803620c12885b6e895ebc29591/librt-0.12.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da12f017b2e404554be14d466cd992459feaa44f252b0f18d909a85266ce1237", size = 573688, upload-time = "2026-06-30T16:13:58.1Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/01/27522995c6627455abc7a939d57535fb1a7836d398ccedb3d7585f46039e/librt-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d97f31003a5c86b9e78155a829572c3a26484064fb7ac1d9695fe628bd93d029", size = 604719, upload-time = "2026-06-30T16:13:59.831Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/1f/099e61b1b688551d6d2ce9d4d2ae2242a938759db8551e6cbac7f7176ee5/librt-0.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:bd43a6c69876aef4f04eaae3d3b99b0be64755fda274002fa445b92480bf664e", size = 598183, upload-time = "2026-06-30T16:14:01.457Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/c1/050400249665503bdd5b83cec518fa7b183b609341c8dcd58161775c4226/librt-0.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c01755c72fca1dc6b8d5c2ed228b8e7b2ffe184675c22f0f05ebd8fe188b9250", size = 582559, upload-time = "2026-06-30T16:14:03.29Z" },
+ { url = "https://files.pythonhosted.org/packages/da/d1/eef8f0e6722518b65a3d3bcd9309f9f44e208ce5d6728070820f988e7078/librt-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:625ae561d5fa36400856dcc27464400d047bc2d5e3446be88f437b03fefd72e4", size = 626375, upload-time = "2026-06-30T16:14:04.957Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/78/f0bb41a6f2bbd3c77bdcc66980dc0d69ca1192a0ecec25377afcc5e6db73/librt-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8d73191883553ee0739741544bf3b00aba2a1224e45d9580b30cbc29e21dc03b", size = 97752, upload-time = "2026-06-30T16:14:06.555Z" },
+ { url = "https://files.pythonhosted.org/packages/92/24/e279c27972ab051a070237cfa45728fa51670c3f22f1a4d391711e9f4c31/librt-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e1cbb037324e759f0afa270229731ff0047772667f3cb38ef5df2cabf0175ede", size = 119562, upload-time = "2026-06-30T16:14:07.908Z" },
+ { url = "https://files.pythonhosted.org/packages/06/e6/42a475bfca683b0cd5366f6dd06580062b7e567bb8534d225c877c2f14f3/librt-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bca1472acbd473eff61059b4409f802c5a1bcb4cd0344d06f939df9c4c125d40", size = 104282, upload-time = "2026-06-30T16:14:09.29Z" },
+]
+
+[[package]]
+name = "mypy"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "ast-serialize" },
+ { name = "librt", marker = "platform_python_implementation != 'PyPy'" },
+ { name = "mypy-extensions" },
+ { name = "pathspec" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" },
+ { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" },
+ { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" },
+ { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" },
+ { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" },
+ { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" },
+ { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" },
+ { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" },
+ { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" },
+ { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" },
+ { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" },
+ { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" },
+ { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" },
+ { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" },
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.4.6"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.12' and sys_platform == 'win32'",
+ "python_full_version < '3.12' and sys_platform == 'emscripten'",
+ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" },
+ { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" },
+ { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" },
+ { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" },
+ { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" },
+ { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" },
+ { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" },
+ { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" },
+ { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" },
+ { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" },
+ { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" },
+ { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" },
+ { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" },
+ { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" },
+ { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" },
+ { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" },
+ { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" },
+ { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" },
+ { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" },
+ { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" },
+ { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" },
+ { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" },
+ { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" },
+ { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" },
+ { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" },
+ { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" },
+ { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" },
+ { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" },
+ { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" },
+ { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" },
+ { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.5.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.15' and sys_platform == 'win32'",
+ "python_full_version == '3.14.*' and sys_platform == 'win32'",
+ "python_full_version >= '3.15' and sys_platform == 'emscripten'",
+ "python_full_version == '3.14.*' and sys_platform == 'emscripten'",
+ "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" },
+ { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" },
+ { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" },
+ { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" },
+ { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" },
+ { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" },
+ { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" },
+ { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" },
+ { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" },
+ { url = "https://files.pythonhosted.org/packages/db/f4/731b6085a83faf6ca843394cbd5e217280c214399f7e8b21b9f552af0ae2/numpy-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8", size = 16795063, upload-time = "2026-07-04T17:07:07.374Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/64/0e215f2048dd11a55bb989ed41b3585ef57452404e638d703a211a3e4157/numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75", size = 11776652, upload-time = "2026-07-04T17:07:09.907Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/59/2b844c7a6e9deff69b404a66221e1542937734f65d5e6e39411876053862/numpy-2.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2", size = 5335944, upload-time = "2026-07-04T17:07:12.227Z" },
+ { url = "https://files.pythonhosted.org/packages/86/51/9bf7cb2cabcebc9e017e4ec7e6322b378317a542c08b4cb68479c1efc716/numpy-2.5.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b", size = 6656266, upload-time = "2026-07-04T17:07:14.368Z" },
+ { url = "https://files.pythonhosted.org/packages/83/3e/fb7615b211b82a32f44d5180a6d421b61f84d4fadd578b48ba4ac34e189f/numpy-2.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95", size = 15179720, upload-time = "2026-07-04T17:07:16.272Z" },
+ { url = "https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21", size = 16664835, upload-time = "2026-07-04T17:07:19.021Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/2f/97d6475ee91afe2587797d09446f9d3e475ad4cb681662d824809327b75a/numpy-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373", size = 16539135, upload-time = "2026-07-04T17:07:22.015Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/5b/4db81e4ba0be7e2776b1de68c82aa862c7f8ec27e1b4927d4ae075e20678/numpy-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438", size = 18426684, upload-time = "2026-07-04T17:07:24.941Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/64/c0ba2d90724d450279a7df8f32057241070250a26a7e2b5337d77347f481/numpy-2.5.1-cp314-cp314-win32.whl", hash = "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace", size = 6116103, upload-time = "2026-07-04T17:07:27.622Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/1a/837f9ed7405adcd7a40538792eb169eddd8fa5630c16a1ef49dae71a30f4/numpy-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a", size = 12562177, upload-time = "2026-07-04T17:07:29.887Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ed/49707938b6dd0a78a9178dd93227dc89e4c11af47f5c798d70366e8d0483/numpy-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0", size = 10627739, upload-time = "2026-07-04T17:07:32.568Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/c7/bb4b882cfe7f299cbc8b66e42e7dd78cf9d14e40f9469fc5e3db7e15b3bd/numpy-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22", size = 11894709, upload-time = "2026-07-04T17:07:34.941Z" },
+ { url = "https://files.pythonhosted.org/packages/40/3f/5af7f4a7f6224aef48017aa82bb6174c7a659d724be0c75017b7e64a55b4/numpy-2.5.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7", size = 5453810, upload-time = "2026-07-04T17:07:37.495Z" },
+ { url = "https://files.pythonhosted.org/packages/20/c9/3474309bc94d634d3f9c3eddf03250ecb8c22cd948ef16fef69a77cc5d7b/numpy-2.5.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d", size = 6761189, upload-time = "2026-07-04T17:07:39.563Z" },
+ { url = "https://files.pythonhosted.org/packages/90/8a/558ae39fdd55d7e7f7fef9a84a6e964ac6b23edbd2a07e52bb084500507d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09", size = 15225039, upload-time = "2026-07-04T17:07:41.682Z" },
+ { url = "https://files.pythonhosted.org/packages/63/27/ca7392b2d030277bdf0273e7d23255b3ee57d57a7c170a6f4fb3981e1e5d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4", size = 16701306, upload-time = "2026-07-04T17:07:44.611Z" },
+ { url = "https://files.pythonhosted.org/packages/02/42/03d53ae7996c44d4374a8262e9dc41671fd56cbb98f7d47ef85cf5da4c6b/numpy-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1", size = 16589955, upload-time = "2026-07-04T17:07:47.694Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/15/6c1784ae469640e65db111e9a34b3d0f14d91e8a38b9ce34810ced370dbb/numpy-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077", size = 18464252, upload-time = "2026-07-04T17:07:50.684Z" },
+ { url = "https://files.pythonhosted.org/packages/94/a8/f98e50356cf167df656c526c2dfeec2d7dde182f2a3da4b458a5938e2776/numpy-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf", size = 6263298, upload-time = "2026-07-04T17:07:53.445Z" },
+ { url = "https://files.pythonhosted.org/packages/72/ac/96ae880cdecad0b3275d9359fcec72667b49a4863c9f12942e43679dda02/numpy-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af", size = 12748623, upload-time = "2026-07-04T17:07:55.384Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
+]
+
+[[package]]
+name = "pandas"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
+ { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+ { name = "python-dateutil" },
+ { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/42/16/b5c76b838fd9bf6ce84d3a53346b8874ec05c5f0040d75ef2c320100cd2a/pandas-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:455f6f8139d4282188f526868dbc3c828470e88a3d9d59a891bd46a455f21b98", size = 10338495, upload-time = "2026-05-11T18:52:11.558Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639", size = 9938250, upload-time = "2026-05-11T18:52:17.005Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/b2/3323601a52caee42c019e370090ca4544b241437240ca04f786cce82b0cf/pandas-3.0.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2", size = 10770558, upload-time = "2026-05-11T18:52:19.865Z" },
+ { url = "https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27", size = 11274611, upload-time = "2026-05-11T18:52:22.622Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/4f/eafabf2d5fae5adf143b4d18d3706c5efdc368a7c4eb1ee8a3eddabbd0f6/pandas-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:14da8316da4d0c5a77618425996bfb1248ca87fc2c1486e6fde4652bd18b5824", size = 11784670, upload-time = "2026-05-11T18:52:25.4Z" },
+ { url = "https://files.pythonhosted.org/packages/49/44/1eb20389301b57b19cc099a1c2f662501f72f08a65f912d05822613c1532/pandas-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a55066a0505dae0ba2b50a46637db34b46f9094c65c5d4800794ef6335010938", size = 12353708, upload-time = "2026-05-11T18:52:28.139Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6674ab18ad8c57802867264b00e15e7bb904700cdd9046e3b2fa1fce237439ea", size = 9907609, upload-time = "2026-05-11T18:52:30.982Z" },
+ { url = "https://files.pythonhosted.org/packages/53/85/1b7f563ebc6357c27233a02a96b589bcce1fa9c6eb89fb4f0e56421d277e/pandas-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:5cc09a68b3120e0f54870dede8287a7bb1fa463907e4fcec1ea77cab6179bf7a", size = 9165596, upload-time = "2026-05-11T18:52:33.334Z" },
+ { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" },
+ { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" },
+ { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" },
+ { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" },
+ { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" },
+ { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" },
+ { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" },
+ { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" },
+ { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" },
+ { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" },
+ { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" },
+ { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" },
+ { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" },
+ { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" },
+ { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" },
+ { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" },
+ { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" },
+ { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" },
+]
+
+[[package]]
+name = "pandas-stubs"
+version = "3.0.3.260530"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
+ { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3d/aa/c41a8a0ff86fd85dbb3ec0c1f3fa488ca64a8b5f82654ae1b07d84acefe5/pandas_stubs-3.0.3.260530.tar.gz", hash = "sha256:d1efe47b2e5a312c047d7feabec5cb7a55365747983420077e9fcbe9ab74f714", size = 113183, upload-time = "2026-05-30T17:47:40.34Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0b/e0/99ec5b02203c4e9ce878bc63d8caa06ac1f891e4d63bded9a5ced70fcb4f/pandas_stubs-3.0.3.260530-py3-none-any.whl", hash = "sha256:a6277eb1c8cebf48d9b2413fcd2e9a6b4ff479c934a223c29eacbc3058c4cb55", size = 173780, upload-time = "2026-05-30T17:47:39.13Z" },
+]
+
+[[package]]
+name = "pathspec"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pydantic"
+version = "2.13.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-types" },
+ { name = "pydantic-core" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.46.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" },
+ { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" },
+ { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" },
+ { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" },
+ { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" },
+ { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" },
+ { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
+ { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
+ { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
+ { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
+ { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
+ { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
+ { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
+ { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
+ { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
+ { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
+ { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
+ { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
+ { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
+ { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
+ { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
+ { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
+ { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
+ { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
+ { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
+ { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
+ { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
+ { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
+ { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
+ { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
+ { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
+ { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
+ { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" },
+ { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" },
+ { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
+ { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
+ { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" },
+ { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" },
+ { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.20.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
+]
+
+[[package]]
+name = "pytest-asyncio"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pytest" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "python-dotenv"
+version = "1.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
+]
+
+[[package]]
+name = "python-multipart"
+version = "0.0.32"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
+ { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
+ { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
+ { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
+ { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
+ { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
+ { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
+ { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
+ { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
+ { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.15.20"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" },
+ { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" },
+ { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" },
+ { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" },
+ { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" },
+ { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" },
+ { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" },
+ { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" },
+ { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" },
+ { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "starlette"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" },
+]
+
+[[package]]
+name = "texttable"
+version = "1.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1c/dc/0aff23d6036a4d3bf4f1d8c8204c5c79c4437e25e0ae94ffe4bbb55ee3c2/texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638", size = 12831, upload-time = "2023-10-03T09:48:12.272Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917", size = 10768, upload-time = "2023-10-03T09:48:10.434Z" },
+]
+
+[[package]]
+name = "truststore"
+version = "0.10.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
+]
+
+[[package]]
+name = "tzdata"
+version = "2026.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" },
+]
+
+[[package]]
+name = "uvicorn"
+version = "0.50.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2e/41/06cce5dbb9f77591512957710ac709e60b12e6216a2f2d0d607fd49706e8/uvicorn-0.50.0.tar.gz", hash = "sha256:0c92e1bc2259cb7faa4fcef774a5966588f2e88542744550b66799fba10b76f1", size = 93257, upload-time = "2026-07-04T05:03:26.33Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/3a/eb70620ca2bf8213603d5c731460687c49fee38b0072f0b4a637781f0a53/uvicorn-0.50.0-py3-none-any.whl", hash = "sha256:05f0eb19edf38208f79f43df8a63081b48df31b0cd1e5997be957a4dc97d1b19", size = 72716, upload-time = "2026-07-04T05:03:24.848Z" },
+]
+
+[package.optional-dependencies]
+standard = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "httptools" },
+ { name = "python-dotenv" },
+ { name = "pyyaml" },
+ { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
+ { name = "watchfiles" },
+ { name = "websockets" },
+]
+
+[[package]]
+name = "uvloop"
+version = "0.22.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" },
+ { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" },
+ { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" },
+ { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" },
+ { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" },
+ { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" },
+ { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" },
+ { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" },
+ { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" },
+ { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" },
+ { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" },
+ { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" },
+ { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" },
+ { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" },
+]
+
+[[package]]
+name = "watchfiles"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fc/3d/8024c801df84d1587740d0359e7fdd80afeae3d159011f3d5376dd82f18e/watchfiles-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201", size = 400242, upload-time = "2026-05-18T04:31:19.014Z" },
+ { url = "https://files.pythonhosted.org/packages/87/5b/f4dfd45323e949984a3a7f9dc31d1cbb049921e7d98253488dda72ccdaa9/watchfiles-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5", size = 394562, upload-time = "2026-05-18T04:30:08.46Z" },
+ { url = "https://files.pythonhosted.org/packages/98/d8/19483ef075d601c409bce8bcbb5c0f81a10876fff870400568f08ce484a1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a", size = 456611, upload-time = "2026-05-18T04:30:45.723Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/6a/cc81fbe7ee42f2f22e661a6e12def7807e01b14b2f39e0ff83fd373fd307/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1", size = 461379, upload-time = "2026-05-18T04:31:29.292Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/57/7e669002082c0a0f4fb5113bb70125f7110124b846b0a11bc5ae8e90eac1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717", size = 493556, upload-time = "2026-05-18T04:30:05.44Z" },
+ { url = "https://files.pythonhosted.org/packages/45/7d/f60a2b19807b21fe8281f3a8da4f59eef0d5f96825ac4680ba2d4f2ebf91/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b", size = 575255, upload-time = "2026-05-18T04:30:40.568Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/49/77f5b5e6efbcd57482f74948ebb1b97e5c0046d6b61475042d830c84b3ff/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5", size = 467052, upload-time = "2026-05-18T04:31:17.942Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/5a/73e2959af1b97fd5d556f9a8bdba017be23ceeef731869d5eaa0a753d5a3/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e", size = 456858, upload-time = "2026-05-18T04:30:30.182Z" },
+ { url = "https://files.pythonhosted.org/packages/50/57/1bc8c27fad7e6c19bddee15d276dbb6ab72480ec01c127afff1673aee417/watchfiles-1.2.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165", size = 467579, upload-time = "2026-05-18T04:32:15.897Z" },
+ { url = "https://files.pythonhosted.org/packages/09/6c/3c2e44edba3553c5e3c3b8c8a2a6dee6b9e12ae2cf4bd2378bebf9dc3038/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6", size = 633253, upload-time = "2026-05-18T04:31:37.123Z" },
+ { url = "https://files.pythonhosted.org/packages/30/c2/d8c84a882ab39bbefcc4915ab3e91830b7a7e990c5570b0b69075aba3faf/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5", size = 660713, upload-time = "2026-05-18T04:31:24.62Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/07/f97736a5fc605364fe67b25e9fa4a6965dfd4840d50c406ada507e9d735f/watchfiles-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8", size = 277222, upload-time = "2026-05-18T04:31:21.131Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/99/2b04981977fc2608afd60360d928c6aecf6b950292ca221d98f4005f6694/watchfiles-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22", size = 290274, upload-time = "2026-05-18T04:31:45.966Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/74/f7f58a7075ee9cf612b0cfcddb78b8cd8234f0742d6f0075cf0da2dde1c6/watchfiles-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7", size = 283460, upload-time = "2026-05-18T04:31:39.126Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" },
+ { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" },
+ { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" },
+ { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" },
+ { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" },
+ { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" },
+ { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" },
+ { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" },
+ { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" },
+ { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" },
+ { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" },
+ { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" },
+ { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" },
+ { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" },
+ { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" },
+ { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" },
+ { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" },
+ { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" },
+ { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" },
+ { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" },
+ { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" },
+ { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" },
+ { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" },
+ { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" },
+ { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" },
+ { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" },
+ { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" },
+ { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" },
+ { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" },
+ { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" },
+ { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" },
+ { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" },
+ { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" },
+ { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" },
+ { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" },
+ { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" },
+ { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" },
+ { url = "https://files.pythonhosted.org/packages/23/f4/7513ef1e85fc4c6331b59479d6d72661fc391fbe543678052ac72c8b6c19/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2", size = 403050, upload-time = "2026-05-18T04:30:36.753Z" },
+ { url = "https://files.pythonhosted.org/packages/27/0b/a54103cfd732bb703c7a749222011a0483ef3705948dae3b203158601119/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db", size = 396629, upload-time = "2026-05-18T04:32:03.268Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/2c/73f31a3b893886206c3f54d73e8ad8dee58cdb2f69ad2622e0a8a9e07f4e/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7", size = 457318, upload-time = "2026-05-18T04:31:01.932Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/f9/45d021e4a5cc7b9dd567f7cbb06d3b75f751a690063fb6cc7ec60f4e46b7/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0", size = 457771, upload-time = "2026-05-18T04:30:56.331Z" },
+]
+
+[[package]]
+name = "websockets"
+version = "16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" },
+ { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" },
+ { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" },
+ { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" },
+ { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" },
+ { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" },
+ { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" },
+ { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" },
+ { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" },
+ { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" },
+ { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" },
+ { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" },
+ { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" },
+ { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" },
+ { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" },
+ { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" },
+ { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" },
+ { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" },
+ { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" },
+ { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
+]
diff --git a/config/global_variables.R b/config/global_variables.R
deleted file mode 100644
index fc9f95b..0000000
--- a/config/global_variables.R
+++ /dev/null
@@ -1 +0,0 @@
-TOPOLOGY_METRICS <- c("Degree", "Clustering Coefficient", "Betweenness Centrality")
diff --git a/config/server_variables.R b/config/server_variables.R
deleted file mode 100644
index 7afbd5e..0000000
--- a/config/server_variables.R
+++ /dev/null
@@ -1,31 +0,0 @@
-# Input ####
-READ_LIBRARY <- "base" # options: base, fread; fread faster load but slower operations
-MANDATORY_NETWORK_COLUMNS <- c("SourceNode", "SourceLayer", "TargetNode", "TargetLayer")
-OPTIONAL_NETWORK_COLUMNS <- c("Channel", "Weight")
-MANDATORY_JSON_OBJECTS <- c("layers", "nodes", "edges")
-OPTIONAL_JSON_OBJECTS <- c("scene", "universalLabelColor", "direction", "edgeOpacityByWeight")
-MANDATORY_JSON_NODE_COLUMNS <- c("name", "layer")
-MANDATORY_JSON_EDGE_COLUMNS <- c("src", "trg")
-MAX_EDGES <- 10000
-MAX_CHANNELS <- 9
-MAX_LAYERS <- 18
-DEFAULT_MAP_VALUE <- 0.3
-TARGET_NODE_SCALE_MIN <- 0.5
-TARGET_NODE_SCALE_MAX <- 2.5
-# API
-POST_REQUEST_PATH <- 'tmp/'
-API_URL <- "https://bib.fleming.gr/bib/api/arena3dweb/vr/" # "http://localhost:8080/api/arena3dweb/vr/"
-# UI ####
-HIDE_BUTTONS <- paste0("hideButton", c(1:8))
-FLOOR_DEFAULT_COLOR <- "#777777"
-FLOOR_DEFAULT_WIDTH <- "1000"
-# Themes ####
-NODE_COLORS <- c(RColorBrewer::brewer.pal(12, 'Set3'),
- RColorBrewer::brewer.pal(6, 'Set3')) # 18 Layers
-EDGE_DEFAULT_COLOR = "#CFCFCF"
-CHANNEL_COLORS_LIGHT <- RColorBrewer::brewer.pal(MAX_CHANNELS, 'Set3')
-CHANNEL_COLORS_DARK <- RColorBrewer::brewer.pal(MAX_CHANNELS, 'Set1')
-# VR ####
-VR_DOWNSCALE_FACTOR <- 300
-# Layouts ####
-NO_EDGE_LAYOUTS <- c("Circle", "Grid", "Random")
diff --git a/config/static_variables.R b/config/static_variables.R
deleted file mode 100644
index f4958af..0000000
--- a/config/static_variables.R
+++ /dev/null
@@ -1,2 +0,0 @@
-networkDF <- data.frame()
-node_layerDF <- data.frame()
diff --git a/config/ui_variables.R b/config/ui_variables.R
deleted file mode 100644
index dfde144..0000000
--- a/config/ui_variables.R
+++ /dev/null
@@ -1 +0,0 @@
-YEAR <- substr(Sys.Date(), 1, 4)
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..40ce1aa
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,39 @@
+services:
+ backend:
+ image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim
+ working_dir: /app
+ volumes:
+ - ./backend:/app
+ - backend-venv:/app/.venv
+ command: uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
+ # Explicit here (matching config.py's defaults) so the resource-limit DoS
+ # fix is deployment-tunable without editing code — see backend/app/config.py.
+ environment:
+ - ARENA_MAX_EDGES=10000
+ - ARENA_MAX_NODES=20000
+ ports:
+ - "8000:8000"
+
+ frontend:
+ image: node:22-slim
+ working_dir: /app
+ volumes:
+ - ./frontend:/app
+ - frontend-modules:/app/node_modules
+ command: sh -c "npm install && npm run dev -- --host"
+ environment:
+ - VITE_PROXY_TARGET=http://backend:8000
+ # Fix for Vite's "Blocked request" host-check error when this dev
+ # server is reverse-proxied under a public domain — see vite.config.ts.
+ - VITE_ALLOWED_HOSTS=pavlopoulos-lab-services.org
+ # Sub-path deployment fix: matches the Apache ProxyPass prefix
+ # (/arena3/) so generated asset URLs resolve through the proxy.
+ - VITE_BASE_PATH=/arena3/
+ ports:
+ - "5173:5173"
+ depends_on:
+ - backend
+
+volumes:
+ backend-venv:
+ frontend-modules:
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000..18805c2
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+dist/
+test-results/
+playwright-report/
+.playwright-cli/
+*.tsbuildinfo
diff --git a/frontend/.prettierrc b/frontend/.prettierrc
new file mode 100644
index 0000000..ad4d895
--- /dev/null
+++ b/frontend/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5"
+}
diff --git a/frontend/e2e/context-menu.spec.ts b/frontend/e2e/context-menu.spec.ts
new file mode 100644
index 0000000..54ea370
--- /dev/null
+++ b/frontend/e2e/context-menu.spec.ts
@@ -0,0 +1,47 @@
+import { expect, test } from '@playwright/test'
+
+// Regression: the right-click node menu must survive the mouse leaving the
+// canvas on its way to the menu. mouseleave routes through clickUp; before the
+// fix it called removeContextMenu and the menu vanished before it was usable.
+test('context menu survives canvas mouseleave', async ({ page }) => {
+ await page.goto('/')
+ await page.waitForFunction(
+ () => '__arena' in (window as unknown as Record)
+ )
+
+ await page.getByRole('tab', { name: 'File' }).click()
+ await page.getByRole('button', { name: 'Load Example' }).click()
+ await expect(page.locator('#file_status')).toContainText('Loaded network')
+
+ // layerX/layerY the menu builder expects for node 0 (mirrors its own math).
+ const at = await page.evaluate(() => {
+ const ctx = (
+ window as unknown as {
+ __arena: {
+ ctx: {
+ xBoundMax: number
+ yBoundMax: number
+ nodeObjects: { getWorldPosition: (a: string) => number }[]
+ }
+ }
+ }
+ ).__arena.ctx
+ const n = ctx.nodeObjects[0]
+ return {
+ x: ctx.xBoundMax + n.getWorldPosition('x'),
+ y: ctx.yBoundMax - n.getWorldPosition('y'),
+ }
+ })
+
+ const canvas = page.locator('#app canvas')
+ await canvas.dispatchEvent('contextmenu', { layerX: at.x, layerY: at.y })
+ await expect(page.locator('select.optionsBox')).toHaveCount(1)
+
+ // The buggy trigger: moving toward the menu leaves the canvas.
+ await canvas.dispatchEvent('mouseleave', { button: 0 })
+ await expect(page.locator('select.optionsBox')).toHaveCount(1)
+
+ // A real left-click on the canvas still dismisses it.
+ await canvas.dispatchEvent('mouseup', { button: 0 })
+ await expect(page.locator('select.optionsBox')).toHaveCount(0)
+})
diff --git a/frontend/e2e/minimap.spec.ts b/frontend/e2e/minimap.spec.ts
new file mode 100644
index 0000000..1007c20
--- /dev/null
+++ b/frontend/e2e/minimap.spec.ts
@@ -0,0 +1,63 @@
+import { expect, test } from '@playwright/test'
+
+// 2D navigator: appears on network load and its thumbnail re-projects when the
+// scene rotates (the core requirement — it must always show the current
+// monitor 2D projection).
+
+test('minimap shows on load and redraws on rotation', async ({ page }) => {
+ await page.goto('/')
+ await page.waitForFunction(
+ () => '__arena' in (window as unknown as Record)
+ )
+
+ // Hidden until a network exists.
+ await expect(page.locator('#minimap')).toBeHidden()
+
+ await page.getByRole('tab', { name: 'File' }).click()
+ await page.getByRole('button', { name: 'Load Example' }).click()
+ await expect(page.locator('#file_status')).toContainText('Loaded network')
+
+ const minimap = page.locator('#minimap')
+ await expect(minimap).toBeVisible()
+
+ const snapshot = () =>
+ page.evaluate(() =>
+ (
+ document.querySelector('#minimap canvas') as HTMLCanvasElement
+ ).toDataURL()
+ )
+
+ // Let the render loop draw at least one frame.
+ await page.waitForFunction(() => {
+ const c = document.querySelector('#minimap canvas') as HTMLCanvasElement
+ const g = c.getContext('2d')!
+ const { data } = g.getImageData(0, 0, c.width, c.height)
+ return data.some((v) => v !== 0) // something was drawn
+ })
+
+ const before = await snapshot()
+
+ // Rotate the scene; the thumbnail must change.
+ await page.evaluate(() => {
+ ;(
+ window as unknown as {
+ __arena: { ctx: { scene: { rotateY: (r: number) => void } } }
+ }
+ ).__arena.ctx.scene.rotateY(0.6)
+ })
+ await page.waitForFunction(
+ (prev) =>
+ (
+ document.querySelector('#minimap canvas') as HTMLCanvasElement
+ ).toDataURL() !== prev,
+ before,
+ { timeout: 5_000 }
+ )
+
+ // Toggle via the Scene panel hides it. dispatchEvent, not click()/uncheck():
+ // the post-load WebGL render loop saturates the main thread on headless CI,
+ // so Playwright's actionability wait times out — fire the handler directly.
+ await page.getByRole('tab', { name: 'Scene Actions' }).dispatchEvent('click')
+ await page.locator('#toggleMinimap').dispatchEvent('click')
+ await expect(minimap).toBeHidden()
+})
diff --git a/frontend/e2e/network-flow.spec.ts b/frontend/e2e/network-flow.spec.ts
new file mode 100644
index 0000000..a5492aa
--- /dev/null
+++ b/frontend/e2e/network-flow.spec.ts
@@ -0,0 +1,119 @@
+import { expect, test } from '@playwright/test'
+
+// Full flow from PLAN Phase 14: upload (example) network → apply layout →
+// apply clustered layout → export session. The WebGL canvas is opaque to the
+// DOM, so scene assertions read window.__arena.ctx (the Phase 12 test hook).
+
+// Set a value and fire change directly — Playwright's selectOption
+// waits for actionability, which times out under the post-load render-loop
+// main-thread starvation on headless CI.
+async function selectOption(
+ page: import('@playwright/test').Page,
+ selector: string,
+ value: string
+) {
+ await page.locator(selector).evaluate((el, v) => {
+ ;(el as HTMLSelectElement).value = v as string
+ el.dispatchEvent(new Event('change', { bubbles: true }))
+ }, value)
+}
+
+type NodeSnapshot = { pos: [number, number, number]; cluster: string }
+
+function readNodes() {
+ return async (): Promise => {
+ const ctx = (
+ window as unknown as { __arena: { ctx: { nodeObjects: unknown[] } } }
+ ).__arena.ctx
+ return (
+ ctx.nodeObjects as {
+ getPosition: (a: string) => number
+ cluster: string
+ }[]
+ ).map((n) => ({
+ pos: [n.getPosition('x'), n.getPosition('y'), n.getPosition('z')],
+ cluster: n.cluster,
+ }))
+ }
+}
+
+test('load example → layout → clustered layout → export', async ({ page }) => {
+ await page.goto('/')
+ await page.waitForFunction(
+ () => '__arena' in (window as unknown as Record)
+ )
+
+ // --- Upload: Load Example -----------------------------------------------
+ await page.getByRole('tab', { name: 'File' }).click()
+ await page.getByRole('button', { name: 'Load Example' }).click()
+ await expect(page.locator('#file_status')).toContainText('Loaded network')
+ const nodeCount = await page.evaluate(
+ () =>
+ (window as unknown as { __arena: { ctx: { nodeObjects: unknown[] } } })
+ .__arena.ctx.nodeObjects.length
+ )
+ expect(nodeCount).toBe(255)
+
+ const before = await page.evaluate(readNodes())
+
+ // --- Apply layout (all layers, Fruchterman-Reingold) --------------------
+ // dispatchEvent, not click()/check(): once a network loads the WebGL render
+ // loop saturates the main thread on headless CI, so Playwright's actionability
+ // wait times out (and force-clicks land on drawer overlays, never toggling
+ // the input). Firing the handler directly on the element sidesteps both.
+ await page
+ .getByRole('tab', { name: 'Layer Selection & Layouts' })
+ .dispatchEvent('click')
+ await page.locator('#subgraph_allLayers').dispatchEvent('click')
+ await page.locator('#selectAllLayersCheckbox').dispatchEvent('click')
+ await selectOption(page, '#layoutAlgorithmChoice', 'Fruchterman-Reingold')
+ await page.locator('#runLayout').dispatchEvent('click')
+ // The #layouts_status text is identical after every run, so wait on the
+ // actual scene change (a moved node) rather than the status string.
+ const beforeJson = JSON.stringify(before.map((n) => n.pos))
+ await page.waitForFunction(
+ (prev) => {
+ const ctx = (
+ window as unknown as {
+ __arena: {
+ ctx: { nodeObjects: { getPosition: (a: string) => number }[] }
+ }
+ }
+ ).__arena.ctx
+ const now = JSON.stringify(
+ ctx.nodeObjects.map((n) => [
+ n.getPosition('x'),
+ n.getPosition('y'),
+ n.getPosition('z'),
+ ])
+ )
+ return now !== prev
+ },
+ beforeJson,
+ { timeout: 15_000 }
+ )
+
+ // --- Apply clustered layout (Louvain) -----------------------------------
+ await selectOption(page, '#clusteringAlgorithmChoice', 'Louvain')
+ await page.locator('#runLayout').dispatchEvent('click')
+ // Wait for clustering to assign ids (status text is unchanged between runs).
+ await page.waitForFunction(
+ () => {
+ const ctx = (
+ window as unknown as {
+ __arena: { ctx: { nodeObjects: { cluster: string }[] } }
+ }
+ ).__arena.ctx
+ return ctx.nodeObjects.some((n) => n.cluster !== '')
+ },
+ undefined,
+ { timeout: 15_000 }
+ )
+
+ // --- Export session ------------------------------------------------------
+ await page.getByRole('tab', { name: 'File' }).dispatchEvent('click')
+ const downloadPromise = page.waitForEvent('download')
+ await page.getByRole('button', { name: 'Save Session' }).dispatchEvent('click')
+ const download = await downloadPromise
+ expect(download.suggestedFilename()).toMatch(/\.json$/)
+})
diff --git a/frontend/e2e/themes.spec.ts b/frontend/e2e/themes.spec.ts
new file mode 100644
index 0000000..002e12c
--- /dev/null
+++ b/frontend/e2e/themes.spec.ts
@@ -0,0 +1,51 @@
+import { expect, test } from '@playwright/test'
+
+// Theme bar (v2 port): buttons appear after a network loads; clicking a
+// preset recolours the scene; undo restores the previous theme. The canvas
+// is opaque to the DOM, so assertions read window.__arena.ctx (applyTheme
+// sets ctx.edgeDefaultColor, distinct per theme).
+
+function edgeColor() {
+ return () =>
+ (window as unknown as { __arena: { ctx: { edgeDefaultColor: string } } })
+ .__arena.ctx.edgeDefaultColor
+}
+
+test('theme buttons attach on load and switch scene theme (undoable)', async ({
+ page,
+}) => {
+ await page.goto('/')
+ await page.waitForFunction(
+ () => '__arena' in (window as unknown as Record)
+ )
+
+ // No buttons before a network exists.
+ await expect(page.locator('#themeDiv .themeButton')).toHaveCount(0)
+
+ await page.getByRole('tab', { name: 'File' }).click()
+ await page.getByRole('button', { name: 'Load Example' }).click()
+ await expect(page.locator('#file_status')).toContainText('Loaded network')
+
+ await expect(page.locator('#themeDiv .themeButton')).toHaveCount(3)
+
+ // dispatchEvent, not click(): once a network loads the WebGL render loop
+ // saturates the main thread on headless CI, so Playwright's actionability
+ // wait (visible/stable/receives-events) times out. Firing the handler
+ // directly on the element sidesteps that and the drawer-overlay hit-test.
+ await page.locator('#lightThemeButton').dispatchEvent('click')
+ expect(await page.evaluate(edgeColor())).toBe('#5c5c5c')
+ // Regression: floor_color input is the picker-path source of truth, so a
+ // theme toggle must update it — otherwise hover-exit repaint reverts layers
+ // to the stale (dark) color.
+ await expect(page.locator('#floor_color')).toHaveValue('#8aa185')
+
+ await page.locator('#grayThemeButton').dispatchEvent('click')
+ expect(await page.evaluate(edgeColor())).toBe('#6e2a5a')
+
+ await page.locator('#darkThemeButton').dispatchEvent('click')
+ expect(await page.evaluate(edgeColor())).toBe('#ffffff')
+
+ // Undo walks back dark → gray.
+ await page.locator('#undoButton').dispatchEvent('click')
+ expect(await page.evaluate(edgeColor())).toBe('#6e2a5a')
+})
diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js
new file mode 100644
index 0000000..3d53844
--- /dev/null
+++ b/frontend/eslint.config.js
@@ -0,0 +1,16 @@
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ ...tseslint.configs.recommendedTypeChecked,
+ {
+ languageOptions: {
+ parserOptions: {
+ project: true,
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ },
+ {
+ ignores: ['dist/**'],
+ }
+)
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..afb0394
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,220 @@
+
+
+
+
+
+ Arena3D
+
+
+
+
+
+
+
+
+
+
+
+ Home
+
+
+
+
+ Main View
+
+
+
+
+ File
+
+
+
+
+ Layer Selection & Layouts
+
+
+
+
+ Scene Actions
+
+
+
+
+ Layer Actions
+
+
+
+
+ Node Actions
+
+
+
+
+ Edge Actions
+
+
+
+
+ View Data
+
+
+
+
+ FPS
+
+
+
+
+ Help
+
+
+
+
+
+ ↶ Undo
+
+
+ ↷ Redo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No network loaded yet. Open the File tab to upload a network or
+ load an example.
+
+
+
+
+
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..71a493e
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,2596 @@
+{
+ "name": "arena3d",
+ "version": "3.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "arena3d",
+ "version": "3.0.0",
+ "dependencies": {
+ "bootstrap": "^5.3.8",
+ "three": "^0.185.1"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.61.1",
+ "@types/bootstrap": "^5.2.11",
+ "@types/three": "^0.185.0",
+ "eslint": "^10.6.0",
+ "prettier": "^3.9.4",
+ "typescript": "^6.0.3",
+ "typescript-eslint": "^8.62.1",
+ "vite": "^8.1.3",
+ "vitest": "^4.1.9"
+ }
+ },
+ "node_modules/@dimforge/rapier3d-compat": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
+ "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
+ "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.2",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
+ "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
+ "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.23.5",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz",
+ "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^3.0.5",
+ "debug": "^4.3.1",
+ "minimatch": "^10.2.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz",
+ "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz",
+ "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
+ "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz",
+ "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
+ "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.3"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.138.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz",
+ "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.61.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz",
+ "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.61.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz",
+ "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz",
+ "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz",
+ "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz",
+ "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz",
+ "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz",
+ "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz",
+ "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz",
+ "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz",
+ "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz",
+ "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz",
+ "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz",
+ "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz",
+ "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.11.1",
+ "@emnapi/runtime": "1.11.1",
+ "@napi-rs/wasm-runtime": "^1.1.6"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz",
+ "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz",
+ "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tweenjs/tween.js": {
+ "version": "23.1.3",
+ "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
+ "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
+ "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/bootstrap": {
+ "version": "5.2.11",
+ "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.11.tgz",
+ "integrity": "sha512-69ykbLiNzQxi6Q/y/aYJLrlzI5QkLQjhIYW2U+cxujBxAmaBtmXKtxnFes59Ttfpt936as6xJU0dYnmVpcwIwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@popperjs/core": "^2.9.2"
+ }
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/esrecurse": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+ "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/stats.js": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
+ "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/three": {
+ "version": "0.185.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.0.tgz",
+ "integrity": "sha512-O2Uy8Cj4Nonr8dWUUbifMdPe8B0Mq7EdOHb89S4+kjUw/KhbjTZrUuYlrQ1bpUKG+EP9QJnN7qNxbHGlGoLHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@dimforge/rapier3d-compat": "~0.12.0",
+ "@tweenjs/tween.js": "~23.1.3",
+ "@types/stats.js": "*",
+ "@types/webxr": ">=0.5.17",
+ "fflate": "~0.8.2",
+ "meshoptimizer": "~1.1.1"
+ }
+ },
+ "node_modules/@types/webxr": {
+ "version": "0.5.24",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
+ "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz",
+ "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.62.1",
+ "@typescript-eslint/type-utils": "8.62.1",
+ "@typescript-eslint/utils": "8.62.1",
+ "@typescript-eslint/visitor-keys": "8.62.1",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.62.1",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz",
+ "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.62.1",
+ "@typescript-eslint/types": "8.62.1",
+ "@typescript-eslint/typescript-estree": "8.62.1",
+ "@typescript-eslint/visitor-keys": "8.62.1",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz",
+ "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.62.1",
+ "@typescript-eslint/types": "^8.62.1",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz",
+ "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.62.1",
+ "@typescript-eslint/visitor-keys": "8.62.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz",
+ "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz",
+ "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.62.1",
+ "@typescript-eslint/typescript-estree": "8.62.1",
+ "@typescript-eslint/utils": "8.62.1",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz",
+ "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz",
+ "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.62.1",
+ "@typescript-eslint/tsconfig-utils": "8.62.1",
+ "@typescript-eslint/types": "8.62.1",
+ "@typescript-eslint/visitor-keys": "8.62.1",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz",
+ "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.62.1",
+ "@typescript-eslint/types": "8.62.1",
+ "@typescript-eslint/typescript-estree": "8.62.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz",
+ "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.62.1",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
+ "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz",
+ "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz",
+ "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz",
+ "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.9",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz",
+ "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz",
+ "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz",
+ "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.17.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
+ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "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/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/bootstrap": {
+ "version": "5.3.8",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
+ "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "@popperjs/core": "^2.11.8"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
+ "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz",
+ "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz",
+ "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==",
+ "dev": true,
+ "license": "MIT",
+ "workspaces": [
+ "packages/*"
+ ],
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.5",
+ "@eslint/config-helpers": "^0.6.0",
+ "@eslint/core": "^1.2.1",
+ "@eslint/plugin-kit": "^0.7.2",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^9.1.2",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.2.0",
+ "esquery": "^1.7.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "minimatch": "^10.2.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+ "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^5.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/meshoptimizer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz",
+ "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.15",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
+ "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/obug": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
+ "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.61.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz",
+ "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.61.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.61.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz",
+ "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.16",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
+ "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.9.4",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz",
+ "integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz",
+ "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.138.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.1.4",
+ "@rolldown/binding-darwin-arm64": "1.1.4",
+ "@rolldown/binding-darwin-x64": "1.1.4",
+ "@rolldown/binding-freebsd-x64": "1.1.4",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.1.4",
+ "@rolldown/binding-linux-arm64-gnu": "1.1.4",
+ "@rolldown/binding-linux-arm64-musl": "1.1.4",
+ "@rolldown/binding-linux-ppc64-gnu": "1.1.4",
+ "@rolldown/binding-linux-s390x-gnu": "1.1.4",
+ "@rolldown/binding-linux-x64-gnu": "1.1.4",
+ "@rolldown/binding-linux-x64-musl": "1.1.4",
+ "@rolldown/binding-openharmony-arm64": "1.1.4",
+ "@rolldown/binding-wasm32-wasi": "1.1.4",
+ "@rolldown/binding-win32-arm64-msvc": "1.1.4",
+ "@rolldown/binding-win32-x64-msvc": "1.1.4"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/three": {
+ "version": "0.185.1",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz",
+ "integrity": "sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==",
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
+ "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.62.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz",
+ "integrity": "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.62.1",
+ "@typescript-eslint/parser": "8.62.1",
+ "@typescript-eslint/typescript-estree": "8.62.1",
+ "@typescript-eslint/utils": "8.62.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.1.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz",
+ "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.16",
+ "rolldown": "~1.1.3",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.3.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz",
+ "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.9",
+ "@vitest/mocker": "4.1.9",
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/runner": "4.1.9",
+ "@vitest/snapshot": "4.1.9",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.9",
+ "@vitest/browser-preview": "4.1.9",
+ "@vitest/browser-webdriverio": "4.1.9",
+ "@vitest/coverage-istanbul": "4.1.9",
+ "@vitest/coverage-v8": "4.1.9",
+ "@vitest/ui": "4.1.9",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..b8c4a62
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "arena3d",
+ "version": "3.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc --noEmit && vite build",
+ "preview": "vite preview",
+ "lint": "eslint src",
+ "format": "prettier --write src",
+ "test": "vitest",
+ "test:e2e": "playwright test"
+ },
+ "dependencies": {
+ "bootstrap": "^5.3.8",
+ "three": "^0.185.1"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.61.1",
+ "@types/bootstrap": "^5.2.11",
+ "@types/three": "^0.185.0",
+ "eslint": "^10.6.0",
+ "prettier": "^3.9.4",
+ "typescript": "^6.0.3",
+ "typescript-eslint": "^8.62.1",
+ "vite": "^8.1.3",
+ "vitest": "^4.1.9"
+ }
+}
diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts
new file mode 100644
index 0000000..68bf373
--- /dev/null
+++ b/frontend/playwright.config.ts
@@ -0,0 +1,22 @@
+import { defineConfig, devices } from '@playwright/test'
+
+// E2E runs against the dev server with the backend proxied on /api. Both must
+// be up: the webServer block starts Vite; start the backend separately
+// (cd backend && uv run uvicorn app.main:app --port 8000) or via docker-compose.
+export default defineConfig({
+ testDir: './e2e',
+ fullyParallel: false,
+ retries: process.env.CI ? 2 : 0,
+ reporter: [['list']],
+ use: {
+ baseURL: 'http://localhost:5173',
+ trace: 'on-first-retry',
+ },
+ projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
+ webServer: {
+ command: 'npm run dev',
+ url: 'http://localhost:5173',
+ reuseExistingServer: true,
+ timeout: 60_000,
+ },
+})
diff --git a/frontend/public/data/Arena3DApp_aspirin.json b/frontend/public/data/Arena3DApp_aspirin.json
new file mode 100644
index 0000000..5dc1da8
--- /dev/null
+++ b/frontend/public/data/Arena3DApp_aspirin.json
@@ -0,0 +1,751 @@
+{
+ "scene": {
+ "position_x": "85",
+ "position_y": "-19",
+ "scale": "0.809455827869314",
+ "color": "#ffffff",
+ "rotation_x": "-2.78896419700599",
+ "rotation_y": "-0.302066641937101",
+ "rotation_z": "0.0029771022900188"
+ },
+ "layers": [
+ {
+ "name": "enriched_term",
+ "position_x": "442.058002797784",
+ "position_y": "160.901161622552",
+ "position_z": "233.674720828911",
+ "last_layer_scale": "1",
+ "rotation_x": "0",
+ "rotation_y": "0",
+ "rotation_z": "0",
+ "floor_current_color": "#ddb3ff",
+ "geometry_parameters_width": "603.326074473483"
+ },
+ {
+ "name": "protein",
+ "position_x": "-81.1191257882335",
+ "position_y": "-78.6699211047243",
+ "position_z": "-125.599774259377",
+ "last_layer_scale": "1",
+ "rotation_x": "0",
+ "rotation_y": "0",
+ "rotation_z": "0",
+ "floor_current_color": "#ddb3ff",
+ "geometry_parameters_width": "603.326074473483"
+ },
+ {
+ "name": "compound",
+ "position_x": "-410.500521507677",
+ "position_y": "120.553179984998",
+ "position_z": "326.354756898516",
+ "last_layer_scale": "1",
+ "rotation_x": "0",
+ "rotation_y": "0",
+ "rotation_z": "0",
+ "floor_current_color": "#ddb3ff",
+ "geometry_parameters_width": "603.326074473483"
+ }
+ ],
+ "nodes": [
+ {
+ "name": "Small cell lung cancer",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "-184.390180387424",
+ "position_z": "-0.235312365146985",
+ "scale": "0.5",
+ "color": "#D9D9D9",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "Regulation of lipolysis in adipocytes",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "-208.898649698721",
+ "position_z": "96.0095153453069",
+ "scale": "0.5",
+ "color": "#D9D9D9",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "Serotonergic synapse",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "-248.554186056634",
+ "position_z": "59.2472624012911",
+ "scale": "0.5",
+ "color": "#D9D9D9",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "Platelet activation",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "-248.554186056634",
+ "position_z": "-59.7178871315854",
+ "scale": "0.5",
+ "color": "#D9D9D9",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "Arachidonic acid metabolism",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "-208.898649698721",
+ "position_z": "-96.4801400756012",
+ "scale": "0.5",
+ "color": "#D9D9D9",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "Thrombosis",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "255.264259857242",
+ "position_z": "-5.5065133261044",
+ "scale": "0.5",
+ "color": "#3690C0",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "Blood platelet",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "60.0846252433915",
+ "position_z": "-1.45083035896154",
+ "scale": "0.653846153846154",
+ "color": "#99D8C9",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "Connective tissue",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "25.6560387280491",
+ "position_z": "54.1468041959962",
+ "scale": "0.807692307692308",
+ "color": "#99D8C9",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "Peripheral artery disease",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "279.629653012579",
+ "position_z": "-84.5585799875458",
+ "scale": "0.5",
+ "color": "#3690C0",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "Blood vessel",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "-23.1214547872933",
+ "position_z": "-1.45083035896154",
+ "scale": "0.807692307692308",
+ "color": "#99D8C9",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "Blood",
+ "layer": "enriched_term",
+ "position_x": "-10",
+ "position_y": "1.74119372804912",
+ "position_z": "-80.9633099139193",
+ "scale": "1.11538461538462",
+ "color": "#99D8C9",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "TP53",
+ "layer": "protein",
+ "position_x": "-10",
+ "position_y": "-243.463965867211",
+ "position_z": "37.4827708709913",
+ "scale": "1.42307692307692",
+ "color": "#FF0000",
+ "url": "",
+ "descr": "Cellular tumor antigen p53; Acts as a tumor suppressor in many tumor types; induces growth arrest or apoptosis depending on the physiological circumstances and cell type. Involved in cell cycle regulation as a trans-activator that acts to negatively regulate cell division by controlling a set of genes required for this process. One of the activated genes is an inhibitor of cyclin-dependent kinases. Apoptosis induction seems to be mediated either by stimulation of BAX and FAS antigen expression or by repression of Bcl-2 expression. In cooperation with mitochondrial PPIF is involved in activating oxidative stress-induced necrosis; the function is largely independent of transcription. Induces the transcription of long intergenic non-coding RNA p21 (lincRNA-p21) and lincRNA- Mkln1. LincRNA-p21 participates in TP53-dependent transcriptional repression leading to apoptosis and seem to have to effect on cell-cycle regulation. Implicated in Notch signaling cross-over. Prevents CDK7 kinase activity when associated to CAK complex in response to DNA damage thus stopping cell cycle progression. Isoform 2 enhances the transactivation activity of isoform 1 from some but not all TP53-inducible promoters. Isoform 4 suppresses transactivation activity and impairs growth suppression mediated by isoform 1. Isoform 7 inhibits isoform 1-mediated apoptosis. Regulates the circadian clock by repressing CLOCK-ARNTL/BMAL1- mediated transcriptional activation of PER2."
+ },
+ {
+ "name": "warfarin",
+ "layer": "compound",
+ "position_x": "-10",
+ "position_y": "-183.309324858094",
+ "position_z": "-133.489189479766",
+ "scale": "0.961538461538462",
+ "color": "#FFC580",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "ibuprofen",
+ "layer": "compound",
+ "position_x": "-10",
+ "position_y": "55.5495507646588",
+ "position_z": "254.759524441928",
+ "scale": "1.11538461538462",
+ "color": "#E8FF00",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "clopidogrel",
+ "layer": "compound",
+ "position_x": "-10",
+ "position_y": "219.148397777884",
+ "position_z": "-254.453267529946",
+ "scale": "0.961538461538462",
+ "color": "#AEFF80",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "SELP",
+ "layer": "protein",
+ "position_x": "-10",
+ "position_y": "162.757038084004",
+ "position_z": "-124.383602137524",
+ "scale": "1.88461538461538",
+ "color": "#00FF2E",
+ "url": "",
+ "descr": "Platelet activation dependent granule-external membrane protein; Ca(2+)-dependent receptor for myeloid cells that binds to carbohydrates on neutrophils and monocytes. Mediates the interaction of activated endothelial cells or platelets with leukocytes. The ligand recognized is sialyl-Lewis X. Mediates rapid rolling of leukocyte rolling over vascular surfaces during the initial steps in inflammation through interaction with PSGL1; Belongs to the selectin/LECAM family."
+ },
+ {
+ "name": "TBXA2R",
+ "layer": "protein",
+ "position_x": "-10",
+ "position_y": "269.733593919743",
+ "position_z": "61.7684463326417",
+ "scale": "1.57692307692308",
+ "color": "#80FFDC",
+ "url": "",
+ "descr": "Thromboxane A2 receptor; Receptor for thromboxane A2 (TXA2) a potent stimulator of platelet aggregation. The activity of this receptor is mediated by a G-protein that activates a phosphatidylinositol-calcium second messenger system. In the kidney the binding of TXA2 to glomerular TP receptors causes intense vasoconstriction. Activates phospholipase C. Isoform 1 activates adenylyl cyclase. Isoform 2 inhibits adenylyl cyclase."
+ },
+ {
+ "name": "CRP",
+ "layer": "protein",
+ "position_x": "-10",
+ "position_y": "60.2105170350341",
+ "position_z": "-150.420318051157",
+ "scale": "1.73076923076923",
+ "color": "#00B9FF",
+ "url": "",
+ "descr": "C-reactive protein pentraxin-related; Displays several functions associated with host defense: it promotes agglutination bacterial capsular swelling phagocytosis and complement fixation through its calcium-dependent binding to phosphorylcholine. Can interact with DNA and histones and may scavenge nuclear material released from damaged circulating cells; Short pentraxins"
+ },
+ {
+ "name": "salicylate",
+ "layer": "compound",
+ "position_x": "-10",
+ "position_y": "190.916466660542",
+ "position_z": "121.062673551418",
+ "scale": "1.26923076923077",
+ "color": "#8097FF",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "PTGS1",
+ "layer": "protein",
+ "position_x": "-10",
+ "position_y": "88.2359754509011",
+ "position_z": "144.912065235429",
+ "scale": "2.19230769230769",
+ "color": "#5D00FF",
+ "url": "",
+ "descr": "Prostaglandin-endoperoxide synthase 1 (prostaglandin G/H synthase and cyclooxygenase); Converts arachidonate to prostaglandin H2 (PGH2) a committed step in prostanoid synthesis. Involved in the constitutive production of prostanoids in particular in the stomach and platelets. In gastric epithelial cells it is a key step in the generation of prostaglandins such as prostaglandin E2 (PGE2) which plays an important role in cytoprotection. In platelets it is involved in the generation of thromboxane A2 (TXA2) which promotes platelet activation and aggregation vasoconstriction and proliferation of vascular smooth muscle cells; Belongs to the prostaglandin G/H synthase family."
+ },
+ {
+ "name": "PTGS2",
+ "layer": "protein",
+ "position_x": "-10",
+ "position_y": "-35.1740864612164",
+ "position_z": "122.803637014825",
+ "scale": "2.5",
+ "color": "#F380FF",
+ "url": "",
+ "descr": "Prostaglandin-endoperoxide synthase 2 (prostaglandin G/H synthase and cyclooxygenase); Converts arachidonate to prostaglandin H2 (PGH2) a committed step in prostanoid synthesis. Constitutively expressed in some tissues in physiological conditions such as the endothelium kidney and brain and in pathological conditions such as in cancer. PTGS2 is responsible for production of inflammatory prostaglandins. Up-regulation of PTGS2 is also associated with increased cell adhesion phenotypic changes resistance to apoptosis and tumor angiogenesis. In cancer cells PTGS2 is a key step in the production of prostaglandin E2 (PGE2) which plays important roles in modulating motility proliferation and resistance to apoptosis; Belongs to the prostaglandin G/H synthase family."
+ },
+ {
+ "name": "aspirin",
+ "layer": "compound",
+ "position_x": "-10",
+ "position_y": "247.498437602146",
+ "position_z": "9.03979852559709",
+ "scale": "1.73076923076923",
+ "color": "#FF008B",
+ "url": "",
+ "descr": ""
+ }
+ ],
+ "edges": [
+ {
+ "src": "PTGS2_protein",
+ "trg": "Small cell lung cancer_enriched_term",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Small cell lung cancer_enriched_term",
+ "trg": "TP53_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "PTGS2_protein",
+ "trg": "Regulation of lipolysis in adipocytes_enriched_term",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "PTGS1_protein",
+ "trg": "Regulation of lipolysis in adipocytes_enriched_term",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "PTGS2_protein",
+ "trg": "Serotonergic synapse_enriched_term",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "PTGS1_protein",
+ "trg": "Serotonergic synapse_enriched_term",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Platelet activation_enriched_term",
+ "trg": "TBXA2R_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "PTGS1_protein",
+ "trg": "Platelet activation_enriched_term",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Arachidonic acid metabolism_enriched_term",
+ "trg": "PTGS2_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Arachidonic acid metabolism_enriched_term",
+ "trg": "PTGS1_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "SELP_protein",
+ "trg": "Thrombosis_enriched_term",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "CRP_protein",
+ "trg": "Thrombosis_enriched_term",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood platelet_enriched_term",
+ "trg": "TBXA2R_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood platelet_enriched_term",
+ "trg": "PTGS1_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood platelet_enriched_term",
+ "trg": "SELP_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Connective tissue_enriched_term",
+ "trg": "PTGS2_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Connective tissue_enriched_term",
+ "trg": "PTGS1_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Connective tissue_enriched_term",
+ "trg": "TP53_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "CRP_protein",
+ "trg": "Connective tissue_enriched_term",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Peripheral artery disease_enriched_term",
+ "trg": "SELP_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "CRP_protein",
+ "trg": "Peripheral artery disease_enriched_term",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood vessel_enriched_term",
+ "trg": "TBXA2R_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood vessel_enriched_term",
+ "trg": "PTGS2_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood vessel_enriched_term",
+ "trg": "SELP_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood vessel_enriched_term",
+ "trg": "CRP_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood_enriched_term",
+ "trg": "TBXA2R_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood_enriched_term",
+ "trg": "PTGS2_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood_enriched_term",
+ "trg": "PTGS1_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood_enriched_term",
+ "trg": "TP53_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood_enriched_term",
+ "trg": "SELP_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "Blood_enriched_term",
+ "trg": "CRP_protein",
+ "opacity": "0.500500500500501",
+ "color": "#8DD3C7",
+ "channel": "enrichment"
+ },
+ {
+ "src": "TP53_protein",
+ "trg": "salicylate_compound",
+ "opacity": "0.451745863510569",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "TP53_protein",
+ "trg": "aspirin_compound",
+ "opacity": "0.628275334157687",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "TP53_protein",
+ "trg": "warfarin_compound",
+ "opacity": "0.326993660326994",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "PTGS2_protein",
+ "trg": "TP53_protein",
+ "opacity": "0.659428055506487",
+ "color": "#BEBADA",
+ "channel": "pp"
+ },
+ {
+ "src": "ibuprofen_compound",
+ "trg": "warfarin_compound",
+ "opacity": "0.182719974876838",
+ "color": "#FB8072",
+ "channel": "cc"
+ },
+ {
+ "src": "clopidogrel_compound",
+ "trg": "warfarin_compound",
+ "opacity": "0.560325030913266",
+ "color": "#FB8072",
+ "channel": "cc"
+ },
+ {
+ "src": "SELP_protein",
+ "trg": "salicylate_compound",
+ "opacity": "0.427093760427094",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "SELP_protein",
+ "trg": "aspirin_compound",
+ "opacity": "0.652888182299947",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "SELP_protein",
+ "trg": "clopidogrel_compound",
+ "opacity": "0.599772321340949",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "PTGS2_protein",
+ "trg": "SELP_protein",
+ "opacity": "0.12012012012012",
+ "color": "#BEBADA",
+ "channel": "pp"
+ },
+ {
+ "src": "PTGS1_protein",
+ "trg": "SELP_protein",
+ "opacity": "0.144203026555968",
+ "color": "#BEBADA",
+ "channel": "pp"
+ },
+ {
+ "src": "TBXA2R_protein",
+ "trg": "aspirin_compound",
+ "opacity": "0.652888182299947",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "TBXA2R_protein",
+ "trg": "clopidogrel_compound",
+ "opacity": "0.154829339143065",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "TBXA2R_protein",
+ "trg": "ibuprofen_compound",
+ "opacity": "0.183418712830478",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "CRP_protein",
+ "trg": "aspirin_compound",
+ "opacity": "0.653547665312371",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "CRP_protein",
+ "trg": "clopidogrel_compound",
+ "opacity": "0.65025025025025",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "CRP_protein",
+ "trg": "TP53_protein",
+ "opacity": "0.117058234705294",
+ "color": "#BEBADA",
+ "channel": "pp"
+ },
+ {
+ "src": "CRP_protein",
+ "trg": "SELP_protein",
+ "opacity": "0.45229543268759",
+ "color": "#BEBADA",
+ "channel": "pp"
+ },
+ {
+ "src": "CRP_protein",
+ "trg": "PTGS2_protein",
+ "opacity": "0.269634340222576",
+ "color": "#BEBADA",
+ "channel": "pp"
+ },
+ {
+ "src": "salicylate_compound",
+ "trg": "warfarin_compound",
+ "opacity": "0.187246069599011",
+ "color": "#FB8072",
+ "channel": "cc"
+ },
+ {
+ "src": "ibuprofen_compound",
+ "trg": "salicylate_compound",
+ "opacity": "0.45229543268759",
+ "color": "#FB8072",
+ "channel": "cc"
+ },
+ {
+ "src": "aspirin_compound",
+ "trg": "salicylate_compound",
+ "opacity": "0.660754872519578",
+ "color": "#FB8072",
+ "channel": "cc"
+ },
+ {
+ "src": "PTGS1_protein",
+ "trg": "salicylate_compound",
+ "opacity": "0.65025025025025",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "PTGS1_protein",
+ "trg": "aspirin_compound",
+ "opacity": "0.666666666666667",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "PTGS1_protein",
+ "trg": "ibuprofen_compound",
+ "opacity": "0.665999332665999",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "PTGS1_protein",
+ "trg": "PTGS2_protein",
+ "opacity": "0.504865649963689",
+ "color": "#BEBADA",
+ "channel": "pp"
+ },
+ {
+ "src": "PTGS1_protein",
+ "trg": "TBXA2R_protein",
+ "opacity": "0.167500834167501",
+ "color": "#BEBADA",
+ "channel": "pp"
+ },
+ {
+ "src": "PTGS2_protein",
+ "trg": "salicylate_compound",
+ "opacity": "0.650909733262674",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "PTGS2_protein",
+ "trg": "aspirin_compound",
+ "opacity": "0.666666666666667",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "PTGS2_protein",
+ "trg": "ibuprofen_compound",
+ "opacity": "0.658101238493395",
+ "color": "#FFFFB3",
+ "channel": "pc"
+ },
+ {
+ "src": "PTGS2_protein",
+ "trg": "TBXA2R_protein",
+ "opacity": "0.120402755696873",
+ "color": "#BEBADA",
+ "channel": "pp"
+ },
+ {
+ "src": "aspirin_compound",
+ "trg": "warfarin_compound",
+ "opacity": "0.628923040687747",
+ "color": "#FB8072",
+ "channel": "cc"
+ },
+ {
+ "src": "aspirin_compound",
+ "trg": "ibuprofen_compound",
+ "opacity": "0.635341223576518",
+ "color": "#FB8072",
+ "channel": "cc"
+ },
+ {
+ "src": "aspirin_compound",
+ "trg": "clopidogrel_compound",
+ "opacity": "0.637947751673242",
+ "color": "#FB8072",
+ "channel": "cc"
+ }
+ ],
+ "universalLabelColor": "#000000"
+}
\ No newline at end of file
diff --git a/www/data/GPCRs_data.tsv b/frontend/public/data/GPCRs_data.tsv
similarity index 100%
rename from www/data/GPCRs_data.tsv
rename to frontend/public/data/GPCRs_data.tsv
diff --git a/www/data/GPCRs_data_edge_attributes.tsv b/frontend/public/data/GPCRs_data_edge_attributes.tsv
similarity index 100%
rename from www/data/GPCRs_data_edge_attributes.tsv
rename to frontend/public/data/GPCRs_data_edge_attributes.tsv
diff --git a/www/data/GPCRs_data_node_attributes.tsv b/frontend/public/data/GPCRs_data_node_attributes.tsv
similarity index 100%
rename from www/data/GPCRs_data_node_attributes.tsv
rename to frontend/public/data/GPCRs_data_node_attributes.tsv
diff --git a/www/data/GPCRs_export.json b/frontend/public/data/GPCRs_export.json
similarity index 100%
rename from www/data/GPCRs_export.json
rename to frontend/public/data/GPCRs_export.json
diff --git a/www/data/StringApp_aspirin.cys b/frontend/public/data/StringApp_aspirin.cys
similarity index 100%
rename from www/data/StringApp_aspirin.cys
rename to frontend/public/data/StringApp_aspirin.cys
diff --git a/frontend/public/data/aspirin_3channels.tsv b/frontend/public/data/aspirin_3channels.tsv
new file mode 100644
index 0000000..079af5d
--- /dev/null
+++ b/frontend/public/data/aspirin_3channels.tsv
@@ -0,0 +1,40 @@
+SourceNode SourceLayer TargetNode TargetLayer Channel Weight
+aspirin Drug PTGS1 Protein Experimental 0.725
+aspirin Drug PTGS1 Protein Database 0.9
+aspirin Drug PTGS1 Protein Text Mining 0.986
+aspirin Drug PTGS2 Protein Experimental 0.643
+aspirin Drug PTGS2 Protein Database 0.9
+aspirin Drug PTGS2 Protein Text Mining 0.98
+ibuprofen Drug PTGS1 Protein Experimental 0.61
+ibuprofen Drug PTGS1 Protein Database 0.9
+ibuprofen Drug PTGS1 Protein Text Mining 0.959
+TP53 Protein PTGS2 Protein Experimental 0.961
+TP53 Protein PTGS2 Protein Text Mining 0.94
+prostaglandin Drug TBXA2R Protein Experimental 0.235
+prostaglandin Drug TBXA2R Protein Database 0.9
+prostaglandin Drug TBXA2R Protein Text Mining 0.952
+ibuprofen Drug PTGS2 Protein Experimental 0.658
+ibuprofen Drug PTGS2 Protein Database 0.9
+ibuprofen Drug PTGS2 Protein Text Mining 0.799
+aspirin Drug CRP Protein Database 0.9
+aspirin Drug CRP Protein Text Mining 0.92
+prostaglandin Drug PTGS2 Protein Text Mining 0.99
+aspirin Drug SELP Protein Database 0.9
+aspirin Drug SELP Protein Text Mining 0.911
+aspirin Drug TBXA2R Protein Database 0.7
+aspirin Drug TBXA2R Protein Text Mining 0.968
+clopidogrel Drug CRP Protein Database 0.9
+clopidogrel Drug CRP Protein Text Mining 0.865
+clopidogrel Drug aspirin Drug Text Mining 0.979
+ibuprofen Drug aspirin Drug Experimental 0.246
+ibuprofen Drug aspirin Drug Text Mining 0.969
+warfarin Drug aspirin Drug Text Mining 0.971
+aspirin Drug TP53 Protein Database 0.8
+aspirin Drug TP53 Protein Text Mining 0.857
+aspirin Drug prostaglandin Drug Text Mining 0.969
+clopidogrel Drug SELP Protein Database 0.9
+clopidogrel Drug SELP Protein Text Mining 0.507
+prostaglandin Drug PTGS1 Protein Text Mining 0.923
+warfarin Drug clopidogrel Drug Text Mining 0.915
+ibuprofen Drug prostaglandin Drug Text Mining 0.804
+warfarin Drug TP53 Protein Database 0.7
diff --git a/www/data/aspirin_3channels_directed.json b/frontend/public/data/aspirin_3channels_directed.json
similarity index 100%
rename from www/data/aspirin_3channels_directed.json
rename to frontend/public/data/aspirin_3channels_directed.json
diff --git a/www/data/covid19_data.tsv b/frontend/public/data/covid19_data.tsv
similarity index 100%
rename from www/data/covid19_data.tsv
rename to frontend/public/data/covid19_data.tsv
diff --git a/www/data/covid19_data_edge_attributes.tsv b/frontend/public/data/covid19_data_edge_attributes.tsv
similarity index 100%
rename from www/data/covid19_data_edge_attributes.tsv
rename to frontend/public/data/covid19_data_edge_attributes.tsv
diff --git a/www/data/covid19_data_node_attributes.tsv b/frontend/public/data/covid19_data_node_attributes.tsv
similarity index 100%
rename from www/data/covid19_data_node_attributes.tsv
rename to frontend/public/data/covid19_data_node_attributes.tsv
diff --git a/www/data/covid19_export.json b/frontend/public/data/covid19_export.json
similarity index 100%
rename from www/data/covid19_export.json
rename to frontend/public/data/covid19_export.json
diff --git a/www/data/figure1_data.tsv b/frontend/public/data/figure1_data.tsv
similarity index 100%
rename from www/data/figure1_data.tsv
rename to frontend/public/data/figure1_data.tsv
diff --git a/frontend/public/data/figure1_export.json b/frontend/public/data/figure1_export.json
new file mode 100644
index 0000000..b6d70c3
--- /dev/null
+++ b/frontend/public/data/figure1_export.json
@@ -0,0 +1,34462 @@
+{
+ "scene_pan": {
+ "position_x": "0",
+ "position_y": "0",
+ "scale": "0.578739249",
+ "color": "#000000",
+ "rotation_x": "0.309473704968813",
+ "rotation_y": "-0.329635272691083",
+ "rotation_z": "0.0181231412426912"
+ },
+ "layers": [
+ {
+ "name": "Layer1",
+ "position_x": "-888.75",
+ "position_y": "0",
+ "position_z": "0",
+ "last_layer_scale": "1",
+ "rotation_x": "0",
+ "rotation_y": "0",
+ "rotation_z": "0",
+ "floor_current_color": "#777777",
+ "geometry_parameters_width": "1001.90476190476"
+ },
+ {
+ "name": "Layer2",
+ "position_x": "-533.25",
+ "position_y": "0",
+ "position_z": "0",
+ "last_layer_scale": "1",
+ "rotation_x": "0",
+ "rotation_y": "0",
+ "rotation_z": "0",
+ "floor_current_color": "#777777",
+ "geometry_parameters_width": "1001.90476190476"
+ },
+ {
+ "name": "Layer3",
+ "position_x": "-177.75",
+ "position_y": "0",
+ "position_z": "0",
+ "last_layer_scale": "1",
+ "rotation_x": "0",
+ "rotation_y": "0",
+ "rotation_z": "0",
+ "floor_current_color": "#777777",
+ "geometry_parameters_width": "1001.90476190476"
+ },
+ {
+ "name": "Layer4",
+ "position_x": "177.75",
+ "position_y": "0",
+ "position_z": "0",
+ "last_layer_scale": "1",
+ "rotation_x": "0",
+ "rotation_y": "0",
+ "rotation_z": "0",
+ "floor_current_color": "#777777",
+ "geometry_parameters_width": "1001.90476190476"
+ },
+ {
+ "name": "Layer5",
+ "position_x": "533.25",
+ "position_y": "0",
+ "position_z": "0",
+ "last_layer_scale": "1",
+ "rotation_x": "0",
+ "rotation_y": "0",
+ "rotation_z": "0",
+ "floor_current_color": "#777777",
+ "geometry_parameters_width": "1001.90476190476"
+ },
+ {
+ "name": "Layer6",
+ "position_x": "888.75",
+ "position_y": "0",
+ "position_z": "0",
+ "last_layer_scale": "1",
+ "rotation_x": "0",
+ "rotation_y": "0",
+ "rotation_z": "0",
+ "floor_current_color": "#777777",
+ "geometry_parameters_width": "1001.90476190476"
+ }
+ ],
+ "nodes": [
+ {
+ "name": "2",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "-420.8",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "1",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-389.62962962963",
+ "position_z": "420.8",
+ "scale": "2.18181818181818",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "3",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-389.62962962963",
+ "position_z": "-420.8",
+ "scale": "0.727272727272727",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "4",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-278.306878306878",
+ "position_z": "-420.8",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "5",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-166.984126984127",
+ "position_z": "-420.8",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "6",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-55.6613756613756",
+ "position_z": "-420.8",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "7",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "55.6613756613756",
+ "position_z": "-420.8",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "8",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "166.984126984127",
+ "position_z": "-420.8",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "9",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "278.306878306878",
+ "position_z": "-420.8",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "10",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "389.62962962963",
+ "position_z": "-420.8",
+ "scale": "0.681818181818182",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "11",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "-420.8",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "12",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "-327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "13",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-389.62962962963",
+ "position_z": "-327.288888888889",
+ "scale": "0.590909090909091",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "14",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-278.306878306878",
+ "position_z": "-327.288888888889",
+ "scale": "0.590909090909091",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "16",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-166.984126984127",
+ "position_z": "-327.288888888889",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "17",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-55.6613756613756",
+ "position_z": "-327.288888888889",
+ "scale": "0.863636363636364",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "18",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "55.6613756613756",
+ "position_z": "-327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "19",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "166.984126984127",
+ "position_z": "-327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "20",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "278.306878306878",
+ "position_z": "-327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "21",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "389.62962962963",
+ "position_z": "-327.288888888889",
+ "scale": "0.636363636363636",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "22",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "-327.288888888889",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "23",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "-233.777777777778",
+ "scale": "0.590909090909091",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "24",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-389.62962962963",
+ "position_z": "-233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "25",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-278.306878306878",
+ "position_z": "-233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "26",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-166.984126984127",
+ "position_z": "-233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "27",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-55.6613756613756",
+ "position_z": "-233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "28",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "55.6613756613756",
+ "position_z": "-233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "30",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "166.984126984127",
+ "position_z": "-233.777777777778",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "15",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-278.306878306878",
+ "position_z": "420.8",
+ "scale": "0.590909090909091",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "31",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "278.306878306878",
+ "position_z": "-233.777777777778",
+ "scale": "0.590909090909091",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "32",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "389.62962962963",
+ "position_z": "-233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "33",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "-233.777777777778",
+ "scale": "0.590909090909091",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "34",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "-140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "35",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-389.62962962963",
+ "position_z": "-140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "36",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-278.306878306878",
+ "position_z": "-140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "37",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-166.984126984127",
+ "position_z": "-140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "38",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-55.6613756613756",
+ "position_z": "-140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "39",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "55.6613756613756",
+ "position_z": "-140.266666666667",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "40",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "166.984126984127",
+ "position_z": "-140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "41",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "278.306878306878",
+ "position_z": "-140.266666666667",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "42",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "389.62962962963",
+ "position_z": "-140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "44",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "-140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "45",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "-46.7555555555555",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "46",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-389.62962962963",
+ "position_z": "-46.7555555555555",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "47",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-278.306878306878",
+ "position_z": "-46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "48",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-166.984126984127",
+ "position_z": "-46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "49",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-55.6613756613756",
+ "position_z": "-46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "50",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "55.6613756613756",
+ "position_z": "-46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "51",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "166.984126984127",
+ "position_z": "-46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "52",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "278.306878306878",
+ "position_z": "-46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "53",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "389.62962962963",
+ "position_z": "-46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "54",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "-46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "55",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "56",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-389.62962962963",
+ "position_z": "46.7555555555555",
+ "scale": "0.727272727272727",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "43",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-166.984126984127",
+ "position_z": "420.8",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "58",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-278.306878306878",
+ "position_z": "46.7555555555555",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "59",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-166.984126984127",
+ "position_z": "46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "60",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-55.6613756613756",
+ "position_z": "46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "61",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "55.6613756613756",
+ "position_z": "46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "62",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "166.984126984127",
+ "position_z": "46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "63",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "278.306878306878",
+ "position_z": "46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "64",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "389.62962962963",
+ "position_z": "46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "65",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "46.7555555555555",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "66",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "67",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-389.62962962963",
+ "position_z": "140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "68",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-278.306878306878",
+ "position_z": "140.266666666667",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "69",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-166.984126984127",
+ "position_z": "140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "70",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-55.6613756613756",
+ "position_z": "140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "72",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "55.6613756613756",
+ "position_z": "140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "73",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "166.984126984127",
+ "position_z": "140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "74",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "278.306878306878",
+ "position_z": "140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "75",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "389.62962962963",
+ "position_z": "140.266666666667",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "76",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "140.266666666667",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "77",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "78",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-389.62962962963",
+ "position_z": "233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "79",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-278.306878306878",
+ "position_z": "233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "80",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-166.984126984127",
+ "position_z": "233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "81",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-55.6613756613756",
+ "position_z": "233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "82",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "55.6613756613756",
+ "position_z": "233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "83",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "166.984126984127",
+ "position_z": "233.777777777778",
+ "scale": "0.545454545454545",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "84",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "278.306878306878",
+ "position_z": "233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "86",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "389.62962962963",
+ "position_z": "233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "87",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "233.777777777778",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "88",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "89",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-389.62962962963",
+ "position_z": "327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "90",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-278.306878306878",
+ "position_z": "327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "91",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-166.984126984127",
+ "position_z": "327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "92",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-55.6613756613756",
+ "position_z": "327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "93",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "55.6613756613756",
+ "position_z": "327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "94",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "166.984126984127",
+ "position_z": "327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "95",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "278.306878306878",
+ "position_z": "327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "96",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "389.62962962963",
+ "position_z": "327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "97",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "327.288888888889",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "98",
+ "layer": "Layer1",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "420.8",
+ "scale": "0.5",
+ "color": "#63b598",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "2",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-490.889728506066",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "1",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "136.610015353014",
+ "position_z": "420.8",
+ "scale": "2.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "3",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-484.181293541856",
+ "position_z": "300.571428571429",
+ "scale": "0.772727272727273",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "4",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-442.812611262559",
+ "position_z": "300.571428571429",
+ "scale": "0.772727272727273",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "5",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-180.911296666233",
+ "position_z": "300.571428571429",
+ "scale": "2.27272727272727",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "6",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-174.202861702023",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "7",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "8",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-417.096943899756",
+ "position_z": "180.342857142857",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "9",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-410.388508935546",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "10",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-167.494426737813",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "11",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-160.785991773603",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "12",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-19.6221722703132",
+ "position_z": "300.571428571429",
+ "scale": "0.681818181818182",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "13",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "27.7095633105019",
+ "position_z": "300.571428571429",
+ "scale": "0.909090909090909",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "15",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-2.85108485978822",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "16",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-383.554769078706",
+ "position_z": "180.342857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "17",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "3.85735010442164",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "18",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "19",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "10.5657850686317",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "20",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "74.2959172286265",
+ "position_z": "300.571428571429",
+ "scale": "0.681818181818182",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "21",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "370.137899150287",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "14",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "407.369713201652",
+ "position_z": "420.8",
+ "scale": "0.909090909090909",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "22",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-394.455975895548",
+ "position_z": "60.1142857142857",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "23",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-420.451161381861",
+ "position_z": "60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "24",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "81.0043521928364",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "25",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-413.742726417651",
+ "position_z": "-60.1142857142857",
+ "scale": "0.863636363636364",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "26",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "91.0670046391514",
+ "position_z": "300.571428571429",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "28",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "97.7754396033613",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "29",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-331.005361859057",
+ "position_z": "180.342857142857",
+ "scale": "0.772727272727273",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "30",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-445.607792497649",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "31",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-494.243945988171",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "32",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "104.483874567571",
+ "position_z": "300.571428571429",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "33",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "111.192309531781",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "34",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-355.043920480814",
+ "position_z": "60.1142857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "35",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-438.899357533439",
+ "position_z": "-180.342857142857",
+ "scale": "0.772727272727273",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "36",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "117.900744495991",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "37",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-291.313788320819",
+ "position_z": "180.342857142857",
+ "scale": "0.681818181818182",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "38",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "124.609179460201",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "39",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "131.317614424411",
+ "position_z": "300.571428571429",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "41",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-487.535511023961",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "42",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-274.542700910294",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "43",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "162.176415259777",
+ "position_z": "300.571428571429",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "44",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-267.834265946084",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "45",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-274.542700910294",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "46",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "155.467980295567",
+ "position_z": "180.342857142857",
+ "scale": "0.727272727272727",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "47",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "376.846334114497",
+ "position_z": "300.571428571429",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "48",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-261.125830981874",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "49",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "386.908986560811",
+ "position_z": "300.571428571429",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "50",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "175.593285188197",
+ "position_z": "300.571428571429",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "51",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-361.752355445024",
+ "position_z": "-60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "52",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "376.846334114497",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "54",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-348.335485516604",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "55",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-254.417396017664",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "56",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "17.2742200328416",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "57",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-247.708961053454",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "58",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-241.000526089242",
+ "position_z": "180.342857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "59",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "182.301720152407",
+ "position_z": "300.571428571429",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "60",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-234.292091125032",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "61",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "189.010155116617",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "62",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "202.427025045037",
+ "position_z": "300.571428571429",
+ "scale": "0.727272727272727",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "63",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-455.670444943964",
+ "position_z": "-300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "64",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-227.583656160822",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "65",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-220.875221196612",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "67",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-338.272833070289",
+ "position_z": "60.1142857142857",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "68",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-29.6848247166282",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "69",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-214.166786232403",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "70",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-207.458351268193",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "71",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-321.501745659764",
+ "position_z": "60.1142857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "72",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-460.701771167121",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "73",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "30.6910899612616",
+ "position_z": "180.342857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "74",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "131.317614424411",
+ "position_z": "60.1142857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "75",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-227.583656160822",
+ "position_z": "60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "76",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "124.609179460201",
+ "position_z": "-60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "77",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "209.135460009247",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "78",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-214.166786232403",
+ "position_z": "60.1142857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "80",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "396.971639007126",
+ "position_z": "300.571428571429",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "81",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-480.827076059751",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "82",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "215.843894973457",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "83",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "189.010155116617",
+ "position_z": "180.342857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "84",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-432.190922569229",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "85",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-412.065617676599",
+ "position_z": "-180.342857142857",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "86",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "151.442919317041",
+ "position_z": "60.1142857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "87",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "158.151354281251",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "88",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-220.875221196612",
+ "position_z": "-60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "89",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "144.734484352831",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "90",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-453.993336202911",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "91",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-447.284901238701",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "93",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "151.442919317041",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "94",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "222.552329937667",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "95",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "195.718590080827",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "96",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-194.041481339773",
+ "position_z": "180.342857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "97",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-440.576466274491",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "98",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-180.624611411353",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "99",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-301.376440767134",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "100",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "64.2332647823116",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "101",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-200.749916303983",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "102",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-314.793310695554",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "103",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "104.483874567571",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "104",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-173.916176447143",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "106",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-167.207741482933",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "107",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-160.499306518723",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "108",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-153.790871554513",
+ "position_z": "180.342857142857",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "109",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-147.082436590303",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "110",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "232.614982383982",
+ "position_z": "300.571428571429",
+ "scale": "0.681818181818182",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "111",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-405.357182712389",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "112",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "407.034291453441",
+ "position_z": "300.571428571429",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "113",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "37.3995249254716",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "114",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "23.9826549970516",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "115",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-448.962009979754",
+ "position_z": "-300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "116",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "249.386069794507",
+ "position_z": "300.571428571429",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "117",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-294.668005802924",
+ "position_z": "60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "119",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "256.094504758717",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "120",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "262.802939722927",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "121",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-227.583656160822",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "122",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-140.374001626093",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "123",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-173.916176447143",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "124",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-247.708961053454",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "125",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "44.1079598896816",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "126",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "413.742726417651",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "127",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "17.2742200328416",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "128",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "249.386069794507",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "129",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "124.609179460201",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "130",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "30.6910899612616",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "132",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "403.680073971337",
+ "position_z": "180.342857142857",
+ "scale": "0.636363636363636",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "133",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-241.000526089242",
+ "position_z": "60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "134",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "420.451161381861",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "135",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-430.513813828176",
+ "position_z": "180.342857142857",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "136",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-341.627050552394",
+ "position_z": "-60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "137",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "222.552329937667",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "138",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-334.918615588184",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "139",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-22.9763897524182",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "140",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-442.253575015544",
+ "position_z": "-300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "141",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-2.85108485978822",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "142",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "269.511374687137",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "143",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-287.959570838714",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "145",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-398.648747748179",
+ "position_z": "-180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "146",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "164.859789245461",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "147",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "70.9416997465215",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "148",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "131.317614424411",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "149",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-133.665566661883",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "150",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-420.451161381861",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "151",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-214.166786232403",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "152",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-415.419835158704",
+ "position_z": "-300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "153",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-361.752355445024",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "154",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "162.176415259777",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "155",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-460.701771167121",
+ "position_z": "60.1142857142857",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "156",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-123.602914215568",
+ "position_z": "180.342857142857",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "158",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-328.210180623974",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "159",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-126.957131697673",
+ "position_z": "60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "160",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "168.884850223987",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "161",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-120.248696733463",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "162",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-116.894479251358",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "163",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "396.971639007126",
+ "position_z": "60.1142857142857",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "164",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "403.680073971337",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "165",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-464.055988649226",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "166",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "276.219809651347",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "167",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-194.041481339773",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "168",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-391.940312783969",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "169",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "182.301720152407",
+ "position_z": "60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "171",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "131.317614424411",
+ "position_z": "-60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "172",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-16.2679547882082",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "173",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-207.458351268193",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "174",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "282.928244615557",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "175",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-398.648747748179",
+ "position_z": "-300.571428571429",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "176",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "189.010155116617",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "177",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-308.084875731344",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "178",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-433.868031310281",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "179",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "229.260764901877",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "180",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "91.0670046391514",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "181",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "77.6501347107314",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "182",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-110.186044287148",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "184",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "202.427025045037",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "185",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "209.135460009247",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "186",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "410.388508935547",
+ "position_z": "60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "187",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-103.477609322938",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "188",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "289.636679579767",
+ "position_z": "300.571428571429",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "189",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-96.7691743587279",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "190",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-281.251135874504",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "191",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "50.8163948538916",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "192",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "450.639118720806",
+ "position_z": "420.8",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "118",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "447.284901238701",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "193",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-355.043920480814",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "194",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "410.388508935547",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "195",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-90.060739394518",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "197",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "138.026049388621",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "198",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-234.292091125032",
+ "position_z": "60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "199",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "138.026049388621",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "200",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "158.151354281251",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "201",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-423.805378863966",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "202",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "460.701771167121",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "66",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "460.701771167121",
+ "position_z": "420.8",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "203",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "296.345114543977",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "204",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "235.969199866087",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "205",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-83.352304430308",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "206",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-427.159596346071",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "207",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-76.643869466098",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "208",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-474.118641095541",
+ "position_z": "180.342857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "210",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "303.053549508187",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "211",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "467.410206131331",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "157",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "470.764423613436",
+ "position_z": "420.8",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "212",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-69.935434501888",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "213",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "393.617421525021",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "214",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "383.554769078707",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "215",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-435.545140051334",
+ "position_z": "-300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "216",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-341.627050552394",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "217",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "427.159596346071",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "218",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-133.665566661883",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "219",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "453.993336202911",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "220",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-234.292091125032",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "221",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "182.301720152407",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "223",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-375.169225373444",
+ "position_z": "-60.1142857142857",
+ "scale": "0.590909090909091",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "224",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "396.971639007126",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "225",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "84.3585696749414",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "226",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-398.648747748179",
+ "position_z": "-420.8",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "227",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "182.301720152407",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "228",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "309.761984472397",
+ "position_z": "300.571428571429",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "229",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-261.125830981874",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "230",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "37.3995249254716",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "231",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-428.836705087124",
+ "position_z": "-300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "232",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-63.2269995376781",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "233",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "242.677634830297",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "234",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-487.535511023961",
+ "position_z": "60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "236",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-378.523442855549",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "237",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-56.5185645734681",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "238",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-422.128270122914",
+ "position_z": "-300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "239",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-321.501745659764",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "240",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "316.470419436607",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "241",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-487.535511023961",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "242",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "195.718590080827",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "243",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-49.8101296092581",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "244",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "323.178854400817",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "245",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-371.815007891339",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "246",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "480.827076059751",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "222",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "480.827076059751",
+ "position_z": "420.8",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "247",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "124.609179460201",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "249",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "410.388508935547",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "250",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-126.957131697673",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "251",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-467.410206131331",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "252",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-43.1016946450482",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "253",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-348.335485516604",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "254",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-187.333046375563",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "255",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-294.668005802924",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "256",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-387.747540931338",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "257",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-474.118641095541",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "258",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "329.887289365027",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "259",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-385.231877819759",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "260",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "490.889728506066",
+ "position_z": "420.8",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "40",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "487.535511023961",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "262",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "336.595724329237",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "263",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-9.55951982399819",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "264",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-157.145089036618",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "265",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-207.458351268193",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "266",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-150.436654072408",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "267",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "289.636679579767",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "268",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-368.460790409234",
+ "position_z": "60.1142857142857",
+ "scale": "0.545454545454545",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "269",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-413.742726417651",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "270",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-314.793310695554",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "271",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "309.761984472397",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "272",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "474.118641095541",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "273",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-180.624611411353",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "275",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "343.304159293447",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "276",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "57.5248298181015",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "277",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-408.711400194494",
+ "position_z": "-300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "278",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "390.263204042917",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "279",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-220.875221196612",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "280",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "350.012594257657",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "281",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-241.000526089242",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "282",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "433.868031310281",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "283",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "440.576466274491",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "284",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "171.568224209671",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "285",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "196",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "420.8",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "286",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "131.317614424411",
+ "position_z": "-180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "288",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-368.460790409234",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "289",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-36.3932596808381",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "290",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-457.347553685016",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "291",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "356.721029221867",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "292",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "494.243945988171",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "293",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-254.417396017664",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "294",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "363.429464186077",
+ "position_z": "300.571428571429",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "295",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "215.843894973457",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "296",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "124.609179460201",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "297",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "-440.576466274491",
+ "position_z": "60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "298",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "400.325856489231",
+ "position_z": "-60.1142857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "299",
+ "layer": "Layer2",
+ "position_x": "0",
+ "position_y": "175.593285188197",
+ "position_z": "180.342857142857",
+ "scale": "0.5",
+ "color": "#ce7d78",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "1",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "349.432987380873",
+ "position_z": "162.78689281544",
+ "scale": "1.68367346938775",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "2",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "230.503286319307",
+ "position_z": "82.6008501922399",
+ "scale": "1.16666666666667",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "3",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "358.84922978415",
+ "position_z": "125.28546482211",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "4",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "275.427303398749",
+ "position_z": "143.911478834362",
+ "scale": "2.21428571428571",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "5",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "216.250589216411",
+ "position_z": "153.809206528615",
+ "scale": "1.78571428571428",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "6",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "141.226322586546",
+ "position_z": "111.950028225522",
+ "scale": "1.07142857142857",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "7",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "262.717504124773",
+ "position_z": "178.093138567019",
+ "scale": "1.68367346938775",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "8",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "83.3992212048037",
+ "position_z": "128.558357933849",
+ "scale": "0.5",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "9",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "282.015615588429",
+ "position_z": "41.4439530009591",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "24",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "336.547072855656",
+ "position_z": "-124.524773485164",
+ "scale": "1.63265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "10",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "149.339923509567",
+ "position_z": "76.2080023754569",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "11",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "387.650785373286",
+ "position_z": "150.574078304858",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "12",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "322.516204495618",
+ "position_z": "165.645781118758",
+ "scale": "1.3265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "13",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "395.702696868108",
+ "position_z": "87.9708934482608",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "14",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "286.002661358031",
+ "position_z": "87.7610954651597",
+ "scale": "1.3265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "15",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "472.68281759871",
+ "position_z": "83.4806234094803",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "16",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "24.2622985126268",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "17",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "441.995399559668",
+ "position_z": "2.92190241464726",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "18",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "404.178544817199",
+ "position_z": "-45.422834745407",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "19",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "485.533887433001",
+ "position_z": "-88.3364712924152",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "20",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "471.820038596795",
+ "position_z": "-128.913882269728",
+ "scale": "1.78571428571428",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "21",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "346.607157252951",
+ "position_z": "-99.0163310184299",
+ "scale": "1.07142857142857",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "22",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "327.920389069018",
+ "position_z": "-211.439979167272",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "125",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "102.603451620308",
+ "position_z": "-319.964035567883",
+ "scale": "1.63265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "23",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "440.775035716321",
+ "position_z": "-190.546016623987",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "25",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "380.150253121328",
+ "position_z": "-232.980760400747",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "26",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "307.471389445948",
+ "position_z": "-249.996691602299",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "27",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "291.692734448908",
+ "position_z": "-173.798972988389",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "28",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "179.530147964572",
+ "position_z": "-216.97997785887",
+ "scale": "1.19047619047619",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "29",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "208.253012658487",
+ "position_z": "-139.53350500691",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "30",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "155.559152174455",
+ "position_z": "-173.807536598735",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "31",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "157.129603608507",
+ "position_z": "-69.7272499261923",
+ "scale": "1.54761904761905",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "32",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "179.943492321362",
+ "position_z": "-20.0352454976685",
+ "scale": "1.92857142857143",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "33",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "57.3813192871469",
+ "position_z": "-103.808338829916",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "34",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "36.2810815887005",
+ "position_z": "-62.3108296507483",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "35",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-5.19346208855831",
+ "position_z": "-19.1362095912799",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "36",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-100.215422902867",
+ "position_z": "-48.7468696843197",
+ "scale": "1.3265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "37",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-158.880218372838",
+ "position_z": "-94.433869043633",
+ "scale": "2.21428571428571",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "38",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-177.254050732194",
+ "position_z": "-40.9275429396801",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "39",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-139.494900054599",
+ "position_z": "26.8289487188826",
+ "scale": "1.68367346938775",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "40",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-385.540502026291",
+ "position_z": "-0.962177584201527",
+ "scale": "1.78571428571428",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "41",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-269.308012963717",
+ "position_z": "15.3221519231988",
+ "scale": "1.1734693877551",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "42",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-94.8767291530179",
+ "position_z": "44.6606170199644",
+ "scale": "0.561224489795917",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "43",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-275.274324049932",
+ "position_z": "67.4466466940995",
+ "scale": "1.1734693877551",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "44",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-466.541436084108",
+ "position_z": "-37.4517245464784",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "45",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-291.870776864992",
+ "position_z": "-93.4362359136939",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "46",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-296.44633900818",
+ "position_z": "-50.7931162259506",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "47",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-435.272253207668",
+ "position_z": "-88.8202666158869",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "48",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-393.785121531861",
+ "position_z": "-159.658012054991",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "49",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-304.377494911577",
+ "position_z": "-181.581141205615",
+ "scale": "1.63265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "50",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-365.074954576396",
+ "position_z": "-119.193143752876",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "51",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-461.111301731551",
+ "position_z": "-122.983700599925",
+ "scale": "1.92857142857143",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "52",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-213.349882478995",
+ "position_z": "-111.438651632216",
+ "scale": "0.714285714285714",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "137",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-65.0360150266461",
+ "position_z": "-238.565014073602",
+ "scale": "1.54761904761905",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "53",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-448.765074360723",
+ "position_z": "-17.526461507471",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "54",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-263.07592433978",
+ "position_z": "-19.5491813363716",
+ "scale": "0.976190476190475",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "55",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-361.545363737629",
+ "position_z": "-69.1080191150343",
+ "scale": "1.68367346938775",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "56",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-342.246293361988",
+ "position_z": "14.2961063408594",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "57",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-437.760128474049",
+ "position_z": "64.3154724932113",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "58",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-403.991902854599",
+ "position_z": "90.7965806683081",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "59",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-469.244155105532",
+ "position_z": "151.612781805823",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "60",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "203.705626815134",
+ "scale": "2.5",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "61",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-337.972984489479",
+ "position_z": "229.52759414485",
+ "scale": "1.54761904761905",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "62",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-403.42595909048",
+ "position_z": "232.860457167921",
+ "scale": "1.3265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "63",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-393.461254124001",
+ "position_z": "307.420161528722",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "64",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-386.734311777152",
+ "position_z": "354.561401259554",
+ "scale": "1.92857142857143",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "65",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-257.183594034368",
+ "position_z": "316.617892089394",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "66",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-227.075064668668",
+ "position_z": "335.928079758852",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "67",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-252.094655206158",
+ "position_z": "420.8",
+ "scale": "1.68367346938775",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "68",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-12.1113789297719",
+ "position_z": "268.945661079364",
+ "scale": "1.27551020408163",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "69",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-112.619023897063",
+ "position_z": "384.322975948493",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "70",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-72.6743182701833",
+ "position_z": "369.249134100797",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "71",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-22.6056382631938",
+ "position_z": "387.724407407744",
+ "scale": "1.92857142857143",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "72",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "42.6143087627061",
+ "position_z": "313.597726012522",
+ "scale": "1.63265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "73",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-99.0143017601146",
+ "position_z": "277.90440754392",
+ "scale": "1.07142857142857",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "74",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "35.0612698649896",
+ "position_z": "274.594789451744",
+ "scale": "1.68367346938775",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "75",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "84.4623941462101",
+ "position_z": "180.753868672889",
+ "scale": "0.714285714285714",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "76",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-134.130050193448",
+ "position_z": "224.604317433533",
+ "scale": "1.92857142857143",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "77",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-40.5139756391737",
+ "position_z": "191.449845725485",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "78",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-69.3095223693599",
+ "position_z": "144.737762749249",
+ "scale": "1.68367346938775",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "79",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-109.121039514681",
+ "position_z": "138.438346164514",
+ "scale": "1.68367346938775",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "80",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-167.803744108533",
+ "position_z": "70.2403043848428",
+ "scale": "1.19047619047619",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "81",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-269.003851120152",
+ "position_z": "91.4989799819096",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "82",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-220.137469438798",
+ "position_z": "105.808292221426",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "83",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-290.097649494845",
+ "position_z": "158.976863541477",
+ "scale": "1.3265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "84",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-431.928647445648",
+ "position_z": "108.959417883667",
+ "scale": "2.5",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "85",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-282.143701733495",
+ "position_z": "21.1379075486535",
+ "scale": "1.3265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "86",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-377.560877614292",
+ "position_z": "135.700163523567",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "87",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-340.654000818895",
+ "position_z": "185.445878830986",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "88",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-265.383293769446",
+ "position_z": "194.12518142277",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "89",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-180.691686954698",
+ "position_z": "203.828992721559",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "90",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-239.609093753464",
+ "position_z": "234.252802538363",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "92",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-74.7122957032192",
+ "position_z": "280.864590978164",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "93",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-24.5589145985177",
+ "position_z": "294.55083939387",
+ "scale": "1.78571428571428",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "94",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "82.1047914204123",
+ "position_z": "366.27328060445",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "95",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "41.2688897671816",
+ "position_z": "371.588873135241",
+ "scale": "2.5",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "96",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "162.196581490753",
+ "position_z": "278.511793265037",
+ "scale": "1.54761904761905",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "97",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "175.43674883497",
+ "position_z": "324.752758247966",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "98",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "246.279732442363",
+ "position_z": "324.908021052682",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "99",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "322.414389828737",
+ "position_z": "263.982101315013",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "100",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "250.886641436868",
+ "position_z": "247.611645861955",
+ "scale": "2.24489795918367",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "101",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "187.89162381426",
+ "position_z": "214.911815235443",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "102",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "164.367479514336",
+ "position_z": "167.189580060575",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "103",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "252.915687122706",
+ "position_z": "128.960667036671",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "104",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "114.234079489535",
+ "position_z": "78.7092632926685",
+ "scale": "1.68367346938775",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "105",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "100.546218340116",
+ "position_z": "33.9746061967047",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "106",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "155.068584734598",
+ "position_z": "-9.815974245912",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "107",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "113.875148571668",
+ "position_z": "-65.4969936391149",
+ "scale": "1.3265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "108",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-34.9418753218264",
+ "position_z": "-43.3116873973144",
+ "scale": "0.785714285714287",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "109",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "14.0307313314392",
+ "position_z": "-108.802741438325",
+ "scale": "1.07142857142857",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "110",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-1.80472983652112",
+ "position_z": "-145.631521176035",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "111",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-101.724467997448",
+ "position_z": "-91.3497981479358",
+ "scale": "1.42857142857143",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "112",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-100.517594455498",
+ "position_z": "-176.61614119107",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "113",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-170.998264423768",
+ "position_z": "-176.614041430303",
+ "scale": "2.24489795918367",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "114",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-238.053628198198",
+ "position_z": "-201.598222181315",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "115",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-227.621903370713",
+ "position_z": "-245.395559345173",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "116",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-294.525130428728",
+ "position_z": "-289.909071960507",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "117",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-330.700949367146",
+ "position_z": "-236.308498112583",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "118",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-221.756903382683",
+ "position_z": "-370.069079833305",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "119",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-122.165051264278",
+ "position_z": "-334.310185922123",
+ "scale": "1.63265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "120",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-202.101570578629",
+ "position_z": "-409.500122733223",
+ "scale": "2.21428571428571",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "121",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-75.9474812498577",
+ "position_z": "-396.618147201341",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "122",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-1.32296114123227",
+ "position_z": "-420.8",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "123",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "128.720429032177",
+ "position_z": "-346.400436123816",
+ "scale": "1.07142857142857",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "124",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "45.5815865108358",
+ "position_z": "-401.619046849009",
+ "scale": "1.78571428571428",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "126",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "0.021267154614236",
+ "position_z": "-370.674441724982",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "127",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-0.0121645988986643",
+ "position_z": "-265.160679624776",
+ "scale": "1.63265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "128",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-142.712872418386",
+ "position_z": "-331.933462454457",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "129",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-124.123275904651",
+ "position_z": "-262.294463157757",
+ "scale": "1.30952380952381",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "130",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-143.827490624782",
+ "position_z": "-398.277532331988",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "131",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-189.859161151435",
+ "position_z": "-189.735132313804",
+ "scale": "1.3265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "132",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-249.766349555708",
+ "position_z": "-336.442947318287",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "133",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-264.10735233819",
+ "position_z": "-293.886468764042",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "134",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-205.591565059593",
+ "position_z": "-303.027071569882",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "135",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-125.635924176838",
+ "position_z": "-298.19466749555",
+ "scale": "2.24489795918367",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "136",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-51.9316018744424",
+ "position_z": "-341.189229630402",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "138",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "24.7692624420307",
+ "position_z": "-330.522165240762",
+ "scale": "2.29591836734694",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "139",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "92.4397196555045",
+ "position_z": "-304.49425708059",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "140",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "88.8986473372116",
+ "position_z": "-228.768150423268",
+ "scale": "1.63265306122449",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "141",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "169.507417988095",
+ "position_z": "-270.70223020413",
+ "scale": "1.47959183673469",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "142",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "239.660271443795",
+ "position_z": "-307.193505598058",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "143",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "334.094786943163",
+ "position_z": "-282.845961553969",
+ "scale": "1.92857142857143",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "144",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "279.07871953237",
+ "position_z": "-188.127254441705",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "145",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "348.801889353644",
+ "position_z": "-172.866471153184",
+ "scale": "1.93877551020408",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "146",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "440.88593247958",
+ "position_z": "-100.973639511131",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "147",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "445.056143707169",
+ "position_z": "-136.240420587991",
+ "scale": "2.21428571428571",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "148",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "339.776813873808",
+ "position_z": "-28.9715976786231",
+ "scale": "1.88775510204082",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "149",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "378.929223123387",
+ "position_z": "14.0014881908934",
+ "scale": "2.09183673469387",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "150",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "416.890622549243",
+ "position_z": "59.8160793912365",
+ "scale": "1.68367346938775",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "91",
+ "layer": "Layer3",
+ "position_x": "0",
+ "position_y": "-165.231664248217",
+ "position_z": "333.995511702447",
+ "scale": "1.64285714285714",
+ "color": "#ea9e70",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "2",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-45.4059861524478",
+ "position_z": "-245.240327814837",
+ "scale": "1",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "1",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-76.6042969947985",
+ "position_z": "-187.341227844456",
+ "scale": "1.75",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "3",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-159.790006018503",
+ "position_z": "-167.448320074508",
+ "scale": "2.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "4",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-127.319091137358",
+ "position_z": "-199.797037037753",
+ "scale": "0.833333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "5",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-208.518910158957",
+ "position_z": "-109.554077306984",
+ "scale": "2.16666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "6",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-261.518006844351",
+ "position_z": "-137.22493955958",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "7",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-21.930532699373",
+ "position_z": "-185.113962102257",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "8",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-235.490651047704",
+ "position_z": "-95.5863663438095",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "9",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-184.498611586064",
+ "position_z": "-156.826184935287",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "10",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-201.843525284886",
+ "position_z": "-227.336317751154",
+ "scale": "1.83333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "11",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-198.989467133537",
+ "position_z": "-168.316905908805",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "12",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-120.738923614705",
+ "position_z": "-265.603510784938",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "13",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-223.901029746223",
+ "position_z": "-38.0912484311579",
+ "scale": "1",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "15",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-173.319850121503",
+ "position_z": "-61.6647914899419",
+ "scale": "0.75",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "16",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-294.55119744884",
+ "position_z": "-109.866586569434",
+ "scale": "1.58333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "17",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-336.668126866787",
+ "position_z": "-60.599293262756",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "18",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-139.544405202187",
+ "position_z": "-150.00874023879",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "19",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-373.597489786683",
+ "position_z": "-108.745589616018",
+ "scale": "0.833333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "20",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-220.281247260909",
+ "position_z": "-257.343474775219",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "21",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-12.9681181787255",
+ "position_z": "-164.493843231115",
+ "scale": "0.75",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "22",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-207.407030049811",
+ "position_z": "-195.275235027003",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "23",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-437.452499727822",
+ "position_z": "-85.0911680150313",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "24",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-432.768131999805",
+ "position_z": "-148.547125651657",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "25",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-189.335220584516",
+ "position_z": "-186.142735896548",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "26",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-182.256374604415",
+ "position_z": "-292.736518675557",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "28",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "67.0532360215723",
+ "position_z": "385.725599215629",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "14",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "120.284402873053",
+ "position_z": "368.563160411256",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "29",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-25.5216251797819",
+ "position_z": "-200.831606328515",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "30",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-127.175171518267",
+ "position_z": "-179.674025384931",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "31",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-4.75301813154152",
+ "position_z": "-145.001028733236",
+ "scale": "1",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "32",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-156.176323478598",
+ "position_z": "-64.0996602863438",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "33",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-106.945599118734",
+ "position_z": "-5.74188200635786",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "34",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-431.974195799686",
+ "position_z": "-127.243452357603",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "35",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-243.203856697651",
+ "position_z": "-286.213262569035",
+ "scale": "0.75",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "36",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-210.602011257325",
+ "position_z": "-181.158884723602",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "37",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-28.0270364799769",
+ "position_z": "-232.633677038321",
+ "scale": "0.833333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "38",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-334.967996267005",
+ "position_z": "-175.625136921866",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "39",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-206.278551306388",
+ "position_z": "-291.119106077232",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "41",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "46.9318484738736",
+ "position_z": "-129.293862745842",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "42",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-42.6454173492393",
+ "position_z": "-145.53606656638",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "43",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-232.987294968039",
+ "position_z": "-78.3807364796509",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "44",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-399.326969333011",
+ "position_z": "-29.298115927614",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "45",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-205.729114519238",
+ "position_z": "22.4834880408465",
+ "scale": "0.916666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "46",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "169.955288543773",
+ "position_z": "343.875914840761",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "47",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-215.260681468029",
+ "position_z": "83.0565988378609",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "48",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "33.857664711259",
+ "position_z": "-269.700018645767",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "49",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "75.4621060489204",
+ "position_z": "-317.453357906938",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "50",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "74.5716540659728",
+ "position_z": "-142.932800755891",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "51",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-178.636482669379",
+ "position_z": "-195.786565372241",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "52",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-244.966207041551",
+ "position_z": "-265.559197812607",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "54",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "136.902773958703",
+ "position_z": "-330.228174751401",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "55",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "41.1840266215767",
+ "position_z": "-232.649742598126",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "56",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-143.457813115819",
+ "position_z": "-187.874974296013",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "57",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-50.1449512846741",
+ "position_z": "-210.968207908503",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "58",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "10.9237679830995",
+ "position_z": "389.448885883126",
+ "scale": "0.75",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "59",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-69.6384968560861",
+ "position_z": "-232.630850562292",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "60",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-114.374803692498",
+ "position_z": "-136.21831364628",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "61",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-65.4573250944279",
+ "position_z": "-304.723693612303",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "62",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-257.602552729963",
+ "position_z": "-126.901264851484",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "63",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-141.514188078157",
+ "position_z": "-259.820530115392",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "64",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-53.2301064217775",
+ "position_z": "402.224433443679",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "65",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-348.749007774145",
+ "position_z": "-76.5970886866002",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "67",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-41.553228173878",
+ "position_z": "-178.347960753713",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "68",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-277.344851747404",
+ "position_z": "-252.910263837422",
+ "scale": "0.75",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "69",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-47.7629459855745",
+ "position_z": "-315.589744364072",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "70",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "141.526844997837",
+ "position_z": "-152.613060896299",
+ "scale": "0.75",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "71",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-277.151082997564",
+ "position_z": "-231.159514130037",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "72",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-37.5541307998188",
+ "position_z": "-156.617805903279",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "73",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-177.979030175972",
+ "position_z": "-89.3892268471681",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "74",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-284.468556724011",
+ "position_z": "-318.778447786506",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "75",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-27.6770636182899",
+ "position_z": "-370.642633764729",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "76",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-192.2995536029",
+ "position_z": "-94.1027216659714",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "77",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "198.457908134652",
+ "position_z": "303.713911273959",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "78",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "193.48249342926",
+ "position_z": "-112.476287788011",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "80",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-106.827395393358",
+ "position_z": "405.412601405888",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "81",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "32.2916676069584",
+ "position_z": "-96.4723028521125",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "82",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-279.423360608305",
+ "position_z": "-209.768354040635",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "83",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-273.084583552294",
+ "position_z": "-4.88302947095383",
+ "scale": "0.75",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "84",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-361.218760894724",
+ "position_z": "-135.40359976396",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "85",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-45.4731907385332",
+ "position_z": "-299.484521882779",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "86",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-215.537177971033",
+ "position_z": "-88.6407459460477",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "87",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-54.5191483007169",
+ "position_z": "39.0117890982635",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "88",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-213.930495654012",
+ "position_z": "-350.115737148492",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "89",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-177.851005173365",
+ "position_z": "4.88176376086165",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "90",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "247.765848520544",
+ "position_z": "-85.6692355030862",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "91",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-329.225791559667",
+ "position_z": "-151.606556403751",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "93",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-76.6259657253493",
+ "position_z": "-243.436419531126",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "94",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-323.827261063994",
+ "position_z": "37.622384116173",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "95",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-77.5534100848142",
+ "position_z": "-317.566299629535",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "96",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "-162.095993254303",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "53",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "495.118389817833",
+ "position_z": "-207.256235678984",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "97",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "46.4538292267787",
+ "position_z": "-212.443065101743",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "98",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-120.815698450151",
+ "position_z": "-127.416238871501",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "99",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-28.9922567187789",
+ "position_z": "373.255515194621",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "100",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-131.814874246867",
+ "position_z": "-131.825406681466",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "101",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-27.1964226140307",
+ "position_z": "-291.705318210325",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "102",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-251.777915026165",
+ "position_z": "-77.8801881808121",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "103",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "86.4117815925686",
+ "position_z": "-374.935022230664",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "104",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-164.507223068837",
+ "position_z": "-208.983309121832",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "106",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-100.939721904435",
+ "position_z": "-30.8299050321265",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "107",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-251.161156812902",
+ "position_z": "-98.0152116847572",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "108",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-322.071564740969",
+ "position_z": "-165.451868532643",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "109",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "0.554232805573918",
+ "position_z": "-288.529183106228",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "110",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-7.28513591343273",
+ "position_z": "420.8",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "111",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-405.667169666745",
+ "position_z": "-205.646570834985",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "112",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "30.035399725831",
+ "position_z": "-111.975354061307",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "113",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-317.536213551006",
+ "position_z": "-70.617973668685",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "114",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-134.60175162083",
+ "position_z": "-167.698944774154",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "115",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-49.0151538687194",
+ "position_z": "-164.377553142434",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "116",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "52.639235354048",
+ "position_z": "-164.290534065034",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "117",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-67.9867738154695",
+ "position_z": "-377.491062706215",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "119",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-430.365780872574",
+ "position_z": "-116.622758043582",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "120",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-184.153588974314",
+ "position_z": "-172.614006033125",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "121",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "58.4211257772882",
+ "position_z": "-184.358812049457",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "122",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "61.5947093144923",
+ "position_z": "352.136923032579",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "123",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "47.5898713558842",
+ "position_z": "-144.529123738673",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "124",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-237.48776490906",
+ "position_z": "-127.714796943111",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "125",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "49.047549206348",
+ "position_z": "-195.271775396161",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "126",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-9.36882462861553",
+ "position_z": "78.0378366057448",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "127",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "349.936453034222",
+ "position_z": "209.660456637163",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "27",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "381.327430206893",
+ "position_z": "175.680490910735",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "128",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-153.717387326824",
+ "position_z": "-141.621320638933",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "129",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-261.285049247632",
+ "position_z": "-258.019086730811",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "130",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "292.683998171145",
+ "position_z": "-52.6804166835757",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "132",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-251.482875756172",
+ "position_z": "6.8472690530661",
+ "scale": "0.75",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "133",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-342.640988064473",
+ "position_z": "-96.7104911720846",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "134",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-153.726781046611",
+ "position_z": "-277.430029384581",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "135",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-169.745879777971",
+ "position_z": "-264.693709696135",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "136",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-254.827130499572",
+ "position_z": "70.9179960706052",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "137",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "486.113203758345",
+ "position_z": "-15.037291134405",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "79",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "496.212822431703",
+ "position_z": "-59.1850849517023",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "138",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-148.957297013596",
+ "position_z": "-207.480444548568",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "139",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "294.230281536947",
+ "position_z": "259.328014413107",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "66",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "330.076004361792",
+ "position_z": "228.776337045523",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "140",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "113.432669151549",
+ "position_z": "-420.8",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "141",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-283.560307849549",
+ "position_z": "-17.1633196492992",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "142",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-64.5526319578219",
+ "position_z": "-79.8377451889223",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "143",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-46.2448910766955",
+ "position_z": "-196.016133313313",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "145",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-276.775719493239",
+ "position_z": "59.6723699244358",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "146",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-187.277906472508",
+ "position_z": "-351.869930050043",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "147",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "18.5810840539095",
+ "position_z": "-260.306865327825",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "148",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-217.457218700375",
+ "position_z": "-73.2680480207432",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "149",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "126.456810454479",
+ "position_z": "332.324948968295",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "150",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-343.03560792873",
+ "position_z": "-273.829310245676",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "151",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "29.6013340905951",
+ "position_z": "-245.192027517467",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "152",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-293.061872918069",
+ "position_z": "52.7315643074724",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "153",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-316.628710865682",
+ "position_z": "50.1865094825942",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "154",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "-88.1443653320935",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "155",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-57.8521468973766",
+ "position_z": "-88.2057166031209",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "156",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-312.309468412583",
+ "position_z": "-58.1819811985508",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "158",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-83.3707082591342",
+ "position_z": "44.2804060684235",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "159",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-270.422098669817",
+ "position_z": "-243.001438803336",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "160",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-305.076479676534",
+ "position_z": "-36.4453240822122",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "161",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-113.186615031566",
+ "position_z": "-146.40526886507",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "162",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "253.369353142517",
+ "position_z": "-136.557094932567",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "163",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-239.347510548213",
+ "position_z": "65.6793109703734",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "164",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "400.521502463017",
+ "position_z": "151.894742425511",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "157",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "426.799409524515",
+ "position_z": "114.611268935709",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "165",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "21.289644806616",
+ "position_z": "-88.3171431313932",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "166",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-134.067972178351",
+ "position_z": "-12.2108430100412",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "167",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "43.5491380932983",
+ "position_z": "-110.848398274652",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "168",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-191.730794314581",
+ "position_z": "136.92688887112",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "169",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-328.76478999704",
+ "position_z": "-137.971713761784",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "171",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "193.118021308676",
+ "position_z": "-200.206992302003",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "172",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-322.730852186813",
+ "position_z": "-303.169669466168",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "173",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-269.296713857386",
+ "position_z": "-335.923567287025",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "174",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-335.608837497559",
+ "position_z": "29.1873986718945",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "175",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-63.4806044589575",
+ "position_z": "-133.952952501452",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "176",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-50.9205015263921",
+ "position_z": "100.530596146742",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "177",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-485.883864281209",
+ "position_z": "-42.7719240551811",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "178",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-246.046354626379",
+ "position_z": "134.51734819984",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "179",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-74.3499866986181",
+ "position_z": "-82.9070439636947",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "180",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-381.304505741267",
+ "position_z": "-7.18799797180037",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "181",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-55.8911703153726",
+ "position_z": "-97.1948562880204",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "182",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "239.773168625237",
+ "position_z": "-243.587675762135",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "184",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-250.024367368671",
+ "position_z": "-231.076046584724",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "185",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "122.023630383326",
+ "position_z": "-209.630497715358",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "186",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-337.131269506406",
+ "position_z": "-110.885941201081",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "187",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-195.438028252384",
+ "position_z": "-78.0789405129861",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "188",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "466.406027876025",
+ "position_z": "40.5026589538202",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "105",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "446.778464811323",
+ "position_z": "80.8013692351749",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "189",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-239.173863265253",
+ "position_z": "-345.456349040068",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "190",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-497.518895990012",
+ "position_z": "-156.853508406845",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "191",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-9.58688065369978",
+ "position_z": "-420.354806533553",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "192",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "312.649645477543",
+ "position_z": "-107.542327357563",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "193",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "187.832994782854",
+ "position_z": "-357.729499185824",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "194",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-348.595495745908",
+ "position_z": "-155.315670265485",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "195",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-170.934263649635",
+ "position_z": "-76.1313878380547",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "197",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-421.292000293486",
+ "position_z": "-169.344067134475",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "198",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-295.426647439688",
+ "position_z": "121.75078876686",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "199",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-127.153528146176",
+ "position_z": "-22.6682746431654",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "200",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-368.110397066259",
+ "position_z": "90.9899814824861",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "201",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-139.308386237946",
+ "position_z": "-275.789375599362",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "202",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-206.235909775272",
+ "position_z": "0.892273933518311",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "203",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-145.844916274497",
+ "position_z": "-117.422220638827",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "204",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-120.884609549433",
+ "position_z": "-162.563164485712",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "205",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-144.279611990128",
+ "position_z": "60.7400818642998",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "206",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-327.211289644903",
+ "position_z": "-85.2696701432809",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "207",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-197.352671981227",
+ "position_z": "-276.001980904419",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "208",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-170.834169255511",
+ "position_z": "-280.077454525419",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "210",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-263.141581044048",
+ "position_z": "-221.567107256363",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "211",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-202.359593076003",
+ "position_z": "-61.235411671596",
+ "scale": "0.666666666666667",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "212",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-249.742787259869",
+ "position_z": "-111.788214247638",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "213",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-168.792451536742",
+ "position_z": "-147.909513010894",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "214",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-349.326725472802",
+ "position_z": "-356.45042819291",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "215",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-126.047499446347",
+ "position_z": "60.5666023005056",
+ "scale": "0.583333333333333",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "216",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-221.993768459637",
+ "position_z": "-274.41216473481",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "217",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "190.929813698565",
+ "position_z": "-154.136693852395",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "218",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-108.174509026945",
+ "position_z": "-252.498264931572",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "219",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-162.315892809102",
+ "position_z": "-189.987097596992",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "220",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-411.096274427735",
+ "position_z": "-46.3445841901896",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "221",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-168.409411989743",
+ "position_z": "-10.1421601566818",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "223",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-196.111465404191",
+ "position_z": "70.4574748090868",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "224",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-340.11512948549",
+ "position_z": "-126.060070833234",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "225",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "18.554178745011",
+ "position_z": "-347.326329536778",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "226",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-231.468381095795",
+ "position_z": "-116.05409740725",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "227",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-118.860817596373",
+ "position_z": "121.35029561279",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "228",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-349.410993568781",
+ "position_z": "18.6876552729267",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "229",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-241.459070778392",
+ "position_z": "-246.566211709126",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "230",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-190.808608397206",
+ "position_z": "-261.429203124548",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "231",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-177.015033128734",
+ "position_z": "63.8908319244539",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "232",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-359.767122113893",
+ "position_z": "7.45073507405647",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "233",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-15.6795280096804",
+ "position_z": "-293.353231360374",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "234",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-231.221025193892",
+ "position_z": "-11.3572481376359",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "236",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-420.905209444237",
+ "position_z": "-69.3642743241671",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "237",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-304.538758950225",
+ "position_z": "-314.108490548834",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "238",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-110.353905299321",
+ "position_z": "-26.4970411395078",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "239",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-343.457277550073",
+ "position_z": "382.756118032526",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "196",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-291.540231814659",
+ "position_z": "395.038703843442",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "240",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "111.55285310359",
+ "position_z": "-92.5299341598762",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "241",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "254.247863334543",
+ "position_z": "-207.777579079239",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "242",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-162.820614574936",
+ "position_z": "69.7160530410102",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "243",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-413.028998182273",
+ "position_z": "-185.118825426172",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "244",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "218.3323759375",
+ "position_z": "328.552588923446",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "245",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-155.419002056414",
+ "position_z": "-7.96584491555478",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "246",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "5.13402929138476",
+ "position_z": "-275.576978230919",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "247",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-367.105908874797",
+ "position_z": "-405.12313898961",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "249",
+ "layer": "Layer4",
+ "position_x": "0",
+ "position_y": "-66.6733986474273",
+ "position_z": "-215.535210866444",
+ "scale": "0.5",
+ "color": "#a48a9e",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR236C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-410.987910079932",
+ "position_z": "-231.554714649436",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML010W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-367.997590530782",
+ "position_z": "-216.869053120483",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNR016C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-196.540079016992",
+ "position_z": "199.210945655139",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR064W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-140.962861698219",
+ "position_z": "154.931166396109",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL047W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-236.227848450949",
+ "position_z": "216.145348970271",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR386W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-222.044573460883",
+ "position_z": "228.853441552686",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR359W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-67.6844835236312",
+ "position_z": "-3.7315453095984",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR361C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-58.76996047799",
+ "position_z": "-37.1584559867262",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR300C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-451.414123133065",
+ "position_z": "206.339378368469",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL012W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-434.225516312028",
+ "position_z": "200.42725679508",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR235C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-461.240053651207",
+ "position_z": "219.213942696302",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR180W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "56.0064965214194",
+ "position_z": "159.309219967506",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL086C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-21.2725128245013",
+ "position_z": "78.7099132343517",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR390C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "97.3859178460381",
+ "position_z": "214.484191446194",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR249W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "102.650702301688",
+ "position_z": "194.546941767198",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR127C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "429.094534142316",
+ "position_z": "66.1842182081436",
+ "scale": "0.75",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL172W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "450.982946684238",
+ "position_z": "52.0220586171503",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL022C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "415.181731432233",
+ "position_z": "85.3242848097009",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR166C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "421.741234869196",
+ "position_z": "43.4938893035007",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL084C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "445.849234728811",
+ "position_z": "86.8383149236775",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR128C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "340.792994312942",
+ "position_z": "-175.917255216488",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL185W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "320.373749017673",
+ "position_z": "-121.332511048738",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL120W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "347.783266388867",
+ "position_z": "-209.323543892619",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL135C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "32.6868617605361",
+ "position_z": "-262.768207808272",
+ "scale": "0.875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR029C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "59.9815784031403",
+ "position_z": "-309.360639313891",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL259C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "83.9416106498415",
+ "position_z": "-300.273758482233",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHL019C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "72.5325943470168",
+ "position_z": "-307.716050557207",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR170C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "43.7614401663113",
+ "position_z": "-313.882230317168",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR084W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-7.17905536048454",
+ "position_z": "-216.750870415752",
+ "scale": "0.75",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR010C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-25.6156509640734",
+ "position_z": "-173.384860491745",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL037W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-326.599872565735",
+ "position_z": "-357.408371301114",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR005W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-345.465643739574",
+ "position_z": "-338.707167919454",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL062C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-306.662079672867",
+ "position_z": "-374.03952425004",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR058C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-313.130218985288",
+ "position_z": "-351.269775935707",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL195W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "63.3923067188489",
+ "position_z": "194.848081692619",
+ "scale": "0.75",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR261C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "33.1809431310797",
+ "position_z": "238.003149511965",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR288C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "64.2978358940996",
+ "position_z": "244.819215637911",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL024C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "87.6002468310231",
+ "position_z": "237.381830539815",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL019W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "58.3984753525376",
+ "position_z": "113.702691739732",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR174W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-21.1992012233995",
+ "position_z": "225.226041274209",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL062C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-84.3297852638265",
+ "position_z": "73.5017071426556",
+ "scale": "0.875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR370C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-62.3324703667469",
+ "position_z": "45.7808713902676",
+ "scale": "1",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL013C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-91.9002257688591",
+ "position_z": "77.9186136255522",
+ "scale": "0.875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNR035C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-75.1651834314816",
+ "position_z": "78.3409976819232",
+ "scale": "0.875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR065C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-103.945211674169",
+ "position_z": "66.4420217756766",
+ "scale": "0.9375",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL029W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-76.2658040242558",
+ "position_z": "99.8471025608514",
+ "scale": "1",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR240C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "19.1681840876848",
+ "position_z": "38.6157033881925",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR234C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-79.3599692831178",
+ "position_z": "84.5796954722109",
+ "scale": "0.875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR013C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-34.7547704523071",
+ "position_z": "-284.197420658455",
+ "scale": "0.875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR116C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "42.6720261652718",
+ "position_z": "-281.491683001574",
+ "scale": "0.75",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR254W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "13.9910421474283",
+ "position_z": "-331.569120389471",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR023W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-14.2033374347232",
+ "position_z": "-334.702312286015",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL040C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-10.9377824463239",
+ "position_z": "-327.62969867443",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR090W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-166.047526737497",
+ "position_z": "-240.294075507781",
+ "scale": "1.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR127W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "81.0063690906789",
+ "position_z": "54.4241981197919",
+ "scale": "1.0625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL008C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "6.43215327872412",
+ "position_z": "88.1776249353793",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL104C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "184.610210720552",
+ "position_z": "14.0518468211238",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR186W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "18.4261355144571",
+ "position_z": "112.085047427749",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR136W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "95.9860678864498",
+ "position_z": "94.5244717726104",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR427W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "139.390963975772",
+ "position_z": "64.124131876555",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL048C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "5.37754694362746",
+ "position_z": "-9.92419203261272",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL235W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "114.35625924218",
+ "position_z": "84.8002089363388",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR234W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "159.964730951022",
+ "position_z": "50.1258633989357",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNR053C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-159.945194310852",
+ "position_z": "34.0250629344476",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL115W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-392.458553035531",
+ "position_z": "258.679512254911",
+ "scale": "0.8125",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR140C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-408.376456781242",
+ "position_z": "239.093928510586",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL070C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-393.076431351553",
+ "position_z": "280.187597988117",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL153C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-362.835518300685",
+ "position_z": "268.342520399131",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL026W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-377.28025955176",
+ "position_z": "238.541545149768",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR180C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-416.879537292269",
+ "position_z": "268.303791795515",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR148W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-354.709567927614",
+ "position_z": "321.872064920065",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR208W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-335.979166459483",
+ "position_z": "318.277060547759",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL070W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "251.26475209867",
+ "position_z": "-116.568744084798",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR399C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "293.402052224828",
+ "position_z": "-127.214126326476",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR200W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "170.408959618522",
+ "position_z": "-384.823543355155",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER114C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "191.097175475566",
+ "position_z": "-386.934501999386",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL041W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "146.341678502839",
+ "position_z": "-388.801261142918",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR068C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-138.184608560136",
+ "position_z": "316.048547938017",
+ "scale": "1.125",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML077W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-136.688901061888",
+ "position_z": "349.833418142706",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR342W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-89.8371647549923",
+ "position_z": "255.928877856169",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR108W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-175.914644933272",
+ "position_z": "331.164918123233",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR166W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-101.062526522084",
+ "position_z": "329.132253229652",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR407C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-182.235311644438",
+ "position_z": "316.540194077102",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR218C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-157.580775649134",
+ "position_z": "324.309361670535",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR254C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-178.34878067003",
+ "position_z": "304.720536539897",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR246W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-161.149476941688",
+ "position_z": "337.428701294437",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR472W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-148.629935867511",
+ "position_z": "345.196562217423",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR115C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-121.018511270679",
+ "position_z": "343.362469511766",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL031C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "288.837117454979",
+ "position_z": "314.811020891757",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR176C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "287.581017396293",
+ "position_z": "335.215925013952",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR099W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "461.170696146516",
+ "position_z": "-34.4311279365096",
+ "scale": "0.875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER177W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "482.718243167598",
+ "position_z": "-14.2596087318548",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR001C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "454.163238214043",
+ "position_z": "-10.4208469766801",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR001C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "488.952969235006",
+ "position_z": "-42.8770043705333",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL267W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "469.704821370148",
+ "position_z": "-58.4312611158094",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL252C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "451.659536373183",
+ "position_z": "-61.0798092281626",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL017W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "434.93009907124",
+ "position_z": "-32.7167271559789",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAR014C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "53.5920449875023",
+ "position_z": "-253.210150735253",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL126W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-168.065073655623",
+ "position_z": "-299.714667359739",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL014W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-101.741431986006",
+ "position_z": "-184.491494010245",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL148C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-190.783625440371",
+ "position_z": "-268.08785621913",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL171W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-182.894705343788",
+ "position_z": "-281.371513248993",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCL031C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-147.318491173065",
+ "position_z": "-289.453033615632",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL004W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-159.334524193857",
+ "position_z": "-146.146592245069",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR324C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-219.281504203294",
+ "position_z": "-279.684544863271",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR449C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-219.93205742107",
+ "position_z": "-256.602263732882",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR128C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-203.84140219666",
+ "position_z": "-280.409705944573",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR145W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-179.836585952146",
+ "position_z": "-297.689618833439",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL109C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-225.100482809981",
+ "position_z": "-269.141345176516",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR060W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-201.118369381869",
+ "position_z": "-291.801599814682",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR003C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-158.392115762195",
+ "position_z": "-293.248012490363",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR409C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-270.519157800712",
+ "position_z": "-235.940843784788",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR145C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-202.284147952488",
+ "position_z": "-191.140474732918",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR206W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-183.816770091069",
+ "position_z": "-129.632478314473",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR144C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-156.148777291071",
+ "position_z": "-275.575965846947",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR298C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-423.564210861107",
+ "position_z": "106.376125553668",
+ "scale": "1.125",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR119W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-443.152246831302",
+ "position_z": "127.564272102538",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML046W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-468.275622147246",
+ "position_z": "118.306841014937",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL012W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-450.650154253268",
+ "position_z": "138.269986338005",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR235W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-432.750508636063",
+ "position_z": "140.90706057136",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER029C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-459.947938856155",
+ "position_z": "101.130134485961",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR074W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-460.33974601372",
+ "position_z": "127.335350875532",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR275W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-398.66350964047",
+ "position_z": "94.6115144200788",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL061C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-392.623560231598",
+ "position_z": "141.000755184986",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR240C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-392.320506678444",
+ "position_z": "124.124745776286",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR013W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-382.334194739467",
+ "position_z": "133.194660820021",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR125W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-397.392982375649",
+ "position_z": "20.512581987031",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHL021C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-299.75679524172",
+ "position_z": "313.755173104046",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR210W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-307.720442825253",
+ "position_z": "298.527977477465",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL140W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-292.485431541969",
+ "position_z": "327.268516825682",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL035W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-52.9373096396649",
+ "position_z": "-129.10446620585",
+ "scale": "0.9375",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL030W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "10.9668584132012",
+ "position_z": "-74.3241858233511",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR309C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-63.4696425976009",
+ "position_z": "-101.873611180552",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR496C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-129.976553647537",
+ "position_z": "-89.0373977682842",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR102C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-46.2499536467276",
+ "position_z": "-180.454329893857",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL060W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "402.852656778977",
+ "position_z": "-248.025566112687",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR314C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "384.540936463043",
+ "position_z": "-260.716311147447",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL069C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-251.283176827131",
+ "position_z": "-275.58112718952",
+ "scale": "1.0625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR011W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-299.772895456157",
+ "position_z": "-285.781581413801",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR129W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-282.498631117643",
+ "position_z": "-304.354894535715",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR222C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-290.207555323803",
+ "position_z": "-294.31877481367",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR093W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-241.177710381858",
+ "position_z": "-283.833216330748",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR059C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-270.527982159977",
+ "position_z": "-313.505380019768",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR014W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "93.8817601110774",
+ "position_z": "-320.262684572569",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL095W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-457.942885463252",
+ "position_z": "-42.4346339063486",
+ "scale": "1.3125",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR364C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-480.252102740833",
+ "position_z": "-68.5669511609412",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR117C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-480.326033714424",
+ "position_z": "-29.9408970956746",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL213W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-500.862006043142",
+ "position_z": "-6.84056288368942",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLL036C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-489.857560769721",
+ "position_z": "-45.6175103329655",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL120C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-357.092944923396",
+ "position_z": "-55.7134521933773",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL032C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-492.085340289777",
+ "position_z": "-57.7045577762946",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL151C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-485.16972719581",
+ "position_z": "-12.0817175311372",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR101W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-470.672151231721",
+ "position_z": "-56.3710461910857",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL173W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-462.050473889973",
+ "position_z": "-84.1568794565205",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR416W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-500.952380952381",
+ "position_z": "-35.4942807554801",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL209C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-460.951129578727",
+ "position_z": "-70.7522411678554",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR278W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-499.150849957582",
+ "position_z": "-23.2888347351294",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR424W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-494.137759890919",
+ "position_z": "-79.3957479483921",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL059C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "161.254097060005",
+ "position_z": "-187.671110427185",
+ "scale": "1.0625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER133W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "203.538063586831",
+ "position_z": "-186.448399516986",
+ "scale": "0.75",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR228C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "212.602962669145",
+ "position_z": "-238.044572572971",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL317W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "193.274008660729",
+ "position_z": "-200.267414724414",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR061W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "175.207244571095",
+ "position_z": "-231.32069580099",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL049C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "19.2163981794894",
+ "position_z": "-126.739478271492",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR344C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "222.972374532691",
+ "position_z": "-223.026064116656",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR156W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "211.643427442016",
+ "position_z": "-185.953353480359",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR277C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "191.976389233213",
+ "position_z": "-188.749697815057",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR107C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "207.479950322365",
+ "position_z": "-156.773244118681",
+ "scale": "1.125",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR079C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "466.128486074432",
+ "position_z": "154.884424700253",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL208C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "462.958174382815",
+ "position_z": "142.416766420508",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR081C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-200.041698831559",
+ "position_z": "-19.1674046810322",
+ "scale": "2.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL077C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-203.799905083616",
+ "position_z": "-6.6458513631286",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL031W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-244.649350863461",
+ "position_z": "-49.3587610865684",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR276C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-226.471261724646",
+ "position_z": "42.1574667987647",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLL008W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-235.905103971277",
+ "position_z": "-55.7903940873622",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL172W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-215.946413163576",
+ "position_z": "36.9216772939902",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR049C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-182.412217825963",
+ "position_z": "3.85842028798828",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR449W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-244.496116625906",
+ "position_z": "29.9272574762615",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR290C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-148.992218388506",
+ "position_z": "-50.2119182231524",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER126C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-235.043642426256",
+ "position_z": "-17.798909743126",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL211W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-185.091294423532",
+ "position_z": "48.8932069216354",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL093W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-235.345873979797",
+ "position_z": "-40.6067618436189",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL061W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-244.436821840337",
+ "position_z": "20.7679756061256",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL002C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-190.351364454382",
+ "position_z": "-36.5062029023836",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR087C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-172.270476757285",
+ "position_z": "50.9564000764861",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR245C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-241.01842495054",
+ "position_z": "-27.712354089324",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCL054W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-178.2667124532",
+ "position_z": "-30.3873624560169",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFL002C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-181.70567813379",
+ "position_z": "25.4709256279903",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR060W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-241.30449609451",
+ "position_z": "40.0828096994688",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR101C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-171.335170750142",
+ "position_z": "-8.53520819181028",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER006W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-236.606005044827",
+ "position_z": "-5.40531131166648",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER139C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-232.648770996192",
+ "position_z": "31.3385887194382",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL111W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-185.94030521656",
+ "position_z": "-51.7957668404723",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR103W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-220.736625450613",
+ "position_z": "-47.8968784856791",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR052W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-247.201649651731",
+ "position_z": "-12.0959785550621",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR197W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-228.999591541338",
+ "position_z": "-28.0944631135199",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLL034C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-97.2450224753813",
+ "position_z": "-83.0222175875356",
+ "scale": "0.75",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR002C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-276.42356654359",
+ "position_z": "-12.3328589076322",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL110C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-198.95307706789",
+ "position_z": "-58.9575908259891",
+ "scale": "2",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR272W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-223.732975558169",
+ "position_z": "-66.438976908948",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR284W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "359.632362301524",
+ "position_z": "291.93740844671",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR106C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "343.702215131587",
+ "position_z": "286.557378397064",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLL013C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "212.305658242684",
+ "position_z": "16.6390084610686",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML124C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "253.819531608526",
+ "position_z": "24.4914434421601",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR121W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "161.605450541071",
+ "position_z": "-27.5174495545239",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR341W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-88.6477968806325",
+ "position_z": "-160.331773043193",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML059C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "236.388745227221",
+ "position_z": "-97.3845100459612",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR221C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "179.654364373493",
+ "position_z": "-119.712941049112",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR308C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "221.738145621855",
+ "position_z": "-52.7458229263937",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL067W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "10.8827479468974",
+ "position_z": "-135.764333872621",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL045W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "16.2926689954733",
+ "position_z": "71.9814636305325",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL043W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-188.779494541382",
+ "position_z": "-108.241848482603",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR088W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-237.831248436276",
+ "position_z": "-103.736493564197",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR106C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-140.394195172362",
+ "position_z": "-39.7426169156283",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL182C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-261.078811359382",
+ "position_z": "-81.1093024013733",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL012W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-184.996529607909",
+ "position_z": "-118.863485611619",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL127W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-75.7584316148516",
+ "position_z": "158.264519308778",
+ "scale": "1.1875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR052W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-104.071475619977",
+ "position_z": "202.876062919652",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL060C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-117.772457345151",
+ "position_z": "195.596212364116",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL152C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-103.514430418973",
+ "position_z": "190.381347733979",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL251C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-121.37809961228",
+ "position_z": "183.581306010827",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR044C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "14.5049870980138",
+ "position_z": "153.517445037499",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR371W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-156.911506904825",
+ "position_z": "68.6561272016564",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR169C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-144.823415457035",
+ "position_z": "210.208883297291",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR050C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-87.8946639139694",
+ "position_z": "200.311083848751",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL131C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-81.6886055904201",
+ "position_z": "209.366785342619",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL201C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-292.712394651561",
+ "position_z": "-201.620043245674",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER155C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-189.444562417993",
+ "position_z": "-171.77451346312",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR379W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-337.194421200935",
+ "position_z": "-218.997054978303",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL207W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-205.247066831235",
+ "position_z": "-130.009850596464",
+ "scale": "1.1875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL266W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-258.128856300688",
+ "position_z": "-147.102128475392",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR247C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-185.973609377649",
+ "position_z": "-176.632811476032",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL204W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-274.157984779118",
+ "position_z": "-147.731226416297",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL143W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-237.70012417645",
+ "position_z": "-178.428538778076",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR290C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-130.135078827063",
+ "position_z": "-36.0670103011901",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR289W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-246.851809611637",
+ "position_z": "-163.433261798712",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL176C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-258.229649097868",
+ "position_z": "-161.590916870294",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR054W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-269.540212714671",
+ "position_z": "-158.712325607418",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR056C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-119.78424706244",
+ "position_z": "-52.0448801775474",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL313C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "265.861487135263",
+ "position_z": "-43.4523386112023",
+ "scale": "0.8125",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR121W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "321.533738781929",
+ "position_z": "-63.5546683514246",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER110C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "182.368462362582",
+ "position_z": "1.29396255010312",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL094C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "291.521714778712",
+ "position_z": "-14.3375995006477",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR117W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "293.504931933386",
+ "position_z": "-38.0919114726672",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNR046W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "489.421257013389",
+ "position_z": "53.9937636321555",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNR050C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "489.236750530507",
+ "position_z": "73.7484345693651",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML014W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "500.952380952381",
+ "position_z": "41.6612322730242",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL124C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "483.694398253816",
+ "position_z": "34.5786219122132",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL060W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-185.781665425809",
+ "position_z": "-132.944677902543",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR164C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "487.648030663227",
+ "position_z": "90.1233893619947",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR179C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "248.045672041893",
+ "position_z": "-197.142063114422",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR195W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "221.160717592643",
+ "position_z": "-175.849117424113",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR356W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "460.497974602141",
+ "position_z": "-171.507028464906",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR243C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "462.81284906261",
+ "position_z": "-156.333224199204",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR301W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "195.511953957586",
+ "position_z": "-156.924096759483",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR115W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "216.63340677352",
+ "position_z": "-196.072662800315",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR093C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "217.21114219017",
+ "position_z": "-204.636971004308",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR208W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "165.425743419893",
+ "position_z": "-50.7717960588",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL222W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "267.380239428422",
+ "position_z": "-174.175714765484",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR273C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-20.8307477206399",
+ "position_z": "242.594332664258",
+ "scale": "1",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL194C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-23.5903411239741",
+ "position_z": "288.213836859352",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR347C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-118.971594015678",
+ "position_z": "218.343428735751",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL188C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "63.2401282661038",
+ "position_z": "234.613679293261",
+ "scale": "0.625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR103W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "7.30335179515026",
+ "position_z": "257.373520739164",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL189W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-99.8840410588763",
+ "position_z": "246.912506587161",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCR033W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-1.68992954773995",
+ "position_z": "238.022811325311",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL112W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-34.2333589384692",
+ "position_z": "233.723156377904",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR029C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-9.67116933303947",
+ "position_z": "291.57426241052",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML109W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "120.888266918331",
+ "position_z": "250.633868471796",
+ "scale": "0.6875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFL007W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "128.398520695645",
+ "position_z": "290.587477786242",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR362C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "145.035365945238",
+ "position_z": "288.43838272956",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR285C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "45.8838620660385",
+ "position_z": "-238.123519566",
+ "scale": "0.875",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR064C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "70.967771644718",
+ "position_z": "-215.734942499191",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL080C:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "88.4197806750583",
+ "position_z": "-273.316201438881",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR189W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "99.9993182132941",
+ "position_z": "-273.035995268364",
+ "scale": "0.5",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR162W:1",
+ "layer": "Layer5",
+ "position_x": "0",
+ "position_y": "-30.7034085370229",
+ "position_z": "-211.019460077238",
+ "scale": "0.5625",
+ "color": "#c6e1e8",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR236C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-435.598419959934",
+ "position_z": "-228.632455138504",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML010W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-411.021533193999",
+ "position_z": "-219.311847513853",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNR016C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-212.469140011696",
+ "position_z": "201.988839259788",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR064W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-157.585256344065",
+ "position_z": "160.737039871868",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL047W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-251.657526672999",
+ "position_z": "219.640480841891",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR386W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-241.531783591524",
+ "position_z": "233.441424195111",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR359W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-85.2341146742834",
+ "position_z": "7.22483839794717",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR361C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-79.1318465827047",
+ "position_z": "-22.5878405164011",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR300C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-467.435668078068",
+ "position_z": "193.626589131253",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL012W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-450.406970442808",
+ "position_z": "187.868518129618",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR235C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-476.889613523305",
+ "position_z": "207.293214597938",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR180W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "106.846115973911",
+ "position_z": "176.699312290841",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL086C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "102.640145303817",
+ "position_z": "111.077007781487",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR390C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "126.346517995647",
+ "position_z": "224.547276225172",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR249W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "116.49081023084",
+ "position_z": "159.939056466767",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR127C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "406.149428407233",
+ "position_z": "55.4528824122733",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL172W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "437.333352464721",
+ "position_z": "39.851601040917",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL022C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "391.893776160092",
+ "position_z": "68.087903934409",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR166C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "393.865011491714",
+ "position_z": "40.2804288932272",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL084C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "436.443451299816",
+ "position_z": "74.6630621300778",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR128C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "351.884072481854",
+ "position_z": "-196.419308325587",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL185W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "347.629384938652",
+ "position_z": "-161.803458032841",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL120W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "350.021521299912",
+ "position_z": "-224.839551405771",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL135C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "20.6938053193545",
+ "position_z": "-238.922173496225",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR029C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "45.6996247385522",
+ "position_z": "-291.5352723096",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL259C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "70.3118963402753",
+ "position_z": "-281.033071963986",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHL019C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "59.5709376218732",
+ "position_z": "-286.60060170367",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR170C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "31.8457743965344",
+ "position_z": "-294.096309187724",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR084W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "2.29031502988465",
+ "position_z": "-170.096365328141",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR010C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-45.7638868607659",
+ "position_z": "-154.238423911403",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL037W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-300.811102745648",
+ "position_z": "-359.324158958857",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR005W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-325.297374518552",
+ "position_z": "-341.806796481135",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL062C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-283.856596812566",
+ "position_z": "-375.876470144467",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR058C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-297.801255690898",
+ "position_z": "-344.796107760081",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL195W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "64.1862200391238",
+ "position_z": "148.526224923234",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR261C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "47.7022918671842",
+ "position_z": "222.014367236275",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR288C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "66.909306938607",
+ "position_z": "212.827288170874",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL024C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "83.6114748185428",
+ "position_z": "205.742851157976",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL019W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "56.8867959050262",
+ "position_z": "21.0517640237267",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR174W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "22.3501949185839",
+ "position_z": "269.047684756876",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL062C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-92.5510610806656",
+ "position_z": "89.303438125728",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR370C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-54.277214498578",
+ "position_z": "51.6706685173354",
+ "scale": "1",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL013C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-101.688708372878",
+ "position_z": "87.7202196862285",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNR035C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-85.0328476916451",
+ "position_z": "93.1159261756715",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR065C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-114.532394952141",
+ "position_z": "68.8481185271299",
+ "scale": "0.9375",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL029W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-90.2469892048867",
+ "position_z": "108.573898631387",
+ "scale": "1",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR240C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "65.0817668738555",
+ "position_z": "-6.43904620914293",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR234C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-98.4705915412447",
+ "position_z": "95.9104478784466",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR013C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "0.182994963500676",
+ "position_z": "-253.659202513301",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR116C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "32.4114497383632",
+ "position_z": "-252.125893789833",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR254W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "52.2810354701909",
+ "position_z": "-321.421929779433",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR023W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-0.25145895304621",
+ "position_z": "-310.100661208672",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL040C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "11.7618580851696",
+ "position_z": "-310.15074010454",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR090W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-57.9321067780822",
+ "position_z": "-171.864444753975",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR127W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "70.8872883679795",
+ "position_z": "60.5032685357077",
+ "scale": "1.125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL008C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-7.66440912372025",
+ "position_z": "98.030052468909",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL104C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "176.925491855173",
+ "position_z": "50.3165018259367",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR186W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "85.2177907599344",
+ "position_z": "95.776175547416",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR136W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "79.1157962587512",
+ "position_z": "105.5156983805",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR427W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "128.754528754997",
+ "position_z": "42.519320315164",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL048C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "77.0040433838462",
+ "position_z": "17.300004533011",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL235W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "90.4050472166323",
+ "position_z": "66.2595510263699",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR234W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "132.925831048607",
+ "position_z": "72.3750513189489",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNR053C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-167.011188401821",
+ "position_z": "9.26897358641145",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL115W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-382.539701695818",
+ "position_z": "249.637894990808",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR140C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-396.811110335016",
+ "position_z": "228.740334966009",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL070C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-378.378393149725",
+ "position_z": "273.960220473584",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL153C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-353.554737234788",
+ "position_z": "255.396126180805",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL026W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-362.835120788722",
+ "position_z": "235.14238144675",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR180C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-413.864092351288",
+ "position_z": "253.967894722889",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR148W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-359.827266724348",
+ "position_z": "307.215419528597",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR208W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-341.166536815149",
+ "position_z": "302.57890562457",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL070W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "184.681807255834",
+ "position_z": "-92.7070667381593",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR399C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "259.675235269608",
+ "position_z": "-112.800404194263",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR200W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "157.381687316034",
+ "position_z": "-398.687541845168",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER114C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "169.375734853947",
+ "position_z": "-401.994163750012",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL041W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "137.309984284637",
+ "position_z": "-404.930807170636",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR068C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-112.094013753206",
+ "position_z": "280.428177561028",
+ "scale": "1.125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML077W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-118.222677129897",
+ "position_z": "326.164546051634",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR342W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-44.0163868319418",
+ "position_z": "180.204819319612",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR108W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-157.410339054932",
+ "position_z": "306.616560023887",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR166W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-87.2902887160677",
+ "position_z": "310.295076695411",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR407C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-163.188045980442",
+ "position_z": "294.044128575255",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR218C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-131.101164785732",
+ "position_z": "301.414954156836",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR254C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-154.104283899898",
+ "position_z": "286.07163018837",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR246W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-143.950706444488",
+ "position_z": "309.29801076588",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR472W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-126.660441873368",
+ "position_z": "319.400134688774",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR115C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-103.157503532278",
+ "position_z": "316.383251726313",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL031C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "300.051527604562",
+ "position_z": "323.580384347403",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR176C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "278.135256679804",
+ "position_z": "325.989406303243",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR099W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "467.787166502713",
+ "position_z": "-46.5943623832219",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER177W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "491.479221020467",
+ "position_z": "-26.6977203198098",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR001C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "465.175069041457",
+ "position_z": "-20.6137352150803",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR001C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "492.582339284177",
+ "position_z": "-58.1201445222148",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL267W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "475.231176546498",
+ "position_z": "-71.8295794256417",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL252C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "441.708816279955",
+ "position_z": "-67.7880406003939",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL017W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "438.718255802993",
+ "position_z": "-47.3075241303252",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAR014C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "19.6396565841036",
+ "position_z": "-204.656241467681",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER155C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-68.896307095619",
+ "position_z": "-160.230260393224",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR064C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "66.0378240330883",
+ "position_z": "-166.394385699386",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR285C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "50.2219002357791",
+ "position_z": "-178.163381878611",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR074C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-211.600017203597",
+ "position_z": "-19.3429784195508",
+ "scale": "1.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR016C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-192.303017095732",
+ "position_z": "-65.9553442266465",
+ "scale": "2.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER126C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-249.851031495023",
+ "position_z": "-24.9877908590723",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL051W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-166.318158024277",
+ "position_z": "-87.48808600795",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL009W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-259.029682485735",
+ "position_z": "-31.1463128649405",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL093W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-254.20418488003",
+ "position_z": "-46.5547071821649",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL002C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-187.925995788527",
+ "position_z": "-27.6310103342877",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR245C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-258.422893841486",
+ "position_z": "-38.8251680374372",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCR072C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-286.086481262199",
+ "position_z": "7.58498450933581",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR101C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-135.024973028698",
+ "position_z": "25.8955054830516",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER006W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-263.710887942728",
+ "position_z": "15.1652940601006",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR103W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-203.601633161548",
+ "position_z": "-35.43805672946",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR052W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-273.191066311298",
+ "position_z": "6.5369015330661",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR085W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-270.110645157485",
+ "position_z": "28.7631380680323",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR197W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-246.379808220046",
+ "position_z": "-37.0624897993691",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR081C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-217.761829083962",
+ "position_z": "-12.0401511938217",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR106C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-80.8015881083614",
+ "position_z": "-35.146536809288",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL110C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-197.546413890517",
+ "position_z": "-44.4054391703933",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL182C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-252.406837067384",
+ "position_z": "-57.2290804000977",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL007W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "417.673729390657",
+ "position_z": "-164.825611157448",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL034C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "424.431256458596",
+ "position_z": "-180.292169709335",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER071C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "411.698948685047",
+ "position_z": "-150.002686679462",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIR003W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "434.696918061683",
+ "position_z": "-164.117434487313",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL111W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "262.459577020937",
+ "position_z": "49.1822944518103",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL160W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "319.224533890879",
+ "position_z": "43.1453714613512",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL178W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-266.419282556369",
+ "position_z": "95.1236725866582",
+ "scale": "1.375",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR347C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-202.957514680211",
+ "position_z": "165.808628108795",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL087C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-286.655612288413",
+ "position_z": "146.437807435745",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL190C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-297.005864444663",
+ "position_z": "130.141636530768",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR086W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-309.356569314069",
+ "position_z": "128.476671959918",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR432W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-300.780155312241",
+ "position_z": "142.656740937523",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL251C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-273.593051320097",
+ "position_z": "141.747808113642",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML049C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-371.508206918671",
+ "position_z": "38.7596823841993",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR275W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-359.368409485329",
+ "position_z": "62.4683989302862",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL061C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-337.043727502345",
+ "position_z": "134.040849104778",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR240C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-339.223560556443",
+ "position_z": "117.984911579176",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR013W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-329.800078069198",
+ "position_z": "126.643496629218",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL189W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-174.299538400681",
+ "position_z": "222.627849439255",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR125W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-305.379327866735",
+ "position_z": "-0.944704493693735",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL049C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-129.791728359985",
+ "position_z": "-57.2995043169581",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML117W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-283.19666427996",
+ "position_z": "130.56927171928",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR175W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-73.9128210823141",
+ "position_z": "-133.401802175511",
+ "scale": "1.3125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR290C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-73.2906884351798",
+ "position_z": "-63.7322794175109",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCL059C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-107.529085613572",
+ "position_z": "-175.525610773021",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR309C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-64.5246860675052",
+ "position_z": "-71.1654152827113",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL014W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-77.0387011403324",
+ "position_z": "-135.541138413746",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL043W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-135.611400057887",
+ "position_z": "-134.438000926233",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR197W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-9.34357471614305",
+ "position_z": "-169.750845952726",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR162W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-103.575765137617",
+ "position_z": "-163.095156814713",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR496C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-117.456058362884",
+ "position_z": "-138.308246468097",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL035W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-44.4382666510064",
+ "position_z": "-115.155566122829",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL012W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-133.391257256675",
+ "position_z": "-151.918346962541",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER168C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "36.7046107901779",
+ "position_z": "311.54079196498",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR025C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-294.557436972281",
+ "position_z": "22.3397547673712",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL108W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-341.684886088145",
+ "position_z": "60.2224799803177",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR081C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-331.867150639616",
+ "position_z": "65.1805721678026",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR460W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-342.631268635039",
+ "position_z": "46.7610565288136",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR173W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-320.408967405349",
+ "position_z": "66.0260127669097",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL021C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "276.646780897023",
+ "position_z": "154.250554521974",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR134W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "280.131691724234",
+ "position_z": "169.042905243725",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL288W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "266.043518134055",
+ "position_z": "176.41185509307",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR188W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "324.220165877012",
+ "position_z": "169.912269455921",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCR093W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "222.778735159127",
+ "position_z": "152.890478104092",
+ "scale": "0.9375",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL195W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "249.522592155497",
+ "position_z": "111.041898116072",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNR052C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "267.269717557176",
+ "position_z": "166.372991295149",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFR028C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "331.925962795785",
+ "position_z": "315.56013238035",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR171W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "320.97612548993",
+ "position_z": "304.184360086783",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR357W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "112.536742784734",
+ "position_z": "-410.873574012797",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR160W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-211.102553873372",
+ "position_z": "388.818594919279",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR135W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-239.837776560135",
+ "position_z": "369.701188662292",
+ "scale": "0.9375",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR199W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-214.325265943913",
+ "position_z": "373.971024334244",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL256C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-226.501278767228",
+ "position_z": "387.268080729821",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIR031C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-189.131644306735",
+ "position_z": "401.86731344796",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR314C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "359.432160956601",
+ "position_z": "-270.454752463584",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCR002C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "365.776512524215",
+ "position_z": "-253.184483634315",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR076C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "334.992699510899",
+ "position_z": "-275.414260983424",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR107C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "342.508898266246",
+ "position_z": "-290.071601365605",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL225W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "365.096041414985",
+ "position_z": "-284.772461215022",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL139C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-176.763847942852",
+ "position_z": "-106.653336745033",
+ "scale": "0.9375",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL262W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-118.004439470506",
+ "position_z": "-217.263535275739",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR371W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-217.573826059069",
+ "position_z": "-35.1861849359565",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHL034C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-222.609614652844",
+ "position_z": "-146.213222475095",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL004W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-78.5992701566361",
+ "position_z": "-116.676972219241",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL165W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "97.2879267695176",
+ "position_z": "361.967249975134",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL315C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "112.898970479394",
+ "position_z": "370.632238339986",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR072W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "79.2545604774421",
+ "position_z": "363.550361426949",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL145C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "282.947388202936",
+ "position_z": "215.347769997631",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL137W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "273.14093275872",
+ "position_z": "192.818755017675",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR110W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "253.069330289665",
+ "position_z": "189.202601638623",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR103C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "325.335834988937",
+ "position_z": "-3.9462479530888",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL094C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "240.128409171644",
+ "position_z": "13.1729214669107",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL330C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "369.853998620057",
+ "position_z": "-8.02715388112551",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL004W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "368.40454697026",
+ "position_z": "-22.8161002888897",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR202W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "404.21901232754",
+ "position_z": "-127.048862293672",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL001C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "408.241244877214",
+ "position_z": "-113.104818475775",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL126C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-41.9724620072826",
+ "position_z": "-358.549484155082",
+ "scale": "1",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL271C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-25.2125297225018",
+ "position_z": "-400.847161318599",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR337W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-18.5497898630407",
+ "position_z": "-388.785430620056",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR170C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-43.4397294752088",
+ "position_z": "-395.258163438104",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL246C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-4.80509878943627",
+ "position_z": "-386.869085041125",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR028C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-34.695724132086",
+ "position_z": "-386.053931615905",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL058W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-54.6567048682317",
+ "position_z": "-325.333812045698",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR048W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-55.9077331014935",
+ "position_z": "-396.133441325039",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR049W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-68.0042080375251",
+ "position_z": "-391.273220888689",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL190C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "105.594569126798",
+ "position_z": "93.0166277208055",
+ "scale": "1.375",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR349W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "137.830594602041",
+ "position_z": "131.065066440997",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL030W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "61.5656750069681",
+ "position_z": "12.951988766952",
+ "scale": "1",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR138W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "102.316188158811",
+ "position_z": "12.593478708455",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR109W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "124.626156342333",
+ "position_z": "140.579125643411",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL134C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "150.667796497052",
+ "position_z": "128.832135204824",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL188C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "92.2426366337907",
+ "position_z": "172.780613344825",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL081W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "135.855292882318",
+ "position_z": "147.173879905001",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL016W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "162.349763032509",
+ "position_z": "130.418171312486",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL104C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "159.849876663669",
+ "position_z": "117.10981166048",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR381W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "148.899892037447",
+ "position_z": "141.67959290355",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR273C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "50.8289052544322",
+ "position_z": "178.854737105783",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML109W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "126.082547550916",
+ "position_z": "181.03425514974",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL031W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-270.156613222122",
+ "position_z": "-65.7313165153221",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLL008W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-259.183505341076",
+ "position_z": "-73.0685426460331",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR049C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-160.813023263375",
+ "position_z": "-32.5185687227391",
+ "scale": "1.125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL021W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-310.173108892139",
+ "position_z": "-70.7963939173921",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR202W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-252.677824298468",
+ "position_z": "-94.5887687047833",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL099W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-269.494542899776",
+ "position_z": "-96.5255321442957",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFR001W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-258.966155177938",
+ "position_z": "-103.967367945053",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL036W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-272.442766350531",
+ "position_z": "-86.4229975182784",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR088W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-246.930770567205",
+ "position_z": "-109.313692638487",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR079C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-62.8706928650134",
+ "position_z": "-54.2325844864511",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER002W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-278.20084232408",
+ "position_z": "-74.6350757520992",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR002C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-277.822187195509",
+ "position_z": "-45.7481025113843",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR272W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-233.159532086538",
+ "position_z": "-91.8931761983947",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR213W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-401.339032601375",
+ "position_z": "-11.4046908551621",
+ "scale": "1.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR364C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-443.866041667511",
+ "position_z": "-49.3730845360773",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR117C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-437.499449318933",
+ "position_z": "-10.4053318497026",
+ "scale": "1.125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR065C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-435.523184209254",
+ "position_z": "-4.04271775832302",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR288W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-404.213706975904",
+ "position_z": "29.035924702334",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLL036C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-451.135410445064",
+ "position_z": "-22.3321782938091",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNR011C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-450.334273855107",
+ "position_z": "17.1089186701885",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER013W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-432.285416537749",
+ "position_z": "30.3248887008718",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL120C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-240.157860622759",
+ "position_z": "-64.4730101362831",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL032C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-456.182623616777",
+ "position_z": "-32.2185376277224",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL151C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-448.74154339029",
+ "position_z": "6.75439114038335",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR101W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-438.109349226777",
+ "position_z": "-32.3265950460091",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL173W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-433.019108567854",
+ "position_z": "-71.8802720578718",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR416W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-462.145182970482",
+ "position_z": "-16.4895909309717",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCR063W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-425.500347405037",
+ "position_z": "9.56072520212655",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL209C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-430.682601461133",
+ "position_z": "-50.2466632627277",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL128C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-436.876422063173",
+ "position_z": "20.6219547198579",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR278W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-465.999432594905",
+ "position_z": "-4.74726629317337",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL130W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-15.6902182027198",
+ "position_z": "-86.7004130829831",
+ "scale": "1.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL228W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "10.0712858535973",
+ "position_z": "-110.626008989787",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR012W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-2.73957865366964",
+ "position_z": "-119.72470199335",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL145C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "113.305832069229",
+ "position_z": "-37.4778221873485",
+ "scale": "1.125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR221C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "87.870481094194",
+ "position_z": "-116.865629140209",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL207W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "11.7261716945973",
+ "position_z": "-36.6874286877199",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL075C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "78.2871724262478",
+ "position_z": "-169.177195306656",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER103W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-9.89603267563751",
+ "position_z": "-63.816358159476",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL088W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "24.8495602503775",
+ "position_z": "-110.350030843975",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR001C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-10.9777793140286",
+ "position_z": "-144.743592541567",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR121W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "78.8691484624921",
+ "position_z": "-61.6670382526825",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL078W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-10.727704083763",
+ "position_z": "-106.394192342573",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR301W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "133.839605559024",
+ "position_z": "-148.334603446114",
+ "scale": "1.25",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR115W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "148.993714812801",
+ "position_z": "-191.694314683508",
+ "scale": "1.1875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER133W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "140.453053219278",
+ "position_z": "-175.347507942518",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR002W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "119.296213084833",
+ "position_z": "-189.495231537094",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL317W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "135.024098889498",
+ "position_z": "-191.195970230194",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL043C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "120.303640769785",
+ "position_z": "-182.592673876154",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR195W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "148.724706079539",
+ "position_z": "-167.092227811644",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR061W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "132.262168727236",
+ "position_z": "-206.063244777525",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR156W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "148.752797610226",
+ "position_z": "-179.602991339877",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL059C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "174.976821126832",
+ "position_z": "-188.13379500338",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR277C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "135.337333485666",
+ "position_z": "-183.281190811899",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHL035C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "196.096052147739",
+ "position_z": "-230.57840189528",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL206C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "268.819099369155",
+ "position_z": "103.156281427671",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR167W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "323.96307701546",
+ "position_z": "116.984559144321",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER164W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "27.7328899683075",
+ "position_z": "-81.803677850144",
+ "scale": "1",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL035C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "58.6449343870591",
+ "position_z": "-57.7303413069591",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR061W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "57.0695622735849",
+ "position_z": "-48.2934781601726",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR039W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "10.9191212461272",
+ "position_z": "-43.7941458755301",
+ "scale": "1.4375",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR245C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-4.7221564431548",
+ "position_z": "-158.56136378113",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR304W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "5.05415021244323",
+ "position_z": "-158.035496478389",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR207C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "9.95369969240346",
+ "position_z": "-165.063376927113",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR172W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-16.516595905908",
+ "position_z": "-26.0919874388777",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR429C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "29.4788774297711",
+ "position_z": "-54.2558502107185",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL088W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-11.5066159165715",
+ "position_z": "-10.3881599531043",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR270W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "23.2184372533198",
+ "position_z": "-13.3443384242349",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL155W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-247.554824089377",
+ "position_z": "387.365201191236",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR295W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-265.70243982655",
+ "position_z": "360.548958142051",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR079W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-235.734905795921",
+ "position_z": "350.965676603754",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR091W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-256.247252529331",
+ "position_z": "347.596851779458",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL014W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-261.974237052231",
+ "position_z": "376.045319621044",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR050W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-493.293561659346",
+ "position_z": "8.86977640573309",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR250C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "154.117808179102",
+ "position_z": "-244.678600118525",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR228C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "206.357022085833",
+ "position_z": "-268.271925532212",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL044C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "180.634614139194",
+ "position_z": "-290.244595866803",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL195W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "194.671792777337",
+ "position_z": "-286.141556612303",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR155W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "242.064025715989",
+ "position_z": "368.110404316976",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR187C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "225.520155043921",
+ "position_z": "373.342251724395",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFR051C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "326.433278062272",
+ "position_z": "221.034613097699",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL010W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "284.390199098257",
+ "position_z": "253.740397505564",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL287W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "315.133095205372",
+ "position_z": "231.57690119425",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR238C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "303.572079770998",
+ "position_z": "238.001522172564",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL076W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "301.222346743341",
+ "position_z": "250.788836567539",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL045C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "434.305210523071",
+ "position_z": "-110.416802214243",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR191W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "450.350732659236",
+ "position_z": "-107.947841777175",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL067C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "60.6144423614041",
+ "position_z": "401.837612960819",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL187C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "55.8915144383952",
+ "position_z": "385.613884456024",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL052W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "69.7725547301096",
+ "position_z": "415.904820319591",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR051W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "46.3329840148427",
+ "position_z": "411.530050752394",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR155C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "19.1205434577569",
+ "position_z": "117.743651413663",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR103W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "22.9214386405667",
+ "position_z": "209.770464043853",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR290C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-60.8729092381898",
+ "position_z": "49.652911739899",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCR033W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "4.52227444483583",
+ "position_z": "186.996267744193",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL112W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-14.5808632338525",
+ "position_z": "184.273256919697",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL232W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-100.937909047097",
+ "position_z": "337.353201586517",
+ "scale": "1.0625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL021C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-74.8403505984568",
+ "position_z": "384.409325009813",
+ "scale": "1.0625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR158C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-75.8119555642265",
+ "position_z": "364.310600140844",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR069C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-101.205588279531",
+ "position_z": "380.322434663144",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL111C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-89.4912443451734",
+ "position_z": "367.286989095489",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCR035C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-66.4558110696656",
+ "position_z": "371.948067776618",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR001W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-61.7453829936445",
+ "position_z": "362.798678750209",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR195W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-89.4754220681205",
+ "position_z": "377.31669724734",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR076C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-118.892264366689",
+ "position_z": "373.379615293809",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIR035C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-59.6215922222333",
+ "position_z": "347.318086742748",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR158W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "57.2590645369119",
+ "position_z": "-101.38478863329",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR078C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-119.419712552977",
+ "position_z": "-316.711802512232",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR191C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-141.218144239902",
+ "position_z": "-361.268172927716",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR175W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-94.8296520533532",
+ "position_z": "-266.691829725386",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL290W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-114.245164522122",
+ "position_z": "-354.622776648437",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL094C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-137.077122787104",
+ "position_z": "-350.331291279192",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR087W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-127.185553426373",
+ "position_z": "-365.178228449905",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLL040C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-110.698491002291",
+ "position_z": "-364.481231919249",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL139W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-248.502553219057",
+ "position_z": "272.099554317989",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL006C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-280.084106613178",
+ "position_z": "274.871126323789",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML112W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-266.207911477378",
+ "position_z": "290.901703465811",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR169C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-201.770032733924",
+ "position_z": "248.685226892743",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR418C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "172.171511281993",
+ "position_z": "-69.1380368927205",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR123C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "185.955179189296",
+ "position_z": "-62.4168208525102",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR279W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "177.036100550444",
+ "position_z": "-55.1182562363426",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL244W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "186.550173543566",
+ "position_z": "-48.0706767199213",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR240C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-420.896824744251",
+ "position_z": "33.8195862889916",
+ "scale": "1.125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL213W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-481.180048930528",
+ "position_z": "27.2173053159705",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL043C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-443.231695985281",
+ "position_z": "61.655903581454",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL203W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-465.835539888256",
+ "position_z": "56.2859324632405",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL030W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-461.331788865805",
+ "position_z": "65.5387336074147",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER029C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-451.10127899175",
+ "position_z": "76.6871024785044",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR147C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-474.116850554683",
+ "position_z": "48.7764603154744",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL005W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "331.993982824837",
+ "position_z": "62.3762960943577",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFL039C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "264.532625056811",
+ "position_z": "37.8312164547382",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR233C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "364.863813402815",
+ "position_z": "86.8585169551211",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL138W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "367.606946753157",
+ "position_z": "71.2047955252818",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR155W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "149.255048299536",
+ "position_z": "-360.359896236121",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR045C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "109.012503973808",
+ "position_z": "-351.374480368866",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL118C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "404.820062161654",
+ "position_z": "-205.283675594094",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL149W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "422.765362595042",
+ "position_z": "-213.305083985195",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML091C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "393.968257696474",
+ "position_z": "-192.463623050619",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YEL015W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "398.143232284354",
+ "position_z": "-221.825683965895",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL142W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-45.5975814918509",
+ "position_z": "401.332629731893",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR095C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-82.2978511520081",
+ "position_z": "413.115700044263",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR081W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-60.8665127564532",
+ "position_z": "412.644411635607",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR164C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "14.5948486672018",
+ "position_z": "403.935858300488",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR499W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-4.09314501607304",
+ "position_z": "410.451398142261",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAR007C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "19.0243999836965",
+ "position_z": "387.177635408768",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR122W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "15.5725568193289",
+ "position_z": "420.8",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL213C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-53.6568516471594",
+ "position_z": "-264.194132996708",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR341W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-75.7299477350429",
+ "position_z": "-224.173305220913",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR116C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-63.1700183656382",
+ "position_z": "-270.284062202768",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL240W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "352.987850630516",
+ "position_z": "47.9094545219762",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR121W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-79.123498284659",
+ "position_z": "-272.717397788559",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR278W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-109.620288218709",
+ "position_z": "-272.107618862527",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL059W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "160.185599678198",
+ "position_z": "78.943444424311",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER110C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "82.6057909888968",
+ "position_z": "45.610039633299",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR115C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "199.142529765712",
+ "position_z": "113.015027190857",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR308C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "155.230502751787",
+ "position_z": "-7.42482130146271",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR395W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "210.501489684286",
+ "position_z": "108.166849321422",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR128W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-176.935864993778",
+ "position_z": "-286.61278323424",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL097C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-209.763332394242",
+ "position_z": "-329.309811226853",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL084C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-197.006083141697",
+ "position_z": "-340.105720565421",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL139C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-188.346658198424",
+ "position_z": "-333.556736423746",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL047C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "272.080456729201",
+ "position_z": "-334.477752479688",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL243W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "288.583506795548",
+ "position_z": "-338.425913535282",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCR030C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "286.902798564159",
+ "position_z": "-321.461608326904",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR348C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "256.951577583912",
+ "position_z": "-347.376055350569",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR200C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "385.722114123949",
+ "position_z": "250.268024811577",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL086C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "374.89150308627",
+ "position_z": "265.249025922393",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR187W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "368.606862641606",
+ "position_z": "243.053663760706",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR384C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "395.017083991501",
+ "position_z": "235.115600044468",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL101W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "402.804195184452",
+ "position_z": "256.568288902218",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR247C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-126.325092490669",
+ "position_z": "-186.562171458973",
+ "scale": "2",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR186W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-129.945186531997",
+ "position_z": "-233.576577713221",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR148W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-168.524229517482",
+ "position_z": "-207.746072448705",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL075W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-162.674903721389",
+ "position_z": "-217.346250909296",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER082C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-167.004771343716",
+ "position_z": "-226.370612242234",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL132W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-29.3262085038274",
+ "position_z": "-123.097522084776",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL143W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-202.972648272419",
+ "position_z": "-200.464046318251",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR002W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-147.610113130514",
+ "position_z": "-241.532129658035",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL148C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-169.903202828885",
+ "position_z": "-243.920339783496",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL010W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-148.985371891024",
+ "position_z": "-224.515383916481",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR180W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "43.0494258612699",
+ "position_z": "-81.1768453821565",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLL011W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-194.1671361587",
+ "position_z": "-219.001361465085",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL060W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-230.877700282727",
+ "position_z": "-198.798202782916",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR449C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-187.702357175143",
+ "position_z": "-231.21080181605",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR081C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-183.113662823238",
+ "position_z": "-212.063248090605",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR093W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-195.694927420551",
+ "position_z": "-243.577874206477",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR056C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-129.385762024407",
+ "position_z": "-120.654755848359",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR144C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-135.665215910822",
+ "position_z": "-247.738742161138",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFL024C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "201.878571372777",
+ "position_z": "57.4068176012439",
+ "scale": "0.9375",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR023C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "245.630585596924",
+ "position_z": "90.3844308121131",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR244W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "221.101158526431",
+ "position_z": "92.0942299191713",
+ "scale": "1",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR099W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "239.315138093995",
+ "position_z": "82.2443510838499",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR359C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "255.003226980064",
+ "position_z": "80.3244130242799",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL107W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "244.545690780709",
+ "position_z": "72.8028244531897",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YEL018W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "263.974887347522",
+ "position_z": "62.4420418036791",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL077C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-191.426256078918",
+ "position_z": "12.698713336296",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL061W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-210.159389149379",
+ "position_z": "15.9763886395839",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL111W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-184.813509289849",
+ "position_z": "-70.3657816836028",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR206W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-182.610749098566",
+ "position_z": "-87.1580283911222",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML126C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "191.636486162031",
+ "position_z": "-31.6029366864379",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL125W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "253.649084258234",
+ "position_z": "-57.6511857147194",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL119W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "253.972802941707",
+ "position_z": "-42.4320869721882",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML130C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "27.2037893507339",
+ "position_z": "146.475358612828",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML067C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-2.52495649193838",
+ "position_z": "197.868983530648",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL042W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "16.0980417507301",
+ "position_z": "198.317966460706",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR208W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "101.01771626502",
+ "position_z": "52.9129134359814",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAR002C-A:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-4.31527304271191",
+ "position_z": "82.2338301680957",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL200C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-35.0382730747043",
+ "position_z": "104.428607731011",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YAL007C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-41.4889951094967",
+ "position_z": "64.1671529301557",
+ "scale": "1.0625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML012W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-45.6421566018076",
+ "position_z": "120.192296500465",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR132C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-21.2714063440288",
+ "position_z": "132.457486164459",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL211W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-128.090374067757",
+ "position_z": "76.6130304165289",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR087C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-120.064606214008",
+ "position_z": "84.6633637886439",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL081C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "268.432703341102",
+ "position_z": "123.942918925449",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML007W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "281.244776544141",
+ "position_z": "112.048407383855",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR102C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "173.077798259873",
+ "position_z": "367.442812654032",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR166C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "178.616417310334",
+ "position_z": "348.319514840207",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER008C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "184.418861125551",
+ "position_z": "384.605685735204",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR166C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "162.246282415323",
+ "position_z": "384.419324842596",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL068C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "150.445137280298",
+ "position_z": "368.224035605721",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR055W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "193.595460818642",
+ "position_z": "364.651888820043",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML097C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "156.827293804139",
+ "position_z": "351.911100128242",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR021W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-5.41959241853408",
+ "position_z": "-420.8",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR093C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "152.169323076366",
+ "position_z": "-203.125626828428",
+ "scale": "1.25",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR221C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "200.789576666089",
+ "position_z": "-246.293293442176",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML030W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "185.911632940928",
+ "position_z": "-251.377077529748",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL135C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "121.53292032286",
+ "position_z": "402.37159167021",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL023C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "105.016855965934",
+ "position_z": "412.427303692042",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR005W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "104.550444231529",
+ "position_z": "394.218027033282",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR311W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "139.873553620238",
+ "position_z": "405.021142069529",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR060W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "465.340392920713",
+ "position_z": "118.899570765209",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFL022C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "447.583922912903",
+ "position_z": "124.897944887183",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR207W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "47.0849222016537",
+ "position_z": "127.500691117318",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFL041W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "34.3646254490013",
+ "position_z": "179.94400778081",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YEL022W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "41.1232542766143",
+ "position_z": "171.011938701881",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER021W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "104.661772376872",
+ "position_z": "73.2367102810916",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR089C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-104.019726029837",
+ "position_z": "-145.793823276532",
+ "scale": "1.125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL005C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-121.983694112043",
+ "position_z": "-194.731739048898",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKL014C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-102.38250055973",
+ "position_z": "-197.362866684585",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR260W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "185.739711581128",
+ "position_z": "189.482258901755",
+ "scale": "1",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER025W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "220.40242304116",
+ "position_z": "216.570712377226",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR083C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "212.443626748671",
+ "position_z": "190.122659798827",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR211W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "205.498803131906",
+ "position_z": "205.660050279899",
+ "scale": "0.9375",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR291C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "205.788993258775",
+ "position_z": "196.203703541041",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR026C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "231.21546130834",
+ "position_z": "187.289061847784",
+ "scale": "1.0625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR044C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "110.334932023331",
+ "position_z": "123.823210037314",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR007W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "195.542500640621",
+ "position_z": "232.08660694126",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL237W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "213.025996245387",
+ "position_z": "222.995334615702",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR283C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-432.577074751325",
+ "position_z": "-151.088146548561",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL074C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-444.188997257181",
+ "position_z": "-177.113646739979",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR091W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-462.139782273836",
+ "position_z": "-163.434099324082",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR178W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-467.867629776396",
+ "position_z": "-146.64366649494",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR055C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-476.763212432065",
+ "position_z": "-159.993665260922",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR308C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-459.531881732385",
+ "position_z": "-176.49181018838",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR409C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-360.158226294062",
+ "position_z": "-198.260964958602",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFR009W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "303.698844901527",
+ "position_z": "130.347341722518",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL143W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "243.11427969283",
+ "position_z": "238.028211189524",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHL032C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "255.120821383712",
+ "position_z": "226.847175255716",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER136W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "237.538317223276",
+ "position_z": "337.966720989061",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFL038C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "231.47075805428",
+ "position_z": "322.557059932413",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER031C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "257.139252266506",
+ "position_z": "346.390380167257",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR014C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "215.732193077757",
+ "position_z": "338.386182259349",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YHR108W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "276.380482413638",
+ "position_z": "69.5467684607003",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER122C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "300.86859643043",
+ "position_z": "218.157899691233",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL142C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-68.5108771913492",
+ "position_z": "166.671227923313",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR163W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "47.7938826179235",
+ "position_z": "5.12438878729694",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR098C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "30.8732737128428",
+ "position_z": "46.4424686426828",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCR015C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "48.009834159637",
+ "position_z": "38.6720953506028",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR203W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "46.176896506524",
+ "position_z": "51.7295380075062",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL237C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "463.544191711004",
+ "position_z": "-129.785806161998",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR358W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "477.236877719758",
+ "position_z": "-121.626096640537",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL001W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "23.367731322402",
+ "position_z": "-29.2488990037443",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YEL056W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "84.7759382277566",
+ "position_z": "-4.60710350465621",
+ "scale": "0.625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLL022C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "92.3989117491033",
+ "position_z": "-12.8584843626379",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL033W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "61.1006701691251",
+ "position_z": "-167.060754676377",
+ "scale": "1.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL041C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "87.7622126755352",
+ "position_z": "-218.318391360634",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR310C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "95.528251526405",
+ "position_z": "-205.0997089567",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCL037C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "100.35503845727",
+ "position_z": "-216.171889903775",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR179C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-370.577170607619",
+ "position_z": "-83.4093161538954",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR295C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-371.089930680101",
+ "position_z": "-69.6275030350501",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR009C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "66.776459425831",
+ "position_z": "-53.421827295133",
+ "scale": "2.4375",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR054C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "101.829926239984",
+ "position_z": "-78.4310432706208",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR176W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "103.525921974173",
+ "position_z": "-91.8398024814579",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YEL046C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "82.1827462680438",
+ "position_z": "-94.8834159377921",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER065C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "132.554552801881",
+ "position_z": "-58.447862888221",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR021C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "125.498593759357",
+ "position_z": "-48.9390335965161",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR188W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "115.414501315257",
+ "position_z": "-98.6282562712555",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR132W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "108.132614054971",
+ "position_z": "-52.5342250163552",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR300C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "131.084481804629",
+ "position_z": "52.8985513422533",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGR170W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "137.233070228259",
+ "position_z": "-72.95999805583",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR110C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "84.5691565634277",
+ "position_z": "-82.5196816492338",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR303C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "109.737032634458",
+ "position_z": "-66.0178529926753",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL088C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "115.631612202651",
+ "position_z": "-81.1893268675893",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YER093C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "95.9937515084907",
+ "position_z": "-100.985648857682",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL058W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "131.670485405186",
+ "position_z": "-82.5071265801039",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJL222W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "99.3848782665413",
+ "position_z": "-37.9931548240228",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFR006W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "123.834431084809",
+ "position_z": "-67.0717306588604",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR239C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "125.661968136098",
+ "position_z": "-91.6268173315534",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL054W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "124.050674971065",
+ "position_z": "-25.8330199563401",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YCL030C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "116.921633889588",
+ "position_z": "20.3500430762278",
+ "scale": "0.6875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL160C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "169.874444342641",
+ "position_z": "30.6763504947121",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YML124C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "199.45231073734",
+ "position_z": "31.7611090656494",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL123W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-324.341331947369",
+ "position_z": "-242.635545682729",
+ "scale": "0.75",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR064W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-350.87434265279",
+ "position_z": "-270.895929914181",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR282C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-372.67885732223",
+ "position_z": "-248.747820172515",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR023W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-366.406255901923",
+ "position_z": "-267.044390164373",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR350C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-361.311559959717",
+ "position_z": "-255.945753651571",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL133W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-305.028036132268",
+ "position_z": "357.295045604175",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR319W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-318.01620598109",
+ "position_z": "347.198606359972",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR272C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "142.349423478533",
+ "position_z": "3.87141058428864",
+ "scale": "1.3125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YFR052W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "202.628018487314",
+ "position_z": "16.4901809222053",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL097C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "206.23056647345",
+ "position_z": "-16.895211875719",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR261C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "203.58784771197",
+ "position_z": "-0.141529257966738",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL007W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "217.834056638973",
+ "position_z": "3.78384073011324",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR394W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "213.248842746657",
+ "position_z": "-8.71152970490232",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOR117W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "235.975480800534",
+ "position_z": "-18.9604754001776",
+ "scale": "0.5625",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL068C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "39.0518518200775",
+ "position_z": "262.985812618055",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBL002W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-46.0848645495768",
+ "position_z": "-221.267047134229",
+ "scale": "1.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YGL241W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-44.0120238379974",
+ "position_z": "-275.489836575834",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL082C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-30.2141179016683",
+ "position_z": "-263.222689128702",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YKR048C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-59.0507497851486",
+ "position_z": "-284.073470702771",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR091C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-20.3806029348485",
+ "position_z": "-274.169825063084",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPR190C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-43.0944885919405",
+ "position_z": "-267.547653474334",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL126W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-16.6989881577908",
+ "position_z": "-262.460704081029",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YOL006C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-45.542270719974",
+ "position_z": "-286.304218583436",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR107C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "422.952518696504",
+ "position_z": "187.333618335161",
+ "scale": "0.875",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR211C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "405.643855513836",
+ "position_z": "203.715118429391",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR254W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "401.899690553325",
+ "position_z": "188.491723415212",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL018W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "442.416522427112",
+ "position_z": "176.357981033269",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR318W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "407.537615524726",
+ "position_z": "171.652920323622",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YJR135C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "442.462022345794",
+ "position_z": "195.579733342017",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDR383C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "425.12825177749",
+ "position_z": "163.824842289395",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR381W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "425.259446558365",
+ "position_z": "208.768842216516",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YBR011C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-313.337972257653",
+ "position_z": "-88.7524733221626",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YMR267W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-359.066097457785",
+ "position_z": "-126.213308421174",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YLR425W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-372.141337232341",
+ "position_z": "-115.26062815842",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YDL203C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-359.913259308576",
+ "position_z": "-114.398968199183",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YPL066W:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "-369.528393194495",
+ "position_z": "-102.474559822962",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YIL026C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "130.39282566131",
+ "position_z": "82.3047253914209",
+ "scale": "0.5",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ },
+ {
+ "name": "YNL265C:2",
+ "layer": "Layer6",
+ "position_x": "0",
+ "position_y": "187.516586793731",
+ "position_z": "154.380973525853",
+ "scale": "0.8125",
+ "color": "#648177",
+ "url": "",
+ "descr": ""
+ }
+ ],
+ "edges": [
+ {
+ "src": "2_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "3_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "4_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "5_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "6_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "7_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "8_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "9_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "10_Layer1",
+ "trg": "3_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "11_Layer1",
+ "trg": "5_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "12_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "13_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "14_Layer1",
+ "trg": "4_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "16_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "17_Layer1",
+ "trg": "3_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "18_Layer1",
+ "trg": "17_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "19_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "20_Layer1",
+ "trg": "3_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "21_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "22_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "23_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "24_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "25_Layer1",
+ "trg": "17_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "26_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "27_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "28_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "30_Layer1",
+ "trg": "15_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "31_Layer1",
+ "trg": "10_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "32_Layer1",
+ "trg": "3_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "33_Layer1",
+ "trg": "23_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "34_Layer1",
+ "trg": "13_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "35_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "36_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "37_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "38_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "39_Layer1",
+ "trg": "31_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "40_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "41_Layer1",
+ "trg": "13_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "42_Layer1",
+ "trg": "15_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "44_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "45_Layer1",
+ "trg": "10_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "46_Layer1",
+ "trg": "16_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "47_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "48_Layer1",
+ "trg": "10_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "49_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "50_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "51_Layer1",
+ "trg": "17_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "52_Layer1",
+ "trg": "22_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "53_Layer1",
+ "trg": "10_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "54_Layer1",
+ "trg": "21_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "55_Layer1",
+ "trg": "17_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "56_Layer1",
+ "trg": "43_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "58_Layer1",
+ "trg": "30_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "59_Layer1",
+ "trg": "21_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "60_Layer1",
+ "trg": "14_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "61_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "62_Layer1",
+ "trg": "21_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "63_Layer1",
+ "trg": "56_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "64_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "65_Layer1",
+ "trg": "14_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "66_Layer1",
+ "trg": "17_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "67_Layer1",
+ "trg": "17_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "68_Layer1",
+ "trg": "33_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "69_Layer1",
+ "trg": "41_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "70_Layer1",
+ "trg": "45_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "72_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "73_Layer1",
+ "trg": "17_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "74_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "75_Layer1",
+ "trg": "46_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "76_Layer1",
+ "trg": "33_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "77_Layer1",
+ "trg": "58_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "78_Layer1",
+ "trg": "56_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "79_Layer1",
+ "trg": "17_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "80_Layer1",
+ "trg": "56_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "81_Layer1",
+ "trg": "2_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "82_Layer1",
+ "trg": "68_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "83_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "84_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "86_Layer1",
+ "trg": "56_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "87_Layer1",
+ "trg": "83_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "88_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "89_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "90_Layer1",
+ "trg": "56_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "91_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "92_Layer1",
+ "trg": "1_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "93_Layer1",
+ "trg": "39_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "94_Layer1",
+ "trg": "31_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "95_Layer1",
+ "trg": "3_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "96_Layer1",
+ "trg": "75_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "97_Layer1",
+ "trg": "23_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "98_Layer1",
+ "trg": "15_Layer1",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "3_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "4_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "5_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "6_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "7_Layer2",
+ "trg": "3_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "8_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "9_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "10_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "11_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "12_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "13_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "15_Layer2",
+ "trg": "13_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "16_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "17_Layer2",
+ "trg": "13_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "18_Layer2",
+ "trg": "7_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "19_Layer2",
+ "trg": "13_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "20_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "21_Layer2",
+ "trg": "14_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "22_Layer2",
+ "trg": "16_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "23_Layer2",
+ "trg": "8_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "24_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "25_Layer2",
+ "trg": "22_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "26_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "28_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "29_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "30_Layer2",
+ "trg": "25_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "31_Layer2",
+ "trg": "3_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "32_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "33_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "34_Layer2",
+ "trg": "29_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "35_Layer2",
+ "trg": "25_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "36_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "37_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "38_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "39_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "41_Layer2",
+ "trg": "3_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "42_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "43_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "44_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "45_Layer2",
+ "trg": "42_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "46_Layer2",
+ "trg": "43_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "47_Layer2",
+ "trg": "14_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "48_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "49_Layer2",
+ "trg": "14_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "50_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "51_Layer2",
+ "trg": "34_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "52_Layer2",
+ "trg": "47_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "54_Layer2",
+ "trg": "29_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "55_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "56_Layer2",
+ "trg": "13_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "57_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "58_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "59_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "60_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "61_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "62_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "63_Layer2",
+ "trg": "35_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "64_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "65_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "67_Layer2",
+ "trg": "29_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "68_Layer2",
+ "trg": "12_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "69_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "70_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "71_Layer2",
+ "trg": "29_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "72_Layer2",
+ "trg": "4_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "73_Layer2",
+ "trg": "13_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "74_Layer2",
+ "trg": "46_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "75_Layer2",
+ "trg": "64_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "76_Layer2",
+ "trg": "74_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "77_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "78_Layer2",
+ "trg": "69_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "80_Layer2",
+ "trg": "14_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "81_Layer2",
+ "trg": "3_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "82_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "83_Layer2",
+ "trg": "62_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "84_Layer2",
+ "trg": "25_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "85_Layer2",
+ "trg": "25_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "86_Layer2",
+ "trg": "46_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "87_Layer2",
+ "trg": "46_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "88_Layer2",
+ "trg": "78_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "89_Layer2",
+ "trg": "86_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "90_Layer2",
+ "trg": "4_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "91_Layer2",
+ "trg": "4_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "93_Layer2",
+ "trg": "86_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "94_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "95_Layer2",
+ "trg": "62_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "96_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "97_Layer2",
+ "trg": "4_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "98_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "99_Layer2",
+ "trg": "37_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "100_Layer2",
+ "trg": "20_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "101_Layer2",
+ "trg": "96_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "102_Layer2",
+ "trg": "29_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "103_Layer2",
+ "trg": "32_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "104_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "106_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "107_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "108_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "109_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "110_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "111_Layer2",
+ "trg": "25_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "112_Layer2",
+ "trg": "14_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "113_Layer2",
+ "trg": "13_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "114_Layer2",
+ "trg": "73_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "115_Layer2",
+ "trg": "35_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "116_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "117_Layer2",
+ "trg": "37_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "119_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "120_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "121_Layer2",
+ "trg": "75_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "122_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "123_Layer2",
+ "trg": "104_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "124_Layer2",
+ "trg": "58_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "125_Layer2",
+ "trg": "13_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "126_Layer2",
+ "trg": "14_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "127_Layer2",
+ "trg": "56_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "128_Layer2",
+ "trg": "116_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "129_Layer2",
+ "trg": "39_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "130_Layer2",
+ "trg": "73_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "132_Layer2",
+ "trg": "112_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "133_Layer2",
+ "trg": "58_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "134_Layer2",
+ "trg": "14_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "135_Layer2",
+ "trg": "4_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "136_Layer2",
+ "trg": "67_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "137_Layer2",
+ "trg": "110_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "138_Layer2",
+ "trg": "67_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "139_Layer2",
+ "trg": "12_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "140_Layer2",
+ "trg": "35_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "141_Layer2",
+ "trg": "15_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "142_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "143_Layer2",
+ "trg": "37_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "145_Layer2",
+ "trg": "25_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "146_Layer2",
+ "trg": "46_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "147_Layer2",
+ "trg": "20_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "148_Layer2",
+ "trg": "39_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "149_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "150_Layer2",
+ "trg": "23_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "151_Layer2",
+ "trg": "78_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "152_Layer2",
+ "trg": "85_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "153_Layer2",
+ "trg": "51_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "154_Layer2",
+ "trg": "43_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "155_Layer2",
+ "trg": "72_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "156_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "158_Layer2",
+ "trg": "71_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "159_Layer2",
+ "trg": "156_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "160_Layer2",
+ "trg": "43_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "161_Layer2",
+ "trg": "156_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "162_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "163_Layer2",
+ "trg": "132_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "164_Layer2",
+ "trg": "132_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "165_Layer2",
+ "trg": "155_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "166_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "167_Layer2",
+ "trg": "96_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "168_Layer2",
+ "trg": "25_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "169_Layer2",
+ "trg": "83_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "171_Layer2",
+ "trg": "74_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "172_Layer2",
+ "trg": "12_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "173_Layer2",
+ "trg": "78_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "174_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "175_Layer2",
+ "trg": "145_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "176_Layer2",
+ "trg": "83_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "177_Layer2",
+ "trg": "29_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "178_Layer2",
+ "trg": "135_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "179_Layer2",
+ "trg": "110_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "180_Layer2",
+ "trg": "26_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "181_Layer2",
+ "trg": "20_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "182_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "184_Layer2",
+ "trg": "62_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "185_Layer2",
+ "trg": "62_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "186_Layer2",
+ "trg": "132_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "187_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "188_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "189_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "190_Layer2",
+ "trg": "37_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "191_Layer2",
+ "trg": "13_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "192_Layer2",
+ "trg": "118_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "193_Layer2",
+ "trg": "34_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "194_Layer2",
+ "trg": "186_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "195_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "197_Layer2",
+ "trg": "74_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "198_Layer2",
+ "trg": "58_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "199_Layer2",
+ "trg": "39_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "200_Layer2",
+ "trg": "86_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "201_Layer2",
+ "trg": "4_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "202_Layer2",
+ "trg": "66_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "203_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "204_Layer2",
+ "trg": "110_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "205_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "206_Layer2",
+ "trg": "135_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "207_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "208_Layer2",
+ "trg": "3_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "210_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "211_Layer2",
+ "trg": "157_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "212_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "213_Layer2",
+ "trg": "163_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "214_Layer2",
+ "trg": "49_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "215_Layer2",
+ "trg": "35_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "216_Layer2",
+ "trg": "136_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "217_Layer2",
+ "trg": "14_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "218_Layer2",
+ "trg": "149_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "219_Layer2",
+ "trg": "192_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "220_Layer2",
+ "trg": "198_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "221_Layer2",
+ "trg": "59_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "223_Layer2",
+ "trg": "22_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "224_Layer2",
+ "trg": "80_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "225_Layer2",
+ "trg": "20_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "226_Layer2",
+ "trg": "175_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "227_Layer2",
+ "trg": "169_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "228_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "229_Layer2",
+ "trg": "48_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "230_Layer2",
+ "trg": "73_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "231_Layer2",
+ "trg": "35_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "232_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "233_Layer2",
+ "trg": "110_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "234_Layer2",
+ "trg": "41_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "236_Layer2",
+ "trg": "223_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "237_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "238_Layer2",
+ "trg": "35_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "239_Layer2",
+ "trg": "71_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "240_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "241_Layer2",
+ "trg": "234_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "242_Layer2",
+ "trg": "83_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "243_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "244_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "245_Layer2",
+ "trg": "223_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "246_Layer2",
+ "trg": "222_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "247_Layer2",
+ "trg": "76_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "249_Layer2",
+ "trg": "112_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "250_Layer2",
+ "trg": "159_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "251_Layer2",
+ "trg": "3_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "252_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "253_Layer2",
+ "trg": "34_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "254_Layer2",
+ "trg": "96_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "255_Layer2",
+ "trg": "117_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "256_Layer2",
+ "trg": "16_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "257_Layer2",
+ "trg": "208_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "258_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "259_Layer2",
+ "trg": "25_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "260_Layer2",
+ "trg": "40_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "262_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "263_Layer2",
+ "trg": "12_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "264_Layer2",
+ "trg": "108_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "265_Layer2",
+ "trg": "70_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "266_Layer2",
+ "trg": "108_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "267_Layer2",
+ "trg": "188_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "268_Layer2",
+ "trg": "16_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "269_Layer2",
+ "trg": "8_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "270_Layer2",
+ "trg": "71_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "271_Layer2",
+ "trg": "228_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "272_Layer2",
+ "trg": "157_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "273_Layer2",
+ "trg": "98_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "275_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "276_Layer2",
+ "trg": "13_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "277_Layer2",
+ "trg": "85_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "278_Layer2",
+ "trg": "49_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "279_Layer2",
+ "trg": "88_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "280_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "281_Layer2",
+ "trg": "133_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "282_Layer2",
+ "trg": "14_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "283_Layer2",
+ "trg": "14_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "284_Layer2",
+ "trg": "46_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "285_Layer2",
+ "trg": "196_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "286_Layer2",
+ "trg": "171_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "288_Layer2",
+ "trg": "268_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "289_Layer2",
+ "trg": "5_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "290_Layer2",
+ "trg": "155_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "291_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "292_Layer2",
+ "trg": "260_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "293_Layer2",
+ "trg": "55_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "294_Layer2",
+ "trg": "1_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "295_Layer2",
+ "trg": "62_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "296_Layer2",
+ "trg": "129_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "297_Layer2",
+ "trg": "97_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "298_Layer2",
+ "trg": "163_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "299_Layer2",
+ "trg": "50_Layer2",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "1_Layer3",
+ "trg": "2_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer3",
+ "trg": "3_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "3_Layer3",
+ "trg": "4_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "4_Layer3",
+ "trg": "5_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "5_Layer3",
+ "trg": "6_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "6_Layer3",
+ "trg": "7_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "7_Layer3",
+ "trg": "8_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "9_Layer3",
+ "trg": "24_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "9_Layer3",
+ "trg": "10_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "10_Layer3",
+ "trg": "11_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "11_Layer3",
+ "trg": "12_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "12_Layer3",
+ "trg": "13_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "14_Layer3",
+ "trg": "15_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "15_Layer3",
+ "trg": "16_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "16_Layer3",
+ "trg": "17_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "17_Layer3",
+ "trg": "18_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "18_Layer3",
+ "trg": "19_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "19_Layer3",
+ "trg": "20_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "20_Layer3",
+ "trg": "21_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "22_Layer3",
+ "trg": "125_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "22_Layer3",
+ "trg": "23_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "23_Layer3",
+ "trg": "24_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "24_Layer3",
+ "trg": "25_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "25_Layer3",
+ "trg": "26_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "27_Layer3",
+ "trg": "28_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "28_Layer3",
+ "trg": "29_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "29_Layer3",
+ "trg": "30_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "30_Layer3",
+ "trg": "31_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "31_Layer3",
+ "trg": "32_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "32_Layer3",
+ "trg": "33_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "33_Layer3",
+ "trg": "34_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "34_Layer3",
+ "trg": "35_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "35_Layer3",
+ "trg": "36_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "36_Layer3",
+ "trg": "37_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "37_Layer3",
+ "trg": "38_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "38_Layer3",
+ "trg": "39_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "40_Layer3",
+ "trg": "41_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "41_Layer3",
+ "trg": "42_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "42_Layer3",
+ "trg": "43_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "43_Layer3",
+ "trg": "44_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "44_Layer3",
+ "trg": "45_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "45_Layer3",
+ "trg": "46_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "46_Layer3",
+ "trg": "47_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "47_Layer3",
+ "trg": "48_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "48_Layer3",
+ "trg": "49_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "49_Layer3",
+ "trg": "50_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "50_Layer3",
+ "trg": "51_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "52_Layer3",
+ "trg": "137_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "53_Layer3",
+ "trg": "54_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "54_Layer3",
+ "trg": "55_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "55_Layer3",
+ "trg": "56_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "56_Layer3",
+ "trg": "57_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "57_Layer3",
+ "trg": "58_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "58_Layer3",
+ "trg": "59_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "59_Layer3",
+ "trg": "60_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "60_Layer3",
+ "trg": "61_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "61_Layer3",
+ "trg": "62_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "62_Layer3",
+ "trg": "63_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "63_Layer3",
+ "trg": "64_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "64_Layer3",
+ "trg": "65_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "66_Layer3",
+ "trg": "67_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "6_Layer3",
+ "trg": "68_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "68_Layer3",
+ "trg": "69_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "69_Layer3",
+ "trg": "70_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "70_Layer3",
+ "trg": "71_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "71_Layer3",
+ "trg": "72_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "72_Layer3",
+ "trg": "73_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "61_Layer3",
+ "trg": "73_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "74_Layer3",
+ "trg": "75_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer3",
+ "trg": "75_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "76_Layer3",
+ "trg": "77_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "77_Layer3",
+ "trg": "78_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "79_Layer3",
+ "trg": "80_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "80_Layer3",
+ "trg": "81_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "81_Layer3",
+ "trg": "82_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "82_Layer3",
+ "trg": "83_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "83_Layer3",
+ "trg": "84_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "84_Layer3",
+ "trg": "85_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "85_Layer3",
+ "trg": "86_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "86_Layer3",
+ "trg": "87_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "87_Layer3",
+ "trg": "88_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "88_Layer3",
+ "trg": "89_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "89_Layer3",
+ "trg": "90_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "43_Layer3",
+ "trg": "90_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "92_Layer3",
+ "trg": "93_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "93_Layer3",
+ "trg": "94_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "94_Layer3",
+ "trg": "95_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "95_Layer3",
+ "trg": "96_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "96_Layer3",
+ "trg": "97_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "97_Layer3",
+ "trg": "98_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "98_Layer3",
+ "trg": "99_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "99_Layer3",
+ "trg": "100_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "100_Layer3",
+ "trg": "101_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "101_Layer3",
+ "trg": "102_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "102_Layer3",
+ "trg": "103_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "103_Layer3",
+ "trg": "104_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "105_Layer3",
+ "trg": "106_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "106_Layer3",
+ "trg": "107_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "107_Layer3",
+ "trg": "108_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "36_Layer3",
+ "trg": "109_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "109_Layer3",
+ "trg": "110_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "110_Layer3",
+ "trg": "111_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "111_Layer3",
+ "trg": "112_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "112_Layer3",
+ "trg": "113_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "113_Layer3",
+ "trg": "114_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "114_Layer3",
+ "trg": "115_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "115_Layer3",
+ "trg": "116_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "116_Layer3",
+ "trg": "117_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "118_Layer3",
+ "trg": "119_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "119_Layer3",
+ "trg": "120_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "120_Layer3",
+ "trg": "121_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "121_Layer3",
+ "trg": "122_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "122_Layer3",
+ "trg": "123_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "123_Layer3",
+ "trg": "124_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "124_Layer3",
+ "trg": "125_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "125_Layer3",
+ "trg": "126_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "126_Layer3",
+ "trg": "127_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "127_Layer3",
+ "trg": "128_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "128_Layer3",
+ "trg": "129_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "129_Layer3",
+ "trg": "130_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "131_Layer3",
+ "trg": "132_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "132_Layer3",
+ "trg": "133_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "133_Layer3",
+ "trg": "134_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "134_Layer3",
+ "trg": "135_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "135_Layer3",
+ "trg": "136_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "136_Layer3",
+ "trg": "137_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "137_Layer3",
+ "trg": "138_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "138_Layer3",
+ "trg": "139_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "139_Layer3",
+ "trg": "140_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "140_Layer3",
+ "trg": "141_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "141_Layer3",
+ "trg": "142_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "142_Layer3",
+ "trg": "143_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "144_Layer3",
+ "trg": "145_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "145_Layer3",
+ "trg": "146_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "146_Layer3",
+ "trg": "147_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "147_Layer3",
+ "trg": "148_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "148_Layer3",
+ "trg": "149_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "149_Layer3",
+ "trg": "150_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "1_Layer3",
+ "trg": "150_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "1_Layer3",
+ "trg": "3_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "1_Layer3",
+ "trg": "149_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "1_Layer3",
+ "trg": "96_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "1_Layer3",
+ "trg": "148_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "1_Layer3",
+ "trg": "5_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer3",
+ "trg": "150_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer3",
+ "trg": "4_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer3",
+ "trg": "149_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer3",
+ "trg": "5_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer3",
+ "trg": "148_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer3",
+ "trg": "6_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "3_Layer3",
+ "trg": "5_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "3_Layer3",
+ "trg": "150_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "3_Layer3",
+ "trg": "6_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "3_Layer3",
+ "trg": "149_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "3_Layer3",
+ "trg": "7_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "4_Layer3",
+ "trg": "6_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "4_Layer3",
+ "trg": "150_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "4_Layer3",
+ "trg": "8_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "5_Layer3",
+ "trg": "7_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "5_Layer3",
+ "trg": "8_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "5_Layer3",
+ "trg": "9_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "6_Layer3",
+ "trg": "31_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "6_Layer3",
+ "trg": "9_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "6_Layer3",
+ "trg": "10_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "7_Layer3",
+ "trg": "9_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "7_Layer3",
+ "trg": "10_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "7_Layer3",
+ "trg": "11_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "8_Layer3",
+ "trg": "42_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "8_Layer3",
+ "trg": "12_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "9_Layer3",
+ "trg": "11_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "9_Layer3",
+ "trg": "12_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "9_Layer3",
+ "trg": "13_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "10_Layer3",
+ "trg": "12_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "10_Layer3",
+ "trg": "13_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "10_Layer3",
+ "trg": "14_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "11_Layer3",
+ "trg": "13_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "11_Layer3",
+ "trg": "14_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "11_Layer3",
+ "trg": "15_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "12_Layer3",
+ "trg": "14_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "12_Layer3",
+ "trg": "15_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "13_Layer3",
+ "trg": "15_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "13_Layer3",
+ "trg": "16_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "13_Layer3",
+ "trg": "17_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "14_Layer3",
+ "trg": "16_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "14_Layer3",
+ "trg": "17_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "14_Layer3",
+ "trg": "18_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "15_Layer3",
+ "trg": "17_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "15_Layer3",
+ "trg": "18_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "15_Layer3",
+ "trg": "19_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "16_Layer3",
+ "trg": "18_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "16_Layer3",
+ "trg": "19_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "16_Layer3",
+ "trg": "20_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "17_Layer3",
+ "trg": "20_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "17_Layer3",
+ "trg": "21_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "18_Layer3",
+ "trg": "20_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "21_Layer3",
+ "trg": "107_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "18_Layer3",
+ "trg": "22_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "19_Layer3",
+ "trg": "21_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "19_Layer3",
+ "trg": "22_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "19_Layer3",
+ "trg": "23_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "20_Layer3",
+ "trg": "22_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "20_Layer3",
+ "trg": "23_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "20_Layer3",
+ "trg": "24_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "21_Layer3",
+ "trg": "23_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "21_Layer3",
+ "trg": "25_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "22_Layer3",
+ "trg": "24_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "22_Layer3",
+ "trg": "25_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "22_Layer3",
+ "trg": "26_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "23_Layer3",
+ "trg": "25_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "23_Layer3",
+ "trg": "26_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "23_Layer3",
+ "trg": "27_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "24_Layer3",
+ "trg": "26_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "24_Layer3",
+ "trg": "27_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "24_Layer3",
+ "trg": "28_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "25_Layer3",
+ "trg": "27_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "25_Layer3",
+ "trg": "28_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "26_Layer3",
+ "trg": "28_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "26_Layer3",
+ "trg": "29_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "26_Layer3",
+ "trg": "30_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "27_Layer3",
+ "trg": "29_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "27_Layer3",
+ "trg": "30_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "27_Layer3",
+ "trg": "31_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "28_Layer3",
+ "trg": "30_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "28_Layer3",
+ "trg": "31_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "28_Layer3",
+ "trg": "123_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "29_Layer3",
+ "trg": "31_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "29_Layer3",
+ "trg": "32_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "29_Layer3",
+ "trg": "33_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "30_Layer3",
+ "trg": "33_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "30_Layer3",
+ "trg": "34_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "31_Layer3",
+ "trg": "33_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "31_Layer3",
+ "trg": "34_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "31_Layer3",
+ "trg": "35_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "32_Layer3",
+ "trg": "34_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "32_Layer3",
+ "trg": "35_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer3",
+ "trg": "32_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "33_Layer3",
+ "trg": "35_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "33_Layer3",
+ "trg": "36_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "33_Layer3",
+ "trg": "37_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "34_Layer3",
+ "trg": "36_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "34_Layer3",
+ "trg": "38_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "35_Layer3",
+ "trg": "37_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "35_Layer3",
+ "trg": "38_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "35_Layer3",
+ "trg": "39_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "36_Layer3",
+ "trg": "38_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "36_Layer3",
+ "trg": "39_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "36_Layer3",
+ "trg": "54_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "37_Layer3",
+ "trg": "39_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "37_Layer3",
+ "trg": "40_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "41_Layer3",
+ "trg": "53_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "38_Layer3",
+ "trg": "40_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "38_Layer3",
+ "trg": "41_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "39_Layer3",
+ "trg": "41_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "39_Layer3",
+ "trg": "42_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "39_Layer3",
+ "trg": "43_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "10_Layer3",
+ "trg": "42_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "40_Layer3",
+ "trg": "43_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "40_Layer3",
+ "trg": "44_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "41_Layer3",
+ "trg": "43_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "41_Layer3",
+ "trg": "44_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "41_Layer3",
+ "trg": "45_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "42_Layer3",
+ "trg": "54_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "42_Layer3",
+ "trg": "45_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "42_Layer3",
+ "trg": "46_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "43_Layer3",
+ "trg": "46_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "43_Layer3",
+ "trg": "47_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "44_Layer3",
+ "trg": "46_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "44_Layer3",
+ "trg": "47_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "44_Layer3",
+ "trg": "48_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "45_Layer3",
+ "trg": "47_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "45_Layer3",
+ "trg": "48_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "45_Layer3",
+ "trg": "49_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "46_Layer3",
+ "trg": "48_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "46_Layer3",
+ "trg": "49_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "46_Layer3",
+ "trg": "50_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "47_Layer3",
+ "trg": "49_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "47_Layer3",
+ "trg": "51_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "48_Layer3",
+ "trg": "50_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "48_Layer3",
+ "trg": "51_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "48_Layer3",
+ "trg": "52_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "49_Layer3",
+ "trg": "51_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "49_Layer3",
+ "trg": "52_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "49_Layer3",
+ "trg": "129_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "50_Layer3",
+ "trg": "52_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "50_Layer3",
+ "trg": "53_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "50_Layer3",
+ "trg": "54_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "51_Layer3",
+ "trg": "53_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "51_Layer3",
+ "trg": "54_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "52_Layer3",
+ "trg": "54_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "52_Layer3",
+ "trg": "55_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "52_Layer3",
+ "trg": "56_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "53_Layer3",
+ "trg": "55_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "53_Layer3",
+ "trg": "56_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "53_Layer3",
+ "trg": "57_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "54_Layer3",
+ "trg": "56_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "54_Layer3",
+ "trg": "57_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "54_Layer3",
+ "trg": "58_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "55_Layer3",
+ "trg": "57_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "55_Layer3",
+ "trg": "58_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "55_Layer3",
+ "trg": "117_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "56_Layer3",
+ "trg": "59_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "56_Layer3",
+ "trg": "80_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "57_Layer3",
+ "trg": "59_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "57_Layer3",
+ "trg": "60_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "57_Layer3",
+ "trg": "61_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "58_Layer3",
+ "trg": "60_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "58_Layer3",
+ "trg": "61_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "58_Layer3",
+ "trg": "62_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "59_Layer3",
+ "trg": "61_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "59_Layer3",
+ "trg": "62_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "59_Layer3",
+ "trg": "63_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "60_Layer3",
+ "trg": "62_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "60_Layer3",
+ "trg": "64_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "61_Layer3",
+ "trg": "63_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "61_Layer3",
+ "trg": "64_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "61_Layer3",
+ "trg": "65_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "62_Layer3",
+ "trg": "83_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "62_Layer3",
+ "trg": "65_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "62_Layer3",
+ "trg": "66_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "63_Layer3",
+ "trg": "65_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "63_Layer3",
+ "trg": "66_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "63_Layer3",
+ "trg": "67_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "64_Layer3",
+ "trg": "66_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "64_Layer3",
+ "trg": "67_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "65_Layer3",
+ "trg": "67_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "65_Layer3",
+ "trg": "68_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "65_Layer3",
+ "trg": "69_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "66_Layer3",
+ "trg": "68_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "66_Layer3",
+ "trg": "69_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "66_Layer3",
+ "trg": "70_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "67_Layer3",
+ "trg": "69_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "67_Layer3",
+ "trg": "70_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "67_Layer3",
+ "trg": "71_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "68_Layer3",
+ "trg": "70_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "68_Layer3",
+ "trg": "71_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "68_Layer3",
+ "trg": "72_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "69_Layer3",
+ "trg": "72_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "69_Layer3",
+ "trg": "73_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "70_Layer3",
+ "trg": "72_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "70_Layer3",
+ "trg": "73_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "70_Layer3",
+ "trg": "74_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "71_Layer3",
+ "trg": "73_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "71_Layer3",
+ "trg": "74_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "14_Layer3",
+ "trg": "75_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "72_Layer3",
+ "trg": "74_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "72_Layer3",
+ "trg": "75_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "72_Layer3",
+ "trg": "76_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "73_Layer3",
+ "trg": "75_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "73_Layer3",
+ "trg": "77_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "74_Layer3",
+ "trg": "76_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "74_Layer3",
+ "trg": "77_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "74_Layer3",
+ "trg": "78_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "75_Layer3",
+ "trg": "77_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "75_Layer3",
+ "trg": "78_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "75_Layer3",
+ "trg": "79_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "76_Layer3",
+ "trg": "78_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "76_Layer3",
+ "trg": "79_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "76_Layer3",
+ "trg": "80_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "77_Layer3",
+ "trg": "79_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "77_Layer3",
+ "trg": "80_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "78_Layer3",
+ "trg": "80_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "78_Layer3",
+ "trg": "81_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "78_Layer3",
+ "trg": "82_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "79_Layer3",
+ "trg": "81_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "79_Layer3",
+ "trg": "82_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "79_Layer3",
+ "trg": "83_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "80_Layer3",
+ "trg": "82_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "80_Layer3",
+ "trg": "83_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "80_Layer3",
+ "trg": "111_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "81_Layer3",
+ "trg": "83_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "81_Layer3",
+ "trg": "84_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "81_Layer3",
+ "trg": "85_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "82_Layer3",
+ "trg": "85_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "82_Layer3",
+ "trg": "86_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "85_Layer3",
+ "trg": "131_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "83_Layer3",
+ "trg": "86_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "83_Layer3",
+ "trg": "87_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "84_Layer3",
+ "trg": "86_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "84_Layer3",
+ "trg": "87_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "84_Layer3",
+ "trg": "88_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "85_Layer3",
+ "trg": "87_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "85_Layer3",
+ "trg": "88_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "85_Layer3",
+ "trg": "89_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "86_Layer3",
+ "trg": "88_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "86_Layer3",
+ "trg": "90_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "87_Layer3",
+ "trg": "89_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "87_Layer3",
+ "trg": "90_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "87_Layer3",
+ "trg": "91_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "88_Layer3",
+ "trg": "90_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "88_Layer3",
+ "trg": "91_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "88_Layer3",
+ "trg": "92_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "89_Layer3",
+ "trg": "91_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "89_Layer3",
+ "trg": "92_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "89_Layer3",
+ "trg": "93_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "90_Layer3",
+ "trg": "92_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "90_Layer3",
+ "trg": "93_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "91_Layer3",
+ "trg": "93_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "91_Layer3",
+ "trg": "94_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "91_Layer3",
+ "trg": "95_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "92_Layer3",
+ "trg": "94_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "92_Layer3",
+ "trg": "95_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "92_Layer3",
+ "trg": "96_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "93_Layer3",
+ "trg": "95_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "93_Layer3",
+ "trg": "96_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "93_Layer3",
+ "trg": "97_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "94_Layer3",
+ "trg": "96_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "94_Layer3",
+ "trg": "97_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "94_Layer3",
+ "trg": "98_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "95_Layer3",
+ "trg": "98_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "12_Layer3",
+ "trg": "99_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "96_Layer3",
+ "trg": "98_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "96_Layer3",
+ "trg": "99_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "96_Layer3",
+ "trg": "100_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "97_Layer3",
+ "trg": "99_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "97_Layer3",
+ "trg": "100_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "97_Layer3",
+ "trg": "101_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "98_Layer3",
+ "trg": "100_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "98_Layer3",
+ "trg": "101_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "98_Layer3",
+ "trg": "102_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "99_Layer3",
+ "trg": "101_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "99_Layer3",
+ "trg": "103_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "100_Layer3",
+ "trg": "102_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "100_Layer3",
+ "trg": "103_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "100_Layer3",
+ "trg": "104_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "101_Layer3",
+ "trg": "103_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "101_Layer3",
+ "trg": "104_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "101_Layer3",
+ "trg": "105_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "102_Layer3",
+ "trg": "104_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "102_Layer3",
+ "trg": "105_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "102_Layer3",
+ "trg": "106_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "103_Layer3",
+ "trg": "105_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "103_Layer3",
+ "trg": "106_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "104_Layer3",
+ "trg": "106_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "104_Layer3",
+ "trg": "107_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "104_Layer3",
+ "trg": "108_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "105_Layer3",
+ "trg": "107_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "105_Layer3",
+ "trg": "108_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "105_Layer3",
+ "trg": "109_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "52_Layer3",
+ "trg": "108_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "106_Layer3",
+ "trg": "109_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "106_Layer3",
+ "trg": "110_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "107_Layer3",
+ "trg": "109_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "107_Layer3",
+ "trg": "110_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "107_Layer3",
+ "trg": "111_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "108_Layer3",
+ "trg": "111_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "108_Layer3",
+ "trg": "112_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "109_Layer3",
+ "trg": "111_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "109_Layer3",
+ "trg": "112_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "109_Layer3",
+ "trg": "113_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "110_Layer3",
+ "trg": "112_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "110_Layer3",
+ "trg": "113_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "110_Layer3",
+ "trg": "114_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "111_Layer3",
+ "trg": "113_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "111_Layer3",
+ "trg": "114_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "111_Layer3",
+ "trg": "115_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "112_Layer3",
+ "trg": "114_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "112_Layer3",
+ "trg": "116_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "113_Layer3",
+ "trg": "115_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "113_Layer3",
+ "trg": "116_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "113_Layer3",
+ "trg": "117_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "114_Layer3",
+ "trg": "116_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "114_Layer3",
+ "trg": "117_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "114_Layer3",
+ "trg": "118_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "115_Layer3",
+ "trg": "117_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "115_Layer3",
+ "trg": "118_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "115_Layer3",
+ "trg": "119_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "116_Layer3",
+ "trg": "118_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "116_Layer3",
+ "trg": "119_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "117_Layer3",
+ "trg": "119_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "117_Layer3",
+ "trg": "120_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "121_Layer3",
+ "trg": "129_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "118_Layer3",
+ "trg": "120_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "118_Layer3",
+ "trg": "121_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "118_Layer3",
+ "trg": "122_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "119_Layer3",
+ "trg": "121_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "119_Layer3",
+ "trg": "122_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "119_Layer3",
+ "trg": "123_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "120_Layer3",
+ "trg": "122_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "123_Layer3",
+ "trg": "141_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "120_Layer3",
+ "trg": "124_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "121_Layer3",
+ "trg": "124_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "121_Layer3",
+ "trg": "125_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "122_Layer3",
+ "trg": "124_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "122_Layer3",
+ "trg": "125_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "122_Layer3",
+ "trg": "126_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "123_Layer3",
+ "trg": "125_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "123_Layer3",
+ "trg": "126_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "28_Layer3",
+ "trg": "127_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "124_Layer3",
+ "trg": "126_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "124_Layer3",
+ "trg": "127_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "124_Layer3",
+ "trg": "128_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "125_Layer3",
+ "trg": "127_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "125_Layer3",
+ "trg": "129_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "126_Layer3",
+ "trg": "128_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "126_Layer3",
+ "trg": "129_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "126_Layer3",
+ "trg": "130_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "127_Layer3",
+ "trg": "129_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "127_Layer3",
+ "trg": "130_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "127_Layer3",
+ "trg": "131_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "128_Layer3",
+ "trg": "130_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "128_Layer3",
+ "trg": "131_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "128_Layer3",
+ "trg": "132_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "129_Layer3",
+ "trg": "131_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "129_Layer3",
+ "trg": "132_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "130_Layer3",
+ "trg": "132_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "130_Layer3",
+ "trg": "133_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "130_Layer3",
+ "trg": "134_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "131_Layer3",
+ "trg": "133_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "131_Layer3",
+ "trg": "134_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "131_Layer3",
+ "trg": "135_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "132_Layer3",
+ "trg": "134_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "132_Layer3",
+ "trg": "135_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "132_Layer3",
+ "trg": "136_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "133_Layer3",
+ "trg": "135_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "133_Layer3",
+ "trg": "136_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "133_Layer3",
+ "trg": "137_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "134_Layer3",
+ "trg": "137_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "134_Layer3",
+ "trg": "138_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "135_Layer3",
+ "trg": "137_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "135_Layer3",
+ "trg": "138_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "135_Layer3",
+ "trg": "139_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "136_Layer3",
+ "trg": "138_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "136_Layer3",
+ "trg": "139_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "136_Layer3",
+ "trg": "140_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "137_Layer3",
+ "trg": "139_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "137_Layer3",
+ "trg": "140_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "137_Layer3",
+ "trg": "141_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "138_Layer3",
+ "trg": "140_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "138_Layer3",
+ "trg": "142_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "139_Layer3",
+ "trg": "141_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "139_Layer3",
+ "trg": "142_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "139_Layer3",
+ "trg": "143_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "140_Layer3",
+ "trg": "142_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "109_Layer3",
+ "trg": "140_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "140_Layer3",
+ "trg": "144_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "141_Layer3",
+ "trg": "143_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "141_Layer3",
+ "trg": "144_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "141_Layer3",
+ "trg": "145_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "142_Layer3",
+ "trg": "144_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "142_Layer3",
+ "trg": "145_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "143_Layer3",
+ "trg": "145_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "143_Layer3",
+ "trg": "146_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "143_Layer3",
+ "trg": "147_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "144_Layer3",
+ "trg": "146_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "144_Layer3",
+ "trg": "147_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "144_Layer3",
+ "trg": "148_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "145_Layer3",
+ "trg": "147_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "145_Layer3",
+ "trg": "148_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "145_Layer3",
+ "trg": "149_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "146_Layer3",
+ "trg": "148_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "146_Layer3",
+ "trg": "149_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "146_Layer3",
+ "trg": "150_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "147_Layer3",
+ "trg": "150_Layer3",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "2_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "3_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "4_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "5_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "6_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "7_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "8_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "9_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "10_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "11_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "12_Layer4",
+ "trg": "4_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "13_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "15_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "16_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "17_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "18_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "19_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "20_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "21_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "22_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "23_Layer4",
+ "trg": "19_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "24_Layer4",
+ "trg": "19_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "25_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "26_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "28_Layer4",
+ "trg": "14_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "29_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "30_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "31_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "32_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "33_Layer4",
+ "trg": "32_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "34_Layer4",
+ "trg": "19_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "35_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "36_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "37_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "38_Layer4",
+ "trg": "6_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "39_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "41_Layer4",
+ "trg": "31_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "42_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "43_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "44_Layer4",
+ "trg": "17_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "45_Layer4",
+ "trg": "13_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "46_Layer4",
+ "trg": "14_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "47_Layer4",
+ "trg": "45_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "48_Layer4",
+ "trg": "37_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "49_Layer4",
+ "trg": "48_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "50_Layer4",
+ "trg": "31_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "51_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "52_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "54_Layer4",
+ "trg": "49_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "55_Layer4",
+ "trg": "37_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "56_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "57_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "58_Layer4",
+ "trg": "28_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "59_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "60_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "61_Layer4",
+ "trg": "59_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "62_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "63_Layer4",
+ "trg": "4_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "64_Layer4",
+ "trg": "58_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "65_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "67_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "68_Layer4",
+ "trg": "22_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "69_Layer4",
+ "trg": "2_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "70_Layer4",
+ "trg": "50_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "71_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "72_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "73_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "74_Layer4",
+ "trg": "68_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "75_Layer4",
+ "trg": "69_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "76_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "77_Layer4",
+ "trg": "46_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "78_Layer4",
+ "trg": "70_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "80_Layer4",
+ "trg": "64_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "81_Layer4",
+ "trg": "31_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "82_Layer4",
+ "trg": "36_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "83_Layer4",
+ "trg": "13_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "84_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "85_Layer4",
+ "trg": "2_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "86_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "87_Layer4",
+ "trg": "33_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "88_Layer4",
+ "trg": "39_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "89_Layer4",
+ "trg": "13_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "90_Layer4",
+ "trg": "78_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "91_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "93_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "94_Layer4",
+ "trg": "83_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "95_Layer4",
+ "trg": "93_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "96_Layer4",
+ "trg": "53_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "97_Layer4",
+ "trg": "29_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "98_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "99_Layer4",
+ "trg": "58_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "100_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "101_Layer4",
+ "trg": "37_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "102_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "103_Layer4",
+ "trg": "49_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "104_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "106_Layer4",
+ "trg": "32_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "107_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "108_Layer4",
+ "trg": "62_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "109_Layer4",
+ "trg": "37_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "110_Layer4",
+ "trg": "58_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "111_Layer4",
+ "trg": "38_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "112_Layer4",
+ "trg": "21_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "113_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "114_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "115_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "116_Layer4",
+ "trg": "31_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "117_Layer4",
+ "trg": "95_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "119_Layer4",
+ "trg": "84_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "120_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "121_Layer4",
+ "trg": "21_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "122_Layer4",
+ "trg": "28_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "123_Layer4",
+ "trg": "21_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "124_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "125_Layer4",
+ "trg": "7_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "126_Layer4",
+ "trg": "87_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "127_Layer4",
+ "trg": "27_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "128_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "129_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "130_Layer4",
+ "trg": "90_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "132_Layer4",
+ "trg": "13_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "133_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "134_Layer4",
+ "trg": "104_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "135_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "136_Layer4",
+ "trg": "45_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "137_Layer4",
+ "trg": "79_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "138_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "139_Layer4",
+ "trg": "66_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "140_Layer4",
+ "trg": "103_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "141_Layer4",
+ "trg": "13_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "142_Layer4",
+ "trg": "98_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "143_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "145_Layer4",
+ "trg": "132_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "146_Layer4",
+ "trg": "26_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "147_Layer4",
+ "trg": "2_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "148_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "149_Layer4",
+ "trg": "14_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "150_Layer4",
+ "trg": "68_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "151_Layer4",
+ "trg": "2_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "152_Layer4",
+ "trg": "83_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "153_Layer4",
+ "trg": "132_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "154_Layer4",
+ "trg": "23_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "155_Layer4",
+ "trg": "60_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "156_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "158_Layer4",
+ "trg": "33_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "159_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "160_Layer4",
+ "trg": "102_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "161_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "162_Layer4",
+ "trg": "78_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "163_Layer4",
+ "trg": "132_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "164_Layer4",
+ "trg": "157_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "165_Layer4",
+ "trg": "31_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "166_Layer4",
+ "trg": "15_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "167_Layer4",
+ "trg": "31_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "168_Layer4",
+ "trg": "47_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "169_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "171_Layer4",
+ "trg": "70_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "172_Layer4",
+ "trg": "68_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "173_Layer4",
+ "trg": "35_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "174_Layer4",
+ "trg": "141_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "175_Layer4",
+ "trg": "4_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "176_Layer4",
+ "trg": "87_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "177_Layer4",
+ "trg": "23_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "178_Layer4",
+ "trg": "47_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "179_Layer4",
+ "trg": "100_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "180_Layer4",
+ "trg": "160_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "181_Layer4",
+ "trg": "161_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "182_Layer4",
+ "trg": "171_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "184_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "185_Layer4",
+ "trg": "121_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "186_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "187_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "188_Layer4",
+ "trg": "105_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "189_Layer4",
+ "trg": "35_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "190_Layer4",
+ "trg": "24_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "191_Layer4",
+ "trg": "75_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "192_Layer4",
+ "trg": "90_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "193_Layer4",
+ "trg": "54_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "194_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "195_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "197_Layer4",
+ "trg": "84_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "198_Layer4",
+ "trg": "136_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "199_Layer4",
+ "trg": "195_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "200_Layer4",
+ "trg": "153_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "201_Layer4",
+ "trg": "138_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "202_Layer4",
+ "trg": "13_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "203_Layer4",
+ "trg": "22_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "204_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "205_Layer4",
+ "trg": "45_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "206_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "207_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "208_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "210_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "211_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "212_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "213_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "214_Layer4",
+ "trg": "172_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "215_Layer4",
+ "trg": "89_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "216_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "217_Layer4",
+ "trg": "70_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "218_Layer4",
+ "trg": "4_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "219_Layer4",
+ "trg": "3_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "220_Layer4",
+ "trg": "65_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "221_Layer4",
+ "trg": "211_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "223_Layer4",
+ "trg": "45_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "224_Layer4",
+ "trg": "16_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "225_Layer4",
+ "trg": "109_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "226_Layer4",
+ "trg": "5_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "227_Layer4",
+ "trg": "215_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "228_Layer4",
+ "trg": "83_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "229_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "230_Layer4",
+ "trg": "10_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "231_Layer4",
+ "trg": "202_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "232_Layer4",
+ "trg": "141_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "233_Layer4",
+ "trg": "2_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "234_Layer4",
+ "trg": "211_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "236_Layer4",
+ "trg": "19_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "237_Layer4",
+ "trg": "35_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "238_Layer4",
+ "trg": "15_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "239_Layer4",
+ "trg": "196_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "240_Layer4",
+ "trg": "50_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "241_Layer4",
+ "trg": "171_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "242_Layer4",
+ "trg": "45_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "243_Layer4",
+ "trg": "194_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "244_Layer4",
+ "trg": "46_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "245_Layer4",
+ "trg": "15_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "246_Layer4",
+ "trg": "2_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "247_Layer4",
+ "trg": "214_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "249_Layer4",
+ "trg": "1_Layer4",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR236C:1_Layer5",
+ "trg": "YML010W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR016C:1_Layer5",
+ "trg": "YJR064W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR016C:1_Layer5",
+ "trg": "YDL047W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR016C:1_Layer5",
+ "trg": "YLR386W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR359W:1_Layer5",
+ "trg": "YOR361C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR300C:1_Layer5",
+ "trg": "YAL012W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR300C:1_Layer5",
+ "trg": "YMR235C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR180W:1_Layer5",
+ "trg": "YOL086C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR180W:1_Layer5",
+ "trg": "YDR390C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR180W:1_Layer5",
+ "trg": "YLR249W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR127C:1_Layer5",
+ "trg": "YNL172W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR127C:1_Layer5",
+ "trg": "YKL022C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR127C:1_Layer5",
+ "trg": "YHR166C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR127C:1_Layer5",
+ "trg": "YBL084C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR128C:1_Layer5",
+ "trg": "YDL185W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR128C:1_Layer5",
+ "trg": "YPL120W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:1_Layer5",
+ "trg": "YPR029C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:1_Layer5",
+ "trg": "YPL259C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:1_Layer5",
+ "trg": "YHL019C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:1_Layer5",
+ "trg": "YLR170C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:1_Layer5",
+ "trg": "YBR084W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:1_Layer5",
+ "trg": "YPR010C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL037W:1_Layer5",
+ "trg": "YJR005W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL037W:1_Layer5",
+ "trg": "YOL062C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL037W:1_Layer5",
+ "trg": "YJR058C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL195W:1_Layer5",
+ "trg": "YGR261C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL195W:1_Layer5",
+ "trg": "YBR288C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL195W:1_Layer5",
+ "trg": "YJL024C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL195W:1_Layer5",
+ "trg": "YGL019W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR261C:1_Layer5",
+ "trg": "YBR288C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR261C:1_Layer5",
+ "trg": "YHR174W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL062C:1_Layer5",
+ "trg": "YLR370C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL062C:1_Layer5",
+ "trg": "YKL013C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL062C:1_Layer5",
+ "trg": "YNR035C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL062C:1_Layer5",
+ "trg": "YJR065C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:1_Layer5",
+ "trg": "YKL013C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:1_Layer5",
+ "trg": "YNR035C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:1_Layer5",
+ "trg": "YDL029W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:1_Layer5",
+ "trg": "YJR065C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:1_Layer5",
+ "trg": "YGR240C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:1_Layer5",
+ "trg": "YOR361C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR035C:1_Layer5",
+ "trg": "YKL013C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR035C:1_Layer5",
+ "trg": "YBR234C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR035C:1_Layer5",
+ "trg": "YDL029W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR035C:1_Layer5",
+ "trg": "YJR065C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR234C:1_Layer5",
+ "trg": "YIL062C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR234C:1_Layer5",
+ "trg": "YLR370C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR234C:1_Layer5",
+ "trg": "YKL013C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR234C:1_Layer5",
+ "trg": "YJR065C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:1_Layer5",
+ "trg": "YMR116C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:1_Layer5",
+ "trg": "YGR254W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:1_Layer5",
+ "trg": "YBR084W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:1_Layer5",
+ "trg": "YHR023W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:1_Layer5",
+ "trg": "YDL040C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:1_Layer5",
+ "trg": "YGR090W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:1_Layer5",
+ "trg": "YJL008C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:1_Layer5",
+ "trg": "YKL104C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:1_Layer5",
+ "trg": "YMR186W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:1_Layer5",
+ "trg": "YOR136W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:1_Layer5",
+ "trg": "YGR240C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:1_Layer5",
+ "trg": "YDR427W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:1_Layer5",
+ "trg": "YGL048C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:1_Layer5",
+ "trg": "YPL235W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:1_Layer5",
+ "trg": "YGR234W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:1_Layer5",
+ "trg": "YIL062C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:1_Layer5",
+ "trg": "YKL013C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:1_Layer5",
+ "trg": "YBR234C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:1_Layer5",
+ "trg": "YJR065C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:1_Layer5",
+ "trg": "YJR064W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:1_Layer5",
+ "trg": "YJL008C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR065C:1_Layer5",
+ "trg": "YKL013C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR065C:1_Layer5",
+ "trg": "YNR053C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:1_Layer5",
+ "trg": "YJR140C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:1_Layer5",
+ "trg": "YIL070C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:1_Layer5",
+ "trg": "YPL153C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:1_Layer5",
+ "trg": "YJL026W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:1_Layer5",
+ "trg": "YGR180C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR148W:1_Layer5",
+ "trg": "YHR208W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL070W:1_Layer5",
+ "trg": "YLR399C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR200W:1_Layer5",
+ "trg": "YER114C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR200W:1_Layer5",
+ "trg": "YAL041W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YML077W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YLR342W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YDR108W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YGR166W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YDR407C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YMR218C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YBR254C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YDR246W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YDR472W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YOR115C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL031C:1_Layer5",
+ "trg": "YPR176C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:1_Layer5",
+ "trg": "YER177W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:1_Layer5",
+ "trg": "YDR001C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:1_Layer5",
+ "trg": "YBR001C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:1_Layer5",
+ "trg": "YNL267W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:1_Layer5",
+ "trg": "YGL252C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:1_Layer5",
+ "trg": "YAL017W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR014C:1_Layer5",
+ "trg": "YMR116C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YPL126W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YDL014W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YDL148C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YGL171W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YCL031C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YBL004W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YDR324C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YDR449C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YGR128C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YGR145W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YJL109C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YKR060W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YLR003C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YLR409C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YOR145C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YOR206W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YPR144C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YBR119W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YML046W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YKL012W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YDR235W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YER029C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YGR074W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YLR275W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YIL061C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YDR240C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YGR013W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR298C:1_Layer5",
+ "trg": "YMR125W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHL021C:1_Layer5",
+ "trg": "YOR210W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHL021C:1_Layer5",
+ "trg": "YJL140W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL035W:1_Layer5",
+ "trg": "YNL030W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL035W:1_Layer5",
+ "trg": "YBR084W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL035W:1_Layer5",
+ "trg": "YMR309C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL035W:1_Layer5",
+ "trg": "YDL014W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL035W:1_Layer5",
+ "trg": "YOR361C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL035W:1_Layer5",
+ "trg": "YDR496C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL035W:1_Layer5",
+ "trg": "YGR102C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL060W:1_Layer5",
+ "trg": "YLR314C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL069C:1_Layer5",
+ "trg": "YHR011W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL069C:1_Layer5",
+ "trg": "YLR129W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL069C:1_Layer5",
+ "trg": "YDR324C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL069C:1_Layer5",
+ "trg": "YDR449C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL069C:1_Layer5",
+ "trg": "YGR090W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL069C:1_Layer5",
+ "trg": "YJL109C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL069C:1_Layer5",
+ "trg": "YLR222C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL069C:1_Layer5",
+ "trg": "YLR409C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL069C:1_Layer5",
+ "trg": "YMR093W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL069C:1_Layer5",
+ "trg": "YOR059C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR014W:1_Layer5",
+ "trg": "YMR116C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YDR364C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YLR117C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YPL213W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YLL036C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YGL120C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YAL032C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YPL151C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YPR101W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YKL173W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YMR125W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YDR416W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YDL209C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YGR278W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL095W:1_Layer5",
+ "trg": "YLR424W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL059C:1_Layer5",
+ "trg": "YER133W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL059C:1_Layer5",
+ "trg": "YDR228C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL059C:1_Layer5",
+ "trg": "YNL317W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL059C:1_Layer5",
+ "trg": "YMR061W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL059C:1_Layer5",
+ "trg": "YGL049C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL059C:1_Layer5",
+ "trg": "YOR344C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL059C:1_Layer5",
+ "trg": "YGR156W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL059C:1_Layer5",
+ "trg": "YLR277C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL059C:1_Layer5",
+ "trg": "YPR107C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR079C:1_Layer5",
+ "trg": "YJL208C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YOL077C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YDL031W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YLR276C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YLL008W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YKL172W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YMR049C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YLR449W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YMR290C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YER126C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YPL211W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YPL093W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YNL061W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YNL002C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YDR087C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YGR245C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YCL054W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YFL002C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YDR060W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YDR101C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YDR496C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YER006W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YER139C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YGL111W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YGR103W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YHR052W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YHR197W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YLL034C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YLR002C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YNL110C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YNR053C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YOR206W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YOR272W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR284W:1_Layer5",
+ "trg": "YMR106C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLL013C:1_Layer5",
+ "trg": "YML124C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLL013C:1_Layer5",
+ "trg": "YGR234W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLL013C:1_Layer5",
+ "trg": "YHR121W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLL034C:1_Layer5",
+ "trg": "YPR010C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLL034C:1_Layer5",
+ "trg": "YOR341W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLL034C:1_Layer5",
+ "trg": "YGL048C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML059C:1_Layer5",
+ "trg": "YBR221C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML059C:1_Layer5",
+ "trg": "YMR308C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL067W:1_Layer5",
+ "trg": "YNL030W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL067W:1_Layer5",
+ "trg": "YPR010C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL045W:1_Layer5",
+ "trg": "YGR240C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YOL077C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YDL031W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YLL008W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YMR290C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YER126C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YPL093W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YPL043W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YNL002C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YHR088W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YGR245C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YCL054W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YBL004W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YDR101C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YER006W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YGL111W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YGR103W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YHR052W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YHR197W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YLL034C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YLR106C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YNL182C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YPL012W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YOR272W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YOL086C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YHR174W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YMR052W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YKL060C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YKL152C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YGL251C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YMR186W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YLR044C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YLR371W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YBR169C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YDR050C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL127W:1_Layer5",
+ "trg": "YGL131C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL201C:1_Layer5",
+ "trg": "YER155C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL201C:1_Layer5",
+ "trg": "YDR379W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL201C:1_Layer5",
+ "trg": "YML010W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YER155C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YPL266W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YBR247C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YPL204W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YKL143W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YOR290C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YBR289W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YJL176C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YGR054W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YOR056C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YOR145C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL207W:1_Layer5",
+ "trg": "YPL012W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL313C:1_Layer5",
+ "trg": "YJR121W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL313C:1_Layer5",
+ "trg": "YKL104C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL313C:1_Layer5",
+ "trg": "YER110C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL313C:1_Layer5",
+ "trg": "YIL094C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL313C:1_Layer5",
+ "trg": "YOR117W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL313C:1_Layer5",
+ "trg": "YDL185W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR046W:1_Layer5",
+ "trg": "YNR050C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR046W:1_Layer5",
+ "trg": "YML014W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR046W:1_Layer5",
+ "trg": "YOL124C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR056C:1_Layer5",
+ "trg": "YOL086C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR056C:1_Layer5",
+ "trg": "YDL060W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR164C:1_Layer5",
+ "trg": "YNR050C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR179C:1_Layer5",
+ "trg": "YER133W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR179C:1_Layer5",
+ "trg": "YDR195W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR179C:1_Layer5",
+ "trg": "YGR156W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR356W:1_Layer5",
+ "trg": "YOR243C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR277C:1_Layer5",
+ "trg": "YER133W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR277C:1_Layer5",
+ "trg": "YPR107C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR107C:1_Layer5",
+ "trg": "YDR301W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR107C:1_Layer5",
+ "trg": "YLR115W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR107C:1_Layer5",
+ "trg": "YJR093C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR107C:1_Layer5",
+ "trg": "YER133W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR107C:1_Layer5",
+ "trg": "YNL317W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR107C:1_Layer5",
+ "trg": "YDR195W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR107C:1_Layer5",
+ "trg": "YLR208W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR107C:1_Layer5",
+ "trg": "YNL222W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR107C:1_Layer5",
+ "trg": "YGR156W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR273C:1_Layer5",
+ "trg": "YGL194C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR273C:1_Layer5",
+ "trg": "YLR347C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR273C:1_Layer5",
+ "trg": "YDL188C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR273C:1_Layer5",
+ "trg": "YBR103W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR273C:1_Layer5",
+ "trg": "YNL189W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR273C:1_Layer5",
+ "trg": "YCR033W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR273C:1_Layer5",
+ "trg": "YIL112W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR273C:1_Layer5",
+ "trg": "YKR029C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML109W:1_Layer5",
+ "trg": "YFL007W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML109W:1_Layer5",
+ "trg": "YDL188C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML109W:1_Layer5",
+ "trg": "YOR362C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR285C:1_Layer5",
+ "trg": "YMR116C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR285C:1_Layer5",
+ "trg": "YBR084W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR285C:1_Layer5",
+ "trg": "YHR064C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR285C:1_Layer5",
+ "trg": "YJL080C:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR285C:1_Layer5",
+ "trg": "YPR189W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR285C:1_Layer5",
+ "trg": "YGR162W:1_Layer5",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR236C:2_Layer6",
+ "trg": "YML010W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR016C:2_Layer6",
+ "trg": "YJR064W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR016C:2_Layer6",
+ "trg": "YDL047W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR016C:2_Layer6",
+ "trg": "YLR386W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR359W:2_Layer6",
+ "trg": "YOR361C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR300C:2_Layer6",
+ "trg": "YAL012W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR300C:2_Layer6",
+ "trg": "YMR235C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR180W:2_Layer6",
+ "trg": "YOL086C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR180W:2_Layer6",
+ "trg": "YDR390C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR180W:2_Layer6",
+ "trg": "YLR249W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR127C:2_Layer6",
+ "trg": "YNL172W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR127C:2_Layer6",
+ "trg": "YKL022C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR127C:2_Layer6",
+ "trg": "YHR166C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR127C:2_Layer6",
+ "trg": "YBL084C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR128C:2_Layer6",
+ "trg": "YDL185W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR128C:2_Layer6",
+ "trg": "YPL120W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:2_Layer6",
+ "trg": "YPR029C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:2_Layer6",
+ "trg": "YPL259C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:2_Layer6",
+ "trg": "YHL019C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:2_Layer6",
+ "trg": "YLR170C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:2_Layer6",
+ "trg": "YBR084W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:2_Layer6",
+ "trg": "YPR010C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL037W:2_Layer6",
+ "trg": "YJR005W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL037W:2_Layer6",
+ "trg": "YOL062C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL037W:2_Layer6",
+ "trg": "YJR058C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL195W:2_Layer6",
+ "trg": "YGR261C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL195W:2_Layer6",
+ "trg": "YBR288C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL195W:2_Layer6",
+ "trg": "YJL024C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL195W:2_Layer6",
+ "trg": "YGL019W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR261C:2_Layer6",
+ "trg": "YBR288C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR261C:2_Layer6",
+ "trg": "YHR174W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL062C:2_Layer6",
+ "trg": "YLR370C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL062C:2_Layer6",
+ "trg": "YKL013C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL062C:2_Layer6",
+ "trg": "YNR035C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL062C:2_Layer6",
+ "trg": "YJR065C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:2_Layer6",
+ "trg": "YKL013C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:2_Layer6",
+ "trg": "YNR035C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:2_Layer6",
+ "trg": "YDL029W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:2_Layer6",
+ "trg": "YJR065C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:2_Layer6",
+ "trg": "YGR240C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:2_Layer6",
+ "trg": "YOR361C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR035C:2_Layer6",
+ "trg": "YKL013C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR035C:2_Layer6",
+ "trg": "YBR234C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR035C:2_Layer6",
+ "trg": "YDL029W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR035C:2_Layer6",
+ "trg": "YJR065C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR234C:2_Layer6",
+ "trg": "YIL062C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR234C:2_Layer6",
+ "trg": "YLR370C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR234C:2_Layer6",
+ "trg": "YKL013C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR234C:2_Layer6",
+ "trg": "YJR065C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:2_Layer6",
+ "trg": "YMR116C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:2_Layer6",
+ "trg": "YGR254W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:2_Layer6",
+ "trg": "YBR084W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:2_Layer6",
+ "trg": "YHR023W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:2_Layer6",
+ "trg": "YDL040C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:2_Layer6",
+ "trg": "YGR090W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:2_Layer6",
+ "trg": "YJL008C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:2_Layer6",
+ "trg": "YKL104C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:2_Layer6",
+ "trg": "YMR186W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:2_Layer6",
+ "trg": "YOR136W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:2_Layer6",
+ "trg": "YGR240C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:2_Layer6",
+ "trg": "YDR427W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:2_Layer6",
+ "trg": "YGL048C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:2_Layer6",
+ "trg": "YPL235W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:2_Layer6",
+ "trg": "YGR234W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:2_Layer6",
+ "trg": "YIL062C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:2_Layer6",
+ "trg": "YKL013C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:2_Layer6",
+ "trg": "YBR234C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:2_Layer6",
+ "trg": "YJR065C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:2_Layer6",
+ "trg": "YJR064W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:2_Layer6",
+ "trg": "YJL008C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR065C:2_Layer6",
+ "trg": "YKL013C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR065C:2_Layer6",
+ "trg": "YNR053C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:2_Layer6",
+ "trg": "YJR140C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:2_Layer6",
+ "trg": "YIL070C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:2_Layer6",
+ "trg": "YPL153C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:2_Layer6",
+ "trg": "YJL026W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:2_Layer6",
+ "trg": "YGR180C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR148W:2_Layer6",
+ "trg": "YHR208W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL070W:2_Layer6",
+ "trg": "YLR399C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR200W:2_Layer6",
+ "trg": "YER114C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR200W:2_Layer6",
+ "trg": "YAL041W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:2_Layer6",
+ "trg": "YML077W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:2_Layer6",
+ "trg": "YLR342W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:2_Layer6",
+ "trg": "YDR108W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:2_Layer6",
+ "trg": "YGR166W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:2_Layer6",
+ "trg": "YDR407C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:2_Layer6",
+ "trg": "YMR218C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:2_Layer6",
+ "trg": "YBR254C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:2_Layer6",
+ "trg": "YDR246W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:2_Layer6",
+ "trg": "YDR472W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:2_Layer6",
+ "trg": "YOR115C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL031C:2_Layer6",
+ "trg": "YPR176C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:2_Layer6",
+ "trg": "YER177W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:2_Layer6",
+ "trg": "YDR001C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:2_Layer6",
+ "trg": "YBR001C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:2_Layer6",
+ "trg": "YNL267W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:2_Layer6",
+ "trg": "YGL252C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:2_Layer6",
+ "trg": "YAL017W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR014C:2_Layer6",
+ "trg": "YMR116C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR014C:2_Layer6",
+ "trg": "YER155C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR014C:2_Layer6",
+ "trg": "YHR064C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR014C:2_Layer6",
+ "trg": "YGR285C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YPR016C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YER126C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YDL051W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YKL009W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YPL093W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YNL002C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YGR245C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YCR072C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YDR101C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YER006W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YGR103W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YHR052W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YHR085W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YHR197W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YKR081C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YLR106C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YNL110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YNL182C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR074C:2_Layer6",
+ "trg": "YNR053C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL007W:2_Layer6",
+ "trg": "YIL034C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL007W:2_Layer6",
+ "trg": "YER071C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL007W:2_Layer6",
+ "trg": "YIR003W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL111W:2_Layer6",
+ "trg": "YPL160W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL111W:2_Layer6",
+ "trg": "YKL104C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YLR347C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YDL087C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YPL190C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YHR086W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YDR432W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YNL251C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YML049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YLR275W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YIL061C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YDR240C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YGR013W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YNL189W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YMR125W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YGL049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL178W:2_Layer6",
+ "trg": "YML117W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YMR290C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YCL059C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YMR309C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YDL014W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YPL043W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YLR197W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YGR162W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YGL049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YDR496C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YAL035W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR175W:2_Layer6",
+ "trg": "YPL012W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER168C:2_Layer6",
+ "trg": "YHR174W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR025C:2_Layer6",
+ "trg": "YPR016C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR025C:2_Layer6",
+ "trg": "YDL108W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR025C:2_Layer6",
+ "trg": "YBR081C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR025C:2_Layer6",
+ "trg": "YDR460W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR025C:2_Layer6",
+ "trg": "YOR173W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL021C:2_Layer6",
+ "trg": "YGR134W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL021C:2_Layer6",
+ "trg": "YNL288W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL021C:2_Layer6",
+ "trg": "YDR188W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL021C:2_Layer6",
+ "trg": "YCR093W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL021C:2_Layer6",
+ "trg": "YGL195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL021C:2_Layer6",
+ "trg": "YNR052C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YFR028C:2_Layer6",
+ "trg": "YDR171W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL041W:2_Layer6",
+ "trg": "YER114C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL041W:2_Layer6",
+ "trg": "YLR357W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR160W:2_Layer6",
+ "trg": "YBR135W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR160W:2_Layer6",
+ "trg": "YMR199W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR160W:2_Layer6",
+ "trg": "YPL256C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR160W:2_Layer6",
+ "trg": "YIR031C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR314C:2_Layer6",
+ "trg": "YCR002C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR314C:2_Layer6",
+ "trg": "YJR076C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR314C:2_Layer6",
+ "trg": "YHR107C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR314C:2_Layer6",
+ "trg": "YDL225W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL139C:2_Layer6",
+ "trg": "YDL051W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL139C:2_Layer6",
+ "trg": "YNL262W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL139C:2_Layer6",
+ "trg": "YLR371W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL139C:2_Layer6",
+ "trg": "YHL034C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL139C:2_Layer6",
+ "trg": "YMR125W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL139C:2_Layer6",
+ "trg": "YGR162W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL139C:2_Layer6",
+ "trg": "YGL049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL139C:2_Layer6",
+ "trg": "YBL004W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL165W:2_Layer6",
+ "trg": "YNL315C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL165W:2_Layer6",
+ "trg": "YPR072W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCR093W:2_Layer6",
+ "trg": "YGR134W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCR093W:2_Layer6",
+ "trg": "YNL288W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCR093W:2_Layer6",
+ "trg": "YDL145C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCR093W:2_Layer6",
+ "trg": "YNR052C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCR093W:2_Layer6",
+ "trg": "YPL235W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCR093W:2_Layer6",
+ "trg": "YGL137W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCR093W:2_Layer6",
+ "trg": "YOR110W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR103C:2_Layer6",
+ "trg": "YIL094C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR103C:2_Layer6",
+ "trg": "YNL330C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR103C:2_Layer6",
+ "trg": "YOL004W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR202W:2_Layer6",
+ "trg": "YGL001C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL126C:2_Layer6",
+ "trg": "YNL271C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL126C:2_Layer6",
+ "trg": "YDR337W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL126C:2_Layer6",
+ "trg": "YBR170C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL126C:2_Layer6",
+ "trg": "YGL246C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL126C:2_Layer6",
+ "trg": "YDR028C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL126C:2_Layer6",
+ "trg": "YBL058W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL126C:2_Layer6",
+ "trg": "YGR048W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL126C:2_Layer6",
+ "trg": "YDR049W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YOR349W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YNL030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YJR138W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YMR109W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YGR240C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YDL134C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YDL188C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YKL081W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YAL016W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YBL104C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YLR249W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YDR381W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YMR273C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL190C:2_Layer6",
+ "trg": "YML109W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YDL031W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YLL008W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YMR049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YMR290C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YNL021W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YOR202W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YER126C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YGL099W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YDL051W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YFR001W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YKL009W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YGL036W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YMR309C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YPL093W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YDL014W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YOR361C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YNL002C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YPR010C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YHR088W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YBR079C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YGR245C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YDR496C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YER002W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YGR103W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YHR197W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YKR081C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YLR002C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YNL110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YNL182C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YNR053C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR016C:2_Layer6",
+ "trg": "YOR272W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YDR364C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YLR117C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YBR065C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YMR288W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YLL036C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YNR011C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YER013W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YGL120C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YAL032C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YPL151C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YML049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YLR275W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YPR101W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YKL173W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YDR416W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YCR063W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YDL209C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YGL128C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR213W:2_Layer6",
+ "trg": "YGR278W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YER155C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YPL228W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YMR012W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YOL145C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YNL030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YMR309C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YDL014W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YBR221C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YGL120C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YBR079C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YGL207W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YBL075C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YER103W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YNL088W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YKR001C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YHR121W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YAL035W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL130W:2_Layer6",
+ "trg": "YOL078W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YLR115W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YER133W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YKR002W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YGR240C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YNL317W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YAL043C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YDR195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YMR061W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YGR156W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YKL059C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:2_Layer6",
+ "trg": "YLR277C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR115W:2_Layer6",
+ "trg": "YER133W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR115W:2_Layer6",
+ "trg": "YKR002W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR115W:2_Layer6",
+ "trg": "YNL317W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR115W:2_Layer6",
+ "trg": "YAL043C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR115W:2_Layer6",
+ "trg": "YGR156W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR115W:2_Layer6",
+ "trg": "YHL035C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR115W:2_Layer6",
+ "trg": "YKL059C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR115W:2_Layer6",
+ "trg": "YLR277C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL206C:2_Layer6",
+ "trg": "YGR167W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL206C:2_Layer6",
+ "trg": "YIL094C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER164W:2_Layer6",
+ "trg": "YIL035C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER164W:2_Layer6",
+ "trg": "YOR061W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER164W:2_Layer6",
+ "trg": "YGL019W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER164W:2_Layer6",
+ "trg": "YOR039W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER164W:2_Layer6",
+ "trg": "YNL030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER164W:2_Layer6",
+ "trg": "YBR245C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER164W:2_Layer6",
+ "trg": "YOR304W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER164W:2_Layer6",
+ "trg": "YOR207C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER164W:2_Layer6",
+ "trg": "YKR001C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YIL035C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YOR061W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YGL019W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YMR172W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YMR309C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YOR361C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YBR079C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YPL235W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YGL207W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YER103W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YDR429C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YGR090W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YKL088W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR039W:2_Layer6",
+ "trg": "YGR270W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR135W:2_Layer6",
+ "trg": "YDL155W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR135W:2_Layer6",
+ "trg": "YMR199W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR135W:2_Layer6",
+ "trg": "YPL256C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR135W:2_Layer6",
+ "trg": "YBR295W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR135W:2_Layer6",
+ "trg": "YLR079W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR135W:2_Layer6",
+ "trg": "YKR091W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR135W:2_Layer6",
+ "trg": "YPL014W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR117C:2_Layer6",
+ "trg": "YJR050W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR117C:2_Layer6",
+ "trg": "YLL036C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR117C:2_Layer6",
+ "trg": "YAL032C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR117C:2_Layer6",
+ "trg": "YML049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR117C:2_Layer6",
+ "trg": "YPR101W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR117C:2_Layer6",
+ "trg": "YKL173W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR117C:2_Layer6",
+ "trg": "YDR416W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR250C:2_Layer6",
+ "trg": "YDR228C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR250C:2_Layer6",
+ "trg": "YMR061W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR250C:2_Layer6",
+ "trg": "YGL044C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR250C:2_Layer6",
+ "trg": "YDL195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR250C:2_Layer6",
+ "trg": "YBL075C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR155W:2_Layer6",
+ "trg": "YGR187C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL145C:2_Layer6",
+ "trg": "YFR051C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL145C:2_Layer6",
+ "trg": "YPL010W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL145C:2_Layer6",
+ "trg": "YNL287W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL145C:2_Layer6",
+ "trg": "YDR238C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL145C:2_Layer6",
+ "trg": "YIL076W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL045C:2_Layer6",
+ "trg": "YPR191W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL067C:2_Layer6",
+ "trg": "YGL187C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL067C:2_Layer6",
+ "trg": "YNL052W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL067C:2_Layer6",
+ "trg": "YHR051W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR155C:2_Layer6",
+ "trg": "YNL030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR155C:2_Layer6",
+ "trg": "YPL235W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR155C:2_Layer6",
+ "trg": "YBR103W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR155C:2_Layer6",
+ "trg": "YOR290C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR155C:2_Layer6",
+ "trg": "YCR033W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR155C:2_Layer6",
+ "trg": "YIL112W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL232W:2_Layer6",
+ "trg": "YOL021C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL232W:2_Layer6",
+ "trg": "YGR158C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL232W:2_Layer6",
+ "trg": "YHR069C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL232W:2_Layer6",
+ "trg": "YDL111C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL232W:2_Layer6",
+ "trg": "YCR035C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL232W:2_Layer6",
+ "trg": "YOR001W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL232W:2_Layer6",
+ "trg": "YGR195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL232W:2_Layer6",
+ "trg": "YOR076C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL232W:2_Layer6",
+ "trg": "YNL189W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL232W:2_Layer6",
+ "trg": "YIR035C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR158W:2_Layer6",
+ "trg": "YHR064C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR158W:2_Layer6",
+ "trg": "YGR285C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR078C:2_Layer6",
+ "trg": "YHR191C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR078C:2_Layer6",
+ "trg": "YPR175W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR078C:2_Layer6",
+ "trg": "YNL262W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR078C:2_Layer6",
+ "trg": "YNL290W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR078C:2_Layer6",
+ "trg": "YOL094C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR078C:2_Layer6",
+ "trg": "YBR087W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR078C:2_Layer6",
+ "trg": "YLL040C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL139W:2_Layer6",
+ "trg": "YJL006C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL139W:2_Layer6",
+ "trg": "YML112W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL139W:2_Layer6",
+ "trg": "YBR169C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL145C:2_Layer6",
+ "trg": "YLR418C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL145C:2_Layer6",
+ "trg": "YIL035C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL145C:2_Layer6",
+ "trg": "YOR061W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL145C:2_Layer6",
+ "trg": "YGL019W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL145C:2_Layer6",
+ "trg": "YOR039W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL145C:2_Layer6",
+ "trg": "YJR138W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL145C:2_Layer6",
+ "trg": "YOR123C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL145C:2_Layer6",
+ "trg": "YIL094C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL145C:2_Layer6",
+ "trg": "YBR279W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL145C:2_Layer6",
+ "trg": "YGL244W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YLR117C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YMR288W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YPL213W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YDL043C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YJL203W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YPL151C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YDL030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YML049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YER029C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YLR147C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR240C:2_Layer6",
+ "trg": "YMR125W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL005W:2_Layer6",
+ "trg": "YFL039C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL005W:2_Layer6",
+ "trg": "YGR233C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL005W:2_Layer6",
+ "trg": "YNL138W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR155W:2_Layer6",
+ "trg": "YJR045C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL118C:2_Layer6",
+ "trg": "YOL149W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL118C:2_Layer6",
+ "trg": "YML091C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL118C:2_Layer6",
+ "trg": "YEL015W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL021C:2_Layer6",
+ "trg": "YGR158C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL021C:2_Layer6",
+ "trg": "YHR069C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL021C:2_Layer6",
+ "trg": "YOL142W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL021C:2_Layer6",
+ "trg": "YDL111C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL021C:2_Layer6",
+ "trg": "YCR035C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL021C:2_Layer6",
+ "trg": "YGR095C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL021C:2_Layer6",
+ "trg": "YOR001W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL021C:2_Layer6",
+ "trg": "YGR195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL021C:2_Layer6",
+ "trg": "YHR081W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR164C:2_Layer6",
+ "trg": "YDR499W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR164C:2_Layer6",
+ "trg": "YAR007C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR164C:2_Layer6",
+ "trg": "YHR122W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL213C:2_Layer6",
+ "trg": "YDL126C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL213C:2_Layer6",
+ "trg": "YPR010C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL213C:2_Layer6",
+ "trg": "YOR341W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL213C:2_Layer6",
+ "trg": "YOR116C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL213C:2_Layer6",
+ "trg": "YBL058W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL240W:2_Layer6",
+ "trg": "YFL039C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL240W:2_Layer6",
+ "trg": "YLR127C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL240W:2_Layer6",
+ "trg": "YKL022C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL240W:2_Layer6",
+ "trg": "YHR166C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR175W:2_Layer6",
+ "trg": "YDR121W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR175W:2_Layer6",
+ "trg": "YNL262W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR278W:2_Layer6",
+ "trg": "YPR175W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR278W:2_Layer6",
+ "trg": "YDR121W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR278W:2_Layer6",
+ "trg": "YNL262W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL059W:2_Layer6",
+ "trg": "YOL086C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL059W:2_Layer6",
+ "trg": "YGL195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL059W:2_Layer6",
+ "trg": "YMR186W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL059W:2_Layer6",
+ "trg": "YER110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL059W:2_Layer6",
+ "trg": "YBR115C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL059W:2_Layer6",
+ "trg": "YMR308C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL059W:2_Layer6",
+ "trg": "YDR395W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR128W:2_Layer6",
+ "trg": "YNL097C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR128W:2_Layer6",
+ "trg": "YIL084C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR128W:2_Layer6",
+ "trg": "YPL139C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR065C:2_Layer6",
+ "trg": "YLR117C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR065C:2_Layer6",
+ "trg": "YLL036C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR065C:2_Layer6",
+ "trg": "YPL151C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL047C:2_Layer6",
+ "trg": "YNL243W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL047C:2_Layer6",
+ "trg": "YCR030C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL047C:2_Layer6",
+ "trg": "YDR348C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR200C:2_Layer6",
+ "trg": "YPL086C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR200C:2_Layer6",
+ "trg": "YHR187W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR200C:2_Layer6",
+ "trg": "YLR384C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR200C:2_Layer6",
+ "trg": "YPL101W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR254W:2_Layer6",
+ "trg": "YJR045C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YLR175W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YMR128W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YLR186W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YHR148W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YNL075W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YER082C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YNL132W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YCL059C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YKL143W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YJR002W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YDL014W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YDL148C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YOL010W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YLR180W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YLL011W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YBL004W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YDL060W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YDR449C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YGR081C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YGR090W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YMR093W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YOR056C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YPL012W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:2_Layer6",
+ "trg": "YPR144C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YFL024C:2_Layer6",
+ "trg": "YFL039C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YFL024C:2_Layer6",
+ "trg": "YPR023C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YFL024C:2_Layer6",
+ "trg": "YOR244W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YFL024C:2_Layer6",
+ "trg": "YNL030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YFL024C:2_Layer6",
+ "trg": "YHR099W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YFL024C:2_Layer6",
+ "trg": "YDR359C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YFL024C:2_Layer6",
+ "trg": "YNL107W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YFL024C:2_Layer6",
+ "trg": "YEL018W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR049C:2_Layer6",
+ "trg": "YOL077C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR049C:2_Layer6",
+ "trg": "YMR290C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR049C:2_Layer6",
+ "trg": "YNL061W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR049C:2_Layer6",
+ "trg": "YOR361C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR049C:2_Layer6",
+ "trg": "YNL002C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR049C:2_Layer6",
+ "trg": "YGL111W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR049C:2_Layer6",
+ "trg": "YGR103W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR049C:2_Layer6",
+ "trg": "YNL110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR049C:2_Layer6",
+ "trg": "YOR206W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML126C:2_Layer6",
+ "trg": "YFL039C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML126C:2_Layer6",
+ "trg": "YIL094C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML126C:2_Layer6",
+ "trg": "YKL125W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML126C:2_Layer6",
+ "trg": "YLR180W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML126C:2_Layer6",
+ "trg": "YNL119W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML130C:2_Layer6",
+ "trg": "YML067C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML130C:2_Layer6",
+ "trg": "YAL042W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML130C:2_Layer6",
+ "trg": "YLR342W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML130C:2_Layer6",
+ "trg": "YLR208W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR002C-A:2_Layer6",
+ "trg": "YGL200C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR002C-A:2_Layer6",
+ "trg": "YAL007C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR002C-A:2_Layer6",
+ "trg": "YML012W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR002C-A:2_Layer6",
+ "trg": "YNL030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR002C-A:2_Layer6",
+ "trg": "YGR132C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL007C:2_Layer6",
+ "trg": "YDR127W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL007C:2_Layer6",
+ "trg": "YGL200C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL007C:2_Layer6",
+ "trg": "YML012W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL007C:2_Layer6",
+ "trg": "YLR342W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL007C:2_Layer6",
+ "trg": "YMR290C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL007C:2_Layer6",
+ "trg": "YER110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL007C:2_Layer6",
+ "trg": "YPL211W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL007C:2_Layer6",
+ "trg": "YDR087C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL007C:2_Layer6",
+ "trg": "YDR101C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR244W:2_Layer6",
+ "trg": "YFL039C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR244W:2_Layer6",
+ "trg": "YOL086C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR244W:2_Layer6",
+ "trg": "YJL081C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR244W:2_Layer6",
+ "trg": "YPR023C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR244W:2_Layer6",
+ "trg": "YHR099W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR244W:2_Layer6",
+ "trg": "YDR359C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR244W:2_Layer6",
+ "trg": "YNL107W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR244W:2_Layer6",
+ "trg": "YML007W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR102C:2_Layer6",
+ "trg": "YLR166C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR102C:2_Layer6",
+ "trg": "YER008C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR102C:2_Layer6",
+ "trg": "YDR166C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR102C:2_Layer6",
+ "trg": "YIL068C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR102C:2_Layer6",
+ "trg": "YPR055W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR102C:2_Layer6",
+ "trg": "YML097C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR021W:2_Layer6",
+ "trg": "YNL271C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YDR301W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YLR115W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YKR002W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YNL317W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YAL043C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YMR061W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YGR156W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YKL059C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YLR221C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YML030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:2_Layer6",
+ "trg": "YLR277C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL135C:2_Layer6",
+ "trg": "YNL023C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL135C:2_Layer6",
+ "trg": "YLR005W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL135C:2_Layer6",
+ "trg": "YDR311W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR060W:2_Layer6",
+ "trg": "YFL022C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR207W:2_Layer6",
+ "trg": "YOL086C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR207W:2_Layer6",
+ "trg": "YFL041W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR207W:2_Layer6",
+ "trg": "YLR342W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR207W:2_Layer6",
+ "trg": "YEL022W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR207W:2_Layer6",
+ "trg": "YER110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR207W:2_Layer6",
+ "trg": "YER021W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YLR175W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YDL051W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YBR084W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YNL005C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YPL043W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YNL262W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YGL049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YBL004W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YDR496C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YKL014C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR089C:2_Layer6",
+ "trg": "YPL012W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR260W:2_Layer6",
+ "trg": "YER025W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR260W:2_Layer6",
+ "trg": "YGR083C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR260W:2_Layer6",
+ "trg": "YDR211W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR260W:2_Layer6",
+ "trg": "YLR291C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR260W:2_Layer6",
+ "trg": "YKR026C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR260W:2_Layer6",
+ "trg": "YLR044C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR260W:2_Layer6",
+ "trg": "YJR007W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR260W:2_Layer6",
+ "trg": "YPL237W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR211W:2_Layer6",
+ "trg": "YER025W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR211W:2_Layer6",
+ "trg": "YGR083C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR211W:2_Layer6",
+ "trg": "YLR291C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR211W:2_Layer6",
+ "trg": "YKR026C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR211W:2_Layer6",
+ "trg": "YJR007W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR211W:2_Layer6",
+ "trg": "YPL237W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR283C:2_Layer6",
+ "trg": "YBL074C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR283C:2_Layer6",
+ "trg": "YGR091W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR283C:2_Layer6",
+ "trg": "YPR178W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR283C:2_Layer6",
+ "trg": "YBR055C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR283C:2_Layer6",
+ "trg": "YKL173W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR283C:2_Layer6",
+ "trg": "YOR308C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR283C:2_Layer6",
+ "trg": "YLR409C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YFR009W:2_Layer6",
+ "trg": "YGL195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR026C:2_Layer6",
+ "trg": "YDL143W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR026C:2_Layer6",
+ "trg": "YGL206C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR026C:2_Layer6",
+ "trg": "YER025W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR026C:2_Layer6",
+ "trg": "YGR083C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR026C:2_Layer6",
+ "trg": "YLR291C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR026C:2_Layer6",
+ "trg": "YHL032C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR026C:2_Layer6",
+ "trg": "YPL237W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER136W:2_Layer6",
+ "trg": "YFL038C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER136W:2_Layer6",
+ "trg": "YER031C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER136W:2_Layer6",
+ "trg": "YKR014C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR108W:2_Layer6",
+ "trg": "YGL206C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR108W:2_Layer6",
+ "trg": "YGL195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR108W:2_Layer6",
+ "trg": "YIL094C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER122C:2_Layer6",
+ "trg": "YDL145C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER122C:2_Layer6",
+ "trg": "YNL287W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER122C:2_Layer6",
+ "trg": "YDR238C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER122C:2_Layer6",
+ "trg": "YGL137W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL142C:2_Layer6",
+ "trg": "YML012W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR163W:2_Layer6",
+ "trg": "YBR158W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR163W:2_Layer6",
+ "trg": "YGR098C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR163W:2_Layer6",
+ "trg": "YPL235W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR163W:2_Layer6",
+ "trg": "YGL207W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR163W:2_Layer6",
+ "trg": "YCR015C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR163W:2_Layer6",
+ "trg": "YGR203W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL237C:2_Layer6",
+ "trg": "YOR358W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL001W:2_Layer6",
+ "trg": "YEL056W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL001W:2_Layer6",
+ "trg": "YLL022C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL001W:2_Layer6",
+ "trg": "YMR309C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL001W:2_Layer6",
+ "trg": "YOR361C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL001W:2_Layer6",
+ "trg": "YBR079C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YEL056W:2_Layer6",
+ "trg": "YNL030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YLR175W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YLR115W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YJR093C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YER133W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YMR290C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YDL014W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YOL041C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YOR310C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YKR002W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YNL317W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YAL043C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YDR195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YMR061W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YLR197W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YCL037C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YGR156W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL033W:2_Layer6",
+ "trg": "YLR277C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL021W:2_Layer6",
+ "trg": "YPR179C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL021W:2_Layer6",
+ "trg": "YDR295C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YDL070W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YGL190C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YGL019W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YKR054C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YMR176W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YEL046C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YPL001W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YER065C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YJR138W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YER110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YNL132W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YMR021C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YBR084W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YOR188W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YJR132W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YGR240C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YDR300C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YGR170W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YMR308C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YPR010C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YPR110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YDR303C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YLR180W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YBL088C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YER093C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YDL058W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YJL222W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YBL004W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YFR006W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YLR106C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YLR239C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR009C:2_Layer6",
+ "trg": "YOL054W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCL030C:2_Layer6",
+ "trg": "YDL160C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCL030C:2_Layer6",
+ "trg": "YLR044C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCL030C:2_Layer6",
+ "trg": "YLR180W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCL030C:2_Layer6",
+ "trg": "YML124C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL123W:2_Layer6",
+ "trg": "YMR064W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL123W:2_Layer6",
+ "trg": "YMR282C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL123W:2_Layer6",
+ "trg": "YDR023W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL123W:2_Layer6",
+ "trg": "YDR350C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL123W:2_Layer6",
+ "trg": "YDL060W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL133W:2_Layer6",
+ "trg": "YOR319W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR288W:2_Layer6",
+ "trg": "YML049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YPL001W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YEL056W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YNL030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YLL022C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YFR052W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YER021W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YDL097C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YOR261C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YDR427W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YDL007W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YDR394W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YOR117W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YGL048C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR272C:2_Layer6",
+ "trg": "YOL054W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL068C:2_Layer6",
+ "trg": "YBR103W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YPR175W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YDR121W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YBR245C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YOR304W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YGL241W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YPL082C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YKR048C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YMR091C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YNL262W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YOR207C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YPR010C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YPR190C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YOR116C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YIL126W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YOL006C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YKR001C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL002W:2_Layer6",
+ "trg": "YDR496C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR107C:2_Layer6",
+ "trg": "YBR211C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR107C:2_Layer6",
+ "trg": "YDR254W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR107C:2_Layer6",
+ "trg": "YPL018W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR107C:2_Layer6",
+ "trg": "YDR318W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR107C:2_Layer6",
+ "trg": "YJR135C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR107C:2_Layer6",
+ "trg": "YDR383C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR107C:2_Layer6",
+ "trg": "YLR381W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR011C:2_Layer6",
+ "trg": "YMR267W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR011C:2_Layer6",
+ "trg": "YGL120C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR011C:2_Layer6",
+ "trg": "YLR371W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR011C:2_Layer6",
+ "trg": "YLR425W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR011C:2_Layer6",
+ "trg": "YDL203C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR011C:2_Layer6",
+ "trg": "YPL066W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL026C:2_Layer6",
+ "trg": "YER110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL265C:2_Layer6",
+ "trg": "YOR260W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL265C:2_Layer6",
+ "trg": "YGR083C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL265C:2_Layer6",
+ "trg": "YDR211W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL265C:2_Layer6",
+ "trg": "YLR291C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL265C:2_Layer6",
+ "trg": "YKR026C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL265C:2_Layer6",
+ "trg": "YDR300C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR236C:1_Layer5",
+ "trg": "YBR236C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR016C:1_Layer5",
+ "trg": "YNR016C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR359W:1_Layer5",
+ "trg": "YLR359W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR300C:1_Layer5",
+ "trg": "YMR300C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR180W:1_Layer5",
+ "trg": "YPR180W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR127C:1_Layer5",
+ "trg": "YLR127C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR128C:1_Layer5",
+ "trg": "YBR128C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL135C:1_Layer5",
+ "trg": "YKL135C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL037W:1_Layer5",
+ "trg": "YBL037W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL195W:1_Layer5",
+ "trg": "YPL195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR261C:1_Layer5",
+ "trg": "YGR261C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL062C:1_Layer5",
+ "trg": "YIL062C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR370C:1_Layer5",
+ "trg": "YLR370C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR035C:1_Layer5",
+ "trg": "YNR035C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR234C:1_Layer5",
+ "trg": "YBR234C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR013C:1_Layer5",
+ "trg": "YHR013C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR127W:1_Layer5",
+ "trg": "YDR127W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL029W:1_Layer5",
+ "trg": "YDL029W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR065C:1_Layer5",
+ "trg": "YJR065C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL115W:1_Layer5",
+ "trg": "YJL115W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR148W:1_Layer5",
+ "trg": "YJR148W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL070W:1_Layer5",
+ "trg": "YDL070W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR200W:1_Layer5",
+ "trg": "YBR200W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR068C:1_Layer5",
+ "trg": "YKR068C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL031C:1_Layer5",
+ "trg": "YJL031C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR099W:1_Layer5",
+ "trg": "YDR099W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAR014C:1_Layer5",
+ "trg": "YAR014C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR090W:1_Layer5",
+ "trg": "YGR090W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL035W:1_Layer5",
+ "trg": "YAL035W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL059C:1_Layer5",
+ "trg": "YKL059C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKR081C:1_Layer5",
+ "trg": "YKR081C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL110C:1_Layer5",
+ "trg": "YNL110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR056C:1_Layer5",
+ "trg": "YOR056C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR277C:1_Layer5",
+ "trg": "YLR277C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR273C:1_Layer5",
+ "trg": "YMR273C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML109W:1_Layer5",
+ "trg": "YML109W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR285C:1_Layer5",
+ "trg": "YGR285C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML010W:1_Layer5",
+ "trg": "YML010W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR064W:1_Layer5",
+ "trg": "YJR064W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL047W:1_Layer5",
+ "trg": "YDL047W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR386W:1_Layer5",
+ "trg": "YLR386W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR361C:1_Layer5",
+ "trg": "YOR361C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL012W:1_Layer5",
+ "trg": "YAL012W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR235C:1_Layer5",
+ "trg": "YMR235C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL086C:1_Layer5",
+ "trg": "YOL086C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR390C:1_Layer5",
+ "trg": "YDR390C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR249W:1_Layer5",
+ "trg": "YLR249W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL172W:1_Layer5",
+ "trg": "YNL172W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL022C:1_Layer5",
+ "trg": "YKL022C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR166C:1_Layer5",
+ "trg": "YHR166C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL084C:1_Layer5",
+ "trg": "YBL084C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL185W:1_Layer5",
+ "trg": "YDL185W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL120W:1_Layer5",
+ "trg": "YPL120W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR029C:1_Layer5",
+ "trg": "YPR029C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL259C:1_Layer5",
+ "trg": "YPL259C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHL019C:1_Layer5",
+ "trg": "YHL019C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR170C:1_Layer5",
+ "trg": "YLR170C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR084W:1_Layer5",
+ "trg": "YBR084W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR010C:1_Layer5",
+ "trg": "YPR010C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR005W:1_Layer5",
+ "trg": "YJR005W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL062C:1_Layer5",
+ "trg": "YOL062C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR058C:1_Layer5",
+ "trg": "YJR058C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR288C:1_Layer5",
+ "trg": "YBR288C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL024C:1_Layer5",
+ "trg": "YJL024C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL019W:1_Layer5",
+ "trg": "YGL019W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR174W:1_Layer5",
+ "trg": "YHR174W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL013C:1_Layer5",
+ "trg": "YKL013C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR240C:1_Layer5",
+ "trg": "YGR240C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR116C:1_Layer5",
+ "trg": "YMR116C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR254W:1_Layer5",
+ "trg": "YGR254W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR023W:1_Layer5",
+ "trg": "YHR023W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL040C:1_Layer5",
+ "trg": "YDL040C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL008C:1_Layer5",
+ "trg": "YJL008C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL104C:1_Layer5",
+ "trg": "YKL104C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR186W:1_Layer5",
+ "trg": "YMR186W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR136W:1_Layer5",
+ "trg": "YOR136W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR427W:1_Layer5",
+ "trg": "YDR427W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL048C:1_Layer5",
+ "trg": "YGL048C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL235W:1_Layer5",
+ "trg": "YPL235W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR234W:1_Layer5",
+ "trg": "YGR234W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNR053C:1_Layer5",
+ "trg": "YNR053C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR140C:1_Layer5",
+ "trg": "YJR140C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL070C:1_Layer5",
+ "trg": "YIL070C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL153C:1_Layer5",
+ "trg": "YPL153C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJL026W:1_Layer5",
+ "trg": "YJL026W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR180C:1_Layer5",
+ "trg": "YGR180C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR208W:1_Layer5",
+ "trg": "YHR208W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR399C:1_Layer5",
+ "trg": "YLR399C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER114C:1_Layer5",
+ "trg": "YER114C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL041W:1_Layer5",
+ "trg": "YAL041W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML077W:1_Layer5",
+ "trg": "YML077W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR342W:1_Layer5",
+ "trg": "YLR342W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR108W:1_Layer5",
+ "trg": "YDR108W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR166W:1_Layer5",
+ "trg": "YGR166W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR407C:1_Layer5",
+ "trg": "YDR407C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR218C:1_Layer5",
+ "trg": "YMR218C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR254C:1_Layer5",
+ "trg": "YBR254C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR246W:1_Layer5",
+ "trg": "YDR246W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR472W:1_Layer5",
+ "trg": "YDR472W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR115C:1_Layer5",
+ "trg": "YOR115C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR176C:1_Layer5",
+ "trg": "YPR176C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER177W:1_Layer5",
+ "trg": "YER177W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR001C:1_Layer5",
+ "trg": "YDR001C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR001C:1_Layer5",
+ "trg": "YBR001C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL267W:1_Layer5",
+ "trg": "YNL267W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL252C:1_Layer5",
+ "trg": "YGL252C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL017W:1_Layer5",
+ "trg": "YAL017W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL014W:1_Layer5",
+ "trg": "YDL014W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL148C:1_Layer5",
+ "trg": "YDL148C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBL004W:1_Layer5",
+ "trg": "YBL004W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR449C:1_Layer5",
+ "trg": "YDR449C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR409C:1_Layer5",
+ "trg": "YLR409C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR206W:1_Layer5",
+ "trg": "YOR206W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR144C:1_Layer5",
+ "trg": "YPR144C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER029C:1_Layer5",
+ "trg": "YER029C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR275W:1_Layer5",
+ "trg": "YLR275W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL061C:1_Layer5",
+ "trg": "YIL061C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR240C:1_Layer5",
+ "trg": "YDR240C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR013W:1_Layer5",
+ "trg": "YGR013W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR125W:1_Layer5",
+ "trg": "YMR125W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL030W:1_Layer5",
+ "trg": "YNL030W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR309C:1_Layer5",
+ "trg": "YMR309C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR496C:1_Layer5",
+ "trg": "YDR496C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR314C:1_Layer5",
+ "trg": "YLR314C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR093W:1_Layer5",
+ "trg": "YMR093W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR364C:1_Layer5",
+ "trg": "YDR364C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR117C:1_Layer5",
+ "trg": "YLR117C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL213W:1_Layer5",
+ "trg": "YPL213W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLL036C:1_Layer5",
+ "trg": "YLL036C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL120C:1_Layer5",
+ "trg": "YGL120C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YAL032C:1_Layer5",
+ "trg": "YAL032C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL151C:1_Layer5",
+ "trg": "YPL151C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPR101W:1_Layer5",
+ "trg": "YPR101W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL173W:1_Layer5",
+ "trg": "YKL173W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR416W:1_Layer5",
+ "trg": "YDR416W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL209C:1_Layer5",
+ "trg": "YDL209C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR278W:1_Layer5",
+ "trg": "YGR278W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER133W:1_Layer5",
+ "trg": "YER133W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR228C:1_Layer5",
+ "trg": "YDR228C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL317W:1_Layer5",
+ "trg": "YNL317W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR061W:1_Layer5",
+ "trg": "YMR061W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL049C:1_Layer5",
+ "trg": "YGL049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR156W:1_Layer5",
+ "trg": "YGR156W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOL077C:1_Layer5",
+ "trg": "YOL077C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL031W:1_Layer5",
+ "trg": "YDL031W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLL008W:1_Layer5",
+ "trg": "YLL008W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR049C:1_Layer5",
+ "trg": "YMR049C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR290C:1_Layer5",
+ "trg": "YMR290C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER126C:1_Layer5",
+ "trg": "YER126C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL211W:1_Layer5",
+ "trg": "YPL211W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL093W:1_Layer5",
+ "trg": "YPL093W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL061W:1_Layer5",
+ "trg": "YNL061W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL002C:1_Layer5",
+ "trg": "YNL002C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR087C:1_Layer5",
+ "trg": "YDR087C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR245C:1_Layer5",
+ "trg": "YGR245C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR101C:1_Layer5",
+ "trg": "YDR101C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER006W:1_Layer5",
+ "trg": "YER006W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGL111W:1_Layer5",
+ "trg": "YGL111W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR103W:1_Layer5",
+ "trg": "YGR103W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR052W:1_Layer5",
+ "trg": "YHR052W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR197W:1_Layer5",
+ "trg": "YHR197W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR002C:1_Layer5",
+ "trg": "YLR002C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR272W:1_Layer5",
+ "trg": "YOR272W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YML124C:1_Layer5",
+ "trg": "YML124C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR121W:1_Layer5",
+ "trg": "YHR121W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR341W:1_Layer5",
+ "trg": "YOR341W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR221C:1_Layer5",
+ "trg": "YBR221C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YMR308C:1_Layer5",
+ "trg": "YMR308C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL043W:1_Layer5",
+ "trg": "YPL043W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR088W:1_Layer5",
+ "trg": "YHR088W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR106C:1_Layer5",
+ "trg": "YLR106C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL182C:1_Layer5",
+ "trg": "YNL182C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YPL012W:1_Layer5",
+ "trg": "YPL012W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR044C:1_Layer5",
+ "trg": "YLR044C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR371W:1_Layer5",
+ "trg": "YLR371W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR169C:1_Layer5",
+ "trg": "YBR169C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER155C:1_Layer5",
+ "trg": "YER155C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR247C:1_Layer5",
+ "trg": "YBR247C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YKL143W:1_Layer5",
+ "trg": "YKL143W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR290C:1_Layer5",
+ "trg": "YOR290C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YER110C:1_Layer5",
+ "trg": "YER110C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL094C:1_Layer5",
+ "trg": "YIL094C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YOR117W:1_Layer5",
+ "trg": "YOR117W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL060W:1_Layer5",
+ "trg": "YDL060W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR195W:1_Layer5",
+ "trg": "YDR195W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDR301W:1_Layer5",
+ "trg": "YDR301W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR115W:1_Layer5",
+ "trg": "YLR115W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YJR093C:1_Layer5",
+ "trg": "YJR093C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR208W:1_Layer5",
+ "trg": "YLR208W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YLR347C:1_Layer5",
+ "trg": "YLR347C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YDL188C:1_Layer5",
+ "trg": "YDL188C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YBR103W:1_Layer5",
+ "trg": "YBR103W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YNL189W:1_Layer5",
+ "trg": "YNL189W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YCR033W:1_Layer5",
+ "trg": "YCR033W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YIL112W:1_Layer5",
+ "trg": "YIL112W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YHR064C:1_Layer5",
+ "trg": "YHR064C:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ },
+ {
+ "src": "YGR162W:1_Layer5",
+ "trg": "YGR162W:2_Layer6",
+ "opacity": "0.3",
+ "color": "#CFCFCF",
+ "channel": ""
+ }
+ ],
+ "universalLabelColor": "#ffffff"
+}
\ No newline at end of file
diff --git a/www/data/figure2A_data.tsv b/frontend/public/data/figure2A_data.tsv
similarity index 100%
rename from www/data/figure2A_data.tsv
rename to frontend/public/data/figure2A_data.tsv
diff --git a/www/data/figure2A_export.json b/frontend/public/data/figure2A_export.json
similarity index 100%
rename from www/data/figure2A_export.json
rename to frontend/public/data/figure2A_export.json
diff --git a/www/data/figure2B_data.tsv b/frontend/public/data/figure2B_data.tsv
similarity index 100%
rename from www/data/figure2B_data.tsv
rename to frontend/public/data/figure2B_data.tsv
diff --git a/www/data/figure2B_data_edge_attributes.tsv b/frontend/public/data/figure2B_data_edge_attributes.tsv
similarity index 100%
rename from www/data/figure2B_data_edge_attributes.tsv
rename to frontend/public/data/figure2B_data_edge_attributes.tsv
diff --git a/www/data/figure2B_data_node_attributes.tsv b/frontend/public/data/figure2B_data_node_attributes.tsv
similarity index 100%
rename from www/data/figure2B_data_node_attributes.tsv
rename to frontend/public/data/figure2B_data_node_attributes.tsv
diff --git a/www/data/figure2B_export.json b/frontend/public/data/figure2B_export.json
similarity index 100%
rename from www/data/figure2B_export.json
rename to frontend/public/data/figure2B_export.json
diff --git a/www/data/prego.json b/frontend/public/data/prego.json
similarity index 100%
rename from www/data/prego.json
rename to frontend/public/data/prego.json
diff --git a/www/data/prego.tsv b/frontend/public/data/prego.tsv
similarity index 100%
rename from www/data/prego.tsv
rename to frontend/public/data/prego.tsv
diff --git a/www/data/transpose.py b/frontend/public/data/transpose.py
similarity index 100%
rename from www/data/transpose.py
rename to frontend/public/data/transpose.py
diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico
new file mode 100644
index 0000000..0e35e97
Binary files /dev/null and b/frontend/public/favicon.ico differ
diff --git a/www/images/canvas_control/collapseLayers.png b/frontend/public/images/canvas_control/collapseLayers.png
similarity index 100%
rename from www/images/canvas_control/collapseLayers.png
rename to frontend/public/images/canvas_control/collapseLayers.png
diff --git a/www/images/canvas_control/collapseLayers_active.png b/frontend/public/images/canvas_control/collapseLayers_active.png
similarity index 100%
rename from www/images/canvas_control/collapseLayers_active.png
rename to frontend/public/images/canvas_control/collapseLayers_active.png
diff --git a/www/images/canvas_control/collapseLayers_hover.png b/frontend/public/images/canvas_control/collapseLayers_hover.png
similarity index 100%
rename from www/images/canvas_control/collapseLayers_hover.png
rename to frontend/public/images/canvas_control/collapseLayers_hover.png
diff --git a/www/images/canvas_control/collapseNodes.png b/frontend/public/images/canvas_control/collapseNodes.png
similarity index 100%
rename from www/images/canvas_control/collapseNodes.png
rename to frontend/public/images/canvas_control/collapseNodes.png
diff --git a/www/images/canvas_control/collapseNodes_active.png b/frontend/public/images/canvas_control/collapseNodes_active.png
similarity index 100%
rename from www/images/canvas_control/collapseNodes_active.png
rename to frontend/public/images/canvas_control/collapseNodes_active.png
diff --git a/www/images/canvas_control/collapseNodes_hover.png b/frontend/public/images/canvas_control/collapseNodes_hover.png
similarity index 100%
rename from www/images/canvas_control/collapseNodes_hover.png
rename to frontend/public/images/canvas_control/collapseNodes_hover.png
diff --git a/www/images/canvas_control/expandLayers.png b/frontend/public/images/canvas_control/expandLayers.png
similarity index 100%
rename from www/images/canvas_control/expandLayers.png
rename to frontend/public/images/canvas_control/expandLayers.png
diff --git a/www/images/canvas_control/expandLayers_active.png b/frontend/public/images/canvas_control/expandLayers_active.png
similarity index 100%
rename from www/images/canvas_control/expandLayers_active.png
rename to frontend/public/images/canvas_control/expandLayers_active.png
diff --git a/www/images/canvas_control/expandLayers_hover.png b/frontend/public/images/canvas_control/expandLayers_hover.png
similarity index 100%
rename from www/images/canvas_control/expandLayers_hover.png
rename to frontend/public/images/canvas_control/expandLayers_hover.png
diff --git a/www/images/canvas_control/expandNodes.png b/frontend/public/images/canvas_control/expandNodes.png
similarity index 100%
rename from www/images/canvas_control/expandNodes.png
rename to frontend/public/images/canvas_control/expandNodes.png
diff --git a/www/images/canvas_control/expandNodes_active.png b/frontend/public/images/canvas_control/expandNodes_active.png
similarity index 100%
rename from www/images/canvas_control/expandNodes_active.png
rename to frontend/public/images/canvas_control/expandNodes_active.png
diff --git a/www/images/canvas_control/expandNodes_hover.png b/frontend/public/images/canvas_control/expandNodes_hover.png
similarity index 100%
rename from www/images/canvas_control/expandNodes_hover.png
rename to frontend/public/images/canvas_control/expandNodes_hover.png
diff --git a/www/images/canvas_control/minusX.png b/frontend/public/images/canvas_control/minusX.png
similarity index 100%
rename from www/images/canvas_control/minusX.png
rename to frontend/public/images/canvas_control/minusX.png
diff --git a/www/images/canvas_control/minusX_active.png b/frontend/public/images/canvas_control/minusX_active.png
similarity index 100%
rename from www/images/canvas_control/minusX_active.png
rename to frontend/public/images/canvas_control/minusX_active.png
diff --git a/www/images/canvas_control/minusX_active_t.png b/frontend/public/images/canvas_control/minusX_active_t.png
similarity index 100%
rename from www/images/canvas_control/minusX_active_t.png
rename to frontend/public/images/canvas_control/minusX_active_t.png
diff --git a/www/images/canvas_control/minusX_hover.png b/frontend/public/images/canvas_control/minusX_hover.png
similarity index 100%
rename from www/images/canvas_control/minusX_hover.png
rename to frontend/public/images/canvas_control/minusX_hover.png
diff --git a/www/images/canvas_control/minusX_hover_t.png b/frontend/public/images/canvas_control/minusX_hover_t.png
similarity index 100%
rename from www/images/canvas_control/minusX_hover_t.png
rename to frontend/public/images/canvas_control/minusX_hover_t.png
diff --git a/www/images/canvas_control/minusX_t.png b/frontend/public/images/canvas_control/minusX_t.png
similarity index 100%
rename from www/images/canvas_control/minusX_t.png
rename to frontend/public/images/canvas_control/minusX_t.png
diff --git a/www/images/canvas_control/minusY.png b/frontend/public/images/canvas_control/minusY.png
similarity index 100%
rename from www/images/canvas_control/minusY.png
rename to frontend/public/images/canvas_control/minusY.png
diff --git a/www/images/canvas_control/minusY_active.png b/frontend/public/images/canvas_control/minusY_active.png
similarity index 100%
rename from www/images/canvas_control/minusY_active.png
rename to frontend/public/images/canvas_control/minusY_active.png
diff --git a/www/images/canvas_control/minusY_active_t.png b/frontend/public/images/canvas_control/minusY_active_t.png
similarity index 100%
rename from www/images/canvas_control/minusY_active_t.png
rename to frontend/public/images/canvas_control/minusY_active_t.png
diff --git a/www/images/canvas_control/minusY_hover.png b/frontend/public/images/canvas_control/minusY_hover.png
similarity index 100%
rename from www/images/canvas_control/minusY_hover.png
rename to frontend/public/images/canvas_control/minusY_hover.png
diff --git a/www/images/canvas_control/minusY_hover_t.png b/frontend/public/images/canvas_control/minusY_hover_t.png
similarity index 100%
rename from www/images/canvas_control/minusY_hover_t.png
rename to frontend/public/images/canvas_control/minusY_hover_t.png
diff --git a/www/images/canvas_control/minusY_t.png b/frontend/public/images/canvas_control/minusY_t.png
similarity index 100%
rename from www/images/canvas_control/minusY_t.png
rename to frontend/public/images/canvas_control/minusY_t.png
diff --git a/www/images/canvas_control/minusZ.png b/frontend/public/images/canvas_control/minusZ.png
similarity index 100%
rename from www/images/canvas_control/minusZ.png
rename to frontend/public/images/canvas_control/minusZ.png
diff --git a/www/images/canvas_control/minusZ_active.png b/frontend/public/images/canvas_control/minusZ_active.png
similarity index 100%
rename from www/images/canvas_control/minusZ_active.png
rename to frontend/public/images/canvas_control/minusZ_active.png
diff --git a/www/images/canvas_control/minusZ_active_t.png b/frontend/public/images/canvas_control/minusZ_active_t.png
similarity index 100%
rename from www/images/canvas_control/minusZ_active_t.png
rename to frontend/public/images/canvas_control/minusZ_active_t.png
diff --git a/www/images/canvas_control/minusZ_hover.png b/frontend/public/images/canvas_control/minusZ_hover.png
similarity index 100%
rename from www/images/canvas_control/minusZ_hover.png
rename to frontend/public/images/canvas_control/minusZ_hover.png
diff --git a/www/images/canvas_control/minusZ_hover_t.png b/frontend/public/images/canvas_control/minusZ_hover_t.png
similarity index 100%
rename from www/images/canvas_control/minusZ_hover_t.png
rename to frontend/public/images/canvas_control/minusZ_hover_t.png
diff --git a/www/images/canvas_control/minusZ_t.png b/frontend/public/images/canvas_control/minusZ_t.png
similarity index 100%
rename from www/images/canvas_control/minusZ_t.png
rename to frontend/public/images/canvas_control/minusZ_t.png
diff --git a/www/images/canvas_control/plusX.png b/frontend/public/images/canvas_control/plusX.png
similarity index 100%
rename from www/images/canvas_control/plusX.png
rename to frontend/public/images/canvas_control/plusX.png
diff --git a/www/images/canvas_control/plusX_active.png b/frontend/public/images/canvas_control/plusX_active.png
similarity index 100%
rename from www/images/canvas_control/plusX_active.png
rename to frontend/public/images/canvas_control/plusX_active.png
diff --git a/www/images/canvas_control/plusX_active_t.png b/frontend/public/images/canvas_control/plusX_active_t.png
similarity index 100%
rename from www/images/canvas_control/plusX_active_t.png
rename to frontend/public/images/canvas_control/plusX_active_t.png
diff --git a/www/images/canvas_control/plusX_hover.png b/frontend/public/images/canvas_control/plusX_hover.png
similarity index 100%
rename from www/images/canvas_control/plusX_hover.png
rename to frontend/public/images/canvas_control/plusX_hover.png
diff --git a/www/images/canvas_control/plusX_hover_t.png b/frontend/public/images/canvas_control/plusX_hover_t.png
similarity index 100%
rename from www/images/canvas_control/plusX_hover_t.png
rename to frontend/public/images/canvas_control/plusX_hover_t.png
diff --git a/www/images/canvas_control/plusX_t.png b/frontend/public/images/canvas_control/plusX_t.png
similarity index 100%
rename from www/images/canvas_control/plusX_t.png
rename to frontend/public/images/canvas_control/plusX_t.png
diff --git a/www/images/canvas_control/plusY.png b/frontend/public/images/canvas_control/plusY.png
similarity index 100%
rename from www/images/canvas_control/plusY.png
rename to frontend/public/images/canvas_control/plusY.png
diff --git a/www/images/canvas_control/plusY_active.png b/frontend/public/images/canvas_control/plusY_active.png
similarity index 100%
rename from www/images/canvas_control/plusY_active.png
rename to frontend/public/images/canvas_control/plusY_active.png
diff --git a/www/images/canvas_control/plusY_active_t.png b/frontend/public/images/canvas_control/plusY_active_t.png
similarity index 100%
rename from www/images/canvas_control/plusY_active_t.png
rename to frontend/public/images/canvas_control/plusY_active_t.png
diff --git a/www/images/canvas_control/plusY_hover.png b/frontend/public/images/canvas_control/plusY_hover.png
similarity index 100%
rename from www/images/canvas_control/plusY_hover.png
rename to frontend/public/images/canvas_control/plusY_hover.png
diff --git a/www/images/canvas_control/plusY_hover_t.png b/frontend/public/images/canvas_control/plusY_hover_t.png
similarity index 100%
rename from www/images/canvas_control/plusY_hover_t.png
rename to frontend/public/images/canvas_control/plusY_hover_t.png
diff --git a/www/images/canvas_control/plusY_t.png b/frontend/public/images/canvas_control/plusY_t.png
similarity index 100%
rename from www/images/canvas_control/plusY_t.png
rename to frontend/public/images/canvas_control/plusY_t.png
diff --git a/www/images/canvas_control/plusZ.png b/frontend/public/images/canvas_control/plusZ.png
similarity index 100%
rename from www/images/canvas_control/plusZ.png
rename to frontend/public/images/canvas_control/plusZ.png
diff --git a/www/images/canvas_control/plusZ_active.png b/frontend/public/images/canvas_control/plusZ_active.png
similarity index 100%
rename from www/images/canvas_control/plusZ_active.png
rename to frontend/public/images/canvas_control/plusZ_active.png
diff --git a/www/images/canvas_control/plusZ_active_t.png b/frontend/public/images/canvas_control/plusZ_active_t.png
similarity index 100%
rename from www/images/canvas_control/plusZ_active_t.png
rename to frontend/public/images/canvas_control/plusZ_active_t.png
diff --git a/www/images/canvas_control/plusZ_hover.png b/frontend/public/images/canvas_control/plusZ_hover.png
similarity index 100%
rename from www/images/canvas_control/plusZ_hover.png
rename to frontend/public/images/canvas_control/plusZ_hover.png
diff --git a/www/images/canvas_control/plusZ_hover_t.png b/frontend/public/images/canvas_control/plusZ_hover_t.png
similarity index 100%
rename from www/images/canvas_control/plusZ_hover_t.png
rename to frontend/public/images/canvas_control/plusZ_hover_t.png
diff --git a/www/images/canvas_control/plusZ_t.png b/frontend/public/images/canvas_control/plusZ_t.png
similarity index 100%
rename from www/images/canvas_control/plusZ_t.png
rename to frontend/public/images/canvas_control/plusZ_t.png
diff --git a/www/images/help/arena3dwebapp.png b/frontend/public/images/help/arena3dapp.png
similarity index 100%
rename from www/images/help/arena3dwebapp.png
rename to frontend/public/images/help/arena3dapp.png
diff --git a/www/images/help/arena_cytoscape_aspirin.png b/frontend/public/images/help/arena_cytoscape_aspirin.png
similarity index 100%
rename from www/images/help/arena_cytoscape_aspirin.png
rename to frontend/public/images/help/arena_cytoscape_aspirin.png
diff --git a/www/images/help/channels.png b/frontend/public/images/help/channels.png
similarity index 100%
rename from www/images/help/channels.png
rename to frontend/public/images/help/channels.png
diff --git a/www/images/help/clustering.png b/frontend/public/images/help/clustering.png
similarity index 100%
rename from www/images/help/clustering.png
rename to frontend/public/images/help/clustering.png
diff --git a/www/images/help/cytoscape_aspirin.png b/frontend/public/images/help/cytoscape_aspirin.png
similarity index 100%
rename from www/images/help/cytoscape_aspirin.png
rename to frontend/public/images/help/cytoscape_aspirin.png
diff --git a/www/images/help/directed_graph.png b/frontend/public/images/help/directed_graph.png
similarity index 100%
rename from www/images/help/directed_graph.png
rename to frontend/public/images/help/directed_graph.png
diff --git a/www/images/help/edges.png b/frontend/public/images/help/edges.png
similarity index 100%
rename from www/images/help/edges.png
rename to frontend/public/images/help/edges.png
diff --git a/www/images/help/file.png b/frontend/public/images/help/file.png
similarity index 100%
rename from www/images/help/file.png
rename to frontend/public/images/help/file.png
diff --git a/www/images/help/fps.PNG b/frontend/public/images/help/fps.PNG
similarity index 100%
rename from www/images/help/fps.PNG
rename to frontend/public/images/help/fps.PNG
diff --git a/www/images/help/fps2.PNG b/frontend/public/images/help/fps2.PNG
similarity index 100%
rename from www/images/help/fps2.PNG
rename to frontend/public/images/help/fps2.PNG
diff --git a/www/images/help/layers.png b/frontend/public/images/help/layers.png
similarity index 100%
rename from www/images/help/layers.png
rename to frontend/public/images/help/layers.png
diff --git a/www/images/help/layouts.png b/frontend/public/images/help/layouts.png
similarity index 100%
rename from www/images/help/layouts.png
rename to frontend/public/images/help/layouts.png
diff --git a/www/images/help/layouts_figure.png b/frontend/public/images/help/layouts_figure.png
similarity index 100%
rename from www/images/help/layouts_figure.png
rename to frontend/public/images/help/layouts_figure.png
diff --git a/www/images/help/mainview.png b/frontend/public/images/help/mainview.png
similarity index 100%
rename from www/images/help/mainview.png
rename to frontend/public/images/help/mainview.png
diff --git a/www/images/help/mainview2.png b/frontend/public/images/help/mainview2.png
similarity index 100%
rename from www/images/help/mainview2.png
rename to frontend/public/images/help/mainview2.png
diff --git a/www/images/help/navigation_panel.png b/frontend/public/images/help/navigation_panel.png
similarity index 100%
rename from www/images/help/navigation_panel.png
rename to frontend/public/images/help/navigation_panel.png
diff --git a/www/images/help/nodes.png b/frontend/public/images/help/nodes.png
similarity index 100%
rename from www/images/help/nodes.png
rename to frontend/public/images/help/nodes.png
diff --git a/www/images/help/predefined_layouts.png b/frontend/public/images/help/predefined_layouts.png
similarity index 100%
rename from www/images/help/predefined_layouts.png
rename to frontend/public/images/help/predefined_layouts.png
diff --git a/www/images/help/scene1_1.PNG b/frontend/public/images/help/scene1_1.PNG
similarity index 100%
rename from www/images/help/scene1_1.PNG
rename to frontend/public/images/help/scene1_1.PNG
diff --git a/www/images/help/themes.png b/frontend/public/images/help/themes.png
similarity index 100%
rename from www/images/help/themes.png
rename to frontend/public/images/help/themes.png
diff --git a/www/images/help/vr.png b/frontend/public/images/help/vr.png
similarity index 100%
rename from www/images/help/vr.png
rename to frontend/public/images/help/vr.png
diff --git a/frontend/public/images/logo_dark.webp b/frontend/public/images/logo_dark.webp
new file mode 100644
index 0000000..cb74bc8
Binary files /dev/null and b/frontend/public/images/logo_dark.webp differ
diff --git a/frontend/public/images/logo_light.webp b/frontend/public/images/logo_light.webp
new file mode 100644
index 0000000..0e24273
Binary files /dev/null and b/frontend/public/images/logo_light.webp differ
diff --git a/frontend/src/actions/.gitkeep b/frontend/src/actions/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/actions/canvas_controls.test.ts b/frontend/src/actions/canvas_controls.test.ts
new file mode 100644
index 0000000..427d898
--- /dev/null
+++ b/frontend/src/actions/canvas_controls.test.ts
@@ -0,0 +1,131 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { ctx, Layer, Node, resetContext, Scene } from '../three'
+import {
+ axisRelease,
+ clickDown,
+ clickDrag,
+ clickUp,
+ dblClick,
+ keyPressed,
+ lassoSelectNodes,
+ rotateLayersWithHeldKey,
+ sceneZoom,
+ easeZoomStep,
+} from './canvas_controls'
+
+type AnyEvent = Parameters[0]
+
+function fakeEvent(partial: Partial): AnyEvent {
+ return {
+ button: 0,
+ shiftKey: false,
+ screenX: 0,
+ screenY: 0,
+ layerX: 0,
+ layerY: 0,
+ preventDefault: () => {},
+ ...partial,
+ } as AnyEvent
+}
+
+beforeEach(() => {
+ resetContext()
+ ctx.scene = new Scene()
+ ctx.layers = [new Layer({ id: 0, name: 'L1' })]
+ ctx.layerGroups = { L1: 0 }
+ ctx.nodeGroups = { A_L1: 'L1', B_L1: 'L1' }
+ ctx.nodeLayerNames = ['A_L1', 'B_L1']
+ ctx.nodeObjects = [
+ new Node({ id: 0, name: 'A', nodeLayerName: 'A_L1', layer: 'L1' }),
+ new Node({
+ id: 1,
+ name: 'B',
+ nodeLayerName: 'B_L1',
+ layer: 'L1',
+ position_x: 50,
+ position_y: 50,
+ }),
+ ]
+})
+
+describe('keyboard', () => {
+ it('z/x/c set the pressed axis; keyup releases it', () => {
+ keyPressed({ key: 'z', preventDefault: () => {} } as KeyboardEvent)
+ expect(ctx.scene!.axisPressed).toBe('z')
+ axisRelease()
+ expect(ctx.scene!.axisPressed).toBe('')
+ })
+
+ it('arrow keys pan the scene', () => {
+ const spy = vi.spyOn(ctx.scene!, 'translatePanWithArrow')
+ keyPressed({
+ key: 'ArrowRight',
+ preventDefault: () => {},
+ } as KeyboardEvent)
+ expect(spy).toHaveBeenCalledWith(39)
+ })
+})
+
+describe('zoom', () => {
+ it('wheel scroll eases the scene scale toward the target', () => {
+ const before = ctx.scene!.getScale()
+ sceneZoom({ deltaY: -1, preventDefault: () => {} } as WheelEvent)
+ for (let i = 0; i < 60; i++) easeZoomStep()
+ expect(ctx.scene!.getScale()).toBeCloseTo(before * 1.1)
+ })
+})
+
+describe('drag', () => {
+ it('left-drag with nothing hovered pans the scene', () => {
+ const spy = vi.spyOn(ctx.scene!, 'translatePanWithMouse')
+ clickDown(fakeEvent({ button: 0 }))
+ clickDrag(fakeEvent({ screenX: 50, screenY: 0 }))
+ expect(ctx.scene!.dragging).toBe(true)
+ expect(spy).toHaveBeenCalledWith(50, 0)
+ expect(ctx.mousePreviousX).toBe(50)
+ })
+
+ it('middle-drag orbits the scene sphere', () => {
+ const spy = vi.spyOn(ctx.scene!, 'orbitSphereWithMouse')
+ clickDown(fakeEvent({ button: 1 }))
+ clickDrag(fakeEvent({ screenX: 0, screenY: 50 }))
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it('held axis key rotates selected layers instead of panning', () => {
+ ctx.layers[0].isSelected = true
+ ctx.scene!.axisPressed = 'z'
+ const before = ctx.layers[0].getRotation('z')
+ rotateLayersWithHeldKey({ screenX: 50, screenY: 0 })
+ expect(ctx.layers[0].getRotation('z')).toBeCloseTo(before + 0.05)
+ })
+})
+
+describe('lasso', () => {
+ it('shift-click anchors, drag dims inside nodes, mouse-up selects them', () => {
+ // anchor at (-5, 5): layerX = xBoundMax - 5, layerY = yBoundMax - 5
+ clickDown(
+ fakeEvent({
+ shiftKey: true,
+ layerX: ctx.xBoundMax - 5,
+ layerY: ctx.yBoundMax - 5,
+ })
+ )
+ lassoSelectNodes(5, -5) // rectangle (-5..5) x (-5..5)
+ expect(ctx.nodeObjects[0].getOpacity()).toBe(0.5) // A at origin: inside
+ expect(ctx.nodeObjects[1].getOpacity()).toBe(1) // B at (50,50): outside
+
+ clickUp(fakeEvent({}))
+ expect(ctx.nodeObjects[0].isSelected).toBe(true)
+ expect(ctx.nodeObjects[1].isSelected).toBe(false)
+ expect(ctx.scene!.dragging).toBe(false)
+ })
+})
+
+describe('dblClick', () => {
+ it('with nothing hovered unselects all nodes', () => {
+ ctx.nodeObjects[0].isSelected = true
+ dblClick(fakeEvent({ clientX: 0, clientY: 0 }))
+ expect(ctx.nodeObjects[0].isSelected).toBe(false)
+ })
+})
diff --git a/frontend/src/actions/canvas_controls.ts b/frontend/src/actions/canvas_controls.ts
new file mode 100644
index 0000000..f79adf2
--- /dev/null
+++ b/frontend/src/actions/canvas_controls.ts
@@ -0,0 +1,377 @@
+// Canvas mouse/keyboard scene controls — port of the canvas half of v2
+// www/js/event_listeners.js plus the held-key/lasso helpers it pulled from
+// node.js/layer.js. Shiny syncs and the 100ms update debounces dropped.
+// Deferred: DragControls layer dragging, nav-button/slider control table
+// (Phase 13 UI).
+
+import * as THREE from 'three'
+import { bus } from '../bus'
+import { ctx } from '../three'
+import { registerAnimateHook } from './screen'
+import { history } from '../commands/base'
+import {
+ captureTransforms,
+ TransformCommand,
+ type TransformSnapshot,
+} from '../commands/scene'
+import { redrawIntraLayerEdges, unselectAllEdges } from './edge'
+import {
+ checkHoverOverLayer,
+ getSelectedLayers,
+ performDoubleClickLayerSelection,
+} from './layer'
+import {
+ checkHoverOverNode,
+ decideNodeLabelFlags,
+ getSelectedNodes,
+ performDoubleClickNodeSelection,
+ repaintNode,
+ unselectAllNodes,
+ updateSelectedNodesStore,
+} from './node'
+import {
+ removeContextMenu,
+ replaceContextMenuOverNode,
+} from './right_click_menu'
+
+// v2 globals: lasso anchor (shift+click) and the lasso rectangle line.
+// Node indices the current lasso rectangle covers. Tracked explicitly so
+// selection doesn't rely on the opacity-0.5 sentinel, which hover also sets
+// (a node hovered at mouse-up would otherwise be selected too).
+const lassoHits = new Set()
+let shiftX: number | null = null
+let shiftY: number | null = null
+let lasso: THREE.Line | null = null
+
+type CanvasMouseEvent = MouseEvent & { layerX: number; layerY: number }
+
+// Mouse drags (pan, orbit, held-key node/layer transforms) register one undo
+// entry per drag: snapshot on press, command pushed on release if a transform
+// branch actually ran (lasso/selection drags don't count).
+let dragBefore: TransformSnapshot | null = null
+let transformDragged = false
+
+// Smooth wheel zoom: the wheel sets a target scale (same 0.2–2 bounds as
+// Scene.zoom) and easeZoomStep lerps toward it each frame. Users with
+// prefers-reduced-motion get the old instant zoom.
+let zoomTarget: number | null = null
+// World point (pan-space) under the cursor to keep fixed while zooming.
+let zoomAnchorX = 0
+let zoomAnchorY = 0
+const reducedMotion = (): boolean =>
+ typeof window !== 'undefined' &&
+ typeof window.matchMedia === 'function' &&
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches
+
+// Shift pan so the anchor stays under the cursor as scale goes from -> to.
+// Ortho camera + full-window frustum means world units == centered pixels,
+// and the pan node scales about its own origin, so anchoring is pure math.
+function anchorZoom(from: number, to: number): void {
+ const ratio = to / from
+ for (const axis of ['x', 'y'] as const) {
+ const anchor = axis === 'x' ? zoomAnchorX : zoomAnchorY
+ const pos = ctx.scene!.getPosition(axis)
+ ctx.scene!.setPosition(axis, anchor + (pos - anchor) * ratio)
+ }
+}
+
+// on mouse wheel scroll
+export function sceneZoom(event: WheelEvent): void {
+ if (!ctx.scene?.exists()) return
+ event.preventDefault() // keep the page from scrolling (v2 initializeCanvasDiv)
+ zoomAnchorX = (event.offsetX ?? ctx.xBoundMax) - ctx.xBoundMax
+ zoomAnchorY = ctx.yBoundMax - (event.offsetY ?? ctx.yBoundMax)
+ if (reducedMotion()) {
+ const from = ctx.scene.getScale()
+ ctx.scene.zoom(event.deltaY)
+ anchorZoom(from, ctx.scene.getScale())
+ return
+ }
+ const current = zoomTarget ?? ctx.scene.getScale()
+ const factor = event.deltaY < 0 ? 1.1 : 0.9
+ zoomTarget = Math.min(2, Math.max(0.2, current * factor))
+}
+
+export function easeZoomStep(): void {
+ if (zoomTarget === null || !ctx.scene?.exists()) return
+ const scale = ctx.scene.getScale()
+ let next = scale + (zoomTarget - scale) * 0.25
+ const done = Math.abs(next - zoomTarget) < 0.001
+ if (done) next = zoomTarget
+ anchorZoom(scale, next)
+ ctx.scene.setScale(next)
+ if (done) zoomTarget = null
+}
+
+export function resetZoomTarget(): void {
+ zoomTarget = null
+}
+
+const ARROW_CODES: Record = {
+ ArrowLeft: 37,
+ ArrowUp: 38,
+ ArrowRight: 39,
+ ArrowDown: 40,
+}
+
+// on z/x/c axis select (held-key layer/node transforms) or arrow-key pan
+export function keyPressed(event: KeyboardEvent): void {
+ if (!ctx.scene?.exists()) return
+ if (event.ctrlKey || event.metaKey) return // Ctrl+Z etc. aren't axis holds
+ const key = event.key.toLowerCase()
+ if (key === 'z' || key === 'x' || key === 'c') ctx.scene.axisPressed = key
+ else if (event.key in ARROW_CODES) {
+ event.preventDefault()
+ ctx.scene.translatePanWithArrow(ARROW_CODES[event.key])
+ }
+}
+
+export function axisRelease(): void {
+ if (ctx.scene?.exists()) ctx.scene.axisPressed = ''
+}
+
+// mouse: 0 left, 1 middle, 2 right click
+export function clickDown(event: CanvasMouseEvent): void {
+ if (!ctx.scene?.exists()) return
+ if (event.button === 0 || event.button === 1) {
+ dragBefore = captureTransforms()
+ transformDragged = false
+ }
+ if (event.button === 0) {
+ ctx.scene.leftClickPressed = true
+ ctx.scene.middleClickPressed = false
+ if (event.shiftKey && shiftX === null) {
+ shiftX = event.layerX - ctx.xBoundMax
+ shiftY = ctx.yBoundMax - event.layerY // used in drag
+ }
+ } else if (event.button === 1) {
+ event.preventDefault() // no middle-click autoscroll
+ ctx.scene.middleClickPressed = true
+ ctx.scene.leftClickPressed = false
+ } else {
+ ctx.scene.middleClickPressed = false
+ ctx.scene.leftClickPressed = false
+ }
+}
+
+// while mouse button held, drag event
+export function clickDrag(event: CanvasMouseEvent): void {
+ if (!ctx.scene?.exists()) return
+ const distance = Math.hypot(
+ ctx.mousePreviousX - event.screenX,
+ ctx.mousePreviousY - event.screenY
+ )
+
+ // Held-key node/layer transforms apply a fixed step per processed event, so
+ // they keep the v2 10px granularity; pan/orbit/lasso use deltas and get a
+ // finer 2px threshold for smoother motion (v2's 10px gate made orbit jump
+ // in ~10-degree increments).
+ const heldKeyTransform =
+ ctx.scene.leftClickPressed &&
+ !event.shiftKey &&
+ ctx.scene.axisPressed !== ''
+ const threshold = heldKeyTransform ? 10 : 2
+
+ if (distance > threshold) {
+ const x = event.screenX
+ const y = event.screenY
+
+ if (ctx.scene.leftClickPressed) {
+ ctx.scene.dragging = true
+ if (event.shiftKey) {
+ ctx.lastHoveredLayerIndex = null // to be able to lasso inside a layer
+ lassoSelectNodes(
+ event.layerX - ctx.xBoundMax,
+ ctx.yBoundMax - event.layerY
+ )
+ } else if (
+ ctx.scene.axisPressed !== '' &&
+ getSelectedNodes().length > 0
+ ) {
+ translateNodesWithHeldKey(event)
+ transformDragged = true
+ } else if (ctx.scene.axisPressed !== '') {
+ rotateLayersWithHeldKey(event)
+ transformDragged = true
+ } else if (
+ ctx.lastHoveredLayerIndex === null &&
+ ctx.lastHoveredNodeIndex === null
+ ) {
+ ctx.scene.translatePanWithMouse(x, y)
+ transformDragged = true
+ }
+ } else if (ctx.scene.middleClickPressed) {
+ ctx.scene.dragging = true
+ event.preventDefault()
+ ctx.scene.orbitSphereWithMouse(x, y)
+ transformDragged = true
+ }
+
+ ctx.mousePreviousX = x
+ ctx.mousePreviousY = y
+ }
+
+ // Coalesce hover raycasts to one per rendered frame — raycasting every
+ // node sphere per mousemove gets expensive on large networks.
+ if (!ctx.scene.leftClickPressed && !ctx.scene.middleClickPressed)
+ pendingHover = { clientX: event.clientX, clientY: event.clientY }
+}
+
+let pendingHover: { clientX: number; clientY: number } | null = null
+
+export function processPendingHover(): void {
+ if (!pendingHover || !ctx.scene?.exists()) return
+ const event = pendingHover
+ pendingHover = null
+ if (!checkHoverOverNode(event)) checkHoverOverLayer(event)
+}
+
+export function clickUp(event: MouseEvent): void {
+ if (!ctx.scene?.exists()) return
+ ctx.scene.dragging = false
+ if (dragBefore && transformDragged) {
+ history.execute(
+ new TransformCommand('Drag scene', dragBefore, captureTransforms())
+ )
+ }
+ dragBefore = null
+ transformDragged = false
+ if (event.button === 0) {
+ ctx.scene.leftClickPressed = false
+ // Only a real left-click on the canvas dismisses the menu. mouseleave also
+ // routes here (to release drag state), but firing removeContextMenu on it
+ // killed the just-opened menu the instant the mouse moved off the canvas
+ // toward it — so the options were never reachable.
+ if (event.type === 'mouseup') removeContextMenu() // v2 removed the right-click options list here
+ if (lasso) {
+ // select exactly the nodes the lasso rectangle covered
+ ctx.nodeObjects.forEach((node, i) => {
+ if (lassoHits.has(i)) {
+ node.setOpacity(1)
+ node.isSelected = true
+ repaintNode(i)
+ }
+ })
+ decideNodeLabelFlags()
+ updateSelectedNodesStore()
+ ctx.scene.remove(lasso)
+ lasso.geometry.dispose()
+ lasso = null
+ lassoHits.clear()
+ }
+ shiftX = null
+ shiftY = null
+ } else if (event.button === 1) ctx.scene.middleClickPressed = false
+}
+
+// double click: select node -> select layer -> unselect everything
+export function dblClick(event: CanvasMouseEvent): void {
+ if (!ctx.scene?.exists()) return
+ if (performDoubleClickNodeSelection(event)) return
+ if (ctx.lastHoveredLayerIndex !== null) performDoubleClickLayerSelection()
+ else {
+ unselectAllNodes()
+ unselectAllEdges()
+ redrawIntraLayerEdges()
+ }
+}
+
+// v2 node.js translateNodesWithHeldKey — z/c + drag moves selected nodes.
+export function translateNodesWithHeldKey(event: {
+ screenX: number
+ screenY: number
+}): void {
+ const step =
+ event.screenX - event.screenY >= ctx.mousePreviousX - ctx.mousePreviousY
+ ? 20
+ : -20
+
+ for (const i of getSelectedNodes()) {
+ if (ctx.scene!.axisPressed === 'z') ctx.nodeObjects[i].translateZ(step)
+ else if (ctx.scene!.axisPressed === 'c') ctx.nodeObjects[i].translateY(step)
+ }
+ redrawIntraLayerEdges()
+ ctx.renderInterLayerEdgesFlag = true
+ ctx.renderNodeLabelsFlag = true // labels follow the moved nodes
+}
+
+// v2 layer.js rotateLayersWithHeldKey — z/x/c + drag rotates selected layers.
+export function rotateLayersWithHeldKey(event: {
+ screenX: number
+ screenY: number
+}): void {
+ const rads =
+ event.screenX - event.screenY >= ctx.mousePreviousX - ctx.mousePreviousY
+ ? 0.05
+ : -0.05
+
+ for (const i of getSelectedLayers()) {
+ if (ctx.scene!.axisPressed === 'z') ctx.layers[i].rotateZ(rads)
+ else if (ctx.scene!.axisPressed === 'x') ctx.layers[i].rotateX(rads)
+ else if (ctx.scene!.axisPressed === 'c') ctx.layers[i].rotateY(rads)
+ }
+ ctx.renderInterLayerEdgesFlag = true
+}
+
+// v2 node.js lassoSelectNodes — shift + left-drag rectangle select.
+export function lassoSelectNodes(x: number, y: number): void {
+ if (shiftX === null || shiftY === null) return
+ const minX = Math.min(shiftX, x)
+ const maxX = Math.max(shiftX, x)
+ const minY = Math.min(shiftY, y)
+ const maxY = Math.max(shiftY, y)
+
+ createLassoGeometry(x, y)
+
+ lassoHits.clear()
+ ctx.nodeObjects.forEach((node, i) => {
+ const nodeX = node.getWorldPosition('x')
+ const nodeY = node.getWorldPosition('y')
+ const inside = nodeX < maxX && nodeX > minX && nodeY < maxY && nodeY > minY
+ node.setOpacity(inside ? 0.5 : 1) // visual feedback only
+ if (inside) lassoHits.add(i)
+ })
+}
+
+const lassoMaterial = new THREE.LineBasicMaterial({ color: '#eef1b6' })
+
+function createLassoGeometry(x: number, y: number): void {
+ if (lasso) {
+ ctx.scene!.remove(lasso)
+ lasso.geometry.dispose() // rebuilt every pointer move — don't accumulate
+ }
+ const points = [
+ new THREE.Vector3(shiftX!, shiftY!, 0),
+ new THREE.Vector3(x, shiftY!, 0),
+ new THREE.Vector3(x, y, 0),
+ new THREE.Vector3(shiftX!, y, 0),
+ new THREE.Vector3(shiftX!, shiftY!, 0),
+ ]
+ lasso = new THREE.Line(
+ new THREE.BufferGeometry().setFromPoints(points),
+ lassoMaterial
+ )
+ ctx.scene!.add(lasso)
+}
+
+// Called once from main.ts, after the canvas is mounted.
+export function registerCanvasControls(): void {
+ const canvas = ctx.renderer?.domElement
+ if (!canvas) return
+ registerAnimateHook(easeZoomStep)
+ registerAnimateHook(processPendingHover)
+ bus.on('network:loaded', resetZoomTarget) // new scene -> stale zoom target
+ canvas.tabIndex = 1 // focusable, so it receives keydown events (v2)
+ canvas.addEventListener('wheel', sceneZoom)
+ canvas.addEventListener('keydown', keyPressed)
+ canvas.addEventListener('keyup', axisRelease)
+ canvas.addEventListener('mousedown', clickDown as EventListener)
+ canvas.addEventListener('mousemove', clickDrag as EventListener)
+ canvas.addEventListener('mouseup', clickUp)
+ canvas.addEventListener('dblclick', dblClick as EventListener)
+ canvas.addEventListener('mouseleave', clickUp) // release buttons on exit (v2)
+ canvas.addEventListener(
+ 'contextmenu',
+ replaceContextMenuOverNode as unknown as EventListener
+ )
+}
diff --git a/frontend/src/actions/drag_controls.test.ts b/frontend/src/actions/drag_controls.test.ts
new file mode 100644
index 0000000..f58d1e9
--- /dev/null
+++ b/frontend/src/actions/drag_controls.test.ts
@@ -0,0 +1,102 @@
+import * as THREE from 'three'
+import { beforeEach, describe, expect, it } from 'vitest'
+import { bus } from '../bus'
+import { ctx, Layer, resetContext, Scene } from '../three'
+import { setCamera } from './screen'
+import {
+ attachLayerDragControls,
+ onPointerCancel,
+ onPointerDown,
+ onPointerMove,
+ type DragSurface,
+} from './drag_controls'
+
+function fakeSurface(): DragSurface {
+ return {
+ getBoundingClientRect: () => ({ left: 0, top: 0, width: 800, height: 800 }),
+ addEventListener: () => {},
+ style: { cursor: '', touchAction: '' },
+ }
+}
+
+let surface: DragSurface
+
+beforeEach(() => {
+ resetContext()
+ ctx.scene = new Scene()
+ setCamera() // orthographic, ±ctx.xBoundMax (=400 headless), at z=100
+ ctx.layers = [new Layer({ id: 0, name: 'L1' })]
+ // Layer planes face +x (geometry baked rotateY(90°)); turn this one toward
+ // the camera so a screen-center ray hits it.
+ ctx.layers[0].plane.rotation.y = THREE.MathUtils.degToRad(-90)
+ ctx.scene.addLayer(ctx.layers[0].plane)
+ ctx.scene.THREE_Object.updateMatrixWorld(true)
+
+ surface = fakeSurface()
+ attachLayerDragControls(surface)
+ onPointerCancel() // clear selection state left over from previous tests
+})
+
+describe('layer drag', () => {
+ it('pointer-down over a plane selects it and sets the move cursor', () => {
+ ctx.lastHoveredLayerIndex = 0 // hover pass resolved the plane
+ onPointerDown({ clientX: 400, clientY: 400 }) // screen center -> origin
+ expect(surface.style.cursor).toBe('move')
+ })
+
+ it('drags the hovered plane while the left button is held', () => {
+ ctx.lastHoveredLayerIndex = 0
+ onPointerDown({ clientX: 400, clientY: 400 })
+ ctx.scene!.leftClickPressed = true
+
+ onPointerMove({ clientX: 500, clientY: 400, pointerType: 'mouse' })
+ // NDC +0.25 on an 800px-wide ortho frustum (±400) -> world x = 100
+ expect(ctx.layers[0].plane.position.x).toBeCloseTo(100)
+ expect(ctx.renderLayerLabelsFlag).toBe(true)
+ expect(ctx.renderNodeLabelsFlag).toBe(true)
+ })
+
+ it('drags a plane sitting behind the ortho ray origin (zoomed-in regression)', () => {
+ // Zooming in pushes a tilted layer's world z past the ortho ray origin
+ // (~camera z=100). THREE.Ray.intersectPlane rejects t<0, which used to
+ // silently freeze the drag even though hover still highlighted the plane.
+ ctx.layers[0].plane.position.z = 300 // behind the origin
+ ctx.scene!.THREE_Object.updateMatrixWorld(true)
+ ctx.lastHoveredLayerIndex = 0
+ onPointerDown({ clientX: 400, clientY: 400 })
+ ctx.scene!.leftClickPressed = true
+ onPointerMove({ clientX: 500, clientY: 400, pointerType: 'mouse' })
+ expect(ctx.layers[0].plane.position.x).toBeCloseTo(100)
+ })
+
+ it('does not engage while a node is hovered (v2 gate)', () => {
+ ctx.lastHoveredLayerIndex = 0
+ ctx.lastHoveredNodeIndex = 3
+ onPointerDown({ clientX: 400, clientY: 400 })
+ expect(surface.style.cursor).not.toBe('move')
+ ctx.scene!.leftClickPressed = true
+ onPointerMove({ clientX: 500, clientY: 400, pointerType: 'mouse' })
+ expect(ctx.layers[0].plane.position.x).toBe(0)
+ })
+
+ it('does not drag when no layer is hovered (v2 gate)', () => {
+ ctx.lastHoveredLayerIndex = 0
+ onPointerDown({ clientX: 400, clientY: 400 })
+ ctx.lastHoveredLayerIndex = null
+ ctx.scene!.leftClickPressed = true
+ onPointerMove({ clientX: 500, clientY: 400, pointerType: 'mouse' })
+ expect(ctx.layers[0].plane.position.x).toBe(0)
+ })
+
+ it('release emits layer:moved and raises the inter-edge render flag', () => {
+ let moved = -1
+ const off = bus.on('layer:moved', ({ layerIndex }) => (moved = layerIndex))
+ ctx.lastHoveredLayerIndex = 0
+ onPointerDown({ clientX: 400, clientY: 400 })
+ onPointerCancel()
+ expect(moved).toBe(0)
+ expect(ctx.renderInterLayerEdgesFlag).toBe(true)
+ expect(surface.style.cursor).toBe('auto')
+ off()
+ })
+})
diff --git a/frontend/src/actions/drag_controls.ts b/frontend/src/actions/drag_controls.ts
new file mode 100644
index 0000000..d3106fe
--- /dev/null
+++ b/frontend/src/actions/drag_controls.ts
@@ -0,0 +1,183 @@
+// Layer-plane dragging — port of v2 www/js/three/drag_controls.js (itself a
+// tweaked copy of three.js DragControls; its matrix4.js shim is replaced by
+// npm three's Matrix4). v2 tweaks kept: dragging only engages while the left
+// button is down over a hovered layer, and label render flags are raised
+// while dragging. Added: inter-layer edge redraw + layer:moved on release
+// (v2 did this through its Shiny syncs).
+
+import * as THREE from 'three'
+import { bus } from '../bus'
+import { ctx } from '../three'
+import { findIndexByUuid } from '../utils'
+import { history } from '../commands/base'
+import {
+ captureTransforms,
+ TransformCommand,
+ type TransformSnapshot,
+} from '../commands/scene'
+
+const _plane = new THREE.Plane()
+const _pointer = new THREE.Vector2()
+const _offset = new THREE.Vector3()
+const _intersection = new THREE.Vector3()
+const _worldPosition = new THREE.Vector3()
+const _inverseMatrix = new THREE.Matrix4()
+const _raycaster = new THREE.Raycaster()
+
+let selected: THREE.Object3D | null = null
+let hovered: THREE.Object3D | null = null
+let surface: DragSurface | null = null
+// One undo entry per layer drag: snapshot on grab, pushed on release.
+let dragBefore: TransformSnapshot | null = null
+let dragMoved = false
+
+// Structural subset of HTMLCanvasElement, so tests can drive a fake surface.
+export interface DragSurface {
+ getBoundingClientRect(): {
+ left: number
+ top: number
+ width: number
+ height: number
+ }
+ addEventListener(type: string, listener: EventListener): void
+ style: { cursor: string; touchAction: string }
+}
+
+type PointerLikeEvent = {
+ clientX: number
+ clientY: number
+ pointerType?: string
+}
+
+function updatePointer(event: PointerLikeEvent): void {
+ const rect = surface!.getBoundingClientRect()
+ _pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
+ _pointer.y = (-(event.clientY - rect.top) / rect.height) * 2 + 1
+}
+
+function layerPlanes(): THREE.Object3D[] {
+ return ctx.layers.map(({ plane }) => plane)
+}
+
+// Orthographic `setFromCamera` puts the ray origin at the mid-depth plane
+// (NDC z=0, world z ~= camera z). A layer tilted + zoom-scaled far enough sits
+// BEHIND that origin, and THREE.Ray.intersectPlane rejects t<0, so layer drag
+// silently stopped working once zoomed in. The screen-facing drag plane is
+// crossed by the cursor ray exactly once — take that point regardless of sign.
+function rayPlanePoint(target: THREE.Vector3): THREE.Vector3 | null {
+ const denom = _plane.normal.dot(_raycaster.ray.direction)
+ if (denom === 0) return null
+ const t =
+ -(_raycaster.ray.origin.dot(_plane.normal) + _plane.constant) / denom
+ return _raycaster.ray.at(t, target)
+}
+
+export function onPointerDown(event: PointerLikeEvent): void {
+ if (!surface || !ctx.camera) return
+ // Don't re-raycast here: the pointer-down ray kept landing on other
+ // objects (coord/edge Lines have a fat world-unit raycast threshold, and
+ // an overlapping layer's children can sit in front), which either aborted
+ // the drag or grabbed the wrong plane. The hover pass already resolved
+ // which plane the user sees under the cursor — trust it.
+ if (ctx.lastHoveredNodeIndex !== null) return // grabbing a node, not a layer
+ const idx = ctx.lastHoveredLayerIndex
+ if (idx === null || !ctx.layers[idx]) return
+ updatePointer(event)
+ _raycaster.setFromCamera(_pointer, ctx.camera)
+
+ selected = ctx.layers[idx].plane
+ dragBefore = captureTransforms()
+ dragMoved = false
+ _plane.setFromNormalAndCoplanarPoint(
+ ctx.camera.getWorldDirection(_plane.normal),
+ _worldPosition.setFromMatrixPosition(selected.matrixWorld)
+ )
+ if (rayPlanePoint(_intersection)) {
+ _inverseMatrix.copy(selected.parent!.matrixWorld).invert()
+ _offset
+ .copy(_intersection)
+ .sub(_worldPosition.setFromMatrixPosition(selected.matrixWorld))
+ }
+ surface.style.cursor = 'move'
+}
+
+export function onPointerMove(event: PointerLikeEvent): void {
+ if (!surface || !ctx.camera) return
+ updatePointer(event)
+ _raycaster.setFromCamera(_pointer, ctx.camera)
+
+ // v2 gate: only drag while the left button is held over a hovered layer
+ if (
+ selected &&
+ ctx.lastHoveredLayerIndex !== null &&
+ ctx.scene?.leftClickPressed &&
+ findIndexByUuid(layerPlanes(), selected.uuid) !== -1
+ ) {
+ if (rayPlanePoint(_intersection)) {
+ ctx.renderLayerLabelsFlag = true
+ ctx.renderNodeLabelsFlag = true
+ ctx.renderInterLayerEdgesFlag = true // edges follow the dragged layer
+ selected.position.copy(
+ _intersection.sub(_offset).applyMatrix4(_inverseMatrix)
+ )
+ dragMoved = true
+ }
+ return
+ }
+
+ // hover cursor support
+ if (event.pointerType === 'mouse' || event.pointerType === 'pen') {
+ const intersections = _raycaster.intersectObjects(layerPlanes(), true)
+ if (intersections.length > 0) {
+ const object = intersections[0].object
+ _plane.setFromNormalAndCoplanarPoint(
+ ctx.camera.getWorldDirection(_plane.normal),
+ _worldPosition.setFromMatrixPosition(object.matrixWorld)
+ )
+ if (hovered !== object) {
+ surface.style.cursor = 'pointer'
+ hovered = object
+ }
+ } else if (hovered !== null) {
+ surface.style.cursor = 'auto'
+ hovered = null
+ }
+ }
+}
+
+export function onPointerCancel(): void {
+ if (!surface) return
+ if (selected) {
+ const layerIndex = findIndexByUuid(layerPlanes(), selected.uuid)
+ selected = null
+ // redraw what the drag displaced (v2 routed this through Shiny syncs)
+ ctx.renderInterLayerEdgesFlag = true
+ if (layerIndex !== -1) bus.emit('layer:moved', { layerIndex })
+ if (dragBefore && dragMoved)
+ history.execute(
+ new TransformCommand('Drag layer', dragBefore, captureTransforms())
+ )
+ dragBefore = null
+ dragMoved = false
+ }
+ surface.style.cursor = hovered ? 'pointer' : 'auto'
+}
+
+export function attachLayerDragControls(el: DragSurface): void {
+ surface = el
+ selected = null
+ hovered = null
+ el.style.touchAction = 'none' // disable touch scroll
+ el.addEventListener('pointermove', onPointerMove as unknown as EventListener)
+ el.addEventListener('pointerdown', onPointerDown as unknown as EventListener)
+ el.addEventListener('pointerup', onPointerCancel)
+ el.addEventListener('pointerleave', onPointerCancel)
+}
+
+// Called once from main.ts, after the canvas is mounted. v2 recreated
+// DragControls per network load; the planes list is read live from ctx, so
+// one registration survives reloads.
+export function registerLayerDragControls(): void {
+ const el = ctx.renderer?.domElement
+ if (el) attachLayerDragControls(el)
+}
diff --git a/frontend/src/actions/edge.test.ts b/frontend/src/actions/edge.test.ts
new file mode 100644
index 0000000..5b2a48d
--- /dev/null
+++ b/frontend/src/actions/edge.test.ts
@@ -0,0 +1,159 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import { ctx, Edge, Layer, Node, resetContext, Scene } from '../three'
+import {
+ applyEdgeAttributes,
+ renderInterLayerEdges,
+ setChannelColor,
+ setChannelVisibility,
+ setEdgeWidthByWeight,
+ setInterLayerEdgeOpacity,
+ toggleInterLayerEdgesRendering,
+} from './edge'
+
+// Two layers, one node each, one inter-layer edge.
+function seed(): void {
+ ctx.scene = new Scene()
+ ctx.layers = [
+ new Layer({ id: 0, name: 'L1' }),
+ new Layer({ id: 1, name: 'L2' }),
+ ]
+ ctx.layerGroups = { L1: 0, L2: 1 }
+ ctx.nodeGroups = { A_L1: 'L1', B_L2: 'L2' }
+ ctx.nodeLayerNames = ['A_L1', 'B_L2']
+ ctx.nodeObjects = [
+ new Node({ id: 0, name: 'A', nodeLayerName: 'A_L1', layer: 'L1' }),
+ new Node({ id: 1, name: 'B', nodeLayerName: 'B_L2', layer: 'L2' }),
+ ]
+ ctx.layers[0].addNode(ctx.nodeObjects[0].sphere)
+ ctx.layers[1].addNode(ctx.nodeObjects[1].sphere)
+ ctx.edgeObjects = [
+ new Edge({
+ id: 0,
+ source: 'A_L1',
+ target: 'B_L2',
+ weights: [1],
+ interLayer: true,
+ }),
+ ]
+}
+
+beforeEach(() => {
+ resetContext()
+ seed()
+})
+
+describe('renderInterLayerEdges', () => {
+ it('removes inter-layer edges while paused, redraws after unpausing', () => {
+ const obj = ctx.edgeObjects[0].THREE_Object
+ expect(ctx.scene!.exists()).toBe(true)
+
+ toggleInterLayerEdgesRendering() // pause
+ renderInterLayerEdges()
+ expect(ctx.interEdgesRemoved).toBe(true)
+ expect(ctx.scene!.THREE_Object.children).not.toContain(obj)
+
+ toggleInterLayerEdgesRendering() // unpause raises the render flag
+ expect(ctx.renderInterLayerEdgesFlag).toBe(true)
+ renderInterLayerEdges()
+ expect(ctx.interEdgesRemoved).toBe(false)
+ expect(ctx.scene!.THREE_Object.children).toContain(
+ ctx.edgeObjects[0].THREE_Object
+ )
+ })
+
+ it('redraws for one extra tick, then clears the flag (locked-flag pair)', () => {
+ ctx.renderInterLayerEdgesFlag = true
+ renderInterLayerEdges() // tick 1: waitEdgeRenderFlag drops
+ expect(ctx.renderInterLayerEdgesFlag).toBe(true)
+ renderInterLayerEdges() // tick 2: flag clears, re-arms
+ expect(ctx.renderInterLayerEdgesFlag).toBe(false)
+ expect(ctx.waitEdgeRenderFlag).toBe(true)
+ })
+
+ it('removes edges when opacity is 0 and width-by-weight is off', () => {
+ setEdgeWidthByWeight(false)
+ setInterLayerEdgeOpacity(0)
+ renderInterLayerEdges()
+ expect(ctx.interEdgesRemoved).toBe(true)
+ })
+})
+
+describe('channels', () => {
+ it('setChannelColor updates the palette and raises the render flag', () => {
+ ctx.renderInterLayerEdgesFlag = false
+ setChannelColor('ch1', '#abcdef')
+ expect(ctx.channelColors['ch1']).toBe('#abcdef')
+ expect(ctx.renderInterLayerEdgesFlag).toBe(true)
+ })
+
+ it('setChannelVisibility toggles the tagged channel line', () => {
+ ctx.channelColors = { ch1: '#ff0000' }
+ ctx.edgeObjects = [
+ new Edge({
+ id: 0,
+ source: 'A_L1',
+ target: 'B_L2',
+ weights: [1],
+ channels: ['ch1'],
+ colors: ['#ff0000'],
+ interLayer: true,
+ }),
+ ]
+ setChannelVisibility('ch1', false)
+ const line = ctx.edgeObjects[0].THREE_Object.children.find(
+ (c) => c.userData.tag === 'ch1'
+ )
+ expect(line?.visible).toBe(false)
+ expect(ctx.channelVisibility['ch1']).toBe(false)
+ })
+
+ it('toggles the right channel when a sibling channel has no arrow', () => {
+ // ch1 has weight 0 -> opacity 0 -> no arrow drawn; ch2 keeps its arrow.
+ // The old positional children[j+1] lookup corrupted ch1's line here.
+ ctx.isDirectionEnabled = true
+ ctx.channelColors = { ch1: '#ff0000', ch2: '#00ff00' }
+ ctx.channelVisibility = { ch1: true, ch2: true }
+ ctx.edgeObjects = [
+ new Edge({
+ id: 0,
+ source: 'A_L1',
+ target: 'B_L2',
+ weights: [0, 1],
+ channels: ['ch1', 'ch2'],
+ colors: ['#ff0000', '#00ff00'],
+ interLayer: true,
+ }),
+ ]
+ setChannelVisibility('ch2', false)
+ const children = ctx.edgeObjects[0].THREE_Object.children
+ for (const c of children) {
+ if (c.userData.tag === 'ch2') expect(c.visible).toBe(false)
+ if (c.userData.tag === 'ch1') expect(c.visible).toBe(true)
+ }
+ ctx.isDirectionEnabled = false
+ })
+})
+
+describe('applyEdgeAttributes', () => {
+ it('recolors matching edges and forces file-color priority', () => {
+ ctx.edgeObjects[0].channels = ['ppi']
+ ctx.edgeObjects[0].colors = ['#111111']
+ ctx.edgeObjects[0].importedColors = ['#111111']
+ applyEdgeAttributes([
+ { edge_pair: 'A_L1---B_L2', color: '#ff0000', channel: 'ppi' },
+ { edge_pair: 'X_L1---Y_L2', color: '#00ff00', channel: null }, // no such edge
+ ])
+ expect(ctx.edgeObjects[0].importedColors[0]).toBe('#ff0000')
+ expect(ctx.edgeObjects[0].colors[0]).toBe('#ff0000')
+ expect(ctx.edgeFileColorPriority).toBe(true)
+ })
+
+ it('skips channels the edge does not carry', () => {
+ ctx.edgeObjects[0].channels = ['ppi']
+ ctx.edgeObjects[0].importedColors = ['#111111']
+ applyEdgeAttributes([
+ { edge_pair: 'A_L1---B_L2', color: '#ff0000', channel: 'coexpression' },
+ ])
+ expect(ctx.edgeObjects[0].importedColors[0]).toBe('#111111')
+ })
+})
diff --git a/frontend/src/actions/edge.ts b/frontend/src/actions/edge.ts
new file mode 100644
index 0000000..6bce486
--- /dev/null
+++ b/frontend/src/actions/edge.ts
@@ -0,0 +1,172 @@
+// Port of v2 www/js/object_actions/edge.js — inter/intra-layer edge render
+// machinery, channel color/visibility, and edge render settings.
+// createEdgeObjects/assignChannelColorsFromPalette live in network.ts's
+// build. Deferred with their phases: channel layout/edit lists + color-picker
+// DOM (Phase 13 UI), edge attributes upload (POST /api/attributes),
+// pause-button label (canvas_controls.ts).
+
+import { ctx } from '../three'
+
+// v2 assignChannelColorsFromPalette — reassigns every known channel's color
+// in insertion order (channel order from the network build).
+export function assignChannelColorsFromPalette(palette: string[]): void {
+ Object.keys(ctx.channelColors).forEach((channel, i) => {
+ ctx.channelColors[channel] = palette[i % palette.length]
+ })
+}
+
+export function redrawIntraLayerEdges(): void {
+ for (const e of ctx.edgeObjects) if (!e.interLayer) e.redrawEdge()
+}
+
+export function redrawInterLayerEdges(): void {
+ for (const e of ctx.edgeObjects) if (e.interLayer) e.redrawEdge()
+
+ ctx.interEdgesRemoved = false
+ // v2's locked-flag pair: redraw runs for one extra animate tick after the
+ // flag was raised, then re-arms
+ if (ctx.waitEdgeRenderFlag) {
+ ctx.waitEdgeRenderFlag = false
+ } else {
+ ctx.renderInterLayerEdgesFlag = false
+ ctx.waitEdgeRenderFlag = true
+ }
+}
+
+// Called every animate() tick.
+export function renderInterLayerEdges(): void {
+ if (existsConditionToRemoveInterEdges()) {
+ if (!ctx.interEdgesRemoved) removeInterLayerEdges()
+ } else if (ctx.renderInterLayerEdgesFlag) {
+ redrawInterLayerEdges()
+ }
+}
+
+// v2 also removed the edges while ctx.scene.dragging; that made them vanish
+// during every pan/orbit (where they move with the scene and need no redraw)
+// and lag behind layer drags. Layer/node drags now raise
+// renderInterLayerEdgesFlag per move instead, so the edges follow live; the
+// render-pause button remains the escape hatch for huge networks.
+function existsConditionToRemoveInterEdges(): boolean {
+ return (
+ ctx.interLayerEdgesRenderPauseFlag ||
+ (!ctx.edgeWidthByWeight && ctx.interLayerEdgeOpacity === 0)
+ )
+}
+
+function removeInterLayerEdges(): void {
+ for (const e of ctx.edgeObjects)
+ if (e.interLayer) ctx.scene?.remove(e.THREE_Object)
+ ctx.interEdgesRemoved = true
+}
+
+// @return whether rendering is now paused (v2 toggleInterLayerEdgesRendering;
+// the pause-button label is Phase 13's job)
+export function toggleInterLayerEdgesRendering(): boolean {
+ ctx.interLayerEdgesRenderPauseFlag = !ctx.interLayerEdgesRenderPauseFlag
+ if (!ctx.interLayerEdgesRenderPauseFlag) ctx.renderInterLayerEdgesFlag = true
+ return ctx.interLayerEdgesRenderPauseFlag
+}
+
+// v2 changeChannelColor minus the DOM picker element.
+export function setChannelColor(channel: string, color: string): void {
+ ctx.channelColors[channel] = color
+ ctx.renderInterLayerEdgesFlag = true
+ redrawIntraLayerEdges()
+}
+
+// v2 toggleChannelVisibility minus the DOM checkbox (checked = hidden there;
+// this takes `visible` directly). Both the curve line and its arrow carry
+// userData.tag = channel, so match by tag rather than assuming a fixed
+// line/arrow interleave — arrows are skipped for zero-opacity channels, which
+// broke the old positional children[j+1] lookup.
+export function setChannelVisibility(channel: string, visible: boolean): void {
+ for (const edge of ctx.edgeObjects) {
+ for (const child of edge.THREE_Object.children) {
+ if (child.userData.tag === channel) child.visible = visible
+ }
+ }
+ ctx.channelVisibility[channel] = visible
+}
+
+export function unselectAllEdges(): void {
+ for (const e of ctx.edgeObjects) e.deselect()
+ ctx.renderInterLayerEdgesFlag = true
+ redrawIntraLayerEdges()
+}
+
+// Settings setters (v2 Shiny handlers) =====
+
+export function toggleDirection(enabled: boolean): void {
+ ctx.isDirectionEnabled = enabled
+ ctx.renderInterLayerEdgesFlag = true
+ redrawIntraLayerEdges()
+}
+
+export function setIntraDirectionArrowSize(size: number): void {
+ ctx.intraDirectionArrowSize = size
+ redrawIntraLayerEdges()
+}
+
+export function setInterDirectionArrowSize(size: number): void {
+ ctx.interDirectionArrowSize = size
+ ctx.renderInterLayerEdgesFlag = true
+}
+
+export function setEdgeWidthByWeight(flag: boolean): void {
+ ctx.edgeWidthByWeight = flag
+ ctx.renderInterLayerEdgesFlag = true
+ redrawIntraLayerEdges()
+}
+
+export function setIntraLayerEdgeOpacity(opacity: number): void {
+ ctx.intraLayerEdgeOpacity = opacity
+ redrawIntraLayerEdges()
+}
+
+export function setInterLayerEdgeOpacity(opacity: number): void {
+ ctx.interLayerEdgeOpacity = opacity
+ ctx.renderInterLayerEdgesFlag = true
+}
+
+export function setEdgeSelectedColorPriority(flag: boolean): void {
+ ctx.selectedEdgeColorFlag = flag
+ ctx.renderInterLayerEdgesFlag = true
+ redrawIntraLayerEdges()
+}
+
+export function setEdgeFileColorPriority(flag: boolean): void {
+ ctx.edgeFileColorPriority = flag
+ ctx.renderInterLayerEdgesFlag = true
+ redrawIntraLayerEdges()
+ // v2 also showed/hid the channel color-picker div — Phase 13 UI
+}
+
+export function setIntraChannelCurvature(curvature: number): void {
+ ctx.intraChannelCurvature = curvature
+ redrawIntraLayerEdges()
+}
+
+export function setInterChannelCurvature(curvature: number): void {
+ ctx.interChannelCurvature = curvature
+ ctx.renderInterLayerEdgesFlag = true
+}
+
+// v2 edge.js setEdgeAttributes/setEdgeColorFromAttributes: recolor matching
+// edges from an attribute file (per channel when given, else the first
+// channel), then force loaded-edge-color priority so the colors show.
+export function applyEdgeAttributes(
+ rows: { edge_pair: string; color: string; channel: string | null }[]
+): void {
+ const pairs = ctx.edgeObjects.map((e) => `${e.source}---${e.target}`)
+ for (const row of rows) {
+ const pos = pairs.indexOf(row.edge_pair)
+ if (pos === -1) continue // edge not in network
+ const edge = ctx.edgeObjects[pos]
+ const channelPos = row.channel ? edge.channels.indexOf(row.channel) : 0
+ if (channelPos === -1) continue // channel not on this edge
+ edge.importedColors[channelPos] = row.color
+ edge.colors[channelPos] = row.color
+ }
+ setEdgeFileColorPriority(true)
+}
diff --git a/frontend/src/actions/labels.test.ts b/frontend/src/actions/labels.test.ts
new file mode 100644
index 0000000..030d5d3
--- /dev/null
+++ b/frontend/src/actions/labels.test.ts
@@ -0,0 +1,70 @@
+// Headless (node-env) tests: flag machinery only. Div creation/positioning
+// is exercised by the Phase 13 Playwright E2E against the real DOM.
+import { beforeEach, describe, expect, it } from 'vitest'
+import { ctx, Layer, Node, resetContext } from '../three'
+import {
+ createLabels,
+ renderLayerLabels,
+ renderNodeLabels,
+ setLabelColor,
+ showLayerLabels,
+ showNodeLabels,
+} from './labels'
+
+beforeEach(() => {
+ resetContext()
+ ctx.layers = [new Layer({ id: 0, name: 'L1' })]
+ ctx.layerGroups = { L1: 0 }
+ ctx.nodeGroups = { A_L1: 'L1' }
+ ctx.nodeLayerNames = ['A_L1']
+ ctx.nodeObjects = [
+ new Node({ id: 0, name: 'A', nodeLayerName: 'A_L1', layer: 'L1' }),
+ ]
+})
+
+describe('showNodeLabels', () => {
+ it('maps modes onto the node label flags and recomputes them', () => {
+ showNodeLabels('all')
+ expect(ctx.showAllNodeLabelsFlag).toBe(true)
+ expect(ctx.showSelectedNodeLabelsFlag).toBe(false)
+ expect(ctx.renderNodeLabelsFlag).toBe(true)
+ expect(ctx.nodeObjects[0].showLabel).toBe(true)
+
+ showNodeLabels('none')
+ expect(ctx.showAllNodeLabelsFlag).toBe(false)
+ expect(ctx.showSelectedNodeLabelsFlag).toBe(false)
+ expect(ctx.nodeObjects[0].showLabel).toBe(false)
+ })
+})
+
+describe('showLayerLabels', () => {
+ it('maps modes onto the layer label flags', () => {
+ showLayerLabels('selected')
+ expect(ctx.showAllLayerLabelsFlag).toBe(false)
+ expect(ctx.showSelectedLayerLabelsFlag).toBe(true)
+ expect(ctx.renderLayerLabelsFlag).toBe(true)
+
+ showLayerLabels('none')
+ expect(ctx.showAllLayerLabelsFlag).toBe(false)
+ expect(ctx.showSelectedLayerLabelsFlag).toBe(false)
+ })
+})
+
+describe('animate-loop renderers', () => {
+ it('run flag-gated and clear their flags (headless no-op on divs)', () => {
+ createLabels() // no document -> no divs, must not throw
+ ctx.renderLayerLabelsFlag = true
+ ctx.renderNodeLabelsFlag = true
+ renderLayerLabels()
+ renderNodeLabels()
+ expect(ctx.renderLayerLabelsFlag).toBe(false)
+ expect(ctx.renderNodeLabelsFlag).toBe(false)
+ })
+})
+
+describe('setLabelColor', () => {
+ it('updates the universal label color', () => {
+ setLabelColor('#ff00ff')
+ expect(ctx.labelColor).toBe('#ff00ff')
+ })
+})
diff --git a/frontend/src/actions/labels.ts b/frontend/src/actions/labels.ts
new file mode 100644
index 0000000..5318600
--- /dev/null
+++ b/frontend/src/actions/labels.ts
@@ -0,0 +1,154 @@
+// Port of v2 www/js/object_actions/labels.js — plain absolutely-positioned
+// divs in #labelDiv, repositioned in the animate loop (flag-gated, as v2).
+// Positioning is set inline so it works before the Phase 13 stylesheet lands.
+
+import { Vector3 } from 'three'
+import { ctx, NODE_LABEL_DEFAULT_SIZE } from '../three'
+import { decideNodeLabelFlags } from './node'
+import { registerAnimateHook } from './screen'
+
+const VEC3 = new Vector3() // scratch, reused per frame
+
+// Rebuilt on every network build (v2 resetValues cleared #labelDiv).
+let nodeLabelsDivs: HTMLDivElement[] = []
+let layerLabelsDivs: HTMLDivElement[] = []
+
+function labelContainer(): HTMLElement | null {
+ return typeof document === 'undefined'
+ ? null
+ : document.getElementById('labelDiv')
+}
+
+function createLabel(
+ container: HTMLElement,
+ text: string,
+ id: string,
+ className: string
+): HTMLDivElement {
+ const div = document.createElement('div')
+ div.textContent = text
+ div.className = className
+ div.id = id
+ div.style.position = 'absolute'
+ div.style.color = ctx.labelColor
+ container.appendChild(div)
+ return div
+}
+
+// Called from the network build (v2 executePostNetworkSetup). No-op headless.
+export function createLabels(): void {
+ const container = labelContainer()
+ nodeLabelsDivs = []
+ layerLabelsDivs = []
+ if (!container) return
+ container.innerHTML = ''
+
+ ctx.nodeObjects.forEach((node, i) => {
+ const div = createLabel(
+ container,
+ node.name,
+ `${ctx.nodeLayerNames[i]}_label`,
+ 'labels'
+ )
+ div.style.fontSize = NODE_LABEL_DEFAULT_SIZE
+ div.style.display = 'none'
+ nodeLabelsDivs.push(div)
+ })
+
+ for (const layer of ctx.layers) {
+ const div = createLabel(
+ container,
+ layer.getName(),
+ `${layer.getName()}_label`,
+ 'layer-labels'
+ )
+ div.style.display = 'inline-block'
+ layerLabelsDivs.push(div)
+ }
+ ctx.renderLayerLabelsFlag = true
+}
+
+// v2 setLabelColorVariable + setLabelColor.
+export function setLabelColor(color = ctx.labelColor): void {
+ ctx.labelColor = color
+ for (const div of layerLabelsDivs) div.style.color = color
+ for (const div of nodeLabelsDivs) div.style.color = color
+}
+
+// Handlers (v2 Shiny handlers) =====
+
+export function showLayerLabels(mode: 'all' | 'selected' | 'none'): void {
+ ctx.showAllLayerLabelsFlag = mode === 'all'
+ ctx.showSelectedLayerLabelsFlag = mode === 'selected'
+
+ layerLabelsDivs.forEach((div, i) => {
+ const visible =
+ mode === 'all' || (mode === 'selected' && ctx.layers[i].isSelected)
+ div.style.display = visible ? 'inline-block' : 'none'
+ })
+ if (mode !== 'none') ctx.renderLayerLabelsFlag = true
+}
+
+export function showNodeLabels(mode: 'all' | 'selected' | 'none'): void {
+ ctx.showAllNodeLabelsFlag = mode === 'all'
+ ctx.showSelectedNodeLabelsFlag = mode === 'selected'
+ decideNodeLabelFlags()
+}
+
+export function resizeLayerLabels(size: number): void {
+ for (const div of layerLabelsDivs) div.style.fontSize = `${size}px`
+}
+
+export function resizeNodeLabels(size: number): void {
+ for (const div of nodeLabelsDivs) div.style.fontSize = `${size}px`
+}
+
+// Animate-loop rendering =====
+
+export function renderLayerLabels(): void {
+ if (!ctx.renderLayerLabelsFlag) return
+
+ if (ctx.showAllLayerLabelsFlag) {
+ redrawLayerLabels(ctx.layers.map((l) => l.id))
+ } else if (ctx.showSelectedLayerLabelsFlag) {
+ for (const div of layerLabelsDivs) div.style.display = 'none'
+ const selected = ctx.layers.filter((l) => l.isSelected).map((l) => l.id)
+ if (selected.length > 0) redrawLayerLabels(selected)
+ }
+ ctx.renderLayerLabelsFlag = false
+}
+
+// v2 read the hideLayer DOM checkboxes; hidden state lives on Layer now.
+function redrawLayerLabels(indexes: number[]): void {
+ for (const i of indexes) {
+ const div = layerLabelsDivs[i]
+ if (!div) continue
+ if (ctx.layers[i].isVisible) {
+ const world = ctx.layers[i].sphere.getWorldPosition(VEC3.set(0, 0, 0))
+ div.style.left = `${ctx.xBoundMax + world.x}px`
+ div.style.top = `${ctx.yBoundMax - world.y}px`
+ div.style.display = 'inline-block'
+ } else div.style.display = 'none'
+ }
+}
+
+export function renderNodeLabels(): void {
+ if (!ctx.renderNodeLabelsFlag) return
+
+ ctx.nodeObjects.forEach((node, i) => {
+ const div = nodeLabelsDivs[i]
+ if (!div) return
+ if (node.showLabel) {
+ div.style.left = `${ctx.xBoundMax + node.getWorldPosition('x') + 7}px`
+ div.style.top = `${ctx.yBoundMax - node.getWorldPosition('y') - 10}px`
+ div.style.display = 'inline-block'
+ } else div.style.display = 'none'
+ })
+ ctx.renderNodeLabelsFlag = false
+}
+
+// Called once from main.ts.
+export function registerLabelRendering(): void {
+ registerAnimateHook(renderLayerLabels)
+ registerAnimateHook(renderNodeLabels)
+}
diff --git a/frontend/src/actions/layer.test.ts b/frontend/src/actions/layer.test.ts
new file mode 100644
index 0000000..6c5305f
--- /dev/null
+++ b/frontend/src/actions/layer.test.ts
@@ -0,0 +1,91 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import { store } from '../store'
+import {
+ ctx,
+ Layer,
+ resetContext,
+ SELECTED_LAYER_DEFAULT_COLOR,
+} from '../three'
+import {
+ getSelectedLayers,
+ initialSpreadLayers,
+ repaintLayers,
+ selectAllLayers,
+ selectLayer,
+ setLayerVisibility,
+} from './layer'
+
+function seed(n: number): void {
+ ctx.layers = Array.from(
+ { length: n },
+ (_, i) => new Layer({ id: i, name: `L${i + 1}` })
+ )
+ ctx.layerGroups = Object.fromEntries(ctx.layers.map((l, i) => [l.name, i]))
+}
+
+beforeEach(() => {
+ resetContext()
+ store.update({ selectedLayers: [] })
+})
+
+describe('initialSpreadLayers', () => {
+ it('spreads an odd count symmetrically around 0', () => {
+ seed(3)
+ initialSpreadLayers()
+ const xs = ctx.layers.map((l) => l.getPosition('x'))
+ expect(xs[1]).toBe(0)
+ expect(xs[0]).toBe(-xs[2])
+ expect(xs[0]).toBeLessThan(0)
+ })
+
+ it('direction -1 stacks an even count back to 0', () => {
+ seed(2)
+ initialSpreadLayers(1)
+ expect(ctx.layers[0].getPosition('x')).not.toBe(0)
+ initialSpreadLayers(-1)
+ expect(ctx.layers[0].getPosition('x')).toBeCloseTo(0)
+ })
+})
+
+describe('selection', () => {
+ it('selectLayer repaints selected color and syncs the store', () => {
+ seed(2)
+ selectLayer(0, true)
+ expect(getSelectedLayers()).toEqual([0])
+ expect(ctx.layers[0].color).toBe(SELECTED_LAYER_DEFAULT_COLOR)
+ expect(ctx.layers[1].color).not.toBe(SELECTED_LAYER_DEFAULT_COLOR)
+ expect(store.get().selectedLayers).toEqual(['L1'])
+ expect(ctx.renderLayerLabelsFlag).toBe(true)
+ })
+
+ it('selectAllLayers(false) restores imported colors', () => {
+ seed(2)
+ selectAllLayers(true)
+ expect(getSelectedLayers()).toEqual([0, 1])
+ selectAllLayers(false)
+ expect(getSelectedLayers()).toEqual([])
+ expect(ctx.layers[0].color).toBe(ctx.layers[0].importedColor)
+ expect(store.get().selectedLayers).toEqual([])
+ })
+})
+
+describe('repaintLayers', () => {
+ it('picker priority falls back to current color without the DOM input', () => {
+ seed(1)
+ ctx.layers[0].setColor('#ababab')
+ ctx.layerColorPrioritySource = 'picker'
+ repaintLayers()
+ expect(ctx.layers[0].color).toBe('#ababab')
+ })
+})
+
+describe('setLayerVisibility', () => {
+ it('hides the plane, raises render flags and recomputes node label flags', () => {
+ seed(1)
+ setLayerVisibility(0, false)
+ expect(ctx.layers[0].isVisible).toBe(false)
+ expect(ctx.layers[0].plane.visible).toBe(false)
+ expect(ctx.renderInterLayerEdgesFlag).toBe(true)
+ expect(ctx.renderLayerLabelsFlag).toBe(true)
+ })
+})
diff --git a/frontend/src/actions/layer.ts b/frontend/src/actions/layer.ts
new file mode 100644
index 0000000..a82a883
--- /dev/null
+++ b/frontend/src/actions/layer.ts
@@ -0,0 +1,152 @@
+// Port of v2 www/js/object_actions/layer.js — spread, hover, selection,
+// repaint, visibility and layer-wide setters. Selection syncs to the store
+// instead of Shiny inputs. Deferred with their phases: checkbox group +
+// color-picker DOM (Phase 13 UI), DragControls + held-key/slider
+// rotate/move/scale intervals (canvas_controls.ts).
+
+import type { MeshBasicMaterial } from 'three'
+import { store } from '../store'
+import { ctx, SELECTED_LAYER_DEFAULT_COLOR } from '../three'
+import { LAYER_DEFAULT_COLOR } from '../three/constants'
+import type { ColorPrioritySource } from '../three'
+import { findIndexByUuid } from '../utils'
+import { decideNodeLabelFlags } from './node'
+import { raycaster, setRaycaster } from './screen'
+
+// Spread layers evenly along x (v2: predefined layouts, upload/import,
+// canvas controls; direction -1 stacks them back).
+export function initialSpreadLayers(direction = 1): void {
+ const n = ctx.layers.length
+ const spacing = (ctx.xBoundMax * 2) / n
+ ctx.layers.forEach((layer, i) => {
+ const offset =
+ n % 2
+ ? (i - Math.floor(n / 2)) * spacing
+ : (i - n / 2) * spacing + spacing / 2
+ layer.translateX(direction * offset)
+ })
+}
+
+function updateSelectedLayersStore(): void {
+ store.update({
+ selectedLayers: ctx.layers.filter((l) => l.isSelected).map((l) => l.name),
+ })
+}
+
+// @return selected layer indexes
+export function getSelectedLayers(): number[] {
+ return ctx.layers.filter((l) => l.isSelected).map((l) => l.id)
+}
+
+// Last floor-picker color applied. Tracked so ChangeFloorColorCommand can
+// recover the previous value on undo — the DOM color input already holds the
+// new value by the time its 'change' event fires, so it can't be read back.
+let lastFloorColor = LAYER_DEFAULT_COLOR
+
+export function getLastFloorColor(): string {
+ return lastFloorColor
+}
+
+// `pickerColor` overrides the DOM color input (themes.ts passes the theme's
+// floor color; the input itself only exists once Phase 13 builds the UI).
+export function repaintLayers(pickerColor?: string): void {
+ if (ctx.layerColorPrioritySource === 'picker' && pickerColor)
+ lastFloorColor = pickerColor
+ for (const layer of ctx.layers) {
+ if (layer.isSelected) layer.setColor(SELECTED_LAYER_DEFAULT_COLOR)
+ else if (ctx.layerColorPrioritySource === 'default')
+ layer.setColor(layer.importedColor)
+ else if (ctx.layerColorPrioritySource === 'picker')
+ layer.setColor(
+ pickerColor ||
+ (typeof document !== 'undefined' &&
+ (document.getElementById('floor_color') as HTMLInputElement | null)
+ ?.value) ||
+ layer.color
+ )
+ }
+}
+
+export function checkHoverOverLayer(event: {
+ clientX: number
+ clientY: number
+}): void {
+ setRaycaster(event)
+ const planes = ctx.layers.map(({ plane }) => plane)
+ // Non-recursive: planes carry node spheres/labels/coord lines as children;
+ // a child hit isn't in `planes`, so findIndexByUuid returned -1 and crashed.
+ const intersects = raycaster().intersectObjects(planes, false)
+ if (intersects.length > 0) {
+ if (ctx.lastHoveredLayerIndex !== null) {
+ repaintLayers()
+ ctx.hoveredLayerPaintedFlag = true
+ }
+ const i = findIndexByUuid(planes, intersects[0].object.uuid)
+ ;(ctx.layers[i].plane.material as MeshBasicMaterial).color.set(0xff0000)
+ ctx.lastHoveredLayerIndex = i
+ } else {
+ if (ctx.hoveredLayerPaintedFlag) {
+ repaintLayers() // remove red color from last hovered
+ ctx.hoveredLayerPaintedFlag = false
+ }
+ ctx.lastHoveredLayerIndex = null
+ }
+}
+
+// v2 toggled the matching DOM checkbox too — Phase 13 wires that back up.
+export function performDoubleClickLayerSelection(): void {
+ if (ctx.lastHoveredLayerIndex === null) return
+ ctx.layers[ctx.lastHoveredLayerIndex].toggleSelection()
+ ctx.lastHoveredLayerIndex = null
+ ctx.renderLayerLabelsFlag = true
+ repaintLayers()
+ updateSelectedLayersStore()
+}
+
+// v2 selectCheckedLayer minus the DOM checkbox element.
+export function selectLayer(index: number, selected: boolean): void {
+ ctx.layers[index].isSelected = selected
+ ctx.renderLayerLabelsFlag = true
+ repaintLayers()
+ updateSelectedLayersStore()
+}
+
+export function selectAllLayers(selected: boolean): void {
+ for (const layer of ctx.layers) layer.isSelected = selected
+ ctx.renderLayerLabelsFlag = true
+ repaintLayers()
+ updateSelectedLayersStore()
+}
+
+// v2 hideLayers core, per layer (the checkbox loop is Phase 13's).
+export function setLayerVisibility(index: number, visible: boolean): void {
+ ctx.layers[index].toggleVisibility(visible)
+ ctx.renderInterLayerEdgesFlag = true
+ ctx.renderLayerLabelsFlag = true
+ decideNodeLabelFlags()
+}
+
+// v2 showLayerNodeLabels core, per layer.
+export function setLayerNodeLabels(index: number, show: boolean): void {
+ ctx.layers[index].showNodeLabels = show
+ decideNodeLabelFlags()
+}
+
+// Handlers (v2 Shiny handlers) =====
+
+export function showLayerCoords(show: boolean): void {
+ for (const layer of ctx.layers) layer.toggleCoords(show)
+}
+
+export function setFloorOpacity(opacity: number): void {
+ for (const layer of ctx.layers) layer.setOpacity(opacity)
+}
+
+export function showWireFrames(flag: boolean): void {
+ for (const layer of ctx.layers) layer.toggleWireframe(flag)
+}
+
+export function setLayerColorPriority(source: ColorPrioritySource): void {
+ ctx.layerColorPrioritySource = source
+ repaintLayers()
+}
diff --git a/frontend/src/actions/layout.test.ts b/frontend/src/actions/layout.test.ts
new file mode 100644
index 0000000..d91b77f
--- /dev/null
+++ b/frontend/src/actions/layout.test.ts
@@ -0,0 +1,130 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { Node, Layer, Scene, ctx, resetContext } from '../three'
+import { api } from '../api/client'
+import { history } from '../commands/base'
+import {
+ normalizeLayoutPositions,
+ applyLayout,
+ applyTopology,
+ applyPredefinedLayout,
+} from './layout'
+
+// Two layers of different widths; one node each.
+function seed(): void {
+ ctx.scene = new Scene()
+ ctx.layers = [
+ new Layer({ id: 0, name: 'L1', geometry_parameters_width: 100 }),
+ new Layer({ id: 1, name: 'L2', geometry_parameters_width: 400 }),
+ ]
+ ctx.layerGroups = { L1: 0, L2: 1 }
+ ctx.nodeGroups = { 'A::L1': 'L1', 'B::L1': 'L1', 'C::L2': 'L2' }
+ ctx.nodeLayerNames = ['A::L1', 'B::L1', 'C::L2']
+ ctx.nodeObjects = [
+ new Node({ name: 'A', nodeLayerName: 'A::L1', layer: 'L1' }),
+ new Node({ name: 'B', nodeLayerName: 'B::L1', layer: 'L1' }),
+ new Node({ name: 'C', nodeLayerName: 'C::L2', layer: 'L2' }),
+ ]
+ ctx.edgeObjects = []
+}
+
+beforeEach(() => {
+ resetContext()
+ seed()
+})
+
+describe('normalizeLayoutPositions', () => {
+ it('maps coords into [-minWidth/2, minWidth/2] (minWidth = 100 -> [-50, 50])', () => {
+ // Two L1 nodes span the full y range -> min maps to -50, max to +50.
+ const out = normalizeLayoutPositions({ 'A::L1': [0, 0], 'B::L1': [10, 10] })
+ expect(out['A::L1']).toEqual([-50, -50])
+ expect(out['B::L1']).toEqual([50, 50])
+ })
+
+ it('collapses a degenerate (single-value) axis to 0', () => {
+ const out = normalizeLayoutPositions({ 'A::L1': [5, 5] })
+ expect(out['A::L1']).toEqual([0, 0])
+ })
+
+ it('multiplies by the node layer scale', () => {
+ ctx.layers[0].setScale(2)
+ const out = normalizeLayoutPositions({ 'A::L1': [0, 0], 'B::L1': [10, 10] })
+ expect(out['B::L1']).toEqual([100, 100]) // 50 * scale 2
+ })
+})
+
+describe('applyLayout', () => {
+ it('fetches, normalizes, and pushes one ApplyLayoutCommand', async () => {
+ const spy = vi.spyOn(api, 'layout').mockResolvedValue({
+ positions: { 'A::L1': [0, 0], 'B::L1': [10, 10] },
+ clusters: null,
+ })
+ await applyLayout({
+ nodes: [],
+ edges: [],
+ algorithm: 'DrL',
+ selected_layers: ['L1'],
+ })
+ expect(spy).toHaveBeenCalledOnce()
+ expect(ctx.nodeObjects[0].getPosition('y')).toBe(-50)
+ expect(ctx.nodeObjects[1].getPosition('y')).toBe(50)
+ expect(history.canUndo).toBe(true)
+ spy.mockRestore()
+ })
+})
+
+describe('applyPredefinedLayout', () => {
+ it('parallel spreads layers along x from a reset position', () => {
+ applyPredefinedLayout('parallel')
+ const xs = ctx.layers.map((l) => l.getPosition('x'))
+ expect(xs[0]).toBeLessThan(0)
+ expect(xs[0]).toBeCloseTo(-xs[1])
+ expect(ctx.layers.every((l) => l.getPosition('y') === 0)).toBe(true)
+ expect(ctx.renderInterLayerEdgesFlag).toBe(true)
+ })
+
+ it('zigZag lifts every other layer by 500', () => {
+ applyPredefinedLayout('zigZag')
+ expect(ctx.layers[0].getPosition('y')).toBe(0)
+ expect(ctx.layers[1].getPosition('y')).toBe(500)
+ })
+
+ it('starLike rotates layers around the origin and pushes them outward', () => {
+ applyPredefinedLayout('starLike')
+ // layer 0 (width 100): rotZ 0°, translateY(-150) -> y = -150
+ expect(ctx.layers[0].getPosition('y')).toBeCloseTo(-150)
+ // layer 1 (width 400): rotZ 180°, translateY(-300) -> y = +300
+ expect(ctx.layers[1].getPosition('y')).toBeCloseTo(300)
+ })
+
+ it('cube arranges layers as box sides', () => {
+ applyPredefinedLayout('cube')
+ // side 0 (width 100): rotZ 90° then translateX(+150) -> (0, 150, 0)
+ expect(ctx.layers[0].getPosition('y')).toBeCloseTo(150)
+ // side 1 (width 400): no rotation, translateX(+300) -> (300, 0, 0)
+ expect(ctx.layers[1].getPosition('x')).toBeCloseTo(300)
+ })
+
+ it('needs more than one layer', () => {
+ ctx.layers = [ctx.layers[0]]
+ ctx.layers[0].translateX(42)
+ applyPredefinedLayout('parallel')
+ expect(ctx.layers[0].getPosition('x')).toBe(42) // untouched
+ })
+})
+
+describe('applyTopology', () => {
+ it('fetches and applies scales verbatim (already mapped server-side)', async () => {
+ const spy = vi
+ .spyOn(api, 'topology')
+ .mockResolvedValue({ scales: { 'A::L1': 1.7 }, raw: { 'A::L1': 12 } })
+ await applyTopology({
+ nodes: [],
+ edges: [],
+ metric: 'Degree',
+ selected_layers: ['L1'],
+ })
+ expect(spy).toHaveBeenCalledOnce()
+ expect(ctx.nodeObjects[0].getScale()).toBe(1.7)
+ spy.mockRestore()
+ })
+})
diff --git a/frontend/src/actions/layout.ts b/frontend/src/actions/layout.ts
new file mode 100644
index 0000000..af367ff
--- /dev/null
+++ b/frontend/src/actions/layout.ts
@@ -0,0 +1,142 @@
+// Layout + topology actions — port of v2 www/js/object_actions/layout.js
+// (executeLayout / scaleTopology). Fetches from the API, normalizes the raw
+// igraph coordinates into each node's layer plane, and dispatches the Phase 10
+// commands so the change is one undo step.
+
+import { MathUtils } from 'three'
+import { api, type LayoutRequest, type TopologyRequest } from '../api/client'
+import { ctx } from '../three'
+import { history } from '../commands/base'
+import { ApplyLayoutCommand, ApplyTopologyCommand } from '../commands/scene'
+import { initialSpreadLayers } from './layer'
+
+// v2 executeLayout: the backend returns raw 2D [y, z] coords; map them into the
+// layer plane, target range [-minWidth/2, minWidth/2], then scale by the node's
+// own layer scale. x is always 0 (position on the floor).
+//
+// ponytail: implements the allLayers scope (one global min/max + the narrowest
+// layer width as target). The perLayer / local-layout refinements (v2's
+// perLayerLayoutFLag / localLayoutFlag, which re-run per layer or preserve the
+// current sub-region) are deferred until Phase 13 wires the scope UI.
+export function normalizeLayoutPositions(
+ raw: Record
+): Record {
+ const names = Object.keys(raw)
+ if (names.length === 0) return {}
+
+ const ys = names.map((n) => raw[n][0])
+ const zs = names.map((n) => raw[n][1])
+ const yMin = Math.min(...ys)
+ const yMax = Math.max(...ys)
+ const zMin = Math.min(...zs)
+ const zMax = Math.max(...zs)
+
+ const minWidth = Math.min(
+ ...ctx.layers.map((l) => l.geometry_parameters_width)
+ )
+ const t = minWidth / 2 // target range is [-t, t]
+
+ const out: Record = {}
+ for (const name of names) {
+ const [y, z] = raw[name]
+ const scale = ctx.layers[ctx.layerGroups[ctx.nodeGroups[name]]].getScale()
+ const ny =
+ yMax - yMin !== 0 ? ((y - yMin) * (2 * t)) / (yMax - yMin) - t : 0
+ const nz =
+ zMax - zMin !== 0 ? ((z - zMin) * (2 * t)) / (zMax - zMin) - t : 0
+ out[name] = [ny * scale, nz * scale]
+ }
+ return out
+}
+
+export async function applyLayout(req: LayoutRequest): Promise {
+ const res = await api.layout(req)
+ const positions = normalizeLayoutPositions(res.positions)
+ history.execute(new ApplyLayoutCommand(positions, res.clusters))
+}
+
+export async function applyTopology(req: TopologyRequest): Promise {
+ // scales are already mapped into the target range server-side (SPEC §6).
+ const res = await api.topology(req)
+ history.execute(new ApplyTopologyCommand(req.metric, res.scales))
+}
+
+// Predefined layer arrangements (v2 applyPredefinedLayout). Direct layer
+// transforms, not undoable — as in v2. Needs > 1 layer.
+export type PredefinedLayout = 'parallel' | 'zigZag' | 'starLike' | 'cube'
+
+export function applyPredefinedLayout(name: PredefinedLayout): void {
+ if (ctx.layers.length <= 1) return
+ resetSceneAndLayerPositions()
+
+ if (name === 'parallel') initialSpreadLayers(1)
+ else if (name === 'zigZag') {
+ for (let i = 1; i < ctx.layers.length; i += 2) ctx.layers[i].translateY(500)
+ initialSpreadLayers(1)
+ } else if (name === 'starLike') applyStarLayout()
+ else if (name === 'cube') applyCubeLayout()
+
+ // replaces the v2 Shiny syncs: displaced layers need edge + label redraw
+ ctx.renderInterLayerEdgesFlag = true
+ ctx.renderLayerLabelsFlag = true
+ ctx.renderNodeLabelsFlag = true
+}
+
+function resetSceneAndLayerPositions(): void {
+ ctx.scene!.tiltDefault()
+ for (const layer of ctx.layers) {
+ layer.plane.position.set(0, 0, 0)
+ if (ctx.camera) layer.plane.quaternion.copy(ctx.camera.quaternion)
+ }
+}
+
+// Petals around the origin: each layer rotated by its share of 360° and
+// pushed outward by half its width + 100.
+function applyStarLayout(): void {
+ const degree = 360 / ctx.layers.length
+ ctx.layers.forEach((layer, i) => {
+ layer.plane.rotateZ(MathUtils.degToRad(degree * i))
+ layer.plane.translateY(-layer.geometry_parameters_width / 2 - 100)
+ })
+}
+
+// Up to 6 layers form a cube's sides; additional cubes line up along x.
+function applyCubeLayout(): void {
+ const layersPerCube = 6
+ const cubes = Math.ceil(ctx.layers.length / layersPerCube)
+ const largest = Math.max(
+ ...ctx.layers.map((l) => l.geometry_parameters_width)
+ )
+ const distance = cubes * (largest + 400)
+
+ for (let j = 0; j < cubes; j++) {
+ let cubeSideCode = 0
+ const maxI = Math.min(j * layersPerCube + layersPerCube, ctx.layers.length)
+
+ for (let i = j * layersPerCube; i < maxI; i++) {
+ const plane = ctx.layers[i].plane
+ if (cubes > 1)
+ plane.position.set(decideCubeStartingX(j, cubes, distance), 0, 0)
+
+ if (!(cubeSideCode % 2)) plane.rotateZ(MathUtils.degToRad(90))
+ if (cubeSideCode >= 4) plane.rotateY(MathUtils.degToRad(90))
+
+ const offset = ctx.layers[i].geometry_parameters_width / 2 + 100
+ if (cubeSideCode === 0 || cubeSideCode === 1 || cubeSideCode === 5)
+ plane.translateX(offset)
+ else plane.translateX(-offset)
+
+ cubeSideCode++
+ }
+ }
+}
+
+function decideCubeStartingX(
+ j: number,
+ cubes: number,
+ distance: number
+): number {
+ const half = Math.floor(cubes / 2)
+ if (cubes % 2 && j === half) return 0
+ return j < half ? -distance / cubes : distance / cubes
+}
diff --git a/frontend/src/actions/nav_controls.ts b/frontend/src/actions/nav_controls.ts
new file mode 100644
index 0000000..6962592
--- /dev/null
+++ b/frontend/src/actions/nav_controls.ts
@@ -0,0 +1,457 @@
+// Port of v2 www/js/object_actions/canvas_controls.js nav-control remainder:
+// the two fixed buttons (#navControlButtonsDiv) and the Scene/Layers/Nodes
+// control table inside #info. Hold-to-repeat buttons run a 70ms interval, as
+// v2 did; Shiny syncs are replaced by render/label flags.
+
+import * as THREE from 'three'
+import { bus } from '../bus'
+import { ctx } from '../three'
+import { redrawIntraLayerEdges, toggleInterLayerEdgesRendering } from './edge'
+import { getSelectedLayers, initialSpreadLayers } from './layer'
+import { getSelectedNodes } from './node'
+import { history } from '../commands/base'
+import {
+ captureTransforms,
+ TransformCommand,
+ type TransformSnapshot,
+} from '../commands/scene'
+
+type Axis = 'X' | 'Y' | 'Z'
+
+const REPEAT_MS = 70
+
+let repeatId: ReturnType | undefined
+// Hold-to-repeat interactions register one undo entry per hold: snapshot on
+// press, command pushed on release (only if a tick actually ran).
+let repeatBefore: TransformSnapshot | null = null
+let repeatMoved = false
+
+function stopRepeat(): void {
+ clearInterval(repeatId)
+ repeatId = undefined
+ if (repeatBefore && repeatMoved)
+ history.execute(
+ new TransformCommand(
+ 'Nav control move',
+ repeatBefore,
+ captureTransforms()
+ )
+ )
+ repeatBefore = null
+ repeatMoved = false
+}
+
+function startRepeat(fn: () => void): void {
+ stopRepeat()
+ repeatBefore = captureTransforms()
+ repeatId = setInterval(() => {
+ repeatMoved = true
+ fn()
+ }, REPEAT_MS)
+}
+
+function sliderValue(id: string): number {
+ return Number((document.getElementById(id) as HTMLInputElement).value)
+}
+
+function raiseMovedFlags(): void {
+ ctx.renderInterLayerEdgesFlag = true
+ ctx.renderLayerLabelsFlag = true
+ ctx.renderNodeLabelsFlag = true
+}
+
+// --- Scene ---------------------------------------------------------------
+
+function rotateScene(direction: number, axis: Axis): void {
+ startRepeat(() => {
+ if (!ctx.scene?.exists()) return
+ const rad =
+ direction * THREE.MathUtils.degToRad(sliderValue('sceneRotateSlider'))
+ if (axis === 'X') ctx.scene.rotateX(rad)
+ else if (axis === 'Y') ctx.scene.rotateY(rad)
+ else ctx.scene.rotateZ(rad)
+ raiseMovedFlags()
+ })
+}
+
+export function recenterNetwork(): void {
+ if (ctx.scene?.exists()) {
+ const before = captureTransforms()
+ ctx.scene.recenter()
+ raiseMovedFlags()
+ history.execute(
+ new TransformCommand('Recenter network', before, captureTransforms())
+ )
+ }
+}
+
+// --- Layers --------------------------------------------------------------
+
+function rotateSelectedLayers(direction: number, axis: Axis): void {
+ const selected = getSelectedLayers()
+ if (selected.length === 0) return alert('Please select at least one layer.')
+ startRepeat(() => {
+ const rad =
+ direction * THREE.MathUtils.degToRad(sliderValue('layerRotateSlider'))
+ for (const i of selected) {
+ if (axis === 'X') ctx.layers[i].rotateX(rad)
+ else if (axis === 'Y') ctx.layers[i].rotateY(rad)
+ else ctx.layers[i].rotateZ(rad)
+ }
+ raiseMovedFlags()
+ })
+}
+
+function moveLayers(direction: number, axis: Axis): void {
+ const selected = getSelectedLayers()
+ if (selected.length === 0) return alert('Please select at least one layer.')
+ startRepeat(() => {
+ const step = direction * sliderValue('layerTranslateSlider')
+ for (const i of selected) {
+ if (axis === 'X') ctx.layers[i].translateX(step)
+ else if (axis === 'Y') ctx.layers[i].translateY(step)
+ else ctx.layers[i].translateZ(step)
+ }
+ raiseMovedFlags()
+ })
+}
+
+function spreadLayers(direction: number): void {
+ const before = captureTransforms()
+ initialSpreadLayers(direction)
+ raiseMovedFlags()
+ history.execute(
+ new TransformCommand('Spread layers', before, captureTransforms())
+ )
+}
+
+function scaleLayers(): void {
+ const value = sliderValue('layerScaleSlider')
+ const td = document.getElementById('sliderValue4')
+ if (td) td.textContent = `x${value}`
+
+ const selected = getSelectedLayers()
+ if (selected.length === 0) return alert('Please select at least one layer.')
+ for (const i of selected) {
+ // nodes keep their in-plane offsets proportional (v2 scaleLayers)
+ const moveFactor = value / ctx.layers[i].getScale()
+ for (const child of ctx.layers[i].plane.children) {
+ if (child.type === 'Mesh') {
+ child.position.y *= moveFactor
+ child.position.z *= moveFactor
+ }
+ }
+ ctx.layers[i].setScale(value)
+ }
+ redrawIntraLayerEdges()
+ raiseMovedFlags()
+}
+
+// --- Nodes ---------------------------------------------------------------
+
+function spreadNodes(multiplier: number): void {
+ const selected = getSelectedNodes()
+ if (selected.length === 0) return alert('Please select at least one node.')
+ const before = captureTransforms()
+ for (const i of selected) {
+ const node = ctx.nodeObjects[i]
+ node.setPosition('y', node.getPosition('y') * multiplier)
+ node.setPosition('z', node.getPosition('z') * multiplier)
+ }
+ redrawIntraLayerEdges()
+ raiseMovedFlags()
+ history.execute(
+ new TransformCommand('Spread nodes', before, captureTransforms())
+ )
+}
+
+function moveNodes(direction: number, axis: Axis): void {
+ const selected = getSelectedNodes()
+ if (selected.length === 0) return alert('Please select at least one node.')
+ startRepeat(() => {
+ const step = direction * sliderValue('nodeTranslateSlider')
+ for (const i of selected) {
+ if (axis === 'X') ctx.nodeObjects[i].translateX(step)
+ else if (axis === 'Y') ctx.nodeObjects[i].translateY(step)
+ else ctx.nodeObjects[i].translateZ(step)
+ }
+ redrawIntraLayerEdges()
+ raiseMovedFlags()
+ })
+}
+
+function scaleNodes(): void {
+ const value = sliderValue('nodeScaleSlider')
+ const td = document.getElementById('sliderValue6')
+ if (td) td.textContent = `x${value}`
+
+ const selected = getSelectedNodes()
+ if (selected.length === 0) return alert('Please select at least one node.')
+ for (const i of selected) ctx.nodeObjects[i].setScale(value)
+}
+
+// --- DOM -----------------------------------------------------------------
+
+// One Angle/Step group: value label + slider + the six axis buttons laid out
+// as v2's four table rows (label,±X / slider / ±Y / ±Z).
+function axisRowsHtml(
+ valueId: string,
+ valueText: string,
+ sliderId: string,
+ slider: { min: number; max: number; value: number; step: number },
+ t: '' | '_T'
+): string {
+ const btn = (name: string): string =>
+ ` `
+ return `
+
+ ${valueText}
+ ${btn('minusX')}${btn('plusX')}
+
+
+
+
+
+
+ ${btn('minusY')}${btn('plusY')}
+ ${btn('minusZ')}${btn('plusZ')} `
+}
+
+const INFO_HTML = `
+
+ Zoom Wheel
+ Pan Drag / ←↑→↓
+ Orbit Middle Drag
+ Drag Layer Drag
+ Rotate Layer Z X C + Drag
+ Move Selected Nodes Z C + Drag
+ Select Node/Layer Dbl Click
+ Lasso Nodes Shift + Drag
+ Unselect All Dbl Click Scene
+
+
+ Scene
+ Rotation Controls
+ ${axisRowsHtml('sliderValue1', 'Angle: 5˚', 'sceneRotateSlider', { min: 1, max: 15, value: 5, step: 1 }, '')}
+ Recenter Network
+ Layers
+ Rotation Controls
+ ${axisRowsHtml('sliderValue2', 'Angle: 5˚', 'layerRotateSlider', { min: 1, max: 15, value: 5, step: 1 }, '')}
+ Translation Controls
+
+
+
+
+ ${axisRowsHtml('sliderValue3', 'Step: 25', 'layerTranslateSlider', { min: 5, max: 50, value: 25, step: 5 }, '_T')}
+
+ Scale
+
+
+
+ x1
+
+ Nodes
+ Translation Controls
+
+
+
+
+ ${axisRowsHtml('sliderValue5', 'Step: 25', 'nodeTranslateSlider', { min: 5, max: 50, value: 25, step: 5 }, '_T')}
+
+ Scale
+
+
+
+ x1
+
+
`
+
+// data-nav name (without _T suffix stripping) → hold-to-repeat action
+function holdAction(name: string): (() => void) | null {
+ const map: Record void> = {
+ minusX: () => rotateScene(-1, 'X'),
+ plusX: () => rotateScene(1, 'X'),
+ minusY: () => rotateScene(-1, 'Y'),
+ plusY: () => rotateScene(1, 'Y'),
+ minusZ: () => rotateScene(-1, 'Z'),
+ plusZ: () => rotateScene(1, 'Z'),
+ }
+ return map[name] ?? null
+}
+
+// Real stop/start affordance for the render-pause button (previously a
+// static "Stop:Render..." label that never reflected the actual flag).
+function setPauseButtonState(paused: boolean): void {
+ const btn = document.getElementById('interLayerEdgesRenderPauseButton')
+ if (!btn) return
+ btn.classList.toggle('is-paused', paused)
+ btn.innerHTML = paused
+ ? '▶ Render Inter-Layer Edges'
+ : '❚❚ Pause Inter-Layer Edges'
+ btn.setAttribute(
+ 'aria-label',
+ paused
+ ? 'Resume inter-layer edge rendering'
+ : 'Pause inter-layer edge rendering'
+ )
+}
+
+// Up/down chevron reflecting whether #info is currently expanded.
+function setControlsToggleState(open: boolean): void {
+ const btn = document.getElementById('displayCanvasControlsButton')
+ if (!btn) return
+ btn.classList.toggle('is-open', open)
+ btn.innerHTML = `${open ? '▴' : '▾'} Navigation Controls`
+ btn.setAttribute(
+ 'aria-label',
+ open ? 'Hide navigation controls' : 'Show navigation controls'
+ )
+}
+
+// v2 attached the controls on first network build (canvasControlsAttached
+// flag); #info keeps its "waiting for network" hint until then.
+export function registerNavControls(): void {
+ const off = bus.on('network:loaded', () => {
+ attachNavControls()
+ off()
+ })
+}
+
+function attachNavControls(): void {
+ const buttonsDiv = document.getElementById('navControlButtonsDiv')
+ const info = document.getElementById('info')
+ if (!buttonsDiv || !info) return
+
+ buttonsDiv.innerHTML = `
+
+ `
+ document
+ .getElementById('interLayerEdgesRenderPauseButton')!
+ .addEventListener('click', () => {
+ setPauseButtonState(toggleInterLayerEdgesRendering())
+ })
+ document
+ .getElementById('displayCanvasControlsButton')!
+ .addEventListener('click', () => {
+ const open = info.style.display === 'none'
+ info.style.display = open ? 'inline-block' : 'none'
+ setControlsToggleState(open)
+ })
+
+ info.innerHTML = INFO_HTML
+ info.style.display = 'inline-block'
+ setPauseButtonState(ctx.interLayerEdgesRenderPauseFlag)
+ setControlsToggleState(true)
+
+ document
+ .getElementById('recenterButton')!
+ .addEventListener('click', recenterNetwork)
+
+ // Hold-to-repeat axis buttons. The table holds three axis groups in DOM
+ // order: scene rotate, layer rotate (plain names), then layer + node
+ // translate (_T names) — disambiguated by which group's rows they sit in.
+ const groups: ((name: string) => void)[] = [
+ (n) => holdAction(n)?.(),
+ (n) =>
+ rotateSelectedLayers(n.startsWith('minus') ? -1 : 1, n.slice(-1) as Axis),
+ (n) =>
+ moveLayers(
+ n.startsWith('minus') ? -1 : 1,
+ n.charAt(n.length - 3) as Axis
+ ),
+ (n) =>
+ moveNodes(n.startsWith('minus') ? -1 : 1, n.charAt(n.length - 3) as Axis),
+ ]
+ let plainSeen = 0
+ let tSeen = 0
+ for (const el of info.querySelectorAll('[data-nav]')) {
+ const name = el.dataset.nav!
+ if (name === 'expandLayers')
+ el.addEventListener('click', () => spreadLayers(1))
+ else if (name === 'collapseLayers')
+ el.addEventListener('click', () => spreadLayers(-1))
+ else if (name === 'expandNodes')
+ el.addEventListener('click', () => spreadNodes(1.1))
+ else if (name === 'collapseNodes')
+ el.addEventListener('click', () => spreadNodes(0.9))
+ else {
+ const isT = name.endsWith('_T')
+ const group = isT
+ ? 2 + Math.floor(tSeen++ / 6)
+ : Math.floor(plainSeen++ / 6)
+ el.addEventListener('mousedown', () => groups[group](name))
+ el.addEventListener('mouseup', stopRepeat)
+ el.addEventListener('mousemove', stopRepeat)
+ }
+ }
+
+ // Live-scale on input; one undo entry per drag, finalized on 'change'.
+ // `sync` re-applies the restored scale to the slider + its value label on
+ // undo/redo (the transform snapshot only covers the 3D objects).
+ const wireScaleSlider = (
+ el: HTMLElement,
+ run: () => void,
+ description: string,
+ sync: () => void
+ ): void => {
+ let before: TransformSnapshot | null = null
+ el.addEventListener('input', () => {
+ before ??= captureTransforms()
+ run()
+ })
+ el.addEventListener('change', () => {
+ if (before) {
+ history.execute(
+ new TransformCommand(description, before, captureTransforms(), sync)
+ )
+ before = null
+ }
+ })
+ }
+ const setSlider = (sliderId: string, tdId: string, value: number): void => {
+ const s = document.getElementById(sliderId) as HTMLInputElement | null
+ if (s) s.value = String(value)
+ const td = document.getElementById(tdId)
+ if (td) td.textContent = `x${value}`
+ }
+ wireScaleSlider(
+ document.getElementById('layerScaleSlider')!,
+ scaleLayers,
+ 'Scale layers',
+ () => {
+ const sel = getSelectedLayers()
+ setSlider(
+ 'layerScaleSlider',
+ 'sliderValue4',
+ sel.length ? ctx.layers[sel[0]].getScale() : 1
+ )
+ }
+ )
+ wireScaleSlider(
+ document.getElementById('nodeScaleSlider')!,
+ scaleNodes,
+ 'Scale nodes',
+ () => {
+ const sel = getSelectedNodes()
+ setSlider(
+ 'nodeScaleSlider',
+ 'sliderValue6',
+ sel.length ? ctx.nodeObjects[sel[0]].getScale() : 1
+ )
+ }
+ )
+ for (const [sliderId, tdId, fmt] of [
+ ['sceneRotateSlider', 'sliderValue1', 'Angle: %v˚'],
+ ['layerRotateSlider', 'sliderValue2', 'Angle: %v˚'],
+ ['layerTranslateSlider', 'sliderValue3', 'Step: %v'],
+ ['nodeTranslateSlider', 'sliderValue5', 'Step: %v'],
+ ] as const) {
+ document.getElementById(sliderId)!.addEventListener('input', (e) => {
+ document.getElementById(tdId)!.textContent = fmt.replace(
+ '%v',
+ (e.target as HTMLInputElement).value
+ )
+ })
+ }
+}
diff --git a/frontend/src/actions/network.test.ts b/frontend/src/actions/network.test.ts
new file mode 100644
index 0000000..8a6c8a3
--- /dev/null
+++ b/frontend/src/actions/network.test.ts
@@ -0,0 +1,100 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { AppConfig } from '../api/config'
+import type { EdgeModel, NetworkData } from '../api/client'
+import { history } from '../commands/base'
+import { store } from '../store'
+import { ctx, resetContext } from '../three'
+import { buildNetwork, loadNetwork } from './network'
+
+function edge(partial: Partial): EdgeModel {
+ return {
+ src: 'A_L1',
+ trg: 'B_L2',
+ source_node: 'A',
+ source_layer: 'L1',
+ target_node: 'B',
+ target_layer: 'L2',
+ weight: 1,
+ scaled_weight: 1,
+ channel: null,
+ ...partial,
+ }
+}
+
+function network(partial: Partial = {}): NetworkData {
+ return {
+ layers: ['L1', 'L2'],
+ channels: [],
+ warnings: [],
+ nodes: [
+ { id: 'A_L1', label: 'A', layer: 'L1' },
+ { id: 'B_L2', label: 'B', layer: 'L2' },
+ { id: 'C_L1', label: 'C', layer: 'L1' },
+ ],
+ edges: [
+ edge({}),
+ edge({ trg: 'C_L1', target_node: 'C', target_layer: 'L1' }),
+ ],
+ ...partial,
+ }
+}
+
+beforeEach(() => {
+ resetContext()
+ store.update({
+ config: {
+ max_channels: 2,
+ channel_colors_light: ['#111111', '#222222'],
+ } as AppConfig,
+ })
+})
+
+describe('buildNetwork', () => {
+ it('builds layers, nodes and edges into ctx', () => {
+ buildNetwork(network())
+ expect(ctx.layers.map((l) => l.name)).toEqual(['L1', 'L2'])
+ expect(ctx.nodeObjects).toHaveLength(3)
+ expect(ctx.nodeLayerNames).toEqual(['A_L1', 'B_L2', 'C_L1'])
+ expect(ctx.nodeGroups['C_L1']).toBe('L1')
+ expect(ctx.edgeObjects).toHaveLength(2)
+ expect(ctx.edgeObjects[0].interLayer).toBe(true)
+ expect(ctx.edgeObjects[1].interLayer).toBe(false)
+ // layers spread apart on x, nodes scrambled onto their layer
+ expect(ctx.layers[0].getPosition('x')).not.toBe(
+ ctx.layers[1].getPosition('x')
+ )
+ })
+
+ it('collapses same-pair channel rows into one Edge with channel colors', () => {
+ buildNetwork(
+ network({
+ channels: ['ch1', 'ch2'],
+ edges: [edge({ channel: 'ch1' }), edge({ channel: 'ch2' })],
+ })
+ )
+ expect(ctx.edgeObjects).toHaveLength(1)
+ expect(ctx.edgeObjects[0].channels).toEqual(['ch1', 'ch2'])
+ expect(ctx.edgeObjects[0].colors).toEqual(['#111111', '#222222'])
+ expect(ctx.channelVisibility).toEqual({ ch1: true, ch2: true })
+ expect(store.get().selectedChannels).toEqual(['ch1', 'ch2'])
+ })
+})
+
+describe('loadNetwork', () => {
+ it('is one undoable step; undo restores the previous (empty) registries', () => {
+ loadNetwork(network())
+ expect(ctx.nodeObjects).toHaveLength(3)
+ expect(history.canUndo).toBe(true)
+ history.undo()
+ expect(ctx.nodeObjects).toHaveLength(0)
+ expect(ctx.layers).toHaveLength(0)
+ history.redo()
+ expect(ctx.nodeObjects).toHaveLength(3)
+ })
+
+ it('rejects networks above the channel limit', () => {
+ expect(() => loadNetwork(network({ channels: ['a', 'b', 'c'] }))).toThrow(
+ /no more than 2 channels/
+ )
+ })
+})
diff --git a/frontend/src/actions/network.ts b/frontend/src/actions/network.ts
new file mode 100644
index 0000000..01f1ed1
--- /dev/null
+++ b/frontend/src/actions/network.ts
@@ -0,0 +1,262 @@
+// Port of v2 www/js/object_actions/network.js — buildNetwork (TSV upload via
+// POST /api/network) and importNetwork (session JSON via POST
+// /api/session/import; the backend normalizes + fills defaults). Layer/edge
+// count limits are enforced server-side. Channel UI stays with Phase 13.
+
+import type {
+ EdgeModel,
+ NetworkData,
+ NodeModel,
+ SessionData,
+} from '../api/client'
+import { history } from '../commands/base'
+import { LoadNetworkCommand } from '../commands/scene'
+import { store } from '../store'
+import {
+ COLOR_VECTOR_280,
+ ctx,
+ Edge,
+ Layer,
+ Node,
+ resetContext,
+ Scene,
+} from '../three'
+import { createLabels, setLabelColor } from './labels'
+import { initialSpreadLayers } from './layer'
+import { scrambleNodes } from './node'
+import { setRendererColor } from './screen'
+
+// v2 uploadNetwork(): one undoable step wrapping the full build.
+export function loadNetwork(data: NetworkData): void {
+ const maxChannels = store.get().config?.max_channels
+ if (maxChannels !== undefined && data.channels.length > maxChannels)
+ throw new Error(
+ `Network must contain no more than ${maxChannels} channels.`
+ )
+ history.execute(new LoadNetworkCommand(() => buildNetwork(data)))
+}
+
+export function buildNetwork(data: NetworkData): void {
+ // Fresh Scene + registries (not mutated in place) so LoadNetworkCommand's
+ // prev-snapshot still points at the intact previous scene graph.
+ const edgeDefaultColor = ctx.edgeDefaultColor
+ resetContext()
+ ctx.edgeDefaultColor = edgeDefaultColor
+
+ const scene = new Scene()
+ scene.tiltDefault()
+ scene.setScale(0.9)
+ ctx.scene = scene
+
+ initializeLayers(data.layers)
+ initializeChannels(data.channels)
+ initializeNodes(data.nodes)
+ createEdgeObjects(data.edges)
+ createLabels() // v2 executePostNetworkSetup
+
+ store.update({ network: data, selectedChannels: [...data.channels] })
+}
+
+function initializeLayers(names: string[]): void {
+ names.forEach((name, i) => {
+ ctx.layerGroups[name] = i
+ const layer = new Layer({ id: i, name })
+ ctx.layers.push(layer)
+ ctx.scene!.addLayer(layer.plane)
+ })
+ initialSpreadLayers()
+}
+
+function initializeChannels(channels: string[]): void {
+ const palette = store.get().config?.channel_colors_light ?? []
+ channels.forEach((channel, i) => {
+ ctx.channelColors[channel] =
+ palette[i % palette.length] ?? ctx.edgeDefaultColor
+ ctx.channelVisibility[channel] = true
+ })
+}
+
+function initializeNodes(nodes: NodeModel[]): void {
+ nodes.forEach((n, i) => {
+ ctx.nodeLayerNames.push(n.id)
+ ctx.nodeGroups[n.id] = n.layer
+ const layerIndex = ctx.layerGroups[n.layer]
+ const node = new Node({
+ id: i,
+ name: n.label,
+ layer: n.layer,
+ nodeLayerName: n.id,
+ color: COLOR_VECTOR_280[layerIndex % COLOR_VECTOR_280.length],
+ })
+ ctx.nodeObjects.push(node)
+ ctx.layers[layerIndex].addNode(node.sphere)
+ })
+ scrambleNodes()
+}
+
+// EdgeModel plus an optional per-row file color (session imports carry one).
+type EdgeRow = EdgeModel & { color?: string }
+
+// v2 edge.js createEdgeObjects: one Edge per src---trg pair; multi-channel
+// rows between the same pair collapse into that Edge's channel arrays. A
+// row's own color wins over the channel palette (v2 decideEdgeColors).
+function createEdgeObjects(edges: EdgeRow[]): void {
+ const byPair = new Map()
+ for (const e of edges) {
+ const key = `${e.src}---${e.trg}`
+ const group = byPair.get(key)
+ if (group) group.push(e)
+ else byPair.set(key, [e])
+ }
+
+ let id = 0
+ for (const group of byPair.values()) {
+ const first = group[0]
+ const hasChannels = first.channel != null
+ const colors = hasChannels
+ ? group.map((e) => e.color || ctx.channelColors[e.channel!])
+ : first.color
+ ? [first.color]
+ : undefined
+ ctx.edgeObjects.push(
+ new Edge({
+ id: id++,
+ source: first.src,
+ target: first.trg,
+ weights: group.map((e) => e.scaled_weight),
+ channels: hasChannels ? group.map((e) => e.channel!) : [],
+ colors,
+ interLayer: first.source_layer !== first.target_layer,
+ })
+ )
+ }
+}
+
+// v2 importNetwork(): one undoable step wrapping the session build.
+export function loadSession(session: SessionData): void {
+ history.execute(new LoadNetworkCommand(() => buildFromSession(session)))
+}
+
+// v2 importNetwork/initialize*FromJSON/setJSONExtras.
+export function buildFromSession(s: SessionData): void {
+ const edgeDefaultColor = ctx.edgeDefaultColor
+ resetContext()
+ ctx.edgeDefaultColor = edgeDefaultColor
+
+ // scene from JSON
+ const scene = new Scene()
+ scene.setPosition('x', Number(s.scene.position_x))
+ scene.setPosition('y', Number(s.scene.position_y))
+ scene.setScale(Number(s.scene.scale))
+ scene.setRotation('x', Number(s.scene.rotation_x))
+ scene.setRotation('y', Number(s.scene.rotation_y))
+ scene.setRotation('z', Number(s.scene.rotation_z))
+ ctx.scene = scene
+ setRendererColor(s.scene.color)
+
+ // layers from JSON
+ s.layers.forEach((l, i) => {
+ ctx.layerGroups[l.name] = i
+ const layer = new Layer({
+ id: i,
+ name: l.name,
+ position_x: Number(l.position_x),
+ position_y: Number(l.position_y),
+ position_z: Number(l.position_z),
+ last_layer_scale: Number(l.last_layer_scale),
+ rotation_x: Number(l.rotation_x),
+ rotation_y: Number(l.rotation_y),
+ rotation_z: Number(l.rotation_z),
+ floor_current_color: l.floor_current_color,
+ geometry_parameters_width: Number(l.geometry_parameters_width),
+ })
+ ctx.layers.push(layer)
+ ctx.scene!.addLayer(layer.plane)
+ })
+ // sessions without layer coordinates get the upload spread (backend flag)
+ if (s.layers.length > 0 && s.layers.every((l) => l.generate_coordinates))
+ initialSpreadLayers()
+
+ // channels from the edge rows
+ const channels = [
+ ...new Set(s.edges.map((e) => e.channel).filter((c): c is string => !!c)),
+ ]
+ initializeChannels(channels)
+
+ // nodes from JSON
+ s.nodes.forEach((n, i) => {
+ const nodeLayerName = `${n.name}_${n.layer}`
+ ctx.nodeLayerNames.push(nodeLayerName)
+ ctx.nodeGroups[nodeLayerName] = n.layer
+ const node = new Node({
+ id: i,
+ name: n.name,
+ layer: n.layer,
+ nodeLayerName,
+ position_x: Number(n.position_x),
+ position_y: Number(n.position_y),
+ position_z: Number(n.position_z),
+ scale: Number(n.scale),
+ color: n.color,
+ url: n.url,
+ descr: n.descr,
+ })
+ ctx.nodeObjects.push(node)
+ ctx.layers[ctx.layerGroups[n.layer]].addNode(node.sphere)
+ })
+ if (s.scramble_nodes) {
+ const minWidth = Math.min(
+ ...ctx.layers.map((l) => l.geometry_parameters_width)
+ )
+ scrambleNodes(-minWidth / 2, minWidth / 2, -minWidth / 2, minWidth / 2)
+ }
+
+ // edges from JSON (opacity plays the scaled-weight role; colors are file
+ // colors, hence edgeFileColorPriority below). Resolve endpoints against the
+ // node list rather than slicing the id — a node name may itself contain
+ // '_', and the backend guarantees src/trg reference a known node.
+ const nodeById = new Map(s.nodes.map((n) => [`${n.name}_${n.layer}`, n]))
+ const rows: EdgeRow[] = s.edges.map((e) => {
+ const src = nodeById.get(e.src)
+ const trg = nodeById.get(e.trg)
+ return {
+ src: e.src,
+ trg: e.trg,
+ source_node: src?.name ?? e.src,
+ source_layer: src?.layer ?? '',
+ target_node: trg?.name ?? e.trg,
+ target_layer: trg?.layer ?? '',
+ weight: Number(e.opacity),
+ scaled_weight: Number(e.opacity),
+ channel: e.channel ?? null,
+ color: e.color,
+ }
+ })
+ createEdgeObjects(rows)
+ createLabels()
+
+ // v2 setJSONExtras
+ setLabelColor(s.universalLabelColor)
+ ctx.isDirectionEnabled = Boolean(s.direction)
+ ctx.edgeWidthByWeight = Boolean(s.edgeOpacityByWeight)
+ ctx.edgeFileColorPriority = true
+
+ store.update({
+ network: {
+ nodes: s.nodes.map((n) => ({
+ id: `${n.name}_${n.layer}`,
+ label: n.name,
+ layer: n.layer,
+ })),
+ edges: rows.map((r) => {
+ const edge = { ...r }
+ delete edge.color
+ return edge
+ }),
+ layers: s.layers.map((l) => l.name),
+ channels,
+ warnings: [],
+ },
+ selectedChannels: [...channels],
+ })
+}
diff --git a/frontend/src/actions/node.test.ts b/frontend/src/actions/node.test.ts
new file mode 100644
index 0000000..75766a8
--- /dev/null
+++ b/frontend/src/actions/node.test.ts
@@ -0,0 +1,139 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import { store } from '../store'
+import {
+ ctx,
+ Layer,
+ Node,
+ resetContext,
+ SELECTED_DEFAULT_COLOR,
+} from '../three'
+import {
+ applyNodeAttributes,
+ decideNodeLabelFlags,
+ getSelectedNodes,
+ repaintNode,
+ selectAllNodes,
+ selectNodesByName,
+ setNodeSelectedColorPriority,
+ unselectAllNodes,
+} from './node'
+
+function seed(): void {
+ ctx.layers = [new Layer({ id: 0, name: 'L1' })]
+ ctx.layerGroups = { L1: 0 }
+ ctx.nodeGroups = { A_L1: 'L1', B_L1: 'L1' }
+ ctx.nodeLayerNames = ['A_L1', 'B_L1']
+ ctx.nodeObjects = [
+ new Node({
+ id: 0,
+ name: 'A',
+ nodeLayerName: 'A_L1',
+ layer: 'L1',
+ color: '#123456',
+ }),
+ new Node({
+ id: 1,
+ name: 'B',
+ nodeLayerName: 'B_L1',
+ layer: 'L1',
+ color: '#123456',
+ }),
+ ]
+}
+
+beforeEach(() => {
+ resetContext()
+ store.update({ selectedNodes: [] })
+ seed()
+})
+
+describe('selection', () => {
+ it('selectAllNodes selects, repaints and syncs the store; unselect reverts', () => {
+ selectAllNodes(true)
+ expect(getSelectedNodes()).toEqual([0, 1])
+ expect(ctx.nodeObjects[0].color).toBe(SELECTED_DEFAULT_COLOR)
+ expect(store.get().selectedNodes).toEqual(['A_L1', 'B_L1'])
+
+ unselectAllNodes()
+ expect(getSelectedNodes()).toEqual([])
+ expect(ctx.nodeObjects[0].color).toBe('#123456')
+ expect(store.get().selectedNodes).toEqual([])
+ })
+
+ it('selectNodesByName matches comma-separated names case-insensitively', () => {
+ selectNodesByName(' a , nosuch')
+ expect(getSelectedNodes()).toEqual([0])
+ expect(store.get().selectedNodes).toEqual(['A_L1'])
+ })
+})
+
+describe('repaintNode', () => {
+ it('honours the selected-color priority flag', () => {
+ ctx.nodeObjects[0].isSelected = true
+ setNodeSelectedColorPriority(false)
+ expect(ctx.nodeObjects[0].color).toBe('#123456')
+ setNodeSelectedColorPriority(true)
+ expect(ctx.nodeObjects[0].color).toBe(SELECTED_DEFAULT_COLOR)
+ })
+
+ it('falls back to cluster color when cluster priority is active', () => {
+ ctx.nodeColorPrioritySource = 'cluster'
+ ctx.nodeObjects[0].setColor('#ff0000', false, true) // clusterMode
+ ctx.nodeObjects[0].setColor('#00ff00') // transient color
+ repaintNode(0)
+ expect(ctx.nodeObjects[0].color).toBe('#ff0000')
+ })
+})
+
+describe('decideNodeLabelFlags', () => {
+ it('hidden layer wins over everything; selected shows when flag set', () => {
+ ctx.nodeObjects[0].isSelected = true
+ decideNodeLabelFlags() // showSelectedNodeLabelsFlag default true
+ expect(ctx.nodeObjects[0].showLabel).toBe(true)
+ expect(ctx.nodeObjects[1].showLabel).toBe(false)
+
+ ctx.layers[0].isVisible = false
+ decideNodeLabelFlags()
+ expect(ctx.nodeObjects[0].showLabel).toBe(false)
+ })
+
+ it('showAllNodeLabelsFlag shows every visible-layer label', () => {
+ ctx.showAllNodeLabelsFlag = true
+ decideNodeLabelFlags()
+ expect(ctx.nodeObjects.every((n) => n.showLabel)).toBe(true)
+ })
+
+ it('hovered node label shows', () => {
+ ctx.lastHoveredNodeIndex = 1
+ decideNodeLabelFlags()
+ expect(ctx.nodeObjects[1].showLabel).toBe(true)
+ })
+})
+
+describe('applyNodeAttributes', () => {
+ it('applies color/size/url/descr to matching nodes and sets default priority', () => {
+ ctx.nodeColorPrioritySource = 'cluster'
+ applyNodeAttributes([
+ {
+ node_layer: 'A_L1',
+ color: '#ff0000',
+ size: 2.5,
+ url: 'http://x',
+ description: 'hello',
+ },
+ {
+ node_layer: 'ghost_L9',
+ color: '#00ff00',
+ size: null,
+ url: null,
+ description: null,
+ },
+ ])
+ const node = ctx.nodeObjects[0]
+ expect(node.importedColor).toBe('#ff0000')
+ expect(node.sphere.scale.x).toBe(2.5)
+ expect(node.url).toBe('http://x')
+ expect(node.descr).toBe('hello')
+ expect(ctx.nodeColorPrioritySource).toBe('default')
+ })
+})
diff --git a/frontend/src/actions/node.ts b/frontend/src/actions/node.ts
new file mode 100644
index 0000000..7217b4d
--- /dev/null
+++ b/frontend/src/actions/node.ts
@@ -0,0 +1,192 @@
+// Port of v2 www/js/object_actions/node.js — hover, selection, repaint,
+// shape/color-priority setters. Selection state syncs to the store instead
+// of Shiny inputs. Deferred with their phases: search bar / sliders / node
+// description div (Phase 13 UI), lasso + held-key node moves + spread/move/
+// scale sliders (canvas_controls.ts), attribute upload (POST /api/attributes).
+
+import type { NodeAttributeRow } from '../api/client'
+import { store } from '../store'
+import { ctx, SELECTED_DEFAULT_COLOR } from '../three'
+import type { ColorPrioritySource, NodeGeometryType } from '../three'
+import {
+ findIndexByUuid,
+ getCaseInsensitiveIndices,
+ getRandomArbitrary,
+ setRandomSeed,
+} from '../utils'
+import { raycaster, setRaycaster } from './screen'
+
+// v2 set.seed(123): fixed so every network load scatters to the same layout.
+const SCRAMBLE_SEED = 123
+
+// v2 node.js scrambleNodes — random y/z spread onto each node's layer plane.
+// Re-seeds first so the same network always scatters identically (Load Example
+// and uploads are reproducible).
+export function scrambleNodes(
+ yMin = ctx.yBoundMin,
+ yMax = ctx.yBoundMax,
+ zMin = ctx.zBoundMin,
+ zMax = ctx.zBoundMax
+): void {
+ setRandomSeed(SCRAMBLE_SEED)
+ for (const node of ctx.nodeObjects) {
+ node.translateY(getRandomArbitrary(yMin, yMax))
+ node.translateZ(getRandomArbitrary(zMin, zMax))
+ }
+}
+
+export function updateSelectedNodesStore(): void {
+ store.update({
+ selectedNodes: ctx.nodeObjects
+ .filter((n) => n.isSelected)
+ .map((n) => n.nodeLayerName),
+ })
+}
+
+// @return whether the pointer is over a node
+export function checkHoverOverNode(event: {
+ clientX: number
+ clientY: number
+}): boolean {
+ setRaycaster(event)
+ const spheres = ctx.nodeObjects.map(({ sphere }) => sphere)
+ const intersects = raycaster().intersectObjects(spheres, false)
+ let changed = false
+ let hovering = false
+
+ if (ctx.lastHoveredNodeIndex !== null) {
+ ctx.nodeObjects[ctx.lastHoveredNodeIndex].setOpacity(1)
+ ctx.lastHoveredNodeIndex = null
+ changed = true
+ }
+
+ if (intersects.length > 0) {
+ const i = findIndexByUuid(spheres, intersects[0].object.uuid)
+ ctx.nodeObjects[i].setOpacity(0.5)
+ ctx.lastHoveredNodeIndex = i
+ changed = true
+ hovering = true
+ }
+
+ if (changed) decideNodeLabelFlags()
+ return hovering
+}
+
+// v2 read the hideLayer DOM checkboxes; layer visibility now lives on Layer.
+export function decideNodeLabelFlags(): void {
+ ctx.renderNodeLabelsFlag = true
+ ctx.nodeObjects.forEach((node, i) => {
+ const layer =
+ ctx.layers[ctx.layerGroups[ctx.nodeGroups[ctx.nodeLayerNames[i]]]]
+ // Priorities list:
+ if (!layer.isVisible) node.showLabel = false
+ else if (ctx.showAllNodeLabelsFlag) node.showLabel = true
+ else if (layer.showNodeLabels) node.showLabel = true
+ else if (ctx.showSelectedNodeLabelsFlag && node.isSelected)
+ node.showLabel = true
+ else if (i === ctx.lastHoveredNodeIndex) node.showLabel = true
+ else node.showLabel = false
+ })
+}
+
+// @return selected node indexes
+export function getSelectedNodes(): number[] {
+ return ctx.nodeObjects.filter((n) => n.isSelected).map((n) => n.id)
+}
+
+// @return whether a node was toggled
+export function performDoubleClickNodeSelection(event: {
+ clientX: number
+ clientY: number
+}): boolean {
+ setRaycaster(event)
+ const spheres = ctx.nodeObjects.map(({ sphere }) => sphere)
+ const intersects = raycaster().intersectObjects(spheres, false)
+ if (intersects.length === 0) return false
+
+ const i = findIndexByUuid(spheres, intersects[0].object.uuid)
+ ctx.nodeObjects[i].isSelected = !ctx.nodeObjects[i].isSelected
+ repaintNode(i)
+ decideNodeLabelFlags()
+ updateSelectedNodesStore()
+ return true
+}
+
+export function repaintNode(i: number): void {
+ const node = ctx.nodeObjects[i]
+ if (ctx.selectedNodeColorFlag && node.isSelected)
+ node.setColor(SELECTED_DEFAULT_COLOR)
+ // getColor resolves the default/picker/cluster priority (v2 repaintNode's
+ // manual cluster branch is folded into it)
+ else node.setColor(node.getColor())
+}
+
+export function repaintNodes(): void {
+ ctx.nodeObjects.forEach((_, i) => repaintNode(i))
+}
+
+export function selectAllNodes(selected: boolean): void {
+ ctx.nodeObjects.forEach((node, i) => {
+ node.isSelected = selected
+ repaintNode(i)
+ })
+ decideNodeLabelFlags()
+ updateSelectedNodesStore()
+}
+
+export function unselectAllNodes(): void {
+ selectAllNodes(false)
+}
+
+// Core of v2 selectSearchedNodes minus the DOM search bar (Phase 13 wires
+// the input + Enter key to this). Comma-separated, case-insensitive names.
+export function selectNodesByName(search: string): void {
+ const names = ctx.nodeObjects.map(({ name }) => name)
+ for (const term of search.split(',')) {
+ for (const i of getCaseInsensitiveIndices(names, term.trim())) {
+ if (!ctx.nodeObjects[i].isSelected) {
+ ctx.nodeObjects[i].isSelected = true
+ repaintNode(i)
+ }
+ }
+ }
+ decideNodeLabelFlags()
+ updateSelectedNodesStore()
+}
+
+let currentNodeShape: NodeGeometryType = 'sphere'
+
+export function getNodeShape(): NodeGeometryType {
+ return currentNodeShape
+}
+
+export function setNodeShape(shape: NodeGeometryType): void {
+ currentNodeShape = shape
+ for (const node of ctx.nodeObjects) node.setGeometry(shape)
+}
+
+export function setNodeColorPriority(source: ColorPrioritySource): void {
+ ctx.nodeColorPrioritySource = source
+ repaintNodes()
+}
+
+export function setNodeSelectedColorPriority(flag: boolean): void {
+ ctx.selectedNodeColorFlag = flag
+ repaintNodes()
+}
+
+// v2 node.js setNodeAttributes + handler_clickNodeColorPriority("default"):
+// apply per-node color/size/url/description from an attribute file, then
+// flip color priority to default so imported colors show.
+export function applyNodeAttributes(rows: NodeAttributeRow[]): void {
+ for (const row of rows) {
+ const pos = ctx.nodeLayerNames.indexOf(row.node_layer)
+ if (pos === -1) continue // node not in network
+ const node = ctx.nodeObjects[pos]
+ if (row.color) node.setColor(row.color, true)
+ if (row.size !== null) node.setScale(row.size)
+ if (row.url) node.url = row.url
+ if (row.description) node.descr = row.description
+ }
+ setNodeColorPriority('default')
+}
diff --git a/frontend/src/actions/right_click_menu.test.ts b/frontend/src/actions/right_click_menu.test.ts
new file mode 100644
index 0000000..e6fa716
--- /dev/null
+++ b/frontend/src/actions/right_click_menu.test.ts
@@ -0,0 +1,104 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import { store } from '../store'
+import { ctx, Edge, Layer, Node, resetContext, Scene } from '../three'
+import {
+ executeCommand,
+ selectDownstreamPath,
+ selectMultiLayerPath,
+ selectNeighbors,
+} from './right_click_menu'
+
+// Three layers; A,D on L1, B on L2, C on L3.
+// Edges: A-B (inter), B-C (inter), A-D (intra L1).
+function seed(): void {
+ ctx.scene = new Scene()
+ ctx.layers = [
+ new Layer({ id: 0, name: 'L1' }),
+ new Layer({ id: 1, name: 'L2' }),
+ new Layer({ id: 2, name: 'L3' }),
+ ]
+ ctx.layerGroups = { L1: 0, L2: 1, L3: 2 }
+ ctx.nodeGroups = { A_L1: 'L1', B_L2: 'L2', C_L3: 'L3', D_L1: 'L1' }
+ ctx.nodeLayerNames = ['A_L1', 'B_L2', 'C_L3', 'D_L1']
+ ctx.nodeObjects = ctx.nodeLayerNames.map(
+ (nl, i) =>
+ new Node({
+ id: i,
+ name: nl.split('_')[0],
+ nodeLayerName: nl,
+ layer: ctx.nodeGroups[nl],
+ })
+ )
+ ctx.nodeObjects.forEach((n, i) =>
+ ctx.layers[ctx.layerGroups[ctx.nodeGroups[ctx.nodeLayerNames[i]]]].addNode(
+ n.sphere
+ )
+ )
+ ctx.edgeObjects = [
+ new Edge({
+ id: 0,
+ source: 'A_L1',
+ target: 'B_L2',
+ weights: [1],
+ interLayer: true,
+ }),
+ new Edge({
+ id: 1,
+ source: 'B_L2',
+ target: 'C_L3',
+ weights: [1],
+ interLayer: true,
+ }),
+ new Edge({ id: 2, source: 'A_L1', target: 'D_L1', weights: [1] }),
+ ]
+}
+
+const selectedNodes = (): number[] =>
+ ctx.nodeObjects.filter((n) => n.isSelected).map((n) => n.id)
+
+beforeEach(() => {
+ resetContext()
+ store.update({ selectedNodes: [] })
+ seed()
+})
+
+describe('selectNeighbors', () => {
+ it('selects direct neighbors and their edges (inter and intra)', () => {
+ selectNeighbors(0) // A
+ expect(selectedNodes()).toEqual([1, 3]) // B and D
+ expect(ctx.edgeObjects[0].isSelected).toBe(true)
+ expect(ctx.edgeObjects[2].isSelected).toBe(true)
+ expect(ctx.edgeObjects[1].isSelected).toBe(false)
+ })
+})
+
+describe('selectMultiLayerPath', () => {
+ it('walks across layers, skipping the starting and current layer', () => {
+ selectMultiLayerPath(0) // A: -> B (L2) -> C (L3); D (L1) excluded
+ expect(selectedNodes()).toEqual([1, 2])
+ })
+})
+
+describe('selectDownstreamPath', () => {
+ it('follows inter-layer edges into unvisited layers only', () => {
+ selectDownstreamPath(0) // A -> B -> C; D shares the starting layer
+ expect(selectedNodes()).toEqual([1, 2])
+ expect(ctx.edgeObjects[0].isSelected).toBe(true)
+ expect(ctx.edgeObjects[1].isSelected).toBe(true)
+ expect(ctx.edgeObjects[2].isSelected).toBe(false)
+ })
+})
+
+describe('executeCommand', () => {
+ it('runs the command, raises render flags and syncs the store', () => {
+ executeCommand(0, 'Select Neighbors')
+ expect(ctx.renderInterLayerEdgesFlag).toBe(true)
+ expect(store.get().selectedNodes).toEqual(['B_L2', 'D_L1'])
+ })
+
+ it('"-" is a no-op', () => {
+ executeCommand(0, '-')
+ expect(selectedNodes()).toEqual([])
+ expect(ctx.renderInterLayerEdgesFlag).toBe(false)
+ })
+})
diff --git a/frontend/src/actions/right_click_menu.ts b/frontend/src/actions/right_click_menu.ts
new file mode 100644
index 0000000..3c3dcbd
--- /dev/null
+++ b/frontend/src/actions/right_click_menu.ts
@@ -0,0 +1,247 @@
+// Port of v2 www/js/object_actions/right_click_menu.js + the context-menu
+// builder from event_listeners.js. The graph commands (neighbors, multilayer
+// path, downstream path) are pure; the menu is a appended to
+// #labelDiv over the clicked node, as in v2. Loader spinner + description
+// panel content stay with Phase 13 UI.
+
+import { ctx, SPHERE_RADIUS } from '../three'
+import { exists } from '../utils'
+import { redrawIntraLayerEdges } from './edge'
+import {
+ decideNodeLabelFlags,
+ repaintNode,
+ updateSelectedNodesStore,
+} from './node'
+
+let optionsList: HTMLSelectElement | null = null
+
+function selectNodeAndEdge(nodeIndex: number, edgeIndex: number): void {
+ ctx.nodeObjects[nodeIndex].isSelected = true
+ repaintNode(nodeIndex)
+ ctx.edgeObjects[edgeIndex].select()
+}
+
+// Select every direct neighbor of `node` and the connecting edges.
+export function selectNeighbors(node: number): void {
+ ctx.edgeObjects.forEach((edge, i) => {
+ const index1 = ctx.nodeLayerNames.indexOf(edge.source)
+ const index2 = ctx.nodeLayerNames.indexOf(edge.target)
+ if (index1 === node) selectNodeAndEdge(index2, i)
+ else if (index2 === node) selectNodeAndEdge(index1, i)
+ })
+}
+
+// Walk outward from `node`, selecting neighbors that sit on layers other
+// than the starting layer and the current node's layer (v2 semantics).
+export function selectMultiLayerPath(node: number): void {
+ const startingLayer = ctx.nodeGroups[ctx.nodeLayerNames[node]]
+ const selected: number[] = []
+ const checked: number[] = []
+ let currentNode = node
+
+ for (;;) {
+ ctx.edgeObjects.forEach((edge, i) => {
+ const index1 = ctx.nodeLayerNames.indexOf(edge.source)
+ const index2 = ctx.nodeLayerNames.indexOf(edge.target)
+ const currentLayer = ctx.nodeGroups[ctx.nodeLayerNames[currentNode]]
+
+ const trySelect = (other: number): void => {
+ const otherLayer = ctx.nodeGroups[ctx.nodeLayerNames[other]]
+ if (
+ otherLayer !== startingLayer && // path must not re-enter the starting layer
+ otherLayer !== currentLayer && // or stay on its own layer
+ !exists(selected, other)
+ ) {
+ selected.push(other)
+ selectNodeAndEdge(other, i)
+ }
+ }
+
+ if (index1 === currentNode) trySelect(index2)
+ else if (index2 === currentNode) trySelect(index1)
+ })
+
+ checked.push(currentNode)
+ const unchecked = selected.filter((x) => !checked.includes(x))
+ if (unchecked.length === 0) return
+ currentNode = unchecked[0]
+ }
+}
+
+// Recursively select the downstream path: follow neighbors whose layer has
+// not been visited on the current path.
+export function selectDownstreamPath(node: number): void {
+ const layerPath = [ctx.nodeGroups[ctx.nodeLayerNames[node]]]
+ recursiveDownstreamHighlight(layerPath, node, null, [])
+}
+
+function recursiveDownstreamHighlight(
+ layerPath: string[],
+ currentNode: number,
+ previousNode: number | null,
+ checkedNodes: number[]
+): void {
+ if (exists(checkedNodes, currentNode)) return
+ checkedNodes.push(currentNode)
+
+ if (previousNode !== null) {
+ const edge = getInterLayerEdge(currentNode, previousNode)
+ if (edge !== null) selectNodeAndEdge(currentNode, edge)
+ }
+
+ for (const neighbor of getNeighbors(currentNode)) {
+ const toCheckLayer = ctx.nodeGroups[ctx.nodeLayerNames[neighbor]]
+ if (!exists(layerPath, toCheckLayer)) {
+ layerPath.push(toCheckLayer)
+ recursiveDownstreamHighlight(
+ layerPath,
+ neighbor,
+ currentNode,
+ checkedNodes
+ )
+ layerPath.pop()
+ }
+ }
+}
+
+// Index of the inter-layer edge connecting the two nodes, or null.
+function getInterLayerEdge(node1: number, node2: number): number | null {
+ for (let i = 0; i < ctx.edgeObjects.length; i++) {
+ if (!ctx.edgeObjects[i].interLayer) continue
+ const index1 = ctx.nodeLayerNames.indexOf(ctx.edgeObjects[i].source)
+ const index2 = ctx.nodeLayerNames.indexOf(ctx.edgeObjects[i].target)
+ if (
+ (node1 === index1 && node2 === index2) ||
+ (node1 === index2 && node2 === index1)
+ )
+ return i
+ }
+ return null
+}
+
+// All neighbor node indexes (v2's getInterLayerNeighbors — despite the name
+// it walks every edge; the layerPath check filters same-layer hops).
+function getNeighbors(node: number): number[] {
+ const neighbors: number[] = []
+ ctx.edgeObjects.forEach((edge) => {
+ const index1 = ctx.nodeLayerNames.indexOf(edge.source)
+ const index2 = ctx.nodeLayerNames.indexOf(edge.target)
+ if (node === index1) neighbors.push(index2)
+ else if (node === index2) neighbors.push(index1)
+ })
+ return neighbors
+}
+
+// v2 executeCommand's tail: redraw + labels + store sync after any command.
+function finishCommand(): void {
+ ctx.renderInterLayerEdgesFlag = true
+ redrawIntraLayerEdges()
+ decideNodeLabelFlags()
+ updateSelectedNodesStore()
+}
+
+export function removeContextMenu(): void {
+ if (optionsList) {
+ optionsList.remove()
+ optionsList = null
+ }
+}
+
+// Right-click on a node: build the options over it (v2
+// replaceContextMenuOverNode from event_listeners.js).
+export function replaceContextMenuOverNode(event: {
+ layerX: number
+ layerY: number
+ preventDefault: () => void
+}): void {
+ removeContextMenu()
+ const container =
+ typeof document === 'undefined' ? null : document.getElementById('labelDiv')
+ if (!container) return
+
+ for (let i = 0; i < ctx.nodeObjects.length; i++) {
+ const nodeX = ctx.xBoundMax + ctx.nodeObjects[i].getWorldPosition('x')
+ const nodeY = ctx.yBoundMax - ctx.nodeObjects[i].getWorldPosition('y')
+ if (
+ (nodeX - event.layerX) ** 2 + (nodeY - event.layerY) ** 2 >
+ (SPHERE_RADIUS + 1) ** 2
+ )
+ continue
+
+ event.preventDefault()
+ optionsList = buildMenu(i, nodeX, nodeY)
+ container.appendChild(optionsList)
+ return
+ }
+}
+
+function buildMenu(nodeIndex: number, x: number, y: number): HTMLSelectElement {
+ const node = ctx.nodeObjects[nodeIndex]
+ const select = document.createElement('select')
+ select.className = 'optionsBox'
+ select.style.position = 'absolute'
+ select.style.left = `${x}px`
+ select.style.top = `${y}px`
+ select.style.display = 'inline-block'
+
+ const options = [
+ '-',
+ 'Select Neighbors',
+ 'Select MultiLayer Path',
+ 'Select Downstream Path',
+ ]
+ if (node.url !== '') options.push('Link')
+ if (node.descr !== '') options.push('Description')
+ for (const text of options) {
+ const option = document.createElement('option')
+ option.text = text
+ select.appendChild(option)
+ }
+
+ select.addEventListener('change', () => {
+ executeCommand(nodeIndex, select.options[select.selectedIndex].text)
+ })
+ return select
+}
+
+// v2 setupDescrDiv + 'Description' command: close button + paragraph created
+// once, paragraph text swapped per node. textContent (not v2's innerHTML) —
+// descriptions come from uploaded/external session files.
+function showDescription(descr: string): void {
+ const descrDiv = document.getElementById('descrDiv')
+ if (!descrDiv) return
+ let p = descrDiv.querySelector('p')
+ if (!p) {
+ const btn = document.createElement('button')
+ btn.id = 'closeButton'
+ btn.textContent = 'X'
+ btn.onclick = () => (descrDiv.style.display = 'none')
+ p = document.createElement('p')
+ p.className = 'descrDiv_paragraph'
+ descrDiv.appendChild(btn)
+ descrDiv.appendChild(p)
+ }
+ p.textContent = descr
+ descrDiv.style.display = 'inline-block'
+}
+
+// XSS fix: only http(s)/mailto — blocks javascript:/data: payloads smuggled
+// in via an uploaded node-attributes file's Url column. noopener/noreferrer
+// so the opened tab can't reach back into this window via window.opener.
+function openNodeLink(url: string): void {
+ if (!/^(https?:|mailto:)/i.test(url.trim())) return
+ window.open(url, '_blank', 'noopener,noreferrer')
+}
+
+export function executeCommand(nodeIndex: number, option: string): void {
+ const node = ctx.nodeObjects[nodeIndex]
+ if (option === '-') return
+
+ if (option === 'Select Neighbors') selectNeighbors(nodeIndex)
+ else if (option === 'Select MultiLayer Path') selectMultiLayerPath(nodeIndex)
+ else if (option === 'Select Downstream Path') selectDownstreamPath(nodeIndex)
+ else if (option === 'Link') openNodeLink(node.url)
+ else if (option === 'Description') showDescription(node.descr)
+
+ finishCommand()
+}
diff --git a/frontend/src/actions/screen.ts b/frontend/src/actions/screen.ts
new file mode 100644
index 0000000..4412f4e
--- /dev/null
+++ b/frontend/src/actions/screen.ts
@@ -0,0 +1,288 @@
+// Renderer, camera, window bounds, raycaster and the render loop.
+// Port of v2 www/js/object_actions/screen.js. Shiny sync calls dropped.
+
+import * as THREE from 'three'
+import { ctx } from '../three'
+import {
+ onBackgroundColor,
+ renderFrame,
+ resizePostprocessing,
+} from '../three/postprocessing'
+import { renderInterLayerEdges } from './edge'
+
+// v2 global raycaster (config/global_variables.js). Reused each frame.
+const RAYCASTER = new THREE.Raycaster()
+const RAYVECTOR = new THREE.Vector3()
+const RAYDIR = new THREE.Vector3()
+
+export function setRenderer(): void {
+ ctx.renderer = new THREE.WebGLRenderer({
+ antialias: true,
+ powerPreference: 'high-performance',
+ })
+ // Cap DPR at 2: full retina sharpness, avoids 3x+ fill-rate cost on mobile.
+ ctx.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
+}
+
+export function resetScreen(): void {
+ setWindowBounds()
+ setCamera()
+ resizeRenderer()
+ // Bounds changed — label divs are positioned off xBoundMax/yBoundMax.
+ ctx.renderLayerLabelsFlag = true
+ ctx.renderNodeLabelsFlag = true
+}
+
+export function setWindowBounds(): void {
+ ctx.xBoundMin = -window.innerWidth / 2
+ ctx.xBoundMax = window.innerWidth / 2
+ ctx.yBoundMin = -window.innerHeight / 2
+ ctx.yBoundMax = window.innerHeight / 2
+ ctx.zBoundMin = -window.innerHeight / 2.5
+ ctx.zBoundMax = window.innerHeight / 2.5
+}
+
+export function setCamera(): void {
+ // orthographic: left, right, top, bottom, near, far
+ ctx.camera = new THREE.OrthographicCamera(
+ ctx.xBoundMin,
+ ctx.xBoundMax,
+ ctx.yBoundMax,
+ ctx.yBoundMin,
+ -4 * ctx.xBoundMax,
+ 4 * ctx.xBoundMax
+ )
+ ctx.camera.position.set(0, 0, 100)
+ ctx.camera.lookAt(0, 0, 0)
+}
+
+export function resizeRenderer(): void {
+ // Re-read DPR too: the window may have moved between monitors.
+ ctx.renderer?.setPixelRatio(Math.min(window.devicePixelRatio, 2))
+ ctx.renderer?.setSize(2 * ctx.xBoundMax, 2 * ctx.yBoundMax)
+ resizePostprocessing(2 * ctx.xBoundMax, 2 * ctx.yBoundMax)
+}
+
+export function setRendererColor(hexColor: string): void {
+ if (ctx.scene?.exists()) {
+ ctx.renderer?.setClearColor(hexColor)
+ onBackgroundColor(hexColor) // bloom only stays on over dark backgrounds
+ }
+}
+
+// Publication image export: render the scene into an offscreen renderer with
+// an orthographic frustum fitted to the visible layers/nodes, at a fixed
+// long-edge resolution, composite the visible DOM-overlay labels on top,
+// then download as PNG.
+const EXPORT_LONG_EDGE = 4096 // safe max renderbuffer size on all GPUs
+
+export function exportSceneImage(): boolean {
+ if (!ctx.scene?.exists() || !ctx.renderer || !ctx.camera) return false
+
+ ctx.scene.THREE_Object.updateMatrixWorld(true)
+ const box = new THREE.Box3()
+ const meshBox = new THREE.Box3()
+ for (const layer of ctx.layers) {
+ if (!layer.isVisible) continue
+ // traverseVisible skips individually hidden nodes (channel toggles etc.)
+ layer.plane.traverseVisible((obj) => {
+ if (obj instanceof THREE.Mesh || obj instanceof THREE.Line) {
+ meshBox.setFromObject(obj)
+ box.union(meshBox)
+ }
+ })
+ }
+ if (box.isEmpty()) return false
+
+ // Grow the box so visible labels fit too. Label divs render at 1 CSS px per
+ // world unit in the main view, so offsetWidth/Height are world sizes.
+ const zMid = (box.min.z + box.max.z) / 2
+ const corner = new THREE.Vector3()
+ forEachVisibleLabel((div, worldX, worldY, offsetX, offsetY) => {
+ const left = worldX + offsetX
+ const top = worldY - offsetY
+ box.expandByPoint(corner.set(left, top, zMid))
+ box.expandByPoint(
+ corner.set(left + div.offsetWidth, top - div.offsetHeight, zMid)
+ )
+ })
+
+ // Camera sits at (0,0,100) looking down -z with no roll, so world x/y map
+ // straight onto the frustum sides. 5% margin around the fitted box.
+ const size = box.getSize(new THREE.Vector3())
+ const pad = 0.05 * Math.max(size.x, size.y)
+ const camera = ctx.camera.clone()
+ camera.left = box.min.x - pad
+ camera.right = box.max.x + pad
+ camera.top = box.max.y + pad
+ camera.bottom = box.min.y - pad
+ camera.updateProjectionMatrix()
+
+ const w = camera.right - camera.left
+ const h = camera.top - camera.bottom
+ const scale = EXPORT_LONG_EDGE / Math.max(w, h)
+ const renderer = new THREE.WebGLRenderer({
+ antialias: true,
+ preserveDrawingBuffer: true,
+ })
+ renderer.setSize(Math.round(w * scale), Math.round(h * scale), false)
+ renderer.setClearColor(ctx.renderer.getClearColor(new THREE.Color()), 1)
+ renderer.render(ctx.scene.THREE_Object, camera)
+
+ // Composite onto a 2D canvas so the DOM-overlay labels can be drawn on top.
+ const out = document.createElement('canvas')
+ out.width = renderer.domElement.width
+ out.height = renderer.domElement.height
+ const g = out.getContext('2d')
+ if (!g) return false
+ g.drawImage(renderer.domElement, 0, 0)
+ drawExportLabels(g, camera, scale)
+
+ out.toBlob((blob) => {
+ if (blob) {
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = 'arena3d_scene.png'
+ a.click()
+ URL.revokeObjectURL(url)
+ }
+ renderer.dispose()
+ renderer.forceContextLoss()
+ }, 'image/png')
+ return true
+}
+
+// Draw the currently visible label divs (#labelDiv, kept in sync by the
+// animate loop) into the export canvas. Read from the DOM rather than
+// labels.ts — importing labels here would recreate the screen -> labels ->
+// node -> screen cycle the animate hooks exist to avoid. Positions come from
+// the same world coords the divs use; offsets/fonts scale with the export.
+function drawExportLabels(
+ g: CanvasRenderingContext2D,
+ camera: THREE.OrthographicCamera,
+ scale: number
+): void {
+ g.textBaseline = 'top'
+ forEachVisibleLabel((div, worldX, worldY, offsetX, offsetY) => {
+ const cs = getComputedStyle(div)
+ g.font = `${parseFloat(cs.fontSize) * scale}px ${cs.fontFamily}`
+ g.fillStyle = cs.color
+ g.fillText(
+ div.textContent ?? '',
+ (worldX - camera.left + offsetX) * scale,
+ (camera.top - worldY + offsetY) * scale
+ )
+ })
+}
+
+// Iterate the currently visible label divs with their world-space anchor and
+// screen-px nudge (same +7/-10 renderNodeLabels applies; layers anchor at
+// their sphere). Shared by the fit box and the label drawing above.
+function forEachVisibleLabel(
+ cb: (
+ div: HTMLDivElement,
+ worldX: number,
+ worldY: number,
+ offsetX: number,
+ offsetY: number
+ ) => void
+): void {
+ const container = document.getElementById('labelDiv')
+ if (!container) return
+
+ // div order matches ctx registries: createLabels appends one div per
+ // nodeObjects entry, then one per layer.
+ container.querySelectorAll('.labels').forEach((div, i) => {
+ const node = ctx.nodeObjects[i]
+ if (!node || div.style.display === 'none') return
+ cb(div, node.getWorldPosition('x'), node.getWorldPosition('y'), 7, -10)
+ })
+ container
+ .querySelectorAll('.layer-labels')
+ .forEach((div, i) => {
+ const layer = ctx.layers[i]
+ if (!layer || div.style.display === 'none') return
+ const world = layer.sphere.getWorldPosition(RAYVECTOR)
+ cb(div, world.x, world.y, 0, 0)
+ })
+}
+
+// Loading spinner (v2 handler_startLoader/finishLoader): show #loader and dim
+// the canvas while a backend call is in flight.
+export function startLoader(): void {
+ const app = document.getElementById('app')
+ const loader = document.getElementById('loader')
+ if (app) app.style.opacity = '0.5'
+ if (loader) loader.style.display = 'inline-block'
+}
+
+export function finishLoader(): void {
+ const app = document.getElementById('app')
+ const loader = document.getElementById('loader')
+ if (app) app.style.opacity = '1'
+ if (loader) loader.style.display = 'none'
+}
+
+// Build the raycaster from a mouse event (used by node/layer hover — Phase 12+).
+export function setRaycaster(event: {
+ clientX: number
+ clientY: number
+}): void {
+ if (!ctx.camera) return
+ RAYVECTOR.set(
+ (event.clientX / window.innerWidth) * 2 - 1,
+ -(event.clientY / window.innerHeight) * 2 + 1,
+ -1 // z = -1 important!
+ )
+ RAYVECTOR.unproject(ctx.camera)
+ RAYDIR.set(0, 0, -1).transformDirection(ctx.camera.matrixWorld)
+ RAYCASTER.set(RAYVECTOR, RAYDIR)
+}
+
+export function raycaster(): THREE.Raycaster {
+ return RAYCASTER
+}
+
+// Per-tick hooks (labels.ts registers its render fns here; avoids an
+// import cycle screen -> labels -> node -> screen).
+const animateHooks: Array<() => void> = []
+export function registerAnimateHook(fn: () => void): void {
+ animateHooks.push(fn)
+}
+
+// Labels are DOM overlays positioned from world coordinates, and inter-layer
+// edges bake world coordinates into their geometry (they hang off the scene
+// root, not the pan/sphere hierarchy) — neither follows scene transforms on
+// its own. Any scene-level transform (pan, orbit, zoom, auto-rotate) moves
+// everything under the scene sphere, so watch its world matrix and re-flag
+// both when it changes.
+const lastSphereMatrix = new THREE.Matrix4()
+function flagWorldSpaceRedrawsOnSceneMove(): void {
+ const sphere = ctx.scene?.sphere
+ if (!sphere) return
+ sphere.updateWorldMatrix(true, false)
+ if (!lastSphereMatrix.equals(sphere.matrixWorld)) {
+ lastSphereMatrix.copy(sphere.matrixWorld)
+ ctx.renderLayerLabelsFlag = true
+ ctx.renderNodeLabelsFlag = true
+ ctx.renderInterLayerEdgesFlag = true
+ }
+}
+
+// FPS-limited render loop. Pure rAF (no setTimeout) so the browser can
+// align frames with vsync and auto-pause when the tab is hidden; the
+// timestamp check enforces ctx.fps as an upper bound.
+let lastFrameTime = 0
+export function animate(now = 0): void {
+ requestAnimationFrame(animate)
+ const interval = 1000 / ctx.fps
+ if (now - lastFrameTime < interval) return
+ // Snap to the frame grid so throttled rates stay even (e.g. 30 on 60Hz).
+ lastFrameTime = now - ((now - lastFrameTime) % interval)
+
+ flagWorldSpaceRedrawsOnSceneMove()
+ renderInterLayerEdges()
+ for (const fn of animateHooks) fn()
+ renderFrame()
+}
diff --git a/frontend/src/actions/session.test.ts b/frontend/src/actions/session.test.ts
new file mode 100644
index 0000000..7a65117
--- /dev/null
+++ b/frontend/src/actions/session.test.ts
@@ -0,0 +1,250 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import type { SessionData } from '../api/client'
+import { api } from '../api/client'
+import { history } from '../commands/base'
+import { store } from '../store'
+import { ctx, Edge, Layer, Node, resetContext, Scene } from '../three'
+import { buildFromSession, loadSession } from './network'
+import { collectSession, exportSession } from './session'
+
+function session(partial: Partial = {}): SessionData {
+ return {
+ scene: {
+ position_x: 10,
+ position_y: -10,
+ scale: 0.8,
+ color: '#101010',
+ rotation_x: 0.1,
+ rotation_y: 0.2,
+ rotation_z: 0.3,
+ },
+ layers: (['L1', 'L2'] as const).map((name, i) => ({
+ name,
+ position_x: i === 0 ? -100 : 100,
+ position_y: 0,
+ position_z: 0,
+ rotation_x: 0,
+ rotation_y: 0,
+ rotation_z: 0,
+ last_layer_scale: 1,
+ floor_current_color: '#777777',
+ geometry_parameters_width: 200,
+ generate_coordinates: false,
+ })),
+ nodes: [
+ {
+ name: 'A',
+ layer: 'L1',
+ position_x: 0,
+ position_y: 5,
+ position_z: 6,
+ scale: 1.5,
+ color: '#ff0000',
+ url: 'http://a',
+ descr: 'node A',
+ },
+ {
+ name: 'B',
+ layer: 'L2',
+ position_x: 0,
+ position_y: -5,
+ position_z: -6,
+ scale: 1,
+ color: '#00ff00',
+ url: '',
+ descr: '',
+ },
+ ],
+ edges: [{ src: 'A_L1', trg: 'B_L2', opacity: 0.7, color: '#123123' }],
+ universalLabelColor: '#abcabc',
+ direction: true,
+ edgeOpacityByWeight: false,
+ scramble_nodes: false,
+ ...partial,
+ }
+}
+
+beforeEach(() => {
+ resetContext()
+ store.update({ network: null, selectedChannels: [] })
+})
+
+describe('buildFromSession', () => {
+ it('restores scene, layers, nodes and edges from the session JSON', () => {
+ buildFromSession(session())
+ expect(ctx.scene!.getScale()).toBeCloseTo(0.8)
+ expect(ctx.scene!.getRotation('z')).toBeCloseTo(0.3)
+ expect(ctx.layers.map((l) => l.getPosition('x'))).toEqual([-100, 100])
+ expect(ctx.layers[0].geometry_parameters_width).toBe(200)
+
+ expect(ctx.nodeObjects[0].getPosition('y')).toBe(5)
+ expect(ctx.nodeObjects[0].getScale()).toBe(1.5)
+ expect(ctx.nodeObjects[0].color).toBe('#ff0000')
+ expect(ctx.nodeObjects[0].url).toBe('http://a')
+
+ expect(ctx.edgeObjects).toHaveLength(1)
+ expect(ctx.edgeObjects[0].interLayer).toBe(true)
+ expect(ctx.edgeObjects[0].weights).toEqual([0.7])
+ expect(ctx.edgeObjects[0].colors).toEqual(['#123123']) // file color
+ })
+
+ it('applies the v2 setJSONExtras flags', () => {
+ buildFromSession(session())
+ expect(ctx.labelColor).toBe('#abcabc')
+ expect(ctx.isDirectionEnabled).toBe(true)
+ expect(ctx.edgeWidthByWeight).toBe(false)
+ expect(ctx.edgeFileColorPriority).toBe(true)
+ })
+
+ it('spreads layers when the session has no layer coordinates', () => {
+ const s = session()
+ s.layers = s.layers.map((l) => ({
+ ...l,
+ position_x: 0,
+ generate_coordinates: true,
+ }))
+ buildFromSession(s)
+ expect(ctx.layers[0].getPosition('x')).not.toBe(0)
+ })
+
+ it('fills the store with a NetworkData equivalent for later layout calls', () => {
+ buildFromSession(session())
+ const net = store.get().network!
+ expect(net.nodes.map((n) => n.id)).toEqual(['A_L1', 'B_L2'])
+ expect(net.edges[0].source_node).toBe('A')
+ expect(net.edges[0].target_layer).toBe('L2')
+ })
+})
+
+describe('loadSession', () => {
+ it('is one undoable step', () => {
+ loadSession(session())
+ expect(ctx.nodeObjects).toHaveLength(2)
+ history.undo()
+ expect(ctx.nodeObjects).toHaveLength(0)
+ history.redo()
+ expect(ctx.nodeObjects).toHaveLength(2)
+ })
+})
+
+describe('collectSession', () => {
+ // A two-layer scene with one channel-less inter-layer edge.
+ function seed(): void {
+ ctx.scene = new Scene()
+ ctx.scene.setScale(0.5)
+ ctx.layers = [
+ new Layer({ id: 0, name: 'L1', geometry_parameters_width: 300 }),
+ new Layer({ id: 1, name: 'L2' }),
+ ]
+ ctx.layerGroups = { L1: 0, L2: 1 }
+ ctx.nodeGroups = { A_L1: 'L1', B_L2: 'L2' }
+ ctx.nodeLayerNames = ['A_L1', 'B_L2']
+ ctx.nodeObjects = [
+ new Node({
+ id: 0,
+ name: 'A',
+ nodeLayerName: 'A_L1',
+ layer: 'L1',
+ color: '#aa0000',
+ url: 'http://a',
+ descr: 'the A node',
+ }),
+ new Node({ id: 1, name: 'B', nodeLayerName: 'B_L2', layer: 'L2' }),
+ ]
+ ctx.layers[0].addNode(ctx.nodeObjects[0].sphere)
+ ctx.layers[1].addNode(ctx.nodeObjects[1].sphere)
+ ctx.edgeObjects = [
+ new Edge({
+ id: 0,
+ source: 'A_L1',
+ target: 'B_L2',
+ weights: [0.4],
+ interLayer: true,
+ }),
+ ]
+ }
+
+ it('round-trips: collectSession output rebuilds an equivalent scene', () => {
+ seed()
+ ctx.labelColor = '#cccccc'
+ ctx.isDirectionEnabled = true
+ const out = collectSession()
+
+ expect(out.scene.scale).toBeCloseTo(0.5)
+ expect(out.layers.map((l) => l.name)).toEqual(['L1', 'L2'])
+ expect(out.layers[0].geometry_parameters_width).toBe(300)
+ expect(out.nodes[0]).toMatchObject({
+ name: 'A',
+ layer: 'L1',
+ color: '#aa0000',
+ url: 'http://a',
+ descr: 'the A node',
+ })
+ expect(out.edges).toEqual([
+ expect.objectContaining({ src: 'A_L1', trg: 'B_L2', opacity: 0.4 }),
+ ])
+ expect(out.universalLabelColor).toBe('#cccccc')
+ expect(out.direction).toBe(true)
+
+ // feed it back through the importer
+ resetContext()
+ store.update({ network: null })
+ buildFromSession(out)
+ expect(ctx.nodeObjects.map((n) => n.name)).toEqual(['A', 'B'])
+ expect(ctx.edgeObjects).toHaveLength(1)
+ })
+
+ it('emits one edge row per channel', () => {
+ ctx.scene = new Scene()
+ ctx.channelColors = { c1: '#111', c2: '#222' }
+ ctx.edgeFileColorPriority = false
+ ctx.layers = [new Layer({ id: 0, name: 'L1' })]
+ ctx.layerGroups = { L1: 0 }
+ ctx.nodeGroups = { A_L1: 'L1', B_L1: 'L1' }
+ ctx.nodeLayerNames = ['A_L1', 'B_L1']
+ ctx.nodeObjects = [
+ new Node({ id: 0, name: 'A', nodeLayerName: 'A_L1', layer: 'L1' }),
+ new Node({ id: 1, name: 'B', nodeLayerName: 'B_L1', layer: 'L1' }),
+ ]
+ ctx.edgeObjects = [
+ new Edge({
+ id: 0,
+ source: 'A_L1',
+ target: 'B_L1',
+ weights: [0.3, 0.6],
+ channels: ['c1', 'c2'],
+ colors: ['#111', '#222'],
+ }),
+ ]
+ const rows = collectSession().edges
+ expect(rows).toHaveLength(2)
+ expect(rows.map((r) => r.channel)).toEqual(['c1', 'c2'])
+ })
+})
+
+describe('exportSession', () => {
+ it('POSTs the collected session and triggers a download', async () => {
+ ctx.scene = new Scene()
+ store.update({
+ network: { nodes: [], edges: [], layers: [], channels: [], warnings: [] },
+ })
+ const blob = new Blob(['{}'], { type: 'application/json' })
+ const spy = vi.spyOn(api, 'exportSession').mockResolvedValue(blob)
+ // jsdom-free: stub the URL + anchor bits collectSession's download uses
+ const createURL = vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:x')
+ const revokeURL = vi
+ .spyOn(URL, 'revokeObjectURL')
+ .mockImplementation(() => {})
+
+ await exportSession('out.json')
+ expect(spy).toHaveBeenCalledOnce()
+ createURL.mockRestore()
+ revokeURL.mockRestore()
+ spy.mockRestore()
+ })
+
+ it('refuses to export with no network loaded', async () => {
+ store.update({ network: null })
+ await expect(exportSession()).rejects.toThrow(/No network/)
+ })
+})
diff --git a/frontend/src/actions/session.ts b/frontend/src/actions/session.ts
new file mode 100644
index 0000000..733c1f6
--- /dev/null
+++ b/frontend/src/actions/session.ts
@@ -0,0 +1,104 @@
+// Session export — port of v2 convertSessionToJSON (functions/input.R) fed by
+// rshiny_update.js. Assembles the export dict from live ctx state; the backend
+// (/api/session/export) just packages it with a download header. Import lives
+// in network.ts (buildFromSession). File-panel upload/download wiring is
+// Phase 13.
+
+import { Color } from 'three'
+import { api, type SessionData } from '../api/client'
+import { store } from '../store'
+import { ctx } from '../three'
+
+// v2 collected scene/layers/nodes/edges through the Shiny bridge; here we read
+// the Three.js objects directly. Values are numbers (v2 stringified them via
+// JSON; the importer coerces with Number(), so either is fine).
+export function collectSession(): SessionData {
+ const color = ctx.renderer
+ ? `#${ctx.renderer.getClearColor(new Color()).getHexString()}`
+ : '#000000'
+
+ return {
+ scene: {
+ position_x: ctx.scene?.getPosition('x') ?? 0,
+ position_y: ctx.scene?.getPosition('y') ?? 0,
+ scale: ctx.scene?.getScale() ?? 1,
+ color,
+ rotation_x: ctx.scene?.getRotation('x') ?? 0,
+ rotation_y: ctx.scene?.getRotation('y') ?? 0,
+ rotation_z: ctx.scene?.getRotation('z') ?? 0,
+ },
+ layers: ctx.layers.map((l) => ({
+ name: l.getName(),
+ position_x: l.getPosition('x'),
+ position_y: l.getPosition('y'),
+ position_z: l.getPosition('z'),
+ last_layer_scale: l.getScale(),
+ rotation_x: l.getRotation('x'),
+ rotation_y: l.getRotation('y'),
+ rotation_z: l.getRotation('z'),
+ floor_current_color: l.getColor(),
+ geometry_parameters_width: l.getWidth(),
+ generate_coordinates: false,
+ })),
+ nodes: ctx.nodeObjects.map((n) => ({
+ name: n.getName(),
+ layer: n.getLayer(),
+ position_x: n.getPosition('x'),
+ position_y: n.getPosition('y'),
+ position_z: n.getPosition('z'),
+ scale: n.getScale(),
+ color: n.getColor(),
+ url: n.url,
+ descr: n.descr,
+ })),
+ edges: collectEdges(),
+ universalLabelColor: ctx.labelColor,
+ direction: ctx.isDirectionEnabled,
+ edgeOpacityByWeight: ctx.edgeWidthByWeight,
+ scramble_nodes: false,
+ }
+}
+
+// v2 updateEdgesRShiny + updateEdgeColorsRShiny: one export row per channel
+// (or a single channel-less row), color resolved with forExport=true.
+function collectEdges(): SessionData['edges'] {
+ const rows: SessionData['edges'] = []
+ for (const edge of ctx.edgeObjects) {
+ if (edge.channels.length > 0) {
+ edge.channels.forEach((channel, j) => {
+ rows.push({
+ src: edge.source,
+ trg: edge.target,
+ opacity: edge.weights[j],
+ color: edge.decideColor(j, true),
+ channel,
+ })
+ })
+ } else {
+ rows.push({
+ src: edge.source,
+ trg: edge.target,
+ opacity: edge.weights[0],
+ color: edge.decideColor(0, true),
+ })
+ }
+ }
+ return rows
+}
+
+// Named as v2's download: POST the assembled session, trigger a file save.
+export async function exportSession(filename = 'network.json'): Promise {
+ if (!store.get().network) throw new Error('No network to export.')
+ const blob = await api.exportSession(collectSession())
+ triggerDownload(blob, filename)
+}
+
+function triggerDownload(blob: Blob, filename: string): void {
+ if (typeof document === 'undefined') return
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = filename
+ a.click()
+ URL.revokeObjectURL(url)
+}
diff --git a/frontend/src/actions/themes.test.ts b/frontend/src/actions/themes.test.ts
new file mode 100644
index 0000000..99e86c7
--- /dev/null
+++ b/frontend/src/actions/themes.test.ts
@@ -0,0 +1,65 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { AppConfig } from '../api/config'
+import { history } from '../commands/base'
+import { ChangeThemeCommand } from '../commands/scene'
+import { store } from '../store'
+import { ctx, Layer, resetContext, Scene } from '../three'
+import { applyTheme, registerThemeListener, THEMES } from './themes'
+
+beforeEach(() => {
+ resetContext()
+ ctx.scene = new Scene()
+ ctx.layers = [new Layer({ id: 0, name: 'L1' })]
+ ctx.channelColors = { ch1: '#000001', ch2: '#000002' }
+ store.update({
+ currentTheme: 'dark',
+ config: {
+ channel_colors_light: ['#l1', '#l2'],
+ channel_colors_dark: ['#d1', '#d2'],
+ } as unknown as AppConfig,
+ })
+})
+
+describe('applyTheme', () => {
+ it('recolours edges/labels/channels and repaints layers with the floor color', () => {
+ applyTheme('light')
+ expect(ctx.edgeDefaultColor).toBe(THEMES.light.edge)
+ expect(ctx.labelColor).toBe('#000000')
+ expect(ctx.channelColors).toEqual({ ch1: '#d1', ch2: '#d2' })
+ expect(ctx.layerColorPrioritySource).toBe('picker')
+ expect(ctx.layers[0].color).toBe(THEMES.light.floor)
+ expect(ctx.renderInterLayerEdgesFlag).toBe(true)
+ })
+
+ it('fromInit applies colors but skips the repaint pass', () => {
+ applyTheme('dark', true)
+ expect(ctx.edgeDefaultColor).toBe('#ffffff')
+ expect(ctx.channelColors).toEqual({ ch1: '#l1', ch2: '#l2' })
+ expect(ctx.layerColorPrioritySource).toBe('default')
+ expect(ctx.renderInterLayerEdgesFlag).toBe(false)
+ })
+
+ it('unknown theme is a no-op', () => {
+ applyTheme('neon')
+ expect(ctx.channelColors).toEqual({ ch1: '#000001', ch2: '#000002' })
+ })
+})
+
+describe('theme:changed wiring', () => {
+ it('ChangeThemeCommand execute/undo re-applies themes through the bus', () => {
+ registerThemeListener()
+ history.execute(
+ new ChangeThemeCommand(
+ 'light',
+ () => store.get().currentTheme,
+ (t) => store.update({ currentTheme: t })
+ )
+ )
+ expect(store.get().currentTheme).toBe('light')
+ expect(ctx.labelColor).toBe('#000000')
+
+ history.undo()
+ expect(store.get().currentTheme).toBe('dark')
+ expect(ctx.labelColor).toBe('#ffffff')
+ })
+})
diff --git a/frontend/src/actions/themes.ts b/frontend/src/actions/themes.ts
new file mode 100644
index 0000000..b32df09
--- /dev/null
+++ b/frontend/src/actions/themes.ts
@@ -0,0 +1,118 @@
+// Port of v2 www/js/object_actions/themes.js. The theme buttons (Phase 13
+// UI) dispatch ChangeThemeCommand; its theme:changed event lands here and
+// applyTheme does the concrete recolour. Channel palettes come from
+// /api/config (v2 CHANNEL_COLORS_LIGHT/DARK).
+
+import { bus } from '../bus'
+import { history } from '../commands/base'
+import { ChangeThemeCommand } from '../commands/scene'
+import { store } from '../store'
+import { ctx } from '../three'
+import { assignChannelColorsFromPalette, redrawIntraLayerEdges } from './edge'
+import { setLabelColor } from './labels'
+import { repaintLayers } from './layer'
+import { setRendererColor } from './screen'
+
+export interface Theme {
+ background: string
+ floor: string
+ edge: string
+ channelPalette: 'light' | 'dark'
+ label: string
+}
+
+// v2 attachThemeButtons' three presets.
+export const THEMES: Record = {
+ light: {
+ background: '#ffffff',
+ floor: '#8aa185',
+ edge: '#5c5c5c',
+ channelPalette: 'dark',
+ label: '#000000',
+ },
+ dark: {
+ background: '#000000',
+ floor: '#777777',
+ edge: '#ffffff',
+ channelPalette: 'light',
+ label: '#ffffff',
+ },
+ gray: {
+ background: '#999999',
+ floor: '#1d4991',
+ edge: '#6e2a5a',
+ channelPalette: 'light',
+ label: '#ffffff',
+ },
+}
+
+export function applyTheme(name: string, fromInit = false): void {
+ const theme = THEMES[name]
+ if (!theme || !ctx.scene?.exists()) return
+
+ setRendererColor(theme.background)
+ ctx.edgeDefaultColor = theme.edge
+ ctx.labelColor = theme.label
+
+ const config = store.get().config
+ const palette =
+ theme.channelPalette === 'dark'
+ ? config?.channel_colors_dark
+ : config?.channel_colors_light
+ if (palette) assignChannelColorsFromPalette(palette)
+
+ if (!fromInit) {
+ // v2 repaintLayersFromPicker: switch layers to picker priority with the
+ // theme's floor color. The floor_color input is the source of truth on the
+ // picker path (repaintLayers()/Layer.getColor() read it on hover-exit), so
+ // update it too — otherwise hover-exit reverts to the stale (dark) color.
+ ctx.layerColorPrioritySource = 'picker'
+ const floorInput =
+ typeof document !== 'undefined' &&
+ (document.getElementById('floor_color') as HTMLInputElement | null)
+ if (floorInput) floorInput.value = theme.floor
+ repaintLayers(theme.floor)
+ redrawIntraLayerEdges()
+ ctx.renderInterLayerEdgesFlag = true
+ setLabelColor(theme.label)
+ // v2 also rebuilt the channel edit list — Phase 13 UI
+ }
+}
+
+// Called once from main.ts.
+export function registerThemeListener(): void {
+ bus.on('theme:changed', ({ theme }) => applyTheme(theme))
+}
+
+// Port of v2 attachThemeButtons: the fixed top-right Light/Dark/Gray bar.
+// Attached on first network load (themes only recolour a live scene), routed
+// through ChangeThemeCommand so theme switches are undoable.
+export function registerThemeButtons(): void {
+ const off = bus.on('network:loaded', () => {
+ attachThemeButtons()
+ off()
+ })
+}
+
+function attachThemeButtons(): void {
+ const themeDiv = document.getElementById('themeDiv')
+ if (!themeDiv || themeDiv.childElementCount > 0) return
+ for (const name of Object.keys(THEMES)) {
+ const btn = document.createElement('button')
+ btn.type = 'button'
+ btn.id = `${name}ThemeButton`
+ btn.className = 'themeButton'
+ btn.textContent = name.charAt(0).toUpperCase() + name.slice(1)
+ btn.addEventListener('click', () => {
+ // store.currentTheme is the single source of truth (defaults to 'dark').
+ history.execute(
+ new ChangeThemeCommand(
+ name,
+ () => store.get().currentTheme,
+ (t) => store.update({ currentTheme: t })
+ )
+ )
+ })
+ themeDiv.appendChild(btn)
+ }
+}
diff --git a/frontend/src/api/.gitkeep b/frontend/src/api/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts
new file mode 100644
index 0000000..49be009
--- /dev/null
+++ b/frontend/src/api/client.ts
@@ -0,0 +1,199 @@
+// Typed REST client — mirrors the FastAPI Pydantic models (backend/app/models).
+// Every interaction with the backend is one typed fetch() call (SPEC §3).
+
+export interface NodeModel {
+ id: string
+ label: string
+ layer: string
+}
+
+export interface EdgeModel {
+ src: string
+ trg: string
+ source_node: string
+ source_layer: string
+ target_node: string
+ target_layer: string
+ weight: number
+ scaled_weight: number
+ channel: string | null
+}
+
+export interface NetworkData {
+ nodes: NodeModel[]
+ edges: EdgeModel[]
+ layers: string[]
+ channels: string[]
+ warnings: string[]
+}
+
+// Normalized session (SessionImportResponse; backend fills defaults, values
+// stay stringly-typed as in the v2 export format).
+export interface SessionScene {
+ position_x: string | number
+ position_y: string | number
+ scale: string | number
+ color: string
+ rotation_x: string | number
+ rotation_y: string | number
+ rotation_z: string | number
+}
+
+export interface SessionLayer {
+ name: string
+ position_x: string | number
+ position_y: string | number
+ position_z: string | number
+ rotation_x: string | number
+ rotation_y: string | number
+ rotation_z: string | number
+ last_layer_scale: string | number
+ floor_current_color: string
+ geometry_parameters_width: string | number
+ generate_coordinates: boolean
+}
+
+export interface SessionNode {
+ name: string
+ layer: string
+ position_x: string | number
+ position_y: string | number
+ position_z: string | number
+ scale: string | number
+ color: string
+ url: string
+ descr: string
+}
+
+export interface SessionEdge {
+ src: string
+ trg: string
+ opacity: string | number
+ color: string
+ channel?: string
+}
+
+export interface SessionData {
+ scene: SessionScene
+ layers: SessionLayer[]
+ nodes: SessionNode[]
+ edges: SessionEdge[]
+ universalLabelColor: string
+ direction: boolean
+ edgeOpacityByWeight: boolean
+ scramble_nodes: boolean
+ warnings?: string[]
+}
+
+export type Scope = 'perLayer' | 'allLayers' | 'nodesPerLayers'
+
+export interface ClusteringOptions {
+ algorithm: string
+ local_layout: string
+}
+
+export interface LayoutRequest {
+ nodes: NodeModel[]
+ edges: EdgeModel[]
+ algorithm: string
+ scope?: Scope
+ selected_layers: string[]
+ selected_nodes?: string[] | null
+ selected_channels?: string[] | null
+ clustering?: ClusteringOptions | null
+ seed?: number
+}
+
+export interface LayoutResponse {
+ positions: Record
+ clusters: Record | null
+}
+
+export interface TopologyRequest {
+ nodes: NodeModel[]
+ edges: EdgeModel[]
+ metric: string
+ scope?: Scope
+ selected_layers: string[]
+ selected_nodes?: string[] | null
+ selected_channels?: string[] | null
+ directed?: boolean
+}
+
+export interface TopologyResponse {
+ scales: Record
+ raw: Record
+}
+
+async function post(path: string, body: unknown): Promise {
+ const res = await fetch(path, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body),
+ })
+ if (!res.ok)
+ throw new Error(`POST ${path} failed: ${res.status} ${await res.text()}`)
+ return res.json() as Promise
+}
+
+async function postFile(
+ path: string,
+ file: File | Blob,
+ name = 'file'
+): Promise {
+ const form = new FormData()
+ form.append('file', file, name)
+ const res = await fetch(path, { method: 'POST', body: form })
+ if (!res.ok)
+ throw new Error(`POST ${path} failed: ${res.status} ${await res.text()}`)
+ return res.json() as Promise
+}
+
+export interface NodeAttributeRow {
+ node_layer: string
+ color: string | null
+ size: number | null
+ url: string | null
+ description: string | null
+}
+
+export interface EdgeAttributeRow {
+ edge_pair: string
+ color: string
+ channel: string | null
+}
+
+export const api = {
+ uploadNetwork: (file: File | Blob) =>
+ postFile('/api/network', file),
+ uploadNodeAttributes: (file: File | Blob) =>
+ postFile('/api/attributes/nodes', file),
+ uploadEdgeAttributes: (file: File | Blob) =>
+ postFile('/api/attributes/edges', file),
+ layout: (req: LayoutRequest) => post('/api/layout', req),
+ topology: (req: TopologyRequest) =>
+ post('/api/topology', req),
+ importSession: (file: File | Blob) =>
+ postFile('/api/session/import', file),
+ exportSession: async (session: SessionData): Promise => {
+ const res = await fetch('/api/session/export', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(session),
+ })
+ if (!res.ok)
+ throw new Error(`POST /api/session/export failed: ${res.status}`)
+ return res.blob()
+ },
+ createExternal: (session: unknown) =>
+ post<{ token: string; url: string }>('/api/external', session),
+ // Backend returns a normalized SessionImportResponse, same shape as import.
+ resolveExternal: async (token: string): Promise => {
+ // token comes from the ?session= query param — encode it so a stray
+ // '/' or '?' can't build a malformed URL / wrong path
+ const path = `/api/external/${encodeURIComponent(token)}`
+ const res = await fetch(path)
+ if (!res.ok) throw new Error(`GET ${path} failed: ${res.status}`)
+ return res.json() as Promise
+ },
+}
diff --git a/frontend/src/api/config.ts b/frontend/src/api/config.ts
new file mode 100644
index 0000000..7f5e1e7
--- /dev/null
+++ b/frontend/src/api/config.ts
@@ -0,0 +1,20 @@
+// GET /api/config — replaces handler_initializeGlobals (v2 pushed these from R at startup)
+export interface AppConfig {
+ max_edges: number
+ max_channels: number
+ max_layers: number
+ edge_default_color: string
+ channel_colors_dark: string[]
+ channel_colors_light: string[]
+ node_colors: string[]
+ floor_default_color: string
+ floor_default_width: number
+ topology_metrics: string[]
+ no_edge_layouts: string[]
+}
+
+export async function fetchConfig(): Promise {
+ const res = await fetch('/api/config')
+ if (!res.ok) throw new Error(`GET /api/config failed: ${res.status}`)
+ return res.json() as Promise
+}
diff --git a/frontend/src/bus/.gitkeep b/frontend/src/bus/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/bus/index.ts b/frontend/src/bus/index.ts
new file mode 100644
index 0000000..ace7c58
--- /dev/null
+++ b/frontend/src/bus/index.ts
@@ -0,0 +1,48 @@
+// Typed event bus — decouples UI panels from the Three.js scene.
+// Replaces the role of rshiny_handlers.js / rshiny_update.js.
+
+export interface BusEvents {
+ 'network:loaded': { nodeCount: number; edgeCount: number }
+ 'node:color-changed': { nodeId: string; color: string }
+ 'node:size-changed': { nodeId: string; size: number }
+ 'edge:color-changed': { edgeId: string; color: string }
+ 'layout:applied': { positions: Record }
+ 'clustering:applied': { clusters: Record }
+ 'topology:applied': { metric: string; scales: Record }
+ 'layer:moved': { layerIndex: number }
+ 'theme:changed': { theme: string }
+ 'history:changed': { canUndo: boolean; canRedo: boolean }
+}
+
+type Handler = (payload: T) => void
+
+class EventBus {
+ // one Set of handlers per event name; typed via the public method signatures
+ private handlers = new Map>>()
+
+ on(
+ event: K,
+ handler: Handler
+ ): () => void {
+ let set = this.handlers.get(event)
+ if (!set) {
+ set = new Set()
+ this.handlers.set(event, set)
+ }
+ set.add(handler as Handler)
+ return () => this.off(event, handler)
+ }
+
+ off(
+ event: K,
+ handler: Handler
+ ): void {
+ this.handlers.get(event)?.delete(handler as Handler)
+ }
+
+ emit(event: K, payload: BusEvents[K]): void {
+ this.handlers.get(event)?.forEach((h) => h(payload))
+ }
+}
+
+export const bus = new EventBus()
diff --git a/frontend/src/classes/.gitkeep b/frontend/src/classes/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/commands/.gitkeep b/frontend/src/commands/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/commands/base.ts b/frontend/src/commands/base.ts
new file mode 100644
index 0000000..b159a0f
--- /dev/null
+++ b/frontend/src/commands/base.ts
@@ -0,0 +1,59 @@
+// Command pattern + history — undo/redo for all scene mutations (SPEC §4).
+import { bus } from '../bus'
+
+export interface Command {
+ execute(): void
+ undo(): void
+ description: string // shown in UI, e.g. "Apply Force Layout"
+ // Called when the command is permanently discarded (redo branch cut off);
+ // frees GPU resources that only this command still references.
+ dispose?(): void
+}
+
+class CommandHistory {
+ private undoStack: Command[] = []
+ private redoStack: Command[] = []
+
+ execute(command: Command): void {
+ command.execute()
+ this.undoStack.push(command)
+ for (const discarded of this.redoStack) discarded.dispose?.()
+ this.redoStack = []
+ this.notify()
+ }
+
+ undo(): void {
+ const cmd = this.undoStack.pop()
+ if (cmd) {
+ cmd.undo()
+ this.redoStack.push(cmd)
+ this.notify()
+ }
+ }
+
+ redo(): void {
+ const cmd = this.redoStack.pop()
+ if (cmd) {
+ cmd.execute()
+ this.undoStack.push(cmd)
+ this.notify()
+ }
+ }
+
+ get canUndo(): boolean {
+ return this.undoStack.length > 0
+ }
+
+ get canRedo(): boolean {
+ return this.redoStack.length > 0
+ }
+
+ private notify(): void {
+ bus.emit('history:changed', {
+ canUndo: this.canUndo,
+ canRedo: this.canRedo,
+ })
+ }
+}
+
+export const history = new CommandHistory()
diff --git a/frontend/src/commands/scene.test.ts b/frontend/src/commands/scene.test.ts
new file mode 100644
index 0000000..8fe578d
--- /dev/null
+++ b/frontend/src/commands/scene.test.ts
@@ -0,0 +1,214 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import { Node, Layer, Scene, Edge, ctx, resetContext } from '../three'
+import {
+ ChangeNodeColorCommand,
+ ChangeNodeSizeCommand,
+ ChangeEdgeColorCommand,
+ MoveLayerCommand,
+ ApplyLayoutCommand,
+ ApplyTopologyCommand,
+ ChangeThemeCommand,
+ LoadNetworkCommand,
+} from './scene'
+
+// Minimal populated scene: one layer "L", nodes A/B, one intra edge A->B.
+function seed(): void {
+ const scene = new Scene()
+ const layer = new Layer({ geometry_parameters_width: 100 })
+ ctx.scene = scene
+ ctx.layers = [layer]
+ ctx.layerGroups = { L: 0 }
+ ctx.nodeGroups = { 'A::L': 'L', 'B::L': 'L' }
+ ctx.nodeLayerNames = ['A::L', 'B::L']
+ ctx.nodeObjects = [
+ new Node({
+ name: 'A',
+ nodeLayerName: 'A::L',
+ layer: 'L',
+ color: '#111111',
+ }),
+ new Node({
+ name: 'B',
+ nodeLayerName: 'B::L',
+ layer: 'L',
+ color: '#222222',
+ }),
+ ]
+ ctx.edgeObjects = [new Edge({ source: 'A::L', target: 'B::L', weights: [1] })]
+}
+
+beforeEach(() => {
+ resetContext()
+ seed()
+})
+
+describe('ChangeNodeColorCommand', () => {
+ it('executes, undoes and redoes', () => {
+ const c = new ChangeNodeColorCommand(0, '#00ff00')
+ c.execute()
+ expect(ctx.nodeObjects[0].color).toBe('#00ff00')
+ c.undo()
+ expect(ctx.nodeObjects[0].color).toBe('#111111')
+ c.execute() // redo
+ expect(ctx.nodeObjects[0].color).toBe('#00ff00')
+ })
+})
+
+describe('ChangeNodeSizeCommand', () => {
+ it('executes and undoes', () => {
+ const c = new ChangeNodeSizeCommand(1, 3)
+ c.execute()
+ expect(ctx.nodeObjects[1].getScale()).toBe(3)
+ c.undo()
+ expect(ctx.nodeObjects[1].getScale()).toBe(1)
+ })
+})
+
+describe('ChangeEdgeColorCommand', () => {
+ it('recolours the edge and restores colour + priority on undo', () => {
+ expect(ctx.edgeFileColorPriority).toBe(false)
+ const c = new ChangeEdgeColorCommand(0, '#abcdef')
+ c.execute()
+ expect(ctx.edgeObjects[0].colors).toEqual(['#abcdef'])
+ expect(ctx.edgeObjects[0].importedColors).toEqual(['#abcdef'])
+ expect(ctx.edgeFileColorPriority).toBe(true)
+ c.undo()
+ expect(ctx.edgeObjects[0].colors).toEqual([ctx.edgeDefaultColor])
+ expect(ctx.edgeFileColorPriority).toBe(false)
+ })
+})
+
+describe('MoveLayerCommand', () => {
+ it('applies then reverts a layer transform', () => {
+ const c = new MoveLayerCommand(0, {
+ position: { x: 50 },
+ rotation: { y: 0.3 },
+ })
+ c.execute()
+ expect(ctx.layers[0].getPosition('x')).toBe(50)
+ expect(ctx.layers[0].getRotation('y')).toBeCloseTo(0.3)
+ c.undo()
+ expect(ctx.layers[0].getPosition('x')).toBe(0)
+ expect(ctx.layers[0].getRotation('y')).toBe(0)
+ })
+})
+
+describe('ApplyLayoutCommand', () => {
+ it('places nodes and restores previous positions', () => {
+ const c = new ApplyLayoutCommand({ 'A::L': [5, 6], 'B::L': [-5, -6] })
+ c.execute()
+ expect(ctx.nodeObjects[0].getPosition('y')).toBe(5)
+ expect(ctx.nodeObjects[0].getPosition('z')).toBe(6)
+ expect(ctx.nodeObjects[0].getPosition('x')).toBe(0)
+ c.undo()
+ expect(ctx.nodeObjects[0].getPosition('y')).toBe(0)
+ expect(ctx.nodeObjects[0].getPosition('z')).toBe(0)
+ })
+
+ it('assigns clusters + colours and reverts them', () => {
+ const c = new ApplyLayoutCommand(
+ { 'A::L': [1, 1], 'B::L': [2, 2] },
+ { 'A::L': 0, 'B::L': 1 }
+ )
+ c.execute()
+ expect(ctx.nodeObjects[0].getCluster()).toBe('0')
+ expect(ctx.nodeColorPrioritySource).toBe('cluster')
+ c.undo()
+ expect(ctx.nodeObjects[0].getCluster()).toBe('')
+ expect(ctx.nodeColorPrioritySource).toBe('default')
+ })
+})
+
+describe('ApplyTopologyCommand', () => {
+ it('scales nodes and restores', () => {
+ const c = new ApplyTopologyCommand('Degree', { 'A::L': 2.5, 'B::L': 0.5 })
+ c.execute()
+ expect(ctx.nodeObjects[0].getScale()).toBe(2.5)
+ expect(ctx.nodeObjects[1].getScale()).toBe(0.5)
+ c.undo()
+ expect(ctx.nodeObjects[0].getScale()).toBe(1)
+ expect(ctx.nodeObjects[1].getScale()).toBe(1)
+ })
+})
+
+describe('ChangeThemeCommand', () => {
+ it('drives a store setter and reverts', () => {
+ let theme = 'dark'
+ const c = new ChangeThemeCommand(
+ 'light',
+ () => theme,
+ (t) => (theme = t)
+ )
+ c.execute()
+ expect(theme).toBe('light')
+ c.undo()
+ expect(theme).toBe('dark')
+ })
+})
+
+describe('LoadNetworkCommand', () => {
+ it('builds on execute, clears on undo, rebuilds on redo', () => {
+ // Start from an empty context so we can observe the load.
+ resetContext()
+ const build = (): void => {
+ ctx.scene = new Scene()
+ ctx.layers = [new Layer({ geometry_parameters_width: 100 })]
+ ctx.nodeLayerNames = ['X::L']
+ ctx.nodeGroups = { 'X::L': 'L' }
+ ctx.layerGroups = { L: 0 }
+ ctx.nodeObjects = [
+ new Node({ name: 'X', nodeLayerName: 'X::L', layer: 'L' }),
+ ]
+ ctx.edgeObjects = []
+ }
+ const c = new LoadNetworkCommand(build)
+ c.execute()
+ expect(ctx.nodeObjects).toHaveLength(1)
+ c.undo()
+ expect(ctx.nodeObjects).toHaveLength(0)
+ expect(ctx.scene).toBeNull()
+ c.execute() // redo
+ expect(ctx.nodeObjects).toHaveLength(1)
+ })
+})
+
+describe('TransformCommand', () => {
+ it('restores node/layer transforms on undo', async () => {
+ const { captureTransforms, TransformCommand } = await import('./scene')
+ const before = captureTransforms()
+ ctx.nodeObjects[0].setPosition('y', 42)
+ ctx.layers[0].plane.position.set(1, 2, 3)
+ const c = new TransformCommand('move', before, captureTransforms())
+ c.undo()
+ expect(ctx.nodeObjects[0].getPosition('y')).toBe(0)
+ expect(ctx.layers[0].plane.position.x).toBe(0)
+ c.execute() // redo
+ expect(ctx.nodeObjects[0].getPosition('y')).toBe(42)
+ expect(ctx.layers[0].plane.position.z).toBe(3)
+ })
+})
+
+describe('ChangeChannelColorCommand', () => {
+ it('sets and restores the channel color', async () => {
+ const { ChangeChannelColorCommand } = await import('./scene')
+ ctx.channelColors = { ppi: '#cfcfcf' }
+ const c = new ChangeChannelColorCommand('ppi', '#ff0000')
+ c.execute()
+ expect(ctx.channelColors['ppi']).toBe('#ff0000')
+ c.undo()
+ expect(ctx.channelColors['ppi']).toBe('#cfcfcf')
+ })
+})
+
+describe('ChangeFloorColorCommand', () => {
+ it('rewinds picker color to previous, not the just-set value', async () => {
+ const { ChangeFloorColorCommand } = await import('./scene')
+ ctx.layerColorPrioritySource = 'picker'
+ new ChangeFloorColorCommand('#111111').execute() // establishes prior picker
+ const c = new ChangeFloorColorCommand('#ff0000')
+ c.execute()
+ expect(ctx.layers[0].color.toLowerCase()).toBe('#ff0000')
+ c.undo()
+ expect(ctx.layers[0].color.toLowerCase()).toBe('#111111')
+ })
+})
diff --git a/frontend/src/commands/scene.ts b/frontend/src/commands/scene.ts
new file mode 100644
index 0000000..80ccb86
--- /dev/null
+++ b/frontend/src/commands/scene.ts
@@ -0,0 +1,599 @@
+// Scene-mutation commands (SPEC §4). Each captures before/after state on the
+// Phase 9 Three.js object model (via ctx) so CommandHistory can undo/redo.
+//
+// Commands are thin: they apply/reverse already-decided values. Fetching from
+// the API and computing final positions/scales is the job of the Phase 11
+// actions, which construct these commands with the results.
+
+import { Color } from 'three'
+import { bus } from '../bus'
+import type { Command } from './base'
+import {
+ redrawIntraLayerEdges,
+ redrawInterLayerEdges,
+ setChannelColor,
+} from '../actions/edge'
+import { setRendererColor } from '../actions/screen'
+import { repaintLayers, getLastFloorColor } from '../actions/layer'
+import { createLabels } from '../actions/labels'
+import { setNodeShape } from '../actions/node'
+import type { NodeGeometryType } from '../three/Node'
+import {
+ ctx,
+ COLOR_VECTOR_280,
+ snapshotRegistries,
+ restoreRegistries,
+ disposeSnapshot,
+ type RegistrySnapshot,
+} from '../three'
+
+type Vec3 = { x: number; y: number; z: number }
+
+function clusterColor(id: number): string {
+ return COLOR_VECTOR_280[id % COLOR_VECTOR_280.length]
+}
+
+export class ChangeNodeColorCommand implements Command {
+ description: string
+ private readonly oldColor: string
+
+ constructor(
+ private readonly nodeIndex: number,
+ private readonly newColor: string
+ ) {
+ this.oldColor = ctx.nodeObjects[nodeIndex].color
+ this.description = `Change node color to ${newColor}`
+ }
+
+ private apply(color: string): void {
+ const node = ctx.nodeObjects[this.nodeIndex]
+ node.setColor(color)
+ bus.emit('node:color-changed', { nodeId: node.nodeLayerName, color })
+ }
+
+ execute(): void {
+ this.apply(this.newColor)
+ }
+ undo(): void {
+ this.apply(this.oldColor)
+ }
+}
+
+export class ChangeNodeSizeCommand implements Command {
+ description: string
+ private readonly oldSize: number
+
+ constructor(
+ private readonly nodeIndex: number,
+ private readonly newSize: number
+ ) {
+ this.oldSize = ctx.nodeObjects[nodeIndex].getScale()
+ this.description = `Change node size to ${newSize}`
+ }
+
+ private apply(size: number): void {
+ const node = ctx.nodeObjects[this.nodeIndex]
+ node.setScale(size)
+ bus.emit('node:size-changed', { nodeId: node.nodeLayerName, size })
+ }
+
+ execute(): void {
+ this.apply(this.newSize)
+ }
+ undo(): void {
+ this.apply(this.oldSize)
+ }
+}
+
+export class ChangeEdgeColorCommand implements Command {
+ description: string
+ private readonly oldColors: string[]
+ private readonly oldImported: string[]
+ private readonly oldPriority: boolean
+
+ constructor(
+ private readonly edgeIndex: number,
+ private readonly newColor: string
+ ) {
+ const edge = ctx.edgeObjects[edgeIndex]
+ this.oldColors = edge.colors.slice()
+ this.oldImported = edge.importedColors.slice()
+ this.oldPriority = ctx.edgeFileColorPriority
+ this.description = `Change edge color to ${newColor}`
+ }
+
+ execute(): void {
+ const edge = ctx.edgeObjects[this.edgeIndex]
+ edge.colors = edge.colors.map(() => this.newColor)
+ edge.importedColors = edge.importedColors.map(() => this.newColor)
+ // v2: recolouring an edge switches the scene into file-colour-priority mode.
+ ctx.edgeFileColorPriority = true
+ edge.redrawEdge()
+ bus.emit('edge:color-changed', { edgeId: edge.name, color: this.newColor })
+ }
+
+ undo(): void {
+ const edge = ctx.edgeObjects[this.edgeIndex]
+ edge.colors = this.oldColors.slice()
+ edge.importedColors = this.oldImported.slice()
+ ctx.edgeFileColorPriority = this.oldPriority
+ edge.redrawEdge()
+ bus.emit('edge:color-changed', {
+ edgeId: edge.name,
+ color: this.oldColors[0],
+ })
+ }
+}
+
+export interface LayerTransform {
+ position?: Partial
+ rotation?: Partial
+ scale?: number
+}
+
+export class MoveLayerCommand implements Command {
+ description: string
+ private readonly before: Required<
+ Omit
+ > & {
+ position: Vec3
+ rotation: Vec3
+ }
+
+ constructor(
+ private readonly layerIndex: number,
+ private readonly after: LayerTransform
+ ) {
+ const l = ctx.layers[layerIndex]
+ this.before = {
+ position: {
+ x: l.getPosition('x'),
+ y: l.getPosition('y'),
+ z: l.getPosition('z'),
+ },
+ rotation: {
+ x: l.getRotation('x'),
+ y: l.getRotation('y'),
+ z: l.getRotation('z'),
+ },
+ scale: l.getScale(),
+ }
+ this.description = `Move layer ${l.getName()}`
+ }
+
+ private applyTransform(t: LayerTransform): void {
+ const l = ctx.layers[this.layerIndex]
+ if (t.position)
+ for (const [axis, v] of Object.entries(t.position))
+ l.setPosition(axis as 'x' | 'y' | 'z', v)
+ if (t.rotation)
+ for (const [axis, v] of Object.entries(t.rotation))
+ l.setRotation(axis as 'x' | 'y' | 'z', v)
+ if (t.scale !== undefined) l.setScale(t.scale)
+ redrawInterLayerEdges()
+ bus.emit('layer:moved', { layerIndex: this.layerIndex })
+ }
+
+ execute(): void {
+ this.applyTransform(this.after)
+ }
+ undo(): void {
+ this.applyTransform(this.before)
+ }
+}
+
+export class ApplyLayoutCommand implements Command {
+ description = 'Apply layout'
+ private readonly beforePos: Record = {}
+ private readonly beforeCluster: Record<
+ string,
+ { cluster: string; color: string }
+ > = {}
+ private readonly beforePriority = ctx.nodeColorPrioritySource
+
+ constructor(
+ private readonly positions: Record,
+ private readonly clusters: Record | null = null
+ ) {
+ for (const name of Object.keys(positions)) {
+ const node = ctx.nodeObjects[ctx.nodeLayerNames.indexOf(name)]
+ this.beforePos[name] = [node.getPosition('y'), node.getPosition('z')]
+ if (clusters)
+ this.beforeCluster[name] = {
+ cluster: node.cluster,
+ color: node.clusterColor,
+ }
+ }
+ }
+
+ private place(positions: Record): void {
+ for (const [name, [y, z]] of Object.entries(positions)) {
+ const node = ctx.nodeObjects[ctx.nodeLayerNames.indexOf(name)]
+ node.setPosition('x', 0)
+ node.setPosition('y', y)
+ node.setPosition('z', z)
+ }
+ }
+
+ execute(): void {
+ this.place(this.positions)
+ if (this.clusters) {
+ for (const [name, id] of Object.entries(this.clusters)) {
+ const node = ctx.nodeObjects[ctx.nodeLayerNames.indexOf(name)]
+ node.setCluster(String(id))
+ node.setColor(clusterColor(id), false, true /* clusterMode */)
+ }
+ ctx.nodeColorPrioritySource = 'cluster'
+ bus.emit('clustering:applied', { clusters: this.clusters })
+ }
+ redrawIntraLayerEdges()
+ bus.emit('layout:applied', { positions: this.positions })
+ }
+
+ undo(): void {
+ this.place(this.beforePos)
+ if (this.clusters) {
+ for (const [name, prev] of Object.entries(this.beforeCluster)) {
+ const node = ctx.nodeObjects[ctx.nodeLayerNames.indexOf(name)]
+ node.setCluster(prev.cluster)
+ node.setColor(prev.color, false, true)
+ }
+ ctx.nodeColorPrioritySource = this.beforePriority
+ }
+ redrawIntraLayerEdges()
+ bus.emit('layout:applied', { positions: this.beforePos })
+ }
+}
+
+export class ApplyTopologyCommand implements Command {
+ description = 'Apply topology metric'
+ private readonly before: Record = {}
+
+ constructor(
+ private readonly metric: string,
+ private readonly scales: Record
+ ) {
+ for (const name of Object.keys(scales))
+ this.before[name] =
+ ctx.nodeObjects[ctx.nodeLayerNames.indexOf(name)].getScale()
+ }
+
+ private apply(scales: Record): void {
+ for (const [name, s] of Object.entries(scales))
+ ctx.nodeObjects[ctx.nodeLayerNames.indexOf(name)].setScale(s)
+ bus.emit('topology:applied', { metric: this.metric, scales })
+ }
+
+ execute(): void {
+ this.apply(this.scales)
+ }
+ undo(): void {
+ this.apply(this.before)
+ }
+}
+
+export class ChangeThemeCommand implements Command {
+ description: string
+ private readonly oldTheme: string
+
+ // Store-only: the concrete recolour (renderer clear color, floor/edge/label
+ // colors) is applied by the Phase 11 themes action listening to theme:changed.
+ constructor(
+ private readonly newTheme: string,
+ getTheme: () => string,
+ private readonly setTheme: (t: string) => void
+ ) {
+ this.oldTheme = getTheme()
+ this.description = `Change theme to ${newTheme}`
+ }
+
+ private apply(theme: string): void {
+ this.setTheme(theme)
+ bus.emit('theme:changed', { theme })
+ }
+
+ execute(): void {
+ this.apply(this.newTheme)
+ }
+ undo(): void {
+ this.apply(this.oldTheme)
+ }
+}
+
+export class LoadNetworkCommand implements Command {
+ description = 'Load network'
+ private prev: RegistrySnapshot | null = null
+ private next: RegistrySnapshot | null = null
+
+ // `build` populates ctx with the new scene graph (concrete builder lands in
+ // Phase 11 network action). The command wraps it with a reversible snapshot.
+ constructor(private readonly build: () => void) {}
+
+ private emitLoaded(): void {
+ bus.emit('network:loaded', {
+ nodeCount: ctx.nodeObjects.length,
+ edgeCount: ctx.edgeObjects.length,
+ })
+ }
+
+ execute(): void {
+ if (this.next === null) {
+ this.prev = snapshotRegistries()
+ this.build() // build() rebuilds the label divs itself
+ this.next = snapshotRegistries()
+ } else {
+ restoreRegistries(this.next)
+ createLabels() // restore only swaps the 3D registries — relabel the DOM
+ }
+ this.emitLoaded()
+ }
+
+ undo(): void {
+ if (this.prev) restoreRegistries(this.prev)
+ createLabels() // clears the label divs when rewinding to the empty scene
+ this.emitLoaded()
+ }
+
+ dispose(): void {
+ // Only `next` is exclusively ours (built by this command); `prev` may be
+ // shared with earlier history entries. disposeSnapshot no-ops if the
+ // scene is still the live one.
+ if (this.next) disposeSnapshot(this.next)
+ }
+}
+
+// --- DOM sync helpers (undo/redo must reflect back into the panels) --------
+
+function syncRadio(name: string, value: string | null): void {
+ if (typeof document === 'undefined') return
+ for (const radio of document.querySelectorAll(
+ `input[name="${name}"]`
+ ))
+ radio.checked = radio.value === value
+}
+
+function checkedRadio(name: string): string | null {
+ if (typeof document === 'undefined') return null
+ return (
+ document.querySelector(`input[name="${name}"]:checked`)
+ ?.value ?? null
+ )
+}
+
+function syncInput(id: string, value: string): void {
+ if (typeof document === 'undefined') return
+ const el = document.getElementById(id) as HTMLInputElement | null
+ if (el) el.value = value
+}
+
+// --- Whole-scene transform snapshot (nav controls, predefined layouts) -----
+
+type Quat = [number, number, number, number]
+
+export interface TransformSnapshot {
+ scene: { pan: Vec3; quaternion: Quat; scale: number } | null
+ // `labelPos` is the layer's label sphere (layer.sphere) position — scaleLayers
+ // moves it along with the node meshes, so it must be restored too.
+ layers: {
+ position: Vec3
+ quaternion: Quat
+ scale: number
+ labelPos: Vec3 | null
+ }[]
+ nodes: { position: Vec3; scale: number }[]
+}
+
+export function captureTransforms(): TransformSnapshot {
+ const s = ctx.scene?.exists() ? ctx.scene : null
+ return {
+ scene: s
+ ? {
+ pan: {
+ x: s.getPosition('x'),
+ y: s.getPosition('y'),
+ z: s.getPosition('z'),
+ },
+ quaternion: s.getQuaternion().toArray(),
+ scale: s.getScale(),
+ }
+ : null,
+ layers: ctx.layers.map((l) => ({
+ position: l.plane.position.clone(),
+ quaternion: l.plane.quaternion.toArray(),
+ scale: l.getScale(),
+ labelPos: l.sphere ? l.sphere.position.clone() : null,
+ })),
+ nodes: ctx.nodeObjects.map((n) => ({
+ position: {
+ x: n.getPosition('x'),
+ y: n.getPosition('y'),
+ z: n.getPosition('z'),
+ },
+ scale: n.getScale(),
+ })),
+ }
+}
+
+function applyTransforms(t: TransformSnapshot): void {
+ if (t.scene && ctx.scene?.exists()) {
+ ctx.scene.setPosition('x', t.scene.pan.x)
+ ctx.scene.setPosition('y', t.scene.pan.y)
+ ctx.scene.setPosition('z', t.scene.pan.z)
+ ctx.scene.getQuaternion().fromArray(t.scene.quaternion)
+ ctx.scene.setScale(t.scene.scale)
+ }
+ t.layers.forEach((l, i) => {
+ const layer = ctx.layers[i]
+ if (!layer) return
+ layer.plane.position.set(l.position.x, l.position.y, l.position.z)
+ layer.plane.quaternion.fromArray(l.quaternion)
+ layer.setScale(l.scale)
+ if (l.labelPos && layer.sphere)
+ layer.sphere.position.set(l.labelPos.x, l.labelPos.y, l.labelPos.z)
+ })
+ t.nodes.forEach((n, i) => {
+ const node = ctx.nodeObjects[i]
+ if (!node) return
+ node.setPosition('x', n.position.x)
+ node.setPosition('y', n.position.y)
+ node.setPosition('z', n.position.z)
+ node.setScale(n.scale)
+ })
+ redrawIntraLayerEdges()
+ ctx.renderInterLayerEdgesFlag = true
+ ctx.renderLayerLabelsFlag = true
+ ctx.renderNodeLabelsFlag = true
+}
+
+// Generic before/after transform command. The mutation has already run when
+// the command is pushed (hold-to-repeat buttons, sliders), so execute() is a
+// re-apply of `after` — idempotent on first push, the redo path afterwards.
+export class TransformCommand implements Command {
+ // `onApply` runs after undo/redo to re-sync DOM the snapshot doesn't cover
+ // (e.g. the scale sliders + their value labels).
+ constructor(
+ public description: string,
+ private readonly before: TransformSnapshot,
+ private readonly after: TransformSnapshot,
+ private readonly onApply?: () => void
+ ) {}
+
+ execute(): void {
+ applyTransforms(this.after)
+ this.onApply?.()
+ }
+ undo(): void {
+ applyTransforms(this.before)
+ this.onApply?.()
+ }
+}
+
+export class PredefinedLayoutCommand implements Command {
+ description: string
+ private readonly before = captureTransforms()
+ private readonly oldRadio = checkedRadio('predefined_layout')
+
+ constructor(
+ private readonly layout: string,
+ private readonly applyFn: () => void
+ ) {
+ this.description = `Apply ${layout} layout`
+ }
+
+ execute(): void {
+ this.applyFn()
+ syncRadio('predefined_layout', this.layout)
+ }
+
+ undo(): void {
+ applyTransforms(this.before)
+ syncRadio('predefined_layout', this.oldRadio)
+ }
+}
+
+export class ChangeBackgroundColorCommand implements Command {
+ description: string
+ private readonly oldColor: string
+
+ constructor(private readonly newColor: string) {
+ this.oldColor = `#${ctx.renderer!.getClearColor(new Color()).getHexString()}`
+ this.description = `Change background color to ${newColor}`
+ }
+
+ private apply(color: string): void {
+ setRendererColor(color)
+ syncInput('scene_color', color)
+ }
+
+ execute(): void {
+ this.apply(this.newColor)
+ }
+ undo(): void {
+ this.apply(this.oldColor)
+ }
+}
+
+export class ChangeFloorColorCommand implements Command {
+ description: string
+ private readonly oldColors = ctx.layers.map((l) => l.color)
+ private readonly oldPriority = ctx.layerColorPrioritySource
+ private readonly oldRadio = checkedRadio('layerColorPriorityRadio')
+ // The DOM color input already holds newColor when its 'change' event fires,
+ // so the previous picker value is read from the tracked last-applied color.
+ private readonly oldPicker = getLastFloorColor()
+
+ constructor(private readonly newColor: string) {
+ this.description = `Change floor color to ${newColor}`
+ }
+
+ execute(): void {
+ // v2 repaintLayersFromPicker: picking a color switches priority to picker.
+ ctx.layerColorPrioritySource = 'picker'
+ syncInput('floor_color', this.newColor)
+ syncRadio('layerColorPriorityRadio', 'picker')
+ repaintLayers(this.newColor)
+ }
+
+ undo(): void {
+ ctx.layerColorPrioritySource = this.oldPriority
+ syncInput('floor_color', this.oldPicker)
+ syncRadio('layerColorPriorityRadio', this.oldRadio)
+ if (this.oldPriority === 'picker') {
+ // Re-apply the previous picker color through repaintLayers so the tracked
+ // last-floor-color rewinds too (keeps a following undo consistent).
+ repaintLayers(this.oldPicker)
+ } else {
+ ctx.layers.forEach((l, i) => {
+ if (this.oldColors[i] !== undefined) l.setColor(this.oldColors[i])
+ })
+ }
+ }
+}
+
+export class ChangeNodeGeometryCommand implements Command {
+ description: string
+
+ constructor(
+ private readonly newShape: NodeGeometryType,
+ private readonly oldShape: NodeGeometryType
+ ) {
+ this.description = `Change node geometry to ${newShape}`
+ }
+
+ private apply(shape: NodeGeometryType): void {
+ setNodeShape(shape)
+ syncRadio('nodeGeometryRadio', shape)
+ }
+
+ execute(): void {
+ this.apply(this.newShape)
+ }
+ undo(): void {
+ this.apply(this.oldShape)
+ }
+}
+
+export class ChangeChannelColorCommand implements Command {
+ description: string
+ private readonly oldColor: string
+
+ constructor(
+ private readonly channel: string,
+ private readonly newColor: string
+ ) {
+ this.oldColor = ctx.channelColors[channel] ?? '#cfcfcf'
+ this.description = `Change ${channel} channel color to ${newColor}`
+ }
+
+ private apply(color: string): void {
+ setChannelColor(this.channel, color)
+ syncInput(`color${this.channel}`, color)
+ }
+
+ execute(): void {
+ this.apply(this.newColor)
+ }
+ undo(): void {
+ this.apply(this.oldColor)
+ }
+}
diff --git a/frontend/src/config/.gitkeep b/frontend/src/config/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/event_listeners.ts b/frontend/src/event_listeners.ts
new file mode 100644
index 0000000..2821074
--- /dev/null
+++ b/frontend/src/event_listeners.ts
@@ -0,0 +1,65 @@
+// Global event listeners. Port of v2 www/js/event_listeners.js (window-level
+// bits) plus new Ctrl+Z / Ctrl+Shift+Z undo/redo.
+//
+// ponytail: the canvas mouse/keyboard scene controls (clickDown/Drag/Up,
+// sceneZoom, keyPressed, right-click menu) live in canvas_controls — deferred
+// until that action lands. Only the window-level listeners are wired here.
+
+import { Collapse } from 'bootstrap'
+import { bus } from './bus'
+import { history } from './commands/base'
+import { resetScreen } from './actions/screen'
+
+export function registerGlobalListeners(): void {
+ window.addEventListener('resize', resetScreen)
+ window.addEventListener('keydown', handleUndoRedo)
+ wireHistoryButtons()
+ wireMobileNavCollapse()
+}
+
+// On small screens the navbar is a collapsed menu; picking a tab should close
+// it so the chosen panel/scene isn't left buried under the open menu.
+function wireMobileNavCollapse(): void {
+ const menu = document.getElementById('navbarTabs')
+ if (!menu) return
+ for (const link of menu.querySelectorAll('.navbar-nav .nav-link')) {
+ link.addEventListener('click', () => {
+ if (menu.classList.contains('show')) {
+ Collapse.getOrCreateInstance(menu).hide()
+ }
+ })
+ }
+}
+
+function wireHistoryButtons(): void {
+ const undo = document.getElementById('undoButton') as HTMLButtonElement | null
+ const redo = document.getElementById('redoButton') as HTMLButtonElement | null
+ if (!undo || !redo) return
+ undo.addEventListener('click', () => history.undo())
+ redo.addEventListener('click', () => history.redo())
+ bus.on('history:changed', ({ canUndo, canRedo }) => {
+ undo.disabled = !canUndo
+ redo.disabled = !canRedo
+ })
+}
+
+function isTextEntry(target: EventTarget | null): boolean {
+ const el = target as HTMLElement | null
+ if (!el) return false
+ return (
+ el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable
+ )
+}
+
+function handleUndoRedo(e: KeyboardEvent): void {
+ if (!(e.ctrlKey || e.metaKey)) return
+ if (isTextEntry(e.target)) return // let the input handle its own undo/redo
+ const key = e.key.toLowerCase()
+ if (key === 'z' && !e.shiftKey) {
+ e.preventDefault()
+ history.undo()
+ } else if ((key === 'z' && e.shiftKey) || key === 'y') {
+ e.preventDefault()
+ history.redo()
+ }
+}
diff --git a/frontend/src/foundation.test.ts b/frontend/src/foundation.test.ts
new file mode 100644
index 0000000..74e4972
--- /dev/null
+++ b/frontend/src/foundation.test.ts
@@ -0,0 +1,63 @@
+import { describe, expect, it, vi } from 'vitest'
+import { bus } from './bus'
+import { store } from './store'
+import { history, type Command } from './commands/base'
+
+describe('EventBus', () => {
+ it('delivers emitted payloads to subscribers', () => {
+ const seen: string[] = []
+ const off = bus.on('node:color-changed', ({ nodeId }) => seen.push(nodeId))
+ bus.emit('node:color-changed', { nodeId: 'A', color: '#fff' })
+ expect(seen).toEqual(['A'])
+ off()
+ bus.emit('node:color-changed', { nodeId: 'B', color: '#000' })
+ expect(seen).toEqual(['A']) // unsubscribed
+ })
+})
+
+describe('store', () => {
+ it('merges partial updates', () => {
+ store.update({ selectedLayers: ['L1'] })
+ expect(store.get().selectedLayers).toEqual(['L1'])
+ store.update({ selectedNodes: ['A_L1'] })
+ expect(store.get().selectedLayers).toEqual(['L1']) // preserved
+ expect(store.get().selectedNodes).toEqual(['A_L1'])
+ })
+})
+
+describe('CommandHistory', () => {
+ function counterCommand(log: string[]): Command {
+ return {
+ description: 'test',
+ execute: () => log.push('do'),
+ undo: () => log.push('undo'),
+ }
+ }
+
+ it('executes, undoes and redoes', () => {
+ const log: string[] = []
+ history.execute(counterCommand(log))
+ expect(history.canUndo).toBe(true)
+ history.undo()
+ expect(log).toEqual(['do', 'undo'])
+ expect(history.canRedo).toBe(true)
+ history.redo()
+ expect(log).toEqual(['do', 'undo', 'do'])
+ })
+
+ it('clears redo stack on a new execute', () => {
+ const log: string[] = []
+ history.execute(counterCommand(log))
+ history.undo()
+ history.execute(counterCommand(log))
+ expect(history.canRedo).toBe(false)
+ })
+
+ it('emits history:changed', () => {
+ const spy = vi.fn()
+ const off = bus.on('history:changed', spy)
+ history.execute({ description: 'x', execute: () => {}, undo: () => {} })
+ expect(spy).toHaveBeenCalled()
+ off()
+ })
+})
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
new file mode 100644
index 0000000..fd27d91
--- /dev/null
+++ b/frontend/src/main.ts
@@ -0,0 +1,111 @@
+// Entry point — port of v2 www/js/on_page_load.js.
+// Fetches config, sets up the Three.js renderer/camera/scene, mounts the
+// canvas, wires global listeners and starts the render loop.
+
+import 'bootstrap/dist/css/bootstrap.min.css'
+import 'bootstrap' // data-bs-* tab + collapse behavior for the navbar
+import './style.css'
+
+import { api } from './api/client'
+import { fetchConfig } from './api/config'
+import { loadSession } from './actions/network'
+import { showTab } from './ui/tabs'
+import { initHomePanel } from './ui/home'
+import { initFilePanel } from './ui/file'
+import { initLayoutsPanel } from './ui/layouts'
+import { initScenePanel } from './ui/scene'
+import { initLayerPanel } from './ui/layer'
+import { initNodePanel } from './ui/node'
+import { initEdgePanel } from './ui/edge'
+import { initFpsPanel } from './ui/fps'
+import { initDataPanel } from './ui/data'
+import { initHelpPanel } from './ui/help'
+import { initMinimap } from './ui/minimap'
+import { registerDrawerToggle } from './ui/drawer_toggle'
+import { store } from './store'
+import { ctx, Scene } from './three'
+import { history } from './commands/base'
+import { registerCanvasControls } from './actions/canvas_controls'
+import { executeCommand } from './actions/right_click_menu'
+import { registerLayerDragControls } from './actions/drag_controls'
+import { registerLabelRendering } from './actions/labels'
+import { registerNavControls } from './actions/nav_controls'
+import { setRenderer, resetScreen, animate } from './actions/screen'
+import { registerThemeButtons, registerThemeListener } from './actions/themes'
+import { registerGlobalListeners } from './event_listeners'
+
+async function main(): Promise {
+ const config = await fetchConfig().catch((err: unknown) => {
+ console.error('[arena3d] config fetch failed', err)
+ return null
+ })
+ if (config) {
+ store.update({ config })
+ ctx.edgeDefaultColor = config.edge_default_color
+ }
+
+ setRenderer()
+ resetScreen()
+
+ ctx.scene = new Scene()
+ ctx.scene.tiltDefault()
+ ctx.scene.setScale(0.9)
+
+ const app = document.getElementById('app')
+ if (app && ctx.renderer) {
+ ctx.renderer.setClearColor('#000000') // dark default until a theme is applied
+ ctx.renderer.domElement.style.display = 'block'
+ app.appendChild(ctx.renderer.domElement)
+ }
+
+ initHomePanel()
+ initFilePanel()
+ initLayoutsPanel()
+ initScenePanel()
+ initLayerPanel()
+ initNodePanel()
+ initEdgePanel()
+ initFpsPanel()
+ initDataPanel()
+ initHelpPanel()
+ initMinimap()
+ registerDrawerToggle()
+ registerGlobalListeners()
+ registerThemeListener()
+ registerThemeButtons()
+ registerLabelRendering()
+ registerCanvasControls()
+ registerLayerDragControls()
+ registerNavControls()
+ const year = document.getElementById('footer-year')
+ if (year) year.textContent = String(new Date().getFullYear())
+ animate()
+
+ // Test hook: lets Playwright read scene state via page.evaluate (WebGL is
+ // opaque to the a11y tree). See PLAN Phase 13.
+ ;(window as unknown as { __arena: unknown }).__arena = {
+ ctx,
+ history,
+ executeCommand,
+ }
+
+ await loadExternalSession()
+}
+
+// A partner app can hand off a session via POST /api/external, which returns a
+// URL of the form `/?session=`. Resolve that token back to a normalized
+// session and load it, same as a file import.
+async function loadExternalSession(): Promise {
+ if (typeof window === 'undefined') return
+ const token = new URLSearchParams(window.location.search).get('session')
+ if (!token) return
+ try {
+ const session = await api.resolveExternal(token)
+ loadSession(session)
+ showTab('#panel-main-view')
+ } catch (err) {
+ console.error('[arena3d] external session load failed', err)
+ }
+}
+
+void main()
diff --git a/frontend/src/store/.gitkeep b/frontend/src/store/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts
new file mode 100644
index 0000000..f0c3e3e
--- /dev/null
+++ b/frontend/src/store/index.ts
@@ -0,0 +1,33 @@
+// Single typed source of truth — replaces scattered Shiny input$ values and JS globals.
+import type { AppConfig } from '../api/config'
+import type { NetworkData } from '../api/client'
+
+export interface AppState {
+ config: AppConfig | null
+ network: NetworkData | null
+ selectedLayers: string[]
+ selectedNodes: string[]
+ selectedChannels: string[]
+ currentTheme: string
+}
+
+class Store {
+ private state: AppState = {
+ config: null,
+ network: null,
+ selectedLayers: [],
+ selectedNodes: [],
+ selectedChannels: [],
+ currentTheme: 'dark',
+ }
+
+ get(): Readonly {
+ return this.state
+ }
+
+ update(patch: Partial): void {
+ this.state = { ...this.state, ...patch }
+ }
+}
+
+export const store = new Store()
diff --git a/frontend/src/style.css b/frontend/src/style.css
new file mode 100644
index 0000000..9d7fb6d
--- /dev/null
+++ b/frontend/src/style.css
@@ -0,0 +1,1128 @@
+/* App-level layout: fullscreen Three.js canvas with panel overlays.
+ Replaces v2 www/arena3d.css positioning hacks; per-panel rules land
+ with their panels. */
+
+/* Design tokens shared by the 2D chrome so it reads as one system with the
+ dark canvas. One cool "instrument" palette; a single blue accent. */
+:root {
+ --arena-accent: #5b9bff;
+ --arena-accent-hover: #7db0ff;
+ --arena-accent-rgb: 91, 155, 255;
+ --arena-bg: #0a0d13;
+ --arena-surface: rgba(18, 23, 32, 0.86);
+ --arena-surface-solid: #12171f;
+ --arena-elevated: rgba(28, 34, 45, 0.92);
+ --arena-border: rgba(255, 255, 255, 0.09);
+ --arena-border-strong: rgba(255, 255, 255, 0.16);
+ --arena-text: #e2e8f2;
+ --arena-text-muted: #94a1b5;
+ --arena-radius: 10px;
+ --arena-radius-sm: 7px;
+ --arena-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
+ --arena-ease: cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+/* Retint Bootstrap's dark theme so every .btn / .form-control / .nav / .table
+ inherits the palette instead of being restyled one component at a time. */
+[data-bs-theme='dark'] {
+ --bs-body-bg: #0a0d13;
+ --bs-body-color: #e2e8f2;
+ --bs-secondary-color: #94a1b5;
+ --bs-tertiary-bg: rgba(18, 23, 32, 0.86);
+ --bs-border-color: rgba(255, 255, 255, 0.12);
+ --bs-primary: #5b9bff;
+ --bs-primary-rgb: 91, 155, 255;
+ --bs-link-color: #7db0ff;
+ --bs-link-color-rgb: 125, 176, 255;
+ --bs-link-hover-color: #a3c6ff;
+ --bs-body-font-family:
+ system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
+}
+
+body {
+ font-family: var(--bs-body-font-family);
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+/* Frosted navbar so the canvas glows through without hurting legibility. */
+#navBar {
+ background-color: var(--arena-surface) !important;
+ backdrop-filter: blur(14px) saturate(140%);
+ -webkit-backdrop-filter: blur(14px) saturate(140%);
+ border-bottom: 1px solid var(--arena-border);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
+ min-height: 56px;
+}
+
+#navBar .navbar-nav .nav-link {
+ font-size: 0.9rem;
+ font-weight: 500;
+ color: var(--arena-text-muted);
+ padding: 0.4rem 0.72rem;
+ border-radius: var(--arena-radius-sm);
+ transition:
+ color 0.15s var(--arena-ease),
+ background-color 0.15s var(--arena-ease);
+}
+
+#navBar .navbar-nav .nav-link:hover {
+ color: var(--arena-text);
+ background-color: rgba(255, 255, 255, 0.05);
+}
+
+#navBar .navbar-nav .nav-link.active {
+ color: #fff;
+ background-color: rgba(var(--arena-accent-rgb), 0.16);
+ box-shadow: inset 0 0 0 1px rgba(var(--arena-accent-rgb), 0.4);
+}
+
+/* Muted ghost button — undo/redo group, and any secondary/utility action
+ elsewhere (e.g. File panel) that shouldn't compete with a primary CTA. */
+.btn-outline-secondary {
+ --bs-btn-color: var(--arena-text-muted);
+ --bs-btn-border-color: var(--arena-border-strong);
+ --bs-btn-hover-bg: rgba(255, 255, 255, 0.06);
+ --bs-btn-hover-border-color: var(--arena-accent);
+ --bs-btn-hover-color: var(--arena-text);
+ font-weight: 500;
+}
+
+/* Shared component polish -------------------------------------------------- */
+.btn {
+ --bs-btn-font-weight: 500;
+ border-radius: var(--arena-radius-sm);
+ transition:
+ transform 0.12s var(--arena-ease),
+ filter 0.15s var(--arena-ease),
+ background-color 0.15s var(--arena-ease),
+ box-shadow 0.15s var(--arena-ease);
+}
+
+.btn-primary {
+ --bs-btn-bg: var(--arena-accent);
+ --bs-btn-border-color: var(--arena-accent);
+ --bs-btn-hover-bg: var(--arena-accent-hover);
+ --bs-btn-hover-border-color: var(--arena-accent-hover);
+ --bs-btn-active-bg: #4a88ec;
+ --bs-btn-color: #06101f;
+ --bs-btn-hover-color: #06101f;
+ box-shadow: 0 4px 14px rgba(var(--arena-accent-rgb), 0.28);
+}
+
+.btn:not(:disabled):hover {
+ transform: translateY(-1px);
+}
+
+.btn:not(:disabled):active {
+ transform: translateY(0) scale(0.985);
+}
+
+/* File panel action row: one solid primary CTA (Save) plus two ghost actions,
+ so three buttons don't compete as equals the way three btn-primarys did. */
+.file-actions {
+ display: flex;
+ flex-direction: column;
+ gap: 0.55rem;
+ margin-top: 1.1rem;
+}
+
+.file-action {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.55rem;
+ width: 100%;
+ padding: 0.55rem 1rem;
+}
+
+.file-action svg {
+ width: 16px;
+ height: 16px;
+ flex: 0 0 auto;
+}
+
+.form-control,
+.form-select {
+ background-color: rgba(255, 255, 255, 0.03);
+ border-color: var(--arena-border-strong);
+ border-radius: var(--arena-radius-sm);
+ color: var(--arena-text);
+ transition:
+ border-color 0.15s var(--arena-ease),
+ box-shadow 0.15s var(--arena-ease);
+}
+
+/* The native dropdown popup (option list) renders with the OS default light
+ background, so inheriting only the light --arena-text made options
+ unreadable (light-on-light). Force an opaque dark background + light text. */
+.form-select option,
+.form-select optgroup {
+ background-color: var(--arena-surface-solid);
+ color: var(--arena-text);
+}
+
+.form-control:focus,
+.form-select:focus {
+ background-color: rgba(255, 255, 255, 0.04);
+ border-color: var(--arena-accent);
+ box-shadow: 0 0 0 3px rgba(var(--arena-accent-rgb), 0.22);
+}
+
+.form-control::file-selector-button {
+ background-color: rgba(var(--arena-accent-rgb), 0.14);
+ color: var(--arena-text);
+ border: 0;
+ border-right: 1px solid var(--arena-border);
+}
+
+.form-label {
+ font-size: 0.82rem;
+ font-weight: 600;
+ letter-spacing: 0.01em;
+ color: var(--arena-text-muted);
+ margin-bottom: 0.35rem;
+}
+
+.form-check-input:checked {
+ background-color: var(--arena-accent);
+ border-color: var(--arena-accent);
+}
+
+.form-check-input:focus {
+ border-color: var(--arena-accent);
+ box-shadow: 0 0 0 3px rgba(var(--arena-accent-rgb), 0.22);
+}
+
+.form-range::-webkit-slider-thumb {
+ background: var(--arena-accent);
+}
+.form-range::-moz-range-thumb {
+ background: var(--arena-accent);
+}
+
+:focus-visible {
+ outline: 2px solid var(--arena-accent);
+ outline-offset: 2px;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ * {
+ transition-duration: 0.01ms !important;
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ }
+}
+
+#app {
+ position: fixed;
+ inset: 0;
+ z-index: 0;
+}
+
+/* Action panels open as a right-side drawer so the 3D scene stays visible;
+ only Home and Help take the full view (they are reading, not operating on
+ the scene). Main View's pane is display:none, so the canvas shows through. */
+#panelContent .tab-pane {
+ position: fixed;
+ top: 56px; /* navbar height */
+ right: 0;
+ bottom: 0;
+ width: min(440px, 94vw);
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ z-index: 5; /* above the #info / nav-control overlays (z-index 2) */
+ background-color: var(--arena-surface);
+ backdrop-filter: blur(16px) saturate(140%);
+ -webkit-backdrop-filter: blur(16px) saturate(140%);
+ border-left: 1px solid var(--arena-border);
+ box-shadow: var(--arena-shadow);
+ /* extra bottom padding clears the fixed footer bar */
+ padding: 1.4rem 1.35rem calc(3rem + env(safe-area-inset-bottom, 0px));
+}
+
+/* Slide the drawer in when it becomes the active pane. */
+#panelContent .tab-pane.show {
+ animation: drawer-in 0.28s var(--arena-ease);
+}
+
+@keyframes drawer-in {
+ from {
+ transform: translateX(16px);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+}
+
+/* Collapse handle for the right-side drawer: sits on the seam between the
+ open drawer and the canvas, or on the viewport edge once the drawer is
+ closed, so a panel can be dismissed/reopened without the navbar tabs. */
+.panel-drawer-toggle {
+ position: fixed;
+ top: 50%;
+ right: 0;
+ transform: translateY(-50%);
+ z-index: 6; /* above the drawer's z-index 5 */
+ width: 20px;
+ height: 64px;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ color: var(--arena-text-muted);
+ background-color: var(--arena-surface);
+ border: 1px solid var(--arena-border);
+ border-right: 0;
+ border-radius: var(--arena-radius-sm) 0 0 var(--arena-radius-sm);
+ box-shadow: var(--arena-shadow);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ cursor: pointer;
+ transition:
+ right 0.28s var(--arena-ease),
+ color 0.15s var(--arena-ease),
+ background-color 0.15s var(--arena-ease);
+}
+
+.panel-drawer-toggle:hover {
+ color: var(--arena-text);
+ background-color: var(--arena-elevated);
+}
+
+.panel-drawer-toggle.is-open {
+ color: var(--arena-accent);
+}
+
+/* Home and Help are full-width reading surfaces, not scene controls.
+ Selector carries two ids to out-specify the base .tab-pane drawer rule. */
+#panelContent #panel-home,
+#panelContent #panel-help {
+ left: 0;
+ width: auto;
+ background-color: var(--bs-body-bg);
+ backdrop-filter: none;
+ -webkit-backdrop-filter: none;
+ border-left: 0;
+ box-shadow: none;
+ padding: 1.6rem 2rem;
+}
+
+/* Wide data tables get a roomier drawer. */
+#panel-data {
+ width: min(680px, 96vw);
+}
+
+/* Inside a drawer the old responsive column wrappers would shrink content to a
+ fraction of the (already narrow) drawer — let panel content use full width. */
+#panelContent .tab-pane:not(#panel-home):not(#panel-help) [class*='col-'] {
+ flex: 0 0 100%;
+ max-width: 100%;
+ width: 100%;
+ padding-left: 0;
+ padding-right: 0;
+}
+
+/* Per-layer selection rows: name on the left, Hide/Labels toggles on the right,
+ never wrapping into an unreadable pile in the narrow drawer. */
+.layer-row {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.4rem 0.55rem;
+ margin-bottom: 0.3rem;
+ border: 1px solid var(--arena-border);
+ border-radius: var(--arena-radius-sm);
+ background: rgba(255, 255, 255, 0.02);
+ transition: border-color 0.15s var(--arena-ease);
+}
+
+.layer-row:hover {
+ border-color: var(--arena-border-strong);
+}
+
+.layer-row__name {
+ flex: 1 1 auto;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-weight: 500;
+ margin: 0;
+}
+
+.layer-row__opt {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.3rem;
+ flex: 0 0 auto;
+ font-size: 0.8rem;
+ color: var(--arena-text-muted);
+ margin: 0;
+ cursor: pointer;
+}
+
+.layer-row__opt .form-check-input {
+ margin: 0;
+}
+
+/* Main View has no panel content: keep it invisible so the canvas shows. */
+#panel-main-view {
+ display: none !important;
+}
+
+#info {
+ position: fixed;
+ top: 70px;
+ left: 10px;
+ z-index: 2;
+ font-size: 12px;
+ color: var(--bs-body-color);
+}
+
+/* Nav control buttons + Scene/Layers/Nodes control table (port of v2
+ arena3d.css canvas-control rules; positions adapted to the fixed
+ navbar). Higher z-index than #info so the toggle/pause buttons are never
+ clipped by the controls panel below them, regardless of exact heights. */
+#navControlButtonsDiv {
+ position: fixed;
+ top: 64px;
+ left: 10px;
+ z-index: 3;
+}
+
+.displayCanvasControls {
+ display: block;
+ margin-top: 8px;
+ padding: 4px 12px;
+ text-align: center;
+ font-size: 12px;
+ color: var(--bs-body-color);
+ background-color: var(--arena-surface);
+ border: 1px solid var(--arena-border);
+ border-radius: 6px;
+ transition: border-color 0.15s ease;
+}
+
+.displayCanvasControls:hover {
+ border-color: var(--arena-accent);
+}
+
+/* Icon + label toggle buttons (render-pause, controls disclosure): the icon
+ swaps per state instead of the label alone carrying the meaning. */
+.nav-toggle-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.4rem;
+}
+
+.nav-toggle-icon {
+ color: var(--arena-accent);
+ font-size: 10px;
+ line-height: 1;
+}
+
+/* Paused = an actual stopped state worth calling out, not just a hover tint. */
+.nav-toggle-btn.is-paused {
+ border-color: rgba(var(--arena-accent-rgb), 0.5);
+ background-color: rgba(var(--arena-accent-rgb), 0.1);
+}
+
+.nav-toggle-btn.is-paused .nav-toggle-icon {
+ color: #ffb454;
+}
+
+/* Theme bar (port of v2 #themeDiv/.themeButton): fixed top-right, one button
+ per THEMES preset, each previewing its own scene colors. */
+#themeDiv {
+ position: fixed;
+ top: 64px;
+ right: 10px;
+ z-index: 3;
+ display: flex;
+ gap: 4px;
+}
+
+.themeButton {
+ width: 56px;
+ padding: 3px 0;
+ font-size: 12px;
+ font-weight: bold;
+ border: 1px solid var(--arena-border);
+ border-radius: 6px;
+ transition: border-color 0.15s ease;
+}
+
+.themeButton:hover {
+ border-color: var(--arena-accent);
+}
+
+#lightThemeButton {
+ color: #5c5c5c;
+ background-color: #ffffff;
+}
+
+#darkThemeButton {
+ color: #ffffff;
+ background-color: #000000;
+}
+
+#grayThemeButton {
+ color: #6e2a5a;
+ background-color: #bababa;
+}
+
+#info {
+ width: 258px;
+ top: 150px; /* below the nav control buttons */
+ overflow-y: auto;
+ max-height: calc(100vh - 190px);
+ padding: 10px 12px;
+ background-color: var(--arena-surface);
+ border: 1px solid var(--arena-border);
+ border-radius: 8px;
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+}
+
+/* Before a network is loaded, #info holds only the waiting hint: present it as
+ a centered empty-state card over the canvas instead of a top-left line. */
+#info:not(:has(table)) {
+ top: 50%;
+ left: 50%;
+ right: auto;
+ transform: translate(-50%, -50%);
+ width: auto;
+ max-width: 340px;
+ max-height: none;
+ text-align: center;
+ font-size: 14px;
+ line-height: 1.6;
+ color: var(--arena-text-muted);
+ padding: 1.5rem 1.75rem;
+ border-radius: var(--arena-radius);
+}
+
+#info:not(:has(table))::before {
+ content: '◇';
+ display: block;
+ font-size: 1.6rem;
+ color: var(--arena-accent);
+ margin-bottom: 0.5rem;
+}
+
+/* Before a network loads, #info is just passive hint text and must not block
+ canvas interaction. Once it holds the controls table it becomes a real
+ panel — including its own scrollbar — so it needs normal pointer events. */
+#info:not(:has(table)) {
+ pointer-events: none;
+}
+
+/* Node/layer labels are display-only overlays; let the mouse fall through to
+ the canvas (lasso, layer drag). Scoped to the divs, not #labelDiv — the
+ right-click node menu also mounts there and needs clicks. */
+.labels,
+.layer-labels {
+ pointer-events: none;
+}
+
+/* Navigation shortcut cheat-sheet: action left, keys right. */
+#navShortcuts {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+#navShortcuts li {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+ padding: 3px 0;
+ color: var(--arena-text-muted);
+}
+
+#navShortcuts li span:last-child {
+ white-space: nowrap;
+ text-align: right;
+}
+
+#navShortcuts li + li {
+ border-top: 1px solid var(--arena-border);
+}
+
+#navShortcuts kbd {
+ display: inline-block;
+ padding: 1px 5px;
+ font-size: 10px;
+ line-height: 1.5;
+ color: var(--arena-text);
+ background: var(--arena-elevated);
+ border: 1px solid var(--arena-border-strong);
+ border-bottom-width: 2px;
+ border-radius: 4px;
+}
+
+#navShortcuts kbd + kbd {
+ margin-left: 3px;
+}
+
+#canvasControls_table {
+ color: var(--bs-body-color);
+ width: 100%;
+ margin-top: 0.5rem;
+}
+
+#canvasControls_table h5 {
+ font-size: 0.95rem;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ color: var(--arena-text);
+ margin: 0.35rem 0;
+}
+
+#canvasControls_table #recenterButton {
+ width: 100%;
+ margin: 0.25rem 0 0.5rem;
+ padding: 0.35rem 0.6rem;
+ font-size: 0.82rem;
+ color: var(--arena-text);
+ background: rgba(var(--arena-accent-rgb), 0.14);
+ border: 1px solid rgba(var(--arena-accent-rgb), 0.4);
+ border-radius: var(--arena-radius-sm);
+ transition:
+ background-color 0.15s var(--arena-ease),
+ border-color 0.15s var(--arena-ease);
+}
+
+#canvasControls_table #recenterButton:hover {
+ background: rgba(var(--arena-accent-rgb), 0.24);
+ border-color: var(--arena-accent);
+}
+
+.border_tr {
+ border-top: 1px solid var(--arena-border-strong);
+}
+
+.border_tr td {
+ padding-top: 0.5rem;
+}
+
+.controlsSubLabel {
+ font-size: 0.7rem;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--arena-text-muted);
+ padding: 0.1rem 0 0.3rem;
+}
+
+.canvasSlider {
+ width: 100%;
+ accent-color: var(--arena-accent);
+ cursor: pointer;
+}
+
+.labelDrop {
+ vertical-align: bottom;
+ color: var(--arena-text-muted);
+ font-variant-numeric: tabular-nums;
+}
+
+.raiseSlider {
+ margin-top: -30px;
+}
+
+.red {
+ font-weight: bold;
+ color: #fb3d2a;
+}
+
+.green {
+ font-weight: bold;
+ color: #46fb2a;
+}
+
+.blue {
+ font-weight: bold;
+ color: #2ac2fb;
+}
+
+.canvasControls {
+ background-repeat: no-repeat;
+ background-position: center;
+ width: 40px;
+ height: 40px;
+ cursor: pointer;
+ border-radius: var(--arena-radius-sm);
+ transition: background-color 0.15s var(--arena-ease);
+}
+
+.canvasControls:hover {
+ background-color: rgba(var(--arena-accent-rgb), 0.12);
+}
+
+.canvasControls:active {
+ background-color: rgba(var(--arena-accent-rgb), 0.22);
+}
+
+.image_expandLayers,
+.image_collapseLayers,
+.image_nodeExpand,
+.image_nodeCollapse {
+ width: 50%;
+ height: 45px;
+}
+
+/* prettier-ignore */
+.image_minusX { background-image: url(/images/canvas_control/minusX.png); }
+/* prettier-ignore */
+.image_minusX:hover { background-image: url(/images/canvas_control/minusX_hover.png); }
+/* prettier-ignore */
+.image_minusX:active { background-image: url(/images/canvas_control/minusX_active.png); }
+/* prettier-ignore */
+.image_plusX { background-image: url(/images/canvas_control/plusX.png); }
+/* prettier-ignore */
+.image_plusX:hover { background-image: url(/images/canvas_control/plusX_hover.png); }
+/* prettier-ignore */
+.image_plusX:active { background-image: url(/images/canvas_control/plusX_active.png); }
+/* prettier-ignore */
+.image_minusY { background-image: url(/images/canvas_control/minusY.png); }
+/* prettier-ignore */
+.image_minusY:hover { background-image: url(/images/canvas_control/minusY_hover.png); }
+/* prettier-ignore */
+.image_minusY:active { background-image: url(/images/canvas_control/minusY_active.png); }
+/* prettier-ignore */
+.image_plusY { background-image: url(/images/canvas_control/plusY.png); }
+/* prettier-ignore */
+.image_plusY:hover { background-image: url(/images/canvas_control/plusY_hover.png); }
+/* prettier-ignore */
+.image_plusY:active { background-image: url(/images/canvas_control/plusY_active.png); }
+/* prettier-ignore */
+.image_minusZ { background-image: url(/images/canvas_control/minusZ.png); }
+/* prettier-ignore */
+.image_minusZ:hover { background-image: url(/images/canvas_control/minusZ_hover.png); }
+/* prettier-ignore */
+.image_minusZ:active { background-image: url(/images/canvas_control/minusZ_active.png); }
+/* prettier-ignore */
+.image_plusZ { background-image: url(/images/canvas_control/plusZ.png); }
+/* prettier-ignore */
+.image_plusZ:hover { background-image: url(/images/canvas_control/plusZ_hover.png); }
+/* prettier-ignore */
+.image_plusZ:active { background-image: url(/images/canvas_control/plusZ_active.png); }
+/* prettier-ignore */
+.image_minusX_T { background-image: url(/images/canvas_control/minusX_t.png); }
+/* prettier-ignore */
+.image_minusX_T:hover { background-image: url(/images/canvas_control/minusX_hover_t.png); }
+/* prettier-ignore */
+.image_minusX_T:active { background-image: url(/images/canvas_control/minusX_active_t.png); }
+/* prettier-ignore */
+.image_plusX_T { background-image: url(/images/canvas_control/plusX_t.png); }
+/* prettier-ignore */
+.image_plusX_T:hover { background-image: url(/images/canvas_control/plusX_hover_t.png); }
+/* prettier-ignore */
+.image_plusX_T:active { background-image: url(/images/canvas_control/plusX_active_t.png); }
+/* prettier-ignore */
+.image_minusY_T { background-image: url(/images/canvas_control/minusY_t.png); }
+/* prettier-ignore */
+.image_minusY_T:hover { background-image: url(/images/canvas_control/minusY_hover_t.png); }
+/* prettier-ignore */
+.image_minusY_T:active { background-image: url(/images/canvas_control/minusY_active_t.png); }
+/* prettier-ignore */
+.image_plusY_T { background-image: url(/images/canvas_control/plusY_t.png); }
+/* prettier-ignore */
+.image_plusY_T:hover { background-image: url(/images/canvas_control/plusY_hover_t.png); }
+/* prettier-ignore */
+.image_plusY_T:active { background-image: url(/images/canvas_control/plusY_active_t.png); }
+/* prettier-ignore */
+.image_minusZ_T { background-image: url(/images/canvas_control/minusZ_t.png); }
+/* prettier-ignore */
+.image_minusZ_T:hover { background-image: url(/images/canvas_control/minusZ_hover_t.png); }
+/* prettier-ignore */
+.image_minusZ_T:active { background-image: url(/images/canvas_control/minusZ_active_t.png); }
+/* prettier-ignore */
+.image_plusZ_T { background-image: url(/images/canvas_control/plusZ_t.png); }
+/* prettier-ignore */
+.image_plusZ_T:hover { background-image: url(/images/canvas_control/plusZ_hover_t.png); }
+/* prettier-ignore */
+.image_plusZ_T:active { background-image: url(/images/canvas_control/plusZ_active_t.png); }
+/* prettier-ignore */
+.image_expandLayers { background-image: url(/images/canvas_control/expandLayers.png); }
+/* prettier-ignore */
+.image_expandLayers:hover { background-image: url(/images/canvas_control/expandLayers_hover.png); }
+/* prettier-ignore */
+.image_expandLayers:active { background-image: url(/images/canvas_control/expandLayers_active.png); }
+/* prettier-ignore */
+.image_collapseLayers { background-image: url(/images/canvas_control/collapseLayers.png); }
+/* prettier-ignore */
+.image_collapseLayers:hover { background-image: url(/images/canvas_control/collapseLayers_hover.png); }
+/* prettier-ignore */
+.image_collapseLayers:active { background-image: url(/images/canvas_control/collapseLayers_active.png); }
+/* prettier-ignore */
+.image_nodeExpand { background-image: url(/images/canvas_control/expandNodes.png); }
+/* prettier-ignore */
+.image_nodeExpand:hover { background-image: url(/images/canvas_control/expandNodes_hover.png); }
+/* prettier-ignore */
+.image_nodeExpand:active { background-image: url(/images/canvas_control/expandNodes_active.png); }
+/* prettier-ignore */
+.image_nodeCollapse { background-image: url(/images/canvas_control/collapseNodes.png); }
+/* prettier-ignore */
+.image_nodeCollapse:hover { background-image: url(/images/canvas_control/collapseNodes_hover.png); }
+/* prettier-ignore */
+.image_nodeCollapse:active { background-image: url(/images/canvas_control/collapseNodes_active.png); }
+
+/* Loading spinner — slim centered ring (v2's 16px ring at margin hacks). */
+#loader {
+ border: 4px solid rgba(255, 255, 255, 0.15);
+ border-radius: 50%;
+ border-top: 4px solid var(--arena-accent);
+ width: 56px;
+ height: 56px;
+ animation: spin 0.9s linear infinite;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ margin: -28px 0 0 -28px;
+ z-index: 6; /* above open panels, which sit at z-index 5 */
+ display: none;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ #loader {
+ animation-duration: 2.5s;
+ }
+}
+
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+/* Node description panel (port of v2 #descrDiv rules; fixed right instead of
+ v2's float + negative-margin hack). */
+#descrDiv {
+ position: fixed;
+ display: none;
+ top: 160px;
+ right: 10px;
+ z-index: 3;
+ background-color: var(--arena-surface);
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+ height: 35vh;
+ width: 15%;
+ min-width: 220px;
+ border: 1px solid var(--arena-border);
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
+ overflow-y: auto;
+}
+
+.descrDiv_paragraph {
+ color: white;
+ padding: 15px;
+ text-align: justify;
+ text-indent: 15px;
+ margin-top: 10px;
+}
+
+#closeButton {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ width: 26px;
+ height: 26px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--arena-text-muted);
+ background-color: rgba(255, 255, 255, 0.06);
+ border: 1px solid var(--arena-border);
+ border-radius: var(--arena-radius-sm);
+ transition:
+ color 0.15s var(--arena-ease),
+ background-color 0.15s var(--arena-ease),
+ border-color 0.15s var(--arena-ease);
+}
+
+#closeButton:hover {
+ color: #fff;
+ background-color: rgba(220, 70, 60, 0.85);
+ border-color: rgba(220, 70, 60, 0.9);
+}
+
+#closeButton:active {
+ background-color: rgba(180, 40, 32, 0.9);
+}
+
+/* Footer — quiet translucent bar instead of v2's solid blue banner. */
+footer {
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ padding: 4px 0;
+ background-color: var(--arena-surface);
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+ border-top: 1px solid var(--arena-border);
+ text-align: center;
+ font-size: 13px;
+ color: rgba(255, 255, 255, 0.65);
+ z-index: 2;
+}
+
+footer a {
+ color: rgba(255, 255, 255, 0.85);
+ text-decoration: none;
+}
+
+footer a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+
+#logo1 {
+ background-image: url(/images/logo_dark.webp);
+ background-repeat: no-repeat;
+ background-size: contain;
+ width: 220px;
+ height: 52px;
+}
+
+/* Home panel ---------------------------------------------------------------- */
+.home-wrap {
+ max-width: 1080px;
+ margin: 0 auto;
+ padding-bottom: 4rem;
+}
+
+.home-hero {
+ padding: 0.5rem 0 1.75rem;
+ border-bottom: 1px solid var(--arena-border);
+ margin-bottom: 2rem;
+}
+
+.home-tagline {
+ margin: 0.75rem 0 0;
+ font-size: 1.05rem;
+ color: var(--arena-text-muted);
+ letter-spacing: 0.01em;
+}
+
+.home-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
+ gap: 2.5rem;
+ align-items: start;
+}
+
+.home-figure {
+ margin: 0;
+ border: 1px solid var(--arena-border);
+ border-radius: var(--arena-radius);
+ overflow: hidden;
+ background: rgba(255, 255, 255, 0.02);
+}
+
+.home-figure img {
+ display: block;
+ width: 100%;
+}
+
+.home-intro p {
+ font-size: 1rem;
+ line-height: 1.7;
+ color: var(--arena-text);
+ max-width: 62ch;
+}
+
+.home-actions {
+ display: grid;
+ gap: 0.75rem;
+ margin-top: 1.5rem;
+}
+
+.home-action {
+ display: block;
+ padding: 0.85rem 1.05rem;
+ border: 1px solid var(--arena-border-strong);
+ border-radius: var(--arena-radius);
+ text-decoration: none;
+ background: rgba(255, 255, 255, 0.02);
+ transition:
+ transform 0.15s var(--arena-ease),
+ border-color 0.15s var(--arena-ease),
+ background-color 0.15s var(--arena-ease);
+}
+
+.home-action:hover {
+ transform: translateY(-2px);
+ border-color: var(--arena-accent);
+ background: rgba(var(--arena-accent-rgb), 0.08);
+}
+
+.home-action--primary {
+ border-color: rgba(var(--arena-accent-rgb), 0.55);
+ background: rgba(var(--arena-accent-rgb), 0.12);
+}
+
+.home-action__title {
+ display: block;
+ font-weight: 600;
+ color: var(--arena-text);
+}
+
+.home-action__sub {
+ display: block;
+ font-size: 0.85rem;
+ color: var(--arena-text-muted);
+ margin-top: 0.15rem;
+}
+
+.home-cite {
+ margin-top: 2.75rem;
+ padding: 1.5rem 1.6rem;
+ border: 1px solid var(--arena-border);
+ border-radius: var(--arena-radius);
+ background: rgba(255, 255, 255, 0.015);
+}
+
+.home-cite__heading {
+ font-size: 0.82rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--arena-text-muted);
+ margin: 0 0 1rem;
+}
+
+.home-cite p {
+ font-size: 0.9rem;
+ line-height: 1.6;
+ color: var(--arena-text-muted);
+ margin-bottom: 1rem;
+}
+
+.home-cite p:last-child {
+ margin-bottom: 0;
+}
+
+@media (max-width: 768px) {
+ #panel-home {
+ padding: 1.25rem 1rem;
+ }
+ .home-grid {
+ grid-template-columns: 1fr;
+ gap: 1.5rem;
+ }
+}
+
+/* Help panel — inner tab bar + content, themed to match the dark chrome. */
+#helpDiv {
+ max-width: 1080px;
+ margin: 0 auto;
+}
+
+#helpDiv .tab {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+ width: 100%;
+ padding-bottom: 1.25rem;
+ margin-bottom: 1.5rem;
+ border-bottom: 1px solid var(--arena-border);
+}
+
+#helpDiv .tab button {
+ background-color: rgba(255, 255, 255, 0.03);
+ border: 1px solid var(--arena-border);
+ border-radius: 999px;
+ outline: none;
+ cursor: pointer;
+ padding: 0.4rem 0.9rem;
+ font-size: 0.88rem;
+ font-weight: 500;
+ color: var(--arena-text-muted);
+ transition:
+ color 0.15s var(--arena-ease),
+ background-color 0.15s var(--arena-ease),
+ border-color 0.15s var(--arena-ease);
+}
+
+#helpDiv .tab button:hover {
+ color: var(--arena-text);
+ border-color: var(--arena-border-strong);
+ background-color: rgba(255, 255, 255, 0.06);
+}
+
+#helpDiv .tab button.active {
+ color: #06101f;
+ background-color: var(--arena-accent);
+ border-color: var(--arena-accent);
+}
+
+#helpDiv .tabcontent {
+ display: none;
+ width: 100%;
+ max-width: 78ch;
+ line-height: 1.65;
+ word-break: break-word;
+}
+
+#helpDiv .tabcontent h2,
+#helpDiv .tabcontent h3 {
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ margin-top: 1.75rem;
+}
+
+#helpDiv .tabcontent img {
+ max-width: 100%;
+ border: 1px solid var(--arena-border);
+ border-radius: var(--arena-radius-sm);
+}
+
+#helpDiv pre {
+ overflow-x: auto;
+ padding: 0.85rem 1rem;
+ border: 1px solid var(--arena-border);
+ border-radius: var(--arena-radius-sm);
+ background-color: rgba(255, 255, 255, 0.04);
+ color: var(--arena-text);
+}
+
+#helpDiv .numbering {
+ color: var(--arena-accent);
+ font-weight: 700;
+ font-size: 1.05rem;
+}
+
+#helpDiv .indent {
+ margin-left: 20px;
+}
+
+#helpDiv .last_p {
+ margin-bottom: 100px;
+}
+
+/* 2D navigator (satellite view): fixed bottom-right thumbnail of the network's
+ current projection. Click/drag it to pan the 3D view. */
+#minimap {
+ position: fixed;
+ right: 10px;
+ bottom: 30px;
+ z-index: 3;
+ background-color: var(--arena-surface);
+ border: 1px solid var(--arena-border);
+ border-radius: 8px;
+ padding: 4px;
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+ cursor: crosshair;
+ line-height: 0;
+}
diff --git a/frontend/src/three/.gitkeep b/frontend/src/three/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/three/Edge.ts b/frontend/src/three/Edge.ts
new file mode 100644
index 0000000..49fd802
--- /dev/null
+++ b/frontend/src/three/Edge.ts
@@ -0,0 +1,296 @@
+import * as THREE from 'three'
+import { SELECTED_DEFAULT_COLOR } from './constants'
+import { ctx } from './runtime'
+
+export interface EdgeOptions {
+ id?: number
+ source?: string
+ target?: string
+ colors?: string[]
+ weights?: number[]
+ channels?: string[]
+ interLayer?: boolean
+}
+
+export class Edge {
+ THREE_Object!: THREE.Object3D
+ sourceNodeIndex = -1
+ targetNodeIndex = -1
+ sourceLayerIndex = -1
+ targetLayerIndex = -1
+
+ id: number
+ source: string
+ target: string
+ name: string
+ colors: string[]
+ importedColors: string[]
+ weights: number[]
+ channels: string[]
+ interLayer: boolean
+
+ isSelected = false
+
+ constructor({
+ id = 0,
+ source = '',
+ target = '',
+ colors,
+ weights = [],
+ channels = [],
+ interLayer = false,
+ }: EdgeOptions) {
+ this.id = id
+ this.source = source
+ this.target = target
+ this.name = this.source.concat('---').concat(this.target)
+ this.colors = colors ?? [ctx.edgeDefaultColor]
+ this.importedColors = [...this.colors] // distinct array: baseline for file-color priority
+ this.weights = weights
+ this.channels = channels
+ this.interLayer = interLayer
+
+ this.initIndexVariables()
+ this.drawEdge()
+ }
+
+ initIndexVariables(): void {
+ this.sourceNodeIndex = ctx.nodeLayerNames.indexOf(this.source)
+ this.targetNodeIndex = ctx.nodeLayerNames.indexOf(this.target)
+ this.sourceLayerIndex =
+ ctx.layerGroups[ctx.nodeGroups[ctx.nodeLayerNames[this.sourceNodeIndex]]]
+ this.targetLayerIndex =
+ ctx.layerGroups[ctx.nodeGroups[ctx.nodeLayerNames[this.targetNodeIndex]]]
+ }
+
+ drawEdge(): void {
+ const points = this.decidePoints()
+
+ if (this.channels.length === 0) this.createEdge(points)
+ else this.createChannels(points)
+
+ if (this.interLayer) ctx.scene!.add(this.THREE_Object)
+ else ctx.layers[this.sourceLayerIndex].addEdge(this.THREE_Object)
+ }
+
+ decidePoints(): THREE.Vector3[] {
+ const points: THREE.Vector3[] = []
+ if (this.interLayer)
+ points.push(
+ ctx.nodeObjects[this.sourceNodeIndex].getWorldPosition(),
+ ctx.nodeObjects[this.targetNodeIndex].getWorldPosition()
+ )
+ else
+ points.push(
+ ctx.nodeObjects[this.sourceNodeIndex].getPosition(),
+ ctx.nodeObjects[this.targetNodeIndex].getPosition()
+ )
+ return points
+ }
+
+ createEdge(points: THREE.Vector3[]): void {
+ const geometry = new THREE.BufferGeometry().setFromPoints(points)
+ const color = this.decideColor()
+ const opacity = this.decideOpacity()
+ const material = new THREE.LineBasicMaterial({
+ color: color,
+ alphaTest: 0.05,
+ transparent: true,
+ opacity: opacity,
+ })
+
+ this.THREE_Object = new THREE.Line(geometry, material)
+
+ if (ctx.isDirectionEnabled && opacity !== 0) this.createArrow(points, color)
+ }
+
+ decideColor(i = 0, forExport = false): string {
+ let color = ctx.edgeDefaultColor
+
+ if (!forExport && this.isSelected && ctx.selectedEdgeColorFlag)
+ color = SELECTED_DEFAULT_COLOR
+ else if (this.channels.length > 0 && !ctx.edgeFileColorPriority)
+ // a channel missing from the registry would make THREE.Color throw/white
+ color = ctx.channelColors[this.channels[i]] ?? ctx.edgeDefaultColor
+ else if (ctx.edgeFileColorPriority) color = this.importedColors[i]
+
+ return color
+ }
+
+ decideOpacity(i = 0): number {
+ let opacity: number
+ if (ctx.edgeWidthByWeight) opacity = this.weights[i]
+ else
+ opacity = this.interLayer
+ ? ctx.interLayerEdgeOpacity
+ : ctx.intraLayerEdgeOpacity
+ return opacity
+ }
+
+ createArrow(points: THREE.Vector3[], arrowColor: string): void {
+ const THREE_arrowHelper = this.createArrowHelper(points, arrowColor)
+ const THREE_Group = new THREE.Group()
+ THREE_Group.add(this.THREE_Object)
+ THREE_Group.add(THREE_arrowHelper)
+ this.THREE_Object = THREE_Group
+ }
+
+ createArrowHelper(
+ points: THREE.Vector3[],
+ edgeColor: string
+ ): THREE.ArrowHelper {
+ const direction = points[1].clone().sub(points[0])
+ const origin = points[1]
+ const length = 1
+ let headLength = this.interLayer
+ ? ctx.interDirectionArrowSize
+ : ctx.intraDirectionArrowSize
+ headLength = headLength * 6
+ const headWidth = headLength / 4
+
+ return new THREE.ArrowHelper(
+ direction.normalize(),
+ origin,
+ length,
+ edgeColor,
+ headLength,
+ headWidth
+ )
+ }
+
+ // Channels ======
+ createChannels(points: THREE.Vector3[]): void {
+ let THREE_curveGroup = new THREE.Group()
+ let pushForce = 0
+ let pushForceFlag = false
+ let direction = 1
+ const curveFactor = this.interLayer
+ ? ctx.interChannelCurvature
+ : ctx.intraChannelCurvature
+
+ const verticalPushConstant =
+ (points[0].distanceTo(points[1]) * curveFactor) / 400
+ if (this.channels.length % 2 == 0) pushForce = 1 // skip straight line
+
+ for (let i = 0; i < this.channels.length; i++) {
+ direction = -1 * direction // flipping direction
+
+ if (pushForceFlag) pushForce = pushForce + 1
+ pushForceFlag = !pushForceFlag // flipping flag to increase pushForce next round
+
+ const verticalPush = direction * (verticalPushConstant * pushForce)
+ const color = this.decideColor(i)
+ const opacity = this.decideOpacity(i)
+
+ THREE_curveGroup = this.createCurve(
+ THREE_curveGroup,
+ points[0],
+ points[1],
+ verticalPush,
+ color,
+ this.channels[i],
+ opacity
+ )
+ }
+
+ this.THREE_Object = THREE_curveGroup
+ }
+
+ createCurve(
+ curveGroup: THREE.Group,
+ p1: THREE.Vector3,
+ p2: THREE.Vector3,
+ verticalPush: number,
+ color: string,
+ channelName: string,
+ opacity: number
+ ): THREE.Group {
+ const p3 = p1.clone()
+ const p4 = p2.clone()
+ const points = 50
+
+ p3.addScalar(verticalPush)
+ p4.addScalar(verticalPush)
+
+ let curve: THREE.CubicBezierCurve3
+ if (!this.interLayer)
+ curve = new THREE.CubicBezierCurve3(
+ p1,
+ this.transformMiddlePointOnLayer(p3),
+ this.transformMiddlePointOnLayer(p4),
+ p2
+ )
+ else curve = new THREE.CubicBezierCurve3(p1, p3, p4, p2)
+
+ const curvePoints = curve.getPoints(points)
+ const curveGeometry = new THREE.BufferGeometry().setFromPoints(curvePoints)
+ const curveMaterial = new THREE.LineBasicMaterial({
+ color: color,
+ alphaTest: 0.05,
+ transparent: true,
+ opacity: opacity,
+ })
+ const curveLine = new THREE.Line(curveGeometry, curveMaterial)
+ curveLine.userData.tag = channelName
+ curveLine.visible = ctx.channelVisibility[channelName]
+ curveGroup.add(curveLine)
+
+ if (ctx.isDirectionEnabled && opacity !== 0)
+ curveGroup = this.createCurvedArrow(
+ curveGroup,
+ curvePoints,
+ points,
+ color,
+ channelName
+ )
+
+ return curveGroup
+ }
+
+ // Helps place arrow lines onto the layer instead of through it.
+ transformMiddlePointOnLayer(point: THREE.Vector3): THREE.Vector3 {
+ point.x = 0
+ return point
+ }
+
+ createCurvedArrow(
+ curveGroup: THREE.Group,
+ curvePoints: THREE.Vector3[],
+ points: number,
+ color: string,
+ channelName: string
+ ): THREE.Group {
+ const arrowHelper = this.createArrowHelper(
+ [curvePoints[points - 4], curvePoints[points - 1]],
+ color
+ )
+ arrowHelper.userData.tag = channelName
+ arrowHelper.visible = ctx.channelVisibility[channelName]
+ curveGroup.add(arrowHelper)
+ return curveGroup
+ }
+
+ // On animate ======
+ redrawEdge(): void {
+ if (this.interLayer) ctx.scene!.remove(this.THREE_Object)
+ else ctx.layers[this.sourceLayerIndex].removeEdge(this.THREE_Object)
+
+ if (!this.interLayer || (this.interLayer && this.areLayersNotHidden()))
+ this.drawEdge()
+ }
+
+ areLayersNotHidden(): boolean {
+ return (
+ ctx.layers[this.sourceLayerIndex].isVisible &&
+ ctx.layers[this.targetLayerIndex].isVisible
+ )
+ }
+
+ select(): void {
+ this.isSelected = true
+ }
+
+ deselect(): void {
+ this.isSelected = false
+ }
+}
diff --git a/frontend/src/three/Layer.ts b/frontend/src/three/Layer.ts
new file mode 100644
index 0000000..f99785f
--- /dev/null
+++ b/frontend/src/three/Layer.ts
@@ -0,0 +1,261 @@
+import * as THREE from 'three'
+import {
+ PLANE_WIDTHSEGMENTS,
+ PLANE_HEIGHTSEGMENTS,
+ LAYER_DEFAULT_COLOR,
+} from './constants'
+import { ctx } from './runtime'
+import type { Axis } from './Node'
+
+export interface LayerOptions {
+ id?: number
+ name?: string
+ position_x?: number
+ position_y?: number
+ position_z?: number
+ last_layer_scale?: number
+ rotation_x?: number
+ rotation_y?: number
+ rotation_z?: number
+ floor_current_color?: string
+ geometry_parameters_width?: number
+}
+
+export class Layer {
+ plane!: THREE.Mesh
+ sphere!: THREE.Mesh
+
+ id: number
+ name: string
+ last_layer_scale: number
+ geometry_parameters_width: number
+ importedColor: string
+ color: string
+
+ showNodeLabels = false
+ isSelected = false
+ isVisible = true
+ coordSystem: THREE.Line[] = []
+
+ constructor({
+ id = 1,
+ name = 'Layer1',
+ position_x = 0,
+ position_y = 0,
+ position_z = 0,
+ last_layer_scale = 1,
+ rotation_x = 0,
+ rotation_y = 0,
+ rotation_z = 0,
+ floor_current_color = LAYER_DEFAULT_COLOR,
+ geometry_parameters_width = 2 * ctx.yBoundMax,
+ }: LayerOptions) {
+ this.id = id
+ this.name = name
+ this.last_layer_scale = last_layer_scale
+ this.geometry_parameters_width = geometry_parameters_width
+ this.importedColor = floor_current_color
+ this.color = floor_current_color
+
+ this.createPlane(geometry_parameters_width, floor_current_color)
+ this.appendCoordSystem()
+ this.addLabelSphere(geometry_parameters_width, last_layer_scale)
+ this.initTranslate(position_x, position_y, position_z)
+ this.initRotate(rotation_x, rotation_y, rotation_z)
+ this.initScale(last_layer_scale)
+ }
+
+ // inits
+ createPlane(width: number, color: string): void {
+ const planeGeometry = new THREE.PlaneGeometry(
+ width,
+ width,
+ PLANE_WIDTHSEGMENTS,
+ PLANE_HEIGHTSEGMENTS
+ )
+ planeGeometry.rotateY(THREE.MathUtils.degToRad(90))
+ const planeMaterial = new THREE.MeshBasicMaterial({
+ color: color,
+ alphaTest: 0.05,
+ wireframe: false,
+ transparent: true,
+ opacity: 0.6,
+ side: THREE.DoubleSide,
+ })
+ this.plane = new THREE.Mesh(planeGeometry, planeMaterial)
+ }
+
+ appendCoordSystem(): void {
+ this.coordSystem[0] = this.appendLine(150, 0, 0, '#FB3D2A') // red
+ this.coordSystem[1] = this.appendLine(0, 150, 0, '#46FB2A') // green
+ this.coordSystem[2] = this.appendLine(0, 0, 150, '#2AC2FB') // blue
+ this.toggleCoords(false)
+ }
+
+ appendLine(x: number, y: number, z: number, color: string): THREE.Line {
+ const points = [
+ this.plane.position,
+ new THREE.Vector3(x, y, z),
+ this.plane.position,
+ new THREE.Vector3(-x, -y, -z),
+ ]
+ const geometry = new THREE.BufferGeometry().setFromPoints(points)
+ const material = new THREE.LineBasicMaterial({ color: color })
+ const line = new THREE.Line(geometry, material)
+ this.plane.add(line)
+ return line
+ }
+
+ addLabelSphere(width: number, scale: number): void {
+ // label will be attached here
+ const threeSphere = this.createSphere()
+ this.plane.add(threeSphere)
+ threeSphere.translateY(-width / 2)
+ threeSphere.translateZ(width / 2)
+ threeSphere.position.y = threeSphere.position.y * scale
+ threeSphere.position.z = threeSphere.position.z * scale
+ this.sphere = threeSphere
+ }
+
+ createSphere(): THREE.Mesh {
+ const geometry = new THREE.SphereGeometry()
+ const material = new THREE.MeshBasicMaterial({
+ color: 'white',
+ transparent: true,
+ opacity: 0,
+ })
+ return new THREE.Mesh(geometry, material)
+ }
+
+ initTranslate(x: number, y: number, z: number): void {
+ this.setPosition('x', x)
+ this.setPosition('y', y)
+ this.setPosition('z', z)
+ }
+
+ initRotate(x: number, y: number, z: number): void {
+ this.setRotation('x', x)
+ this.setRotation('y', y)
+ this.setRotation('z', z)
+ }
+
+ initScale(value: number): void {
+ this.plane.geometry.scale(1, Number(value), Number(value))
+ }
+
+ addNode(sphere: THREE.Object3D): void {
+ this.plane.add(sphere)
+ }
+
+ addEdge(line: THREE.Object3D): void {
+ this.plane.add(line)
+ }
+
+ removeEdge(line: THREE.Object3D): void {
+ this.plane.remove(line)
+ }
+
+ // toggle functions
+ toggleSelection(): void {
+ this.isSelected = !this.isSelected
+ }
+
+ toggleVisibility(flag: boolean): void {
+ this.plane.visible = flag
+ this.isVisible = flag
+ }
+
+ toggleCoords(layerCoordsSwitch: boolean): void {
+ this.coordSystem[0].visible =
+ this.coordSystem[1].visible =
+ this.coordSystem[2].visible =
+ layerCoordsSwitch
+ }
+
+ toggleWireframe(flag: boolean): void {
+ ;(this.plane.material as THREE.MeshBasicMaterial).wireframe = flag
+ }
+
+ // transformations
+ translateX(x: number): void {
+ this.plane.translateX(x)
+ }
+ translateY(y: number): void {
+ this.plane.translateY(y)
+ }
+ translateZ(z: number): void {
+ this.plane.translateZ(z)
+ }
+
+ rotateX(x: number): void {
+ this.plane.rotateX(x)
+ }
+ rotateY(y: number): void {
+ this.plane.rotateY(y)
+ }
+ rotateZ(z: number): void {
+ this.plane.rotateZ(z)
+ }
+
+ // setters and getters
+ getName(): string {
+ return this.name
+ }
+
+ setPosition(axis: Axis, value: number): void {
+ this.plane.position[axis] = value
+ }
+
+ getPosition(axis: Axis): number {
+ return this.plane.position[axis]
+ }
+
+ setRotation(axis: Axis, value: number): void {
+ this.plane.rotation[axis] = value
+ }
+
+ getRotation(axis: Axis): number {
+ return this.plane.rotation[axis]
+ }
+
+ getWidth(): number {
+ return (this.plane.geometry as THREE.PlaneGeometry).parameters.width
+ }
+
+ setScale(value: number | string): void {
+ const newScaleValue = parseFloat(String(value)) / this.getScale()
+ this.plane.geometry.scale(1, newScaleValue, newScaleValue)
+ this.last_layer_scale = parseFloat(String(value))
+ }
+
+ getScale(): number {
+ return this.last_layer_scale
+ }
+
+ setColor(color: string): void {
+ this.color = color
+ ;(this.plane.material as THREE.MeshBasicMaterial).color = new THREE.Color(
+ color
+ )
+ }
+
+ getColor(): string {
+ let color = LAYER_DEFAULT_COLOR
+ if (this.isSelected) {
+ if (ctx.layerColorPrioritySource === 'default') color = this.importedColor
+ else if (ctx.layerColorPrioritySource === 'picker')
+ // ponytail: DOM picker read; the input only exists in the browser UI
+ // (Phase 13). Guarded so headless/tests fall back to the stored color.
+ color =
+ (typeof document !== 'undefined' &&
+ (document.getElementById('floor_color') as HTMLInputElement | null)
+ ?.value) ||
+ this.color
+ } else color = this.color
+ return color
+ }
+
+ setOpacity(value: number): void {
+ ;(this.plane.material as THREE.MeshBasicMaterial).opacity = value
+ }
+}
diff --git a/frontend/src/three/Node.ts b/frontend/src/three/Node.ts
new file mode 100644
index 0000000..72bc7ba
--- /dev/null
+++ b/frontend/src/three/Node.ts
@@ -0,0 +1,203 @@
+import * as THREE from 'three'
+import {
+ SPHERE_RADIUS,
+ SPHERE_WIDTHSEGMENTS,
+ SPHERE_HEIGHTSEGMENTS,
+ BOX_SIZE,
+ DIAMOND_RADIUS,
+ CONE_RADIUS,
+ CONE_HEIGHT,
+} from './constants'
+import { ctx } from './runtime'
+
+export type Axis = 'x' | 'y' | 'z'
+export type NodeGeometryType = 'sphere' | 'box' | 'diamond' | 'cone'
+
+export interface NodeOptions {
+ id?: number
+ name?: string
+ layer?: string
+ nodeLayerName?: string
+ position_x?: number
+ position_y?: number
+ position_z?: number
+ scale?: number
+ color?: string
+ url?: string
+ descr?: string
+}
+
+export class Node {
+ sphere!: THREE.Mesh
+
+ id: number
+ name: string
+ layer: string
+ nodeLayerName: string
+ color: string
+ importedColor: string
+ clusterColor: string
+ url: string
+ descr: string
+
+ isSelected = false
+ showLabel = false
+ cluster = ''
+
+ constructor({
+ id = 0,
+ name = '',
+ layer = '',
+ nodeLayerName = '',
+ position_x = 0,
+ position_y = 0,
+ position_z = 0,
+ scale = 1,
+ color = '#FFFFFF',
+ url = '',
+ descr = '',
+ }: NodeOptions) {
+ this.id = id
+ this.name = name
+ this.layer = layer
+ this.nodeLayerName = nodeLayerName
+ this.color = color
+ this.importedColor = color
+ this.clusterColor = color
+ this.url = url
+ this.descr = descr
+
+ this.createSphere(color)
+ this.initTranslate(position_x, position_y, position_z)
+ this.setScale(scale)
+ }
+
+ createSphere(nodeColor: string): void {
+ const geometry = new THREE.SphereGeometry(
+ SPHERE_RADIUS,
+ SPHERE_WIDTHSEGMENTS,
+ SPHERE_HEIGHTSEGMENTS
+ )
+ const material = new THREE.MeshStandardMaterial({
+ color: nodeColor,
+ transparent: true,
+ })
+ this.sphere = new THREE.Mesh(geometry, material)
+ }
+
+ initTranslate(x: number, y: number, z: number): void {
+ this.setPosition('x', x)
+ this.setPosition('y', y)
+ this.setPosition('z', z)
+ }
+
+ // transformations
+ translateX(x: number): void {
+ this.sphere.translateX(x)
+ }
+ translateY(y: number): void {
+ this.sphere.translateY(y)
+ }
+ translateZ(z: number): void {
+ this.sphere.translateZ(z)
+ }
+
+ // setters and getters
+ getName(): string {
+ return this.name
+ }
+
+ getLayer(): string {
+ return this.layer
+ }
+
+ getNodeLayerName(): string {
+ return this.nodeLayerName
+ }
+
+ getPosition(): THREE.Vector3
+ getPosition(axis: Axis): number
+ getPosition(axis?: Axis): THREE.Vector3 | number {
+ return axis === undefined
+ ? this.sphere.position
+ : this.sphere.position[axis]
+ }
+
+ getWorldPosition(): THREE.Vector3
+ getWorldPosition(axis: Axis): number
+ getWorldPosition(axis?: Axis): THREE.Vector3 | number {
+ const world = this.sphere.getWorldPosition(new THREE.Vector3())
+ return axis === undefined ? world : world[axis]
+ }
+
+ getOpacity(): number {
+ return (this.sphere.material as THREE.MeshStandardMaterial).opacity
+ }
+
+ getColor(): string {
+ let color = this.color
+ if (ctx.nodeColorPrioritySource === 'default') color = this.importedColor
+ else if (ctx.nodeColorPrioritySource === 'cluster')
+ color = this.clusterColor
+ return color
+ }
+
+ getScale(): number {
+ return this.sphere.scale.x
+ }
+
+ getCluster(): string {
+ return this.cluster
+ }
+
+ setPosition(axis: Axis, value: number): void {
+ this.sphere.position[axis] = value
+ }
+
+ setOpacity(value: number): void {
+ ;(this.sphere.material as THREE.MeshStandardMaterial).opacity = value
+ }
+
+ setGeometry(type: NodeGeometryType): void {
+ let geometry: THREE.BufferGeometry
+ switch (type) {
+ case 'sphere':
+ geometry = new THREE.SphereGeometry(
+ SPHERE_RADIUS,
+ SPHERE_WIDTHSEGMENTS,
+ SPHERE_HEIGHTSEGMENTS
+ )
+ break
+ case 'box':
+ geometry = new THREE.BoxGeometry(BOX_SIZE, BOX_SIZE, BOX_SIZE)
+ break
+ case 'diamond':
+ geometry = new THREE.OctahedronGeometry(DIAMOND_RADIUS, 0)
+ break
+ case 'cone':
+ geometry = new THREE.ConeGeometry(CONE_RADIUS, CONE_HEIGHT, CONE_RADIUS)
+ break
+ default:
+ console.error(`Unknown geometry type: ${type as string}`)
+ return
+ }
+ this.sphere.geometry.dispose()
+ this.sphere.geometry = geometry
+ }
+
+ setColor(hexColor: string, importMode = false, clusterMode = false): void {
+ ;(this.sphere.material as THREE.MeshStandardMaterial).color =
+ new THREE.Color(hexColor)
+ this.color = hexColor
+ if (importMode) this.importedColor = hexColor
+ if (clusterMode) this.clusterColor = hexColor
+ }
+
+ setScale(value: number): void {
+ this.sphere.scale.x = this.sphere.scale.y = this.sphere.scale.z = value
+ }
+
+ setCluster(clusterName: string): void {
+ this.cluster = clusterName
+ }
+}
diff --git a/frontend/src/three/Scene.ts b/frontend/src/three/Scene.ts
new file mode 100644
index 0000000..ceea4b4
--- /dev/null
+++ b/frontend/src/three/Scene.ts
@@ -0,0 +1,229 @@
+import * as THREE from 'three'
+import { ctx } from './runtime'
+import type { Axis } from './Node'
+
+export class Scene {
+ THREE_Object!: THREE.Scene
+ pan: THREE.Mesh | null = null // scene translations applied here
+ sphere: THREE.Mesh | null = null // scene rotations applied here
+ coordSystem: THREE.Line[] = []
+ defaultColor = '#000000'
+ autoRotate = false
+ dragging = false
+ leftClickPressed = false // drag translating scene
+ middleClickPressed = false // drag rotating scene
+ axisPressed = '' // z, x, c
+
+ constructor() {
+ this.reset()
+ }
+
+ reset(): void {
+ this.THREE_Object = new THREE.Scene()
+ this.pan = null
+ this.sphere = null
+ this.coordSystem = ['', '', ''] as unknown as THREE.Line[]
+ this.defaultColor = '#000000'
+ this.autoRotate = false
+ this.dragging = false
+ this.leftClickPressed = false
+ this.middleClickPressed = false
+ this.axisPressed = ''
+
+ this.addLights()
+ this.addPan()
+ this.addSphere()
+ this.appendCoordSystem()
+ }
+
+ addLights(): void {
+ this.add(this.createPointLight(1))
+ this.add(this.createPointLight(-1))
+ this.add(new THREE.AmbientLight(0xffffff))
+ }
+
+ createPointLight(orientation: number): THREE.PointLight {
+ const sphereGeom = new THREE.SphereGeometry()
+ const lightObject = new THREE.PointLight(0xffffff, 1, 2 * ctx.yBoundMax)
+ lightObject.add(
+ new THREE.Mesh(
+ sphereGeom,
+ new THREE.MeshBasicMaterial({ color: 0xffffff })
+ )
+ )
+ lightObject.position.set(0, orientation * ctx.yBoundMax, 0)
+ lightObject.rotateX(THREE.MathUtils.degToRad(90))
+ return lightObject
+ }
+
+ addPan(): void {
+ const threePan = this.createSphere()
+ this.add(threePan)
+ this.pan = threePan
+ }
+
+ createSphere(): THREE.Mesh {
+ const geometry = new THREE.SphereGeometry()
+ const material = new THREE.MeshBasicMaterial({
+ color: 'white',
+ transparent: true,
+ opacity: 0,
+ })
+ return new THREE.Mesh(geometry, material)
+ }
+
+ add(threeObject: THREE.Object3D): void {
+ this.THREE_Object.add(threeObject)
+ }
+
+ remove(threeObject: THREE.Object3D): void {
+ this.THREE_Object.remove(threeObject)
+ }
+
+ addSphere(): void {
+ const threeSphere = this.createSphere()
+ this.pan!.add(threeSphere)
+ this.sphere = threeSphere
+ }
+
+ appendCoordSystem(): void {
+ this.coordSystem[0] = this.appendLine(800, 0, 0, '#FB3D2A') // red
+ this.coordSystem[1] = this.appendLine(0, 800, 0, '#46FB2A') // green
+ this.coordSystem[2] = this.appendLine(0, 0, 800, '#2AC2FB') // blue
+ }
+
+ appendLine(x: number, y: number, z: number, color: string): THREE.Line {
+ const points = [
+ this.getPointPosition(),
+ new THREE.Vector3(x, y, z),
+ this.getPointPosition(),
+ new THREE.Vector3(-x, -y, -z),
+ ]
+ const geometry = new THREE.BufferGeometry().setFromPoints(points)
+ const material = new THREE.LineBasicMaterial({ color: color })
+ const line = new THREE.Line(geometry, material)
+ this.sphere!.add(line)
+ return line
+ }
+
+ toggleCoords(sceneCoordsSwitch: boolean): void {
+ this.coordSystem[0].visible =
+ this.coordSystem[1].visible =
+ this.coordSystem[2].visible =
+ sceneCoordsSwitch
+ }
+
+ exists(): boolean {
+ return this.pan !== null
+ }
+
+ tiltDefault(): void {
+ this.setRotation('x', THREE.MathUtils.degToRad(15))
+ this.setRotation('y', THREE.MathUtils.degToRad(15))
+ this.setRotation('z', THREE.MathUtils.degToRad(5))
+ }
+
+ addLayer(threePlane: THREE.Object3D): void {
+ this.sphere!.add(threePlane)
+ }
+
+ translateX(x: number): void {
+ this.pan!.translateX(x)
+ }
+
+ translateY(y: number): void {
+ this.pan!.translateY(y)
+ }
+
+ translatePanWithArrow(code: number): void {
+ if (code == 37) this.translateX(-25) // left
+ if (code == 38) this.translateY(25) // up
+ if (code == 39) this.translateX(25) // right
+ if (code == 40) this.translateY(-25) // down
+ }
+
+ translatePanWithMouse(x: number, y: number): void {
+ this.translateX(x - ctx.mousePreviousX)
+ this.translateY(ctx.mousePreviousY - y)
+ }
+
+ orbitSphereWithMouse(x: number, y: number): void {
+ const deltaMove = { x: x - ctx.mousePreviousX, y: y - ctx.mousePreviousY }
+ const deltaRotationQuaternion = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(deltaMove.y),
+ THREE.MathUtils.degToRad(deltaMove.x),
+ 0,
+ 'XYZ'
+ )
+ )
+ this.setQuaternion(deltaRotationQuaternion)
+ }
+
+ setQuaternion(deltaRotationQuaternion: THREE.Quaternion): void {
+ this.sphere!.quaternion.multiplyQuaternions(
+ deltaRotationQuaternion,
+ this.getQuaternion()
+ )
+ }
+
+ getQuaternion(): THREE.Quaternion {
+ return this.sphere!.quaternion
+ }
+
+ // ponytail: v2 Scene.rotate() (continuous-rotation button: setInterval + DOM
+ // slider read + Shiny sync) is UI glue, not scene data model. It lands in
+ // Phase 11 with canvas_controls, wired to the real slider + EventBus. The
+ // rotateX/Y/Z primitives it drove are kept here.
+
+ rotateX(x: number): void {
+ this.sphere!.rotateX(x)
+ }
+
+ rotateY(y: number): void {
+ this.sphere!.rotateY(y)
+ }
+
+ rotateZ(z: number): void {
+ this.sphere!.rotateZ(z)
+ }
+
+ recenter(): void {
+ this.pan!.position.x = this.pan!.position.y = 0
+ }
+
+ zoom(deltaY: number): void {
+ let tempScale = this.getScale()
+ if (deltaY < 0 && tempScale < 2) tempScale = tempScale * 1.1
+ else if (deltaY > 0 && tempScale > 0.2) tempScale = tempScale * 0.9
+ this.setScale(tempScale)
+ }
+
+ getPosition(axis: Axis): number {
+ return this.pan!.position[axis]
+ }
+
+ setPosition(axis: Axis, value: number): void {
+ this.pan!.position[axis] = value
+ }
+
+ getPointPosition(): THREE.Vector3 {
+ return this.sphere!.position
+ }
+
+ getRotation(axis: Axis): number {
+ return this.sphere!.rotation[axis]
+ }
+
+ setRotation(axis: Axis, value: number): void {
+ this.sphere!.rotation[axis] = value
+ }
+
+ getScale(): number {
+ return this.pan!.scale.x
+ }
+
+ setScale(value: number): void {
+ this.pan!.scale.set(value, value, value)
+ }
+}
diff --git a/frontend/src/three/constants.ts b/frontend/src/three/constants.ts
new file mode 100644
index 0000000..d23971b
--- /dev/null
+++ b/frontend/src/three/constants.ts
@@ -0,0 +1,307 @@
+// Static geometry + color constants for the Three.js object model.
+// Ported from v2 www/js/config/global_variables.js (the `const` block).
+// Config-driven / mutable values live in runtime.ts, not here.
+
+export const NODE_LABEL_DEFAULT_SIZE = '12px'
+
+// Node geometry
+export const SPHERE_RADIUS = 8
+export const SPHERE_WIDTHSEGMENTS = 8
+export const SPHERE_HEIGHTSEGMENTS = 8
+export const BOX_SIZE = 12
+export const DIAMOND_RADIUS = 10
+export const CONE_RADIUS = 8
+export const CONE_HEIGHT = 15
+
+// Layer plane geometry
+export const PLANE_WIDTHSEGMENTS = 8
+export const PLANE_HEIGHTSEGMENTS = 8
+
+// Colors
+export const LAYER_DEFAULT_COLOR = '#777777'
+export const SELECTED_LAYER_DEFAULT_COLOR = '#f7f43e'
+export const SELECTED_DEFAULT_COLOR = '#A3FF00'
+
+// Categorical node/layer color palette (280 colors). Verbatim from v2.
+export const COLOR_VECTOR_280: readonly string[] = [
+ '#e41a1c',
+ '#377eb8',
+ '#4daf4a',
+ '#984ea3',
+ '#ff7f00',
+ '#ffff33',
+ '#999999',
+ '#a65628',
+ '#f781bf',
+ '#14a9ad',
+ '#4ca2f9',
+ '#a4e43f',
+ '#d298e2',
+ '#6119d0',
+ '#d2737d',
+ '#c0a43c',
+ '#f2510e',
+ '#651be6',
+ '#79806e',
+ '#61da5e',
+ '#cd2f00',
+ '#9348af',
+ '#01ac53',
+ '#c5a4fb',
+ '#996635',
+ '#b11573',
+ '#4bb473',
+ '#75d89e',
+ '#2f3f94',
+ '#2f7b99',
+ '#da967d',
+ '#34891f',
+ '#b0d87b',
+ '#ca4751',
+ '#7e50a8',
+ '#c4d647',
+ '#e0eeb8',
+ '#11dec1',
+ '#289812',
+ '#566ca0',
+ '#ffdbe1',
+ '#2f1179',
+ '#935b6d',
+ '#916988',
+ '#513d98',
+ '#aead3a',
+ '#9e6d71',
+ '#4b5bdc',
+ '#0cd36d',
+ '#250662',
+ '#cb5bea',
+ '#228916',
+ '#ac3e1b',
+ '#df514a',
+ '#539397',
+ '#880977',
+ '#f697c1',
+ '#ba96ce',
+ '#679c9d',
+ '#c6c42c',
+ '#5d2c52',
+ '#48b41b',
+ '#e1cf3b',
+ '#5be4f0',
+ '#57c4d8',
+ '#a4d17a',
+ '#225b80',
+ '#be608b',
+ '#96b00c',
+ '#088baf',
+ '#f158bf',
+ '#e145ba',
+ '#ee91e3',
+ '#05d371',
+ '#5426e0',
+ '#4834d0',
+ '#802234',
+ '#6749e8',
+ '#0971f0',
+ '#8fb413',
+ '#b2b4f0',
+ '#c3c89d',
+ '#c9a941',
+ '#41d158',
+ '#fb21a3',
+ '#51aed9',
+ '#5bb32d',
+ '#807fb0',
+ '#21538e',
+ '#89d534',
+ '#d36647',
+ '#7fb411',
+ '#0023b8',
+ '#3b8c2a',
+ '#986b53',
+ '#f50422',
+ '#983f7a',
+ '#ea24a3',
+ '#79352c',
+ '#521250',
+ '#c79ed2',
+ '#d6dd92',
+ '#e33e52',
+ '#b2be57',
+ '#fa06ec',
+ '#1bb699',
+ '#6b2e5f',
+ '#64820f',
+ '#1c2710',
+ '#21538e',
+ '#89d534',
+ '#d36647',
+ '#7fb411',
+ '#0023b8',
+ '#3b8c2a',
+ '#986b53',
+ '#f50422',
+ '#983f7a',
+ '#ea24a3',
+ '#79352c',
+ '#521250',
+ '#c79ed2',
+ '#d6dd92',
+ '#e33e52',
+ '#b2be57',
+ '#fa06ec',
+ '#1bb699',
+ '#6b2e5f',
+ '#64820f',
+ '#1c2710',
+ '#9cb64a',
+ '#996c48',
+ '#9ab9b7',
+ '#06e052',
+ '#e3a481',
+ '#0eb621',
+ '#fc458e',
+ '#b2db15',
+ '#aa226d',
+ '#792ed8',
+ '#73872a',
+ '#520d3a',
+ '#cefcb8',
+ '#a5b3d9',
+ '#7d1d85',
+ '#c4fd57',
+ '#f1ae16',
+ '#8fe22a',
+ '#ef6e3c',
+ '#243eeb',
+ '#1dc180',
+ '#dd93fd',
+ '#3f8473',
+ '#e7dbce',
+ '#421f79',
+ '#7a3d93',
+ '#635f6d',
+ '#93f2d7',
+ '#9b5c2a',
+ '#15b9ee',
+ '#0f5997',
+ '#409188',
+ '#911e20',
+ '#1350ce',
+ '#10e5b1',
+ '#fff4d7',
+ '#cb2582',
+ '#ce00be',
+ '#32d5d6',
+ '#172320',
+ '#608572',
+ '#c79bc2',
+ '#00f87c',
+ '#77772a',
+ '#6995ba',
+ '#fc6b57',
+ '#f07815',
+ '#8fd883',
+ '#060e27',
+ '#96e591',
+ '#21d52e',
+ '#d00043',
+ '#b47162',
+ '#1ec227',
+ '#4f0f6f',
+ '#1d1d58',
+ '#947002',
+ '#bde052',
+ '#e08c56',
+ '#28fcfd',
+ '#bb09b0',
+ '#36486a',
+ '#d02e29',
+ '#1ae6db',
+ '#3e464c',
+ '#a84a8f',
+ '#911e7e',
+ '#3f16d9',
+ '#0f525f',
+ '#ac7c0a',
+ '#b4c086',
+ '#c9d730',
+ '#30cc49',
+ '#3d6751',
+ '#fb4c03',
+ '#640fc1',
+ '#62c03e',
+ '#d3493a',
+ '#88aa0b',
+ '#406df9',
+ '#615af0',
+ '#4be470',
+ '#2a3434',
+ '#4a543f',
+ '#79bca0',
+ '#a8b8d4',
+ '#00efd4',
+ '#7ad236',
+ '#7260d8',
+ '#1deaa7',
+ '#06f43a',
+ '#823c59',
+ '#e3d94c',
+ '#dc1c06',
+ '#f53b2a',
+ '#b46238',
+ '#2dfff6',
+ '#a82b89',
+ '#1a8011',
+ '#436a9f',
+ '#1a806a',
+ '#4cf09d',
+ '#c188a2',
+ '#67eb4b',
+ '#b308d3',
+ '#fc7e41',
+ '#af3101',
+ '#ff0650',
+ '#71b1f4',
+ '#a2f8a5',
+ '#e23dd0',
+ '#d3486d',
+ '#00f7f9',
+ '#474893',
+ '#3cec35',
+ '#1c65cb',
+ '#5d1d0c',
+ '#2d7d2a',
+ '#ff3420',
+ '#5cdd87',
+ '#a259a4',
+ '#e4ac44',
+ '#1bede6',
+ '#8798a4',
+ '#d7790f',
+ '#b2c24f',
+ '#de73c2',
+ '#d70a9c',
+ '#25b670',
+ '#88e9b8',
+ '#c2b0e2',
+ '#86e98f',
+ '#ae90e2',
+ '#1a806b',
+ '#436a9e',
+ '#0ec0ff',
+ '#f812b3',
+ '#b17fc9',
+ '#8d6c2f',
+ '#d3277a',
+ '#2ca1ae',
+ '#9685eb',
+ '#8a96c6',
+ '#dba2e6',
+ '#76fc1b',
+ '#608fa4',
+ '#20f6ba',
+ '#07d7f6',
+ '#dce77a',
+ '#77ecca',
+]
diff --git a/frontend/src/three/index.ts b/frontend/src/three/index.ts
new file mode 100644
index 0000000..769e47a
--- /dev/null
+++ b/frontend/src/three/index.ts
@@ -0,0 +1,9 @@
+export * from './constants'
+export * from './runtime'
+export { Node } from './Node'
+export type { NodeOptions, NodeGeometryType, Axis } from './Node'
+export { Layer } from './Layer'
+export type { LayerOptions } from './Layer'
+export { Scene } from './Scene'
+export { Edge } from './Edge'
+export type { EdgeOptions } from './Edge'
diff --git a/frontend/src/three/postprocessing.ts b/frontend/src/three/postprocessing.ts
new file mode 100644
index 0000000..3751382
--- /dev/null
+++ b/frontend/src/three/postprocessing.ts
@@ -0,0 +1,75 @@
+// Optional post-processing: a subtle bloom pass that makes node/edge colors
+// glow against dark backgrounds. Deliberately conservative:
+// - only active on dark backgrounds (bloom on white washes the scene out)
+// - user-toggleable (Scene Actions panel), default off on mobile
+// - falls back to the plain renderer path when inactive, so lower-end
+// devices pay zero extra GPU cost with the toggle off.
+import * as THREE from 'three'
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'
+import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'
+import { ctx } from './runtime'
+
+let composer: EffectComposer | null = null
+let renderPass: RenderPass | null = null
+let bloomPass: UnrealBloomPass | null = null
+
+const isMobile =
+ typeof navigator !== 'undefined' &&
+ /iPhone|iPad|Android/i.test(navigator.userAgent)
+
+let userEnabled = !isMobile
+let backgroundDark = true // main.ts starts with a black clear color
+
+export function setBloomEnabled(on: boolean): void {
+ userEnabled = on
+}
+
+export function isBloomEnabled(): boolean {
+ return userEnabled
+}
+
+// Called with every clear-color change (theme switch).
+export function onBackgroundColor(hexColor: string): void {
+ const c = new THREE.Color(hexColor)
+ // Rec. 601 luma — cheap and good enough to split light/dark themes.
+ backgroundDark = 0.299 * c.r + 0.587 * c.g + 0.114 * c.b < 0.5
+}
+
+export function bloomActive(): boolean {
+ return userEnabled && backgroundDark && composer !== null
+}
+
+function ensureComposer(): void {
+ if (composer || !ctx.renderer) return
+ const size = ctx.renderer.getSize(new THREE.Vector2())
+ composer = new EffectComposer(ctx.renderer)
+ renderPass = new RenderPass(new THREE.Scene(), new THREE.Camera())
+ composer.addPass(renderPass)
+ // Subtle accent glow: high threshold so only bright saturated colors
+ // (nodes, colored edges) bloom; labels are DOM overlays and unaffected.
+ bloomPass = new UnrealBloomPass(size, 0.35, 0.3, 0.8)
+ composer.addPass(bloomPass)
+}
+
+export function resizePostprocessing(width: number, height: number): void {
+ composer?.setSize(width, height)
+ bloomPass?.resolution.set(width, height)
+}
+
+// Render one frame: through the composer when bloom is active, otherwise
+// the plain renderer (zero-cost fallback).
+export function renderFrame(): void {
+ if (!ctx.renderer || !ctx.scene || !ctx.camera) return
+ if (userEnabled && backgroundDark) {
+ ensureComposer()
+ if (composer && renderPass) {
+ // Scene object is replaced on every network load; re-point per frame.
+ renderPass.scene = ctx.scene.THREE_Object
+ renderPass.camera = ctx.camera
+ composer.render()
+ return
+ }
+ }
+ ctx.renderer.render(ctx.scene.THREE_Object, ctx.camera)
+}
diff --git a/frontend/src/three/runtime.ts b/frontend/src/three/runtime.ts
new file mode 100644
index 0000000..3561cf4
--- /dev/null
+++ b/frontend/src/three/runtime.ts
@@ -0,0 +1,258 @@
+// Shared mutable runtime state for the Three.js object model.
+// Replaces the ambient globals from v2 www/js/config/static_variables.js
+// (registries, screen bounds, edge-render tunables, color-priority flags).
+//
+// The four classes read from this singleton instead of window globals, which
+// keeps them unit-testable: a test populates `ctx` before constructing objects.
+// Later phases (actions/commands/UI) mutate these fields in response to events.
+//
+// v2 declared nodeGroups/layerGroups as `new Map()` but only ever used bracket
+// notation on them (`layerGroups[name] = i`), i.e. plain dictionaries — typed
+// here as records accordingly.
+
+import type { Scene } from './Scene'
+import type * as THREE from 'three'
+import type { Layer } from './Layer'
+import type { Node } from './Node'
+import type { Edge } from './Edge'
+
+export type ColorPrioritySource = 'default' | 'picker' | 'cluster'
+
+export interface RuntimeContext {
+ // Renderer + camera (Phase 12; persist across network reloads, so NOT reset
+ // by resetContext). null until main.ts sets up the screen.
+ renderer: THREE.WebGLRenderer | null
+ camera: THREE.OrthographicCamera | null
+ fps: number
+
+ // Screen bounds (v2 screen.js; yBoundMax = window.innerHeight / 2)
+ xBoundMin: number
+ xBoundMax: number
+ yBoundMin: number
+ yBoundMax: number
+ zBoundMin: number
+ zBoundMax: number
+ mousePreviousX: number
+ mousePreviousY: number
+
+ // Registries — populated when a network loads
+ scene: Scene | null
+ layers: Layer[]
+ nodeObjects: Node[]
+ edgeObjects: Edge[]
+ nodeLayerNames: string[]
+ nodeGroups: Record // nodeLayerName -> layerName
+ layerGroups: Record // layerName -> layer index
+
+ // Channels
+ channelColors: Record
+ channelVisibility: Record
+
+ // Edge render config (config-driven; defaults from v2 static_variables.js)
+ edgeDefaultColor: string
+ selectedEdgeColorFlag: boolean
+ edgeFileColorPriority: boolean
+ isDirectionEnabled: boolean
+ edgeWidthByWeight: boolean
+ interLayerEdgeOpacity: number
+ intraLayerEdgeOpacity: number
+ interDirectionArrowSize: number
+ intraDirectionArrowSize: number
+ interChannelCurvature: number
+ intraChannelCurvature: number
+
+ // Color-priority sources
+ layerColorPrioritySource: ColorPrioritySource
+ nodeColorPrioritySource: ColorPrioritySource
+
+ // Node hover/selection/label state (v2 node.js globals)
+ lastHoveredNodeIndex: number | null
+ selectedNodeColorFlag: boolean
+ showAllNodeLabelsFlag: boolean
+ showSelectedNodeLabelsFlag: boolean
+ renderNodeLabelsFlag: boolean
+ showAllLayerLabelsFlag: boolean
+ showSelectedLayerLabelsFlag: boolean
+
+ // Inter-layer edge render machinery (v2 edge.js globals)
+ renderInterLayerEdgesFlag: boolean
+ waitEdgeRenderFlag: boolean
+ interEdgesRemoved: boolean
+ interLayerEdgesRenderPauseFlag: boolean
+
+ // Layer hover/label state (v2 layer.js globals)
+ lastHoveredLayerIndex: number | null
+ hoveredLayerPaintedFlag: boolean
+ renderLayerLabelsFlag: boolean
+
+ // Universal label color (v2 globalLabelColor; read by labels.ts)
+ labelColor: string
+}
+
+const winW = typeof window !== 'undefined' ? window.innerWidth : 800
+const winH = typeof window !== 'undefined' ? window.innerHeight : 800
+
+export const ctx: RuntimeContext = {
+ renderer: null,
+ camera: null,
+ fps: 60,
+
+ xBoundMin: -winW / 2,
+ xBoundMax: winW / 2,
+ yBoundMin: -winH / 2,
+ yBoundMax: winH / 2,
+ zBoundMin: -winH / 2.5,
+ zBoundMax: winH / 2.5,
+ mousePreviousX: 0,
+ mousePreviousY: 0,
+
+ scene: null,
+ layers: [],
+ nodeObjects: [],
+ edgeObjects: [],
+ nodeLayerNames: [],
+ nodeGroups: {},
+ layerGroups: {},
+
+ channelColors: {},
+ channelVisibility: {},
+
+ edgeDefaultColor: '#000000',
+ selectedEdgeColorFlag: true,
+ edgeFileColorPriority: false,
+ isDirectionEnabled: false,
+ edgeWidthByWeight: true,
+ interLayerEdgeOpacity: 0.4,
+ intraLayerEdgeOpacity: 1,
+ interDirectionArrowSize: 5,
+ intraDirectionArrowSize: 5,
+ interChannelCurvature: 5,
+ intraChannelCurvature: 15,
+
+ layerColorPrioritySource: 'default',
+ nodeColorPrioritySource: 'default',
+
+ lastHoveredNodeIndex: null,
+ selectedNodeColorFlag: true,
+ showAllNodeLabelsFlag: false,
+ showSelectedNodeLabelsFlag: true,
+ renderNodeLabelsFlag: false,
+ showAllLayerLabelsFlag: true,
+ showSelectedLayerLabelsFlag: false,
+
+ renderInterLayerEdgesFlag: false,
+ waitEdgeRenderFlag: true,
+ interEdgesRemoved: false,
+ interLayerEdgesRenderPauseFlag: false,
+
+ lastHoveredLayerIndex: null,
+ hoveredLayerPaintedFlag: false,
+ renderLayerLabelsFlag: false,
+
+ labelColor: '#ffffff',
+}
+
+// Reset registries + tunables to defaults (used by tests and network reload).
+export function resetContext(): void {
+ ctx.mousePreviousX = 0
+ ctx.mousePreviousY = 0
+ ctx.scene = null
+ ctx.layers = []
+ ctx.nodeObjects = []
+ ctx.edgeObjects = []
+ ctx.nodeLayerNames = []
+ ctx.nodeGroups = {}
+ ctx.layerGroups = {}
+ ctx.channelColors = {}
+ ctx.channelVisibility = {}
+ ctx.edgeDefaultColor = '#000000'
+ ctx.selectedEdgeColorFlag = true
+ ctx.edgeFileColorPriority = false
+ ctx.isDirectionEnabled = false
+ ctx.edgeWidthByWeight = true
+ ctx.interLayerEdgeOpacity = 0.4
+ ctx.intraLayerEdgeOpacity = 1
+ ctx.interDirectionArrowSize = 5
+ ctx.intraDirectionArrowSize = 5
+ ctx.interChannelCurvature = 5
+ ctx.intraChannelCurvature = 15
+ ctx.layerColorPrioritySource = 'default'
+ ctx.nodeColorPrioritySource = 'default'
+ ctx.lastHoveredNodeIndex = null
+ ctx.selectedNodeColorFlag = true
+ ctx.showAllNodeLabelsFlag = false
+ ctx.showSelectedNodeLabelsFlag = true
+ ctx.renderNodeLabelsFlag = false
+ ctx.showAllLayerLabelsFlag = true
+ ctx.showSelectedLayerLabelsFlag = false
+ ctx.renderInterLayerEdgesFlag = false
+ ctx.waitEdgeRenderFlag = true
+ ctx.interEdgesRemoved = false
+ ctx.interLayerEdgesRenderPauseFlag = false
+ ctx.lastHoveredLayerIndex = null
+ ctx.hoveredLayerPaintedFlag = false
+ ctx.renderLayerLabelsFlag = false
+ ctx.labelColor = '#ffffff'
+}
+
+// Registry snapshot — the mutable scene-graph state a network load replaces.
+// LoadNetworkCommand snapshots before/after so a load is one undo step.
+export interface RegistrySnapshot {
+ scene: Scene | null
+ layers: Layer[]
+ nodeObjects: Node[]
+ edgeObjects: Edge[]
+ nodeLayerNames: string[]
+ nodeGroups: Record
+ layerGroups: Record
+ channelColors: Record
+ channelVisibility: Record
+}
+
+export function snapshotRegistries(): RegistrySnapshot {
+ return {
+ scene: ctx.scene,
+ layers: ctx.layers,
+ nodeObjects: ctx.nodeObjects,
+ edgeObjects: ctx.edgeObjects,
+ nodeLayerNames: ctx.nodeLayerNames,
+ nodeGroups: ctx.nodeGroups,
+ layerGroups: ctx.layerGroups,
+ channelColors: ctx.channelColors,
+ channelVisibility: ctx.channelVisibility,
+ }
+}
+
+// Free GPU resources of a snapshot whose scene graph became unreachable
+// (e.g. a redo entry discarded by a new command). Never call on a snapshot
+// whose scene is still live (ctx.scene) or still reachable from undo history.
+export function disposeSnapshot(snapshot: RegistrySnapshot): void {
+ const scene = snapshot.scene
+ if (!scene || scene === ctx.scene) return
+ scene.THREE_Object.traverse((obj) => {
+ const mesh = obj as Partial & THREE.Object3D
+ if (mesh.geometry) mesh.geometry.dispose()
+ const materials = Array.isArray(mesh.material)
+ ? mesh.material
+ : mesh.material
+ ? [mesh.material]
+ : []
+ for (const mat of materials) {
+ const tex = (mat as THREE.Material & { map?: THREE.Texture }).map
+ tex?.dispose()
+ mat.dispose()
+ }
+ })
+}
+
+export function restoreRegistries(s: RegistrySnapshot): void {
+ ctx.scene = s.scene
+ ctx.layers = s.layers
+ ctx.nodeObjects = s.nodeObjects
+ ctx.edgeObjects = s.edgeObjects
+ ctx.nodeLayerNames = s.nodeLayerNames
+ ctx.nodeGroups = s.nodeGroups
+ ctx.layerGroups = s.layerGroups
+ ctx.channelColors = s.channelColors
+ ctx.channelVisibility = s.channelVisibility
+}
diff --git a/frontend/src/three/three.test.ts b/frontend/src/three/three.test.ts
new file mode 100644
index 0000000..f127639
--- /dev/null
+++ b/frontend/src/three/three.test.ts
@@ -0,0 +1,175 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import * as THREE from 'three'
+import { Node, Layer, Scene, Edge, ctx, resetContext } from './index'
+
+beforeEach(() => {
+ resetContext()
+})
+
+describe('Node', () => {
+ it('constructs a sphere mesh at the given position with defaults', () => {
+ const n = new Node({
+ name: 'A',
+ position_x: 1,
+ position_y: 2,
+ position_z: 3,
+ })
+ expect(n.sphere).toBeInstanceOf(THREE.Mesh)
+ expect(n.getPosition('x')).toBe(1)
+ expect(n.getPosition()).toBeInstanceOf(THREE.Vector3)
+ expect(n.color).toBe('#FFFFFF')
+ })
+
+ it('getColor follows the runtime priority source', () => {
+ const n = new Node({ color: '#111111' })
+ n.setColor('#222222', true /* import */, false)
+ n.setColor('#333333', false, true /* cluster */)
+ ctx.nodeColorPrioritySource = 'default'
+ expect(n.getColor()).toBe('#222222')
+ ctx.nodeColorPrioritySource = 'cluster'
+ expect(n.getColor()).toBe('#333333')
+ })
+
+ it('setScale and getScale round-trip', () => {
+ const n = new Node({})
+ n.setScale(2.5)
+ expect(n.getScale()).toBe(2.5)
+ })
+
+ it('setGeometry swaps the mesh geometry', () => {
+ const n = new Node({})
+ n.setGeometry('box')
+ expect(n.sphere.geometry).toBeInstanceOf(THREE.BoxGeometry)
+ n.setGeometry('diamond')
+ expect(n.sphere.geometry).toBeInstanceOf(THREE.OctahedronGeometry)
+ })
+})
+
+describe('Layer', () => {
+ it('constructs a plane of the requested width', () => {
+ const l = new Layer({ name: 'L1', geometry_parameters_width: 400 })
+ expect(l.plane).toBeInstanceOf(THREE.Mesh)
+ expect(l.getWidth()).toBe(400)
+ expect(l.coordSystem).toHaveLength(3)
+ })
+
+ it('setColor updates the material color', () => {
+ const l = new Layer({ geometry_parameters_width: 100 })
+ l.setColor('#ff0000')
+ expect(l.color).toBe('#ff0000')
+ const mat = l.plane.material as THREE.MeshBasicMaterial
+ expect(mat.color.getHexString()).toBe('ff0000')
+ })
+
+ it('toggleVisibility syncs plane + flag', () => {
+ const l = new Layer({ geometry_parameters_width: 100 })
+ l.toggleVisibility(false)
+ expect(l.isVisible).toBe(false)
+ expect(l.plane.visible).toBe(false)
+ })
+
+ it('getColor prefers importedColor when selected in default mode', () => {
+ const l = new Layer({
+ geometry_parameters_width: 100,
+ floor_current_color: '#abcabc',
+ })
+ l.setColor('#123123')
+ l.isSelected = true
+ ctx.layerColorPrioritySource = 'default'
+ expect(l.getColor()).toBe('#abcabc') // importedColor
+ l.isSelected = false
+ expect(l.getColor()).toBe('#123123') // stored color
+ })
+
+ it('setScale records last_layer_scale', () => {
+ const l = new Layer({ geometry_parameters_width: 100 })
+ l.setScale(3)
+ expect(l.getScale()).toBe(3)
+ })
+})
+
+describe('Scene', () => {
+ it('constructs with pan/sphere and a 3-axis coord system', () => {
+ const s = new Scene()
+ expect(s.exists()).toBe(true)
+ expect(s.THREE_Object).toBeInstanceOf(THREE.Scene)
+ expect(s.coordSystem).toHaveLength(3)
+ })
+
+ it('zoom scales within clamps', () => {
+ const s = new Scene()
+ expect(s.getScale()).toBe(1)
+ s.zoom(-1) // zoom in
+ expect(s.getScale()).toBeCloseTo(1.1)
+ s.setScale(0.2)
+ s.zoom(1) // zoom out below floor -> no change
+ expect(s.getScale()).toBe(0.2)
+ })
+
+ it('rotate primitives rotate the sphere', () => {
+ const s = new Scene()
+ s.setRotation('x', 0)
+ s.rotateX(0.5)
+ expect(s.getRotation('x')).toBeCloseTo(0.5)
+ })
+})
+
+describe('Edge', () => {
+ // Minimal populated runtime: one layer, two nodes, both on layer "L".
+ function seedTwoNodeLayer(): void {
+ const scene = new Scene()
+ const layer = new Layer({ geometry_parameters_width: 100 })
+ const a = new Node({ name: 'A', position_y: 0, position_z: 0 })
+ const b = new Node({ name: 'B', position_y: 10, position_z: 10 })
+ ctx.scene = scene
+ ctx.layers = [layer]
+ ctx.nodeObjects = [a, b]
+ ctx.nodeLayerNames = ['A::L', 'B::L']
+ ctx.nodeGroups = { 'A::L': 'L', 'B::L': 'L' }
+ ctx.layerGroups = { L: 0 }
+ }
+
+ it('draws an intra-layer line and attaches it to the source layer', () => {
+ seedTwoNodeLayer()
+ const e = new Edge({ source: 'A::L', target: 'B::L', weights: [1] })
+ expect(e.THREE_Object).toBeInstanceOf(THREE.Line)
+ expect(e.sourceLayerIndex).toBe(0)
+ expect(ctx.layers[0].plane.children).toContain(e.THREE_Object)
+ expect(e.decideColor()).toBe(ctx.edgeDefaultColor)
+ })
+
+ it('attaches an inter-layer edge to the scene, not a layer', () => {
+ seedTwoNodeLayer()
+ const e = new Edge({
+ source: 'A::L',
+ target: 'B::L',
+ weights: [1],
+ interLayer: true,
+ })
+ expect(ctx.scene!.THREE_Object.children).toContain(e.THREE_Object)
+ })
+
+ it('builds a channel curve group coloured by channelColors', () => {
+ seedTwoNodeLayer()
+ ctx.channelColors = { ch1: '#123456' }
+ ctx.channelVisibility = { ch1: true }
+ const e = new Edge({
+ source: 'A::L',
+ target: 'B::L',
+ weights: [0.5],
+ channels: ['ch1'],
+ })
+ expect(e.THREE_Object).toBeInstanceOf(THREE.Group)
+ expect(e.THREE_Object.children.length).toBeGreaterThan(0)
+ expect(e.decideColor(0)).toBe('#123456')
+ })
+
+ it('decideOpacity uses weight when edgeWidthByWeight is on', () => {
+ seedTwoNodeLayer()
+ const e = new Edge({ source: 'A::L', target: 'B::L', weights: [0.7] })
+ ctx.edgeWidthByWeight = true
+ expect(e.decideOpacity(0)).toBe(0.7)
+ ctx.edgeWidthByWeight = false
+ expect(e.decideOpacity(0)).toBe(ctx.intraLayerEdgeOpacity)
+ })
+})
diff --git a/frontend/src/ui/.gitkeep b/frontend/src/ui/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/ui/data.ts b/frontend/src/ui/data.ts
new file mode 100644
index 0000000..4f1d4c7
--- /dev/null
+++ b/frontend/src/ui/data.ts
@@ -0,0 +1,195 @@
+// View Data panel — port of v2 views/data.R plus its table renderers:
+// functions/render.R (renderNetworkDF / renderClusteringDF / renderMetricTable,
+// hideDataMetricTabs) and functions/edges.R updateSelectedEdgesView.
+//
+// v2 rendered DT datatables (excel/csv/copy/pdf/print buttons, per-column
+// filters). Ported as plain HTML tables with a CSV download button.
+// ponytail: no per-column filters or excel/pdf/print export, add if missed.
+
+import { Tab } from 'bootstrap'
+import { bus } from '../bus'
+import { store } from '../store'
+import { ctx } from '../three'
+import { escapeHtml } from '../utils'
+
+const TABS = [
+ { id: 'network', label: 'Network Data' },
+ { id: 'selectedEdges', label: 'Selected Edges' },
+ { id: 'clustering', label: 'Clustering Data' },
+ { id: 'degree', label: 'Degree' },
+ { id: 'transitivity', label: 'Clustering Coefficient' },
+ { id: 'betweenness', label: 'Betweenness Centrality' },
+] as const
+
+// v2 renderMetricTable keyed output ids by metric (init.R TOPOLOGY_METRICS)
+const METRIC_TAB: Record = {
+ Degree: 'degree',
+ 'Clustering Coefficient': 'transitivity',
+ 'Betweenness Centrality': 'betweenness',
+}
+
+const DATA_HTML = `
+
+ ${TABS.map(
+ (t) => `
+
+ ${t.label}
+ `
+ ).join('')}
+
+
+ ${TABS.map(
+ (t) =>
+ `
`
+ ).join('')}
+
+`
+
+function toCsv(columns: string[], rows: (string | number)[][]): string {
+ const q = (v: string | number) => {
+ const s = String(v)
+ return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s
+ }
+ return [columns, ...rows].map((r) => r.map(q).join(',')).join('\n')
+}
+
+function activateDataTab(tabId: string): void {
+ const btn = document.querySelector(`[data-bs-target="#dataPane-${tabId}"]`)
+ if (btn) Tab.getOrCreateInstance(btn).show()
+}
+
+/** v2 renderShinyDataTable + showTab: fill a pane, unhide its tab, focus it. */
+function renderTable(
+ tabId: string,
+ columns: string[],
+ rows: (string | number)[][],
+ fileName: string,
+ focus = true
+): void {
+ const pane = document.getElementById(`dataPane-${tabId}`)
+ if (!pane) return
+ pane.innerHTML = `
+ Download CSV
+
+
+ ${columns.map((c) => `${escapeHtml(c)} `).join('')}
+ ${rows
+ .map(
+ (r) =>
+ `${r.map((v) => `${escapeHtml(v)} `).join('')} `
+ )
+ .join('')}
+
+
`
+ document.getElementById(`dataCsv-${tabId}`)?.addEventListener('click', () => {
+ const url = URL.createObjectURL(
+ new Blob([toCsv(columns, rows)], { type: 'text/csv' })
+ )
+ const a = document.createElement('a')
+ a.href = url
+ a.download = `${fileName}.csv`
+ a.click()
+ URL.revokeObjectURL(url)
+ })
+ document.getElementById(`dataTab-${tabId}`)?.classList.remove('d-none')
+ if (focus) activateDataTab(tabId)
+}
+
+function renderNetworkTable(): void {
+ const network = store.get().network
+ if (!network) return
+ const hasChannels = network.channels.length > 0
+ const columns = [
+ 'SourceNode',
+ 'SourceLayer',
+ 'TargetNode',
+ 'TargetLayer',
+ 'Weight',
+ ]
+ if (hasChannels) columns.push('Channel')
+ const rows = network.edges.map((e) => {
+ const row: (string | number)[] = [
+ e.source_node,
+ e.source_layer,
+ e.target_node,
+ e.target_layer,
+ e.weight,
+ ]
+ if (hasChannels) row.push(e.channel ?? '')
+ return row
+ })
+ renderTable('network', columns, rows, 'networkData')
+}
+
+/** v2 updateSelectedEdgesView: per-channel rows; tab hidden when none selected. */
+function refreshSelectedEdges(): void {
+ const rows: (string | number)[][] = []
+ for (const e of ctx.edgeObjects) {
+ if (!e.isSelected) continue
+ const base = [
+ ctx.nodeObjects[e.sourceNodeIndex].name,
+ ctx.layers[e.sourceLayerIndex].name,
+ ctx.nodeObjects[e.targetNodeIndex].name,
+ ctx.layers[e.targetLayerIndex].name,
+ ]
+ if (e.channels.length > 0)
+ e.channels.forEach((ch, j) => rows.push([...base, ch, e.weights[j]]))
+ else rows.push([...base, '', e.weights[0] ?? ''])
+ }
+ if (rows.length > 0) {
+ renderTable(
+ 'selectedEdges',
+ [
+ 'SourceNode',
+ 'SourceLayer',
+ 'TargetNode',
+ 'TargetLayer',
+ 'Channel',
+ 'Weight',
+ ],
+ rows,
+ 'selectedEdgeData',
+ false
+ )
+ } else {
+ const item = document.getElementById('dataTab-selectedEdges')
+ if (item && !item.classList.contains('d-none')) {
+ const wasActive = item
+ .querySelector('.nav-link')
+ ?.classList.contains('active')
+ item.classList.add('d-none')
+ if (wasActive) activateDataTab('network')
+ }
+ }
+}
+
+export function initDataPanel(): void {
+ const pane = document.getElementById('panel-data')
+ if (!pane) return
+ pane.innerHTML = DATA_HTML
+
+ bus.on('network:loaded', () => renderNetworkTable())
+ bus.on('clustering:applied', ({ clusters }) => {
+ renderTable(
+ 'clustering',
+ ['Node', 'Cluster'],
+ Object.entries(clusters),
+ 'clusteringData'
+ )
+ })
+ bus.on('topology:applied', ({ metric, scales }) => {
+ const tabId = METRIC_TAB[metric]
+ if (tabId)
+ renderTable(
+ tabId,
+ ['Node', 'Scale'],
+ Object.entries(scales),
+ `${tabId}Data`
+ )
+ })
+ // Selected edges have no bus event (selection is pure ctx state); recompute
+ // whenever the View Data panel is opened.
+ document
+ .querySelector('[data-bs-target="#panel-data"]')
+ ?.addEventListener('shown.bs.tab', () => refreshSelectedEdges())
+}
diff --git a/frontend/src/ui/drawer_toggle.ts b/frontend/src/ui/drawer_toggle.ts
new file mode 100644
index 0000000..f4ad662
--- /dev/null
+++ b/frontend/src/ui/drawer_toggle.ts
@@ -0,0 +1,66 @@
+// Collapse handle for the right-side action-panel drawer (File/Layouts/
+// Scene/.../Data — everything except Home, Main View and Help, which aren't
+// canvas-overlay drawers). Lets a drawer be dismissed/reopened without going
+// back through the navbar tabs.
+
+import { showTab } from './tabs'
+
+const NON_DRAWER_IDS = new Set(['panel-home', 'panel-main-view', 'panel-help'])
+
+let lastDrawerSelector: string | null = null
+
+// Bootstrap fires shown.bs.tab as soon as the (unanimated) nav button itself
+// finishes activating — before the target pane's own fade transition adds its
+// 'show' class. Querying '.tab-pane.show' at that moment can still match the
+// pane being deactivated, so read the pane id straight from the event's
+// tab-trigger target instead of asking the DOM which pane is "showing".
+function activePaneId(): string | null {
+ return document.querySelector('#panelContent .tab-pane.active')?.id ?? null
+}
+
+function isDrawerPane(id: string | null): boolean {
+ return !!id && !NON_DRAWER_IDS.has(id)
+}
+
+function updateToggle(paneId?: string | null): void {
+ const btn = document.getElementById('panelDrawerToggle')
+ if (!btn) return
+ const id = paneId ?? activePaneId()
+
+ if (id === 'panel-home' || id === 'panel-help') {
+ // Full-width reading panes, not a drawer to collapse.
+ btn.style.display = 'none'
+ return
+ }
+
+ const open = isDrawerPane(id)
+ if (open) lastDrawerSelector = `#${id}`
+
+ btn.style.display = 'flex'
+ btn.classList.toggle('is-open', open)
+ btn.innerHTML = open ? '›' : '‹'
+ btn.setAttribute('aria-label', open ? 'Hide panel' : 'Show panel')
+ btn.style.right =
+ open && id
+ ? `${document.getElementById(id)!.getBoundingClientRect().width}px`
+ : '0'
+}
+
+export function registerDrawerToggle(): void {
+ const btn = document.getElementById('panelDrawerToggle')
+ if (!btn) return
+
+ btn.addEventListener('click', () => {
+ if (isDrawerPane(activePaneId())) showTab('#panel-main-view')
+ else if (lastDrawerSelector) showTab(lastDrawerSelector)
+ })
+
+ document.addEventListener('shown.bs.tab', (e) => {
+ const paneSelector = (e.target as HTMLElement).getAttribute(
+ 'data-bs-target'
+ )
+ updateToggle(paneSelector?.replace('#', ''))
+ })
+ window.addEventListener('resize', () => updateToggle())
+ updateToggle()
+}
diff --git a/frontend/src/ui/edge.ts b/frontend/src/ui/edge.ts
new file mode 100644
index 0000000..8c53239
--- /dev/null
+++ b/frontend/src/ui/edge.ts
@@ -0,0 +1,161 @@
+// Edge Actions panel — port of v2 views/edge.R plus the show/hide slider
+// toggles from functions/edges.R (handleEdgeWidthByWeightCheckbox,
+// handleEdgeDirectionCheckbox) and the channel color/visibility list from
+// www/js/object_actions/edge.js attachChannelEditList.
+
+import { ctx } from '../three'
+import { bus } from '../bus'
+import { history } from '../commands/base'
+import { ChangeChannelColorCommand } from '../commands/scene'
+import { store } from '../store'
+import {
+ toggleDirection,
+ setIntraDirectionArrowSize,
+ setInterDirectionArrowSize,
+ setEdgeWidthByWeight,
+ setIntraLayerEdgeOpacity,
+ setInterLayerEdgeOpacity,
+ setIntraChannelCurvature,
+ setInterChannelCurvature,
+ setEdgeSelectedColorPriority,
+ setEdgeFileColorPriority,
+ setChannelVisibility,
+} from '../actions/edge'
+import { escapeHtml } from '../utils'
+
+const EDGE_HTML = `
+
+`
+
+function show(id: string, visible: boolean): void {
+ document.getElementById(id)?.classList.toggle('d-none', !visible)
+}
+
+// v2 attachChannelEditList: per channel a color picker + a Hide checkbox.
+// XSS fix: `ch` is a user-controlled channel name (TSV Channel column) —
+// escapeHtml() it before interpolating into innerHTML, including inside the
+// id/value attributes below.
+function buildChannelEditList(): void {
+ const container = document.getElementById('channelColorPicker')
+ if (!container) return
+ container.innerHTML = ''
+ const channels = store.get().network?.channels ?? []
+ if (channels.length === 0) return
+
+ const title = document.createElement('h4')
+ title.textContent = 'Channels'
+ container.appendChild(title)
+
+ for (const ch of channels) {
+ const row = document.createElement('div')
+ row.className = 'channel_subcontainer d-flex align-items-center gap-2 mb-2'
+ row.innerHTML = `
+ ${escapeHtml(ch)}:
+
+
+ Hide
+ `
+ const picker = row.querySelector('.channel_colorPicker')!
+ const hide = row.querySelector('.channel_checkbox')!
+ picker.addEventListener('change', () =>
+ history.execute(new ChangeChannelColorCommand(ch, picker.value))
+ )
+ hide.addEventListener('change', () =>
+ setChannelVisibility(ch, !hide.checked)
+ )
+ container.appendChild(row)
+ }
+}
+
+export function initEdgePanel(): void {
+ const pane = document.getElementById('panel-edge')
+ if (!pane) return
+ pane.innerHTML = EDGE_HTML
+
+ const range = (id: string, fn: (v: number) => void) =>
+ document.getElementById(id)?.addEventListener('input', (e) => {
+ fn(Number((e.target as HTMLInputElement).value))
+ })
+
+ // Direction toggle shows/hides the arrow-size sliders (edges.R).
+ document
+ .getElementById('edgeDirectionToggle')
+ ?.addEventListener('change', (e) => {
+ const on = (e.target as HTMLInputElement).checked
+ show('intraDirectionArrowSizeWrap', on)
+ show('interDirectionArrowSizeWrap', on)
+ toggleDirection(on)
+ })
+ range('intraDirectionArrowSize', setIntraDirectionArrowSize)
+ range('interDirectionArrowSize', setInterDirectionArrowSize)
+
+ // Opacity-by-weight hides the manual opacity sliders (edges.R).
+ document
+ .getElementById('edgeWidthByWeight')
+ ?.addEventListener('change', (e) => {
+ const byWeight = (e.target as HTMLInputElement).checked
+ show('intraLayerEdgeOpacityWrap', !byWeight)
+ show('interLayerEdgeOpacityWrap', !byWeight)
+ setEdgeWidthByWeight(byWeight)
+ })
+ range('intraLayerEdgeOpacity', setIntraLayerEdgeOpacity)
+ range('interLayerEdgeOpacity', setInterLayerEdgeOpacity)
+
+ range('intraChannelCurvature', setIntraChannelCurvature)
+ range('interChannelCurvature', setInterChannelCurvature)
+
+ document
+ .getElementById('edgeSelectedColorPriority')
+ ?.addEventListener('change', (e) => {
+ setEdgeSelectedColorPriority((e.target as HTMLInputElement).checked)
+ })
+ document
+ .getElementById('edgeFileColorPriority')
+ ?.addEventListener('change', (e) => {
+ setEdgeFileColorPriority((e.target as HTMLInputElement).checked)
+ })
+
+ bus.on('network:loaded', buildChannelEditList)
+}
diff --git a/frontend/src/ui/file.ts b/frontend/src/ui/file.ts
new file mode 100644
index 0000000..4b412b0
--- /dev/null
+++ b/frontend/src/ui/file.ts
@@ -0,0 +1,205 @@
+// File panel — port of v2 views/file.R + functions/input.R upload handlers.
+
+import { api } from '../api/client'
+import { applyEdgeAttributes } from '../actions/edge'
+import { loadNetwork, loadSession } from '../actions/network'
+import { applyNodeAttributes } from '../actions/node'
+import { startLoader, finishLoader, exportSceneImage } from '../actions/screen'
+import { exportSession } from '../actions/session'
+import { showTab } from './tabs'
+
+const FILE_HTML = `
+
+
+ Upload Network:
+
+
+
+ Load Session:
+
+
+
+ Upload NODE attributes:
+
+
+
+ Upload EDGE attributes:
+
+
+
+
+
+ Save Session
+
+
+
+ Export Image
+
+
+
+ Load Example
+
+
+
+
+`
+
+function status(msg: string, isError = false): void {
+ const el = document.getElementById('file_status')
+ if (!el) return
+ el.textContent = msg
+ el.className = `mt-3 small ${isError ? 'text-danger' : 'text-success'}`
+}
+
+async function onUploadNetwork(file: File): Promise {
+ startLoader()
+ try {
+ const data = await api.uploadNetwork(file)
+ loadNetwork(data)
+ status(
+ `Loaded network: ${data.layers.length} layers, ${data.nodes.length} nodes.`
+ )
+ showTab('#panel-main-view') // reveal the scene; drawer toggle shows closed
+ } catch (err) {
+ status(err instanceof Error ? err.message : 'Network upload failed.', true)
+ } finally {
+ finishLoader()
+ }
+}
+
+async function onLoadSession(file: File): Promise {
+ startLoader()
+ try {
+ const session = await api.importSession(file)
+ loadSession(session)
+ const warnings = session.warnings ?? []
+ status(
+ warnings.length
+ ? `Session loaded. ${warnings.join(' ')}`
+ : 'Session loaded.'
+ )
+ showTab('#panel-main-view') // reveal the scene; drawer toggle shows closed
+ } catch (err) {
+ status(err instanceof Error ? err.message : 'Session load failed.', true)
+ } finally {
+ finishLoader()
+ }
+}
+
+async function onLoadExample(): Promise {
+ try {
+ // Sub-path deployment fix: this is a hardcoded root-absolute fetch, so
+ // Vite's `base` rewriting (which only covers Vite-generated asset/HTML
+ // URLs) doesn't apply here — prefix it manually via BASE_URL so it still
+ // resolves under a reverse-proxied sub-path (e.g. /arena3/). Without the
+ // res.ok check, a 404 here would silently POST the error page's body to
+ // /api/network as if it were the TSV.
+ const res = await fetch(`${import.meta.env.BASE_URL}data/figure2A_data.tsv`)
+ if (!res.ok)
+ throw new Error(`Failed to fetch example network: ${res.status}`)
+ const file = new File([await res.blob()], 'figure2A_data.tsv', {
+ type: 'text/tab-separated-values',
+ })
+ await onUploadNetwork(file)
+ } catch (err) {
+ status(err instanceof Error ? err.message : 'Example load failed.', true)
+ }
+}
+
+// v2 handleInputNodeAttributeFileUpload / handleInputEdgeAttributeFileUpload.
+async function onUploadNodeAttributes(file: File): Promise {
+ startLoader()
+ try {
+ const rows = await api.uploadNodeAttributes(file)
+ applyNodeAttributes(rows)
+ // keep the Node Actions color-priority radio in sync (v2 clicked it)
+ const radio = document.querySelector(
+ 'input[name="nodeColorPriorityRadio"][value="default"]'
+ )
+ if (radio) radio.checked = true
+ status('Node attributes applied.')
+ } catch (err) {
+ status(
+ err instanceof Error ? err.message : 'Bad node attributes file format.',
+ true
+ )
+ } finally {
+ finishLoader()
+ }
+}
+
+async function onUploadEdgeAttributes(file: File): Promise {
+ startLoader()
+ try {
+ const rows = await api.uploadEdgeAttributes(file)
+ applyEdgeAttributes(rows)
+ // keep the Edge Actions priority checkbox in sync (v2 clicked it)
+ const box = document.getElementById(
+ 'edgeFileColorPriority'
+ ) as HTMLInputElement | null
+ if (box) box.checked = true
+ status('Edge attributes applied.')
+ } catch (err) {
+ status(
+ err instanceof Error ? err.message : 'Bad edge attributes file format.',
+ true
+ )
+ } finally {
+ finishLoader()
+ }
+}
+
+export function initFilePanel(): void {
+ const pane = document.getElementById('panel-file')
+ if (!pane) return
+ pane.innerHTML = FILE_HTML
+
+ const netInput = document.getElementById(
+ 'input_network_file'
+ ) as HTMLInputElement
+ netInput.addEventListener('change', () => {
+ if (netInput.files?.[0]) void onUploadNetwork(netInput.files[0])
+ })
+
+ const sessInput = document.getElementById(
+ 'load_network_file'
+ ) as HTMLInputElement
+ sessInput.addEventListener('change', () => {
+ if (sessInput.files?.[0]) void onLoadSession(sessInput.files[0])
+ })
+
+ const nodeAttrInput = document.getElementById(
+ 'node_attributes_file'
+ ) as HTMLInputElement
+ nodeAttrInput.addEventListener('change', () => {
+ if (nodeAttrInput.files?.[0])
+ void onUploadNodeAttributes(nodeAttrInput.files[0])
+ })
+
+ const edgeAttrInput = document.getElementById(
+ 'edge_attributes_file'
+ ) as HTMLInputElement
+ edgeAttrInput.addEventListener('change', () => {
+ if (edgeAttrInput.files?.[0])
+ void onUploadEdgeAttributes(edgeAttrInput.files[0])
+ })
+
+ document
+ .getElementById('save_network_object')
+ ?.addEventListener('click', () => {
+ void exportSession().catch((err: unknown) =>
+ status(err instanceof Error ? err.message : 'Save failed.', true)
+ )
+ })
+
+ document
+ .getElementById('export_scene_image')
+ ?.addEventListener('click', () => {
+ if (exportSceneImage()) status('Image exported.')
+ else status('Nothing to export — load a network first.', true)
+ })
+
+ document.getElementById('exampleButton')?.addEventListener('click', () => {
+ void onLoadExample()
+ })
+}
diff --git a/frontend/src/ui/fps.ts b/frontend/src/ui/fps.ts
new file mode 100644
index 0000000..25a5e9b
--- /dev/null
+++ b/frontend/src/ui/fps.ts
@@ -0,0 +1,32 @@
+// FPS panel — port of v2 views/fps.R. Sets ctx.fps, which the render loop
+// (screen.ts animate) reads each frame. Default checked = current ctx.fps.
+
+import { ctx } from '../three'
+
+const FPS_HTML = `
+
+
FPS:
+ ${[15, 30, 60]
+ .map(
+ (f) => `
+
+
+ ${f}FPS
+
`
+ )
+ .join('')}
+
+`
+
+export function initFpsPanel(): void {
+ const pane = document.getElementById('panel-fps')
+ if (!pane) return
+ pane.innerHTML = FPS_HTML
+ for (const radio of document.querySelectorAll(
+ 'input[name="fps"]'
+ )) {
+ radio.addEventListener('change', () => {
+ ctx.fps = Number(radio.value) || 60
+ })
+ }
+}
diff --git a/views/help.R b/frontend/src/ui/help.ts
similarity index 52%
rename from views/help.R
rename to frontend/src/ui/help.ts
index fb4cd05..26a105d 100644
--- a/views/help.R
+++ b/frontend/src/ui/help.ts
@@ -1,20 +1,20 @@
-generateHelpDiv <- function() {
- tags$div(
- id = "helpDiv",
- HTML('
+// Help panel — port of v2 views/help.R. Static HTML fragment + inner tab switching.
+
+const HELP_HTML = `
+
- Examples
- API
- Cytoscape App
- Input & Output Files
- Navigation Controls
- Layer Selection & Layouts
- Scene Actions
- Layer Actions
- Node Actions
- Edge Actions
- FPS
- About
+ Examples
+ API
+ Cytoscape App
+ Input & Output Files
+ Navigation Controls
+ Layer Selection & Layouts
+ Scene Actions
+ Layer Actions
+ Node Actions
+ Edge Actions
+ FPS
+ About
@@ -23,10 +23,10 @@ generateHelpDiv <- function() {
This is the action panel that allows the user to upload network data as well as export the network in its current
state.
-
1. The Upload Network option allows the user to upload network data in the
- Arena3Dweb format. This file consists of 4 mandatory columns with headers SourceNode, TargetNode,
+ Arena3D format. This file consists of 4 mandatory columns with headers SourceNode, TargetNode,
SourceLayer and TargetLayer and 2 optional columns with the headers Weight and Channel .
After the file is uploaded, the weight
values are
@@ -35,9 +35,9 @@ generateHelpDiv <- function() {
weight column. The channel column is only used in the case of a multi-edge graphs. The column order in the input
file is
irrelevant. In its online version,
- Arena3Dweb supports networks of up to 5000 edges and 9 channels. For larger networks, one can
+ Arena3D supports networks of up to 5000 edges and 9 channels. For larger networks, one can
download and run
- Arena3Dweb locally from GitHub , and manually adjust the MAX_EDGES , the
MAX_LAYERS or
MAX_CHANNELS variable in the global.R file.
@@ -56,7 +56,7 @@ generateHelpDiv <- function() {
2. ).
-
Example of the Arena3Dweb Upload Network file format.
+
Example of the Arena3D Upload Network file format.
SourceNode SourceLayer TargetNode TargetLayer Weight Channel
An Group1 Cn Group1 2 1
An Group1 Bn Group1 10 1
@@ -84,267 +84,116 @@ Kn Group4 Sn Group7 1 1
Kn Group4 Tn Group7 10 1
-
Example of the Arena3Dweb Upload NODE attributes file format. All columns are ommitable except
+
Example of the Arena3D Upload NODE attributes file format. All columns are ommitable except
from Node and Layer ones. Users do not need to mention every node, just the ones of interest.
Node Layer Color Size Url Description
-An Group1 #6b6a4c 1 This is a node\'s description.
-Bn Group1 #ccccff 2
-Cn Group1 #254284 3
+An Group1 #6b6a4c 1 This is a node's description.
+Bn Group1 #ccccff 2
+Cn Group1 #254284 3
En Group2 4 https://www.frontiersin.org/articles/10.3389/fbioe.2020.00034/full Click the Link of this node to read our review on biological networks.
Fn Group2 #7fe5f0 5 http://norma.pavlopouloslab.info/ Click the Link of this node to access our network annotation tool, NORMA.
-Gn Group2 1
-In Group3 2
-Dn Group1 #ffb3b3 http://nap.pavlopouloslab.info/
-Hn Group2 3
-Kn Group4 #e0f2f2
-Qn Group6 #ff0067
-Rn Group6
-Sn Group7 #ffd8e8
-Tn Group7 4
+Gn Group2 1
+In Group3 2
+Dn Group1 #ffb3b3 http://nap.pavlopouloslab.info/
+Hn Group2 3
+Kn Group4 #e0f2f2
+Qn Group6 #ff0067
+Rn Group6
+Sn Group7 #ffd8e8
+Tn Group7 4
+
+
+
Example of the Arena3D Upload EDGE attributes file format. Optional column: Channel
+
SourceNode SourceLayer TargetNode TargetLayer Color
+An Group1 Cn Group1 #4EFBE9
+Bn Group1 Fn Group2 #D64EFB
+In Group3 Jn Group3
+Kn Group4 Tn Group7 #4EFB7D
-
-
Example of the Arena3Dweb Upload EDGE attributes file format. Optional column: Channel
-
SourceNode SourceLayer TargetNode TargetLayer Color
- An Group1 Cn Group1 #4EFBE9
- Bn Group1 Fn Group2 #D64EFB
- In Group3 Jn Group3
- Kn Group4 Tn Group7 #4EFB7D
-
-
-
- Example of the JSON file format. Before importing, users can alter the Scene,
- Layer, Node and/or Edge attributes manually.
- Scene object : Position x, Position Y, Scale, Background Color, Rotation X, Rotation Y, Rotation Z
- Layer object : Layer name, Position X, Position Y, Position Z, Scale, Rotation X, Rotation Y, Rotation Z,
- , Floor Color, Floor Width
- Node : Node Name, Node Layer, Position X, Position Y, Position Z, Scale, Color, Url,
- Description
- Edge attributes : Edge, Weight, Opacity, Color, Channel
- Univers Label Color : hex code for the labels
- Enable Direction : boolean
-
-
- {
- "scene_pan": {
- "position_x": "-320",
- "position_y": "10",
- "scale": "0.456302989349695",
- "color": "#000000",
- "rotation_x": "0.279252680319093",
- "rotation_y": "0.279252680319093",
- "rotation_z": "-1.58824961931484"
- },
- "layers": [
- {
- "name": "Complex",
- "position_x": "-819.2",
- "position_y": "300",
- "position_z": "0",
- "last_layer_scale": "0.6",
- "rotation_x": "0",
- "rotation_y": "0",
- "rotation_z": "0",
- "floor_current_color": "#5e5a5a",
- "geometry_parameters_width": "1244.7619047619"
- },
- {
- "name": "Proteins",
- "position_x": "-159.6",
- "position_y": "850",
- "position_z": "0",
- "last_layer_scale": "1",
- "rotation_x": "0",
- "rotation_y": "0",
- "rotation_z": "0",
- "floor_current_color": "#5e5a5a",
- "geometry_parameters_width": "1244.7619047619"
- },
- ],
- "nodes": [
- {
- "name": "Pericentrin-GCP Complex",
- "layer": "Complex",
- "position_x": "0",
- "position_y": "-187.632917471854",
- "position_z": "212.593778709733",
- "scale": "0.53921568627451",
- "color": "#63b598",
- "url": "",
- "descr": ""
- },
- {
- "name": "TUBGCP2",
- "layer": "Proteins",
- "position_x": "0",
- "position_y": "-354.817961725556",
- "position_z": "347.86115539628",
- "scale": "0.57843137254902",
- "color": "#ce7d78",
- "url": "",
- "descr": ""
- },
- {
- "name": "TUBGCP3",
- "layer": "Proteins",
- "position_x": "0",
- "position_y": "-346.070269403277",
- "position_z": "332.464903691254",
- "scale": "0.57843137254902",
- "color": "#ce7d78",
- "url": "",
- "descr": ""
- },
- {
- "name": "DNA Polymerase Alpha",
- "layer": "Complex",
- "position_x": "0",
- "position_y": "40.5705071933332",
- "position_z": "-308.931155161144",
- "scale": "0.617647058823529",
- "color": "#63b598",
- "url": "",
- "descr": ""
- }
- ],
- "edges": [
- {
- "src": "Pericentrin-GCP Complex_Complex",
- "trg": "TUBGCP2_Proteins",
- "opacity": "0.1",
- "color": "#CFCFCF",
- "channel": ""
- },
- {
- "src": "Pericentrin-GCP Complex_Complex",
- "trg": "TUBGCP3_Proteins",
- "opacity": "0.1",
- "color": "#CFCFCF",
- "channel": ""
- },
- {
- "src": "DNA Polymerase Alpha_Complex",
- "trg": "TUBGCP2_Proteins",
- "opacity": "0.1",
- "color": "#CFCFCF",
- "channel": ""
- },
- {
- "src": "DNA Polymerase Alpha_Complex",
- "trg": "TUBGCP3_Proteins",
- "opacity": "0.1",
- "color": "#CFCFCF",
- "channel": ""
- },
- {
- "src": "TUBGCP2_Proteins",
- "trg": "TUBGCP3_Proteins",
- "opacity": "0.1",
- "color": "#CFCFCF",
- "channel": ""
- }
- ],
- "universalLabelColor": "#ffffff",
- "direction": true
-
-
-
-
-
-
- To open Arena3Dweb from an external application, we offer an API that allows a POST request along
- with an Arena3Dweb JSON object.
- The API link is https://bib.fleming.gr/bib/api/arena3dweb . Don\'t forget to set the Header
- Content-Type to application/json . The JSON object must follow the Arena3Dweb export format
+
+
+ Example of the JSON file format. Before importing, users can alter the Scene,
+ Layer, Node and/or Edge attributes manually.
+ Scene object : Position x, Position Y, Scale, Background Color, Rotation X, Rotation Y, Rotation Z
+ Layer object : Layer name, Position X, Position Y, Position Z, Scale, Rotation X, Rotation Y, Rotation Z,
+ , Floor Color, Floor Width
+ Node : Node Name, Node Layer, Position X, Position Y, Position Z, Scale, Color, Url,
+ Description
+ Edge attributes : Edge, Weight, Opacity, Color, Channel
+ Univers Label Color : hex code for the labels
+ Enable Direction : boolean
+
+
+{
+ "scene_pan": {
+ "position_x": "-320",
+ "position_y": "10",
+ "scale": "0.456302989349695",
+ "color": "#000000",
+ "rotation_x": "0.279252680319093",
+ "rotation_y": "0.279252680319093",
+ "rotation_z": "-1.58824961931484"
+ },
+ "layers": [
+ { "name": "Complex", "position_x": "-819.2", "position_y": "300", "position_z": "0",
+ "last_layer_scale": "0.6", "rotation_x": "0", "rotation_y": "0", "rotation_z": "0",
+ "floor_current_color": "#5e5a5a", "geometry_parameters_width": "1244.7619047619" },
+ { "name": "Proteins", "position_x": "-159.6", "position_y": "850", "position_z": "0",
+ "last_layer_scale": "1", "rotation_x": "0", "rotation_y": "0", "rotation_z": "0",
+ "floor_current_color": "#5e5a5a", "geometry_parameters_width": "1244.7619047619" }
+ ],
+ "nodes": [
+ { "name": "Pericentrin-GCP Complex", "layer": "Complex", "position_x": "0",
+ "position_y": "-187.632917471854", "position_z": "212.593778709733",
+ "scale": "0.53921568627451", "color": "#63b598", "url": "", "descr": "" },
+ { "name": "TUBGCP2", "layer": "Proteins", "position_x": "0",
+ "position_y": "-354.817961725556", "position_z": "347.86115539628",
+ "scale": "0.57843137254902", "color": "#ce7d78", "url": "", "descr": "" }
+ ],
+ "edges": [
+ { "src": "Pericentrin-GCP Complex_Complex", "trg": "TUBGCP2_Proteins",
+ "opacity": "0.1", "color": "#CFCFCF", "channel": "" }
+ ],
+ "universalLabelColor": "#ffffff",
+ "direction": true
+}
+
+
+
+
+
+
+ To open Arena3D from an external application, we offer an API that allows a POST request along
+ with an Arena3D JSON object.
+ The API endpoint is /api/external on the Arena3D server
+ (e.g. https://arena3d.org/api/external ). Don't forget to set the Header
+ Content-Type to application/json . The JSON object must follow the Arena3D export format
as follows:
Simple 3-node, 2-edge, 2-layer network example
{
- "scene": {
- "position_x": "0",
- "position_y": "0",
- "scale": "0.6561",
- "color": "#000000",
- "rotation_x": "0.261799387799149",
- "rotation_y": "0.261799387799149",
- "rotation_z": "0.0872664625997165"
- },
+ "scene": { "position_x": "0", "position_y": "0", "scale": "0.6561", "color": "#000000",
+ "rotation_x": "0.261799387799149", "rotation_y": "0.261799387799149", "rotation_z": "0.0872664625997165" },
"layers": [
- {
- "name": "1",
- "position_x": "-480",
- "position_y": "0",
- "position_z": "0",
- "last_layer_scale": "1",
- "rotation_x": "0",
- "rotation_y": "0",
- "rotation_z": "0",
- "floor_current_color": "#777777",
- "geometry_parameters_width": "947"
- },
- {
- "name": "2",
- "position_x": "480",
- "position_y": "0",
- "position_z": "0",
- "last_layer_scale": "1",
- "rotation_x": "0",
- "rotation_y": "0",
- "rotation_z": "0",
- "floor_current_color": "#777777",
- "geometry_parameters_width": "947"
- }
+ { "name": "1", "position_x": "-480", "position_y": "0", "position_z": "0", "last_layer_scale": "1",
+ "rotation_x": "0", "rotation_y": "0", "rotation_z": "0", "floor_current_color": "#777777",
+ "geometry_parameters_width": "947" },
+ { "name": "2", "position_x": "480", "position_y": "0", "position_z": "0", "last_layer_scale": "1",
+ "rotation_x": "0", "rotation_y": "0", "rotation_z": "0", "floor_current_color": "#777777",
+ "geometry_parameters_width": "947" }
],
"nodes": [
- {
- "name": "A",
- "layer": "1",
- "position_x": "0",
- "position_y": "-410.179206860405",
- "position_z": "87.2109740224067",
- "scale": "1",
- "color": "#e41a1c",
- "url": "",
- "descr": ""
- },
- {
- "name": "B",
- "layer": "1",
- "position_x": "0",
- "position_y": "244.693623604753",
- "position_z": "-203.550830988035",
- "scale": "1",
- "color": "#e41a1c",
- "url": "",
- "descr": ""
- },
- {
- "name": "C",
- "layer": "2",
- "position_x": "0",
- "position_y": "-10.2895227857923",
- "position_z": "361.274295019168",
- "scale": "1",
- "color": "#377eb8",
- "url": "",
- "descr": ""
- }
+ { "name": "A", "layer": "1", "position_x": "0", "position_y": "-410.179206860405",
+ "position_z": "87.2109740224067", "scale": "1", "color": "#e41a1c", "url": "", "descr": "" },
+ { "name": "B", "layer": "1", "position_x": "0", "position_y": "244.693623604753",
+ "position_z": "-203.550830988035", "scale": "1", "color": "#e41a1c", "url": "", "descr": "" },
+ { "name": "C", "layer": "2", "position_x": "0", "position_y": "-10.2895227857923",
+ "position_z": "361.274295019168", "scale": "1", "color": "#377eb8", "url": "", "descr": "" }
],
"edges": [
- {
- "src": "A_1",
- "trg": "B_1",
- "opacity": "1",
- "color": "#CFCFCF",
- "channel": ""
- },
- {
- "src": "A_1",
- "trg": "C_2",
- "opacity": "1",
- "color": "#CFCFCF",
- "channel": ""
- }
+ { "src": "A_1", "trg": "B_1", "opacity": "1", "color": "#CFCFCF", "channel": "" },
+ { "src": "A_1", "trg": "C_2", "opacity": "1", "color": "#CFCFCF", "channel": "" }
],
"universalLabelColor": "#FFFFFF",
"direction": false,
@@ -352,29 +201,31 @@ Tn Group7 4
}
- The server then returns a JSON response with the url that links to the Arena3Dweb application, having the
- requested network loaded:
+ The server then returns a JSON response with a token and the url that links to the Arena3D
+ application, having the requested network loaded. Just open the returned url — the token is
+ generated server-side, so there is no need to construct the link yourself:
{
- "url": "https://bib.fleming.gr:8084/app/arena3d?f=081436639JURotmRGQeFJ.json"
+ "token": "081436639JURotmRGQeFJ",
+ "url": "https://arena3d.org/?session=081436639JURotmRGQeFJ"
}
@@ -405,117 +256,81 @@ Tn Group7 4
Random networks with different topologies mapped in 6 layers respectively:
- The
- example network in the Arena3Dweb format.
- An
- exported state file of this example.
+ The example network in the Arena3D format.
+ An exported state file of this example.
-
-
Network example with 4 layers:
- The
- network file in Arena3Dweb input format.
- An
- exported state file of this example forming a cube in 3D space.
+ The network file in Arena3D input format.
+ An exported state file of this example forming a cube in 3D space.
-
-
Another network example with 4 layers, accompanied by node and edge attribute files:
- The
- network file in Arena3Dweb input format.
-
- Node attributes file for this example.
-
- Edge attributes file for this example.
- An
- exported state file of this example.
+ The network file in Arena3D input format.
+ Node attributes file for this example.
+ Edge attributes file for this example.
+ An exported state file of this example.
-
-
SARS-CoV-2 example:
- A
- Covid-19 network based on the work of Gordon et al. , in the Arena3Dweb format.
-
- Node attributes file for this example.
-
- Edge attributes file for this example.
- An
- exported state file of this example.
+ A Covid-19 network based on the work of
+ Gordon et al. ,
+ in the Arena3D format.
+ Node attributes file for this example.
+ Edge attributes file for this example.
+ An exported state file of this example.
-
-
GPCR example:
- The
- network file in the Arena3Dweb format.
-
- Node attributes file for this example.
-
- Edge attributes file for this example.
- An
- exported state file of this example.
+ The network file in the Arena3D format.
+ Node attributes file for this example.
+ Edge attributes file for this example.
+ An exported state file of this example.
-
-
Aspirin network example with 3 data channels:
- The
- network file in the Arena3Dweb format.
- An
- exported state file of this example.
+ The network file in the Arena3D format.
+ An exported state file of this example.
-
-
PREGO 3-channel example
- for \'anaerobic ammonium oxidation\' process associations:
- The
- network file in the Arena3Dweb format.
- An
- exported state file of this example.
+ for 'anaerobic ammonium oxidation' process associations:
+ The network file in the Arena3D format.
+ An exported state file of this example.
-
-
Cytoscape-Arena3Dweb App aspirin multi-channel interoperability example:
- The
- network file in Cytoscape format.
- An
- exported state file of this example in the Arena3Dweb exported format.
+ The network file in Cytoscape format.
+ An exported state file of this example in the
+ Arena3D exported format.
-
-
Scripts:
- A Python script for parsing edgelist data into Arena3Dweb
+ A Python script for parsing edgelist data into Arena3D
format.
As an example, this input file:
- Group1 A,B,C,D
- Group2 X,Y,Z
+ Group1 A,B,C,D
+ Group2 X,Y,Z
will be converted to this output file:
- Group1 A
- Group1 B
- Group1 C
- Group1 D
- Group2 X
- Group2 Y
- Group2 Z
+ Group1 A
+ Group1 B
+ Group1 C
+ Group1 D
+ Group2 X
+ Group2 Y
+ Group2 Z
Run as: python filename.txt
The coverted file will be stored in a file named reformatted_filename.txt.
@@ -528,15 +343,15 @@ Tn Group7 4
This is the main panel for network transformations in 3D space. The actions are divided into 3 subsections,
namely, Scene, Layers and Nodes.
-
+
General
1. The Navigation Controls button is used to hide/show the navigation
panel, while the Stop:Render Inter-Layer Edges button
hides inter-layer edges to greatly improve rendering performance.
2. General instructions on network hotkeys. 1. The user
can zoom-in/out by mouse scrolling. 2. The network can be translated by
- dragging with the mouse or by pressing the keyboard\'s arrow keys. 3. The
+ dragging with the mouse or by pressing the keyboard's arrow keys. 3. The
network view is also orbitable by dragging while holding the middle-mouse button.
4. The user can move a layer by click and dragging it.
@@ -576,104 +391,102 @@ Tn Group7 4
transformations are applied on selected nodes only.
11. Scaling transformation in a range of 0.2x - 5x for selected nodes.
-
-
-
-
-
-
Layer Selection & Layouts
-
This control panel allows the user to select, deselect and hide layers, show layer-specific node labels, as well as
- apply layout and clustering algorithms and node scaling based on network metrics, on subgraphs of the network.
-
-
-
- 1. This consists of a group of 3 exclusive options for subgraph calculations, upon
+
+
+
+
+
+
Layer Selection & Layouts
+
This control panel allows the user to select, deselect and hide layers, show layer-specific node labels, as well as
+ apply layout and clustering algorithms and node scaling based on network metrics, on subgraphs of the network.
+
+
+
+ 1. This consists of a group of 3 exclusive options for subgraph calculations, upon
which, layout algorithms (3, 5 ), clustering algorithms (4 ) and node scaling (6 ) is
+ class="numbering">4) and node scaling (6 ) is
applied. The Per Layer choice treats each selected layer (2 ) as an
individual network. The All Selected Layers choice treats all selected Layers (2 ) as one, combined network. After the execution of a layout or scaling algorithm, nodes
+ class="numbering">2) as one, combined network. After the execution of a layout or scaling algorithm, nodes
are mapped back to their respective Layer. With this option, the application of force-directed layout algorithms
allows network alignment among the different layers. The Local Layout option for the Selected Nodes Per
Layer choice allows layout and scaling algorithms to be applied on a selected sub-group of nodes, per each
selected layer respectively.
- 2. The Select/Deselect All Layers checkbox allows the user to quickly select
+ 2. The Select/Deselect All Layers checkbox allows the user to quickly select
or deselect all available network layers. After the user uploads or imports a network, a grid of n x 3
checkboxes is created, where n is the number of network layers and 3 are the available actions for
each layer; the 1st column allows the individual selection/deselection of layers, the
2nd column allows the user to hide individual layers and their inter-layer connections and the
3rd column allows the user to show node labels per layer.
- 3. A list of available layout algorithms of the igraph package, to apply on
- selected layers (2 ) based on the execution mode of (1 ).
- 4. A list of available clustering algorithms of the igraph package, to apply on
- selected layers (2 ) based on the execution mode of (1 ).
+ 3. A list of available layout algorithms of the igraph package, to apply on
+ selected layers (2 ) based on the execution mode of (1 ).
+ 4. A list of available clustering algorithms of the igraph package, to apply on
+ selected layers (2 ) based on the execution mode of (1 ).
- 5. A list of available layout algorithms of the igraph package, to apply as
+ 5. A list of available layout algorithms of the igraph package, to apply as
local layouts on clusters (4 ) based on the execution mode of (1 ). Visible when a clustering algorithm has been selected.
+ class="numbering">1). Visible when a clustering algorithm has been selected.
- 6. A list of available network metrics of the igraph package, used for
+ 6. A list of available network metrics of the igraph package, used for
node-scaling, to apply on selected layers (2 ) based on the execution mode of option
(1 ).
-
-
-
Below, we briefly describe the available layout algorithms (3, 5 ).
-
- Reingold-Tilford: This is a tree-like layout and is suitable for trees, hierarchies and graphs without many
+
+
+ Below, we briefly describe the available layout algorithms (3, 5 ).
+
+ Reingold-Tilford: This is a tree-like layout and is suitable for trees, hierarchies and graphs without many
cycles.
- Sugiyama: Like with Reingold-Tilford, this layout algorithm is more suitable for layered directed acyclic
+ Sugiyama: Like with Reingold-Tilford, this layout algorithm is more suitable for layered directed acyclic
graphs.
- Fruchterman-Reingold: It places nodes on the plane using the force-directed layout algorithm developed by
+ Fruchterman-Reingold: It places nodes on the plane using the force-directed layout algorithm developed by
Fruchterman and Reingold.
- Circle: It places vertices on a circle, ordered by their vertex ids.
- Grid: This layout places vertices on a rectangular 2D grid.
- Davidson-Harel: It is a force-directed algorithm which uses simulated annealing and a sophisticated energy
+ Circle: It places vertices on a circle, ordered by their vertex ids.
+ Grid: This layout places vertices on a rectangular 2D grid.
+ Davidson-Harel: It is a force-directed algorithm which uses simulated annealing and a sophisticated energy
function to place nodes on a plane.
- Distributed Recursive (Graph) Layout: DrL is a force-directed graph layout toolbox focused on real-world
+ Distributed Recursive (Graph) Layout: DrL is a force-directed graph layout toolbox focused on real-world
large-scale graphs.
- Multidimensional scaling: It aims to place points from a higher dimensional space in 2D plane, so that the
+ Multidimensional scaling: It aims to place points from a higher dimensional space in 2D plane, so that the
distance between the points are kept as much as possible.
- Random: This function places the vertices of the graph on a 2D plane uniformly using random coordinates.
- Kamada-Kawai: This layout places the vertices on a 2D plane by simulating a physical model of springs.
- Large Graph Layout (LGL): A force directed layout suitable for larger graphs.
- Graphopt: A force-directed layout algorithm, which scales relatively well to large graphs.
- Gem: It places vertices on the plane using the GEM force-directed layout algorithm.
- Star: It places vertices of a graph on the plane, according to the simulated annealing algorithm by Davidson and
+ Random: This function places the vertices of the graph on a 2D plane uniformly using random coordinates.
+ Kamada-Kawai: This layout places the vertices on a 2D plane by simulating a physical model of springs.
+ Large Graph Layout (LGL): A force directed layout suitable for larger graphs.
+ Graphopt: A force-directed layout algorithm, which scales relatively well to large graphs.
+ Gem: It places vertices on the plane using the GEM force-directed layout algorithm.
+ Star: It places vertices of a graph on the plane, according to the simulated annealing algorithm by Davidson and
Harel.
-
-
-
-
- Below, we briefly desribe the available clustering algorithms (4 ).
-
- Louvain: This algorithm is a greedy optimization method that appears to run in time O(nlogn) where n is the
+
+
+
+
+ Below, we briefly desribe the available clustering algorithms (4 ).
+
+ Louvain: This algorithm is a greedy optimization method that appears to run in time O(nlogn) where n is the
number of nodes in the network.
- Walktrap: This algorithm in graph theory, used to identify communities in large networks via random walks.
- Edge Betweenness: performs this algorithm by calculating the edge betweenness of the graph, removing the edge
+ Walktrap: This algorithm in graph theory, used to identify communities in large networks via random walks.
+ Edge Betweenness: performs this algorithm by calculating the edge betweenness of the graph, removing the edge
with the highest edge
betweenness score, then recalculating edge betweenness of the edges and again removing the one with the highest
score,
etc.
- Fast Greedy: This algorithm hierarchical approach, but it is bottom-up instead of top-down. It tries to optimize
+ Fast Greedy: This algorithm hierarchical approach, but it is bottom-up instead of top-down. It tries to optimize
a quality function
called modularity in a greedy manner.
- Label Propagation: This algoritm a semi-supervised machine learning algorithm that assigns labels to previously
+ Label Propagation: This algoritm a semi-supervised machine learning algorithm that assigns labels to previously
unlabeled data points. At the start of
the algorithm, a subset of the data points have labels (or classifications). These labels are
propagated to the unlabeled points throughout the course of the algorithm.
-
-
-
-
- Below, we briefly desribe the available network metrics for node scaling (6 ).
-
-
- The Degree metric describes the total number of connections adjacent to a node.
- The Clustering Coefficient of a node shows whether this node has the tendency to form clusters and is
- defined as the number of Edges between a node\'s neighbors divided by the number of all possible connections
+
+
+
+
+ Below, we briefly desribe the available network metrics for node scaling (6 ).
+
+
+ The Degree metric describes the total number of connections adjacent to a node.
+ The Clustering Coefficient of a node shows whether this node has the tendency to form clusters and is
+ defined as the number of Edges between a node's neighbors divided by the number of all possible connections
between these neighbors.
The Betweenness Centrality highlights nodes which can act as mediators in order for two communities to
communicate with each other.
@@ -687,8 +500,8 @@ Tn Group7 4
Scene Actions
In this tab, the user has 2 scene-related options.
-
+
1. A checkbox that toggles the visibility of the scene coordinates system.
2. A checkbox that enables scene auto rotate. (The user must enable it and then
from the Navigation Controls click the arrows to rotate).
@@ -699,19 +512,19 @@ Tn Group7 4
be sure to set higher opacity values for layer floors (Layer Actions tab) and edges (Edge Actions tab).
5. A button to see the network in VR. Works only in the online version of the
tool.
-
-
-
-
-
-
-
-
-
- A dedicated theme bar is also offered on the top-right corner of the UI, allowing the user to choose among a
- Light, a Dark and a Gray mode.
-
-
+
+
+
+
+
+
+
+
+
+ A dedicated theme bar is also offered on the top-right corner of the UI, allowing the user to choose among a
+ Light, a Dark and a Gray mode.
+
+
@@ -720,8 +533,8 @@ Tn Group7 4
Layers
This control panel incorporates layer-related actions.
-
+
1. This checkbox allows the user to show or hide all layer labels.
2. This checkbox gives the option of showing the labels of selected layers only.
Option (1 ) has priority over this option.
@@ -742,8 +555,8 @@ Tn Group7 4
Nodes
This control panel incorporates node-related actions.
-
+
1. This options allows the user to select/deselect all nodes. Selected nodes can
then be translated in 3D space via the Navigation Controls , and via the Layer Selection & Layouts
action tab can be either rearranged in a local layout or rescaled based on network metrics.
@@ -765,8 +578,8 @@ Tn Group7 4
-
+
@@ -833,8 +646,8 @@ Tn Group7 4
FPS
The option for frames per second. The user is allowed to choose between 3 options:
-
+
• 15FPS, for larger, more processing-heavy networks.
• 30FPS, the default option.
• 60FPS, for smaller networks that allow smoother rendering.
@@ -852,9 +665,9 @@ Tn Group7 4
• Georgios A. Pavlopoulos, email: pavlopoulos@fleming.gr
- Install Arena3Dweb
+ Install Arena3D
- Github
+ Github
DockerHub
@@ -866,7 +679,7 @@ Tn Group7 4
• NAP(v2.0): The Network Analysis Profiler
-
+
Related Literature
• A Guide to
@@ -874,7 +687,7 @@ Tn Group7 4
- Cite Arena3Dweb
+ Cite Arena3D
- Kokoli, M., Karatzas, E., Baltoumas, F.A., Schneider, R., Pafilis, E., Paragkamian, S., Doncheva, N.T., Jensen, L.J.
and Pavlopoulos, G., 2022.
@@ -888,33 +701,41 @@ Tn Group7 4
Nucleic Acids
Research, 49(W1), pp.W36-W45.
doi: https://doi.org/10.1093/nar/gkab278 ; PubMed: 33885790
-
-
+
+`
+
+function openHelpTab(
+ pane: HTMLElement,
+ tabName: string,
+ btn: HTMLElement
+): void {
+ pane
+ .querySelectorAll('.tabcontent')
+ .forEach((el) => (el.style.display = 'none'))
+ pane
+ .querySelectorAll('.tablinks')
+ .forEach((el) => el.classList.remove('active'))
+ const target = pane.querySelector(`#${tabName}`)
+ if (target) target.style.display = 'inline-block'
+ btn.classList.add('active')
+}
-
-
- '),
- generateFooter()
- )
+export function initHelpPanel(): void {
+ const pane = document.getElementById('panel-help')
+ if (!pane) return
+ pane.innerHTML = HELP_HTML
+ const buttons = pane.querySelectorAll('.tablinks')
+ buttons.forEach((btn) => {
+ btn.addEventListener('click', () => {
+ const tabName = btn.dataset.helpTab
+ if (tabName) openHelpTab(pane, tabName, btn)
+ })
+ })
+ // v2 default: open the Examples tab.
+ const first = buttons[0]
+ if (first?.dataset.helpTab) openHelpTab(pane, first.dataset.helpTab, first)
}
diff --git a/frontend/src/ui/home.ts b/frontend/src/ui/home.ts
new file mode 100644
index 0000000..6d13673
--- /dev/null
+++ b/frontend/src/ui/home.ts
@@ -0,0 +1,79 @@
+// Home panel — port of v2 views/home.R.
+
+import { showTab } from './tabs'
+
+const HOME_HTML = `
+
+
+
+
+
+
+
+
+
+ Arena3D is the first fully interactive, dependency-free web application for
+ visualizing multi-layered graphs in 3D space. Integrate multiple networks in a single view
+ with their intra- and inter-layer connections, apply a wide range of layout algorithms to
+ selected layers individually or in combination, and highlight topological features. Layers
+ and the whole scene can be translated, rotated, and scaled, while node position, color, and
+ size adjust on-the-fly. The current version supports weighted and unweighted undirected graphs.
+
+
+
+
+
+
+
+ Please cite Arena3D
+
+ Kokoli, M., Karatzas, E., Baltoumas, F.A., Schneider, R., Pafilis, E., Paragkamian, S., Doncheva, N.T., Jensen, L.J.
+ and Pavlopoulos, G., 2022.
+ Arena3Dweb : interactive 3D visualization of multilayered networks
+ supporting multiple directional information channels, clustering analysis and application integration.
+ NAR Genomics and Bioinformatics, 5(2), p.lqad053.
+ doi: 10.1093/nargab/lqad053
+ ·
+ PubMed: 37260509
+
+
+ Karatzas, E., Baltoumas, F.A., Panayiotou, N.A., Schneider, R. and Pavlopoulos, G.A., 2021.
+ Arena3Dweb : Interactive 3D visualization of multilayered networks.
+ Nucleic Acids Research, 49(W1), pp.W36-W45.
+ doi: 10.1093/nar/gkab278
+ ·
+ PubMed: 33885790
+
+
+
+`
+
+export function initHomePanel(): void {
+ const pane = document.getElementById('panel-home')
+ if (!pane) return
+ pane.innerHTML = HOME_HTML
+ document
+ .getElementById('link_to_examples')
+ ?.addEventListener('click', (e) => {
+ e.preventDefault()
+ showTab('#panel-help')
+ })
+ document
+ .getElementById('link_to_fileInput')
+ ?.addEventListener('click', (e) => {
+ e.preventDefault()
+ showTab('#panel-file')
+ })
+}
diff --git a/frontend/src/ui/layer.ts b/frontend/src/ui/layer.ts
new file mode 100644
index 0000000..f03a4c7
--- /dev/null
+++ b/frontend/src/ui/layer.ts
@@ -0,0 +1,110 @@
+// Layer Actions panel — port of v2 views/layer.R plus the floor color picker
+// v2 injected from on_page_load.js initializeColorPickers.
+
+import { LAYER_DEFAULT_COLOR } from '../three/constants'
+import { showLayerLabels, resizeLayerLabels } from '../actions/labels'
+import {
+ showLayerCoords,
+ showWireFrames,
+ setFloorOpacity,
+ setLayerColorPriority,
+} from '../actions/layer'
+import { history } from '../commands/base'
+import { ChangeFloorColorCommand } from '../commands/scene'
+
+const LAYER_HTML = `
+
+
Show Labels:
+
+
+
+ Show Layer Coord Systems
+
+
+
+ Show Floors in Wireframes
+
+
+ Resize Labels:
+
+
+
+ Floor Opacity:
+
+
+
Color Priority:
+
+
+
+ Floor Color
+
+
+`
+
+export function initLayerPanel(): void {
+ const pane = document.getElementById('panel-layer')
+ if (!pane) return
+ pane.innerHTML = LAYER_HTML
+
+ for (const radio of document.querySelectorAll(
+ 'input[name="showLayerLabelsRadio"]'
+ )) {
+ radio.addEventListener('change', () => {
+ showLayerLabels(radio.value as 'all' | 'selected' | 'none')
+ })
+ }
+
+ document
+ .getElementById('showLayerCoords')
+ ?.addEventListener('change', (e) => {
+ showLayerCoords((e.target as HTMLInputElement).checked)
+ })
+ document.getElementById('showWireFrames')?.addEventListener('change', (e) => {
+ showWireFrames((e.target as HTMLInputElement).checked)
+ })
+ document
+ .getElementById('resizeLayerLabels')
+ ?.addEventListener('input', (e) => {
+ resizeLayerLabels(Number((e.target as HTMLInputElement).value))
+ })
+ document.getElementById('layerOpacity')?.addEventListener('input', (e) => {
+ setFloorOpacity(Number((e.target as HTMLInputElement).value))
+ })
+
+ for (const radio of document.querySelectorAll(
+ 'input[name="layerColorPriorityRadio"]'
+ )) {
+ radio.addEventListener('change', () => {
+ setLayerColorPriority(radio.value as 'default' | 'picker')
+ })
+ }
+
+ // v2 repaintLayersFromPicker: picking a color switches priority to picker.
+ document.getElementById('floor_color')?.addEventListener('change', (e) => {
+ history.execute(
+ new ChangeFloorColorCommand((e.target as HTMLInputElement).value)
+ )
+ })
+}
diff --git a/frontend/src/ui/layouts.ts b/frontend/src/ui/layouts.ts
new file mode 100644
index 0000000..d51a5aa
--- /dev/null
+++ b/frontend/src/ui/layouts.ts
@@ -0,0 +1,274 @@
+// Layer Selection & Layouts panel — port of v2 views/layouts.R, the layer
+// checkbox group (www/js/object_actions/layer.js attachLayerCheckboxes) and
+// the channel layout list (edge.js attachChannelLayoutList).
+
+import { store } from '../store'
+import { bus } from '../bus'
+import { ctx } from '../three'
+import { applyLayout, applyTopology } from '../actions/layout'
+import { startLoader, finishLoader } from '../actions/screen'
+import {
+ selectLayer,
+ selectAllLayers,
+ setLayerVisibility,
+ setLayerNodeLabels,
+} from '../actions/layer'
+import { escapeHtml } from '../utils'
+import type { Scope } from '../api/client'
+
+// v2 views/layouts.R option lists (Davidson-Harel / GEM / Edge Betweenness
+// were commented out there too).
+const LAYOUT_ALGORITHMS = [
+ '-',
+ 'Fruchterman-Reingold',
+ 'Reingold-Tilford',
+ 'Circle',
+ 'Grid',
+ 'Random',
+ 'DrL',
+ 'Graphopt',
+ 'Kamada-Kawai',
+ 'Large Graph Layout',
+ 'Multidimensional Scaling',
+ 'Sugiyama',
+]
+const CLUSTERING_ALGORITHMS = [
+ '-',
+ 'Louvain',
+ 'Walktrap',
+ 'Fast Greedy',
+ 'Label Propagation',
+]
+
+const options = (values: string[]) =>
+ values.map((v) => `${v} `).join('')
+
+const LAYOUTS_HTML = `
+
+
+
+
+ Select/Deselect All Layers
+
+
+
+
+ Apply Layout Algorithm on Selected Layers:
+ ${options(LAYOUT_ALGORITHMS)}
+
+
+ Apply Clustering on Selected Layers (Optional):
+ ${options(CLUSTERING_ALGORITHMS)}
+
+
+ Apply Local Layout Algorithm:
+ ${options(LAYOUT_ALGORITHMS)}
+
+
Run
+
+ Scale Nodes of Selected Layers by Topology Metric:
+
+
+
Run
+
+
+`
+
+function status(msg: string, isError = false): void {
+ const el = document.getElementById('layouts_status')
+ if (!el) return
+ el.textContent = msg
+ el.className = `small ${isError ? 'text-danger' : 'text-success'}`
+}
+
+// v2 attachLayerCheckboxes: per layer a select checkbox (labelled with the
+// layer name) plus Hide and Labels checkboxes.
+// XSS fix: layer.name comes straight from an uploaded TSV/session file, so it
+// must go through escapeHtml() before landing in the innerHTML template below.
+function buildLayerCheckboxes(): void {
+ const container = document.getElementById('checkboxdiv')
+ if (!container) return
+ container.innerHTML = ''
+ ctx.layers.forEach((layer, i) => {
+ const row = document.createElement('div')
+ row.className = 'layer-row'
+ row.innerHTML = `
+
+ ${escapeHtml(layer.name)}
+
+ Hide
+
+ Labels
+ `
+ const [sel, hide, labels] = row.querySelectorAll('input')
+ sel.addEventListener('change', () => selectLayer(i, sel.checked))
+ hide.addEventListener('change', () => setLayerVisibility(i, !hide.checked))
+ labels.addEventListener('change', () =>
+ setLayerNodeLabels(i, labels.checked)
+ )
+ container.appendChild(row)
+ })
+}
+
+// v2 attachChannelLayoutList, minus the collapse toggle — all channels
+// checked by default; checked ones are sent as selected_channels.
+// XSS fix: channel names are user-controlled (TSV Channel column) — same
+// escapeHtml() treatment as buildLayerCheckboxes above.
+function buildChannelList(): void {
+ const container = document.getElementById('channelColorLayoutDiv')
+ if (!container) return
+ container.innerHTML = ''
+ const channels = store.get().network?.channels ?? []
+ if (channels.length === 0) return
+ const title = document.createElement('label')
+ title.className = 'form-label'
+ title.textContent = 'Select Channels for Layouts'
+ container.appendChild(title)
+ for (const ch of channels) {
+ const row = document.createElement('div')
+ row.className = 'form-check'
+ row.innerHTML = `
+
+ ${escapeHtml(ch)}
+ `
+ container.appendChild(row)
+ }
+}
+
+function selectedChannels(): string[] | null {
+ const boxes = document.querySelectorAll(
+ '#channelColorLayoutDiv .channel_checkbox'
+ )
+ if (boxes.length === 0) return null
+ return [...boxes]
+ .filter((b) => b.checked)
+ .map((b) => b.id.replace('checkbox_layout', ''))
+}
+
+function currentScope(): Scope {
+ const checked = document.querySelector(
+ 'input[name="subgraphChoice"]:checked'
+ )
+ return (checked?.value ?? 'perLayer') as Scope
+}
+
+async function onRunLayout(): Promise {
+ const st = store.get()
+ if (!st.network) return status('Upload a network first.', true)
+ if (st.selectedLayers.length === 0)
+ return status('Select at least one layer.', true)
+
+ const scope = currentScope()
+ const algorithm =
+ scope === 'nodesPerLayers'
+ ? (
+ document.getElementById(
+ 'localLayoutAlgorithmChoice'
+ ) as HTMLSelectElement
+ ).value
+ : (document.getElementById('layoutAlgorithmChoice') as HTMLSelectElement)
+ .value
+ if (algorithm === '-') return status('Select a layout algorithm.', true)
+
+ const clusteringAlg = (
+ document.getElementById('clusteringAlgorithmChoice') as HTMLSelectElement
+ ).value
+ startLoader()
+ try {
+ await applyLayout({
+ nodes: st.network.nodes,
+ edges: st.network.edges,
+ algorithm,
+ scope,
+ selected_layers: st.selectedLayers,
+ selected_nodes: scope === 'nodesPerLayers' ? st.selectedNodes : null,
+ selected_channels: selectedChannels(),
+ clustering:
+ clusteringAlg !== '-'
+ ? { algorithm: clusteringAlg, local_layout: algorithm }
+ : null,
+ })
+ status('Layout applied.')
+ } catch (err) {
+ status(err instanceof Error ? err.message : 'Layout failed.', true)
+ } finally {
+ finishLoader()
+ }
+}
+
+async function onRunTopology(): Promise {
+ const st = store.get()
+ if (!st.network) return status('Upload a network first.', true)
+ if (st.selectedLayers.length === 0)
+ return status('Select at least one layer.', true)
+ const metric = (
+ document.getElementById('topologyScaleMetricChoice') as HTMLSelectElement
+ ).value
+ if (metric === '-') return status('Select a topology metric.', true)
+ startLoader()
+ try {
+ await applyTopology({
+ nodes: st.network.nodes,
+ edges: st.network.edges,
+ metric,
+ scope: currentScope(),
+ selected_layers: st.selectedLayers,
+ selected_channels: selectedChannels(),
+ })
+ status('Node scaling applied.')
+ } catch (err) {
+ status(
+ err instanceof Error ? err.message : 'Topology scaling failed.',
+ true
+ )
+ } finally {
+ finishLoader()
+ }
+}
+
+export function initLayoutsPanel(): void {
+ const pane = document.getElementById('panel-layouts')
+ if (!pane) return
+ pane.innerHTML = LAYOUTS_HTML
+
+ const metricSel = document.getElementById(
+ 'topologyScaleMetricChoice'
+ ) as HTMLSelectElement
+ metricSel.innerHTML = options([
+ '-',
+ ...(store.get().config?.topology_metrics ?? []),
+ ])
+
+ document
+ .getElementById('selectAllLayersCheckbox')
+ ?.addEventListener('change', (e) => {
+ selectAllLayers((e.target as HTMLInputElement).checked)
+ buildLayerCheckboxes()
+ })
+ document.getElementById('runLayout')?.addEventListener('click', () => {
+ void onRunLayout()
+ })
+ document.getElementById('runTopologyScale')?.addEventListener('click', () => {
+ void onRunTopology()
+ })
+
+ bus.on('network:loaded', () => {
+ buildLayerCheckboxes()
+ buildChannelList()
+ status('')
+ })
+}
diff --git a/frontend/src/ui/minimap.test.ts b/frontend/src/ui/minimap.test.ts
new file mode 100644
index 0000000..0be1ad9
--- /dev/null
+++ b/frontend/src/ui/minimap.test.ts
@@ -0,0 +1,45 @@
+import { describe, it, expect } from 'vitest'
+import { computeFit, project, unproject } from './minimap'
+
+describe('minimap projection', () => {
+ it('centers a symmetric bbox at the canvas middle', () => {
+ const fit = computeFit([
+ { x: -10, y: -10 },
+ { x: 10, y: 10 },
+ ])
+ expect(fit.cx).toBe(0)
+ expect(fit.cy).toBe(0)
+ const c = project(0, 0, fit)
+ expect(c.x).toBeCloseTo(100) // W/2
+ expect(c.y).toBeCloseTo(75) // H/2
+ })
+
+ it('flips Y (world up -> canvas up)', () => {
+ const fit = computeFit([
+ { x: -10, y: -10 },
+ { x: 10, y: 10 },
+ ])
+ const top = project(0, 10, fit)
+ const bottom = project(0, -10, fit)
+ expect(top.y).toBeLessThan(bottom.y)
+ })
+
+ it('project/unproject round-trip', () => {
+ const fit = computeFit([
+ { x: -5, y: -3 },
+ { x: 7, y: 9 },
+ ])
+ const p = project(2, 4, fit)
+ const back = unproject(p.x, p.y, fit)
+ expect(back.x).toBeCloseTo(2)
+ expect(back.y).toBeCloseTo(4)
+ })
+
+ it('degenerate input does not divide by zero', () => {
+ const fit = computeFit([{ x: 5, y: 5 }])
+ expect(Number.isFinite(fit.scale)).toBe(true)
+ const p = project(5, 5, fit)
+ expect(Number.isFinite(p.x)).toBe(true)
+ expect(Number.isFinite(p.y)).toBe(true)
+ })
+})
diff --git a/frontend/src/ui/minimap.ts b/frontend/src/ui/minimap.ts
new file mode 100644
index 0000000..be99549
--- /dev/null
+++ b/frontend/src/ui/minimap.ts
@@ -0,0 +1,250 @@
+// 2D navigator (satellite view), Cytoscape-style. A small canvas in the
+// bottom-right showing the whole network's current 2D projection: node dots +
+// edges, plus a rectangle marking what the main view currently sees. Click /
+// drag the map to pan the 3D view there.
+//
+// Why it tracks scene rotation for free: node.getWorldPosition() already carries
+// pan-translate, pan-scale and sphere-rotation. Stripping only the pan
+// transform (pan has no rotation) leaves rotation-only coords — the exact
+// monitor XY projection, but stable under pan/zoom so the thumbnail doesn't
+// drift while you navigate.
+
+import * as THREE from 'three'
+import { ctx } from '../three'
+import { registerAnimateHook } from '../actions/screen'
+import { bus } from '../bus'
+import { history } from '../commands/base'
+import { captureTransforms, TransformCommand } from '../commands/scene'
+import type { TransformSnapshot } from '../commands/scene'
+
+const W = 200
+const H = 150
+const PAD = 8
+
+// --- pure projection math (unit-tested) ----------------------------------
+
+export interface Fit {
+ scale: number
+ cx: number
+ cy: number
+}
+
+interface Pt {
+ x: number
+ y: number
+}
+
+// Map local-space points into the W×H canvas: uniform scale to fit the bbox
+// with PAD margin, centered. Empty/degenerate input -> scale 1 at origin.
+export function computeFit(points: Pt[]): Fit {
+ if (points.length === 0) return { scale: 1, cx: 0, cy: 0 }
+ let minX = Infinity
+ let maxX = -Infinity
+ let minY = Infinity
+ let maxY = -Infinity
+ for (const p of points) {
+ if (p.x < minX) minX = p.x
+ if (p.x > maxX) maxX = p.x
+ if (p.y < minY) minY = p.y
+ if (p.y > maxY) maxY = p.y
+ }
+ const spanX = maxX - minX || 1
+ const spanY = maxY - minY || 1
+ const scale = Math.min((W - 2 * PAD) / spanX, (H - 2 * PAD) / spanY)
+ return { scale, cx: (minX + maxX) / 2, cy: (minY + maxY) / 2 }
+}
+
+// local -> canvas px (canvas Y grows downward, world Y upward -> flip).
+export function project(x: number, y: number, f: Fit): Pt {
+ return { x: W / 2 + (x - f.cx) * f.scale, y: H / 2 - (y - f.cy) * f.scale }
+}
+
+// canvas px -> local (inverse of project).
+export function unproject(px: number, py: number, f: Fit): Pt {
+ return { x: f.cx + (px - W / 2) / f.scale, y: f.cy - (py - H / 2) / f.scale }
+}
+
+// --- module state --------------------------------------------------------
+
+let enabled = true
+let container: HTMLDivElement | null = null
+let canvas: HTMLCanvasElement | null = null
+let g: CanvasRenderingContext2D | null = null
+let hasNetwork = false
+
+let dragging = false
+let dragBefore: TransformSnapshot | null = null
+
+// --- rendering -----------------------------------------------------------
+
+const _v = new THREE.Vector3()
+const _inv = new THREE.Matrix4()
+
+function layerVisible(nodeLayerName: string): boolean {
+ const i = ctx.layerGroups[ctx.nodeGroups[nodeLayerName]]
+ return ctx.layers[i]?.isVisible ?? true
+}
+
+// Project every node to pan-local (rotation-only) space, keyed by node index
+// so edges can reuse the points. Nodes on hidden layers become null: they drop
+// out of the fit, the dot loop, and (via the guard) any edge touching them.
+// Refreshes pan.matrixWorld into _inv as a side effect. ponytail: O(nodes)
+// every frame; fine for typical nets, gate behind a dirty-flag if huge.
+function projectNodes(): (Pt | null)[] {
+ ctx.scene!.pan!.updateWorldMatrix(true, false)
+ _inv.copy(ctx.scene!.pan!.matrixWorld).invert()
+ return ctx.nodeObjects.map((n) => {
+ if (!layerVisible(n.nodeLayerName)) return null
+ _v.copy(n.getWorldPosition()).applyMatrix4(_inv)
+ return { x: _v.x, y: _v.y }
+ })
+}
+
+function draw(): void {
+ if (!enabled || !hasNetwork || !g || !canvas) return
+ const scene = ctx.scene
+ if (!scene?.pan || ctx.nodeObjects.length === 0) return
+
+ const local = projectNodes()
+ const fit = computeFit(local.filter((p): p is Pt => p !== null))
+
+ g.clearRect(0, 0, W, H)
+
+ // edges (faint) first, dots on top
+ g.strokeStyle = 'rgba(128,128,128,0.35)'
+ g.lineWidth = 0.5
+ g.beginPath()
+ for (const e of ctx.edgeObjects) {
+ const a = local[e.sourceNodeIndex]
+ const b = local[e.targetNodeIndex]
+ if (!a || !b) continue
+ const pa = project(a.x, a.y, fit)
+ const pb = project(b.x, b.y, fit)
+ g.moveTo(pa.x, pa.y)
+ g.lineTo(pb.x, pb.y)
+ }
+ g.stroke()
+
+ for (let i = 0; i < ctx.nodeObjects.length; i++) {
+ const pt = local[i]
+ if (!pt) continue
+ const p = project(pt.x, pt.y, fit)
+ g.fillStyle = ctx.nodeObjects[i].getColor()
+ g.beginPath()
+ g.arc(p.x, p.y, 1.5, 0, Math.PI * 2)
+ g.fill()
+ }
+
+ drawViewportBox(fit)
+}
+
+// The visible world window is [-xBoundMax,xBoundMax]×[-yBoundMax,yBoundMax].
+// In pan-local space it stays axis-aligned (pan has no rotation), so two
+// opposite corners define the rectangle.
+function drawViewportBox(fit: Fit): void {
+ if (!g) return
+ const c1 = _v.set(ctx.xBoundMin, ctx.yBoundMin, 0).applyMatrix4(_inv).clone()
+ const c2 = _v.set(ctx.xBoundMax, ctx.yBoundMax, 0).applyMatrix4(_inv).clone()
+ const p1 = project(c1.x, c1.y, fit)
+ const p2 = project(c2.x, c2.y, fit)
+ const x = Math.min(p1.x, p2.x)
+ const y = Math.min(p1.y, p2.y)
+ g.strokeStyle = 'rgba(230,180,60,0.9)'
+ g.lineWidth = 1
+ g.strokeRect(x, y, Math.abs(p2.x - p1.x), Math.abs(p2.y - p1.y))
+}
+
+// --- interaction: click/drag the map -> pan the 3D view ------------------
+
+// Pan so the clicked local point lands at screen center (world origin):
+// localToWorld(l) = panPos + panScale*l, set = 0 -> panPos = -panScale*l.
+function panToCanvasPoint(px: number, py: number): void {
+ const scene = ctx.scene
+ if (!scene?.pan) return
+ const fit = computeFit(projectNodes().filter((p): p is Pt => p !== null))
+ const l = unproject(px, py, fit)
+ const s = scene.getScale()
+ scene.setPosition('x', -s * l.x)
+ scene.setPosition('y', -s * l.y)
+ ctx.renderInterLayerEdgesFlag = true
+ ctx.renderLayerLabelsFlag = true
+ ctx.renderNodeLabelsFlag = true
+}
+
+function canvasPoint(e: MouseEvent): { px: number; py: number } {
+ const r = canvas!.getBoundingClientRect()
+ return {
+ px: ((e.clientX - r.left) / r.width) * W,
+ py: ((e.clientY - r.top) / r.height) * H,
+ }
+}
+
+function onDown(e: MouseEvent): void {
+ if (!hasNetwork || !ctx.scene?.exists()) return
+ e.preventDefault()
+ dragging = true
+ dragBefore = captureTransforms()
+ const { px, py } = canvasPoint(e)
+ panToCanvasPoint(px, py)
+}
+
+function onMove(e: MouseEvent): void {
+ if (!dragging) return
+ const { px, py } = canvasPoint(e)
+ panToCanvasPoint(px, py)
+}
+
+function onUp(): void {
+ if (!dragging) return
+ dragging = false
+ if (dragBefore) {
+ history.execute(
+ new TransformCommand(
+ 'Navigate (minimap)',
+ dragBefore,
+ captureTransforms()
+ )
+ )
+ dragBefore = null
+ }
+}
+
+// --- setup ---------------------------------------------------------------
+
+function applyVisibility(): void {
+ if (container)
+ container.style.display = enabled && hasNetwork ? 'block' : 'none'
+}
+
+export function setMinimapVisible(on: boolean): void {
+ enabled = on
+ applyVisibility()
+}
+
+export function initMinimap(): void {
+ container = document.createElement('div')
+ container.id = 'minimap'
+ container.style.display = 'none'
+ canvas = document.createElement('canvas')
+ const dpr = Math.min(window.devicePixelRatio || 1, 2)
+ canvas.width = W * dpr
+ canvas.height = H * dpr
+ canvas.style.width = `${W}px`
+ canvas.style.height = `${H}px`
+ container.appendChild(canvas)
+ document.body.appendChild(container)
+
+ g = canvas.getContext('2d')
+ g?.scale(dpr, dpr)
+
+ canvas.addEventListener('mousedown', onDown)
+ window.addEventListener('mousemove', onMove)
+ window.addEventListener('mouseup', onUp)
+
+ bus.on('network:loaded', () => {
+ hasNetwork = true
+ applyVisibility()
+ })
+
+ registerAnimateHook(draw)
+}
diff --git a/frontend/src/ui/node.ts b/frontend/src/ui/node.ts
new file mode 100644
index 0000000..3903561
--- /dev/null
+++ b/frontend/src/ui/node.ts
@@ -0,0 +1,142 @@
+// Node Actions panel — port of v2 views/node.R (search-bar Enter handling
+// from on_page_load.js selectSearchedNodes).
+
+import { showNodeLabels, resizeNodeLabels } from '../actions/labels'
+import {
+ selectAllNodes,
+ selectNodesByName,
+ getNodeShape,
+ setNodeColorPriority,
+ setNodeSelectedColorPriority,
+} from '../actions/node'
+import type { NodeGeometryType } from '../three/Node'
+import { history } from '../commands/base'
+import { ChangeNodeGeometryCommand } from '../commands/scene'
+
+const NODE_HTML = `
+
+
+
+ Select / Deselect all Nodes
+
+
Show Labels:
+
+
+ Resize Labels:
+
+
+
Node Geometry:
+
+
Color Priority:
+
+
+
+ Highlight Selected Nodes
+
+
+ Search Nodes:
+
+
+
+`
+
+export function initNodePanel(): void {
+ const pane = document.getElementById('panel-node')
+ if (!pane) return
+ pane.innerHTML = NODE_HTML
+
+ document.getElementById('selectAllNodes')?.addEventListener('change', (e) => {
+ selectAllNodes((e.target as HTMLInputElement).checked)
+ })
+
+ for (const radio of document.querySelectorAll(
+ 'input[name="showNodeLabelsRadio"]'
+ )) {
+ radio.addEventListener('change', () => {
+ showNodeLabels(radio.value as 'all' | 'selected' | 'none')
+ })
+ }
+
+ document
+ .getElementById('resizeNodeLabels')
+ ?.addEventListener('input', (e) => {
+ resizeNodeLabels(Number((e.target as HTMLInputElement).value))
+ })
+
+ for (const radio of document.querySelectorAll(
+ 'input[name="nodeGeometryRadio"]'
+ )) {
+ radio.addEventListener('change', () => {
+ history.execute(
+ new ChangeNodeGeometryCommand(
+ radio.value as NodeGeometryType,
+ getNodeShape()
+ )
+ )
+ })
+ }
+
+ for (const radio of document.querySelectorAll(
+ 'input[name="nodeColorPriorityRadio"]'
+ )) {
+ radio.addEventListener('change', () => {
+ setNodeColorPriority(radio.value as 'default' | 'cluster')
+ })
+ }
+
+ document
+ .getElementById('nodeSelectedColorPriority')
+ ?.addEventListener('change', (e) => {
+ setNodeSelectedColorPriority((e.target as HTMLInputElement).checked)
+ })
+
+ const searchBar = document.getElementById(
+ 'nodeSearchBar'
+ ) as HTMLTextAreaElement
+ searchBar.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault() // v2: bypass the newline
+ selectNodesByName(searchBar.value.replace(/\n/g, ''))
+ }
+ })
+}
diff --git a/frontend/src/ui/scene.ts b/frontend/src/ui/scene.ts
new file mode 100644
index 0000000..748871d
--- /dev/null
+++ b/frontend/src/ui/scene.ts
@@ -0,0 +1,115 @@
+// Scene Actions panel — port of v2 views/scene.R plus the scene color picker
+// v2 injected from on_page_load.js initializeColorPickers. The VR button is
+// dropped (functions/vr.R deleted, PLAN Phase 7).
+
+import { MathUtils } from 'three'
+import { ctx } from '../three'
+import { registerAnimateHook } from '../actions/screen'
+import { history } from '../commands/base'
+import {
+ ChangeBackgroundColorCommand,
+ PredefinedLayoutCommand,
+} from '../commands/scene'
+import { isBloomEnabled, setBloomEnabled } from '../three/postprocessing'
+import { setMinimapVisible } from '../ui/minimap'
+import { applyPredefinedLayout, type PredefinedLayout } from '../actions/layout'
+
+const SCENE_HTML = `
+
+`
+
+export function initScenePanel(): void {
+ const pane = document.getElementById('panel-scene')
+ if (!pane) return
+ pane.innerHTML = SCENE_HTML
+
+ document
+ .getElementById('toggleSceneCoords')
+ ?.addEventListener('change', (e) => {
+ ctx.scene?.toggleCoords((e.target as HTMLInputElement).checked)
+ })
+
+ // ponytail: v2 auto-rotate kept the canvas-button rotation interval alive;
+ // those buttons aren't ported, so this is a steady Y spin in the render loop.
+ document
+ .getElementById('autoRotateScene')
+ ?.addEventListener('change', (e) => {
+ if (ctx.scene)
+ ctx.scene.autoRotate = (e.target as HTMLInputElement).checked
+ })
+ registerAnimateHook(() => {
+ if (ctx.scene?.autoRotate) ctx.scene.rotateY(MathUtils.degToRad(0.5))
+ })
+
+ const bloomToggle = document.getElementById(
+ 'toggleBloom'
+ ) as HTMLInputElement | null
+ if (bloomToggle) {
+ bloomToggle.checked = isBloomEnabled()
+ bloomToggle.addEventListener('change', (e) => {
+ setBloomEnabled((e.target as HTMLInputElement).checked)
+ })
+ }
+
+ document.getElementById('toggleMinimap')?.addEventListener('change', (e) => {
+ setMinimapVisible((e.target as HTMLInputElement).checked)
+ })
+
+ document.getElementById('scene_color')?.addEventListener('change', (e) => {
+ history.execute(
+ new ChangeBackgroundColorCommand((e.target as HTMLInputElement).value)
+ )
+ })
+
+ // v2 observeEvent(input$predefined_layout, ignoreInit = T): applied on
+ // change, not on the initially-selected value.
+ for (const radio of document.querySelectorAll(
+ 'input[name="predefined_layout"]'
+ )) {
+ radio.addEventListener('change', () => {
+ history.execute(
+ new PredefinedLayoutCommand(radio.value, () =>
+ applyPredefinedLayout(radio.value as PredefinedLayout)
+ )
+ )
+ })
+ }
+}
diff --git a/frontend/src/ui/tabs.ts b/frontend/src/ui/tabs.ts
new file mode 100644
index 0000000..fe73a66
--- /dev/null
+++ b/frontend/src/ui/tabs.ts
@@ -0,0 +1,7 @@
+import { Tab } from 'bootstrap'
+
+/** Programmatically activate a navbar tab by its pane selector, e.g. '#panel-file'. */
+export function showTab(paneSelector: string): void {
+ const trigger = document.querySelector(`[data-bs-target="${paneSelector}"]`)
+ if (trigger) Tab.getOrCreateInstance(trigger).show()
+}
diff --git a/frontend/src/utils.test.ts b/frontend/src/utils.test.ts
new file mode 100644
index 0000000..98cb799
--- /dev/null
+++ b/frontend/src/utils.test.ts
@@ -0,0 +1,51 @@
+import { describe, expect, it } from 'vitest'
+import {
+ escapeHtml,
+ exists,
+ getUniqueValues,
+ getCaseInsensitiveIndices,
+ findIndexByUuid,
+ getRandomArbitrary,
+ toRadians,
+} from './utils'
+
+describe('utils', () => {
+ it('exists uses loose equality', () => {
+ expect(exists([1, 2, 3], 2)).toBe(true)
+ expect(exists(['a'], 'b')).toBe(false)
+ })
+
+ it('getUniqueValues dedupes preserving order', () => {
+ expect(getUniqueValues(['a', 'b', 'a', 'c'])).toEqual(['a', 'b', 'c'])
+ })
+
+ it('getCaseInsensitiveIndices finds all case-insensitive matches', () => {
+ expect(getCaseInsensitiveIndices(['Foo', 'bar', 'FOO'], 'foo')).toEqual([
+ 0, 2,
+ ])
+ })
+
+ it('findIndexByUuid returns position or -1', () => {
+ const arr = [{ uuid: 'x' }, { uuid: 'y' }]
+ expect(findIndexByUuid(arr, 'y')).toBe(1)
+ expect(findIndexByUuid(arr, 'z')).toBe(-1)
+ })
+
+ it('getRandomArbitrary stays within [min, max)', () => {
+ for (let i = 0; i < 100; i++) {
+ const r = getRandomArbitrary(5, 10)
+ expect(r).toBeGreaterThanOrEqual(5)
+ expect(r).toBeLessThan(10)
+ }
+ })
+
+ it('toRadians converts degrees', () => {
+ expect(toRadians(180)).toBeCloseTo(Math.PI)
+ })
+
+ it('escapeHtml escapes all five HTML metacharacters incl. single quote', () => {
+ expect(escapeHtml(`&" `)).toBe(
+ '<a href='x'>&"</a>'
+ )
+ })
+})
diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts
new file mode 100644
index 0000000..e333007
--- /dev/null
+++ b/frontend/src/utils.ts
@@ -0,0 +1,69 @@
+// General utilities — port of v2 www/js/general.js. Pure, framework-free.
+
+// XSS fix: escapes HTML metacharacters so user-controlled strings
+// (layer/channel names from uploaded network files) can't break out of an
+// innerHTML template. Shared by ui/data.ts, ui/edge.ts, ui/layouts.ts.
+export function escapeHtml(v: string | number): string {
+ return String(v)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+}
+
+export function exists(array: T[], element: T): boolean {
+ return array.some((l) => l == element)
+}
+
+export function getUniqueValues(array: T[]): T[] {
+ return [...new Set(array)]
+}
+
+export function getCaseInsensitiveIndices(
+ array: string[],
+ element: string
+): number[] {
+ const target = element.toLowerCase()
+ const indexes: number[] = []
+ for (let i = 0; i < array.length; i++)
+ if (array[i].toLowerCase() === target) indexes.push(i)
+ return indexes
+}
+
+// Position of a Three.js object's uuid in an array (-1 if absent).
+export function findIndexByUuid(
+ array: { uuid: string }[],
+ uuid: string
+): number {
+ return array.findIndex((object) => object.uuid === uuid)
+}
+
+// Seeded PRNG (mulberry32) so a given network always scatters to the same
+// layout — Load Example and uploads are reproducible instead of random each
+// time. Call setRandomSeed() to restart the sequence. Mirrors the backend's
+// random.seed(123) for layout algorithms.
+let rngState = 123 >>> 0
+
+export function setRandomSeed(seed: number): void {
+ rngState = seed >>> 0
+}
+
+function seededRandom(): number {
+ rngState = (rngState + 0x6d2b79f5) | 0
+ let t = Math.imul(rngState ^ (rngState >>> 15), 1 | rngState)
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296
+}
+
+// Random float in [min, max) from the seeded sequence.
+export function getRandomArbitrary(min: number, max: number): number {
+ return seededRandom() * (max - min) + min
+}
+
+export function toRadians(angle: number): number {
+ return angle * (Math.PI / 180)
+}
+
+export const delay_ms = (ms: number): Promise =>
+ new Promise((res) => setTimeout(res, ms))
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/frontend/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..9bb92fc
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "sourceMap": true,
+ "noEmit": true,
+ "skipLibCheck": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"]
+ },
+ "include": ["src"]
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 0000000..3a786b0
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,29 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ // Sub-path deployment fix: Vite emits root-absolute asset URLs (e.g.
+ // "/assets/x.js") by default, which 404 when the app is reverse-proxied
+ // under a path prefix (e.g. Apache's ProxyPass /arena3/ -> this server).
+ // Set via env so the prefix isn't baked into the repo per-deployment.
+ base: process.env.VITE_BASE_PATH ?? '/',
+ server: {
+ // Vite's dev-server Host-header check (anti DNS-rebinding) rejects any
+ // request whose Host doesn't match localhost/an IP/an allowed entry.
+ // Deployments reverse-proxying a real domain to this dev server need
+ // their hostname listed here — set via env so it's not baked into the
+ // repo. Comma-separated (e.g. "example.org,example.org:8080").
+ allowedHosts: process.env.VITE_ALLOWED_HOSTS
+ ? process.env.VITE_ALLOWED_HOSTS.split(',').map((h) => h.trim())
+ : undefined,
+ proxy: {
+ // VITE_PROXY_TARGET set by docker-compose (backend service hostname)
+ '/api': process.env.VITE_PROXY_TARGET ?? 'http://localhost:8000',
+ },
+ },
+ // Vitest owns src/*.test.ts; the e2e/ Playwright specs run via
+ // `npm run test:e2e` and must not be collected here (they import
+ // @playwright/test, which conflicts with Vitest's runner).
+ test: {
+ exclude: ['e2e/**', 'node_modules/**'],
+ },
+})
diff --git a/functions/edges.R b/functions/edges.R
deleted file mode 100644
index ed9feef..0000000
--- a/functions/edges.R
+++ /dev/null
@@ -1,40 +0,0 @@
-handleEdgeWidthByWeightCheckbox <- function() {
- tryCatch({
- if (input$edgeWidthByWeight){
- shinyjs::hide("intraLayerEdgeOpacity")
- shinyjs::hide("interLayerEdgeOpacity")
- } else {
- shinyjs::show("intraLayerEdgeOpacity")
- shinyjs::show("interLayerEdgeOpacity")
- }
- callJSHandler("handler_setEdgeWidthByWeight", input$edgeWidthByWeight)
- }, error = function(e) {
- print(paste0("Error in Edge Opacity: ", e))
- renderError("Error on Edge Opacity interface.")
- })
-}
-
-handleEdgeDirectionCheckbox <- function() {
- tryCatch({
- if (input$edgeDirectionToggle){
- shinyjs::show("intraDirectionArrowSize")
- shinyjs::show("interDirectionArrowSize")
- } else {
- shinyjs::hide("intraDirectionArrowSize")
- shinyjs::hide("interDirectionArrowSize")
- }
- callJSHandler("handler_toggleDirection", input$edgeDirectionToggle)
- }, error = function(e) {
- print(paste0("Error in Direction Size Arrow: ", e))
- renderError("Error on Direction interface.")
- })
-}
-
-updateSelectedEdgesView <- function (selectedEdgesDF) {
- if (length(selectedEdgesDF) > 0) {
- renderSelectedEdgesDF(selectedEdgesDF)
- } else {
- renderShinyDataTable("selectedEdges_dataView", data.frame())
- hideTab(inputId = "dataViewPanel", target = "Selected Edges")
- }
-}
diff --git a/functions/general.R b/functions/general.R
deleted file mode 100644
index 14f3377..0000000
--- a/functions/general.R
+++ /dev/null
@@ -1,32 +0,0 @@
-callJSHandler <- function(handlerName, handlerFunctionParameter) {
- session$sendCustomMessage(handlerName, handlerFunctionParameter)
-}
-
-readFromTableFileToDataFrame <- function(path) {
- df <- switch(
- READ_LIBRARY,
- "fread" = data.table::fread(path),
- "base" = read.delim(path, header = T)
- )
- return(df)
-}
-
-mapper <- function(inArr, min, max, defaultValue = DEFAULT_MAP_VALUE){
- outArr <- inArr
- inArr_min <- min(inArr)
- inArr_max <- max(inArr)
- if (inArr_max - inArr_min != 0) {
- for (i in 0:length(inArr)){
- outArr[i] <- (inArr[i] - inArr_min) * (max - min) / (inArr_max - inArr_min) + min;
- }
- } else outArr[] <- defaultValue;
- return(outArr);
-}
-
-extractColumnFrom_node_layerDF <- function(nodeLayerNames, column) { # column = Node or Layer
- nodeLayerNames <- as.data.frame(nodeLayerNames)
- colnames(nodeLayerNames)[1] <- "NodeLayer"
- nodeLayerNames <- plyr::join(nodeLayerNames, node_layerDF, type = "left",
- by = "NodeLayer")
- return(nodeLayerNames[[column]])
-}
diff --git a/functions/igraph/cluster.R b/functions/igraph/cluster.R
deleted file mode 100644
index e1cc587..0000000
--- a/functions/igraph/cluster.R
+++ /dev/null
@@ -1,210 +0,0 @@
-handleClusterAlgorithmSelection <- function() {
- tryCatch({
- if (input$clusteringAlgorithmChoice != "-") {
- shinyjs::show("localLayoutAlgorithmChoice")
- } else {
- shinyjs::hide("localLayoutAlgorithmChoice")
- }
- }, error = function(e) {
- print(paste0("Error in clustering algorithm selection: ", e))
- renderError("Unexpected error on cluster interface.")
- })
-}
-
-isClusteringEnabled <- function() {
- isEnabled <- F
- if (input$clusteringAlgorithmChoice != "-") {
- isEnabled <- T
- if (input$localLayoutAlgorithmChoice == "-") {
- isEnabled <- F
- renderWarning("You need to select a local layout algorithm for the clusters.
- Executing layout without clustering.")
- }
- }
- return(isEnabled)
-}
-
-calculateClusteredLayout <- function(networkGraph) {
- assignedClusters <- applyClustering(networkGraph)
- globalLayoutFunc <- getLayoutFunction(input$layoutAlgorithmChoice)
- localLayoutFunc <- getLayoutFunction(input$localLayoutAlgorithmChoice)
-
- nodeCoords <-
- execute_strategy3_superNodes_strictPartitioning(
- networkGraph, assignedClusters, globalLayoutFunc, localLayoutFunc)
- return(nodeCoords)
-}
-
-applyClustering <- function(networkGraph) {
- clusterFunction <- getClusteringFunction(input$clusteringAlgorithmChoice)
- clusteringResults <-
- eval(parse(text = paste0("igraph::", clusterFunction, "(networkGraph)")))
-
- assignedClusters <- data.frame()
- for (i in 1:length(clusteringResults)) {
- assignedClusters <- rbind(assignedClusters, cbind(i, paste(clusteringResults[[i]], collapse = ", ")))
- }
- colnames(assignedClusters) <- c("Annotations", "Nodes")
- renderClusteringDF(assignedClusters)
- callJSHandler("handler_clickNodeColorPriority", "cluster")
- return(assignedClusters)
-}
-
-getClusteringFunction <- function(clustering_name) {
- clusterFunction <- switch(
- clustering_name,
- "Louvain" = "cluster_louvain",
- "Walktrap" = "cluster_walktrap",
- "Fast Greedy" = "cluster_fast_greedy",
- "Label Propagation" = "cluster_label_prop"
- )
- return(clusterFunction)
-}
-
-# Strategy 3 ####
-execute_strategy3_superNodes_strictPartitioning <- function(
- networkGraph, assignedClusters, globalLayoutFunc, localLayoutFunc,
- repelling_force = 3) {
- subNetwork <- convertGraphToDF(networkGraph)
- expandedGroups <- assignedClusters %>% tidyr::separate_rows(Nodes, sep = ", ")
- subNetworkWithGroups <- appendClusters(subNetwork, expandedGroups)
- groupsGraph <- extractGroupsGraph(subNetworkWithGroups)
- superNodeCoords <- calculateSuperNodeCoords(groupsGraph, globalLayoutFunc,
- repelling_force)
- nodeCoords <- calculateNodeCoordsPerCluster(expandedGroups, subNetworkWithGroups,
- superNodeCoords, globalLayoutFunc,
- localLayoutFunc, repelling_force)
- return(nodeCoords)
-}
-
-convertGraphToDF <- function(networkGraph) {
- subNetwork <- as.data.frame(igraph::as_edgelist(networkGraph))
- colnames(subNetwork) <- c('Source', 'Target')
- subNetwork$weight <- igraph::E(networkGraph)$weight
- return(subNetwork)
-}
-
-appendClusters <- function(subNetwork, expandedGroups) {
- subNetworkWithGroups <- merge(subNetwork, expandedGroups,
- by.x = 'Source', by.y = 'Nodes', all.x = T)
- subNetworkWithGroups <- merge(subNetworkWithGroups, expandedGroups,
- by.x = 'Target', by.y = 'Nodes', all.x = T)
- subNetworkWithGroups <-
- subNetworkWithGroups[, c("Source", "Annotations.x",
- "Target", "Annotations.y", "weight")]
- colnames(subNetworkWithGroups)[2] <- "SourceGroup"
- colnames(subNetworkWithGroups)[4] <- "TargetGroup"
- return(subNetworkWithGroups)
-}
-
-extractGroupsGraph <- function(subNetworkWithGroups) {
- groupsNetwork <- subNetworkWithGroups[, c("SourceGroup", "TargetGroup",
- "weight")]
- groupsGraph <- igraph::graph_from_data_frame(groupsNetwork, directed = F)
- groupsGraph <- igraph::simplify(groupsGraph, remove.multiple = T,
- remove.loops = F, edge.attr.comb = "mean")
- return(groupsGraph)
-}
-
-calculateSuperNodeCoords <- function(groupsGraph, globalLayoutFunc, repelling_force) {
- superNodeCoords <- eval(parse(text = paste0("igraph::", globalLayoutFunc, "(groupsGraph)")))
- superNodeCoords <- bindNamesToLayout(globalLayoutFunc, superNodeCoords,
- groupsGraph)
- colnames(superNodeCoords) <- c("x", "y", "superNode")
-
- # The following will repel all super nodes away from 0,0:
- # Foreach node, calculate a = y/x
- # Then multiply x by the repeling force and solve for y
- superNodeCoords$x <- as.double(superNodeCoords$x)
- superNodeCoords$y <- as.double(superNodeCoords$y)
- superNodeCoords$a <- ifelse(superNodeCoords$x != 0, superNodeCoords$y / superNodeCoords$x, superNodeCoords$y / 0.01)
- superNodeCoords$x <- repelling_force * superNodeCoords$x
- superNodeCoords$y <- superNodeCoords$a * superNodeCoords$x
- return(superNodeCoords)
-}
-
-bindNamesToLayout <- function(layoutFunc, nodeCoords, graphObject) {
- if (layoutFunc == "layout_with_sugiyama")
- nodeCoords <- cbind(nodeCoords$layout, names(igraph::V(graphObject)))
- else
- nodeCoords <- cbind(nodeCoords, names(igraph::V(graphObject)))
- nodeCoords <- as.data.frame(nodeCoords)
- return(nodeCoords)
-}
-
-# Foreach group, add low-weight within group edges and
-# apply local layout on the respective (x,y) superNode coords system
-calculateNodeCoordsPerCluster <- function(expandedGroups, subNetworkWithGroups,
- superNodeCoords, globalLayoutFunc,
- localLayoutFunc, repelling_force) {
- interGroupEdges <- merge(expandedGroups, expandedGroups,
- by.x = "Annotations", by.y = "Annotations")
- tinyWeight <- min(subNetworkWithGroups$weight) / 100
-
- nodeCoords <- do.call(rbind, lapply(
- unique(expandedGroups$Annotations), function(group) {
- groupNetwork <- calculateGroupNetworkWithExistingEdges(subNetworkWithGroups,
- group)
- groupNetworkAllEdges <-
- calculateGroupNetworkWithAllEdges(interGroupEdges, group, globalLayoutFunc,
- tinyWeight, repelling_force)
- groupNetwork <- rbind(groupNetwork, groupNetworkAllEdges)
- groupGraph <- removeExistingEdges(groupNetwork)
- tempCoords <- executeLocalLayout(groupGraph, localLayoutFunc,
- superNodeCoords, group)
- # returns tempCoords to rbind
- translateGroupCoords(superNodeCoords, group, tempCoords)
- }))
-
- nodeCoords <- merge(nodeCoords, expandedGroups)
- colnames(nodeCoords) <- c("name", "y", "z", "group")
- return(nodeCoords)
-}
-
-calculateGroupNetworkWithExistingEdges <- function(subNetworkWithGroups,
- group) {
- groupNetwork <- subNetworkWithGroups
- groupNetwork <- groupNetwork[(groupNetwork$SourceGroup == group &
- groupNetwork$TargetGroup == group), ]
- groupNetwork <- groupNetwork[, c('Source', 'Target', 'weight')]
- return(groupNetwork)
-}
-
-calculateGroupNetworkWithAllEdges <- function(interGroupEdges, group,
- globalLayoutFunc, tinyWeight,
- repelling_force) {
- groupNetworkAllEdges <-
- interGroupEdges[interGroupEdges$Annotations == group, ]
- tempGraph <- igraph::graph_from_data_frame(
- groupNetworkAllEdges[, c("Nodes.x", "Nodes.y")], directed = F)
-
- if (globalLayoutFunc == 'layout_with_kk')
- igraph::E(tempGraph)$weight <- tinyWeight * repelling_force
- else
- igraph::E(tempGraph)$weight <- tinyWeight / repelling_force
-
- tempGraph <- igraph::simplify(tempGraph, remove.multiple = T,
- remove.loops = T, edge.attr.comb = "first")
- groupNetworkAllEdges <- as.data.frame(igraph::as_edgelist(tempGraph))
- groupNetworkAllEdges$weight <- igraph::E(tempGraph)$weight
- colnames(groupNetworkAllEdges) <- c('Source', 'Target', 'weight')
- return(groupNetworkAllEdges)
-}
-
-executeLocalLayout <- function(groupGraph, localLayoutFunc, superNodeCoords,
- group) {
- tempCoords <- eval(parse(text = paste0("igraph::", localLayoutFunc, "(groupGraph)")))
- tempCoords <- bindNamesToLayout(localLayoutFunc, tempCoords, groupGraph)
- colnames(tempCoords) <- c("x", "y", "Nodes")
- tempCoords$x <- as.double(tempCoords$x)
- tempCoords$y <- as.double(tempCoords$y)
- return(tempCoords)
-}
-
-translateGroupCoords <- function(superNodeCoords, group, tempCoords) {
- groupX <- superNodeCoords[superNodeCoords$superNode == group, ]$x
- groupY <- superNodeCoords[superNodeCoords$superNode == group, ]$y
- tempCoords$x <- tempCoords$x + groupX
- tempCoords$y <- tempCoords$y + groupY
- return(tempCoords) # gets rbind
-}
diff --git a/functions/igraph/general.R b/functions/igraph/general.R
deleted file mode 100644
index 40f5e57..0000000
--- a/functions/igraph/general.R
+++ /dev/null
@@ -1,93 +0,0 @@
-existsNetwork <- function(silent = F) {
- exist <- T
- if (nrow(networkDF) == 0) {
- exist <- F
- if (!silent)
- renderWarning("Upload/import a network first through the File tab.")
- }
- return(exist)
-}
-
-existsSelectedLayer <- function() {
- exist <- T
- if (is.null(input$js_selected_layers)) {
- exist <- F
- renderWarning("Select at least one layer.")
- }
- return(exist)
-}
-
-filterSelectedChannels <- function(netData) {
- if ("Channel" %in% colnames(netData)) {
- netData <- netData[(netData$Channel %in% input$js_selectedChannels), , drop = F]
- if (is.null(input$js_selectedChannels))
- renderWarning("Select at least one channel.")
- }
- return(netData)
-}
-
-filterPerLayer <- function(netData, layerName) {
- filteredNetworkDF <- netData[(netData$SourceLayer == layerName) &
- (netData$TargetLayer == layerName), , drop = F]
- return(filteredNetworkDF)
-}
-
-filterAllSelectedLayers <- function(netData, selectedLayerNames) {
- filteredNetworkDF <-
- netData[(netData$SourceLayer %in% selectedLayerNames) &
- (netData$TargetLayer %in% selectedLayerNames), , drop = F]
- return(filteredNetworkDF)
-}
-
-filterPerSelectedNodes <- function(netData, selectedNodeNamesWithLayer) {
- filteredNetworkDF <-
- netData[(netData$SourceNode_Layer %in% selectedNodeNamesWithLayer) &
- (netData$TargetNode_Layer %in% selectedNodeNamesWithLayer), ,
- drop = F]
- return(filteredNetworkDF)
-}
-
-parseEdgelistIntoGraph <- function(filteredNetworkDF, subgraphChoice, layerName) {
- networkGraph <- ""
- if (isIGraphObjectValid(filteredNetworkDF, subgraphChoice, layerName)) {
- networkEdgelist <- filteredNetworkDF[, c("SourceNode_Layer",
- "TargetNode_Layer", "ScaledWeight")]
- # distinct, in case multiple rows remained in multi-channel nets
- networkEdgelist <- dplyr::distinct(networkEdgelist, SourceNode_Layer,
- TargetNode_Layer, .keep_all = T)
- networkGraph <- createGraph(networkEdgelist)
- }
- return(networkGraph)
-}
-
-isIGraphObjectValid <- function(filteredNetworkDF, subgraphChoice, layerName) {
- isValid <- T
- if (nrow(filteredNetworkDF) < 2) {
- isValid <- F
- if (subgraphChoice == "allLayers")
- renderWarning(paste0("Subgraph of selected layers cannot form a graph."))
- else
- renderWarning(paste0("Layer ", layerName, " cannot form a graph."))
- }
- return(isValid)
-}
-
-createGraph <- function(edgelist) {
- graph <- igraph::graph_from_data_frame(edgelist, directed = F)
- igraph::E(graph)$weight <- as.double(edgelist[, 'ScaledWeight'])
- # if it does not have channels remove multiple edges else not
- removeMultiple <- is.na(input$js_selectedChannels)
- # remove loops and multiple edges, simplify sum aggregates same edges
- graph <- igraph::simplify(graph, remove.multiple = removeMultiple,
- remove.loops = F, edge.attr.comb = "sum")
- return(graph)
-}
-
-existSelectedNodes <- function(selectedNodePositions) {
- exist <- T
- if (length(selectedNodePositions) == 0) {
- exist <- F
- renderWarning("Cannot execute local layouts without selected nodes.")
- }
- return(exist)
-}
diff --git a/functions/igraph/layout.R b/functions/igraph/layout.R
deleted file mode 100644
index 1411c33..0000000
--- a/functions/igraph/layout.R
+++ /dev/null
@@ -1,250 +0,0 @@
-handleLayout <- function() {
- tryCatch({
- if (areValidLayoutInputs()) {
- renderModal("Please wait. Generating layout.
")
- callJSHandler("handler_startLoader", T)
-
- selectedLayerPositions <- input$js_selected_layers + 1 # from JS to R counters
- selectedLayerNames <- input$js_layer_names[selectedLayerPositions]
- subgraphChoice <- input$subgraphChoice
-
- runLayoutAlgorithm(selectedLayerNames, subgraphChoice)
- }
- }, error = function(e) {
- print(paste0("Error on layout or clustering: ", e))
- renderError("Error on layout or clustering algorithm.")
- }, finally = {
- removeModal()
- callJSHandler("handler_finishLoader", T)
- })
-}
-
-areValidLayoutInputs <- function() {
- areValid <- F
- if (existsNetwork())
- if (isLayoutAlgorithmSelected())
- if (existsSelectedLayer())
- areValid <- T
- return(areValid)
-}
-
-isLayoutAlgorithmSelected <- function() {
- isSelected <- T
- if (input$layoutAlgorithmChoice == "-") {
- isSelected <- F
- renderWarning("Please, select a layout algorithm.")
- }
- return(isSelected)
-}
-
-runLayoutAlgorithm <- function(selectedLayerNames, subgraphChoice) {
- filteredNetworkDF <- filterSelectedChannels(networkDF)
-
- if (nrow(filteredNetworkDF) > 0) {
- set.seed(123)
- switch(
- subgraphChoice,
- "perLayer" = runPerLayerLayout(filteredNetworkDF, selectedLayerNames,
- subgraphChoice),
- "allLayers" = runAllLayersLayout(filteredNetworkDF, selectedLayerNames,
- subgraphChoice),
- "nodesPerLayers" = runLocalLayout(filteredNetworkDF, selectedLayerNames,
- subgraphChoice)
- )
- }
-}
-
-runPerLayerLayout <- function(filteredNetworkDF, selectedLayerNames,
- subgraphChoice) {
- for (layerName in selectedLayerNames) {
- if (input$layoutAlgorithmChoice %in% NO_EDGE_LAYOUTS)
- tempFilteredNetworkDF <- filterPseudoNetwork(filteredNetworkDF, layerName)
- else
- tempFilteredNetworkDF <- filterPerLayer(filteredNetworkDF, layerName)
- networkGraph <- parseEdgelistIntoGraph(tempFilteredNetworkDF, subgraphChoice,
- layerName)
- callJSHandler("handler_setPerLayerFlag", layerName)
- applyLayoutWithOptionalClustering(networkGraph)
- }
-}
-
-filterPseudoNetwork <- function(filteredNetworkDF, layerNames,
- selectedNodeNamesWithLayer = NULL) {
- tinyWeight <- min(filteredNetworkDF$ScaledWeight) / 100
-
- tempNetwork <- extractSelectedNetworkEdgelist(filteredNetworkDF, layerNames,
- selectedNodeNamesWithLayer)
- tempNetworkNodesLinked <-
- extractSelectedNetworkEdgelistAllLinks(filteredNetworkDF, layerNames,
- selectedNodeNamesWithLayer, tinyWeight)
- tempNetwork <- rbind(tempNetwork, tempNetworkNodesLinked)
- colnames(tempNetwork)[3] <- "weight"
- tempGraph <- removeExistingEdges(tempNetwork)
- filteredNetworkDF <- parseGraphIntoPseudoFrame(tempGraph)
- return(filteredNetworkDF)
-}
-
-extractSelectedNetworkEdgelist <- function(filteredNetworkDF, layerNames,
- selectedNodeNamesWithLayer) {
- if (is.null(selectedNodeNamesWithLayer))
- tempNetwork <-
- filteredNetworkDF[(filteredNetworkDF$SourceLayer %in% layerNames) &
- (filteredNetworkDF$TargetLayer %in% layerNames), , drop = F]
- else
- tempNetwork <-
- filteredNetworkDF[(filteredNetworkDF$SourceLayer %in% layerNames) &
- (filteredNetworkDF$TargetLayer %in% layerNames) &
- (filteredNetworkDF$SourceNode %in% selectedNodeNamesWithLayer) &
- (filteredNetworkDF$TargetNode %in% selectedNodeNamesWithLayer),
- , drop = F]
- tempNetwork <- tempNetwork[, c("SourceNode_Layer", "TargetNode_Layer",
- "ScaledWeight")]
- return(tempNetwork)
-}
-
-extractSelectedNetworkEdgelistAllLinks <- function(filteredNetworkDF, layerNames,
- selectedNodeNamesWithLayer,
- tinyWeight) {
- tempNetworkDF1 <-
- extractSelectedLayerNodes(filteredNetworkDF, "Source", layerNames,
- selectedNodeNamesWithLayer)
- tempNetworkDF2 <-
- extractSelectedLayerNodes(filteredNetworkDF, "Target", layerNames,
- selectedNodeNamesWithLayer)
- colnames(tempNetworkDF2) <- "SourceNode_Layer"
- tempNetworkNodesLinked <- interlinkNodeColumns(tempNetworkDF1, tempNetworkDF2,
- tinyWeight)
- return(tempNetworkNodesLinked)
-}
-
-extractSelectedLayerNodes <- function(filteredNetworkDF, column, layerNames,
- selectedNodeNamesWithLayer) {
- if (is.null(selectedNodeNamesWithLayer))
- tempDF <-
- filteredNetworkDF[(
- filteredNetworkDF[[paste0(column, "Layer")]] %in% layerNames), , drop = F]
- else
- tempDF <-
- filteredNetworkDF[
- (filteredNetworkDF[[paste0(column, "Layer")]] %in% layerNames) &
- (filteredNetworkDF[[paste0(
- column, "Node_Layer")]] %in% selectedNodeNamesWithLayer), , drop = F]
- tempDF <- tempDF[, c(paste0(column, "Node_Layer")), drop = F]
- return(tempDF)
-}
-
-interlinkNodeColumns <- function(df1, df2, tinyWeight) {
- df <- rbind(df1, df2)
- if (nrow(df) > 0) {
- df <- unique(df)
- df$TargetNode_Layer <-
- shiftValuesByOne(df$SourceNode_Layer)
- df$ScaledWeight <- tinyWeight
- }
- return(df)
-}
-
-shiftValuesByOne <- function(vec) {
- vec <- c("", vec)
- vec[1] <- tail(vec, n = 1)
- vec <- head(vec, -1)
- return(vec)
-}
-
-removeExistingEdges <- function(networkObject) {
- graphObject <- igraph::graph_from_data_frame(networkObject, directed = F)
- graphObject <- igraph::simplify(graphObject, remove.multiple = T,
- remove.loops = F, edge.attr.comb = "max")
- return(graphObject)
-}
-
-parseGraphIntoPseudoFrame <- function(tempGraph) {
- filteredNetworkDF <- as.data.frame(igraph::as_edgelist(tempGraph))
- if (nrow(filteredNetworkDF > 0)) {
- filteredNetworkDF$weight <- igraph::E(tempGraph)$weight
- colnames(filteredNetworkDF) <- c('SourceNode_Layer', 'TargetNode_Layer',
- 'weight')
- colnames(filteredNetworkDF)[3] <- "ScaledWeight"
- }
- return(filteredNetworkDF)
-}
-
-applyLayoutWithOptionalClustering <- function(networkGraph) {
- if (class(networkGraph) == "igraph") {
- if (isClusteringEnabled())
- nodeCoords <- calculateClusteredLayout(networkGraph)
- else
- nodeCoords <- calculateLayout(networkGraph, input$layoutAlgorithmChoice)
- callJSHandler("handler_executeLayout", nodeCoords)
- }
-}
-
-calculateLayout <- function(networkGraph, layoutAlgorithm) {
- layoutFunc <- getLayoutFunction(layoutAlgorithm)
- nodeCoords <-
- eval(parse(text = paste0("igraph::", layoutFunc, "(networkGraph)")))
- nodeCoords <- as.data.frame(nodeCoords)
- colnames(nodeCoords) <- c("y", "z")
- nodeCoords$name <- igraph::V(networkGraph)$name
- return(nodeCoords)
-}
-
-getLayoutFunction <- function(layout_name) {
- layoutFunction <- switch(
- layout_name,
- "Reingold-Tilford" = "layout_as_tree",
- "Circle" = "layout_in_circle",
- "Grid" = "layout_on_grid",
- "Random" = "layout_randomly",
- "Davidson-Harel" = "layout_with_dh",
- "DrL" = "layout_with_drl",
- "Fruchterman-Reingold" = "layout_with_fr",
- "GEM" = "layout_with_gem",
- "Graphopt" = "layout_with_graphopt",
- "Kamada-Kawai" = "layout_with_kk",
- "Large Graph Layout" = "layout_with_lgl",
- "Multidimensional Scaling" = "layout_with_mds",
- "Sugiyama" = "layout_with_sugiyama"
- )
- return(layoutFunction)
-}
-
-runAllLayersLayout <- function(filteredNetworkDF, selectedLayerNames,
- subgraphChoice) {
- if (input$layoutAlgorithmChoice %in% NO_EDGE_LAYOUTS)
- filteredNetworkDF <- filterPseudoNetwork(filteredNetworkDF, selectedLayerNames)
- else
- filteredNetworkDF <- filterAllSelectedLayers(filteredNetworkDF,
- selectedLayerNames)
-
- networkGraph <- parseEdgelistIntoGraph(filteredNetworkDF, subgraphChoice,
- layerName)
- applyLayoutWithOptionalClustering(networkGraph)
-}
-
-runLocalLayout <- function(filteredNetworkDF, selectedLayerNames,
- subgraphChoice) {
- selectedNodePositions <- input$js_selectedNodePositions + 1 # JS to R iterator
- if (existSelectedNodes(selectedNodePositions)) {
- nodeNamesWithLayer <- input$js_node_names
- selectedNodeNamesWithLayer <- nodeNamesWithLayer[selectedNodePositions]
-
- if (!input$layoutAlgorithmChoice %in% NO_EDGE_LAYOUTS)
- filteredNetworkDF <- filterPerSelectedNodes(filteredNetworkDF,
- selectedNodeNamesWithLayer)
- for (layerName in selectedLayerNames) {
- if (input$layoutAlgorithmChoice %in% NO_EDGE_LAYOUTS)
- tempFilteredNetworkDF <-
- filterPseudoNetwork(filteredNetworkDF, layerName,
- selectedNodeNamesWithLayer)
- else
- tempFilteredNetworkDF <- filterPerLayer(filteredNetworkDF, layerName)
-
- networkGraph <- parseEdgelistIntoGraph(tempFilteredNetworkDF,
- subgraphChoice, layerName)
- # map coordinates on local bounds in executeLayout
- callJSHandler("handler_setLocalFlag", T)
- applyLayoutWithOptionalClustering(networkGraph)
- }
- }
-}
diff --git a/functions/igraph/topology.R b/functions/igraph/topology.R
deleted file mode 100644
index 3e8d04a..0000000
--- a/functions/igraph/topology.R
+++ /dev/null
@@ -1,145 +0,0 @@
-handleTopologyScaling <- function() {
- tryCatch({
- if (areValidTopologyScalingInputs()) {
- renderModal("Please wait. Rescaling nodes.
")
- callJSHandler("handler_startLoader", T)
-
- selectedLayerPositions <- input$js_selected_layers + 1 # from JS to R counters
- selectedLayerNames <- input$js_layer_names[selectedLayerPositions]
- subgraphChoice <- input$subgraphChoice
-
- runTopologyScaling(selectedLayerNames, subgraphChoice)
- }
- }, error = function(e) {
- print(paste0("Error in topological scaling: ", e))
- renderError("Unexpected topological scaling error.")
- }, finally = {
- removeModal()
- callJSHandler("handler_finishLoader", T)
- })
-}
-
-areValidTopologyScalingInputs <- function() {
- areValid <- F
- if (existsNetwork())
- if (isTopologyMetricSelected())
- if (existsSelectedLayer())
- areValid <- T
- return(areValid)
-}
-
-isTopologyMetricSelected <- function() {
- isSelected <- T
- if (input$topologyScaleMetricChoice == "-") {
- isSelected <- F
- renderWarning("Please, select a topology metric.")
- }
- return(isSelected)
-}
-
-runTopologyScaling <- function(selectedLayerNames, subgraphChoice) {
- filteredNetworkDF <- filterSelectedChannels(networkDF)
-
- if (nrow(filteredNetworkDF) > 0) {
- switch(
- subgraphChoice,
- "perLayer" = runPerLayerScaling(filteredNetworkDF, selectedLayerNames,
- subgraphChoice),
- "allLayers" = runAllLayersScaling(filteredNetworkDF, selectedLayerNames,
- subgraphChoice),
- "nodesPerLayers" = runLocalScaling(filteredNetworkDF, selectedLayerNames,
- subgraphChoice)
- )
- }
-}
-
-runPerLayerScaling <- function(filteredNetworkDF, selectedLayerNames,
- subgraphChoice) {
- for (layerName in selectedLayerNames) {
- tempFilteredNetworkDF <- filterPerLayer(filteredNetworkDF, layerName)
- networkGraph <- parseEdgelistIntoGraph(tempFilteredNetworkDF, subgraphChoice,
- layerName)
- scaleTopology(networkGraph)
- }
-}
-
-scaleTopology <- function(networkGraph) {
- if (class(networkGraph) == "igraph") {
- topologyMetricChoice <- input$topologyScaleMetricChoice
-
- scale <- switch(
- topologyMetricChoice,
- "Degree" =
- igraph::degree(networkGraph, v = igraph::V(networkGraph), mode = "all", loops = T,
- normalized = F),
- "Clustering Coefficient" = {
- scale <- igraph::transitivity(networkGraph, type = "weighted", vids = NULL,
- weights = NULL, isolates = "zero") # NULL: E(networkGraph)$weight
- names(scale) <- igraph::V(networkGraph)$name
- scale
- },
- "Betweenness Centrality" =
- igraph::betweenness(networkGraph, v = igraph::V(networkGraph),
- directed = input$edgeDirectionToggle, weights = NULL, # NULL == E(networkGraph)$weight
- normalized = F)
- )
- nodeScale <- calculateNodeScaleDF(scale)
- prepareMetricTable(topologyMetricChoice, nodeScale)
- nodeScale$scale <- mapper(nodeScale$scale,
- TARGET_NODE_SCALE_MIN, TARGET_NODE_SCALE_MAX,
- defaultValue = 1)
- callJSHandler("handler_scaleTopology", nodeScale)
- }
-}
-
-prepareMetricTable <- function(topologyMetricChoice, nodeScale) {
- colnames(nodeScale) <- c("Node", topologyMetricChoice)
- nodeScale <-
- nodeScale[order(-nodeScale[[topologyMetricChoice]]), ]
- nodeScale[[topologyMetricChoice]] <-
- format(round(nodeScale[[topologyMetricChoice]], 2))
- nodeScale$Layer <- extractColumnFrom_node_layerDF(nodeScale$Node, "Layer")
- nodeScale$Node <- extractColumnFrom_node_layerDF(nodeScale$Node, "Node")
- nodeScale <- nodeScale[, c("Node", "Layer", topologyMetricChoice)]
-
- metric <- switch(
- topologyMetricChoice,
- "Degree" = "degree",
- "Clustering Coefficient" = "transitivity",
- "Betweenness Centrality" = "betweenness"
- )
- renderMetricTable(topologyMetricChoice, nodeScale, metric)
-}
-
-calculateNodeScaleDF <- function(scale) {
- nodeScale <- as.data.frame(scale)
- nodeScale$nodeName <- rownames(nodeScale)
- rownames(nodeScale) <- NULL
- nodeScale <- nodeScale[, c("nodeName", "scale")]
- return(nodeScale)
-}
-
-runAllLayersScaling <- function(filteredNetworkDF, selectedLayerNames,
- subgraphChoice) {
- filteredNetworkDF <- filterAllSelectedLayers(filteredNetworkDF, selectedLayerNames)
- networkGraph <- parseEdgelistIntoGraph(filteredNetworkDF, subgraphChoice,
- layerName)
- scaleTopology(networkGraph)
-}
-
-runLocalScaling <- function(filteredNetworkDF, selectedLayerNames,
- subgraphChoice) {
- selectedNodePositions <- input$js_selectedNodePositions + 1 # JS to R iterator
- if (existSelectedNodes(selectedNodePositions)) {
- nodeNamesWithLayer <- input$js_node_names
- selectedNodeNamesWithLayer <- nodeNamesWithLayer[selectedNodePositions]
- filterPerSelectedNodes <- filterPerSelectedNodes(filteredNetworkDF,
- selectedNodeNamesWithLayer)
- for (layerName in selectedLayerNames) {
- tempFilteredNetworkDF <- filterPerLayer(filterPerSelectedNodes, layerName)
- networkGraph <- parseEdgelistIntoGraph(tempFilteredNetworkDF,
- subgraphChoice, layerName)
- scaleTopology(networkGraph)
- }
- }
-}
diff --git a/functions/init.R b/functions/init.R
deleted file mode 100644
index 8676198..0000000
--- a/functions/init.R
+++ /dev/null
@@ -1,34 +0,0 @@
-initializeServerApp <- function() {
- initializeJSVariables()
- attachDownloadHandler()
- hideDataMetricTabs()
-}
-
-initializeJSVariables <- function() {
- callJSHandler("handler_initializeGlobals",
- list(MAX_EDGES = MAX_EDGES, MAX_CHANNELS = MAX_CHANNELS,
- MAX_LAYERS = MAX_LAYERS, EDGE_DEFAULT_COLOR = EDGE_DEFAULT_COLOR,
- CHANNEL_COLORS_DARK = CHANNEL_COLORS_DARK,
- CHANNEL_COLORS_LIGHT = CHANNEL_COLORS_LIGHT))
-}
-
-attachDownloadHandler <- function() {
- output$save_network_object <- downloadHandler(
- filename = function() {
- paste0("network-", Sys.Date(), ".json")
- }, content = function(con) {
- if (existsNetwork()) {
- exportData <- convertSessionToJSON()
- write(exportData, con)
- }
- }
- )
-}
-
-hideDataMetricTabs <- function() {
- lapply(c("Network Data", "Selected Edges", "Clustering Data", TOPOLOGY_METRICS),
- function(metricName) {
- hideTab(inputId = "dataViewPanel", target = metricName)
- }
- )
-}
diff --git a/functions/input.R b/functions/input.R
deleted file mode 100644
index bd4b15d..0000000
--- a/functions/input.R
+++ /dev/null
@@ -1,662 +0,0 @@
-# Upload Network ####
-handleUploadNetwork <- function() {
- tryCatch({
- renderModal("Please wait. Uploading network.
")
- inFile <- input$input_network_file
- tempNetworkDF <- readFromTableFileToDataFrame(inFile$datapath)
- if (isNetworkFormatValid(tempNetworkDF)) {
- reset_UI_values()
- networkDF <<- parseUploadedNetwork(tempNetworkDF)
- generateNetworkFromDF()
- }
- }, error = function(e) {
- print(paste0("Upload network file error: ", e))
- renderError("Bad uploaded network file format.")
- }, finally = {
- removeModal()
- })
-}
-
-isNetworkFormatValid <- function(df) {
- isValid <- T
- if (!existMandatoryColumns(df))
- isValid <- F
- else if (existsNonNumericWeight(df))
- isValid <- F
- else if (existsEmptyChannelName(df))
- isValid <- F
- return(isValid)
-}
-
-existMandatoryColumns <- function(df) {
- exist <- T
- if (!all(MANDATORY_NETWORK_COLUMNS %in%
- colnames(df))) {
- exist <- F
- renderWarning("Your network file must contain at least these four columns:\n
- SourceNode, SourceLayer, TargetNode, TargetLayer\n
- See our Help page -> Input & Output Files")
- }
- return(exist)
-}
-
-existsNonNumericWeight <- function(df) {
- exist <- F
- if ("Weight" %in% colnames(df)) {
- if (!is.numeric(df$Weight)) {
- exist <- T
- renderWarning("Make sure all input weights all numeric values.")
- }
- }
- return(exist)
-}
-
-existsEmptyChannelName <- function(df) {
- exist <- F
- if ("Channel" %in% colnames(df)) {
- if ('' %in% df$Channel) {
- exist <- T
- renderWarning("At least one edge has no channel name.\n
- Please reupload the network file with all edge channels.")
- }
- }
- return(exist)
-}
-
-parseUploadedNetwork <- function(df) {
- df <- subsetLegitColumns(df)
- df <- trimNetworkData(df)
- df <- removeDuplicateNetworkRows(df)
- df <- appendScaledNetworkWeights(df)
- df <- appendNodeLayerCombinations(df)
- df <- reorderNetworkColumns(df)
- return(df)
-}
-
-subsetLegitColumns <- function(df) {
- combinedLegitColumns <- c(MANDATORY_NETWORK_COLUMNS, OPTIONAL_NETWORK_COLUMNS)
- existingLegitColumns <- combinedLegitColumns[which(combinedLegitColumns %in%
- colnames(df))]
- return(df[, existingLegitColumns])
-}
-
-trimNetworkData <- function(df) {
- invisible(lapply(colnames(df), function(colName) {
- df[[colName]] <<- trimws(df[[colName]])
- }))
- return(df)
-}
-
-removeDuplicateNetworkRows <- function(df) {
- if ("Channel" %in% colnames(df)) {
- df <- dplyr::distinct(df, SourceNode, SourceLayer, TargetNode, TargetLayer,
- Channel, .keep_all = T)
- } else {
- df <- dplyr::distinct(df, SourceNode, SourceLayer, TargetNode, TargetLayer,
- .keep_all = T)
- }
- return(df)
-}
-
-appendScaledNetworkWeights <- function(df) {
- if ("Weight" %in% colnames(df)) {
- df$Weight <- as.numeric(df$Weight)
- } else {
- df$Weight <- rep(1, nrow(df))
- }
- df$ScaledWeight <- mapper(df$Weight, 0.1, 1)
- return(df)
-}
-
-appendNodeLayerCombinations <- function(df) {
- df$SourceNode_Layer <- paste(df$SourceNode, df$SourceLayer, sep = "_")
- df$TargetNode_Layer <- paste(df$TargetNode, df$TargetLayer, sep = "_")
- return(df)
-}
-
-reorderNetworkColumns <- function(df) {
- channelColumn <- NULL
- if ("Channel" %in% colnames(df))
- channelColumn <- "Channel"
- df <- df[, c(MANDATORY_NETWORK_COLUMNS, channelColumn, "Weight",
- "SourceNode_Layer", "TargetNode_Layer", "ScaledWeight")]
- return(df)
-}
-
-generateNetworkFromDF <- function(fromUpload = T) {
- callJSHandler("handler_uploadNetwork", networkDF)
- create_node_layerDF()
- renderNetworkDF(networkDF)
-
- updateSelectInput(session, "navBar", selected = "Main View")
- if (!fromUpload)
- reset("input_network_file")
- reset("load_network_file")
- reset("node_attributes_file")
- reset("edge_attributes_file")
-}
-
-create_node_layerDF <- function() {
- node_layerDF <<-
- as.data.frame(c(networkDF$SourceNode_Layer, networkDF$TargetNode_Layer))
- colnames(node_layerDF)[1] <<- "NodeLayer"
- node_layerDF$Node <<- c(networkDF$SourceNode, networkDF$TargetNode)
- node_layerDF$Layer <<- c(networkDF$SourceLayer, networkDF$TargetLayer)
- node_layerDF <<- unique(node_layerDF)
-}
-
-# Load Session ####
-handleLoadSession <- function() {
- tryCatch({
- renderModal("Please wait. Importing network from JSON file.
")
- inFile <- input$load_network_file
- loadNetworkFromJSONFilepath(inFile$datapath)
- }, error = function(e) {
- print(paste0("Import network file error: ", e))
- renderError("Bad imported network file format.")
- }, finally = {
- removeModal()
- })
-}
-
-loadNetworkFromJSONFilepath <- function(filePath) {
- jsonNetwork <- jsonlite::fromJSON(filePath)
- if (isJSONValid(jsonNetwork)) {
- reset_UI_values()
- jsonNetwork <- parseUploadedJSON(jsonNetwork)
- networkDF <<- extractNetworkDFFromJSONNetwork(jsonNetwork)
- generateNetworkFromDF_JSONVersion(jsonNetwork)
- }
-}
-
-isJSONValid <- function(jsonNetwork) {
- isValid <- T
- if (!existMandatoryObjects(jsonNetwork))
- isValid <- F
- if (!areLayersValid(jsonNetwork$layers))
- isValid <- F
- if (!existMandatoryNodeColumns(jsonNetwork$nodes))
- isValid <- F
- if (!existMandatoryEdgeColumns(jsonNetwork$edges))
- isValid <- F
- return(isValid)
-}
-
-existMandatoryObjects <- function(jsonNetwork) {
- exist <- T
- if (!all(MANDATORY_JSON_OBJECTS %in%
- names(jsonNetwork))) {
- exist <- F
- renderWarning("Your JSON file must contain at least these four objects:\n
- scene, layers, nodes, edges\n
- See our Help page -> API")
- }
- return(exist)
-}
-
-areLayersValid <- function(layers) {
- areValid <- T
- if (!("name" %in% colnames(layers))) {
- areLayersValid <- F
- renderWarning("Your JSON layers must contain at least a name property.\n
- See our Help page -> API")
- } else if (containsEmptyValue(layers$name)) {
- areLayersValid <- F
- renderWarning("Your JSON layers contain at least one empty name value.\n
- Layer names cannot be empty.")
- } else {
- uniqueLayers <- unique(layers$name)
- if (length(uniqueLayers) > MAX_LAYERS) {
- areLayersValid <- F
- renderWarning(paste0("The network must contain no more than ",
- MAX_LAYERS, " layers."))
- }
- }
- return(areValid)
-}
-
-existMandatoryNodeColumns <- function(nodes) {
- exist <- T
- if (!all(MANDATORY_JSON_NODE_COLUMNS %in%
- colnames(nodes))) {
- exist <- F
- renderWarning("Your JSON nodes must contain at least a name and a layer.\n
- See our Help page -> API")
- } else if (containsEmptyValue(nodes$name)) {
- exist <- F
- renderWarning("Your JSON nodes contain at least one empty name value.\n
- Node names cannot be empty.")
- } else if (containsEmptyValue(nodes$layer)) {
- exist <- F
- renderWarning("Your JSON nodes contain at least one empty layer value.\n
- Node layers cannot be empty.")
- }
- return(exist)
-}
-
-containsEmptyValue <- function(vec) {
- containsEmpty <- F
- if (anyNA(vec) || "" %in% vec)
- containsEmpty <- T
- return(containsEmpty)
-}
-
-existMandatoryEdgeColumns <- function(edges) {
- exist <- T
- if (!all(MANDATORY_JSON_EDGE_COLUMNS %in%
- colnames(edges))) {
- exist <- F
- renderWarning("Your JSON edges must contain at least a src and a trg node.\n
- See our Help page -> API")
- } else if (containsEmptyValue(edges$src)) {
- exist <- F
- renderWarning("Your JSON edges contain at least one empty src value.\n
- Source Nodes along with their layers cannot be empty.")
- } else if (containsEmptyValue(edges$trg)) {
- exist <- F
- renderWarning("Your JSON edges contain at least one empty trg value.\n
- Target Nodes along with their layers cannot be empty.")
- }
- return(exist)
-}
-
-parseUploadedJSON <- function(jsonNetwork) {
- jsonNetwork <- subsetLegitObjects(jsonNetwork)
- jsonNetwork <- trimJSONData(jsonNetwork)
- jsonNetwork <- handleJSONChannels(jsonNetwork)
- jsonNetwork$edges <- removeDuplicateJSONEdges(jsonNetwork$edges)
- jsonNetwork <- chooseSceneOrDefaults(jsonNetwork)
- jsonNetwork <- chooseLayersOrDefaults(jsonNetwork)
- jsonNetwork <- chooseToScrambleNodes(jsonNetwork)
- jsonNetwork <- chooseNodesOrDefaults(jsonNetwork)
- jsonNetwork <- chooseEdgesOrDefaults(jsonNetwork)
- jsonNetwork <- addExtraJSONCommands(jsonNetwork)
- return(jsonNetwork)
-}
-
-subsetLegitObjects <- function(jsonNetwork) {
- combinedLegitObjects <- c(MANDATORY_JSON_OBJECTS, OPTIONAL_JSON_OBJECTS)
- existingLegitObjects <- combinedLegitObjects[which(combinedLegitObjects %in%
- names(jsonNetwork))]
- return(jsonNetwork[existingLegitObjects])
-}
-
-trimJSONData <- function(jsonNetwork) {
- invisible(lapply(names(jsonNetwork), function(objName) {
- if (typeof(jsonNetwork[[objName]]) == "list") {
- invisible(lapply(names(jsonNetwork[[objName]]), function(insideObjName) {
- jsonNetwork[[objName]][[insideObjName]] <<-
- trimws(jsonNetwork[[objName]][[insideObjName]])
- }))
- } else
- jsonNetwork[[objName]] <<- trimws(jsonNetwork[[objName]])
- }))
- return(jsonNetwork)
-}
-
-handleJSONChannels <- function(jsonNetwork) {
- if (!is.null(jsonNetwork$edges$channel)) {
- if (all(jsonNetwork$edges$channel == "")) {
- jsonNetwork$edges$channel <- NULL
- } else if ((any(jsonNetwork$edges$channel == "")) ||
- (any(is.na(jsonNetwork$edges$channel)))) {
- jsonNetwork$edges$channel <- NULL
- renderWarning("At least one edge has no channel name.\n
- Removing channels completely.")
- }
- }
- return(jsonNetwork)
-}
-
-removeDuplicateJSONEdges <- function(edges) {
- if (is.null(edges$channel))
- edges <- dplyr::distinct(edges, src, trg, .keep_all = T)
- else
- edges <- dplyr::distinct(edges, src, trg, channel, .keep_all = T)
- return(edges)
-}
-
-chooseSceneOrDefaults <- function(jsonNetwork) {
- if (!"scene" %in% names(jsonNetwork))
- jsonNetwork$scene <- list()
- jsonNetwork$scene$position_x <-
- keepValuesOrDefault(jsonNetwork$scene$position_x)
- jsonNetwork$scene$position_y <-
- keepValuesOrDefault(jsonNetwork$scene$position_y)
- jsonNetwork$scene$scale <-
- keepValuesOrDefault(jsonNetwork$scene$scale, "0.9")
- jsonNetwork$scene$color <-
- keepValuesOrDefault(jsonNetwork$scene$color, "#000000")
- jsonNetwork$scene$rotation_x <-
- keepValuesOrDefault(jsonNetwork$scene$rotation_x, "0.261799388")
- jsonNetwork$scene$rotation_y <-
- keepValuesOrDefault(jsonNetwork$scene$rotation_y, "0.261799388")
- jsonNetwork$scene$rotation_z <-
- keepValuesOrDefault(jsonNetwork$scene$rotation_z, "0.261799388")
- return(jsonNetwork)
-}
-
-keepValuesOrDefault <- function(jsonNetworkVec, default = "0") {
- if (is.null(jsonNetworkVec)) {
- jsonNetworkVec <- default
- } else if (identical(jsonNetworkVec, "TRUE")) {
- jsonNetworkVec <- T
- } else if (identical(jsonNetworkVec, "FALSE")) {
- jsonNetworkVec <- F
- } else {
- jsonNetworkVec[is.na(jsonNetworkVec)] <- default
- jsonNetworkVec[jsonNetworkVec == ""] <- default
- }
- return(jsonNetworkVec)
-}
-
-chooseLayersOrDefaults <- function(jsonNetwork) {
- # flag to move layer with JS if a coord was not given
- jsonNetwork$layers$generate_coordinates <-
- decideGenerateCoordinatesFlag(jsonNetwork$layers)
- jsonNetwork$layers$position_x <-
- keepValuesOrDefault(jsonNetwork$layers$position_x)
- jsonNetwork$layers$position_y <-
- keepValuesOrDefault(jsonNetwork$layers$position_y)
- jsonNetwork$layers$position_z <-
- keepValuesOrDefault(jsonNetwork$layers$position_z)
- jsonNetwork$layers$last_layer_scale <-
- keepValuesOrDefault(jsonNetwork$layers$last_layer_scale, "1")
- jsonNetwork$layers$rotation_x <-
- keepValuesOrDefault(jsonNetwork$layers$rotation_x)
- jsonNetwork$layers$rotation_y <-
- keepValuesOrDefault(jsonNetwork$layers$rotation_y)
- jsonNetwork$layers$rotation_z <-
- keepValuesOrDefault(jsonNetwork$layers$rotation_z)
- jsonNetwork$layers$floor_current_color <-
- keepValuesOrDefault(jsonNetwork$layers$floor_current_color,
- FLOOR_DEFAULT_COLOR)
- jsonNetwork$layers$geometry_parameters_width <-
- keepValuesOrDefault(jsonNetwork$layers$geometry_parameters_width,
- FLOOR_DEFAULT_WIDTH)
- return(jsonNetwork)
-}
-
-decideGenerateCoordinatesFlag <- function(layers) {
- layers$generate_coordinates <- F
- if (is.null(layers$position_x) || is.null(layers$position_y) ||
- is.null(layers$position_z)) {
- layers$generate_coordinates <- T
- } else {
- invisible(lapply(c("x", "y", "z"), function(dimension) {
- layers$generate_coordinates[
- which(is.na(layers[[paste0("position_", dimension)]]))] <<- T
- layers$generate_coordinates[
- which(layers[[paste0("position_", dimension)]] == "")] <<- T
- }))
- }
- return(layers$generate_coordinates)
-}
-
-chooseToScrambleNodes <- function(jsonNetwork) {
- if (is.null(jsonNetwork$nodes$position_x) &&
- is.null(jsonNetwork$nodes$position_y) &&
- is.null(jsonNetwork$nodes$position_z))
- jsonNetwork$scramble_nodes <- T
- else
- jsonNetwork$scramble_nodes <- F
- return(jsonNetwork)
-}
-
-chooseNodesOrDefaults <- function(jsonNetwork) {
- jsonNetwork$nodes$position_x <-
- keepValuesOrDefault(jsonNetwork$nodes$position_x)
- jsonNetwork$nodes$position_y <-
- keepValuesOrDefault(jsonNetwork$nodes$position_y)
- jsonNetwork$nodes$position_z <-
- keepValuesOrDefault(jsonNetwork$nodes$position_z)
- jsonNetwork$nodes$scale <-
- keepValuesOrDefault(jsonNetwork$nodes$scale)
- jsonNetwork$nodes$scale <-
- keepValuesOrDefault(jsonNetwork$nodes$scale, 1)
- jsonNetwork$nodes$color <-
- keepValuesOrDefault(jsonNetwork$nodes$color,
- NODE_COLORS[match(jsonNetwork$nodes$layer,
- jsonNetwork$layer$name)])
- jsonNetwork$nodes$url <-
- keepValuesOrDefault(jsonNetwork$nodes$url, "")
- jsonNetwork$nodes$descr <-
- keepValuesOrDefault(jsonNetwork$nodes$descr, "")
- return(jsonNetwork)
-}
-
-chooseEdgesOrDefaults <- function(jsonNetwork) {
- jsonNetwork$edges$opacity <-
- keepValuesOrDefault(jsonNetwork$edges$opacity, 1)
- jsonNetwork$edges$color <-
- keepValuesOrDefault(jsonNetwork$edges$color, EDGE_DEFAULT_COLOR)
- return(jsonNetwork)
-}
-
-addExtraJSONCommands <- function(jsonNetwork) {
- jsonNetwork$universalLabelColor <-
- keepValuesOrDefault(jsonNetwork$universalLabelColor, "#FFFFFF")
- jsonNetwork$direction <-
- keepValuesOrDefault(jsonNetwork$direction, F)
- jsonNetwork$edgeOpacityByWeight <-
- keepValuesOrDefault(jsonNetwork$edgeOpacityByWeight, T)
- return(jsonNetwork)
-}
-
-extractNetworkDFFromJSONNetwork <- function(jsonNetwork) {
- create_node_layerDF_fromJSON(jsonNetwork$nodes)
- df <- parseJSONEdgesIntoNetwork(jsonNetwork$edges)
- return(df)
-}
-
-create_node_layerDF_fromJSON <- function(jsonNodes) {
- node_layerDF <<- jsonNodes[, c("name", "layer")]
- node_layerDF$NodeLayer <<- paste(node_layerDF$name,
- node_layerDF$layer, sep = "_")
- node_layerDF <<- node_layerDF[, c("NodeLayer", "name", "layer")]
- colnames(node_layerDF)[2] <<- "Node"
- colnames(node_layerDF)[3] <<- "Layer"
- node_layerDF <<- unique(node_layerDF)
-}
-
-parseJSONEdgesIntoNetwork <- function(edges) {
- channelColumn <- NULL
- if ("channel" %in% colnames(edges))
- channelColumn <- "channel"
- opacityColumn <- NULL
- if ("opacity" %in% colnames(edges))
- opacityColumn <- "opacity"
-
- df <- edges[, c(MANDATORY_JSON_EDGE_COLUMNS, channelColumn, opacityColumn)]
- colnames(df)[colnames(df) == "src"] <- "SourceNode_Layer"
- colnames(df)[colnames(df) == "trg"] <- "TargetNode_Layer"
- colnames(df)[colnames(df) == "channel"] <- "Channel"
- colnames(df)[colnames(df) == "opacity"] <- "Weight"
-
- df$SourceNode <- extractColumnFrom_node_layerDF(df$SourceNode_Layer, "Node")
- df$SourceLayer <- extractColumnFrom_node_layerDF(df$SourceNode_Layer, "Layer")
- df$TargetNode <- extractColumnFrom_node_layerDF(df$TargetNode_Layer, "Node")
- df$TargetLayer <- extractColumnFrom_node_layerDF(df$TargetNode_Layer, "Layer")
- df <- appendScaledNetworkWeights(df)
-
- channelColumn <- NULL
- if ("Channel" %in% colnames(df))
- channelColumn <- "Channel"
- df <- df[, c(MANDATORY_NETWORK_COLUMNS, channelColumn, "Weight",
- "SourceNode_Layer", "TargetNode_Layer", "ScaledWeight")]
- return(df)
-}
-
-generateNetworkFromDF_JSONVersion <- function(jsonNetwork) {
- callJSHandler("handler_importNetwork", jsonNetwork)
- renderNetworkDF(networkDF)
-
- updateSelectInput(session, "navBar", selected = "Main View")
- reset("input_network_file")
- reset("node_attributes_file")
- reset("edge_attributes_file")
-}
-
-# API ####
-resolveAPI <- function() {
- tryCatch({
- query <- parseQueryString(session$clientData$url_search)
- if (length(query$f) > 0) {
- renderModal("Please wait. Importing network from API.
")
- loadNetworkFromJSONFilepath(paste0(POST_REQUEST_PATH, query$f))
- updateNavbarPage(session, "navBar", selected = "Main View")
- }
- }, error = function(e) {
- print(paste0("API error: ", e))
- renderError("Error with external API call.")
- }, finally = {
- removeModal()
- })
-}
-
-# Upload NODE attributes ####
-handleInputNodeAttributeFileUpload <- function() {
- tryCatch({
- renderModal("Please wait. Uploading node attributes.
")
- nodeFile <- input$node_attributes_file$datapath
- if (!is.null(nodeFile)) {
- nodeAttributes <- read.delim(nodeFile)
- if (existMandatoryNodeAttributeColumns(nodeAttributes)) {
- nodeAttributes$NodeLayer <- paste(trimws(nodeAttributes$Node),
- trimws(nodeAttributes$Layer), sep = "_")
- if (!is.null(nodeAttributes$Color))
- nodeAttributes$Color <- trimws(nodeAttributes$Color)
- if (!is.null(nodeAttributes$Size))
- nodeAttributes$Size <- trimws(nodeAttributes$Size)
- if (!is.null(nodeAttributes$Url))
- nodeAttributes$Url <- trimws(nodeAttributes$Url)
- if (!is.null(nodeAttributes$Description))
- nodeAttributes$Description <- trimws(nodeAttributes$Description)
-
- callJSHandler("handler_setNodeAttributes", jsonlite::toJSON(nodeAttributes))
- callJSHandler("handler_clickNodeColorPriority", "default")
- updateSelectInput(session, "navBar", selected = "Main View")
- }
- }
- }, error = function(e) {
- print(paste0("Error during input node attributes file upload: ", e))
- renderError("Bad node attributes file format.")
- }, finally = {
- removeModal()
- })
-}
-
-existMandatoryNodeAttributeColumns <- function(nodeAttributes) {
- exist <- T
- if (is.null(nodeAttributes$Node) || is.null(nodeAttributes$Layer)) {
- exist <- F
- renderWarning("Your node attribute file must contain at least two columns:\n
- Node and Layer")
- }
- return(exist)
-}
-
-# Upload EDGE attributes ####
-handleInputEdgeAttributeFileUpload <- function() {
- tryCatch({
- renderModal("Please wait. Uploading edge attributes.
")
- edgeFile <- input$edge_attributes_file$datapath
- if (!is.null(edgeFile)) {
- edgeAttributes <- read.delim(edgeFile)
- if (existMandatoryEdgeAttributeColumns(edgeAttributes)) {
- edgeAttributes$EdgePair <- paste(
- paste(trimws(edgeAttributes$SourceNode), trimws(edgeAttributes$SourceLayer), sep = "_"),
- paste(trimws(edgeAttributes$TargetNode), trimws(edgeAttributes$TargetLayer), sep = "_"),
- sep = "---"
- )
- edgeAttributes$Color <- trimws(edgeAttributes$Color)
- if ("Channel" %in% colnames(edgeAttributes)) {
- edgeAttributes$Channel <- trimws(edgeAttributes$Channel)
- edgeAttributes <- edgeAttributes[, c("EdgePair", "Color", "Channel")]
- } else
- edgeAttributes <- edgeAttributes[, c("EdgePair", "Color")]
- callJSHandler("handler_setEdgeAttributes", jsonlite::toJSON(edgeAttributes))
- updateSelectInput(session, "navBar", selected = "Main View")
- }
- }
- }, error = function(e) {
- print(paste0("Error during input edge attributes file upload: ", e))
- renderError("Bad edge attributes file format.")
- }, finally = {
- removeModal()
- })
-}
-
-existMandatoryEdgeAttributeColumns <- function(edgeAttributes) {
- exist <- T
- if (is.null(edgeAttributes$SourceNode) || is.null(edgeAttributes$SourceLayer) ||
- is.null(edgeAttributes$TargetNode) || is.null(edgeAttributes$TargetLayer) ||
- is.null(edgeAttributes$Color)) {
- exist <- F
- renderWarning("Your edge attribute file must contain at least these five columns:\n
- SourceNode, SourceLayer, TargetNode and TargetLayer,
- and optionally a Channel name.")
- }
- return(exist)
-}
-
-# Save Session ####
-convertSessionToJSON <- function() {
- js_scene_pan <- jsonlite::fromJSON(input$js_scene_pan)
- js_scene_sphere <- jsonlite::fromJSON(input$js_scene_sphere)
- js_layers <- jsonlite::fromJSON(input$js_layers)
- js_nodes <- jsonlite::fromJSON(input$js_nodes)
- js_edge_pairs <- jsonlite::fromJSON(input$js_edge_pairs)
- js_edge_colors <- jsonlite::fromJSON(input$js_edge_colors)
- js_label_color <- input$js_label_color
- direction_flag <- input$edgeDirectionToggle
- edgeByWeight_flag <- input$edgeWidthByWeight
-
- scene <- c(js_scene_pan, js_scene_sphere)
- edges <- as.data.frame(c(js_edge_pairs, js_edge_colors))
-
- exportData <- list(
- scene = scene, layers = js_layers, nodes = js_nodes,
- edges = edges, universalLabelColor = js_label_color,
- direction = direction_flag, edgeOpacityByWeight = edgeByWeight_flag
- )
- exportData <- jsonlite::toJSON(exportData, auto_unbox = T)
- return(exportData)
-}
-
-# Load Example ####
-handleLoadExample <- function() {
- tryCatch({
- if (existsNetwork(silent = T)) {
- showModal(modalDialog(
- title = "Load Example Network",
- paste0("The current network will be discarded. Are you sure?"),
- footer = tagList(
- actionButton("loadExample_ok", "Yes"),
- modalButton("Cancel")
- )
- ))
- } else
- loadExampleNetwork()
- }, error = function(e) {
- print(paste0("Example network upload error: ", e))
- renderError("Unexpected error.")
- })
-}
-
-loadExampleNetwork <- function() {
- reset_UI_values()
- networkDF <<- fst::read.fst("./www/data/networkDF.fst")
- generateNetworkFromDF(fromUpload = F)
-}
-
-handleLoadExampleAccept <- function() {
- tryCatch({
- loadExampleNetwork()
- }, error = function(e) {
- print(paste0("Example network upload error: ", e))
- renderError("Unexpected error.")
- }, finally = {
- removeModal()
- })
-}
diff --git a/functions/js_handling.R b/functions/js_handling.R
deleted file mode 100644
index 8397438..0000000
--- a/functions/js_handling.R
+++ /dev/null
@@ -1,24 +0,0 @@
-updateCheckboxInputFromJS <- function(checkboxName, checkboxValue) {
- tryCatch({
- updateCheckboxInput(session, checkboxName,
- value = (checkboxValue == 'TRUE'))
- }, error = function(e) {
- print(paste("Checkbox error: ", e))
- renderError("Error with checkbox action.")
- })
-}
-
-toggleCurvatureInputsFromJS <- function() {
- tryCatch({
- if (input$js_channel_curvature_flag) {
- shinyjs::show("intraChannelCurvature")
- shinyjs::show("interChannelCurvature")
- } else {
- shinyjs::hide("intraChannelCurvature")
- shinyjs::hide("interChannelCurvature")
- }
- }, error = function(e) {
- print(paste("Curvature input toggling error: ", e))
- renderError("Error with curvature input toggling.")
- })
-}
diff --git a/functions/render.R b/functions/render.R
deleted file mode 100644
index 7a464d5..0000000
--- a/functions/render.R
+++ /dev/null
@@ -1,85 +0,0 @@
-renderError <- function(prompt) {
- if (exists("session"))
- shinyalert::shinyalert("Error!", prompt, type = "error")
-}
-
-renderWarning <- function(prompt) {
- if (exists("session"))
- shinyalert::shinyalert("Warning!", prompt, type = "warning")
-}
-
-renderModal <- function(prompt) {
- showModal(modalDialog(HTML(prompt), footer = NULL))
-}
-
-renderNetworkDF <- function(formattedNetwork) {
- showTab(inputId = "dataViewPanel", target = "Network Data")
- updateTabsetPanel(session, "dataViewPanel", selected = "Network Data")
-
- hiddenColumns <- c(6, 7, 8)
- if (is.null(formattedNetwork$Channel))
- hiddenColumns <- hiddenColumns - 1
- renderShinyDataTable("network_dataView", formattedNetwork, fileName = "networkData",
- hiddenColumns = hiddenColumns, filter = "top")
-}
-
-renderSelectedEdgesDF <- function(selectedEdgesDF) {
- showTab(inputId = "dataViewPanel", target = "Selected Edges")
- updateTabsetPanel(session, "dataViewPanel", selected = "Selected Edges")
-
- hiddenColumns <- c()
- if (selectedEdgesDF$Channel[1] == "")
- hiddenColumns <- c(4)
- renderShinyDataTable("selectedEdges_dataView", selectedEdgesDF,
- fileName = "selectedEdgeData",
- hiddenColumns = hiddenColumns, filter = "top")
-}
-
-renderClusteringDF <- function(clusteringData) {
- showTab(inputId = "dataViewPanel", target = "Clustering Data")
- updateTabsetPanel(session, "dataViewPanel", selected = "Clustering Data")
-
- renderShinyDataTable("clustering_dataView",
- clusteringData,
- fileName = "clusteringData",
- filter = "top")
-}
-
-renderMetricTable <- function(topologyMetricChoice, nodeScale, metric) {
- showTab(inputId = "dataViewPanel", target = topologyMetricChoice)
- updateTabsetPanel(session, "dataViewPanel", selected = topologyMetricChoice)
- renderShinyDataTable(paste0(metric, "_dataView"),
- nodeScale,
- fileName = paste0(metric, "Data"),
- filter = "top")
-}
-
-renderShinyDataTable <- function(shinyOutputId, outputData,
- caption = NULL, fileName = "",
- scrollY = NULL, hiddenColumns = c(),
- filter = "none") {
- output[[shinyOutputId]] <- DT::renderDataTable(
- outputData,
- server = F,
- extensions = 'Buttons',
- caption = caption,
- options = list(
- scrollY = scrollY,
- scroller = T,
- "dom" = 'Blfiprt',
- buttons = list(
- list(extend = 'excel', filename = fileName),
- list(extend = 'csv', filename = fileName),
- list(extend = 'copy', filename = fileName),
- list(extend = 'pdf', filename = fileName),
- list(extend = 'print', filename = fileName)
- ),
- columnDefs = list(
- list(visible = F, targets = hiddenColumns)
- )
- ),
- filter = filter,
- rownames = F,
- escape = F
- )
-}
diff --git a/functions/reset.R b/functions/reset.R
deleted file mode 100644
index dfa6b2c..0000000
--- a/functions/reset.R
+++ /dev/null
@@ -1,44 +0,0 @@
-reset_UI_values <- function() {
- hideDataMetricTabs()
- # scene
- reset("toggleSceneCoords")
- reset("autoRotateScene")
- # layers
- reset("showLayerLabelsRadio")
- reset("showLayerCoords")
- reset("showWireFrames")
- reset("resizeLayerLabels")
- reset("layerColorPriorityRadio")
- reset("layerOpacity")
- # nodes
- reset("showNodeLabelsRadio")
- reset("resizeNodeLabels")
- reset("nodeSelectedColorPriority")
- reset("selectAllNodes")
- # edges
- reset("edgeSelectedColorPriority")
- reset("edgeFileColorPriority")
- reset("edgeDirectionToggle")
- reset("intraLayerEdgeOpacity")
- shinyjs::hide("intraLayerEdgeOpacity")
- reset("interLayerEdgeOpacity")
- shinyjs::hide("interLayerEdgeOpacity")
- reset("edgeWidthByWeight")
- reset("interDirectionArrowSize")
- reset("intraDirectionArrowSize")
- shinyjs::hide("interDirectionArrowSize")
- shinyjs::hide("intraDirectionArrowSize")
- reset("intraChannelCurvature")
- shinyjs::hide("intraChannelCurvature")
- reset("interChannelCurvature")
- shinyjs::hide("interChannelCurvature")
- # layouts
- reset("selectAllLayersCheckbox")
- reset("topologyScaleMetricChoice")
- reset("layoutAlgorithmChoice")
- reset("clusteringAlgorithmChoice")
- reset("localLayoutAlgorithmChoice")
- shinyjs::hide("localLayoutAlgorithmChoice")
- # other
- reset("fps")
-}
diff --git a/functions/vr.R b/functions/vr.R
deleted file mode 100644
index 1f512e4..0000000
--- a/functions/vr.R
+++ /dev/null
@@ -1,113 +0,0 @@
-handleVRCall <- function() {
- tryCatch({
- producePLY(session$token)
- produceHTML(session$token)
- callJSHandler("handler_browseUrl", paste0(API_URL, session$token))
- }, error = function(e) {
- print(paste0("Error in VR parser: ", e))
- renderError("Error while parsing network for VR mode.")
- })
-}
-
-# void function that creates the user-specific VR ply file
-# and moves it to the api folder
-producePLY <- function(id) {
- filename <- paste0('tmp/', id, '.ply')
- con <- file(filename)
- open(con, "w")
- cat(sprintf("ply
-format ascii 1.0
-element vertex "), file = con)
- # number of nodes
- js_nodes <- jsonlite::fromJSON(input$js_nodes_world)
- cat(sprintf("%d", nrow(js_nodes)), file = con)
- cat(sprintf("\nproperty float x
-property float y
-property float z
-property uint8 red
-property uint8 green
-property uint8 blue
-element edge "), file = con)
- # number of edges
- js_edge_pairs <- jsonlite::fromJSON(input$js_edge_pairs)
- js_edge_colors <- jsonlite::fromJSON(input$js_edge_colors)
- edges <- as.data.frame(c(js_edge_pairs, js_edge_colors))
- cat(sprintf("%d", nrow(edges)), file = con)
- cat(sprintf("\nproperty int vertex1
-property int vertex2
-property uint8 red
-property uint8 green
-property uint8 blue
-end_header\n"), file = con)
-
- # node parsing
- js_nodes$worldPosition_x <-
- js_nodes$worldPosition_x / VR_DOWNSCALE_FACTOR
- js_nodes$worldPosition_y <-
- js_nodes$worldPosition_y / VR_DOWNSCALE_FACTOR
- js_nodes$worldPosition_z <-
- js_nodes$worldPosition_z / VR_DOWNSCALE_FACTOR - 5
-
- for (i in 1:nrow(js_nodes)){
- rgbColor <- col2rgb(js_nodes$color[i])
- cat(sprintf("%f %f %f %s %s %s\n",
- js_nodes$worldPosition_x[i], js_nodes$worldPosition_y[i],
- js_nodes$worldPosition_z[i], rgbColor[1], rgbColor[2],
- rgbColor[3]), file = con) # r,g,b
- }
- nodeNames <- paste0(js_nodes$name, "_", js_nodes$layer)
-
- # edge parsing
- for (i in 1:nrow(edges)){
- rgbColor <- col2rgb(edges$color[i])
- nodeIndex1 <- match(edges$src[i], nodeNames) - 1 # starting from index 0
- nodeIndex2 <- match(edges$trg[i], nodeNames) - 1
- cat(sprintf("%s %s %s %s %s\n",
- nodeIndex1, nodeIndex2, # from,to
- rgbColor[1], rgbColor[2], rgbColor[3]), file = con) # r,g,b
- }
- close(con)
-}
-
-# void function that creates the user-specific VR html file
-# and moves it to the api folder
-produceHTML <- function(id) {
- js_layers <- jsonlite::fromJSON(input$js_vr_layer_labels)
- js_layers$worldPosition_x <- as.numeric(js_layers$worldPosition_x) / VR_DOWNSCALE_FACTOR # pos x
- js_layers$worldPosition_y <- as.numeric(js_layers$worldPosition_y) / VR_DOWNSCALE_FACTOR + 1.5 # pos y
- js_layers$worldPosition_z <- as.numeric(js_layers$worldPosition_z) / VR_DOWNSCALE_FACTOR - 5 # pos z
-
- layers_string <- ""
- for (i in 1:nrow(js_layers)) {
- temp_str <- sprintf(' ',
- js_layers$name[i], js_layers$worldPosition_x[i],
- js_layers$worldPosition_y[i], js_layers$worldPosition_z[i])
- layers_string <- paste0(layers_string, temp_str, "\n")
- }
-
- html_string <- paste0('
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ', layers_string, '
-
-
-
-
-
-
-')
- filename <- paste0('tmp/', id, '.html')
- write(html_string, filename) # writing html file to tmp folder
-}
diff --git a/global.R b/global.R
deleted file mode 100644
index ed58bb0..0000000
--- a/global.R
+++ /dev/null
@@ -1,11 +0,0 @@
-# suppressMessages(suppressWarnings(library(shiny)))
-# suppressMessages(suppressWarnings(library(shinythemes)))
-# suppressMessages(suppressWarnings(library(RColorBrewer)))
-# suppressMessages(suppressWarnings(library(data.table)))
-# suppressMessages(suppressWarnings(library(fst)))
-# suppressMessages(suppressWarnings(library(tidyr)))
-# suppressMessages(suppressWarnings(library(igraph)))
-# suppressMessages(suppressWarnings(library(jsonlite)))
-
-suppressMessages(suppressWarnings(library(shinyjs)))
-suppressMessages(suppressWarnings(library(dplyr)))
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
new file mode 100644
index 0000000..0f366fd
--- /dev/null
+++ b/nginx/nginx.conf
@@ -0,0 +1,33 @@
+worker_processes auto;
+events { worker_connections 1024; }
+
+http {
+ include /etc/nginx/mime.types;
+ sendfile on;
+ client_max_body_size 20m; # network TSV / session JSON uploads
+
+ # Rate-limit the API: /api/layout and /api/topology run O(V*E) algorithms
+ # (betweenness) and are unauthenticated, so a script could saturate CPU.
+ # 20 r/s with burst 40 nodelay absorbs an interactive click-burst of a few
+ # requests while throttling a sustained flood. Per client IP.
+ limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
+
+ server {
+ listen 8080;
+
+ location /api/ {
+ limit_req zone=api burst=40 nodelay;
+ proxy_pass http://127.0.0.1:8000;
+ proxy_set_header Host $host;
+ }
+
+ location / {
+ root /usr/share/nginx/html;
+ try_files $uri /index.html;
+ # XSS defense-in-depth: blocks inline/injected scripts even if an
+ # escaping fix is missed somewhere else (see the layer/channel-name
+ # and node-url XSS fixes in frontend/src/).
+ add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self';" always;
+ }
+ }
+}
diff --git a/server.R b/server.R
deleted file mode 100644
index cdcc707..0000000
--- a/server.R
+++ /dev/null
@@ -1,222 +0,0 @@
-server <- function(input, output, session) {
- source("config/global_variables.R", local = T)
- source("config/server_variables.R", local = T)
- source("config/static_variables.R", local = T)
-
- source("functions/input.R", local = T)
- source("functions/js_handling.R", local = T)
- source("functions/init.R", local = T)
- source("functions/reset.R", local = T)
- source("functions/render.R", local = T)
- source("functions/general.R", local = T)
- source("functions/igraph/general.R", local = T)
- source("functions/igraph/layout.R", local = T)
- source("functions/igraph/cluster.R", local = T)
- source("functions/igraph/topology.R", local = T)
- source("functions/vr.R", local = T)
- source("functions/edges.R", local = T)
-
- # API GET file request ####
- observeEvent(session$clientData$url_search, {
- resolveAPI()
- })
-
- # START ####
- initializeServerApp()
-
- # ~Welcome ####
- observeEvent(input$link_to_examples, {
- updateNavbarPage(session, "navBar", selected = "Help")
- }, ignoreInit = T)
-
- observeEvent(input$link_to_fileInput, {
- updateNavbarPage(session, "navBar", selected = "File")
- }, ignoreInit = T)
-
- # JS variables ####
- observeEvent(input$js_direction_checkbox_flag, {
- updateCheckboxInputFromJS(input$js_direction_checkbox_flag[1],
- input$js_direction_checkbox_flag[2])
- }, ignoreInit = T)
-
- observeEvent(input$js_edgeByWeight_checkbox_flag, {
- updateCheckboxInputFromJS(input$js_edgeByWeight_checkbox_flag[1],
- input$js_edgeByWeight_checkbox_flag[2])
- }, ignoreInit = T)
-
- observeEvent(input$js_edgeFileColorPriority_checkbox_flag, {
- updateCheckboxInputFromJS(input$js_edgeFileColorPriority_checkbox_flag[1],
- input$js_edgeFileColorPriority_checkbox_flag[2])
- }, ignoreInit = T)
-
- observeEvent(input$js_channel_curvature_flag, {
- toggleCurvatureInputsFromJS()
- }, ignoreInit = T)
-
- # FILE I/O ####
- observeEvent(input$input_network_file, {
- handleUploadNetwork()
- }, ignoreInit = T)
-
- observeEvent(input$load_network_file, {
- handleLoadSession()
- }, ignoreInit = T)
-
- observeEvent(input$node_attributes_file, {
- handleInputNodeAttributeFileUpload()
- }, ignoreInit = T)
-
- observeEvent(input$edge_attributes_file, {
- handleInputEdgeAttributeFileUpload()
- }, ignoreInit = T)
-
- observeEvent(input$exampleButton, {
- handleLoadExample()
- }, ignoreInit = T)
-
- observeEvent(input$loadExample_ok, {
- handleLoadExampleAccept()
- }, ignoreInit = T)
-
- # LAYOUT ####
- observeEvent(input$runLayout, {
- handleLayout()
- }, ignoreInit = T)
-
- observeEvent(input$clusteringAlgorithmChoice, {
- handleClusterAlgorithmSelection()
- }, ignoreInit = T)
-
- observeEvent(input$runTopologyScale, {
- handleTopologyScaling()
- }, ignoreInit = T)
-
- # SCENE ####
- observeEvent(input$toggleSceneCoords, {
- callJSHandler("handler_toggleSceneCoords", input$toggleSceneCoords)
- }, ignoreInit = T)
-
- observeEvent(input$autoRotateScene, {
- callJSHandler("handler_autoRotateScene", input$autoRotateScene)
- }, ignoreInit = T)
-
- observeEvent(input$predefined_layout, {
- callJSHandler("handler_applyPredefinedLayout", input$predefined_layout)
- }, ignoreInit = T)
-
- # ~VR ####
- observeEvent(input$vr_button, {
- handleVRCall()
- }, ignoreInit = T)
-
- # LAYERS ####
- observeEvent(input$selectAllLayersCheckbox, {
- callJSHandler("handler_selectAllLayers", input$selectAllLayersCheckbox)
- }, ignoreInit = T)
-
- observeEvent(input$showLayerLabelsRadio, {
- callJSHandler("handler_showLayerLabels", input$showLayerLabelsRadio)
- }, ignoreInit = T)
-
- observeEvent(input$resizeLayerLabels, {
- callJSHandler("handler_resizeLayerLabels", input$resizeLayerLabels)
- }, ignoreInit = T)
-
- observeEvent(input$showLayerCoords, {
- callJSHandler("handler_showLayerCoords", input$showLayerCoords)
- }, ignoreInit = T)
-
- observeEvent(input$showWireFrames, {
- callJSHandler("handler_showWireFrames", input$showWireFrames)
- }, ignoreInit = T)
-
- observeEvent(input$layerColorPriorityRadio, {
- callJSHandler("handler_setLayerColorPriority", input$layerColorPriorityRadio)
- }, ignoreInit = T)
-
- observeEvent(input$layerOpacity, {
- callJSHandler("handler_floorOpacity", input$layerOpacity)
- }, ignoreInit = T)
-
- # NODES ####
- observeEvent(input$showNodeLabelsRadio, {
- callJSHandler("handler_showNodeLabels", input$showNodeLabelsRadio)
- }, ignoreInit = T)
-
- observeEvent(input$resizeNodeLabels, {
- callJSHandler("handler_resizeNodeLabels", input$resizeNodeLabels)
- }, ignoreInit = T)
-
- observeEvent(input$selectAllNodes, {
- callJSHandler("handler_selectAllNodes", input$selectAllNodes)
- }, ignoreInit = T)
-
- observeEvent(input$nodeGeometryRadio, {
- callJSHandler("handler_setNodeShape", input$nodeGeometryRadio)
- }, ignoreInit = T)
-
- observeEvent(input$nodeColorPriorityRadio, {
- callJSHandler("handler_setNodeColorPriority", input$nodeColorPriorityRadio)
- }, ignoreInit = T)
-
- observeEvent(input$nodeSelectedColorPriority, {
- callJSHandler("handler_setNodeSelectedColorPriority", input$nodeSelectedColorPriority)
- }, ignoreInit = T)
-
- # EDGES ####
- observeEvent(input$edgeSelectedColorPriority, {
- callJSHandler("handler_setEdgeSelectedColorPriority", input$edgeSelectedColorPriority)
- }, ignoreInit = T)
-
- observeEvent(input$edgeFileColorPriority, {
- callJSHandler("handler_setEdgeFileColorPriority", input$edgeFileColorPriority)
- }, ignoreInit = T)
-
- observeEvent(input$edgeWidthByWeight, {
- handleEdgeWidthByWeightCheckbox()
- }, ignoreInit = T)
-
- observeEvent(input$edgeDirectionToggle, {
- handleEdgeDirectionCheckbox()
- }, ignoreInit = T)
-
- observeEvent(input$interDirectionArrowSize, {
- callJSHandler("handler_setInterDirectionArrowSize", input$interDirectionArrowSize)
- }, ignoreInit = T)
-
- observeEvent(input$intraDirectionArrowSize, {
- callJSHandler("handler_setIntraDirectionArrowSize", input$intraDirectionArrowSize)
- }, ignoreInit = T)
-
- observeEvent(input$intraLayerEdgeOpacity, {
- callJSHandler("handler_setIntraLayerEdgeOpacity", input$intraLayerEdgeOpacity)
- }, ignoreInit = T)
-
- observeEvent(input$interLayerEdgeOpacity, {
- callJSHandler("handler_setInterLayerEdgeOpacity", input$interLayerEdgeOpacity)
- }, ignoreInit = T)
-
- observeEvent(input$intraChannelCurvature, {
- callJSHandler("handler_toggleIntraChannelCurvature", input$intraChannelCurvature)
- }, ignoreInit = T)
-
- observeEvent(input$interChannelCurvature, {
- callJSHandler("handler_toggleInterChannelCurvature", input$interChannelCurvature)
- }, ignoreInit = T)
-
- observeEvent(input$js_selectedEdges, {
- updateSelectedEdgesView(jsonlite::fromJSON(input$js_selectedEdges))
- }, ignoreInit = T)
-
- # FPS ####
- observeEvent(input$fps, {
- callJSHandler("handler_fps", input$fps)
- }, ignoreInit = T)
-
- # ~Hide buttons ####
- lapply(HIDE_BUTTONS, function(buttonId) {
- observeEvent(input[[buttonId]], {
- updateNavbarPage(session, "navBar", selected = "Main View")
- }, ignoreInit = T)
- })
-}
diff --git a/ui.R b/ui.R
deleted file mode 100644
index d66fe49..0000000
--- a/ui.R
+++ /dev/null
@@ -1,65 +0,0 @@
-source("config/global_variables.R", local = T)
-source("config/ui_variables.R", local = T)
-source("views/home.R", local = T)
-source("views/file.R", local = T)
-source("views/layouts.R", local = T)
-source("views/scene.R", local = T)
-source("views/layer.R", local = T)
-source("views/node.R", local = T)
-source("views/edge.R", local = T)
-source("views/fps.R", local = T)
-source("views/data.R", local = T)
-source("views/help.R", local = T)
-source("views/footer.R", local = T)
-
-ui <- fluidPage(
- useShinyjs(),
- tags$head(tags$link(rel = "stylesheet", type = "text/css", href = "arena3dweb.css")),
- tags$head(tags$script(src = "js/three/three.js")),
- tags$head(tags$script(src = "js/three/matrix4.js")), # for dragcontrols
- tags$head(tags$script(src = "js/three/drag_controls.js")),
- tags$head(tags$script(src = "js/config/global_variables.js")),
- tags$head(tags$script(src = "js/config/static_variables.js")),
- tags$head(tags$script(src = "js/classes/Scene.js")),
- tags$head(tags$script(src = "js/classes/Layer.js")),
- tags$head(tags$script(src = "js/classes/Node.js")),
- tags$head(tags$script(src = "js/classes/Edge.js")),
- tags$head(tags$script(src = "js/object_actions/screen.js")),
- tags$head(tags$script(src = "js/object_actions/network.js")),
- tags$head(tags$script(src = "js/object_actions/themes.js")),
- tags$head(tags$script(src = "js/object_actions/layout.js")),
- tags$head(tags$script(src = "js/object_actions/labels.js")),
- tags$head(tags$script(src = "js/object_actions/layer.js")),
- tags$head(tags$script(src = "js/object_actions/node.js")),
- tags$head(tags$script(src = "js/object_actions/edge.js")),
- tags$head(tags$script(src = "js/object_actions/canvas_controls.js")),
- tags$head(tags$script(src = "js/object_actions/right_click_menu.js")),
- tags$head(tags$script(src = "js/rshiny_handlers.js")),
- tags$head(tags$script(src = "js/rshiny_update.js")),
- tags$head(tags$script(src = "js/general.js")),
- tags$head(tags$script(src = "js/event_listeners.js")),
- tags$head(tags$script(src = "js/on_page_load.js")),
- theme = shinythemes::shinytheme("darkly"),
- navbarPage(
- title = "", id = "navBar", selected = "Home", windowTitle = "Arena3Dweb",
- tabPanel(title = "Home", generateHomeDiv()),
- tabPanel(title = "Main View"),
- tabPanel(title = "File", generateFileDiv()),
- tabPanel(title = "Layer Selection & Layouts", generateLayoutsDiv()),
- tabPanel(title = "Scene Actions", generateSceneDiv()),
- tabPanel(title = "Layer Actions", generateLayerDiv()),
- tabPanel(title = "Node Actions", generateNodeDiv()),
- tabPanel(title = "Edge Actions", generateEdgeDiv()),
- tabPanel(title = "View Data", generateDataDiv()),
- tabPanel(title = "FPS", generateFPSDiv()),
- tabPanel(title = "Help", generateHelpDiv()),
- ),
- tags$div(id = "navControlButtonsDiv"),
- tags$div(id = "info", "Waiting for Network to be uploaded."),
- tags$div(id = "logo"),
- tags$div(id = "themeDiv"),
- tags$div(id = "loader"),
- tags$div(id = "labelDiv"),
- tags$div(id = "descrDiv"),
- tags$div(id = "3d-graph")
-)
diff --git a/views/data.R b/views/data.R
deleted file mode 100644
index c7d5a4d..0000000
--- a/views/data.R
+++ /dev/null
@@ -1,33 +0,0 @@
-generateDataDiv <- function() {
- tags$div(
- class = "networkDataTab",
- tabsetPanel(
- id = "dataViewPanel",
- tabPanel(
- "Network Data",
- DT::dataTableOutput(outputId = "network_dataView")
- ),
- tabPanel(
- "Selected Edges",
- DT::dataTableOutput(outputId = "selectedEdges_dataView")
- ),
- tabPanel(
- "Clustering Data",
- DT::dataTableOutput(outputId = "clustering_dataView")
- ),
- tabPanel(
- "Degree",
- DT::dataTableOutput(outputId = "degree_dataView")
- ),
- tabPanel(
- "Clustering Coefficient",
- DT::dataTableOutput(outputId = "transitivity_dataView")
- ),
- tabPanel(
- "Betweenness Centrality",
- DT::dataTableOutput(outputId = "betweenness_dataView")
- )
- ),
- actionButton("hideButton7", icon("angle-up"), class = "hideButton")
- )
-}
diff --git a/views/edge.R b/views/edge.R
deleted file mode 100644
index 3d052c8..0000000
--- a/views/edge.R
+++ /dev/null
@@ -1,23 +0,0 @@
-generateEdgeDiv <- function() {
- tags$div(
- checkboxInput("edgeDirectionToggle", "Enable Edge Direction", F),
- sliderInput("intraDirectionArrowSize", "Intra-Layer Direction Arrow Size:",
- min = 1, max = 10, value = 5, step = 1),
- sliderInput("interDirectionArrowSize", "Inter-Layer Direction Arrow Size:",
- min = 1, max = 10, value = 5, step = 1),
- checkboxInput("edgeWidthByWeight", "Edge Opacity By Weight",
- value = T, width = NULL),
- sliderInput("intraLayerEdgeOpacity", "Intra-Layer Edge Opacity:",
- min = 0, max = 1, value = 1, step = 0.1),
- sliderInput("interLayerEdgeOpacity", "Inter-Layer Edge Opacity:",
- min = 0, max = 1, value = 0.4, step = 0.1),
- sliderInput("intraChannelCurvature", "Intra-Layer Channel Curvature:",
- min = 10, max = 20, value = 15, step = 1),
- sliderInput("interChannelCurvature", "Inter-Layer Channel Curvature:",
- min = 1, max = 10, value = 5, step = 1),
- checkboxInput("edgeSelectedColorPriority", "Highlight Selected Edges in Color", T),
- checkboxInput("edgeFileColorPriority", "Priority on Loaded Edge Color", F),
- tags$div(id = "channelColorPicker", class = "channelColorPicker"),
- actionButton("hideButton6", icon("angle-up"), class = "hideButton")
- )
-}
diff --git a/views/file.R b/views/file.R
deleted file mode 100644
index 7af6f06..0000000
--- a/views/file.R
+++ /dev/null
@@ -1,13 +0,0 @@
-generateFileDiv <- function() {
- tags$div(
- fileInput("input_network_file", "Upload Network:", accept = c(".tsv", ".txt")),
- fileInput("load_network_file", "Load Session:", accept = c(".json")),
- fileInput("node_attributes_file", "Upload NODE attributes:",
- accept = c(".tsv", ".txt")),
- fileInput("edge_attributes_file", "Upload EDGE attributes:",
- accept = c(".tsv", ".txt")),
- downloadButton("save_network_object", "Save Session"),
- actionButton("exampleButton", "Load Example", icon("message")),
- actionButton("hideButton1", icon("angle-up"), class = "hideButton")
- )
-}
diff --git a/views/footer.R b/views/footer.R
deleted file mode 100644
index 7d07175..0000000
--- a/views/footer.R
+++ /dev/null
@@ -1,10 +0,0 @@
-generateFooter <- function() {
- HTML('
-
- ')
-}
diff --git a/views/fps.R b/views/fps.R
deleted file mode 100644
index a1453f9..0000000
--- a/views/fps.R
+++ /dev/null
@@ -1,8 +0,0 @@
-generateFPSDiv <- function() {
- tags$div(
- radioButtons("fps", "FPS:",
- c("15FPS" = "15", "30FPS" = "30", "60FPS" = "60"),
- selected = "15"),
- actionButton("hideButton8", icon("angle-up"), class = "hideButton")
- )
-}
diff --git a/views/home.R b/views/home.R
deleted file mode 100644
index 22dafe6..0000000
--- a/views/home.R
+++ /dev/null
@@ -1,59 +0,0 @@
-generateHomeDiv <- function() {
-
- tags$div(
- id = "homeDiv",
- HTML('
-
-
-
-
-
-
-
-
-
-
- Arena3Dweb is the first, fully interactive and dependency-free, web application which allows the
- visualization of multi-layered graphs in 3D space. With Arena3Dweb , users can integrate multiple networks
- in a single view along with their intra- and inter-layer connections. For clearer and more informative views, users
- can choose between a plethora of layout algorithms and apply them on a set of selected layers either individually or
- in combination. Users can align networks and highlight node topological features, whereas each layer as well as the
- whole scene can be translated, rotated and scaled in 3D space. User-selected edge colors can be used to highlight
- important paths, while node positioning, coloring and resizing can be adjusted on-the-fly. In its current version,
- Arena3Dweb supports weighted and unweighted undirected graphs, is written in R, Shiny and Javascript.
-
-
- → To start using Arena3Dweb click on the File menu tab and upload a network.
-
-
- → Visit the'),
- actionLink("link_to_examples", "Help/Examples page"),
- HTML(' to download some Arena3Dweb example files.
-
-
- → Get started by uploading your networks'),
- actionLink("link_to_fileInput", "here."),
- HTML('
-
-
- Please ackwnoledge the use of Arena3Dweb by citing the following publications:
- - Kokoli, M., Karatzas, E., Baltoumas, F.A., Schneider, R., Pafilis, E., Paragkamian, S., Doncheva, N.T., Jensen, L.J.
- and Pavlopoulos, G., 2022.
- Arena3Dweb : interactive 3D visualization of multilayered networks
- supporting multiple directional information channels, clustering analysis and application integration,
- NAR Genomics and Bioinformatics, 5(2), p.lqad053.
- doi: https://doi.org/10.1093/nargab/lqad053 ; PubMed: 37260509
-
- - Karatzas, E., Baltoumas, F.A., Panayiotou, N.A., Schneider, R. and Pavlopoulos, G.A., 2021.
- Arena3Dweb : Interactive 3D visualization of multilayered networks,
- Nucleic Acids
- Research, 49(W1), pp.W36-W45.
- doi: https://doi.org/10.1093/nar/gkab278 ; PubMed: 33885790
-
-
-
- '),
- generateFooter()
- )
-}
diff --git a/views/layer.R b/views/layer.R
deleted file mode 100644
index befe91b..0000000
--- a/views/layer.R
+++ /dev/null
@@ -1,22 +0,0 @@
-generateLayerDiv <- function() {
- tags$div(
- radioButtons("showLayerLabelsRadio", "Show Labels:",
- inline = T,
- choiceNames = list("All", "Selected", "None"),
- choiceValues = list("all", "selected", "none")
- ),
- checkboxInput("showLayerCoords", "Show Layer Coord Systems", F),
- checkboxInput("showWireFrames", "Show Floors in Wireframes", F),
- sliderInput("resizeLayerLabels", "Resize Labels:",
- min = 12, max = 30, value = 20, step = 1),
- sliderInput("layerOpacity", "Floor Opacity:",
- min = 0, max = 1, value = 0.6, step = 0.05),
- radioButtons("layerColorPriorityRadio", "Color Priority:",
- inline = T,
- choiceNames = list("Default / Imported", "Theme / Colorpicker"),
- choiceValues = list("default", "picker")
- ),
- tags$div(id = "floorColorPicker", class = "colorPicker"),
- actionButton("hideButton4", icon("angle-up"), class = "hideButton")
- )
-}
diff --git a/views/layouts.R b/views/layouts.R
deleted file mode 100644
index 31c21ce..0000000
--- a/views/layouts.R
+++ /dev/null
@@ -1,29 +0,0 @@
-generateLayoutsDiv <- function() {
- tags$div(
- radioButtons("subgraphChoice", "Select Subgraph to Apply Calculations:",
- c("Per Layer" = "perLayer", "All Selected Layers" = "allLayers",
- "Local Layout for Selected Nodes Per Layer" = "nodesPerLayers")),
- checkboxInput("selectAllLayersCheckbox", "Select/Deselect All Layers", value = F),
- tags$div(id = "checkboxdiv", class = "checkboxdiv"),
- tags$div(id = "channelColorLayoutDiv", class = "channelColorLayoutDiv"),
- selectInput("layoutAlgorithmChoice", "Apply Layout Algorithm on Selected Layers:",
- c("-", "Fruchterman-Reingold", "Reingold-Tilford", "Circle",
- "Grid", "Random", "DrL", "Graphopt", "Kamada-Kawai",
- "Large Graph Layout", "Multidimensional Scaling", "Sugiyama"), # "Davidson-Harel", "GEM"
- width = "500px"),
- selectInput("clusteringAlgorithmChoice", "Apply Clustering on Selected Layers (Optional):",
- c("-", "Louvain", "Walktrap", "Fast Greedy", "Label Propagation"), # "Edge Betweenness"
- width = "500px"),
- selectInput("localLayoutAlgorithmChoice", "Apply Local Layout Algorithm:",
- c("-", "Fruchterman-Reingold", "Reingold-Tilford", "Circle",
- "Grid", "Random", "DrL", "Graphopt", "Kamada-Kawai",
- "Large Graph Layout", "Multidimensional Scaling", "Sugiyama"), # "Davidson-Harel", "GEM"
- width = "500px"),
- actionButton("runLayout", "Run", class = "runButton"),
- selectInput("topologyScaleMetricChoice", "Scale Nodes of Selected Layers by Topology Metric:",
- c("-", TOPOLOGY_METRICS),
- width = "500px"),
- actionButton("runTopologyScale", "Run", class = "runButton"),
- actionButton("hideButton2", icon("angle-up"), class = "hideButton")
- )
-}
diff --git a/views/node.R b/views/node.R
deleted file mode 100644
index e29a390..0000000
--- a/views/node.R
+++ /dev/null
@@ -1,31 +0,0 @@
-generateNodeDiv <- function() {
- tags$div(
- checkboxInput("selectAllNodes", "Select / Deselect all Nodes",
- value = F, width = NULL),
- radioButtons("showNodeLabelsRadio", "Show Labels:",
- inline = T,
- choiceNames = list("All", "Selected", "None"),
- choiceValues = list("all", "selected", "none"),
- selected = "selected"
- ),
- sliderInput("resizeNodeLabels", "Resize Labels:",
- min = 5, max = 15, value = 12, step = 1),
- radioButtons("nodeGeometryRadio", "Node Geometry:",
- inline = T,
- choiceNames = list("Sphere", "Box", "Diamond", "Cone"),
- choiceValues = list("sphere", "box", "diamond", "cone"),
- selected = "sphere"
- ),
- radioButtons("nodeColorPriorityRadio", "Color Priority:",
- inline = T,
- choiceNames = list("Default / Imported", "Clustering"),
- choiceValues = list("default", "cluster")
- ),
- checkboxInput("nodeSelectedColorPriority", "Highlight Selected Nodes", T),
- textAreaInput("nodeSearchBar", "Search Nodes:", value = "",
- width = "100%", height = "100%", cols = 100, rows = 4,
- placeholder = "Insert comma separated Node names and then hit the Enter button",
- resize = "vertical"),
- actionButton("hideButton5", icon("angle-up"), class = "hideButton")
- )
-}
diff --git a/views/scene.R b/views/scene.R
deleted file mode 100644
index 96129f5..0000000
--- a/views/scene.R
+++ /dev/null
@@ -1,12 +0,0 @@
-generateSceneDiv <- function() {
- tags$div(
- checkboxInput("toggleSceneCoords", "Show Scene Coord System", T),
- checkboxInput("autoRotateScene", "Enable Scene Auto Rotate", F),
- radioButtons("predefined_layout", "Select Predefined Layout:",
- c("Parallel Coordinates" = "parallel", "Zig Zag" = "zigZag",
- "Star" = "starLike", "Cube" = "cube") ),
- tags$div(id = "sceneColorPicker", class = "colorPicker"),
- actionButton("vr_button", "See in VR"),
- actionButton("hideButton3", icon("angle-up"), class = "hideButton")
- )
-}
diff --git a/www/arena3dweb.css b/www/arena3dweb.css
deleted file mode 100644
index 5defbc6..0000000
--- a/www/arena3dweb.css
+++ /dev/null
@@ -1,913 +0,0 @@
-/*** TAGS ***/
-body, div, h2, h3, a {
- font-family: Arial;
- margin: 0;
- padding: 0;
-}
-
-p {
- display: inline-block;
- width: 100%;
- word-break: break-word;
- font-family: Arial;
- line-height: 1.6;
- font-size: medium;
-}
-
-pre {
- font-family: Courier;
- border: 1px solid white;
- width: fit-content;
- max-width: 100%;
- overflow-x: scroll;
-}
-
-h5 {
- font-size: 18px;
-}
-
-ul li {
- font-size: medium;
- word-break: break-word;
-}
-
-a {
- color: lightblue;
-}
-
-label, span {
- font-size: 14px;
-}
-
-canvas {
- display: block;
- position: relative;
-}
-
-table {
- font-family: Arial;
- border: 1px solid lightgray;
- overflow-y: scroll;
- max-height: 65vh;
- display: block;
- margin-top: 10px;
- table-layout: fixed;
- text-align: center;
-}
-
-td {
- border: 1px solid lightgray;
-}
-
-input[type="range"] {
- -webkit-appearance: none;
- overflow: hidden;
- width: 100%;
- color: white;
-}
-
-input[type="range"]::-webkit-slider-runnable-track {
- -webkit-appearance: none;
- height: 15px;
- background-color: #817d82;
-}
-
-input[type="range"].sliderRed::-webkit-slider-runnable-track {
- background-color: #c90c2f;
-}
-
-input[type="range"].sliderGreen::-webkit-slider-runnable-track {
- background-color: #529109;
-}
-
-input[type="range"].sliderBlue::-webkit-slider-runnable-track {
- background-color: #044f91;
-}
-
-input[type="range"]::-webkit-slider-thumb {
- -webkit-appearance: none;
- width: 15px;
- height: 15px;
- cursor: ew-resize;
- background: white;
-}
-
-input[type="checkbox"] {
- margin-left: 5px;
- margin-right: 2px;
-}
-
-footer {
- font-family: Arial;
- position: fixed;
- bottom: 0;
- width: 100%;
- background-color: #2b4e99;
- text-align: center;
- font-size: 18px;
- margin-left: -8px;
- color: white;
-}
-
-/*** GENERAL SHINY ***/
-.container-fluid, .col-sm-12 {
- padding: 0;
-}
-
-.row {
- margin: 0;
-}
-
-.navbar {
- margin: 0;
- position: relative;
- z-index: 3;
-}
-
-.navbar-static-top {
- position: fixed !important;
- width: 100%;
-}
-
-.navbar-header {
- display: none;
-}
-
-.tab {
- overflow: hidden;
- background-color: #f1f1f1;
- position: relative;
- width: 100%;
- display: inline-block;
-}
-
-.tabcontent {
- display: none;
- padding: 1%;
- width: 100%;
- height: 100%;
- float: left;
- word-break: break-word;
- background-color: black;
- color: white;
- margin-top: -4px;
- text-align: justify;
- overflow:scroll;
-}
-
-.tab-content:nth-child(1) > .tab-pane { /* .tab-content:nth-child(1) = navBar */
- padding: 10px;
- position: fixed;
- margin-left: 250px;
- margin-top: 70px;
- z-index: 4;
- color: white;
- border: 1px solid white;
- background-color: black;
- overflow-y: auto;
- max-height: 70vh;
-}
-
-.tab-content:nth-child(1) > .tab-pane:nth-child(1), /* Welcome, Main View and Help border remove */
-.tab-content:nth-child(1) > .tab-pane:nth-child(2),
-.tab-content:nth-child(1) > .tab-pane:nth-child(11) {
- border: 0;
- padding: 0;
-}
-
-.tab button {
- background-color: inherit;
- float: left;
- border: none;
- outline: none;
- cursor: pointer;
- padding: 14px 16px;
- transition: 0.3s;
- font-size: 17px;
-}
-
-.tab button:hover {
- background-color: #ddd;
-}
-
-.tab button.active {
- background-color: #ccc;
-}
-
-.shiny-input-container,
-.btn,
-.form-control {
- font-size: 12px;
-}
-
-.hideButton {
- display: block;
- float: right;
-}
-
-/*** HOME ***/
-#logo1 {
- background-image: url(./images/logo.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 200px;
- height: 48px;
- position: relative;
- z-index: 1;
- float: left;
- margin-top: 25px;
- margin-right: 10px;
-}
-
-#homeDiv,
-#helpDiv {
- position: fixed;
- display: inline-block;
- color: black;
- font-size: 12px;
- background-color: black;
- border: 4px solid #2b4e99;
- width: 100%;
- height: 95%;
- margin-top: -10px;
- margin-left: -250px;
-}
-
-/*** MAIN VIEW ***/
-.main_tabcontent {
- padding: 1%;
- width: 100%;
- height: 100%;
- float: left;
- word-break: break-word;
- background-color: black;
- color: white;
- margin-top: -4px;
- text-align: justify;
-}
-
-/*** LAYOUTS ***/
-.runButton {
- margin-left: calc(100% - 56px);
- margin-bottom: 16px;
-}
-
-/*** LAYER ACTIONS ***/
-.checkboxdiv {
- width: 100%;
- max-width: 500px;
-}
-
-.checkbox_element {
- width: 10%;
- word-wrap: break-word;
-}
-
-.layer_label {
- width: 58%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-bottom: -5px;
-}
-
-.checkbox_check {
- width: 5%;
-}
-
-/*** EDGE ACTIONS ***/
-.channel_subcontainer {
- margin: -16px -8px;
- display: flex;
- justify-content: flex-start;
- align-items: center;
- padding: 4px;
-}
-
-.channel_subcontainer .channel_colorPicker {
- margin: 16px;
- margin-left: 8px;
-}
-
-.channel_subcontainer .channelLabel,
-.channel_subcontainer .channel_checkbox {
- margin: 16px 8px;
-}
-
-.channel_subcontainer .channelCheckboxLabel {
- margin: 8px 4px;
-}
-
-.channel_subcontainer .channelColorPicker h4 {
- margin-bottom: 20px;
-}
-
-.channelLayoutsub {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.channelLayoutsub label {
- margin: 0;
-}
-
-.channelLayoutsub .channel-tooltip {
- margin-left: 12px;
-}
-
-.channelColorLayoutDiv {
- margin: 16px 0;
-}
-
-.buttonChannelLayout {
- font-size: 24px;
-}
-
-.buttonChannelLayout.close {
- transform: rotate(180deg);
-}
-
-.display-none {
- display: none;
-}
-
-.channelsLayout div {
- display: flex;
- align-items: center;
-}
-
-.channelsLayout p {
- margin: 0;
- margin-left: 12px;
-}
-
-.channelsLayout input[type="checkbox"] {
- margin: 0;
-}
-
-/*** VIEW DATA ***/
-.networkDataTab {
- width: 70vw;
-}
-
-[id$="_dataView"] {
- background-color: lavender;
- padding: 5px;
- margin-bottom: 10px
-}
-
-/*** HELP PAGES ***/
-.last_p {
- margin-bottom: 100px;
-}
-
-.numbering {
- color: red;
- font-weight: bold;
- font-size: 18px;
-}
-
-.indent {
- margin-left: 20px;
-}
-
-/*** LABELS ***/
-#labelDiv {
- position: fixed;
- z-index: 1;
- display: inline-block;
- font-size: 12px;
-}
-
-.labels {
- position: absolute;
- text-align: center;
- z-index: 1;
- display: inline-block;
- font-weight: bold;
- white-space: nowrap;
-}
-
-.layer-labels {
- position: absolute;
- text-align: center;
- z-index: 1;
- display: inline-block;
- font-size: 20px;
- font-weight: bold;
- white-space: nowrap;
-}
-
-/*** CANVAS CONTROLS ***/
-#navControlButtonsDiv {
- position: fixed;
- z-index: 1;
- margin-top: 60px;
-}
-
-#info {
- width: 241px;
- position: fixed;
- text-align: left;
- z-index: 3;
- display: inline-block;
- color: white;
- font-size: 12px;
- margin-top: 140px;
- margin-left: 10px;
- background-color: black;
- padding: 1px;
-}
-
-.displayCanvasControls {
- margin-top: 18px;
- margin-left: 10px;
- color: black;
- text-align: center;
-}
-
-.border_tr {
- border-top: 10px solid lightgray;
-}
-
-.labelDrop {
- vertical-align: bottom;
-}
-
-.raiseSlider {
- margin-top: -30px;
-}
-
-.red {
- font-weight: bold;
- color: #FB3D2A;
-}
-
-.green {
- font-weight: bold;
- color: #46FB2A;
-}
-
-.blue {
- font-weight: bold;
- color: #2AC2FB;
-}
-
-.image_minusX {
- background-image: url(images/canvas_control/minusX.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_minusX:hover {
- background-image: url(images/canvas_control/minusX_hover.png);
-}
-
-.image_minusX:active {
- background-image: url(images/canvas_control/minusX_active.png);
-}
-
-.image_plusX {
- background-image: url(images/canvas_control/plusX.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_plusX:hover {
- background-image: url(images/canvas_control/plusX_hover.png);
-}
-
-.image_plusX:active {
- background-image: url(images/canvas_control/plusX_active.png);
-}
-
-.image_minusY {
- background-image: url(images/canvas_control/minusY.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_minusY:hover {
- background-image: url(images/canvas_control/minusY_hover.png);
-}
-
-.image_minusY:active {
- background-image: url(images/canvas_control/minusY_active.png);
-}
-
-.image_plusY {
- background-image: url(images/canvas_control/plusY.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_plusY:hover {
- background-image: url(images/canvas_control/plusY_hover.png);
-}
-
-.image_plusY:active {
- background-image: url(images/canvas_control/plusY_active.png);
-}
-
-.image_minusZ {
- background-image: url(images/canvas_control/minusZ.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_minusZ:hover {
- background-image: url(images/canvas_control/minusZ_hover.png);
-}
-
-.image_minusZ:active {
- background-image: url(images/canvas_control/minusZ_active.png);
-}
-
-.image_plusZ {
- background-image: url(images/canvas_control/plusZ.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_plusZ:hover {
- background-image: url(images/canvas_control/plusZ_hover.png);
-}
-
-.image_plusZ:active {
- background-image: url(images/canvas_control/plusZ_active.png);
-}
-
-.image_minusX_T {
- background-image: url(images/canvas_control/minusX_t.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_minusX_T:hover {
- background-image: url(images/canvas_control/minusX_hover_t.png);
-}
-
-.image_minusX_T:active {
- background-image: url(images/canvas_control/minusX_active_t.png);
-}
-
-.image_plusX_T {
- background-image: url(images/canvas_control/plusX_t.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_plusX_T:hover {
- background-image: url(images/canvas_control/plusX_hover_t.png);
-}
-
-.image_plusX_T:active {
- background-image: url(images/canvas_control/plusX_active_t.png);
-}
-
-.image_minusY_T {
- background-image: url(images/canvas_control/minusY_t.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_minusY_T:hover {
- background-image: url(images/canvas_control/minusY_hover_t.png);
-}
-
-.image_minusY_T:active {
- background-image: url(images/canvas_control/minusY_active_t.png);
-}
-
-.image_plusY_T {
- background-image: url(images/canvas_control/plusY_t.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_plusY_T:hover {
- background-image: url(images/canvas_control/plusY_hover_t.png);
-}
-
-.image_plusY_T:active {
- background-image: url(images/canvas_control/plusY_active_t.png);
-}
-
-.image_minusZ_T {
- background-image: url(images/canvas_control/minusZ_t.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_minusZ_T:hover {
- background-image: url(images/canvas_control/minusZ_hover_t.png);
-}
-
-.image_minusZ_T:active {
- background-image: url(images/canvas_control/minusZ_active_t.png);
-}
-
-.image_plusZ_T {
- background-image: url(images/canvas_control/plusZ_t.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 40px;
- height: 40px;
-}
-
-.image_plusZ_T:hover {
- background-image: url(images/canvas_control/plusZ_hover_t.png);
-}
-
-.image_plusZ_T:active {
- background-image: url(images/canvas_control/plusZ_active_t.png);
-}
-
-.image_expandLayers {
- background-image: url(images/canvas_control/expandLayers.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 50%;
- height: 45px;
-}
-
-.image_expandLayers:hover {
- background-image: url(images/canvas_control/expandLayers_hover.png);
-}
-
-.image_expandLayers:active {
- background-image: url(images/canvas_control/expandLayers_active.png);
-}
-
-.image_collapseLayers {
- background-image: url(images/canvas_control/collapseLayers.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 50%;
- height: 45px;
-}
-
-.image_collapseLayers:hover {
- background-image: url(images/canvas_control/collapseLayers_hover.png);
-}
-
-.image_collapseLayers:active {
- background-image: url(images/canvas_control/collapseLayers_active.png);
-}
-
-.image_nodeExpand {
- background-image: url(images/canvas_control/expandNodes.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 50%;
- height: 45px;
-}
-
-.image_nodeExpand:hover {
- background-image: url(images/canvas_control/expandNodes_hover.png);
-}
-
-.image_nodeExpand:active {
- background-image: url(images/canvas_control/expandNodes_active.png);
-}
-
-.image_nodeCollapse {
- background-image: url(images/canvas_control/collapseNodes.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 50%;
- height: 45px;
-}
-
-.image_nodeCollapse:hover {
- background-image: url(images/canvas_control/collapseNodes_hover.png);
-}
-
-.image_nodeCollapse:active {
- background-image: url(images/canvas_control/collapseNodes_active.png);
-}
-
-#nodeScaleSlider,
-#layerScaleSlider {
- width: 150%;
- margin-left: -50%;
-}
-
-/*** THEMES ***/
-#themeDiv {
- position: fixed;
- right: 10px;
- width: 200px;
- height: 20px;
- margin-top: 130px;
- z-index: 1;
-}
-
-.themeButton {
- width: 66px;
- height: 20px;
- font-weight: bold;
- font-size: 12px;
- vertical-align: top;
-}
-
-#lightThemeButton {
- color: #5c5c5c;
- background-color: white;
-}
-
-#darkThemeButton {
- color: white;
- background-color: black;
-}
-
-#grayThemeButton {
- color: #6e2a5a;
- background-color: #bababa;
-}
-
-/*** MODALS ***/
-.modal-dialog {
- text-align: center;
-}
-
-.modal-body {
- background-color: seagreen;
-}
-
-.sweet-alert p {
- display: inline-block !important;
-}
-
-/*** COMMAND BOX ***/
-.optionsBox {
- width: 150px;
- position: absolute;
- text-align: center;
- z-index: 2;
- display: inline-block;
- color: black;
- background-color: aliceblue;
- font-size: 16px;
-}
-
-/*** NODE DESCRIPTION ***/
-#descrDiv {
- position: relative;
- display: none;
- float: right;
- z-index: 3;
- background-color: black;
- height: 35vh;
- width: 15%;
- min-width: 200px;
- border: 4px solid #faf5e3;
- overflow-y: scroll;
- margin-right: -200px;
- margin-top: 160px;
-}
-
-.descrDiv_paragraph {
- color: white;
- padding: 15px;
- text-align: justify;
- text-indent: 15px;
- margin-top: 10px;
-}
-
-#closeButton {
- position: absolute;
- right: 0;
- float: right;
- background-color: red;
- border: 1px solid gray;
- border-radius: 5px;
-}
-
-#closeButton:hover {
- background-color: #ff756b;
- border: 1px solid white;
-}
-
-#closeButton:active {
- background-color: #ab150a;
-}
-
-/*** OTHER UI ELEMENTS ***/
-#logo {
- background-image: url(images/logo.png);
- background-repeat: no-repeat;
- background-position: center;
- width: 200px;
- height: 48px;
- position: relative;
- z-index: 1;
- float: right;
- margin-top: 70px;
- margin-right: 10px;
-}
-
-#loader {
- border: 16px solid #f3f3f3;
- border-radius: 50%;
- border-top: 16px solid #3498db;
- width: 120px;
- height: 120px;
- -webkit-animation: spin 2s linear infinite;
- /* Safari */
- animation: spin 2s linear infinite;
- margin-top: 20%;
- margin-left: 45%;
- position: fixed;
- z-index: 1;
- display: none;
-}
-
-/*** SAFARI ***/
-@-webkit-keyframes spin {
- 0% {
- -webkit-transform: rotate(0deg);
- }
-
- 100% {
- -webkit-transform: rotate(360deg);
- }
-}
-
-@keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
-
- 100% {
- transform: rotate(360deg);
- }
-}
-
-/*** @MEDIA ***/
-@media only screen and (max-width: 1179px) {
- /* .tab-content:nth-child(1) = navBar */
- .tab-content:nth-child(1) > .tab-pane {
- margin-top: 130px;
- }
-
- #helpDiv, #homeDiv {
- margin-top: -10px;
- }
-
- #navControlButtonsDiv {
- margin-top: 120px;
- }
-
- #info {
- margin-top: 200px;
- }
-}
-
-@media only screen and (max-width: 767px) {
- /* .tab-content:nth-child(1) = navBar */
- .tab-content:nth-child(1) > .tab-pane {
- margin-top: 15px;
- }
-
- #helpDiv,
- #homeDiv {
- margin-top: 450px;
- }
-
- #navControlButtonsDiv {
- margin-top: 460px;
- }
-
- #themeDiv {
- margin-top: 480px;
- }
-
- #info {
- margin-top: 540px;
- margin-bottom: 100px;
- }
-}
diff --git a/www/data/TAU_example.txt b/www/data/TAU_example.txt
deleted file mode 100644
index d9bd297..0000000
--- a/www/data/TAU_example.txt
+++ /dev/null
@@ -1,3534 +0,0 @@
-SourceNode SourceLayer TargetNode TargetLayer
-Group-1 Layer1 Mgstl Layer2
-Group-1 Layer1 KP78a Layer2
-Group-1 Layer1 CG9257 Layer2
-Group-1 Layer1 par-1 Layer2
-Group-1 Layer1 CG31064 Layer2
-Group-1 Layer1 LpR1 Layer2
-Group-1 Layer1 Rab39 Layer2
-Group-1 Layer1 LpR2 Layer2
-Group-1 Layer1 CG3529 Layer2
-Group-1 Layer1 Lam Layer2
-Group-1 Layer1 LamC Layer2
-Group-1 Layer1 Sap-r Layer2
-Group-1 Layer1 Vps60 Layer2
-Group-1 Layer1 lqf Layer2
-Group-1 Layer1 ninaC Layer2
-Group-1 Layer1 Hrs Layer2
-Group-1 Layer1 Arr2 Layer2
-Group-1 Layer1 Arr1 Layer2
-Group-1 Layer1 Rab5 Layer2
-Group-1 Layer1 CG32683 Layer2
-Group-1 Layer1 pins Layer2
-Group-1 Layer1 chp Layer2
-Group-2 Layer1 Sc2 Layer2
-Group-2 Layer1 Uch Layer2
-Group-2 Layer1 Prx2540-1 Layer2
-Group-2 Layer1 Ace Layer2
-Group-2 Layer1 CG4572 Layer2
-Group-2 Layer1 Sgt Layer2
-Group-2 Layer1 CG14715 Layer2
-Group-2 Layer1 CG10359 Layer2
-Group-2 Layer1 Ect3 Layer2
-Group-2 Layer1 CalpB Layer2
-Group-2 Layer1 Prx5 Layer2
-Group-2 Layer1 Ost48 Layer2
-Group-2 Layer1 Hsp60C Layer2
-Group-2 Layer1 Prx2540-2 Layer2
-Group-2 Layer1 Hsp60 Layer2
-Group-2 Layer1 Ubqn Layer2
-Group-2 Layer1 Hsp70Ab Layer2
-Group-2 Layer1 Ugt Layer2
-Group-2 Layer1 Iswi Layer2
-Group-2 Layer1 Trap1 Layer2
-Group-2 Layer1 Cat Layer2
-Group-2 Layer1 Hsc70-5 Layer2
-Group-2 Layer1 CG8209 Layer2
-Group-2 Layer1 DnaJ-H Layer2
-Group-2 Layer1 p47 Layer2
-Group-2 Layer1 Rad23 Layer2
-Group-2 Layer1 Cnx99A Layer2
-Group-2 Layer1 pont Layer2
-Group-2 Layer1 Cp1 Layer2
-Group-2 Layer1 cathD Layer2
-Group-2 Layer1 Uba2 Layer2
-Group-2 Layer1 Pfk Layer2
-Group-2 Layer1 Gdi Layer2
-Group-2 Layer1 Uch-L5 Layer2
-Group-2 Layer1 Rpt4R Layer2
-Group-2 Layer1 Prosalpha1 Layer2
-Group-2 Layer1 mor Layer2
-Group-2 Layer1 Bap55 Layer2
-Group-2 Layer1 Ufd1-like Layer2
-Group-2 Layer1 TER94 Layer2
-Group-2 Layer1 Hsc70-3 Layer2
-Group-2 Layer1 PyK Layer2
-Group-2 Layer1 Hsp83 Layer2
-Group-2 Layer1 ERp60 Layer2
-Group-2 Layer1 arm Layer2
-Group-2 Layer1 Rpt1 Layer2
-Group-2 Layer1 Rpn13 Layer2
-Group-2 Layer1 Rpt2 Layer2
-Group-2 Layer1 Rpn5 Layer2
-Group-2 Layer1 Rpt6R Layer2
-Group-2 Layer1 Rpn2 Layer2
-Group-2 Layer1 Pdi Layer2
-Group-2 Layer1 CaBP1 Layer2
-Group-2 Layer1 Gp93 Layer2
-Group-2 Layer1 Rpt4 Layer2
-Group-2 Layer1 Hop Layer2
-Group-2 Layer1 CG1416 Layer2
-Group-2 Layer1 Prosalpha6 Layer2
-Group-2 Layer1 Rpt5 Layer2
-Group-2 Layer1 Prosalpha7 Layer2
-Group-2 Layer1 Rpn6 Layer2
-Group-2 Layer1 CG8258 Layer2
-Group-2 Layer1 Rpt6 Layer2
-Group-2 Layer1 Rpn3 Layer2
-Group-2 Layer1 Rpn12 Layer2
-Group-2 Layer1 Prosbeta7 Layer2
-Group-2 Layer1 Usp5 Layer2
-Group-2 Layer1 CG2918 Layer2
-Group-2 Layer1 CG2852 Layer2
-Group-2 Layer1 CG1354 Layer2
-Group-2 Layer1 CG6891 Layer2
-Group-2 Layer1 HDAC6 Layer2
-Group-2 Layer1 Hsp27 Layer2
-Group-2 Layer1 His1 Layer2
-Group-2 Layer1 CG11577 Layer2
-Group-3 Layer1 shep Layer2
-Group-3 Layer1 CG5590 Layer2
-Group-3 Layer1 CG17597 Layer2
-Group-3 Layer1 ScpX Layer2
-Group-3 Layer1 Mfe2 Layer2
-Group-4 Layer1 CG4882 Layer2
-Group-4 Layer1 EfTuM Layer2
-Group-4 Layer1 Rbp2 Layer2
-Group-4 Layer1 eEF1delta Layer2
-Group-4 Layer1 CG10576 Layer2
-Group-4 Layer1 Nacalpha Layer2
-Group-4 Layer1 mRpS34 Layer2
-Group-4 Layer1 vig Layer2
-Group-4 Layer1 CG4679 Layer2
-Group-4 Layer1 mRpS35 Layer2
-Group-4 Layer1 mRpS7 Layer2
-Group-4 Layer1 mRpL44 Layer2
-Group-4 Layer1 mRpS22 Layer2
-Group-4 Layer1 eIF4G Layer2
-Group-4 Layer1 bonsai Layer2
-Group-4 Layer1 mRpS10 Layer2
-Group-4 Layer1 Srp54k Layer2
-Group-4 Layer1 mRpL12 Layer2
-Group-4 Layer1 Tctp Layer2
-Group-4 Layer1 eIF-2gamma Layer2
-Group-4 Layer1 mRpS23 Layer2
-Group-4 Layer1 mRpS18B Layer2
-Group-4 Layer1 ade3 Layer2
-Group-4 Layer1 mRpS9 Layer2
-Group-4 Layer1 mRpS5 Layer2
-Group-4 Layer1 Srp19 Layer2
-Group-4 Layer1 Srp72 Layer2
-Group-4 Layer1 eIF-3p66 Layer2
-Group-4 Layer1 Cctgamma Layer2
-Group-4 Layer1 Ef1beta Layer2
-Group-4 Layer1 Tango7 Layer2
-Group-4 Layer1 RpL10Ab Layer2
-Group-4 Layer1 Trip1 Layer2
-Group-4 Layer1 eIF3-S10 Layer2
-Group-4 Layer1 eIF3-S9 Layer2
-Group-4 Layer1 Tcp-1eta Layer2
-Group-4 Layer1 RpL13 Layer2
-Group-4 Layer1 RpS11 Layer2
-Group-4 Layer1 eIF3-S8 Layer2
-Group-4 Layer1 RpS6 Layer2
-Group-4 Layer1 RpS2 Layer2
-Group-4 Layer1 RpL12 Layer2
-Group-4 Layer1 RpS7 Layer2
-Group-4 Layer1 RpL10 Layer2
-Group-4 Layer1 RpS8 Layer2
-Group-4 Layer1 RpL30 Layer2
-Group-4 Layer1 RpS10a Layer2
-Group-4 Layer1 RpS17 Layer2
-Group-4 Layer1 RpLP0 Layer2
-Group-4 Layer1 eRF1 Layer2
-Group-4 Layer1 Elf Layer2
-Group-4 Layer1 RpS15Aa Layer2
-Group-4 Layer1 RpS27 Layer2
-Group-4 Layer1 RpS18 Layer2
-Group-4 Layer1 RpS10b Layer2
-Group-4 Layer1 RpS3A Layer2
-Group-4 Layer1 RpS16 Layer2
-Group-4 Layer1 RpS15Ab Layer2
-Group-4 Layer1 RpL3 Layer2
-Group-4 Layer1 Tcp-1zeta Layer2
-Group-4 Layer1 T-cp1 Layer2
-Group-4 Layer1 RpS3 Layer2
-Group-4 Layer1 RpL26 Layer2
-Group-4 Layer1 RpS12 Layer2
-Group-4 Layer1 CG5642 Layer2
-Group-4 Layer1 CG9769 Layer2
-Group-4 Layer1 CG7033 Layer2
-Group-4 Layer1 Cct5 Layer2
-Group-4 Layer1 RpLP2 Layer2
-Group-4 Layer1 RpS23 Layer2
-Group-4 Layer1 RpS14a Layer2
-Group-4 Layer1 Rack1 Layer2
-Group-4 Layer1 RpL11 Layer2
-Group-4 Layer1 RpS13 Layer2
-Group-4 Layer1 RpL18A Layer2
-Group-4 Layer1 RpS4 Layer2
-Group-4 Layer1 RpL4 Layer2
-Group-4 Layer1 sta Layer2
-Group-4 Layer1 wuho Layer2
-Group-4 Layer1 bsf Layer2
-Group-4 Layer1 kra Layer2
-Group-4 Layer1 Hsp67Bc Layer2
-Group-4 Layer1 su(r) Layer2
-Group-4 Layer1 FeCH Layer2
-Group-4 Layer1 CG8635 Layer2
-Group-5 Layer1 nito Layer2
-Group-5 Layer1 bol Layer2
-Group-5 Layer1 Kap-alpha3 Layer2
-Group-5 Layer1 CG2082 Layer2
-Group-5 Layer1 lig Layer2
-Group-5 Layer1 clu Layer2
-Group-5 Layer1 Dbp80 Layer2
-Group-5 Layer1 Plc21C Layer2
-Group-5 Layer1 Hrb27C Layer2
-Group-5 Layer1 bel Layer2
-Group-5 Layer1 Dp1 Layer2
-Group-5 Layer1 Lpin Layer2
-Group-5 Layer1 Pkc53E Layer2
-Group-5 Layer1 Nlp Layer2
-Group-5 Layer1 rin Layer2
-Group-5 Layer1 poe Layer2
-Group-5 Layer1 CG43367 Layer2
-Group-5 Layer1 emb Layer2
-Group-5 Layer1 fne Layer2
-Group-5 Layer1 RanBPM Layer2
-Group-5 Layer1 yps Layer2
-Group-5 Layer1 Art4 Layer2
-Group-5 Layer1 CG10077 Layer2
-Group-5 Layer1 mub Layer2
-Group-5 Layer1 AGO1 Layer2
-Group-5 Layer1 glo Layer2
-Group-5 Layer1 Srp54 Layer2
-Group-5 Layer1 Rbp9 Layer2
-Group-5 Layer1 Rae1 Layer2
-Group-5 Layer1 Mtor Layer2
-Group-5 Layer1 Ref1 Layer2
-Group-5 Layer1 mago Layer2
-Group-5 Layer1 B52 Layer2
-Group-5 Layer1 LS2 Layer2
-Group-5 Layer1 U2af50 Layer2
-Group-5 Layer1 Fmr1 Layer2
-Group-5 Layer1 Top2 Layer2
-Group-5 Layer1 heph Layer2
-Group-5 Layer1 Hrb98DE Layer2
-Group-5 Layer1 Tudor-SN Layer2
-Group-5 Layer1 SF2 Layer2
-Group-5 Layer1 me31B Layer2
-Group-5 Layer1 CG16941 Layer2
-Group-5 Layer1 Prp8 Layer2
-Group-5 Layer1 Tango4 Layer2
-Group-5 Layer1 U2A Layer2
-Group-5 Layer1 smt3 Layer2
-Group-5 Layer1 pAbp Layer2
-Group-5 Layer1 Not1 Layer2
-Group-5 Layer1 Not3 Layer2
-Group-5 Layer1 CG2091 Layer2
-Group-5 Layer1 Capr Layer2
-Group-5 Layer1 Gbeta76C Layer2
-Group-5 Layer1 CG10777 Layer2
-Group-5 Layer1 CG6617 Layer2
-Group-5 Layer1 CG3800 Layer2
-Group-5 Layer1 tyf Layer2
-Group-5 Layer1 CG11505 Layer2
-Group-6 Layer1 CG6178 Layer2
-Group-6 Layer1 frj Layer2
-Group-6 Layer1 CG9090 Layer2
-Group-6 Layer1 Ssadh Layer2
-Group-6 Layer1 l(1)G0156 Layer2
-Group-6 Layer1 CG32026 Layer2
-Group-6 Layer1 Men-b Layer2
-Group-6 Layer1 CG11899 Layer2
-Group-6 Layer1 CG16935 Layer2
-Group-6 Layer1 AGBE Layer2
-Group-6 Layer1 ImpL3 Layer2
-Group-6 Layer1 Nc73EF Layer2
-Group-6 Layer1 CG3902 Layer2
-Group-6 Layer1 CG11876 Layer2
-Group-6 Layer1 Pdk Layer2
-Group-6 Layer1 Vha100-1 Layer2
-Group-6 Layer1 AcCoAS Layer2
-Group-6 Layer1 ACC Layer2
-Group-6 Layer1 Mdh2 Layer2
-Group-6 Layer1 Cyt-c-p Layer2
-Group-6 Layer1 Aldh Layer2
-Group-6 Layer1 CG15093 Layer2
-Group-6 Layer1 ND-51L1 Layer2
-Group-6 Layer1 ND-51 Layer2
-Group-6 Layer1 Vha68-2 Layer2
-Group-6 Layer1 Vha100-2 Layer2
-Group-6 Layer1 ND-15 Layer2
-Group-6 Layer1 CG12262 Layer2
-Group-6 Layer1 yip2 Layer2
-Group-6 Layer1 ade5 Layer2
-Group-6 Layer1 CG11089 Layer2
-Group-6 Layer1 COX6B Layer2
-Group-6 Layer1 CG11752 Layer2
-Group-6 Layer1 COX4 Layer2
-Group-6 Layer1 COX5A Layer2
-Group-6 Layer1 Eno Layer2
-Group-6 Layer1 Tpi Layer2
-Group-6 Layer1 ND-PDSW Layer2
-Group-6 Layer1 NP15.6 Layer2
-Group-6 Layer1 ATPsynbeta Layer2
-Group-6 Layer1 ATPsynCF6 Layer2
-Group-6 Layer1 CG10932 Layer2
-Group-6 Layer1 CG33791 Layer2
-Group-6 Layer1 CG3011 Layer2
-Group-6 Layer1 Men Layer2
-Group-6 Layer1 sun Layer2
-Group-6 Layer1 porin Layer2
-Group-6 Layer1 ND-B16.6 Layer2
-Group-6 Layer1 CG7603 Layer2
-Group-6 Layer1 Mpcp Layer2
-Group-7 Layer1 Ndg Layer2
-Group-7 Layer1 trol Layer2
-Group-7 Layer1 Cg25C Layer2
-Group-7 Layer1 vkg Layer2
-Group-7 Layer1 LanA Layer2
-Group-7 Layer1 LanB2 Layer2
-Group-7 Layer1 Glt Layer2
-Group-8 Layer1 Lar Layer2
-Group-8 Layer1 CG31715 Layer2
-Group-8 Layer1 PlexA Layer2
-Group-8 Layer1 gammaSnap2 Layer2
-Group-8 Layer1 CG10186 Layer2
-Group-8 Layer1 Klp10A Layer2
-Group-8 Layer1 Mtl Layer2
-Group-8 Layer1 bai Layer2
-Group-8 Layer1 Act79B Layer2
-Group-8 Layer1 Abl Layer2
-Group-8 Layer1 Tim17b Layer2
-Group-8 Layer1 p115 Layer2
-Group-8 Layer1 Sec24AB Layer2
-Group-8 Layer1 robl Layer2
-Group-8 Layer1 tsr Layer2
-Group-8 Layer1 flr Layer2
-Group-8 Layer1 FKBP59 Layer2
-Group-8 Layer1 bic Layer2
-Group-8 Layer1 BicD Layer2
-Group-8 Layer1 CadN Layer2
-Group-8 Layer1 syd Layer2
-Group-8 Layer1 Rac1 Layer2
-Group-8 Layer1 zip Layer2
-Group-8 Layer1 alphaCOP Layer2
-Group-8 Layer1 zetaCOP Layer2
-Group-8 Layer1 CG7048 Layer2
-Group-8 Layer1 CG7770 Layer2
-Group-8 Layer1 Dlic Layer2
-Group-8 Layer1 Dhc64C Layer2
-Group-8 Layer1 betaCOP Layer2
-Group-8 Layer1 cpa Layer2
-Group-8 Layer1 cpb Layer2
-Group-8 Layer1 epsilonCOP Layer2
-Group-8 Layer1 beta'COP Layer2
-Group-8 Layer1 Klc Layer2
-Group-8 Layer1 Khc Layer2
-Group-8 Layer1 alpha-Cat Layer2
-Group-8 Layer1 deltaCOP Layer2
-Group-8 Layer1 RhoGDI Layer2
-Group-8 Layer1 Dscam1 Layer2
-Group-8 Layer1 Tim9a Layer2
-Group-8 Layer1 sqh Layer2
-Group-8 Layer1 dock Layer2
-Group-8 Layer1 Dscam2 Layer2
-Group-8 Layer1 CG8498 Layer2
-Group-8 Layer1 CLIP-190 Layer2
-Group-8 Layer1 betaTub56D Layer2
-Group-9 Layer1 Mapmodulin Layer2
-Group-9 Layer1 Aats-val Layer2
-Group-9 Layer1 Aats-his Layer2
-Group-9 Layer1 Aats-asp Layer2
-Group-9 Layer1 CG33123 Layer2
-Group-9 Layer1 Aats-ile Layer2
-Group-9 Layer1 Aats-arg Layer2
-Group-9 Layer1 CG31739 Layer2
-CG6178 Layer2 yip2 Layer2
-shep Layer2 ScpX Layer2
-nito Layer2 Ref1 Layer2
-bol Layer2 Hrb27C Layer2
-Lar Layer2 Abl Layer2
-Lar Layer2 CadN Layer2
-CG31715 Layer2 cpb Layer2
-Mapmodulin Layer2 Aats-ile Layer2
-CG5590 Layer2 CG17597 Layer2
-Sc2 Layer2 Ost48 Layer2
-frj Layer2 CG10932 Layer2
-Kap-alpha3 Layer2 emb Layer2
-Uch Layer2 Usp5 Layer2
-Prx2540-1 Layer2 Prx2540-2 Layer2
-CG2082 Layer2 CG2091 Layer2
-lig Layer2 Capr Layer2
-lig Layer2 rin Layer2
-clu Layer2 Dp1 Layer2
-clu Layer2 eIF3-S10 Layer2
-clu Layer2 rin Layer2
-clu Layer2 eIF3-S8 Layer2
-clu Layer2 eIF3-S9 Layer2
-CG9090 Layer2 Cyt-c-p Layer2
-CG9090 Layer2 COX5A Layer2
-Ace Layer2 Cat Layer2
-Dbp80 Layer2 emb Layer2
-Ssadh Layer2 CG33791 Layer2
-Ssadh Layer2 CG32026 Layer2
-Ssadh Layer2 Mdh2 Layer2
-CG4572 Layer2 CG10576 Layer2
-CG4572 Layer2 Hsc70-3 Layer2
-PlexA Layer2 alpha-Cat Layer2
-l(1)G0156 Layer2 CG11876 Layer2
-l(1)G0156 Layer2 Ssadh Layer2
-l(1)G0156 Layer2 CG33791 Layer2
-Plc21C Layer2 Gbeta76C Layer2
-Plc21C Layer2 Pkc53E Layer2
-Hrb27C Layer2 poe Layer2
-Hrb27C Layer2 heph Layer2
-Hrb27C Layer2 PyK Layer2
-Hrb27C Layer2 bel Layer2
-Hrb27C Layer2 me31B Layer2
-Hrb27C Layer2 pAbp Layer2
-Hrb27C Layer2 TER94 Layer2
-Khc Layer2 TAU Layer2
-CG32026 Layer2 CG33791 Layer2
-bel Layer2 Ref1 Layer2
-bel Layer2 poe Layer2
-bel Layer2 emb Layer2
-bel Layer2 vig Layer2
-bel Layer2 me31B Layer2
-Dp1 Layer2 CG2918 Layer2
-Sgt Layer2 Trap1 Layer2
-Sgt Layer2 Tcp-1zeta Layer2
-Men-b Layer2 CG11876 Layer2
-Men-b Layer2 PyK Layer2
-Men-b Layer2 Mdh2 Layer2
-CG11899 Layer2 CG3011 Layer2
-Mgstl Layer2 CG3529 Layer2
-Mgstl Layer2 Ost48 Layer2
-CG14715 Layer2 CG2852 Layer2
-Lpin Layer2 Pkc53E Layer2
-gammaSnap2 Layer2 zetaCOP Layer2
-gammaSnap2 Layer2 betaCOP Layer2
-gammaSnap2 Layer2 epsilonCOP Layer2
-gammaSnap2 Layer2 beta'COP Layer2
-gammaSnap2 Layer2 deltaCOP Layer2
-CG10359 Layer2 Gp93 Layer2
-CG10359 Layer2 CG1354 Layer2
-CG10359 Layer2 CaBP1 Layer2
-CG10359 Layer2 Pdi Layer2
-KP78a Layer2 CG32683 Layer2
-KP78a Layer2 Arr2 Layer2
-CG9257 Layer2 poe Layer2
-CG9257 Layer2 CG3529 Layer2
-Ect3 Layer2 Rpn5 Layer2
-Ect3 Layer2 CG6891 Layer2
-Ect3 Layer2 Cat Layer2
-Ect3 Layer2 CalpB Layer2
-CG4882 Layer2 bonsai Layer2
-CalpB Layer2 CG6891 Layer2
-CalpB Layer2 Cat Layer2
-par-1 Layer2 CG32683 Layer2
-par-1 Layer2 KP78a Layer2
-par-1 Layer2 Arr2 Layer2
-Prx5 Layer2 Prx2540-2 Layer2
-Prx5 Layer2 Cat Layer2
-Pkc53E Layer2 zip Layer2
-CG31064 Layer2 Rab5 Layer2
-Nlp Layer2 mago Layer2
-Nlp Layer2 eEF1delta Layer2
-Nlp Layer2 smt3 Layer2
-CG10186 Layer2 betaCOP Layer2
-CG10186 Layer2 cpb Layer2
-CG10186 Layer2 cpa Layer2
-CG10186 Layer2 beta'COP Layer2
-CG10186 Layer2 bai Layer2
-CG10186 Layer2 epsilonCOP Layer2
-CG10186 Layer2 Dlic Layer2
-CG10186 Layer2 p115 Layer2
-CG10186 Layer2 zetaCOP Layer2
-CG10186 Layer2 deltaCOP Layer2
-rin Layer2 Capr Layer2
-LpR1 Layer2 Arr2 Layer2
-LpR1 Layer2 Arr1 Layer2
-LpR1 Layer2 Ubqn Layer2
-LpR1 Layer2 CG32683 Layer2
-LpR1 Layer2 lqf Layer2
-LpR1 Layer2 Hrs Layer2
-LpR1 Layer2 LpR2 Layer2
-LpR1 Layer2 Rab5 Layer2
-CG16935 Layer2 CG12262 Layer2
-CG16935 Layer2 yip2 Layer2
-poe Layer2 betaCOP Layer2
-poe Layer2 CG3529 Layer2
-CG43367 Layer2 betaCOP Layer2
-CG43367 Layer2 poe Layer2
-Rab39 Layer2 Rab5 Layer2
-Rab39 Layer2 Gdi Layer2
-emb Layer2 Rae1 Layer2
-emb Layer2 alphaCOP Layer2
-emb Layer2 CG10576 Layer2
-AGBE Layer2 Eno Layer2
-AGBE Layer2 PyK Layer2
-LpR2 Layer2 Ubqn Layer2
-LpR2 Layer2 CG32683 Layer2
-LpR2 Layer2 Arr2 Layer2
-LpR2 Layer2 Hrs Layer2
-LpR2 Layer2 lqf Layer2
-LpR2 Layer2 Rab5 Layer2
-CG3529 Layer2 Ost48 Layer2
-Ost48 Layer2 CG2918 Layer2
-Hsp60C Layer2 Hop Layer2
-Hsp60C Layer2 Hsc70-3 Layer2
-Prx2540-2 Layer2 Cat Layer2
-EfTuM Layer2 RpS14a Layer2
-EfTuM Layer2 Hsc70-5 Layer2
-EfTuM Layer2 RpL10Ab Layer2
-EfTuM Layer2 RpS13 Layer2
-EfTuM Layer2 RpS16 Layer2
-EfTuM Layer2 RpS23 Layer2
-EfTuM Layer2 RpS15Aa Layer2
-EfTuM Layer2 RpL4 Layer2
-EfTuM Layer2 CG11876 Layer2
-EfTuM Layer2 bonsai Layer2
-EfTuM Layer2 mRpS10 Layer2
-EfTuM Layer2 RpL12 Layer2
-EfTuM Layer2 sta Layer2
-EfTuM Layer2 RpL3 Layer2
-EfTuM Layer2 Hsp60 Layer2
-EfTuM Layer2 RpS18 Layer2
-EfTuM Layer2 RpS2 Layer2
-EfTuM Layer2 mRpS9 Layer2
-EfTuM Layer2 RpS3 Layer2
-EfTuM Layer2 mRpL12 Layer2
-EfTuM Layer2 RpL11 Layer2
-fne Layer2 Tango4 Layer2
-fne Layer2 Srp54 Layer2
-fne Layer2 CG10777 Layer2
-fne Layer2 Prp8 Layer2
-fne Layer2 mago Layer2
-fne Layer2 CG16941 Layer2
-fne Layer2 yps Layer2
-fne Layer2 CG10077 Layer2
-fne Layer2 Rbp9 Layer2
-fne Layer2 U2A Layer2
-fne Layer2 Pkc53E Layer2
-fne Layer2 Hrb98DE Layer2
-fne Layer2 LS2 Layer2
-fne Layer2 SF2 Layer2
-fne Layer2 mub Layer2
-fne Layer2 heph Layer2
-Klp10A Layer2 deltaCOP Layer2
-Mtl Layer2 zip Layer2
-Mtl Layer2 tsr Layer2
-Mtl Layer2 RhoGDI Layer2
-Hsp60 Layer2 CG7033 Layer2
-RanBPM Layer2 CG6617 Layer2
-yps Layer2 Tango4 Layer2
-yps Layer2 LS2 Layer2
-yps Layer2 mago Layer2
-yps Layer2 CG10777 Layer2
-Art4 Layer2 CG10777 Layer2
-Art4 Layer2 CG10077 Layer2
-Art4 Layer2 pAbp Layer2
-ImpL3 Layer2 Cat Layer2
-ImpL3 Layer2 Men-b Layer2
-ImpL3 Layer2 Pfk Layer2
-ImpL3 Layer2 Men Layer2
-ImpL3 Layer2 Eno Layer2
-ImpL3 Layer2 Tpi Layer2
-ImpL3 Layer2 PyK Layer2
-ImpL3 Layer2 CG11876 Layer2
-CG10077 Layer2 CG10777 Layer2
-CG10077 Layer2 LS2 Layer2
-CG10077 Layer2 Tango4 Layer2
-CG10077 Layer2 yps Layer2
-CG10077 Layer2 mago Layer2
-Lam Layer2 Rab5 Layer2
-LamC Layer2 Lam Layer2
-Ubqn Layer2 HDAC6 Layer2
-Ubqn Layer2 CG32683 Layer2
-mub Layer2 CG3800 Layer2
-mub Layer2 mago Layer2
-mub Layer2 U2A Layer2
-mub Layer2 Srp54 Layer2
-mub Layer2 Tango4 Layer2
-mub Layer2 Prp8 Layer2
-mub Layer2 CG10777 Layer2
-mub Layer2 CG16941 Layer2
-mub Layer2 yps Layer2
-mub Layer2 CG10077 Layer2
-mub Layer2 LS2 Layer2
-mub Layer2 Hrb98DE Layer2
-mub Layer2 Rbp9 Layer2
-mub Layer2 SF2 Layer2
-AGO1 Layer2 Rbp9 Layer2
-AGO1 Layer2 pAbp Layer2
-AGO1 Layer2 Rae1 Layer2
-AGO1 Layer2 me31B Layer2
-glo Layer2 Hrb98DE Layer2
-Sap-r Layer2 CG3529 Layer2
-Sap-r Layer2 Mgstl Layer2
-Sap-r Layer2 pins Layer2
-Sap-r Layer2 Rab5 Layer2
-Sap-r Layer2 Ost48 Layer2
-Ndg Layer2 Cg25C Layer2
-Ndg Layer2 LanB2 Layer2
-trol Layer2 Cp1 Layer2
-trol Layer2 LanB2 Layer2
-trol Layer2 Cg25C Layer2
-trol Layer2 Ndg Layer2
-Hsp70Ab Layer2 Pdi Layer2
-Hsp70Ab Layer2 DnaJ-H Layer2
-Hsp70Ab Layer2 Hop Layer2
-Hsp70Ab Layer2 Hsp27 Layer2
-bai Layer2 Srp19 Layer2
-bai Layer2 CG2852 Layer2
-bai Layer2 gammaSnap2 Layer2
-bai Layer2 Klp10A Layer2
-bai Layer2 Klc Layer2
-bai Layer2 Dlic Layer2
-bai Layer2 cpa Layer2
-bai Layer2 p115 Layer2
-bai Layer2 cpb Layer2
-bai Layer2 deltaCOP Layer2
-bai Layer2 betaCOP Layer2
-bai Layer2 epsilonCOP Layer2
-bai Layer2 beta'COP Layer2
-bai Layer2 zetaCOP Layer2
-Nc73EF Layer2 Ssadh Layer2
-Nc73EF Layer2 CG32026 Layer2
-Nc73EF Layer2 l(1)G0156 Layer2
-Nc73EF Layer2 CG33791 Layer2
-Nc73EF Layer2 CG11876 Layer2
-Srp54 Layer2 yps Layer2
-Srp54 Layer2 CG10777 Layer2
-Srp54 Layer2 mago Layer2
-Srp54 Layer2 LS2 Layer2
-Srp54 Layer2 CG10077 Layer2
-Srp54 Layer2 Tango4 Layer2
-Ugt Layer2 CG2918 Layer2
-Rbp9 Layer2 CG10777 Layer2
-Rbp9 Layer2 Tango4 Layer2
-Rbp9 Layer2 Srp54 Layer2
-Rbp9 Layer2 Prp8 Layer2
-Rbp9 Layer2 CG10077 Layer2
-Rbp9 Layer2 CG16941 Layer2
-Rbp9 Layer2 U2A Layer2
-Rbp9 Layer2 Pkc53E Layer2
-Rbp9 Layer2 LS2 Layer2
-Rbp9 Layer2 Hrb98DE Layer2
-Rbp9 Layer2 yps Layer2
-Rbp9 Layer2 mago Layer2
-Act79B Layer2 cpb Layer2
-Act79B Layer2 flr Layer2
-Act79B Layer2 zip Layer2
-Rbp2 Layer2 pAbp Layer2
-Rbp2 Layer2 RpL10Ab Layer2
-Rbp2 Layer2 RpS4 Layer2
-Rbp2 Layer2 RpL18A Layer2
-Rbp2 Layer2 RpS3 Layer2
-Rbp2 Layer2 RpS11 Layer2
-Rbp2 Layer2 eIF3-S9 Layer2
-Rbp2 Layer2 RpL11 Layer2
-Rbp2 Layer2 eIF3-S10 Layer2
-Rbp2 Layer2 RpL13 Layer2
-Rbp2 Layer2 eIF3-S8 Layer2
-Rbp2 Layer2 RpS10b Layer2
-Rbp2 Layer2 RpS10a Layer2
-Rbp2 Layer2 RpS27 Layer2
-Rbp2 Layer2 RpL3 Layer2
-Rbp2 Layer2 RpS12 Layer2
-Rbp2 Layer2 RpS7 Layer2
-Rbp2 Layer2 RpL12 Layer2
-Rbp2 Layer2 Tango7 Layer2
-Rbp2 Layer2 RpS23 Layer2
-Rbp2 Layer2 RpS13 Layer2
-Rbp2 Layer2 sta Layer2
-Rbp2 Layer2 RpS18 Layer2
-Rbp2 Layer2 RpS16 Layer2
-Rbp2 Layer2 RpL4 Layer2
-Rbp2 Layer2 RpS6 Layer2
-Rbp2 Layer2 Trip1 Layer2
-Rbp2 Layer2 RpS2 Layer2
-Rbp2 Layer2 eIF-3p66 Layer2
-Rbp2 Layer2 CG5642 Layer2
-Rbp2 Layer2 CG9769 Layer2
-Iswi Layer2 His1 Layer2
-Iswi Layer2 Bap55 Layer2
-Iswi Layer2 smt3 Layer2
-Iswi Layer2 mor Layer2
-Vps60 Layer2 Hrs Layer2
-eEF1delta Layer2 eIF3-S10 Layer2
-eEF1delta Layer2 RpS10b Layer2
-eEF1delta Layer2 RpS13 Layer2
-eEF1delta Layer2 RpS6 Layer2
-eEF1delta Layer2 RpS16 Layer2
-eEF1delta Layer2 RpL12 Layer2
-eEF1delta Layer2 sta Layer2
-eEF1delta Layer2 RpS2 Layer2
-eEF1delta Layer2 CG5642 Layer2
-eEF1delta Layer2 RpL13 Layer2
-eEF1delta Layer2 Trip1 Layer2
-CG10576 Layer2 RpS6 Layer2
-CG10576 Layer2 RpS4 Layer2
-CG10576 Layer2 Capr Layer2
-CG10576 Layer2 sta Layer2
-CG10576 Layer2 RpS16 Layer2
-CG10576 Layer2 Tcp-1zeta Layer2
-CG10576 Layer2 wuho Layer2
-CG10576 Layer2 RpL12 Layer2
-CG10576 Layer2 CG7033 Layer2
-Abl Layer2 Dscam1 Layer2
-Abl Layer2 syd Layer2
-Abl Layer2 alpha-Cat Layer2
-Abl Layer2 arm Layer2
-Rae1 Layer2 mago Layer2
-Mtor Layer2 mago Layer2
-Mtor Layer2 AGO1 Layer2
-Mtor Layer2 Hsc70-3 Layer2
-Mtor Layer2 Hsc70-5 Layer2
-Mtor Layer2 smt3 Layer2
-Mtor Layer2 Rae1 Layer2
-Ref1 Layer2 LS2 Layer2
-Ref1 Layer2 mago Layer2
-Trap1 Layer2 Hsp70Ab Layer2
-Trap1 Layer2 Hop Layer2
-Trap1 Layer2 Hsp60C Layer2
-Trap1 Layer2 Hsp60 Layer2
-Trap1 Layer2 CG2918 Layer2
-Trap1 Layer2 Hsc70-3 Layer2
-Trap1 Layer2 CG1416 Layer2
-CG17597 Layer2 Cat Layer2
-CG17597 Layer2 ScpX Layer2
-CG17597 Layer2 Mfe2 Layer2
-lqf Layer2 CG32683 Layer2
-lqf Layer2 Ubqn Layer2
-lqf Layer2 Arr2 Layer2
-lqf Layer2 Rab5 Layer2
-lqf Layer2 Hrs Layer2
-Tim17b Layer2 Tim9a Layer2
-CG3902 Layer2 CG10932 Layer2
-Cat Layer2 CG6891 Layer2
-p115 Layer2 deltaCOP Layer2
-Sec24AB Layer2 deltaCOP Layer2
-Sec24AB Layer2 betaCOP Layer2
-Sec24AB Layer2 epsilonCOP Layer2
-Sec24AB Layer2 zetaCOP Layer2
-Sec24AB Layer2 alphaCOP Layer2
-Sec24AB Layer2 beta'COP Layer2
-Sec24AB Layer2 p115 Layer2
-ninaC Layer2 Act79B Layer2
-ninaC Layer2 zip Layer2
-ninaC Layer2 Arr2 Layer2
-mago Layer2 sta Layer2
-mago Layer2 RpS6 Layer2
-mago Layer2 CG10777 Layer2
-CG11876 Layer2 Men Layer2
-CG11876 Layer2 Mdh2 Layer2
-CG11876 Layer2 PyK Layer2
-CG11876 Layer2 ND-PDSW Layer2
-CG11876 Layer2 AcCoAS Layer2
-CG11876 Layer2 Eno Layer2
-CG11876 Layer2 CG33791 Layer2
-Pdk Layer2 ade3 Layer2
-Pdk Layer2 CG11876 Layer2
-B52 Layer2 RpS3 Layer2
-B52 Layer2 Hrb27C Layer2
-B52 Layer2 glo Layer2
-B52 Layer2 CG10777 Layer2
-B52 Layer2 fne Layer2
-B52 Layer2 CG10077 Layer2
-B52 Layer2 yps Layer2
-B52 Layer2 Rbp9 Layer2
-B52 Layer2 CG16941 Layer2
-B52 Layer2 Tango4 Layer2
-B52 Layer2 U2A Layer2
-B52 Layer2 mago Layer2
-B52 Layer2 Srp54 Layer2
-B52 Layer2 mub Layer2
-B52 Layer2 heph Layer2
-B52 Layer2 Prp8 Layer2
-B52 Layer2 LS2 Layer2
-B52 Layer2 Hrb98DE Layer2
-B52 Layer2 U2af50 Layer2
-B52 Layer2 SF2 Layer2
-Vha100-1 Layer2 sun Layer2
-Vha100-1 Layer2 Vha100-2 Layer2
-Vha100-1 Layer2 Vha68-2 Layer2
-robl Layer2 Dlic Layer2
-Hsc70-5 Layer2 CG7033 Layer2
-Hsc70-5 Layer2 T-cp1 Layer2
-Hsc70-5 Layer2 Tpi Layer2
-Hsc70-5 Layer2 DnaJ-H Layer2
-Hsc70-5 Layer2 Tcp-1eta Layer2
-Hsc70-5 Layer2 Hop Layer2
-Hsc70-5 Layer2 Rae1 Layer2
-Hsc70-5 Layer2 Trap1 Layer2
-Hsc70-5 Layer2 Hsp60C Layer2
-Hsc70-5 Layer2 Hsc70-3 Layer2
-Hsc70-5 Layer2 Hsp60 Layer2
-Hsc70-5 Layer2 Gp93 Layer2
-tsr Layer2 sqh Layer2
-tsr Layer2 cpa Layer2
-tsr Layer2 RpS6 Layer2
-flr Layer2 cpa Layer2
-flr Layer2 tsr Layer2
-AcCoAS Layer2 CG12262 Layer2
-AcCoAS Layer2 CG10932 Layer2
-ACC Layer2 Eno Layer2
-ACC Layer2 CG10932 Layer2
-ACC Layer2 AcCoAS Layer2
-LS2 Layer2 mago Layer2
-LS2 Layer2 CG10777 Layer2
-FKBP59 Layer2 ninaC Layer2
-FKBP59 Layer2 Dlic Layer2
-FKBP59 Layer2 cpb Layer2
-FKBP59 Layer2 cpa Layer2
-FKBP59 Layer2 Hop Layer2
-U2af50 Layer2 Ref1 Layer2
-U2af50 Layer2 LS2 Layer2
-U2af50 Layer2 yps Layer2
-U2af50 Layer2 CG10777 Layer2
-U2af50 Layer2 CG10077 Layer2
-U2af50 Layer2 Rbp9 Layer2
-U2af50 Layer2 fne Layer2
-U2af50 Layer2 Srp54 Layer2
-U2af50 Layer2 mago Layer2
-U2af50 Layer2 mub Layer2
-U2af50 Layer2 Tango4 Layer2
-U2af50 Layer2 heph Layer2
-U2af50 Layer2 CG16941 Layer2
-U2af50 Layer2 Hrb98DE Layer2
-U2af50 Layer2 Prp8 Layer2
-U2af50 Layer2 SF2 Layer2
-U2af50 Layer2 U2A Layer2
-Fmr1 Layer2 dock Layer2
-Fmr1 Layer2 PyK Layer2
-Fmr1 Layer2 poe Layer2
-Fmr1 Layer2 bel Layer2
-Fmr1 Layer2 pAbp Layer2
-Fmr1 Layer2 eIF4G Layer2
-Fmr1 Layer2 Dscam1 Layer2
-Fmr1 Layer2 RanBPM Layer2
-Fmr1 Layer2 Hrb98DE Layer2
-Fmr1 Layer2 Hrb27C Layer2
-Fmr1 Layer2 Capr Layer2
-Fmr1 Layer2 TER94 Layer2
-Fmr1 Layer2 Rbp9 Layer2
-Fmr1 Layer2 me31B Layer2
-Fmr1 Layer2 AGO1 Layer2
-Fmr1 Layer2 vig Layer2
-ScpX Layer2 Cat Layer2
-ScpX Layer2 Mfe2 Layer2
-Top2 Layer2 RpS13 Layer2
-Top2 Layer2 Pkc53E Layer2
-Top2 Layer2 Uba2 Layer2
-Top2 Layer2 Iswi Layer2
-Top2 Layer2 smt3 Layer2
-CG8209 Layer2 Ufd1-like Layer2
-heph Layer2 Tango4 Layer2
-heph Layer2 Srp54 Layer2
-heph Layer2 CG16941 Layer2
-heph Layer2 CG10077 Layer2
-heph Layer2 CG10777 Layer2
-heph Layer2 U2A Layer2
-heph Layer2 mago Layer2
-heph Layer2 yps Layer2
-heph Layer2 Prp8 Layer2
-heph Layer2 mub Layer2
-heph Layer2 LS2 Layer2
-heph Layer2 pAbp Layer2
-heph Layer2 Hrb98DE Layer2
-heph Layer2 Rbp9 Layer2
-heph Layer2 SF2 Layer2
-Hrs Layer2 CG4572 Layer2
-Hrs Layer2 CG32683 Layer2
-Hrs Layer2 Arr2 Layer2
-Hrs Layer2 Ubqn Layer2
-Hrs Layer2 Rab5 Layer2
-Hrb98DE Layer2 CG10777 Layer2
-Hrb98DE Layer2 mago Layer2
-Hrb98DE Layer2 Srp54 Layer2
-Hrb98DE Layer2 CG16941 Layer2
-Hrb98DE Layer2 CG10077 Layer2
-Hrb98DE Layer2 yps Layer2
-Hrb98DE Layer2 LS2 Layer2
-Hrb98DE Layer2 Tango4 Layer2
-bic Layer2 RpS3 Layer2
-Nacalpha Layer2 RpS13 Layer2
-Nacalpha Layer2 RpS2 Layer2
-Nacalpha Layer2 sta Layer2
-Nacalpha Layer2 RpS6 Layer2
-Nacalpha Layer2 RpS16 Layer2
-Nacalpha Layer2 RpS27 Layer2
-Nacalpha Layer2 RpS4 Layer2
-Nacalpha Layer2 RpS23 Layer2
-Nacalpha Layer2 RpS18 Layer2
-Nacalpha Layer2 RpL12 Layer2
-Nacalpha Layer2 RpL18A Layer2
-Nacalpha Layer2 RpL11 Layer2
-Nacalpha Layer2 RpS10b Layer2
-Nacalpha Layer2 RpS17 Layer2
-Nacalpha Layer2 RpL13 Layer2
-Nacalpha Layer2 RpS3 Layer2
-Nacalpha Layer2 RpL4 Layer2
-Nacalpha Layer2 bic Layer2
-Aats-val Layer2 eEF1delta Layer2
-Aats-val Layer2 Ef1beta Layer2
-Aats-val Layer2 Aats-asp Layer2
-Aats-val Layer2 Aats-ile Layer2
-Aats-val Layer2 CG33123 Layer2
-Aats-val Layer2 Aats-arg Layer2
-BicD Layer2 Lam Layer2
-BicD Layer2 bic Layer2
-BicD Layer2 Khc Layer2
-BicD Layer2 cpb Layer2
-BicD Layer2 cpa Layer2
-BicD Layer2 Dlic Layer2
-DnaJ-H Layer2 CG2918 Layer2
-DnaJ-H Layer2 Hsc70-3 Layer2
-DnaJ-H Layer2 Hop Layer2
-mRpS34 Layer2 CG4882 Layer2
-mRpS34 Layer2 bonsai Layer2
-CadN Layer2 alpha-Cat Layer2
-CadN Layer2 arm Layer2
-vig Layer2 RpS15Aa Layer2
-vig Layer2 RpS13 Layer2
-vig Layer2 RpS16 Layer2
-vig Layer2 RpS17 Layer2
-vig Layer2 RpL12 Layer2
-Tudor-SN Layer2 bel Layer2
-Tudor-SN Layer2 AGO1 Layer2
-Tudor-SN Layer2 Fmr1 Layer2
-Tudor-SN Layer2 vig Layer2
-Mdh2 Layer2 Aldh Layer2
-Mdh2 Layer2 ND-51 Layer2
-Mdh2 Layer2 COX4 Layer2
-Mdh2 Layer2 Cyt-c-p Layer2
-Mdh2 Layer2 COX5A Layer2
-Mdh2 Layer2 Men Layer2
-Mdh2 Layer2 Eno Layer2
-Cyt-c-p Layer2 Cat Layer2
-Cyt-c-p Layer2 porin Layer2
-Cyt-c-p Layer2 ND-PDSW Layer2
-Cyt-c-p Layer2 COX6B Layer2
-Aldh Layer2 Cat Layer2
-Aldh Layer2 Eno Layer2
-Aldh Layer2 CG12262 Layer2
-Aldh Layer2 AcCoAS Layer2
-CG15093 Layer2 CG12262 Layer2
-CG15093 Layer2 AcCoAS Layer2
-CG15093 Layer2 CG3902 Layer2
-CG15093 Layer2 Aldh Layer2
-CG4679 Layer2 mRpS34 Layer2
-CG4679 Layer2 bsf Layer2
-CG4679 Layer2 CG4882 Layer2
-CG4679 Layer2 mRpL12 Layer2
-CG4679 Layer2 mRpL44 Layer2
-CG4679 Layer2 bonsai Layer2
-CG4679 Layer2 mRpS10 Layer2
-CG4679 Layer2 mRpS23 Layer2
-CG4679 Layer2 mRpS22 Layer2
-CG4679 Layer2 mRpS9 Layer2
-CG4679 Layer2 mRpS18B Layer2
-SF2 Layer2 CG10777 Layer2
-SF2 Layer2 CG10077 Layer2
-SF2 Layer2 U2A Layer2
-SF2 Layer2 Tango4 Layer2
-SF2 Layer2 yps Layer2
-SF2 Layer2 Prp8 Layer2
-SF2 Layer2 Rbp9 Layer2
-SF2 Layer2 CG16941 Layer2
-SF2 Layer2 Srp54 Layer2
-SF2 Layer2 Ref1 Layer2
-SF2 Layer2 LS2 Layer2
-SF2 Layer2 mago Layer2
-SF2 Layer2 Hrb98DE Layer2
-p47 Layer2 CG8209 Layer2
-p47 Layer2 Ufd1-like Layer2
-p47 Layer2 Rpt2 Layer2
-Rad23 Layer2 me31B Layer2
-Rad23 Layer2 Uch-L5 Layer2
-Rad23 Layer2 Rpt1 Layer2
-Rad23 Layer2 HDAC6 Layer2
-Rad23 Layer2 Usp5 Layer2
-Rad23 Layer2 Uba2 Layer2
-Rad23 Layer2 Ufd1-like Layer2
-Rad23 Layer2 Sgt Layer2
-Rad23 Layer2 Rpt5 Layer2
-Rad23 Layer2 Bap55 Layer2
-Rad23 Layer2 pont Layer2
-Rad23 Layer2 CG8209 Layer2
-Rad23 Layer2 Iswi Layer2
-Rad23 Layer2 Rpn13 Layer2
-Rad23 Layer2 Ubqn Layer2
-Rad23 Layer2 smt3 Layer2
-Rad23 Layer2 Rpt2 Layer2
-Rad23 Layer2 TER94 Layer2
-me31B Layer2 poe Layer2
-me31B Layer2 CG2091 Layer2
-me31B Layer2 yps Layer2
-ND-51L1 Layer2 ND-51 Layer2
-ND-51L1 Layer2 NP15.6 Layer2
-ND-51L1 Layer2 ND-PDSW Layer2
-ND-51L1 Layer2 ND-B16.6 Layer2
-syd Layer2 alpha-Cat Layer2
-syd Layer2 Dlic Layer2
-syd Layer2 Klc Layer2
-Arr2 Layer2 Ubqn Layer2
-Arr2 Layer2 CG32683 Layer2
-Arr1 Layer2 ninaC Layer2
-Arr1 Layer2 chp Layer2
-Arr1 Layer2 KP78a Layer2
-Arr1 Layer2 LpR2 Layer2
-Arr1 Layer2 Ubqn Layer2
-Arr1 Layer2 CG32683 Layer2
-Arr1 Layer2 par-1 Layer2
-Arr1 Layer2 Hrs Layer2
-Arr1 Layer2 lqf Layer2
-Arr1 Layer2 Rab5 Layer2
-Arr1 Layer2 Arr2 Layer2
-ND-51 Layer2 ND-B16.6 Layer2
-ND-51 Layer2 ND-PDSW Layer2
-CG16941 Layer2 yps Layer2
-CG16941 Layer2 mago Layer2
-CG16941 Layer2 CG10777 Layer2
-CG16941 Layer2 CG10077 Layer2
-CG16941 Layer2 Srp54 Layer2
-CG16941 Layer2 LS2 Layer2
-CG16941 Layer2 Tango4 Layer2
-Cnx99A Layer2 Ost48 Layer2
-Cnx99A Layer2 Hsp70Ab Layer2
-Cnx99A Layer2 CG2918 Layer2
-Cnx99A Layer2 Ugt Layer2
-Cnx99A Layer2 Gp93 Layer2
-Cnx99A Layer2 Pdi Layer2
-Cnx99A Layer2 Hsc70-3 Layer2
-Cnx99A Layer2 ERp60 Layer2
-Aats-his Layer2 Aats-asp Layer2
-Aats-his Layer2 Cct5 Layer2
-Aats-his Layer2 Aats-arg Layer2
-Aats-his Layer2 Aats-val Layer2
-Aats-his Layer2 CG33123 Layer2
-Aats-his Layer2 Aats-ile Layer2
-mRpS35 Layer2 RpS2 Layer2
-mRpS35 Layer2 CG4882 Layer2
-mRpS35 Layer2 mRpL12 Layer2
-mRpS35 Layer2 mRpS34 Layer2
-mRpS35 Layer2 mRpL44 Layer2
-mRpS35 Layer2 CG4679 Layer2
-mRpS35 Layer2 mRpS23 Layer2
-mRpS35 Layer2 mRpS18B Layer2
-mRpS35 Layer2 mRpS10 Layer2
-mRpS35 Layer2 mRpS9 Layer2
-mRpS35 Layer2 mRpS22 Layer2
-mRpS35 Layer2 bonsai Layer2
-mRpS35 Layer2 mRpS5 Layer2
-Vha68-2 Layer2 sun Layer2
-Vha68-2 Layer2 Eno Layer2
-Vha100-2 Layer2 sun Layer2
-Vha100-2 Layer2 Vha68-2 Layer2
-Rac1 Layer2 sqh Layer2
-Rac1 Layer2 Fmr1 Layer2
-Rac1 Layer2 tsr Layer2
-Rac1 Layer2 Dscam2 Layer2
-Rac1 Layer2 Gdi Layer2
-Rac1 Layer2 Abl Layer2
-Rac1 Layer2 dock Layer2
-Rac1 Layer2 zip Layer2
-Rac1 Layer2 RhoGDI Layer2
-mRpS7 Layer2 ade3 Layer2
-mRpS7 Layer2 CG9769 Layer2
-mRpS7 Layer2 CG4882 Layer2
-mRpS7 Layer2 eIF3-S8 Layer2
-mRpS7 Layer2 Srp19 Layer2
-mRpS7 Layer2 EfTuM Layer2
-mRpS7 Layer2 eIF3-S10 Layer2
-mRpS7 Layer2 eIF-2gamma Layer2
-mRpS7 Layer2 Srp54k Layer2
-mRpS7 Layer2 eRF1 Layer2
-mRpS7 Layer2 Tango7 Layer2
-mRpS7 Layer2 RpLP2 Layer2
-mRpS7 Layer2 eIF3-S9 Layer2
-mRpS7 Layer2 RpL4 Layer2
-mRpS7 Layer2 RpS10a Layer2
-mRpS7 Layer2 RpS12 Layer2
-mRpS7 Layer2 RpS10b Layer2
-mRpS7 Layer2 CG7048 Layer2
-mRpS7 Layer2 RpS15Ab Layer2
-mRpS7 Layer2 RpS15Aa Layer2
-mRpS7 Layer2 RpL10Ab Layer2
-mRpS7 Layer2 RpS14a Layer2
-mRpS7 Layer2 RpL18A Layer2
-mRpS7 Layer2 Rack1 Layer2
-mRpS7 Layer2 RpS4 Layer2
-mRpS7 Layer2 RpS17 Layer2
-mRpS7 Layer2 RpL13 Layer2
-mRpS7 Layer2 RpS7 Layer2
-mRpS7 Layer2 RpS6 Layer2
-mRpS7 Layer2 RpS8 Layer2
-mRpS7 Layer2 RpS16 Layer2
-mRpS7 Layer2 sta Layer2
-mRpS7 Layer2 RpS13 Layer2
-mRpS7 Layer2 RpL3 Layer2
-mRpS7 Layer2 RpS23 Layer2
-mRpS7 Layer2 RpS27 Layer2
-mRpS7 Layer2 RpL12 Layer2
-mRpS7 Layer2 RpS18 Layer2
-mRpS7 Layer2 RpS3A Layer2
-mRpS7 Layer2 RpS2 Layer2
-mRpS7 Layer2 RpS3 Layer2
-mRpS7 Layer2 RpS11 Layer2
-mRpS7 Layer2 RpL11 Layer2
-mRpS7 Layer2 RpL30 Layer2
-mRpS7 Layer2 mRpL44 Layer2
-mRpS7 Layer2 mRpS22 Layer2
-mRpS7 Layer2 mRpS34 Layer2
-mRpS7 Layer2 CG4679 Layer2
-mRpS7 Layer2 mRpS23 Layer2
-mRpS7 Layer2 bonsai Layer2
-mRpS7 Layer2 mRpS10 Layer2
-mRpS7 Layer2 mRpS35 Layer2
-mRpS7 Layer2 mRpL12 Layer2
-mRpS7 Layer2 mRpS18B Layer2
-mRpS7 Layer2 mRpS9 Layer2
-mRpS7 Layer2 mRpS5 Layer2
-Prp8 Layer2 yps Layer2
-Prp8 Layer2 Srp54 Layer2
-Prp8 Layer2 CG10777 Layer2
-Prp8 Layer2 CG10077 Layer2
-Prp8 Layer2 LS2 Layer2
-Prp8 Layer2 mago Layer2
-Prp8 Layer2 Hrb98DE Layer2
-Prp8 Layer2 CG16941 Layer2
-Prp8 Layer2 Tango4 Layer2
-pont Layer2 Tcp-1zeta Layer2
-pont Layer2 Tcp-1eta Layer2
-pont Layer2 Rpt6 Layer2
-pont Layer2 Uch-L5 Layer2
-mRpL44 Layer2 mRpS34 Layer2
-mRpL44 Layer2 mRpL12 Layer2
-mRpL44 Layer2 bonsai Layer2
-mRpS22 Layer2 CG4882 Layer2
-mRpS22 Layer2 mRpL12 Layer2
-mRpS22 Layer2 mRpS10 Layer2
-mRpS22 Layer2 mRpL44 Layer2
-mRpS22 Layer2 mRpS34 Layer2
-mRpS22 Layer2 mRpS23 Layer2
-mRpS22 Layer2 bonsai Layer2
-mRpS22 Layer2 mRpS18B Layer2
-mRpS22 Layer2 mRpS9 Layer2
-Cp1 Layer2 Ect3 Layer2
-Cp1 Layer2 PyK Layer2
-Cp1 Layer2 Rpn5 Layer2
-Cp1 Layer2 CG6891 Layer2
-Cp1 Layer2 CalpB Layer2
-Cp1 Layer2 Rpn6 Layer2
-Cp1 Layer2 Cat Layer2
-Cp1 Layer2 Rpt5 Layer2
-cathD Layer2 CG4572 Layer2
-cathD Layer2 CG6891 Layer2
-cathD Layer2 PyK Layer2
-cathD Layer2 CG8258 Layer2
-cathD Layer2 Ect3 Layer2
-cathD Layer2 Pfk Layer2
-cathD Layer2 Rpn5 Layer2
-cathD Layer2 CalpB Layer2
-cathD Layer2 Rpt5 Layer2
-cathD Layer2 Rpn6 Layer2
-cathD Layer2 Cat Layer2
-cathD Layer2 Sap-r Layer2
-cathD Layer2 Cp1 Layer2
-Tango4 Layer2 CG10777 Layer2
-Tango4 Layer2 LS2 Layer2
-Tango4 Layer2 mago Layer2
-U2A Layer2 yps Layer2
-U2A Layer2 CG10077 Layer2
-U2A Layer2 CG10777 Layer2
-U2A Layer2 Srp54 Layer2
-U2A Layer2 Hrb98DE Layer2
-U2A Layer2 mago Layer2
-U2A Layer2 LS2 Layer2
-U2A Layer2 CG16941 Layer2
-U2A Layer2 Prp8 Layer2
-U2A Layer2 Tango4 Layer2
-Uba2 Layer2 Usp5 Layer2
-Uba2 Layer2 Ubqn Layer2
-smt3 Layer2 tsr Layer2
-smt3 Layer2 mago Layer2
-smt3 Layer2 Ufd1-like Layer2
-smt3 Layer2 CG10777 Layer2
-smt3 Layer2 CG10077 Layer2
-smt3 Layer2 Rae1 Layer2
-smt3 Layer2 Uba2 Layer2
-zip Layer2 sqh Layer2
-ND-15 Layer2 CG11752 Layer2
-ND-15 Layer2 ND-51L1 Layer2
-ND-15 Layer2 COX5A Layer2
-ND-15 Layer2 ND-51 Layer2
-ND-15 Layer2 NP15.6 Layer2
-ND-15 Layer2 ND-B16.6 Layer2
-ND-15 Layer2 ND-PDSW Layer2
-pAbp Layer2 rin Layer2
-pAbp Layer2 RpL3 Layer2
-pAbp Layer2 RpL4 Layer2
-pAbp Layer2 emb Layer2
-pAbp Layer2 Dp1 Layer2
-pAbp Layer2 RpS6 Layer2
-pAbp Layer2 tyf Layer2
-pAbp Layer2 bel Layer2
-pAbp Layer2 yps Layer2
-pAbp Layer2 CG11505 Layer2
-pAbp Layer2 me31B Layer2
-eIF4G Layer2 Dp1 Layer2
-eIF4G Layer2 kra Layer2
-eIF4G Layer2 bel Layer2
-eIF4G Layer2 PlexA Layer2
-eIF4G Layer2 me31B Layer2
-eIF4G Layer2 RpL18A Layer2
-eIF4G Layer2 RpL12 Layer2
-eIF4G Layer2 RpS12 Layer2
-eIF4G Layer2 Hsp67Bc Layer2
-eIF4G Layer2 RpS4 Layer2
-eIF4G Layer2 RpL11 Layer2
-eIF4G Layer2 RpS23 Layer2
-eIF4G Layer2 RpS7 Layer2
-eIF4G Layer2 RpS27 Layer2
-eIF4G Layer2 RpLP2 Layer2
-eIF4G Layer2 RpS10b Layer2
-eIF4G Layer2 RpS10a Layer2
-eIF4G Layer2 sta Layer2
-eIF4G Layer2 RpS16 Layer2
-eIF4G Layer2 RpL4 Layer2
-eIF4G Layer2 RpL13 Layer2
-eIF4G Layer2 RpS18 Layer2
-eIF4G Layer2 RpS13 Layer2
-eIF4G Layer2 RpL3 Layer2
-eIF4G Layer2 RpL10Ab Layer2
-eIF4G Layer2 RpS11 Layer2
-eIF4G Layer2 RpS8 Layer2
-eIF4G Layer2 RpS2 Layer2
-eIF4G Layer2 Hsp27 Layer2
-eIF4G Layer2 eIF-3p66 Layer2
-eIF4G Layer2 mago Layer2
-eIF4G Layer2 Tango7 Layer2
-eIF4G Layer2 RpS3 Layer2
-eIF4G Layer2 CG5642 Layer2
-eIF4G Layer2 CG9769 Layer2
-eIF4G Layer2 Trip1 Layer2
-eIF4G Layer2 Rbp2 Layer2
-eIF4G Layer2 RpS6 Layer2
-eIF4G Layer2 alpha-Cat Layer2
-eIF4G Layer2 eIF3-S10 Layer2
-eIF4G Layer2 eIF3-S8 Layer2
-eIF4G Layer2 eIF3-S9 Layer2
-eIF4G Layer2 pAbp Layer2
-Pfk Layer2 CG11876 Layer2
-Pfk Layer2 CG6891 Layer2
-Pfk Layer2 Ect3 Layer2
-Pfk Layer2 Rpt5 Layer2
-Pfk Layer2 Cp1 Layer2
-Pfk Layer2 Rpn6 Layer2
-Pfk Layer2 CalpB Layer2
-Pfk Layer2 Rpn5 Layer2
-Pfk Layer2 Cat Layer2
-Pfk Layer2 Eno Layer2
-Pfk Layer2 Tpi Layer2
-Pfk Layer2 PyK Layer2
-bonsai Layer2 RpS14a Layer2
-bonsai Layer2 RpS16 Layer2
-bonsai Layer2 sta Layer2
-bonsai Layer2 RpS6 Layer2
-CG12262 Layer2 CG10932 Layer2
-yip2 Layer2 Uch Layer2
-yip2 Layer2 AcCoAS Layer2
-yip2 Layer2 CG10932 Layer2
-yip2 Layer2 CG3902 Layer2
-yip2 Layer2 CG12262 Layer2
-Rab5 Layer2 Mgstl Layer2
-Rab5 Layer2 CG32683 Layer2
-Rab5 Layer2 Ost48 Layer2
-Rab5 Layer2 CG3529 Layer2
-Rab5 Layer2 Arr2 Layer2
-Gdi Layer2 Cat Layer2
-Gdi Layer2 CG6891 Layer2
-Gdi Layer2 Pfk Layer2
-Gdi Layer2 Rpt1 Layer2
-Gdi Layer2 Rpt5 Layer2
-Gdi Layer2 Rpn5 Layer2
-Gdi Layer2 CG8258 Layer2
-Gdi Layer2 Rpn6 Layer2
-Gdi Layer2 Rpn3 Layer2
-Gdi Layer2 PyK Layer2
-Gdi Layer2 TER94 Layer2
-Gdi Layer2 RhoGDI Layer2
-Gdi Layer2 Rab5 Layer2
-mRpS10 Layer2 Srp54k Layer2
-mRpS10 Layer2 CG4882 Layer2
-mRpS10 Layer2 RpL10Ab Layer2
-mRpS10 Layer2 RpS14a Layer2
-mRpS10 Layer2 RpS16 Layer2
-mRpS10 Layer2 RpS10b Layer2
-mRpS10 Layer2 RpS13 Layer2
-mRpS10 Layer2 RpS2 Layer2
-mRpS10 Layer2 RpS15Aa Layer2
-mRpS10 Layer2 RpL3 Layer2
-mRpS10 Layer2 RpL12 Layer2
-mRpS10 Layer2 RpL13 Layer2
-mRpS10 Layer2 RpS4 Layer2
-mRpS10 Layer2 RpS17 Layer2
-mRpS10 Layer2 RpS6 Layer2
-mRpS10 Layer2 sta Layer2
-mRpS10 Layer2 mRpS23 Layer2
-mRpS10 Layer2 mRpS18B Layer2
-mRpS10 Layer2 mRpS34 Layer2
-mRpS10 Layer2 mRpL12 Layer2
-mRpS10 Layer2 mRpL44 Layer2
-mRpS10 Layer2 bonsai Layer2
-mRpS10 Layer2 mRpS9 Layer2
-Srp54k Layer2 bonsai Layer2
-Srp54k Layer2 RpS14a Layer2
-Srp54k Layer2 RpS15Aa Layer2
-Srp54k Layer2 RpS17 Layer2
-Srp54k Layer2 RpL10Ab Layer2
-Srp54k Layer2 RpS10b Layer2
-Srp54k Layer2 RpL12 Layer2
-Srp54k Layer2 RpS4 Layer2
-Srp54k Layer2 RpS6 Layer2
-Srp54k Layer2 RpS16 Layer2
-Srp54k Layer2 RpS12 Layer2
-Srp54k Layer2 sta Layer2
-Srp54k Layer2 Srp19 Layer2
-mRpL12 Layer2 RpS14a Layer2
-mRpL12 Layer2 RpL10Ab Layer2
-mRpL12 Layer2 RpS16 Layer2
-mRpL12 Layer2 sta Layer2
-mRpL12 Layer2 mRpS34 Layer2
-mRpL12 Layer2 RpS15Aa Layer2
-mRpL12 Layer2 RpL12 Layer2
-mRpL12 Layer2 bonsai Layer2
-Tctp Layer2 RpS10b Layer2
-Tctp Layer2 eEF1delta Layer2
-Tctp Layer2 RpL10Ab Layer2
-Tctp Layer2 RpS14a Layer2
-Tctp Layer2 RpS15Aa Layer2
-Tctp Layer2 RpS4 Layer2
-Tctp Layer2 RpS17 Layer2
-Tctp Layer2 sta Layer2
-Tctp Layer2 RpS6 Layer2
-Tctp Layer2 RpS16 Layer2
-Tctp Layer2 RpL12 Layer2
-Tctp Layer2 RpS13 Layer2
-Tctp Layer2 RpS12 Layer2
-Tctp Layer2 RpS2 Layer2
-Tctp Layer2 RpL13 Layer2
-eIF-2gamma Layer2 Tcp-1zeta Layer2
-eIF-2gamma Layer2 Tcp-1eta Layer2
-eIF-2gamma Layer2 CG10576 Layer2
-eIF-2gamma Layer2 Cctgamma Layer2
-eIF-2gamma Layer2 CG7033 Layer2
-eIF-2gamma Layer2 pAbp Layer2
-eIF-2gamma Layer2 Cct5 Layer2
-eIF-2gamma Layer2 RpS14a Layer2
-eIF-2gamma Layer2 CG8258 Layer2
-eIF-2gamma Layer2 CG33123 Layer2
-eIF-2gamma Layer2 CG1354 Layer2
-eIF-2gamma Layer2 RpS15Ab Layer2
-eIF-2gamma Layer2 RpS15Aa Layer2
-eIF-2gamma Layer2 T-cp1 Layer2
-eIF-2gamma Layer2 RpS17 Layer2
-eIF-2gamma Layer2 RpL10Ab Layer2
-eIF-2gamma Layer2 RpLP2 Layer2
-eIF-2gamma Layer2 RpL18A Layer2
-eIF-2gamma Layer2 Rbp2 Layer2
-eIF-2gamma Layer2 CG5642 Layer2
-eIF-2gamma Layer2 RpL30 Layer2
-eIF-2gamma Layer2 eIF-3p66 Layer2
-eIF-2gamma Layer2 eIF4G Layer2
-eIF-2gamma Layer2 RpS3A Layer2
-eIF-2gamma Layer2 CG9769 Layer2
-eIF-2gamma Layer2 RpL12 Layer2
-eIF-2gamma Layer2 RpL11 Layer2
-eIF-2gamma Layer2 RpS8 Layer2
-eIF-2gamma Layer2 RpS10a Layer2
-eIF-2gamma Layer2 RpS10b Layer2
-eIF-2gamma Layer2 RpL13 Layer2
-eIF-2gamma Layer2 RpS27 Layer2
-eIF-2gamma Layer2 RpS23 Layer2
-eIF-2gamma Layer2 RpS18 Layer2
-eIF-2gamma Layer2 RpS11 Layer2
-eIF-2gamma Layer2 RpS7 Layer2
-eIF-2gamma Layer2 RpL3 Layer2
-eIF-2gamma Layer2 RpS12 Layer2
-eIF-2gamma Layer2 RpL4 Layer2
-eIF-2gamma Layer2 RpS4 Layer2
-eIF-2gamma Layer2 RpS13 Layer2
-eIF-2gamma Layer2 Trip1 Layer2
-eIF-2gamma Layer2 RpS2 Layer2
-eIF-2gamma Layer2 eIF3-S10 Layer2
-eIF-2gamma Layer2 RpS16 Layer2
-eIF-2gamma Layer2 RpS6 Layer2
-eIF-2gamma Layer2 Tango7 Layer2
-eIF-2gamma Layer2 sta Layer2
-eIF-2gamma Layer2 RpS3 Layer2
-eIF-2gamma Layer2 eIF3-S8 Layer2
-eIF-2gamma Layer2 eIF3-S9 Layer2
-Uch-L5 Layer2 Usp5 Layer2
-Uch-L5 Layer2 Rpt4R Layer2
-Uch-L5 Layer2 Rpn12 Layer2
-Rpt4R Layer2 Rpn12 Layer2
-Prosalpha1 Layer2 Rpt4R Layer2
-Prosalpha1 Layer2 Rpn2 Layer2
-Prosalpha1 Layer2 Rpn12 Layer2
-Prosalpha1 Layer2 Rpn6 Layer2
-Prosalpha1 Layer2 Rpt6R Layer2
-Prosalpha1 Layer2 Rpn5 Layer2
-Prosalpha1 Layer2 Rpt6 Layer2
-Prosalpha1 Layer2 Rpt5 Layer2
-Prosalpha1 Layer2 Prosalpha6 Layer2
-Prosalpha1 Layer2 Rpt2 Layer2
-Prosalpha1 Layer2 Prosbeta7 Layer2
-mRpS23 Layer2 CG4882 Layer2
-mRpS23 Layer2 mRpL44 Layer2
-mRpS23 Layer2 mRpL12 Layer2
-mRpS23 Layer2 bonsai Layer2
-mRpS23 Layer2 mRpS34 Layer2
-mRpS18B Layer2 CG4882 Layer2
-mRpS18B Layer2 mRpL12 Layer2
-mRpS18B Layer2 bonsai Layer2
-mRpS18B Layer2 mRpS34 Layer2
-mRpS18B Layer2 mRpL44 Layer2
-mRpS18B Layer2 mRpS23 Layer2
-mor Layer2 pont Layer2
-Bap55 Layer2 Uch-L5 Layer2
-Bap55 Layer2 pont Layer2
-Bap55 Layer2 mor Layer2
-ade5 Layer2 CG3011 Layer2
-Cg25C Layer2 LanA Layer2
-vkg Layer2 Glt Layer2
-vkg Layer2 LanA Layer2
-vkg Layer2 Ndg Layer2
-vkg Layer2 trol Layer2
-vkg Layer2 Cg25C Layer2
-Ufd1-like Layer2 Ubqn Layer2
-Ufd1-like Layer2 Usp5 Layer2
-TER94 Layer2 poe Layer2
-TER94 Layer2 bel Layer2
-TER94 Layer2 Cnx99A Layer2
-TER94 Layer2 ERp60 Layer2
-TER94 Layer2 Pdi Layer2
-TER94 Layer2 smt3 Layer2
-TER94 Layer2 Hop Layer2
-TER94 Layer2 Hsc70-3 Layer2
-TER94 Layer2 CalpB Layer2
-TER94 Layer2 Pfk Layer2
-TER94 Layer2 CG6891 Layer2
-TER94 Layer2 Ect3 Layer2
-TER94 Layer2 PyK Layer2
-TER94 Layer2 Rpn2 Layer2
-TER94 Layer2 Cat Layer2
-TER94 Layer2 Cp1 Layer2
-TER94 Layer2 me31B Layer2
-TER94 Layer2 CG8258 Layer2
-TER94 Layer2 cathD Layer2
-TER94 Layer2 Ubqn Layer2
-TER94 Layer2 Rpn5 Layer2
-TER94 Layer2 Rpn6 Layer2
-TER94 Layer2 Rpt1 Layer2
-TER94 Layer2 Rpt5 Layer2
-TER94 Layer2 CG8209 Layer2
-TER94 Layer2 Rpt2 Layer2
-TER94 Layer2 p47 Layer2
-TER94 Layer2 Ufd1-like Layer2
-ade3 Layer2 RpS15Aa Layer2
-ade3 Layer2 RpL12 Layer2
-ade3 Layer2 RpS14a Layer2
-ade3 Layer2 RpL10Ab Layer2
-ade3 Layer2 su(r) Layer2
-ade3 Layer2 RpS6 Layer2
-ade3 Layer2 RpS4 Layer2
-ade3 Layer2 ade5 Layer2
-ade3 Layer2 CG3011 Layer2
-mRpS9 Layer2 CG4882 Layer2
-mRpS9 Layer2 ade3 Layer2
-mRpS9 Layer2 RpS14a Layer2
-mRpS9 Layer2 RpL10Ab Layer2
-mRpS9 Layer2 RpS16 Layer2
-mRpS9 Layer2 sta Layer2
-mRpS9 Layer2 RpS13 Layer2
-mRpS9 Layer2 RpS15Aa Layer2
-mRpS9 Layer2 RpL12 Layer2
-mRpS9 Layer2 RpS2 Layer2
-mRpS9 Layer2 mRpS34 Layer2
-mRpS9 Layer2 mRpL44 Layer2
-mRpS9 Layer2 mRpS18B Layer2
-mRpS9 Layer2 mRpS23 Layer2
-mRpS9 Layer2 bonsai Layer2
-mRpS9 Layer2 mRpL12 Layer2
-mRpS5 Layer2 CG11876 Layer2
-mRpS5 Layer2 RpS16 Layer2
-mRpS5 Layer2 RpS14a Layer2
-mRpS5 Layer2 RpL10Ab Layer2
-mRpS5 Layer2 RpLP2 Layer2
-mRpS5 Layer2 CG4882 Layer2
-mRpS5 Layer2 Srp54k Layer2
-mRpS5 Layer2 RpS10b Layer2
-mRpS5 Layer2 RpL4 Layer2
-mRpS5 Layer2 RpS10a Layer2
-mRpS5 Layer2 RpS8 Layer2
-mRpS5 Layer2 RpS13 Layer2
-mRpS5 Layer2 sta Layer2
-mRpS5 Layer2 RpL3 Layer2
-mRpS5 Layer2 RpL12 Layer2
-mRpS5 Layer2 RpS15Aa Layer2
-mRpS5 Layer2 RpS23 Layer2
-mRpS5 Layer2 RpS15Ab Layer2
-mRpS5 Layer2 RpS27 Layer2
-mRpS5 Layer2 RpS7 Layer2
-mRpS5 Layer2 EfTuM Layer2
-mRpS5 Layer2 RpL18A Layer2
-mRpS5 Layer2 RpL13 Layer2
-mRpS5 Layer2 RpS4 Layer2
-mRpS5 Layer2 RpS17 Layer2
-mRpS5 Layer2 RpS6 Layer2
-mRpS5 Layer2 RpS18 Layer2
-mRpS5 Layer2 RpS11 Layer2
-mRpS5 Layer2 RpL11 Layer2
-mRpS5 Layer2 RpS3 Layer2
-mRpS5 Layer2 mRpS34 Layer2
-mRpS5 Layer2 mRpS18B Layer2
-mRpS5 Layer2 mRpS23 Layer2
-mRpS5 Layer2 mRpS22 Layer2
-mRpS5 Layer2 CG4679 Layer2
-mRpS5 Layer2 mRpL44 Layer2
-mRpS5 Layer2 mRpL12 Layer2
-mRpS5 Layer2 mRpS10 Layer2
-mRpS5 Layer2 bonsai Layer2
-mRpS5 Layer2 mRpS9 Layer2
-Not1 Layer2 Iswi Layer2
-Not1 Layer2 eIF4G Layer2
-Not1 Layer2 pAbp Layer2
-Not1 Layer2 Prp8 Layer2
-Not1 Layer2 me31B Layer2
-Not3 Layer2 frj Layer2
-Not3 Layer2 me31B Layer2
-Not3 Layer2 Not1 Layer2
-Hsc70-3 Layer2 CG2852 Layer2
-Hsc70-3 Layer2 Hsp60 Layer2
-Hsc70-3 Layer2 Ost48 Layer2
-Hsc70-3 Layer2 Rae1 Layer2
-Hsc70-3 Layer2 CG2918 Layer2
-PyK Layer2 bel Layer2
-PyK Layer2 poe Layer2
-PyK Layer2 me31B Layer2
-PyK Layer2 Men Layer2
-PyK Layer2 Aldh Layer2
-PyK Layer2 Ect3 Layer2
-PyK Layer2 CG6891 Layer2
-PyK Layer2 CalpB Layer2
-PyK Layer2 ND-51 Layer2
-PyK Layer2 Rpn5 Layer2
-PyK Layer2 Vha68-2 Layer2
-PyK Layer2 Cat Layer2
-PyK Layer2 Mdh2 Layer2
-PyK Layer2 Eno Layer2
-LanA Layer2 LanB2 Layer2
-alphaCOP Layer2 deltaCOP Layer2
-Srp19 Layer2 RpS14a Layer2
-Srp19 Layer2 RpS15Aa Layer2
-Srp19 Layer2 RpS17 Layer2
-Srp19 Layer2 RpL10Ab Layer2
-Srp19 Layer2 RpS10b Layer2
-Srp19 Layer2 RpS6 Layer2
-Srp19 Layer2 RpS4 Layer2
-Srp19 Layer2 sta Layer2
-Srp19 Layer2 RpS12 Layer2
-Srp19 Layer2 RpL12 Layer2
-Srp19 Layer2 RpS16 Layer2
-Srp72 Layer2 RpS12 Layer2
-Srp72 Layer2 RpS10b Layer2
-Srp72 Layer2 RpS6 Layer2
-Srp72 Layer2 RpS13 Layer2
-Srp72 Layer2 RpL10Ab Layer2
-Srp72 Layer2 RpL13 Layer2
-Srp72 Layer2 sta Layer2
-Srp72 Layer2 RpS4 Layer2
-Srp72 Layer2 RpS2 Layer2
-Srp72 Layer2 RpL3 Layer2
-Srp72 Layer2 RpS16 Layer2
-Srp72 Layer2 RpL12 Layer2
-Srp72 Layer2 Srp54k Layer2
-Srp72 Layer2 Srp19 Layer2
-Hsp83 Layer2 Tcp-1zeta Layer2
-Hsp83 Layer2 Rack1 Layer2
-Hsp83 Layer2 zip Layer2
-Hsp83 Layer2 ERp60 Layer2
-Hsp83 Layer2 pont Layer2
-Hsp83 Layer2 Pdi Layer2
-Hsp83 Layer2 Tpi Layer2
-Hsp83 Layer2 HDAC6 Layer2
-Hsp83 Layer2 bai Layer2
-Hsp83 Layer2 Cctgamma Layer2
-Hsp83 Layer2 Cnx99A Layer2
-Hsp83 Layer2 CG2918 Layer2
-Hsp83 Layer2 Cct5 Layer2
-Hsp83 Layer2 CG7033 Layer2
-Hsp83 Layer2 Sgt Layer2
-Hsp83 Layer2 Hsp27 Layer2
-Hsp83 Layer2 T-cp1 Layer2
-Hsp83 Layer2 CalpB Layer2
-Hsp83 Layer2 Ect3 Layer2
-Hsp83 Layer2 CG6891 Layer2
-Hsp83 Layer2 Dlic Layer2
-Hsp83 Layer2 cpa Layer2
-Hsp83 Layer2 Cp1 Layer2
-Hsp83 Layer2 Dhc64C Layer2
-Hsp83 Layer2 Hsp60C Layer2
-Hsp83 Layer2 cpb Layer2
-Hsp83 Layer2 cathD Layer2
-Hsp83 Layer2 Pfk Layer2
-Hsp83 Layer2 Gdi Layer2
-Hsp83 Layer2 Rac1 Layer2
-Hsp83 Layer2 PyK Layer2
-Hsp83 Layer2 Rpn6 Layer2
-Hsp83 Layer2 Hsp60 Layer2
-Hsp83 Layer2 Rpn5 Layer2
-Hsp83 Layer2 Rpt1 Layer2
-Hsp83 Layer2 Rpn3 Layer2
-Hsp83 Layer2 Rpt5 Layer2
-Hsp83 Layer2 Hsp70Ab Layer2
-Hsp83 Layer2 Cat Layer2
-Hsp83 Layer2 CG8258 Layer2
-Hsp83 Layer2 Hsc70-5 Layer2
-Hsp83 Layer2 FKBP59 Layer2
-Hsp83 Layer2 TER94 Layer2
-Hsp83 Layer2 DnaJ-H Layer2
-Hsp83 Layer2 Hsc70-3 Layer2
-Hsp83 Layer2 CG1416 Layer2
-Hsp83 Layer2 Hop Layer2
-CG11089 Layer2 ade5 Layer2
-CG11089 Layer2 ade3 Layer2
-CG11089 Layer2 CG3011 Layer2
-ERp60 Layer2 Pdi Layer2
-ERp60 Layer2 Ost48 Layer2
-ERp60 Layer2 CG2852 Layer2
-ERp60 Layer2 CG2918 Layer2
-ERp60 Layer2 CaBP1 Layer2
-ERp60 Layer2 Hsc70-3 Layer2
-ERp60 Layer2 Gp93 Layer2
-zetaCOP Layer2 Klp10A Layer2
-zetaCOP Layer2 Dlic Layer2
-zetaCOP Layer2 p115 Layer2
-zetaCOP Layer2 cpa Layer2
-zetaCOP Layer2 alphaCOP Layer2
-zetaCOP Layer2 betaCOP Layer2
-zetaCOP Layer2 deltaCOP Layer2
-COX6B Layer2 ND-B16.6 Layer2
-CG7048 Layer2 cpa Layer2
-CG7048 Layer2 Srp19 Layer2
-CG7048 Layer2 Nlp Layer2
-CG7048 Layer2 mRpS23 Layer2
-CG7048 Layer2 CG8498 Layer2
-CG7048 Layer2 robl Layer2
-CG7048 Layer2 Tim9a Layer2
-CG7048 Layer2 eEF1delta Layer2
-CG7770 Layer2 RpS18 Layer2
-CG7770 Layer2 pont Layer2
-CG7770 Layer2 CG7048 Layer2
-arm Layer2 Hsc70-3 Layer2
-arm Layer2 Prosalpha6 Layer2
-arm Layer2 Uch-L5 Layer2
-arm Layer2 Rpn5 Layer2
-arm Layer2 Prosbeta7 Layer2
-arm Layer2 Rpn12 Layer2
-arm Layer2 Rpt6R Layer2
-arm Layer2 Rpt6 Layer2
-arm Layer2 syd Layer2
-arm Layer2 Rpn2 Layer2
-arm Layer2 Rpn6 Layer2
-arm Layer2 Rpt1 Layer2
-arm Layer2 Rpt2 Layer2
-arm Layer2 Rpt5 Layer2
-arm Layer2 emb Layer2
-arm Layer2 Rpt4R Layer2
-arm Layer2 pont Layer2
-arm Layer2 alpha-Cat Layer2
-eIF-3p66 Layer2 Tcp-1eta Layer2
-eIF-3p66 Layer2 Cct5 Layer2
-eIF-3p66 Layer2 Cctgamma Layer2
-eIF-3p66 Layer2 RpS12 Layer2
-eIF-3p66 Layer2 RpS16 Layer2
-eIF-3p66 Layer2 RpS11 Layer2
-eIF-3p66 Layer2 RpS27 Layer2
-eIF-3p66 Layer2 RpS23 Layer2
-eIF-3p66 Layer2 RpS10a Layer2
-eIF-3p66 Layer2 RpS10b Layer2
-eIF-3p66 Layer2 RpS13 Layer2
-eIF-3p66 Layer2 RpL10Ab Layer2
-eIF-3p66 Layer2 RpL13 Layer2
-eIF-3p66 Layer2 RpS7 Layer2
-eIF-3p66 Layer2 RpL12 Layer2
-eIF-3p66 Layer2 sta Layer2
-eIF-3p66 Layer2 RpS4 Layer2
-eIF-3p66 Layer2 RpL11 Layer2
-eIF-3p66 Layer2 RpS18 Layer2
-eIF-3p66 Layer2 RpS2 Layer2
-eIF-3p66 Layer2 RpL3 Layer2
-eIF-3p66 Layer2 RpL18A Layer2
-eIF-3p66 Layer2 RpS3 Layer2
-eIF-3p66 Layer2 RpL4 Layer2
-eIF-3p66 Layer2 RpS6 Layer2
-eIF-3p66 Layer2 CG9769 Layer2
-eIF-3p66 Layer2 eIF3-S10 Layer2
-eIF-3p66 Layer2 Tango7 Layer2
-eIF-3p66 Layer2 eIF3-S8 Layer2
-eIF-3p66 Layer2 eIF3-S9 Layer2
-eIF-3p66 Layer2 Trip1 Layer2
-eIF-3p66 Layer2 CG5642 Layer2
-CG11752 Layer2 ND-B16.6 Layer2
-COX4 Layer2 CG11752 Layer2
-COX4 Layer2 porin Layer2
-COX4 Layer2 ND-B16.6 Layer2
-COX4 Layer2 ND-PDSW Layer2
-COX4 Layer2 Cyt-c-p Layer2
-COX4 Layer2 COX6B Layer2
-COX5A Layer2 sun Layer2
-COX5A Layer2 ND-51 Layer2
-COX5A Layer2 CG7603 Layer2
-COX5A Layer2 CG11752 Layer2
-COX5A Layer2 ND-B16.6 Layer2
-COX5A Layer2 Cyt-c-p Layer2
-COX5A Layer2 ND-PDSW Layer2
-COX5A Layer2 COX6B Layer2
-COX5A Layer2 COX4 Layer2
-Eno Layer2 Hsc70-3 Layer2
-Eno Layer2 sta Layer2
-Tpi Layer2 Cat Layer2
-Tpi Layer2 CG11899 Layer2
-Tpi Layer2 RpS13 Layer2
-Tpi Layer2 CG11876 Layer2
-Tpi Layer2 ade3 Layer2
-Tpi Layer2 CG3011 Layer2
-Tpi Layer2 Hsc70-3 Layer2
-Tpi Layer2 Mdh2 Layer2
-Tpi Layer2 PyK Layer2
-Tpi Layer2 Eno Layer2
-Cctgamma Layer2 Trip1 Layer2
-Cctgamma Layer2 CG10576 Layer2
-Cctgamma Layer2 CG3800 Layer2
-Cctgamma Layer2 Hop Layer2
-Cctgamma Layer2 Tcp-1zeta Layer2
-Cctgamma Layer2 Tcp-1eta Layer2
-Cctgamma Layer2 CG7033 Layer2
-Rpt1 Layer2 Uch Layer2
-Rpt1 Layer2 RpS15Aa Layer2
-Rpt1 Layer2 RpS15Ab Layer2
-Rpt1 Layer2 RpS6 Layer2
-Rpt1 Layer2 Ubqn Layer2
-Rpt1 Layer2 RpL3 Layer2
-Rpt1 Layer2 RpS11 Layer2
-Rpt1 Layer2 RpS18 Layer2
-Rpt1 Layer2 Ufd1-like Layer2
-Rpt1 Layer2 Cct5 Layer2
-Rpt1 Layer2 CalpB Layer2
-Rpt1 Layer2 Ect3 Layer2
-Rpt1 Layer2 CG6891 Layer2
-Rpt1 Layer2 PyK Layer2
-Rpt1 Layer2 Cp1 Layer2
-Rpt1 Layer2 Cat Layer2
-Rpt1 Layer2 Pfk Layer2
-Rpt1 Layer2 CG8258 Layer2
-Rpt1 Layer2 cathD Layer2
-Rpt1 Layer2 Prosalpha1 Layer2
-Rpt1 Layer2 Uch-L5 Layer2
-Rpt1 Layer2 Rpt6R Layer2
-Rpt1 Layer2 Rpt4R Layer2
-Rpt1 Layer2 Prosalpha6 Layer2
-Rpt1 Layer2 Rpn12 Layer2
-Rpt1 Layer2 Rpt5 Layer2
-Rpt1 Layer2 Rpn5 Layer2
-Rpt1 Layer2 Prosbeta7 Layer2
-Rpt1 Layer2 Rpn2 Layer2
-Rpt1 Layer2 Rpt6 Layer2
-Rpt1 Layer2 Rpt2 Layer2
-Rpt1 Layer2 Rpn6 Layer2
-Dlic Layer2 p115 Layer2
-Dlic Layer2 deltaCOP Layer2
-Dlic Layer2 cpa Layer2
-Dhc64C Layer2 CLIP-190 Layer2
-Dhc64C Layer2 betaTub56D Layer2
-Dhc64C Layer2 Klc Layer2
-Dhc64C Layer2 poe Layer2
-Dhc64C Layer2 bai Layer2
-Dhc64C Layer2 FKBP59 Layer2
-Dhc64C Layer2 betaCOP Layer2
-Dhc64C Layer2 deltaCOP Layer2
-Dhc64C Layer2 epsilonCOP Layer2
-Dhc64C Layer2 beta'COP Layer2
-Dhc64C Layer2 CG10186 Layer2
-Dhc64C Layer2 zetaCOP Layer2
-Dhc64C Layer2 p115 Layer2
-Dhc64C Layer2 cpa Layer2
-Dhc64C Layer2 cpb Layer2
-Dhc64C Layer2 robl Layer2
-Dhc64C Layer2 Khc Layer2
-Dhc64C Layer2 BicD Layer2
-Dhc64C Layer2 Dlic Layer2
-Ef1beta Layer2 FeCH Layer2
-Ef1beta Layer2 CG7048 Layer2
-Ef1beta Layer2 Aats-asp Layer2
-Ef1beta Layer2 RpS15Aa Layer2
-Ef1beta Layer2 RpS10a Layer2
-Ef1beta Layer2 RpS15Ab Layer2
-Ef1beta Layer2 CG5642 Layer2
-Ef1beta Layer2 RpL3 Layer2
-Ef1beta Layer2 RpS3A Layer2
-Ef1beta Layer2 RpS12 Layer2
-Ef1beta Layer2 RpS13 Layer2
-Ef1beta Layer2 RpS10b Layer2
-Ef1beta Layer2 RpS16 Layer2
-Ef1beta Layer2 RpS2 Layer2
-Ef1beta Layer2 RpS6 Layer2
-Ef1beta Layer2 RpL12 Layer2
-Ef1beta Layer2 RpL30 Layer2
-Ef1beta Layer2 RpS23 Layer2
-Ef1beta Layer2 RpS11 Layer2
-Ef1beta Layer2 eEF1delta Layer2
-Ef1beta Layer2 RpL13 Layer2
-Ef1beta Layer2 sta Layer2
-Ef1beta Layer2 RpS7 Layer2
-Ef1beta Layer2 Tctp Layer2
-Ef1beta Layer2 Trip1 Layer2
-Ef1beta Layer2 RpS8 Layer2
-Ef1beta Layer2 RpS18 Layer2
-Ef1beta Layer2 Rack1 Layer2
-Ef1beta Layer2 RpS17 Layer2
-Ef1beta Layer2 RpL10Ab Layer2
-Ef1beta Layer2 RpL18A Layer2
-Ef1beta Layer2 RpS3 Layer2
-Ef1beta Layer2 RpS4 Layer2
-Ef1beta Layer2 RpL11 Layer2
-Ef1beta Layer2 RpLP2 Layer2
-Ef1beta Layer2 RpL4 Layer2
-Rpn13 Layer2 TER94 Layer2
-Rpn13 Layer2 Ufd1-like Layer2
-Rpn13 Layer2 Ubqn Layer2
-Rpn13 Layer2 Prosalpha6 Layer2
-Rpn13 Layer2 Prosbeta7 Layer2
-Rpn13 Layer2 Rpt4R Layer2
-Rpn13 Layer2 Prosalpha7 Layer2
-Rpn13 Layer2 Rpn12 Layer2
-Rpn13 Layer2 Rpt6R Layer2
-Rpn13 Layer2 Rpn5 Layer2
-Rpn13 Layer2 Rpt6 Layer2
-Rpn13 Layer2 Rpn6 Layer2
-Rpn13 Layer2 Uch-L5 Layer2
-Rpn13 Layer2 Rpt2 Layer2
-Rpn13 Layer2 Rpt5 Layer2
-Rpn13 Layer2 Rpt1 Layer2
-Rpn13 Layer2 Rpn2 Layer2
-Tango7 Layer2 CG7033 Layer2
-Tango7 Layer2 Tcp-1zeta Layer2
-Tango7 Layer2 mRpS10 Layer2
-Tango7 Layer2 Rpn5 Layer2
-Tango7 Layer2 bonsai Layer2
-Tango7 Layer2 Rpn2 Layer2
-Tango7 Layer2 Rpn6 Layer2
-Tango7 Layer2 RpS14a Layer2
-Tango7 Layer2 RpS15Aa Layer2
-Tango7 Layer2 RpS17 Layer2
-Tango7 Layer2 RpL10Ab Layer2
-Tango7 Layer2 RpL12 Layer2
-Tango7 Layer2 RpL13 Layer2
-Tango7 Layer2 RpL18A Layer2
-Tango7 Layer2 RpL3 Layer2
-Tango7 Layer2 RpS10a Layer2
-Tango7 Layer2 RpL11 Layer2
-Tango7 Layer2 RpS10b Layer2
-Tango7 Layer2 RpS2 Layer2
-Tango7 Layer2 RpS27 Layer2
-Tango7 Layer2 RpS23 Layer2
-Tango7 Layer2 RpS18 Layer2
-Tango7 Layer2 RpS12 Layer2
-Tango7 Layer2 RpS13 Layer2
-Tango7 Layer2 RpS4 Layer2
-Tango7 Layer2 RpS6 Layer2
-Tango7 Layer2 RpS16 Layer2
-Tango7 Layer2 sta Layer2
-Tango7 Layer2 eIF3-S10 Layer2
-Tango7 Layer2 RpS3 Layer2
-Tango7 Layer2 Trip1 Layer2
-Tango7 Layer2 RpL4 Layer2
-Tango7 Layer2 CG9769 Layer2
-Tango7 Layer2 eIF3-S9 Layer2
-Tango7 Layer2 CG5642 Layer2
-Tango7 Layer2 eIF3-S8 Layer2
-Rpt2 Layer2 Ubqn Layer2
-Rpt2 Layer2 Ufd1-like Layer2
-Rpt2 Layer2 CG8209 Layer2
-Rpt2 Layer2 Uch-L5 Layer2
-Rpt2 Layer2 Rpt4R Layer2
-Rpt2 Layer2 Rpn12 Layer2
-Rpt2 Layer2 Rpt5 Layer2
-Rpt2 Layer2 Rpt6 Layer2
-Rpt2 Layer2 Prosalpha6 Layer2
-Rpt2 Layer2 Rpn5 Layer2
-Rpt2 Layer2 Prosbeta7 Layer2
-Rpn5 Layer2 Ubqn Layer2
-Rpn5 Layer2 Ufd1-like Layer2
-Rpn5 Layer2 Cat Layer2
-Rpn5 Layer2 CalpB Layer2
-Rpn5 Layer2 CG6891 Layer2
-Rpn5 Layer2 Uch-L5 Layer2
-Rpn5 Layer2 Rpt4R Layer2
-Rpn5 Layer2 Rpt6 Layer2
-Rpn5 Layer2 Rpn12 Layer2
-betaCOP Layer2 cpa Layer2
-betaCOP Layer2 Dlic Layer2
-betaCOP Layer2 Klp10A Layer2
-betaCOP Layer2 p115 Layer2
-betaCOP Layer2 alphaCOP Layer2
-betaCOP Layer2 deltaCOP Layer2
-Rpt6R Layer2 RpS4 Layer2
-Rpt6R Layer2 pont Layer2
-Rpt6R Layer2 Rpt6 Layer2
-Rpt6R Layer2 Uch-L5 Layer2
-Rpt6R Layer2 Rpt4R Layer2
-Rpt6R Layer2 Prosalpha6 Layer2
-Rpt6R Layer2 Rpt2 Layer2
-Rpt6R Layer2 Rpt5 Layer2
-Rpt6R Layer2 Prosbeta7 Layer2
-Rpt6R Layer2 Rpn12 Layer2
-Rpt6R Layer2 Rpn2 Layer2
-Rpt6R Layer2 Rpn5 Layer2
-Rpn2 Layer2 Trip1 Layer2
-Rpn2 Layer2 RpS2 Layer2
-Rpn2 Layer2 Usp5 Layer2
-Rpn2 Layer2 Uch-L5 Layer2
-Rpn2 Layer2 Prosalpha6 Layer2
-Rpn2 Layer2 Rpt4R Layer2
-Rpn2 Layer2 Prosbeta7 Layer2
-Rpn2 Layer2 Rpn12 Layer2
-Rpn2 Layer2 Rpn5 Layer2
-Rpn2 Layer2 Rpt6 Layer2
-Rpn2 Layer2 Rpt2 Layer2
-Rpn2 Layer2 Rpt5 Layer2
-RpL10Ab Layer2 bonsai Layer2
-RpL10Ab Layer2 mago Layer2
-RpL10Ab Layer2 CG5642 Layer2
-RpL10Ab Layer2 RpS15Aa Layer2
-RpL10Ab Layer2 RpS14a Layer2
-RpL10Ab Layer2 RpL12 Layer2
-RpL10Ab Layer2 RpS16 Layer2
-RpL10Ab Layer2 RpS12 Layer2
-RpL10Ab Layer2 RpS10b Layer2
-RpL10Ab Layer2 RpS4 Layer2
-RpL10Ab Layer2 sta Layer2
-RpL10Ab Layer2 RpS6 Layer2
-Trip1 Layer2 RpS14a Layer2
-Trip1 Layer2 Tcp-1zeta Layer2
-Trip1 Layer2 CG10576 Layer2
-Trip1 Layer2 RpS17 Layer2
-Trip1 Layer2 CG7033 Layer2
-Trip1 Layer2 RpL10Ab Layer2
-Trip1 Layer2 RpS10b Layer2
-Trip1 Layer2 RpL12 Layer2
-Trip1 Layer2 RpS12 Layer2
-Trip1 Layer2 RpS4 Layer2
-Trip1 Layer2 RpS6 Layer2
-Trip1 Layer2 RpS16 Layer2
-Trip1 Layer2 sta Layer2
-Trip1 Layer2 CG9769 Layer2
-Trip1 Layer2 eIF3-S10 Layer2
-Trip1 Layer2 CG5642 Layer2
-cpa Layer2 p115 Layer2
-cpa Layer2 deltaCOP Layer2
-cpb Layer2 CG6891 Layer2
-cpb Layer2 flr Layer2
-cpb Layer2 p115 Layer2
-cpb Layer2 deltaCOP Layer2
-cpb Layer2 tsr Layer2
-cpb Layer2 betaCOP Layer2
-cpb Layer2 zetaCOP Layer2
-cpb Layer2 Dlic Layer2
-cpb Layer2 cpa Layer2
-eIF3-S10 Layer2 CG33123 Layer2
-eIF3-S10 Layer2 Aats-ile Layer2
-eIF3-S10 Layer2 RpS14a Layer2
-eIF3-S10 Layer2 RpS17 Layer2
-eIF3-S10 Layer2 RpS15Aa Layer2
-eIF3-S10 Layer2 RpL10Ab Layer2
-eIF3-S10 Layer2 RpL12 Layer2
-eIF3-S10 Layer2 RpS12 Layer2
-eIF3-S10 Layer2 RpS10b Layer2
-eIF3-S10 Layer2 RpS6 Layer2
-eIF3-S10 Layer2 RpS16 Layer2
-eIF3-S10 Layer2 RpS4 Layer2
-eIF3-S10 Layer2 sta Layer2
-eIF3-S10 Layer2 CG5642 Layer2
-eIF3-S10 Layer2 CG9769 Layer2
-eIF3-S9 Layer2 Cctgamma Layer2
-eIF3-S9 Layer2 rin Layer2
-eIF3-S9 Layer2 RpS14a Layer2
-eIF3-S9 Layer2 RpS15Aa Layer2
-eIF3-S9 Layer2 RpS17 Layer2
-eIF3-S9 Layer2 Tcp-1eta Layer2
-eIF3-S9 Layer2 CG7033 Layer2
-eIF3-S9 Layer2 T-cp1 Layer2
-eIF3-S9 Layer2 CG10576 Layer2
-eIF3-S9 Layer2 pAbp Layer2
-eIF3-S9 Layer2 RpL10Ab Layer2
-eIF3-S9 Layer2 RpL12 Layer2
-eIF3-S9 Layer2 RpS12 Layer2
-eIF3-S9 Layer2 RpS10a Layer2
-eIF3-S9 Layer2 RpS27 Layer2
-eIF3-S9 Layer2 RpS10b Layer2
-eIF3-S9 Layer2 RpS18 Layer2
-eIF3-S9 Layer2 RpL11 Layer2
-eIF3-S9 Layer2 RpL13 Layer2
-eIF3-S9 Layer2 RpL3 Layer2
-eIF3-S9 Layer2 RpL4 Layer2
-eIF3-S9 Layer2 RpS16 Layer2
-eIF3-S9 Layer2 sta Layer2
-eIF3-S9 Layer2 RpS2 Layer2
-eIF3-S9 Layer2 RpS13 Layer2
-eIF3-S9 Layer2 RpS4 Layer2
-eIF3-S9 Layer2 RpS6 Layer2
-eIF3-S9 Layer2 RpS3 Layer2
-eIF3-S9 Layer2 CG5642 Layer2
-eIF3-S9 Layer2 eIF3-S8 Layer2
-eIF3-S9 Layer2 CG9769 Layer2
-eIF3-S9 Layer2 Trip1 Layer2
-eIF3-S9 Layer2 eIF3-S10 Layer2
-Pdi Layer2 Ugt Layer2
-Pdi Layer2 Ost48 Layer2
-Pdi Layer2 CG2852 Layer2
-Pdi Layer2 CG2918 Layer2
-Pdi Layer2 Hsc70-3 Layer2
-epsilonCOP Layer2 Dlic Layer2
-epsilonCOP Layer2 Klc Layer2
-epsilonCOP Layer2 Klp10A Layer2
-epsilonCOP Layer2 cpb Layer2
-epsilonCOP Layer2 cpa Layer2
-epsilonCOP Layer2 p115 Layer2
-epsilonCOP Layer2 deltaCOP Layer2
-epsilonCOP Layer2 zetaCOP Layer2
-epsilonCOP Layer2 alphaCOP Layer2
-epsilonCOP Layer2 betaCOP Layer2
-beta'COP Layer2 cpb Layer2
-beta'COP Layer2 cpa Layer2
-beta'COP Layer2 Dlic Layer2
-beta'COP Layer2 Klp10A Layer2
-beta'COP Layer2 Klc Layer2
-beta'COP Layer2 p115 Layer2
-beta'COP Layer2 zetaCOP Layer2
-beta'COP Layer2 alphaCOP Layer2
-beta'COP Layer2 deltaCOP Layer2
-beta'COP Layer2 betaCOP Layer2
-beta'COP Layer2 epsilonCOP Layer2
-CaBP1 Layer2 Ugt Layer2
-CaBP1 Layer2 Ost48 Layer2
-CaBP1 Layer2 CG2918 Layer2
-CaBP1 Layer2 Hsc70-3 Layer2
-CaBP1 Layer2 Pdi Layer2
-Gp93 Layer2 Cctgamma Layer2
-Gp93 Layer2 bai Layer2
-Gp93 Layer2 Hsp27 Layer2
-Gp93 Layer2 Hop Layer2
-Gp93 Layer2 Hsp70Ab Layer2
-Gp93 Layer2 CG11577 Layer2
-Gp93 Layer2 Pdi Layer2
-Gp93 Layer2 Hsc70-3 Layer2
-Gp93 Layer2 CG2918 Layer2
-Gp93 Layer2 CaBP1 Layer2
-Tcp-1eta Layer2 RpL12 Layer2
-Tcp-1eta Layer2 RpS6 Layer2
-Tcp-1eta Layer2 RpL13 Layer2
-Tcp-1eta Layer2 RpL10Ab Layer2
-Tcp-1eta Layer2 sta Layer2
-Tcp-1eta Layer2 RpS2 Layer2
-Tcp-1eta Layer2 RpS12 Layer2
-Tcp-1eta Layer2 RpS13 Layer2
-Tcp-1eta Layer2 CG10576 Layer2
-Tcp-1eta Layer2 RpS4 Layer2
-Tcp-1eta Layer2 RpS16 Layer2
-Tcp-1eta Layer2 Trip1 Layer2
-Tcp-1eta Layer2 Hop Layer2
-Tcp-1eta Layer2 ade3 Layer2
-Tcp-1eta Layer2 Tcp-1zeta Layer2
-Tcp-1eta Layer2 CG7033 Layer2
-RpL13 Layer2 mRpL12 Layer2
-RpL13 Layer2 CG7033 Layer2
-RpL13 Layer2 ade3 Layer2
-RpL13 Layer2 bonsai Layer2
-RpL13 Layer2 Srp19 Layer2
-RpL13 Layer2 mago Layer2
-RpL13 Layer2 CG5642 Layer2
-RpL13 Layer2 eIF3-S10 Layer2
-RpL13 Layer2 CG10576 Layer2
-RpL13 Layer2 Srp54k Layer2
-RpL13 Layer2 CG9769 Layer2
-RpL13 Layer2 Trip1 Layer2
-RpL13 Layer2 RpS15Aa Layer2
-RpL13 Layer2 RpL12 Layer2
-RpL13 Layer2 RpS12 Layer2
-RpL13 Layer2 RpS13 Layer2
-RpL13 Layer2 RpS6 Layer2
-RpL13 Layer2 RpS14a Layer2
-RpL13 Layer2 RpL10Ab Layer2
-RpL13 Layer2 RpS4 Layer2
-RpL13 Layer2 sta Layer2
-RpL13 Layer2 RpS17 Layer2
-RpL13 Layer2 RpS10b Layer2
-RpL13 Layer2 RpS16 Layer2
-Rpt4 Layer2 Prosalpha1 Layer2
-Rpt4 Layer2 arm Layer2
-Rpt4 Layer2 Rpt4R Layer2
-Rpt4 Layer2 Uch-L5 Layer2
-Rpt4 Layer2 Rpt6R Layer2
-Rpt4 Layer2 Rpn13 Layer2
-Rpt4 Layer2 Rpt1 Layer2
-Rpt4 Layer2 Rpt6 Layer2
-Rpt4 Layer2 Prosalpha6 Layer2
-Rpt4 Layer2 Prosbeta7 Layer2
-Rpt4 Layer2 Rpn6 Layer2
-Rpt4 Layer2 Rpt2 Layer2
-Rpt4 Layer2 Rpn3 Layer2
-Rpt4 Layer2 Prosalpha7 Layer2
-Rpt4 Layer2 Rpn2 Layer2
-Rpt4 Layer2 Rpn12 Layer2
-Rpt4 Layer2 Rpn5 Layer2
-Rpt4 Layer2 Rpt5 Layer2
-RpS11 Layer2 vig Layer2
-RpS11 Layer2 bic Layer2
-RpS11 Layer2 CG7033 Layer2
-RpS11 Layer2 Tctp Layer2
-RpS11 Layer2 Tcp-1eta Layer2
-RpS11 Layer2 eEF1delta Layer2
-RpS11 Layer2 mago Layer2
-RpS11 Layer2 EfTuM Layer2
-RpS11 Layer2 Srp72 Layer2
-RpS11 Layer2 bonsai Layer2
-RpS11 Layer2 mRpS10 Layer2
-RpS11 Layer2 CG5642 Layer2
-RpS11 Layer2 mRpS9 Layer2
-RpS11 Layer2 mRpL12 Layer2
-RpS11 Layer2 eIF3-S8 Layer2
-RpS11 Layer2 CG9769 Layer2
-RpS11 Layer2 eIF3-S10 Layer2
-RpS11 Layer2 Srp19 Layer2
-RpS11 Layer2 ade3 Layer2
-RpS11 Layer2 eIF3-S9 Layer2
-RpS11 Layer2 Srp54k Layer2
-RpS11 Layer2 Tango7 Layer2
-RpS11 Layer2 Trip1 Layer2
-RpS11 Layer2 RpL10Ab Layer2
-RpS11 Layer2 RpS23 Layer2
-RpS11 Layer2 RpL3 Layer2
-RpS11 Layer2 RpL12 Layer2
-RpS11 Layer2 RpL11 Layer2
-RpS11 Layer2 RpS14a Layer2
-RpS11 Layer2 RpL4 Layer2
-RpS11 Layer2 RpS18 Layer2
-RpS11 Layer2 RpS6 Layer2
-RpS11 Layer2 sta Layer2
-RpS11 Layer2 RpS10a Layer2
-RpS11 Layer2 RpS17 Layer2
-RpS11 Layer2 RpS2 Layer2
-RpS11 Layer2 RpL18A Layer2
-RpS11 Layer2 RpS12 Layer2
-RpS11 Layer2 RpS3 Layer2
-RpS11 Layer2 RpS13 Layer2
-RpS11 Layer2 RpS27 Layer2
-RpS11 Layer2 RpS10b Layer2
-RpS11 Layer2 RpL13 Layer2
-RpS11 Layer2 RpS16 Layer2
-RpS11 Layer2 RpS15Aa Layer2
-RpS11 Layer2 RpS4 Layer2
-eIF3-S8 Layer2 CG33123 Layer2
-eIF3-S8 Layer2 Tcp-1zeta Layer2
-eIF3-S8 Layer2 eEF1delta Layer2
-eIF3-S8 Layer2 pAbp Layer2
-eIF3-S8 Layer2 RpS14a Layer2
-eIF3-S8 Layer2 RpS17 Layer2
-eIF3-S8 Layer2 RpS15Aa Layer2
-eIF3-S8 Layer2 CG10576 Layer2
-eIF3-S8 Layer2 RpL12 Layer2
-eIF3-S8 Layer2 RpL10Ab Layer2
-eIF3-S8 Layer2 RpL11 Layer2
-eIF3-S8 Layer2 RpL13 Layer2
-eIF3-S8 Layer2 RpL3 Layer2
-eIF3-S8 Layer2 RpS12 Layer2
-eIF3-S8 Layer2 sta Layer2
-eIF3-S8 Layer2 RpS2 Layer2
-eIF3-S8 Layer2 RpS13 Layer2
-eIF3-S8 Layer2 RpS4 Layer2
-eIF3-S8 Layer2 RpS10b Layer2
-eIF3-S8 Layer2 RpS27 Layer2
-eIF3-S8 Layer2 RpS10a Layer2
-eIF3-S8 Layer2 RpS16 Layer2
-eIF3-S8 Layer2 RpL4 Layer2
-eIF3-S8 Layer2 RpS18 Layer2
-eIF3-S8 Layer2 RpS6 Layer2
-eIF3-S8 Layer2 RpS3 Layer2
-eIF3-S8 Layer2 eIF3-S10 Layer2
-eIF3-S8 Layer2 Trip1 Layer2
-eIF3-S8 Layer2 CG9769 Layer2
-eIF3-S8 Layer2 CG5642 Layer2
-Hop Layer2 Hsp60 Layer2
-Hop Layer2 Hsc70-3 Layer2
-Hop Layer2 Ubqn Layer2
-CG1416 Layer2 CG7033 Layer2
-CG1416 Layer2 Hsp60 Layer2
-CG1416 Layer2 Gp93 Layer2
-CG1416 Layer2 Cctgamma Layer2
-CG1416 Layer2 Hop Layer2
-RpS6 Layer2 RpS14a Layer2
-RpS6 Layer2 sta Layer2
-RpS2 Layer2 CG10576 Layer2
-RpS2 Layer2 ade3 Layer2
-RpS2 Layer2 bonsai Layer2
-RpS2 Layer2 CG7033 Layer2
-RpS2 Layer2 mago Layer2
-RpS2 Layer2 mRpL12 Layer2
-RpS2 Layer2 CG5642 Layer2
-RpS2 Layer2 eIF3-S10 Layer2
-RpS2 Layer2 Srp19 Layer2
-RpS2 Layer2 CG9769 Layer2
-RpS2 Layer2 Srp54k Layer2
-RpS2 Layer2 Trip1 Layer2
-RpS2 Layer2 RpS6 Layer2
-RpS2 Layer2 RpS15Aa Layer2
-RpS2 Layer2 RpL12 Layer2
-RpS2 Layer2 RpS14a Layer2
-RpS2 Layer2 RpS10b Layer2
-RpS2 Layer2 RpS16 Layer2
-RpS2 Layer2 RpL10Ab Layer2
-RpS2 Layer2 RpS17 Layer2
-RpS2 Layer2 sta Layer2
-RpS2 Layer2 RpS4 Layer2
-RpS2 Layer2 RpS13 Layer2
-RpS2 Layer2 RpL13 Layer2
-RpS2 Layer2 RpS12 Layer2
-Prosalpha6 Layer2 Trip1 Layer2
-Prosalpha6 Layer2 Tcp-1zeta Layer2
-Prosalpha6 Layer2 Uch-L5 Layer2
-Prosalpha6 Layer2 Rpn5 Layer2
-Prosalpha6 Layer2 Rpt4R Layer2
-Prosalpha6 Layer2 Rpt6 Layer2
-Prosalpha6 Layer2 Prosbeta7 Layer2
-Prosalpha6 Layer2 Rpn12 Layer2
-ND-PDSW Layer2 COX6B Layer2
-ND-PDSW Layer2 CG7603 Layer2
-ND-PDSW Layer2 ND-B16.6 Layer2
-ND-PDSW Layer2 CG11752 Layer2
-NP15.6 Layer2 CG7603 Layer2
-NP15.6 Layer2 COX4 Layer2
-NP15.6 Layer2 CG11752 Layer2
-NP15.6 Layer2 ND-51 Layer2
-NP15.6 Layer2 COX5A Layer2
-NP15.6 Layer2 ND-B16.6 Layer2
-NP15.6 Layer2 ND-PDSW Layer2
-Klc Layer2 Dlic Layer2
-Klc Layer2 betaCOP Layer2
-Klc Layer2 deltaCOP Layer2
-Klc Layer2 zetaCOP Layer2
-Klc Layer2 Klp10A Layer2
-Khc Layer2 Dlic Layer2
-Khc Layer2 zetaCOP Layer2
-Khc Layer2 epsilonCOP Layer2
-Khc Layer2 bai Layer2
-Khc Layer2 deltaCOP Layer2
-Khc Layer2 beta'COP Layer2
-Khc Layer2 syd Layer2
-Khc Layer2 betaCOP Layer2
-Khc Layer2 Klp10A Layer2
-Khc Layer2 betaTub56D Layer2
-Khc Layer2 Klc Layer2
-RpL12 Layer2 bonsai Layer2
-RpL12 Layer2 mago Layer2
-RpL12 Layer2 RpS16 Layer2
-RpL12 Layer2 sta Layer2
-RpL12 Layer2 RpS14a Layer2
-RpL12 Layer2 RpS6 Layer2
-Rpt5 Layer2 Ubqn Layer2
-Rpt5 Layer2 Cat Layer2
-Rpt5 Layer2 CalpB Layer2
-Rpt5 Layer2 CG6891 Layer2
-Rpt5 Layer2 Ect3 Layer2
-Rpt5 Layer2 PyK Layer2
-Rpt5 Layer2 Tcp-1eta Layer2
-Rpt5 Layer2 Ufd1-like Layer2
-Rpt5 Layer2 Uch-L5 Layer2
-Rpt5 Layer2 Rpt4R Layer2
-Rpt5 Layer2 Prosalpha6 Layer2
-Rpt5 Layer2 Prosbeta7 Layer2
-Rpt5 Layer2 Rpn5 Layer2
-Rpt5 Layer2 Rpt6 Layer2
-Rpt5 Layer2 Rpn12 Layer2
-Prosalpha7 Layer2 RpS16 Layer2
-Prosalpha7 Layer2 Cct5 Layer2
-Prosalpha7 Layer2 Tcp-1eta Layer2
-Prosalpha7 Layer2 CG7033 Layer2
-Prosalpha7 Layer2 Trip1 Layer2
-Prosalpha7 Layer2 CG8258 Layer2
-Prosalpha7 Layer2 arm Layer2
-Prosalpha7 Layer2 Uch-L5 Layer2
-Prosalpha7 Layer2 Rpt4R Layer2
-Prosalpha7 Layer2 Prosalpha1 Layer2
-Prosalpha7 Layer2 Rpt6R Layer2
-Prosalpha7 Layer2 Rpn12 Layer2
-Prosalpha7 Layer2 Rpt6 Layer2
-Prosalpha7 Layer2 Rpn6 Layer2
-Prosalpha7 Layer2 Rpt2 Layer2
-Prosalpha7 Layer2 Rpt1 Layer2
-Prosalpha7 Layer2 Prosbeta7 Layer2
-Prosalpha7 Layer2 Prosalpha6 Layer2
-Prosalpha7 Layer2 Rpn5 Layer2
-Prosalpha7 Layer2 Rpn2 Layer2
-Prosalpha7 Layer2 Rpt5 Layer2
-Rpn6 Layer2 Trip1 Layer2
-Rpn6 Layer2 RpS2 Layer2
-Rpn6 Layer2 Ubqn Layer2
-Rpn6 Layer2 CalpB Layer2
-Rpn6 Layer2 Cat Layer2
-Rpn6 Layer2 PyK Layer2
-Rpn6 Layer2 Ect3 Layer2
-Rpn6 Layer2 CG6891 Layer2
-Rpn6 Layer2 Uch-L5 Layer2
-Rpn6 Layer2 Prosalpha6 Layer2
-Rpn6 Layer2 Rpt4R Layer2
-Rpn6 Layer2 Prosbeta7 Layer2
-Rpn6 Layer2 Rpn2 Layer2
-Rpn6 Layer2 Rpt5 Layer2
-Rpn6 Layer2 Rpn12 Layer2
-Rpn6 Layer2 Rpn5 Layer2
-Rpn6 Layer2 Rpt2 Layer2
-Rpn6 Layer2 Rpt6R Layer2
-Rpn6 Layer2 Rpt6 Layer2
-ATPsynbeta Layer2 Rack1 Layer2
-ATPsynbeta Layer2 COX4 Layer2
-ATPsynbeta Layer2 COX6B Layer2
-ATPsynbeta Layer2 sta Layer2
-ATPsynbeta Layer2 Hsc70-5 Layer2
-ATPsynbeta Layer2 Eno Layer2
-ATPsynbeta Layer2 RpL4 Layer2
-ATPsynbeta Layer2 RpS3A Layer2
-ATPsynbeta Layer2 CG11876 Layer2
-ATPsynbeta Layer2 Mpcp Layer2
-ATPsynbeta Layer2 porin Layer2
-ATPsynbeta Layer2 Vha100-1 Layer2
-ATPsynbeta Layer2 RpS2 Layer2
-ATPsynbeta Layer2 Vha100-2 Layer2
-ATPsynbeta Layer2 Hsp83 Layer2
-ATPsynbeta Layer2 ND-51 Layer2
-ATPsynbeta Layer2 Vha68-2 Layer2
-ATPsynbeta Layer2 EfTuM Layer2
-ATPsynbeta Layer2 Mdh2 Layer2
-ATPsynbeta Layer2 CG9090 Layer2
-ATPsynbeta Layer2 COX5A Layer2
-ATPsynbeta Layer2 sun Layer2
-ATPsynCF6 Layer2 Vha100-1 Layer2
-ATPsynCF6 Layer2 Vha100-2 Layer2
-ATPsynCF6 Layer2 NP15.6 Layer2
-ATPsynCF6 Layer2 Vha68-2 Layer2
-ATPsynCF6 Layer2 ND-B16.6 Layer2
-ATPsynCF6 Layer2 COX6B Layer2
-ATPsynCF6 Layer2 CG11752 Layer2
-ATPsynCF6 Layer2 Cyt-c-p Layer2
-ATPsynCF6 Layer2 COX4 Layer2
-ATPsynCF6 Layer2 ND-PDSW Layer2
-ATPsynCF6 Layer2 COX5A Layer2
-ATPsynCF6 Layer2 sun Layer2
-ATPsynCF6 Layer2 ATPsynbeta Layer2
-CG8258 Layer2 RpS13 Layer2
-CG8258 Layer2 RpL11 Layer2
-CG8258 Layer2 eIF3-S9 Layer2
-CG8258 Layer2 Tango7 Layer2
-CG8258 Layer2 Hsp60C Layer2
-CG8258 Layer2 Rpn2 Layer2
-CG8258 Layer2 RpS3 Layer2
-CG8258 Layer2 RpL12 Layer2
-CG8258 Layer2 RpL3 Layer2
-CG8258 Layer2 Hsc70-5 Layer2
-CG8258 Layer2 RpL4 Layer2
-CG8258 Layer2 RpS4 Layer2
-CG8258 Layer2 RpS2 Layer2
-CG8258 Layer2 RpS16 Layer2
-CG8258 Layer2 Hsp60 Layer2
-CG8258 Layer2 CG10576 Layer2
-CG8258 Layer2 Cat Layer2
-CG8258 Layer2 CG6891 Layer2
-CG8258 Layer2 CalpB Layer2
-CG8258 Layer2 Ect3 Layer2
-CG8258 Layer2 Cp1 Layer2
-CG8258 Layer2 Pfk Layer2
-CG8258 Layer2 PyK Layer2
-CG8258 Layer2 ade3 Layer2
-CG8258 Layer2 Trip1 Layer2
-CG8258 Layer2 Rpt5 Layer2
-CG8258 Layer2 Rpn6 Layer2
-CG8258 Layer2 Rpn5 Layer2
-CG8258 Layer2 Hop Layer2
-CG8258 Layer2 Cct5 Layer2
-CG8258 Layer2 Cctgamma Layer2
-CG8258 Layer2 Tcp-1zeta Layer2
-CG8258 Layer2 CG7033 Layer2
-CG8258 Layer2 Tcp-1eta Layer2
-CG8258 Layer2 T-cp1 Layer2
-RpS7 Layer2 Rpt6R Layer2
-RpS7 Layer2 Rpt6 Layer2
-RpS7 Layer2 vig Layer2
-RpS7 Layer2 eEF1delta Layer2
-RpS7 Layer2 Rpt1 Layer2
-RpS7 Layer2 Rpt2 Layer2
-RpS7 Layer2 ade3 Layer2
-RpS7 Layer2 mRpS10 Layer2
-RpS7 Layer2 Srp72 Layer2
-RpS7 Layer2 mago Layer2
-RpS7 Layer2 bonsai Layer2
-RpS7 Layer2 Tctp Layer2
-RpS7 Layer2 CG5642 Layer2
-RpS7 Layer2 CG9769 Layer2
-RpS7 Layer2 eIF3-S10 Layer2
-RpS7 Layer2 Srp54k Layer2
-RpS7 Layer2 Srp19 Layer2
-RpS7 Layer2 Trip1 Layer2
-RpS7 Layer2 eIF3-S9 Layer2
-RpS7 Layer2 eIF3-S8 Layer2
-RpS7 Layer2 Tango7 Layer2
-RpS7 Layer2 RpS15Ab Layer2
-RpS7 Layer2 RpS10a Layer2
-RpS7 Layer2 RpS15Aa Layer2
-RpS7 Layer2 RpS14a Layer2
-RpS7 Layer2 RpL4 Layer2
-RpS7 Layer2 RpL13 Layer2
-RpS7 Layer2 sta Layer2
-RpS7 Layer2 RpS2 Layer2
-RpS7 Layer2 RpS23 Layer2
-RpS7 Layer2 RpS16 Layer2
-RpS7 Layer2 RpL10Ab Layer2
-RpS7 Layer2 RpS6 Layer2
-RpS7 Layer2 RpS18 Layer2
-RpS7 Layer2 RpL11 Layer2
-RpS7 Layer2 RpS12 Layer2
-RpS7 Layer2 RpS4 Layer2
-RpS7 Layer2 RpS3 Layer2
-RpS7 Layer2 RpS13 Layer2
-RpS7 Layer2 RpS27 Layer2
-RpS7 Layer2 RpL3 Layer2
-RpS7 Layer2 RpS10b Layer2
-RpS7 Layer2 RpS17 Layer2
-RpS7 Layer2 RpL18A Layer2
-RpS7 Layer2 RpS11 Layer2
-RpS7 Layer2 RpL12 Layer2
-RpL10 Layer2 mRpS10 Layer2
-RpL10 Layer2 ade3 Layer2
-RpL10 Layer2 Cct5 Layer2
-RpL10 Layer2 mRpS5 Layer2
-RpL10 Layer2 eEF1delta Layer2
-RpL10 Layer2 mRpL12 Layer2
-RpL10 Layer2 mRpS9 Layer2
-RpL10 Layer2 Ef1beta Layer2
-RpL10 Layer2 eIF4G Layer2
-RpL10 Layer2 Srp72 Layer2
-RpL10 Layer2 mago Layer2
-RpL10 Layer2 Rbp2 Layer2
-RpL10 Layer2 eIF3-S10 Layer2
-RpL10 Layer2 eIF3-S8 Layer2
-RpL10 Layer2 CG5642 Layer2
-RpL10 Layer2 CG10576 Layer2
-RpL10 Layer2 Tango7 Layer2
-RpL10 Layer2 Trip1 Layer2
-RpL10 Layer2 Srp19 Layer2
-RpL10 Layer2 mRpS7 Layer2
-RpL10 Layer2 eIF-2gamma Layer2
-RpL10 Layer2 Elf Layer2
-RpL10 Layer2 eIF-3p66 Layer2
-RpL10 Layer2 CG9769 Layer2
-RpL10 Layer2 eIF3-S9 Layer2
-RpL10 Layer2 Srp54k Layer2
-RpL10 Layer2 eRF1 Layer2
-RpL10 Layer2 RpS15Ab Layer2
-RpL10 Layer2 Tctp Layer2
-RpL10 Layer2 RpS14a Layer2
-RpL10 Layer2 RpS15Aa Layer2
-RpL10 Layer2 RpS10b Layer2
-RpL10 Layer2 RpS17 Layer2
-RpL10 Layer2 RpL13 Layer2
-RpL10 Layer2 RpS8 Layer2
-RpL10 Layer2 RpS16 Layer2
-RpL10 Layer2 sta Layer2
-RpL10 Layer2 RpL12 Layer2
-RpL10 Layer2 RpS3 Layer2
-RpL10 Layer2 RpL4 Layer2
-RpL10 Layer2 RpS27 Layer2
-RpL10 Layer2 RpL3 Layer2
-RpL10 Layer2 RpS12 Layer2
-RpL10 Layer2 RpL11 Layer2
-RpL10 Layer2 RpLP2 Layer2
-RpL10 Layer2 RpL18A Layer2
-RpL10 Layer2 RpL26 Layer2
-RpL10 Layer2 RpS4 Layer2
-RpL10 Layer2 Rack1 Layer2
-RpL10 Layer2 RpS3A Layer2
-RpL10 Layer2 RpS11 Layer2
-RpL10 Layer2 RpS13 Layer2
-RpL10 Layer2 RpS10a Layer2
-RpL10 Layer2 RpL30 Layer2
-RpL10 Layer2 RpS2 Layer2
-RpL10 Layer2 RpS18 Layer2
-RpL10 Layer2 RpS23 Layer2
-RpL10 Layer2 RpL10Ab Layer2
-RpL10 Layer2 RpS6 Layer2
-RpL10 Layer2 RpS7 Layer2
-Aats-asp Layer2 CG5642 Layer2
-Aats-asp Layer2 CG31739 Layer2
-Aats-asp Layer2 CG33123 Layer2
-Aats-asp Layer2 Aats-arg Layer2
-Aats-asp Layer2 Aats-ile Layer2
-CG33123 Layer2 Aats-arg Layer2
-Aats-ile Layer2 CG33123 Layer2
-Aats-ile Layer2 Aats-arg Layer2
-RpS8 Layer2 T-cp1 Layer2
-RpS8 Layer2 vig Layer2
-RpS8 Layer2 Rpn2 Layer2
-RpS8 Layer2 Cct5 Layer2
-RpS8 Layer2 eEF1delta Layer2
-RpS8 Layer2 mRpS10 Layer2
-RpS8 Layer2 CG8258 Layer2
-RpS8 Layer2 Tcp-1eta Layer2
-RpS8 Layer2 bonsai Layer2
-RpS8 Layer2 CG7033 Layer2
-RpS8 Layer2 ade3 Layer2
-RpS8 Layer2 CG10576 Layer2
-RpS8 Layer2 Srp72 Layer2
-RpS8 Layer2 mago Layer2
-RpS8 Layer2 Rbp2 Layer2
-RpS8 Layer2 Nacalpha Layer2
-RpS8 Layer2 CG5642 Layer2
-RpS8 Layer2 eIF-3p66 Layer2
-RpS8 Layer2 Srp54k Layer2
-RpS8 Layer2 Srp19 Layer2
-RpS8 Layer2 eIF3-S10 Layer2
-RpS8 Layer2 Tctp Layer2
-RpS8 Layer2 Trip1 Layer2
-RpS8 Layer2 Tango7 Layer2
-RpS8 Layer2 eIF3-S8 Layer2
-RpS8 Layer2 CG9769 Layer2
-RpS8 Layer2 eIF3-S9 Layer2
-RpS8 Layer2 RpL13 Layer2
-RpS8 Layer2 RpS17 Layer2
-RpS8 Layer2 RpL11 Layer2
-RpS8 Layer2 RpS12 Layer2
-RpS8 Layer2 RpL4 Layer2
-RpS8 Layer2 RpS3 Layer2
-RpS8 Layer2 RpS27 Layer2
-RpS8 Layer2 RpS18 Layer2
-RpS8 Layer2 RpS2 Layer2
-RpS8 Layer2 RpS13 Layer2
-RpS8 Layer2 RpS7 Layer2
-RpS8 Layer2 RpS6 Layer2
-RpS8 Layer2 sta Layer2
-RpS8 Layer2 RpL10Ab Layer2
-RpS8 Layer2 RpS10b Layer2
-RpS8 Layer2 RpS10a Layer2
-RpS8 Layer2 RpS14a Layer2
-RpS8 Layer2 RpS4 Layer2
-RpS8 Layer2 RpL12 Layer2
-RpS8 Layer2 RpS11 Layer2
-RpS8 Layer2 RpL18A Layer2
-RpS8 Layer2 RpLP2 Layer2
-RpS8 Layer2 RpL3 Layer2
-RpS8 Layer2 RpS23 Layer2
-RpS8 Layer2 RpS15Ab Layer2
-RpS8 Layer2 RpS15Aa Layer2
-RpS8 Layer2 RpS16 Layer2
-Rpt6 Layer2 Ubqn Layer2
-Rpt6 Layer2 RpS4 Layer2
-Rpt6 Layer2 Uch-L5 Layer2
-Rpt6 Layer2 Rpt4R Layer2
-Rpt6 Layer2 Rpn12 Layer2
-Rpn3 Layer2 bel Layer2
-Rpn3 Layer2 Ubqn Layer2
-Rpn3 Layer2 CalpB Layer2
-Rpn3 Layer2 Ect3 Layer2
-Rpn3 Layer2 Pfk Layer2
-Rpn3 Layer2 CG6891 Layer2
-Rpn3 Layer2 Cat Layer2
-Rpn3 Layer2 Cp1 Layer2
-Rpn3 Layer2 PyK Layer2
-Rpn3 Layer2 arm Layer2
-Rpn3 Layer2 cathD Layer2
-Rpn3 Layer2 CG8258 Layer2
-Rpn3 Layer2 Prosalpha1 Layer2
-Rpn3 Layer2 Uch-L5 Layer2
-Rpn3 Layer2 TER94 Layer2
-Rpn3 Layer2 Prosalpha6 Layer2
-Rpn3 Layer2 Prosbeta7 Layer2
-Rpn3 Layer2 Prosalpha7 Layer2
-Rpn3 Layer2 Rpt4R Layer2
-Rpn3 Layer2 Rpn2 Layer2
-Rpn3 Layer2 Rpt6R Layer2
-Rpn3 Layer2 Rpt2 Layer2
-Rpn3 Layer2 Rpn5 Layer2
-Rpn3 Layer2 Rpt1 Layer2
-Rpn3 Layer2 Rpt5 Layer2
-Rpn3 Layer2 Rpn6 Layer2
-Rpn3 Layer2 Rpn13 Layer2
-Rpn3 Layer2 Rpn12 Layer2
-Rpn3 Layer2 Rpt6 Layer2
-RpL30 Layer2 vig Layer2
-RpL30 Layer2 Nacalpha Layer2
-RpL30 Layer2 CG10576 Layer2
-RpL30 Layer2 Srp72 Layer2
-RpL30 Layer2 eIF4G Layer2
-RpL30 Layer2 bonsai Layer2
-RpL30 Layer2 eIF3-S8 Layer2
-RpL30 Layer2 mago Layer2
-RpL30 Layer2 eIF3-S10 Layer2
-RpL30 Layer2 CG5642 Layer2
-RpL30 Layer2 mRpS5 Layer2
-RpL30 Layer2 Rbp2 Layer2
-RpL30 Layer2 mRpS10 Layer2
-RpL30 Layer2 eIF-3p66 Layer2
-RpL30 Layer2 Tctp Layer2
-RpL30 Layer2 eIF3-S9 Layer2
-RpL30 Layer2 Tango7 Layer2
-RpL30 Layer2 CG9769 Layer2
-RpL30 Layer2 Trip1 Layer2
-RpL30 Layer2 Srp54k Layer2
-RpL30 Layer2 Srp19 Layer2
-RpL30 Layer2 RpS15Aa Layer2
-RpL30 Layer2 RpS15Ab Layer2
-RpL30 Layer2 RpS10a Layer2
-RpL30 Layer2 RpL18A Layer2
-RpL30 Layer2 RpS27 Layer2
-RpL30 Layer2 RpL12 Layer2
-RpL30 Layer2 RpS12 Layer2
-RpL30 Layer2 RpS18 Layer2
-RpL30 Layer2 RpS17 Layer2
-RpL30 Layer2 RpS2 Layer2
-RpL30 Layer2 RpL11 Layer2
-RpL30 Layer2 RpS7 Layer2
-RpL30 Layer2 RpS14a Layer2
-RpL30 Layer2 RpS4 Layer2
-RpL30 Layer2 RpS8 Layer2
-RpL30 Layer2 RpS10b Layer2
-RpL30 Layer2 RpLP2 Layer2
-RpL30 Layer2 RpL13 Layer2
-RpL30 Layer2 RpS16 Layer2
-RpL30 Layer2 RpS3 Layer2
-RpL30 Layer2 RpS13 Layer2
-RpL30 Layer2 RpS11 Layer2
-RpL30 Layer2 RpS6 Layer2
-RpL30 Layer2 RpL3 Layer2
-RpL30 Layer2 RpL10Ab Layer2
-RpL30 Layer2 RpS23 Layer2
-RpL30 Layer2 RpL4 Layer2
-RpL30 Layer2 sta Layer2
-RpS10a Layer2 Tctp Layer2
-RpS10a Layer2 eEF1delta Layer2
-RpS10a Layer2 bonsai Layer2
-RpS10a Layer2 mRpS10 Layer2
-RpS10a Layer2 RpS10b Layer2
-RpS10a Layer2 Srp72 Layer2
-RpS10a Layer2 mago Layer2
-RpS10a Layer2 CG5642 Layer2
-RpS10a Layer2 CG9769 Layer2
-RpS10a Layer2 eIF3-S10 Layer2
-RpS10a Layer2 Trip1 Layer2
-RpS10a Layer2 Srp19 Layer2
-RpS10a Layer2 Srp54k Layer2
-RpS10a Layer2 RpS15Aa Layer2
-RpS10a Layer2 sta Layer2
-RpS10a Layer2 RpS2 Layer2
-RpS10a Layer2 RpS3 Layer2
-RpS10a Layer2 RpL10Ab Layer2
-RpS10a Layer2 RpS4 Layer2
-RpS10a Layer2 RpS13 Layer2
-RpS10a Layer2 RpS6 Layer2
-RpS10a Layer2 RpS12 Layer2
-RpS10a Layer2 RpS16 Layer2
-RpS10a Layer2 RpS14a Layer2
-RpS10a Layer2 RpS27 Layer2
-RpS10a Layer2 RpL13 Layer2
-RpS10a Layer2 RpL12 Layer2
-RpS10a Layer2 RpS17 Layer2
-RpS10a Layer2 RpL3 Layer2
-RpS17 Layer2 CG7033 Layer2
-RpS17 Layer2 CG5642 Layer2
-RpS17 Layer2 bonsai Layer2
-RpS17 Layer2 RpS6 Layer2
-RpS17 Layer2 RpL12 Layer2
-RpS17 Layer2 RpL10Ab Layer2
-RpS17 Layer2 RpS15Aa Layer2
-RpS17 Layer2 RpS12 Layer2
-RpS17 Layer2 sta Layer2
-RpS17 Layer2 RpS14a Layer2
-RpS17 Layer2 RpS4 Layer2
-RpS17 Layer2 RpS16 Layer2
-RpS17 Layer2 RpS10b Layer2
-RpLP0 Layer2 ATPsynbeta Layer2
-RpLP0 Layer2 Tcp-1zeta Layer2
-RpLP0 Layer2 T-cp1 Layer2
-RpLP0 Layer2 CG7033 Layer2
-RpLP0 Layer2 EfTuM Layer2
-RpLP0 Layer2 Nacalpha Layer2
-RpLP0 Layer2 bonsai Layer2
-RpLP0 Layer2 Tcp-1eta Layer2
-RpLP0 Layer2 mRpS10 Layer2
-RpLP0 Layer2 ade3 Layer2
-RpLP0 Layer2 Cct5 Layer2
-RpLP0 Layer2 mRpS5 Layer2
-RpLP0 Layer2 eEF1delta Layer2
-RpLP0 Layer2 Srp72 Layer2
-RpLP0 Layer2 eIF4G Layer2
-RpLP0 Layer2 mRpS9 Layer2
-RpLP0 Layer2 Rbp2 Layer2
-RpLP0 Layer2 mago Layer2
-RpLP0 Layer2 CG5642 Layer2
-RpLP0 Layer2 eIF3-S8 Layer2
-RpLP0 Layer2 Elf Layer2
-RpLP0 Layer2 eIF3-S10 Layer2
-RpLP0 Layer2 CG10576 Layer2
-RpLP0 Layer2 mRpL12 Layer2
-RpLP0 Layer2 mRpS7 Layer2
-RpLP0 Layer2 Tctp Layer2
-RpLP0 Layer2 Srp19 Layer2
-RpLP0 Layer2 Tango7 Layer2
-RpLP0 Layer2 eIF-2gamma Layer2
-RpLP0 Layer2 eIF-3p66 Layer2
-RpLP0 Layer2 CG9769 Layer2
-RpLP0 Layer2 eIF3-S9 Layer2
-RpLP0 Layer2 RpS15Aa Layer2
-RpLP0 Layer2 Srp54k Layer2
-RpLP0 Layer2 Trip1 Layer2
-RpLP0 Layer2 RpS15Ab Layer2
-RpLP0 Layer2 eRF1 Layer2
-RpLP0 Layer2 RpS14a Layer2
-RpLP0 Layer2 RpS10a Layer2
-RpLP0 Layer2 Ef1beta Layer2
-RpLP0 Layer2 RpS6 Layer2
-RpLP0 Layer2 RpS13 Layer2
-RpLP0 Layer2 RpS23 Layer2
-RpLP0 Layer2 sta Layer2
-RpLP0 Layer2 RpS11 Layer2
-RpLP0 Layer2 RpS16 Layer2
-RpLP0 Layer2 RpS7 Layer2
-RpLP0 Layer2 RpS27 Layer2
-RpLP0 Layer2 RpL10 Layer2
-RpLP0 Layer2 RpS3 Layer2
-RpLP0 Layer2 RpS10b Layer2
-RpLP0 Layer2 RpS8 Layer2
-RpLP0 Layer2 RpLP2 Layer2
-RpLP0 Layer2 RpS12 Layer2
-RpLP0 Layer2 RpL3 Layer2
-RpLP0 Layer2 Rack1 Layer2
-RpLP0 Layer2 RpL13 Layer2
-RpLP0 Layer2 RpL26 Layer2
-RpLP0 Layer2 RpL30 Layer2
-RpLP0 Layer2 RpL18A Layer2
-RpLP0 Layer2 RpL4 Layer2
-RpLP0 Layer2 RpL10Ab Layer2
-RpLP0 Layer2 RpS18 Layer2
-RpLP0 Layer2 RpS3A Layer2
-RpLP0 Layer2 RpL12 Layer2
-RpLP0 Layer2 RpL11 Layer2
-RpLP0 Layer2 RpS2 Layer2
-RpLP0 Layer2 RpS4 Layer2
-RpLP0 Layer2 RpS17 Layer2
-eRF1 Layer2 bonsai Layer2
-eRF1 Layer2 T-cp1 Layer2
-eRF1 Layer2 eIF3-S8 Layer2
-eRF1 Layer2 CG8635 Layer2
-eRF1 Layer2 CG7033 Layer2
-eRF1 Layer2 Cctgamma Layer2
-eRF1 Layer2 mRpS5 Layer2
-eRF1 Layer2 mRpS10 Layer2
-eRF1 Layer2 wuho Layer2
-eRF1 Layer2 RpS15Ab Layer2
-eRF1 Layer2 RpS14a Layer2
-eRF1 Layer2 RpS15Aa Layer2
-eRF1 Layer2 eIF3-S10 Layer2
-eRF1 Layer2 pAbp Layer2
-eRF1 Layer2 CG10576 Layer2
-eRF1 Layer2 RpS17 Layer2
-eRF1 Layer2 eIF-2gamma Layer2
-eRF1 Layer2 Trip1 Layer2
-eRF1 Layer2 mago Layer2
-eRF1 Layer2 RpLP2 Layer2
-eRF1 Layer2 eIF3-S9 Layer2
-eRF1 Layer2 eIF4G Layer2
-eRF1 Layer2 Rack1 Layer2
-eRF1 Layer2 RpS10a Layer2
-eRF1 Layer2 RpS10b Layer2
-eRF1 Layer2 RpL3 Layer2
-eRF1 Layer2 RpL10Ab Layer2
-eRF1 Layer2 RpS27 Layer2
-eRF1 Layer2 RpS12 Layer2
-eRF1 Layer2 RpS7 Layer2
-eRF1 Layer2 RpS18 Layer2
-eRF1 Layer2 RpL12 Layer2
-eRF1 Layer2 RpS23 Layer2
-eRF1 Layer2 RpL18A Layer2
-eRF1 Layer2 RpS16 Layer2
-eRF1 Layer2 RpL30 Layer2
-eRF1 Layer2 RpS11 Layer2
-eRF1 Layer2 RpS3 Layer2
-eRF1 Layer2 RpS3A Layer2
-eRF1 Layer2 sta Layer2
-eRF1 Layer2 RpS13 Layer2
-eRF1 Layer2 RpS6 Layer2
-eRF1 Layer2 RpS8 Layer2
-eRF1 Layer2 RpL4 Layer2
-eRF1 Layer2 RpS4 Layer2
-eRF1 Layer2 RpL11 Layer2
-eRF1 Layer2 RpS2 Layer2
-eRF1 Layer2 RpL13 Layer2
-Elf Layer2 ade5 Layer2
-Elf Layer2 mRpS7 Layer2
-Elf Layer2 RpS15Aa Layer2
-Elf Layer2 RpS15Ab Layer2
-Elf Layer2 T-cp1 Layer2
-Elf Layer2 eIF3-S9 Layer2
-Elf Layer2 Cctgamma Layer2
-Elf Layer2 RpS17 Layer2
-Elf Layer2 CG33123 Layer2
-Elf Layer2 Cct5 Layer2
-Elf Layer2 RpS14a Layer2
-Elf Layer2 eIF3-S8 Layer2
-Elf Layer2 eIF3-S10 Layer2
-Elf Layer2 eIF-2gamma Layer2
-Elf Layer2 RpL10Ab Layer2
-Elf Layer2 RpLP2 Layer2
-Elf Layer2 mago Layer2
-Elf Layer2 RpL13 Layer2
-Elf Layer2 RpL3 Layer2
-Elf Layer2 RpL30 Layer2
-Elf Layer2 RpL18A Layer2
-Elf Layer2 RpL12 Layer2
-Elf Layer2 RpL11 Layer2
-Elf Layer2 Rack1 Layer2
-Elf Layer2 RpL4 Layer2
-Elf Layer2 RpS4 Layer2
-Elf Layer2 RpS10b Layer2
-Elf Layer2 RpS10a Layer2
-Elf Layer2 RpS12 Layer2
-Elf Layer2 eIF4G Layer2
-Elf Layer2 RpS11 Layer2
-Elf Layer2 RpS13 Layer2
-Elf Layer2 RpS27 Layer2
-Elf Layer2 RpS8 Layer2
-Elf Layer2 RpS16 Layer2
-Elf Layer2 RpS3 Layer2
-Elf Layer2 sta Layer2
-Elf Layer2 RpS23 Layer2
-Elf Layer2 RpS18 Layer2
-Elf Layer2 RpS7 Layer2
-Elf Layer2 RpS6 Layer2
-Elf Layer2 RpS3A Layer2
-Elf Layer2 RpS2 Layer2
-Elf Layer2 pAbp Layer2
-Elf Layer2 eRF1 Layer2
-RpS15Aa Layer2 bonsai Layer2
-RpS15Aa Layer2 RpL12 Layer2
-RpS15Aa Layer2 sta Layer2
-RpS15Aa Layer2 RpS6 Layer2
-RpS15Aa Layer2 RpS16 Layer2
-RpS15Aa Layer2 RpS14a Layer2
-RpS27 Layer2 vig Layer2
-RpS27 Layer2 Srp72 Layer2
-RpS27 Layer2 mRpS10 Layer2
-RpS27 Layer2 bonsai Layer2
-RpS27 Layer2 mago Layer2
-RpS27 Layer2 Tctp Layer2
-RpS27 Layer2 CG5642 Layer2
-RpS27 Layer2 CG9769 Layer2
-RpS27 Layer2 Trip1 Layer2
-RpS27 Layer2 eIF3-S10 Layer2
-RpS27 Layer2 Srp19 Layer2
-RpS27 Layer2 Srp54k Layer2
-RpS27 Layer2 RpS13 Layer2
-RpS27 Layer2 RpS17 Layer2
-RpS27 Layer2 RpS4 Layer2
-RpS27 Layer2 RpS2 Layer2
-RpS27 Layer2 RpL13 Layer2
-RpS27 Layer2 RpL12 Layer2
-RpS27 Layer2 RpS6 Layer2
-RpS27 Layer2 RpS12 Layer2
-RpS27 Layer2 RpS10b Layer2
-RpS27 Layer2 sta Layer2
-RpS27 Layer2 RpS16 Layer2
-RpS27 Layer2 RpS14a Layer2
-RpS27 Layer2 RpL10Ab Layer2
-RpS27 Layer2 RpL3 Layer2
-RpS27 Layer2 RpS3 Layer2
-RpS27 Layer2 RpS15Aa Layer2
-RpS18 Layer2 Rpt6R Layer2
-RpS18 Layer2 Rpt6 Layer2
-RpS18 Layer2 Tcp-1eta Layer2
-RpS18 Layer2 Rpt2 Layer2
-RpS18 Layer2 vig Layer2
-RpS18 Layer2 ade3 Layer2
-RpS18 Layer2 Srp72 Layer2
-RpS18 Layer2 mago Layer2
-RpS18 Layer2 bonsai Layer2
-RpS18 Layer2 Tctp Layer2
-RpS18 Layer2 CG5642 Layer2
-RpS18 Layer2 mRpL12 Layer2
-RpS18 Layer2 mRpS10 Layer2
-RpS18 Layer2 mRpS9 Layer2
-RpS18 Layer2 eIF3-S10 Layer2
-RpS18 Layer2 CG9769 Layer2
-RpS18 Layer2 Srp19 Layer2
-RpS18 Layer2 Srp54k Layer2
-RpS18 Layer2 Trip1 Layer2
-RpS18 Layer2 RpS12 Layer2
-RpS18 Layer2 RpS2 Layer2
-RpS18 Layer2 RpS16 Layer2
-RpS18 Layer2 sta Layer2
-RpS18 Layer2 RpL10Ab Layer2
-RpS18 Layer2 RpL4 Layer2
-RpS18 Layer2 RpS14a Layer2
-RpS18 Layer2 RpS17 Layer2
-RpS18 Layer2 RpS3 Layer2
-RpS18 Layer2 RpS4 Layer2
-RpS18 Layer2 RpL3 Layer2
-RpS18 Layer2 RpS10a Layer2
-RpS18 Layer2 RpS13 Layer2
-RpS18 Layer2 RpS10b Layer2
-RpS18 Layer2 RpL13 Layer2
-RpS18 Layer2 RpL12 Layer2
-RpS18 Layer2 RpS15Aa Layer2
-RpS18 Layer2 RpS6 Layer2
-RpS18 Layer2 RpS27 Layer2
-RpS10b Layer2 bonsai Layer2
-RpS10b Layer2 mago Layer2
-RpS10b Layer2 RpS15Aa Layer2
-RpS10b Layer2 RpS16 Layer2
-RpS10b Layer2 RpS14a Layer2
-RpS10b Layer2 RpL12 Layer2
-RpS10b Layer2 sta Layer2
-RpS10b Layer2 RpS6 Layer2
-RpS3A Layer2 CG7048 Layer2
-RpS3A Layer2 Nacalpha Layer2
-RpS3A Layer2 vig Layer2
-RpS3A Layer2 eEF1delta Layer2
-RpS3A Layer2 Cct5 Layer2
-RpS3A Layer2 CG8258 Layer2
-RpS3A Layer2 CG7033 Layer2
-RpS3A Layer2 Tcp-1eta Layer2
-RpS3A Layer2 Srp72 Layer2
-RpS3A Layer2 mRpS5 Layer2
-RpS3A Layer2 Rbp2 Layer2
-RpS3A Layer2 mRpS10 Layer2
-RpS3A Layer2 eIF4G Layer2
-RpS3A Layer2 bonsai Layer2
-RpS3A Layer2 mago Layer2
-RpS3A Layer2 CG5642 Layer2
-RpS3A Layer2 eIF-3p66 Layer2
-RpS3A Layer2 eIF3-S8 Layer2
-RpS3A Layer2 Srp54k Layer2
-RpS3A Layer2 eIF3-S10 Layer2
-RpS3A Layer2 Srp19 Layer2
-RpS3A Layer2 CG9769 Layer2
-RpS3A Layer2 ade3 Layer2
-RpS3A Layer2 Trip1 Layer2
-RpS3A Layer2 Tango7 Layer2
-RpS3A Layer2 eIF3-S9 Layer2
-RpS3A Layer2 Tctp Layer2
-RpS3A Layer2 RpS12 Layer2
-RpS3A Layer2 RpS13 Layer2
-RpS3A Layer2 RpS2 Layer2
-RpS3A Layer2 RpS17 Layer2
-RpS3A Layer2 RpS15Ab Layer2
-RpS3A Layer2 RpL13 Layer2
-RpS3A Layer2 RpS15Aa Layer2
-RpS3A Layer2 RpS8 Layer2
-RpS3A Layer2 RpS10a Layer2
-RpS3A Layer2 RpS4 Layer2
-RpS3A Layer2 RpL11 Layer2
-RpS3A Layer2 RpS23 Layer2
-RpS3A Layer2 RpS7 Layer2
-RpS3A Layer2 RpLP2 Layer2
-RpS3A Layer2 sta Layer2
-RpS3A Layer2 RpS6 Layer2
-RpS3A Layer2 RpL12 Layer2
-RpS3A Layer2 RpS11 Layer2
-RpS3A Layer2 RpL30 Layer2
-RpS3A Layer2 RpL3 Layer2
-RpS3A Layer2 RpS3 Layer2
-RpS3A Layer2 RpS14a Layer2
-RpS3A Layer2 RpL10Ab Layer2
-RpS3A Layer2 RpL18A Layer2
-RpS3A Layer2 RpS27 Layer2
-RpS3A Layer2 RpL4 Layer2
-RpS3A Layer2 RpS16 Layer2
-RpS3A Layer2 RpS18 Layer2
-RpS3A Layer2 RpS10b Layer2
-RpS16 Layer2 mago Layer2
-RpS16 Layer2 RpS14a Layer2
-RpS16 Layer2 RpS6 Layer2
-RpS16 Layer2 sta Layer2
-RpS15Ab Layer2 vig Layer2
-RpS15Ab Layer2 CG9769 Layer2
-RpS15Ab Layer2 ade3 Layer2
-RpS15Ab Layer2 EfTuM Layer2
-RpS15Ab Layer2 Srp19 Layer2
-RpS15Ab Layer2 eIF3-S9 Layer2
-RpS15Ab Layer2 eIF3-S10 Layer2
-RpS15Ab Layer2 eIF3-S8 Layer2
-RpS15Ab Layer2 bonsai Layer2
-RpS15Ab Layer2 mRpS10 Layer2
-RpS15Ab Layer2 Srp54k Layer2
-RpS15Ab Layer2 Tango7 Layer2
-RpS15Ab Layer2 Tctp Layer2
-RpS15Ab Layer2 mRpS9 Layer2
-RpS15Ab Layer2 mRpL12 Layer2
-RpS15Ab Layer2 RpS15Aa Layer2
-RpS15Ab Layer2 RpS12 Layer2
-RpS15Ab Layer2 RpL10Ab Layer2
-RpS15Ab Layer2 RpL13 Layer2
-RpS15Ab Layer2 RpL18A Layer2
-RpS15Ab Layer2 RpL12 Layer2
-RpS15Ab Layer2 RpL3 Layer2
-RpS15Ab Layer2 RpL4 Layer2
-RpS15Ab Layer2 RpS10a Layer2
-RpS15Ab Layer2 RpL11 Layer2
-RpS15Ab Layer2 RpS6 Layer2
-RpS15Ab Layer2 RpS2 Layer2
-RpS15Ab Layer2 RpS13 Layer2
-RpS15Ab Layer2 RpS23 Layer2
-RpS15Ab Layer2 RpS18 Layer2
-RpS15Ab Layer2 RpS3 Layer2
-RpS15Ab Layer2 RpS10b Layer2
-RpS15Ab Layer2 RpS17 Layer2
-RpS15Ab Layer2 RpS14a Layer2
-RpS15Ab Layer2 sta Layer2
-RpS15Ab Layer2 RpS11 Layer2
-RpS15Ab Layer2 RpS27 Layer2
-RpS15Ab Layer2 RpS4 Layer2
-RpS15Ab Layer2 RpS16 Layer2
-RpL3 Layer2 eEF1delta Layer2
-RpL3 Layer2 bonsai Layer2
-RpL3 Layer2 Tcp-1zeta Layer2
-RpL3 Layer2 Tcp-1eta Layer2
-RpL3 Layer2 CG7033 Layer2
-RpL3 Layer2 Tctp Layer2
-RpL3 Layer2 mago Layer2
-RpL3 Layer2 Srp19 Layer2
-RpL3 Layer2 mRpL12 Layer2
-RpL3 Layer2 mRpS9 Layer2
-RpL3 Layer2 eIF3-S10 Layer2
-RpL3 Layer2 CG10576 Layer2
-RpL3 Layer2 CG5642 Layer2
-RpL3 Layer2 CG9769 Layer2
-RpL3 Layer2 Srp54k Layer2
-RpL3 Layer2 Trip1 Layer2
-RpL3 Layer2 ade3 Layer2
-RpL3 Layer2 RpS14a Layer2
-RpL3 Layer2 RpS10b Layer2
-RpL3 Layer2 RpS15Aa Layer2
-RpL3 Layer2 RpS13 Layer2
-RpL3 Layer2 RpS16 Layer2
-RpL3 Layer2 RpS2 Layer2
-RpL3 Layer2 RpS4 Layer2
-RpL3 Layer2 RpS17 Layer2
-RpL3 Layer2 sta Layer2
-RpL3 Layer2 RpL12 Layer2
-RpL3 Layer2 RpL13 Layer2
-RpL3 Layer2 RpL10Ab Layer2
-RpL3 Layer2 RpS12 Layer2
-RpL3 Layer2 RpS6 Layer2
-Tcp-1zeta Layer2 Tim9a Layer2
-Tcp-1zeta Layer2 RpL12 Layer2
-Tcp-1zeta Layer2 Hsp60 Layer2
-Tcp-1zeta Layer2 sta Layer2
-Tcp-1zeta Layer2 CG7033 Layer2
-T-cp1 Layer2 RpS13 Layer2
-T-cp1 Layer2 RpS2 Layer2
-T-cp1 Layer2 RpL13 Layer2
-T-cp1 Layer2 Hop Layer2
-T-cp1 Layer2 sta Layer2
-T-cp1 Layer2 RpL12 Layer2
-T-cp1 Layer2 Aats-ile Layer2
-T-cp1 Layer2 RpS4 Layer2
-T-cp1 Layer2 RpL3 Layer2
-T-cp1 Layer2 CG10576 Layer2
-T-cp1 Layer2 CG7033 Layer2
-T-cp1 Layer2 Cctgamma Layer2
-T-cp1 Layer2 Tcp-1eta Layer2
-T-cp1 Layer2 Tcp-1zeta Layer2
-RpS3 Layer2 Tcp-1zeta Layer2
-RpS3 Layer2 vig Layer2
-RpS3 Layer2 CG7033 Layer2
-RpS3 Layer2 eEF1delta Layer2
-RpS3 Layer2 ade3 Layer2
-RpS3 Layer2 Tcp-1eta Layer2
-RpS3 Layer2 Srp72 Layer2
-RpS3 Layer2 mago Layer2
-RpS3 Layer2 bonsai Layer2
-RpS3 Layer2 mRpS10 Layer2
-RpS3 Layer2 mRpS9 Layer2
-RpS3 Layer2 mRpL12 Layer2
-RpS3 Layer2 Srp19 Layer2
-RpS3 Layer2 CG5642 Layer2
-RpS3 Layer2 Srp54k Layer2
-RpS3 Layer2 CG9769 Layer2
-RpS3 Layer2 Tctp Layer2
-RpS3 Layer2 eIF3-S10 Layer2
-RpS3 Layer2 RpS15Aa Layer2
-RpS3 Layer2 RpL13 Layer2
-RpS3 Layer2 RpS6 Layer2
-RpS3 Layer2 RpS2 Layer2
-RpS3 Layer2 RpL12 Layer2
-RpS3 Layer2 RpL10Ab Layer2
-RpS3 Layer2 RpS14a Layer2
-RpS3 Layer2 RpS12 Layer2
-RpS3 Layer2 RpS13 Layer2
-RpS3 Layer2 RpS16 Layer2
-RpS3 Layer2 RpS10b Layer2
-RpS3 Layer2 sta Layer2
-RpS3 Layer2 RpS4 Layer2
-RpS3 Layer2 RpL3 Layer2
-RpS3 Layer2 RpS17 Layer2
-RpS3 Layer2 Trip1 Layer2
-RpL26 Layer2 CG10576 Layer2
-RpL26 Layer2 eEF1delta Layer2
-RpL26 Layer2 EfTuM Layer2
-RpL26 Layer2 Nacalpha Layer2
-RpL26 Layer2 bonsai Layer2
-RpL26 Layer2 ade3 Layer2
-RpL26 Layer2 mRpS10 Layer2
-RpL26 Layer2 mRpS5 Layer2
-RpL26 Layer2 Tctp Layer2
-RpL26 Layer2 eIF4G Layer2
-RpL26 Layer2 CG5642 Layer2
-RpL26 Layer2 mago Layer2
-RpL26 Layer2 eIF3-S10 Layer2
-RpL26 Layer2 Rbp2 Layer2
-RpL26 Layer2 mRpS9 Layer2
-RpL26 Layer2 eIF3-S9 Layer2
-RpL26 Layer2 mRpL12 Layer2
-RpL26 Layer2 eIF-3p66 Layer2
-RpL26 Layer2 eIF3-S8 Layer2
-RpL26 Layer2 Srp72 Layer2
-RpL26 Layer2 eIF-2gamma Layer2
-RpL26 Layer2 mRpS7 Layer2
-RpL26 Layer2 CG9769 Layer2
-RpL26 Layer2 Elf Layer2
-RpL26 Layer2 Tango7 Layer2
-RpL26 Layer2 Trip1 Layer2
-RpL26 Layer2 Ef1beta Layer2
-RpL26 Layer2 Srp19 Layer2
-RpL26 Layer2 eRF1 Layer2
-RpL26 Layer2 Srp54k Layer2
-RpL26 Layer2 RpS15Aa Layer2
-RpL26 Layer2 RpS15Ab Layer2
-RpL26 Layer2 RpL10Ab Layer2
-RpL26 Layer2 RpL11 Layer2
-RpL26 Layer2 RpL3 Layer2
-RpL26 Layer2 RpS13 Layer2
-RpL26 Layer2 RpS2 Layer2
-RpL26 Layer2 RpS6 Layer2
-RpL26 Layer2 RpS4 Layer2
-RpL26 Layer2 RpS27 Layer2
-RpL26 Layer2 RpL30 Layer2
-RpL26 Layer2 RpL18A Layer2
-RpL26 Layer2 RpS12 Layer2
-RpL26 Layer2 RpS10a Layer2
-RpL26 Layer2 RpS18 Layer2
-RpL26 Layer2 RpS3A Layer2
-RpL26 Layer2 RpS8 Layer2
-RpL26 Layer2 RpS17 Layer2
-RpL26 Layer2 sta Layer2
-RpL26 Layer2 RpS14a Layer2
-RpL26 Layer2 RpS11 Layer2
-RpL26 Layer2 RpS23 Layer2
-RpL26 Layer2 RpL13 Layer2
-RpL26 Layer2 RpL12 Layer2
-RpL26 Layer2 RpS10b Layer2
-RpL26 Layer2 Rack1 Layer2
-RpL26 Layer2 RpL4 Layer2
-RpL26 Layer2 RpS3 Layer2
-RpL26 Layer2 RpS7 Layer2
-RpL26 Layer2 RpS16 Layer2
-RpL26 Layer2 RpLP2 Layer2
-RpS12 Layer2 mago Layer2
-RpS12 Layer2 RpS14a Layer2
-RpS12 Layer2 RpS15Aa Layer2
-RpS12 Layer2 RpS10b Layer2
-RpS12 Layer2 RpS6 Layer2
-RpS12 Layer2 RpS16 Layer2
-RpS12 Layer2 RpL12 Layer2
-RpS12 Layer2 sta Layer2
-CG5642 Layer2 RpL12 Layer2
-CG5642 Layer2 RpS6 Layer2
-CG5642 Layer2 sta Layer2
-CG5642 Layer2 RpS10b Layer2
-CG5642 Layer2 RpS12 Layer2
-CG5642 Layer2 RpS16 Layer2
-CG5642 Layer2 RpS4 Layer2
-CG9769 Layer2 RpS15Aa Layer2
-CG9769 Layer2 RpS17 Layer2
-CG9769 Layer2 RpS12 Layer2
-CG9769 Layer2 RpL12 Layer2
-CG9769 Layer2 RpS10b Layer2
-CG9769 Layer2 RpL10Ab Layer2
-CG9769 Layer2 RpS4 Layer2
-CG9769 Layer2 sta Layer2
-CG9769 Layer2 RpS6 Layer2
-CG9769 Layer2 RpS16 Layer2
-CG9769 Layer2 CG5642 Layer2
-CG7033 Layer2 RpS14a Layer2
-CG7033 Layer2 sta Layer2
-Cct5 Layer2 RpS13 Layer2
-Cct5 Layer2 ade3 Layer2
-Cct5 Layer2 Prosalpha6 Layer2
-Cct5 Layer2 Hsc70-5 Layer2
-Cct5 Layer2 Rpt6 Layer2
-Cct5 Layer2 RpS3 Layer2
-Cct5 Layer2 Ref1 Layer2
-Cct5 Layer2 RpS16 Layer2
-Cct5 Layer2 RpL12 Layer2
-Cct5 Layer2 RpL13 Layer2
-Cct5 Layer2 CG5642 Layer2
-Cct5 Layer2 RpL18A Layer2
-Cct5 Layer2 RpS6 Layer2
-Cct5 Layer2 sta Layer2
-Cct5 Layer2 RpL11 Layer2
-Cct5 Layer2 Aats-ile Layer2
-Cct5 Layer2 pont Layer2
-Cct5 Layer2 Trip1 Layer2
-Cct5 Layer2 RpS4 Layer2
-Cct5 Layer2 Tango7 Layer2
-Cct5 Layer2 eIF3-S9 Layer2
-Cct5 Layer2 RpL3 Layer2
-Cct5 Layer2 RpS2 Layer2
-Cct5 Layer2 CG10576 Layer2
-Cct5 Layer2 RpL4 Layer2
-Cct5 Layer2 Hop Layer2
-Cct5 Layer2 Cctgamma Layer2
-Cct5 Layer2 Tcp-1eta Layer2
-Cct5 Layer2 T-cp1 Layer2
-Cct5 Layer2 Tcp-1zeta Layer2
-Cct5 Layer2 CG7033 Layer2
-RpLP2 Layer2 CG10576 Layer2
-RpLP2 Layer2 bonsai Layer2
-RpLP2 Layer2 mRpS10 Layer2
-RpLP2 Layer2 Srp72 Layer2
-RpLP2 Layer2 Srp54k Layer2
-RpLP2 Layer2 Srp19 Layer2
-RpLP2 Layer2 mago Layer2
-RpLP2 Layer2 eIF3-S8 Layer2
-RpLP2 Layer2 eIF3-S10 Layer2
-RpLP2 Layer2 CG5642 Layer2
-RpLP2 Layer2 Rbp2 Layer2
-RpLP2 Layer2 Tango7 Layer2
-RpLP2 Layer2 eIF3-S9 Layer2
-RpLP2 Layer2 eIF-3p66 Layer2
-RpLP2 Layer2 Nacalpha Layer2
-RpLP2 Layer2 RpS15Aa Layer2
-RpLP2 Layer2 CG9769 Layer2
-RpLP2 Layer2 Trip1 Layer2
-RpLP2 Layer2 Tctp Layer2
-RpLP2 Layer2 RpS10a Layer2
-RpLP2 Layer2 RpS10b Layer2
-RpLP2 Layer2 RpS27 Layer2
-RpLP2 Layer2 RpS3 Layer2
-RpLP2 Layer2 RpS13 Layer2
-RpLP2 Layer2 RpS18 Layer2
-RpLP2 Layer2 RpL4 Layer2
-RpLP2 Layer2 RpS7 Layer2
-RpLP2 Layer2 RpL13 Layer2
-RpLP2 Layer2 RpS23 Layer2
-RpLP2 Layer2 RpL10Ab Layer2
-RpLP2 Layer2 RpS15Ab Layer2
-RpLP2 Layer2 RpS6 Layer2
-RpLP2 Layer2 RpS14a Layer2
-RpLP2 Layer2 RpS2 Layer2
-RpLP2 Layer2 RpL3 Layer2
-RpLP2 Layer2 RpL12 Layer2
-RpLP2 Layer2 RpL18A Layer2
-RpLP2 Layer2 RpL11 Layer2
-RpLP2 Layer2 RpS11 Layer2
-RpLP2 Layer2 sta Layer2
-RpLP2 Layer2 RpS16 Layer2
-RpLP2 Layer2 RpS17 Layer2
-RpLP2 Layer2 RpS12 Layer2
-RpLP2 Layer2 RpS4 Layer2
-RpS23 Layer2 CG7033 Layer2
-RpS23 Layer2 vig Layer2
-RpS23 Layer2 eEF1delta Layer2
-RpS23 Layer2 bonsai Layer2
-RpS23 Layer2 mRpS10 Layer2
-RpS23 Layer2 Srp72 Layer2
-RpS23 Layer2 mago Layer2
-RpS23 Layer2 CG5642 Layer2
-RpS23 Layer2 mRpL12 Layer2
-RpS23 Layer2 mRpS9 Layer2
-RpS23 Layer2 Tctp Layer2
-RpS23 Layer2 eIF3-S9 Layer2
-RpS23 Layer2 eIF3-S8 Layer2
-RpS23 Layer2 eIF3-S10 Layer2
-RpS23 Layer2 CG9769 Layer2
-RpS23 Layer2 Trip1 Layer2
-RpS23 Layer2 Srp19 Layer2
-RpS23 Layer2 Srp54k Layer2
-RpS23 Layer2 RpS16 Layer2
-RpS23 Layer2 RpS27 Layer2
-RpS23 Layer2 RpL18A Layer2
-RpS23 Layer2 RpS10b Layer2
-RpS23 Layer2 RpS15Aa Layer2
-RpS23 Layer2 RpS12 Layer2
-RpS23 Layer2 RpS3 Layer2
-RpS23 Layer2 RpS18 Layer2
-RpS23 Layer2 RpS14a Layer2
-RpS23 Layer2 RpL11 Layer2
-RpS23 Layer2 RpL3 Layer2
-RpS23 Layer2 RpS2 Layer2
-RpS23 Layer2 sta Layer2
-RpS23 Layer2 RpS6 Layer2
-RpS23 Layer2 RpL10Ab Layer2
-RpS23 Layer2 RpL12 Layer2
-RpS23 Layer2 RpL13 Layer2
-RpS23 Layer2 RpS17 Layer2
-RpS23 Layer2 RpL4 Layer2
-RpS23 Layer2 RpS4 Layer2
-RpS23 Layer2 RpS10a Layer2
-RpS23 Layer2 RpS13 Layer2
-RpS14a Layer2 sta Layer2
-Rack1 Layer2 vig Layer2
-Rack1 Layer2 bel Layer2
-Rack1 Layer2 Prosalpha7 Layer2
-Rack1 Layer2 Rpn6 Layer2
-Rack1 Layer2 Rpn2 Layer2
-Rack1 Layer2 eEF1delta Layer2
-Rack1 Layer2 Cctgamma Layer2
-Rack1 Layer2 Eno Layer2
-Rack1 Layer2 pAbp Layer2
-Rack1 Layer2 Nacalpha Layer2
-Rack1 Layer2 eIF3-S8 Layer2
-Rack1 Layer2 CG10576 Layer2
-Rack1 Layer2 CG9769 Layer2
-Rack1 Layer2 ade3 Layer2
-Rack1 Layer2 Dp1 Layer2
-Rack1 Layer2 T-cp1 Layer2
-Rack1 Layer2 Tcp-1zeta Layer2
-Rack1 Layer2 Srp54k Layer2
-Rack1 Layer2 Tcp-1eta Layer2
-Rack1 Layer2 Srp19 Layer2
-Rack1 Layer2 CG8258 Layer2
-Rack1 Layer2 CG7033 Layer2
-Rack1 Layer2 Cct5 Layer2
-Rack1 Layer2 mRpS5 Layer2
-Rack1 Layer2 bonsai Layer2
-Rack1 Layer2 mRpS10 Layer2
-Rack1 Layer2 eIF3-S10 Layer2
-Rack1 Layer2 Tctp Layer2
-Rack1 Layer2 eIF3-S9 Layer2
-Rack1 Layer2 eIF-2gamma Layer2
-Rack1 Layer2 Trip1 Layer2
-Rack1 Layer2 RpS15Ab Layer2
-Rack1 Layer2 Tango7 Layer2
-Rack1 Layer2 RpS15Aa Layer2
-Rack1 Layer2 RpS10a Layer2
-Rack1 Layer2 RpS14a Layer2
-Rack1 Layer2 RpS10b Layer2
-Rack1 Layer2 RpL10Ab Layer2
-Rack1 Layer2 RpS8 Layer2
-Rack1 Layer2 RpL3 Layer2
-Rack1 Layer2 RpS11 Layer2
-Rack1 Layer2 RpS17 Layer2
-Rack1 Layer2 RpS2 Layer2
-Rack1 Layer2 RpS16 Layer2
-Rack1 Layer2 RpS13 Layer2
-Rack1 Layer2 RpS12 Layer2
-Rack1 Layer2 RpS4 Layer2
-Rack1 Layer2 RpS3A Layer2
-Rack1 Layer2 RpS3 Layer2
-Rack1 Layer2 RpS18 Layer2
-Rack1 Layer2 RpS23 Layer2
-Rack1 Layer2 RpS6 Layer2
-Rack1 Layer2 RpL12 Layer2
-Rack1 Layer2 RpL13 Layer2
-Rack1 Layer2 RpS27 Layer2
-Rack1 Layer2 RpL11 Layer2
-Rack1 Layer2 RpL18A Layer2
-Rack1 Layer2 RpL30 Layer2
-Rack1 Layer2 RpS7 Layer2
-Rack1 Layer2 RpLP2 Layer2
-Rack1 Layer2 RpL4 Layer2
-Rack1 Layer2 sta Layer2
-RpL11 Layer2 CG7033 Layer2
-RpL11 Layer2 vig Layer2
-RpL11 Layer2 eEF1delta Layer2
-RpL11 Layer2 Tcp-1eta Layer2
-RpL11 Layer2 mago Layer2
-RpL11 Layer2 eIF3-S10 Layer2
-RpL11 Layer2 CG10576 Layer2
-RpL11 Layer2 CG5642 Layer2
-RpL11 Layer2 bonsai Layer2
-RpL11 Layer2 mRpS10 Layer2
-RpL11 Layer2 Srp72 Layer2
-RpL11 Layer2 mRpS9 Layer2
-RpL11 Layer2 Tctp Layer2
-RpL11 Layer2 mRpL12 Layer2
-RpL11 Layer2 CG9769 Layer2
-RpL11 Layer2 Srp19 Layer2
-RpL11 Layer2 Srp54k Layer2
-RpL11 Layer2 Trip1 Layer2
-RpL11 Layer2 RpS15Aa Layer2
-RpL11 Layer2 RpS13 Layer2
-RpL11 Layer2 RpS18 Layer2
-RpL11 Layer2 RpL3 Layer2
-RpL11 Layer2 RpL12 Layer2
-RpL11 Layer2 RpS10b Layer2
-RpL11 Layer2 RpS17 Layer2
-RpL11 Layer2 RpL10Ab Layer2
-RpL11 Layer2 RpS10a Layer2
-RpL11 Layer2 RpL13 Layer2
-RpL11 Layer2 RpS4 Layer2
-RpL11 Layer2 RpS16 Layer2
-RpL11 Layer2 RpS12 Layer2
-RpL11 Layer2 RpS27 Layer2
-RpL11 Layer2 RpS2 Layer2
-RpL11 Layer2 RpS3 Layer2
-RpL11 Layer2 RpS6 Layer2
-RpL11 Layer2 RpS14a Layer2
-RpL11 Layer2 RpL4 Layer2
-RpL11 Layer2 sta Layer2
-Rpn12 Layer2 Ubqn Layer2
-Prosbeta7 Layer2 Uba2 Layer2
-Prosbeta7 Layer2 CG7033 Layer2
-Prosbeta7 Layer2 Uch-L5 Layer2
-Prosbeta7 Layer2 Rpt4R Layer2
-Prosbeta7 Layer2 Rpn5 Layer2
-Prosbeta7 Layer2 Rpt6 Layer2
-Prosbeta7 Layer2 Rpn12 Layer2
-RpS13 Layer2 CG10576 Layer2
-RpS13 Layer2 CG7033 Layer2
-RpS13 Layer2 mRpL12 Layer2
-RpS13 Layer2 ade3 Layer2
-RpS13 Layer2 mago Layer2
-RpS13 Layer2 CG5642 Layer2
-RpS13 Layer2 eIF3-S10 Layer2
-RpS13 Layer2 CG9769 Layer2
-RpS13 Layer2 Srp19 Layer2
-RpS13 Layer2 Srp54k Layer2
-RpS13 Layer2 Trip1 Layer2
-RpS13 Layer2 RpL10Ab Layer2
-RpS13 Layer2 RpS15Aa Layer2
-RpS13 Layer2 RpS12 Layer2
-RpS13 Layer2 RpS17 Layer2
-RpS13 Layer2 RpL12 Layer2
-RpS13 Layer2 RpS10b Layer2
-RpS13 Layer2 sta Layer2
-RpS13 Layer2 RpS16 Layer2
-RpS13 Layer2 RpS4 Layer2
-RpS13 Layer2 RpS6 Layer2
-RpS13 Layer2 RpS14a Layer2
-RpL18A Layer2 Tcp-1zeta Layer2
-RpL18A Layer2 vig Layer2
-RpL18A Layer2 CG7033 Layer2
-RpL18A Layer2 eEF1delta Layer2
-RpL18A Layer2 Tcp-1eta Layer2
-RpL18A Layer2 ade3 Layer2
-RpL18A Layer2 mRpS10 Layer2
-RpL18A Layer2 mago Layer2
-RpL18A Layer2 bonsai Layer2
-RpL18A Layer2 CG10576 Layer2
-RpL18A Layer2 CG5642 Layer2
-RpL18A Layer2 eIF3-S8 Layer2
-RpL18A Layer2 eIF3-S10 Layer2
-RpL18A Layer2 Srp72 Layer2
-RpL18A Layer2 eIF3-S9 Layer2
-RpL18A Layer2 Tctp Layer2
-RpL18A Layer2 Srp19 Layer2
-RpL18A Layer2 CG9769 Layer2
-RpL18A Layer2 Srp54k Layer2
-RpL18A Layer2 Trip1 Layer2
-RpL18A Layer2 RpS15Aa Layer2
-RpL18A Layer2 RpS14a Layer2
-RpL18A Layer2 sta Layer2
-RpL18A Layer2 RpL10Ab Layer2
-RpL18A Layer2 RpS2 Layer2
-RpL18A Layer2 RpL3 Layer2
-RpL18A Layer2 RpS17 Layer2
-RpL18A Layer2 RpL4 Layer2
-RpL18A Layer2 RpL13 Layer2
-RpL18A Layer2 RpS4 Layer2
-RpL18A Layer2 RpL11 Layer2
-RpL18A Layer2 RpS10a Layer2
-RpL18A Layer2 RpS6 Layer2
-RpL18A Layer2 RpL12 Layer2
-RpL18A Layer2 RpS18 Layer2
-RpL18A Layer2 RpS16 Layer2
-RpL18A Layer2 RpS12 Layer2
-RpL18A Layer2 RpS27 Layer2
-RpL18A Layer2 RpS10b Layer2
-RpL18A Layer2 RpS3 Layer2
-RpL18A Layer2 RpS13 Layer2
-RpS4 Layer2 Tcp-1zeta Layer2
-RpS4 Layer2 CG7033 Layer2
-RpS4 Layer2 mago Layer2
-RpS4 Layer2 bonsai Layer2
-RpS4 Layer2 RpS15Aa Layer2
-RpS4 Layer2 RpL12 Layer2
-RpS4 Layer2 sta Layer2
-RpS4 Layer2 RpS10b Layer2
-RpS4 Layer2 RpS14a Layer2
-RpS4 Layer2 RpS16 Layer2
-RpS4 Layer2 RpS6 Layer2
-RpS4 Layer2 RpS12 Layer2
-RpL4 Layer2 vig Layer2
-RpL4 Layer2 bonsai Layer2
-RpL4 Layer2 Cctgamma Layer2
-RpL4 Layer2 mRpS10 Layer2
-RpL4 Layer2 Tcp-1eta Layer2
-RpL4 Layer2 Tctp Layer2
-RpL4 Layer2 T-cp1 Layer2
-RpL4 Layer2 CG7033 Layer2
-RpL4 Layer2 Tcp-1zeta Layer2
-RpL4 Layer2 ade3 Layer2
-RpL4 Layer2 mRpS9 Layer2
-RpL4 Layer2 mRpL12 Layer2
-RpL4 Layer2 Srp72 Layer2
-RpL4 Layer2 mago Layer2
-RpL4 Layer2 Srp19 Layer2
-RpL4 Layer2 CG5642 Layer2
-RpL4 Layer2 CG10576 Layer2
-RpL4 Layer2 Srp54k Layer2
-RpL4 Layer2 CG9769 Layer2
-RpL4 Layer2 eIF3-S10 Layer2
-RpL4 Layer2 RpS15Aa Layer2
-RpL4 Layer2 RpS10a Layer2
-RpL4 Layer2 RpL10Ab Layer2
-RpL4 Layer2 Trip1 Layer2
-RpL4 Layer2 RpS16 Layer2
-RpL4 Layer2 RpS6 Layer2
-RpL4 Layer2 RpS12 Layer2
-RpL4 Layer2 RpS14a Layer2
-RpL4 Layer2 RpS2 Layer2
-RpL4 Layer2 RpL12 Layer2
-RpL4 Layer2 RpS27 Layer2
-RpL4 Layer2 sta Layer2
-RpL4 Layer2 RpS13 Layer2
-RpL4 Layer2 RpS10b Layer2
-RpL4 Layer2 RpS3 Layer2
-RpL4 Layer2 RpS17 Layer2
-RpL4 Layer2 RpL13 Layer2
-RpL4 Layer2 RpL3 Layer2
-RpL4 Layer2 RpS4 Layer2
diff --git a/www/data/TAU_example_one_layer.txt b/www/data/TAU_example_one_layer.txt
deleted file mode 100644
index d8722b9..0000000
--- a/www/data/TAU_example_one_layer.txt
+++ /dev/null
@@ -1,3177 +0,0 @@
-SourceNode SourceLayer TargetNode TargetLayer
-CG6178 Layer2 yip2 Layer2
-shep Layer2 ScpX Layer2
-nito Layer2 Ref1 Layer2
-bol Layer2 Hrb27C Layer2
-Lar Layer2 Abl Layer2
-Lar Layer2 CadN Layer2
-CG31715 Layer2 cpb Layer2
-Mapmodulin Layer2 Aats-ile Layer2
-CG5590 Layer2 CG17597 Layer2
-Sc2 Layer2 Ost48 Layer2
-frj Layer2 CG10932 Layer2
-Kap-alpha3 Layer2 emb Layer2
-Uch Layer2 Usp5 Layer2
-Prx2540-1 Layer2 Prx2540-2 Layer2
-CG2082 Layer2 CG2091 Layer2
-lig Layer2 Capr Layer2
-lig Layer2 rin Layer2
-clu Layer2 Dp1 Layer2
-clu Layer2 eIF3-S10 Layer2
-clu Layer2 rin Layer2
-clu Layer2 eIF3-S8 Layer2
-clu Layer2 eIF3-S9 Layer2
-CG9090 Layer2 Cyt-c-p Layer2
-CG9090 Layer2 COX5A Layer2
-Ace Layer2 Cat Layer2
-Dbp80 Layer2 emb Layer2
-Ssadh Layer2 CG33791 Layer2
-Ssadh Layer2 CG32026 Layer2
-Ssadh Layer2 Mdh2 Layer2
-CG4572 Layer2 CG10576 Layer2
-CG4572 Layer2 Hsc70-3 Layer2
-PlexA Layer2 alpha-Cat Layer2
-l(1)G0156 Layer2 CG11876 Layer2
-l(1)G0156 Layer2 Ssadh Layer2
-l(1)G0156 Layer2 CG33791 Layer2
-Plc21C Layer2 Gbeta76C Layer2
-Plc21C Layer2 Pkc53E Layer2
-Hrb27C Layer2 poe Layer2
-Hrb27C Layer2 heph Layer2
-Hrb27C Layer2 PyK Layer2
-Hrb27C Layer2 bel Layer2
-Hrb27C Layer2 me31B Layer2
-Hrb27C Layer2 pAbp Layer2
-Hrb27C Layer2 TER94 Layer2
-Khc Layer2 TAU Layer2
-CG32026 Layer2 CG33791 Layer2
-bel Layer2 Ref1 Layer2
-bel Layer2 poe Layer2
-bel Layer2 emb Layer2
-bel Layer2 vig Layer2
-bel Layer2 me31B Layer2
-Dp1 Layer2 CG2918 Layer2
-Sgt Layer2 Trap1 Layer2
-Sgt Layer2 Tcp-1zeta Layer2
-Men-b Layer2 CG11876 Layer2
-Men-b Layer2 PyK Layer2
-Men-b Layer2 Mdh2 Layer2
-CG11899 Layer2 CG3011 Layer2
-Mgstl Layer2 CG3529 Layer2
-Mgstl Layer2 Ost48 Layer2
-CG14715 Layer2 CG2852 Layer2
-Lpin Layer2 Pkc53E Layer2
-gammaSnap2 Layer2 zetaCOP Layer2
-gammaSnap2 Layer2 betaCOP Layer2
-gammaSnap2 Layer2 epsilonCOP Layer2
-gammaSnap2 Layer2 beta'COP Layer2
-gammaSnap2 Layer2 deltaCOP Layer2
-CG10359 Layer2 Gp93 Layer2
-CG10359 Layer2 CG1354 Layer2
-CG10359 Layer2 CaBP1 Layer2
-CG10359 Layer2 Pdi Layer2
-KP78a Layer2 CG32683 Layer2
-KP78a Layer2 Arr2 Layer2
-CG9257 Layer2 poe Layer2
-CG9257 Layer2 CG3529 Layer2
-Ect3 Layer2 Rpn5 Layer2
-Ect3 Layer2 CG6891 Layer2
-Ect3 Layer2 Cat Layer2
-Ect3 Layer2 CalpB Layer2
-CG4882 Layer2 bonsai Layer2
-CalpB Layer2 CG6891 Layer2
-CalpB Layer2 Cat Layer2
-par-1 Layer2 CG32683 Layer2
-par-1 Layer2 KP78a Layer2
-par-1 Layer2 Arr2 Layer2
-Prx5 Layer2 Prx2540-2 Layer2
-Prx5 Layer2 Cat Layer2
-Pkc53E Layer2 zip Layer2
-CG31064 Layer2 Rab5 Layer2
-Nlp Layer2 mago Layer2
-Nlp Layer2 eEF1delta Layer2
-Nlp Layer2 smt3 Layer2
-CG10186 Layer2 betaCOP Layer2
-CG10186 Layer2 cpb Layer2
-CG10186 Layer2 cpa Layer2
-CG10186 Layer2 beta'COP Layer2
-CG10186 Layer2 bai Layer2
-CG10186 Layer2 epsilonCOP Layer2
-CG10186 Layer2 Dlic Layer2
-CG10186 Layer2 p115 Layer2
-CG10186 Layer2 zetaCOP Layer2
-CG10186 Layer2 deltaCOP Layer2
-rin Layer2 Capr Layer2
-LpR1 Layer2 Arr2 Layer2
-LpR1 Layer2 Arr1 Layer2
-LpR1 Layer2 Ubqn Layer2
-LpR1 Layer2 CG32683 Layer2
-LpR1 Layer2 lqf Layer2
-LpR1 Layer2 Hrs Layer2
-LpR1 Layer2 LpR2 Layer2
-LpR1 Layer2 Rab5 Layer2
-CG16935 Layer2 CG12262 Layer2
-CG16935 Layer2 yip2 Layer2
-poe Layer2 betaCOP Layer2
-poe Layer2 CG3529 Layer2
-CG43367 Layer2 betaCOP Layer2
-CG43367 Layer2 poe Layer2
-Rab39 Layer2 Rab5 Layer2
-Rab39 Layer2 Gdi Layer2
-emb Layer2 Rae1 Layer2
-emb Layer2 alphaCOP Layer2
-emb Layer2 CG10576 Layer2
-AGBE Layer2 Eno Layer2
-AGBE Layer2 PyK Layer2
-LpR2 Layer2 Ubqn Layer2
-LpR2 Layer2 CG32683 Layer2
-LpR2 Layer2 Arr2 Layer2
-LpR2 Layer2 Hrs Layer2
-LpR2 Layer2 lqf Layer2
-LpR2 Layer2 Rab5 Layer2
-CG3529 Layer2 Ost48 Layer2
-Ost48 Layer2 CG2918 Layer2
-Hsp60C Layer2 Hop Layer2
-Hsp60C Layer2 Hsc70-3 Layer2
-Prx2540-2 Layer2 Cat Layer2
-EfTuM Layer2 RpS14a Layer2
-EfTuM Layer2 Hsc70-5 Layer2
-EfTuM Layer2 RpL10Ab Layer2
-EfTuM Layer2 RpS13 Layer2
-EfTuM Layer2 RpS16 Layer2
-EfTuM Layer2 RpS23 Layer2
-EfTuM Layer2 RpS15Aa Layer2
-EfTuM Layer2 RpL4 Layer2
-EfTuM Layer2 CG11876 Layer2
-EfTuM Layer2 bonsai Layer2
-EfTuM Layer2 mRpS10 Layer2
-EfTuM Layer2 RpL12 Layer2
-EfTuM Layer2 sta Layer2
-EfTuM Layer2 RpL3 Layer2
-EfTuM Layer2 Hsp60 Layer2
-EfTuM Layer2 RpS18 Layer2
-EfTuM Layer2 RpS2 Layer2
-EfTuM Layer2 mRpS9 Layer2
-EfTuM Layer2 RpS3 Layer2
-EfTuM Layer2 mRpL12 Layer2
-EfTuM Layer2 RpL11 Layer2
-fne Layer2 Tango4 Layer2
-fne Layer2 Srp54 Layer2
-fne Layer2 CG10777 Layer2
-fne Layer2 Prp8 Layer2
-fne Layer2 mago Layer2
-fne Layer2 CG16941 Layer2
-fne Layer2 yps Layer2
-fne Layer2 CG10077 Layer2
-fne Layer2 Rbp9 Layer2
-fne Layer2 U2A Layer2
-fne Layer2 Pkc53E Layer2
-fne Layer2 Hrb98DE Layer2
-fne Layer2 LS2 Layer2
-fne Layer2 SF2 Layer2
-fne Layer2 mub Layer2
-fne Layer2 heph Layer2
-Klp10A Layer2 deltaCOP Layer2
-Mtl Layer2 zip Layer2
-Mtl Layer2 tsr Layer2
-Mtl Layer2 RhoGDI Layer2
-Hsp60 Layer2 CG7033 Layer2
-RanBPM Layer2 CG6617 Layer2
-yps Layer2 Tango4 Layer2
-yps Layer2 LS2 Layer2
-yps Layer2 mago Layer2
-yps Layer2 CG10777 Layer2
-Art4 Layer2 CG10777 Layer2
-Art4 Layer2 CG10077 Layer2
-Art4 Layer2 pAbp Layer2
-ImpL3 Layer2 Cat Layer2
-ImpL3 Layer2 Men-b Layer2
-ImpL3 Layer2 Pfk Layer2
-ImpL3 Layer2 Men Layer2
-ImpL3 Layer2 Eno Layer2
-ImpL3 Layer2 Tpi Layer2
-ImpL3 Layer2 PyK Layer2
-ImpL3 Layer2 CG11876 Layer2
-CG10077 Layer2 CG10777 Layer2
-CG10077 Layer2 LS2 Layer2
-CG10077 Layer2 Tango4 Layer2
-CG10077 Layer2 yps Layer2
-CG10077 Layer2 mago Layer2
-Lam Layer2 Rab5 Layer2
-LamC Layer2 Lam Layer2
-Ubqn Layer2 HDAC6 Layer2
-Ubqn Layer2 CG32683 Layer2
-mub Layer2 CG3800 Layer2
-mub Layer2 mago Layer2
-mub Layer2 U2A Layer2
-mub Layer2 Srp54 Layer2
-mub Layer2 Tango4 Layer2
-mub Layer2 Prp8 Layer2
-mub Layer2 CG10777 Layer2
-mub Layer2 CG16941 Layer2
-mub Layer2 yps Layer2
-mub Layer2 CG10077 Layer2
-mub Layer2 LS2 Layer2
-mub Layer2 Hrb98DE Layer2
-mub Layer2 Rbp9 Layer2
-mub Layer2 SF2 Layer2
-AGO1 Layer2 Rbp9 Layer2
-AGO1 Layer2 pAbp Layer2
-AGO1 Layer2 Rae1 Layer2
-AGO1 Layer2 me31B Layer2
-glo Layer2 Hrb98DE Layer2
-Sap-r Layer2 CG3529 Layer2
-Sap-r Layer2 Mgstl Layer2
-Sap-r Layer2 pins Layer2
-Sap-r Layer2 Rab5 Layer2
-Sap-r Layer2 Ost48 Layer2
-Ndg Layer2 Cg25C Layer2
-Ndg Layer2 LanB2 Layer2
-trol Layer2 Cp1 Layer2
-trol Layer2 LanB2 Layer2
-trol Layer2 Cg25C Layer2
-trol Layer2 Ndg Layer2
-Hsp70Ab Layer2 Pdi Layer2
-Hsp70Ab Layer2 DnaJ-H Layer2
-Hsp70Ab Layer2 Hop Layer2
-Hsp70Ab Layer2 Hsp27 Layer2
-bai Layer2 Srp19 Layer2
-bai Layer2 CG2852 Layer2
-bai Layer2 gammaSnap2 Layer2
-bai Layer2 Klp10A Layer2
-bai Layer2 Klc Layer2
-bai Layer2 Dlic Layer2
-bai Layer2 cpa Layer2
-bai Layer2 p115 Layer2
-bai Layer2 cpb Layer2
-bai Layer2 deltaCOP Layer2
-bai Layer2 betaCOP Layer2
-bai Layer2 epsilonCOP Layer2
-bai Layer2 beta'COP Layer2
-bai Layer2 zetaCOP Layer2
-Nc73EF Layer2 Ssadh Layer2
-Nc73EF Layer2 CG32026 Layer2
-Nc73EF Layer2 l(1)G0156 Layer2
-Nc73EF Layer2 CG33791 Layer2
-Nc73EF Layer2 CG11876 Layer2
-Srp54 Layer2 yps Layer2
-Srp54 Layer2 CG10777 Layer2
-Srp54 Layer2 mago Layer2
-Srp54 Layer2 LS2 Layer2
-Srp54 Layer2 CG10077 Layer2
-Srp54 Layer2 Tango4 Layer2
-Ugt Layer2 CG2918 Layer2
-Rbp9 Layer2 CG10777 Layer2
-Rbp9 Layer2 Tango4 Layer2
-Rbp9 Layer2 Srp54 Layer2
-Rbp9 Layer2 Prp8 Layer2
-Rbp9 Layer2 CG10077 Layer2
-Rbp9 Layer2 CG16941 Layer2
-Rbp9 Layer2 U2A Layer2
-Rbp9 Layer2 Pkc53E Layer2
-Rbp9 Layer2 LS2 Layer2
-Rbp9 Layer2 Hrb98DE Layer2
-Rbp9 Layer2 yps Layer2
-Rbp9 Layer2 mago Layer2
-Act79B Layer2 cpb Layer2
-Act79B Layer2 flr Layer2
-Act79B Layer2 zip Layer2
-Rbp2 Layer2 pAbp Layer2
-Rbp2 Layer2 RpL10Ab Layer2
-Rbp2 Layer2 RpS4 Layer2
-Rbp2 Layer2 RpL18A Layer2
-Rbp2 Layer2 RpS3 Layer2
-Rbp2 Layer2 RpS11 Layer2
-Rbp2 Layer2 eIF3-S9 Layer2
-Rbp2 Layer2 RpL11 Layer2
-Rbp2 Layer2 eIF3-S10 Layer2
-Rbp2 Layer2 RpL13 Layer2
-Rbp2 Layer2 eIF3-S8 Layer2
-Rbp2 Layer2 RpS10b Layer2
-Rbp2 Layer2 RpS10a Layer2
-Rbp2 Layer2 RpS27 Layer2
-Rbp2 Layer2 RpL3 Layer2
-Rbp2 Layer2 RpS12 Layer2
-Rbp2 Layer2 RpS7 Layer2
-Rbp2 Layer2 RpL12 Layer2
-Rbp2 Layer2 Tango7 Layer2
-Rbp2 Layer2 RpS23 Layer2
-Rbp2 Layer2 RpS13 Layer2
-Rbp2 Layer2 sta Layer2
-Rbp2 Layer2 RpS18 Layer2
-Rbp2 Layer2 RpS16 Layer2
-Rbp2 Layer2 RpL4 Layer2
-Rbp2 Layer2 RpS6 Layer2
-Rbp2 Layer2 Trip1 Layer2
-Rbp2 Layer2 RpS2 Layer2
-Rbp2 Layer2 eIF-3p66 Layer2
-Rbp2 Layer2 CG5642 Layer2
-Rbp2 Layer2 CG9769 Layer2
-Iswi Layer2 His1 Layer2
-Iswi Layer2 Bap55 Layer2
-Iswi Layer2 smt3 Layer2
-Iswi Layer2 mor Layer2
-Vps60 Layer2 Hrs Layer2
-eEF1delta Layer2 eIF3-S10 Layer2
-eEF1delta Layer2 RpS10b Layer2
-eEF1delta Layer2 RpS13 Layer2
-eEF1delta Layer2 RpS6 Layer2
-eEF1delta Layer2 RpS16 Layer2
-eEF1delta Layer2 RpL12 Layer2
-eEF1delta Layer2 sta Layer2
-eEF1delta Layer2 RpS2 Layer2
-eEF1delta Layer2 CG5642 Layer2
-eEF1delta Layer2 RpL13 Layer2
-eEF1delta Layer2 Trip1 Layer2
-CG10576 Layer2 RpS6 Layer2
-CG10576 Layer2 RpS4 Layer2
-CG10576 Layer2 Capr Layer2
-CG10576 Layer2 sta Layer2
-CG10576 Layer2 RpS16 Layer2
-CG10576 Layer2 Tcp-1zeta Layer2
-CG10576 Layer2 wuho Layer2
-CG10576 Layer2 RpL12 Layer2
-CG10576 Layer2 CG7033 Layer2
-Abl Layer2 Dscam1 Layer2
-Abl Layer2 syd Layer2
-Abl Layer2 alpha-Cat Layer2
-Abl Layer2 arm Layer2
-Rae1 Layer2 mago Layer2
-Mtor Layer2 mago Layer2
-Mtor Layer2 AGO1 Layer2
-Mtor Layer2 Hsc70-3 Layer2
-Mtor Layer2 Hsc70-5 Layer2
-Mtor Layer2 smt3 Layer2
-Mtor Layer2 Rae1 Layer2
-Ref1 Layer2 LS2 Layer2
-Ref1 Layer2 mago Layer2
-Trap1 Layer2 Hsp70Ab Layer2
-Trap1 Layer2 Hop Layer2
-Trap1 Layer2 Hsp60C Layer2
-Trap1 Layer2 Hsp60 Layer2
-Trap1 Layer2 CG2918 Layer2
-Trap1 Layer2 Hsc70-3 Layer2
-Trap1 Layer2 CG1416 Layer2
-CG17597 Layer2 Cat Layer2
-CG17597 Layer2 ScpX Layer2
-CG17597 Layer2 Mfe2 Layer2
-lqf Layer2 CG32683 Layer2
-lqf Layer2 Ubqn Layer2
-lqf Layer2 Arr2 Layer2
-lqf Layer2 Rab5 Layer2
-lqf Layer2 Hrs Layer2
-Tim17b Layer2 Tim9a Layer2
-CG3902 Layer2 CG10932 Layer2
-Cat Layer2 CG6891 Layer2
-p115 Layer2 deltaCOP Layer2
-Sec24AB Layer2 deltaCOP Layer2
-Sec24AB Layer2 betaCOP Layer2
-Sec24AB Layer2 epsilonCOP Layer2
-Sec24AB Layer2 zetaCOP Layer2
-Sec24AB Layer2 alphaCOP Layer2
-Sec24AB Layer2 beta'COP Layer2
-Sec24AB Layer2 p115 Layer2
-ninaC Layer2 Act79B Layer2
-ninaC Layer2 zip Layer2
-ninaC Layer2 Arr2 Layer2
-mago Layer2 sta Layer2
-mago Layer2 RpS6 Layer2
-mago Layer2 CG10777 Layer2
-CG11876 Layer2 Men Layer2
-CG11876 Layer2 Mdh2 Layer2
-CG11876 Layer2 PyK Layer2
-CG11876 Layer2 ND-PDSW Layer2
-CG11876 Layer2 AcCoAS Layer2
-CG11876 Layer2 Eno Layer2
-CG11876 Layer2 CG33791 Layer2
-Pdk Layer2 ade3 Layer2
-Pdk Layer2 CG11876 Layer2
-B52 Layer2 RpS3 Layer2
-B52 Layer2 Hrb27C Layer2
-B52 Layer2 glo Layer2
-B52 Layer2 CG10777 Layer2
-B52 Layer2 fne Layer2
-B52 Layer2 CG10077 Layer2
-B52 Layer2 yps Layer2
-B52 Layer2 Rbp9 Layer2
-B52 Layer2 CG16941 Layer2
-B52 Layer2 Tango4 Layer2
-B52 Layer2 U2A Layer2
-B52 Layer2 mago Layer2
-B52 Layer2 Srp54 Layer2
-B52 Layer2 mub Layer2
-B52 Layer2 heph Layer2
-B52 Layer2 Prp8 Layer2
-B52 Layer2 LS2 Layer2
-B52 Layer2 Hrb98DE Layer2
-B52 Layer2 U2af50 Layer2
-B52 Layer2 SF2 Layer2
-Vha100-1 Layer2 sun Layer2
-Vha100-1 Layer2 Vha100-2 Layer2
-Vha100-1 Layer2 Vha68-2 Layer2
-robl Layer2 Dlic Layer2
-Hsc70-5 Layer2 CG7033 Layer2
-Hsc70-5 Layer2 T-cp1 Layer2
-Hsc70-5 Layer2 Tpi Layer2
-Hsc70-5 Layer2 DnaJ-H Layer2
-Hsc70-5 Layer2 Tcp-1eta Layer2
-Hsc70-5 Layer2 Hop Layer2
-Hsc70-5 Layer2 Rae1 Layer2
-Hsc70-5 Layer2 Trap1 Layer2
-Hsc70-5 Layer2 Hsp60C Layer2
-Hsc70-5 Layer2 Hsc70-3 Layer2
-Hsc70-5 Layer2 Hsp60 Layer2
-Hsc70-5 Layer2 Gp93 Layer2
-tsr Layer2 sqh Layer2
-tsr Layer2 cpa Layer2
-tsr Layer2 RpS6 Layer2
-flr Layer2 cpa Layer2
-flr Layer2 tsr Layer2
-AcCoAS Layer2 CG12262 Layer2
-AcCoAS Layer2 CG10932 Layer2
-ACC Layer2 Eno Layer2
-ACC Layer2 CG10932 Layer2
-ACC Layer2 AcCoAS Layer2
-LS2 Layer2 mago Layer2
-LS2 Layer2 CG10777 Layer2
-FKBP59 Layer2 ninaC Layer2
-FKBP59 Layer2 Dlic Layer2
-FKBP59 Layer2 cpb Layer2
-FKBP59 Layer2 cpa Layer2
-FKBP59 Layer2 Hop Layer2
-U2af50 Layer2 Ref1 Layer2
-U2af50 Layer2 LS2 Layer2
-U2af50 Layer2 yps Layer2
-U2af50 Layer2 CG10777 Layer2
-U2af50 Layer2 CG10077 Layer2
-U2af50 Layer2 Rbp9 Layer2
-U2af50 Layer2 fne Layer2
-U2af50 Layer2 Srp54 Layer2
-U2af50 Layer2 mago Layer2
-U2af50 Layer2 mub Layer2
-U2af50 Layer2 Tango4 Layer2
-U2af50 Layer2 heph Layer2
-U2af50 Layer2 CG16941 Layer2
-U2af50 Layer2 Hrb98DE Layer2
-U2af50 Layer2 Prp8 Layer2
-U2af50 Layer2 SF2 Layer2
-U2af50 Layer2 U2A Layer2
-Fmr1 Layer2 dock Layer2
-Fmr1 Layer2 PyK Layer2
-Fmr1 Layer2 poe Layer2
-Fmr1 Layer2 bel Layer2
-Fmr1 Layer2 pAbp Layer2
-Fmr1 Layer2 eIF4G Layer2
-Fmr1 Layer2 Dscam1 Layer2
-Fmr1 Layer2 RanBPM Layer2
-Fmr1 Layer2 Hrb98DE Layer2
-Fmr1 Layer2 Hrb27C Layer2
-Fmr1 Layer2 Capr Layer2
-Fmr1 Layer2 TER94 Layer2
-Fmr1 Layer2 Rbp9 Layer2
-Fmr1 Layer2 me31B Layer2
-Fmr1 Layer2 AGO1 Layer2
-Fmr1 Layer2 vig Layer2
-ScpX Layer2 Cat Layer2
-ScpX Layer2 Mfe2 Layer2
-Top2 Layer2 RpS13 Layer2
-Top2 Layer2 Pkc53E Layer2
-Top2 Layer2 Uba2 Layer2
-Top2 Layer2 Iswi Layer2
-Top2 Layer2 smt3 Layer2
-CG8209 Layer2 Ufd1-like Layer2
-heph Layer2 Tango4 Layer2
-heph Layer2 Srp54 Layer2
-heph Layer2 CG16941 Layer2
-heph Layer2 CG10077 Layer2
-heph Layer2 CG10777 Layer2
-heph Layer2 U2A Layer2
-heph Layer2 mago Layer2
-heph Layer2 yps Layer2
-heph Layer2 Prp8 Layer2
-heph Layer2 mub Layer2
-heph Layer2 LS2 Layer2
-heph Layer2 pAbp Layer2
-heph Layer2 Hrb98DE Layer2
-heph Layer2 Rbp9 Layer2
-heph Layer2 SF2 Layer2
-Hrs Layer2 CG4572 Layer2
-Hrs Layer2 CG32683 Layer2
-Hrs Layer2 Arr2 Layer2
-Hrs Layer2 Ubqn Layer2
-Hrs Layer2 Rab5 Layer2
-Hrb98DE Layer2 CG10777 Layer2
-Hrb98DE Layer2 mago Layer2
-Hrb98DE Layer2 Srp54 Layer2
-Hrb98DE Layer2 CG16941 Layer2
-Hrb98DE Layer2 CG10077 Layer2
-Hrb98DE Layer2 yps Layer2
-Hrb98DE Layer2 LS2 Layer2
-Hrb98DE Layer2 Tango4 Layer2
-bic Layer2 RpS3 Layer2
-Nacalpha Layer2 RpS13 Layer2
-Nacalpha Layer2 RpS2 Layer2
-Nacalpha Layer2 sta Layer2
-Nacalpha Layer2 RpS6 Layer2
-Nacalpha Layer2 RpS16 Layer2
-Nacalpha Layer2 RpS27 Layer2
-Nacalpha Layer2 RpS4 Layer2
-Nacalpha Layer2 RpS23 Layer2
-Nacalpha Layer2 RpS18 Layer2
-Nacalpha Layer2 RpL12 Layer2
-Nacalpha Layer2 RpL18A Layer2
-Nacalpha Layer2 RpL11 Layer2
-Nacalpha Layer2 RpS10b Layer2
-Nacalpha Layer2 RpS17 Layer2
-Nacalpha Layer2 RpL13 Layer2
-Nacalpha Layer2 RpS3 Layer2
-Nacalpha Layer2 RpL4 Layer2
-Nacalpha Layer2 bic Layer2
-Aats-val Layer2 eEF1delta Layer2
-Aats-val Layer2 Ef1beta Layer2
-Aats-val Layer2 Aats-asp Layer2
-Aats-val Layer2 Aats-ile Layer2
-Aats-val Layer2 CG33123 Layer2
-Aats-val Layer2 Aats-arg Layer2
-BicD Layer2 Lam Layer2
-BicD Layer2 bic Layer2
-BicD Layer2 Khc Layer2
-BicD Layer2 cpb Layer2
-BicD Layer2 cpa Layer2
-BicD Layer2 Dlic Layer2
-DnaJ-H Layer2 CG2918 Layer2
-DnaJ-H Layer2 Hsc70-3 Layer2
-DnaJ-H Layer2 Hop Layer2
-mRpS34 Layer2 CG4882 Layer2
-mRpS34 Layer2 bonsai Layer2
-CadN Layer2 alpha-Cat Layer2
-CadN Layer2 arm Layer2
-vig Layer2 RpS15Aa Layer2
-vig Layer2 RpS13 Layer2
-vig Layer2 RpS16 Layer2
-vig Layer2 RpS17 Layer2
-vig Layer2 RpL12 Layer2
-Tudor-SN Layer2 bel Layer2
-Tudor-SN Layer2 AGO1 Layer2
-Tudor-SN Layer2 Fmr1 Layer2
-Tudor-SN Layer2 vig Layer2
-Mdh2 Layer2 Aldh Layer2
-Mdh2 Layer2 ND-51 Layer2
-Mdh2 Layer2 COX4 Layer2
-Mdh2 Layer2 Cyt-c-p Layer2
-Mdh2 Layer2 COX5A Layer2
-Mdh2 Layer2 Men Layer2
-Mdh2 Layer2 Eno Layer2
-Cyt-c-p Layer2 Cat Layer2
-Cyt-c-p Layer2 porin Layer2
-Cyt-c-p Layer2 ND-PDSW Layer2
-Cyt-c-p Layer2 COX6B Layer2
-Aldh Layer2 Cat Layer2
-Aldh Layer2 Eno Layer2
-Aldh Layer2 CG12262 Layer2
-Aldh Layer2 AcCoAS Layer2
-CG15093 Layer2 CG12262 Layer2
-CG15093 Layer2 AcCoAS Layer2
-CG15093 Layer2 CG3902 Layer2
-CG15093 Layer2 Aldh Layer2
-CG4679 Layer2 mRpS34 Layer2
-CG4679 Layer2 bsf Layer2
-CG4679 Layer2 CG4882 Layer2
-CG4679 Layer2 mRpL12 Layer2
-CG4679 Layer2 mRpL44 Layer2
-CG4679 Layer2 bonsai Layer2
-CG4679 Layer2 mRpS10 Layer2
-CG4679 Layer2 mRpS23 Layer2
-CG4679 Layer2 mRpS22 Layer2
-CG4679 Layer2 mRpS9 Layer2
-CG4679 Layer2 mRpS18B Layer2
-SF2 Layer2 CG10777 Layer2
-SF2 Layer2 CG10077 Layer2
-SF2 Layer2 U2A Layer2
-SF2 Layer2 Tango4 Layer2
-SF2 Layer2 yps Layer2
-SF2 Layer2 Prp8 Layer2
-SF2 Layer2 Rbp9 Layer2
-SF2 Layer2 CG16941 Layer2
-SF2 Layer2 Srp54 Layer2
-SF2 Layer2 Ref1 Layer2
-SF2 Layer2 LS2 Layer2
-SF2 Layer2 mago Layer2
-SF2 Layer2 Hrb98DE Layer2
-p47 Layer2 CG8209 Layer2
-p47 Layer2 Ufd1-like Layer2
-p47 Layer2 Rpt2 Layer2
-Rad23 Layer2 me31B Layer2
-Rad23 Layer2 Uch-L5 Layer2
-Rad23 Layer2 Rpt1 Layer2
-Rad23 Layer2 HDAC6 Layer2
-Rad23 Layer2 Usp5 Layer2
-Rad23 Layer2 Uba2 Layer2
-Rad23 Layer2 Ufd1-like Layer2
-Rad23 Layer2 Sgt Layer2
-Rad23 Layer2 Rpt5 Layer2
-Rad23 Layer2 Bap55 Layer2
-Rad23 Layer2 pont Layer2
-Rad23 Layer2 CG8209 Layer2
-Rad23 Layer2 Iswi Layer2
-Rad23 Layer2 Rpn13 Layer2
-Rad23 Layer2 Ubqn Layer2
-Rad23 Layer2 smt3 Layer2
-Rad23 Layer2 Rpt2 Layer2
-Rad23 Layer2 TER94 Layer2
-me31B Layer2 poe Layer2
-me31B Layer2 CG2091 Layer2
-me31B Layer2 yps Layer2
-ND-51L1 Layer2 ND-51 Layer2
-ND-51L1 Layer2 NP15.6 Layer2
-ND-51L1 Layer2 ND-PDSW Layer2
-ND-51L1 Layer2 ND-B16.6 Layer2
-syd Layer2 alpha-Cat Layer2
-syd Layer2 Dlic Layer2
-syd Layer2 Klc Layer2
-Arr2 Layer2 Ubqn Layer2
-Arr2 Layer2 CG32683 Layer2
-Arr1 Layer2 ninaC Layer2
-Arr1 Layer2 chp Layer2
-Arr1 Layer2 KP78a Layer2
-Arr1 Layer2 LpR2 Layer2
-Arr1 Layer2 Ubqn Layer2
-Arr1 Layer2 CG32683 Layer2
-Arr1 Layer2 par-1 Layer2
-Arr1 Layer2 Hrs Layer2
-Arr1 Layer2 lqf Layer2
-Arr1 Layer2 Rab5 Layer2
-Arr1 Layer2 Arr2 Layer2
-ND-51 Layer2 ND-B16.6 Layer2
-ND-51 Layer2 ND-PDSW Layer2
-CG16941 Layer2 yps Layer2
-CG16941 Layer2 mago Layer2
-CG16941 Layer2 CG10777 Layer2
-CG16941 Layer2 CG10077 Layer2
-CG16941 Layer2 Srp54 Layer2
-CG16941 Layer2 LS2 Layer2
-CG16941 Layer2 Tango4 Layer2
-Cnx99A Layer2 Ost48 Layer2
-Cnx99A Layer2 Hsp70Ab Layer2
-Cnx99A Layer2 CG2918 Layer2
-Cnx99A Layer2 Ugt Layer2
-Cnx99A Layer2 Gp93 Layer2
-Cnx99A Layer2 Pdi Layer2
-Cnx99A Layer2 Hsc70-3 Layer2
-Cnx99A Layer2 ERp60 Layer2
-Aats-his Layer2 Aats-asp Layer2
-Aats-his Layer2 Cct5 Layer2
-Aats-his Layer2 Aats-arg Layer2
-Aats-his Layer2 Aats-val Layer2
-Aats-his Layer2 CG33123 Layer2
-Aats-his Layer2 Aats-ile Layer2
-mRpS35 Layer2 RpS2 Layer2
-mRpS35 Layer2 CG4882 Layer2
-mRpS35 Layer2 mRpL12 Layer2
-mRpS35 Layer2 mRpS34 Layer2
-mRpS35 Layer2 mRpL44 Layer2
-mRpS35 Layer2 CG4679 Layer2
-mRpS35 Layer2 mRpS23 Layer2
-mRpS35 Layer2 mRpS18B Layer2
-mRpS35 Layer2 mRpS10 Layer2
-mRpS35 Layer2 mRpS9 Layer2
-mRpS35 Layer2 mRpS22 Layer2
-mRpS35 Layer2 bonsai Layer2
-mRpS35 Layer2 mRpS5 Layer2
-Vha68-2 Layer2 sun Layer2
-Vha68-2 Layer2 Eno Layer2
-Vha100-2 Layer2 sun Layer2
-Vha100-2 Layer2 Vha68-2 Layer2
-Rac1 Layer2 sqh Layer2
-Rac1 Layer2 Fmr1 Layer2
-Rac1 Layer2 tsr Layer2
-Rac1 Layer2 Dscam2 Layer2
-Rac1 Layer2 Gdi Layer2
-Rac1 Layer2 Abl Layer2
-Rac1 Layer2 dock Layer2
-Rac1 Layer2 zip Layer2
-Rac1 Layer2 RhoGDI Layer2
-mRpS7 Layer2 ade3 Layer2
-mRpS7 Layer2 CG9769 Layer2
-mRpS7 Layer2 CG4882 Layer2
-mRpS7 Layer2 eIF3-S8 Layer2
-mRpS7 Layer2 Srp19 Layer2
-mRpS7 Layer2 EfTuM Layer2
-mRpS7 Layer2 eIF3-S10 Layer2
-mRpS7 Layer2 eIF-2gamma Layer2
-mRpS7 Layer2 Srp54k Layer2
-mRpS7 Layer2 eRF1 Layer2
-mRpS7 Layer2 Tango7 Layer2
-mRpS7 Layer2 RpLP2 Layer2
-mRpS7 Layer2 eIF3-S9 Layer2
-mRpS7 Layer2 RpL4 Layer2
-mRpS7 Layer2 RpS10a Layer2
-mRpS7 Layer2 RpS12 Layer2
-mRpS7 Layer2 RpS10b Layer2
-mRpS7 Layer2 CG7048 Layer2
-mRpS7 Layer2 RpS15Ab Layer2
-mRpS7 Layer2 RpS15Aa Layer2
-mRpS7 Layer2 RpL10Ab Layer2
-mRpS7 Layer2 RpS14a Layer2
-mRpS7 Layer2 RpL18A Layer2
-mRpS7 Layer2 Rack1 Layer2
-mRpS7 Layer2 RpS4 Layer2
-mRpS7 Layer2 RpS17 Layer2
-mRpS7 Layer2 RpL13 Layer2
-mRpS7 Layer2 RpS7 Layer2
-mRpS7 Layer2 RpS6 Layer2
-mRpS7 Layer2 RpS8 Layer2
-mRpS7 Layer2 RpS16 Layer2
-mRpS7 Layer2 sta Layer2
-mRpS7 Layer2 RpS13 Layer2
-mRpS7 Layer2 RpL3 Layer2
-mRpS7 Layer2 RpS23 Layer2
-mRpS7 Layer2 RpS27 Layer2
-mRpS7 Layer2 RpL12 Layer2
-mRpS7 Layer2 RpS18 Layer2
-mRpS7 Layer2 RpS3A Layer2
-mRpS7 Layer2 RpS2 Layer2
-mRpS7 Layer2 RpS3 Layer2
-mRpS7 Layer2 RpS11 Layer2
-mRpS7 Layer2 RpL11 Layer2
-mRpS7 Layer2 RpL30 Layer2
-mRpS7 Layer2 mRpL44 Layer2
-mRpS7 Layer2 mRpS22 Layer2
-mRpS7 Layer2 mRpS34 Layer2
-mRpS7 Layer2 CG4679 Layer2
-mRpS7 Layer2 mRpS23 Layer2
-mRpS7 Layer2 bonsai Layer2
-mRpS7 Layer2 mRpS10 Layer2
-mRpS7 Layer2 mRpS35 Layer2
-mRpS7 Layer2 mRpL12 Layer2
-mRpS7 Layer2 mRpS18B Layer2
-mRpS7 Layer2 mRpS9 Layer2
-mRpS7 Layer2 mRpS5 Layer2
-Prp8 Layer2 yps Layer2
-Prp8 Layer2 Srp54 Layer2
-Prp8 Layer2 CG10777 Layer2
-Prp8 Layer2 CG10077 Layer2
-Prp8 Layer2 LS2 Layer2
-Prp8 Layer2 mago Layer2
-Prp8 Layer2 Hrb98DE Layer2
-Prp8 Layer2 CG16941 Layer2
-Prp8 Layer2 Tango4 Layer2
-pont Layer2 Tcp-1zeta Layer2
-pont Layer2 Tcp-1eta Layer2
-pont Layer2 Rpt6 Layer2
-pont Layer2 Uch-L5 Layer2
-mRpL44 Layer2 mRpS34 Layer2
-mRpL44 Layer2 mRpL12 Layer2
-mRpL44 Layer2 bonsai Layer2
-mRpS22 Layer2 CG4882 Layer2
-mRpS22 Layer2 mRpL12 Layer2
-mRpS22 Layer2 mRpS10 Layer2
-mRpS22 Layer2 mRpL44 Layer2
-mRpS22 Layer2 mRpS34 Layer2
-mRpS22 Layer2 mRpS23 Layer2
-mRpS22 Layer2 bonsai Layer2
-mRpS22 Layer2 mRpS18B Layer2
-mRpS22 Layer2 mRpS9 Layer2
-Cp1 Layer2 Ect3 Layer2
-Cp1 Layer2 PyK Layer2
-Cp1 Layer2 Rpn5 Layer2
-Cp1 Layer2 CG6891 Layer2
-Cp1 Layer2 CalpB Layer2
-Cp1 Layer2 Rpn6 Layer2
-Cp1 Layer2 Cat Layer2
-Cp1 Layer2 Rpt5 Layer2
-cathD Layer2 CG4572 Layer2
-cathD Layer2 CG6891 Layer2
-cathD Layer2 PyK Layer2
-cathD Layer2 CG8258 Layer2
-cathD Layer2 Ect3 Layer2
-cathD Layer2 Pfk Layer2
-cathD Layer2 Rpn5 Layer2
-cathD Layer2 CalpB Layer2
-cathD Layer2 Rpt5 Layer2
-cathD Layer2 Rpn6 Layer2
-cathD Layer2 Cat Layer2
-cathD Layer2 Sap-r Layer2
-cathD Layer2 Cp1 Layer2
-Tango4 Layer2 CG10777 Layer2
-Tango4 Layer2 LS2 Layer2
-Tango4 Layer2 mago Layer2
-U2A Layer2 yps Layer2
-U2A Layer2 CG10077 Layer2
-U2A Layer2 CG10777 Layer2
-U2A Layer2 Srp54 Layer2
-U2A Layer2 Hrb98DE Layer2
-U2A Layer2 mago Layer2
-U2A Layer2 LS2 Layer2
-U2A Layer2 CG16941 Layer2
-U2A Layer2 Prp8 Layer2
-U2A Layer2 Tango4 Layer2
-Uba2 Layer2 Usp5 Layer2
-Uba2 Layer2 Ubqn Layer2
-smt3 Layer2 tsr Layer2
-smt3 Layer2 mago Layer2
-smt3 Layer2 Ufd1-like Layer2
-smt3 Layer2 CG10777 Layer2
-smt3 Layer2 CG10077 Layer2
-smt3 Layer2 Rae1 Layer2
-smt3 Layer2 Uba2 Layer2
-zip Layer2 sqh Layer2
-ND-15 Layer2 CG11752 Layer2
-ND-15 Layer2 ND-51L1 Layer2
-ND-15 Layer2 COX5A Layer2
-ND-15 Layer2 ND-51 Layer2
-ND-15 Layer2 NP15.6 Layer2
-ND-15 Layer2 ND-B16.6 Layer2
-ND-15 Layer2 ND-PDSW Layer2
-pAbp Layer2 rin Layer2
-pAbp Layer2 RpL3 Layer2
-pAbp Layer2 RpL4 Layer2
-pAbp Layer2 emb Layer2
-pAbp Layer2 Dp1 Layer2
-pAbp Layer2 RpS6 Layer2
-pAbp Layer2 tyf Layer2
-pAbp Layer2 bel Layer2
-pAbp Layer2 yps Layer2
-pAbp Layer2 CG11505 Layer2
-pAbp Layer2 me31B Layer2
-eIF4G Layer2 Dp1 Layer2
-eIF4G Layer2 kra Layer2
-eIF4G Layer2 bel Layer2
-eIF4G Layer2 PlexA Layer2
-eIF4G Layer2 me31B Layer2
-eIF4G Layer2 RpL18A Layer2
-eIF4G Layer2 RpL12 Layer2
-eIF4G Layer2 RpS12 Layer2
-eIF4G Layer2 Hsp67Bc Layer2
-eIF4G Layer2 RpS4 Layer2
-eIF4G Layer2 RpL11 Layer2
-eIF4G Layer2 RpS23 Layer2
-eIF4G Layer2 RpS7 Layer2
-eIF4G Layer2 RpS27 Layer2
-eIF4G Layer2 RpLP2 Layer2
-eIF4G Layer2 RpS10b Layer2
-eIF4G Layer2 RpS10a Layer2
-eIF4G Layer2 sta Layer2
-eIF4G Layer2 RpS16 Layer2
-eIF4G Layer2 RpL4 Layer2
-eIF4G Layer2 RpL13 Layer2
-eIF4G Layer2 RpS18 Layer2
-eIF4G Layer2 RpS13 Layer2
-eIF4G Layer2 RpL3 Layer2
-eIF4G Layer2 RpL10Ab Layer2
-eIF4G Layer2 RpS11 Layer2
-eIF4G Layer2 RpS8 Layer2
-eIF4G Layer2 RpS2 Layer2
-eIF4G Layer2 Hsp27 Layer2
-eIF4G Layer2 eIF-3p66 Layer2
-eIF4G Layer2 mago Layer2
-eIF4G Layer2 Tango7 Layer2
-eIF4G Layer2 RpS3 Layer2
-eIF4G Layer2 CG5642 Layer2
-eIF4G Layer2 CG9769 Layer2
-eIF4G Layer2 Trip1 Layer2
-eIF4G Layer2 Rbp2 Layer2
-eIF4G Layer2 RpS6 Layer2
-eIF4G Layer2 alpha-Cat Layer2
-eIF4G Layer2 eIF3-S10 Layer2
-eIF4G Layer2 eIF3-S8 Layer2
-eIF4G Layer2 eIF3-S9 Layer2
-eIF4G Layer2 pAbp Layer2
-Pfk Layer2 CG11876 Layer2
-Pfk Layer2 CG6891 Layer2
-Pfk Layer2 Ect3 Layer2
-Pfk Layer2 Rpt5 Layer2
-Pfk Layer2 Cp1 Layer2
-Pfk Layer2 Rpn6 Layer2
-Pfk Layer2 CalpB Layer2
-Pfk Layer2 Rpn5 Layer2
-Pfk Layer2 Cat Layer2
-Pfk Layer2 Eno Layer2
-Pfk Layer2 Tpi Layer2
-Pfk Layer2 PyK Layer2
-bonsai Layer2 RpS14a Layer2
-bonsai Layer2 RpS16 Layer2
-bonsai Layer2 sta Layer2
-bonsai Layer2 RpS6 Layer2
-CG12262 Layer2 CG10932 Layer2
-yip2 Layer2 Uch Layer2
-yip2 Layer2 AcCoAS Layer2
-yip2 Layer2 CG10932 Layer2
-yip2 Layer2 CG3902 Layer2
-yip2 Layer2 CG12262 Layer2
-Rab5 Layer2 Mgstl Layer2
-Rab5 Layer2 CG32683 Layer2
-Rab5 Layer2 Ost48 Layer2
-Rab5 Layer2 CG3529 Layer2
-Rab5 Layer2 Arr2 Layer2
-Gdi Layer2 Cat Layer2
-Gdi Layer2 CG6891 Layer2
-Gdi Layer2 Pfk Layer2
-Gdi Layer2 Rpt1 Layer2
-Gdi Layer2 Rpt5 Layer2
-Gdi Layer2 Rpn5 Layer2
-Gdi Layer2 CG8258 Layer2
-Gdi Layer2 Rpn6 Layer2
-Gdi Layer2 Rpn3 Layer2
-Gdi Layer2 PyK Layer2
-Gdi Layer2 TER94 Layer2
-Gdi Layer2 RhoGDI Layer2
-Gdi Layer2 Rab5 Layer2
-mRpS10 Layer2 Srp54k Layer2
-mRpS10 Layer2 CG4882 Layer2
-mRpS10 Layer2 RpL10Ab Layer2
-mRpS10 Layer2 RpS14a Layer2
-mRpS10 Layer2 RpS16 Layer2
-mRpS10 Layer2 RpS10b Layer2
-mRpS10 Layer2 RpS13 Layer2
-mRpS10 Layer2 RpS2 Layer2
-mRpS10 Layer2 RpS15Aa Layer2
-mRpS10 Layer2 RpL3 Layer2
-mRpS10 Layer2 RpL12 Layer2
-mRpS10 Layer2 RpL13 Layer2
-mRpS10 Layer2 RpS4 Layer2
-mRpS10 Layer2 RpS17 Layer2
-mRpS10 Layer2 RpS6 Layer2
-mRpS10 Layer2 sta Layer2
-mRpS10 Layer2 mRpS23 Layer2
-mRpS10 Layer2 mRpS18B Layer2
-mRpS10 Layer2 mRpS34 Layer2
-mRpS10 Layer2 mRpL12 Layer2
-mRpS10 Layer2 mRpL44 Layer2
-mRpS10 Layer2 bonsai Layer2
-mRpS10 Layer2 mRpS9 Layer2
-Srp54k Layer2 bonsai Layer2
-Srp54k Layer2 RpS14a Layer2
-Srp54k Layer2 RpS15Aa Layer2
-Srp54k Layer2 RpS17 Layer2
-Srp54k Layer2 RpL10Ab Layer2
-Srp54k Layer2 RpS10b Layer2
-Srp54k Layer2 RpL12 Layer2
-Srp54k Layer2 RpS4 Layer2
-Srp54k Layer2 RpS6 Layer2
-Srp54k Layer2 RpS16 Layer2
-Srp54k Layer2 RpS12 Layer2
-Srp54k Layer2 sta Layer2
-Srp54k Layer2 Srp19 Layer2
-mRpL12 Layer2 RpS14a Layer2
-mRpL12 Layer2 RpL10Ab Layer2
-mRpL12 Layer2 RpS16 Layer2
-mRpL12 Layer2 sta Layer2
-mRpL12 Layer2 mRpS34 Layer2
-mRpL12 Layer2 RpS15Aa Layer2
-mRpL12 Layer2 RpL12 Layer2
-mRpL12 Layer2 bonsai Layer2
-Tctp Layer2 RpS10b Layer2
-Tctp Layer2 eEF1delta Layer2
-Tctp Layer2 RpL10Ab Layer2
-Tctp Layer2 RpS14a Layer2
-Tctp Layer2 RpS15Aa Layer2
-Tctp Layer2 RpS4 Layer2
-Tctp Layer2 RpS17 Layer2
-Tctp Layer2 sta Layer2
-Tctp Layer2 RpS6 Layer2
-Tctp Layer2 RpS16 Layer2
-Tctp Layer2 RpL12 Layer2
-Tctp Layer2 RpS13 Layer2
-Tctp Layer2 RpS12 Layer2
-Tctp Layer2 RpS2 Layer2
-Tctp Layer2 RpL13 Layer2
-eIF-2gamma Layer2 Tcp-1zeta Layer2
-eIF-2gamma Layer2 Tcp-1eta Layer2
-eIF-2gamma Layer2 CG10576 Layer2
-eIF-2gamma Layer2 Cctgamma Layer2
-eIF-2gamma Layer2 CG7033 Layer2
-eIF-2gamma Layer2 pAbp Layer2
-eIF-2gamma Layer2 Cct5 Layer2
-eIF-2gamma Layer2 RpS14a Layer2
-eIF-2gamma Layer2 CG8258 Layer2
-eIF-2gamma Layer2 CG33123 Layer2
-eIF-2gamma Layer2 CG1354 Layer2
-eIF-2gamma Layer2 RpS15Ab Layer2
-eIF-2gamma Layer2 RpS15Aa Layer2
-eIF-2gamma Layer2 T-cp1 Layer2
-eIF-2gamma Layer2 RpS17 Layer2
-eIF-2gamma Layer2 RpL10Ab Layer2
-eIF-2gamma Layer2 RpLP2 Layer2
-eIF-2gamma Layer2 RpL18A Layer2
-eIF-2gamma Layer2 Rbp2 Layer2
-eIF-2gamma Layer2 CG5642 Layer2
-eIF-2gamma Layer2 RpL30 Layer2
-eIF-2gamma Layer2 eIF-3p66 Layer2
-eIF-2gamma Layer2 eIF4G Layer2
-eIF-2gamma Layer2 RpS3A Layer2
-eIF-2gamma Layer2 CG9769 Layer2
-eIF-2gamma Layer2 RpL12 Layer2
-eIF-2gamma Layer2 RpL11 Layer2
-eIF-2gamma Layer2 RpS8 Layer2
-eIF-2gamma Layer2 RpS10a Layer2
-eIF-2gamma Layer2 RpS10b Layer2
-eIF-2gamma Layer2 RpL13 Layer2
-eIF-2gamma Layer2 RpS27 Layer2
-eIF-2gamma Layer2 RpS23 Layer2
-eIF-2gamma Layer2 RpS18 Layer2
-eIF-2gamma Layer2 RpS11 Layer2
-eIF-2gamma Layer2 RpS7 Layer2
-eIF-2gamma Layer2 RpL3 Layer2
-eIF-2gamma Layer2 RpS12 Layer2
-eIF-2gamma Layer2 RpL4 Layer2
-eIF-2gamma Layer2 RpS4 Layer2
-eIF-2gamma Layer2 RpS13 Layer2
-eIF-2gamma Layer2 Trip1 Layer2
-eIF-2gamma Layer2 RpS2 Layer2
-eIF-2gamma Layer2 eIF3-S10 Layer2
-eIF-2gamma Layer2 RpS16 Layer2
-eIF-2gamma Layer2 RpS6 Layer2
-eIF-2gamma Layer2 Tango7 Layer2
-eIF-2gamma Layer2 sta Layer2
-eIF-2gamma Layer2 RpS3 Layer2
-eIF-2gamma Layer2 eIF3-S8 Layer2
-eIF-2gamma Layer2 eIF3-S9 Layer2
-Uch-L5 Layer2 Usp5 Layer2
-Uch-L5 Layer2 Rpt4R Layer2
-Uch-L5 Layer2 Rpn12 Layer2
-Rpt4R Layer2 Rpn12 Layer2
-Prosalpha1 Layer2 Rpt4R Layer2
-Prosalpha1 Layer2 Rpn2 Layer2
-Prosalpha1 Layer2 Rpn12 Layer2
-Prosalpha1 Layer2 Rpn6 Layer2
-Prosalpha1 Layer2 Rpt6R Layer2
-Prosalpha1 Layer2 Rpn5 Layer2
-Prosalpha1 Layer2 Rpt6 Layer2
-Prosalpha1 Layer2 Rpt5 Layer2
-Prosalpha1 Layer2 Prosalpha6 Layer2
-Prosalpha1 Layer2 Rpt2 Layer2
-Prosalpha1 Layer2 Prosbeta7 Layer2
-mRpS23 Layer2 CG4882 Layer2
-mRpS23 Layer2 mRpL44 Layer2
-mRpS23 Layer2 mRpL12 Layer2
-mRpS23 Layer2 bonsai Layer2
-mRpS23 Layer2 mRpS34 Layer2
-mRpS18B Layer2 CG4882 Layer2
-mRpS18B Layer2 mRpL12 Layer2
-mRpS18B Layer2 bonsai Layer2
-mRpS18B Layer2 mRpS34 Layer2
-mRpS18B Layer2 mRpL44 Layer2
-mRpS18B Layer2 mRpS23 Layer2
-mor Layer2 pont Layer2
-Bap55 Layer2 Uch-L5 Layer2
-Bap55 Layer2 pont Layer2
-Bap55 Layer2 mor Layer2
-ade5 Layer2 CG3011 Layer2
-Cg25C Layer2 LanA Layer2
-vkg Layer2 Glt Layer2
-vkg Layer2 LanA Layer2
-vkg Layer2 Ndg Layer2
-vkg Layer2 trol Layer2
-vkg Layer2 Cg25C Layer2
-Ufd1-like Layer2 Ubqn Layer2
-Ufd1-like Layer2 Usp5 Layer2
-TER94 Layer2 poe Layer2
-TER94 Layer2 bel Layer2
-TER94 Layer2 Cnx99A Layer2
-TER94 Layer2 ERp60 Layer2
-TER94 Layer2 Pdi Layer2
-TER94 Layer2 smt3 Layer2
-TER94 Layer2 Hop Layer2
-TER94 Layer2 Hsc70-3 Layer2
-TER94 Layer2 CalpB Layer2
-TER94 Layer2 Pfk Layer2
-TER94 Layer2 CG6891 Layer2
-TER94 Layer2 Ect3 Layer2
-TER94 Layer2 PyK Layer2
-TER94 Layer2 Rpn2 Layer2
-TER94 Layer2 Cat Layer2
-TER94 Layer2 Cp1 Layer2
-TER94 Layer2 me31B Layer2
-TER94 Layer2 CG8258 Layer2
-TER94 Layer2 cathD Layer2
-TER94 Layer2 Ubqn Layer2
-TER94 Layer2 Rpn5 Layer2
-TER94 Layer2 Rpn6 Layer2
-TER94 Layer2 Rpt1 Layer2
-TER94 Layer2 Rpt5 Layer2
-TER94 Layer2 CG8209 Layer2
-TER94 Layer2 Rpt2 Layer2
-TER94 Layer2 p47 Layer2
-TER94 Layer2 Ufd1-like Layer2
-ade3 Layer2 RpS15Aa Layer2
-ade3 Layer2 RpL12 Layer2
-ade3 Layer2 RpS14a Layer2
-ade3 Layer2 RpL10Ab Layer2
-ade3 Layer2 su(r) Layer2
-ade3 Layer2 RpS6 Layer2
-ade3 Layer2 RpS4 Layer2
-ade3 Layer2 ade5 Layer2
-ade3 Layer2 CG3011 Layer2
-mRpS9 Layer2 CG4882 Layer2
-mRpS9 Layer2 ade3 Layer2
-mRpS9 Layer2 RpS14a Layer2
-mRpS9 Layer2 RpL10Ab Layer2
-mRpS9 Layer2 RpS16 Layer2
-mRpS9 Layer2 sta Layer2
-mRpS9 Layer2 RpS13 Layer2
-mRpS9 Layer2 RpS15Aa Layer2
-mRpS9 Layer2 RpL12 Layer2
-mRpS9 Layer2 RpS2 Layer2
-mRpS9 Layer2 mRpS34 Layer2
-mRpS9 Layer2 mRpL44 Layer2
-mRpS9 Layer2 mRpS18B Layer2
-mRpS9 Layer2 mRpS23 Layer2
-mRpS9 Layer2 bonsai Layer2
-mRpS9 Layer2 mRpL12 Layer2
-mRpS5 Layer2 CG11876 Layer2
-mRpS5 Layer2 RpS16 Layer2
-mRpS5 Layer2 RpS14a Layer2
-mRpS5 Layer2 RpL10Ab Layer2
-mRpS5 Layer2 RpLP2 Layer2
-mRpS5 Layer2 CG4882 Layer2
-mRpS5 Layer2 Srp54k Layer2
-mRpS5 Layer2 RpS10b Layer2
-mRpS5 Layer2 RpL4 Layer2
-mRpS5 Layer2 RpS10a Layer2
-mRpS5 Layer2 RpS8 Layer2
-mRpS5 Layer2 RpS13 Layer2
-mRpS5 Layer2 sta Layer2
-mRpS5 Layer2 RpL3 Layer2
-mRpS5 Layer2 RpL12 Layer2
-mRpS5 Layer2 RpS15Aa Layer2
-mRpS5 Layer2 RpS23 Layer2
-mRpS5 Layer2 RpS15Ab Layer2
-mRpS5 Layer2 RpS27 Layer2
-mRpS5 Layer2 RpS7 Layer2
-mRpS5 Layer2 EfTuM Layer2
-mRpS5 Layer2 RpL18A Layer2
-mRpS5 Layer2 RpL13 Layer2
-mRpS5 Layer2 RpS4 Layer2
-mRpS5 Layer2 RpS17 Layer2
-mRpS5 Layer2 RpS6 Layer2
-mRpS5 Layer2 RpS18 Layer2
-mRpS5 Layer2 RpS11 Layer2
-mRpS5 Layer2 RpL11 Layer2
-mRpS5 Layer2 RpS3 Layer2
-mRpS5 Layer2 mRpS34 Layer2
-mRpS5 Layer2 mRpS18B Layer2
-mRpS5 Layer2 mRpS23 Layer2
-mRpS5 Layer2 mRpS22 Layer2
-mRpS5 Layer2 CG4679 Layer2
-mRpS5 Layer2 mRpL44 Layer2
-mRpS5 Layer2 mRpL12 Layer2
-mRpS5 Layer2 mRpS10 Layer2
-mRpS5 Layer2 bonsai Layer2
-mRpS5 Layer2 mRpS9 Layer2
-Not1 Layer2 Iswi Layer2
-Not1 Layer2 eIF4G Layer2
-Not1 Layer2 pAbp Layer2
-Not1 Layer2 Prp8 Layer2
-Not1 Layer2 me31B Layer2
-Not3 Layer2 frj Layer2
-Not3 Layer2 me31B Layer2
-Not3 Layer2 Not1 Layer2
-Hsc70-3 Layer2 CG2852 Layer2
-Hsc70-3 Layer2 Hsp60 Layer2
-Hsc70-3 Layer2 Ost48 Layer2
-Hsc70-3 Layer2 Rae1 Layer2
-Hsc70-3 Layer2 CG2918 Layer2
-PyK Layer2 bel Layer2
-PyK Layer2 poe Layer2
-PyK Layer2 me31B Layer2
-PyK Layer2 Men Layer2
-PyK Layer2 Aldh Layer2
-PyK Layer2 Ect3 Layer2
-PyK Layer2 CG6891 Layer2
-PyK Layer2 CalpB Layer2
-PyK Layer2 ND-51 Layer2
-PyK Layer2 Rpn5 Layer2
-PyK Layer2 Vha68-2 Layer2
-PyK Layer2 Cat Layer2
-PyK Layer2 Mdh2 Layer2
-PyK Layer2 Eno Layer2
-LanA Layer2 LanB2 Layer2
-alphaCOP Layer2 deltaCOP Layer2
-Srp19 Layer2 RpS14a Layer2
-Srp19 Layer2 RpS15Aa Layer2
-Srp19 Layer2 RpS17 Layer2
-Srp19 Layer2 RpL10Ab Layer2
-Srp19 Layer2 RpS10b Layer2
-Srp19 Layer2 RpS6 Layer2
-Srp19 Layer2 RpS4 Layer2
-Srp19 Layer2 sta Layer2
-Srp19 Layer2 RpS12 Layer2
-Srp19 Layer2 RpL12 Layer2
-Srp19 Layer2 RpS16 Layer2
-Srp72 Layer2 RpS12 Layer2
-Srp72 Layer2 RpS10b Layer2
-Srp72 Layer2 RpS6 Layer2
-Srp72 Layer2 RpS13 Layer2
-Srp72 Layer2 RpL10Ab Layer2
-Srp72 Layer2 RpL13 Layer2
-Srp72 Layer2 sta Layer2
-Srp72 Layer2 RpS4 Layer2
-Srp72 Layer2 RpS2 Layer2
-Srp72 Layer2 RpL3 Layer2
-Srp72 Layer2 RpS16 Layer2
-Srp72 Layer2 RpL12 Layer2
-Srp72 Layer2 Srp54k Layer2
-Srp72 Layer2 Srp19 Layer2
-Hsp83 Layer2 Tcp-1zeta Layer2
-Hsp83 Layer2 Rack1 Layer2
-Hsp83 Layer2 zip Layer2
-Hsp83 Layer2 ERp60 Layer2
-Hsp83 Layer2 pont Layer2
-Hsp83 Layer2 Pdi Layer2
-Hsp83 Layer2 Tpi Layer2
-Hsp83 Layer2 HDAC6 Layer2
-Hsp83 Layer2 bai Layer2
-Hsp83 Layer2 Cctgamma Layer2
-Hsp83 Layer2 Cnx99A Layer2
-Hsp83 Layer2 CG2918 Layer2
-Hsp83 Layer2 Cct5 Layer2
-Hsp83 Layer2 CG7033 Layer2
-Hsp83 Layer2 Sgt Layer2
-Hsp83 Layer2 Hsp27 Layer2
-Hsp83 Layer2 T-cp1 Layer2
-Hsp83 Layer2 CalpB Layer2
-Hsp83 Layer2 Ect3 Layer2
-Hsp83 Layer2 CG6891 Layer2
-Hsp83 Layer2 Dlic Layer2
-Hsp83 Layer2 cpa Layer2
-Hsp83 Layer2 Cp1 Layer2
-Hsp83 Layer2 Dhc64C Layer2
-Hsp83 Layer2 Hsp60C Layer2
-Hsp83 Layer2 cpb Layer2
-Hsp83 Layer2 cathD Layer2
-Hsp83 Layer2 Pfk Layer2
-Hsp83 Layer2 Gdi Layer2
-Hsp83 Layer2 Rac1 Layer2
-Hsp83 Layer2 PyK Layer2
-Hsp83 Layer2 Rpn6 Layer2
-Hsp83 Layer2 Hsp60 Layer2
-Hsp83 Layer2 Rpn5 Layer2
-Hsp83 Layer2 Rpt1 Layer2
-Hsp83 Layer2 Rpn3 Layer2
-Hsp83 Layer2 Rpt5 Layer2
-Hsp83 Layer2 Hsp70Ab Layer2
-Hsp83 Layer2 Cat Layer2
-Hsp83 Layer2 CG8258 Layer2
-Hsp83 Layer2 Hsc70-5 Layer2
-Hsp83 Layer2 FKBP59 Layer2
-Hsp83 Layer2 TER94 Layer2
-Hsp83 Layer2 DnaJ-H Layer2
-Hsp83 Layer2 Hsc70-3 Layer2
-Hsp83 Layer2 CG1416 Layer2
-Hsp83 Layer2 Hop Layer2
-CG11089 Layer2 ade5 Layer2
-CG11089 Layer2 ade3 Layer2
-CG11089 Layer2 CG3011 Layer2
-ERp60 Layer2 Pdi Layer2
-ERp60 Layer2 Ost48 Layer2
-ERp60 Layer2 CG2852 Layer2
-ERp60 Layer2 CG2918 Layer2
-ERp60 Layer2 CaBP1 Layer2
-ERp60 Layer2 Hsc70-3 Layer2
-ERp60 Layer2 Gp93 Layer2
-zetaCOP Layer2 Klp10A Layer2
-zetaCOP Layer2 Dlic Layer2
-zetaCOP Layer2 p115 Layer2
-zetaCOP Layer2 cpa Layer2
-zetaCOP Layer2 alphaCOP Layer2
-zetaCOP Layer2 betaCOP Layer2
-zetaCOP Layer2 deltaCOP Layer2
-COX6B Layer2 ND-B16.6 Layer2
-CG7048 Layer2 cpa Layer2
-CG7048 Layer2 Srp19 Layer2
-CG7048 Layer2 Nlp Layer2
-CG7048 Layer2 mRpS23 Layer2
-CG7048 Layer2 CG8498 Layer2
-CG7048 Layer2 robl Layer2
-CG7048 Layer2 Tim9a Layer2
-CG7048 Layer2 eEF1delta Layer2
-CG7770 Layer2 RpS18 Layer2
-CG7770 Layer2 pont Layer2
-CG7770 Layer2 CG7048 Layer2
-arm Layer2 Hsc70-3 Layer2
-arm Layer2 Prosalpha6 Layer2
-arm Layer2 Uch-L5 Layer2
-arm Layer2 Rpn5 Layer2
-arm Layer2 Prosbeta7 Layer2
-arm Layer2 Rpn12 Layer2
-arm Layer2 Rpt6R Layer2
-arm Layer2 Rpt6 Layer2
-arm Layer2 syd Layer2
-arm Layer2 Rpn2 Layer2
-arm Layer2 Rpn6 Layer2
-arm Layer2 Rpt1 Layer2
-arm Layer2 Rpt2 Layer2
-arm Layer2 Rpt5 Layer2
-arm Layer2 emb Layer2
-arm Layer2 Rpt4R Layer2
-arm Layer2 pont Layer2
-arm Layer2 alpha-Cat Layer2
-eIF-3p66 Layer2 Tcp-1eta Layer2
-eIF-3p66 Layer2 Cct5 Layer2
-eIF-3p66 Layer2 Cctgamma Layer2
-eIF-3p66 Layer2 RpS12 Layer2
-eIF-3p66 Layer2 RpS16 Layer2
-eIF-3p66 Layer2 RpS11 Layer2
-eIF-3p66 Layer2 RpS27 Layer2
-eIF-3p66 Layer2 RpS23 Layer2
-eIF-3p66 Layer2 RpS10a Layer2
-eIF-3p66 Layer2 RpS10b Layer2
-eIF-3p66 Layer2 RpS13 Layer2
-eIF-3p66 Layer2 RpL10Ab Layer2
-eIF-3p66 Layer2 RpL13 Layer2
-eIF-3p66 Layer2 RpS7 Layer2
-eIF-3p66 Layer2 RpL12 Layer2
-eIF-3p66 Layer2 sta Layer2
-eIF-3p66 Layer2 RpS4 Layer2
-eIF-3p66 Layer2 RpL11 Layer2
-eIF-3p66 Layer2 RpS18 Layer2
-eIF-3p66 Layer2 RpS2 Layer2
-eIF-3p66 Layer2 RpL3 Layer2
-eIF-3p66 Layer2 RpL18A Layer2
-eIF-3p66 Layer2 RpS3 Layer2
-eIF-3p66 Layer2 RpL4 Layer2
-eIF-3p66 Layer2 RpS6 Layer2
-eIF-3p66 Layer2 CG9769 Layer2
-eIF-3p66 Layer2 eIF3-S10 Layer2
-eIF-3p66 Layer2 Tango7 Layer2
-eIF-3p66 Layer2 eIF3-S8 Layer2
-eIF-3p66 Layer2 eIF3-S9 Layer2
-eIF-3p66 Layer2 Trip1 Layer2
-eIF-3p66 Layer2 CG5642 Layer2
-CG11752 Layer2 ND-B16.6 Layer2
-COX4 Layer2 CG11752 Layer2
-COX4 Layer2 porin Layer2
-COX4 Layer2 ND-B16.6 Layer2
-COX4 Layer2 ND-PDSW Layer2
-COX4 Layer2 Cyt-c-p Layer2
-COX4 Layer2 COX6B Layer2
-COX5A Layer2 sun Layer2
-COX5A Layer2 ND-51 Layer2
-COX5A Layer2 CG7603 Layer2
-COX5A Layer2 CG11752 Layer2
-COX5A Layer2 ND-B16.6 Layer2
-COX5A Layer2 Cyt-c-p Layer2
-COX5A Layer2 ND-PDSW Layer2
-COX5A Layer2 COX6B Layer2
-COX5A Layer2 COX4 Layer2
-Eno Layer2 Hsc70-3 Layer2
-Eno Layer2 sta Layer2
-Tpi Layer2 Cat Layer2
-Tpi Layer2 CG11899 Layer2
-Tpi Layer2 RpS13 Layer2
-Tpi Layer2 CG11876 Layer2
-Tpi Layer2 ade3 Layer2
-Tpi Layer2 CG3011 Layer2
-Tpi Layer2 Hsc70-3 Layer2
-Tpi Layer2 Mdh2 Layer2
-Tpi Layer2 PyK Layer2
-Tpi Layer2 Eno Layer2
-Cctgamma Layer2 Trip1 Layer2
-Cctgamma Layer2 CG10576 Layer2
-Cctgamma Layer2 CG3800 Layer2
-Cctgamma Layer2 Hop Layer2
-Cctgamma Layer2 Tcp-1zeta Layer2
-Cctgamma Layer2 Tcp-1eta Layer2
-Cctgamma Layer2 CG7033 Layer2
-Rpt1 Layer2 Uch Layer2
-Rpt1 Layer2 RpS15Aa Layer2
-Rpt1 Layer2 RpS15Ab Layer2
-Rpt1 Layer2 RpS6 Layer2
-Rpt1 Layer2 Ubqn Layer2
-Rpt1 Layer2 RpL3 Layer2
-Rpt1 Layer2 RpS11 Layer2
-Rpt1 Layer2 RpS18 Layer2
-Rpt1 Layer2 Ufd1-like Layer2
-Rpt1 Layer2 Cct5 Layer2
-Rpt1 Layer2 CalpB Layer2
-Rpt1 Layer2 Ect3 Layer2
-Rpt1 Layer2 CG6891 Layer2
-Rpt1 Layer2 PyK Layer2
-Rpt1 Layer2 Cp1 Layer2
-Rpt1 Layer2 Cat Layer2
-Rpt1 Layer2 Pfk Layer2
-Rpt1 Layer2 CG8258 Layer2
-Rpt1 Layer2 cathD Layer2
-Rpt1 Layer2 Prosalpha1 Layer2
-Rpt1 Layer2 Uch-L5 Layer2
-Rpt1 Layer2 Rpt6R Layer2
-Rpt1 Layer2 Rpt4R Layer2
-Rpt1 Layer2 Prosalpha6 Layer2
-Rpt1 Layer2 Rpn12 Layer2
-Rpt1 Layer2 Rpt5 Layer2
-Rpt1 Layer2 Rpn5 Layer2
-Rpt1 Layer2 Prosbeta7 Layer2
-Rpt1 Layer2 Rpn2 Layer2
-Rpt1 Layer2 Rpt6 Layer2
-Rpt1 Layer2 Rpt2 Layer2
-Rpt1 Layer2 Rpn6 Layer2
-Dlic Layer2 p115 Layer2
-Dlic Layer2 deltaCOP Layer2
-Dlic Layer2 cpa Layer2
-Dhc64C Layer2 CLIP-190 Layer2
-Dhc64C Layer2 betaTub56D Layer2
-Dhc64C Layer2 Klc Layer2
-Dhc64C Layer2 poe Layer2
-Dhc64C Layer2 bai Layer2
-Dhc64C Layer2 FKBP59 Layer2
-Dhc64C Layer2 betaCOP Layer2
-Dhc64C Layer2 deltaCOP Layer2
-Dhc64C Layer2 epsilonCOP Layer2
-Dhc64C Layer2 beta'COP Layer2
-Dhc64C Layer2 CG10186 Layer2
-Dhc64C Layer2 zetaCOP Layer2
-Dhc64C Layer2 p115 Layer2
-Dhc64C Layer2 cpa Layer2
-Dhc64C Layer2 cpb Layer2
-Dhc64C Layer2 robl Layer2
-Dhc64C Layer2 Khc Layer2
-Dhc64C Layer2 BicD Layer2
-Dhc64C Layer2 Dlic Layer2
-Ef1beta Layer2 FeCH Layer2
-Ef1beta Layer2 CG7048 Layer2
-Ef1beta Layer2 Aats-asp Layer2
-Ef1beta Layer2 RpS15Aa Layer2
-Ef1beta Layer2 RpS10a Layer2
-Ef1beta Layer2 RpS15Ab Layer2
-Ef1beta Layer2 CG5642 Layer2
-Ef1beta Layer2 RpL3 Layer2
-Ef1beta Layer2 RpS3A Layer2
-Ef1beta Layer2 RpS12 Layer2
-Ef1beta Layer2 RpS13 Layer2
-Ef1beta Layer2 RpS10b Layer2
-Ef1beta Layer2 RpS16 Layer2
-Ef1beta Layer2 RpS2 Layer2
-Ef1beta Layer2 RpS6 Layer2
-Ef1beta Layer2 RpL12 Layer2
-Ef1beta Layer2 RpL30 Layer2
-Ef1beta Layer2 RpS23 Layer2
-Ef1beta Layer2 RpS11 Layer2
-Ef1beta Layer2 eEF1delta Layer2
-Ef1beta Layer2 RpL13 Layer2
-Ef1beta Layer2 sta Layer2
-Ef1beta Layer2 RpS7 Layer2
-Ef1beta Layer2 Tctp Layer2
-Ef1beta Layer2 Trip1 Layer2
-Ef1beta Layer2 RpS8 Layer2
-Ef1beta Layer2 RpS18 Layer2
-Ef1beta Layer2 Rack1 Layer2
-Ef1beta Layer2 RpS17 Layer2
-Ef1beta Layer2 RpL10Ab Layer2
-Ef1beta Layer2 RpL18A Layer2
-Ef1beta Layer2 RpS3 Layer2
-Ef1beta Layer2 RpS4 Layer2
-Ef1beta Layer2 RpL11 Layer2
-Ef1beta Layer2 RpLP2 Layer2
-Ef1beta Layer2 RpL4 Layer2
-Rpn13 Layer2 TER94 Layer2
-Rpn13 Layer2 Ufd1-like Layer2
-Rpn13 Layer2 Ubqn Layer2
-Rpn13 Layer2 Prosalpha6 Layer2
-Rpn13 Layer2 Prosbeta7 Layer2
-Rpn13 Layer2 Rpt4R Layer2
-Rpn13 Layer2 Prosalpha7 Layer2
-Rpn13 Layer2 Rpn12 Layer2
-Rpn13 Layer2 Rpt6R Layer2
-Rpn13 Layer2 Rpn5 Layer2
-Rpn13 Layer2 Rpt6 Layer2
-Rpn13 Layer2 Rpn6 Layer2
-Rpn13 Layer2 Uch-L5 Layer2
-Rpn13 Layer2 Rpt2 Layer2
-Rpn13 Layer2 Rpt5 Layer2
-Rpn13 Layer2 Rpt1 Layer2
-Rpn13 Layer2 Rpn2 Layer2
-Tango7 Layer2 CG7033 Layer2
-Tango7 Layer2 Tcp-1zeta Layer2
-Tango7 Layer2 mRpS10 Layer2
-Tango7 Layer2 Rpn5 Layer2
-Tango7 Layer2 bonsai Layer2
-Tango7 Layer2 Rpn2 Layer2
-Tango7 Layer2 Rpn6 Layer2
-Tango7 Layer2 RpS14a Layer2
-Tango7 Layer2 RpS15Aa Layer2
-Tango7 Layer2 RpS17 Layer2
-Tango7 Layer2 RpL10Ab Layer2
-Tango7 Layer2 RpL12 Layer2
-Tango7 Layer2 RpL13 Layer2
-Tango7 Layer2 RpL18A Layer2
-Tango7 Layer2 RpL3 Layer2
-Tango7 Layer2 RpS10a Layer2
-Tango7 Layer2 RpL11 Layer2
-Tango7 Layer2 RpS10b Layer2
-Tango7 Layer2 RpS2 Layer2
-Tango7 Layer2 RpS27 Layer2
-Tango7 Layer2 RpS23 Layer2
-Tango7 Layer2 RpS18 Layer2
-Tango7 Layer2 RpS12 Layer2
-Tango7 Layer2 RpS13 Layer2
-Tango7 Layer2 RpS4 Layer2
-Tango7 Layer2 RpS6 Layer2
-Tango7 Layer2 RpS16 Layer2
-Tango7 Layer2 sta Layer2
-Tango7 Layer2 eIF3-S10 Layer2
-Tango7 Layer2 RpS3 Layer2
-Tango7 Layer2 Trip1 Layer2
-Tango7 Layer2 RpL4 Layer2
-Tango7 Layer2 CG9769 Layer2
-Tango7 Layer2 eIF3-S9 Layer2
-Tango7 Layer2 CG5642 Layer2
-Tango7 Layer2 eIF3-S8 Layer2
-Rpt2 Layer2 Ubqn Layer2
-Rpt2 Layer2 Ufd1-like Layer2
-Rpt2 Layer2 CG8209 Layer2
-Rpt2 Layer2 Uch-L5 Layer2
-Rpt2 Layer2 Rpt4R Layer2
-Rpt2 Layer2 Rpn12 Layer2
-Rpt2 Layer2 Rpt5 Layer2
-Rpt2 Layer2 Rpt6 Layer2
-Rpt2 Layer2 Prosalpha6 Layer2
-Rpt2 Layer2 Rpn5 Layer2
-Rpt2 Layer2 Prosbeta7 Layer2
-Rpn5 Layer2 Ubqn Layer2
-Rpn5 Layer2 Ufd1-like Layer2
-Rpn5 Layer2 Cat Layer2
-Rpn5 Layer2 CalpB Layer2
-Rpn5 Layer2 CG6891 Layer2
-Rpn5 Layer2 Uch-L5 Layer2
-Rpn5 Layer2 Rpt4R Layer2
-Rpn5 Layer2 Rpt6 Layer2
-Rpn5 Layer2 Rpn12 Layer2
-betaCOP Layer2 cpa Layer2
-betaCOP Layer2 Dlic Layer2
-betaCOP Layer2 Klp10A Layer2
-betaCOP Layer2 p115 Layer2
-betaCOP Layer2 alphaCOP Layer2
-betaCOP Layer2 deltaCOP Layer2
-Rpt6R Layer2 RpS4 Layer2
-Rpt6R Layer2 pont Layer2
-Rpt6R Layer2 Rpt6 Layer2
-Rpt6R Layer2 Uch-L5 Layer2
-Rpt6R Layer2 Rpt4R Layer2
-Rpt6R Layer2 Prosalpha6 Layer2
-Rpt6R Layer2 Rpt2 Layer2
-Rpt6R Layer2 Rpt5 Layer2
-Rpt6R Layer2 Prosbeta7 Layer2
-Rpt6R Layer2 Rpn12 Layer2
-Rpt6R Layer2 Rpn2 Layer2
-Rpt6R Layer2 Rpn5 Layer2
-Rpn2 Layer2 Trip1 Layer2
-Rpn2 Layer2 RpS2 Layer2
-Rpn2 Layer2 Usp5 Layer2
-Rpn2 Layer2 Uch-L5 Layer2
-Rpn2 Layer2 Prosalpha6 Layer2
-Rpn2 Layer2 Rpt4R Layer2
-Rpn2 Layer2 Prosbeta7 Layer2
-Rpn2 Layer2 Rpn12 Layer2
-Rpn2 Layer2 Rpn5 Layer2
-Rpn2 Layer2 Rpt6 Layer2
-Rpn2 Layer2 Rpt2 Layer2
-Rpn2 Layer2 Rpt5 Layer2
-RpL10Ab Layer2 bonsai Layer2
-RpL10Ab Layer2 mago Layer2
-RpL10Ab Layer2 CG5642 Layer2
-RpL10Ab Layer2 RpS15Aa Layer2
-RpL10Ab Layer2 RpS14a Layer2
-RpL10Ab Layer2 RpL12 Layer2
-RpL10Ab Layer2 RpS16 Layer2
-RpL10Ab Layer2 RpS12 Layer2
-RpL10Ab Layer2 RpS10b Layer2
-RpL10Ab Layer2 RpS4 Layer2
-RpL10Ab Layer2 sta Layer2
-RpL10Ab Layer2 RpS6 Layer2
-Trip1 Layer2 RpS14a Layer2
-Trip1 Layer2 Tcp-1zeta Layer2
-Trip1 Layer2 CG10576 Layer2
-Trip1 Layer2 RpS17 Layer2
-Trip1 Layer2 CG7033 Layer2
-Trip1 Layer2 RpL10Ab Layer2
-Trip1 Layer2 RpS10b Layer2
-Trip1 Layer2 RpL12 Layer2
-Trip1 Layer2 RpS12 Layer2
-Trip1 Layer2 RpS4 Layer2
-Trip1 Layer2 RpS6 Layer2
-Trip1 Layer2 RpS16 Layer2
-Trip1 Layer2 sta Layer2
-Trip1 Layer2 CG9769 Layer2
-Trip1 Layer2 eIF3-S10 Layer2
-Trip1 Layer2 CG5642 Layer2
-cpa Layer2 p115 Layer2
-cpa Layer2 deltaCOP Layer2
-cpb Layer2 CG6891 Layer2
-cpb Layer2 flr Layer2
-cpb Layer2 p115 Layer2
-cpb Layer2 deltaCOP Layer2
-cpb Layer2 tsr Layer2
-cpb Layer2 betaCOP Layer2
-cpb Layer2 zetaCOP Layer2
-cpb Layer2 Dlic Layer2
-cpb Layer2 cpa Layer2
-eIF3-S10 Layer2 CG33123 Layer2
-eIF3-S10 Layer2 Aats-ile Layer2
-eIF3-S10 Layer2 RpS14a Layer2
-eIF3-S10 Layer2 RpS17 Layer2
-eIF3-S10 Layer2 RpS15Aa Layer2
-eIF3-S10 Layer2 RpL10Ab Layer2
-eIF3-S10 Layer2 RpL12 Layer2
-eIF3-S10 Layer2 RpS12 Layer2
-eIF3-S10 Layer2 RpS10b Layer2
-eIF3-S10 Layer2 RpS6 Layer2
-eIF3-S10 Layer2 RpS16 Layer2
-eIF3-S10 Layer2 RpS4 Layer2
-eIF3-S10 Layer2 sta Layer2
-eIF3-S10 Layer2 CG5642 Layer2
-eIF3-S10 Layer2 CG9769 Layer2
-eIF3-S9 Layer2 Cctgamma Layer2
-eIF3-S9 Layer2 rin Layer2
-eIF3-S9 Layer2 RpS14a Layer2
-eIF3-S9 Layer2 RpS15Aa Layer2
-eIF3-S9 Layer2 RpS17 Layer2
-eIF3-S9 Layer2 Tcp-1eta Layer2
-eIF3-S9 Layer2 CG7033 Layer2
-eIF3-S9 Layer2 T-cp1 Layer2
-eIF3-S9 Layer2 CG10576 Layer2
-eIF3-S9 Layer2 pAbp Layer2
-eIF3-S9 Layer2 RpL10Ab Layer2
-eIF3-S9 Layer2 RpL12 Layer2
-eIF3-S9 Layer2 RpS12 Layer2
-eIF3-S9 Layer2 RpS10a Layer2
-eIF3-S9 Layer2 RpS27 Layer2
-eIF3-S9 Layer2 RpS10b Layer2
-eIF3-S9 Layer2 RpS18 Layer2
-eIF3-S9 Layer2 RpL11 Layer2
-eIF3-S9 Layer2 RpL13 Layer2
-eIF3-S9 Layer2 RpL3 Layer2
-eIF3-S9 Layer2 RpL4 Layer2
-eIF3-S9 Layer2 RpS16 Layer2
-eIF3-S9 Layer2 sta Layer2
-eIF3-S9 Layer2 RpS2 Layer2
-eIF3-S9 Layer2 RpS13 Layer2
-eIF3-S9 Layer2 RpS4 Layer2
-eIF3-S9 Layer2 RpS6 Layer2
-eIF3-S9 Layer2 RpS3 Layer2
-eIF3-S9 Layer2 CG5642 Layer2
-eIF3-S9 Layer2 eIF3-S8 Layer2
-eIF3-S9 Layer2 CG9769 Layer2
-eIF3-S9 Layer2 Trip1 Layer2
-eIF3-S9 Layer2 eIF3-S10 Layer2
-Pdi Layer2 Ugt Layer2
-Pdi Layer2 Ost48 Layer2
-Pdi Layer2 CG2852 Layer2
-Pdi Layer2 CG2918 Layer2
-Pdi Layer2 Hsc70-3 Layer2
-epsilonCOP Layer2 Dlic Layer2
-epsilonCOP Layer2 Klc Layer2
-epsilonCOP Layer2 Klp10A Layer2
-epsilonCOP Layer2 cpb Layer2
-epsilonCOP Layer2 cpa Layer2
-epsilonCOP Layer2 p115 Layer2
-epsilonCOP Layer2 deltaCOP Layer2
-epsilonCOP Layer2 zetaCOP Layer2
-epsilonCOP Layer2 alphaCOP Layer2
-epsilonCOP Layer2 betaCOP Layer2
-beta'COP Layer2 cpb Layer2
-beta'COP Layer2 cpa Layer2
-beta'COP Layer2 Dlic Layer2
-beta'COP Layer2 Klp10A Layer2
-beta'COP Layer2 Klc Layer2
-beta'COP Layer2 p115 Layer2
-beta'COP Layer2 zetaCOP Layer2
-beta'COP Layer2 alphaCOP Layer2
-beta'COP Layer2 deltaCOP Layer2
-beta'COP Layer2 betaCOP Layer2
-beta'COP Layer2 epsilonCOP Layer2
-CaBP1 Layer2 Ugt Layer2
-CaBP1 Layer2 Ost48 Layer2
-CaBP1 Layer2 CG2918 Layer2
-CaBP1 Layer2 Hsc70-3 Layer2
-CaBP1 Layer2 Pdi Layer2
-Gp93 Layer2 Cctgamma Layer2
-Gp93 Layer2 bai Layer2
-Gp93 Layer2 Hsp27 Layer2
-Gp93 Layer2 Hop Layer2
-Gp93 Layer2 Hsp70Ab Layer2
-Gp93 Layer2 CG11577 Layer2
-Gp93 Layer2 Pdi Layer2
-Gp93 Layer2 Hsc70-3 Layer2
-Gp93 Layer2 CG2918 Layer2
-Gp93 Layer2 CaBP1 Layer2
-Tcp-1eta Layer2 RpL12 Layer2
-Tcp-1eta Layer2 RpS6 Layer2
-Tcp-1eta Layer2 RpL13 Layer2
-Tcp-1eta Layer2 RpL10Ab Layer2
-Tcp-1eta Layer2 sta Layer2
-Tcp-1eta Layer2 RpS2 Layer2
-Tcp-1eta Layer2 RpS12 Layer2
-Tcp-1eta Layer2 RpS13 Layer2
-Tcp-1eta Layer2 CG10576 Layer2
-Tcp-1eta Layer2 RpS4 Layer2
-Tcp-1eta Layer2 RpS16 Layer2
-Tcp-1eta Layer2 Trip1 Layer2
-Tcp-1eta Layer2 Hop Layer2
-Tcp-1eta Layer2 ade3 Layer2
-Tcp-1eta Layer2 Tcp-1zeta Layer2
-Tcp-1eta Layer2 CG7033 Layer2
-RpL13 Layer2 mRpL12 Layer2
-RpL13 Layer2 CG7033 Layer2
-RpL13 Layer2 ade3 Layer2
-RpL13 Layer2 bonsai Layer2
-RpL13 Layer2 Srp19 Layer2
-RpL13 Layer2 mago Layer2
-RpL13 Layer2 CG5642 Layer2
-RpL13 Layer2 eIF3-S10 Layer2
-RpL13 Layer2 CG10576 Layer2
-RpL13 Layer2 Srp54k Layer2
-RpL13 Layer2 CG9769 Layer2
-RpL13 Layer2 Trip1 Layer2
-RpL13 Layer2 RpS15Aa Layer2
-RpL13 Layer2 RpL12 Layer2
-RpL13 Layer2 RpS12 Layer2
-RpL13 Layer2 RpS13 Layer2
-RpL13 Layer2 RpS6 Layer2
-RpL13 Layer2 RpS14a Layer2
-RpL13 Layer2 RpL10Ab Layer2
-RpL13 Layer2 RpS4 Layer2
-RpL13 Layer2 sta Layer2
-RpL13 Layer2 RpS17 Layer2
-RpL13 Layer2 RpS10b Layer2
-RpL13 Layer2 RpS16 Layer2
-Rpt4 Layer2 Prosalpha1 Layer2
-Rpt4 Layer2 arm Layer2
-Rpt4 Layer2 Rpt4R Layer2
-Rpt4 Layer2 Uch-L5 Layer2
-Rpt4 Layer2 Rpt6R Layer2
-Rpt4 Layer2 Rpn13 Layer2
-Rpt4 Layer2 Rpt1 Layer2
-Rpt4 Layer2 Rpt6 Layer2
-Rpt4 Layer2 Prosalpha6 Layer2
-Rpt4 Layer2 Prosbeta7 Layer2
-Rpt4 Layer2 Rpn6 Layer2
-Rpt4 Layer2 Rpt2 Layer2
-Rpt4 Layer2 Rpn3 Layer2
-Rpt4 Layer2 Prosalpha7 Layer2
-Rpt4 Layer2 Rpn2 Layer2
-Rpt4 Layer2 Rpn12 Layer2
-Rpt4 Layer2 Rpn5 Layer2
-Rpt4 Layer2 Rpt5 Layer2
-RpS11 Layer2 vig Layer2
-RpS11 Layer2 bic Layer2
-RpS11 Layer2 CG7033 Layer2
-RpS11 Layer2 Tctp Layer2
-RpS11 Layer2 Tcp-1eta Layer2
-RpS11 Layer2 eEF1delta Layer2
-RpS11 Layer2 mago Layer2
-RpS11 Layer2 EfTuM Layer2
-RpS11 Layer2 Srp72 Layer2
-RpS11 Layer2 bonsai Layer2
-RpS11 Layer2 mRpS10 Layer2
-RpS11 Layer2 CG5642 Layer2
-RpS11 Layer2 mRpS9 Layer2
-RpS11 Layer2 mRpL12 Layer2
-RpS11 Layer2 eIF3-S8 Layer2
-RpS11 Layer2 CG9769 Layer2
-RpS11 Layer2 eIF3-S10 Layer2
-RpS11 Layer2 Srp19 Layer2
-RpS11 Layer2 ade3 Layer2
-RpS11 Layer2 eIF3-S9 Layer2
-RpS11 Layer2 Srp54k Layer2
-RpS11 Layer2 Tango7 Layer2
-RpS11 Layer2 Trip1 Layer2
-RpS11 Layer2 RpL10Ab Layer2
-RpS11 Layer2 RpS23 Layer2
-RpS11 Layer2 RpL3 Layer2
-RpS11 Layer2 RpL12 Layer2
-RpS11 Layer2 RpL11 Layer2
-RpS11 Layer2 RpS14a Layer2
-RpS11 Layer2 RpL4 Layer2
-RpS11 Layer2 RpS18 Layer2
-RpS11 Layer2 RpS6 Layer2
-RpS11 Layer2 sta Layer2
-RpS11 Layer2 RpS10a Layer2
-RpS11 Layer2 RpS17 Layer2
-RpS11 Layer2 RpS2 Layer2
-RpS11 Layer2 RpL18A Layer2
-RpS11 Layer2 RpS12 Layer2
-RpS11 Layer2 RpS3 Layer2
-RpS11 Layer2 RpS13 Layer2
-RpS11 Layer2 RpS27 Layer2
-RpS11 Layer2 RpS10b Layer2
-RpS11 Layer2 RpL13 Layer2
-RpS11 Layer2 RpS16 Layer2
-RpS11 Layer2 RpS15Aa Layer2
-RpS11 Layer2 RpS4 Layer2
-eIF3-S8 Layer2 CG33123 Layer2
-eIF3-S8 Layer2 Tcp-1zeta Layer2
-eIF3-S8 Layer2 eEF1delta Layer2
-eIF3-S8 Layer2 pAbp Layer2
-eIF3-S8 Layer2 RpS14a Layer2
-eIF3-S8 Layer2 RpS17 Layer2
-eIF3-S8 Layer2 RpS15Aa Layer2
-eIF3-S8 Layer2 CG10576 Layer2
-eIF3-S8 Layer2 RpL12 Layer2
-eIF3-S8 Layer2 RpL10Ab Layer2
-eIF3-S8 Layer2 RpL11 Layer2
-eIF3-S8 Layer2 RpL13 Layer2
-eIF3-S8 Layer2 RpL3 Layer2
-eIF3-S8 Layer2 RpS12 Layer2
-eIF3-S8 Layer2 sta Layer2
-eIF3-S8 Layer2 RpS2 Layer2
-eIF3-S8 Layer2 RpS13 Layer2
-eIF3-S8 Layer2 RpS4 Layer2
-eIF3-S8 Layer2 RpS10b Layer2
-eIF3-S8 Layer2 RpS27 Layer2
-eIF3-S8 Layer2 RpS10a Layer2
-eIF3-S8 Layer2 RpS16 Layer2
-eIF3-S8 Layer2 RpL4 Layer2
-eIF3-S8 Layer2 RpS18 Layer2
-eIF3-S8 Layer2 RpS6 Layer2
-eIF3-S8 Layer2 RpS3 Layer2
-eIF3-S8 Layer2 eIF3-S10 Layer2
-eIF3-S8 Layer2 Trip1 Layer2
-eIF3-S8 Layer2 CG9769 Layer2
-eIF3-S8 Layer2 CG5642 Layer2
-Hop Layer2 Hsp60 Layer2
-Hop Layer2 Hsc70-3 Layer2
-Hop Layer2 Ubqn Layer2
-CG1416 Layer2 CG7033 Layer2
-CG1416 Layer2 Hsp60 Layer2
-CG1416 Layer2 Gp93 Layer2
-CG1416 Layer2 Cctgamma Layer2
-CG1416 Layer2 Hop Layer2
-RpS6 Layer2 RpS14a Layer2
-RpS6 Layer2 sta Layer2
-RpS2 Layer2 CG10576 Layer2
-RpS2 Layer2 ade3 Layer2
-RpS2 Layer2 bonsai Layer2
-RpS2 Layer2 CG7033 Layer2
-RpS2 Layer2 mago Layer2
-RpS2 Layer2 mRpL12 Layer2
-RpS2 Layer2 CG5642 Layer2
-RpS2 Layer2 eIF3-S10 Layer2
-RpS2 Layer2 Srp19 Layer2
-RpS2 Layer2 CG9769 Layer2
-RpS2 Layer2 Srp54k Layer2
-RpS2 Layer2 Trip1 Layer2
-RpS2 Layer2 RpS6 Layer2
-RpS2 Layer2 RpS15Aa Layer2
-RpS2 Layer2 RpL12 Layer2
-RpS2 Layer2 RpS14a Layer2
-RpS2 Layer2 RpS10b Layer2
-RpS2 Layer2 RpS16 Layer2
-RpS2 Layer2 RpL10Ab Layer2
-RpS2 Layer2 RpS17 Layer2
-RpS2 Layer2 sta Layer2
-RpS2 Layer2 RpS4 Layer2
-RpS2 Layer2 RpS13 Layer2
-RpS2 Layer2 RpL13 Layer2
-RpS2 Layer2 RpS12 Layer2
-Prosalpha6 Layer2 Trip1 Layer2
-Prosalpha6 Layer2 Tcp-1zeta Layer2
-Prosalpha6 Layer2 Uch-L5 Layer2
-Prosalpha6 Layer2 Rpn5 Layer2
-Prosalpha6 Layer2 Rpt4R Layer2
-Prosalpha6 Layer2 Rpt6 Layer2
-Prosalpha6 Layer2 Prosbeta7 Layer2
-Prosalpha6 Layer2 Rpn12 Layer2
-ND-PDSW Layer2 COX6B Layer2
-ND-PDSW Layer2 CG7603 Layer2
-ND-PDSW Layer2 ND-B16.6 Layer2
-ND-PDSW Layer2 CG11752 Layer2
-NP15.6 Layer2 CG7603 Layer2
-NP15.6 Layer2 COX4 Layer2
-NP15.6 Layer2 CG11752 Layer2
-NP15.6 Layer2 ND-51 Layer2
-NP15.6 Layer2 COX5A Layer2
-NP15.6 Layer2 ND-B16.6 Layer2
-NP15.6 Layer2 ND-PDSW Layer2
-Klc Layer2 Dlic Layer2
-Klc Layer2 betaCOP Layer2
-Klc Layer2 deltaCOP Layer2
-Klc Layer2 zetaCOP Layer2
-Klc Layer2 Klp10A Layer2
-Khc Layer2 Dlic Layer2
-Khc Layer2 zetaCOP Layer2
-Khc Layer2 epsilonCOP Layer2
-Khc Layer2 bai Layer2
-Khc Layer2 deltaCOP Layer2
-Khc Layer2 beta'COP Layer2
-Khc Layer2 syd Layer2
-Khc Layer2 betaCOP Layer2
-Khc Layer2 Klp10A Layer2
-Khc Layer2 betaTub56D Layer2
-Khc Layer2 Klc Layer2
-RpL12 Layer2 bonsai Layer2
-RpL12 Layer2 mago Layer2
-RpL12 Layer2 RpS16 Layer2
-RpL12 Layer2 sta Layer2
-RpL12 Layer2 RpS14a Layer2
-RpL12 Layer2 RpS6 Layer2
-Rpt5 Layer2 Ubqn Layer2
-Rpt5 Layer2 Cat Layer2
-Rpt5 Layer2 CalpB Layer2
-Rpt5 Layer2 CG6891 Layer2
-Rpt5 Layer2 Ect3 Layer2
-Rpt5 Layer2 PyK Layer2
-Rpt5 Layer2 Tcp-1eta Layer2
-Rpt5 Layer2 Ufd1-like Layer2
-Rpt5 Layer2 Uch-L5 Layer2
-Rpt5 Layer2 Rpt4R Layer2
-Rpt5 Layer2 Prosalpha6 Layer2
-Rpt5 Layer2 Prosbeta7 Layer2
-Rpt5 Layer2 Rpn5 Layer2
-Rpt5 Layer2 Rpt6 Layer2
-Rpt5 Layer2 Rpn12 Layer2
-Prosalpha7 Layer2 RpS16 Layer2
-Prosalpha7 Layer2 Cct5 Layer2
-Prosalpha7 Layer2 Tcp-1eta Layer2
-Prosalpha7 Layer2 CG7033 Layer2
-Prosalpha7 Layer2 Trip1 Layer2
-Prosalpha7 Layer2 CG8258 Layer2
-Prosalpha7 Layer2 arm Layer2
-Prosalpha7 Layer2 Uch-L5 Layer2
-Prosalpha7 Layer2 Rpt4R Layer2
-Prosalpha7 Layer2 Prosalpha1 Layer2
-Prosalpha7 Layer2 Rpt6R Layer2
-Prosalpha7 Layer2 Rpn12 Layer2
-Prosalpha7 Layer2 Rpt6 Layer2
-Prosalpha7 Layer2 Rpn6 Layer2
-Prosalpha7 Layer2 Rpt2 Layer2
-Prosalpha7 Layer2 Rpt1 Layer2
-Prosalpha7 Layer2 Prosbeta7 Layer2
-Prosalpha7 Layer2 Prosalpha6 Layer2
-Prosalpha7 Layer2 Rpn5 Layer2
-Prosalpha7 Layer2 Rpn2 Layer2
-Prosalpha7 Layer2 Rpt5 Layer2
-Rpn6 Layer2 Trip1 Layer2
-Rpn6 Layer2 RpS2 Layer2
-Rpn6 Layer2 Ubqn Layer2
-Rpn6 Layer2 CalpB Layer2
-Rpn6 Layer2 Cat Layer2
-Rpn6 Layer2 PyK Layer2
-Rpn6 Layer2 Ect3 Layer2
-Rpn6 Layer2 CG6891 Layer2
-Rpn6 Layer2 Uch-L5 Layer2
-Rpn6 Layer2 Prosalpha6 Layer2
-Rpn6 Layer2 Rpt4R Layer2
-Rpn6 Layer2 Prosbeta7 Layer2
-Rpn6 Layer2 Rpn2 Layer2
-Rpn6 Layer2 Rpt5 Layer2
-Rpn6 Layer2 Rpn12 Layer2
-Rpn6 Layer2 Rpn5 Layer2
-Rpn6 Layer2 Rpt2 Layer2
-Rpn6 Layer2 Rpt6R Layer2
-Rpn6 Layer2 Rpt6 Layer2
-ATPsynbeta Layer2 Rack1 Layer2
-ATPsynbeta Layer2 COX4 Layer2
-ATPsynbeta Layer2 COX6B Layer2
-ATPsynbeta Layer2 sta Layer2
-ATPsynbeta Layer2 Hsc70-5 Layer2
-ATPsynbeta Layer2 Eno Layer2
-ATPsynbeta Layer2 RpL4 Layer2
-ATPsynbeta Layer2 RpS3A Layer2
-ATPsynbeta Layer2 CG11876 Layer2
-ATPsynbeta Layer2 Mpcp Layer2
-ATPsynbeta Layer2 porin Layer2
-ATPsynbeta Layer2 Vha100-1 Layer2
-ATPsynbeta Layer2 RpS2 Layer2
-ATPsynbeta Layer2 Vha100-2 Layer2
-ATPsynbeta Layer2 Hsp83 Layer2
-ATPsynbeta Layer2 ND-51 Layer2
-ATPsynbeta Layer2 Vha68-2 Layer2
-ATPsynbeta Layer2 EfTuM Layer2
-ATPsynbeta Layer2 Mdh2 Layer2
-ATPsynbeta Layer2 CG9090 Layer2
-ATPsynbeta Layer2 COX5A Layer2
-ATPsynbeta Layer2 sun Layer2
-ATPsynCF6 Layer2 Vha100-1 Layer2
-ATPsynCF6 Layer2 Vha100-2 Layer2
-ATPsynCF6 Layer2 NP15.6 Layer2
-ATPsynCF6 Layer2 Vha68-2 Layer2
-ATPsynCF6 Layer2 ND-B16.6 Layer2
-ATPsynCF6 Layer2 COX6B Layer2
-ATPsynCF6 Layer2 CG11752 Layer2
-ATPsynCF6 Layer2 Cyt-c-p Layer2
-ATPsynCF6 Layer2 COX4 Layer2
-ATPsynCF6 Layer2 ND-PDSW Layer2
-ATPsynCF6 Layer2 COX5A Layer2
-ATPsynCF6 Layer2 sun Layer2
-ATPsynCF6 Layer2 ATPsynbeta Layer2
-CG8258 Layer2 RpS13 Layer2
-CG8258 Layer2 RpL11 Layer2
-CG8258 Layer2 eIF3-S9 Layer2
-CG8258 Layer2 Tango7 Layer2
-CG8258 Layer2 Hsp60C Layer2
-CG8258 Layer2 Rpn2 Layer2
-CG8258 Layer2 RpS3 Layer2
-CG8258 Layer2 RpL12 Layer2
-CG8258 Layer2 RpL3 Layer2
-CG8258 Layer2 Hsc70-5 Layer2
-CG8258 Layer2 RpL4 Layer2
-CG8258 Layer2 RpS4 Layer2
-CG8258 Layer2 RpS2 Layer2
-CG8258 Layer2 RpS16 Layer2
-CG8258 Layer2 Hsp60 Layer2
-CG8258 Layer2 CG10576 Layer2
-CG8258 Layer2 Cat Layer2
-CG8258 Layer2 CG6891 Layer2
-CG8258 Layer2 CalpB Layer2
-CG8258 Layer2 Ect3 Layer2
-CG8258 Layer2 Cp1 Layer2
-CG8258 Layer2 Pfk Layer2
-CG8258 Layer2 PyK Layer2
-CG8258 Layer2 ade3 Layer2
-CG8258 Layer2 Trip1 Layer2
-CG8258 Layer2 Rpt5 Layer2
-CG8258 Layer2 Rpn6 Layer2
-CG8258 Layer2 Rpn5 Layer2
-CG8258 Layer2 Hop Layer2
-CG8258 Layer2 Cct5 Layer2
-CG8258 Layer2 Cctgamma Layer2
-CG8258 Layer2 Tcp-1zeta Layer2
-CG8258 Layer2 CG7033 Layer2
-CG8258 Layer2 Tcp-1eta Layer2
-CG8258 Layer2 T-cp1 Layer2
-RpS7 Layer2 Rpt6R Layer2
-RpS7 Layer2 Rpt6 Layer2
-RpS7 Layer2 vig Layer2
-RpS7 Layer2 eEF1delta Layer2
-RpS7 Layer2 Rpt1 Layer2
-RpS7 Layer2 Rpt2 Layer2
-RpS7 Layer2 ade3 Layer2
-RpS7 Layer2 mRpS10 Layer2
-RpS7 Layer2 Srp72 Layer2
-RpS7 Layer2 mago Layer2
-RpS7 Layer2 bonsai Layer2
-RpS7 Layer2 Tctp Layer2
-RpS7 Layer2 CG5642 Layer2
-RpS7 Layer2 CG9769 Layer2
-RpS7 Layer2 eIF3-S10 Layer2
-RpS7 Layer2 Srp54k Layer2
-RpS7 Layer2 Srp19 Layer2
-RpS7 Layer2 Trip1 Layer2
-RpS7 Layer2 eIF3-S9 Layer2
-RpS7 Layer2 eIF3-S8 Layer2
-RpS7 Layer2 Tango7 Layer2
-RpS7 Layer2 RpS15Ab Layer2
-RpS7 Layer2 RpS10a Layer2
-RpS7 Layer2 RpS15Aa Layer2
-RpS7 Layer2 RpS14a Layer2
-RpS7 Layer2 RpL4 Layer2
-RpS7 Layer2 RpL13 Layer2
-RpS7 Layer2 sta Layer2
-RpS7 Layer2 RpS2 Layer2
-RpS7 Layer2 RpS23 Layer2
-RpS7 Layer2 RpS16 Layer2
-RpS7 Layer2 RpL10Ab Layer2
-RpS7 Layer2 RpS6 Layer2
-RpS7 Layer2 RpS18 Layer2
-RpS7 Layer2 RpL11 Layer2
-RpS7 Layer2 RpS12 Layer2
-RpS7 Layer2 RpS4 Layer2
-RpS7 Layer2 RpS3 Layer2
-RpS7 Layer2 RpS13 Layer2
-RpS7 Layer2 RpS27 Layer2
-RpS7 Layer2 RpL3 Layer2
-RpS7 Layer2 RpS10b Layer2
-RpS7 Layer2 RpS17 Layer2
-RpS7 Layer2 RpL18A Layer2
-RpS7 Layer2 RpS11 Layer2
-RpS7 Layer2 RpL12 Layer2
-RpL10 Layer2 mRpS10 Layer2
-RpL10 Layer2 ade3 Layer2
-RpL10 Layer2 Cct5 Layer2
-RpL10 Layer2 mRpS5 Layer2
-RpL10 Layer2 eEF1delta Layer2
-RpL10 Layer2 mRpL12 Layer2
-RpL10 Layer2 mRpS9 Layer2
-RpL10 Layer2 Ef1beta Layer2
-RpL10 Layer2 eIF4G Layer2
-RpL10 Layer2 Srp72 Layer2
-RpL10 Layer2 mago Layer2
-RpL10 Layer2 Rbp2 Layer2
-RpL10 Layer2 eIF3-S10 Layer2
-RpL10 Layer2 eIF3-S8 Layer2
-RpL10 Layer2 CG5642 Layer2
-RpL10 Layer2 CG10576 Layer2
-RpL10 Layer2 Tango7 Layer2
-RpL10 Layer2 Trip1 Layer2
-RpL10 Layer2 Srp19 Layer2
-RpL10 Layer2 mRpS7 Layer2
-RpL10 Layer2 eIF-2gamma Layer2
-RpL10 Layer2 Elf Layer2
-RpL10 Layer2 eIF-3p66 Layer2
-RpL10 Layer2 CG9769 Layer2
-RpL10 Layer2 eIF3-S9 Layer2
-RpL10 Layer2 Srp54k Layer2
-RpL10 Layer2 eRF1 Layer2
-RpL10 Layer2 RpS15Ab Layer2
-RpL10 Layer2 Tctp Layer2
-RpL10 Layer2 RpS14a Layer2
-RpL10 Layer2 RpS15Aa Layer2
-RpL10 Layer2 RpS10b Layer2
-RpL10 Layer2 RpS17 Layer2
-RpL10 Layer2 RpL13 Layer2
-RpL10 Layer2 RpS8 Layer2
-RpL10 Layer2 RpS16 Layer2
-RpL10 Layer2 sta Layer2
-RpL10 Layer2 RpL12 Layer2
-RpL10 Layer2 RpS3 Layer2
-RpL10 Layer2 RpL4 Layer2
-RpL10 Layer2 RpS27 Layer2
-RpL10 Layer2 RpL3 Layer2
-RpL10 Layer2 RpS12 Layer2
-RpL10 Layer2 RpL11 Layer2
-RpL10 Layer2 RpLP2 Layer2
-RpL10 Layer2 RpL18A Layer2
-RpL10 Layer2 RpL26 Layer2
-RpL10 Layer2 RpS4 Layer2
-RpL10 Layer2 Rack1 Layer2
-RpL10 Layer2 RpS3A Layer2
-RpL10 Layer2 RpS11 Layer2
-RpL10 Layer2 RpS13 Layer2
-RpL10 Layer2 RpS10a Layer2
-RpL10 Layer2 RpL30 Layer2
-RpL10 Layer2 RpS2 Layer2
-RpL10 Layer2 RpS18 Layer2
-RpL10 Layer2 RpS23 Layer2
-RpL10 Layer2 RpL10Ab Layer2
-RpL10 Layer2 RpS6 Layer2
-RpL10 Layer2 RpS7 Layer2
-Aats-asp Layer2 CG5642 Layer2
-Aats-asp Layer2 CG31739 Layer2
-Aats-asp Layer2 CG33123 Layer2
-Aats-asp Layer2 Aats-arg Layer2
-Aats-asp Layer2 Aats-ile Layer2
-CG33123 Layer2 Aats-arg Layer2
-Aats-ile Layer2 CG33123 Layer2
-Aats-ile Layer2 Aats-arg Layer2
-RpS8 Layer2 T-cp1 Layer2
-RpS8 Layer2 vig Layer2
-RpS8 Layer2 Rpn2 Layer2
-RpS8 Layer2 Cct5 Layer2
-RpS8 Layer2 eEF1delta Layer2
-RpS8 Layer2 mRpS10 Layer2
-RpS8 Layer2 CG8258 Layer2
-RpS8 Layer2 Tcp-1eta Layer2
-RpS8 Layer2 bonsai Layer2
-RpS8 Layer2 CG7033 Layer2
-RpS8 Layer2 ade3 Layer2
-RpS8 Layer2 CG10576 Layer2
-RpS8 Layer2 Srp72 Layer2
-RpS8 Layer2 mago Layer2
-RpS8 Layer2 Rbp2 Layer2
-RpS8 Layer2 Nacalpha Layer2
-RpS8 Layer2 CG5642 Layer2
-RpS8 Layer2 eIF-3p66 Layer2
-RpS8 Layer2 Srp54k Layer2
-RpS8 Layer2 Srp19 Layer2
-RpS8 Layer2 eIF3-S10 Layer2
-RpS8 Layer2 Tctp Layer2
-RpS8 Layer2 Trip1 Layer2
-RpS8 Layer2 Tango7 Layer2
-RpS8 Layer2 eIF3-S8 Layer2
-RpS8 Layer2 CG9769 Layer2
-RpS8 Layer2 eIF3-S9 Layer2
-RpS8 Layer2 RpL13 Layer2
-RpS8 Layer2 RpS17 Layer2
-RpS8 Layer2 RpL11 Layer2
-RpS8 Layer2 RpS12 Layer2
-RpS8 Layer2 RpL4 Layer2
-RpS8 Layer2 RpS3 Layer2
-RpS8 Layer2 RpS27 Layer2
-RpS8 Layer2 RpS18 Layer2
-RpS8 Layer2 RpS2 Layer2
-RpS8 Layer2 RpS13 Layer2
-RpS8 Layer2 RpS7 Layer2
-RpS8 Layer2 RpS6 Layer2
-RpS8 Layer2 sta Layer2
-RpS8 Layer2 RpL10Ab Layer2
-RpS8 Layer2 RpS10b Layer2
-RpS8 Layer2 RpS10a Layer2
-RpS8 Layer2 RpS14a Layer2
-RpS8 Layer2 RpS4 Layer2
-RpS8 Layer2 RpL12 Layer2
-RpS8 Layer2 RpS11 Layer2
-RpS8 Layer2 RpL18A Layer2
-RpS8 Layer2 RpLP2 Layer2
-RpS8 Layer2 RpL3 Layer2
-RpS8 Layer2 RpS23 Layer2
-RpS8 Layer2 RpS15Ab Layer2
-RpS8 Layer2 RpS15Aa Layer2
-RpS8 Layer2 RpS16 Layer2
-Rpt6 Layer2 Ubqn Layer2
-Rpt6 Layer2 RpS4 Layer2
-Rpt6 Layer2 Uch-L5 Layer2
-Rpt6 Layer2 Rpt4R Layer2
-Rpt6 Layer2 Rpn12 Layer2
-Rpn3 Layer2 bel Layer2
-Rpn3 Layer2 Ubqn Layer2
-Rpn3 Layer2 CalpB Layer2
-Rpn3 Layer2 Ect3 Layer2
-Rpn3 Layer2 Pfk Layer2
-Rpn3 Layer2 CG6891 Layer2
-Rpn3 Layer2 Cat Layer2
-Rpn3 Layer2 Cp1 Layer2
-Rpn3 Layer2 PyK Layer2
-Rpn3 Layer2 arm Layer2
-Rpn3 Layer2 cathD Layer2
-Rpn3 Layer2 CG8258 Layer2
-Rpn3 Layer2 Prosalpha1 Layer2
-Rpn3 Layer2 Uch-L5 Layer2
-Rpn3 Layer2 TER94 Layer2
-Rpn3 Layer2 Prosalpha6 Layer2
-Rpn3 Layer2 Prosbeta7 Layer2
-Rpn3 Layer2 Prosalpha7 Layer2
-Rpn3 Layer2 Rpt4R Layer2
-Rpn3 Layer2 Rpn2 Layer2
-Rpn3 Layer2 Rpt6R Layer2
-Rpn3 Layer2 Rpt2 Layer2
-Rpn3 Layer2 Rpn5 Layer2
-Rpn3 Layer2 Rpt1 Layer2
-Rpn3 Layer2 Rpt5 Layer2
-Rpn3 Layer2 Rpn6 Layer2
-Rpn3 Layer2 Rpn13 Layer2
-Rpn3 Layer2 Rpn12 Layer2
-Rpn3 Layer2 Rpt6 Layer2
-RpL30 Layer2 vig Layer2
-RpL30 Layer2 Nacalpha Layer2
-RpL30 Layer2 CG10576 Layer2
-RpL30 Layer2 Srp72 Layer2
-RpL30 Layer2 eIF4G Layer2
-RpL30 Layer2 bonsai Layer2
-RpL30 Layer2 eIF3-S8 Layer2
-RpL30 Layer2 mago Layer2
-RpL30 Layer2 eIF3-S10 Layer2
-RpL30 Layer2 CG5642 Layer2
-RpL30 Layer2 mRpS5 Layer2
-RpL30 Layer2 Rbp2 Layer2
-RpL30 Layer2 mRpS10 Layer2
-RpL30 Layer2 eIF-3p66 Layer2
-RpL30 Layer2 Tctp Layer2
-RpL30 Layer2 eIF3-S9 Layer2
-RpL30 Layer2 Tango7 Layer2
-RpL30 Layer2 CG9769 Layer2
-RpL30 Layer2 Trip1 Layer2
-RpL30 Layer2 Srp54k Layer2
-RpL30 Layer2 Srp19 Layer2
-RpL30 Layer2 RpS15Aa Layer2
-RpL30 Layer2 RpS15Ab Layer2
-RpL30 Layer2 RpS10a Layer2
-RpL30 Layer2 RpL18A Layer2
-RpL30 Layer2 RpS27 Layer2
-RpL30 Layer2 RpL12 Layer2
-RpL30 Layer2 RpS12 Layer2
-RpL30 Layer2 RpS18 Layer2
-RpL30 Layer2 RpS17 Layer2
-RpL30 Layer2 RpS2 Layer2
-RpL30 Layer2 RpL11 Layer2
-RpL30 Layer2 RpS7 Layer2
-RpL30 Layer2 RpS14a Layer2
-RpL30 Layer2 RpS4 Layer2
-RpL30 Layer2 RpS8 Layer2
-RpL30 Layer2 RpS10b Layer2
-RpL30 Layer2 RpLP2 Layer2
-RpL30 Layer2 RpL13 Layer2
-RpL30 Layer2 RpS16 Layer2
-RpL30 Layer2 RpS3 Layer2
-RpL30 Layer2 RpS13 Layer2
-RpL30 Layer2 RpS11 Layer2
-RpL30 Layer2 RpS6 Layer2
-RpL30 Layer2 RpL3 Layer2
-RpL30 Layer2 RpL10Ab Layer2
-RpL30 Layer2 RpS23 Layer2
-RpL30 Layer2 RpL4 Layer2
-RpL30 Layer2 sta Layer2
-RpS10a Layer2 Tctp Layer2
-RpS10a Layer2 eEF1delta Layer2
-RpS10a Layer2 bonsai Layer2
-RpS10a Layer2 mRpS10 Layer2
-RpS10a Layer2 RpS10b Layer2
-RpS10a Layer2 Srp72 Layer2
-RpS10a Layer2 mago Layer2
-RpS10a Layer2 CG5642 Layer2
-RpS10a Layer2 CG9769 Layer2
-RpS10a Layer2 eIF3-S10 Layer2
-RpS10a Layer2 Trip1 Layer2
-RpS10a Layer2 Srp19 Layer2
-RpS10a Layer2 Srp54k Layer2
-RpS10a Layer2 RpS15Aa Layer2
-RpS10a Layer2 sta Layer2
-RpS10a Layer2 RpS2 Layer2
-RpS10a Layer2 RpS3 Layer2
-RpS10a Layer2 RpL10Ab Layer2
-RpS10a Layer2 RpS4 Layer2
-RpS10a Layer2 RpS13 Layer2
-RpS10a Layer2 RpS6 Layer2
-RpS10a Layer2 RpS12 Layer2
-RpS10a Layer2 RpS16 Layer2
-RpS10a Layer2 RpS14a Layer2
-RpS10a Layer2 RpS27 Layer2
-RpS10a Layer2 RpL13 Layer2
-RpS10a Layer2 RpL12 Layer2
-RpS10a Layer2 RpS17 Layer2
-RpS10a Layer2 RpL3 Layer2
-RpS17 Layer2 CG7033 Layer2
-RpS17 Layer2 CG5642 Layer2
-RpS17 Layer2 bonsai Layer2
-RpS17 Layer2 RpS6 Layer2
-RpS17 Layer2 RpL12 Layer2
-RpS17 Layer2 RpL10Ab Layer2
-RpS17 Layer2 RpS15Aa Layer2
-RpS17 Layer2 RpS12 Layer2
-RpS17 Layer2 sta Layer2
-RpS17 Layer2 RpS14a Layer2
-RpS17 Layer2 RpS4 Layer2
-RpS17 Layer2 RpS16 Layer2
-RpS17 Layer2 RpS10b Layer2
-RpLP0 Layer2 ATPsynbeta Layer2
-RpLP0 Layer2 Tcp-1zeta Layer2
-RpLP0 Layer2 T-cp1 Layer2
-RpLP0 Layer2 CG7033 Layer2
-RpLP0 Layer2 EfTuM Layer2
-RpLP0 Layer2 Nacalpha Layer2
-RpLP0 Layer2 bonsai Layer2
-RpLP0 Layer2 Tcp-1eta Layer2
-RpLP0 Layer2 mRpS10 Layer2
-RpLP0 Layer2 ade3 Layer2
-RpLP0 Layer2 Cct5 Layer2
-RpLP0 Layer2 mRpS5 Layer2
-RpLP0 Layer2 eEF1delta Layer2
-RpLP0 Layer2 Srp72 Layer2
-RpLP0 Layer2 eIF4G Layer2
-RpLP0 Layer2 mRpS9 Layer2
-RpLP0 Layer2 Rbp2 Layer2
-RpLP0 Layer2 mago Layer2
-RpLP0 Layer2 CG5642 Layer2
-RpLP0 Layer2 eIF3-S8 Layer2
-RpLP0 Layer2 Elf Layer2
-RpLP0 Layer2 eIF3-S10 Layer2
-RpLP0 Layer2 CG10576 Layer2
-RpLP0 Layer2 mRpL12 Layer2
-RpLP0 Layer2 mRpS7 Layer2
-RpLP0 Layer2 Tctp Layer2
-RpLP0 Layer2 Srp19 Layer2
-RpLP0 Layer2 Tango7 Layer2
-RpLP0 Layer2 eIF-2gamma Layer2
-RpLP0 Layer2 eIF-3p66 Layer2
-RpLP0 Layer2 CG9769 Layer2
-RpLP0 Layer2 eIF3-S9 Layer2
-RpLP0 Layer2 RpS15Aa Layer2
-RpLP0 Layer2 Srp54k Layer2
-RpLP0 Layer2 Trip1 Layer2
-RpLP0 Layer2 RpS15Ab Layer2
-RpLP0 Layer2 eRF1 Layer2
-RpLP0 Layer2 RpS14a Layer2
-RpLP0 Layer2 RpS10a Layer2
-RpLP0 Layer2 Ef1beta Layer2
-RpLP0 Layer2 RpS6 Layer2
-RpLP0 Layer2 RpS13 Layer2
-RpLP0 Layer2 RpS23 Layer2
-RpLP0 Layer2 sta Layer2
-RpLP0 Layer2 RpS11 Layer2
-RpLP0 Layer2 RpS16 Layer2
-RpLP0 Layer2 RpS7 Layer2
-RpLP0 Layer2 RpS27 Layer2
-RpLP0 Layer2 RpL10 Layer2
-RpLP0 Layer2 RpS3 Layer2
-RpLP0 Layer2 RpS10b Layer2
-RpLP0 Layer2 RpS8 Layer2
-RpLP0 Layer2 RpLP2 Layer2
-RpLP0 Layer2 RpS12 Layer2
-RpLP0 Layer2 RpL3 Layer2
-RpLP0 Layer2 Rack1 Layer2
-RpLP0 Layer2 RpL13 Layer2
-RpLP0 Layer2 RpL26 Layer2
-RpLP0 Layer2 RpL30 Layer2
-RpLP0 Layer2 RpL18A Layer2
-RpLP0 Layer2 RpL4 Layer2
-RpLP0 Layer2 RpL10Ab Layer2
-RpLP0 Layer2 RpS18 Layer2
-RpLP0 Layer2 RpS3A Layer2
-RpLP0 Layer2 RpL12 Layer2
-RpLP0 Layer2 RpL11 Layer2
-RpLP0 Layer2 RpS2 Layer2
-RpLP0 Layer2 RpS4 Layer2
-RpLP0 Layer2 RpS17 Layer2
-eRF1 Layer2 bonsai Layer2
-eRF1 Layer2 T-cp1 Layer2
-eRF1 Layer2 eIF3-S8 Layer2
-eRF1 Layer2 CG8635 Layer2
-eRF1 Layer2 CG7033 Layer2
-eRF1 Layer2 Cctgamma Layer2
-eRF1 Layer2 mRpS5 Layer2
-eRF1 Layer2 mRpS10 Layer2
-eRF1 Layer2 wuho Layer2
-eRF1 Layer2 RpS15Ab Layer2
-eRF1 Layer2 RpS14a Layer2
-eRF1 Layer2 RpS15Aa Layer2
-eRF1 Layer2 eIF3-S10 Layer2
-eRF1 Layer2 pAbp Layer2
-eRF1 Layer2 CG10576 Layer2
-eRF1 Layer2 RpS17 Layer2
-eRF1 Layer2 eIF-2gamma Layer2
-eRF1 Layer2 Trip1 Layer2
-eRF1 Layer2 mago Layer2
-eRF1 Layer2 RpLP2 Layer2
-eRF1 Layer2 eIF3-S9 Layer2
-eRF1 Layer2 eIF4G Layer2
-eRF1 Layer2 Rack1 Layer2
-eRF1 Layer2 RpS10a Layer2
-eRF1 Layer2 RpS10b Layer2
-eRF1 Layer2 RpL3 Layer2
-eRF1 Layer2 RpL10Ab Layer2
-eRF1 Layer2 RpS27 Layer2
-eRF1 Layer2 RpS12 Layer2
-eRF1 Layer2 RpS7 Layer2
-eRF1 Layer2 RpS18 Layer2
-eRF1 Layer2 RpL12 Layer2
-eRF1 Layer2 RpS23 Layer2
-eRF1 Layer2 RpL18A Layer2
-eRF1 Layer2 RpS16 Layer2
-eRF1 Layer2 RpL30 Layer2
-eRF1 Layer2 RpS11 Layer2
-eRF1 Layer2 RpS3 Layer2
-eRF1 Layer2 RpS3A Layer2
-eRF1 Layer2 sta Layer2
-eRF1 Layer2 RpS13 Layer2
-eRF1 Layer2 RpS6 Layer2
-eRF1 Layer2 RpS8 Layer2
-eRF1 Layer2 RpL4 Layer2
-eRF1 Layer2 RpS4 Layer2
-eRF1 Layer2 RpL11 Layer2
-eRF1 Layer2 RpS2 Layer2
-eRF1 Layer2 RpL13 Layer2
-Elf Layer2 ade5 Layer2
-Elf Layer2 mRpS7 Layer2
-Elf Layer2 RpS15Aa Layer2
-Elf Layer2 RpS15Ab Layer2
-Elf Layer2 T-cp1 Layer2
-Elf Layer2 eIF3-S9 Layer2
-Elf Layer2 Cctgamma Layer2
-Elf Layer2 RpS17 Layer2
-Elf Layer2 CG33123 Layer2
-Elf Layer2 Cct5 Layer2
-Elf Layer2 RpS14a Layer2
-Elf Layer2 eIF3-S8 Layer2
-Elf Layer2 eIF3-S10 Layer2
-Elf Layer2 eIF-2gamma Layer2
-Elf Layer2 RpL10Ab Layer2
-Elf Layer2 RpLP2 Layer2
-Elf Layer2 mago Layer2
-Elf Layer2 RpL13 Layer2
-Elf Layer2 RpL3 Layer2
-Elf Layer2 RpL30 Layer2
-Elf Layer2 RpL18A Layer2
-Elf Layer2 RpL12 Layer2
-Elf Layer2 RpL11 Layer2
-Elf Layer2 Rack1 Layer2
-Elf Layer2 RpL4 Layer2
-Elf Layer2 RpS4 Layer2
-Elf Layer2 RpS10b Layer2
-Elf Layer2 RpS10a Layer2
-Elf Layer2 RpS12 Layer2
-Elf Layer2 eIF4G Layer2
-Elf Layer2 RpS11 Layer2
-Elf Layer2 RpS13 Layer2
-Elf Layer2 RpS27 Layer2
-Elf Layer2 RpS8 Layer2
-Elf Layer2 RpS16 Layer2
-Elf Layer2 RpS3 Layer2
-Elf Layer2 sta Layer2
-Elf Layer2 RpS23 Layer2
-Elf Layer2 RpS18 Layer2
-Elf Layer2 RpS7 Layer2
-Elf Layer2 RpS6 Layer2
-Elf Layer2 RpS3A Layer2
-Elf Layer2 RpS2 Layer2
-Elf Layer2 pAbp Layer2
-Elf Layer2 eRF1 Layer2
-RpS15Aa Layer2 bonsai Layer2
-RpS15Aa Layer2 RpL12 Layer2
-RpS15Aa Layer2 sta Layer2
-RpS15Aa Layer2 RpS6 Layer2
-RpS15Aa Layer2 RpS16 Layer2
-RpS15Aa Layer2 RpS14a Layer2
-RpS27 Layer2 vig Layer2
-RpS27 Layer2 Srp72 Layer2
-RpS27 Layer2 mRpS10 Layer2
-RpS27 Layer2 bonsai Layer2
-RpS27 Layer2 mago Layer2
-RpS27 Layer2 Tctp Layer2
-RpS27 Layer2 CG5642 Layer2
-RpS27 Layer2 CG9769 Layer2
-RpS27 Layer2 Trip1 Layer2
-RpS27 Layer2 eIF3-S10 Layer2
-RpS27 Layer2 Srp19 Layer2
-RpS27 Layer2 Srp54k Layer2
-RpS27 Layer2 RpS13 Layer2
-RpS27 Layer2 RpS17 Layer2
-RpS27 Layer2 RpS4 Layer2
-RpS27 Layer2 RpS2 Layer2
-RpS27 Layer2 RpL13 Layer2
-RpS27 Layer2 RpL12 Layer2
-RpS27 Layer2 RpS6 Layer2
-RpS27 Layer2 RpS12 Layer2
-RpS27 Layer2 RpS10b Layer2
-RpS27 Layer2 sta Layer2
-RpS27 Layer2 RpS16 Layer2
-RpS27 Layer2 RpS14a Layer2
-RpS27 Layer2 RpL10Ab Layer2
-RpS27 Layer2 RpL3 Layer2
-RpS27 Layer2 RpS3 Layer2
-RpS27 Layer2 RpS15Aa Layer2
-RpS18 Layer2 Rpt6R Layer2
-RpS18 Layer2 Rpt6 Layer2
-RpS18 Layer2 Tcp-1eta Layer2
-RpS18 Layer2 Rpt2 Layer2
-RpS18 Layer2 vig Layer2
-RpS18 Layer2 ade3 Layer2
-RpS18 Layer2 Srp72 Layer2
-RpS18 Layer2 mago Layer2
-RpS18 Layer2 bonsai Layer2
-RpS18 Layer2 Tctp Layer2
-RpS18 Layer2 CG5642 Layer2
-RpS18 Layer2 mRpL12 Layer2
-RpS18 Layer2 mRpS10 Layer2
-RpS18 Layer2 mRpS9 Layer2
-RpS18 Layer2 eIF3-S10 Layer2
-RpS18 Layer2 CG9769 Layer2
-RpS18 Layer2 Srp19 Layer2
-RpS18 Layer2 Srp54k Layer2
-RpS18 Layer2 Trip1 Layer2
-RpS18 Layer2 RpS12 Layer2
-RpS18 Layer2 RpS2 Layer2
-RpS18 Layer2 RpS16 Layer2
-RpS18 Layer2 sta Layer2
-RpS18 Layer2 RpL10Ab Layer2
-RpS18 Layer2 RpL4 Layer2
-RpS18 Layer2 RpS14a Layer2
-RpS18 Layer2 RpS17 Layer2
-RpS18 Layer2 RpS3 Layer2
-RpS18 Layer2 RpS4 Layer2
-RpS18 Layer2 RpL3 Layer2
-RpS18 Layer2 RpS10a Layer2
-RpS18 Layer2 RpS13 Layer2
-RpS18 Layer2 RpS10b Layer2
-RpS18 Layer2 RpL13 Layer2
-RpS18 Layer2 RpL12 Layer2
-RpS18 Layer2 RpS15Aa Layer2
-RpS18 Layer2 RpS6 Layer2
-RpS18 Layer2 RpS27 Layer2
-RpS10b Layer2 bonsai Layer2
-RpS10b Layer2 mago Layer2
-RpS10b Layer2 RpS15Aa Layer2
-RpS10b Layer2 RpS16 Layer2
-RpS10b Layer2 RpS14a Layer2
-RpS10b Layer2 RpL12 Layer2
-RpS10b Layer2 sta Layer2
-RpS10b Layer2 RpS6 Layer2
-RpS3A Layer2 CG7048 Layer2
-RpS3A Layer2 Nacalpha Layer2
-RpS3A Layer2 vig Layer2
-RpS3A Layer2 eEF1delta Layer2
-RpS3A Layer2 Cct5 Layer2
-RpS3A Layer2 CG8258 Layer2
-RpS3A Layer2 CG7033 Layer2
-RpS3A Layer2 Tcp-1eta Layer2
-RpS3A Layer2 Srp72 Layer2
-RpS3A Layer2 mRpS5 Layer2
-RpS3A Layer2 Rbp2 Layer2
-RpS3A Layer2 mRpS10 Layer2
-RpS3A Layer2 eIF4G Layer2
-RpS3A Layer2 bonsai Layer2
-RpS3A Layer2 mago Layer2
-RpS3A Layer2 CG5642 Layer2
-RpS3A Layer2 eIF-3p66 Layer2
-RpS3A Layer2 eIF3-S8 Layer2
-RpS3A Layer2 Srp54k Layer2
-RpS3A Layer2 eIF3-S10 Layer2
-RpS3A Layer2 Srp19 Layer2
-RpS3A Layer2 CG9769 Layer2
-RpS3A Layer2 ade3 Layer2
-RpS3A Layer2 Trip1 Layer2
-RpS3A Layer2 Tango7 Layer2
-RpS3A Layer2 eIF3-S9 Layer2
-RpS3A Layer2 Tctp Layer2
-RpS3A Layer2 RpS12 Layer2
-RpS3A Layer2 RpS13 Layer2
-RpS3A Layer2 RpS2 Layer2
-RpS3A Layer2 RpS17 Layer2
-RpS3A Layer2 RpS15Ab Layer2
-RpS3A Layer2 RpL13 Layer2
-RpS3A Layer2 RpS15Aa Layer2
-RpS3A Layer2 RpS8 Layer2
-RpS3A Layer2 RpS10a Layer2
-RpS3A Layer2 RpS4 Layer2
-RpS3A Layer2 RpL11 Layer2
-RpS3A Layer2 RpS23 Layer2
-RpS3A Layer2 RpS7 Layer2
-RpS3A Layer2 RpLP2 Layer2
-RpS3A Layer2 sta Layer2
-RpS3A Layer2 RpS6 Layer2
-RpS3A Layer2 RpL12 Layer2
-RpS3A Layer2 RpS11 Layer2
-RpS3A Layer2 RpL30 Layer2
-RpS3A Layer2 RpL3 Layer2
-RpS3A Layer2 RpS3 Layer2
-RpS3A Layer2 RpS14a Layer2
-RpS3A Layer2 RpL10Ab Layer2
-RpS3A Layer2 RpL18A Layer2
-RpS3A Layer2 RpS27 Layer2
-RpS3A Layer2 RpL4 Layer2
-RpS3A Layer2 RpS16 Layer2
-RpS3A Layer2 RpS18 Layer2
-RpS3A Layer2 RpS10b Layer2
-RpS16 Layer2 mago Layer2
-RpS16 Layer2 RpS14a Layer2
-RpS16 Layer2 RpS6 Layer2
-RpS16 Layer2 sta Layer2
-RpS15Ab Layer2 vig Layer2
-RpS15Ab Layer2 CG9769 Layer2
-RpS15Ab Layer2 ade3 Layer2
-RpS15Ab Layer2 EfTuM Layer2
-RpS15Ab Layer2 Srp19 Layer2
-RpS15Ab Layer2 eIF3-S9 Layer2
-RpS15Ab Layer2 eIF3-S10 Layer2
-RpS15Ab Layer2 eIF3-S8 Layer2
-RpS15Ab Layer2 bonsai Layer2
-RpS15Ab Layer2 mRpS10 Layer2
-RpS15Ab Layer2 Srp54k Layer2
-RpS15Ab Layer2 Tango7 Layer2
-RpS15Ab Layer2 Tctp Layer2
-RpS15Ab Layer2 mRpS9 Layer2
-RpS15Ab Layer2 mRpL12 Layer2
-RpS15Ab Layer2 RpS15Aa Layer2
-RpS15Ab Layer2 RpS12 Layer2
-RpS15Ab Layer2 RpL10Ab Layer2
-RpS15Ab Layer2 RpL13 Layer2
-RpS15Ab Layer2 RpL18A Layer2
-RpS15Ab Layer2 RpL12 Layer2
-RpS15Ab Layer2 RpL3 Layer2
-RpS15Ab Layer2 RpL4 Layer2
-RpS15Ab Layer2 RpS10a Layer2
-RpS15Ab Layer2 RpL11 Layer2
-RpS15Ab Layer2 RpS6 Layer2
-RpS15Ab Layer2 RpS2 Layer2
-RpS15Ab Layer2 RpS13 Layer2
-RpS15Ab Layer2 RpS23 Layer2
-RpS15Ab Layer2 RpS18 Layer2
-RpS15Ab Layer2 RpS3 Layer2
-RpS15Ab Layer2 RpS10b Layer2
-RpS15Ab Layer2 RpS17 Layer2
-RpS15Ab Layer2 RpS14a Layer2
-RpS15Ab Layer2 sta Layer2
-RpS15Ab Layer2 RpS11 Layer2
-RpS15Ab Layer2 RpS27 Layer2
-RpS15Ab Layer2 RpS4 Layer2
-RpS15Ab Layer2 RpS16 Layer2
-RpL3 Layer2 eEF1delta Layer2
-RpL3 Layer2 bonsai Layer2
-RpL3 Layer2 Tcp-1zeta Layer2
-RpL3 Layer2 Tcp-1eta Layer2
-RpL3 Layer2 CG7033 Layer2
-RpL3 Layer2 Tctp Layer2
-RpL3 Layer2 mago Layer2
-RpL3 Layer2 Srp19 Layer2
-RpL3 Layer2 mRpL12 Layer2
-RpL3 Layer2 mRpS9 Layer2
-RpL3 Layer2 eIF3-S10 Layer2
-RpL3 Layer2 CG10576 Layer2
-RpL3 Layer2 CG5642 Layer2
-RpL3 Layer2 CG9769 Layer2
-RpL3 Layer2 Srp54k Layer2
-RpL3 Layer2 Trip1 Layer2
-RpL3 Layer2 ade3 Layer2
-RpL3 Layer2 RpS14a Layer2
-RpL3 Layer2 RpS10b Layer2
-RpL3 Layer2 RpS15Aa Layer2
-RpL3 Layer2 RpS13 Layer2
-RpL3 Layer2 RpS16 Layer2
-RpL3 Layer2 RpS2 Layer2
-RpL3 Layer2 RpS4 Layer2
-RpL3 Layer2 RpS17 Layer2
-RpL3 Layer2 sta Layer2
-RpL3 Layer2 RpL12 Layer2
-RpL3 Layer2 RpL13 Layer2
-RpL3 Layer2 RpL10Ab Layer2
-RpL3 Layer2 RpS12 Layer2
-RpL3 Layer2 RpS6 Layer2
-Tcp-1zeta Layer2 Tim9a Layer2
-Tcp-1zeta Layer2 RpL12 Layer2
-Tcp-1zeta Layer2 Hsp60 Layer2
-Tcp-1zeta Layer2 sta Layer2
-Tcp-1zeta Layer2 CG7033 Layer2
-T-cp1 Layer2 RpS13 Layer2
-T-cp1 Layer2 RpS2 Layer2
-T-cp1 Layer2 RpL13 Layer2
-T-cp1 Layer2 Hop Layer2
-T-cp1 Layer2 sta Layer2
-T-cp1 Layer2 RpL12 Layer2
-T-cp1 Layer2 Aats-ile Layer2
-T-cp1 Layer2 RpS4 Layer2
-T-cp1 Layer2 RpL3 Layer2
-T-cp1 Layer2 CG10576 Layer2
-T-cp1 Layer2 CG7033 Layer2
-T-cp1 Layer2 Cctgamma Layer2
-T-cp1 Layer2 Tcp-1eta Layer2
-T-cp1 Layer2 Tcp-1zeta Layer2
-RpS3 Layer2 Tcp-1zeta Layer2
-RpS3 Layer2 vig Layer2
-RpS3 Layer2 CG7033 Layer2
-RpS3 Layer2 eEF1delta Layer2
-RpS3 Layer2 ade3 Layer2
-RpS3 Layer2 Tcp-1eta Layer2
-RpS3 Layer2 Srp72 Layer2
-RpS3 Layer2 mago Layer2
-RpS3 Layer2 bonsai Layer2
-RpS3 Layer2 mRpS10 Layer2
-RpS3 Layer2 mRpS9 Layer2
-RpS3 Layer2 mRpL12 Layer2
-RpS3 Layer2 Srp19 Layer2
-RpS3 Layer2 CG5642 Layer2
-RpS3 Layer2 Srp54k Layer2
-RpS3 Layer2 CG9769 Layer2
-RpS3 Layer2 Tctp Layer2
-RpS3 Layer2 eIF3-S10 Layer2
-RpS3 Layer2 RpS15Aa Layer2
-RpS3 Layer2 RpL13 Layer2
-RpS3 Layer2 RpS6 Layer2
-RpS3 Layer2 RpS2 Layer2
-RpS3 Layer2 RpL12 Layer2
-RpS3 Layer2 RpL10Ab Layer2
-RpS3 Layer2 RpS14a Layer2
-RpS3 Layer2 RpS12 Layer2
-RpS3 Layer2 RpS13 Layer2
-RpS3 Layer2 RpS16 Layer2
-RpS3 Layer2 RpS10b Layer2
-RpS3 Layer2 sta Layer2
-RpS3 Layer2 RpS4 Layer2
-RpS3 Layer2 RpL3 Layer2
-RpS3 Layer2 RpS17 Layer2
-RpS3 Layer2 Trip1 Layer2
-RpL26 Layer2 CG10576 Layer2
-RpL26 Layer2 eEF1delta Layer2
-RpL26 Layer2 EfTuM Layer2
-RpL26 Layer2 Nacalpha Layer2
-RpL26 Layer2 bonsai Layer2
-RpL26 Layer2 ade3 Layer2
-RpL26 Layer2 mRpS10 Layer2
-RpL26 Layer2 mRpS5 Layer2
-RpL26 Layer2 Tctp Layer2
-RpL26 Layer2 eIF4G Layer2
-RpL26 Layer2 CG5642 Layer2
-RpL26 Layer2 mago Layer2
-RpL26 Layer2 eIF3-S10 Layer2
-RpL26 Layer2 Rbp2 Layer2
-RpL26 Layer2 mRpS9 Layer2
-RpL26 Layer2 eIF3-S9 Layer2
-RpL26 Layer2 mRpL12 Layer2
-RpL26 Layer2 eIF-3p66 Layer2
-RpL26 Layer2 eIF3-S8 Layer2
-RpL26 Layer2 Srp72 Layer2
-RpL26 Layer2 eIF-2gamma Layer2
-RpL26 Layer2 mRpS7 Layer2
-RpL26 Layer2 CG9769 Layer2
-RpL26 Layer2 Elf Layer2
-RpL26 Layer2 Tango7 Layer2
-RpL26 Layer2 Trip1 Layer2
-RpL26 Layer2 Ef1beta Layer2
-RpL26 Layer2 Srp19 Layer2
-RpL26 Layer2 eRF1 Layer2
-RpL26 Layer2 Srp54k Layer2
-RpL26 Layer2 RpS15Aa Layer2
-RpL26 Layer2 RpS15Ab Layer2
-RpL26 Layer2 RpL10Ab Layer2
-RpL26 Layer2 RpL11 Layer2
-RpL26 Layer2 RpL3 Layer2
-RpL26 Layer2 RpS13 Layer2
-RpL26 Layer2 RpS2 Layer2
-RpL26 Layer2 RpS6 Layer2
-RpL26 Layer2 RpS4 Layer2
-RpL26 Layer2 RpS27 Layer2
-RpL26 Layer2 RpL30 Layer2
-RpL26 Layer2 RpL18A Layer2
-RpL26 Layer2 RpS12 Layer2
-RpL26 Layer2 RpS10a Layer2
-RpL26 Layer2 RpS18 Layer2
-RpL26 Layer2 RpS3A Layer2
-RpL26 Layer2 RpS8 Layer2
-RpL26 Layer2 RpS17 Layer2
-RpL26 Layer2 sta Layer2
-RpL26 Layer2 RpS14a Layer2
-RpL26 Layer2 RpS11 Layer2
-RpL26 Layer2 RpS23 Layer2
-RpL26 Layer2 RpL13 Layer2
-RpL26 Layer2 RpL12 Layer2
-RpL26 Layer2 RpS10b Layer2
-RpL26 Layer2 Rack1 Layer2
-RpL26 Layer2 RpL4 Layer2
-RpL26 Layer2 RpS3 Layer2
-RpL26 Layer2 RpS7 Layer2
-RpL26 Layer2 RpS16 Layer2
-RpL26 Layer2 RpLP2 Layer2
-RpS12 Layer2 mago Layer2
-RpS12 Layer2 RpS14a Layer2
-RpS12 Layer2 RpS15Aa Layer2
-RpS12 Layer2 RpS10b Layer2
-RpS12 Layer2 RpS6 Layer2
-RpS12 Layer2 RpS16 Layer2
-RpS12 Layer2 RpL12 Layer2
-RpS12 Layer2 sta Layer2
-CG5642 Layer2 RpL12 Layer2
-CG5642 Layer2 RpS6 Layer2
-CG5642 Layer2 sta Layer2
-CG5642 Layer2 RpS10b Layer2
-CG5642 Layer2 RpS12 Layer2
-CG5642 Layer2 RpS16 Layer2
-CG5642 Layer2 RpS4 Layer2
-CG9769 Layer2 RpS15Aa Layer2
-CG9769 Layer2 RpS17 Layer2
-CG9769 Layer2 RpS12 Layer2
-CG9769 Layer2 RpL12 Layer2
-CG9769 Layer2 RpS10b Layer2
-CG9769 Layer2 RpL10Ab Layer2
-CG9769 Layer2 RpS4 Layer2
-CG9769 Layer2 sta Layer2
-CG9769 Layer2 RpS6 Layer2
-CG9769 Layer2 RpS16 Layer2
-CG9769 Layer2 CG5642 Layer2
-CG7033 Layer2 RpS14a Layer2
-CG7033 Layer2 sta Layer2
-Cct5 Layer2 RpS13 Layer2
-Cct5 Layer2 ade3 Layer2
-Cct5 Layer2 Prosalpha6 Layer2
-Cct5 Layer2 Hsc70-5 Layer2
-Cct5 Layer2 Rpt6 Layer2
-Cct5 Layer2 RpS3 Layer2
-Cct5 Layer2 Ref1 Layer2
-Cct5 Layer2 RpS16 Layer2
-Cct5 Layer2 RpL12 Layer2
-Cct5 Layer2 RpL13 Layer2
-Cct5 Layer2 CG5642 Layer2
-Cct5 Layer2 RpL18A Layer2
-Cct5 Layer2 RpS6 Layer2
-Cct5 Layer2 sta Layer2
-Cct5 Layer2 RpL11 Layer2
-Cct5 Layer2 Aats-ile Layer2
-Cct5 Layer2 pont Layer2
-Cct5 Layer2 Trip1 Layer2
-Cct5 Layer2 RpS4 Layer2
-Cct5 Layer2 Tango7 Layer2
-Cct5 Layer2 eIF3-S9 Layer2
-Cct5 Layer2 RpL3 Layer2
-Cct5 Layer2 RpS2 Layer2
-Cct5 Layer2 CG10576 Layer2
-Cct5 Layer2 RpL4 Layer2
-Cct5 Layer2 Hop Layer2
-Cct5 Layer2 Cctgamma Layer2
-Cct5 Layer2 Tcp-1eta Layer2
-Cct5 Layer2 T-cp1 Layer2
-Cct5 Layer2 Tcp-1zeta Layer2
-Cct5 Layer2 CG7033 Layer2
-RpLP2 Layer2 CG10576 Layer2
-RpLP2 Layer2 bonsai Layer2
-RpLP2 Layer2 mRpS10 Layer2
-RpLP2 Layer2 Srp72 Layer2
-RpLP2 Layer2 Srp54k Layer2
-RpLP2 Layer2 Srp19 Layer2
-RpLP2 Layer2 mago Layer2
-RpLP2 Layer2 eIF3-S8 Layer2
-RpLP2 Layer2 eIF3-S10 Layer2
-RpLP2 Layer2 CG5642 Layer2
-RpLP2 Layer2 Rbp2 Layer2
-RpLP2 Layer2 Tango7 Layer2
-RpLP2 Layer2 eIF3-S9 Layer2
-RpLP2 Layer2 eIF-3p66 Layer2
-RpLP2 Layer2 Nacalpha Layer2
-RpLP2 Layer2 RpS15Aa Layer2
-RpLP2 Layer2 CG9769 Layer2
-RpLP2 Layer2 Trip1 Layer2
-RpLP2 Layer2 Tctp Layer2
-RpLP2 Layer2 RpS10a Layer2
-RpLP2 Layer2 RpS10b Layer2
-RpLP2 Layer2 RpS27 Layer2
-RpLP2 Layer2 RpS3 Layer2
-RpLP2 Layer2 RpS13 Layer2
-RpLP2 Layer2 RpS18 Layer2
-RpLP2 Layer2 RpL4 Layer2
-RpLP2 Layer2 RpS7 Layer2
-RpLP2 Layer2 RpL13 Layer2
-RpLP2 Layer2 RpS23 Layer2
-RpLP2 Layer2 RpL10Ab Layer2
-RpLP2 Layer2 RpS15Ab Layer2
-RpLP2 Layer2 RpS6 Layer2
-RpLP2 Layer2 RpS14a Layer2
-RpLP2 Layer2 RpS2 Layer2
-RpLP2 Layer2 RpL3 Layer2
-RpLP2 Layer2 RpL12 Layer2
-RpLP2 Layer2 RpL18A Layer2
-RpLP2 Layer2 RpL11 Layer2
-RpLP2 Layer2 RpS11 Layer2
-RpLP2 Layer2 sta Layer2
-RpLP2 Layer2 RpS16 Layer2
-RpLP2 Layer2 RpS17 Layer2
-RpLP2 Layer2 RpS12 Layer2
-RpLP2 Layer2 RpS4 Layer2
-RpS23 Layer2 CG7033 Layer2
-RpS23 Layer2 vig Layer2
-RpS23 Layer2 eEF1delta Layer2
-RpS23 Layer2 bonsai Layer2
-RpS23 Layer2 mRpS10 Layer2
-RpS23 Layer2 Srp72 Layer2
-RpS23 Layer2 mago Layer2
-RpS23 Layer2 CG5642 Layer2
-RpS23 Layer2 mRpL12 Layer2
-RpS23 Layer2 mRpS9 Layer2
-RpS23 Layer2 Tctp Layer2
-RpS23 Layer2 eIF3-S9 Layer2
-RpS23 Layer2 eIF3-S8 Layer2
-RpS23 Layer2 eIF3-S10 Layer2
-RpS23 Layer2 CG9769 Layer2
-RpS23 Layer2 Trip1 Layer2
-RpS23 Layer2 Srp19 Layer2
-RpS23 Layer2 Srp54k Layer2
-RpS23 Layer2 RpS16 Layer2
-RpS23 Layer2 RpS27 Layer2
-RpS23 Layer2 RpL18A Layer2
-RpS23 Layer2 RpS10b Layer2
-RpS23 Layer2 RpS15Aa Layer2
-RpS23 Layer2 RpS12 Layer2
-RpS23 Layer2 RpS3 Layer2
-RpS23 Layer2 RpS18 Layer2
-RpS23 Layer2 RpS14a Layer2
-RpS23 Layer2 RpL11 Layer2
-RpS23 Layer2 RpL3 Layer2
-RpS23 Layer2 RpS2 Layer2
-RpS23 Layer2 sta Layer2
-RpS23 Layer2 RpS6 Layer2
-RpS23 Layer2 RpL10Ab Layer2
-RpS23 Layer2 RpL12 Layer2
-RpS23 Layer2 RpL13 Layer2
-RpS23 Layer2 RpS17 Layer2
-RpS23 Layer2 RpL4 Layer2
-RpS23 Layer2 RpS4 Layer2
-RpS23 Layer2 RpS10a Layer2
-RpS23 Layer2 RpS13 Layer2
-RpS14a Layer2 sta Layer2
-Rack1 Layer2 vig Layer2
-Rack1 Layer2 bel Layer2
-Rack1 Layer2 Prosalpha7 Layer2
-Rack1 Layer2 Rpn6 Layer2
-Rack1 Layer2 Rpn2 Layer2
-Rack1 Layer2 eEF1delta Layer2
-Rack1 Layer2 Cctgamma Layer2
-Rack1 Layer2 Eno Layer2
-Rack1 Layer2 pAbp Layer2
-Rack1 Layer2 Nacalpha Layer2
-Rack1 Layer2 eIF3-S8 Layer2
-Rack1 Layer2 CG10576 Layer2
-Rack1 Layer2 CG9769 Layer2
-Rack1 Layer2 ade3 Layer2
-Rack1 Layer2 Dp1 Layer2
-Rack1 Layer2 T-cp1 Layer2
-Rack1 Layer2 Tcp-1zeta Layer2
-Rack1 Layer2 Srp54k Layer2
-Rack1 Layer2 Tcp-1eta Layer2
-Rack1 Layer2 Srp19 Layer2
-Rack1 Layer2 CG8258 Layer2
-Rack1 Layer2 CG7033 Layer2
-Rack1 Layer2 Cct5 Layer2
-Rack1 Layer2 mRpS5 Layer2
-Rack1 Layer2 bonsai Layer2
-Rack1 Layer2 mRpS10 Layer2
-Rack1 Layer2 eIF3-S10 Layer2
-Rack1 Layer2 Tctp Layer2
-Rack1 Layer2 eIF3-S9 Layer2
-Rack1 Layer2 eIF-2gamma Layer2
-Rack1 Layer2 Trip1 Layer2
-Rack1 Layer2 RpS15Ab Layer2
-Rack1 Layer2 Tango7 Layer2
-Rack1 Layer2 RpS15Aa Layer2
-Rack1 Layer2 RpS10a Layer2
-Rack1 Layer2 RpS14a Layer2
-Rack1 Layer2 RpS10b Layer2
-Rack1 Layer2 RpL10Ab Layer2
-Rack1 Layer2 RpS8 Layer2
-Rack1 Layer2 RpL3 Layer2
-Rack1 Layer2 RpS11 Layer2
-Rack1 Layer2 RpS17 Layer2
-Rack1 Layer2 RpS2 Layer2
-Rack1 Layer2 RpS16 Layer2
-Rack1 Layer2 RpS13 Layer2
-Rack1 Layer2 RpS12 Layer2
-Rack1 Layer2 RpS4 Layer2
-Rack1 Layer2 RpS3A Layer2
-Rack1 Layer2 RpS3 Layer2
-Rack1 Layer2 RpS18 Layer2
-Rack1 Layer2 RpS23 Layer2
-Rack1 Layer2 RpS6 Layer2
-Rack1 Layer2 RpL12 Layer2
-Rack1 Layer2 RpL13 Layer2
-Rack1 Layer2 RpS27 Layer2
-Rack1 Layer2 RpL11 Layer2
-Rack1 Layer2 RpL18A Layer2
-Rack1 Layer2 RpL30 Layer2
-Rack1 Layer2 RpS7 Layer2
-Rack1 Layer2 RpLP2 Layer2
-Rack1 Layer2 RpL4 Layer2
-Rack1 Layer2 sta Layer2
-RpL11 Layer2 CG7033 Layer2
-RpL11 Layer2 vig Layer2
-RpL11 Layer2 eEF1delta Layer2
-RpL11 Layer2 Tcp-1eta Layer2
-RpL11 Layer2 mago Layer2
-RpL11 Layer2 eIF3-S10 Layer2
-RpL11 Layer2 CG10576 Layer2
-RpL11 Layer2 CG5642 Layer2
-RpL11 Layer2 bonsai Layer2
-RpL11 Layer2 mRpS10 Layer2
-RpL11 Layer2 Srp72 Layer2
-RpL11 Layer2 mRpS9 Layer2
-RpL11 Layer2 Tctp Layer2
-RpL11 Layer2 mRpL12 Layer2
-RpL11 Layer2 CG9769 Layer2
-RpL11 Layer2 Srp19 Layer2
-RpL11 Layer2 Srp54k Layer2
-RpL11 Layer2 Trip1 Layer2
-RpL11 Layer2 RpS15Aa Layer2
-RpL11 Layer2 RpS13 Layer2
-RpL11 Layer2 RpS18 Layer2
-RpL11 Layer2 RpL3 Layer2
-RpL11 Layer2 RpL12 Layer2
-RpL11 Layer2 RpS10b Layer2
-RpL11 Layer2 RpS17 Layer2
-RpL11 Layer2 RpL10Ab Layer2
-RpL11 Layer2 RpS10a Layer2
-RpL11 Layer2 RpL13 Layer2
-RpL11 Layer2 RpS4 Layer2
-RpL11 Layer2 RpS16 Layer2
-RpL11 Layer2 RpS12 Layer2
-RpL11 Layer2 RpS27 Layer2
-RpL11 Layer2 RpS2 Layer2
-RpL11 Layer2 RpS3 Layer2
-RpL11 Layer2 RpS6 Layer2
-RpL11 Layer2 RpS14a Layer2
-RpL11 Layer2 RpL4 Layer2
-RpL11 Layer2 sta Layer2
-Rpn12 Layer2 Ubqn Layer2
-Prosbeta7 Layer2 Uba2 Layer2
-Prosbeta7 Layer2 CG7033 Layer2
-Prosbeta7 Layer2 Uch-L5 Layer2
-Prosbeta7 Layer2 Rpt4R Layer2
-Prosbeta7 Layer2 Rpn5 Layer2
-Prosbeta7 Layer2 Rpt6 Layer2
-Prosbeta7 Layer2 Rpn12 Layer2
-RpS13 Layer2 CG10576 Layer2
-RpS13 Layer2 CG7033 Layer2
-RpS13 Layer2 mRpL12 Layer2
-RpS13 Layer2 ade3 Layer2
-RpS13 Layer2 mago Layer2
-RpS13 Layer2 CG5642 Layer2
-RpS13 Layer2 eIF3-S10 Layer2
-RpS13 Layer2 CG9769 Layer2
-RpS13 Layer2 Srp19 Layer2
-RpS13 Layer2 Srp54k Layer2
-RpS13 Layer2 Trip1 Layer2
-RpS13 Layer2 RpL10Ab Layer2
-RpS13 Layer2 RpS15Aa Layer2
-RpS13 Layer2 RpS12 Layer2
-RpS13 Layer2 RpS17 Layer2
-RpS13 Layer2 RpL12 Layer2
-RpS13 Layer2 RpS10b Layer2
-RpS13 Layer2 sta Layer2
-RpS13 Layer2 RpS16 Layer2
-RpS13 Layer2 RpS4 Layer2
-RpS13 Layer2 RpS6 Layer2
-RpS13 Layer2 RpS14a Layer2
-RpL18A Layer2 Tcp-1zeta Layer2
-RpL18A Layer2 vig Layer2
-RpL18A Layer2 CG7033 Layer2
-RpL18A Layer2 eEF1delta Layer2
-RpL18A Layer2 Tcp-1eta Layer2
-RpL18A Layer2 ade3 Layer2
-RpL18A Layer2 mRpS10 Layer2
-RpL18A Layer2 mago Layer2
-RpL18A Layer2 bonsai Layer2
-RpL18A Layer2 CG10576 Layer2
-RpL18A Layer2 CG5642 Layer2
-RpL18A Layer2 eIF3-S8 Layer2
-RpL18A Layer2 eIF3-S10 Layer2
-RpL18A Layer2 Srp72 Layer2
-RpL18A Layer2 eIF3-S9 Layer2
-RpL18A Layer2 Tctp Layer2
-RpL18A Layer2 Srp19 Layer2
-RpL18A Layer2 CG9769 Layer2
-RpL18A Layer2 Srp54k Layer2
-RpL18A Layer2 Trip1 Layer2
-RpL18A Layer2 RpS15Aa Layer2
-RpL18A Layer2 RpS14a Layer2
-RpL18A Layer2 sta Layer2
-RpL18A Layer2 RpL10Ab Layer2
-RpL18A Layer2 RpS2 Layer2
-RpL18A Layer2 RpL3 Layer2
-RpL18A Layer2 RpS17 Layer2
-RpL18A Layer2 RpL4 Layer2
-RpL18A Layer2 RpL13 Layer2
-RpL18A Layer2 RpS4 Layer2
-RpL18A Layer2 RpL11 Layer2
-RpL18A Layer2 RpS10a Layer2
-RpL18A Layer2 RpS6 Layer2
-RpL18A Layer2 RpL12 Layer2
-RpL18A Layer2 RpS18 Layer2
-RpL18A Layer2 RpS16 Layer2
-RpL18A Layer2 RpS12 Layer2
-RpL18A Layer2 RpS27 Layer2
-RpL18A Layer2 RpS10b Layer2
-RpL18A Layer2 RpS3 Layer2
-RpL18A Layer2 RpS13 Layer2
-RpS4 Layer2 Tcp-1zeta Layer2
-RpS4 Layer2 CG7033 Layer2
-RpS4 Layer2 mago Layer2
-RpS4 Layer2 bonsai Layer2
-RpS4 Layer2 RpS15Aa Layer2
-RpS4 Layer2 RpL12 Layer2
-RpS4 Layer2 sta Layer2
-RpS4 Layer2 RpS10b Layer2
-RpS4 Layer2 RpS14a Layer2
-RpS4 Layer2 RpS16 Layer2
-RpS4 Layer2 RpS6 Layer2
-RpS4 Layer2 RpS12 Layer2
-RpL4 Layer2 vig Layer2
-RpL4 Layer2 bonsai Layer2
-RpL4 Layer2 Cctgamma Layer2
-RpL4 Layer2 mRpS10 Layer2
-RpL4 Layer2 Tcp-1eta Layer2
-RpL4 Layer2 Tctp Layer2
-RpL4 Layer2 T-cp1 Layer2
-RpL4 Layer2 CG7033 Layer2
-RpL4 Layer2 Tcp-1zeta Layer2
-RpL4 Layer2 ade3 Layer2
-RpL4 Layer2 mRpS9 Layer2
-RpL4 Layer2 mRpL12 Layer2
-RpL4 Layer2 Srp72 Layer2
-RpL4 Layer2 mago Layer2
-RpL4 Layer2 Srp19 Layer2
-RpL4 Layer2 CG5642 Layer2
-RpL4 Layer2 CG10576 Layer2
-RpL4 Layer2 Srp54k Layer2
-RpL4 Layer2 CG9769 Layer2
-RpL4 Layer2 eIF3-S10 Layer2
-RpL4 Layer2 RpS15Aa Layer2
-RpL4 Layer2 RpS10a Layer2
-RpL4 Layer2 RpL10Ab Layer2
-RpL4 Layer2 Trip1 Layer2
-RpL4 Layer2 RpS16 Layer2
-RpL4 Layer2 RpS6 Layer2
-RpL4 Layer2 RpS12 Layer2
-RpL4 Layer2 RpS14a Layer2
-RpL4 Layer2 RpS2 Layer2
-RpL4 Layer2 RpL12 Layer2
-RpL4 Layer2 RpS27 Layer2
-RpL4 Layer2 sta Layer2
-RpL4 Layer2 RpS13 Layer2
-RpL4 Layer2 RpS10b Layer2
-RpL4 Layer2 RpS3 Layer2
-RpL4 Layer2 RpS17 Layer2
-RpL4 Layer2 RpL13 Layer2
-RpL4 Layer2 RpL3 Layer2
-RpL4 Layer2 RpS4 Layer2
diff --git a/www/data/networkDF.fst b/www/data/networkDF.fst
deleted file mode 100644
index 64de82c..0000000
Binary files a/www/data/networkDF.fst and /dev/null differ
diff --git a/www/favicon.ico b/www/favicon.ico
deleted file mode 100644
index 4f2e927..0000000
Binary files a/www/favicon.ico and /dev/null differ
diff --git a/www/images/logo.png b/www/images/logo.png
deleted file mode 100644
index ae471a8..0000000
Binary files a/www/images/logo.png and /dev/null differ
diff --git a/www/js/classes/Edge.js b/www/js/classes/Edge.js
deleted file mode 100644
index 89f4169..0000000
--- a/www/js/classes/Edge.js
+++ /dev/null
@@ -1,220 +0,0 @@
-class Edge {
- constructor({id = 0, source = "", target = "",
- colors = [EDGE_DEFAULT_COLOR], weights = [], channels = [],
- interLayer = false}) {
- this.THREE_Object = "";
- this.sourceNodeIndex = "";
- this.targetNodeIndex = "";
- this.sourceLayerIndex = "";
- this.targetLayerIndex = "";
-
- this.id = id;
- this.source = source;
- this.target = target;
- this.name = this.source.concat("---").concat(this.target);
- this.colors = colors;
- this.importedColors = colors;
- this.weights = weights;
- this.channels = channels;
- this.interLayer = interLayer;
-
- this.isSelected = false;
-
- this.initIndexVariables();
- this.drawEdge();
- }
-
- initIndexVariables() {
- this.sourceNodeIndex = nodeLayerNames.indexOf(this.source);
- this.targetNodeIndex = nodeLayerNames.indexOf(this.target);
- this.sourceLayerIndex = layerGroups[nodeGroups[nodeLayerNames[this.sourceNodeIndex]]];
- this.targetLayerIndex = layerGroups[nodeGroups[nodeLayerNames[this.targetNodeIndex]]];
- }
-
- drawEdge() {
- let points = this.decidePoints();
-
- if (this.channels.length === 0)
- this.createEdge(points);
- else
- this.createChannels(points);
-
- if (this.interLayer)
- scene.add(this.THREE_Object);
- else
- layers[this.sourceLayerIndex].addEdge(this.THREE_Object);
- }
-
- decidePoints() {
- let points = [];
-
- if (this.interLayer)
- points.push(nodeObjects[this.sourceNodeIndex].getWorldPosition(),
- nodeObjects[this.targetNodeIndex].getWorldPosition());
- else
- points.push(nodeObjects[this.sourceNodeIndex].getPosition(),
- nodeObjects[this.targetNodeIndex].getPosition());
-
- return(points)
- }
-
- createEdge(points) {
- let geometry, color, opacity, material;
-
- geometry = new THREE.BufferGeometry().setFromPoints(points);
- color = this.decideColor();
- opacity = this.decideOpacity();
- material = new THREE.LineBasicMaterial({
- color: color, alphaTest: 0.05, transparent: true, opacity: opacity
- });
-
- this.THREE_Object = new THREE.Line(geometry, material);
-
- if (isDirectionEnabled && (opacity !== 0))
- this.createArrow(points, color);
- }
-
- decideColor(i = 0, forExport = false) {
- let color = EDGE_DEFAULT_COLOR;
-
- if (!forExport && this.isSelected && selectedEdgeColorFlag)
- color = SELECTED_DEFAULT_COLOR;
- else if (this.channels.length > 0 && !edgeFileColorPriority)
- color = channelColors[this.channels[i]];
- else if (edgeFileColorPriority)
- color = this.importedColors[i];
-
- return(color)
- }
-
- decideOpacity(i = 0) {
- let opacity;
-
- if (edgeWidthByWeight)
- opacity = this.weights[i];
- else
- opacity = this.interLayer ? interLayerEdgeOpacity : intraLayerEdgeOpacity;
-
- return(opacity)
- }
-
- createArrow(points, arrowColor) {
- let THREE_arrowHelper, THREE_Group;
- THREE_arrowHelper = this.createArrowHelper(points, arrowColor);
- THREE_Group = new THREE.Group();
- THREE_Group.add(this.THREE_Object);
- THREE_Group.add(THREE_arrowHelper);
- this.THREE_Object = THREE_Group;
- }
-
- createArrowHelper(points, edgeColor) {
- let direction = points[1].clone().sub(points[0]),
- origin = points[1],
- length = 1, headLength, headWidth;
-
- headLength = this.interLayer ? interDirectionArrowSize : intraDirectionArrowSize;
- headLength = headLength * 6;
- headWidth = headLength / 4;
-
- return(new THREE.ArrowHelper(direction.normalize(), origin, length, edgeColor, headLength, headWidth))
- }
-
- // Channels ======
- createChannels(points) {
- let THREE_curveGroup = new THREE.Group(),
- verticalPushConstant, verticalPush, pushForce = 0, pushForceFlag = false, direction = 1,
- curveFactor = this.interLayer ? interChannelCurvature : intraChannelCurvature,
- color, opacity;
-
- verticalPushConstant = points[0].distanceTo(points[1]) * curveFactor / 400;
- if (this.channels.length % 2 == 0) // skip straight line
- pushForce = 1;
-
- for (let i = 0; i < this.channels.length; i++) {
- direction = -1 * direction; // flipping direction
-
- if (pushForceFlag)
- pushForce = pushForce + 1;
- pushForceFlag = !pushForceFlag; // flipping flag to increase pushForce next round
-
- verticalPush = direction * (verticalPushConstant * pushForce);
- color = this.decideColor(i);
- opacity = this.decideOpacity(i);
-
- THREE_curveGroup = this.createCurve(THREE_curveGroup, points[0], points[1],
- verticalPush, color, this.channels[i], opacity);
- }
-
- this.THREE_Object = THREE_curveGroup;
- }
-
- createCurve(curveGroup, p1, p2, verticalPush, color, channelName, opacity) {
- let p3 = p1.clone(), p4 = p2.clone(),
- curve, curvePoints, points = 50,
- curveLine, curveGeometry, curveMaterial;
-
- p3.addScalar(verticalPush);
- p4.addScalar(verticalPush);
-
- if (!this.interLayer)
- curve = new THREE.CubicBezierCurve3(p1, this.transformMiddlePointOnLayer(p3),
- this.transformMiddlePointOnLayer(p4), p2)
- else
- curve = new THREE.CubicBezierCurve3(p1, p3, p4, p2)
-
- curvePoints = curve.getPoints(points);
- curveGeometry = new THREE.BufferGeometry().setFromPoints(curvePoints);
- curveMaterial = new THREE.LineBasicMaterial({ color: color, alphaTest: 0.05, transparent: true, opacity: opacity });
- curveLine = new THREE.Line(curveGeometry, curveMaterial)
- curveLine.userData.tag = channelName;
- curveLine.visible = channelVisibility[channelName];
- curveGroup.add(curveLine);
-
- if (isDirectionEnabled && (opacity !== 0))
- curveGroup = this.createCurvedArrow(curveGroup, curvePoints, points, color, channelName)
-
- return curveGroup;
- }
-
- // This function helps place arrow lines onto the layer instead of through it
- transformMiddlePointOnLayer(point) {
- point.x = 0;
- return(point)
- }
-
- createCurvedArrow(curveGroup, curvePoints, points, color, channelName) {
- let arrowHelper = this.createArrowHelper(
- [curvePoints[points - 4], curvePoints[points - 1]],
- color
- );
- arrowHelper.userData.tag = channelName;
- arrowHelper.visible = channelVisibility[channelName];
- curveGroup.add(arrowHelper);
- return(curveGroup)
- }
-
- // On animate ======
- redrawEdge() {
- if (this.interLayer)
- scene.remove(this.THREE_Object);
- else
- layers[this.sourceLayerIndex].removeEdge(this.THREE_Object);
-
- if ((!this.interLayer) ||
- (this.interLayer && this.areLayersNotHidden()))
- this.drawEdge();
- }
-
- areLayersNotHidden() {
- return(layers[this.sourceLayerIndex].isVisible &&
- layers[this.targetLayerIndex].isVisible)
- }
-
- select() {
- this.isSelected = true;
- }
-
- deselect() {
- this.isSelected = false;
- }
-}
diff --git a/www/js/classes/Layer.js b/www/js/classes/Layer.js
deleted file mode 100644
index abbc72f..0000000
--- a/www/js/classes/Layer.js
+++ /dev/null
@@ -1,208 +0,0 @@
-class Layer {
- constructor({id = 1, name = "Layer1",
- position_x = 0, position_y = 0, position_z = 0,
- last_layer_scale = 1, rotation_x = 0, rotation_y = 0, rotation_z = 0,
- floor_current_color = LAYER_DEFAULT_COLOR, geometry_parameters_width = 2 * yBoundMax}) {
- this.plane = "";
- this.sphere = "";
-
- this.id = id;
- this.name = name;
- this.last_layer_scale = last_layer_scale;
- this.geometry_parameters_width = geometry_parameters_width;
- this.importedColor = floor_current_color;
- this.color = floor_current_color;
-
- this.showNodeLabels = false;
- this.isSelected = false;
- this.isVisible = true;
- this.coordSystem = ["", "", ""];
-
- this.createPlane(geometry_parameters_width, floor_current_color);
- this.appendCoordSystem();
- this.addLabelSphere(geometry_parameters_width, last_layer_scale);
- this.initTranslate(position_x, position_y, position_z);
- this.initRotate(rotation_x, rotation_y, rotation_z);
- this.initScale(last_layer_scale);
- }
-
- // inits
- createPlane(width, color) {
- let planeGeometry = new THREE.PlaneGeometry(width, width, PLANE_WIDTHSEGMENTS, PLANE_HEIGHTSEGMENTS);
- planeGeometry.rotateY(THREE.Math.degToRad(90));
- let planeMaterial = new THREE.MeshBasicMaterial({
- color: color,
- alphaTest: 0.05,
- wireframe: false,
- transparent: true,
- opacity: 0.6,
- side: THREE.DoubleSide,
- });
- this.plane = new THREE.Mesh(planeGeometry, planeMaterial);
- }
-
- appendCoordSystem() {
- this.coordSystem[0] = this.appendLine(150, 0, 0, "#FB3D2A") // red
- this.coordSystem[1] = this.appendLine(0, 150, 0, "#46FB2A") // green
- this.coordSystem[2] = this.appendLine(0, 0, 150, "#2AC2FB") // blue
- this.toggleCoords(false);
- }
-
- appendLine(x, y, z, color) {
- let points = [];
- points.push(this.plane.position, new THREE.Vector3(x, y, z) );
- points.push(this.plane.position, new THREE.Vector3(-x, -y, -z));
- let geometry = new THREE.BufferGeometry().setFromPoints(points);
- let material = new THREE.LineBasicMaterial({color: color});
- let line = new THREE.Line(geometry, material);
- this.plane.add(line);
- return(line)
- }
-
- addLabelSphere(width, scale) { // label will be attched here
- let threeSphere = this.createSphere();
- this.plane.add(threeSphere);
- threeSphere.translateY(-width / 2);
- threeSphere.translateZ(width / 2);
- threeSphere.position.y = threeSphere.position.y * scale
- threeSphere.position.z = threeSphere.position.z * scale
- this.sphere = threeSphere;
- }
-
- createSphere() {
- let geometry = new THREE.SphereGeometry();
- let material = new THREE.MeshBasicMaterial({
- color: "white", transparent: true, opacity: 0
- });
- let sphere = new THREE.Mesh(geometry, material);
- return(sphere);
- }
-
- initTranslate(x, y, z) {
- this.setPosition("x", x);
- this.setPosition("y", y);
- this.setPosition("z", z);
- }
-
- initRotate(x, y, z) {
- this.setRotation("x", x);
- this.setRotation("y", y);
- this.setRotation("z", z);
- }
-
- initScale(value) {
- this.plane.geometry.scale(1, Number(value), Number(value));
- }
-
- addNode(sphere) {
- this.plane.add(sphere);
- }
-
- addEdge(line) {
- this.plane.add(line);
- }
-
- removeEdge(line) {
- this.plane.remove(line);
- }
-
- // toggle functions
- toggleSelection() {
- this.isSelected = !this.isSelected;
- }
-
- toggleVisibility(flag) {
- this.plane.visible = flag;
- this.isVisible = flag;
- }
-
- toggleCoords(layerCoordsSwitch) {
- this.coordSystem[0].visible = this.coordSystem[1].visible =
- this.coordSystem[2].visible = layerCoordsSwitch;
- }
-
- toggleWireframe(flag) {
- this.plane.material.wireframe = flag;
- }
-
- // transformations
- translateX(x) {
- this.plane.translateX(x);
- }
-
- translateY(y) {
- this.plane.translateY(y);
- }
-
- translateZ(z) {
- this.plane.translateZ(z);
- }
-
- rotateX(x) {
- this.plane.rotateX(x);
- }
-
- rotateY(y) {
- this.plane.rotateY(y);
- }
-
- rotateZ(z) {
- this.plane.rotateZ(z);
- }
-
- // setters and getters
- getName() {
- return(this.name);
- }
-
- setPosition(axis, value) {
- this.plane.position[axis] = value;
- }
-
- getPosition(axis) {
- return(this.plane.position[axis]);
- }
-
- setRotation(axis, value) {
- this.plane.rotation[axis] = value;
- }
-
- getRotation(axis) {
- return(this.plane.rotation[axis]);
- }
-
- getWidth() {
- return(this.plane.geometry.parameters.width);
- }
-
- setScale(value) {
- let newScaleValue = parseFloat(value) / this.getScale();
- this.plane.geometry.scale(1, newScaleValue, newScaleValue);
- this.last_layer_scale = parseFloat(value);
- }
-
- getScale() {
- return(this.last_layer_scale);
- }
-
- setColor(color) {
- this.color = color;
- this.plane.material.color = new THREE.Color(color);
- }
-
- getColor() {
- let color = LAYER_DEFAULT_COLOR;
- if (this.isSelected) {
- if (layerColorPrioritySource == "default")
- color = this.importedColor;
- else if (layerColorPrioritySource == "picker")
- color = document.getElementById("floor_color").value;
- } else
- color = this.color;
- return(color);
- }
-
- setOpacity(value) {
- this.plane.material.opacity = value;
- }
-}
diff --git a/www/js/classes/Node.js b/www/js/classes/Node.js
deleted file mode 100644
index 5fc6943..0000000
--- a/www/js/classes/Node.js
+++ /dev/null
@@ -1,151 +0,0 @@
-class Node {
- constructor({id = 0, name = "", layer = "", nodeLayerName = "",
- position_x = 0, position_y = 0, position_z = 0,
- scale = 1, color = "#FFFFFF", url = "", descr = ""}) {
- this.sphere = "";
-
- this.id = id;
- this.name = name;
- this.layer = layer;
- this.nodeLayerName = nodeLayerName;
- this.color = color;
- this.importedColor = color;
- this.clusterColor = color;
- this.url = url;
- this.descr = descr;
-
- this.isSelected = false;
- this.showLabel= false;
- this.cluster = "";
-
- this.createSphere(color);
- this.initTranslate(position_x, position_y, position_z)
- this.setScale(scale);
- }
-
- createSphere(nodeColor) {
- let geometry = new THREE.SphereGeometry(SPHERE_RADIUS, SPHERE_WIDTHSEGMENTS, SPHERE_HEIGHTSEGMENTS);
- let material = new THREE.MeshStandardMaterial({
- color: nodeColor,
- transparent: true
- });
- this.sphere = new THREE.Mesh(geometry, material);
- }
-
- initTranslate(x, y, z) {
- this.setPosition("x", x);
- this.setPosition("y", y);
- this.setPosition("z", z);
- }
-
- // transformations
- translateX(x) {
- this.sphere.translateX(x);
- }
-
- translateY(y) {
- this.sphere.translateY(y);
- }
-
- translateZ(z) {
- this.sphere.translateZ(z);
- }
-
- // setters and getters
- getName() {
- return(this.name);
- }
-
- getLayer() {
- return(this.layer);
- }
-
- getNodeLayerName() {
- return(this.nodeLayerName);
- }
-
- getPosition(axis = "") {
- if (axis == "")
- return(this.sphere.position);
- else
- return(this.sphere.position[axis]);
- }
-
- getWorldPosition(axis = "") {
- if (axis == "")
- return(this.sphere.getWorldPosition(new THREE.Vector3()));
- else
- return(this.sphere.getWorldPosition(new THREE.Vector3())[axis]);
- }
-
- getOpacity() {
- return(this.sphere.material.opacity);
- }
-
- getColor() {
- let color = this.color;
- if (nodeColorPrioritySource == "default")
- color = this.importedColor;
- else if (nodeColorPrioritySource == "cluster")
- color = this.clusterColor;
- return(color);
- }
-
- getScale() {
- return(this.sphere.scale.x);
- }
-
- getCluster() {
- return(this.cluster);
- }
-
- setPosition(axis, value) {
- this.sphere.position[axis] = value;
- }
-
- setOpacity(value) {
- this.sphere.material.opacity = value;
- }
-
- setGeometry(type) {
- let geometry;
- switch(type) {
- case "sphere":
- geometry = new THREE.SphereGeometry(SPHERE_RADIUS, SPHERE_WIDTHSEGMENTS, SPHERE_HEIGHTSEGMENTS);
- break;
- case "box":
- geometry = new THREE.BoxGeometry(BOX_SIZE, BOX_SIZE, BOX_SIZE);
- break;
- case "diamond":
- geometry = new THREE.OctahedronGeometry(DIAMOND_RADIUS, 0);
- break;
- case "cone":
- geometry = new THREE.ConeGeometry(CONE_RADIUS, CONE_HEIGHT, CONE_RADIUS);
- break;
- default:
- console.error(`Unknown geometry type: ${type}`);
- return;
- }
- this.sphere.geometry.dispose();
- this.sphere.geometry = geometry;
- }
-
- setColor(hexColor, importMode = false, clusterMode = false) {
- this.sphere.material.color = new THREE.Color(hexColor);
- this.color = hexColor;
- if (importMode)
- this.importedColor = hexColor;
- if (clusterMode)
- this.clusterColor = hexColor;
- }
-
- setScale(value) {
- this.sphere.scale.x = this.sphere.scale.y =
- this.sphere.scale.z = value;
- }
-
- setCluster(clusterName) {
- this.cluster = clusterName;
- }
- }
-
\ No newline at end of file
diff --git a/www/js/classes/Scene.js b/www/js/classes/Scene.js
deleted file mode 100644
index 3d7eaa2..0000000
--- a/www/js/classes/Scene.js
+++ /dev/null
@@ -1,216 +0,0 @@
-class Scene {
- constructor() {
- this.reset();
- }
-
- reset() {
- this.THREE_Object = new THREE.Scene();
- this.pan = "";
- this.sphere = "";
- this.coordSystem = ["", "", ""];
- this.defaultColor = "#000000";
- this.intervalTimeout = "";
- this.autoRotate = false;
- this.dragging = false;
- this.leftClickPressed = false; // drag translating scene
- this.middleClickPressed = false; // drag rotating scene
- this.axisPressed = ""; // z, x, c
-
- this.addLights();
- this.addPan();
- this.addSphere();
- this.appendCoordSystem();
- }
-
- addLights() {
- this.add(this.createPointLight(1));
- this.add(this.createPointLight(-1));
- this.add(new THREE.AmbientLight(0xffffff));
- }
-
- createPointLight(orientation) {
- let sphereGeom = new THREE.SphereGeometry();
- let lightObject = new THREE.PointLight(0xffffff, 1, 2 * yBoundMax);
- lightObject.add(new THREE.Mesh(sphereGeom, new THREE.MeshBasicMaterial({color: 0xffffff})));
- lightObject.position.set(0, orientation * yBoundMax, 0);
- lightObject.rotateX(THREE.Math.degToRad(90));
- return(lightObject)
- }
-
- addPan() { // scene translations are applied on pan
- let threePan = this.createSphere();
- this.add(threePan);
- this.pan = threePan;
- }
-
- createSphere() {
- let geometry = new THREE.SphereGeometry();
- let material = new THREE.MeshBasicMaterial(
- {color: "white", transparent: true, opacity: 0}
- );
- let sphere = new THREE.Mesh(geometry, material);
- return(sphere);
- }
-
- add(threeObject) {
- this.THREE_Object.add(threeObject);
- }
-
- remove(threeObject) {
- this.THREE_Object.remove(threeObject);
- }
-
- addSphere() { // scene rotations are applied on sphere
- let threeSphere = this.createSphere();
- this.pan.add(threeSphere);
- this.sphere = threeSphere;
- }
-
- appendCoordSystem() {
- this.coordSystem[0] = this.appendLine(800, 0, 0, "#FB3D2A") // red
- this.coordSystem[1] = this.appendLine(0, 800, 0, "#46FB2A") // green
- this.coordSystem[2] = this.appendLine(0, 0, 800, "#2AC2FB") // blue
- }
-
- appendLine(x, y, z, color) {
- let points = [];
- points.push(this.getPointPosition(), new THREE.Vector3(x, y, z) );
- points.push(this.getPointPosition(), new THREE.Vector3(-x, -y, -z));
- let geometry = new THREE.BufferGeometry().setFromPoints(points);
- let material = new THREE.LineBasicMaterial({color: color});
- let line = new THREE.Line(geometry, material);
- this.sphere.add(line);
- return(line)
- }
-
- toggleCoords(sceneCoordsSwitch) {
- this.coordSystem[0].visible = this.coordSystem[1].visible =
- this.coordSystem[2].visible = sceneCoordsSwitch;
- }
-
- exists() {
- return(this.pan !== "");
- }
-
- tiltDefault() {
- this.setRotation("x", THREE.Math.degToRad(15));
- this.setRotation("y", THREE.Math.degToRad(15));
- this.setRotation("z", THREE.Math.degToRad(5));
- }
-
- addLayer(threePlane) {
- this.sphere.add(threePlane);
- }
-
- translateX(x) {
- this.pan.translateX(x);
- }
-
- translateY(y) {
- this.pan.translateY(y);
- }
-
- translatePanWithArrow(code) {
- if (code == 37) this.translateX(-25); //left
- if (code == 38) this.translateY(25); //up
- if (code == 39) this.translateX(25); //right
- if (code == 40) this.translateY(-25); // down
- }
-
- translatePanWithMouse(x, y) {
- this.translateX(x - mousePreviousX);
- this.translateY(mousePreviousY - y);
- }
-
- orbitSphereWithMouse(x, y) {
- let deltaMove = {
- x: x - mousePreviousX,
- y: y - mousePreviousY
- };
-
- let deltaRotationQuaternion = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(toRadians(deltaMove.y), toRadians(deltaMove.x), 0, 'XYZ'));
-
- this.setQuaternion(deltaRotationQuaternion);
- }
-
- setQuaternion(deltaRotationQuaternion) {
- this.sphere.quaternion.multiplyQuaternions(deltaRotationQuaternion,
- this.getQuaternion());
- }
-
- getQuaternion() {
- return(this.sphere.quaternion);
- }
-
- // direction 1 or -1
- rotate(direction, axis) {
- clearInterval(this.intervalTimeout);
- this.intervalTimeout = setInterval(function() {
- let value = document.getElementsByClassName("canvasSlider")[0].value;
- value = direction * THREE.Math.degToRad(value);
- if (axis == "X")
- scene.rotateX(value);
- else if (axis == "Y")
- scene.rotateY(value);
- else if (axis == "Z")
- scene.rotateZ(value);
- updateSceneSphereRShiny();
- updateVRLayerLabelsRShiny();
- updateVRNodesRShiny();
- }, 70);
- }
-
- rotateX(x) {
- this.sphere.rotateX(x);
- }
-
- rotateY(y) {
- this.sphere.rotateY(y);
- }
-
- rotateZ(z) {
- this.sphere.rotateZ(z);
- }
-
- recenter() {
- this.pan.position.x = this.pan.position.y = 0;
- }
-
- zoom(deltaY) {
- let tempScale = this.getScale();
- if (deltaY < 0 && tempScale < 2)
- tempScale = tempScale * 1.1;
- else if (deltaY > 0 && tempScale > 0.2)
- tempScale = tempScale * 0.9;
- this.setScale(tempScale);
- }
-
- getPosition(axis) {
- return(this.pan.position[axis]);
- }
-
- setPosition(axis, value) {
- this.pan.position[axis] = value;
- }
-
- getPointPosition() {
- return(this.sphere.position);
- }
-
- getRotation(axis) {
- return(this.sphere.rotation[axis]);
- }
-
- setRotation(axis, value) {
- this.sphere.rotation[axis] = value;
- }
-
- getScale() {
- return(this.pan.scale.x);
- }
-
- setScale(value) {
- this.pan.scale.set(value, value, value);
- }
-}
diff --git a/www/js/config/global_variables.js b/www/js/config/global_variables.js
deleted file mode 100644
index 35c3e31..0000000
--- a/www/js/config/global_variables.js
+++ /dev/null
@@ -1,70 +0,0 @@
-// final variables
-//screen
-const RAYCASTER = new THREE.Raycaster(),
- RAYVECTOR = new THREE.Vector3(),
- RAYDIR = new THREE.Vector3();
-
-// graphics
-const NODE_LABEL_DEFAULT_SIZE = "12px",
- SPHERE_RADIUS = 8,
- SPHERE_WIDTHSEGMENTS = 8,
- SPHERE_HEIGHTSEGMENTS = 8,
- BOX_SIZE = 12,
- DIAMOND_RADIUS = 10,
- CONE_RADIUS = 8,
- CONE_HEIGHT = 15,
- PLANE_WIDTHSEGMENTS = 8,
- PLANE_HEIGHTSEGMENTS = 8,
- LAYER_DEFAULT_COLOR = "#777777",
- SELECTED_LAYER_DEFAULT_COLOR = "#f7f43e",
- SELECTED_DEFAULT_COLOR = "#A3FF00",
- COLOR_VECTOR_280 = [
- "#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00", "#ffff33", "#999999",
- "#a65628", "#f781bf", "#14a9ad", "#4ca2f9", "#a4e43f", "#d298e2", "#6119d0",
- "#d2737d", "#c0a43c", "#f2510e", "#651be6", "#79806e", "#61da5e", "#cd2f00",
- "#9348af", "#01ac53", "#c5a4fb", "#996635", "#b11573", "#4bb473", "#75d89e",
- "#2f3f94", "#2f7b99", "#da967d", "#34891f", "#b0d87b", "#ca4751", "#7e50a8",
- "#c4d647", "#e0eeb8", "#11dec1", "#289812", "#566ca0", "#ffdbe1", "#2f1179",
- "#935b6d", "#916988", "#513d98", "#aead3a", "#9e6d71", "#4b5bdc", "#0cd36d",
- "#250662", "#cb5bea", "#228916", "#ac3e1b", "#df514a", "#539397", "#880977",
- "#f697c1", "#ba96ce", "#679c9d", "#c6c42c", "#5d2c52", "#48b41b", "#e1cf3b",
- "#5be4f0", "#57c4d8", "#a4d17a", "#225b80", "#be608b", "#96b00c", "#088baf",
- "#f158bf", "#e145ba", "#ee91e3", "#05d371", "#5426e0", "#4834d0", "#802234",
- "#6749e8", "#0971f0", "#8fb413", "#b2b4f0", "#c3c89d", "#c9a941", "#41d158",
- "#fb21a3", "#51aed9", "#5bb32d", "#807fb0", "#21538e", "#89d534", "#d36647",
- "#7fb411", "#0023b8", "#3b8c2a", "#986b53", "#f50422", "#983f7a", "#ea24a3",
- "#79352c", "#521250", "#c79ed2", "#d6dd92", "#e33e52", "#b2be57", "#fa06ec",
- "#1bb699", "#6b2e5f", "#64820f", "#1c2710", "#21538e", "#89d534", "#d36647",
- "#7fb411", "#0023b8", "#3b8c2a", "#986b53", "#f50422", "#983f7a", "#ea24a3",
- "#79352c", "#521250", "#c79ed2", "#d6dd92", "#e33e52", "#b2be57", "#fa06ec",
- "#1bb699", "#6b2e5f", "#64820f", "#1c2710", "#9cb64a", "#996c48", "#9ab9b7",
- "#06e052", "#e3a481", "#0eb621", "#fc458e", "#b2db15", "#aa226d", "#792ed8",
- "#73872a", "#520d3a", "#cefcb8", "#a5b3d9", "#7d1d85", "#c4fd57", "#f1ae16",
- "#8fe22a", "#ef6e3c", "#243eeb", "#1dc180", "#dd93fd", "#3f8473", "#e7dbce",
- "#421f79", "#7a3d93", "#635f6d", "#93f2d7", "#9b5c2a", "#15b9ee", "#0f5997",
- "#409188", "#911e20", "#1350ce", "#10e5b1", "#fff4d7", "#cb2582", "#ce00be",
- "#32d5d6", "#172320", "#608572", "#c79bc2", "#00f87c", "#77772a", "#6995ba",
- "#fc6b57", "#f07815", "#8fd883", "#060e27", "#96e591", "#21d52e", "#d00043",
- "#b47162", "#1ec227", "#4f0f6f", "#1d1d58", "#947002", "#bde052", "#e08c56",
- "#28fcfd", "#bb09b0", "#36486a", "#d02e29", "#1ae6db", "#3e464c", "#a84a8f",
- "#911e7e", "#3f16d9", "#0f525f", "#ac7c0a", "#b4c086", "#c9d730", "#30cc49",
- "#3d6751", "#fb4c03", "#640fc1", "#62c03e", "#d3493a", "#88aa0b", "#406df9",
- "#615af0", "#4be470", "#2a3434", "#4a543f", "#79bca0", "#a8b8d4", "#00efd4",
- "#7ad236", "#7260d8", "#1deaa7", "#06f43a", "#823c59", "#e3d94c", "#dc1c06",
- "#f53b2a", "#b46238", "#2dfff6", "#a82b89", "#1a8011", "#436a9f", "#1a806a",
- "#4cf09d", "#c188a2", "#67eb4b", "#b308d3", "#fc7e41", "#af3101", "#ff0650",
- "#71b1f4", "#a2f8a5", "#e23dd0", "#d3486d", "#00f7f9", "#474893", "#3cec35",
- "#1c65cb", "#5d1d0c", "#2d7d2a", "#ff3420", "#5cdd87", "#a259a4", "#e4ac44",
- "#1bede6", "#8798a4", "#d7790f", "#b2c24f", "#de73c2", "#d70a9c", "#25b670",
- "#88e9b8", "#c2b0e2", "#86e98f", "#ae90e2", "#1a806b", "#436a9e", "#0ec0ff",
- "#f812b3", "#b17fc9", "#8d6c2f", "#d3277a", "#2ca1ae", "#9685eb", "#8a96c6",
- "#dba2e6", "#76fc1b", "#608fa4", "#20f6ba", "#07d7f6", "#dce77a", "#77ecca"
- ];
-
-// initialized once from R
-let MAX_LAYERS = "",
- MAX_EDGES = "",
- MAX_CHANNELS = "",
- EDGE_DEFAULT_COLOR = "",
- CHANNEL_COLORS_LIGHT = [],
- CHANNEL_COLORS_DARK = [];
diff --git a/www/js/config/static_variables.js b/www/js/config/static_variables.js
deleted file mode 100644
index 4885f6d..0000000
--- a/www/js/config/static_variables.js
+++ /dev/null
@@ -1,74 +0,0 @@
-// class objects
-let scene, layers = [], nodeObjects = [], edgeObjects = [];
-
-// screen
-let xBoundMin, xBoundMax, yBoundMin, yBoundMax, zBoundMin, zBoundMax,
- camera, renderer, mousePreviousX = 0, mousePreviousY = 0, animationRunning = false;
-
-// layouts
-let perLayerLayoutFLag = undefined,
- localLayoutFlag = false;
-
-// labels
-let globalLabelColor = "#ffffff",
- layerLabelsDivs = [],
- renderLayerLabelsFlag = false,
- showAllLayerLabelsFlag = true,
- showSelectedLayerLabelsFlag = false,
- nodeLabelsDivs = [],
- renderNodeLabelsFlag = false,
- showAllNodeLabelsFlag = false,
- showSelectedNodeLabelsFlag = true;
-
-// layers
-let layerGroups = new Map(),
- lastHoveredLayerIndex = "", // for correct hover coloring
- hoveredLayerPaintedFlag = false,
- layerDragControls = "",
- layerIntervalTimeout = "",
- layerColorPrioritySource = "default"; // or "picker"
-
-// nodes
-let nodeIntervalTimeout = "",
- selectedNodeColorFlag = true,
- nodeColorPrioritySource = "default", // or "cluster"
- nodeGroups = new Map(),
- last_hovered_node_index = "",
- nodeLayerNames = [], // keeping this for performance
- nodeNames = []; // to init upload network nodes
-
-// edges
-let renderInterLayerEdgesFlag = false,
- waitEdgeRenderFlag = true,
- interEdgesRemoved = false,
- selectedEdgeColorFlag = true,
- edgeFileColorPriority = false,
- isDirectionEnabled = false,
- edgeWidthByWeight = true,
- interLayerEdgeOpacity = 0.4,
- intraLayerEdgeOpacity = 1,
- interDirectionArrowSize = 5,
- intraDirectionArrowSize = 5,
- interChannelCurvature = 5,
- intraChannelCurvature = 15,
- edgePairs = [], // keeping for faster recursive node/edge selections
- edgePairs_source = [], // to init upload/import edge source nodeLayer names
- edgePairs_target = [], // to init upload/import edge target nodeLayer names
- edgeValues = [], // to init upload/import edge weights
- edgeColors = [], // to init upload/import edge weights
- // channels
- channels = [],
- selectedChannels = [], // selected ones from Layout tab
- channelColors = {},
- channelVisibility = {},
- edgeChannels = []; // to init upload/import edge channels
-
-// others
-let interLayerEdgesRenderPauseFlag = false,
- canvasControlsAttached = false,
- fps = 30,
- shiftX = "",
- shiftY = "",
- lasso = "",
- optionsList = "",
- downstreamCheckedNodes = []; // for recursive 3rd option of onRightClick on node
diff --git a/www/js/event_listeners.js b/www/js/event_listeners.js
deleted file mode 100644
index 066cca1..0000000
--- a/www/js/event_listeners.js
+++ /dev/null
@@ -1,240 +0,0 @@
-// on mouse wheel scroll
-const sceneZoom = async (event) => {
- if (scene.exists()) {
- scene.zoom(event.deltaY);
-
- await delay_ms(100);
- updateScenePanRShiny();
- updateVRLayerLabelsRShiny();
- updateVRNodesRShiny();
- }
-};
-
-// on arrow keys press or axis select to rotate layers
-const keyPressed = (event) => {
- if (scene.exists()) {
- let code = event.keyCode;
- if (code == 90)
- scene.axisPressed = 'z';
- else if (code == 88)
- scene.axisPressed = 'x';
- else if (code == 67)
- scene.axisPressed = 'c';
- else if (code == 37 || code == 38 || code == 39 || code == 40) {
- scene.translatePanWithArrow(code);
-
- updateScenePanRShiny();
- updateVRLayerLabelsRShiny();
- updateVRNodesRShiny();
- }
- }
-};
-
-const axisRelease = () => {
- if (scene.exists())
- scene.axisPressed = "";
-};
-
-// mouse keys press event
-const clickDown = (event) => {
- if (scene.exists()) {
- //mouse: 0 left, 1 middle, 2 right click
- if (event.button == 0) {
- scene.leftClickPressed = true;
- scene.middleClickPressed = false;
- if (event.shiftKey && shiftX === "") {
- shiftX = event.layerX - xBoundMax;
- shiftY = yBoundMax - event.layerY; // used in drag
- }
- } else if (event.button == 1) {
- scene.middleClickPressed = true;
- scene.leftClickPressed = false;
- } else {
- scene.middleClickPressed = false;
- scene.leftClickPressed = false;
- }
- }
-};
-
-// while mouse button held, drag event
-const clickDrag = (event) => {
- if (scene.exists()) {
- let distance = Math.sqrt(
- Math.pow(mousePreviousX - event.screenX, 2) + Math.pow(mousePreviousY - event.screenY, 2)
- );
-
- if (distance > 10) {
- let x = event.screenX, y = event.screenY;
-
- if (scene.leftClickPressed) {
- let selectedNodePositions = getSelectedNodes();
-
- scene.dragging = true;
- if (event.shiftKey) {
- lastHoveredLayerIndex = ""; // to be able to lasso inside layer
- lassoSelectNodes(event.layerX - xBoundMax, yBoundMax - event.layerY);
- } else if (scene.axisPressed !== "" && selectedNodePositions.length > 0)
- translateNodesWithHeldKey(event);
- else if (scene.axisPressed !== "")
- rotateLayersWithHeldKey(event);
- else if (lastHoveredLayerIndex === "" && last_hovered_node_index === "")
- sceneDragPan(x, y);
- } else if (scene.middleClickPressed) {
- scene.dragging = true;
- event.preventDefault();
- sceneOrbit(x, y);
- }
-
- mousePreviousX = x;
- mousePreviousY = y;
- }
-
- if (!scene.leftClickPressed && !scene.middleClickPressed)
- if (!checkHoverOverNode(event))
- checkHoverOverLayer(event);
- }
-};
-
-// left-click drag on scene
-const sceneDragPan = async (x, y) => {
- scene.translatePanWithMouse(x, y);
-
- await delay_ms(100);
- updateScenePanRShiny();
- updateVRLayerLabelsRShiny();
- updateVRNodesRShiny();
-};
-
-// middle-click drag on scene
-const sceneOrbit = async (x, y) => {
- scene.orbitSphereWithMouse(x, y);
-
- await delay_ms(100);
- updateSceneSphereRShiny();
- updateVRLayerLabelsRShiny();
- updateVRNodesRShiny();
-};
-
-const clickUp = (event) => {
- if (scene.exists()) {
- scene.dragging = false;
- if (event.button == 0) {
- scene.leftClickPressed = false;
- if (optionsList !== "") {
- document.getElementById("labelDiv").removeChild(optionsList);
- optionsList = "";
- }
- if (lasso !== "") {
- for (let i = 0; i < nodeObjects.length; i++) {
- if (nodeObjects[i].getOpacity() == 0.5) { // means is inside lasso event
- nodeObjects[i].setOpacity(1);
- nodeObjects[i].isSelected = true;
- repaintNode(i);
- }
- }
- decideNodeLabelFlags();
- updateSelectedNodesRShiny();
- }
-
- shiftX = "";
- shiftY = "";
- scene.remove(lasso);
- lasso = "";
- } else if (event.button == 1)
- scene.middleClickPressed = false;
- }
-};
-
-// double click event (left mouse), select node -> select layer -> unselect all nodes
-const dblClick = (event) => {
- if (scene.exists()) {
- // Priorities:
- if (!performDoubleClickNodeSelection(event)) { // 1. select node
- if (lastHoveredLayerIndex !== "") { // 2. select layer
- performDoubleClickLayerSelection();
- } else { // 3. unselect all nodes
- unselectAllNodes();
- unselectAllEdges();
-
- redrawIntraLayerEdges();
- updateSelectedEdgesRShiny();
- }
- }
- }
-};
-
-// right mouse click on node
-const replaceContextMenuOverNode = (event) => {
- if (optionsList !== "") {
- document.getElementById("labelDiv").removeChild(optionsList);
- optionsList = "";
- }
-
- for (let i = 0; i < nodeObjects.length; i++) {
- let nodeX = xBoundMax + nodeObjects[i].getWorldPosition("x"),
- nodeY = yBoundMax - nodeObjects[i].getWorldPosition("y");
-
- if (rightClickedOnNode(nodeX, nodeY, event)) {
- let option;
-
- event.preventDefault();
-
- // creating list and appending to 3d-graph div
- optionsList = document.createElement("select");
- optionsList.setAttribute('class', 'optionsBox');
- optionsList.setAttribute('id', 'mySelect');
- optionsList.setAttribute('onchange', 'executeCommand(this)');
- optionsList.style.left = nodeX.toString().concat("px");
- optionsList.style.top = nodeY.toString().concat("px");
- optionsList.style.display = "inline-block";
-
- // Do nothing
- option = document.createElement("option");
- option.value = "";
- option.text = "-";
- optionsList.appendChild(option);
-
- // Neighbors
- option = document.createElement("option");
- option.value = i;
- option.text = "Select Neighbors";
- optionsList.appendChild(option);
-
- // MultiLayer Path
- option = document.createElement("option");
- option.value = i;
- option.text = "Select MultiLayer Path";
- optionsList.appendChild(option);
-
- // Downstream Path
- option = document.createElement("option");
- option.value = i;
- option.text = "Select Downstream Path";
- optionsList.appendChild(option);
-
- // Link
- if (nodeObjects[i].url != "") {
- option = document.createElement("option");
- option.value = nodeObjects[i].url;
- option.text = "Link";
- optionsList.appendChild(option);
- }
-
- // Description
- if (nodeObjects[i].descr != "") {
- option = document.createElement("option");
- option.value = nodeObjects[i].descr;
- option.text = "Description";
- optionsList.appendChild(option);
- }
-
- document.getElementById("labelDiv").appendChild(optionsList);
-
- break;
- }
- }
-};
-
-const rightClickedOnNode = (nodeX, nodeY, event) => {
- return(Math.pow(nodeX - event.layerX, 2) + Math.pow(nodeY - event.layerY, 2) <= Math.pow((SPHERE_RADIUS + 1), 2))
-};
diff --git a/www/js/general.js b/www/js/general.js
deleted file mode 100644
index 57f0fc3..0000000
--- a/www/js/general.js
+++ /dev/null
@@ -1,41 +0,0 @@
-const exists = (array, element) => {
- return(array.some(l => l == element))
-};
-
-const getUniqueValues = (array) => {
- return([...new Set(array)])
-};
-
-const getCaseInsensitiveIndices = (array, element) => {
- let indexes = [];
- element = element.toLowerCase(); //case insensitive
- for (let i = 0; i < array.length; i++)
- if (array[i].toLowerCase() === element)
- indexes.push(i);
-
- return(indexes)
-};
-
-// @param array (object): object Array
-// @param uuid (string): unique object id
-// @return index (int): returned position of uuid in object array
-const findIndexByUuid = (array, uuid) => {
- const index = array.findIndex(object => {
- return(object.uuid === uuid)
- });
-
- return(index)
-};
-
-// random float between two values
-const getRandomArbitrary = (min, max) => {
- return(Math.random() * (max - min) + min)
-};
-
-const toRadians = (angle) => {
- return(angle * (Math.PI / 180))
-};
-
-// needed to correctly recenter network for VR
-// and for zoom in/out inter edge rendering
-const delay_ms = ms => new Promise(res => setTimeout(res, ms));
diff --git a/www/js/object_actions/canvas_controls.js b/www/js/object_actions/canvas_controls.js
deleted file mode 100644
index 840c064..0000000
--- a/www/js/object_actions/canvas_controls.js
+++ /dev/null
@@ -1,510 +0,0 @@
-
-const attachCanvasControls = () => {
- attachNavControlButtons();
- attachNavControlTable();
-
- implementSliderFunctionalities();
- implementButtonFunctionalities();
-
- canvasControlsAttached = true;
-};
-
-const attachNavControlButtons = () => {
- let interLayerEdgesRenderPauseButton = document.createElement("button"),
- showNavControlButton = document.createElement("button"),
- navControlButtonsDiv = document.getElementById("navControlButtonsDiv");
-
- interLayerEdgesRenderPauseButton.id = "interLayerEdgesRenderPauseButton";
- interLayerEdgesRenderPauseButton.className = "displayCanvasControls";
- interLayerEdgesRenderPauseButton.innerHTML = "Stop:Render Inter-Layer Edges";
- interLayerEdgesRenderPauseButton.style.position = "fixed";
- interLayerEdgesRenderPauseButton.style.zIndex = 1;
- interLayerEdgesRenderPauseButton.onclick = toggleInterLayerEdgesRendering;
-
- showNavControlButton.id = "displayCanvasControlsButton";
- showNavControlButton.className = "displayCanvasControls";
- showNavControlButton.innerHTML = "Navigation Controls";
- showNavControlButton.style.position = "fixed";
- showNavControlButton.style.zIndex = 1;
- showNavControlButton.style.marginTop = "25px";
- showNavControlButton.onclick = displayControlTable;
-
- navControlButtonsDiv.appendChild(interLayerEdgesRenderPauseButton);
- navControlButtonsDiv.appendChild(document.createElement("br"));
- navControlButtonsDiv.appendChild(showNavControlButton);
-};
-
-const displayControlTable = () => {
- let infoTable = document.getElementById("info");
-
- if (infoTable.style.display == "inline-block")
- infoTable.style.display = "none";
- else
- infoTable.style.display = "inline-block";
-};
-
-const attachNavControlTable = () => {
- let tr, td, tbdy = document.createElement('tbody'),
- btn = document.createElement("button"),
- tbl = document.createElement('table'),
- info = document.getElementById("info");
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '4');
- td.innerHTML = "Scene ";
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '4');
- td.innerHTML = "Rotation Controls";
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '2');
- td.innerHTML = "Angle: 5".concat("˚");
- td.id = "sliderValue1";
- td.setAttribute('class', 'labelDrop');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusX');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusX');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('rowSpan', '3');
- td.setAttribute('colSpan', '2');
- sld = document.createElement("input");
- sld.className = "canvasSlider raiseSlider";
- sld.type = "range";
- sld.min = "1";
- sld.max = "15";
- sld.value = "5";
- sld.step = "1";
- td.appendChild(sld);
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusY');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusY');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusZ');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusZ');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '4');
-
- btn.id = "recenterButton";
- btn.onclick = recenterNetwork;
- btn.style.color = "black";
- btn.innerHTML = "Recenter Network";
- td.appendChild(btn);
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- tr.setAttribute('class', 'border_tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '4');
- td.innerHTML = "Layers ";
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '4');
- td.innerHTML = "Rotation Controls";
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '2');
- td.innerHTML = "Angle: 5".concat("˚");
- td.id = "sliderValue2";
- td.setAttribute('class', 'labelDrop');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusX');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusX');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('rowSpan', '3');
- td.setAttribute('colSpan', '2');
- sld = document.createElement("input");
- sld.className = "canvasSlider raiseSlider";
- sld.type = "range";
- sld.min = "1";
- sld.max = "15";
- sld.value = "5";
- sld.step = "1";
- td.appendChild(sld);
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusY');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusY');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusZ');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusZ');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '4');
- td.innerHTML = "Translation Controls";
- tr.appendChild(td);
- tbdy.appendChild(tr);
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '2');
- td.setAttribute('class', 'canvasControls image_expandLayers');
- tr.appendChild(td);
-
- td = document.createElement('td');
- td.setAttribute('colSpan', '2');
- td.setAttribute('class', 'canvasControls image_collapseLayers');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '2');
- td.innerHTML = "Step: 25";
- td.id = "sliderValue3";
- td.setAttribute('class', 'labelDrop');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusX_T');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusX_T');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('rowSpan', '3');
- td.setAttribute('colSpan', '2');
- sld = document.createElement("input");
- sld.className = "canvasSlider raiseSlider";
- sld.type = "range";
- sld.min = "5";
- sld.max = "50";
- sld.value = "25";
- sld.step = "5";
- td.appendChild(sld);
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusY_T');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusY_T');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusZ_T');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusZ_T');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.innerHTML = "Scale";
- tr.appendChild(td);
-
- td = document.createElement('td');
- td.setAttribute('colSpan', '2');
- sld = document.createElement("input");
- sld.className = "canvasSlider";
- sld.id = "layerScaleSlider";
- sld.type = "range";
- sld.min = 0.2;
- sld.max = 5;
- sld.value = 1;
- sld.step = 0.1;
- td.appendChild(sld);
- tr.appendChild(td);
-
- td = document.createElement('td');
- td.innerHTML = "x1";
- td.id = "sliderValue4";
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- tr.setAttribute('class', 'border_tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '4');
- td.innerHTML = "Nodes ";
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '4');
- td.innerHTML = "Translation Controls";
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '2');
- td.setAttribute('class', 'canvasControls image_nodeExpand');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('colSpan', '2');
- td.setAttribute('class', 'canvasControls image_nodeCollapse');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('colSpan', '2');
- td.innerHTML = "Step: 25";
- td.id = "sliderValue5";
- td.setAttribute('class', 'labelDrop');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusX_T');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusX_T');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('rowSpan', '3');
- td.setAttribute('colSpan', '2');
- sld = document.createElement("input");
- sld.className = "canvasSlider raiseSlider";
- sld.type = "range";
- sld.min = "5";
- sld.max = "50";
- sld.value = "25";
- sld.step = "5";
- td.appendChild(sld);
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusY_T');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusY_T');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_minusZ_T');
- tr.appendChild(td);
- td = document.createElement('td');
- td.setAttribute('class', 'canvasControls image_plusZ_T');
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- tr = document.createElement('tr');
- td = document.createElement('td');
- td.innerHTML = "Scale";
- tr.appendChild(td);
-
- td = document.createElement('td');
- td.setAttribute('colSpan', '2');
- sld = document.createElement("input");
- sld.className = "canvasSlider";
- sld.id = "nodeScaleSlider";
- sld.type = "range";
- sld.min = 0.2;
- sld.max = 5;
- sld.value = 1;
- sld.step = 0.1;
- td.appendChild(sld);
- tr.appendChild(td);
-
- td = document.createElement('td');
- td.innerHTML = "x1";
- td.id = "sliderValue6";
- tr.appendChild(td);
- tbdy.appendChild(tr);
-
- // controls table
- tbl.id = "canvasControls_table";
- tbl.appendChild(tbdy);
-
- // info div
- info.innerHTML = `1. Zoom : Mouse Wheel
- 2. Pan : Click Drag Scene / Arrow Keys
- 3. Orbit : Mouse Middle Drag
- 4. Drag Layer : Click Drag
- 5. Rotate Layer : Z / X / C + Click Drag
- 6. Move Selected Nodes : Z / C + Click Drag
- 7. Node/Layer Selection : Double Click
- 8. Lasso Nodes : Shift + Click Drag
- 9. Unselect All Nodes : Double Click Scene`;
- info.style.display = "inline-block";
- info.appendChild(tbl);
-};
-
-const recenterNetwork = async () => {
- if (scene.exists()) {
- scene.recenter();
-
- await delay_ms(100);
- updateScenePanRShiny();
- updateVRLayerLabelsRShiny();
- updateVRNodesRShiny();
- }
-};
-
-const implementSliderFunctionalities = () => {
- let cavnasSliders = document.getElementsByClassName("canvasSlider");
-
- cavnasSliders[0].oninput = sliderSceneRotate;
- cavnasSliders[1].oninput = sliderLayerRotate;
- cavnasSliders[2].oninput = sliderLayerTranslate;
- cavnasSliders[3].oninput = scaleLayers;
- cavnasSliders[4].oninput = sliderNodeTranslate;
- cavnasSliders[5].oninput = scaleNodes;
-};
-
-const sliderSceneRotate = () => {
- let cavnasSlider = document.getElementsByClassName("canvasSlider")[0],
- td = document.getElementById("sliderValue1");
-
- td.innerHTML = "Angle: ".concat(cavnasSlider.value).concat("˚");
-};
-
-const sliderLayerRotate = () => {
- let cavnasSlider = document.getElementsByClassName("canvasSlider")[1],
- td = document.getElementById("sliderValue2");
-
- td.innerHTML = "Angle: ".concat(cavnasSlider.value).concat("˚");
-};
-
-const sliderLayerTranslate = () => {
- let cavnasSlider = document.getElementsByClassName("canvasSlider")[2],
- td = document.getElementById("sliderValue3");
-
- td.innerHTML = "Step: ".concat(cavnasSlider.value);
-};
-
-const sliderNodeTranslate = () => {
- let cavnasSliders = document.getElementsByClassName("canvasSlider")[4],
- td = document.getElementById("sliderValue5");
-
- td.innerHTML = "Step: ".concat(cavnasSliders.value);
-};
-
-const implementButtonFunctionalities = () => {
- let cavnasButtons = document.getElementsByClassName("canvasControls");
-
- cavnasButtons[0].addEventListener("mousedown", function() { scene.rotate(-1, "X") });
- cavnasButtons[0].onmousemove = cavnasButtons[0].onmouseup = mouseUpClearScene;
- cavnasButtons[1].addEventListener("mousedown", function() { scene.rotate(1, "X") });
- cavnasButtons[1].onmousemove = cavnasButtons[1].onmouseup = mouseUpClearScene;
- cavnasButtons[2].addEventListener("mousedown", function() { scene.rotate(-1, "Y") });
- cavnasButtons[2].onmousemove = cavnasButtons[2].onmouseup = mouseUpClearScene;
- cavnasButtons[3].addEventListener("mousedown", function() { scene.rotate(1, "Y") });
- cavnasButtons[3].onmousemove = cavnasButtons[3].onmouseup = mouseUpClearScene;
- cavnasButtons[4].addEventListener("mousedown", function() { scene.rotate(-1, "Z") });
- cavnasButtons[4].onmousemove = cavnasButtons[4].onmouseup = mouseUpClearScene;
- cavnasButtons[5].addEventListener("mousedown", function() { scene.rotate(1, "Z") });
- cavnasButtons[5].onmousemove = cavnasButtons[5].onmouseup = mouseUpClearScene;
- cavnasButtons[6].addEventListener("mousedown", function() { rotateSelectedLayers(-1, "X") });
- cavnasButtons[6].onmousemove = cavnasButtons[6].onmouseup = mouseUpClear;
- cavnasButtons[7].addEventListener("mousedown", function() { rotateSelectedLayers(1, "X") });
- cavnasButtons[7].onmousemove = cavnasButtons[7].onmouseup = mouseUpClear;
- cavnasButtons[8].addEventListener("mousedown", function() { rotateSelectedLayers(-1, "Y") });
- cavnasButtons[8].onmousemove = cavnasButtons[8].onmouseup = mouseUpClear;
- cavnasButtons[9].addEventListener("mousedown", function() { rotateSelectedLayers(1, "Y") });
- cavnasButtons[9].onmousemove = cavnasButtons[9].onmouseup = mouseUpClear;
- cavnasButtons[10].addEventListener("mousedown", function() { rotateSelectedLayers(-1, "Z") });
- cavnasButtons[10].onmousemove = cavnasButtons[10].onmouseup = mouseUpClear;
- cavnasButtons[11].addEventListener("mousedown", function() { rotateSelectedLayers(1, "Z") });
- cavnasButtons[11].onmousemove = cavnasButtons[11].onmouseup = mouseUpClear;
- cavnasButtons[12].addEventListener("click", function() { spreadLayers(1) });
- cavnasButtons[13].addEventListener("click", function() { spreadLayers(-1) });
- cavnasButtons[14].addEventListener("mousedown", function() { moveLayers(-1, "X") });
- cavnasButtons[14].onmousemove = cavnasButtons[14].onmouseup = mouseUpClear;
- cavnasButtons[15].addEventListener("mousedown", function() { moveLayers(1, "X") });
- cavnasButtons[15].onmousemove = cavnasButtons[15].onmouseup = mouseUpClear;
- cavnasButtons[16].addEventListener("mousedown", function() { moveLayers(-1, "Y") });
- cavnasButtons[16].onmousemove = cavnasButtons[16].onmouseup = mouseUpClear;
- cavnasButtons[17].addEventListener("mousedown", function() { moveLayers(1, "Y") });
- cavnasButtons[17].onmousemove = cavnasButtons[17].onmouseup = mouseUpClear;
- cavnasButtons[18].addEventListener("mousedown", function() { moveLayers(-1, "Z") });
- cavnasButtons[18].onmousemove = cavnasButtons[18].onmouseup = mouseUpClear;
- cavnasButtons[19].addEventListener("mousedown", function() { moveLayers(1, "Z") });
- cavnasButtons[19].onmousemove = cavnasButtons[19].onmouseup = mouseUpClear;
- cavnasButtons[20].addEventListener("click", function() { spreadNodes(1.1) });
- cavnasButtons[21].addEventListener("click", function() { spreadNodes(0.9) });
- cavnasButtons[22].addEventListener("mousedown", function() { moveNodes(-1, "X") });
- cavnasButtons[22].onmousemove = cavnasButtons[22].onmouseup = mouseUpClear;
- cavnasButtons[23].addEventListener("mousedown", function() { moveNodes(1, "X") });
- cavnasButtons[23].onmousemove = cavnasButtons[23].onmouseup = mouseUpClear;
- cavnasButtons[24].addEventListener("mousedown", function() { moveNodes(-1, "Y") });
- cavnasButtons[24].onmousemove = cavnasButtons[24].onmouseup = mouseUpClear;
- cavnasButtons[25].addEventListener("mousedown", function() { moveNodes(1, "Y") });
- cavnasButtons[25].onmousemove = cavnasButtons[25].onmouseup = mouseUpClear;
- cavnasButtons[26].addEventListener("mousedown", function() { moveNodes(-1, "Z") });
- cavnasButtons[26].onmousemove = cavnasButtons[26].onmouseup = mouseUpClear;
- cavnasButtons[27].addEventListener("mousedown", function() { moveNodes(1, "Z") });
- cavnasButtons[27].onmousemove = cavnasButtons[27].onmouseup = mouseUpClear;
-};
-
-const mouseUpClearScene = () => {
- if (!scene.autoRotate)
- clearInterval(scene.intervalTimeout);
-};
-
-const mouseUpClear = () => {
- clearInterval(layerIntervalTimeout);
- clearInterval(nodeIntervalTimeout);
-};
diff --git a/www/js/object_actions/edge.js b/www/js/object_actions/edge.js
deleted file mode 100644
index bc64e0f..0000000
--- a/www/js/object_actions/edge.js
+++ /dev/null
@@ -1,380 +0,0 @@
-// Initialization ======
-const assignChannelColorsFromPalette = (palette = CHANNEL_COLORS_LIGHT) => {
- for (let i = 0; i < channels.length; i++)
- channelColors[channels[i]] = palette[i];
-};
-
-const createEdgeObjects = () => {
- let tempEdgeColors, channels = [], interLayer;
-
- for (let i = 0; i < edgePairs.length; i++) {
- tempEdgeColors = decideEdgeColors(i);
- if (edgeChannels[i])
- channels = edgeChannels[i];
- interLayer = decideEdgeLayerType(i);
-
- edgeObjects.push(new Edge({id: i, source: edgePairs_source[i], target: edgePairs_target[i],
- colors: tempEdgeColors, weights: edgeValues[i], channels: channels, interLayer: interLayer}));
- }
-
- // releasing ram
- edgeValues = edgePairs_source = edgePairs_target =
- edgeChannels = edgeColors = undefined;
-};
-
-const decideEdgeColors = (i) => {
- let tempEdgeColors = [EDGE_DEFAULT_COLOR];
-
- if (edgeChannels && edgeChannels[i]) {
- tempEdgeColors = [];
- for (let j = 0; j < edgeChannels[i].length; j++) {
- if (edgeColors[i][j] !== [""])
- tempEdgeColors.push(edgeColors[i][j]);
- else
- tempEdgeColors.push(channelColors[edgeChannels[i][j]]);
- }
- } else if (edgeColors[i][0] !== "")
- tempEdgeColors[0] = edgeColors[i][0];
-
- return(tempEdgeColors)
-};
-
-const decideEdgeLayerType = (i) => {
- let index1, index2, interLayer = false;
-
- index1 = nodeLayerNames.indexOf(edgePairs_source[i]);
- index2 = nodeLayerNames.indexOf(edgePairs_target[i]);
- if (nodeGroups[nodeLayerNames[index1]] !== nodeGroups[nodeLayerNames[index2]])
- interLayer = true;
-
- return(interLayer)
-};
-
-// Components attached during initialization
-const attachChannelLayoutList = () => {
- let container = document.getElementById('channelColorLayoutDiv'),
- titleContainer, channelContainer;
-
- container.innerHTML = ''; // clear
- titleContainer = createLayoutTitleContainer();
- channelContainer = createLayoutChannelContainer();
- container.appendChild(titleContainer);
- container.appendChild(channelContainer);
-};
-
-const createLayoutTitleContainer = () => {
- let titleContainer = document.createElement('div'),
- label = document.createElement("label"),
- item = document.createElement('div'),
- icon = document.createElement('i');
-
- titleContainer.setAttribute('class', 'channelLayoutsub');
- label.textContent = 'Select Channels for Layouts';
- label.setAttribute("for", "channelsLayout");
- item.appendChild(label);
- icon.setAttribute('class', 'fas fa-angle-up buttonChannelLayout close');
- icon.setAttribute('id', 'buttonChannelLayout');
- icon.setAttribute('onclick', "toggleChannelLayoutMenu(this)");
- titleContainer.appendChild(item);
- titleContainer.appendChild(icon);
-
- return(titleContainer)
-};
-
-const createLayoutChannelContainer = () => {
- let channelContainer = document.createElement('div'),
- row, checkbox, p;
-
- channelContainer.setAttribute("name", "channelsLayout");
- channelContainer.setAttribute("id", "channelsLayout");
- channelContainer.setAttribute("class", "channelsLayout display-none");
- for (let i = 0; i < channels.length; i++) {
- row = document.createElement('div');
-
- checkbox = document.createElement('input');
- checkbox.type = "checkbox";
- checkbox.name = channels[i];
- checkbox.className = "checkbox_check channel_checkbox";
- checkbox.id = "checkbox_layout".concat(channels[i]);
- checkbox.checked = true;
- checkbox.setAttribute('onclick', "updateSelectedChannelsRShiny(this)");
-
- p = document.createElement("p");
- p.className = "channel_layout_name";
- p.textContent = channels[i];
-
- row.appendChild(checkbox);
- row.appendChild(p);
-
- channelContainer.appendChild(row);
- row = '';
- }
-
- return(channelContainer)
-};
-
-const attachChannelEditList = () => {
- let container = document.getElementById('channelColorPicker'),
- title = document.createElement("h4"),
- row, label, colorPicker, checkbox, hideLabel;
-
- container.innerHTML = ''; // clear
- title.textContent = 'Channels';
- container.appendChild(title);
- for (let i = 0; i < channels.length; i++) {
- row = document.createElement("div");
- row.className = "channel_subcontainer";
-
- label = document.createElement('h5');
- label.className = "channelLabel";
- label.textContent = channels[i].concat(":");
-
- colorPicker = document.createElement('input');
- colorPicker.type = "color";
- colorPicker.className = "colorPicker channel_colorPicker";
- colorPicker.name = channels[i];
- colorPicker.id = "color".concat(channels[i]);
- colorPicker.value = channelColors[channels[i]];
- colorPicker.setAttribute('onchange', "changeChannelColor(this)");
-
-
- checkbox = document.createElement('input');
- checkbox.type = "checkbox";
- checkbox.name = "checkbox".concat(channels[i]);
- checkbox.className = "checkbox_check channel_checkbox";
- checkbox.id = "checkbox".concat(channels[i]);
- checkbox.value = channels[i];
- checkbox.setAttribute('onclick', "toggleChannelVisibility(this)");
-
- hideLabel = document.createElement('label');
- hideLabel.className = "channelCheckboxLabel";
- hideLabel.textContent = "Hide";
-
- row.appendChild(label);
- row.appendChild(colorPicker);
- row.appendChild(checkbox);
- row.appendChild(hideLabel);
-
- container.appendChild(row);
- }
-
- toggleChannelColorPicker();
-};
-
-// On animate ======
-const renderInterLayerEdges = () => {
- if (existsConditionToRemoveInterEdges()) {
- if (!interEdgesRemoved)
- removeInterLayerEdges();
- } else {
- if (renderInterLayerEdgesFlag)
- redrawInterLayerEdges();
- }
-};
-
-const existsConditionToRemoveInterEdges = () => {
- return(scene.dragging || interLayerEdgesRenderPauseFlag ||
- (!edgeWidthByWeight && interLayerEdgeOpacity === 0))
-};
-
-const removeInterLayerEdges = () => {
- for (let i = 0; i < edgeObjects.length; i++)
- if (edgeObjects[i].interLayer)
- scene.remove(edgeObjects[i].THREE_Object);
-
- interEdgesRemoved = true;
-};
-
-const redrawInterLayerEdges = () => {
- for (let i = 0; i < edgeObjects.length; i++)
- if (edgeObjects[i].interLayer)
- edgeObjects[i].redrawEdge();
-
- interEdgesRemoved = false;
- // locked flags for best edge redrawing below
- if (waitEdgeRenderFlag) {
- waitEdgeRenderFlag = false;
- } else {
- renderInterLayerEdgesFlag = false;
- waitEdgeRenderFlag = true;
- }
-};
-
-// Event Listeners ======
-const toggleChannelLayoutMenu = () => {
- let icon = document.getElementById('buttonChannelLayout'),
- select = document.getElementById('channelsLayout');
-
- if (icon.classList.contains('close')) {
- icon.classList.remove("close");
- select.classList.remove('display-none');
- } else {
- icon.classList.add("close");
- select.classList.add('display-none');
- }
-};
-
-const changeChannelColor = (pickerElement) => {
- let channelName = pickerElement.name;
- channelColors[channelName] = pickerElement.value;
-
- renderInterLayerEdgesFlag = true;
- redrawIntraLayerEdges();
- updateEdgeColorsRShiny();
-};
-
-const redrawIntraLayerEdges = () => {
- for (let i = 0; i < edgeObjects.length; i++)
- if (!edgeObjects[i].interLayer)
- edgeObjects[i].redrawEdge();
-};
-
-const toggleChannelVisibility = (checkbox) => {
- let channelName = checkbox.value,
- checked = checkbox.checked,
- currentEdge;
-
- for (let i = 0; i < edgeObjects.length; i++) {
- currentEdge = edgeObjects[i].THREE_Object;
- for (let j = 0; j < currentEdge.children.length; j++) {
- if (currentEdge.children[j].userData.tag === channelName) {
- currentEdge.children[j].visible = !checked;
- if (isDirectionEnabled)
- currentEdge.children[j + 1].visible = !checked; // also toggle the arrow
-
- channelVisibility[channelName] = !checked;
- break; // only one channel allowed per edge
- }
- }
- }
-};
-
-const toggleChannelColorPicker = () => {
- if (edgeFileColorPriority || channels.length === 0)
- document.getElementById('channelColorPicker').style.display = 'none';
- else
- document.getElementById('channelColorPicker').style.display = 'block';
-
- updateEdgeColorsRShiny();
-};
-
-const unselectAllEdges = () => {
- for (let i = 0; i < edgeObjects.length; i++)
- edgeObjects[i].deselect();
-
- renderInterLayerEdgesFlag = true;
- redrawIntraLayerEdges();
-};
-
-// Handlers ======
-const toggleDirection = (message) => { // true or false
- isDirectionEnabled = message;
-
- renderInterLayerEdgesFlag = true;
- redrawIntraLayerEdges();
-};
-
-const setIntraDirectionArrowSize = (message) => {
- intraDirectionArrowSize = message;
-
- redrawIntraLayerEdges();
-};
-
-const setInterDirectionArrowSize = (message) => {
- interDirectionArrowSize = message;
-
- renderInterLayerEdgesFlag = true;
-};
-
-const setEdgeWidthByWeight = (message) => { // true or false
- edgeWidthByWeight = message;
-
- renderInterLayerEdgesFlag = true;
- redrawIntraLayerEdges();
-};
-
-const setIntraLayerEdgeOpacity = (message) => {
- intraLayerEdgeOpacity = message;
- redrawIntraLayerEdges();
-};
-
-const setInterLayerEdgeOpacity = (message) => {
- interLayerEdgeOpacity = message;
- renderInterLayerEdgesFlag = true;
-};
-
-const setEdgeSelectedColorPriority = (message) => { // true or false
- selectedEdgeColorFlag = message;
-
- renderInterLayerEdgesFlag = true;
- redrawIntraLayerEdges();
-};
-
-const setEdgeFileColorPriority = (message) => { // true or false
- edgeFileColorPriority = message;
-
- renderInterLayerEdgesFlag = true;
- redrawIntraLayerEdges();
- toggleChannelColorPicker();
-};
-
-const toggleIntraChannelCurvature = (message) => {
- intraChannelCurvature = message;
- redrawIntraLayerEdges();
-};
-
-const toggleInterChannelCurvature = (message) => {
- interChannelCurvature = message;
- renderInterLayerEdgesFlag = true;
-};
-
-const setEdgeAttributes = (edgeAttributes) => {
- let checkbox;
-
- setEdgeColorFromAttributes(edgeAttributes);
-
- checkbox = document.getElementById("edgeFileColorPriority");
- if (!checkbox.checked)
- checkbox.click(); // contains draw events on event click
- else {
- renderInterLayerEdgesFlag = true;
- redrawIntraLayerEdges();
- }
-
- updateEdgeColorsRShiny();
-};
-
-const setEdgeColorFromAttributes = (edgeAttributes) => {
- let pos, pos2;
-
- for (let i = 0; i < edgeAttributes.length; i++) {
- pos = edgePairs.indexOf(edgeAttributes[i].EdgePair);
- if (pos != -1) {
- if (edgeAttributes[i].Channel) {
- for (let j = 0; j < edgeObjects[pos].channels.length; j++) {
- pos2 = edgeObjects[pos].channels.indexOf(edgeAttributes[i].Channel);
- edgeObjects[pos].importedColors[pos2] = edgeAttributes[i].Color;
- edgeObjects[pos].colors[pos2] = edgeAttributes[i].Color;
- }
- } else {
- edgeObjects[pos].importedColors[0] = edgeAttributes[i].Color;
- edgeObjects[pos].colors[pos2] = edgeAttributes[i].Color;
- }
- }
- }
-
- updateEdgeColorsRShiny();
-};
-
-// Canvas Controls ======
-const toggleInterLayerEdgesRendering = () => {
- let interLayerEdgesRenderPauseButton = document.getElementById('interLayerEdgesRenderPauseButton');
-
- interLayerEdgesRenderPauseFlag = !interLayerEdgesRenderPauseFlag;
- if (interLayerEdgesRenderPauseFlag) {
- interLayerEdgesRenderPauseButton.innerText = "Render Inter-Layer Edges";
- } else {
- interLayerEdgesRenderPauseButton.innerText = "Stop:Render Inter-Layer Edges";
- renderInterLayerEdgesFlag = true;
- }
-};
diff --git a/www/js/object_actions/labels.js b/www/js/object_actions/labels.js
deleted file mode 100644
index c46e808..0000000
--- a/www/js/object_actions/labels.js
+++ /dev/null
@@ -1,196 +0,0 @@
-// Initialization ======
-const createLabels = () => {
- createNodeLabels();
- createLayerLabels();
-};
-
-const createNodeLabels = () => {
- let div, nodeNames = nodeObjects.map(({ name }) => name);
-
- for (let i = 0; i < nodeNames.length; i++) {
- div = document.createElement('div');
- div.textContent = nodeNames[i];
- div.setAttribute('class', 'labels');
- div.setAttribute('id', nodeLayerNames[i].concat("_label"));
- document.getElementById("labelDiv").appendChild(div);
-
- nodeLabelsDivs.push(div);
- nodeLabelsDivs[i].style.fontSize = NODE_LABEL_DEFAULT_SIZE;
- nodeLabelsDivs[i].style.display = "none";
- nodeLabelsDivs[i].style.color = globalLabelColor;
- }
-};
-
-const createLayerLabels = () => {
- let div, name;
-
- for (let i = 0; i < layers.length; i++) {
- name = layers[i].getName();
- div = document.createElement('div'),
- div.textContent = name;
- div.setAttribute('class', 'layer-labels');
- div.setAttribute('id', name.concat("_label"));
- document.getElementById("labelDiv").appendChild(div);
-
- layerLabelsDivs.push(div);
- layerLabelsDivs[i].style.display = "inline-block";
- layerLabelsDivs[i].style.color = globalLabelColor;
- }
-};
-
-const setLabelColorVariable = (labelColor) => {
- globalLabelColor = labelColor;
- setLabelColor();
-};
-
-const setLabelColor = () => {
- let i;
-
- for (i = 0; i < layerLabelsDivs.length; i++)
- layerLabelsDivs[i].style.color = globalLabelColor;
-
- for (i = 0; i < nodeLabelsDivs.length; i++)
- nodeLabelsDivs[i].style.color = globalLabelColor;
-};
-
-// Handlers ======
-const showLayerLabels = (mode) => {
- if (mode == "all") {
- showAllLayerLabels();
- } else if (mode == "selected") {
- showSelectedLayerLabels();
- } else if (mode == "none") {
- hideAllLayerLabels();
- }
-};
-
-const showAllLayerLabels = () => {
- setGlobalLayerLabelFlags(true, false);
-
- for (let i = 0; i < layers.length; i++)
- layerLabelsDivs[i].style.display = "inline-block";
-
- renderLayerLabelsFlag = true;
-};
-
-const setGlobalLayerLabelFlags = (allFlag, selectedFLag) => {
- showAllLayerLabelsFlag = allFlag;
- showSelectedLayerLabelsFlag = selectedFLag;
-};
-
-const showSelectedLayerLabels = () => {
- setGlobalLayerLabelFlags(false, true);
-
- for (let i = 0; i < layers.length; i++) {
- if (layers[i].isSelected)
- layerLabelsDivs[i].style.display = "inline-block";
- else
- layerLabelsDivs[i].style.display = "none";
- }
-
- renderLayerLabelsFlag = true;
-};
-
-const hideAllLayerLabels = () => {
- setGlobalLayerLabelFlags(false, false);
-
- for (let i = 0; i < layers.length; i++)
- layerLabelsDivs[i].style.display = "none";
-};
-
-const resizeLayerLabels = (size) => {
- for (let i = 0; i < layerLabelsDivs.length; i++)
- layerLabelsDivs[i].style.fontSize = size.toString().concat("px");
-};
-
-const showNodeLabels = (mode) => {
- if (mode == "all") {
- showAllNodeLabelsFlag = true;
- showSelectedNodeLabelsFlag = false;
- } else if (mode == "selected") {
- showAllNodeLabelsFlag = false;
- showSelectedNodeLabelsFlag = true;
- } else if (mode == "none") {
- showAllNodeLabelsFlag = false;
- showSelectedNodeLabelsFlag = false;
- }
- decideNodeLabelFlags();
-};
-
-const resizeNodeLabels = (size) => {
- for (let i = 0; i < nodeLabelsDivs.length; i++)
- nodeLabelsDivs[i].style.fontSize = size.toString().concat("px");
-};
-
-// on animate ======
-const renderLayerLabels = () => {
- if (renderLayerLabelsFlag) {
-
- if (showAllLayerLabelsFlag)
- redrawLayerLabels("all");
- else {
- if (showSelectedLayerLabelsFlag) {
- let selected_layers = getSelectedLayers();
-
- removeAllLayerLabels();
- if (selected_layers.length > 0)
- redrawLayerLabels("selected");
- }
- }
-
- renderLayerLabelsFlag = false;
- }
-};
-
-const removeAllLayerLabels = () => {
- for (let i = 0; i < layers.length; i++)
- layerLabelsDivs[i].style.display = "none";
-};
-
-const redrawLayerLabels = (mode) => {
- let layerArray, position,
- hidelayerCheckboxes = document.getElementsByClassName("hideLayer_checkbox"),
- layerX, layerY, labelX, labelY,
- layer_spheres = layers.map(({ sphere }) => sphere);
-
- layerArray = mode === "all" ? layers.map(({ name }) => name) : getSelectedLayers();
-
- for (let i = 0; i < layerArray.length; i++) {
- position = mode == "selected" ? layerArray[i] : i;
-
- if (!hidelayerCheckboxes[position].checked) {
- layerX = layer_spheres[position].getWorldPosition(new THREE.Vector3()).x,
- layerY = layer_spheres[position].getWorldPosition(new THREE.Vector3()).y;
- labelX = xBoundMax + layerX;
- labelY = yBoundMax - layerY;
-
- layerLabelsDivs[position].style.left = labelX.toString().concat("px");
- layerLabelsDivs[position].style.top = labelY.toString().concat("px");
- layerLabelsDivs[position].style.display = "inline-block";
- } else
- layerLabelsDivs[position].style.display = "none";
- }
-};
-
-const renderNodeLabels = () => {
- if (renderNodeLabelsFlag) {
-
- let nodeX, nodeY, labelX, labelY;
-
- for (let i = 0; i < nodeObjects.length; i++) {
- if (nodeObjects[i].showLabel) {
- nodeX = nodeObjects[i].getWorldPosition("x");
- nodeY = nodeObjects[i].getWorldPosition("y");
- labelX = xBoundMax + nodeX + 7;
- labelY = yBoundMax - nodeY - 10;
-
- nodeLabelsDivs[i].style.left = labelX.toString().concat("px");
- nodeLabelsDivs[i].style.top = labelY.toString().concat("px");
- nodeLabelsDivs[i].style.display = "inline-block";
- } else
- nodeLabelsDivs[i].style.display = "none";
- }
-
- renderNodeLabelsFlag = false;
- }
-};
diff --git a/www/js/object_actions/layer.js b/www/js/object_actions/layer.js
deleted file mode 100644
index 4cc216d..0000000
--- a/www/js/object_actions/layer.js
+++ /dev/null
@@ -1,284 +0,0 @@
-// Layer Selection Checkbox Group =====
-const attachLayerCheckboxes = () => {
- let br,
- container = document.getElementById('checkboxdiv');
- container.innerHTML = ''; //clear previous checkboxes
-
- for (let i = 0; i < layers.length; i++) {
- attachLayerCheckBox("checkbox_".concat(i), i, "layer_checkbox",
- "selectCheckedLayer(this)", " layer_label", layers[i].getName())
- attachLayerCheckBox("checkbox2_".concat(i), "show_hide".concat(i), "hideLayer_checkbox",
- "hideLayers()", "", 'Hide')
- attachLayerCheckBox("checkbox3_".concat(i), "show_labels".concat(i), "showLayerNodes_checkbox",
- "showLayerNodeLabels()", "", 'Labels')
-
- br = document.createElement('br');
- container.appendChild(br);
- }
-};
-
-const attachLayerCheckBox = (c_id, c_value, c_class, c_func, l_class, l_title) => {
- let checkbox, label,
- container = document.getElementById('checkboxdiv');
-
- checkbox = document.createElement('input');
- checkbox.type = "checkbox";
- checkbox.id = c_id;
- checkbox.value = c_value;
- checkbox.className = "checkbox_check ".concat(c_class);
- checkbox.setAttribute("onclick", c_func);
-
- label = document.createElement('label');
- label.htmlFor = c_value;
- label.className = "checkbox_element".concat(l_class);
- label.appendChild(document.createTextNode(l_title));
-
- container.appendChild(checkbox);
- container.appendChild(label);
-};
-
-const selectCheckedLayer = (checkbox) => {
- layers[checkbox.value].isSelected = checkbox.checked;
-
- renderLayerLabelsFlag = true;
- repaintLayers();
- updateSelectedLayersRShiny();
-};
-
-const repaintLayers = () => {
- for (let i = 0; i < layers.length; i++) {
- if (layers[i].isSelected)
- layers[i].setColor(SELECTED_LAYER_DEFAULT_COLOR);
- else {
- if (layerColorPrioritySource == "default") {
- layers[i].setColor(layers[i].importedColor);
- } else if (layerColorPrioritySource == "picker") {
- layers[i].setColor(document.getElementById("floor_color").value);
- }
- }
- }
-};
-
-const hideLayers = () => {
- let hideLayerCheckboxes = document.getElementsByClassName("hideLayer_checkbox");
- for (let i = 0; i < hideLayerCheckboxes.length; i++)
- layers[i].toggleVisibility(!hideLayerCheckboxes[i].checked);
-
- renderInterLayerEdgesFlag = true;
- renderLayerLabelsFlag = true;
- decideNodeLabelFlags();
-};
-
-const showLayerNodeLabels = () => {
- let showLayerNodesCheckboxes = document.getElementsByClassName("showLayerNodes_checkbox");
- for (let i = 0; i < showLayerNodesCheckboxes.length; i++)
- layers[i].showNodeLabels = showLayerNodesCheckboxes[i].checked;
- decideNodeLabelFlags();
-};
-
-// From R actions (layouts and upload/import) =====
-// called from predefined layouts (parallel/zigzag), from upload and canvas controls
-const initialSpreadLayers = (direction = 1) => {
- let numLayers = layers.length;
- let spacing = xBoundMax * 2 / numLayers;
-
- for (let i = 0; i < numLayers; i++) {
- if (numLayers % 2) // odd
- layers[i].translateX(direction * ((-Math.floor(layers.length / 2) + i) * spacing));
- else // even
- layers[i].translateX(direction * ((-layers.length / 2 + i) * spacing + spacing / 2));
- }
-};
-
-// Event Listeners =====
-const checkHoverOverLayer = (event) => {
- setRaycaster(event);
- let layerPlanes = layers.map(({ plane }) => plane);
- let intersects = RAYCASTER.intersectObjects(layerPlanes);
- if (intersects.length > 0) {
- if (lastHoveredLayerIndex !== "") {
- repaintLayers();
- hoveredLayerPaintedFlag = true;
- lastHoveredLayerIndex = "";
- }
- intersects[0].object.material.color.set( 0xff0000 );
- lastHoveredLayerIndex = findIndexByUuid(layerPlanes, intersects[0].object.uuid);
- } else {
- if (hoveredLayerPaintedFlag) {
- repaintLayers(); // remove red color from last hovered
- hoveredLayerPaintedFlag = false;
- }
- lastHoveredLayerIndex = "";
- }
-};
-
-const performDoubleClickLayerSelection = () => {
- layers[lastHoveredLayerIndex].toggleSelection();
- // toggle respective checkbox
- let layerCheckboxes = document.getElementsByClassName("layer_checkbox");
- layerCheckboxes[lastHoveredLayerIndex].checked ?
- layerCheckboxes[lastHoveredLayerIndex].checked = false :
- layerCheckboxes[lastHoveredLayerIndex].checked = true;
-
- lastHoveredLayerIndex = "";
- repaintLayers();
- updateSelectedLayersRShiny();
-};
-
-const rotateLayersWithHeldKey = (event) => {
- let rads, selected_layers = getSelectedLayers();
-
- if (event.screenX - event.screenY >= mousePreviousX - mousePreviousY)
- rads = 0.05;
- else
- rads = -0.05;
-
- for (let i = 0; i < selected_layers.length; i++) {
- if (scene.axisPressed == "z")
- layers[selected_layers[i]].rotateZ(rads);
- else if (scene.axisPressed == "x")
- layers[selected_layers[i]].rotateX(rads);
- else if (scene.axisPressed == "c")
- layers[selected_layers[i]].rotateY(rads);
- }
-
- updateLayersRShiny();
- updateVRNodesRShiny();
-};
-
-const getSelectedLayers = () => {
- let selected_layers = layers.map(function(layer) {
- if (layer.isSelected)
- return(layer.id)
- });
- selected_layers = selected_layers.filter(function(id) {
- return(id !== undefined)
- });
- return(selected_layers)
-};
-
-const repaintLayersFromPicker = () => {
- chooseColorpickerPriority();
- repaintLayers();
- updateLayersRShiny();
-};
-
-const chooseColorpickerPriority = () => {
- let radioButtonDiv = document.getElementById("layerColorPriorityRadio");
- radioButtonDiv.children[1].children[1].click(); // choosing Theme / Colorpicker priority
-};
-
-// Handlers =====
-const selectAllLayers = (flag) => {
- let layerCheckboxes = document.getElementsByClassName("layer_checkbox");
- for (let i = 0; i < layerCheckboxes.length; i++) {
- layerCheckboxes[i].checked = flag;
- layers[i].isSelected = flag;
- }
-
- renderLayerLabelsFlag = true;
- repaintLayers();
- updateSelectedLayersRShiny();
-};
-
-const showLayerCoords = (showLabelCoordsFlag) => {
- for (let i = 0; i < layers.length; i++)
- layers[i].toggleCoords(showLabelCoordsFlag);
-};
-
-const setFloorOpacity = (selectedOpacity) => {
- for (let i = 0; i < layers.length; i++)
- layers[i].setOpacity(selectedOpacity);
-};
-
-const showWireFrames = (wireframeFlag) => {
- for (let i = 0; i < layers.length; i++)
- layers[i].toggleWireframe(wireframeFlag);
-};
-
-const setLayerColorPriority = (colorPriority) => {
- layerColorPrioritySource = colorPriority;
- repaintLayers();
- updateLayersRShiny();
-};
-
-// Canvas Controls =====
-const rotateSelectedLayers = (direction, axis) => {
- let selected_layers = getSelectedLayers();
- if (selected_layers.length > 0) {
- layerIntervalTimeout = setInterval(function() {
- let value = document.getElementsByClassName("canvasSlider")[1].value;
- value = direction * THREE.Math.degToRad(value);
- for (let i = 0; i < selected_layers.length; i++) {
- if (axis == "X")
- layers[selected_layers[i]].rotateX(value);
- else if (axis == "Y")
- layers[selected_layers[i]].rotateY(value);
- else if (axis == "Z")
- layers[selected_layers[i]].rotateZ(value);
- }
- updateLayersRShiny();
- updateVRNodesRShiny();
- }, 70);
- } else
- alert("Please select at least one layer.");
-};
-
-const spreadLayers = (direction) => {
- initialSpreadLayers(direction);
-
- updateLayersRShiny();
- updateVRLayerLabelsRShiny();
- updateVRNodesRShiny();
-};
-
-const moveLayers = (direction, axis) => {
- let selected_layers = getSelectedLayers();
- if (selected_layers.length > 0) {
- layerIntervalTimeout = setInterval(function() {
- let value = document.getElementsByClassName("canvasSlider")[2].value;
- value = direction * value;
- for (let i = 0; i < selected_layers.length; i++) {
- if (axis == "X")
- layers[selected_layers[i]].translateX(value);
- else if (axis == "Y")
- layers[selected_layers[i]].translateY(value);
- else if (axis == "Z")
- layers[selected_layers[i]].translateZ(value);
- }
- updateLayersRShiny();
- updateVRLayerLabelsRShiny();
- updateVRNodesRShiny();
- }, 70);
- } else
- alert("Please select at least one layer.");
-};
-
-const scaleLayers = () => {
- let moveFactor, childrenArray,
- sliderString = document.getElementById("sliderValue4"),
- scalingValue = document.getElementsByClassName("canvasSlider")[3].value,
- selected_layers = getSelectedLayers();
-
- sliderString.innerHTML = "x".concat(scalingValue);
-
- if (selected_layers.length > 0) {
- for (let i = 0; i < selected_layers.length; i++) {
- moveFactor = parseFloat(scalingValue) / layers[selected_layers[i]].getScale();
- childrenArray = layers[selected_layers[i]].plane.children;
- for (let j = 0; j < childrenArray.length; j++) {
- if (childrenArray[j].type === "Mesh") { // only nodes, edges are "Line"
- childrenArray[j].position.y *= moveFactor;
- childrenArray[j].position.z *= moveFactor;
- }
- }
- layers[selected_layers[i]].setScale(scalingValue);
- }
-
- redrawIntraLayerEdges();
- updateLayersRShiny();
- updateNodesRShiny();
- updateVRNodesRShiny();
- } else
- alert("Please select at least one layer.");
-};
diff --git a/www/js/object_actions/layout.js b/www/js/object_actions/layout.js
deleted file mode 100644
index fcebdeb..0000000
--- a/www/js/object_actions/layout.js
+++ /dev/null
@@ -1,258 +0,0 @@
-const setPerLayerFlag = (numFlag) => { // from -1: to length of Layers
- perLayerLayoutFLag = numFlag;
-};
-
-const setLocalFlag = (flag) => { // flag always true here
- localLayoutFlag = flag;
-};
-
-const executeLayout = (nodeCoords) => {
- // x always 0, assign on floor every time
- let y_arr = [], z_arr = [],
- y_min, y_max, z_min, z_max, minWidth,
- target_y_min, target_y_max, target_z_min, target_z_max,
- nodeName;
-
- for (let i = 0; i < nodeCoords.name.length; i++) {
- y_arr.push(Number(nodeCoords.y[i]));
- z_arr.push(Number(nodeCoords.z[i]));
- }
-
- [y_min, y_max, z_min, z_max] = getMinAndMaxInputCoordValues(y_arr, z_arr);
- minWidth = getMinLayerWidth();
-
- if (isPerLayerOrLocalLayoutChosen())
- minWidth = getPerLayerMinWidth();
-
- target_y_min = target_z_min = -minWidth / 2;
- target_y_max = target_z_max = minWidth / 2;
-
- if (isLocalLayoutChosen())
- [target_y_min, target_y_max, target_z_min, target_z_max] = getMinAndMaxTargetCoordValues(nodeCoords);
-
- // assignXYZ
- for (let i = 0; i < nodeCoords.name.length; i++) {
- nodeName = nodeCoords.name[i];
-
- nodeObjects[nodeLayerNames.indexOf(nodeName)].setPosition("x", 0);
-
- if (y_max - y_min != 0)
- nodeObjects[nodeLayerNames.indexOf(nodeName)].setPosition("y",
- ((y_arr[i] - y_min) * (target_y_max - target_y_min) /
- (y_max - y_min) + target_y_min) * layers[layerGroups[nodeGroups[nodeName]]].getScale());
- else
- nodeObjects[nodeLayerNames.indexOf(nodeName)].setPosition("y", 0);
-
- if (z_max - z_min != 0)
- nodeObjects[nodeLayerNames.indexOf(nodeName)].setPosition("z",
- ((z_arr[i] - z_min) * (target_z_max - target_z_min) /
- (z_max - z_min) + target_z_min) * layers[layerGroups[nodeGroups[nodeName]]].getScale());
- else
- nodeObjects[nodeLayerNames.indexOf(nodeName)].setPosition("z", 0);
- }
-
- if (isClusteringChosen(nodeCoords))
- setClustersAndColors(nodeCoords);
-
- updateNodesRShiny();
- updateVRNodesRShiny();
- redrawIntraLayerEdges();
-};
-
-const getMinAndMaxInputCoordValues = (y_arr, z_arr) => {
- let y_min = Math.min.apply(Math, y_arr),
- y_max = Math.max.apply(Math, y_arr),
- z_min = Math.min.apply(Math, z_arr),
- z_max = Math.max.apply(Math, z_arr);
-
- return([y_min, y_max, z_min, z_max])
-};
-
-const getMinLayerWidth = () => {
- let layerWidths = layers.map(({ geometry_parameters_width }) => geometry_parameters_width),
- minWidth = Math.min(...layerWidths);
-
- return(minWidth);
-};
-
-const isPerLayerOrLocalLayoutChosen = () => {
- return(perLayerLayoutFLag !== undefined)
-};
-
-const getPerLayerMinWidth = () => {
- let minWidth = layers.find(x => x.name === perLayerLayoutFLag).geometry_parameters_width;
- perLayerLayoutFLag = undefined;
- return(minWidth)
-};
-
-const isLocalLayoutChosen = () => {
- return(localLayoutFlag);
-};
-
-const getMinAndMaxTargetCoordValues = (nodeCoords) => {
- // all nodes in same layer always at this point ( = executeLayout executed per Layer, here)
- let layerIndex = layerGroups[nodeGroups[nodeCoords.name[0]]],
- scale = layers[layerIndex].getScale(),
- y_coord, z_coord;
-
- // init with element 0
- target_y_min = target_y_max =
- nodeObjects[nodeLayerNames.indexOf(nodeCoords.name[0])].getPosition("y") / scale;
- target_z_min = target_z_max =
- nodeObjects[nodeLayerNames.indexOf(nodeCoords.name[0])].getPosition("z") / scale;
-
- for (let i = 1; i < nodeCoords.name.length; i++) {
- y_coord = nodeObjects[nodeLayerNames.indexOf(nodeCoords.name[i])].getPosition("y") / scale;
- z_coord = nodeObjects[nodeLayerNames.indexOf(nodeCoords.name[i])].getPosition("z") / scale;
-
- if (y_coord < target_y_min)
- target_y_min = y_coord;
- if (y_coord > target_y_max)
- target_y_max = y_coord;
- if (z_coord < target_z_min)
- target_z_min = z_coord;
- if (z_coord > target_z_max)
- target_z_max = z_coord;
- }
-
- localLayoutFlag = false;
-
- return([target_y_min, target_y_max, target_z_min, target_z_max])
-};
-
-const isClusteringChosen = (nodeCoords) => {
- return(nodeCoords.group != null)
-};
-
-const setClustersAndColors = (nodeCoords) => {
- for (let i = 0; i < nodeCoords.name.length; i++) {
- nodeObjects[nodeLayerNames.indexOf(nodeCoords.name[i])].setColor(COLOR_VECTOR_280[nodeCoords.group[i]]);
- nodeObjects[nodeLayerNames.indexOf(nodeCoords.name[i])].setCluster(nodeCoords.group[i]);
- }
-};
-
-const scaleTopology = (nodeScale) => {
- let nodeName;
-
- for (let i = 0; i < nodeScale.nodeName.length; i++) {
- nodeName = nodeScale.nodeName[i];
- nodeObjects[nodeLayerNames.indexOf(nodeName)].setScale(nodeScale.scale[i]);
- }
-
- updateNodesRShiny();
-};
-
-const applyPredefinedLayout = (message) => {
- if (existEnoughLayers()) {
- resetSceneAndLayerPositions();
-
- if (message == "parallel") {
- initialSpreadLayers(1);
- } else if (message == "zigZag") {
- for (let i = 1; i < layers.length; i += 2)
- layers[i].translateY(500);
-
- initialSpreadLayers(1);
- } else if (message == "starLike") {
- applyStarLayout();
- } else if (message == "cube")
- applyCubeLayout();
-
- updateSceneSphereRShiny();
- updateLayersRShiny();
- updateVRLayerLabelsRShiny();
- updateVRNodesRShiny();
- }
-};
-
-const existEnoughLayers = () => {
- return(layers.length > 1)
-};
-
-const resetSceneAndLayerPositions = () => {
- let layerPlanes = layers.map(({ plane }) => plane);
-
- scene.tiltDefault();
-
- for (let i = 0; i < layers.length; i++) {
- layerPlanes[i].position.set(0, 0, 0);
- layerPlanes[i].quaternion.copy(camera.quaternion);
- }
-};
-
-const applyStarLayout = () => {
- let layerPlanes = layers.map(({ plane }) => plane),
- degree = 360 / layers.length;
-
- for (let i = 0; i < layers.length; i++) {
- layerPlanes[i].rotateZ(THREE.Math.degToRad(degree * i));
- layerPlanes[i].translateY(-layerPlanes[i].geometry.parameters.height / 2 - 100);
- }
-};
-
-const applyCubeLayout = () => {
- let layerPlanes = layers.map(({ plane }) => plane),
- layerSize = getLargestLayerSize(layerPlanes),
- layersPerCube = 6,
- cubes = Math.ceil(layers.length / layersPerCube),
- distance = cubes * (Number(layerSize) + 400),
- cubeSideCode, max_i, x;
-
- for (let j = 0; j < cubes; j++) {
- cubeSideCode = 0;
- max_i = j * layersPerCube + layersPerCube;
- if (max_i > layers.length)
- max_i = layers.length;
-
- // Each cube
- for (let i = j * layersPerCube; i < max_i; i++) {
- if (cubes > 1) {
- x = decideLayerStartingPositionX(j, cubes, distance);
- layerPlanes[i].position.set(x, 0, 0);
- }
-
- if (!(cubeSideCode % 2))
- layerPlanes[i].rotateZ(THREE.Math.degToRad(90));
-
- if (cubeSideCode >= 4)
- layerPlanes[i].rotateY(THREE.Math.degToRad(90));
-
- if (cubeSideCode == 0 || cubeSideCode == 1 || cubeSideCode == 5)
- layerPlanes[i].translateX(layerPlanes[i].geometry.parameters.height / 2 + 100);
- else
- layerPlanes[i].translateX(-layerPlanes[i].geometry.parameters.height / 2 - 100);
-
- cubeSideCode++;
- }
- }
-};
-
-const getLargestLayerSize = (layerPlanes) => {
- let layerSize = layerPlanes[0].geometry.parameters.height;
-
- for (let i = 1; i < layers.length; i++)
- if (layerPlanes[i].geometry.parameters.height > layerSize)
- layerSize = layerPlanes[i].geometry.parameters.height;
-
- return(layerSize)
-};
-
-const decideLayerStartingPositionX = (j, cubes, distance) => {
- let x;
-
- if (cubes % 2) {
- if (j == Math.floor(cubes / 2))
- x = 0;
- else if (j < Math.floor(cubes / 2))
- x = -distance / cubes;
- else
- x = distance / cubes;
- } else {
- if (j < Math.floor(cubes / 2))
- x = -distance / cubes;
- else
- x = distance / cubes;
- }
-
- return(x)
-};
diff --git a/www/js/object_actions/network.js b/www/js/object_actions/network.js
deleted file mode 100644
index e2dfdb1..0000000
--- a/www/js/object_actions/network.js
+++ /dev/null
@@ -1,322 +0,0 @@
-const uploadNetwork = (network) => {
- executePreNetworkSetup();
-
- initializeScene();
- initializeLayers(network);
- if (areObjectsWithinLimit(layers, MAX_LAYERS, "layers")) {
- initializeNodeAndEdgeArrays(network);
- if (areObjectsWithinLimit(edgeValues, MAX_EDGES, "edges") &&
- areObjectsWithinLimit(channels, MAX_CHANNELS, "channels")) {
- initializeNodes();
-
- executePostNetworkSetup();
- }
- }
-};
-
-const executePreNetworkSetup = () => {
- resetValues();
- if (!canvasControlsAttached)
- attachCanvasControls();
-};
-
-const resetValues = () => {
- scene.reset();
-
- // labels
- document.getElementById("labelDiv").innerHTML = "";
- layerLabelsDivs = []; //divs
- renderLayerLabelsFlag = false;
- nodeLabelsDivs = [];
- renderNodeLabelsFlag = false;
- showAllLayerLabelsFlag = true;
- showSelectedLayerLabelsFlag = false;
- showAllNodeLabelsFlag = false;
- showSelectedNodeLabelsFlag = true;
-
- // layers
- layers = [];
- layerGroups = new Map();
- lastHoveredLayerIndex = "";
-
- // nodes
- nodeObjects = [];
- nodeNames = [];
- nodeLayerNames = [];
- nodeGroups = new Map();
- last_hovered_node_index = "";
-
- // edges
- edgeObjects = [];
- renderInterLayerEdgesFlag = false;
- waitEdgeRenderFlag = true;
- interEdgesRemoved = false;
- interLayerEdgeOpacity = 0.4;
- intraLayerEdgeOpacity = 1;
- interDirectionArrowSize = 5;
- intraDirectionArrowSize = 5;
- interChannelCurvature = 5;
- intraChannelCurvature = 15;
- edgeFileColorPriority = false;
- edgePairs = [];
- edgePairs_source = [];
- edgePairs_target = [];
- edgeValues = [];
- edgeColors = [];
- isDirectionEnabled = false;
- updateToggleCurvatureComponentsRShiny(false);
- // channels
- if (document.getElementById("channelColorLayoutDiv"))
- document.getElementById("channelColorLayoutDiv").innerHTML = "";
- if (document.getElementById("channelColorPicker"))
- document.getElementById("channelColorPicker").innerHTML = "";
- channels = [];
- selectedChannels = [];
- channelColors = {};
- channelVisibility = {};
- edgeChannels = [];
-
- // others
- shiftX = "";
- shiftY = "";
- lasso = "";
- optionsList = "";
-};
-
-const initializeScene = () => {
- scene.tiltDefault();
- scene.setScale(0.9);
- applyTheme('#000000', '#777777', '#ffffff',
- CHANNEL_COLORS_LIGHT, '#ffffff', fromInit = true);
-};
-
-const initializeLayers = (network) => {
- let layer_names = network.SourceLayer.concat(network.TargetLayer);
- layer_names = getUniqueValues(layer_names);
- for (let i = 0; i < layer_names.length; i++) {
- layerGroups[layer_names[i]] = i;
- layers.push(new Layer({id: i, name: layer_names[i]}));
- scene.addLayer(layers[i].plane);
- }
- initialSpreadLayers(1);
-};
-
-const areObjectsWithinLimit = (object, limit, objectName) => {
- let areWithinLimit = true;
- if (object.length > limit) {
- areWithinLimit = false;
- alert(`Network must contain no more than ${limit} ${objectName}.`);
- }
- return(areWithinLimit);
-}
-
-const initializeNodeAndEdgeArrays = (network) => {
- let sourceNodeLayerName, targetNodeLayerName;
-
- for (let i = 0; i < network.SourceNode.length; i++) {
- sourceNodeLayerName = network.SourceNode_Layer[i];
- targetNodeLayerName = network.TargetNode_Layer[i];
-
- updateNodeArrays(sourceNodeLayerName, network.SourceNode[i], network.SourceLayer[i]);
- updateNodeArrays(targetNodeLayerName, network.TargetNode[i], network.TargetLayer[i]);
-
- createEdgePairs(network.Channel, i, sourceNodeLayerName, targetNodeLayerName, network.Weight[i]);
- }
-
- updateChannelArrays(network.Channel);
-};
-
-const updateNodeArrays = (nodeLayerName, nodeName, layerName) => {
- if (!nodeLayerNames.includes(nodeLayerName)) {
- nodeLayerNames.push(nodeLayerName);
- nodeNames.push(nodeName);
- nodeGroups[nodeLayerName] = layerName;
- }
-};
-
-const createEdgePairs = (networkChannel, i, sourceNodeLayerName, targetNodeLayerName,
- networkWeight, color = "") => {
- let edgePair = sourceNodeLayerName.concat("---").concat(targetNodeLayerName);
- if (networkChannel)
- updateEdgeChannelArrays(networkChannel[i], edgePair,
- sourceNodeLayerName, targetNodeLayerName, networkWeight, color);
- else {
- edgePairs.push(edgePair);
- edgePairs_source.push(sourceNodeLayerName);
- edgePairs_target.push(targetNodeLayerName);
- edgeValues.push([networkWeight]);
- edgeColors.push([color]);
- }
-};
-
-const updateEdgeChannelArrays = (channelName, edgePair,
- sourceNodeLayerName, targetNodeLayerName, networkWeight, color) => {
- let position;
- if (edgePairs.includes(edgePair) ) {
- position = edgePairs.indexOf(edgePair);
- edgeChannels[position].push(channelName);
- edgeValues[position].push(networkWeight);
- edgeColors[position].push(color);
- } else {
- edgePairs.push(edgePair);
- edgePairs_source.push(sourceNodeLayerName);
- edgePairs_target.push(targetNodeLayerName);
- edgeChannels.push([channelName]);
- edgeValues.push([networkWeight]);
- edgeColors.push([color]);
- }
-};
-
-const updateChannelArrays = (channelArray) => {
- if (channelArray) {
- channels = getUniqueValues(channelArray);
-
- for (let i = 0; i < channels.length; i++)
- channelVisibility[channels[i]] = true;
-
- assignChannelColorsFromPalette();
-
- selectedChannels = channels.slice(); // copy by value, and not by reference
- Shiny.setInputValue("js_selectedChannels", selectedChannels); // R monitors selected Channels
- }
-};
-
-const initializeNodes = () => {
- createNodeObjects();
- scrambleNodes();
-};
-
-const executePostNetworkSetup = () => {
- let layerPlanes = layers.map(({ plane }) => plane);
- drag_controls = new DragControls(layerPlanes, camera, renderer.domElement);
-
- createEdgeObjects();
- createLabels();
-
- attachLayerCheckboxes();
- toggleChannelUIComponents();
-
- if (!animationRunning) { // ensure animation runs only once
- animate();
- animationRunning = true;
- }
-
- updateScenePanRShiny();
- updateSceneSphereRShiny();
- updateLayersRShiny();
- updateVRLayerLabelsRShiny();
- updateLayerNamesRShiny();
- updateNodesRShiny();
- updateVRNodesRShiny();
- updateNodeNamesRShiny();
- updateSelectedNodesRShiny();
- updateEdgesRShiny();
- updateLabelColorRShiny();
-}
-
-const toggleChannelUIComponents = () => {
- if (channels.length > 0) {
- attachChannelLayoutList();
- attachChannelEditList();
- updateToggleCurvatureComponentsRShiny(true);
- } else
- updateToggleCurvatureComponentsRShiny(false);
-};
-
-const importNetwork = (jsonNetwork) => {
- executePreNetworkSetup();
-
- initializeSceneFromJSON(jsonNetwork.scene);
- initializeLayersFromJSON(jsonNetwork.layers);
- if (areObjectsWithinLimit(layers, MAX_LAYERS, "layers")) {
- initializeEdgesFromJSON(jsonNetwork.edges);
- if (areObjectsWithinLimit(edgeValues, MAX_EDGES, "edges") &&
- areObjectsWithinLimit(channels, MAX_CHANNELS, "channels")) {
- initializeNodesFromJSON(jsonNetwork.nodes, jsonNetwork.scramble_nodes);
-
- executePostNetworkSetup();
-
- setJSONExtras(jsonNetwork);
- }
- }
-};
-
-const initializeSceneFromJSON = (jsonScene) => {
- scene.setPosition("x", Number(jsonScene.position_x));
- scene.setPosition("y", Number(jsonScene.position_y));
- scene.setScale(Number(jsonScene.scale));
- renderer.setClearColor(jsonScene.color);
- scene.setRotation("x", Number(jsonScene.rotation_x));
- scene.setRotation("y", Number(jsonScene.rotation_y));
- scene.setRotation("z", Number(jsonScene.rotation_z));
-};
-
-const initializeLayersFromJSON = (jsonLayers) => {
- for (let i = 0; i < jsonLayers.name.length; i++) {
- layerGroups[jsonLayers.name[i]] = i;
- layers.push(new Layer({
- id: i, name: jsonLayers.name[i],
- position_x: Number(jsonLayers.position_x[i]),
- position_y: Number(jsonLayers.position_y[i]),
- position_z: Number(jsonLayers.position_z[i]),
- last_layer_scale: Number(jsonLayers.last_layer_scale[i]),
- rotation_x: Number(jsonLayers.rotation_x[i]),
- rotation_y: Number(jsonLayers.rotation_y[i]),
- rotation_z: Number(jsonLayers.rotation_z[i]),
- floor_current_color: jsonLayers.floor_current_color[i],
- geometry_parameters_width: Number(jsonLayers.geometry_parameters_width[i])
- }));
- scene.addLayer(layers[i].plane);
- }
-};
-
-const initializeEdgesFromJSON = (jsonEdges) => {
- for (let i = 0; i < jsonEdges.src.length; i++) {
- createEdgePairs(jsonEdges.channel, i, jsonEdges.src[i], jsonEdges.trg[i],
- Number(jsonEdges.opacity[i]), jsonEdges.color[i]);
- }
- updateChannelArrays(jsonEdges.channel);
-};
-
-const initializeNodesFromJSON = (jsonNodes, jsonScrambleFlag) => {
- let nodeLayerName = "",
- nodeColor;
- nodeNames = undefined; // releasing ram
-
- for (let i = 0; i < jsonNodes.name.length; i++) {
- currentLayer = jsonNodes.layer[i];
- nodeLayerName = jsonNodes.name[i].concat("_").concat(currentLayer);
- nodeLayerNames.push(nodeLayerName); //name + group
- nodeGroups[nodeLayerName] = jsonNodes.layer[i];
-
- nodeColor = jsonNodes.color[i];
- nodeObjects.push(new Node({id: i, name: jsonNodes.name[i],
- layer: nodeGroups[nodeLayerNames[i]], nodeLayerName: nodeLayerNames[i],
- position_x: Number(jsonNodes.position_x[i]),
- position_y: Number(jsonNodes.position_y[i]),
- position_z: Number(jsonNodes.position_z[i]),
- scale: Number(jsonNodes.scale[i]), color: nodeColor,
- url: jsonNodes.url[i], descr: jsonNodes.descr[i]}));
- layers[layerGroups[nodeGroups[nodeLayerName]]].addNode(nodeObjects[i].sphere);
- }
-
- if (jsonScrambleFlag) {
- let minWidth,
- layerWidths = layers.map(({ geometry_parameters_width }) => geometry_parameters_width);
- minWidth = Math.min(...layerWidths);
- scrambleNodes(-minWidth / 2, minWidth / 2, -minWidth / 2, minWidth / 2);
- }
-};
-
-const setJSONExtras = (jsonNetwork) => {
- setLabelColorVariable(jsonNetwork.universalLabelColor);
-
- isDirectionEnabled = jsonNetwork.direction;
- updateCheckboxRShiny("js_direction_checkbox_flag", "edgeDirectionToggle", isDirectionEnabled);
-
- edgeWidthByWeight = jsonNetwork.edgeOpacityByWeight;
- updateCheckboxRShiny("js_edgeByWeight_checkbox_flag", "edgeWidthByWeight", edgeWidthByWeight);
-
- edgeFileColorPriority = true;
- updateCheckboxRShiny("js_edgeFileColorPriority_checkbox_flag", "edgeFileColorPriority", edgeFileColorPriority);
-};
diff --git a/www/js/object_actions/node.js b/www/js/object_actions/node.js
deleted file mode 100644
index c6a8b6d..0000000
--- a/www/js/object_actions/node.js
+++ /dev/null
@@ -1,342 +0,0 @@
-// Initialization =====
-const createNodeDescriptionDiv = () => {
- let descrDiv = document.getElementById("descrDiv"),
- btn = document.createElement("button"),
- p = document.createElement('p');
-
- btn.id = "closeButton";
- btn.innerHTML = "X";
- btn.onclick = function() { descrDiv.style.display = "none"; };
- p.className = "descrDiv_paragraph";
-
- descrDiv.appendChild(btn);
- descrDiv.appendChild(p);
-};
-
-const createNodeObjects = () => {
- let nodeColor;
- for (let i = 0; i < nodeLayerNames.length; i++) {
- nodeColor = getNodeGroupColor(nodeLayerNames[i]);
-
- nodeObjects.push(new Node({id: i, name: nodeNames[i], layer: nodeGroups[nodeLayerNames[i]],
- nodeLayerName: nodeLayerNames[i], color: nodeColor}));
- layers[layerGroups[nodeGroups[nodeLayerNames[i]]]].addNode(nodeObjects[i].sphere);
- }
- nodeNames = undefined; // releasing ram
-};
-
-// @return: hex color code
-const getNodeGroupColor = (nodeLayerName) => {
- return(COLOR_VECTOR_280[(layerGroups[nodeGroups[nodeLayerName]]) % COLOR_VECTOR_280.length])
-};
-
-const scrambleNodes = (yMin = yBoundMin, yMax = yBoundMax,
- zMin = zBoundMin, zMax = zBoundMax) => {
- for (let i = 0; i < nodeObjects.length; i++) {
- nodeObjects[i].translateY(getRandomArbitrary(yMin, yMax));
- nodeObjects[i].translateZ(getRandomArbitrary(zMin, zMax));
- }
-};
-
-// Event Listeners =====
-const checkHoverOverNode = (event) => {
- setRaycaster(event);
- let node_spheres = nodeObjects.map(({ sphere }) => sphere);
- let intersects = RAYCASTER.intersectObjects(node_spheres),
- event_flag = false, // for performance optimization
- hover_flag = false;
-
- // release previous hovered node if exists
- if (last_hovered_node_index !== "") {
- nodeObjects[last_hovered_node_index].setOpacity(1);
- last_hovered_node_index = "";
- event_flag = true;
- }
-
- // check for new hovered node
- if (intersects.length > 0) {
- last_hovered_node_index = findIndexByUuid(node_spheres, intersects[0].object.uuid);
- nodeObjects[last_hovered_node_index].setOpacity(0.5);
- event_flag = true;
- hover_flag = true;
- }
-
- if (event_flag)
- decideNodeLabelFlags();
-
- return(hover_flag)
-};
-
-const decideNodeLabelFlags = () => {
- let hidelayerCheckboxes = document.getElementsByClassName("hideLayer_checkbox"),
- node_layer = "";
-
- renderNodeLabelsFlag = true;
- for (let i = 0; i < nodeObjects.length; i++) {
- node_layer = layerGroups[nodeGroups[nodeLayerNames[i]]];
- // Priorities list:
- if (hidelayerCheckboxes[node_layer].checked) { // 1. if node's layer not hidden
- nodeObjects[i].showLabel = false;
- } else if (showAllNodeLabelsFlag) { // 2. if showing all node labels
- nodeObjects[i].showLabel= true;
- } else if (layers[node_layer].showNodeLabels) { // 3. if showing layer node labels
- nodeObjects[i].showLabel= true;
- } else if (showSelectedNodeLabelsFlag && nodeObjects[i].isSelected) { // 4. if showing selected node labels, and node is selected
- nodeObjects[i].showLabel= true;
- } else if (i === last_hovered_node_index) { // 5. if hovering over node
- nodeObjects[i].showLabel= true;
- } else
- nodeObjects[i].showLabel= false; // 6. if none of the above apply, don't show label
- }
-};
-
-const translateNodesWithHeldKey = (event) => {
- let i,
- selectedNodePositions = getSelectedNodes(),
- step = event.screenX - event.screenY >= mousePreviousX - mousePreviousY ? 20 : -20;
-
- if (scene.axisPressed == "z") {
- for (i = 0; i < selectedNodePositions.length; i++)
- nodeObjects[selectedNodePositions[i]].translateZ(step);
- } else if (scene.axisPressed == "c") {
- for (i = 0; i < selectedNodePositions.length; i++)
- nodeObjects[selectedNodePositions[i]].translateY(step);
- }
-
- redrawIntraLayerEdges();
- updateNodesRShiny();
- updateVRNodesRShiny();
-};
-
-const getSelectedNodes = () => {
- let selectedNodePositions = nodeObjects.map(function(node) {
- if (node.isSelected)
- return(node.id)
- });
- selectedNodePositions = selectedNodePositions.filter(function(id) {
- return(id !== undefined)
- });
- return(selectedNodePositions)
-};
-
-const performDoubleClickNodeSelection = (event) => {
- let intersects, nodeSelected = false,
- node_spheres = nodeObjects.map(({ sphere }) => sphere);
-
- setRaycaster(event);
-
- intersects = RAYCASTER.intersectObjects(node_spheres);
- if (intersects.length > 0) {
- nodeSelected = true;
-
- let ind = findIndexByUuid(node_spheres, intersects[0].object.uuid);
- nodeObjects[ind].isSelected = !nodeObjects[ind].isSelected;
-
- repaintNode(ind);
- decideNodeLabelFlags();
- updateSelectedNodesRShiny();
- }
-
- return(nodeSelected)
-};
-
-const repaintNode = (i) => {
- if (selectedNodeColorFlag && nodeObjects[i].isSelected)
- nodeObjects[i].setColor(SELECTED_DEFAULT_COLOR);
- else if (nodeObjects[i].getCluster() != "" && nodeColorPrioritySource == "cluster")
- nodeObjects[i].setColor(
- COLOR_VECTOR_280[nodeObjects[i].getCluster()],
- importMode = false, clusterMode = true
- );
- else
- nodeObjects[i].setColor(nodeObjects[i].getColor());
-};
-
-// shift + drag left click
-const lassoSelectNodes = (x, y) => {
- let nodeX, nodeY,
- minX = Math.min(shiftX, x),
- maxX = Math.max(shiftX, x),
- minY = Math.min(shiftY, y),
- maxY = Math.max(shiftY, y);
-
- createLassoGeometry(x, y);
-
- for (let i = 0; i < nodeObjects.length; i++) {
- nodeX = nodeObjects[i].getWorldPosition("x");
- nodeY = nodeObjects[i].getWorldPosition("y");
- if (nodeX < maxX && nodeX > minX && nodeY < maxY && nodeY > minY)
- nodeObjects[i].setOpacity(0.5);
- else
- nodeObjects[i].setOpacity(1);
- }
-};
-
-const createLassoGeometry = (x, y) => {
- let geometry, material, points = [];
-
- scene.remove(lasso);
- points.push(
- new THREE.Vector3(shiftX, shiftY, 0),
- new THREE.Vector3(x, shiftY, 0),
- new THREE.Vector3(x, y, 0),
- new THREE.Vector3(shiftX, y, 0),
- new THREE.Vector3(shiftX, shiftY, 0)
- );
- geometry = new THREE.BufferGeometry().setFromPoints(points);
- material = new THREE.LineBasicMaterial({ color: "#eef1b6" });
- lasso = new THREE.Line(geometry, material);
- scene.add(lasso);
-};
-
-// on node searchbar key-press
-const selectSearchedNodes = (event) => {
- if (scene.exists()) {
- let key = window.event.keyCode;
-
- if (key === 13) { // Enter button
- event.preventDefault(); // bypassing newline enter
- startLoader();
-
- let searchString = document.getElementById("nodeSearchBar").value.replace(/\n/g, ""),
- tempIndexes, i, j,
- nodeNames = nodeObjects.map(({ name }) => name);
-
- searchString = searchString.split(",");
- for (i = 0; i < searchString.length; i++) {
- tempIndexes = getCaseInsensitiveIndices(nodeNames, searchString[i].trim()) // case insensitive function
- if (tempIndexes.length > 0) {
- for (j = 0; j < tempIndexes.length; j++) {
- if (!nodeObjects[tempIndexes[j]].isSelected) {
- nodeObjects[tempIndexes[j]].isSelected = true;
- repaintNode(tempIndexes[j]);
- }
- }
- }
- }
- decideNodeLabelFlags();
- updateSelectedNodesRShiny();
- finishLoader();
- }
- }
-};
-
-const unselectAllNodes = () => {
- selectAllNodes(false);
-};
-
-// Handlers =====
-const selectAllNodes = (selectedFlag) => { // T | F
- for (let i = 0; i < nodeObjects.length; i++) {
- nodeObjects[i].isSelected = selectedFlag;
- repaintNode(i);
- }
-
- decideNodeLabelFlags();
- updateSelectedNodesRShiny();
-};
-
-const setNodeShape = (shape) => {
- for (let i = 0; i < nodeObjects.length; i++) {
- nodeObjects[i].setGeometry(shape)
- }
-};
-
-const setNodeColorPriority = (colorPriority) => {
- nodeColorPrioritySource = colorPriority;
-
- repaintNodes();
- updateNodesRShiny();
- updateVRNodesRShiny();
-};
-
-const repaintNodes = () => {
- for (let i = 0; i < nodeObjects.length; i++)
- repaintNode(i);
-};
-
-const setNodeSelectedColorPriority = (colorPriority) => {
- selectedNodeColorFlag = colorPriority;
- repaintNodes();
-};
-
-const clickNodeColorPriority = (mode) => {
- let radioButtonDiv = document.getElementById("nodeColorPriorityRadio");
- if (mode == "default")
- radioButtonDiv.children[1].children[0].click();
- else if (mode == "cluster")
- radioButtonDiv.children[1].children[1].click();
-};
-
-const setNodeAttributes = (nodeAttributes) => {
- let pos;
- for (let i = 0; i < nodeAttributes.length; i++) {
- pos = nodeLayerNames.indexOf(nodeAttributes[i].NodeLayer);
- if (pos > -1) { // if node exists in network
- if (nodeAttributes[i].Color !== undefined && nodeAttributes[i].Color.trim() !== "")
- nodeObjects[pos].setColor(nodeAttributes[i].Color, importMode = true, clusterMode = false);
- if (nodeAttributes[i].Size !== undefined && nodeAttributes[i].Size.trim() !== "")
- nodeObjects[pos].setScale(Number(nodeAttributes[i].Size));
- if (nodeAttributes[i].Url !== undefined && nodeAttributes[i].Url.trim() !== "")
- nodeObjects[pos].url = nodeAttributes[i].Url;
- if (nodeAttributes[i].Description !== undefined && nodeAttributes[i].Description.trim() !== "")
- nodeObjects[pos].descr = nodeAttributes[i].Description;
- }
- }
- updateNodesRShiny();
- updateVRNodesRShiny();
-};
-
-// Canvas Controls =====
-const spreadNodes = (multiplier) => { // 1.1 or 0.9
- let selectedNodePositions = getSelectedNodes();
- if (selectedNodePositions.length > 0) {
- for (let i = 0; i < selectedNodePositions.length; i++) {
- nodeObjects[selectedNodePositions[i]].setPosition("y",
- nodeObjects[selectedNodePositions[i]].getPosition("y") * multiplier);
- nodeObjects[selectedNodePositions[i]].setPosition("z",
- nodeObjects[selectedNodePositions[i]].getPosition("z") * multiplier);
- }
- updateNodesRShiny();
- updateVRNodesRShiny();
- redrawIntraLayerEdges();
- } else
- alert("Please select at least one node.");
-};
-
-const moveNodes = (direction, axis) => {
- let selectedNodePositions = getSelectedNodes();
- if (selectedNodePositions.length > 0) {
- nodeIntervalTimeout = setInterval(function() {
- let value = document.getElementsByClassName("canvasSlider")[4].value;
- value = direction * value;
- for (let i = 0; i < selectedNodePositions.length; i++) {
- if (axis == "X")
- nodeObjects[selectedNodePositions[i]].translateX(value);
- else if (axis == "Y")
- nodeObjects[selectedNodePositions[i]].translateY(value);
- else if (axis == "Z")
- nodeObjects[selectedNodePositions[i]].translateZ(value);
- }
- redrawIntraLayerEdges();
- updateNodesRShiny();
- updateVRNodesRShiny();
- }, 70);
- } else
- alert("Please select at least one node.");
-};
-
-const scaleNodes = () => {
- let selectedNodePositions = getSelectedNodes(),
- cavnasSlider = document.getElementsByClassName("canvasSlider")[5],
- td = document.getElementById("sliderValue6");
- td.innerHTML = "x".concat(cavnasSlider.value);
-
- if (selectedNodePositions.length > 0) {
- for (let i = 0; i < selectedNodePositions.length; i++)
- nodeObjects[selectedNodePositions[i]].setScale(parseFloat(cavnasSlider.value));
-
- updateNodesRShiny();
- } else
- alert("Please select at least one node.");
-};
diff --git a/www/js/object_actions/right_click_menu.js b/www/js/object_actions/right_click_menu.js
deleted file mode 100644
index f8b87cc..0000000
--- a/www/js/object_actions/right_click_menu.js
+++ /dev/null
@@ -1,164 +0,0 @@
-const executeCommand = (item) => {
- let selectedOption = item.options[item.selectedIndex].text;
-
- if (selectedOption !== "-") {
- startLoader();
- if (selectedOption == "Select Neighbors") {
- executeSelectNeightborsCommand(item);
- } else if (selectedOption == "Select MultiLayer Path") {
- executeSelectMultiLayerPathCommand(item);
- } else if (selectedOption == "Select Downstream Path") {
- executeSelectDownstreamPathCommand(item);
- } else if (selectedOption == "Link") {
- window.open(item.value);
- } else if (selectedOption == "Description") {
- let descrDiv = document.getElementById("descrDiv"),
- p = descrDiv.getElementsByTagName('p')[0];
-
- p.innerHTML = item.value;
- descrDiv.style.display = "inline-block";
- }
-
- renderInterLayerEdgesFlag = true;
- redrawIntraLayerEdges();
- decideNodeLabelFlags();
- updateSelectedNodesRShiny();
- updateSelectedEdgesRShiny();
-
- finishLoader();
- }
-};
-
-const executeSelectNeightborsCommand = (item) => {
- let index1, index2;
-
- for (let i = 0; i < edgeObjects.length; i++) {
- index1 = nodeLayerNames.indexOf(edgeObjects[i].source);
- index2 = nodeLayerNames.indexOf(edgeObjects[i].target);
-
- if (index1 == item.value)
- selectNodeAndEdge(index2, i);
- else if (index2 == item.value)
- selectNodeAndEdge(index1, i);
- }
-};
-
-const selectNodeAndEdge = (nodeIndex, edgeIndex) => {
- nodeObjects[nodeIndex].isSelected = true;
- repaintNode(nodeIndex);
-
- edgeObjects[edgeIndex].select();
-};
-
-const executeSelectMultiLayerPathCommand = (item) => {
- let flag = false, index1, index2,
- tempSelectedNodes = [],
- currentNode = item.value,
- startingLayer = nodeGroups[nodeLayerNames[currentNode]],
- checkedNodes = [],
- arrayDifference;
-
- while (!flag) {
- for (let i = 0; i < edgeObjects.length; i++) {
- index1 = nodeLayerNames.indexOf(edgeObjects[i].source);
- index2 = nodeLayerNames.indexOf(edgeObjects[i].target);
-
- if (index1 == currentNode &&
- nodeGroups[nodeLayerNames[index2]] != startingLayer && // path must not contain other nodes in starting layer
- nodeGroups[nodeLayerNames[index2]] != nodeGroups[nodeLayerNames[currentNode]] && // or its own layer
- !(exists(tempSelectedNodes, index2))) {
- tempSelectedNodes.push(index2);
- selectNodeAndEdge(index2, i);
- } else if (index2 == currentNode &&
- nodeGroups[nodeLayerNames[index1]] != startingLayer &&
- nodeGroups[nodeLayerNames[currentNode]] != nodeGroups[nodeLayerNames[index1]] &&
- !(exists(tempSelectedNodes, index1))) {
- tempSelectedNodes.push(index1);
- selectNodeAndEdge(index1, i);
- }
- }
-
- // decide flag for exit, if no unchecked nodes
- checkedNodes.push(currentNode);
- arrayDifference = tempSelectedNodes.filter(x => !checkedNodes.includes(x));
- if (arrayDifference.length === 0)
- flag = true;
- else
- currentNode = arrayDifference[0];
- }
-};
-
-const executeSelectDownstreamPathCommand = (item) => {
- let currentNode = item.value,
- layerPath = [nodeGroups[nodeLayerNames[currentNode]]]; // begin with array of 1 layer
-
- recursiveDownstreamHighlight(layerPath, currentNode);
- downstreamCheckedNodes = []; // resetting global variable
-};
-
-// This function highlights recursively downstream nodes from the clicked node on the starting layer.
-// Makes use of global variable downstreamCheckedNodes (int array) to keep track of already checked nodes for avoiding re-checks
-// @param layerPath (String Array): current path's already checked Layers
-// @param currentNode (integer): current node, whose Layer must be the last in the layerPath array
-// @param previousNode (integer): previous node, to figure which edge to paint
-const recursiveDownstreamHighlight = (layerPath, currentNode, previousNode = null) => {
- let interLayerEdge, neighbors, toCheckLayer;
-
- if (!exists(downstreamCheckedNodes, currentNode)) {
- downstreamCheckedNodes.push(currentNode);
-
- if (previousNode !== null) { // skipping first node call
- interLayerEdge = getInterLayerEdge(currentNode, previousNode);
- if (interLayerEdge !== null)
- selectNodeAndEdge(currentNode, interLayerEdge);
- }
-
- // find node inter-layer neighbors and continue recursively
- neighbors = getInterLayerNeighbors(currentNode);
- for (let i = 0; i < neighbors.length; i++) {
- toCheckLayer = nodeGroups[nodeLayerNames[neighbors[i]]];
- if (!exists(layerPath, toCheckLayer)) {
- layerPath.push(toCheckLayer);
- recursiveDownstreamHighlight(layerPath, neighbors[i], currentNode);
- layerPath.pop();
- }
- }
- }
-};
-
-// This function returns the inter-layer edge connecting two nodes if exists, else null
-const getInterLayerEdge = (node1, node2) => {
- let index1, index2;
-
- for (let i = 0; i < edgeObjects.length; i++) {
- if (edgeObjects[i].interLayer) {
- index1 = nodeLayerNames.indexOf(edgeObjects[i].source);
- index2 = nodeLayerNames.indexOf(edgeObjects[i].target);
-
- if ((node1 == index1 && node2 == index2) ||
- (node1 == index2 && node2 == index1))
- return(i)
- }
- }
-
- return(null)
-};
-
-// This function returns the node IDs of requested node's inter-layer neighbors
-// @param node (integer): node whose neighbors are requested
-// @return int array of neighbor IDs
-const getInterLayerNeighbors = (node) => {
- let index1, index2, neighbors = [];
-
- for (let i = 0; i < edgeObjects.length; i++) {
- index1 = nodeLayerNames.indexOf(edgeObjects[i].source);
- index2 = nodeLayerNames.indexOf(edgeObjects[i].target);
-
- if (node == index1)
- neighbors.push(index2);
- else if (node == index2)
- neighbors.push(index1);
- }
-
- return(neighbors)
-};
diff --git a/www/js/object_actions/screen.js b/www/js/object_actions/screen.js
deleted file mode 100644
index c5f5368..0000000
--- a/www/js/object_actions/screen.js
+++ /dev/null
@@ -1,58 +0,0 @@
-const setRenderer = () => {
- renderer = new THREE.WebGLRenderer({ antialias: true });
-};
-
-const resetScreen = () => {
- setWindowBounds();
- setCamera();
- resizeRenderer();
-}
-
-const setWindowBounds = () => {
- xBoundMin = -window.innerWidth/2,
- xBoundMax = window.innerWidth/2,
- yBoundMin = -window.innerHeight/2,
- yBoundMax = window.innerHeight/2,
- zBoundMin = -window.innerHeight/2.5,
- zBoundMax = window.innerHeight/2.5;
-}
-
-const setCamera = () => {
- // camera: left, right, top, bottom, near, far
- camera = new THREE.OrthographicCamera(xBoundMin, xBoundMax,
- yBoundMax, yBoundMin, -4 * xBoundMax, 4 * xBoundMax);
- camera.position.set(0, 0, 100);
- camera.lookAt(0, 0, 0);
-}
-
-const resizeRenderer = () => {
- renderer.setSize(2 * xBoundMax , 2 * yBoundMax);
-}
-
-const setRendererColor = (hexColor) => {
- if (scene.exists()) {
- renderer.setClearColor(hexColor);
- updateScenePanRShiny();
- }
-}
-
-const setRaycaster = (event) => {
- RAYVECTOR.set((event.clientX / window.innerWidth) * 2 - 1,
- -(event.clientY / window.innerHeight) * 2 + 1, -1 ); // z = - 1 important!
- RAYVECTOR.unproject(camera);
- RAYDIR.set(0, 0, -1).transformDirection(camera.matrixWorld);
- RAYCASTER.set(RAYVECTOR, RAYDIR);
-}
-
-// 3D graphics render animation ====================
-const animate = () => {
- setTimeout(function() { // limiting FPS
- requestAnimationFrame(animate); // pauses when the user navigates to another browser tab
- }, 1000 / fps);
-
- renderInterLayerEdges();
- renderLayerLabels();
- renderNodeLabels();
-
- renderer.render(scene.THREE_Object, camera);
-}
diff --git a/www/js/object_actions/themes.js b/www/js/object_actions/themes.js
deleted file mode 100644
index efd8563..0000000
--- a/www/js/object_actions/themes.js
+++ /dev/null
@@ -1,61 +0,0 @@
-const attachThemeButtons = () => {
- let themeDiv = document.getElementById("themeDiv"),
- lightThemeButton = document.createElement("button"),
- darkThemeButton = document.createElement("button"),
- grayThemeButton = document.createElement("button");
-
- lightThemeButton.className = "themeButton";
- lightThemeButton.id = "lightThemeButton";
- lightThemeButton.innerHTML = "Light";
-
- darkThemeButton.className = "themeButton";
- darkThemeButton.id = "darkThemeButton";
- darkThemeButton.innerHTML = "Dark";
-
- grayThemeButton.className = "themeButton";
- grayThemeButton.id = "grayThemeButton";
- grayThemeButton.innerHTML = "Gray";
-
- lightThemeButton.addEventListener("click", () => {
- applyTheme('#ffffff', '#8aa185', '#5c5c5c',
- CHANNEL_COLORS_DARK, '#000000')
- });
- darkThemeButton.addEventListener("click", () => {
- applyTheme('#000000', '#777777', '#ffffff',
- CHANNEL_COLORS_LIGHT, '#ffffff')
- });
- grayThemeButton.addEventListener("click", () => {
- applyTheme('#999999', '#1d4991', '#6e2a5a',
- CHANNEL_COLORS_LIGHT, '#ffffff')
- });
-
- themeDiv.appendChild(lightThemeButton);
- themeDiv.appendChild(darkThemeButton);
- themeDiv.appendChild(grayThemeButton);
-};
-
-const applyTheme = (bgColor, floorColor,
- edgeColor, channel_colors_theme, labelColor,
- fromInit = false) => {
- if (scene.exists()) {
- setRendererColor(bgColor);
- document.getElementById("floor_color").value = floorColor;
- EDGE_DEFAULT_COLOR = edgeColor; // global for inter-layer edges
- assignChannelColorsFromPalette(channel_colors_theme);
- globalLabelColor = labelColor;
-
- if (!fromInit) {
- repaintLayersFromPicker();
- attachChannelEditList(); // for new channel colors
- redrawIntraLayerEdges();
- setLabelColor();
-
- updateScenePanRShiny();
- updateLayersRShiny();
- updateNodesRShiny();
- updateVRNodesRShiny();
- updateEdgeColorsRShiny();
- updateLabelColorRShiny();
- }
- }
-};
diff --git a/www/js/on_page_load.js b/www/js/on_page_load.js
deleted file mode 100644
index 3a27144..0000000
--- a/www/js/on_page_load.js
+++ /dev/null
@@ -1,65 +0,0 @@
-document.addEventListener('DOMContentLoaded', function() {
- setRenderer();
- resetScreen();
- scene = new Scene();
-
- initializeCanvasDiv();
- addCanvasEventListeners();
- attachThemeButtons();
- initializeColorPickers();
- createNodeDescriptionDiv();
-
- // other events
- window.onresize = resetScreen;
- // release mouse buttons on network div exit
- document.getElementsByClassName("container-fluid")[0].children[0].addEventListener("mouseleave", clickUp);
- let nodeSearchBar = document.getElementById("nodeSearchBar");
- nodeSearchBar.onkeydown = selectSearchedNodes;
-}, false);
-
-const initializeCanvasDiv = () => {
- let canvas_div = document.getElementById("3d-graph");
- canvas_div.style.position='fixed'; // to scroll down along with the page
- //remove default scroll controls while hovering on canvas div
- canvas_div.addEventListener("keydown", function(e) {
- // space and arrow keys
- if([32, 37, 38, 39, 40].indexOf(e.key) > -1) {
- e.preventDefault();
- }
- }, false);
- // removing scroll controls from mouse navigation
- canvas_div.addEventListener("mousedown", function(e) {
- // middle mouse click
- if(e.button == 1) {
- e.preventDefault();
- }
- }, false);
- canvas_div.addEventListener("mousewheel", function(e) {
- e.preventDefault();
- }, false);
- canvas_div.appendChild(renderer.domElement); //create canvas element once
-};
-
-const addCanvasEventListeners = () => {
- let canvas = document.getElementsByTagName("canvas")[0];
- canvas.tabIndex = 1; // default value = -1, giving focus on canvas so it can register keydown events
- canvas.addEventListener("wheel", sceneZoom);
- canvas.addEventListener("keydown", keyPressed);
- canvas.addEventListener("keyup", axisRelease);
- canvas.addEventListener('mousedown', clickDown);
- canvas.addEventListener('mousemove', clickDrag);
- canvas.addEventListener('mouseup', clickUp);
- canvas.addEventListener('dblclick', dblClick);
- canvas.addEventListener('contextmenu', replaceContextMenuOverNode); // implementing right click toolbox over nodes
-};
-
-const initializeColorPickers = () => {
- let sceneColorDiv = document.getElementById("sceneColorPicker");
- sceneColorDiv.innerHTML = ' Background Color ');
- let floorColorDiv = document.getElementById("floorColorPicker");
- floorColorDiv.innerHTML = ' Floor Color ');
-};
diff --git a/www/js/rshiny_handlers.js b/www/js/rshiny_handlers.js
deleted file mode 100644
index 746952f..0000000
--- a/www/js/rshiny_handlers.js
+++ /dev/null
@@ -1,98 +0,0 @@
-// General ====================
-const initializeGlobals = (RGlobalsList) => {
- MAX_LAYERS = RGlobalsList.MAX_LAYERS;
- MAX_EDGES = RGlobalsList.MAX_EDGES;
- MAX_CHANNELS = RGlobalsList.MAX_CHANNELS;
- EDGE_DEFAULT_COLOR = RGlobalsList.EDGE_DEFAULT_COLOR;
- CHANNEL_COLORS_DARK = RGlobalsList.CHANNEL_COLORS_DARK;
- CHANNEL_COLORS_LIGHT = RGlobalsList.CHANNEL_COLORS_LIGHT;
-};
-
-const startLoader = (m) => {
- let canvas_div = document.getElementById("3d-graph"),
- loader = document.getElementById("loader");
- canvas_div.style.opacity = 0.5;
- loader.style.display = "inline-block";
-};
-
-const finishLoader = (m) => {
- let canvas_div = document.getElementById("3d-graph"),
- loader = document.getElementById("loader");
- canvas_div.style.opacity = 1;
- loader.style.display = "none";
-};
-
-const changeFPS = (message) => {
- fps = Number(message);
- if (isNaN(fps))
- fps = 30;
-};
-
-const browseUrl = url => {
- window.open(url, "_blank");
-};
-
-// Scene ====================
-const toggleSceneCoords = (sceneCoordsSwitch) => { // true or false
- if (scene.exists())
- scene.toggleCoords(sceneCoordsSwitch);
-};
-
-const autoRotateScene = (autoRotateFlag) => {
- if (scene.exists()) {
- scene.autoRotate = autoRotateFlag;
- if (!scene.autoRotate)
- clearInterval(scene.intervalTimeout);
- }
-};
-
-//RSHINY HANDLERS----------------------------
-// General ====================
-Shiny.addCustomMessageHandler("handler_initializeGlobals", initializeGlobals);
-Shiny.addCustomMessageHandler("handler_startLoader", startLoader);
-Shiny.addCustomMessageHandler("handler_finishLoader", finishLoader);
-Shiny.addCustomMessageHandler("handler_fps", changeFPS);
-Shiny.addCustomMessageHandler("handler_browseUrl", browseUrl);
-// Files ====================
-Shiny.addCustomMessageHandler("handler_uploadNetwork", uploadNetwork);
-Shiny.addCustomMessageHandler("handler_importNetwork", importNetwork);
-Shiny.addCustomMessageHandler("handler_setNodeAttributes", setNodeAttributes);
-Shiny.addCustomMessageHandler("handler_setEdgeAttributes", setEdgeAttributes);
-// Scene ====================
-Shiny.addCustomMessageHandler("handler_toggleSceneCoords", toggleSceneCoords);
-Shiny.addCustomMessageHandler("handler_autoRotateScene", autoRotateScene);
-// Layers ====================
-Shiny.addCustomMessageHandler("handler_showLayerCoords", showLayerCoords);
-Shiny.addCustomMessageHandler("handler_floorOpacity", setFloorOpacity);
-Shiny.addCustomMessageHandler("handler_showWireFrames", showWireFrames);
-Shiny.addCustomMessageHandler("handler_selectAllLayers", selectAllLayers);
-Shiny.addCustomMessageHandler("handler_setLayerColorPriority", setLayerColorPriority);
-// Nodes ====================
-Shiny.addCustomMessageHandler("handler_selectAllNodes", selectAllNodes);
-Shiny.addCustomMessageHandler("handler_setNodeShape", setNodeShape);
-Shiny.addCustomMessageHandler("handler_setNodeColorPriority", setNodeColorPriority);
-Shiny.addCustomMessageHandler("handler_setNodeSelectedColorPriority", setNodeSelectedColorPriority);
-Shiny.addCustomMessageHandler("handler_clickNodeColorPriority", clickNodeColorPriority);
-// Edges ====================
-Shiny.addCustomMessageHandler("handler_toggleDirection", toggleDirection);
-Shiny.addCustomMessageHandler("handler_setIntraDirectionArrowSize", setIntraDirectionArrowSize);
-Shiny.addCustomMessageHandler("handler_setInterDirectionArrowSize", setInterDirectionArrowSize);
-Shiny.addCustomMessageHandler("handler_setEdgeWidthByWeight", setEdgeWidthByWeight);
-Shiny.addCustomMessageHandler("handler_setIntraLayerEdgeOpacity", setIntraLayerEdgeOpacity);
-Shiny.addCustomMessageHandler("handler_setInterLayerEdgeOpacity", setInterLayerEdgeOpacity);
-Shiny.addCustomMessageHandler("handler_setEdgeSelectedColorPriority", setEdgeSelectedColorPriority);
-Shiny.addCustomMessageHandler("handler_setEdgeFileColorPriority", setEdgeFileColorPriority);
-// Channels ====================
-Shiny.addCustomMessageHandler("handler_toggleIntraChannelCurvature", toggleIntraChannelCurvature);
-Shiny.addCustomMessageHandler("handler_toggleInterChannelCurvature", toggleInterChannelCurvature);
-// Labels ====================
-Shiny.addCustomMessageHandler("handler_showLayerLabels", showLayerLabels);
-Shiny.addCustomMessageHandler("handler_resizeLayerLabels", resizeLayerLabels);
-Shiny.addCustomMessageHandler("handler_showNodeLabels", showNodeLabels);
-Shiny.addCustomMessageHandler("handler_resizeNodeLabels", resizeNodeLabels);
-// Layouts and Topology ====================
-Shiny.addCustomMessageHandler("handler_executeLayout", executeLayout);
-Shiny.addCustomMessageHandler("handler_setPerLayerFlag", setPerLayerFlag);
-Shiny.addCustomMessageHandler("handler_setLocalFlag", setLocalFlag);
-Shiny.addCustomMessageHandler("handler_scaleTopology", scaleTopology);
-Shiny.addCustomMessageHandler("handler_applyPredefinedLayout", applyPredefinedLayout);
diff --git a/www/js/rshiny_update.js b/www/js/rshiny_update.js
deleted file mode 100644
index be770d3..0000000
--- a/www/js/rshiny_update.js
+++ /dev/null
@@ -1,268 +0,0 @@
-// Scene
-const updateScenePanRShiny = () => {
- renderInterLayerEdgesFlag = true;
- let js_scene_pan = {
- "position_x": scene.getPosition("x"),
- "position_y": scene.getPosition("y"),
- "scale": scene.getScale(),
- "color": "#".concat(renderer.getClearColor().getHexString())
- }
- Shiny.setInputValue("js_scene_pan", JSON.stringify(js_scene_pan));
-};
-
-const updateSceneSphereRShiny = () => {
- renderInterLayerEdgesFlag = true;
- let js_scene_sphere = {
- "rotation_x": scene.getRotation("x"),
- "rotation_y": scene.getRotation("y"),
- "rotation_z": scene.getRotation("z")
- }
- Shiny.setInputValue("js_scene_sphere", JSON.stringify(js_scene_sphere));
-};
-
-// Layers
-const updateLayersRShiny = () => {
- let js_layers = [];
-
- renderInterLayerEdgesFlag = true;
- renderLayerLabelsFlag = true;
- for (let i = 0; i < layers.length; i++) {
- js_layers.push(
- [layers[i].getName(), layers[i].getPosition("x"), layers[i].getPosition("y"), layers[i].getPosition("z"),
- layers[i].getScale(), layers[i].getRotation("x"), layers[i].getRotation("y"), layers[i].getRotation("z"),
- layers[i].getColor(), layers[i].getWidth()]
- );
- }
-
- js_layers = js_layers.map(layer => {
- return {
- name: layer[0],
- position_x: layer[1],
- position_y: layer[2],
- position_z: layer[3],
- last_layer_scale: layer[4],
- rotation_x: layer[5],
- rotation_y: layer[6],
- rotation_z: layer[7],
- floor_current_color: layer[8],
- geometry_parameters_width: layer[9],
- }
- });
- Shiny.setInputValue("js_layers", JSON.stringify(js_layers));
-};
-
-const updateLayerNamesRShiny = () => {
- let layer_names = layers.map(({ name }) => name);
- Shiny.setInputValue("js_layer_names", layer_names);
-};
-
-const updateSelectedLayersRShiny = () => {
- let js_selected_layers = getSelectedLayers();
- Shiny.setInputValue("js_selected_layers", js_selected_layers);
-};
-
-// Nodes
-const updateNodesRShiny = () => {
- let js_nodes = [];
-
- renderInterLayerEdgesFlag = true;
- renderNodeLabelsFlag = true;
- for (let i = 0; i < nodeObjects.length; i++) {
- js_nodes.push([
- nodeObjects[i].getName(), nodeObjects[i].getLayer(),
- nodeObjects[i].getPosition("x"),
- nodeObjects[i].getPosition("y"),
- nodeObjects[i].getPosition("z"),
- nodeObjects[i].getScale(), nodeObjects[i].getColor(),
- nodeObjects[i].url, nodeObjects[i].descr
- ]);
- }
-
- js_nodes = js_nodes.map(node => {
- return {
- name: node[0],
- layer: node[1],
- position_x: node[2],
- position_y: node[3],
- position_z: node[4],
- scale: node[5],
- color: node[6],
- url: node[7],
- descr: node[8],
- }
- });
- Shiny.setInputValue("js_nodes", JSON.stringify(js_nodes));
-};
-
-const updateVRNodesRShiny = () => {
- let js_nodes_world = [];
-
- renderNodeLabelsFlag = true;
- for (let i = 0; i < nodeObjects.length; i++) {
- js_nodes_world.push([
- nodeObjects[i].getName(), nodeObjects[i].getLayer(),
- nodeObjects[i].getWorldPosition("x"),
- nodeObjects[i].getWorldPosition("y"),
- nodeObjects[i].getWorldPosition("z"),
- nodeObjects[i].getColor()
- ]);
- }
-
- js_nodes_world = js_nodes_world.map(node => {
- return {
- name: node[0],
- layer: node[1],
- worldPosition_x: node[2],
- worldPosition_y: node[3],
- worldPosition_z: node[4],
- color: node[5]
- }
- });
- Shiny.setInputValue("js_nodes_world", JSON.stringify(js_nodes_world));
-};
-
-// from JS, because need to get positions from js_selectedNodePositions
-const updateNodeNamesRShiny = () => {
- Shiny.setInputValue("js_node_names", nodeLayerNames);
-};
-
-const updateSelectedNodesRShiny = () => {
- Shiny.setInputValue("js_selectedNodePositions", getSelectedNodes());
-};
-
-// Edges
-const updateEdgesRShiny = () => { // only called during network init once
- updateEdgeColorsRShiny();
- let js_edge_pairs = [];
-
- for (let i = 0; i < edgeObjects.length; i++) {
- if (edgeObjects[i].channels.length > 0) {
- for (let j = 0; j < edgeObjects[i].channels.length; j++)
- js_edge_pairs.push([edgeObjects[i].source, edgeObjects[i].target,
- edgeObjects[i].weights[j], edgeObjects[i].channels[j]]);
- } else
- js_edge_pairs.push([edgeObjects[i].source, edgeObjects[i].target,
- edgeObjects[i].weights[0], ""]);
- }
-
- js_edge_pairs = js_edge_pairs.map(edge => {
- return {
- src: edge[0],
- trg: edge[1],
- opacity: edge[2],
- channel: edge[3]
- }
- });
- Shiny.setInputValue("js_edge_pairs", JSON.stringify(js_edge_pairs));
-};
-
-const updateEdgeColorsRShiny = () => {
- let js_edge_colors = [];
-
- for (let i = 0; i < edgeObjects.length; i++) {
- if (edgeObjects[i].channels.length > 0) {
- for (let j = 0; j < edgeObjects[i].channels.length; j++)
- js_edge_colors.push([edgeObjects[i].decideColor(j, true)]); // forExport: true
- } else
- js_edge_colors.push([edgeObjects[i].decideColor(0, true)]); // forExport: true
- }
-
- js_edge_colors = js_edge_colors.map(edge => {
- return { color: edge[0] }
- });
- Shiny.setInputValue("js_edge_colors", JSON.stringify(js_edge_colors));
-};
-
-const updateSelectedEdgesRShiny = () => {
- let js_selectedEdges = [],
- layerNames = layers.map(({ name }) => name),
- nodeNames = nodeObjects.map(({ name }) => name);
-
- for (let i = 0; i < edgeObjects.length; i++) {
- if (edgeObjects[i].isSelected) {
- if (edgeObjects[i].channels.length > 0) {
- for (let j = 0; j < edgeObjects[i].channels.length; j++)
- js_selectedEdges.push([
- nodeNames[edgeObjects[i].sourceNodeIndex],
- layerNames[edgeObjects[i].sourceLayerIndex],
- nodeNames[edgeObjects[i].targetNodeIndex],
- layerNames[edgeObjects[i].targetLayerIndex],
- edgeObjects[i].channels[j],
- edgeObjects[i].weights[j]
- ]);
- } else
- js_selectedEdges.push([
- nodeNames[edgeObjects[i].sourceNodeIndex],
- layerNames[edgeObjects[i].sourceLayerIndex],
- nodeNames[edgeObjects[i].targetNodeIndex],
- layerNames[edgeObjects[i].targetLayerIndex],
- "",
- edgeObjects[i].weights[0]
- ]);
- }
- }
-
- js_selectedEdges = js_selectedEdges.map(edge => {
- return {
- SourceNode: edge[0],
- SourceLayer: edge[1],
- TargetNode: edge[2],
- TargetLayer: edge[3],
- Channel: edge[4],
- Weight: edge[5]
- }
- });
-
- Shiny.setInputValue("js_selectedEdges", JSON.stringify(js_selectedEdges));
-};
-
-const updateSelectedChannelsRShiny = (element) => {
- let index = selectedChannels.indexOf(element.name);
-
- if (index > -1)
- selectedChannels.splice(index, 1);
- else
- selectedChannels.push(element.name);
-
- Shiny.setInputValue("js_selectedChannels", selectedChannels);
-};
-
-// cannot trigger R event in network init without this function
-const updateCheckboxRShiny = (js_var_name, name, value) => {
- // js_var_name: js_direction_checkbox_flag | js_edgeByWeight_checkbox_flag | js_edgeFileColorPriority_checkbox_flag
- Shiny.setInputValue(js_var_name, [name, !value]) // trigger
- Shiny.setInputValue(js_var_name, [name, value])
-};
-
-const updateToggleCurvatureComponentsRShiny = (message) => {
- Shiny.setInputValue("js_channel_curvature_flag", message);
-};
-
-// Labels
-const updateLabelColorRShiny = () => {
- Shiny.setInputValue("js_label_color", globalLabelColor);
-};
-
-const updateVRLayerLabelsRShiny = () => {
- let js_vr_layer_labels = [],
- target = new THREE.Vector3(),
- layerPlanes = layers.map(({ plane }) => plane);
-
- renderLayerLabelsFlag = true;
- for (let i = 0; i < layers.length; i++) {
- js_vr_layer_labels.push(
- [layers[i].getName(), layerPlanes[i].getWorldPosition(target).x,
- layerPlanes[i].getWorldPosition(target).y, layerPlanes[i].getWorldPosition(target).z]
- );
- }
-
- js_vr_layer_labels = js_vr_layer_labels.map(layer => {
- return {
- name: layer[0],
- worldPosition_x: layer[1],
- worldPosition_y: layer[2],
- worldPosition_z: layer[3]
- }
- });
- Shiny.setInputValue("js_vr_layer_labels", JSON.stringify(js_vr_layer_labels));
-};
diff --git a/www/js/three/drag_controls.js b/www/js/three/drag_controls.js
deleted file mode 100644
index 36d39f3..0000000
--- a/www/js/three/drag_controls.js
+++ /dev/null
@@ -1,210 +0,0 @@
-const _plane = new THREE.Plane();
-const _raycaster = new THREE.Raycaster();
-
-const _pointer = new THREE.Vector2();
-const _offset = new THREE.Vector3();
-const _intersection = new THREE.Vector3();
-const _worldPosition = new THREE.Vector3();
-const _inverseMatrix = new Matrix4v();
-
-class DragControls extends THREE.EventDispatcher {
-
- constructor( _objects, _camera, _domElement ) {
-
- super();
-
- _domElement.style.touchAction = 'none'; // disable touch scroll
-
- let _selected = null, _hovered = null;
-
- const _intersections = [];
-
- //
-
- const scope = this;
-
- function activate() {
-
- _domElement.addEventListener( 'pointermove', onPointerMove );
- _domElement.addEventListener( 'pointerdown', onPointerDown );
- _domElement.addEventListener( 'pointerup', onPointerCancel );
- _domElement.addEventListener( 'pointerleave', onPointerCancel );
-
- }
-
- function deactivate() {
-
- _domElement.removeEventListener( 'pointermove', onPointerMove );
- _domElement.removeEventListener( 'pointerdown', onPointerDown );
- _domElement.removeEventListener( 'pointerup', onPointerCancel );
- _domElement.removeEventListener( 'pointerleave', onPointerCancel );
-
- _domElement.style.cursor = '';
-
- }
-
- function dispose() {
-
- deactivate();
-
- }
-
- function getObjects() {
-
- return _objects;
-
- }
-
- function getRaycaster() {
-
- return _raycaster;
-
- }
-
- function onPointerMove( event ) {
- if ( scope.enabled === false ) return;
-
- updatePointer( event );
-
- _raycaster.setFromCamera( _pointer, _camera );
- let layerPlanes = layers.map(({ plane }) => plane);
- if ( _selected && lastHoveredLayerIndex !== "" && scene.leftClickPressed) {
- if (findIndexByUuid(layerPlanes, _selected.uuid) != -1){ //vag update
- if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
- renderLayerLabelsFlag = true;
- renderNodeLabelsFlag = true;
- _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
-
- }
-
- scope.dispatchEvent( { type: 'drag', object: _selected } );
-
- return;
- }
-
- }
-
- // hover support
-
- if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) {
-
- _intersections.length = 0;
-
- _raycaster.setFromCamera( _pointer, _camera );
- _raycaster.intersectObjects( _objects, true, _intersections );
-
- if ( _intersections.length > 0 ) {
-
- const object = _intersections[ 0 ].object;
-
- _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
-
- if ( _hovered !== object && _hovered !== null ) {
-
- scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
-
- _domElement.style.cursor = 'auto';
- _hovered = null;
-
- }
-
- if ( _hovered !== object ) {
-
- scope.dispatchEvent( { type: 'hoveron', object: object } );
-
- _domElement.style.cursor = 'pointer';
- _hovered = object;
-
- }
-
- } else {
-
- if ( _hovered !== null ) {
-
- scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
-
- _domElement.style.cursor = 'auto';
- _hovered = null;
-
- }
-
- }
-
- }
-
- }
-
- function onPointerDown( event ) {
- if ( scope.enabled === false ) return;
-
- updatePointer( event );
-
- _intersections.length = 0;
-
- _raycaster.setFromCamera( _pointer, _camera );
- _raycaster.intersectObjects( _objects, true, _intersections );
-
- if ( _intersections.length > 0 ) {
-
- _selected = ( scope.transformGroup === true ) ? _objects[ 0 ] : _intersections[ 0 ].object;
-
- _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
-
- if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
-
- _inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
- _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
-
- }
-
- _domElement.style.cursor = 'move';
-
- scope.dispatchEvent( { type: 'dragstart', object: _selected } );
-
- }
-
-
- }
-
- function onPointerCancel() {
-
- if ( scope.enabled === false ) return;
-
- if ( _selected ) {
-
- scope.dispatchEvent( { type: 'dragend', object: _selected } );
-
- _selected = null;
- updateLayersRShiny();
- updateVRLayerLabelsRShiny();
- updateVRNodesRShiny();
- }
-
- _domElement.style.cursor = _hovered ? 'pointer' : 'auto';
-
- }
-
- function updatePointer( event ) {
- const rect = _domElement.getBoundingClientRect();
-
- _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
- _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;
-
- }
-
- activate();
-
- // API
-
- this.enabled = true;
- this.transformGroup = false;
-
- this.activate = activate;
- this.deactivate = deactivate;
- this.dispose = dispose;
- this.getObjects = getObjects;
- this.getRaycaster = getRaycaster;
-
- }
-
-}
diff --git a/www/js/three/matrix4.js b/www/js/three/matrix4.js
deleted file mode 100644
index 9600089..0000000
--- a/www/js/three/matrix4.js
+++ /dev/null
@@ -1,881 +0,0 @@
-class Matrix4v {
-
- constructor() {
-
- this.elements = [
-
- 1, 0, 0, 0,
- 0, 1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1
-
- ];
-
- if ( arguments.length > 0 ) {
-
- console.error( 'THREE.Matrix4v: the constructor no longer reads arguments. use .set() instead.' );
-
- }
-
- }
-
- set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
-
- const te = this.elements;
-
- te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14;
- te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24;
- te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34;
- te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44;
-
- return this;
-
- }
-
- identity() {
-
- this.set(
-
- 1, 0, 0, 0,
- 0, 1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- }
-
- clone() {
-
- return new Matrix4v().fromArray( this.elements );
-
- }
-
- copy( m ) {
-
- const te = this.elements;
- const me = m.elements;
-
- te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ];
- te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ];
- te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ];
- te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ];
-
- return this;
-
- }
-
- copyPosition( m ) {
-
- const te = this.elements, me = m.elements;
-
- te[ 12 ] = me[ 12 ];
- te[ 13 ] = me[ 13 ];
- te[ 14 ] = me[ 14 ];
-
- return this;
-
- }
-
- setFromMatrix3( m ) {
-
- const me = m.elements;
-
- this.set(
-
- me[ 0 ], me[ 3 ], me[ 6 ], 0,
- me[ 1 ], me[ 4 ], me[ 7 ], 0,
- me[ 2 ], me[ 5 ], me[ 8 ], 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- }
-
- extractBasis( xAxis, yAxis, zAxis ) {
-
- xAxis.setFromMatrixColumn( this, 0 );
- yAxis.setFromMatrixColumn( this, 1 );
- zAxis.setFromMatrixColumn( this, 2 );
-
- return this;
-
- }
-
- makeBasis( xAxis, yAxis, zAxis ) {
-
- this.set(
- xAxis.x, yAxis.x, zAxis.x, 0,
- xAxis.y, yAxis.y, zAxis.y, 0,
- xAxis.z, yAxis.z, zAxis.z, 0,
- 0, 0, 0, 1
- );
-
- return this;
-
- }
-
- extractRotation( m ) {
-
- // this method does not support reflection matrices
-
- const te = this.elements;
- const me = m.elements;
-
- const scaleX = 1 / _v1.setFromMatrixColumn( m, 0 ).length();
- const scaleY = 1 / _v1.setFromMatrixColumn( m, 1 ).length();
- const scaleZ = 1 / _v1.setFromMatrixColumn( m, 2 ).length();
-
- te[ 0 ] = me[ 0 ] * scaleX;
- te[ 1 ] = me[ 1 ] * scaleX;
- te[ 2 ] = me[ 2 ] * scaleX;
- te[ 3 ] = 0;
-
- te[ 4 ] = me[ 4 ] * scaleY;
- te[ 5 ] = me[ 5 ] * scaleY;
- te[ 6 ] = me[ 6 ] * scaleY;
- te[ 7 ] = 0;
-
- te[ 8 ] = me[ 8 ] * scaleZ;
- te[ 9 ] = me[ 9 ] * scaleZ;
- te[ 10 ] = me[ 10 ] * scaleZ;
- te[ 11 ] = 0;
-
- te[ 12 ] = 0;
- te[ 13 ] = 0;
- te[ 14 ] = 0;
- te[ 15 ] = 1;
-
- return this;
-
- }
-
- makeRotationFromEuler( euler ) {
-
- if ( ! ( euler && euler.isEuler ) ) {
-
- console.error( 'THREE.Matrix4v: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' );
-
- }
-
- const te = this.elements;
-
- const x = euler.x, y = euler.y, z = euler.z;
- const a = Math.cos( x ), b = Math.sin( x );
- const c = Math.cos( y ), d = Math.sin( y );
- const e = Math.cos( z ), f = Math.sin( z );
-
- if ( euler.order === 'XYZ' ) {
-
- const ae = a * e, af = a * f, be = b * e, bf = b * f;
-
- te[ 0 ] = c * e;
- te[ 4 ] = - c * f;
- te[ 8 ] = d;
-
- te[ 1 ] = af + be * d;
- te[ 5 ] = ae - bf * d;
- te[ 9 ] = - b * c;
-
- te[ 2 ] = bf - ae * d;
- te[ 6 ] = be + af * d;
- te[ 10 ] = a * c;
-
- } else if ( euler.order === 'YXZ' ) {
-
- const ce = c * e, cf = c * f, de = d * e, df = d * f;
-
- te[ 0 ] = ce + df * b;
- te[ 4 ] = de * b - cf;
- te[ 8 ] = a * d;
-
- te[ 1 ] = a * f;
- te[ 5 ] = a * e;
- te[ 9 ] = - b;
-
- te[ 2 ] = cf * b - de;
- te[ 6 ] = df + ce * b;
- te[ 10 ] = a * c;
-
- } else if ( euler.order === 'ZXY' ) {
-
- const ce = c * e, cf = c * f, de = d * e, df = d * f;
-
- te[ 0 ] = ce - df * b;
- te[ 4 ] = - a * f;
- te[ 8 ] = de + cf * b;
-
- te[ 1 ] = cf + de * b;
- te[ 5 ] = a * e;
- te[ 9 ] = df - ce * b;
-
- te[ 2 ] = - a * d;
- te[ 6 ] = b;
- te[ 10 ] = a * c;
-
- } else if ( euler.order === 'ZYX' ) {
-
- const ae = a * e, af = a * f, be = b * e, bf = b * f;
-
- te[ 0 ] = c * e;
- te[ 4 ] = be * d - af;
- te[ 8 ] = ae * d + bf;
-
- te[ 1 ] = c * f;
- te[ 5 ] = bf * d + ae;
- te[ 9 ] = af * d - be;
-
- te[ 2 ] = - d;
- te[ 6 ] = b * c;
- te[ 10 ] = a * c;
-
- } else if ( euler.order === 'YZX' ) {
-
- const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
-
- te[ 0 ] = c * e;
- te[ 4 ] = bd - ac * f;
- te[ 8 ] = bc * f + ad;
-
- te[ 1 ] = f;
- te[ 5 ] = a * e;
- te[ 9 ] = - b * e;
-
- te[ 2 ] = - d * e;
- te[ 6 ] = ad * f + bc;
- te[ 10 ] = ac - bd * f;
-
- } else if ( euler.order === 'XZY' ) {
-
- const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
-
- te[ 0 ] = c * e;
- te[ 4 ] = - f;
- te[ 8 ] = d * e;
-
- te[ 1 ] = ac * f + bd;
- te[ 5 ] = a * e;
- te[ 9 ] = ad * f - bc;
-
- te[ 2 ] = bc * f - ad;
- te[ 6 ] = b * e;
- te[ 10 ] = bd * f + ac;
-
- }
-
- // bottom row
- te[ 3 ] = 0;
- te[ 7 ] = 0;
- te[ 11 ] = 0;
-
- // last column
- te[ 12 ] = 0;
- te[ 13 ] = 0;
- te[ 14 ] = 0;
- te[ 15 ] = 1;
-
- return this;
-
- }
-
- makeRotationFromQuaternion( q ) {
-
- return this.compose( _zero, q, _one );
-
- }
-
- lookAt( eye, target, up ) {
-
- const te = this.elements;
-
- _z.subVectors( eye, target );
-
- if ( _z.lengthSq() === 0 ) {
-
- // eye and target are in the same position
-
- _z.z = 1;
-
- }
-
- _z.normalize();
- _x.crossVectors( up, _z );
-
- if ( _x.lengthSq() === 0 ) {
-
- // up and z are parallel
-
- if ( Math.abs( up.z ) === 1 ) {
-
- _z.x += 0.0001;
-
- } else {
-
- _z.z += 0.0001;
-
- }
-
- _z.normalize();
- _x.crossVectors( up, _z );
-
- }
-
- _x.normalize();
- _y.crossVectors( _z, _x );
-
- te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x;
- te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y;
- te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z;
-
- return this;
-
- }
-
- multiply( m, n ) {
-
- if ( n !== undefined ) {
-
- console.warn( 'THREE.Matrix4v: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );
- return this.multiplyMatrices( m, n );
-
- }
-
- return this.multiplyMatrices( this, m );
-
- }
-
- premultiply( m ) {
-
- return this.multiplyMatrices( m, this );
-
- }
-
- multiplyMatrices( a, b ) {
-
- const ae = a.elements;
- const be = b.elements;
- const te = this.elements;
-
- const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ];
- const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ];
- const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ];
- const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ];
-
- const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ];
- const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ];
- const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ];
- const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ];
-
- te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
- te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
- te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
- te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
-
- te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
- te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
- te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
- te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
-
- te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
- te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
- te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
- te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
-
- te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
- te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
- te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
- te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
-
- return this;
-
- }
-
- multiplyScalar( s ) {
-
- const te = this.elements;
-
- te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s;
- te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s;
- te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s;
- te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s;
-
- return this;
-
- }
-
- determinant() {
-
- const te = this.elements;
-
- const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ];
- const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ];
- const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ];
- const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ];
-
- //TODO: make this more efficient
- //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
-
- return (
- n41 * (
- + n14 * n23 * n32
- - n13 * n24 * n32
- - n14 * n22 * n33
- + n12 * n24 * n33
- + n13 * n22 * n34
- - n12 * n23 * n34
- ) +
- n42 * (
- + n11 * n23 * n34
- - n11 * n24 * n33
- + n14 * n21 * n33
- - n13 * n21 * n34
- + n13 * n24 * n31
- - n14 * n23 * n31
- ) +
- n43 * (
- + n11 * n24 * n32
- - n11 * n22 * n34
- - n14 * n21 * n32
- + n12 * n21 * n34
- + n14 * n22 * n31
- - n12 * n24 * n31
- ) +
- n44 * (
- - n13 * n22 * n31
- - n11 * n23 * n32
- + n11 * n22 * n33
- + n13 * n21 * n32
- - n12 * n21 * n33
- + n12 * n23 * n31
- )
-
- );
-
- }
-
- transpose() {
-
- const te = this.elements;
- let tmp;
-
- tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp;
- tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp;
- tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp;
-
- tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp;
- tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp;
- tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp;
-
- return this;
-
- }
-
- setPosition( x, y, z ) {
-
- const te = this.elements;
-
- if ( x.isVector3 ) {
-
- te[ 12 ] = x.x;
- te[ 13 ] = x.y;
- te[ 14 ] = x.z;
-
- } else {
-
- te[ 12 ] = x;
- te[ 13 ] = y;
- te[ 14 ] = z;
-
- }
-
- return this;
-
- }
-
- invert() {
-
- // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
- const te = this.elements,
-
- n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ],
- n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ],
- n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ],
- n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ],
-
- t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,
- t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,
- t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,
- t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
-
- const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
-
- if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
-
- const detInv = 1 / det;
-
- te[ 0 ] = t11 * detInv;
- te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv;
- te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv;
- te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv;
-
- te[ 4 ] = t12 * detInv;
- te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv;
- te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv;
- te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv;
-
- te[ 8 ] = t13 * detInv;
- te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv;
- te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv;
- te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv;
-
- te[ 12 ] = t14 * detInv;
- te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv;
- te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv;
- te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv;
-
- return this;
-
- }
-
- scale( v ) {
-
- const te = this.elements;
- const x = v.x, y = v.y, z = v.z;
-
- te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z;
- te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z;
- te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z;
- te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z;
-
- return this;
-
- }
-
- getMaxScaleOnAxis() {
-
- const te = this.elements;
-
- const scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ];
- const scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ];
- const scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ];
-
- return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) );
-
- }
-
- makeTranslation( x, y, z ) {
-
- this.set(
-
- 1, 0, 0, x,
- 0, 1, 0, y,
- 0, 0, 1, z,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- }
-
- makeRotationX( theta ) {
-
- const c = Math.cos( theta ), s = Math.sin( theta );
-
- this.set(
-
- 1, 0, 0, 0,
- 0, c, - s, 0,
- 0, s, c, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- }
-
- makeRotationY( theta ) {
-
- const c = Math.cos( theta ), s = Math.sin( theta );
-
- this.set(
-
- c, 0, s, 0,
- 0, 1, 0, 0,
- - s, 0, c, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- }
-
- makeRotationZ( theta ) {
-
- const c = Math.cos( theta ), s = Math.sin( theta );
-
- this.set(
-
- c, - s, 0, 0,
- s, c, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- }
-
- makeRotationAxis( axis, angle ) {
-
- // Based on http://www.gamedev.net/reference/articles/article1199.asp
-
- const c = Math.cos( angle );
- const s = Math.sin( angle );
- const t = 1 - c;
- const x = axis.x, y = axis.y, z = axis.z;
- const tx = t * x, ty = t * y;
-
- this.set(
-
- tx * x + c, tx * y - s * z, tx * z + s * y, 0,
- tx * y + s * z, ty * y + c, ty * z - s * x, 0,
- tx * z - s * y, ty * z + s * x, t * z * z + c, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- }
-
- makeScale( x, y, z ) {
-
- this.set(
-
- x, 0, 0, 0,
- 0, y, 0, 0,
- 0, 0, z, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- }
-
- makeShear( xy, xz, yx, yz, zx, zy ) {
-
- this.set(
-
- 1, yx, zx, 0,
- xy, 1, zy, 0,
- xz, yz, 1, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- }
-
- compose( position, quaternion, scale ) {
-
- const te = this.elements;
-
- const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w;
- const x2 = x + x, y2 = y + y, z2 = z + z;
- const xx = x * x2, xy = x * y2, xz = x * z2;
- const yy = y * y2, yz = y * z2, zz = z * z2;
- const wx = w * x2, wy = w * y2, wz = w * z2;
-
- const sx = scale.x, sy = scale.y, sz = scale.z;
-
- te[ 0 ] = ( 1 - ( yy + zz ) ) * sx;
- te[ 1 ] = ( xy + wz ) * sx;
- te[ 2 ] = ( xz - wy ) * sx;
- te[ 3 ] = 0;
-
- te[ 4 ] = ( xy - wz ) * sy;
- te[ 5 ] = ( 1 - ( xx + zz ) ) * sy;
- te[ 6 ] = ( yz + wx ) * sy;
- te[ 7 ] = 0;
-
- te[ 8 ] = ( xz + wy ) * sz;
- te[ 9 ] = ( yz - wx ) * sz;
- te[ 10 ] = ( 1 - ( xx + yy ) ) * sz;
- te[ 11 ] = 0;
-
- te[ 12 ] = position.x;
- te[ 13 ] = position.y;
- te[ 14 ] = position.z;
- te[ 15 ] = 1;
-
- return this;
-
- }
-
- decompose( position, quaternion, scale ) {
-
- const te = this.elements;
-
- let sx = _v1.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();
- const sy = _v1.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();
- const sz = _v1.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();
-
- // if determine is negative, we need to invert one scale
- const det = this.determinant();
- if ( det < 0 ) sx = - sx;
-
- position.x = te[ 12 ];
- position.y = te[ 13 ];
- position.z = te[ 14 ];
-
- // scale the rotation part
- _m1.copy( this );
-
- const invSX = 1 / sx;
- const invSY = 1 / sy;
- const invSZ = 1 / sz;
-
- _m1.elements[ 0 ] *= invSX;
- _m1.elements[ 1 ] *= invSX;
- _m1.elements[ 2 ] *= invSX;
-
- _m1.elements[ 4 ] *= invSY;
- _m1.elements[ 5 ] *= invSY;
- _m1.elements[ 6 ] *= invSY;
-
- _m1.elements[ 8 ] *= invSZ;
- _m1.elements[ 9 ] *= invSZ;
- _m1.elements[ 10 ] *= invSZ;
-
- quaternion.setFromRotationMatrix( _m1 );
-
- scale.x = sx;
- scale.y = sy;
- scale.z = sz;
-
- return this;
-
- }
-
- makePerspective( left, right, top, bottom, near, far ) {
-
- if ( far === undefined ) {
-
- console.warn( 'THREE.Matrix4v: .makePerspective() has been redefined and has a new signature. Please check the docs.' );
-
- }
-
- const te = this.elements;
- const x = 2 * near / ( right - left );
- const y = 2 * near / ( top - bottom );
-
- const a = ( right + left ) / ( right - left );
- const b = ( top + bottom ) / ( top - bottom );
- const c = - ( far + near ) / ( far - near );
- const d = - 2 * far * near / ( far - near );
-
- te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0;
- te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0;
- te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d;
- te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0;
-
- return this;
-
- }
-
- makeOrthographic( left, right, top, bottom, near, far ) {
-
- const te = this.elements;
- const w = 1.0 / ( right - left );
- const h = 1.0 / ( top - bottom );
- const p = 1.0 / ( far - near );
-
- const x = ( right + left ) * w;
- const y = ( top + bottom ) * h;
- const z = ( far + near ) * p;
-
- te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x;
- te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y;
- te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 2 * p; te[ 14 ] = - z;
- te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1;
-
- return this;
-
- }
-
- equals( matrix ) {
-
- const te = this.elements;
- const me = matrix.elements;
-
- for ( let i = 0; i < 16; i ++ ) {
-
- if ( te[ i ] !== me[ i ] ) return false;
-
- }
-
- return true;
-
- }
-
- fromArray( array, offset = 0 ) {
-
- for ( let i = 0; i < 16; i ++ ) {
-
- this.elements[ i ] = array[ i + offset ];
-
- }
-
- return this;
-
- }
-
- toArray( array = [], offset = 0 ) {
-
- const te = this.elements;
-
- array[ offset ] = te[ 0 ];
- array[ offset + 1 ] = te[ 1 ];
- array[ offset + 2 ] = te[ 2 ];
- array[ offset + 3 ] = te[ 3 ];
-
- array[ offset + 4 ] = te[ 4 ];
- array[ offset + 5 ] = te[ 5 ];
- array[ offset + 6 ] = te[ 6 ];
- array[ offset + 7 ] = te[ 7 ];
-
- array[ offset + 8 ] = te[ 8 ];
- array[ offset + 9 ] = te[ 9 ];
- array[ offset + 10 ] = te[ 10 ];
- array[ offset + 11 ] = te[ 11 ];
-
- array[ offset + 12 ] = te[ 12 ];
- array[ offset + 13 ] = te[ 13 ];
- array[ offset + 14 ] = te[ 14 ];
- array[ offset + 15 ] = te[ 15 ];
-
- return array;
-
- }
-
-}
-
-Matrix4v.prototype.isMatrix4 = true;
-
-const _v1 = /*@__PURE__*/ new THREE.Vector3();
-const _m1 = /*@__PURE__*/ new Matrix4v();
-const _zero = /*@__PURE__*/ new THREE.Vector3( 0, 0, 0 );
-const _one = /*@__PURE__*/ new THREE.Vector3( 1, 1, 1 );
-const _x = /*@__PURE__*/ new THREE.Vector3();
-const _y = /*@__PURE__*/ new THREE.Vector3();
-const _z = /*@__PURE__*/ new THREE.Vector3();
diff --git a/www/js/three/three.js b/www/js/three/three.js
deleted file mode 100644
index 41d4a6e..0000000
--- a/www/js/three/three.js
+++ /dev/null
@@ -1,51266 +0,0 @@
-(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
- (global = global || self, factory(global.THREE = {}));
-}(this, (function (exports) { 'use strict';
-
- // Polyfills
-
- if ( Number.EPSILON === undefined ) {
-
- Number.EPSILON = Math.pow( 2, - 52 );
-
- }
-
- if ( Number.isInteger === undefined ) {
-
- // Missing in IE
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
-
- Number.isInteger = function ( value ) {
-
- return typeof value === 'number' && isFinite( value ) && Math.floor( value ) === value;
-
- };
-
- }
-
- //
-
- if ( Math.sign === undefined ) {
-
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign
-
- Math.sign = function ( x ) {
-
- return ( x < 0 ) ? - 1 : ( x > 0 ) ? 1 : + x;
-
- };
-
- }
-
- if ( 'name' in Function.prototype === false ) {
-
- // Missing in IE
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
-
- Object.defineProperty( Function.prototype, 'name', {
-
- get: function () {
-
- return this.toString().match( /^\s*function\s*([^\(\s]*)/ )[ 1 ];
-
- }
-
- } );
-
- }
-
- if ( Object.assign === undefined ) {
-
- // Missing in IE
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
-
- Object.assign = function ( target ) {
-
- if ( target === undefined || target === null ) {
-
- throw new TypeError( 'Cannot convert undefined or null to object' );
-
- }
-
- var output = Object( target );
-
- for ( var index = 1; index < arguments.length; index ++ ) {
-
- var source = arguments[ index ];
-
- if ( source !== undefined && source !== null ) {
-
- for ( var nextKey in source ) {
-
- if ( Object.prototype.hasOwnProperty.call( source, nextKey ) ) {
-
- output[ nextKey ] = source[ nextKey ];
-
- }
-
- }
-
- }
-
- }
-
- return output;
-
- };
-
- }
-
- var REVISION = '117';
- var MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2, ROTATE: 0, DOLLY: 1, PAN: 2 };
- var TOUCH = { ROTATE: 0, PAN: 1, DOLLY_PAN: 2, DOLLY_ROTATE: 3 };
- var CullFaceNone = 0;
- var CullFaceBack = 1;
- var CullFaceFront = 2;
- var CullFaceFrontBack = 3;
- var FrontFaceDirectionCW = 0;
- var FrontFaceDirectionCCW = 1;
- var BasicShadowMap = 0;
- var PCFShadowMap = 1;
- var PCFSoftShadowMap = 2;
- var VSMShadowMap = 3;
- var FrontSide = 0;
- var BackSide = 1;
- var DoubleSide = 2;
- var FlatShading = 1;
- var SmoothShading = 2;
- var NoBlending = 0;
- var NormalBlending = 1;
- var AdditiveBlending = 2;
- var SubtractiveBlending = 3;
- var MultiplyBlending = 4;
- var CustomBlending = 5;
- var AddEquation = 100;
- var SubtractEquation = 101;
- var ReverseSubtractEquation = 102;
- var MinEquation = 103;
- var MaxEquation = 104;
- var ZeroFactor = 200;
- var OneFactor = 201;
- var SrcColorFactor = 202;
- var OneMinusSrcColorFactor = 203;
- var SrcAlphaFactor = 204;
- var OneMinusSrcAlphaFactor = 205;
- var DstAlphaFactor = 206;
- var OneMinusDstAlphaFactor = 207;
- var DstColorFactor = 208;
- var OneMinusDstColorFactor = 209;
- var SrcAlphaSaturateFactor = 210;
- var NeverDepth = 0;
- var AlwaysDepth = 1;
- var LessDepth = 2;
- var LessEqualDepth = 3;
- var EqualDepth = 4;
- var GreaterEqualDepth = 5;
- var GreaterDepth = 6;
- var NotEqualDepth = 7;
- var MultiplyOperation = 0;
- var MixOperation = 1;
- var AddOperation = 2;
- var NoToneMapping = 0;
- var LinearToneMapping = 1;
- var ReinhardToneMapping = 2;
- var Uncharted2ToneMapping = 3;
- var CineonToneMapping = 4;
- var ACESFilmicToneMapping = 5;
-
- var UVMapping = 300;
- var CubeReflectionMapping = 301;
- var CubeRefractionMapping = 302;
- var EquirectangularReflectionMapping = 303;
- var EquirectangularRefractionMapping = 304;
- var SphericalReflectionMapping = 305;
- var CubeUVReflectionMapping = 306;
- var CubeUVRefractionMapping = 307;
- var RepeatWrapping = 1000;
- var ClampToEdgeWrapping = 1001;
- var MirroredRepeatWrapping = 1002;
- var NearestFilter = 1003;
- var NearestMipmapNearestFilter = 1004;
- var NearestMipMapNearestFilter = 1004;
- var NearestMipmapLinearFilter = 1005;
- var NearestMipMapLinearFilter = 1005;
- var LinearFilter = 1006;
- var LinearMipmapNearestFilter = 1007;
- var LinearMipMapNearestFilter = 1007;
- var LinearMipmapLinearFilter = 1008;
- var LinearMipMapLinearFilter = 1008;
- var UnsignedByteType = 1009;
- var ByteType = 1010;
- var ShortType = 1011;
- var UnsignedShortType = 1012;
- var IntType = 1013;
- var UnsignedIntType = 1014;
- var FloatType = 1015;
- var HalfFloatType = 1016;
- var UnsignedShort4444Type = 1017;
- var UnsignedShort5551Type = 1018;
- var UnsignedShort565Type = 1019;
- var UnsignedInt248Type = 1020;
- var AlphaFormat = 1021;
- var RGBFormat = 1022;
- var RGBAFormat = 1023;
- var LuminanceFormat = 1024;
- var LuminanceAlphaFormat = 1025;
- var RGBEFormat = RGBAFormat;
- var DepthFormat = 1026;
- var DepthStencilFormat = 1027;
- var RedFormat = 1028;
- var RedIntegerFormat = 1029;
- var RGFormat = 1030;
- var RGIntegerFormat = 1031;
- var RGBIntegerFormat = 1032;
- var RGBAIntegerFormat = 1033;
-
- var RGB_S3TC_DXT1_Format = 33776;
- var RGBA_S3TC_DXT1_Format = 33777;
- var RGBA_S3TC_DXT3_Format = 33778;
- var RGBA_S3TC_DXT5_Format = 33779;
- var RGB_PVRTC_4BPPV1_Format = 35840;
- var RGB_PVRTC_2BPPV1_Format = 35841;
- var RGBA_PVRTC_4BPPV1_Format = 35842;
- var RGBA_PVRTC_2BPPV1_Format = 35843;
- var RGB_ETC1_Format = 36196;
- var RGB_ETC2_Format = 37492;
- var RGBA_ETC2_EAC_Format = 37496;
- var RGBA_ASTC_4x4_Format = 37808;
- var RGBA_ASTC_5x4_Format = 37809;
- var RGBA_ASTC_5x5_Format = 37810;
- var RGBA_ASTC_6x5_Format = 37811;
- var RGBA_ASTC_6x6_Format = 37812;
- var RGBA_ASTC_8x5_Format = 37813;
- var RGBA_ASTC_8x6_Format = 37814;
- var RGBA_ASTC_8x8_Format = 37815;
- var RGBA_ASTC_10x5_Format = 37816;
- var RGBA_ASTC_10x6_Format = 37817;
- var RGBA_ASTC_10x8_Format = 37818;
- var RGBA_ASTC_10x10_Format = 37819;
- var RGBA_ASTC_12x10_Format = 37820;
- var RGBA_ASTC_12x12_Format = 37821;
- var RGBA_BPTC_Format = 36492;
- var SRGB8_ALPHA8_ASTC_4x4_Format = 37840;
- var SRGB8_ALPHA8_ASTC_5x4_Format = 37841;
- var SRGB8_ALPHA8_ASTC_5x5_Format = 37842;
- var SRGB8_ALPHA8_ASTC_6x5_Format = 37843;
- var SRGB8_ALPHA8_ASTC_6x6_Format = 37844;
- var SRGB8_ALPHA8_ASTC_8x5_Format = 37845;
- var SRGB8_ALPHA8_ASTC_8x6_Format = 37846;
- var SRGB8_ALPHA8_ASTC_8x8_Format = 37847;
- var SRGB8_ALPHA8_ASTC_10x5_Format = 37848;
- var SRGB8_ALPHA8_ASTC_10x6_Format = 37849;
- var SRGB8_ALPHA8_ASTC_10x8_Format = 37850;
- var SRGB8_ALPHA8_ASTC_10x10_Format = 37851;
- var SRGB8_ALPHA8_ASTC_12x10_Format = 37852;
- var SRGB8_ALPHA8_ASTC_12x12_Format = 37853;
- var LoopOnce = 2200;
- var LoopRepeat = 2201;
- var LoopPingPong = 2202;
- var InterpolateDiscrete = 2300;
- var InterpolateLinear = 2301;
- var InterpolateSmooth = 2302;
- var ZeroCurvatureEnding = 2400;
- var ZeroSlopeEnding = 2401;
- var WrapAroundEnding = 2402;
- var NormalAnimationBlendMode = 2500;
- var AdditiveAnimationBlendMode = 2501;
- var TrianglesDrawMode = 0;
- var TriangleStripDrawMode = 1;
- var TriangleFanDrawMode = 2;
- var LinearEncoding = 3000;
- var sRGBEncoding = 3001;
- var GammaEncoding = 3007;
- var RGBEEncoding = 3002;
- var LogLuvEncoding = 3003;
- var RGBM7Encoding = 3004;
- var RGBM16Encoding = 3005;
- var RGBDEncoding = 3006;
- var BasicDepthPacking = 3200;
- var RGBADepthPacking = 3201;
- var TangentSpaceNormalMap = 0;
- var ObjectSpaceNormalMap = 1;
-
- var ZeroStencilOp = 0;
- var KeepStencilOp = 7680;
- var ReplaceStencilOp = 7681;
- var IncrementStencilOp = 7682;
- var DecrementStencilOp = 7683;
- var IncrementWrapStencilOp = 34055;
- var DecrementWrapStencilOp = 34056;
- var InvertStencilOp = 5386;
-
- var NeverStencilFunc = 512;
- var LessStencilFunc = 513;
- var EqualStencilFunc = 514;
- var LessEqualStencilFunc = 515;
- var GreaterStencilFunc = 516;
- var NotEqualStencilFunc = 517;
- var GreaterEqualStencilFunc = 518;
- var AlwaysStencilFunc = 519;
-
- var StaticDrawUsage = 35044;
- var DynamicDrawUsage = 35048;
- var StreamDrawUsage = 35040;
- var StaticReadUsage = 35045;
- var DynamicReadUsage = 35049;
- var StreamReadUsage = 35041;
- var StaticCopyUsage = 35046;
- var DynamicCopyUsage = 35050;
- var StreamCopyUsage = 35042;
-
- /**
- * https://github.com/mrdoob/eventdispatcher.js/
- */
-
- function EventDispatcher() {}
-
- Object.assign( EventDispatcher.prototype, {
-
- addEventListener: function ( type, listener ) {
-
- if ( this._listeners === undefined ) { this._listeners = {}; }
-
- var listeners = this._listeners;
-
- if ( listeners[ type ] === undefined ) {
-
- listeners[ type ] = [];
-
- }
-
- if ( listeners[ type ].indexOf( listener ) === - 1 ) {
-
- listeners[ type ].push( listener );
-
- }
-
- },
-
- hasEventListener: function ( type, listener ) {
-
- if ( this._listeners === undefined ) { return false; }
-
- var listeners = this._listeners;
-
- return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;
-
- },
-
- removeEventListener: function ( type, listener ) {
-
- if ( this._listeners === undefined ) { return; }
-
- var listeners = this._listeners;
- var listenerArray = listeners[ type ];
-
- if ( listenerArray !== undefined ) {
-
- var index = listenerArray.indexOf( listener );
-
- if ( index !== - 1 ) {
-
- listenerArray.splice( index, 1 );
-
- }
-
- }
-
- },
-
- dispatchEvent: function ( event ) {
-
- if ( this._listeners === undefined ) { return; }
-
- var listeners = this._listeners;
- var listenerArray = listeners[ event.type ];
-
- if ( listenerArray !== undefined ) {
-
- event.target = this;
-
- // Make a copy, in case listeners are removed while iterating.
- var array = listenerArray.slice( 0 );
-
- for ( var i = 0, l = array.length; i < l; i ++ ) {
-
- array[ i ].call( this, event );
-
- }
-
- }
-
- }
-
- } );
-
- /**
- * @author alteredq / http://alteredqualia.com/
- * @author mrdoob / http://mrdoob.com/
- * @author WestLangley / http://github.com/WestLangley
- * @author thezwap
- */
-
- var _lut = [];
-
- for ( var i = 0; i < 256; i ++ ) {
-
- _lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 );
-
- }
-
- var MathUtils = {
-
- DEG2RAD: Math.PI / 180,
- RAD2DEG: 180 / Math.PI,
-
- generateUUID: function () {
-
- // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
-
- var d0 = Math.random() * 0xffffffff | 0;
- var d1 = Math.random() * 0xffffffff | 0;
- var d2 = Math.random() * 0xffffffff | 0;
- var d3 = Math.random() * 0xffffffff | 0;
- var uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
- _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
- _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
- _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
-
- // .toUpperCase() here flattens concatenated strings to save heap memory space.
- return uuid.toUpperCase();
-
- },
-
- clamp: function ( value, min, max ) {
-
- return Math.max( min, Math.min( max, value ) );
-
- },
-
- // compute euclidian modulo of m % n
- // https://en.wikipedia.org/wiki/Modulo_operation
-
- euclideanModulo: function ( n, m ) {
-
- return ( ( n % m ) + m ) % m;
-
- },
-
- // Linear mapping from range to range
-
- mapLinear: function ( x, a1, a2, b1, b2 ) {
-
- return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
-
- },
-
- // https://en.wikipedia.org/wiki/Linear_interpolation
-
- lerp: function ( x, y, t ) {
-
- return ( 1 - t ) * x + t * y;
-
- },
-
- // http://en.wikipedia.org/wiki/Smoothstep
-
- smoothstep: function ( x, min, max ) {
-
- if ( x <= min ) { return 0; }
- if ( x >= max ) { return 1; }
-
- x = ( x - min ) / ( max - min );
-
- return x * x * ( 3 - 2 * x );
-
- },
-
- smootherstep: function ( x, min, max ) {
-
- if ( x <= min ) { return 0; }
- if ( x >= max ) { return 1; }
-
- x = ( x - min ) / ( max - min );
-
- return x * x * x * ( x * ( x * 6 - 15 ) + 10 );
-
- },
-
- // Random integer from interval
-
- randInt: function ( low, high ) {
-
- return low + Math.floor( Math.random() * ( high - low + 1 ) );
-
- },
-
- // Random float from interval
-
- randFloat: function ( low, high ) {
-
- return low + Math.random() * ( high - low );
-
- },
-
- // Random float from <-range/2, range/2> interval
-
- randFloatSpread: function ( range ) {
-
- return range * ( 0.5 - Math.random() );
-
- },
-
- degToRad: function ( degrees ) {
-
- return degrees * MathUtils.DEG2RAD;
-
- },
-
- radToDeg: function ( radians ) {
-
- return radians * MathUtils.RAD2DEG;
-
- },
-
- isPowerOfTwo: function ( value ) {
-
- return ( value & ( value - 1 ) ) === 0 && value !== 0;
-
- },
-
- ceilPowerOfTwo: function ( value ) {
-
- return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) );
-
- },
-
- floorPowerOfTwo: function ( value ) {
-
- return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) );
-
- },
-
- setQuaternionFromProperEuler: function ( q, a, b, c, order ) {
-
- // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles
-
- // rotations are applied to the axes in the order specified by 'order'
- // rotation by angle 'a' is applied first, then by angle 'b', then by angle 'c'
- // angles are in radians
-
- var cos = Math.cos;
- var sin = Math.sin;
-
- var c2 = cos( b / 2 );
- var s2 = sin( b / 2 );
-
- var c13 = cos( ( a + c ) / 2 );
- var s13 = sin( ( a + c ) / 2 );
-
- var c1_3 = cos( ( a - c ) / 2 );
- var s1_3 = sin( ( a - c ) / 2 );
-
- var c3_1 = cos( ( c - a ) / 2 );
- var s3_1 = sin( ( c - a ) / 2 );
-
- switch ( order ) {
-
- case 'XYX':
- q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 );
- break;
-
- case 'YZY':
- q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 );
- break;
-
- case 'ZXZ':
- q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 );
- break;
-
- case 'XZX':
- q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 );
- break;
-
- case 'YXY':
- q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 );
- break;
-
- case 'ZYZ':
- q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 );
- break;
-
- default:
- console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order );
-
- }
-
- }
-
- };
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author philogb / http://blog.thejit.org/
- * @author egraether / http://egraether.com/
- * @author zz85 / http://www.lab4games.net/zz85/blog
- */
-
- function Vector2( x, y ) {
-
- this.x = x || 0;
- this.y = y || 0;
-
- }
-
- Object.defineProperties( Vector2.prototype, {
-
- "width": {
-
- get: function () {
-
- return this.x;
-
- },
-
- set: function ( value ) {
-
- this.x = value;
-
- }
-
- },
-
- "height": {
-
- get: function () {
-
- return this.y;
-
- },
-
- set: function ( value ) {
-
- this.y = value;
-
- }
-
- }
-
- } );
-
- Object.assign( Vector2.prototype, {
-
- isVector2: true,
-
- set: function ( x, y ) {
-
- this.x = x;
- this.y = y;
-
- return this;
-
- },
-
- setScalar: function ( scalar ) {
-
- this.x = scalar;
- this.y = scalar;
-
- return this;
-
- },
-
- setX: function ( x ) {
-
- this.x = x;
-
- return this;
-
- },
-
- setY: function ( y ) {
-
- this.y = y;
-
- return this;
-
- },
-
- setComponent: function ( index, value ) {
-
- switch ( index ) {
-
- case 0: this.x = value; break;
- case 1: this.y = value; break;
- default: throw new Error( 'index is out of range: ' + index );
-
- }
-
- return this;
-
- },
-
- getComponent: function ( index ) {
-
- switch ( index ) {
-
- case 0: return this.x;
- case 1: return this.y;
- default: throw new Error( 'index is out of range: ' + index );
-
- }
-
- },
-
- clone: function () {
-
- return new this.constructor( this.x, this.y );
-
- },
-
- copy: function ( v ) {
-
- this.x = v.x;
- this.y = v.y;
-
- return this;
-
- },
-
- add: function ( v, w ) {
-
- if ( w !== undefined ) {
-
- console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
- return this.addVectors( v, w );
-
- }
-
- this.x += v.x;
- this.y += v.y;
-
- return this;
-
- },
-
- addScalar: function ( s ) {
-
- this.x += s;
- this.y += s;
-
- return this;
-
- },
-
- addVectors: function ( a, b ) {
-
- this.x = a.x + b.x;
- this.y = a.y + b.y;
-
- return this;
-
- },
-
- addScaledVector: function ( v, s ) {
-
- this.x += v.x * s;
- this.y += v.y * s;
-
- return this;
-
- },
-
- sub: function ( v, w ) {
-
- if ( w !== undefined ) {
-
- console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
- return this.subVectors( v, w );
-
- }
-
- this.x -= v.x;
- this.y -= v.y;
-
- return this;
-
- },
-
- subScalar: function ( s ) {
-
- this.x -= s;
- this.y -= s;
-
- return this;
-
- },
-
- subVectors: function ( a, b ) {
-
- this.x = a.x - b.x;
- this.y = a.y - b.y;
-
- return this;
-
- },
-
- multiply: function ( v ) {
-
- this.x *= v.x;
- this.y *= v.y;
-
- return this;
-
- },
-
- multiplyScalar: function ( scalar ) {
-
- this.x *= scalar;
- this.y *= scalar;
-
- return this;
-
- },
-
- divide: function ( v ) {
-
- this.x /= v.x;
- this.y /= v.y;
-
- return this;
-
- },
-
- divideScalar: function ( scalar ) {
-
- return this.multiplyScalar( 1 / scalar );
-
- },
-
- applyMatrix3: function ( m ) {
-
- var x = this.x, y = this.y;
- var e = m.elements;
-
- this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ];
- this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ];
-
- return this;
-
- },
-
- min: function ( v ) {
-
- this.x = Math.min( this.x, v.x );
- this.y = Math.min( this.y, v.y );
-
- return this;
-
- },
-
- max: function ( v ) {
-
- this.x = Math.max( this.x, v.x );
- this.y = Math.max( this.y, v.y );
-
- return this;
-
- },
-
- clamp: function ( min, max ) {
-
- // assumes min < max, componentwise
-
- this.x = Math.max( min.x, Math.min( max.x, this.x ) );
- this.y = Math.max( min.y, Math.min( max.y, this.y ) );
-
- return this;
-
- },
-
- clampScalar: function ( minVal, maxVal ) {
-
- this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
- this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
-
- return this;
-
- },
-
- clampLength: function ( min, max ) {
-
- var length = this.length();
-
- return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
-
- },
-
- floor: function () {
-
- this.x = Math.floor( this.x );
- this.y = Math.floor( this.y );
-
- return this;
-
- },
-
- ceil: function () {
-
- this.x = Math.ceil( this.x );
- this.y = Math.ceil( this.y );
-
- return this;
-
- },
-
- round: function () {
-
- this.x = Math.round( this.x );
- this.y = Math.round( this.y );
-
- return this;
-
- },
-
- roundToZero: function () {
-
- this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
- this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
-
- return this;
-
- },
-
- negate: function () {
-
- this.x = - this.x;
- this.y = - this.y;
-
- return this;
-
- },
-
- dot: function ( v ) {
-
- return this.x * v.x + this.y * v.y;
-
- },
-
- cross: function ( v ) {
-
- return this.x * v.y - this.y * v.x;
-
- },
-
- lengthSq: function () {
-
- return this.x * this.x + this.y * this.y;
-
- },
-
- length: function () {
-
- return Math.sqrt( this.x * this.x + this.y * this.y );
-
- },
-
- manhattanLength: function () {
-
- return Math.abs( this.x ) + Math.abs( this.y );
-
- },
-
- normalize: function () {
-
- return this.divideScalar( this.length() || 1 );
-
- },
-
- angle: function () {
-
- // computes the angle in radians with respect to the positive x-axis
-
- var angle = Math.atan2( - this.y, - this.x ) + Math.PI;
-
- return angle;
-
- },
-
- distanceTo: function ( v ) {
-
- return Math.sqrt( this.distanceToSquared( v ) );
-
- },
-
- distanceToSquared: function ( v ) {
-
- var dx = this.x - v.x, dy = this.y - v.y;
- return dx * dx + dy * dy;
-
- },
-
- manhattanDistanceTo: function ( v ) {
-
- return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y );
-
- },
-
- setLength: function ( length ) {
-
- return this.normalize().multiplyScalar( length );
-
- },
-
- lerp: function ( v, alpha ) {
-
- this.x += ( v.x - this.x ) * alpha;
- this.y += ( v.y - this.y ) * alpha;
-
- return this;
-
- },
-
- lerpVectors: function ( v1, v2, alpha ) {
-
- this.x = v1.x + ( v2.x - v1.x ) * alpha;
- this.y = v1.y + ( v2.y - v1.y ) * alpha;
-
- return this;
-
- },
-
- equals: function ( v ) {
-
- return ( ( v.x === this.x ) && ( v.y === this.y ) );
-
- },
-
- fromArray: function ( array, offset ) {
-
- if ( offset === undefined ) { offset = 0; }
-
- this.x = array[ offset ];
- this.y = array[ offset + 1 ];
-
- return this;
-
- },
-
- toArray: function ( array, offset ) {
-
- if ( array === undefined ) { array = []; }
- if ( offset === undefined ) { offset = 0; }
-
- array[ offset ] = this.x;
- array[ offset + 1 ] = this.y;
-
- return array;
-
- },
-
- fromBufferAttribute: function ( attribute, index, offset ) {
-
- if ( offset !== undefined ) {
-
- console.warn( 'THREE.Vector2: offset has been removed from .fromBufferAttribute().' );
-
- }
-
- this.x = attribute.getX( index );
- this.y = attribute.getY( index );
-
- return this;
-
- },
-
- rotateAround: function ( center, angle ) {
-
- var c = Math.cos( angle ), s = Math.sin( angle );
-
- var x = this.x - center.x;
- var y = this.y - center.y;
-
- this.x = x * c - y * s + center.x;
- this.y = x * s + y * c + center.y;
-
- return this;
-
- },
-
- random: function () {
-
- this.x = Math.random();
- this.y = Math.random();
-
- return this;
-
- }
-
- } );
-
- /**
- * @author alteredq / http://alteredqualia.com/
- * @author WestLangley / http://github.com/WestLangley
- * @author bhouston / http://clara.io
- * @author tschw
- */
-
- function Matrix3() {
-
- this.elements = [
-
- 1, 0, 0,
- 0, 1, 0,
- 0, 0, 1
-
- ];
-
- if ( arguments.length > 0 ) {
-
- console.error( 'THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.' );
-
- }
-
- }
-
- Object.assign( Matrix3.prototype, {
-
- isMatrix3: true,
-
- set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
-
- var te = this.elements;
-
- te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31;
- te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32;
- te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33;
-
- return this;
-
- },
-
- identity: function () {
-
- this.set(
-
- 1, 0, 0,
- 0, 1, 0,
- 0, 0, 1
-
- );
-
- return this;
-
- },
-
- clone: function () {
-
- return new this.constructor().fromArray( this.elements );
-
- },
-
- copy: function ( m ) {
-
- var te = this.elements;
- var me = m.elements;
-
- te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ];
- te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ];
- te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ];
-
- return this;
-
- },
-
- extractBasis: function ( xAxis, yAxis, zAxis ) {
-
- xAxis.setFromMatrix3Column( this, 0 );
- yAxis.setFromMatrix3Column( this, 1 );
- zAxis.setFromMatrix3Column( this, 2 );
-
- return this;
-
- },
-
- setFromMatrix4: function ( m ) {
-
- var me = m.elements;
-
- this.set(
-
- me[ 0 ], me[ 4 ], me[ 8 ],
- me[ 1 ], me[ 5 ], me[ 9 ],
- me[ 2 ], me[ 6 ], me[ 10 ]
-
- );
-
- return this;
-
- },
-
- multiply: function ( m ) {
-
- return this.multiplyMatrices( this, m );
-
- },
-
- premultiply: function ( m ) {
-
- return this.multiplyMatrices( m, this );
-
- },
-
- multiplyMatrices: function ( a, b ) {
-
- var ae = a.elements;
- var be = b.elements;
- var te = this.elements;
-
- var a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ];
- var a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ];
- var a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ];
-
- var b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ];
- var b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ];
- var b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ];
-
- te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31;
- te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32;
- te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33;
-
- te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31;
- te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32;
- te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33;
-
- te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31;
- te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32;
- te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33;
-
- return this;
-
- },
-
- multiplyScalar: function ( s ) {
-
- var te = this.elements;
-
- te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s;
- te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s;
- te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s;
-
- return this;
-
- },
-
- determinant: function () {
-
- var te = this.elements;
-
- var a = te[ 0 ], b = te[ 1 ], c = te[ 2 ],
- d = te[ 3 ], e = te[ 4 ], f = te[ 5 ],
- g = te[ 6 ], h = te[ 7 ], i = te[ 8 ];
-
- return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;
-
- },
-
- getInverse: function ( matrix, throwOnDegenerate ) {
-
- if ( throwOnDegenerate !== undefined ) {
-
- console.warn( "THREE.Matrix3: .getInverse() can no longer be configured to throw on degenerate." );
-
- }
-
- var me = matrix.elements,
- te = this.elements,
-
- n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ],
- n12 = me[ 3 ], n22 = me[ 4 ], n32 = me[ 5 ],
- n13 = me[ 6 ], n23 = me[ 7 ], n33 = me[ 8 ],
-
- t11 = n33 * n22 - n32 * n23,
- t12 = n32 * n13 - n33 * n12,
- t13 = n23 * n12 - n22 * n13,
-
- det = n11 * t11 + n21 * t12 + n31 * t13;
-
- if ( det === 0 ) { return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); }
-
- var detInv = 1 / det;
-
- te[ 0 ] = t11 * detInv;
- te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv;
- te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv;
-
- te[ 3 ] = t12 * detInv;
- te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv;
- te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv;
-
- te[ 6 ] = t13 * detInv;
- te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv;
- te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv;
-
- return this;
-
- },
-
- transpose: function () {
-
- var tmp, m = this.elements;
-
- tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp;
- tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp;
- tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp;
-
- return this;
-
- },
-
- getNormalMatrix: function ( matrix4 ) {
-
- return this.setFromMatrix4( matrix4 ).getInverse( this ).transpose();
-
- },
-
- transposeIntoArray: function ( r ) {
-
- var m = this.elements;
-
- r[ 0 ] = m[ 0 ];
- r[ 1 ] = m[ 3 ];
- r[ 2 ] = m[ 6 ];
- r[ 3 ] = m[ 1 ];
- r[ 4 ] = m[ 4 ];
- r[ 5 ] = m[ 7 ];
- r[ 6 ] = m[ 2 ];
- r[ 7 ] = m[ 5 ];
- r[ 8 ] = m[ 8 ];
-
- return this;
-
- },
-
- setUvTransform: function ( tx, ty, sx, sy, rotation, cx, cy ) {
-
- var c = Math.cos( rotation );
- var s = Math.sin( rotation );
-
- this.set(
- sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx,
- - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty,
- 0, 0, 1
- );
-
- },
-
- scale: function ( sx, sy ) {
-
- var te = this.elements;
-
- te[ 0 ] *= sx; te[ 3 ] *= sx; te[ 6 ] *= sx;
- te[ 1 ] *= sy; te[ 4 ] *= sy; te[ 7 ] *= sy;
-
- return this;
-
- },
-
- rotate: function ( theta ) {
-
- var c = Math.cos( theta );
- var s = Math.sin( theta );
-
- var te = this.elements;
-
- var a11 = te[ 0 ], a12 = te[ 3 ], a13 = te[ 6 ];
- var a21 = te[ 1 ], a22 = te[ 4 ], a23 = te[ 7 ];
-
- te[ 0 ] = c * a11 + s * a21;
- te[ 3 ] = c * a12 + s * a22;
- te[ 6 ] = c * a13 + s * a23;
-
- te[ 1 ] = - s * a11 + c * a21;
- te[ 4 ] = - s * a12 + c * a22;
- te[ 7 ] = - s * a13 + c * a23;
-
- return this;
-
- },
-
- translate: function ( tx, ty ) {
-
- var te = this.elements;
-
- te[ 0 ] += tx * te[ 2 ]; te[ 3 ] += tx * te[ 5 ]; te[ 6 ] += tx * te[ 8 ];
- te[ 1 ] += ty * te[ 2 ]; te[ 4 ] += ty * te[ 5 ]; te[ 7 ] += ty * te[ 8 ];
-
- return this;
-
- },
-
- equals: function ( matrix ) {
-
- var te = this.elements;
- var me = matrix.elements;
-
- for ( var i = 0; i < 9; i ++ ) {
-
- if ( te[ i ] !== me[ i ] ) { return false; }
-
- }
-
- return true;
-
- },
-
- fromArray: function ( array, offset ) {
-
- if ( offset === undefined ) { offset = 0; }
-
- for ( var i = 0; i < 9; i ++ ) {
-
- this.elements[ i ] = array[ i + offset ];
-
- }
-
- return this;
-
- },
-
- toArray: function ( array, offset ) {
-
- if ( array === undefined ) { array = []; }
- if ( offset === undefined ) { offset = 0; }
-
- var te = this.elements;
-
- array[ offset ] = te[ 0 ];
- array[ offset + 1 ] = te[ 1 ];
- array[ offset + 2 ] = te[ 2 ];
-
- array[ offset + 3 ] = te[ 3 ];
- array[ offset + 4 ] = te[ 4 ];
- array[ offset + 5 ] = te[ 5 ];
-
- array[ offset + 6 ] = te[ 6 ];
- array[ offset + 7 ] = te[ 7 ];
- array[ offset + 8 ] = te[ 8 ];
-
- return array;
-
- }
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author alteredq / http://alteredqualia.com/
- * @author szimek / https://github.com/szimek/
- */
-
- var _canvas;
-
- var ImageUtils = {
-
- getDataURL: function ( image ) {
-
- var canvas;
-
- if ( typeof HTMLCanvasElement == 'undefined' ) {
-
- return image.src;
-
- } else if ( image instanceof HTMLCanvasElement ) {
-
- canvas = image;
-
- } else {
-
- if ( _canvas === undefined ) { _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); }
-
- _canvas.width = image.width;
- _canvas.height = image.height;
-
- var context = _canvas.getContext( '2d' );
-
- if ( image instanceof ImageData ) {
-
- context.putImageData( image, 0, 0 );
-
- } else {
-
- context.drawImage( image, 0, 0, image.width, image.height );
-
- }
-
- canvas = _canvas;
-
- }
-
- if ( canvas.width > 2048 || canvas.height > 2048 ) {
-
- return canvas.toDataURL( 'image/jpeg', 0.6 );
-
- } else {
-
- return canvas.toDataURL( 'image/png' );
-
- }
-
- }
-
- };
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author alteredq / http://alteredqualia.com/
- * @author szimek / https://github.com/szimek/
- */
-
- var textureId = 0;
-
- function Texture( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) {
-
- Object.defineProperty( this, 'id', { value: textureId ++ } );
-
- this.uuid = MathUtils.generateUUID();
-
- this.name = '';
-
- this.image = image !== undefined ? image : Texture.DEFAULT_IMAGE;
- this.mipmaps = [];
-
- this.mapping = mapping !== undefined ? mapping : Texture.DEFAULT_MAPPING;
-
- this.wrapS = wrapS !== undefined ? wrapS : ClampToEdgeWrapping;
- this.wrapT = wrapT !== undefined ? wrapT : ClampToEdgeWrapping;
-
- this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
- this.minFilter = minFilter !== undefined ? minFilter : LinearMipmapLinearFilter;
-
- this.anisotropy = anisotropy !== undefined ? anisotropy : 1;
-
- this.format = format !== undefined ? format : RGBAFormat;
- this.internalFormat = null;
- this.type = type !== undefined ? type : UnsignedByteType;
-
- this.offset = new Vector2( 0, 0 );
- this.repeat = new Vector2( 1, 1 );
- this.center = new Vector2( 0, 0 );
- this.rotation = 0;
-
- this.matrixAutoUpdate = true;
- this.matrix = new Matrix3();
-
- this.generateMipmaps = true;
- this.premultiplyAlpha = false;
- this.flipY = true;
- this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)
-
- // Values of encoding !== THREE.LinearEncoding only supported on map, envMap and emissiveMap.
- //
- // Also changing the encoding after already used by a Material will not automatically make the Material
- // update. You need to explicitly call Material.needsUpdate to trigger it to recompile.
- this.encoding = encoding !== undefined ? encoding : LinearEncoding;
-
- this.version = 0;
- this.onUpdate = null;
-
- }
-
- Texture.DEFAULT_IMAGE = undefined;
- Texture.DEFAULT_MAPPING = UVMapping;
-
- Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
-
- constructor: Texture,
-
- isTexture: true,
-
- updateMatrix: function () {
-
- this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y );
-
- },
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- },
-
- copy: function ( source ) {
-
- this.name = source.name;
-
- this.image = source.image;
- this.mipmaps = source.mipmaps.slice( 0 );
-
- this.mapping = source.mapping;
-
- this.wrapS = source.wrapS;
- this.wrapT = source.wrapT;
-
- this.magFilter = source.magFilter;
- this.minFilter = source.minFilter;
-
- this.anisotropy = source.anisotropy;
-
- this.format = source.format;
- this.internalFormat = source.internalFormat;
- this.type = source.type;
-
- this.offset.copy( source.offset );
- this.repeat.copy( source.repeat );
- this.center.copy( source.center );
- this.rotation = source.rotation;
-
- this.matrixAutoUpdate = source.matrixAutoUpdate;
- this.matrix.copy( source.matrix );
-
- this.generateMipmaps = source.generateMipmaps;
- this.premultiplyAlpha = source.premultiplyAlpha;
- this.flipY = source.flipY;
- this.unpackAlignment = source.unpackAlignment;
- this.encoding = source.encoding;
-
- return this;
-
- },
-
- toJSON: function ( meta ) {
-
- var isRootObject = ( meta === undefined || typeof meta === 'string' );
-
- if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) {
-
- return meta.textures[ this.uuid ];
-
- }
-
- var output = {
-
- metadata: {
- version: 4.5,
- type: 'Texture',
- generator: 'Texture.toJSON'
- },
-
- uuid: this.uuid,
- name: this.name,
-
- mapping: this.mapping,
-
- repeat: [ this.repeat.x, this.repeat.y ],
- offset: [ this.offset.x, this.offset.y ],
- center: [ this.center.x, this.center.y ],
- rotation: this.rotation,
-
- wrap: [ this.wrapS, this.wrapT ],
-
- format: this.format,
- type: this.type,
- encoding: this.encoding,
-
- minFilter: this.minFilter,
- magFilter: this.magFilter,
- anisotropy: this.anisotropy,
-
- flipY: this.flipY,
-
- premultiplyAlpha: this.premultiplyAlpha,
- unpackAlignment: this.unpackAlignment
-
- };
-
- if ( this.image !== undefined ) {
-
- // TODO: Move to THREE.Image
-
- var image = this.image;
-
- if ( image.uuid === undefined ) {
-
- image.uuid = MathUtils.generateUUID(); // UGH
-
- }
-
- if ( ! isRootObject && meta.images[ image.uuid ] === undefined ) {
-
- var url;
-
- if ( Array.isArray( image ) ) {
-
- // process array of images e.g. CubeTexture
-
- url = [];
-
- for ( var i = 0, l = image.length; i < l; i ++ ) {
-
- url.push( ImageUtils.getDataURL( image[ i ] ) );
-
- }
-
- } else {
-
- // process single image
-
- url = ImageUtils.getDataURL( image );
-
- }
-
- meta.images[ image.uuid ] = {
- uuid: image.uuid,
- url: url
- };
-
- }
-
- output.image = image.uuid;
-
- }
-
- if ( ! isRootObject ) {
-
- meta.textures[ this.uuid ] = output;
-
- }
-
- return output;
-
- },
-
- dispose: function () {
-
- this.dispatchEvent( { type: 'dispose' } );
-
- },
-
- transformUv: function ( uv ) {
-
- if ( this.mapping !== UVMapping ) { return uv; }
-
- uv.applyMatrix3( this.matrix );
-
- if ( uv.x < 0 || uv.x > 1 ) {
-
- switch ( this.wrapS ) {
-
- case RepeatWrapping:
-
- uv.x = uv.x - Math.floor( uv.x );
- break;
-
- case ClampToEdgeWrapping:
-
- uv.x = uv.x < 0 ? 0 : 1;
- break;
-
- case MirroredRepeatWrapping:
-
- if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) {
-
- uv.x = Math.ceil( uv.x ) - uv.x;
-
- } else {
-
- uv.x = uv.x - Math.floor( uv.x );
-
- }
-
- break;
-
- }
-
- }
-
- if ( uv.y < 0 || uv.y > 1 ) {
-
- switch ( this.wrapT ) {
-
- case RepeatWrapping:
-
- uv.y = uv.y - Math.floor( uv.y );
- break;
-
- case ClampToEdgeWrapping:
-
- uv.y = uv.y < 0 ? 0 : 1;
- break;
-
- case MirroredRepeatWrapping:
-
- if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) {
-
- uv.y = Math.ceil( uv.y ) - uv.y;
-
- } else {
-
- uv.y = uv.y - Math.floor( uv.y );
-
- }
-
- break;
-
- }
-
- }
-
- if ( this.flipY ) {
-
- uv.y = 1 - uv.y;
-
- }
-
- return uv;
-
- }
-
- } );
-
- Object.defineProperty( Texture.prototype, "needsUpdate", {
-
- set: function ( value ) {
-
- if ( value === true ) { this.version ++; }
-
- }
-
- } );
-
- /**
- * @author supereggbert / http://www.paulbrunt.co.uk/
- * @author philogb / http://blog.thejit.org/
- * @author mikael emtinger / http://gomo.se/
- * @author egraether / http://egraether.com/
- * @author WestLangley / http://github.com/WestLangley
- */
-
- function Vector4( x, y, z, w ) {
-
- this.x = x || 0;
- this.y = y || 0;
- this.z = z || 0;
- this.w = ( w !== undefined ) ? w : 1;
-
- }
-
- Object.defineProperties( Vector4.prototype, {
-
- "width": {
-
- get: function () {
-
- return this.z;
-
- },
-
- set: function ( value ) {
-
- this.z = value;
-
- }
-
- },
-
- "height": {
-
- get: function () {
-
- return this.w;
-
- },
-
- set: function ( value ) {
-
- this.w = value;
-
- }
-
- }
-
- } );
-
- Object.assign( Vector4.prototype, {
-
- isVector4: true,
-
- set: function ( x, y, z, w ) {
-
- this.x = x;
- this.y = y;
- this.z = z;
- this.w = w;
-
- return this;
-
- },
-
- setScalar: function ( scalar ) {
-
- this.x = scalar;
- this.y = scalar;
- this.z = scalar;
- this.w = scalar;
-
- return this;
-
- },
-
- setX: function ( x ) {
-
- this.x = x;
-
- return this;
-
- },
-
- setY: function ( y ) {
-
- this.y = y;
-
- return this;
-
- },
-
- setZ: function ( z ) {
-
- this.z = z;
-
- return this;
-
- },
-
- setW: function ( w ) {
-
- this.w = w;
-
- return this;
-
- },
-
- setComponent: function ( index, value ) {
-
- switch ( index ) {
-
- case 0: this.x = value; break;
- case 1: this.y = value; break;
- case 2: this.z = value; break;
- case 3: this.w = value; break;
- default: throw new Error( 'index is out of range: ' + index );
-
- }
-
- return this;
-
- },
-
- getComponent: function ( index ) {
-
- switch ( index ) {
-
- case 0: return this.x;
- case 1: return this.y;
- case 2: return this.z;
- case 3: return this.w;
- default: throw new Error( 'index is out of range: ' + index );
-
- }
-
- },
-
- clone: function () {
-
- return new this.constructor( this.x, this.y, this.z, this.w );
-
- },
-
- copy: function ( v ) {
-
- this.x = v.x;
- this.y = v.y;
- this.z = v.z;
- this.w = ( v.w !== undefined ) ? v.w : 1;
-
- return this;
-
- },
-
- add: function ( v, w ) {
-
- if ( w !== undefined ) {
-
- console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
- return this.addVectors( v, w );
-
- }
-
- this.x += v.x;
- this.y += v.y;
- this.z += v.z;
- this.w += v.w;
-
- return this;
-
- },
-
- addScalar: function ( s ) {
-
- this.x += s;
- this.y += s;
- this.z += s;
- this.w += s;
-
- return this;
-
- },
-
- addVectors: function ( a, b ) {
-
- this.x = a.x + b.x;
- this.y = a.y + b.y;
- this.z = a.z + b.z;
- this.w = a.w + b.w;
-
- return this;
-
- },
-
- addScaledVector: function ( v, s ) {
-
- this.x += v.x * s;
- this.y += v.y * s;
- this.z += v.z * s;
- this.w += v.w * s;
-
- return this;
-
- },
-
- sub: function ( v, w ) {
-
- if ( w !== undefined ) {
-
- console.warn( 'THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
- return this.subVectors( v, w );
-
- }
-
- this.x -= v.x;
- this.y -= v.y;
- this.z -= v.z;
- this.w -= v.w;
-
- return this;
-
- },
-
- subScalar: function ( s ) {
-
- this.x -= s;
- this.y -= s;
- this.z -= s;
- this.w -= s;
-
- return this;
-
- },
-
- subVectors: function ( a, b ) {
-
- this.x = a.x - b.x;
- this.y = a.y - b.y;
- this.z = a.z - b.z;
- this.w = a.w - b.w;
-
- return this;
-
- },
-
- multiplyScalar: function ( scalar ) {
-
- this.x *= scalar;
- this.y *= scalar;
- this.z *= scalar;
- this.w *= scalar;
-
- return this;
-
- },
-
- applyMatrix4: function ( m ) {
-
- var x = this.x, y = this.y, z = this.z, w = this.w;
- var e = m.elements;
-
- this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w;
- this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w;
- this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w;
- this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w;
-
- return this;
-
- },
-
- divideScalar: function ( scalar ) {
-
- return this.multiplyScalar( 1 / scalar );
-
- },
-
- setAxisAngleFromQuaternion: function ( q ) {
-
- // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
-
- // q is assumed to be normalized
-
- this.w = 2 * Math.acos( q.w );
-
- var s = Math.sqrt( 1 - q.w * q.w );
-
- if ( s < 0.0001 ) {
-
- this.x = 1;
- this.y = 0;
- this.z = 0;
-
- } else {
-
- this.x = q.x / s;
- this.y = q.y / s;
- this.z = q.z / s;
-
- }
-
- return this;
-
- },
-
- setAxisAngleFromRotationMatrix: function ( m ) {
-
- // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm
-
- // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
-
- var angle, x, y, z, // variables for result
- epsilon = 0.01, // margin to allow for rounding errors
- epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees
-
- te = m.elements,
-
- m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
- m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
- m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];
-
- if ( ( Math.abs( m12 - m21 ) < epsilon ) &&
- ( Math.abs( m13 - m31 ) < epsilon ) &&
- ( Math.abs( m23 - m32 ) < epsilon ) ) {
-
- // singularity found
- // first check for identity matrix which must have +1 for all terms
- // in leading diagonal and zero in other terms
-
- if ( ( Math.abs( m12 + m21 ) < epsilon2 ) &&
- ( Math.abs( m13 + m31 ) < epsilon2 ) &&
- ( Math.abs( m23 + m32 ) < epsilon2 ) &&
- ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {
-
- // this singularity is identity matrix so angle = 0
-
- this.set( 1, 0, 0, 0 );
-
- return this; // zero angle, arbitrary axis
-
- }
-
- // otherwise this singularity is angle = 180
-
- angle = Math.PI;
-
- var xx = ( m11 + 1 ) / 2;
- var yy = ( m22 + 1 ) / 2;
- var zz = ( m33 + 1 ) / 2;
- var xy = ( m12 + m21 ) / 4;
- var xz = ( m13 + m31 ) / 4;
- var yz = ( m23 + m32 ) / 4;
-
- if ( ( xx > yy ) && ( xx > zz ) ) {
-
- // m11 is the largest diagonal term
-
- if ( xx < epsilon ) {
-
- x = 0;
- y = 0.707106781;
- z = 0.707106781;
-
- } else {
-
- x = Math.sqrt( xx );
- y = xy / x;
- z = xz / x;
-
- }
-
- } else if ( yy > zz ) {
-
- // m22 is the largest diagonal term
-
- if ( yy < epsilon ) {
-
- x = 0.707106781;
- y = 0;
- z = 0.707106781;
-
- } else {
-
- y = Math.sqrt( yy );
- x = xy / y;
- z = yz / y;
-
- }
-
- } else {
-
- // m33 is the largest diagonal term so base result on this
-
- if ( zz < epsilon ) {
-
- x = 0.707106781;
- y = 0.707106781;
- z = 0;
-
- } else {
-
- z = Math.sqrt( zz );
- x = xz / z;
- y = yz / z;
-
- }
-
- }
-
- this.set( x, y, z, angle );
-
- return this; // return 180 deg rotation
-
- }
-
- // as we have reached here there are no singularities so we can handle normally
-
- var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) +
- ( m13 - m31 ) * ( m13 - m31 ) +
- ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize
-
- if ( Math.abs( s ) < 0.001 ) { s = 1; }
-
- // prevent divide by zero, should not happen if matrix is orthogonal and should be
- // caught by singularity test above, but I've left it in just in case
-
- this.x = ( m32 - m23 ) / s;
- this.y = ( m13 - m31 ) / s;
- this.z = ( m21 - m12 ) / s;
- this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );
-
- return this;
-
- },
-
- min: function ( v ) {
-
- this.x = Math.min( this.x, v.x );
- this.y = Math.min( this.y, v.y );
- this.z = Math.min( this.z, v.z );
- this.w = Math.min( this.w, v.w );
-
- return this;
-
- },
-
- max: function ( v ) {
-
- this.x = Math.max( this.x, v.x );
- this.y = Math.max( this.y, v.y );
- this.z = Math.max( this.z, v.z );
- this.w = Math.max( this.w, v.w );
-
- return this;
-
- },
-
- clamp: function ( min, max ) {
-
- // assumes min < max, componentwise
-
- this.x = Math.max( min.x, Math.min( max.x, this.x ) );
- this.y = Math.max( min.y, Math.min( max.y, this.y ) );
- this.z = Math.max( min.z, Math.min( max.z, this.z ) );
- this.w = Math.max( min.w, Math.min( max.w, this.w ) );
-
- return this;
-
- },
-
- clampScalar: function ( minVal, maxVal ) {
-
- this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
- this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
- this.z = Math.max( minVal, Math.min( maxVal, this.z ) );
- this.w = Math.max( minVal, Math.min( maxVal, this.w ) );
-
- return this;
-
- },
-
- clampLength: function ( min, max ) {
-
- var length = this.length();
-
- return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
-
- },
-
- floor: function () {
-
- this.x = Math.floor( this.x );
- this.y = Math.floor( this.y );
- this.z = Math.floor( this.z );
- this.w = Math.floor( this.w );
-
- return this;
-
- },
-
- ceil: function () {
-
- this.x = Math.ceil( this.x );
- this.y = Math.ceil( this.y );
- this.z = Math.ceil( this.z );
- this.w = Math.ceil( this.w );
-
- return this;
-
- },
-
- round: function () {
-
- this.x = Math.round( this.x );
- this.y = Math.round( this.y );
- this.z = Math.round( this.z );
- this.w = Math.round( this.w );
-
- return this;
-
- },
-
- roundToZero: function () {
-
- this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
- this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
- this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );
- this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w );
-
- return this;
-
- },
-
- negate: function () {
-
- this.x = - this.x;
- this.y = - this.y;
- this.z = - this.z;
- this.w = - this.w;
-
- return this;
-
- },
-
- dot: function ( v ) {
-
- return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
-
- },
-
- lengthSq: function () {
-
- return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
-
- },
-
- length: function () {
-
- return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
-
- },
-
- manhattanLength: function () {
-
- return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w );
-
- },
-
- normalize: function () {
-
- return this.divideScalar( this.length() || 1 );
-
- },
-
- setLength: function ( length ) {
-
- return this.normalize().multiplyScalar( length );
-
- },
-
- lerp: function ( v, alpha ) {
-
- this.x += ( v.x - this.x ) * alpha;
- this.y += ( v.y - this.y ) * alpha;
- this.z += ( v.z - this.z ) * alpha;
- this.w += ( v.w - this.w ) * alpha;
-
- return this;
-
- },
-
- lerpVectors: function ( v1, v2, alpha ) {
-
- this.x = v1.x + ( v2.x - v1.x ) * alpha;
- this.y = v1.y + ( v2.y - v1.y ) * alpha;
- this.z = v1.z + ( v2.z - v1.z ) * alpha;
- this.w = v1.w + ( v2.w - v1.w ) * alpha;
-
- return this;
-
- },
-
- equals: function ( v ) {
-
- return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );
-
- },
-
- fromArray: function ( array, offset ) {
-
- if ( offset === undefined ) { offset = 0; }
-
- this.x = array[ offset ];
- this.y = array[ offset + 1 ];
- this.z = array[ offset + 2 ];
- this.w = array[ offset + 3 ];
-
- return this;
-
- },
-
- toArray: function ( array, offset ) {
-
- if ( array === undefined ) { array = []; }
- if ( offset === undefined ) { offset = 0; }
-
- array[ offset ] = this.x;
- array[ offset + 1 ] = this.y;
- array[ offset + 2 ] = this.z;
- array[ offset + 3 ] = this.w;
-
- return array;
-
- },
-
- fromBufferAttribute: function ( attribute, index, offset ) {
-
- if ( offset !== undefined ) {
-
- console.warn( 'THREE.Vector4: offset has been removed from .fromBufferAttribute().' );
-
- }
-
- this.x = attribute.getX( index );
- this.y = attribute.getY( index );
- this.z = attribute.getZ( index );
- this.w = attribute.getW( index );
-
- return this;
-
- },
-
- random: function () {
-
- this.x = Math.random();
- this.y = Math.random();
- this.z = Math.random();
- this.w = Math.random();
-
- return this;
-
- }
-
- } );
-
- /**
- * @author szimek / https://github.com/szimek/
- * @author alteredq / http://alteredqualia.com/
- * @author Marius Kintel / https://github.com/kintel
- */
-
- /*
- In options, we can specify:
- * Texture parameters for an auto-generated target texture
- * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers
- */
- function WebGLRenderTarget( width, height, options ) {
-
- this.width = width;
- this.height = height;
-
- this.scissor = new Vector4( 0, 0, width, height );
- this.scissorTest = false;
-
- this.viewport = new Vector4( 0, 0, width, height );
-
- options = options || {};
-
- this.texture = new Texture( undefined, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding );
-
- this.texture.image = {};
- this.texture.image.width = width;
- this.texture.image.height = height;
-
- this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false;
- this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter;
-
- this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;
- this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true;
- this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null;
-
- }
-
- WebGLRenderTarget.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
-
- constructor: WebGLRenderTarget,
-
- isWebGLRenderTarget: true,
-
- setSize: function ( width, height ) {
-
- if ( this.width !== width || this.height !== height ) {
-
- this.width = width;
- this.height = height;
-
- this.texture.image.width = width;
- this.texture.image.height = height;
-
- this.dispose();
-
- }
-
- this.viewport.set( 0, 0, width, height );
- this.scissor.set( 0, 0, width, height );
-
- },
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- },
-
- copy: function ( source ) {
-
- this.width = source.width;
- this.height = source.height;
-
- this.viewport.copy( source.viewport );
-
- this.texture = source.texture.clone();
-
- this.depthBuffer = source.depthBuffer;
- this.stencilBuffer = source.stencilBuffer;
- this.depthTexture = source.depthTexture;
-
- return this;
-
- },
-
- dispose: function () {
-
- this.dispatchEvent( { type: 'dispose' } );
-
- }
-
- } );
-
- /**
- * @author Mugen87 / https://github.com/Mugen87
- * @author Matt DesLauriers / @mattdesl
- */
-
- function WebGLMultisampleRenderTarget( width, height, options ) {
-
- WebGLRenderTarget.call( this, width, height, options );
-
- this.samples = 4;
-
- }
-
- WebGLMultisampleRenderTarget.prototype = Object.assign( Object.create( WebGLRenderTarget.prototype ), {
-
- constructor: WebGLMultisampleRenderTarget,
-
- isWebGLMultisampleRenderTarget: true,
-
- copy: function ( source ) {
-
- WebGLRenderTarget.prototype.copy.call( this, source );
-
- this.samples = source.samples;
-
- return this;
-
- }
-
- } );
-
- /**
- * @author mikael emtinger / http://gomo.se/
- * @author alteredq / http://alteredqualia.com/
- * @author WestLangley / http://github.com/WestLangley
- * @author bhouston / http://clara.io
- */
-
- function Quaternion( x, y, z, w ) {
-
- this._x = x || 0;
- this._y = y || 0;
- this._z = z || 0;
- this._w = ( w !== undefined ) ? w : 1;
-
- }
-
- Object.assign( Quaternion, {
-
- slerp: function ( qa, qb, qm, t ) {
-
- return qm.copy( qa ).slerp( qb, t );
-
- },
-
- slerpFlat: function ( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) {
-
- // fuzz-free, array-based Quaternion SLERP operation
-
- var x0 = src0[ srcOffset0 + 0 ],
- y0 = src0[ srcOffset0 + 1 ],
- z0 = src0[ srcOffset0 + 2 ],
- w0 = src0[ srcOffset0 + 3 ],
-
- x1 = src1[ srcOffset1 + 0 ],
- y1 = src1[ srcOffset1 + 1 ],
- z1 = src1[ srcOffset1 + 2 ],
- w1 = src1[ srcOffset1 + 3 ];
-
- if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) {
-
- var s = 1 - t,
-
- cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,
-
- dir = ( cos >= 0 ? 1 : - 1 ),
- sqrSin = 1 - cos * cos;
-
- // Skip the Slerp for tiny steps to avoid numeric problems:
- if ( sqrSin > Number.EPSILON ) {
-
- var sin = Math.sqrt( sqrSin ),
- len = Math.atan2( sin, cos * dir );
-
- s = Math.sin( s * len ) / sin;
- t = Math.sin( t * len ) / sin;
-
- }
-
- var tDir = t * dir;
-
- x0 = x0 * s + x1 * tDir;
- y0 = y0 * s + y1 * tDir;
- z0 = z0 * s + z1 * tDir;
- w0 = w0 * s + w1 * tDir;
-
- // Normalize in case we just did a lerp:
- if ( s === 1 - t ) {
-
- var f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 );
-
- x0 *= f;
- y0 *= f;
- z0 *= f;
- w0 *= f;
-
- }
-
- }
-
- dst[ dstOffset ] = x0;
- dst[ dstOffset + 1 ] = y0;
- dst[ dstOffset + 2 ] = z0;
- dst[ dstOffset + 3 ] = w0;
-
- },
-
- multiplyQuaternionsFlat: function ( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) {
-
- var x0 = src0[ srcOffset0 ];
- var y0 = src0[ srcOffset0 + 1 ];
- var z0 = src0[ srcOffset0 + 2 ];
- var w0 = src0[ srcOffset0 + 3 ];
-
- var x1 = src1[ srcOffset1 ];
- var y1 = src1[ srcOffset1 + 1 ];
- var z1 = src1[ srcOffset1 + 2 ];
- var w1 = src1[ srcOffset1 + 3 ];
-
- dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1;
- dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1;
- dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1;
- dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
-
- return dst;
-
- }
-
- } );
-
- Object.defineProperties( Quaternion.prototype, {
-
- x: {
-
- get: function () {
-
- return this._x;
-
- },
-
- set: function ( value ) {
-
- this._x = value;
- this._onChangeCallback();
-
- }
-
- },
-
- y: {
-
- get: function () {
-
- return this._y;
-
- },
-
- set: function ( value ) {
-
- this._y = value;
- this._onChangeCallback();
-
- }
-
- },
-
- z: {
-
- get: function () {
-
- return this._z;
-
- },
-
- set: function ( value ) {
-
- this._z = value;
- this._onChangeCallback();
-
- }
-
- },
-
- w: {
-
- get: function () {
-
- return this._w;
-
- },
-
- set: function ( value ) {
-
- this._w = value;
- this._onChangeCallback();
-
- }
-
- }
-
- } );
-
- Object.assign( Quaternion.prototype, {
-
- isQuaternion: true,
-
- set: function ( x, y, z, w ) {
-
- this._x = x;
- this._y = y;
- this._z = z;
- this._w = w;
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- clone: function () {
-
- return new this.constructor( this._x, this._y, this._z, this._w );
-
- },
-
- copy: function ( quaternion ) {
-
- this._x = quaternion.x;
- this._y = quaternion.y;
- this._z = quaternion.z;
- this._w = quaternion.w;
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- setFromEuler: function ( euler, update ) {
-
- if ( ! ( euler && euler.isEuler ) ) {
-
- throw new Error( 'THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.' );
-
- }
-
- var x = euler._x, y = euler._y, z = euler._z, order = euler.order;
-
- // http://www.mathworks.com/matlabcentral/fileexchange/
- // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
- // content/SpinCalc.m
-
- var cos = Math.cos;
- var sin = Math.sin;
-
- var c1 = cos( x / 2 );
- var c2 = cos( y / 2 );
- var c3 = cos( z / 2 );
-
- var s1 = sin( x / 2 );
- var s2 = sin( y / 2 );
- var s3 = sin( z / 2 );
-
- switch ( order ) {
-
- case 'XYZ':
- this._x = s1 * c2 * c3 + c1 * s2 * s3;
- this._y = c1 * s2 * c3 - s1 * c2 * s3;
- this._z = c1 * c2 * s3 + s1 * s2 * c3;
- this._w = c1 * c2 * c3 - s1 * s2 * s3;
- break;
-
- case 'YXZ':
- this._x = s1 * c2 * c3 + c1 * s2 * s3;
- this._y = c1 * s2 * c3 - s1 * c2 * s3;
- this._z = c1 * c2 * s3 - s1 * s2 * c3;
- this._w = c1 * c2 * c3 + s1 * s2 * s3;
- break;
-
- case 'ZXY':
- this._x = s1 * c2 * c3 - c1 * s2 * s3;
- this._y = c1 * s2 * c3 + s1 * c2 * s3;
- this._z = c1 * c2 * s3 + s1 * s2 * c3;
- this._w = c1 * c2 * c3 - s1 * s2 * s3;
- break;
-
- case 'ZYX':
- this._x = s1 * c2 * c3 - c1 * s2 * s3;
- this._y = c1 * s2 * c3 + s1 * c2 * s3;
- this._z = c1 * c2 * s3 - s1 * s2 * c3;
- this._w = c1 * c2 * c3 + s1 * s2 * s3;
- break;
-
- case 'YZX':
- this._x = s1 * c2 * c3 + c1 * s2 * s3;
- this._y = c1 * s2 * c3 + s1 * c2 * s3;
- this._z = c1 * c2 * s3 - s1 * s2 * c3;
- this._w = c1 * c2 * c3 - s1 * s2 * s3;
- break;
-
- case 'XZY':
- this._x = s1 * c2 * c3 - c1 * s2 * s3;
- this._y = c1 * s2 * c3 - s1 * c2 * s3;
- this._z = c1 * c2 * s3 + s1 * s2 * c3;
- this._w = c1 * c2 * c3 + s1 * s2 * s3;
- break;
-
- default:
- console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order );
-
- }
-
- if ( update !== false ) { this._onChangeCallback(); }
-
- return this;
-
- },
-
- setFromAxisAngle: function ( axis, angle ) {
-
- // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
-
- // assumes axis is normalized
-
- var halfAngle = angle / 2, s = Math.sin( halfAngle );
-
- this._x = axis.x * s;
- this._y = axis.y * s;
- this._z = axis.z * s;
- this._w = Math.cos( halfAngle );
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- setFromRotationMatrix: function ( m ) {
-
- // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
-
- // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
-
- var te = m.elements,
-
- m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
- m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
- m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],
-
- trace = m11 + m22 + m33,
- s;
-
- if ( trace > 0 ) {
-
- s = 0.5 / Math.sqrt( trace + 1.0 );
-
- this._w = 0.25 / s;
- this._x = ( m32 - m23 ) * s;
- this._y = ( m13 - m31 ) * s;
- this._z = ( m21 - m12 ) * s;
-
- } else if ( m11 > m22 && m11 > m33 ) {
-
- s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
-
- this._w = ( m32 - m23 ) / s;
- this._x = 0.25 * s;
- this._y = ( m12 + m21 ) / s;
- this._z = ( m13 + m31 ) / s;
-
- } else if ( m22 > m33 ) {
-
- s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
-
- this._w = ( m13 - m31 ) / s;
- this._x = ( m12 + m21 ) / s;
- this._y = 0.25 * s;
- this._z = ( m23 + m32 ) / s;
-
- } else {
-
- s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
-
- this._w = ( m21 - m12 ) / s;
- this._x = ( m13 + m31 ) / s;
- this._y = ( m23 + m32 ) / s;
- this._z = 0.25 * s;
-
- }
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- setFromUnitVectors: function ( vFrom, vTo ) {
-
- // assumes direction vectors vFrom and vTo are normalized
-
- var EPS = 0.000001;
-
- var r = vFrom.dot( vTo ) + 1;
-
- if ( r < EPS ) {
-
- r = 0;
-
- if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {
-
- this._x = - vFrom.y;
- this._y = vFrom.x;
- this._z = 0;
- this._w = r;
-
- } else {
-
- this._x = 0;
- this._y = - vFrom.z;
- this._z = vFrom.y;
- this._w = r;
-
- }
-
- } else {
-
- // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3
-
- this._x = vFrom.y * vTo.z - vFrom.z * vTo.y;
- this._y = vFrom.z * vTo.x - vFrom.x * vTo.z;
- this._z = vFrom.x * vTo.y - vFrom.y * vTo.x;
- this._w = r;
-
- }
-
- return this.normalize();
-
- },
-
- angleTo: function ( q ) {
-
- return 2 * Math.acos( Math.abs( MathUtils.clamp( this.dot( q ), - 1, 1 ) ) );
-
- },
-
- rotateTowards: function ( q, step ) {
-
- var angle = this.angleTo( q );
-
- if ( angle === 0 ) { return this; }
-
- var t = Math.min( 1, step / angle );
-
- this.slerp( q, t );
-
- return this;
-
- },
-
- inverse: function () {
-
- // quaternion is assumed to have unit length
-
- return this.conjugate();
-
- },
-
- conjugate: function () {
-
- this._x *= - 1;
- this._y *= - 1;
- this._z *= - 1;
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- dot: function ( v ) {
-
- return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;
-
- },
-
- lengthSq: function () {
-
- return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
-
- },
-
- length: function () {
-
- return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
-
- },
-
- normalize: function () {
-
- var l = this.length();
-
- if ( l === 0 ) {
-
- this._x = 0;
- this._y = 0;
- this._z = 0;
- this._w = 1;
-
- } else {
-
- l = 1 / l;
-
- this._x = this._x * l;
- this._y = this._y * l;
- this._z = this._z * l;
- this._w = this._w * l;
-
- }
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- multiply: function ( q, p ) {
-
- if ( p !== undefined ) {
-
- console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
- return this.multiplyQuaternions( q, p );
-
- }
-
- return this.multiplyQuaternions( this, q );
-
- },
-
- premultiply: function ( q ) {
-
- return this.multiplyQuaternions( q, this );
-
- },
-
- multiplyQuaternions: function ( a, b ) {
-
- // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
-
- var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
- var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
-
- this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
- this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
- this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
- this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- slerp: function ( qb, t ) {
-
- if ( t === 0 ) { return this; }
- if ( t === 1 ) { return this.copy( qb ); }
-
- var x = this._x, y = this._y, z = this._z, w = this._w;
-
- // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
-
- var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;
-
- if ( cosHalfTheta < 0 ) {
-
- this._w = - qb._w;
- this._x = - qb._x;
- this._y = - qb._y;
- this._z = - qb._z;
-
- cosHalfTheta = - cosHalfTheta;
-
- } else {
-
- this.copy( qb );
-
- }
-
- if ( cosHalfTheta >= 1.0 ) {
-
- this._w = w;
- this._x = x;
- this._y = y;
- this._z = z;
-
- return this;
-
- }
-
- var sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;
-
- if ( sqrSinHalfTheta <= Number.EPSILON ) {
-
- var s = 1 - t;
- this._w = s * w + t * this._w;
- this._x = s * x + t * this._x;
- this._y = s * y + t * this._y;
- this._z = s * z + t * this._z;
-
- this.normalize();
- this._onChangeCallback();
-
- return this;
-
- }
-
- var sinHalfTheta = Math.sqrt( sqrSinHalfTheta );
- var halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );
- var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
- ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
-
- this._w = ( w * ratioA + this._w * ratioB );
- this._x = ( x * ratioA + this._x * ratioB );
- this._y = ( y * ratioA + this._y * ratioB );
- this._z = ( z * ratioA + this._z * ratioB );
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- equals: function ( quaternion ) {
-
- return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
-
- },
-
- fromArray: function ( array, offset ) {
-
- if ( offset === undefined ) { offset = 0; }
-
- this._x = array[ offset ];
- this._y = array[ offset + 1 ];
- this._z = array[ offset + 2 ];
- this._w = array[ offset + 3 ];
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- toArray: function ( array, offset ) {
-
- if ( array === undefined ) { array = []; }
- if ( offset === undefined ) { offset = 0; }
-
- array[ offset ] = this._x;
- array[ offset + 1 ] = this._y;
- array[ offset + 2 ] = this._z;
- array[ offset + 3 ] = this._w;
-
- return array;
-
- },
-
- fromBufferAttribute: function ( attribute, index ) {
-
- this._x = attribute.getX( index );
- this._y = attribute.getY( index );
- this._z = attribute.getZ( index );
- this._w = attribute.getW( index );
-
- return this;
-
- },
-
- _onChange: function ( callback ) {
-
- this._onChangeCallback = callback;
-
- return this;
-
- },
-
- _onChangeCallback: function () {}
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author kile / http://kile.stravaganza.org/
- * @author philogb / http://blog.thejit.org/
- * @author mikael emtinger / http://gomo.se/
- * @author egraether / http://egraether.com/
- * @author WestLangley / http://github.com/WestLangley
- */
-
- var _vector = new Vector3();
- var _quaternion = new Quaternion();
-
- function Vector3( x, y, z ) {
-
- this.x = x || 0;
- this.y = y || 0;
- this.z = z || 0;
-
- }
-
- Object.assign( Vector3.prototype, {
-
- isVector3: true,
-
- set: function ( x, y, z ) {
-
- this.x = x;
- this.y = y;
- this.z = z;
-
- return this;
-
- },
-
- setScalar: function ( scalar ) {
-
- this.x = scalar;
- this.y = scalar;
- this.z = scalar;
-
- return this;
-
- },
-
- setX: function ( x ) {
-
- this.x = x;
-
- return this;
-
- },
-
- setY: function ( y ) {
-
- this.y = y;
-
- return this;
-
- },
-
- setZ: function ( z ) {
-
- this.z = z;
-
- return this;
-
- },
-
- setComponent: function ( index, value ) {
-
- switch ( index ) {
-
- case 0: this.x = value; break;
- case 1: this.y = value; break;
- case 2: this.z = value; break;
- default: throw new Error( 'index is out of range: ' + index );
-
- }
-
- return this;
-
- },
-
- getComponent: function ( index ) {
-
- switch ( index ) {
-
- case 0: return this.x;
- case 1: return this.y;
- case 2: return this.z;
- default: throw new Error( 'index is out of range: ' + index );
-
- }
-
- },
-
- clone: function () {
-
- return new this.constructor( this.x, this.y, this.z );
-
- },
-
- copy: function ( v ) {
-
- this.x = v.x;
- this.y = v.y;
- this.z = v.z;
-
- return this;
-
- },
-
- add: function ( v, w ) {
-
- if ( w !== undefined ) {
-
- console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
- return this.addVectors( v, w );
-
- }
-
- this.x += v.x;
- this.y += v.y;
- this.z += v.z;
-
- return this;
-
- },
-
- addScalar: function ( s ) {
-
- this.x += s;
- this.y += s;
- this.z += s;
-
- return this;
-
- },
-
- addVectors: function ( a, b ) {
-
- this.x = a.x + b.x;
- this.y = a.y + b.y;
- this.z = a.z + b.z;
-
- return this;
-
- },
-
- addScaledVector: function ( v, s ) {
-
- this.x += v.x * s;
- this.y += v.y * s;
- this.z += v.z * s;
-
- return this;
-
- },
-
- sub: function ( v, w ) {
-
- if ( w !== undefined ) {
-
- console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
- return this.subVectors( v, w );
-
- }
-
- this.x -= v.x;
- this.y -= v.y;
- this.z -= v.z;
-
- return this;
-
- },
-
- subScalar: function ( s ) {
-
- this.x -= s;
- this.y -= s;
- this.z -= s;
-
- return this;
-
- },
-
- subVectors: function ( a, b ) {
-
- this.x = a.x - b.x;
- this.y = a.y - b.y;
- this.z = a.z - b.z;
-
- return this;
-
- },
-
- multiply: function ( v, w ) {
-
- if ( w !== undefined ) {
-
- console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' );
- return this.multiplyVectors( v, w );
-
- }
-
- this.x *= v.x;
- this.y *= v.y;
- this.z *= v.z;
-
- return this;
-
- },
-
- multiplyScalar: function ( scalar ) {
-
- this.x *= scalar;
- this.y *= scalar;
- this.z *= scalar;
-
- return this;
-
- },
-
- multiplyVectors: function ( a, b ) {
-
- this.x = a.x * b.x;
- this.y = a.y * b.y;
- this.z = a.z * b.z;
-
- return this;
-
- },
-
- applyEuler: function ( euler ) {
-
- if ( ! ( euler && euler.isEuler ) ) {
-
- console.error( 'THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.' );
-
- }
-
- return this.applyQuaternion( _quaternion.setFromEuler( euler ) );
-
- },
-
- applyAxisAngle: function ( axis, angle ) {
-
- return this.applyQuaternion( _quaternion.setFromAxisAngle( axis, angle ) );
-
- },
-
- applyMatrix3: function ( m ) {
-
- var x = this.x, y = this.y, z = this.z;
- var e = m.elements;
-
- this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z;
- this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z;
- this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z;
-
- return this;
-
- },
-
- applyNormalMatrix: function ( m ) {
-
- return this.applyMatrix3( m ).normalize();
-
- },
-
- applyMatrix4: function ( m ) {
-
- var x = this.x, y = this.y, z = this.z;
- var e = m.elements;
-
- var w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] );
-
- this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w;
- this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w;
- this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w;
-
- return this;
-
- },
-
- applyQuaternion: function ( q ) {
-
- var x = this.x, y = this.y, z = this.z;
- var qx = q.x, qy = q.y, qz = q.z, qw = q.w;
-
- // calculate quat * vector
-
- var ix = qw * x + qy * z - qz * y;
- var iy = qw * y + qz * x - qx * z;
- var iz = qw * z + qx * y - qy * x;
- var iw = - qx * x - qy * y - qz * z;
-
- // calculate result * inverse quat
-
- this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy;
- this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz;
- this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx;
-
- return this;
-
- },
-
- project: function ( camera ) {
-
- return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix );
-
- },
-
- unproject: function ( camera ) {
-
- return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld );
-
- },
-
- transformDirection: function ( m ) {
-
- // input: THREE.Matrix4 affine matrix
- // vector interpreted as a direction
-
- var x = this.x, y = this.y, z = this.z;
- var e = m.elements;
-
- this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z;
- this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z;
- this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;
-
- return this.normalize();
-
- },
-
- divide: function ( v ) {
-
- this.x /= v.x;
- this.y /= v.y;
- this.z /= v.z;
-
- return this;
-
- },
-
- divideScalar: function ( scalar ) {
-
- return this.multiplyScalar( 1 / scalar );
-
- },
-
- min: function ( v ) {
-
- this.x = Math.min( this.x, v.x );
- this.y = Math.min( this.y, v.y );
- this.z = Math.min( this.z, v.z );
-
- return this;
-
- },
-
- max: function ( v ) {
-
- this.x = Math.max( this.x, v.x );
- this.y = Math.max( this.y, v.y );
- this.z = Math.max( this.z, v.z );
-
- return this;
-
- },
-
- clamp: function ( min, max ) {
-
- // assumes min < max, componentwise
-
- this.x = Math.max( min.x, Math.min( max.x, this.x ) );
- this.y = Math.max( min.y, Math.min( max.y, this.y ) );
- this.z = Math.max( min.z, Math.min( max.z, this.z ) );
-
- return this;
-
- },
-
- clampScalar: function ( minVal, maxVal ) {
-
- this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
- this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
- this.z = Math.max( minVal, Math.min( maxVal, this.z ) );
-
- return this;
-
- },
-
- clampLength: function ( min, max ) {
-
- var length = this.length();
-
- return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
-
- },
-
- floor: function () {
-
- this.x = Math.floor( this.x );
- this.y = Math.floor( this.y );
- this.z = Math.floor( this.z );
-
- return this;
-
- },
-
- ceil: function () {
-
- this.x = Math.ceil( this.x );
- this.y = Math.ceil( this.y );
- this.z = Math.ceil( this.z );
-
- return this;
-
- },
-
- round: function () {
-
- this.x = Math.round( this.x );
- this.y = Math.round( this.y );
- this.z = Math.round( this.z );
-
- return this;
-
- },
-
- roundToZero: function () {
-
- this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
- this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
- this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );
-
- return this;
-
- },
-
- negate: function () {
-
- this.x = - this.x;
- this.y = - this.y;
- this.z = - this.z;
-
- return this;
-
- },
-
- dot: function ( v ) {
-
- return this.x * v.x + this.y * v.y + this.z * v.z;
-
- },
-
- // TODO lengthSquared?
-
- lengthSq: function () {
-
- return this.x * this.x + this.y * this.y + this.z * this.z;
-
- },
-
- length: function () {
-
- return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
-
- },
-
- manhattanLength: function () {
-
- return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );
-
- },
-
- normalize: function () {
-
- return this.divideScalar( this.length() || 1 );
-
- },
-
- setLength: function ( length ) {
-
- return this.normalize().multiplyScalar( length );
-
- },
-
- lerp: function ( v, alpha ) {
-
- this.x += ( v.x - this.x ) * alpha;
- this.y += ( v.y - this.y ) * alpha;
- this.z += ( v.z - this.z ) * alpha;
-
- return this;
-
- },
-
- lerpVectors: function ( v1, v2, alpha ) {
-
- this.x = v1.x + ( v2.x - v1.x ) * alpha;
- this.y = v1.y + ( v2.y - v1.y ) * alpha;
- this.z = v1.z + ( v2.z - v1.z ) * alpha;
-
- return this;
-
- },
-
- cross: function ( v, w ) {
-
- if ( w !== undefined ) {
-
- console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );
- return this.crossVectors( v, w );
-
- }
-
- return this.crossVectors( this, v );
-
- },
-
- crossVectors: function ( a, b ) {
-
- var ax = a.x, ay = a.y, az = a.z;
- var bx = b.x, by = b.y, bz = b.z;
-
- this.x = ay * bz - az * by;
- this.y = az * bx - ax * bz;
- this.z = ax * by - ay * bx;
-
- return this;
-
- },
-
- projectOnVector: function ( v ) {
-
- var denominator = v.lengthSq();
-
- if ( denominator === 0 ) { return this.set( 0, 0, 0 ); }
-
- var scalar = v.dot( this ) / denominator;
-
- return this.copy( v ).multiplyScalar( scalar );
-
- },
-
- projectOnPlane: function ( planeNormal ) {
-
- _vector.copy( this ).projectOnVector( planeNormal );
-
- return this.sub( _vector );
-
- },
-
- reflect: function ( normal ) {
-
- // reflect incident vector off plane orthogonal to normal
- // normal is assumed to have unit length
-
- return this.sub( _vector.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) );
-
- },
-
- angleTo: function ( v ) {
-
- var denominator = Math.sqrt( this.lengthSq() * v.lengthSq() );
-
- if ( denominator === 0 ) { return Math.PI / 2; }
-
- var theta = this.dot( v ) / denominator;
-
- // clamp, to handle numerical problems
-
- return Math.acos( MathUtils.clamp( theta, - 1, 1 ) );
-
- },
-
- distanceTo: function ( v ) {
-
- return Math.sqrt( this.distanceToSquared( v ) );
-
- },
-
- distanceToSquared: function ( v ) {
-
- var dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;
-
- return dx * dx + dy * dy + dz * dz;
-
- },
-
- manhattanDistanceTo: function ( v ) {
-
- return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z );
-
- },
-
- setFromSpherical: function ( s ) {
-
- return this.setFromSphericalCoords( s.radius, s.phi, s.theta );
-
- },
-
- setFromSphericalCoords: function ( radius, phi, theta ) {
-
- var sinPhiRadius = Math.sin( phi ) * radius;
-
- this.x = sinPhiRadius * Math.sin( theta );
- this.y = Math.cos( phi ) * radius;
- this.z = sinPhiRadius * Math.cos( theta );
-
- return this;
-
- },
-
- setFromCylindrical: function ( c ) {
-
- return this.setFromCylindricalCoords( c.radius, c.theta, c.y );
-
- },
-
- setFromCylindricalCoords: function ( radius, theta, y ) {
-
- this.x = radius * Math.sin( theta );
- this.y = y;
- this.z = radius * Math.cos( theta );
-
- return this;
-
- },
-
- setFromMatrixPosition: function ( m ) {
-
- var e = m.elements;
-
- this.x = e[ 12 ];
- this.y = e[ 13 ];
- this.z = e[ 14 ];
-
- return this;
-
- },
-
- setFromMatrixScale: function ( m ) {
-
- var sx = this.setFromMatrixColumn( m, 0 ).length();
- var sy = this.setFromMatrixColumn( m, 1 ).length();
- var sz = this.setFromMatrixColumn( m, 2 ).length();
-
- this.x = sx;
- this.y = sy;
- this.z = sz;
-
- return this;
-
- },
-
- setFromMatrixColumn: function ( m, index ) {
-
- return this.fromArray( m.elements, index * 4 );
-
- },
-
- setFromMatrix3Column: function ( m, index ) {
-
- return this.fromArray( m.elements, index * 3 );
-
- },
-
- equals: function ( v ) {
-
- return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );
-
- },
-
- fromArray: function ( array, offset ) {
-
- if ( offset === undefined ) { offset = 0; }
-
- this.x = array[ offset ];
- this.y = array[ offset + 1 ];
- this.z = array[ offset + 2 ];
-
- return this;
-
- },
-
- toArray: function ( array, offset ) {
-
- if ( array === undefined ) { array = []; }
- if ( offset === undefined ) { offset = 0; }
-
- array[ offset ] = this.x;
- array[ offset + 1 ] = this.y;
- array[ offset + 2 ] = this.z;
-
- return array;
-
- },
-
- fromBufferAttribute: function ( attribute, index, offset ) {
-
- if ( offset !== undefined ) {
-
- console.warn( 'THREE.Vector3: offset has been removed from .fromBufferAttribute().' );
-
- }
-
- this.x = attribute.getX( index );
- this.y = attribute.getY( index );
- this.z = attribute.getZ( index );
-
- return this;
-
- },
-
- random: function () {
-
- this.x = Math.random();
- this.y = Math.random();
- this.z = Math.random();
-
- return this;
-
- }
-
- } );
-
- var _v1 = new Vector3();
- var _m1 = new Matrix4();
- var _zero = new Vector3( 0, 0, 0 );
- var _one = new Vector3( 1, 1, 1 );
- var _x = new Vector3();
- var _y = new Vector3();
- var _z = new Vector3();
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author supereggbert / http://www.paulbrunt.co.uk/
- * @author philogb / http://blog.thejit.org/
- * @author jordi_ros / http://plattsoft.com
- * @author D1plo1d / http://github.com/D1plo1d
- * @author alteredq / http://alteredqualia.com/
- * @author mikael emtinger / http://gomo.se/
- * @author timknip / http://www.floorplanner.com/
- * @author bhouston / http://clara.io
- * @author WestLangley / http://github.com/WestLangley
- */
-
- function Matrix4() {
-
- this.elements = [
-
- 1, 0, 0, 0,
- 0, 1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1
-
- ];
-
- if ( arguments.length > 0 ) {
-
- console.error( 'THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.' );
-
- }
-
- }
-
- Object.assign( Matrix4.prototype, {
-
- isMatrix4: true,
-
- set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
-
- var te = this.elements;
-
- te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14;
- te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24;
- te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34;
- te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44;
-
- return this;
-
- },
-
- identity: function () {
-
- this.set(
-
- 1, 0, 0, 0,
- 0, 1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- },
-
- clone: function () {
-
- return new Matrix4().fromArray( this.elements );
-
- },
-
- copy: function ( m ) {
-
- var te = this.elements;
- var me = m.elements;
-
- te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ];
- te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ];
- te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ];
- te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ];
-
- return this;
-
- },
-
- copyPosition: function ( m ) {
-
- var te = this.elements, me = m.elements;
-
- te[ 12 ] = me[ 12 ];
- te[ 13 ] = me[ 13 ];
- te[ 14 ] = me[ 14 ];
-
- return this;
-
- },
-
- extractBasis: function ( xAxis, yAxis, zAxis ) {
-
- xAxis.setFromMatrixColumn( this, 0 );
- yAxis.setFromMatrixColumn( this, 1 );
- zAxis.setFromMatrixColumn( this, 2 );
-
- return this;
-
- },
-
- makeBasis: function ( xAxis, yAxis, zAxis ) {
-
- this.set(
- xAxis.x, yAxis.x, zAxis.x, 0,
- xAxis.y, yAxis.y, zAxis.y, 0,
- xAxis.z, yAxis.z, zAxis.z, 0,
- 0, 0, 0, 1
- );
-
- return this;
-
- },
-
- extractRotation: function ( m ) {
-
- // this method does not support reflection matrices
-
- var te = this.elements;
- var me = m.elements;
-
- var scaleX = 1 / _v1.setFromMatrixColumn( m, 0 ).length();
- var scaleY = 1 / _v1.setFromMatrixColumn( m, 1 ).length();
- var scaleZ = 1 / _v1.setFromMatrixColumn( m, 2 ).length();
-
- te[ 0 ] = me[ 0 ] * scaleX;
- te[ 1 ] = me[ 1 ] * scaleX;
- te[ 2 ] = me[ 2 ] * scaleX;
- te[ 3 ] = 0;
-
- te[ 4 ] = me[ 4 ] * scaleY;
- te[ 5 ] = me[ 5 ] * scaleY;
- te[ 6 ] = me[ 6 ] * scaleY;
- te[ 7 ] = 0;
-
- te[ 8 ] = me[ 8 ] * scaleZ;
- te[ 9 ] = me[ 9 ] * scaleZ;
- te[ 10 ] = me[ 10 ] * scaleZ;
- te[ 11 ] = 0;
-
- te[ 12 ] = 0;
- te[ 13 ] = 0;
- te[ 14 ] = 0;
- te[ 15 ] = 1;
-
- return this;
-
- },
-
- makeRotationFromEuler: function ( euler ) {
-
- if ( ! ( euler && euler.isEuler ) ) {
-
- console.error( 'THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' );
-
- }
-
- var te = this.elements;
-
- var x = euler.x, y = euler.y, z = euler.z;
- var a = Math.cos( x ), b = Math.sin( x );
- var c = Math.cos( y ), d = Math.sin( y );
- var e = Math.cos( z ), f = Math.sin( z );
-
- if ( euler.order === 'XYZ' ) {
-
- var ae = a * e, af = a * f, be = b * e, bf = b * f;
-
- te[ 0 ] = c * e;
- te[ 4 ] = - c * f;
- te[ 8 ] = d;
-
- te[ 1 ] = af + be * d;
- te[ 5 ] = ae - bf * d;
- te[ 9 ] = - b * c;
-
- te[ 2 ] = bf - ae * d;
- te[ 6 ] = be + af * d;
- te[ 10 ] = a * c;
-
- } else if ( euler.order === 'YXZ' ) {
-
- var ce = c * e, cf = c * f, de = d * e, df = d * f;
-
- te[ 0 ] = ce + df * b;
- te[ 4 ] = de * b - cf;
- te[ 8 ] = a * d;
-
- te[ 1 ] = a * f;
- te[ 5 ] = a * e;
- te[ 9 ] = - b;
-
- te[ 2 ] = cf * b - de;
- te[ 6 ] = df + ce * b;
- te[ 10 ] = a * c;
-
- } else if ( euler.order === 'ZXY' ) {
-
- var ce = c * e, cf = c * f, de = d * e, df = d * f;
-
- te[ 0 ] = ce - df * b;
- te[ 4 ] = - a * f;
- te[ 8 ] = de + cf * b;
-
- te[ 1 ] = cf + de * b;
- te[ 5 ] = a * e;
- te[ 9 ] = df - ce * b;
-
- te[ 2 ] = - a * d;
- te[ 6 ] = b;
- te[ 10 ] = a * c;
-
- } else if ( euler.order === 'ZYX' ) {
-
- var ae = a * e, af = a * f, be = b * e, bf = b * f;
-
- te[ 0 ] = c * e;
- te[ 4 ] = be * d - af;
- te[ 8 ] = ae * d + bf;
-
- te[ 1 ] = c * f;
- te[ 5 ] = bf * d + ae;
- te[ 9 ] = af * d - be;
-
- te[ 2 ] = - d;
- te[ 6 ] = b * c;
- te[ 10 ] = a * c;
-
- } else if ( euler.order === 'YZX' ) {
-
- var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
-
- te[ 0 ] = c * e;
- te[ 4 ] = bd - ac * f;
- te[ 8 ] = bc * f + ad;
-
- te[ 1 ] = f;
- te[ 5 ] = a * e;
- te[ 9 ] = - b * e;
-
- te[ 2 ] = - d * e;
- te[ 6 ] = ad * f + bc;
- te[ 10 ] = ac - bd * f;
-
- } else if ( euler.order === 'XZY' ) {
-
- var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
-
- te[ 0 ] = c * e;
- te[ 4 ] = - f;
- te[ 8 ] = d * e;
-
- te[ 1 ] = ac * f + bd;
- te[ 5 ] = a * e;
- te[ 9 ] = ad * f - bc;
-
- te[ 2 ] = bc * f - ad;
- te[ 6 ] = b * e;
- te[ 10 ] = bd * f + ac;
-
- }
-
- // bottom row
- te[ 3 ] = 0;
- te[ 7 ] = 0;
- te[ 11 ] = 0;
-
- // last column
- te[ 12 ] = 0;
- te[ 13 ] = 0;
- te[ 14 ] = 0;
- te[ 15 ] = 1;
-
- return this;
-
- },
-
- makeRotationFromQuaternion: function ( q ) {
-
- return this.compose( _zero, q, _one );
-
- },
-
- lookAt: function ( eye, target, up ) {
-
- var te = this.elements;
-
- _z.subVectors( eye, target );
-
- if ( _z.lengthSq() === 0 ) {
-
- // eye and target are in the same position
-
- _z.z = 1;
-
- }
-
- _z.normalize();
- _x.crossVectors( up, _z );
-
- if ( _x.lengthSq() === 0 ) {
-
- // up and z are parallel
-
- if ( Math.abs( up.z ) === 1 ) {
-
- _z.x += 0.0001;
-
- } else {
-
- _z.z += 0.0001;
-
- }
-
- _z.normalize();
- _x.crossVectors( up, _z );
-
- }
-
- _x.normalize();
- _y.crossVectors( _z, _x );
-
- te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x;
- te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y;
- te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z;
-
- return this;
-
- },
-
- multiply: function ( m, n ) {
-
- if ( n !== undefined ) {
-
- console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );
- return this.multiplyMatrices( m, n );
-
- }
-
- return this.multiplyMatrices( this, m );
-
- },
-
- premultiply: function ( m ) {
-
- return this.multiplyMatrices( m, this );
-
- },
-
- multiplyMatrices: function ( a, b ) {
-
- var ae = a.elements;
- var be = b.elements;
- var te = this.elements;
-
- var a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ];
- var a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ];
- var a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ];
- var a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ];
-
- var b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ];
- var b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ];
- var b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ];
- var b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ];
-
- te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
- te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
- te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
- te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
-
- te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
- te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
- te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
- te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
-
- te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
- te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
- te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
- te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
-
- te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
- te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
- te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
- te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
-
- return this;
-
- },
-
- multiplyScalar: function ( s ) {
-
- var te = this.elements;
-
- te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s;
- te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s;
- te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s;
- te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s;
-
- return this;
-
- },
-
- determinant: function () {
-
- var te = this.elements;
-
- var n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ];
- var n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ];
- var n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ];
- var n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ];
-
- //TODO: make this more efficient
- //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
-
- return (
- n41 * (
- + n14 * n23 * n32
- - n13 * n24 * n32
- - n14 * n22 * n33
- + n12 * n24 * n33
- + n13 * n22 * n34
- - n12 * n23 * n34
- ) +
- n42 * (
- + n11 * n23 * n34
- - n11 * n24 * n33
- + n14 * n21 * n33
- - n13 * n21 * n34
- + n13 * n24 * n31
- - n14 * n23 * n31
- ) +
- n43 * (
- + n11 * n24 * n32
- - n11 * n22 * n34
- - n14 * n21 * n32
- + n12 * n21 * n34
- + n14 * n22 * n31
- - n12 * n24 * n31
- ) +
- n44 * (
- - n13 * n22 * n31
- - n11 * n23 * n32
- + n11 * n22 * n33
- + n13 * n21 * n32
- - n12 * n21 * n33
- + n12 * n23 * n31
- )
-
- );
-
- },
-
- transpose: function () {
-
- var te = this.elements;
- var tmp;
-
- tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp;
- tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp;
- tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp;
-
- tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp;
- tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp;
- tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp;
-
- return this;
-
- },
-
- setPosition: function ( x, y, z ) {
-
- var te = this.elements;
-
- if ( x.isVector3 ) {
-
- te[ 12 ] = x.x;
- te[ 13 ] = x.y;
- te[ 14 ] = x.z;
-
- } else {
-
- te[ 12 ] = x;
- te[ 13 ] = y;
- te[ 14 ] = z;
-
- }
-
- return this;
-
- },
-
- getInverse: function ( m, throwOnDegenerate ) {
-
- if ( throwOnDegenerate !== undefined ) {
-
- console.warn( "THREE.Matrix4: .getInverse() can no longer be configured to throw on degenerate." );
-
- }
-
- // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
- var te = this.elements,
- me = m.elements,
-
- n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ], n41 = me[ 3 ],
- n12 = me[ 4 ], n22 = me[ 5 ], n32 = me[ 6 ], n42 = me[ 7 ],
- n13 = me[ 8 ], n23 = me[ 9 ], n33 = me[ 10 ], n43 = me[ 11 ],
- n14 = me[ 12 ], n24 = me[ 13 ], n34 = me[ 14 ], n44 = me[ 15 ],
-
- t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,
- t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,
- t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,
- t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
-
- var det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
-
- if ( det === 0 ) { return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); }
-
- var detInv = 1 / det;
-
- te[ 0 ] = t11 * detInv;
- te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv;
- te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv;
- te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv;
-
- te[ 4 ] = t12 * detInv;
- te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv;
- te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv;
- te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv;
-
- te[ 8 ] = t13 * detInv;
- te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv;
- te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv;
- te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv;
-
- te[ 12 ] = t14 * detInv;
- te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv;
- te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv;
- te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv;
-
- return this;
-
- },
-
- scale: function ( v ) {
-
- var te = this.elements;
- var x = v.x, y = v.y, z = v.z;
-
- te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z;
- te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z;
- te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z;
- te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z;
-
- return this;
-
- },
-
- getMaxScaleOnAxis: function () {
-
- var te = this.elements;
-
- var scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ];
- var scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ];
- var scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ];
-
- return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) );
-
- },
-
- makeTranslation: function ( x, y, z ) {
-
- this.set(
-
- 1, 0, 0, x,
- 0, 1, 0, y,
- 0, 0, 1, z,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- },
-
- makeRotationX: function ( theta ) {
-
- var c = Math.cos( theta ), s = Math.sin( theta );
-
- this.set(
-
- 1, 0, 0, 0,
- 0, c, - s, 0,
- 0, s, c, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- },
-
- makeRotationY: function ( theta ) {
-
- var c = Math.cos( theta ), s = Math.sin( theta );
-
- this.set(
-
- c, 0, s, 0,
- 0, 1, 0, 0,
- - s, 0, c, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- },
-
- makeRotationZ: function ( theta ) {
-
- var c = Math.cos( theta ), s = Math.sin( theta );
-
- this.set(
-
- c, - s, 0, 0,
- s, c, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- },
-
- makeRotationAxis: function ( axis, angle ) {
-
- // Based on http://www.gamedev.net/reference/articles/article1199.asp
-
- var c = Math.cos( angle );
- var s = Math.sin( angle );
- var t = 1 - c;
- var x = axis.x, y = axis.y, z = axis.z;
- var tx = t * x, ty = t * y;
-
- this.set(
-
- tx * x + c, tx * y - s * z, tx * z + s * y, 0,
- tx * y + s * z, ty * y + c, ty * z - s * x, 0,
- tx * z - s * y, ty * z + s * x, t * z * z + c, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- },
-
- makeScale: function ( x, y, z ) {
-
- this.set(
-
- x, 0, 0, 0,
- 0, y, 0, 0,
- 0, 0, z, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- },
-
- makeShear: function ( x, y, z ) {
-
- this.set(
-
- 1, y, z, 0,
- x, 1, z, 0,
- x, y, 1, 0,
- 0, 0, 0, 1
-
- );
-
- return this;
-
- },
-
- compose: function ( position, quaternion, scale ) {
-
- var te = this.elements;
-
- var x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w;
- var x2 = x + x, y2 = y + y, z2 = z + z;
- var xx = x * x2, xy = x * y2, xz = x * z2;
- var yy = y * y2, yz = y * z2, zz = z * z2;
- var wx = w * x2, wy = w * y2, wz = w * z2;
-
- var sx = scale.x, sy = scale.y, sz = scale.z;
-
- te[ 0 ] = ( 1 - ( yy + zz ) ) * sx;
- te[ 1 ] = ( xy + wz ) * sx;
- te[ 2 ] = ( xz - wy ) * sx;
- te[ 3 ] = 0;
-
- te[ 4 ] = ( xy - wz ) * sy;
- te[ 5 ] = ( 1 - ( xx + zz ) ) * sy;
- te[ 6 ] = ( yz + wx ) * sy;
- te[ 7 ] = 0;
-
- te[ 8 ] = ( xz + wy ) * sz;
- te[ 9 ] = ( yz - wx ) * sz;
- te[ 10 ] = ( 1 - ( xx + yy ) ) * sz;
- te[ 11 ] = 0;
-
- te[ 12 ] = position.x;
- te[ 13 ] = position.y;
- te[ 14 ] = position.z;
- te[ 15 ] = 1;
-
- return this;
-
- },
-
- decompose: function ( position, quaternion, scale ) {
-
- var te = this.elements;
-
- var sx = _v1.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();
- var sy = _v1.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();
- var sz = _v1.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();
-
- // if determine is negative, we need to invert one scale
- var det = this.determinant();
- if ( det < 0 ) { sx = - sx; }
-
- position.x = te[ 12 ];
- position.y = te[ 13 ];
- position.z = te[ 14 ];
-
- // scale the rotation part
- _m1.copy( this );
-
- var invSX = 1 / sx;
- var invSY = 1 / sy;
- var invSZ = 1 / sz;
-
- _m1.elements[ 0 ] *= invSX;
- _m1.elements[ 1 ] *= invSX;
- _m1.elements[ 2 ] *= invSX;
-
- _m1.elements[ 4 ] *= invSY;
- _m1.elements[ 5 ] *= invSY;
- _m1.elements[ 6 ] *= invSY;
-
- _m1.elements[ 8 ] *= invSZ;
- _m1.elements[ 9 ] *= invSZ;
- _m1.elements[ 10 ] *= invSZ;
-
- quaternion.setFromRotationMatrix( _m1 );
-
- scale.x = sx;
- scale.y = sy;
- scale.z = sz;
-
- return this;
-
- },
-
- makePerspective: function ( left, right, top, bottom, near, far ) {
-
- if ( far === undefined ) {
-
- console.warn( 'THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.' );
-
- }
-
- var te = this.elements;
- var x = 2 * near / ( right - left );
- var y = 2 * near / ( top - bottom );
-
- var a = ( right + left ) / ( right - left );
- var b = ( top + bottom ) / ( top - bottom );
- var c = - ( far + near ) / ( far - near );
- var d = - 2 * far * near / ( far - near );
-
- te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0;
- te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0;
- te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d;
- te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0;
-
- return this;
-
- },
-
- makeOrthographic: function ( left, right, top, bottom, near, far ) {
-
- var te = this.elements;
- var w = 1.0 / ( right - left );
- var h = 1.0 / ( top - bottom );
- var p = 1.0 / ( far - near );
-
- var x = ( right + left ) * w;
- var y = ( top + bottom ) * h;
- var z = ( far + near ) * p;
-
- te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x;
- te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y;
- te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 2 * p; te[ 14 ] = - z;
- te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1;
-
- return this;
-
- },
-
- equals: function ( matrix ) {
-
- var te = this.elements;
- var me = matrix.elements;
-
- for ( var i = 0; i < 16; i ++ ) {
-
- if ( te[ i ] !== me[ i ] ) { return false; }
-
- }
-
- return true;
-
- },
-
- fromArray: function ( array, offset ) {
-
- if ( offset === undefined ) { offset = 0; }
-
- for ( var i = 0; i < 16; i ++ ) {
-
- this.elements[ i ] = array[ i + offset ];
-
- }
-
- return this;
-
- },
-
- toArray: function ( array, offset ) {
-
- if ( array === undefined ) { array = []; }
- if ( offset === undefined ) { offset = 0; }
-
- var te = this.elements;
-
- array[ offset ] = te[ 0 ];
- array[ offset + 1 ] = te[ 1 ];
- array[ offset + 2 ] = te[ 2 ];
- array[ offset + 3 ] = te[ 3 ];
-
- array[ offset + 4 ] = te[ 4 ];
- array[ offset + 5 ] = te[ 5 ];
- array[ offset + 6 ] = te[ 6 ];
- array[ offset + 7 ] = te[ 7 ];
-
- array[ offset + 8 ] = te[ 8 ];
- array[ offset + 9 ] = te[ 9 ];
- array[ offset + 10 ] = te[ 10 ];
- array[ offset + 11 ] = te[ 11 ];
-
- array[ offset + 12 ] = te[ 12 ];
- array[ offset + 13 ] = te[ 13 ];
- array[ offset + 14 ] = te[ 14 ];
- array[ offset + 15 ] = te[ 15 ];
-
- return array;
-
- }
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author WestLangley / http://github.com/WestLangley
- * @author bhouston / http://clara.io
- */
-
- var _matrix = new Matrix4();
- var _quaternion$1 = new Quaternion();
-
- function Euler( x, y, z, order ) {
-
- this._x = x || 0;
- this._y = y || 0;
- this._z = z || 0;
- this._order = order || Euler.DefaultOrder;
-
- }
-
- Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ];
-
- Euler.DefaultOrder = 'XYZ';
-
- Object.defineProperties( Euler.prototype, {
-
- x: {
-
- get: function () {
-
- return this._x;
-
- },
-
- set: function ( value ) {
-
- this._x = value;
- this._onChangeCallback();
-
- }
-
- },
-
- y: {
-
- get: function () {
-
- return this._y;
-
- },
-
- set: function ( value ) {
-
- this._y = value;
- this._onChangeCallback();
-
- }
-
- },
-
- z: {
-
- get: function () {
-
- return this._z;
-
- },
-
- set: function ( value ) {
-
- this._z = value;
- this._onChangeCallback();
-
- }
-
- },
-
- order: {
-
- get: function () {
-
- return this._order;
-
- },
-
- set: function ( value ) {
-
- this._order = value;
- this._onChangeCallback();
-
- }
-
- }
-
- } );
-
- Object.assign( Euler.prototype, {
-
- isEuler: true,
-
- set: function ( x, y, z, order ) {
-
- this._x = x;
- this._y = y;
- this._z = z;
- this._order = order || this._order;
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- clone: function () {
-
- return new this.constructor( this._x, this._y, this._z, this._order );
-
- },
-
- copy: function ( euler ) {
-
- this._x = euler._x;
- this._y = euler._y;
- this._z = euler._z;
- this._order = euler._order;
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- setFromRotationMatrix: function ( m, order, update ) {
-
- var clamp = MathUtils.clamp;
-
- // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
-
- var te = m.elements;
- var m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ];
- var m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ];
- var m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];
-
- order = order || this._order;
-
- switch ( order ) {
-
- case 'XYZ':
-
- this._y = Math.asin( clamp( m13, - 1, 1 ) );
-
- if ( Math.abs( m13 ) < 0.9999999 ) {
-
- this._x = Math.atan2( - m23, m33 );
- this._z = Math.atan2( - m12, m11 );
-
- } else {
-
- this._x = Math.atan2( m32, m22 );
- this._z = 0;
-
- }
-
- break;
-
- case 'YXZ':
-
- this._x = Math.asin( - clamp( m23, - 1, 1 ) );
-
- if ( Math.abs( m23 ) < 0.9999999 ) {
-
- this._y = Math.atan2( m13, m33 );
- this._z = Math.atan2( m21, m22 );
-
- } else {
-
- this._y = Math.atan2( - m31, m11 );
- this._z = 0;
-
- }
-
- break;
-
- case 'ZXY':
-
- this._x = Math.asin( clamp( m32, - 1, 1 ) );
-
- if ( Math.abs( m32 ) < 0.9999999 ) {
-
- this._y = Math.atan2( - m31, m33 );
- this._z = Math.atan2( - m12, m22 );
-
- } else {
-
- this._y = 0;
- this._z = Math.atan2( m21, m11 );
-
- }
-
- break;
-
- case 'ZYX':
-
- this._y = Math.asin( - clamp( m31, - 1, 1 ) );
-
- if ( Math.abs( m31 ) < 0.9999999 ) {
-
- this._x = Math.atan2( m32, m33 );
- this._z = Math.atan2( m21, m11 );
-
- } else {
-
- this._x = 0;
- this._z = Math.atan2( - m12, m22 );
-
- }
-
- break;
-
- case 'YZX':
-
- this._z = Math.asin( clamp( m21, - 1, 1 ) );
-
- if ( Math.abs( m21 ) < 0.9999999 ) {
-
- this._x = Math.atan2( - m23, m22 );
- this._y = Math.atan2( - m31, m11 );
-
- } else {
-
- this._x = 0;
- this._y = Math.atan2( m13, m33 );
-
- }
-
- break;
-
- case 'XZY':
-
- this._z = Math.asin( - clamp( m12, - 1, 1 ) );
-
- if ( Math.abs( m12 ) < 0.9999999 ) {
-
- this._x = Math.atan2( m32, m22 );
- this._y = Math.atan2( m13, m11 );
-
- } else {
-
- this._x = Math.atan2( - m23, m33 );
- this._y = 0;
-
- }
-
- break;
-
- default:
-
- console.warn( 'THREE.Euler: .setFromRotationMatrix() encountered an unknown order: ' + order );
-
- }
-
- this._order = order;
-
- if ( update !== false ) { this._onChangeCallback(); }
-
- return this;
-
- },
-
- setFromQuaternion: function ( q, order, update ) {
-
- _matrix.makeRotationFromQuaternion( q );
-
- return this.setFromRotationMatrix( _matrix, order, update );
-
- },
-
- setFromVector3: function ( v, order ) {
-
- return this.set( v.x, v.y, v.z, order || this._order );
-
- },
-
- reorder: function ( newOrder ) {
-
- // WARNING: this discards revolution information -bhouston
-
- _quaternion$1.setFromEuler( this );
-
- return this.setFromQuaternion( _quaternion$1, newOrder );
-
- },
-
- equals: function ( euler ) {
-
- return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order );
-
- },
-
- fromArray: function ( array ) {
-
- this._x = array[ 0 ];
- this._y = array[ 1 ];
- this._z = array[ 2 ];
- if ( array[ 3 ] !== undefined ) { this._order = array[ 3 ]; }
-
- this._onChangeCallback();
-
- return this;
-
- },
-
- toArray: function ( array, offset ) {
-
- if ( array === undefined ) { array = []; }
- if ( offset === undefined ) { offset = 0; }
-
- array[ offset ] = this._x;
- array[ offset + 1 ] = this._y;
- array[ offset + 2 ] = this._z;
- array[ offset + 3 ] = this._order;
-
- return array;
-
- },
-
- toVector3: function ( optionalResult ) {
-
- if ( optionalResult ) {
-
- return optionalResult.set( this._x, this._y, this._z );
-
- } else {
-
- return new Vector3( this._x, this._y, this._z );
-
- }
-
- },
-
- _onChange: function ( callback ) {
-
- this._onChangeCallback = callback;
-
- return this;
-
- },
-
- _onChangeCallback: function () {}
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- */
-
- function Layers() {
-
- this.mask = 1 | 0;
-
- }
-
- Object.assign( Layers.prototype, {
-
- set: function ( channel ) {
-
- this.mask = 1 << channel | 0;
-
- },
-
- enable: function ( channel ) {
-
- this.mask |= 1 << channel | 0;
-
- },
-
- enableAll: function () {
-
- this.mask = 0xffffffff | 0;
-
- },
-
- toggle: function ( channel ) {
-
- this.mask ^= 1 << channel | 0;
-
- },
-
- disable: function ( channel ) {
-
- this.mask &= ~ ( 1 << channel | 0 );
-
- },
-
- disableAll: function () {
-
- this.mask = 0;
-
- },
-
- test: function ( layers ) {
-
- return ( this.mask & layers.mask ) !== 0;
-
- }
-
- } );
-
- var _object3DId = 0;
-
- var _v1$1 = new Vector3();
- var _q1 = new Quaternion();
- var _m1$1 = new Matrix4();
- var _target = new Vector3();
-
- var _position = new Vector3();
- var _scale = new Vector3();
- var _quaternion$2 = new Quaternion();
-
- var _xAxis = new Vector3( 1, 0, 0 );
- var _yAxis = new Vector3( 0, 1, 0 );
- var _zAxis = new Vector3( 0, 0, 1 );
-
- var _addedEvent = { type: 'added' };
- var _removedEvent = { type: 'removed' };
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author mikael emtinger / http://gomo.se/
- * @author alteredq / http://alteredqualia.com/
- * @author WestLangley / http://github.com/WestLangley
- * @author elephantatwork / www.elephantatwork.ch
- */
-
- function Object3D() {
-
- Object.defineProperty( this, 'id', { value: _object3DId ++ } );
-
- this.uuid = MathUtils.generateUUID();
-
- this.name = '';
- this.type = 'Object3D';
-
- this.parent = null;
- this.children = [];
-
- this.up = Object3D.DefaultUp.clone();
-
- var position = new Vector3();
- var rotation = new Euler();
- var quaternion = new Quaternion();
- var scale = new Vector3( 1, 1, 1 );
-
- function onRotationChange() {
-
- quaternion.setFromEuler( rotation, false );
-
- }
-
- function onQuaternionChange() {
-
- rotation.setFromQuaternion( quaternion, undefined, false );
-
- }
-
- rotation._onChange( onRotationChange );
- quaternion._onChange( onQuaternionChange );
-
- Object.defineProperties( this, {
- position: {
- configurable: true,
- enumerable: true,
- value: position
- },
- rotation: {
- configurable: true,
- enumerable: true,
- value: rotation
- },
- quaternion: {
- configurable: true,
- enumerable: true,
- value: quaternion
- },
- scale: {
- configurable: true,
- enumerable: true,
- value: scale
- },
- modelViewMatrix: {
- value: new Matrix4()
- },
- normalMatrix: {
- value: new Matrix3()
- }
- } );
-
- this.matrix = new Matrix4();
- this.matrixWorld = new Matrix4();
-
- this.matrixAutoUpdate = Object3D.DefaultMatrixAutoUpdate;
- this.matrixWorldNeedsUpdate = false;
-
- this.layers = new Layers();
- this.visible = true;
-
- this.castShadow = false;
- this.receiveShadow = false;
-
- this.frustumCulled = true;
- this.renderOrder = 0;
-
- this.userData = {};
-
- }
-
- Object3D.DefaultUp = new Vector3( 0, 1, 0 );
- Object3D.DefaultMatrixAutoUpdate = true;
-
- Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
-
- constructor: Object3D,
-
- isObject3D: true,
-
- onBeforeRender: function () {},
- onAfterRender: function () {},
-
- applyMatrix4: function ( matrix ) {
-
- if ( this.matrixAutoUpdate ) { this.updateMatrix(); }
-
- this.matrix.premultiply( matrix );
-
- this.matrix.decompose( this.position, this.quaternion, this.scale );
-
- },
-
- applyQuaternion: function ( q ) {
-
- this.quaternion.premultiply( q );
-
- return this;
-
- },
-
- setRotationFromAxisAngle: function ( axis, angle ) {
-
- // assumes axis is normalized
-
- this.quaternion.setFromAxisAngle( axis, angle );
-
- },
-
- setRotationFromEuler: function ( euler ) {
-
- this.quaternion.setFromEuler( euler, true );
-
- },
-
- setRotationFromMatrix: function ( m ) {
-
- // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
-
- this.quaternion.setFromRotationMatrix( m );
-
- },
-
- setRotationFromQuaternion: function ( q ) {
-
- // assumes q is normalized
-
- this.quaternion.copy( q );
-
- },
-
- rotateOnAxis: function ( axis, angle ) {
-
- // rotate object on axis in object space
- // axis is assumed to be normalized
-
- _q1.setFromAxisAngle( axis, angle );
-
- this.quaternion.multiply( _q1 );
-
- return this;
-
- },
-
- rotateOnWorldAxis: function ( axis, angle ) {
-
- // rotate object on axis in world space
- // axis is assumed to be normalized
- // method assumes no rotated parent
-
- _q1.setFromAxisAngle( axis, angle );
-
- this.quaternion.premultiply( _q1 );
-
- return this;
-
- },
-
- rotateX: function ( angle ) {
-
- return this.rotateOnAxis( _xAxis, angle );
-
- },
-
- rotateY: function ( angle ) {
-
- return this.rotateOnAxis( _yAxis, angle );
-
- },
-
- rotateZ: function ( angle ) {
-
- return this.rotateOnAxis( _zAxis, angle );
-
- },
-
- translateOnAxis: function ( axis, distance ) {
-
- // translate object by distance along axis in object space
- // axis is assumed to be normalized
-
- _v1$1.copy( axis ).applyQuaternion( this.quaternion );
-
- this.position.add( _v1$1.multiplyScalar( distance ) );
-
- return this;
-
- },
-
- translateX: function ( distance ) {
-
- return this.translateOnAxis( _xAxis, distance );
-
- },
-
- translateY: function ( distance ) {
-
- return this.translateOnAxis( _yAxis, distance );
-
- },
-
- translateZ: function ( distance ) {
-
- return this.translateOnAxis( _zAxis, distance );
-
- },
-
- localToWorld: function ( vector ) {
-
- return vector.applyMatrix4( this.matrixWorld );
-
- },
-
- worldToLocal: function ( vector ) {
-
- return vector.applyMatrix4( _m1$1.getInverse( this.matrixWorld ) );
-
- },
-
- lookAt: function ( x, y, z ) {
-
- // This method does not support objects having non-uniformly-scaled parent(s)
-
- if ( x.isVector3 ) {
-
- _target.copy( x );
-
- } else {
-
- _target.set( x, y, z );
-
- }
-
- var parent = this.parent;
-
- this.updateWorldMatrix( true, false );
-
- _position.setFromMatrixPosition( this.matrixWorld );
-
- if ( this.isCamera || this.isLight ) {
-
- _m1$1.lookAt( _position, _target, this.up );
-
- } else {
-
- _m1$1.lookAt( _target, _position, this.up );
-
- }
-
- this.quaternion.setFromRotationMatrix( _m1$1 );
-
- if ( parent ) {
-
- _m1$1.extractRotation( parent.matrixWorld );
- _q1.setFromRotationMatrix( _m1$1 );
- this.quaternion.premultiply( _q1.inverse() );
-
- }
-
- },
-
- add: function ( object ) {
-
- if ( arguments.length > 1 ) {
-
- for ( var i = 0; i < arguments.length; i ++ ) {
-
- this.add( arguments[ i ] );
-
- }
-
- return this;
-
- }
-
- if ( object === this ) {
-
- console.error( "THREE.Object3D.add: object can't be added as a child of itself.", object );
- return this;
-
- }
-
- if ( ( object && object.isObject3D ) ) {
-
- if ( object.parent !== null ) {
-
- object.parent.remove( object );
-
- }
-
- object.parent = this;
- this.children.push( object );
-
- object.dispatchEvent( _addedEvent );
-
- } else {
-
- console.error( "THREE.Object3D.add: object not an instance of THREE.Object3D.", object );
-
- }
-
- return this;
-
- },
-
- remove: function ( object ) {
-
- if ( arguments.length > 1 ) {
-
- for ( var i = 0; i < arguments.length; i ++ ) {
-
- this.remove( arguments[ i ] );
-
- }
-
- return this;
-
- }
-
- var index = this.children.indexOf( object );
-
- if ( index !== - 1 ) {
-
- object.parent = null;
- this.children.splice( index, 1 );
-
- object.dispatchEvent( _removedEvent );
-
- }
-
- return this;
-
- },
-
- attach: function ( object ) {
-
- // adds object as a child of this, while maintaining the object's world transform
-
- this.updateWorldMatrix( true, false );
-
- _m1$1.getInverse( this.matrixWorld );
-
- if ( object.parent !== null ) {
-
- object.parent.updateWorldMatrix( true, false );
-
- _m1$1.multiply( object.parent.matrixWorld );
-
- }
-
- object.applyMatrix4( _m1$1 );
-
- object.updateWorldMatrix( false, false );
-
- this.add( object );
-
- return this;
-
- },
-
- getObjectById: function ( id ) {
-
- return this.getObjectByProperty( 'id', id );
-
- },
-
- getObjectByName: function ( name ) {
-
- return this.getObjectByProperty( 'name', name );
-
- },
-
- getObjectByProperty: function ( name, value ) {
-
- if ( this[ name ] === value ) { return this; }
-
- for ( var i = 0, l = this.children.length; i < l; i ++ ) {
-
- var child = this.children[ i ];
- var object = child.getObjectByProperty( name, value );
-
- if ( object !== undefined ) {
-
- return object;
-
- }
-
- }
-
- return undefined;
-
- },
-
- getWorldPosition: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Object3D: .getWorldPosition() target is now required' );
- target = new Vector3();
-
- }
-
- this.updateMatrixWorld( true );
-
- return target.setFromMatrixPosition( this.matrixWorld );
-
- },
-
- getWorldQuaternion: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Object3D: .getWorldQuaternion() target is now required' );
- target = new Quaternion();
-
- }
-
- this.updateMatrixWorld( true );
-
- this.matrixWorld.decompose( _position, target, _scale );
-
- return target;
-
- },
-
- getWorldScale: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Object3D: .getWorldScale() target is now required' );
- target = new Vector3();
-
- }
-
- this.updateMatrixWorld( true );
-
- this.matrixWorld.decompose( _position, _quaternion$2, target );
-
- return target;
-
- },
-
- getWorldDirection: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Object3D: .getWorldDirection() target is now required' );
- target = new Vector3();
-
- }
-
- this.updateMatrixWorld( true );
-
- var e = this.matrixWorld.elements;
-
- return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize();
-
- },
-
- raycast: function () {},
-
- traverse: function ( callback ) {
-
- callback( this );
-
- var children = this.children;
-
- for ( var i = 0, l = children.length; i < l; i ++ ) {
-
- children[ i ].traverse( callback );
-
- }
-
- },
-
- traverseVisible: function ( callback ) {
-
- if ( this.visible === false ) { return; }
-
- callback( this );
-
- var children = this.children;
-
- for ( var i = 0, l = children.length; i < l; i ++ ) {
-
- children[ i ].traverseVisible( callback );
-
- }
-
- },
-
- traverseAncestors: function ( callback ) {
-
- var parent = this.parent;
-
- if ( parent !== null ) {
-
- callback( parent );
-
- parent.traverseAncestors( callback );
-
- }
-
- },
-
- updateMatrix: function () {
-
- this.matrix.compose( this.position, this.quaternion, this.scale );
-
- this.matrixWorldNeedsUpdate = true;
-
- },
-
- updateMatrixWorld: function ( force ) {
-
- if ( this.matrixAutoUpdate ) { this.updateMatrix(); }
-
- if ( this.matrixWorldNeedsUpdate || force ) {
-
- if ( this.parent === null ) {
-
- this.matrixWorld.copy( this.matrix );
-
- } else {
-
- this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
-
- }
-
- this.matrixWorldNeedsUpdate = false;
-
- force = true;
-
- }
-
- // update children
-
- var children = this.children;
-
- for ( var i = 0, l = children.length; i < l; i ++ ) {
-
- children[ i ].updateMatrixWorld( force );
-
- }
-
- },
-
- updateWorldMatrix: function ( updateParents, updateChildren ) {
-
- var parent = this.parent;
-
- if ( updateParents === true && parent !== null ) {
-
- parent.updateWorldMatrix( true, false );
-
- }
-
- if ( this.matrixAutoUpdate ) { this.updateMatrix(); }
-
- if ( this.parent === null ) {
-
- this.matrixWorld.copy( this.matrix );
-
- } else {
-
- this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
-
- }
-
- // update children
-
- if ( updateChildren === true ) {
-
- var children = this.children;
-
- for ( var i = 0, l = children.length; i < l; i ++ ) {
-
- children[ i ].updateWorldMatrix( false, true );
-
- }
-
- }
-
- },
-
- toJSON: function ( meta ) {
-
- // meta is a string when called from JSON.stringify
- var isRootObject = ( meta === undefined || typeof meta === 'string' );
-
- var output = {};
-
- // meta is a hash used to collect geometries, materials.
- // not providing it implies that this is the root object
- // being serialized.
- if ( isRootObject ) {
-
- // initialize meta obj
- meta = {
- geometries: {},
- materials: {},
- textures: {},
- images: {},
- shapes: {}
- };
-
- output.metadata = {
- version: 4.5,
- type: 'Object',
- generator: 'Object3D.toJSON'
- };
-
- }
-
- // standard Object3D serialization
-
- var object = {};
-
- object.uuid = this.uuid;
- object.type = this.type;
-
- if ( this.name !== '' ) { object.name = this.name; }
- if ( this.castShadow === true ) { object.castShadow = true; }
- if ( this.receiveShadow === true ) { object.receiveShadow = true; }
- if ( this.visible === false ) { object.visible = false; }
- if ( this.frustumCulled === false ) { object.frustumCulled = false; }
- if ( this.renderOrder !== 0 ) { object.renderOrder = this.renderOrder; }
- if ( JSON.stringify( this.userData ) !== '{}' ) { object.userData = this.userData; }
-
- object.layers = this.layers.mask;
- object.matrix = this.matrix.toArray();
-
- if ( this.matrixAutoUpdate === false ) { object.matrixAutoUpdate = false; }
-
- // object specific properties
-
- if ( this.isInstancedMesh ) {
-
- object.type = 'InstancedMesh';
- object.count = this.count;
- object.instanceMatrix = this.instanceMatrix.toJSON();
-
- }
-
- //
-
- function serialize( library, element ) {
-
- if ( library[ element.uuid ] === undefined ) {
-
- library[ element.uuid ] = element.toJSON( meta );
-
- }
-
- return element.uuid;
-
- }
-
- if ( this.isMesh || this.isLine || this.isPoints ) {
-
- object.geometry = serialize( meta.geometries, this.geometry );
-
- var parameters = this.geometry.parameters;
-
- if ( parameters !== undefined && parameters.shapes !== undefined ) {
-
- var shapes = parameters.shapes;
-
- if ( Array.isArray( shapes ) ) {
-
- for ( var i = 0, l = shapes.length; i < l; i ++ ) {
-
- var shape = shapes[ i ];
-
- serialize( meta.shapes, shape );
-
- }
-
- } else {
-
- serialize( meta.shapes, shapes );
-
- }
-
- }
-
- }
-
- if ( this.material !== undefined ) {
-
- if ( Array.isArray( this.material ) ) {
-
- var uuids = [];
-
- for ( var i = 0, l = this.material.length; i < l; i ++ ) {
-
- uuids.push( serialize( meta.materials, this.material[ i ] ) );
-
- }
-
- object.material = uuids;
-
- } else {
-
- object.material = serialize( meta.materials, this.material );
-
- }
-
- }
-
- //
-
- if ( this.children.length > 0 ) {
-
- object.children = [];
-
- for ( var i = 0; i < this.children.length; i ++ ) {
-
- object.children.push( this.children[ i ].toJSON( meta ).object );
-
- }
-
- }
-
- if ( isRootObject ) {
-
- var geometries = extractFromCache( meta.geometries );
- var materials = extractFromCache( meta.materials );
- var textures = extractFromCache( meta.textures );
- var images = extractFromCache( meta.images );
- var shapes = extractFromCache( meta.shapes );
-
- if ( geometries.length > 0 ) { output.geometries = geometries; }
- if ( materials.length > 0 ) { output.materials = materials; }
- if ( textures.length > 0 ) { output.textures = textures; }
- if ( images.length > 0 ) { output.images = images; }
- if ( shapes.length > 0 ) { output.shapes = shapes; }
-
- }
-
- output.object = object;
-
- return output;
-
- // extract data from the cache hash
- // remove metadata on each item
- // and return as array
- function extractFromCache( cache ) {
-
- var values = [];
- for ( var key in cache ) {
-
- var data = cache[ key ];
- delete data.metadata;
- values.push( data );
-
- }
-
- return values;
-
- }
-
- },
-
- clone: function ( recursive ) {
-
- return new this.constructor().copy( this, recursive );
-
- },
-
- copy: function ( source, recursive ) {
-
- if ( recursive === undefined ) { recursive = true; }
-
- this.name = source.name;
-
- this.up.copy( source.up );
-
- this.position.copy( source.position );
- this.quaternion.copy( source.quaternion );
- this.scale.copy( source.scale );
-
- this.matrix.copy( source.matrix );
- this.matrixWorld.copy( source.matrixWorld );
-
- this.matrixAutoUpdate = source.matrixAutoUpdate;
- this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate;
-
- this.layers.mask = source.layers.mask;
- this.visible = source.visible;
-
- this.castShadow = source.castShadow;
- this.receiveShadow = source.receiveShadow;
-
- this.frustumCulled = source.frustumCulled;
- this.renderOrder = source.renderOrder;
-
- this.userData = JSON.parse( JSON.stringify( source.userData ) );
-
- if ( recursive === true ) {
-
- for ( var i = 0; i < source.children.length; i ++ ) {
-
- var child = source.children[ i ];
- this.add( child.clone() );
-
- }
-
- }
-
- return this;
-
- }
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- */
-
- function Scene() {
-
- Object3D.call( this );
-
- this.type = 'Scene';
-
- this.background = null;
- this.environment = null;
- this.fog = null;
-
- this.overrideMaterial = null;
-
- this.autoUpdate = true; // checked by the renderer
-
- if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
-
- __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
-
- }
-
- }
-
- Scene.prototype = Object.assign( Object.create( Object3D.prototype ), {
-
- constructor: Scene,
-
- isScene: true,
-
- copy: function ( source, recursive ) {
-
- Object3D.prototype.copy.call( this, source, recursive );
-
- if ( source.background !== null ) { this.background = source.background.clone(); }
- if ( source.environment !== null ) { this.environment = source.environment.clone(); }
- if ( source.fog !== null ) { this.fog = source.fog.clone(); }
-
- if ( source.overrideMaterial !== null ) { this.overrideMaterial = source.overrideMaterial.clone(); }
-
- this.autoUpdate = source.autoUpdate;
- this.matrixAutoUpdate = source.matrixAutoUpdate;
-
- return this;
-
- },
-
- toJSON: function ( meta ) {
-
- var data = Object3D.prototype.toJSON.call( this, meta );
-
- if ( this.background !== null ) { data.object.background = this.background.toJSON( meta ); }
- if ( this.environment !== null ) { data.object.environment = this.environment.toJSON( meta ); }
- if ( this.fog !== null ) { data.object.fog = this.fog.toJSON(); }
-
- return data;
-
- },
-
- dispose: function () {
-
- this.dispatchEvent( { type: 'dispose' } );
-
- }
-
- } );
-
- var _points = [
- new Vector3(),
- new Vector3(),
- new Vector3(),
- new Vector3(),
- new Vector3(),
- new Vector3(),
- new Vector3(),
- new Vector3()
- ];
-
- var _vector$1 = new Vector3();
-
- var _box = new Box3();
-
- // triangle centered vertices
-
- var _v0 = new Vector3();
- var _v1$2 = new Vector3();
- var _v2 = new Vector3();
-
- // triangle edge vectors
-
- var _f0 = new Vector3();
- var _f1 = new Vector3();
- var _f2 = new Vector3();
-
- var _center = new Vector3();
- var _extents = new Vector3();
- var _triangleNormal = new Vector3();
- var _testAxis = new Vector3();
-
- /**
- * @author bhouston / http://clara.io
- * @author WestLangley / http://github.com/WestLangley
- */
-
- function Box3( min, max ) {
-
- this.min = ( min !== undefined ) ? min : new Vector3( + Infinity, + Infinity, + Infinity );
- this.max = ( max !== undefined ) ? max : new Vector3( - Infinity, - Infinity, - Infinity );
-
- }
-
-
- Object.assign( Box3.prototype, {
-
- isBox3: true,
-
- set: function ( min, max ) {
-
- this.min.copy( min );
- this.max.copy( max );
-
- return this;
-
- },
-
- setFromArray: function ( array ) {
-
- var minX = + Infinity;
- var minY = + Infinity;
- var minZ = + Infinity;
-
- var maxX = - Infinity;
- var maxY = - Infinity;
- var maxZ = - Infinity;
-
- for ( var i = 0, l = array.length; i < l; i += 3 ) {
-
- var x = array[ i ];
- var y = array[ i + 1 ];
- var z = array[ i + 2 ];
-
- if ( x < minX ) { minX = x; }
- if ( y < minY ) { minY = y; }
- if ( z < minZ ) { minZ = z; }
-
- if ( x > maxX ) { maxX = x; }
- if ( y > maxY ) { maxY = y; }
- if ( z > maxZ ) { maxZ = z; }
-
- }
-
- this.min.set( minX, minY, minZ );
- this.max.set( maxX, maxY, maxZ );
-
- return this;
-
- },
-
- setFromBufferAttribute: function ( attribute ) {
-
- var minX = + Infinity;
- var minY = + Infinity;
- var minZ = + Infinity;
-
- var maxX = - Infinity;
- var maxY = - Infinity;
- var maxZ = - Infinity;
-
- for ( var i = 0, l = attribute.count; i < l; i ++ ) {
-
- var x = attribute.getX( i );
- var y = attribute.getY( i );
- var z = attribute.getZ( i );
-
- if ( x < minX ) { minX = x; }
- if ( y < minY ) { minY = y; }
- if ( z < minZ ) { minZ = z; }
-
- if ( x > maxX ) { maxX = x; }
- if ( y > maxY ) { maxY = y; }
- if ( z > maxZ ) { maxZ = z; }
-
- }
-
- this.min.set( minX, minY, minZ );
- this.max.set( maxX, maxY, maxZ );
-
- return this;
-
- },
-
- setFromPoints: function ( points ) {
-
- this.makeEmpty();
-
- for ( var i = 0, il = points.length; i < il; i ++ ) {
-
- this.expandByPoint( points[ i ] );
-
- }
-
- return this;
-
- },
-
- setFromCenterAndSize: function ( center, size ) {
-
- var halfSize = _vector$1.copy( size ).multiplyScalar( 0.5 );
-
- this.min.copy( center ).sub( halfSize );
- this.max.copy( center ).add( halfSize );
-
- return this;
-
- },
-
- setFromObject: function ( object ) {
-
- this.makeEmpty();
-
- return this.expandByObject( object );
-
- },
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- },
-
- copy: function ( box ) {
-
- this.min.copy( box.min );
- this.max.copy( box.max );
-
- return this;
-
- },
-
- makeEmpty: function () {
-
- this.min.x = this.min.y = this.min.z = + Infinity;
- this.max.x = this.max.y = this.max.z = - Infinity;
-
- return this;
-
- },
-
- isEmpty: function () {
-
- // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
-
- return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z );
-
- },
-
- getCenter: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Box3: .getCenter() target is now required' );
- target = new Vector3();
-
- }
-
- return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
-
- },
-
- getSize: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Box3: .getSize() target is now required' );
- target = new Vector3();
-
- }
-
- return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min );
-
- },
-
- expandByPoint: function ( point ) {
-
- this.min.min( point );
- this.max.max( point );
-
- return this;
-
- },
-
- expandByVector: function ( vector ) {
-
- this.min.sub( vector );
- this.max.add( vector );
-
- return this;
-
- },
-
- expandByScalar: function ( scalar ) {
-
- this.min.addScalar( - scalar );
- this.max.addScalar( scalar );
-
- return this;
-
- },
-
- expandByObject: function ( object ) {
-
- // Computes the world-axis-aligned bounding box of an object (including its children),
- // accounting for both the object's, and children's, world transforms
-
- object.updateWorldMatrix( false, false );
-
- var geometry = object.geometry;
-
- if ( geometry !== undefined ) {
-
- if ( geometry.boundingBox === null ) {
-
- geometry.computeBoundingBox();
-
- }
-
- _box.copy( geometry.boundingBox );
- _box.applyMatrix4( object.matrixWorld );
-
- this.union( _box );
-
- }
-
- var children = object.children;
-
- for ( var i = 0, l = children.length; i < l; i ++ ) {
-
- this.expandByObject( children[ i ] );
-
- }
-
- return this;
-
- },
-
- containsPoint: function ( point ) {
-
- return point.x < this.min.x || point.x > this.max.x ||
- point.y < this.min.y || point.y > this.max.y ||
- point.z < this.min.z || point.z > this.max.z ? false : true;
-
- },
-
- containsBox: function ( box ) {
-
- return this.min.x <= box.min.x && box.max.x <= this.max.x &&
- this.min.y <= box.min.y && box.max.y <= this.max.y &&
- this.min.z <= box.min.z && box.max.z <= this.max.z;
-
- },
-
- getParameter: function ( point, target ) {
-
- // This can potentially have a divide by zero if the box
- // has a size dimension of 0.
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Box3: .getParameter() target is now required' );
- target = new Vector3();
-
- }
-
- return target.set(
- ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
- ( point.y - this.min.y ) / ( this.max.y - this.min.y ),
- ( point.z - this.min.z ) / ( this.max.z - this.min.z )
- );
-
- },
-
- intersectsBox: function ( box ) {
-
- // using 6 splitting planes to rule out intersections.
- return box.max.x < this.min.x || box.min.x > this.max.x ||
- box.max.y < this.min.y || box.min.y > this.max.y ||
- box.max.z < this.min.z || box.min.z > this.max.z ? false : true;
-
- },
-
- intersectsSphere: function ( sphere ) {
-
- // Find the point on the AABB closest to the sphere center.
- this.clampPoint( sphere.center, _vector$1 );
-
- // If that point is inside the sphere, the AABB and sphere intersect.
- return _vector$1.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );
-
- },
-
- intersectsPlane: function ( plane ) {
-
- // We compute the minimum and maximum dot product values. If those values
- // are on the same side (back or front) of the plane, then there is no intersection.
-
- var min, max;
-
- if ( plane.normal.x > 0 ) {
-
- min = plane.normal.x * this.min.x;
- max = plane.normal.x * this.max.x;
-
- } else {
-
- min = plane.normal.x * this.max.x;
- max = plane.normal.x * this.min.x;
-
- }
-
- if ( plane.normal.y > 0 ) {
-
- min += plane.normal.y * this.min.y;
- max += plane.normal.y * this.max.y;
-
- } else {
-
- min += plane.normal.y * this.max.y;
- max += plane.normal.y * this.min.y;
-
- }
-
- if ( plane.normal.z > 0 ) {
-
- min += plane.normal.z * this.min.z;
- max += plane.normal.z * this.max.z;
-
- } else {
-
- min += plane.normal.z * this.max.z;
- max += plane.normal.z * this.min.z;
-
- }
-
- return ( min <= - plane.constant && max >= - plane.constant );
-
- },
-
- intersectsTriangle: function ( triangle ) {
-
- if ( this.isEmpty() ) {
-
- return false;
-
- }
-
- // compute box center and extents
- this.getCenter( _center );
- _extents.subVectors( this.max, _center );
-
- // translate triangle to aabb origin
- _v0.subVectors( triangle.a, _center );
- _v1$2.subVectors( triangle.b, _center );
- _v2.subVectors( triangle.c, _center );
-
- // compute edge vectors for triangle
- _f0.subVectors( _v1$2, _v0 );
- _f1.subVectors( _v2, _v1$2 );
- _f2.subVectors( _v0, _v2 );
-
- // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb
- // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation
- // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned)
- var axes = [
- 0, - _f0.z, _f0.y, 0, - _f1.z, _f1.y, 0, - _f2.z, _f2.y,
- _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x,
- - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0
- ];
- if ( ! satForAxes( axes, _v0, _v1$2, _v2, _extents ) ) {
-
- return false;
-
- }
-
- // test 3 face normals from the aabb
- axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ];
- if ( ! satForAxes( axes, _v0, _v1$2, _v2, _extents ) ) {
-
- return false;
-
- }
-
- // finally testing the face normal of the triangle
- // use already existing triangle edge vectors here
- _triangleNormal.crossVectors( _f0, _f1 );
- axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ];
-
- return satForAxes( axes, _v0, _v1$2, _v2, _extents );
-
- },
-
- clampPoint: function ( point, target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Box3: .clampPoint() target is now required' );
- target = new Vector3();
-
- }
-
- return target.copy( point ).clamp( this.min, this.max );
-
- },
-
- distanceToPoint: function ( point ) {
-
- var clampedPoint = _vector$1.copy( point ).clamp( this.min, this.max );
-
- return clampedPoint.sub( point ).length();
-
- },
-
- getBoundingSphere: function ( target ) {
-
- if ( target === undefined ) {
-
- console.error( 'THREE.Box3: .getBoundingSphere() target is now required' );
- //target = new Sphere(); // removed to avoid cyclic dependency
-
- }
-
- this.getCenter( target.center );
-
- target.radius = this.getSize( _vector$1 ).length() * 0.5;
-
- return target;
-
- },
-
- intersect: function ( box ) {
-
- this.min.max( box.min );
- this.max.min( box.max );
-
- // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values.
- if ( this.isEmpty() ) { this.makeEmpty(); }
-
- return this;
-
- },
-
- union: function ( box ) {
-
- this.min.min( box.min );
- this.max.max( box.max );
-
- return this;
-
- },
-
- applyMatrix4: function ( matrix ) {
-
- // transform of empty box is an empty box.
- if ( this.isEmpty() ) { return this; }
-
- // NOTE: I am using a binary pattern to specify all 2^3 combinations below
- _points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000
- _points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001
- _points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010
- _points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011
- _points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100
- _points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101
- _points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110
- _points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111
-
- this.setFromPoints( _points );
-
- return this;
-
- },
-
- translate: function ( offset ) {
-
- this.min.add( offset );
- this.max.add( offset );
-
- return this;
-
- },
-
- equals: function ( box ) {
-
- return box.min.equals( this.min ) && box.max.equals( this.max );
-
- }
-
- } );
-
- function satForAxes( axes, v0, v1, v2, extents ) {
-
- var i, j;
-
- for ( i = 0, j = axes.length - 3; i <= j; i += 3 ) {
-
- _testAxis.fromArray( axes, i );
- // project the aabb onto the seperating axis
- var r = extents.x * Math.abs( _testAxis.x ) + extents.y * Math.abs( _testAxis.y ) + extents.z * Math.abs( _testAxis.z );
- // project all 3 vertices of the triangle onto the seperating axis
- var p0 = v0.dot( _testAxis );
- var p1 = v1.dot( _testAxis );
- var p2 = v2.dot( _testAxis );
- // actual test, basically see if either of the most extreme of the triangle points intersects r
- if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) {
-
- // points of the projected triangle are outside the projected half-length of the aabb
- // the axis is seperating and we can exit
- return false;
-
- }
-
- }
-
- return true;
-
- }
-
- var _box$1 = new Box3();
-
- /**
- * @author bhouston / http://clara.io
- * @author mrdoob / http://mrdoob.com/
- */
-
- function Sphere( center, radius ) {
-
- this.center = ( center !== undefined ) ? center : new Vector3();
- this.radius = ( radius !== undefined ) ? radius : - 1;
-
- }
-
- Object.assign( Sphere.prototype, {
-
- set: function ( center, radius ) {
-
- this.center.copy( center );
- this.radius = radius;
-
- return this;
-
- },
-
- setFromPoints: function ( points, optionalCenter ) {
-
- var center = this.center;
-
- if ( optionalCenter !== undefined ) {
-
- center.copy( optionalCenter );
-
- } else {
-
- _box$1.setFromPoints( points ).getCenter( center );
-
- }
-
- var maxRadiusSq = 0;
-
- for ( var i = 0, il = points.length; i < il; i ++ ) {
-
- maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) );
-
- }
-
- this.radius = Math.sqrt( maxRadiusSq );
-
- return this;
-
- },
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- },
-
- copy: function ( sphere ) {
-
- this.center.copy( sphere.center );
- this.radius = sphere.radius;
-
- return this;
-
- },
-
- isEmpty: function () {
-
- return ( this.radius < 0 );
-
- },
-
- makeEmpty: function () {
-
- this.center.set( 0, 0, 0 );
- this.radius = - 1;
-
- return this;
-
- },
-
- containsPoint: function ( point ) {
-
- return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) );
-
- },
-
- distanceToPoint: function ( point ) {
-
- return ( point.distanceTo( this.center ) - this.radius );
-
- },
-
- intersectsSphere: function ( sphere ) {
-
- var radiusSum = this.radius + sphere.radius;
-
- return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum );
-
- },
-
- intersectsBox: function ( box ) {
-
- return box.intersectsSphere( this );
-
- },
-
- intersectsPlane: function ( plane ) {
-
- return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius;
-
- },
-
- clampPoint: function ( point, target ) {
-
- var deltaLengthSq = this.center.distanceToSquared( point );
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Sphere: .clampPoint() target is now required' );
- target = new Vector3();
-
- }
-
- target.copy( point );
-
- if ( deltaLengthSq > ( this.radius * this.radius ) ) {
-
- target.sub( this.center ).normalize();
- target.multiplyScalar( this.radius ).add( this.center );
-
- }
-
- return target;
-
- },
-
- getBoundingBox: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Sphere: .getBoundingBox() target is now required' );
- target = new Box3();
-
- }
-
- if ( this.isEmpty() ) {
-
- // Empty sphere produces empty bounding box
- target.makeEmpty();
- return target;
-
- }
-
- target.set( this.center, this.center );
- target.expandByScalar( this.radius );
-
- return target;
-
- },
-
- applyMatrix4: function ( matrix ) {
-
- this.center.applyMatrix4( matrix );
- this.radius = this.radius * matrix.getMaxScaleOnAxis();
-
- return this;
-
- },
-
- translate: function ( offset ) {
-
- this.center.add( offset );
-
- return this;
-
- },
-
- equals: function ( sphere ) {
-
- return sphere.center.equals( this.center ) && ( sphere.radius === this.radius );
-
- }
-
- } );
-
- var _vector$2 = new Vector3();
- var _segCenter = new Vector3();
- var _segDir = new Vector3();
- var _diff = new Vector3();
-
- var _edge1 = new Vector3();
- var _edge2 = new Vector3();
- var _normal = new Vector3();
-
- /**
- * @author bhouston / http://clara.io
- */
-
- function Ray( origin, direction ) {
-
- this.origin = ( origin !== undefined ) ? origin : new Vector3();
- this.direction = ( direction !== undefined ) ? direction : new Vector3( 0, 0, - 1 );
-
- }
-
- Object.assign( Ray.prototype, {
-
- set: function ( origin, direction ) {
-
- this.origin.copy( origin );
- this.direction.copy( direction );
-
- return this;
-
- },
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- },
-
- copy: function ( ray ) {
-
- this.origin.copy( ray.origin );
- this.direction.copy( ray.direction );
-
- return this;
-
- },
-
- at: function ( t, target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Ray: .at() target is now required' );
- target = new Vector3();
-
- }
-
- return target.copy( this.direction ).multiplyScalar( t ).add( this.origin );
-
- },
-
- lookAt: function ( v ) {
-
- this.direction.copy( v ).sub( this.origin ).normalize();
-
- return this;
-
- },
-
- recast: function ( t ) {
-
- this.origin.copy( this.at( t, _vector$2 ) );
-
- return this;
-
- },
-
- closestPointToPoint: function ( point, target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Ray: .closestPointToPoint() target is now required' );
- target = new Vector3();
-
- }
-
- target.subVectors( point, this.origin );
-
- var directionDistance = target.dot( this.direction );
-
- if ( directionDistance < 0 ) {
-
- return target.copy( this.origin );
-
- }
-
- return target.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
-
- },
-
- distanceToPoint: function ( point ) {
-
- return Math.sqrt( this.distanceSqToPoint( point ) );
-
- },
-
- distanceSqToPoint: function ( point ) {
-
- var directionDistance = _vector$2.subVectors( point, this.origin ).dot( this.direction );
-
- // point behind the ray
-
- if ( directionDistance < 0 ) {
-
- return this.origin.distanceToSquared( point );
-
- }
-
- _vector$2.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
-
- return _vector$2.distanceToSquared( point );
-
- },
-
- distanceSqToSegment: function ( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
-
- // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h
- // It returns the min distance between the ray and the segment
- // defined by v0 and v1
- // It can also set two optional targets :
- // - The closest point on the ray
- // - The closest point on the segment
-
- _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );
- _segDir.copy( v1 ).sub( v0 ).normalize();
- _diff.copy( this.origin ).sub( _segCenter );
-
- var segExtent = v0.distanceTo( v1 ) * 0.5;
- var a01 = - this.direction.dot( _segDir );
- var b0 = _diff.dot( this.direction );
- var b1 = - _diff.dot( _segDir );
- var c = _diff.lengthSq();
- var det = Math.abs( 1 - a01 * a01 );
- var s0, s1, sqrDist, extDet;
-
- if ( det > 0 ) {
-
- // The ray and segment are not parallel.
-
- s0 = a01 * b1 - b0;
- s1 = a01 * b0 - b1;
- extDet = segExtent * det;
-
- if ( s0 >= 0 ) {
-
- if ( s1 >= - extDet ) {
-
- if ( s1 <= extDet ) {
-
- // region 0
- // Minimum at interior points of ray and segment.
-
- var invDet = 1 / det;
- s0 *= invDet;
- s1 *= invDet;
- sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
-
- } else {
-
- // region 1
-
- s1 = segExtent;
- s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
- sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
-
- }
-
- } else {
-
- // region 5
-
- s1 = - segExtent;
- s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
- sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
-
- }
-
- } else {
-
- if ( s1 <= - extDet ) {
-
- // region 4
-
- s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
- s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
- sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
-
- } else if ( s1 <= extDet ) {
-
- // region 3
-
- s0 = 0;
- s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
- sqrDist = s1 * ( s1 + 2 * b1 ) + c;
-
- } else {
-
- // region 2
-
- s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
- s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
- sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
-
- }
-
- }
-
- } else {
-
- // Ray and segment are parallel.
-
- s1 = ( a01 > 0 ) ? - segExtent : segExtent;
- s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
- sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
-
- }
-
- if ( optionalPointOnRay ) {
-
- optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin );
-
- }
-
- if ( optionalPointOnSegment ) {
-
- optionalPointOnSegment.copy( _segDir ).multiplyScalar( s1 ).add( _segCenter );
-
- }
-
- return sqrDist;
-
- },
-
- intersectSphere: function ( sphere, target ) {
-
- _vector$2.subVectors( sphere.center, this.origin );
- var tca = _vector$2.dot( this.direction );
- var d2 = _vector$2.dot( _vector$2 ) - tca * tca;
- var radius2 = sphere.radius * sphere.radius;
-
- if ( d2 > radius2 ) { return null; }
-
- var thc = Math.sqrt( radius2 - d2 );
-
- // t0 = first intersect point - entrance on front of sphere
- var t0 = tca - thc;
-
- // t1 = second intersect point - exit point on back of sphere
- var t1 = tca + thc;
-
- // test to see if both t0 and t1 are behind the ray - if so, return null
- if ( t0 < 0 && t1 < 0 ) { return null; }
-
- // test to see if t0 is behind the ray:
- // if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
- // in order to always return an intersect point that is in front of the ray.
- if ( t0 < 0 ) { return this.at( t1, target ); }
-
- // else t0 is in front of the ray, so return the first collision point scaled by t0
- return this.at( t0, target );
-
- },
-
- intersectsSphere: function ( sphere ) {
-
- return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius );
-
- },
-
- distanceToPlane: function ( plane ) {
-
- var denominator = plane.normal.dot( this.direction );
-
- if ( denominator === 0 ) {
-
- // line is coplanar, return origin
- if ( plane.distanceToPoint( this.origin ) === 0 ) {
-
- return 0;
-
- }
-
- // Null is preferable to undefined since undefined means.... it is undefined
-
- return null;
-
- }
-
- var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
-
- // Return if the ray never intersects the plane
-
- return t >= 0 ? t : null;
-
- },
-
- intersectPlane: function ( plane, target ) {
-
- var t = this.distanceToPlane( plane );
-
- if ( t === null ) {
-
- return null;
-
- }
-
- return this.at( t, target );
-
- },
-
- intersectsPlane: function ( plane ) {
-
- // check if the ray lies on the plane first
-
- var distToPoint = plane.distanceToPoint( this.origin );
-
- if ( distToPoint === 0 ) {
-
- return true;
-
- }
-
- var denominator = plane.normal.dot( this.direction );
-
- if ( denominator * distToPoint < 0 ) {
-
- return true;
-
- }
-
- // ray origin is behind the plane (and is pointing behind it)
-
- return false;
-
- },
-
- intersectBox: function ( box, target ) {
-
- var tmin, tmax, tymin, tymax, tzmin, tzmax;
-
- var invdirx = 1 / this.direction.x,
- invdiry = 1 / this.direction.y,
- invdirz = 1 / this.direction.z;
-
- var origin = this.origin;
-
- if ( invdirx >= 0 ) {
-
- tmin = ( box.min.x - origin.x ) * invdirx;
- tmax = ( box.max.x - origin.x ) * invdirx;
-
- } else {
-
- tmin = ( box.max.x - origin.x ) * invdirx;
- tmax = ( box.min.x - origin.x ) * invdirx;
-
- }
-
- if ( invdiry >= 0 ) {
-
- tymin = ( box.min.y - origin.y ) * invdiry;
- tymax = ( box.max.y - origin.y ) * invdiry;
-
- } else {
-
- tymin = ( box.max.y - origin.y ) * invdiry;
- tymax = ( box.min.y - origin.y ) * invdiry;
-
- }
-
- if ( ( tmin > tymax ) || ( tymin > tmax ) ) { return null; }
-
- // These lines also handle the case where tmin or tmax is NaN
- // (result of 0 * Infinity). x !== x returns true if x is NaN
-
- if ( tymin > tmin || tmin !== tmin ) { tmin = tymin; }
-
- if ( tymax < tmax || tmax !== tmax ) { tmax = tymax; }
-
- if ( invdirz >= 0 ) {
-
- tzmin = ( box.min.z - origin.z ) * invdirz;
- tzmax = ( box.max.z - origin.z ) * invdirz;
-
- } else {
-
- tzmin = ( box.max.z - origin.z ) * invdirz;
- tzmax = ( box.min.z - origin.z ) * invdirz;
-
- }
-
- if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) { return null; }
-
- if ( tzmin > tmin || tmin !== tmin ) { tmin = tzmin; }
-
- if ( tzmax < tmax || tmax !== tmax ) { tmax = tzmax; }
-
- //return point closest to the ray (positive side)
-
- if ( tmax < 0 ) { return null; }
-
- return this.at( tmin >= 0 ? tmin : tmax, target );
-
- },
-
- intersectsBox: function ( box ) {
-
- return this.intersectBox( box, _vector$2 ) !== null;
-
- },
-
- intersectTriangle: function ( a, b, c, backfaceCulling, target ) {
-
- // Compute the offset origin, edges, and normal.
-
- // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
-
- _edge1.subVectors( b, a );
- _edge2.subVectors( c, a );
- _normal.crossVectors( _edge1, _edge2 );
-
- // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
- // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
- // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
- // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
- // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
- var DdN = this.direction.dot( _normal );
- var sign;
-
- if ( DdN > 0 ) {
-
- if ( backfaceCulling ) { return null; }
- sign = 1;
-
- } else if ( DdN < 0 ) {
-
- sign = - 1;
- DdN = - DdN;
-
- } else {
-
- return null;
-
- }
-
- _diff.subVectors( this.origin, a );
- var DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) );
-
- // b1 < 0, no intersection
- if ( DdQxE2 < 0 ) {
-
- return null;
-
- }
-
- var DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) );
-
- // b2 < 0, no intersection
- if ( DdE1xQ < 0 ) {
-
- return null;
-
- }
-
- // b1+b2 > 1, no intersection
- if ( DdQxE2 + DdE1xQ > DdN ) {
-
- return null;
-
- }
-
- // Line intersects triangle, check if ray does.
- var QdN = - sign * _diff.dot( _normal );
-
- // t < 0, no intersection
- if ( QdN < 0 ) {
-
- return null;
-
- }
-
- // Ray intersects triangle.
- return this.at( QdN / DdN, target );
-
- },
-
- applyMatrix4: function ( matrix4 ) {
-
- this.origin.applyMatrix4( matrix4 );
- this.direction.transformDirection( matrix4 );
-
- return this;
-
- },
-
- equals: function ( ray ) {
-
- return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );
-
- }
-
- } );
-
- /**
- * @author bhouston / http://clara.io
- */
-
- var _vector1 = new Vector3();
- var _vector2 = new Vector3();
- var _normalMatrix = new Matrix3();
-
- function Plane( normal, constant ) {
-
- // normal is assumed to be normalized
-
- this.normal = ( normal !== undefined ) ? normal : new Vector3( 1, 0, 0 );
- this.constant = ( constant !== undefined ) ? constant : 0;
-
- }
-
- Object.assign( Plane.prototype, {
-
- isPlane: true,
-
- set: function ( normal, constant ) {
-
- this.normal.copy( normal );
- this.constant = constant;
-
- return this;
-
- },
-
- setComponents: function ( x, y, z, w ) {
-
- this.normal.set( x, y, z );
- this.constant = w;
-
- return this;
-
- },
-
- setFromNormalAndCoplanarPoint: function ( normal, point ) {
-
- this.normal.copy( normal );
- this.constant = - point.dot( this.normal );
-
- return this;
-
- },
-
- setFromCoplanarPoints: function ( a, b, c ) {
-
- var normal = _vector1.subVectors( c, b ).cross( _vector2.subVectors( a, b ) ).normalize();
-
- // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?
-
- this.setFromNormalAndCoplanarPoint( normal, a );
-
- return this;
-
- },
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- },
-
- copy: function ( plane ) {
-
- this.normal.copy( plane.normal );
- this.constant = plane.constant;
-
- return this;
-
- },
-
- normalize: function () {
-
- // Note: will lead to a divide by zero if the plane is invalid.
-
- var inverseNormalLength = 1.0 / this.normal.length();
- this.normal.multiplyScalar( inverseNormalLength );
- this.constant *= inverseNormalLength;
-
- return this;
-
- },
-
- negate: function () {
-
- this.constant *= - 1;
- this.normal.negate();
-
- return this;
-
- },
-
- distanceToPoint: function ( point ) {
-
- return this.normal.dot( point ) + this.constant;
-
- },
-
- distanceToSphere: function ( sphere ) {
-
- return this.distanceToPoint( sphere.center ) - sphere.radius;
-
- },
-
- projectPoint: function ( point, target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Plane: .projectPoint() target is now required' );
- target = new Vector3();
-
- }
-
- return target.copy( this.normal ).multiplyScalar( - this.distanceToPoint( point ) ).add( point );
-
- },
-
- intersectLine: function ( line, target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Plane: .intersectLine() target is now required' );
- target = new Vector3();
-
- }
-
- var direction = line.delta( _vector1 );
-
- var denominator = this.normal.dot( direction );
-
- if ( denominator === 0 ) {
-
- // line is coplanar, return origin
- if ( this.distanceToPoint( line.start ) === 0 ) {
-
- return target.copy( line.start );
-
- }
-
- // Unsure if this is the correct method to handle this case.
- return undefined;
-
- }
-
- var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator;
-
- if ( t < 0 || t > 1 ) {
-
- return undefined;
-
- }
-
- return target.copy( direction ).multiplyScalar( t ).add( line.start );
-
- },
-
- intersectsLine: function ( line ) {
-
- // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.
-
- var startSign = this.distanceToPoint( line.start );
- var endSign = this.distanceToPoint( line.end );
-
- return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 );
-
- },
-
- intersectsBox: function ( box ) {
-
- return box.intersectsPlane( this );
-
- },
-
- intersectsSphere: function ( sphere ) {
-
- return sphere.intersectsPlane( this );
-
- },
-
- coplanarPoint: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Plane: .coplanarPoint() target is now required' );
- target = new Vector3();
-
- }
-
- return target.copy( this.normal ).multiplyScalar( - this.constant );
-
- },
-
- applyMatrix4: function ( matrix, optionalNormalMatrix ) {
-
- var normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix );
-
- var referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix );
-
- var normal = this.normal.applyMatrix3( normalMatrix ).normalize();
-
- this.constant = - referencePoint.dot( normal );
-
- return this;
-
- },
-
- translate: function ( offset ) {
-
- this.constant -= offset.dot( this.normal );
-
- return this;
-
- },
-
- equals: function ( plane ) {
-
- return plane.normal.equals( this.normal ) && ( plane.constant === this.constant );
-
- }
-
- } );
-
- /**
- * @author bhouston / http://clara.io
- * @author mrdoob / http://mrdoob.com/
- */
-
- var _v0$1 = new Vector3();
- var _v1$3 = new Vector3();
- var _v2$1 = new Vector3();
- var _v3 = new Vector3();
-
- var _vab = new Vector3();
- var _vac = new Vector3();
- var _vbc = new Vector3();
- var _vap = new Vector3();
- var _vbp = new Vector3();
- var _vcp = new Vector3();
-
- function Triangle( a, b, c ) {
-
- this.a = ( a !== undefined ) ? a : new Vector3();
- this.b = ( b !== undefined ) ? b : new Vector3();
- this.c = ( c !== undefined ) ? c : new Vector3();
-
- }
-
- Object.assign( Triangle, {
-
- getNormal: function ( a, b, c, target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Triangle: .getNormal() target is now required' );
- target = new Vector3();
-
- }
-
- target.subVectors( c, b );
- _v0$1.subVectors( a, b );
- target.cross( _v0$1 );
-
- var targetLengthSq = target.lengthSq();
- if ( targetLengthSq > 0 ) {
-
- return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) );
-
- }
-
- return target.set( 0, 0, 0 );
-
- },
-
- // static/instance method to calculate barycentric coordinates
- // based on: http://www.blackpawn.com/texts/pointinpoly/default.html
- getBarycoord: function ( point, a, b, c, target ) {
-
- _v0$1.subVectors( c, a );
- _v1$3.subVectors( b, a );
- _v2$1.subVectors( point, a );
-
- var dot00 = _v0$1.dot( _v0$1 );
- var dot01 = _v0$1.dot( _v1$3 );
- var dot02 = _v0$1.dot( _v2$1 );
- var dot11 = _v1$3.dot( _v1$3 );
- var dot12 = _v1$3.dot( _v2$1 );
-
- var denom = ( dot00 * dot11 - dot01 * dot01 );
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Triangle: .getBarycoord() target is now required' );
- target = new Vector3();
-
- }
-
- // collinear or singular triangle
- if ( denom === 0 ) {
-
- // arbitrary location outside of triangle?
- // not sure if this is the best idea, maybe should be returning undefined
- return target.set( - 2, - 1, - 1 );
-
- }
-
- var invDenom = 1 / denom;
- var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
- var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
-
- // barycentric coordinates must always sum to 1
- return target.set( 1 - u - v, v, u );
-
- },
-
- containsPoint: function ( point, a, b, c ) {
-
- Triangle.getBarycoord( point, a, b, c, _v3 );
-
- return ( _v3.x >= 0 ) && ( _v3.y >= 0 ) && ( ( _v3.x + _v3.y ) <= 1 );
-
- },
-
- getUV: function ( point, p1, p2, p3, uv1, uv2, uv3, target ) {
-
- this.getBarycoord( point, p1, p2, p3, _v3 );
-
- target.set( 0, 0 );
- target.addScaledVector( uv1, _v3.x );
- target.addScaledVector( uv2, _v3.y );
- target.addScaledVector( uv3, _v3.z );
-
- return target;
-
- },
-
- isFrontFacing: function ( a, b, c, direction ) {
-
- _v0$1.subVectors( c, b );
- _v1$3.subVectors( a, b );
-
- // strictly front facing
- return ( _v0$1.cross( _v1$3 ).dot( direction ) < 0 ) ? true : false;
-
- }
-
- } );
-
- Object.assign( Triangle.prototype, {
-
- set: function ( a, b, c ) {
-
- this.a.copy( a );
- this.b.copy( b );
- this.c.copy( c );
-
- return this;
-
- },
-
- setFromPointsAndIndices: function ( points, i0, i1, i2 ) {
-
- this.a.copy( points[ i0 ] );
- this.b.copy( points[ i1 ] );
- this.c.copy( points[ i2 ] );
-
- return this;
-
- },
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- },
-
- copy: function ( triangle ) {
-
- this.a.copy( triangle.a );
- this.b.copy( triangle.b );
- this.c.copy( triangle.c );
-
- return this;
-
- },
-
- getArea: function () {
-
- _v0$1.subVectors( this.c, this.b );
- _v1$3.subVectors( this.a, this.b );
-
- return _v0$1.cross( _v1$3 ).length() * 0.5;
-
- },
-
- getMidpoint: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Triangle: .getMidpoint() target is now required' );
- target = new Vector3();
-
- }
-
- return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 );
-
- },
-
- getNormal: function ( target ) {
-
- return Triangle.getNormal( this.a, this.b, this.c, target );
-
- },
-
- getPlane: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Triangle: .getPlane() target is now required' );
- target = new Plane();
-
- }
-
- return target.setFromCoplanarPoints( this.a, this.b, this.c );
-
- },
-
- getBarycoord: function ( point, target ) {
-
- return Triangle.getBarycoord( point, this.a, this.b, this.c, target );
-
- },
-
- getUV: function ( point, uv1, uv2, uv3, target ) {
-
- return Triangle.getUV( point, this.a, this.b, this.c, uv1, uv2, uv3, target );
-
- },
-
- containsPoint: function ( point ) {
-
- return Triangle.containsPoint( point, this.a, this.b, this.c );
-
- },
-
- isFrontFacing: function ( direction ) {
-
- return Triangle.isFrontFacing( this.a, this.b, this.c, direction );
-
- },
-
- intersectsBox: function ( box ) {
-
- return box.intersectsTriangle( this );
-
- },
-
- closestPointToPoint: function ( p, target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Triangle: .closestPointToPoint() target is now required' );
- target = new Vector3();
-
- }
-
- var a = this.a, b = this.b, c = this.c;
- var v, w;
-
- // algorithm thanks to Real-Time Collision Detection by Christer Ericson,
- // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc.,
- // under the accompanying license; see chapter 5.1.5 for detailed explanation.
- // basically, we're distinguishing which of the voronoi regions of the triangle
- // the point lies in with the minimum amount of redundant computation.
-
- _vab.subVectors( b, a );
- _vac.subVectors( c, a );
- _vap.subVectors( p, a );
- var d1 = _vab.dot( _vap );
- var d2 = _vac.dot( _vap );
- if ( d1 <= 0 && d2 <= 0 ) {
-
- // vertex region of A; barycentric coords (1, 0, 0)
- return target.copy( a );
-
- }
-
- _vbp.subVectors( p, b );
- var d3 = _vab.dot( _vbp );
- var d4 = _vac.dot( _vbp );
- if ( d3 >= 0 && d4 <= d3 ) {
-
- // vertex region of B; barycentric coords (0, 1, 0)
- return target.copy( b );
-
- }
-
- var vc = d1 * d4 - d3 * d2;
- if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) {
-
- v = d1 / ( d1 - d3 );
- // edge region of AB; barycentric coords (1-v, v, 0)
- return target.copy( a ).addScaledVector( _vab, v );
-
- }
-
- _vcp.subVectors( p, c );
- var d5 = _vab.dot( _vcp );
- var d6 = _vac.dot( _vcp );
- if ( d6 >= 0 && d5 <= d6 ) {
-
- // vertex region of C; barycentric coords (0, 0, 1)
- return target.copy( c );
-
- }
-
- var vb = d5 * d2 - d1 * d6;
- if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) {
-
- w = d2 / ( d2 - d6 );
- // edge region of AC; barycentric coords (1-w, 0, w)
- return target.copy( a ).addScaledVector( _vac, w );
-
- }
-
- var va = d3 * d6 - d5 * d4;
- if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) {
-
- _vbc.subVectors( c, b );
- w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) );
- // edge region of BC; barycentric coords (0, 1-w, w)
- return target.copy( b ).addScaledVector( _vbc, w ); // edge region of BC
-
- }
-
- // face region
- var denom = 1 / ( va + vb + vc );
- // u = va * denom
- v = vb * denom;
- w = vc * denom;
-
- return target.copy( a ).addScaledVector( _vab, v ).addScaledVector( _vac, w );
-
- },
-
- equals: function ( triangle ) {
-
- return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c );
-
- }
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- */
-
- var _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF,
- 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2,
- 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50,
- 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B,
- 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B,
- 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F,
- 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3,
- 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222,
- 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700,
- 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4,
- 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00,
- 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3,
- 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA,
- 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32,
- 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3,
- 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC,
- 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD,
- 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6,
- 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9,
- 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F,
- 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE,
- 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA,
- 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0,
- 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 };
-
- var _hslA = { h: 0, s: 0, l: 0 };
- var _hslB = { h: 0, s: 0, l: 0 };
-
- function Color( r, g, b ) {
-
- if ( g === undefined && b === undefined ) {
-
- // r is THREE.Color, hex or string
- return this.set( r );
-
- }
-
- return this.setRGB( r, g, b );
-
- }
-
- function hue2rgb( p, q, t ) {
-
- if ( t < 0 ) { t += 1; }
- if ( t > 1 ) { t -= 1; }
- if ( t < 1 / 6 ) { return p + ( q - p ) * 6 * t; }
- if ( t < 1 / 2 ) { return q; }
- if ( t < 2 / 3 ) { return p + ( q - p ) * 6 * ( 2 / 3 - t ); }
- return p;
-
- }
-
- function SRGBToLinear( c ) {
-
- return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
-
- }
-
- function LinearToSRGB( c ) {
-
- return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055;
-
- }
-
- Object.assign( Color.prototype, {
-
- isColor: true,
-
- r: 1, g: 1, b: 1,
-
- set: function ( value ) {
-
- if ( value && value.isColor ) {
-
- this.copy( value );
-
- } else if ( typeof value === 'number' ) {
-
- this.setHex( value );
-
- } else if ( typeof value === 'string' ) {
-
- this.setStyle( value );
-
- }
-
- return this;
-
- },
-
- setScalar: function ( scalar ) {
-
- this.r = scalar;
- this.g = scalar;
- this.b = scalar;
-
- return this;
-
- },
-
- setHex: function ( hex ) {
-
- hex = Math.floor( hex );
-
- this.r = ( hex >> 16 & 255 ) / 255;
- this.g = ( hex >> 8 & 255 ) / 255;
- this.b = ( hex & 255 ) / 255;
-
- return this;
-
- },
-
- setRGB: function ( r, g, b ) {
-
- this.r = r;
- this.g = g;
- this.b = b;
-
- return this;
-
- },
-
- setHSL: function ( h, s, l ) {
-
- // h,s,l ranges are in 0.0 - 1.0
- h = MathUtils.euclideanModulo( h, 1 );
- s = MathUtils.clamp( s, 0, 1 );
- l = MathUtils.clamp( l, 0, 1 );
-
- if ( s === 0 ) {
-
- this.r = this.g = this.b = l;
-
- } else {
-
- var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );
- var q = ( 2 * l ) - p;
-
- this.r = hue2rgb( q, p, h + 1 / 3 );
- this.g = hue2rgb( q, p, h );
- this.b = hue2rgb( q, p, h - 1 / 3 );
-
- }
-
- return this;
-
- },
-
- setStyle: function ( style ) {
-
- function handleAlpha( string ) {
-
- if ( string === undefined ) { return; }
-
- if ( parseFloat( string ) < 1 ) {
-
- console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' );
-
- }
-
- }
-
-
- var m;
-
- if ( m = /^((?:rgb|hsl)a?)\(\s*([^\)]*)\)/.exec( style ) ) {
-
- // rgb / hsl
-
- var color;
- var name = m[ 1 ];
- var components = m[ 2 ];
-
- switch ( name ) {
-
- case 'rgb':
- case 'rgba':
-
- if ( color = /^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) {
-
- // rgb(255,0,0) rgba(255,0,0,0.5)
- this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255;
- this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255;
- this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255;
-
- handleAlpha( color[ 5 ] );
-
- return this;
-
- }
-
- if ( color = /^(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) {
-
- // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5)
- this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100;
- this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100;
- this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100;
-
- handleAlpha( color[ 5 ] );
-
- return this;
-
- }
-
- break;
-
- case 'hsl':
- case 'hsla':
-
- if ( color = /^([0-9]*\.?[0-9]+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) {
-
- // hsl(120,50%,50%) hsla(120,50%,50%,0.5)
- var h = parseFloat( color[ 1 ] ) / 360;
- var s = parseInt( color[ 2 ], 10 ) / 100;
- var l = parseInt( color[ 3 ], 10 ) / 100;
-
- handleAlpha( color[ 5 ] );
-
- return this.setHSL( h, s, l );
-
- }
-
- break;
-
- }
-
- } else if ( m = /^\#([A-Fa-f0-9]+)$/.exec( style ) ) {
-
- // hex color
-
- var hex = m[ 1 ];
- var size = hex.length;
-
- if ( size === 3 ) {
-
- // #ff0
- this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 0 ), 16 ) / 255;
- this.g = parseInt( hex.charAt( 1 ) + hex.charAt( 1 ), 16 ) / 255;
- this.b = parseInt( hex.charAt( 2 ) + hex.charAt( 2 ), 16 ) / 255;
-
- return this;
-
- } else if ( size === 6 ) {
-
- // #ff0000
- this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 1 ), 16 ) / 255;
- this.g = parseInt( hex.charAt( 2 ) + hex.charAt( 3 ), 16 ) / 255;
- this.b = parseInt( hex.charAt( 4 ) + hex.charAt( 5 ), 16 ) / 255;
-
- return this;
-
- }
-
- }
-
- if ( style && style.length > 0 ) {
-
- return this.setColorName( style );
-
- }
-
- return this;
-
- },
-
- setColorName: function ( style ) {
-
- // color keywords
- var hex = _colorKeywords[ style ];
-
- if ( hex !== undefined ) {
-
- // red
- this.setHex( hex );
-
- } else {
-
- // unknown color
- console.warn( 'THREE.Color: Unknown color ' + style );
-
- }
-
- return this;
-
- },
-
- clone: function () {
-
- return new this.constructor( this.r, this.g, this.b );
-
- },
-
- copy: function ( color ) {
-
- this.r = color.r;
- this.g = color.g;
- this.b = color.b;
-
- return this;
-
- },
-
- copyGammaToLinear: function ( color, gammaFactor ) {
-
- if ( gammaFactor === undefined ) { gammaFactor = 2.0; }
-
- this.r = Math.pow( color.r, gammaFactor );
- this.g = Math.pow( color.g, gammaFactor );
- this.b = Math.pow( color.b, gammaFactor );
-
- return this;
-
- },
-
- copyLinearToGamma: function ( color, gammaFactor ) {
-
- if ( gammaFactor === undefined ) { gammaFactor = 2.0; }
-
- var safeInverse = ( gammaFactor > 0 ) ? ( 1.0 / gammaFactor ) : 1.0;
-
- this.r = Math.pow( color.r, safeInverse );
- this.g = Math.pow( color.g, safeInverse );
- this.b = Math.pow( color.b, safeInverse );
-
- return this;
-
- },
-
- convertGammaToLinear: function ( gammaFactor ) {
-
- this.copyGammaToLinear( this, gammaFactor );
-
- return this;
-
- },
-
- convertLinearToGamma: function ( gammaFactor ) {
-
- this.copyLinearToGamma( this, gammaFactor );
-
- return this;
-
- },
-
- copySRGBToLinear: function ( color ) {
-
- this.r = SRGBToLinear( color.r );
- this.g = SRGBToLinear( color.g );
- this.b = SRGBToLinear( color.b );
-
- return this;
-
- },
-
- copyLinearToSRGB: function ( color ) {
-
- this.r = LinearToSRGB( color.r );
- this.g = LinearToSRGB( color.g );
- this.b = LinearToSRGB( color.b );
-
- return this;
-
- },
-
- convertSRGBToLinear: function () {
-
- this.copySRGBToLinear( this );
-
- return this;
-
- },
-
- convertLinearToSRGB: function () {
-
- this.copyLinearToSRGB( this );
-
- return this;
-
- },
-
- getHex: function () {
-
- return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0;
-
- },
-
- getHexString: function () {
-
- return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
-
- },
-
- getHSL: function ( target ) {
-
- // h,s,l ranges are in 0.0 - 1.0
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Color: .getHSL() target is now required' );
- target = { h: 0, s: 0, l: 0 };
-
- }
-
- var r = this.r, g = this.g, b = this.b;
-
- var max = Math.max( r, g, b );
- var min = Math.min( r, g, b );
-
- var hue, saturation;
- var lightness = ( min + max ) / 2.0;
-
- if ( min === max ) {
-
- hue = 0;
- saturation = 0;
-
- } else {
-
- var delta = max - min;
-
- saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min );
-
- switch ( max ) {
-
- case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break;
- case g: hue = ( b - r ) / delta + 2; break;
- case b: hue = ( r - g ) / delta + 4; break;
-
- }
-
- hue /= 6;
-
- }
-
- target.h = hue;
- target.s = saturation;
- target.l = lightness;
-
- return target;
-
- },
-
- getStyle: function () {
-
- return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')';
-
- },
-
- offsetHSL: function ( h, s, l ) {
-
- this.getHSL( _hslA );
-
- _hslA.h += h; _hslA.s += s; _hslA.l += l;
-
- this.setHSL( _hslA.h, _hslA.s, _hslA.l );
-
- return this;
-
- },
-
- add: function ( color ) {
-
- this.r += color.r;
- this.g += color.g;
- this.b += color.b;
-
- return this;
-
- },
-
- addColors: function ( color1, color2 ) {
-
- this.r = color1.r + color2.r;
- this.g = color1.g + color2.g;
- this.b = color1.b + color2.b;
-
- return this;
-
- },
-
- addScalar: function ( s ) {
-
- this.r += s;
- this.g += s;
- this.b += s;
-
- return this;
-
- },
-
- sub: function ( color ) {
-
- this.r = Math.max( 0, this.r - color.r );
- this.g = Math.max( 0, this.g - color.g );
- this.b = Math.max( 0, this.b - color.b );
-
- return this;
-
- },
-
- multiply: function ( color ) {
-
- this.r *= color.r;
- this.g *= color.g;
- this.b *= color.b;
-
- return this;
-
- },
-
- multiplyScalar: function ( s ) {
-
- this.r *= s;
- this.g *= s;
- this.b *= s;
-
- return this;
-
- },
-
- lerp: function ( color, alpha ) {
-
- this.r += ( color.r - this.r ) * alpha;
- this.g += ( color.g - this.g ) * alpha;
- this.b += ( color.b - this.b ) * alpha;
-
- return this;
-
- },
-
- lerpHSL: function ( color, alpha ) {
-
- this.getHSL( _hslA );
- color.getHSL( _hslB );
-
- var h = MathUtils.lerp( _hslA.h, _hslB.h, alpha );
- var s = MathUtils.lerp( _hslA.s, _hslB.s, alpha );
- var l = MathUtils.lerp( _hslA.l, _hslB.l, alpha );
-
- this.setHSL( h, s, l );
-
- return this;
-
- },
-
- equals: function ( c ) {
-
- return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b );
-
- },
-
- fromArray: function ( array, offset ) {
-
- if ( offset === undefined ) { offset = 0; }
-
- this.r = array[ offset ];
- this.g = array[ offset + 1 ];
- this.b = array[ offset + 2 ];
-
- return this;
-
- },
-
- toArray: function ( array, offset ) {
-
- if ( array === undefined ) { array = []; }
- if ( offset === undefined ) { offset = 0; }
-
- array[ offset ] = this.r;
- array[ offset + 1 ] = this.g;
- array[ offset + 2 ] = this.b;
-
- return array;
-
- },
-
- toJSON: function () {
-
- return this.getHex();
-
- }
-
- } );
-
- Color.NAMES = _colorKeywords;
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author alteredq / http://alteredqualia.com/
- */
-
- function Face3( a, b, c, normal, color, materialIndex ) {
-
- this.a = a;
- this.b = b;
- this.c = c;
-
- this.normal = ( normal && normal.isVector3 ) ? normal : new Vector3();
- this.vertexNormals = Array.isArray( normal ) ? normal : [];
-
- this.color = ( color && color.isColor ) ? color : new Color();
- this.vertexColors = Array.isArray( color ) ? color : [];
-
- this.materialIndex = materialIndex !== undefined ? materialIndex : 0;
-
- }
-
- Object.assign( Face3.prototype, {
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- },
-
- copy: function ( source ) {
-
- this.a = source.a;
- this.b = source.b;
- this.c = source.c;
-
- this.normal.copy( source.normal );
- this.color.copy( source.color );
-
- this.materialIndex = source.materialIndex;
-
- for ( var i = 0, il = source.vertexNormals.length; i < il; i ++ ) {
-
- this.vertexNormals[ i ] = source.vertexNormals[ i ].clone();
-
- }
-
- for ( var i = 0, il = source.vertexColors.length; i < il; i ++ ) {
-
- this.vertexColors[ i ] = source.vertexColors[ i ].clone();
-
- }
-
- return this;
-
- }
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author alteredq / http://alteredqualia.com/
- */
-
- var materialId = 0;
-
- function Material() {
-
- Object.defineProperty( this, 'id', { value: materialId ++ } );
-
- this.uuid = MathUtils.generateUUID();
-
- this.name = '';
- this.type = 'Material';
-
- this.fog = true;
-
- this.blending = NormalBlending;
- this.side = FrontSide;
- this.flatShading = false;
- this.vertexColors = false;
-
- this.opacity = 1;
- this.transparent = false;
-
- this.blendSrc = SrcAlphaFactor;
- this.blendDst = OneMinusSrcAlphaFactor;
- this.blendEquation = AddEquation;
- this.blendSrcAlpha = null;
- this.blendDstAlpha = null;
- this.blendEquationAlpha = null;
-
- this.depthFunc = LessEqualDepth;
- this.depthTest = true;
- this.depthWrite = true;
-
- this.stencilWriteMask = 0xff;
- this.stencilFunc = AlwaysStencilFunc;
- this.stencilRef = 0;
- this.stencilFuncMask = 0xff;
- this.stencilFail = KeepStencilOp;
- this.stencilZFail = KeepStencilOp;
- this.stencilZPass = KeepStencilOp;
- this.stencilWrite = false;
-
- this.clippingPlanes = null;
- this.clipIntersection = false;
- this.clipShadows = false;
-
- this.shadowSide = null;
-
- this.colorWrite = true;
-
- this.precision = null; // override the renderer's default precision for this material
-
- this.polygonOffset = false;
- this.polygonOffsetFactor = 0;
- this.polygonOffsetUnits = 0;
-
- this.dithering = false;
-
- this.alphaTest = 0;
- this.premultipliedAlpha = false;
-
- this.visible = true;
-
- this.toneMapped = true;
-
- this.userData = {};
-
- this.version = 0;
-
- }
-
- Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
-
- constructor: Material,
-
- isMaterial: true,
-
- onBeforeCompile: function () {},
-
- setValues: function ( values ) {
-
- if ( values === undefined ) { return; }
-
- for ( var key in values ) {
-
- var newValue = values[ key ];
-
- if ( newValue === undefined ) {
-
- console.warn( "THREE.Material: '" + key + "' parameter is undefined." );
- continue;
-
- }
-
- // for backward compatability if shading is set in the constructor
- if ( key === 'shading' ) {
-
- console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );
- this.flatShading = ( newValue === FlatShading ) ? true : false;
- continue;
-
- }
-
- var currentValue = this[ key ];
-
- if ( currentValue === undefined ) {
-
- console.warn( "THREE." + this.type + ": '" + key + "' is not a property of this material." );
- continue;
-
- }
-
- if ( currentValue && currentValue.isColor ) {
-
- currentValue.set( newValue );
-
- } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) {
-
- currentValue.copy( newValue );
-
- } else {
-
- this[ key ] = newValue;
-
- }
-
- }
-
- },
-
- toJSON: function ( meta ) {
-
- var isRoot = ( meta === undefined || typeof meta === 'string' );
-
- if ( isRoot ) {
-
- meta = {
- textures: {},
- images: {}
- };
-
- }
-
- var data = {
- metadata: {
- version: 4.5,
- type: 'Material',
- generator: 'Material.toJSON'
- }
- };
-
- // standard Material serialization
- data.uuid = this.uuid;
- data.type = this.type;
-
- if ( this.name !== '' ) { data.name = this.name; }
-
- if ( this.color && this.color.isColor ) { data.color = this.color.getHex(); }
-
- if ( this.roughness !== undefined ) { data.roughness = this.roughness; }
- if ( this.metalness !== undefined ) { data.metalness = this.metalness; }
-
- if ( this.sheen && this.sheen.isColor ) { data.sheen = this.sheen.getHex(); }
- if ( this.emissive && this.emissive.isColor ) { data.emissive = this.emissive.getHex(); }
- if ( this.emissiveIntensity && this.emissiveIntensity !== 1 ) { data.emissiveIntensity = this.emissiveIntensity; }
-
- if ( this.specular && this.specular.isColor ) { data.specular = this.specular.getHex(); }
- if ( this.shininess !== undefined ) { data.shininess = this.shininess; }
- if ( this.clearcoat !== undefined ) { data.clearcoat = this.clearcoat; }
- if ( this.clearcoatRoughness !== undefined ) { data.clearcoatRoughness = this.clearcoatRoughness; }
-
- if ( this.clearcoatMap && this.clearcoatMap.isTexture ) {
-
- data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid;
-
- }
-
- if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) {
-
- data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid;
-
- }
-
- if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) {
-
- data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid;
- data.clearcoatNormalScale = this.clearcoatNormalScale.toArray();
-
- }
-
- if ( this.map && this.map.isTexture ) { data.map = this.map.toJSON( meta ).uuid; }
- if ( this.matcap && this.matcap.isTexture ) { data.matcap = this.matcap.toJSON( meta ).uuid; }
- if ( this.alphaMap && this.alphaMap.isTexture ) { data.alphaMap = this.alphaMap.toJSON( meta ).uuid; }
- if ( this.lightMap && this.lightMap.isTexture ) { data.lightMap = this.lightMap.toJSON( meta ).uuid; }
-
- if ( this.aoMap && this.aoMap.isTexture ) {
-
- data.aoMap = this.aoMap.toJSON( meta ).uuid;
- data.aoMapIntensity = this.aoMapIntensity;
-
- }
-
- if ( this.bumpMap && this.bumpMap.isTexture ) {
-
- data.bumpMap = this.bumpMap.toJSON( meta ).uuid;
- data.bumpScale = this.bumpScale;
-
- }
-
- if ( this.normalMap && this.normalMap.isTexture ) {
-
- data.normalMap = this.normalMap.toJSON( meta ).uuid;
- data.normalMapType = this.normalMapType;
- data.normalScale = this.normalScale.toArray();
-
- }
-
- if ( this.displacementMap && this.displacementMap.isTexture ) {
-
- data.displacementMap = this.displacementMap.toJSON( meta ).uuid;
- data.displacementScale = this.displacementScale;
- data.displacementBias = this.displacementBias;
-
- }
-
- if ( this.roughnessMap && this.roughnessMap.isTexture ) { data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; }
- if ( this.metalnessMap && this.metalnessMap.isTexture ) { data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; }
-
- if ( this.emissiveMap && this.emissiveMap.isTexture ) { data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; }
- if ( this.specularMap && this.specularMap.isTexture ) { data.specularMap = this.specularMap.toJSON( meta ).uuid; }
-
- if ( this.envMap && this.envMap.isTexture ) {
-
- data.envMap = this.envMap.toJSON( meta ).uuid;
- data.reflectivity = this.reflectivity; // Scale behind envMap
- data.refractionRatio = this.refractionRatio;
-
- if ( this.combine !== undefined ) { data.combine = this.combine; }
- if ( this.envMapIntensity !== undefined ) { data.envMapIntensity = this.envMapIntensity; }
-
- }
-
- if ( this.gradientMap && this.gradientMap.isTexture ) {
-
- data.gradientMap = this.gradientMap.toJSON( meta ).uuid;
-
- }
-
- if ( this.size !== undefined ) { data.size = this.size; }
- if ( this.sizeAttenuation !== undefined ) { data.sizeAttenuation = this.sizeAttenuation; }
-
- if ( this.blending !== NormalBlending ) { data.blending = this.blending; }
- if ( this.flatShading === true ) { data.flatShading = this.flatShading; }
- if ( this.side !== FrontSide ) { data.side = this.side; }
- if ( this.vertexColors ) { data.vertexColors = true; }
-
- if ( this.opacity < 1 ) { data.opacity = this.opacity; }
- if ( this.transparent === true ) { data.transparent = this.transparent; }
-
- data.depthFunc = this.depthFunc;
- data.depthTest = this.depthTest;
- data.depthWrite = this.depthWrite;
-
- data.stencilWrite = this.stencilWrite;
- data.stencilWriteMask = this.stencilWriteMask;
- data.stencilFunc = this.stencilFunc;
- data.stencilRef = this.stencilRef;
- data.stencilFuncMask = this.stencilFuncMask;
- data.stencilFail = this.stencilFail;
- data.stencilZFail = this.stencilZFail;
- data.stencilZPass = this.stencilZPass;
-
- // rotation (SpriteMaterial)
- if ( this.rotation && this.rotation !== 0 ) { data.rotation = this.rotation; }
-
- if ( this.polygonOffset === true ) { data.polygonOffset = true; }
- if ( this.polygonOffsetFactor !== 0 ) { data.polygonOffsetFactor = this.polygonOffsetFactor; }
- if ( this.polygonOffsetUnits !== 0 ) { data.polygonOffsetUnits = this.polygonOffsetUnits; }
-
- if ( this.linewidth && this.linewidth !== 1 ) { data.linewidth = this.linewidth; }
- if ( this.dashSize !== undefined ) { data.dashSize = this.dashSize; }
- if ( this.gapSize !== undefined ) { data.gapSize = this.gapSize; }
- if ( this.scale !== undefined ) { data.scale = this.scale; }
-
- if ( this.dithering === true ) { data.dithering = true; }
-
- if ( this.alphaTest > 0 ) { data.alphaTest = this.alphaTest; }
- if ( this.premultipliedAlpha === true ) { data.premultipliedAlpha = this.premultipliedAlpha; }
-
- if ( this.wireframe === true ) { data.wireframe = this.wireframe; }
- if ( this.wireframeLinewidth > 1 ) { data.wireframeLinewidth = this.wireframeLinewidth; }
- if ( this.wireframeLinecap !== 'round' ) { data.wireframeLinecap = this.wireframeLinecap; }
- if ( this.wireframeLinejoin !== 'round' ) { data.wireframeLinejoin = this.wireframeLinejoin; }
-
- if ( this.morphTargets === true ) { data.morphTargets = true; }
- if ( this.morphNormals === true ) { data.morphNormals = true; }
- if ( this.skinning === true ) { data.skinning = true; }
-
- if ( this.visible === false ) { data.visible = false; }
-
- if ( this.toneMapped === false ) { data.toneMapped = false; }
-
- if ( JSON.stringify( this.userData ) !== '{}' ) { data.userData = this.userData; }
-
- // TODO: Copied from Object3D.toJSON
-
- function extractFromCache( cache ) {
-
- var values = [];
-
- for ( var key in cache ) {
-
- var data = cache[ key ];
- delete data.metadata;
- values.push( data );
-
- }
-
- return values;
-
- }
-
- if ( isRoot ) {
-
- var textures = extractFromCache( meta.textures );
- var images = extractFromCache( meta.images );
-
- if ( textures.length > 0 ) { data.textures = textures; }
- if ( images.length > 0 ) { data.images = images; }
-
- }
-
- return data;
-
- },
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- },
-
- copy: function ( source ) {
-
- this.name = source.name;
-
- this.fog = source.fog;
-
- this.blending = source.blending;
- this.side = source.side;
- this.flatShading = source.flatShading;
- this.vertexColors = source.vertexColors;
-
- this.opacity = source.opacity;
- this.transparent = source.transparent;
-
- this.blendSrc = source.blendSrc;
- this.blendDst = source.blendDst;
- this.blendEquation = source.blendEquation;
- this.blendSrcAlpha = source.blendSrcAlpha;
- this.blendDstAlpha = source.blendDstAlpha;
- this.blendEquationAlpha = source.blendEquationAlpha;
-
- this.depthFunc = source.depthFunc;
- this.depthTest = source.depthTest;
- this.depthWrite = source.depthWrite;
-
- this.stencilWriteMask = source.stencilWriteMask;
- this.stencilFunc = source.stencilFunc;
- this.stencilRef = source.stencilRef;
- this.stencilFuncMask = source.stencilFuncMask;
- this.stencilFail = source.stencilFail;
- this.stencilZFail = source.stencilZFail;
- this.stencilZPass = source.stencilZPass;
- this.stencilWrite = source.stencilWrite;
-
- var srcPlanes = source.clippingPlanes,
- dstPlanes = null;
-
- if ( srcPlanes !== null ) {
-
- var n = srcPlanes.length;
- dstPlanes = new Array( n );
-
- for ( var i = 0; i !== n; ++ i )
- { dstPlanes[ i ] = srcPlanes[ i ].clone(); }
-
- }
-
- this.clippingPlanes = dstPlanes;
- this.clipIntersection = source.clipIntersection;
- this.clipShadows = source.clipShadows;
-
- this.shadowSide = source.shadowSide;
-
- this.colorWrite = source.colorWrite;
-
- this.precision = source.precision;
-
- this.polygonOffset = source.polygonOffset;
- this.polygonOffsetFactor = source.polygonOffsetFactor;
- this.polygonOffsetUnits = source.polygonOffsetUnits;
-
- this.dithering = source.dithering;
-
- this.alphaTest = source.alphaTest;
- this.premultipliedAlpha = source.premultipliedAlpha;
-
- this.visible = source.visible;
-
- this.toneMapped = source.toneMapped;
-
- this.userData = JSON.parse( JSON.stringify( source.userData ) );
-
- return this;
-
- },
-
- dispose: function () {
-
- this.dispatchEvent( { type: 'dispose' } );
-
- }
-
- } );
-
- Object.defineProperty( Material.prototype, 'needsUpdate', {
-
- set: function ( value ) {
-
- if ( value === true ) { this.version ++; }
-
- }
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author alteredq / http://alteredqualia.com/
- *
- * parameters = {
- * color: ,
- * opacity: ,
- * map: new THREE.Texture( ),
- *
- * lightMap: new THREE.Texture( ),
- * lightMapIntensity:
- *
- * aoMap: new THREE.Texture( ),
- * aoMapIntensity:
- *
- * specularMap: new THREE.Texture( ),
- *
- * alphaMap: new THREE.Texture( ),
- *
- * envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
- * combine: THREE.Multiply,
- * reflectivity: ,
- * refractionRatio: ,
- *
- * depthTest: ,
- * depthWrite: ,
- *
- * wireframe: ,
- * wireframeLinewidth: ,
- *
- * skinning: ,
- * morphTargets:
- * }
- */
-
- function MeshBasicMaterial( parameters ) {
-
- Material.call( this );
-
- this.type = 'MeshBasicMaterial';
-
- this.color = new Color( 0xffffff ); // emissive
-
- this.map = null;
-
- this.lightMap = null;
- this.lightMapIntensity = 1.0;
-
- this.aoMap = null;
- this.aoMapIntensity = 1.0;
-
- this.specularMap = null;
-
- this.alphaMap = null;
-
- this.envMap = null;
- this.combine = MultiplyOperation;
- this.reflectivity = 1;
- this.refractionRatio = 0.98;
-
- this.wireframe = false;
- this.wireframeLinewidth = 1;
- this.wireframeLinecap = 'round';
- this.wireframeLinejoin = 'round';
-
- this.skinning = false;
- this.morphTargets = false;
-
- this.setValues( parameters );
-
- }
-
- MeshBasicMaterial.prototype = Object.create( Material.prototype );
- MeshBasicMaterial.prototype.constructor = MeshBasicMaterial;
-
- MeshBasicMaterial.prototype.isMeshBasicMaterial = true;
-
- MeshBasicMaterial.prototype.copy = function ( source ) {
-
- Material.prototype.copy.call( this, source );
-
- this.color.copy( source.color );
-
- this.map = source.map;
-
- this.lightMap = source.lightMap;
- this.lightMapIntensity = source.lightMapIntensity;
-
- this.aoMap = source.aoMap;
- this.aoMapIntensity = source.aoMapIntensity;
-
- this.specularMap = source.specularMap;
-
- this.alphaMap = source.alphaMap;
-
- this.envMap = source.envMap;
- this.combine = source.combine;
- this.reflectivity = source.reflectivity;
- this.refractionRatio = source.refractionRatio;
-
- this.wireframe = source.wireframe;
- this.wireframeLinewidth = source.wireframeLinewidth;
- this.wireframeLinecap = source.wireframeLinecap;
- this.wireframeLinejoin = source.wireframeLinejoin;
-
- this.skinning = source.skinning;
- this.morphTargets = source.morphTargets;
-
- return this;
-
- };
-
- /**
- * @author mrdoob / http://mrdoob.com/
- */
-
- var _vector$3 = new Vector3();
-
- function BufferAttribute( array, itemSize, normalized ) {
-
- if ( Array.isArray( array ) ) {
-
- throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );
-
- }
-
- this.name = '';
-
- this.array = array;
- this.itemSize = itemSize;
- this.count = array !== undefined ? array.length / itemSize : 0;
- this.normalized = normalized === true;
-
- this.usage = StaticDrawUsage;
- this.updateRange = { offset: 0, count: - 1 };
-
- this.version = 0;
-
- }
-
- Object.defineProperty( BufferAttribute.prototype, 'needsUpdate', {
-
- set: function ( value ) {
-
- if ( value === true ) { this.version ++; }
-
- }
-
- } );
-
- Object.assign( BufferAttribute.prototype, {
-
- isBufferAttribute: true,
-
- onUploadCallback: function () {},
-
- setUsage: function ( value ) {
-
- this.usage = value;
-
- return this;
-
- },
-
- copy: function ( source ) {
-
- this.name = source.name;
- this.array = new source.array.constructor( source.array );
- this.itemSize = source.itemSize;
- this.count = source.count;
- this.normalized = source.normalized;
-
- this.usage = source.usage;
-
- return this;
-
- },
-
- copyAt: function ( index1, attribute, index2 ) {
-
- index1 *= this.itemSize;
- index2 *= attribute.itemSize;
-
- for ( var i = 0, l = this.itemSize; i < l; i ++ ) {
-
- this.array[ index1 + i ] = attribute.array[ index2 + i ];
-
- }
-
- return this;
-
- },
-
- copyArray: function ( array ) {
-
- this.array.set( array );
-
- return this;
-
- },
-
- copyColorsArray: function ( colors ) {
-
- var array = this.array, offset = 0;
-
- for ( var i = 0, l = colors.length; i < l; i ++ ) {
-
- var color = colors[ i ];
-
- if ( color === undefined ) {
-
- console.warn( 'THREE.BufferAttribute.copyColorsArray(): color is undefined', i );
- color = new Color();
-
- }
-
- array[ offset ++ ] = color.r;
- array[ offset ++ ] = color.g;
- array[ offset ++ ] = color.b;
-
- }
-
- return this;
-
- },
-
- copyVector2sArray: function ( vectors ) {
-
- var array = this.array, offset = 0;
-
- for ( var i = 0, l = vectors.length; i < l; i ++ ) {
-
- var vector = vectors[ i ];
-
- if ( vector === undefined ) {
-
- console.warn( 'THREE.BufferAttribute.copyVector2sArray(): vector is undefined', i );
- vector = new Vector2();
-
- }
-
- array[ offset ++ ] = vector.x;
- array[ offset ++ ] = vector.y;
-
- }
-
- return this;
-
- },
-
- copyVector3sArray: function ( vectors ) {
-
- var array = this.array, offset = 0;
-
- for ( var i = 0, l = vectors.length; i < l; i ++ ) {
-
- var vector = vectors[ i ];
-
- if ( vector === undefined ) {
-
- console.warn( 'THREE.BufferAttribute.copyVector3sArray(): vector is undefined', i );
- vector = new Vector3();
-
- }
-
- array[ offset ++ ] = vector.x;
- array[ offset ++ ] = vector.y;
- array[ offset ++ ] = vector.z;
-
- }
-
- return this;
-
- },
-
- copyVector4sArray: function ( vectors ) {
-
- var array = this.array, offset = 0;
-
- for ( var i = 0, l = vectors.length; i < l; i ++ ) {
-
- var vector = vectors[ i ];
-
- if ( vector === undefined ) {
-
- console.warn( 'THREE.BufferAttribute.copyVector4sArray(): vector is undefined', i );
- vector = new Vector4();
-
- }
-
- array[ offset ++ ] = vector.x;
- array[ offset ++ ] = vector.y;
- array[ offset ++ ] = vector.z;
- array[ offset ++ ] = vector.w;
-
- }
-
- return this;
-
- },
-
- applyMatrix3: function ( m ) {
-
- for ( var i = 0, l = this.count; i < l; i ++ ) {
-
- _vector$3.x = this.getX( i );
- _vector$3.y = this.getY( i );
- _vector$3.z = this.getZ( i );
-
- _vector$3.applyMatrix3( m );
-
- this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
-
- }
-
- return this;
-
- },
-
- applyMatrix4: function ( m ) {
-
- for ( var i = 0, l = this.count; i < l; i ++ ) {
-
- _vector$3.x = this.getX( i );
- _vector$3.y = this.getY( i );
- _vector$3.z = this.getZ( i );
-
- _vector$3.applyMatrix4( m );
-
- this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
-
- }
-
- return this;
-
- },
-
- applyNormalMatrix: function ( m ) {
-
- for ( var i = 0, l = this.count; i < l; i ++ ) {
-
- _vector$3.x = this.getX( i );
- _vector$3.y = this.getY( i );
- _vector$3.z = this.getZ( i );
-
- _vector$3.applyNormalMatrix( m );
-
- this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
-
- }
-
- return this;
-
- },
-
- transformDirection: function ( m ) {
-
- for ( var i = 0, l = this.count; i < l; i ++ ) {
-
- _vector$3.x = this.getX( i );
- _vector$3.y = this.getY( i );
- _vector$3.z = this.getZ( i );
-
- _vector$3.transformDirection( m );
-
- this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
-
- }
-
- return this;
-
- },
-
- set: function ( value, offset ) {
-
- if ( offset === undefined ) { offset = 0; }
-
- this.array.set( value, offset );
-
- return this;
-
- },
-
- getX: function ( index ) {
-
- return this.array[ index * this.itemSize ];
-
- },
-
- setX: function ( index, x ) {
-
- this.array[ index * this.itemSize ] = x;
-
- return this;
-
- },
-
- getY: function ( index ) {
-
- return this.array[ index * this.itemSize + 1 ];
-
- },
-
- setY: function ( index, y ) {
-
- this.array[ index * this.itemSize + 1 ] = y;
-
- return this;
-
- },
-
- getZ: function ( index ) {
-
- return this.array[ index * this.itemSize + 2 ];
-
- },
-
- setZ: function ( index, z ) {
-
- this.array[ index * this.itemSize + 2 ] = z;
-
- return this;
-
- },
-
- getW: function ( index ) {
-
- return this.array[ index * this.itemSize + 3 ];
-
- },
-
- setW: function ( index, w ) {
-
- this.array[ index * this.itemSize + 3 ] = w;
-
- return this;
-
- },
-
- setXY: function ( index, x, y ) {
-
- index *= this.itemSize;
-
- this.array[ index + 0 ] = x;
- this.array[ index + 1 ] = y;
-
- return this;
-
- },
-
- setXYZ: function ( index, x, y, z ) {
-
- index *= this.itemSize;
-
- this.array[ index + 0 ] = x;
- this.array[ index + 1 ] = y;
- this.array[ index + 2 ] = z;
-
- return this;
-
- },
-
- setXYZW: function ( index, x, y, z, w ) {
-
- index *= this.itemSize;
-
- this.array[ index + 0 ] = x;
- this.array[ index + 1 ] = y;
- this.array[ index + 2 ] = z;
- this.array[ index + 3 ] = w;
-
- return this;
-
- },
-
- onUpload: function ( callback ) {
-
- this.onUploadCallback = callback;
-
- return this;
-
- },
-
- clone: function () {
-
- return new this.constructor( this.array, this.itemSize ).copy( this );
-
- },
-
- toJSON: function () {
-
- return {
- itemSize: this.itemSize,
- type: this.array.constructor.name,
- array: Array.prototype.slice.call( this.array ),
- normalized: this.normalized
- };
-
- }
-
- } );
-
- //
-
- function Int8BufferAttribute( array, itemSize, normalized ) {
-
- BufferAttribute.call( this, new Int8Array( array ), itemSize, normalized );
-
- }
-
- Int8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
- Int8BufferAttribute.prototype.constructor = Int8BufferAttribute;
-
-
- function Uint8BufferAttribute( array, itemSize, normalized ) {
-
- BufferAttribute.call( this, new Uint8Array( array ), itemSize, normalized );
-
- }
-
- Uint8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
- Uint8BufferAttribute.prototype.constructor = Uint8BufferAttribute;
-
-
- function Uint8ClampedBufferAttribute( array, itemSize, normalized ) {
-
- BufferAttribute.call( this, new Uint8ClampedArray( array ), itemSize, normalized );
-
- }
-
- Uint8ClampedBufferAttribute.prototype = Object.create( BufferAttribute.prototype );
- Uint8ClampedBufferAttribute.prototype.constructor = Uint8ClampedBufferAttribute;
-
-
- function Int16BufferAttribute( array, itemSize, normalized ) {
-
- BufferAttribute.call( this, new Int16Array( array ), itemSize, normalized );
-
- }
-
- Int16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
- Int16BufferAttribute.prototype.constructor = Int16BufferAttribute;
-
-
- function Uint16BufferAttribute( array, itemSize, normalized ) {
-
- BufferAttribute.call( this, new Uint16Array( array ), itemSize, normalized );
-
- }
-
- Uint16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
- Uint16BufferAttribute.prototype.constructor = Uint16BufferAttribute;
-
-
- function Int32BufferAttribute( array, itemSize, normalized ) {
-
- BufferAttribute.call( this, new Int32Array( array ), itemSize, normalized );
-
- }
-
- Int32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
- Int32BufferAttribute.prototype.constructor = Int32BufferAttribute;
-
-
- function Uint32BufferAttribute( array, itemSize, normalized ) {
-
- BufferAttribute.call( this, new Uint32Array( array ), itemSize, normalized );
-
- }
-
- Uint32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
- Uint32BufferAttribute.prototype.constructor = Uint32BufferAttribute;
-
-
- function Float32BufferAttribute( array, itemSize, normalized ) {
-
- BufferAttribute.call( this, new Float32Array( array ), itemSize, normalized );
-
- }
-
- Float32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
- Float32BufferAttribute.prototype.constructor = Float32BufferAttribute;
-
-
- function Float64BufferAttribute( array, itemSize, normalized ) {
-
- BufferAttribute.call( this, new Float64Array( array ), itemSize, normalized );
-
- }
-
- Float64BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
- Float64BufferAttribute.prototype.constructor = Float64BufferAttribute;
-
- /**
- * @author mrdoob / http://mrdoob.com/
- */
-
- function DirectGeometry() {
-
- this.vertices = [];
- this.normals = [];
- this.colors = [];
- this.uvs = [];
- this.uvs2 = [];
-
- this.groups = [];
-
- this.morphTargets = {};
-
- this.skinWeights = [];
- this.skinIndices = [];
-
- // this.lineDistances = [];
-
- this.boundingBox = null;
- this.boundingSphere = null;
-
- // update flags
-
- this.verticesNeedUpdate = false;
- this.normalsNeedUpdate = false;
- this.colorsNeedUpdate = false;
- this.uvsNeedUpdate = false;
- this.groupsNeedUpdate = false;
-
- }
-
- Object.assign( DirectGeometry.prototype, {
-
- computeGroups: function ( geometry ) {
-
- var group;
- var groups = [];
- var materialIndex = undefined;
-
- var faces = geometry.faces;
-
- for ( var i = 0; i < faces.length; i ++ ) {
-
- var face = faces[ i ];
-
- // materials
-
- if ( face.materialIndex !== materialIndex ) {
-
- materialIndex = face.materialIndex;
-
- if ( group !== undefined ) {
-
- group.count = ( i * 3 ) - group.start;
- groups.push( group );
-
- }
-
- group = {
- start: i * 3,
- materialIndex: materialIndex
- };
-
- }
-
- }
-
- if ( group !== undefined ) {
-
- group.count = ( i * 3 ) - group.start;
- groups.push( group );
-
- }
-
- this.groups = groups;
-
- },
-
- fromGeometry: function ( geometry ) {
-
- var faces = geometry.faces;
- var vertices = geometry.vertices;
- var faceVertexUvs = geometry.faceVertexUvs;
-
- var hasFaceVertexUv = faceVertexUvs[ 0 ] && faceVertexUvs[ 0 ].length > 0;
- var hasFaceVertexUv2 = faceVertexUvs[ 1 ] && faceVertexUvs[ 1 ].length > 0;
-
- // morphs
-
- var morphTargets = geometry.morphTargets;
- var morphTargetsLength = morphTargets.length;
-
- var morphTargetsPosition;
-
- if ( morphTargetsLength > 0 ) {
-
- morphTargetsPosition = [];
-
- for ( var i = 0; i < morphTargetsLength; i ++ ) {
-
- morphTargetsPosition[ i ] = {
- name: morphTargets[ i ].name,
- data: []
- };
-
- }
-
- this.morphTargets.position = morphTargetsPosition;
-
- }
-
- var morphNormals = geometry.morphNormals;
- var morphNormalsLength = morphNormals.length;
-
- var morphTargetsNormal;
-
- if ( morphNormalsLength > 0 ) {
-
- morphTargetsNormal = [];
-
- for ( var i = 0; i < morphNormalsLength; i ++ ) {
-
- morphTargetsNormal[ i ] = {
- name: morphNormals[ i ].name,
- data: []
- };
-
- }
-
- this.morphTargets.normal = morphTargetsNormal;
-
- }
-
- // skins
-
- var skinIndices = geometry.skinIndices;
- var skinWeights = geometry.skinWeights;
-
- var hasSkinIndices = skinIndices.length === vertices.length;
- var hasSkinWeights = skinWeights.length === vertices.length;
-
- //
-
- if ( vertices.length > 0 && faces.length === 0 ) {
-
- console.error( 'THREE.DirectGeometry: Faceless geometries are not supported.' );
-
- }
-
- for ( var i = 0; i < faces.length; i ++ ) {
-
- var face = faces[ i ];
-
- this.vertices.push( vertices[ face.a ], vertices[ face.b ], vertices[ face.c ] );
-
- var vertexNormals = face.vertexNormals;
-
- if ( vertexNormals.length === 3 ) {
-
- this.normals.push( vertexNormals[ 0 ], vertexNormals[ 1 ], vertexNormals[ 2 ] );
-
- } else {
-
- var normal = face.normal;
-
- this.normals.push( normal, normal, normal );
-
- }
-
- var vertexColors = face.vertexColors;
-
- if ( vertexColors.length === 3 ) {
-
- this.colors.push( vertexColors[ 0 ], vertexColors[ 1 ], vertexColors[ 2 ] );
-
- } else {
-
- var color = face.color;
-
- this.colors.push( color, color, color );
-
- }
-
- if ( hasFaceVertexUv === true ) {
-
- var vertexUvs = faceVertexUvs[ 0 ][ i ];
-
- if ( vertexUvs !== undefined ) {
-
- this.uvs.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] );
-
- } else {
-
- console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv ', i );
-
- this.uvs.push( new Vector2(), new Vector2(), new Vector2() );
-
- }
-
- }
-
- if ( hasFaceVertexUv2 === true ) {
-
- var vertexUvs = faceVertexUvs[ 1 ][ i ];
-
- if ( vertexUvs !== undefined ) {
-
- this.uvs2.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] );
-
- } else {
-
- console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv2 ', i );
-
- this.uvs2.push( new Vector2(), new Vector2(), new Vector2() );
-
- }
-
- }
-
- // morphs
-
- for ( var j = 0; j < morphTargetsLength; j ++ ) {
-
- var morphTarget = morphTargets[ j ].vertices;
-
- morphTargetsPosition[ j ].data.push( morphTarget[ face.a ], morphTarget[ face.b ], morphTarget[ face.c ] );
-
- }
-
- for ( var j = 0; j < morphNormalsLength; j ++ ) {
-
- var morphNormal = morphNormals[ j ].vertexNormals[ i ];
-
- morphTargetsNormal[ j ].data.push( morphNormal.a, morphNormal.b, morphNormal.c );
-
- }
-
- // skins
-
- if ( hasSkinIndices ) {
-
- this.skinIndices.push( skinIndices[ face.a ], skinIndices[ face.b ], skinIndices[ face.c ] );
-
- }
-
- if ( hasSkinWeights ) {
-
- this.skinWeights.push( skinWeights[ face.a ], skinWeights[ face.b ], skinWeights[ face.c ] );
-
- }
-
- }
-
- this.computeGroups( geometry );
-
- this.verticesNeedUpdate = geometry.verticesNeedUpdate;
- this.normalsNeedUpdate = geometry.normalsNeedUpdate;
- this.colorsNeedUpdate = geometry.colorsNeedUpdate;
- this.uvsNeedUpdate = geometry.uvsNeedUpdate;
- this.groupsNeedUpdate = geometry.groupsNeedUpdate;
-
- if ( geometry.boundingSphere !== null ) {
-
- this.boundingSphere = geometry.boundingSphere.clone();
-
- }
-
- if ( geometry.boundingBox !== null ) {
-
- this.boundingBox = geometry.boundingBox.clone();
-
- }
-
- return this;
-
- }
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- */
-
- function arrayMax( array ) {
-
- if ( array.length === 0 ) { return - Infinity; }
-
- var max = array[ 0 ];
-
- for ( var i = 1, l = array.length; i < l; ++ i ) {
-
- if ( array[ i ] > max ) { max = array[ i ]; }
-
- }
-
- return max;
-
- }
-
- /**
- * @author alteredq / http://alteredqualia.com/
- * @author mrdoob / http://mrdoob.com/
- */
-
- var _bufferGeometryId = 1; // BufferGeometry uses odd numbers as Id
-
- var _m1$2 = new Matrix4();
- var _obj = new Object3D();
- var _offset = new Vector3();
- var _box$2 = new Box3();
- var _boxMorphTargets = new Box3();
- var _vector$4 = new Vector3();
-
- function BufferGeometry() {
-
- Object.defineProperty( this, 'id', { value: _bufferGeometryId += 2 } );
-
- this.uuid = MathUtils.generateUUID();
-
- this.name = '';
- this.type = 'BufferGeometry';
-
- this.index = null;
- this.attributes = {};
-
- this.morphAttributes = {};
- this.morphTargetsRelative = false;
-
- this.groups = [];
-
- this.boundingBox = null;
- this.boundingSphere = null;
-
- this.drawRange = { start: 0, count: Infinity };
-
- this.userData = {};
-
- }
-
- BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
-
- constructor: BufferGeometry,
-
- isBufferGeometry: true,
-
- getIndex: function () {
-
- return this.index;
-
- },
-
- setIndex: function ( index ) {
-
- if ( Array.isArray( index ) ) {
-
- this.index = new ( arrayMax( index ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 );
-
- } else {
-
- this.index = index;
-
- }
-
- },
-
- getAttribute: function ( name ) {
-
- return this.attributes[ name ];
-
- },
-
- setAttribute: function ( name, attribute ) {
-
- this.attributes[ name ] = attribute;
-
- return this;
-
- },
-
- deleteAttribute: function ( name ) {
-
- delete this.attributes[ name ];
-
- return this;
-
- },
-
- addGroup: function ( start, count, materialIndex ) {
-
- this.groups.push( {
-
- start: start,
- count: count,
- materialIndex: materialIndex !== undefined ? materialIndex : 0
-
- } );
-
- },
-
- clearGroups: function () {
-
- this.groups = [];
-
- },
-
- setDrawRange: function ( start, count ) {
-
- this.drawRange.start = start;
- this.drawRange.count = count;
-
- },
-
- applyMatrix4: function ( matrix ) {
-
- var position = this.attributes.position;
-
- if ( position !== undefined ) {
-
- position.applyMatrix4( matrix );
-
- position.needsUpdate = true;
-
- }
-
- var normal = this.attributes.normal;
-
- if ( normal !== undefined ) {
-
- var normalMatrix = new Matrix3().getNormalMatrix( matrix );
-
- normal.applyNormalMatrix( normalMatrix );
-
- normal.needsUpdate = true;
-
- }
-
- var tangent = this.attributes.tangent;
-
- if ( tangent !== undefined ) {
-
- tangent.transformDirection( matrix );
-
- tangent.needsUpdate = true;
-
- }
-
- if ( this.boundingBox !== null ) {
-
- this.computeBoundingBox();
-
- }
-
- if ( this.boundingSphere !== null ) {
-
- this.computeBoundingSphere();
-
- }
-
- return this;
-
- },
-
- rotateX: function ( angle ) {
-
- // rotate geometry around world x-axis
-
- _m1$2.makeRotationX( angle );
-
- this.applyMatrix4( _m1$2 );
-
- return this;
-
- },
-
- rotateY: function ( angle ) {
-
- // rotate geometry around world y-axis
-
- _m1$2.makeRotationY( angle );
-
- this.applyMatrix4( _m1$2 );
-
- return this;
-
- },
-
- rotateZ: function ( angle ) {
-
- // rotate geometry around world z-axis
-
- _m1$2.makeRotationZ( angle );
-
- this.applyMatrix4( _m1$2 );
-
- return this;
-
- },
-
- translate: function ( x, y, z ) {
-
- // translate geometry
-
- _m1$2.makeTranslation( x, y, z );
-
- this.applyMatrix4( _m1$2 );
-
- return this;
-
- },
-
- scale: function ( x, y, z ) {
-
- // scale geometry
-
- _m1$2.makeScale( x, y, z );
-
- this.applyMatrix4( _m1$2 );
-
- return this;
-
- },
-
- lookAt: function ( vector ) {
-
- _obj.lookAt( vector );
-
- _obj.updateMatrix();
-
- this.applyMatrix4( _obj.matrix );
-
- return this;
-
- },
-
- center: function () {
-
- this.computeBoundingBox();
-
- this.boundingBox.getCenter( _offset ).negate();
-
- this.translate( _offset.x, _offset.y, _offset.z );
-
- return this;
-
- },
-
- setFromObject: function ( object ) {
-
- // console.log( 'THREE.BufferGeometry.setFromObject(). Converting', object, this );
-
- var geometry = object.geometry;
-
- if ( object.isPoints || object.isLine ) {
-
- var positions = new Float32BufferAttribute( geometry.vertices.length * 3, 3 );
- var colors = new Float32BufferAttribute( geometry.colors.length * 3, 3 );
-
- this.setAttribute( 'position', positions.copyVector3sArray( geometry.vertices ) );
- this.setAttribute( 'color', colors.copyColorsArray( geometry.colors ) );
-
- if ( geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length ) {
-
- var lineDistances = new Float32BufferAttribute( geometry.lineDistances.length, 1 );
-
- this.setAttribute( 'lineDistance', lineDistances.copyArray( geometry.lineDistances ) );
-
- }
-
- if ( geometry.boundingSphere !== null ) {
-
- this.boundingSphere = geometry.boundingSphere.clone();
-
- }
-
- if ( geometry.boundingBox !== null ) {
-
- this.boundingBox = geometry.boundingBox.clone();
-
- }
-
- } else if ( object.isMesh ) {
-
- if ( geometry && geometry.isGeometry ) {
-
- this.fromGeometry( geometry );
-
- }
-
- }
-
- return this;
-
- },
-
- setFromPoints: function ( points ) {
-
- var position = [];
-
- for ( var i = 0, l = points.length; i < l; i ++ ) {
-
- var point = points[ i ];
- position.push( point.x, point.y, point.z || 0 );
-
- }
-
- this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) );
-
- return this;
-
- },
-
- updateFromObject: function ( object ) {
-
- var geometry = object.geometry;
-
- if ( object.isMesh ) {
-
- var direct = geometry.__directGeometry;
-
- if ( geometry.elementsNeedUpdate === true ) {
-
- direct = undefined;
- geometry.elementsNeedUpdate = false;
-
- }
-
- if ( direct === undefined ) {
-
- return this.fromGeometry( geometry );
-
- }
-
- direct.verticesNeedUpdate = geometry.verticesNeedUpdate;
- direct.normalsNeedUpdate = geometry.normalsNeedUpdate;
- direct.colorsNeedUpdate = geometry.colorsNeedUpdate;
- direct.uvsNeedUpdate = geometry.uvsNeedUpdate;
- direct.groupsNeedUpdate = geometry.groupsNeedUpdate;
-
- geometry.verticesNeedUpdate = false;
- geometry.normalsNeedUpdate = false;
- geometry.colorsNeedUpdate = false;
- geometry.uvsNeedUpdate = false;
- geometry.groupsNeedUpdate = false;
-
- geometry = direct;
-
- }
-
- var attribute;
-
- if ( geometry.verticesNeedUpdate === true ) {
-
- attribute = this.attributes.position;
-
- if ( attribute !== undefined ) {
-
- attribute.copyVector3sArray( geometry.vertices );
- attribute.needsUpdate = true;
-
- }
-
- geometry.verticesNeedUpdate = false;
-
- }
-
- if ( geometry.normalsNeedUpdate === true ) {
-
- attribute = this.attributes.normal;
-
- if ( attribute !== undefined ) {
-
- attribute.copyVector3sArray( geometry.normals );
- attribute.needsUpdate = true;
-
- }
-
- geometry.normalsNeedUpdate = false;
-
- }
-
- if ( geometry.colorsNeedUpdate === true ) {
-
- attribute = this.attributes.color;
-
- if ( attribute !== undefined ) {
-
- attribute.copyColorsArray( geometry.colors );
- attribute.needsUpdate = true;
-
- }
-
- geometry.colorsNeedUpdate = false;
-
- }
-
- if ( geometry.uvsNeedUpdate ) {
-
- attribute = this.attributes.uv;
-
- if ( attribute !== undefined ) {
-
- attribute.copyVector2sArray( geometry.uvs );
- attribute.needsUpdate = true;
-
- }
-
- geometry.uvsNeedUpdate = false;
-
- }
-
- if ( geometry.lineDistancesNeedUpdate ) {
-
- attribute = this.attributes.lineDistance;
-
- if ( attribute !== undefined ) {
-
- attribute.copyArray( geometry.lineDistances );
- attribute.needsUpdate = true;
-
- }
-
- geometry.lineDistancesNeedUpdate = false;
-
- }
-
- if ( geometry.groupsNeedUpdate ) {
-
- geometry.computeGroups( object.geometry );
- this.groups = geometry.groups;
-
- geometry.groupsNeedUpdate = false;
-
- }
-
- return this;
-
- },
-
- fromGeometry: function ( geometry ) {
-
- geometry.__directGeometry = new DirectGeometry().fromGeometry( geometry );
-
- return this.fromDirectGeometry( geometry.__directGeometry );
-
- },
-
- fromDirectGeometry: function ( geometry ) {
-
- var positions = new Float32Array( geometry.vertices.length * 3 );
- this.setAttribute( 'position', new BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices ) );
-
- if ( geometry.normals.length > 0 ) {
-
- var normals = new Float32Array( geometry.normals.length * 3 );
- this.setAttribute( 'normal', new BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals ) );
-
- }
-
- if ( geometry.colors.length > 0 ) {
-
- var colors = new Float32Array( geometry.colors.length * 3 );
- this.setAttribute( 'color', new BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors ) );
-
- }
-
- if ( geometry.uvs.length > 0 ) {
-
- var uvs = new Float32Array( geometry.uvs.length * 2 );
- this.setAttribute( 'uv', new BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs ) );
-
- }
-
- if ( geometry.uvs2.length > 0 ) {
-
- var uvs2 = new Float32Array( geometry.uvs2.length * 2 );
- this.setAttribute( 'uv2', new BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 ) );
-
- }
-
- // groups
-
- this.groups = geometry.groups;
-
- // morphs
-
- for ( var name in geometry.morphTargets ) {
-
- var array = [];
- var morphTargets = geometry.morphTargets[ name ];
-
- for ( var i = 0, l = morphTargets.length; i < l; i ++ ) {
-
- var morphTarget = morphTargets[ i ];
-
- var attribute = new Float32BufferAttribute( morphTarget.data.length * 3, 3 );
- attribute.name = morphTarget.name;
-
- array.push( attribute.copyVector3sArray( morphTarget.data ) );
-
- }
-
- this.morphAttributes[ name ] = array;
-
- }
-
- // skinning
-
- if ( geometry.skinIndices.length > 0 ) {
-
- var skinIndices = new Float32BufferAttribute( geometry.skinIndices.length * 4, 4 );
- this.setAttribute( 'skinIndex', skinIndices.copyVector4sArray( geometry.skinIndices ) );
-
- }
-
- if ( geometry.skinWeights.length > 0 ) {
-
- var skinWeights = new Float32BufferAttribute( geometry.skinWeights.length * 4, 4 );
- this.setAttribute( 'skinWeight', skinWeights.copyVector4sArray( geometry.skinWeights ) );
-
- }
-
- //
-
- if ( geometry.boundingSphere !== null ) {
-
- this.boundingSphere = geometry.boundingSphere.clone();
-
- }
-
- if ( geometry.boundingBox !== null ) {
-
- this.boundingBox = geometry.boundingBox.clone();
-
- }
-
- return this;
-
- },
-
- computeBoundingBox: function () {
-
- if ( this.boundingBox === null ) {
-
- this.boundingBox = new Box3();
-
- }
-
- var position = this.attributes.position;
- var morphAttributesPosition = this.morphAttributes.position;
-
- if ( position !== undefined ) {
-
- this.boundingBox.setFromBufferAttribute( position );
-
- // process morph attributes if present
-
- if ( morphAttributesPosition ) {
-
- for ( var i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
-
- var morphAttribute = morphAttributesPosition[ i ];
- _box$2.setFromBufferAttribute( morphAttribute );
-
- if ( this.morphTargetsRelative ) {
-
- _vector$4.addVectors( this.boundingBox.min, _box$2.min );
- this.boundingBox.expandByPoint( _vector$4 );
-
- _vector$4.addVectors( this.boundingBox.max, _box$2.max );
- this.boundingBox.expandByPoint( _vector$4 );
-
- } else {
-
- this.boundingBox.expandByPoint( _box$2.min );
- this.boundingBox.expandByPoint( _box$2.max );
-
- }
-
- }
-
- }
-
- } else {
-
- this.boundingBox.makeEmpty();
-
- }
-
- if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) {
-
- console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this );
-
- }
-
- },
-
- computeBoundingSphere: function () {
-
- if ( this.boundingSphere === null ) {
-
- this.boundingSphere = new Sphere();
-
- }
-
- var position = this.attributes.position;
- var morphAttributesPosition = this.morphAttributes.position;
-
- if ( position ) {
-
- // first, find the center of the bounding sphere
-
- var center = this.boundingSphere.center;
-
- _box$2.setFromBufferAttribute( position );
-
- // process morph attributes if present
-
- if ( morphAttributesPosition ) {
-
- for ( var i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
-
- var morphAttribute = morphAttributesPosition[ i ];
- _boxMorphTargets.setFromBufferAttribute( morphAttribute );
-
- if ( this.morphTargetsRelative ) {
-
- _vector$4.addVectors( _box$2.min, _boxMorphTargets.min );
- _box$2.expandByPoint( _vector$4 );
-
- _vector$4.addVectors( _box$2.max, _boxMorphTargets.max );
- _box$2.expandByPoint( _vector$4 );
-
- } else {
-
- _box$2.expandByPoint( _boxMorphTargets.min );
- _box$2.expandByPoint( _boxMorphTargets.max );
-
- }
-
- }
-
- }
-
- _box$2.getCenter( center );
-
- // second, try to find a boundingSphere with a radius smaller than the
- // boundingSphere of the boundingBox: sqrt(3) smaller in the best case
-
- var maxRadiusSq = 0;
-
- for ( var i = 0, il = position.count; i < il; i ++ ) {
-
- _vector$4.fromBufferAttribute( position, i );
-
- maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) );
-
- }
-
- // process morph attributes if present
-
- if ( morphAttributesPosition ) {
-
- for ( var i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
-
- var morphAttribute = morphAttributesPosition[ i ];
- var morphTargetsRelative = this.morphTargetsRelative;
-
- for ( var j = 0, jl = morphAttribute.count; j < jl; j ++ ) {
-
- _vector$4.fromBufferAttribute( morphAttribute, j );
-
- if ( morphTargetsRelative ) {
-
- _offset.fromBufferAttribute( position, j );
- _vector$4.add( _offset );
-
- }
-
- maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) );
-
- }
-
- }
-
- }
-
- this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
-
- if ( isNaN( this.boundingSphere.radius ) ) {
-
- console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this );
-
- }
-
- }
-
- },
-
- computeFaceNormals: function () {
-
- // backwards compatibility
-
- },
-
- computeVertexNormals: function () {
-
- var index = this.index;
- var attributes = this.attributes;
-
- if ( attributes.position ) {
-
- var positions = attributes.position.array;
-
- if ( attributes.normal === undefined ) {
-
- this.setAttribute( 'normal', new BufferAttribute( new Float32Array( positions.length ), 3 ) );
-
- } else {
-
- // reset existing normals to zero
-
- var array = attributes.normal.array;
-
- for ( var i = 0, il = array.length; i < il; i ++ ) {
-
- array[ i ] = 0;
-
- }
-
- }
-
- var normals = attributes.normal.array;
-
- var vA, vB, vC;
- var pA = new Vector3(), pB = new Vector3(), pC = new Vector3();
- var cb = new Vector3(), ab = new Vector3();
-
- // indexed elements
-
- if ( index ) {
-
- var indices = index.array;
-
- for ( var i = 0, il = index.count; i < il; i += 3 ) {
-
- vA = indices[ i + 0 ] * 3;
- vB = indices[ i + 1 ] * 3;
- vC = indices[ i + 2 ] * 3;
-
- pA.fromArray( positions, vA );
- pB.fromArray( positions, vB );
- pC.fromArray( positions, vC );
-
- cb.subVectors( pC, pB );
- ab.subVectors( pA, pB );
- cb.cross( ab );
-
- normals[ vA ] += cb.x;
- normals[ vA + 1 ] += cb.y;
- normals[ vA + 2 ] += cb.z;
-
- normals[ vB ] += cb.x;
- normals[ vB + 1 ] += cb.y;
- normals[ vB + 2 ] += cb.z;
-
- normals[ vC ] += cb.x;
- normals[ vC + 1 ] += cb.y;
- normals[ vC + 2 ] += cb.z;
-
- }
-
- } else {
-
- // non-indexed elements (unconnected triangle soup)
-
- for ( var i = 0, il = positions.length; i < il; i += 9 ) {
-
- pA.fromArray( positions, i );
- pB.fromArray( positions, i + 3 );
- pC.fromArray( positions, i + 6 );
-
- cb.subVectors( pC, pB );
- ab.subVectors( pA, pB );
- cb.cross( ab );
-
- normals[ i ] = cb.x;
- normals[ i + 1 ] = cb.y;
- normals[ i + 2 ] = cb.z;
-
- normals[ i + 3 ] = cb.x;
- normals[ i + 4 ] = cb.y;
- normals[ i + 5 ] = cb.z;
-
- normals[ i + 6 ] = cb.x;
- normals[ i + 7 ] = cb.y;
- normals[ i + 8 ] = cb.z;
-
- }
-
- }
-
- this.normalizeNormals();
-
- attributes.normal.needsUpdate = true;
-
- }
-
- },
-
- merge: function ( geometry, offset ) {
-
- if ( ! ( geometry && geometry.isBufferGeometry ) ) {
-
- console.error( 'THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry );
- return;
-
- }
-
- if ( offset === undefined ) {
-
- offset = 0;
-
- console.warn(
- 'THREE.BufferGeometry.merge(): Overwriting original geometry, starting at offset=0. '
- + 'Use BufferGeometryUtils.mergeBufferGeometries() for lossless merge.'
- );
-
- }
-
- var attributes = this.attributes;
-
- for ( var key in attributes ) {
-
- if ( geometry.attributes[ key ] === undefined ) { continue; }
-
- var attribute1 = attributes[ key ];
- var attributeArray1 = attribute1.array;
-
- var attribute2 = geometry.attributes[ key ];
- var attributeArray2 = attribute2.array;
-
- var attributeOffset = attribute2.itemSize * offset;
- var length = Math.min( attributeArray2.length, attributeArray1.length - attributeOffset );
-
- for ( var i = 0, j = attributeOffset; i < length; i ++, j ++ ) {
-
- attributeArray1[ j ] = attributeArray2[ i ];
-
- }
-
- }
-
- return this;
-
- },
-
- normalizeNormals: function () {
-
- var normals = this.attributes.normal;
-
- for ( var i = 0, il = normals.count; i < il; i ++ ) {
-
- _vector$4.x = normals.getX( i );
- _vector$4.y = normals.getY( i );
- _vector$4.z = normals.getZ( i );
-
- _vector$4.normalize();
-
- normals.setXYZ( i, _vector$4.x, _vector$4.y, _vector$4.z );
-
- }
-
- },
-
- toNonIndexed: function () {
-
- function convertBufferAttribute( attribute, indices ) {
-
- var array = attribute.array;
- var itemSize = attribute.itemSize;
- var normalized = attribute.normalized;
-
- var array2 = new array.constructor( indices.length * itemSize );
-
- var index = 0, index2 = 0;
-
- for ( var i = 0, l = indices.length; i < l; i ++ ) {
-
- index = indices[ i ] * itemSize;
-
- for ( var j = 0; j < itemSize; j ++ ) {
-
- array2[ index2 ++ ] = array[ index ++ ];
-
- }
-
- }
-
- return new BufferAttribute( array2, itemSize, normalized );
-
- }
-
- //
-
- if ( this.index === null ) {
-
- console.warn( 'THREE.BufferGeometry.toNonIndexed(): Geometry is already non-indexed.' );
- return this;
-
- }
-
- var geometry2 = new BufferGeometry();
-
- var indices = this.index.array;
- var attributes = this.attributes;
-
- // attributes
-
- for ( var name in attributes ) {
-
- var attribute = attributes[ name ];
-
- var newAttribute = convertBufferAttribute( attribute, indices );
-
- geometry2.setAttribute( name, newAttribute );
-
- }
-
- // morph attributes
-
- var morphAttributes = this.morphAttributes;
-
- for ( name in morphAttributes ) {
-
- var morphArray = [];
- var morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes
-
- for ( var i = 0, il = morphAttribute.length; i < il; i ++ ) {
-
- var attribute = morphAttribute[ i ];
-
- var newAttribute = convertBufferAttribute( attribute, indices );
-
- morphArray.push( newAttribute );
-
- }
-
- geometry2.morphAttributes[ name ] = morphArray;
-
- }
-
- geometry2.morphTargetsRelative = this.morphTargetsRelative;
-
- // groups
-
- var groups = this.groups;
-
- for ( var i = 0, l = groups.length; i < l; i ++ ) {
-
- var group = groups[ i ];
- geometry2.addGroup( group.start, group.count, group.materialIndex );
-
- }
-
- return geometry2;
-
- },
-
- toJSON: function () {
-
- var data = {
- metadata: {
- version: 4.5,
- type: 'BufferGeometry',
- generator: 'BufferGeometry.toJSON'
- }
- };
-
- // standard BufferGeometry serialization
-
- data.uuid = this.uuid;
- data.type = this.type;
- if ( this.name !== '' ) { data.name = this.name; }
- if ( Object.keys( this.userData ).length > 0 ) { data.userData = this.userData; }
-
- if ( this.parameters !== undefined ) {
-
- var parameters = this.parameters;
-
- for ( var key in parameters ) {
-
- if ( parameters[ key ] !== undefined ) { data[ key ] = parameters[ key ]; }
-
- }
-
- return data;
-
- }
-
- data.data = { attributes: {} };
-
- var index = this.index;
-
- if ( index !== null ) {
-
- data.data.index = {
- type: index.array.constructor.name,
- array: Array.prototype.slice.call( index.array )
- };
-
- }
-
- var attributes = this.attributes;
-
- for ( var key in attributes ) {
-
- var attribute = attributes[ key ];
-
- var attributeData = attribute.toJSON();
-
- if ( attribute.name !== '' ) { attributeData.name = attribute.name; }
-
- data.data.attributes[ key ] = attributeData;
-
- }
-
- var morphAttributes = {};
- var hasMorphAttributes = false;
-
- for ( var key in this.morphAttributes ) {
-
- var attributeArray = this.morphAttributes[ key ];
-
- var array = [];
-
- for ( var i = 0, il = attributeArray.length; i < il; i ++ ) {
-
- var attribute = attributeArray[ i ];
-
- var attributeData = attribute.toJSON();
-
- if ( attribute.name !== '' ) { attributeData.name = attribute.name; }
-
- array.push( attributeData );
-
- }
-
- if ( array.length > 0 ) {
-
- morphAttributes[ key ] = array;
-
- hasMorphAttributes = true;
-
- }
-
- }
-
- if ( hasMorphAttributes ) {
-
- data.data.morphAttributes = morphAttributes;
- data.data.morphTargetsRelative = this.morphTargetsRelative;
-
- }
-
- var groups = this.groups;
-
- if ( groups.length > 0 ) {
-
- data.data.groups = JSON.parse( JSON.stringify( groups ) );
-
- }
-
- var boundingSphere = this.boundingSphere;
-
- if ( boundingSphere !== null ) {
-
- data.data.boundingSphere = {
- center: boundingSphere.center.toArray(),
- radius: boundingSphere.radius
- };
-
- }
-
- return data;
-
- },
-
- clone: function () {
-
- /*
- // Handle primitives
-
- var parameters = this.parameters;
-
- if ( parameters !== undefined ) {
-
- var values = [];
-
- for ( var key in parameters ) {
-
- values.push( parameters[ key ] );
-
- }
-
- var geometry = Object.create( this.constructor.prototype );
- this.constructor.apply( geometry, values );
- return geometry;
-
- }
-
- return new this.constructor().copy( this );
- */
-
- return new BufferGeometry().copy( this );
-
- },
-
- copy: function ( source ) {
-
- var name, i, l;
-
- // reset
-
- this.index = null;
- this.attributes = {};
- this.morphAttributes = {};
- this.groups = [];
- this.boundingBox = null;
- this.boundingSphere = null;
-
- // name
-
- this.name = source.name;
-
- // index
-
- var index = source.index;
-
- if ( index !== null ) {
-
- this.setIndex( index.clone() );
-
- }
-
- // attributes
-
- var attributes = source.attributes;
-
- for ( name in attributes ) {
-
- var attribute = attributes[ name ];
- this.setAttribute( name, attribute.clone() );
-
- }
-
- // morph attributes
-
- var morphAttributes = source.morphAttributes;
-
- for ( name in morphAttributes ) {
-
- var array = [];
- var morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes
-
- for ( i = 0, l = morphAttribute.length; i < l; i ++ ) {
-
- array.push( morphAttribute[ i ].clone() );
-
- }
-
- this.morphAttributes[ name ] = array;
-
- }
-
- this.morphTargetsRelative = source.morphTargetsRelative;
-
- // groups
-
- var groups = source.groups;
-
- for ( i = 0, l = groups.length; i < l; i ++ ) {
-
- var group = groups[ i ];
- this.addGroup( group.start, group.count, group.materialIndex );
-
- }
-
- // bounding box
-
- var boundingBox = source.boundingBox;
-
- if ( boundingBox !== null ) {
-
- this.boundingBox = boundingBox.clone();
-
- }
-
- // bounding sphere
-
- var boundingSphere = source.boundingSphere;
-
- if ( boundingSphere !== null ) {
-
- this.boundingSphere = boundingSphere.clone();
-
- }
-
- // draw range
-
- this.drawRange.start = source.drawRange.start;
- this.drawRange.count = source.drawRange.count;
-
- // user data
-
- this.userData = source.userData;
-
- return this;
-
- },
-
- dispose: function () {
-
- this.dispatchEvent( { type: 'dispose' } );
-
- }
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author alteredq / http://alteredqualia.com/
- * @author mikael emtinger / http://gomo.se/
- * @author jonobr1 / http://jonobr1.com/
- */
-
- var _inverseMatrix = new Matrix4();
- var _ray = new Ray();
- var _sphere = new Sphere();
-
- var _vA = new Vector3();
- var _vB = new Vector3();
- var _vC = new Vector3();
-
- var _tempA = new Vector3();
- var _tempB = new Vector3();
- var _tempC = new Vector3();
-
- var _morphA = new Vector3();
- var _morphB = new Vector3();
- var _morphC = new Vector3();
-
- var _uvA = new Vector2();
- var _uvB = new Vector2();
- var _uvC = new Vector2();
-
- var _intersectionPoint = new Vector3();
- var _intersectionPointWorld = new Vector3();
-
- function Mesh( geometry, material ) {
-
- Object3D.call( this );
-
- this.type = 'Mesh';
-
- this.geometry = geometry !== undefined ? geometry : new BufferGeometry();
- this.material = material !== undefined ? material : new MeshBasicMaterial();
-
- this.updateMorphTargets();
-
- }
-
- Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
-
- constructor: Mesh,
-
- isMesh: true,
-
- copy: function ( source ) {
-
- Object3D.prototype.copy.call( this, source );
-
- if ( source.morphTargetInfluences !== undefined ) {
-
- this.morphTargetInfluences = source.morphTargetInfluences.slice();
-
- }
-
- if ( source.morphTargetDictionary !== undefined ) {
-
- this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary );
-
- }
-
- return this;
-
- },
-
- updateMorphTargets: function () {
-
- var geometry = this.geometry;
- var m, ml, name;
-
- if ( geometry.isBufferGeometry ) {
-
- var morphAttributes = geometry.morphAttributes;
- var keys = Object.keys( morphAttributes );
-
- if ( keys.length > 0 ) {
-
- var morphAttribute = morphAttributes[ keys[ 0 ] ];
-
- if ( morphAttribute !== undefined ) {
-
- this.morphTargetInfluences = [];
- this.morphTargetDictionary = {};
-
- for ( m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
-
- name = morphAttribute[ m ].name || String( m );
-
- this.morphTargetInfluences.push( 0 );
- this.morphTargetDictionary[ name ] = m;
-
- }
-
- }
-
- }
-
- } else {
-
- var morphTargets = geometry.morphTargets;
-
- if ( morphTargets !== undefined && morphTargets.length > 0 ) {
-
- console.error( 'THREE.Mesh.updateMorphTargets() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
-
- }
-
- }
-
- },
-
- raycast: function ( raycaster, intersects ) {
-
- var geometry = this.geometry;
- var material = this.material;
- var matrixWorld = this.matrixWorld;
-
- if ( material === undefined ) { return; }
-
- // Checking boundingSphere distance to ray
-
- if ( geometry.boundingSphere === null ) { geometry.computeBoundingSphere(); }
-
- _sphere.copy( geometry.boundingSphere );
- _sphere.applyMatrix4( matrixWorld );
-
- if ( raycaster.ray.intersectsSphere( _sphere ) === false ) { return; }
-
- //
-
- _inverseMatrix.getInverse( matrixWorld );
- _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
-
- // Check boundingBox before continuing
-
- if ( geometry.boundingBox !== null ) {
-
- if ( _ray.intersectsBox( geometry.boundingBox ) === false ) { return; }
-
- }
-
- var intersection;
-
- if ( geometry.isBufferGeometry ) {
-
- var a, b, c;
- var index = geometry.index;
- var position = geometry.attributes.position;
- var morphPosition = geometry.morphAttributes.position;
- var morphTargetsRelative = geometry.morphTargetsRelative;
- var uv = geometry.attributes.uv;
- var uv2 = geometry.attributes.uv2;
- var groups = geometry.groups;
- var drawRange = geometry.drawRange;
- var i, j, il, jl;
- var group, groupMaterial;
- var start, end;
-
- if ( index !== null ) {
-
- // indexed buffer geometry
-
- if ( Array.isArray( material ) ) {
-
- for ( i = 0, il = groups.length; i < il; i ++ ) {
-
- group = groups[ i ];
- groupMaterial = material[ group.materialIndex ];
-
- start = Math.max( group.start, drawRange.start );
- end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
-
- for ( j = start, jl = end; j < jl; j += 3 ) {
-
- a = index.getX( j );
- b = index.getX( j + 1 );
- c = index.getX( j + 2 );
-
- intersection = checkBufferGeometryIntersection( this, groupMaterial, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
-
- if ( intersection ) {
-
- intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics
- intersection.face.materialIndex = group.materialIndex;
- intersects.push( intersection );
-
- }
-
- }
-
- }
-
- } else {
-
- start = Math.max( 0, drawRange.start );
- end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
-
- for ( i = start, il = end; i < il; i += 3 ) {
-
- a = index.getX( i );
- b = index.getX( i + 1 );
- c = index.getX( i + 2 );
-
- intersection = checkBufferGeometryIntersection( this, material, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
-
- if ( intersection ) {
-
- intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics
- intersects.push( intersection );
-
- }
-
- }
-
- }
-
- } else if ( position !== undefined ) {
-
- // non-indexed buffer geometry
-
- if ( Array.isArray( material ) ) {
-
- for ( i = 0, il = groups.length; i < il; i ++ ) {
-
- group = groups[ i ];
- groupMaterial = material[ group.materialIndex ];
-
- start = Math.max( group.start, drawRange.start );
- end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
-
- for ( j = start, jl = end; j < jl; j += 3 ) {
-
- a = j;
- b = j + 1;
- c = j + 2;
-
- intersection = checkBufferGeometryIntersection( this, groupMaterial, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
-
- if ( intersection ) {
-
- intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics
- intersection.face.materialIndex = group.materialIndex;
- intersects.push( intersection );
-
- }
-
- }
-
- }
-
- } else {
-
- start = Math.max( 0, drawRange.start );
- end = Math.min( position.count, ( drawRange.start + drawRange.count ) );
-
- for ( i = start, il = end; i < il; i += 3 ) {
-
- a = i;
- b = i + 1;
- c = i + 2;
-
- intersection = checkBufferGeometryIntersection( this, material, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
-
- if ( intersection ) {
-
- intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics
- intersects.push( intersection );
-
- }
-
- }
-
- }
-
- }
-
- } else if ( geometry.isGeometry ) {
-
- var fvA, fvB, fvC;
- var isMultiMaterial = Array.isArray( material );
-
- var vertices = geometry.vertices;
- var faces = geometry.faces;
- var uvs;
-
- var faceVertexUvs = geometry.faceVertexUvs[ 0 ];
- if ( faceVertexUvs.length > 0 ) { uvs = faceVertexUvs; }
-
- for ( var f = 0, fl = faces.length; f < fl; f ++ ) {
-
- var face = faces[ f ];
- var faceMaterial = isMultiMaterial ? material[ face.materialIndex ] : material;
-
- if ( faceMaterial === undefined ) { continue; }
-
- fvA = vertices[ face.a ];
- fvB = vertices[ face.b ];
- fvC = vertices[ face.c ];
-
- intersection = checkIntersection( this, faceMaterial, raycaster, _ray, fvA, fvB, fvC, _intersectionPoint );
-
- if ( intersection ) {
-
- if ( uvs && uvs[ f ] ) {
-
- var uvs_f = uvs[ f ];
- _uvA.copy( uvs_f[ 0 ] );
- _uvB.copy( uvs_f[ 1 ] );
- _uvC.copy( uvs_f[ 2 ] );
-
- intersection.uv = Triangle.getUV( _intersectionPoint, fvA, fvB, fvC, _uvA, _uvB, _uvC, new Vector2() );
-
- }
-
- intersection.face = face;
- intersection.faceIndex = f;
- intersects.push( intersection );
-
- }
-
- }
-
- }
-
- },
-
- clone: function () {
-
- return new this.constructor( this.geometry, this.material ).copy( this );
-
- }
-
- } );
-
- function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) {
-
- var intersect;
-
- if ( material.side === BackSide ) {
-
- intersect = ray.intersectTriangle( pC, pB, pA, true, point );
-
- } else {
-
- intersect = ray.intersectTriangle( pA, pB, pC, material.side !== DoubleSide, point );
-
- }
-
- if ( intersect === null ) { return null; }
-
- _intersectionPointWorld.copy( point );
- _intersectionPointWorld.applyMatrix4( object.matrixWorld );
-
- var distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld );
-
- if ( distance < raycaster.near || distance > raycaster.far ) { return null; }
-
- return {
- distance: distance,
- point: _intersectionPointWorld.clone(),
- object: object
- };
-
- }
-
- function checkBufferGeometryIntersection( object, material, raycaster, ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c ) {
-
- _vA.fromBufferAttribute( position, a );
- _vB.fromBufferAttribute( position, b );
- _vC.fromBufferAttribute( position, c );
-
- var morphInfluences = object.morphTargetInfluences;
-
- if ( material.morphTargets && morphPosition && morphInfluences ) {
-
- _morphA.set( 0, 0, 0 );
- _morphB.set( 0, 0, 0 );
- _morphC.set( 0, 0, 0 );
-
- for ( var i = 0, il = morphPosition.length; i < il; i ++ ) {
-
- var influence = morphInfluences[ i ];
- var morphAttribute = morphPosition[ i ];
-
- if ( influence === 0 ) { continue; }
-
- _tempA.fromBufferAttribute( morphAttribute, a );
- _tempB.fromBufferAttribute( morphAttribute, b );
- _tempC.fromBufferAttribute( morphAttribute, c );
-
- if ( morphTargetsRelative ) {
-
- _morphA.addScaledVector( _tempA, influence );
- _morphB.addScaledVector( _tempB, influence );
- _morphC.addScaledVector( _tempC, influence );
-
- } else {
-
- _morphA.addScaledVector( _tempA.sub( _vA ), influence );
- _morphB.addScaledVector( _tempB.sub( _vB ), influence );
- _morphC.addScaledVector( _tempC.sub( _vC ), influence );
-
- }
-
- }
-
- _vA.add( _morphA );
- _vB.add( _morphB );
- _vC.add( _morphC );
-
- }
-
- if ( object.isSkinnedMesh ) {
-
- object.boneTransform( a, _vA );
- object.boneTransform( b, _vB );
- object.boneTransform( c, _vC );
-
- }
-
- var intersection = checkIntersection( object, material, raycaster, ray, _vA, _vB, _vC, _intersectionPoint );
-
- if ( intersection ) {
-
- if ( uv ) {
-
- _uvA.fromBufferAttribute( uv, a );
- _uvB.fromBufferAttribute( uv, b );
- _uvC.fromBufferAttribute( uv, c );
-
- intersection.uv = Triangle.getUV( _intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() );
-
- }
-
- if ( uv2 ) {
-
- _uvA.fromBufferAttribute( uv2, a );
- _uvB.fromBufferAttribute( uv2, b );
- _uvC.fromBufferAttribute( uv2, c );
-
- intersection.uv2 = Triangle.getUV( _intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() );
-
- }
-
- var face = new Face3( a, b, c );
- Triangle.getNormal( _vA, _vB, _vC, face.normal );
-
- intersection.face = face;
-
- }
-
- return intersection;
-
- }
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author kile / http://kile.stravaganza.org/
- * @author alteredq / http://alteredqualia.com/
- * @author mikael emtinger / http://gomo.se/
- * @author zz85 / http://www.lab4games.net/zz85/blog
- * @author bhouston / http://clara.io
- */
-
- var _geometryId = 0; // Geometry uses even numbers as Id
- var _m1$3 = new Matrix4();
- var _obj$1 = new Object3D();
- var _offset$1 = new Vector3();
-
- function Geometry() {
-
- Object.defineProperty( this, 'id', { value: _geometryId += 2 } );
-
- this.uuid = MathUtils.generateUUID();
-
- this.name = '';
- this.type = 'Geometry';
-
- this.vertices = [];
- this.colors = [];
- this.faces = [];
- this.faceVertexUvs = [[]];
-
- this.morphTargets = [];
- this.morphNormals = [];
-
- this.skinWeights = [];
- this.skinIndices = [];
-
- this.lineDistances = [];
-
- this.boundingBox = null;
- this.boundingSphere = null;
-
- // update flags
-
- this.elementsNeedUpdate = false;
- this.verticesNeedUpdate = false;
- this.uvsNeedUpdate = false;
- this.normalsNeedUpdate = false;
- this.colorsNeedUpdate = false;
- this.lineDistancesNeedUpdate = false;
- this.groupsNeedUpdate = false;
-
- }
-
- Geometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
-
- constructor: Geometry,
-
- isGeometry: true,
-
- applyMatrix4: function ( matrix ) {
-
- var normalMatrix = new Matrix3().getNormalMatrix( matrix );
-
- for ( var i = 0, il = this.vertices.length; i < il; i ++ ) {
-
- var vertex = this.vertices[ i ];
- vertex.applyMatrix4( matrix );
-
- }
-
- for ( var i = 0, il = this.faces.length; i < il; i ++ ) {
-
- var face = this.faces[ i ];
- face.normal.applyMatrix3( normalMatrix ).normalize();
-
- for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {
-
- face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize();
-
- }
-
- }
-
- if ( this.boundingBox !== null ) {
-
- this.computeBoundingBox();
-
- }
-
- if ( this.boundingSphere !== null ) {
-
- this.computeBoundingSphere();
-
- }
-
- this.verticesNeedUpdate = true;
- this.normalsNeedUpdate = true;
-
- return this;
-
- },
-
- rotateX: function ( angle ) {
-
- // rotate geometry around world x-axis
-
- _m1$3.makeRotationX( angle );
-
- this.applyMatrix4( _m1$3 );
-
- return this;
-
- },
-
- rotateY: function ( angle ) {
-
- // rotate geometry around world y-axis
-
- _m1$3.makeRotationY( angle );
-
- this.applyMatrix4( _m1$3 );
-
- return this;
-
- },
-
- rotateZ: function ( angle ) {
-
- // rotate geometry around world z-axis
-
- _m1$3.makeRotationZ( angle );
-
- this.applyMatrix4( _m1$3 );
-
- return this;
-
- },
-
- translate: function ( x, y, z ) {
-
- // translate geometry
-
- _m1$3.makeTranslation( x, y, z );
-
- this.applyMatrix4( _m1$3 );
-
- return this;
-
- },
-
- scale: function ( x, y, z ) {
-
- // scale geometry
-
- _m1$3.makeScale( x, y, z );
-
- this.applyMatrix4( _m1$3 );
-
- return this;
-
- },
-
- lookAt: function ( vector ) {
-
- _obj$1.lookAt( vector );
-
- _obj$1.updateMatrix();
-
- this.applyMatrix4( _obj$1.matrix );
-
- return this;
-
- },
-
- fromBufferGeometry: function ( geometry ) {
-
- var scope = this;
-
- var indices = geometry.index !== null ? geometry.index.array : undefined;
- var attributes = geometry.attributes;
-
- if ( attributes.position === undefined ) {
-
- console.error( 'THREE.Geometry.fromBufferGeometry(): Position attribute required for conversion.' );
- return this;
-
- }
-
- var positions = attributes.position.array;
- var normals = attributes.normal !== undefined ? attributes.normal.array : undefined;
- var colors = attributes.color !== undefined ? attributes.color.array : undefined;
- var uvs = attributes.uv !== undefined ? attributes.uv.array : undefined;
- var uvs2 = attributes.uv2 !== undefined ? attributes.uv2.array : undefined;
-
- if ( uvs2 !== undefined ) { this.faceVertexUvs[ 1 ] = []; }
-
- for ( var i = 0; i < positions.length; i += 3 ) {
-
- scope.vertices.push( new Vector3().fromArray( positions, i ) );
-
- if ( colors !== undefined ) {
-
- scope.colors.push( new Color().fromArray( colors, i ) );
-
- }
-
- }
-
- function addFace( a, b, c, materialIndex ) {
-
- var vertexColors = ( colors === undefined ) ? [] : [
- scope.colors[ a ].clone(),
- scope.colors[ b ].clone(),
- scope.colors[ c ].clone() ];
-
- var vertexNormals = ( normals === undefined ) ? [] : [
- new Vector3().fromArray( normals, a * 3 ),
- new Vector3().fromArray( normals, b * 3 ),
- new Vector3().fromArray( normals, c * 3 )
- ];
-
- var face = new Face3( a, b, c, vertexNormals, vertexColors, materialIndex );
-
- scope.faces.push( face );
-
- if ( uvs !== undefined ) {
-
- scope.faceVertexUvs[ 0 ].push( [
- new Vector2().fromArray( uvs, a * 2 ),
- new Vector2().fromArray( uvs, b * 2 ),
- new Vector2().fromArray( uvs, c * 2 )
- ] );
-
- }
-
- if ( uvs2 !== undefined ) {
-
- scope.faceVertexUvs[ 1 ].push( [
- new Vector2().fromArray( uvs2, a * 2 ),
- new Vector2().fromArray( uvs2, b * 2 ),
- new Vector2().fromArray( uvs2, c * 2 )
- ] );
-
- }
-
- }
-
- var groups = geometry.groups;
-
- if ( groups.length > 0 ) {
-
- for ( var i = 0; i < groups.length; i ++ ) {
-
- var group = groups[ i ];
-
- var start = group.start;
- var count = group.count;
-
- for ( var j = start, jl = start + count; j < jl; j += 3 ) {
-
- if ( indices !== undefined ) {
-
- addFace( indices[ j ], indices[ j + 1 ], indices[ j + 2 ], group.materialIndex );
-
- } else {
-
- addFace( j, j + 1, j + 2, group.materialIndex );
-
- }
-
- }
-
- }
-
- } else {
-
- if ( indices !== undefined ) {
-
- for ( var i = 0; i < indices.length; i += 3 ) {
-
- addFace( indices[ i ], indices[ i + 1 ], indices[ i + 2 ] );
-
- }
-
- } else {
-
- for ( var i = 0; i < positions.length / 3; i += 3 ) {
-
- addFace( i, i + 1, i + 2 );
-
- }
-
- }
-
- }
-
- this.computeFaceNormals();
-
- if ( geometry.boundingBox !== null ) {
-
- this.boundingBox = geometry.boundingBox.clone();
-
- }
-
- if ( geometry.boundingSphere !== null ) {
-
- this.boundingSphere = geometry.boundingSphere.clone();
-
- }
-
- return this;
-
- },
-
- center: function () {
-
- this.computeBoundingBox();
-
- this.boundingBox.getCenter( _offset$1 ).negate();
-
- this.translate( _offset$1.x, _offset$1.y, _offset$1.z );
-
- return this;
-
- },
-
- normalize: function () {
-
- this.computeBoundingSphere();
-
- var center = this.boundingSphere.center;
- var radius = this.boundingSphere.radius;
-
- var s = radius === 0 ? 1 : 1.0 / radius;
-
- var matrix = new Matrix4();
- matrix.set(
- s, 0, 0, - s * center.x,
- 0, s, 0, - s * center.y,
- 0, 0, s, - s * center.z,
- 0, 0, 0, 1
- );
-
- this.applyMatrix4( matrix );
-
- return this;
-
- },
-
- computeFaceNormals: function () {
-
- var cb = new Vector3(), ab = new Vector3();
-
- for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) {
-
- var face = this.faces[ f ];
-
- var vA = this.vertices[ face.a ];
- var vB = this.vertices[ face.b ];
- var vC = this.vertices[ face.c ];
-
- cb.subVectors( vC, vB );
- ab.subVectors( vA, vB );
- cb.cross( ab );
-
- cb.normalize();
-
- face.normal.copy( cb );
-
- }
-
- },
-
- computeVertexNormals: function ( areaWeighted ) {
-
- if ( areaWeighted === undefined ) { areaWeighted = true; }
-
- var v, vl, f, fl, face, vertices;
-
- vertices = new Array( this.vertices.length );
-
- for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
-
- vertices[ v ] = new Vector3();
-
- }
-
- if ( areaWeighted ) {
-
- // vertex normals weighted by triangle areas
- // http://www.iquilezles.org/www/articles/normals/normals.htm
-
- var vA, vB, vC;
- var cb = new Vector3(), ab = new Vector3();
-
- for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
-
- face = this.faces[ f ];
-
- vA = this.vertices[ face.a ];
- vB = this.vertices[ face.b ];
- vC = this.vertices[ face.c ];
-
- cb.subVectors( vC, vB );
- ab.subVectors( vA, vB );
- cb.cross( ab );
-
- vertices[ face.a ].add( cb );
- vertices[ face.b ].add( cb );
- vertices[ face.c ].add( cb );
-
- }
-
- } else {
-
- this.computeFaceNormals();
-
- for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
-
- face = this.faces[ f ];
-
- vertices[ face.a ].add( face.normal );
- vertices[ face.b ].add( face.normal );
- vertices[ face.c ].add( face.normal );
-
- }
-
- }
-
- for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
-
- vertices[ v ].normalize();
-
- }
-
- for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
-
- face = this.faces[ f ];
-
- var vertexNormals = face.vertexNormals;
-
- if ( vertexNormals.length === 3 ) {
-
- vertexNormals[ 0 ].copy( vertices[ face.a ] );
- vertexNormals[ 1 ].copy( vertices[ face.b ] );
- vertexNormals[ 2 ].copy( vertices[ face.c ] );
-
- } else {
-
- vertexNormals[ 0 ] = vertices[ face.a ].clone();
- vertexNormals[ 1 ] = vertices[ face.b ].clone();
- vertexNormals[ 2 ] = vertices[ face.c ].clone();
-
- }
-
- }
-
- if ( this.faces.length > 0 ) {
-
- this.normalsNeedUpdate = true;
-
- }
-
- },
-
- computeFlatVertexNormals: function () {
-
- var f, fl, face;
-
- this.computeFaceNormals();
-
- for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
-
- face = this.faces[ f ];
-
- var vertexNormals = face.vertexNormals;
-
- if ( vertexNormals.length === 3 ) {
-
- vertexNormals[ 0 ].copy( face.normal );
- vertexNormals[ 1 ].copy( face.normal );
- vertexNormals[ 2 ].copy( face.normal );
-
- } else {
-
- vertexNormals[ 0 ] = face.normal.clone();
- vertexNormals[ 1 ] = face.normal.clone();
- vertexNormals[ 2 ] = face.normal.clone();
-
- }
-
- }
-
- if ( this.faces.length > 0 ) {
-
- this.normalsNeedUpdate = true;
-
- }
-
- },
-
- computeMorphNormals: function () {
-
- var i, il, f, fl, face;
-
- // save original normals
- // - create temp variables on first access
- // otherwise just copy (for faster repeated calls)
-
- for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
-
- face = this.faces[ f ];
-
- if ( ! face.__originalFaceNormal ) {
-
- face.__originalFaceNormal = face.normal.clone();
-
- } else {
-
- face.__originalFaceNormal.copy( face.normal );
-
- }
-
- if ( ! face.__originalVertexNormals ) { face.__originalVertexNormals = []; }
-
- for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) {
-
- if ( ! face.__originalVertexNormals[ i ] ) {
-
- face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone();
-
- } else {
-
- face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] );
-
- }
-
- }
-
- }
-
- // use temp geometry to compute face and vertex normals for each morph
-
- var tmpGeo = new Geometry();
- tmpGeo.faces = this.faces;
-
- for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) {
-
- // create on first access
-
- if ( ! this.morphNormals[ i ] ) {
-
- this.morphNormals[ i ] = {};
- this.morphNormals[ i ].faceNormals = [];
- this.morphNormals[ i ].vertexNormals = [];
-
- var dstNormalsFace = this.morphNormals[ i ].faceNormals;
- var dstNormalsVertex = this.morphNormals[ i ].vertexNormals;
-
- var faceNormal, vertexNormals;
-
- for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
-
- faceNormal = new Vector3();
- vertexNormals = { a: new Vector3(), b: new Vector3(), c: new Vector3() };
-
- dstNormalsFace.push( faceNormal );
- dstNormalsVertex.push( vertexNormals );
-
- }
-
- }
-
- var morphNormals = this.morphNormals[ i ];
-
- // set vertices to morph target
-
- tmpGeo.vertices = this.morphTargets[ i ].vertices;
-
- // compute morph normals
-
- tmpGeo.computeFaceNormals();
- tmpGeo.computeVertexNormals();
-
- // store morph normals
-
- var faceNormal, vertexNormals;
-
- for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
-
- face = this.faces[ f ];
-
- faceNormal = morphNormals.faceNormals[ f ];
- vertexNormals = morphNormals.vertexNormals[ f ];
-
- faceNormal.copy( face.normal );
-
- vertexNormals.a.copy( face.vertexNormals[ 0 ] );
- vertexNormals.b.copy( face.vertexNormals[ 1 ] );
- vertexNormals.c.copy( face.vertexNormals[ 2 ] );
-
- }
-
- }
-
- // restore original normals
-
- for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
-
- face = this.faces[ f ];
-
- face.normal = face.__originalFaceNormal;
- face.vertexNormals = face.__originalVertexNormals;
-
- }
-
- },
-
- computeBoundingBox: function () {
-
- if ( this.boundingBox === null ) {
-
- this.boundingBox = new Box3();
-
- }
-
- this.boundingBox.setFromPoints( this.vertices );
-
- },
-
- computeBoundingSphere: function () {
-
- if ( this.boundingSphere === null ) {
-
- this.boundingSphere = new Sphere();
-
- }
-
- this.boundingSphere.setFromPoints( this.vertices );
-
- },
-
- merge: function ( geometry, matrix, materialIndexOffset ) {
-
- if ( ! ( geometry && geometry.isGeometry ) ) {
-
- console.error( 'THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry );
- return;
-
- }
-
- var normalMatrix,
- vertexOffset = this.vertices.length,
- vertices1 = this.vertices,
- vertices2 = geometry.vertices,
- faces1 = this.faces,
- faces2 = geometry.faces,
- colors1 = this.colors,
- colors2 = geometry.colors;
-
- if ( materialIndexOffset === undefined ) { materialIndexOffset = 0; }
-
- if ( matrix !== undefined ) {
-
- normalMatrix = new Matrix3().getNormalMatrix( matrix );
-
- }
-
- // vertices
-
- for ( var i = 0, il = vertices2.length; i < il; i ++ ) {
-
- var vertex = vertices2[ i ];
-
- var vertexCopy = vertex.clone();
-
- if ( matrix !== undefined ) { vertexCopy.applyMatrix4( matrix ); }
-
- vertices1.push( vertexCopy );
-
- }
-
- // colors
-
- for ( var i = 0, il = colors2.length; i < il; i ++ ) {
-
- colors1.push( colors2[ i ].clone() );
-
- }
-
- // faces
-
- for ( i = 0, il = faces2.length; i < il; i ++ ) {
-
- var face = faces2[ i ], faceCopy, normal, color,
- faceVertexNormals = face.vertexNormals,
- faceVertexColors = face.vertexColors;
-
- faceCopy = new Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset );
- faceCopy.normal.copy( face.normal );
-
- if ( normalMatrix !== undefined ) {
-
- faceCopy.normal.applyMatrix3( normalMatrix ).normalize();
-
- }
-
- for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) {
-
- normal = faceVertexNormals[ j ].clone();
-
- if ( normalMatrix !== undefined ) {
-
- normal.applyMatrix3( normalMatrix ).normalize();
-
- }
-
- faceCopy.vertexNormals.push( normal );
-
- }
-
- faceCopy.color.copy( face.color );
-
- for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) {
-
- color = faceVertexColors[ j ];
- faceCopy.vertexColors.push( color.clone() );
-
- }
-
- faceCopy.materialIndex = face.materialIndex + materialIndexOffset;
-
- faces1.push( faceCopy );
-
- }
-
- // uvs
-
- for ( var i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) {
-
- var faceVertexUvs2 = geometry.faceVertexUvs[ i ];
-
- if ( this.faceVertexUvs[ i ] === undefined ) { this.faceVertexUvs[ i ] = []; }
-
- for ( var j = 0, jl = faceVertexUvs2.length; j < jl; j ++ ) {
-
- var uvs2 = faceVertexUvs2[ j ], uvsCopy = [];
-
- for ( var k = 0, kl = uvs2.length; k < kl; k ++ ) {
-
- uvsCopy.push( uvs2[ k ].clone() );
-
- }
-
- this.faceVertexUvs[ i ].push( uvsCopy );
-
- }
-
- }
-
- },
-
- mergeMesh: function ( mesh ) {
-
- if ( ! ( mesh && mesh.isMesh ) ) {
-
- console.error( 'THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh );
- return;
-
- }
-
- if ( mesh.matrixAutoUpdate ) { mesh.updateMatrix(); }
-
- this.merge( mesh.geometry, mesh.matrix );
-
- },
-
- /*
- * Checks for duplicate vertices with hashmap.
- * Duplicated vertices are removed
- * and faces' vertices are updated.
- */
-
- mergeVertices: function () {
-
- var verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique)
- var unique = [], changes = [];
-
- var v, key;
- var precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001
- var precision = Math.pow( 10, precisionPoints );
- var i, il, face;
- var indices, j, jl;
-
- for ( i = 0, il = this.vertices.length; i < il; i ++ ) {
-
- v = this.vertices[ i ];
- key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision );
-
- if ( verticesMap[ key ] === undefined ) {
-
- verticesMap[ key ] = i;
- unique.push( this.vertices[ i ] );
- changes[ i ] = unique.length - 1;
-
- } else {
-
- //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]);
- changes[ i ] = changes[ verticesMap[ key ] ];
-
- }
-
- }
-
-
- // if faces are completely degenerate after merging vertices, we
- // have to remove them from the geometry.
- var faceIndicesToRemove = [];
-
- for ( i = 0, il = this.faces.length; i < il; i ++ ) {
-
- face = this.faces[ i ];
-
- face.a = changes[ face.a ];
- face.b = changes[ face.b ];
- face.c = changes[ face.c ];
-
- indices = [ face.a, face.b, face.c ];
-
- // if any duplicate vertices are found in a Face3
- // we have to remove the face as nothing can be saved
- for ( var n = 0; n < 3; n ++ ) {
-
- if ( indices[ n ] === indices[ ( n + 1 ) % 3 ] ) {
-
- faceIndicesToRemove.push( i );
- break;
-
- }
-
- }
-
- }
-
- for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) {
-
- var idx = faceIndicesToRemove[ i ];
-
- this.faces.splice( idx, 1 );
-
- for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {
-
- this.faceVertexUvs[ j ].splice( idx, 1 );
-
- }
-
- }
-
- // Use unique set of vertices
-
- var diff = this.vertices.length - unique.length;
- this.vertices = unique;
- return diff;
-
- },
-
- setFromPoints: function ( points ) {
-
- this.vertices = [];
-
- for ( var i = 0, l = points.length; i < l; i ++ ) {
-
- var point = points[ i ];
- this.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) );
-
- }
-
- return this;
-
- },
-
- sortFacesByMaterialIndex: function () {
-
- var faces = this.faces;
- var length = faces.length;
-
- // tag faces
-
- for ( var i = 0; i < length; i ++ ) {
-
- faces[ i ]._id = i;
-
- }
-
- // sort faces
-
- function materialIndexSort( a, b ) {
-
- return a.materialIndex - b.materialIndex;
-
- }
-
- faces.sort( materialIndexSort );
-
- // sort uvs
-
- var uvs1 = this.faceVertexUvs[ 0 ];
- var uvs2 = this.faceVertexUvs[ 1 ];
-
- var newUvs1, newUvs2;
-
- if ( uvs1 && uvs1.length === length ) { newUvs1 = []; }
- if ( uvs2 && uvs2.length === length ) { newUvs2 = []; }
-
- for ( var i = 0; i < length; i ++ ) {
-
- var id = faces[ i ]._id;
-
- if ( newUvs1 ) { newUvs1.push( uvs1[ id ] ); }
- if ( newUvs2 ) { newUvs2.push( uvs2[ id ] ); }
-
- }
-
- if ( newUvs1 ) { this.faceVertexUvs[ 0 ] = newUvs1; }
- if ( newUvs2 ) { this.faceVertexUvs[ 1 ] = newUvs2; }
-
- },
-
- toJSON: function () {
-
- var data = {
- metadata: {
- version: 4.5,
- type: 'Geometry',
- generator: 'Geometry.toJSON'
- }
- };
-
- // standard Geometry serialization
-
- data.uuid = this.uuid;
- data.type = this.type;
- if ( this.name !== '' ) { data.name = this.name; }
-
- if ( this.parameters !== undefined ) {
-
- var parameters = this.parameters;
-
- for ( var key in parameters ) {
-
- if ( parameters[ key ] !== undefined ) { data[ key ] = parameters[ key ]; }
-
- }
-
- return data;
-
- }
-
- var vertices = [];
-
- for ( var i = 0; i < this.vertices.length; i ++ ) {
-
- var vertex = this.vertices[ i ];
- vertices.push( vertex.x, vertex.y, vertex.z );
-
- }
-
- var faces = [];
- var normals = [];
- var normalsHash = {};
- var colors = [];
- var colorsHash = {};
- var uvs = [];
- var uvsHash = {};
-
- for ( var i = 0; i < this.faces.length; i ++ ) {
-
- var face = this.faces[ i ];
-
- var hasMaterial = true;
- var hasFaceUv = false; // deprecated
- var hasFaceVertexUv = this.faceVertexUvs[ 0 ][ i ] !== undefined;
- var hasFaceNormal = face.normal.length() > 0;
- var hasFaceVertexNormal = face.vertexNormals.length > 0;
- var hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1;
- var hasFaceVertexColor = face.vertexColors.length > 0;
-
- var faceType = 0;
-
- faceType = setBit( faceType, 0, 0 ); // isQuad
- faceType = setBit( faceType, 1, hasMaterial );
- faceType = setBit( faceType, 2, hasFaceUv );
- faceType = setBit( faceType, 3, hasFaceVertexUv );
- faceType = setBit( faceType, 4, hasFaceNormal );
- faceType = setBit( faceType, 5, hasFaceVertexNormal );
- faceType = setBit( faceType, 6, hasFaceColor );
- faceType = setBit( faceType, 7, hasFaceVertexColor );
-
- faces.push( faceType );
- faces.push( face.a, face.b, face.c );
- faces.push( face.materialIndex );
-
- if ( hasFaceVertexUv ) {
-
- var faceVertexUvs = this.faceVertexUvs[ 0 ][ i ];
-
- faces.push(
- getUvIndex( faceVertexUvs[ 0 ] ),
- getUvIndex( faceVertexUvs[ 1 ] ),
- getUvIndex( faceVertexUvs[ 2 ] )
- );
-
- }
-
- if ( hasFaceNormal ) {
-
- faces.push( getNormalIndex( face.normal ) );
-
- }
-
- if ( hasFaceVertexNormal ) {
-
- var vertexNormals = face.vertexNormals;
-
- faces.push(
- getNormalIndex( vertexNormals[ 0 ] ),
- getNormalIndex( vertexNormals[ 1 ] ),
- getNormalIndex( vertexNormals[ 2 ] )
- );
-
- }
-
- if ( hasFaceColor ) {
-
- faces.push( getColorIndex( face.color ) );
-
- }
-
- if ( hasFaceVertexColor ) {
-
- var vertexColors = face.vertexColors;
-
- faces.push(
- getColorIndex( vertexColors[ 0 ] ),
- getColorIndex( vertexColors[ 1 ] ),
- getColorIndex( vertexColors[ 2 ] )
- );
-
- }
-
- }
-
- function setBit( value, position, enabled ) {
-
- return enabled ? value | ( 1 << position ) : value & ( ~ ( 1 << position ) );
-
- }
-
- function getNormalIndex( normal ) {
-
- var hash = normal.x.toString() + normal.y.toString() + normal.z.toString();
-
- if ( normalsHash[ hash ] !== undefined ) {
-
- return normalsHash[ hash ];
-
- }
-
- normalsHash[ hash ] = normals.length / 3;
- normals.push( normal.x, normal.y, normal.z );
-
- return normalsHash[ hash ];
-
- }
-
- function getColorIndex( color ) {
-
- var hash = color.r.toString() + color.g.toString() + color.b.toString();
-
- if ( colorsHash[ hash ] !== undefined ) {
-
- return colorsHash[ hash ];
-
- }
-
- colorsHash[ hash ] = colors.length;
- colors.push( color.getHex() );
-
- return colorsHash[ hash ];
-
- }
-
- function getUvIndex( uv ) {
-
- var hash = uv.x.toString() + uv.y.toString();
-
- if ( uvsHash[ hash ] !== undefined ) {
-
- return uvsHash[ hash ];
-
- }
-
- uvsHash[ hash ] = uvs.length / 2;
- uvs.push( uv.x, uv.y );
-
- return uvsHash[ hash ];
-
- }
-
- data.data = {};
-
- data.data.vertices = vertices;
- data.data.normals = normals;
- if ( colors.length > 0 ) { data.data.colors = colors; }
- if ( uvs.length > 0 ) { data.data.uvs = [ uvs ]; } // temporal backward compatibility
- data.data.faces = faces;
-
- return data;
-
- },
-
- clone: function () {
-
- /*
- // Handle primitives
-
- var parameters = this.parameters;
-
- if ( parameters !== undefined ) {
-
- var values = [];
-
- for ( var key in parameters ) {
-
- values.push( parameters[ key ] );
-
- }
-
- var geometry = Object.create( this.constructor.prototype );
- this.constructor.apply( geometry, values );
- return geometry;
-
- }
-
- return new this.constructor().copy( this );
- */
-
- return new Geometry().copy( this );
-
- },
-
- copy: function ( source ) {
-
- var i, il, j, jl, k, kl;
-
- // reset
-
- this.vertices = [];
- this.colors = [];
- this.faces = [];
- this.faceVertexUvs = [[]];
- this.morphTargets = [];
- this.morphNormals = [];
- this.skinWeights = [];
- this.skinIndices = [];
- this.lineDistances = [];
- this.boundingBox = null;
- this.boundingSphere = null;
-
- // name
-
- this.name = source.name;
-
- // vertices
-
- var vertices = source.vertices;
-
- for ( i = 0, il = vertices.length; i < il; i ++ ) {
-
- this.vertices.push( vertices[ i ].clone() );
-
- }
-
- // colors
-
- var colors = source.colors;
-
- for ( i = 0, il = colors.length; i < il; i ++ ) {
-
- this.colors.push( colors[ i ].clone() );
-
- }
-
- // faces
-
- var faces = source.faces;
-
- for ( i = 0, il = faces.length; i < il; i ++ ) {
-
- this.faces.push( faces[ i ].clone() );
-
- }
-
- // face vertex uvs
-
- for ( i = 0, il = source.faceVertexUvs.length; i < il; i ++ ) {
-
- var faceVertexUvs = source.faceVertexUvs[ i ];
-
- if ( this.faceVertexUvs[ i ] === undefined ) {
-
- this.faceVertexUvs[ i ] = [];
-
- }
-
- for ( j = 0, jl = faceVertexUvs.length; j < jl; j ++ ) {
-
- var uvs = faceVertexUvs[ j ], uvsCopy = [];
-
- for ( k = 0, kl = uvs.length; k < kl; k ++ ) {
-
- var uv = uvs[ k ];
-
- uvsCopy.push( uv.clone() );
-
- }
-
- this.faceVertexUvs[ i ].push( uvsCopy );
-
- }
-
- }
-
- // morph targets
-
- var morphTargets = source.morphTargets;
-
- for ( i = 0, il = morphTargets.length; i < il; i ++ ) {
-
- var morphTarget = {};
- morphTarget.name = morphTargets[ i ].name;
-
- // vertices
-
- if ( morphTargets[ i ].vertices !== undefined ) {
-
- morphTarget.vertices = [];
-
- for ( j = 0, jl = morphTargets[ i ].vertices.length; j < jl; j ++ ) {
-
- morphTarget.vertices.push( morphTargets[ i ].vertices[ j ].clone() );
-
- }
-
- }
-
- // normals
-
- if ( morphTargets[ i ].normals !== undefined ) {
-
- morphTarget.normals = [];
-
- for ( j = 0, jl = morphTargets[ i ].normals.length; j < jl; j ++ ) {
-
- morphTarget.normals.push( morphTargets[ i ].normals[ j ].clone() );
-
- }
-
- }
-
- this.morphTargets.push( morphTarget );
-
- }
-
- // morph normals
-
- var morphNormals = source.morphNormals;
-
- for ( i = 0, il = morphNormals.length; i < il; i ++ ) {
-
- var morphNormal = {};
-
- // vertex normals
-
- if ( morphNormals[ i ].vertexNormals !== undefined ) {
-
- morphNormal.vertexNormals = [];
-
- for ( j = 0, jl = morphNormals[ i ].vertexNormals.length; j < jl; j ++ ) {
-
- var srcVertexNormal = morphNormals[ i ].vertexNormals[ j ];
- var destVertexNormal = {};
-
- destVertexNormal.a = srcVertexNormal.a.clone();
- destVertexNormal.b = srcVertexNormal.b.clone();
- destVertexNormal.c = srcVertexNormal.c.clone();
-
- morphNormal.vertexNormals.push( destVertexNormal );
-
- }
-
- }
-
- // face normals
-
- if ( morphNormals[ i ].faceNormals !== undefined ) {
-
- morphNormal.faceNormals = [];
-
- for ( j = 0, jl = morphNormals[ i ].faceNormals.length; j < jl; j ++ ) {
-
- morphNormal.faceNormals.push( morphNormals[ i ].faceNormals[ j ].clone() );
-
- }
-
- }
-
- this.morphNormals.push( morphNormal );
-
- }
-
- // skin weights
-
- var skinWeights = source.skinWeights;
-
- for ( i = 0, il = skinWeights.length; i < il; i ++ ) {
-
- this.skinWeights.push( skinWeights[ i ].clone() );
-
- }
-
- // skin indices
-
- var skinIndices = source.skinIndices;
-
- for ( i = 0, il = skinIndices.length; i < il; i ++ ) {
-
- this.skinIndices.push( skinIndices[ i ].clone() );
-
- }
-
- // line distances
-
- var lineDistances = source.lineDistances;
-
- for ( i = 0, il = lineDistances.length; i < il; i ++ ) {
-
- this.lineDistances.push( lineDistances[ i ] );
-
- }
-
- // bounding box
-
- var boundingBox = source.boundingBox;
-
- if ( boundingBox !== null ) {
-
- this.boundingBox = boundingBox.clone();
-
- }
-
- // bounding sphere
-
- var boundingSphere = source.boundingSphere;
-
- if ( boundingSphere !== null ) {
-
- this.boundingSphere = boundingSphere.clone();
-
- }
-
- // update flags
-
- this.elementsNeedUpdate = source.elementsNeedUpdate;
- this.verticesNeedUpdate = source.verticesNeedUpdate;
- this.uvsNeedUpdate = source.uvsNeedUpdate;
- this.normalsNeedUpdate = source.normalsNeedUpdate;
- this.colorsNeedUpdate = source.colorsNeedUpdate;
- this.lineDistancesNeedUpdate = source.lineDistancesNeedUpdate;
- this.groupsNeedUpdate = source.groupsNeedUpdate;
-
- return this;
-
- },
-
- dispose: function () {
-
- this.dispatchEvent( { type: 'dispose' } );
-
- }
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author Mugen87 / https://github.com/Mugen87
- */
-
- // BoxGeometry
-
- var BoxGeometry = /*@__PURE__*/(function (Geometry) {
- function BoxGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) {
-
- Geometry.call(this);
-
- this.type = 'BoxGeometry';
-
- this.parameters = {
- width: width,
- height: height,
- depth: depth,
- widthSegments: widthSegments,
- heightSegments: heightSegments,
- depthSegments: depthSegments
- };
-
- this.fromBufferGeometry( new BoxBufferGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) );
- this.mergeVertices();
-
- }
-
- if ( Geometry ) BoxGeometry.__proto__ = Geometry;
- BoxGeometry.prototype = Object.create( Geometry && Geometry.prototype );
- BoxGeometry.prototype.constructor = BoxGeometry;
-
- return BoxGeometry;
- }(Geometry));
-
- // BoxBufferGeometry
-
- var BoxBufferGeometry = /*@__PURE__*/(function (BufferGeometry) {
- function BoxBufferGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) {
-
- BufferGeometry.call(this);
-
- this.type = 'BoxBufferGeometry';
-
- this.parameters = {
- width: width,
- height: height,
- depth: depth,
- widthSegments: widthSegments,
- heightSegments: heightSegments,
- depthSegments: depthSegments
- };
-
- var scope = this;
-
- width = width || 1;
- height = height || 1;
- depth = depth || 1;
-
- // segments
-
- widthSegments = Math.floor( widthSegments ) || 1;
- heightSegments = Math.floor( heightSegments ) || 1;
- depthSegments = Math.floor( depthSegments ) || 1;
-
- // buffers
-
- var indices = [];
- var vertices = [];
- var normals = [];
- var uvs = [];
-
- // helper variables
-
- var numberOfVertices = 0;
- var groupStart = 0;
-
- // build each side of the box geometry
-
- buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px
- buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx
- buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py
- buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny
- buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz
- buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz
-
- // build geometry
-
- this.setIndex( indices );
- this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
- this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
- this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
-
- function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) {
-
- var segmentWidth = width / gridX;
- var segmentHeight = height / gridY;
-
- var widthHalf = width / 2;
- var heightHalf = height / 2;
- var depthHalf = depth / 2;
-
- var gridX1 = gridX + 1;
- var gridY1 = gridY + 1;
-
- var vertexCounter = 0;
- var groupCount = 0;
-
- var vector = new Vector3();
-
- // generate vertices, normals and uvs
-
- for ( var iy = 0; iy < gridY1; iy ++ ) {
-
- var y = iy * segmentHeight - heightHalf;
-
- for ( var ix = 0; ix < gridX1; ix ++ ) {
-
- var x = ix * segmentWidth - widthHalf;
-
- // set values to correct vector component
-
- vector[ u ] = x * udir;
- vector[ v ] = y * vdir;
- vector[ w ] = depthHalf;
-
- // now apply vector to vertex buffer
-
- vertices.push( vector.x, vector.y, vector.z );
-
- // set values to correct vector component
-
- vector[ u ] = 0;
- vector[ v ] = 0;
- vector[ w ] = depth > 0 ? 1 : - 1;
-
- // now apply vector to normal buffer
-
- normals.push( vector.x, vector.y, vector.z );
-
- // uvs
-
- uvs.push( ix / gridX );
- uvs.push( 1 - ( iy / gridY ) );
-
- // counters
-
- vertexCounter += 1;
-
- }
-
- }
-
- // indices
-
- // 1. you need three indices to draw a single face
- // 2. a single segment consists of two faces
- // 3. so we need to generate six (2*3) indices per segment
-
- for ( var iy$1 = 0; iy$1 < gridY; iy$1 ++ ) {
-
- for ( var ix$1 = 0; ix$1 < gridX; ix$1 ++ ) {
-
- var a = numberOfVertices + ix$1 + gridX1 * iy$1;
- var b = numberOfVertices + ix$1 + gridX1 * ( iy$1 + 1 );
- var c = numberOfVertices + ( ix$1 + 1 ) + gridX1 * ( iy$1 + 1 );
- var d = numberOfVertices + ( ix$1 + 1 ) + gridX1 * iy$1;
-
- // faces
-
- indices.push( a, b, d );
- indices.push( b, c, d );
-
- // increase counter
-
- groupCount += 6;
-
- }
-
- }
-
- // add a group to the geometry. this will ensure multi material support
-
- scope.addGroup( groupStart, groupCount, materialIndex );
-
- // calculate new start value for groups
-
- groupStart += groupCount;
-
- // update total number of vertices
-
- numberOfVertices += vertexCounter;
-
- }
-
- }
-
- if ( BufferGeometry ) BoxBufferGeometry.__proto__ = BufferGeometry;
- BoxBufferGeometry.prototype = Object.create( BufferGeometry && BufferGeometry.prototype );
- BoxBufferGeometry.prototype.constructor = BoxBufferGeometry;
-
- return BoxBufferGeometry;
- }(BufferGeometry));
-
- /**
- * Uniform Utilities
- */
-
- function cloneUniforms( src ) {
-
- var dst = {};
-
- for ( var u in src ) {
-
- dst[ u ] = {};
-
- for ( var p in src[ u ] ) {
-
- var property = src[ u ][ p ];
-
- if ( property && ( property.isColor ||
- property.isMatrix3 || property.isMatrix4 ||
- property.isVector2 || property.isVector3 || property.isVector4 ||
- property.isTexture ) ) {
-
- dst[ u ][ p ] = property.clone();
-
- } else if ( Array.isArray( property ) ) {
-
- dst[ u ][ p ] = property.slice();
-
- } else {
-
- dst[ u ][ p ] = property;
-
- }
-
- }
-
- }
-
- return dst;
-
- }
-
- function mergeUniforms( uniforms ) {
-
- var merged = {};
-
- for ( var u = 0; u < uniforms.length; u ++ ) {
-
- var tmp = cloneUniforms( uniforms[ u ] );
-
- for ( var p in tmp ) {
-
- merged[ p ] = tmp[ p ];
-
- }
-
- }
-
- return merged;
-
- }
-
- // Legacy
-
- var UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms };
-
- var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}";
-
- var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}";
-
- /**
- * @author alteredq / http://alteredqualia.com/
- *
- * parameters = {
- * defines: { "label" : "value" },
- * uniforms: { "parameter1": { value: 1.0 }, "parameter2": { value2: 2 } },
- *
- * fragmentShader: ,
- * vertexShader: ,
- *
- * wireframe: ,
- * wireframeLinewidth: ,
- *
- * lights: ,
- *
- * skinning: ,
- * morphTargets: ,
- * morphNormals:
- * }
- */
-
- function ShaderMaterial( parameters ) {
-
- Material.call( this );
-
- this.type = 'ShaderMaterial';
-
- this.defines = {};
- this.uniforms = {};
-
- this.vertexShader = default_vertex;
- this.fragmentShader = default_fragment;
-
- this.linewidth = 1;
-
- this.wireframe = false;
- this.wireframeLinewidth = 1;
-
- this.fog = false; // set to use scene fog
- this.lights = false; // set to use scene lights
- this.clipping = false; // set to use user-defined clipping planes
-
- this.skinning = false; // set to use skinning attribute streams
- this.morphTargets = false; // set to use morph targets
- this.morphNormals = false; // set to use morph normals
-
- this.extensions = {
- derivatives: false, // set to use derivatives
- fragDepth: false, // set to use fragment depth values
- drawBuffers: false, // set to use draw buffers
- shaderTextureLOD: false // set to use shader texture LOD
- };
-
- // When rendered geometry doesn't include these attributes but the material does,
- // use these default values in WebGL. This avoids errors when buffer data is missing.
- this.defaultAttributeValues = {
- 'color': [ 1, 1, 1 ],
- 'uv': [ 0, 0 ],
- 'uv2': [ 0, 0 ]
- };
-
- this.index0AttributeName = undefined;
- this.uniformsNeedUpdate = false;
-
- if ( parameters !== undefined ) {
-
- if ( parameters.attributes !== undefined ) {
-
- console.error( 'THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.' );
-
- }
-
- this.setValues( parameters );
-
- }
-
- }
-
- ShaderMaterial.prototype = Object.create( Material.prototype );
- ShaderMaterial.prototype.constructor = ShaderMaterial;
-
- ShaderMaterial.prototype.isShaderMaterial = true;
-
- ShaderMaterial.prototype.copy = function ( source ) {
-
- Material.prototype.copy.call( this, source );
-
- this.fragmentShader = source.fragmentShader;
- this.vertexShader = source.vertexShader;
-
- this.uniforms = cloneUniforms( source.uniforms );
-
- this.defines = Object.assign( {}, source.defines );
-
- this.wireframe = source.wireframe;
- this.wireframeLinewidth = source.wireframeLinewidth;
-
- this.lights = source.lights;
- this.clipping = source.clipping;
-
- this.skinning = source.skinning;
-
- this.morphTargets = source.morphTargets;
- this.morphNormals = source.morphNormals;
-
- this.extensions = Object.assign( {}, source.extensions );
-
- return this;
-
- };
-
- ShaderMaterial.prototype.toJSON = function ( meta ) {
-
- var data = Material.prototype.toJSON.call( this, meta );
-
- data.uniforms = {};
-
- for ( var name in this.uniforms ) {
-
- var uniform = this.uniforms[ name ];
- var value = uniform.value;
-
- if ( value && value.isTexture ) {
-
- data.uniforms[ name ] = {
- type: 't',
- value: value.toJSON( meta ).uuid
- };
-
- } else if ( value && value.isColor ) {
-
- data.uniforms[ name ] = {
- type: 'c',
- value: value.getHex()
- };
-
- } else if ( value && value.isVector2 ) {
-
- data.uniforms[ name ] = {
- type: 'v2',
- value: value.toArray()
- };
-
- } else if ( value && value.isVector3 ) {
-
- data.uniforms[ name ] = {
- type: 'v3',
- value: value.toArray()
- };
-
- } else if ( value && value.isVector4 ) {
-
- data.uniforms[ name ] = {
- type: 'v4',
- value: value.toArray()
- };
-
- } else if ( value && value.isMatrix3 ) {
-
- data.uniforms[ name ] = {
- type: 'm3',
- value: value.toArray()
- };
-
- } else if ( value && value.isMatrix4 ) {
-
- data.uniforms[ name ] = {
- type: 'm4',
- value: value.toArray()
- };
-
- } else {
-
- data.uniforms[ name ] = {
- value: value
- };
-
- // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far
-
- }
-
- }
-
- if ( Object.keys( this.defines ).length > 0 ) { data.defines = this.defines; }
-
- data.vertexShader = this.vertexShader;
- data.fragmentShader = this.fragmentShader;
-
- var extensions = {};
-
- for ( var key in this.extensions ) {
-
- if ( this.extensions[ key ] === true ) { extensions[ key ] = true; }
-
- }
-
- if ( Object.keys( extensions ).length > 0 ) { data.extensions = extensions; }
-
- return data;
-
- };
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author mikael emtinger / http://gomo.se/
- * @author WestLangley / http://github.com/WestLangley
- */
-
- function Camera() {
-
- Object3D.call( this );
-
- this.type = 'Camera';
-
- this.matrixWorldInverse = new Matrix4();
-
- this.projectionMatrix = new Matrix4();
- this.projectionMatrixInverse = new Matrix4();
-
- }
-
- Camera.prototype = Object.assign( Object.create( Object3D.prototype ), {
-
- constructor: Camera,
-
- isCamera: true,
-
- copy: function ( source, recursive ) {
-
- Object3D.prototype.copy.call( this, source, recursive );
-
- this.matrixWorldInverse.copy( source.matrixWorldInverse );
-
- this.projectionMatrix.copy( source.projectionMatrix );
- this.projectionMatrixInverse.copy( source.projectionMatrixInverse );
-
- return this;
-
- },
-
- getWorldDirection: function ( target ) {
-
- if ( target === undefined ) {
-
- console.warn( 'THREE.Camera: .getWorldDirection() target is now required' );
- target = new Vector3();
-
- }
-
- this.updateMatrixWorld( true );
-
- var e = this.matrixWorld.elements;
-
- return target.set( - e[ 8 ], - e[ 9 ], - e[ 10 ] ).normalize();
-
- },
-
- updateMatrixWorld: function ( force ) {
-
- Object3D.prototype.updateMatrixWorld.call( this, force );
-
- this.matrixWorldInverse.getInverse( this.matrixWorld );
-
- },
-
- updateWorldMatrix: function ( updateParents, updateChildren ) {
-
- Object3D.prototype.updateWorldMatrix.call( this, updateParents, updateChildren );
-
- this.matrixWorldInverse.getInverse( this.matrixWorld );
-
- },
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- }
-
- } );
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author greggman / http://games.greggman.com/
- * @author zz85 / http://www.lab4games.net/zz85/blog
- * @author tschw
- */
-
- function PerspectiveCamera( fov, aspect, near, far ) {
-
- Camera.call( this );
-
- this.type = 'PerspectiveCamera';
-
- this.fov = fov !== undefined ? fov : 50;
- this.zoom = 1;
-
- this.near = near !== undefined ? near : 0.1;
- this.far = far !== undefined ? far : 2000;
- this.focus = 10;
-
- this.aspect = aspect !== undefined ? aspect : 1;
- this.view = null;
-
- this.filmGauge = 35; // width of the film (default in millimeters)
- this.filmOffset = 0; // horizontal film offset (same unit as gauge)
-
- this.updateProjectionMatrix();
-
- }
-
- PerspectiveCamera.prototype = Object.assign( Object.create( Camera.prototype ), {
-
- constructor: PerspectiveCamera,
-
- isPerspectiveCamera: true,
-
- copy: function ( source, recursive ) {
-
- Camera.prototype.copy.call( this, source, recursive );
-
- this.fov = source.fov;
- this.zoom = source.zoom;
-
- this.near = source.near;
- this.far = source.far;
- this.focus = source.focus;
-
- this.aspect = source.aspect;
- this.view = source.view === null ? null : Object.assign( {}, source.view );
-
- this.filmGauge = source.filmGauge;
- this.filmOffset = source.filmOffset;
-
- return this;
-
- },
-
- /**
- * Sets the FOV by focal length in respect to the current .filmGauge.
- *
- * The default film gauge is 35, so that the focal length can be specified for
- * a 35mm (full frame) camera.
- *
- * Values for focal length and film gauge must have the same unit.
- */
- setFocalLength: function ( focalLength ) {
-
- // see http://www.bobatkins.com/photography/technical/field_of_view.html
- var vExtentSlope = 0.5 * this.getFilmHeight() / focalLength;
-
- this.fov = MathUtils.RAD2DEG * 2 * Math.atan( vExtentSlope );
- this.updateProjectionMatrix();
-
- },
-
- /**
- * Calculates the focal length from the current .fov and .filmGauge.
- */
- getFocalLength: function () {
-
- var vExtentSlope = Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov );
-
- return 0.5 * this.getFilmHeight() / vExtentSlope;
-
- },
-
- getEffectiveFOV: function () {
-
- return MathUtils.RAD2DEG * 2 * Math.atan(
- Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) / this.zoom );
-
- },
-
- getFilmWidth: function () {
-
- // film not completely covered in portrait format (aspect < 1)
- return this.filmGauge * Math.min( this.aspect, 1 );
-
- },
-
- getFilmHeight: function () {
-
- // film not completely covered in landscape format (aspect > 1)
- return this.filmGauge / Math.max( this.aspect, 1 );
-
- },
-
- /**
- * Sets an offset in a larger frustum. This is useful for multi-window or
- * multi-monitor/multi-machine setups.
- *
- * For example, if you have 3x2 monitors and each monitor is 1920x1080 and
- * the monitors are in grid like this
- *
- * +---+---+---+
- * | A | B | C |
- * +---+---+---+
- * | D | E | F |
- * +---+---+---+
- *
- * then for each monitor you would call it like this
- *
- * var w = 1920;
- * var h = 1080;
- * var fullWidth = w * 3;
- * var fullHeight = h * 2;
- *
- * --A--
- * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );
- * --B--
- * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );
- * --C--
- * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );
- * --D--
- * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );
- * --E--
- * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );
- * --F--
- * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );
- *
- * Note there is no reason monitors have to be the same size or in a grid.
- */
- setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {
-
- this.aspect = fullWidth / fullHeight;
-
- if ( this.view === null ) {
-
- this.view = {
- enabled: true,
- fullWidth: 1,
- fullHeight: 1,
- offsetX: 0,
- offsetY: 0,
- width: 1,
- height: 1
- };
-
- }
-
- this.view.enabled = true;
- this.view.fullWidth = fullWidth;
- this.view.fullHeight = fullHeight;
- this.view.offsetX = x;
- this.view.offsetY = y;
- this.view.width = width;
- this.view.height = height;
-
- this.updateProjectionMatrix();
-
- },
-
- clearViewOffset: function () {
-
- if ( this.view !== null ) {
-
- this.view.enabled = false;
-
- }
-
- this.updateProjectionMatrix();
-
- },
-
- updateProjectionMatrix: function () {
-
- var near = this.near,
- top = near * Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) / this.zoom,
- height = 2 * top,
- width = this.aspect * height,
- left = - 0.5 * width,
- view = this.view;
-
- if ( this.view !== null && this.view.enabled ) {
-
- var fullWidth = view.fullWidth,
- fullHeight = view.fullHeight;
-
- left += view.offsetX * width / fullWidth;
- top -= view.offsetY * height / fullHeight;
- width *= view.width / fullWidth;
- height *= view.height / fullHeight;
-
- }
-
- var skew = this.filmOffset;
- if ( skew !== 0 ) { left += near * skew / this.getFilmWidth(); }
-
- this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far );
-
- this.projectionMatrixInverse.getInverse( this.projectionMatrix );
-
- },
-
- toJSON: function ( meta ) {
-
- var data = Object3D.prototype.toJSON.call( this, meta );
-
- data.object.fov = this.fov;
- data.object.zoom = this.zoom;
-
- data.object.near = this.near;
- data.object.far = this.far;
- data.object.focus = this.focus;
-
- data.object.aspect = this.aspect;
-
- if ( this.view !== null ) { data.object.view = Object.assign( {}, this.view ); }
-
- data.object.filmGauge = this.filmGauge;
- data.object.filmOffset = this.filmOffset;
-
- return data;
-
- }
-
- } );
-
- /**
- * Camera for rendering cube maps
- * - renders scene into axis-aligned cube
- *
- * @author alteredq / http://alteredqualia.com/
- */
-
- var fov = 90, aspect = 1;
-
- function CubeCamera( near, far, renderTarget ) {
-
- Object3D.call( this );
-
- this.type = 'CubeCamera';
-
- if ( renderTarget.isWebGLCubeRenderTarget !== true ) {
-
- console.error( 'THREE.CubeCamera: The constructor now expects an instance of WebGLCubeRenderTarget as third parameter.' );
- return;
-
- }
-
- this.renderTarget = renderTarget;
-
- var cameraPX = new PerspectiveCamera( fov, aspect, near, far );
- cameraPX.layers = this.layers;
- cameraPX.up.set( 0, - 1, 0 );
- cameraPX.lookAt( new Vector3( 1, 0, 0 ) );
- this.add( cameraPX );
-
- var cameraNX = new PerspectiveCamera( fov, aspect, near, far );
- cameraNX.layers = this.layers;
- cameraNX.up.set( 0, - 1, 0 );
- cameraNX.lookAt( new Vector3( - 1, 0, 0 ) );
- this.add( cameraNX );
-
- var cameraPY = new PerspectiveCamera( fov, aspect, near, far );
- cameraPY.layers = this.layers;
- cameraPY.up.set( 0, 0, 1 );
- cameraPY.lookAt( new Vector3( 0, 1, 0 ) );
- this.add( cameraPY );
-
- var cameraNY = new PerspectiveCamera( fov, aspect, near, far );
- cameraNY.layers = this.layers;
- cameraNY.up.set( 0, 0, - 1 );
- cameraNY.lookAt( new Vector3( 0, - 1, 0 ) );
- this.add( cameraNY );
-
- var cameraPZ = new PerspectiveCamera( fov, aspect, near, far );
- cameraPZ.layers = this.layers;
- cameraPZ.up.set( 0, - 1, 0 );
- cameraPZ.lookAt( new Vector3( 0, 0, 1 ) );
- this.add( cameraPZ );
-
- var cameraNZ = new PerspectiveCamera( fov, aspect, near, far );
- cameraNZ.layers = this.layers;
- cameraNZ.up.set( 0, - 1, 0 );
- cameraNZ.lookAt( new Vector3( 0, 0, - 1 ) );
- this.add( cameraNZ );
-
- this.update = function ( renderer, scene ) {
-
- if ( this.parent === null ) { this.updateMatrixWorld(); }
-
- var currentXrEnabled = renderer.xr.enabled;
- var currentRenderTarget = renderer.getRenderTarget();
-
- renderer.xr.enabled = false;
-
- var generateMipmaps = renderTarget.texture.generateMipmaps;
-
- renderTarget.texture.generateMipmaps = false;
-
- renderer.setRenderTarget( renderTarget, 0 );
- renderer.render( scene, cameraPX );
-
- renderer.setRenderTarget( renderTarget, 1 );
- renderer.render( scene, cameraNX );
-
- renderer.setRenderTarget( renderTarget, 2 );
- renderer.render( scene, cameraPY );
-
- renderer.setRenderTarget( renderTarget, 3 );
- renderer.render( scene, cameraNY );
-
- renderer.setRenderTarget( renderTarget, 4 );
- renderer.render( scene, cameraPZ );
-
- renderTarget.texture.generateMipmaps = generateMipmaps;
-
- renderer.setRenderTarget( renderTarget, 5 );
- renderer.render( scene, cameraNZ );
-
- renderer.setRenderTarget( currentRenderTarget );
-
- renderer.xr.enabled = currentXrEnabled;
-
- };
-
- this.clear = function ( renderer, color, depth, stencil ) {
-
- var currentRenderTarget = renderer.getRenderTarget();
-
- for ( var i = 0; i < 6; i ++ ) {
-
- renderer.setRenderTarget( renderTarget, i );
-
- renderer.clear( color, depth, stencil );
-
- }
-
- renderer.setRenderTarget( currentRenderTarget );
-
- };
-
- }
-
- CubeCamera.prototype = Object.create( Object3D.prototype );
- CubeCamera.prototype.constructor = CubeCamera;
-
- /**
- * @author alteredq / http://alteredqualia.com
- * @author WestLangley / http://github.com/WestLangley
- */
-
- function WebGLCubeRenderTarget( size, options, dummy ) {
-
- if ( Number.isInteger( options ) ) {
-
- console.warn( 'THREE.WebGLCubeRenderTarget: constructor signature is now WebGLCubeRenderTarget( size, options )' );
-
- options = dummy;
-
- }
-
- WebGLRenderTarget.call( this, size, size, options );
-
- }
-
- WebGLCubeRenderTarget.prototype = Object.create( WebGLRenderTarget.prototype );
- WebGLCubeRenderTarget.prototype.constructor = WebGLCubeRenderTarget;
-
- WebGLCubeRenderTarget.prototype.isWebGLCubeRenderTarget = true;
-
- WebGLCubeRenderTarget.prototype.fromEquirectangularTexture = function ( renderer, texture ) {
-
- this.texture.type = texture.type;
- this.texture.format = texture.format;
- this.texture.encoding = texture.encoding;
-
- var scene = new Scene();
-
- var shader = {
-
- uniforms: {
- tEquirect: { value: null },
- },
-
- vertexShader: [
-
- "varying vec3 vWorldDirection;",
-
- "vec3 transformDirection( in vec3 dir, in mat4 matrix ) {",
-
- " return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );",
-
- "}",
-
- "void main() {",
-
- " vWorldDirection = transformDirection( position, modelMatrix );",
-
- " #include ",
- " #include ",
-
- "}"
-
- ].join( '\n' ),
-
- fragmentShader: [
-
- "uniform sampler2D tEquirect;",
-
- "varying vec3 vWorldDirection;",
-
- "#include ",
-
- "void main() {",
-
- " vec3 direction = normalize( vWorldDirection );",
-
- " vec2 sampleUV = equirectUv( direction );",
-
- " gl_FragColor = texture2D( tEquirect, sampleUV );",
-
- "}"
-
- ].join( '\n' ),
- };
-
- var material = new ShaderMaterial( {
-
- type: 'CubemapFromEquirect',
-
- uniforms: cloneUniforms( shader.uniforms ),
- vertexShader: shader.vertexShader,
- fragmentShader: shader.fragmentShader,
- side: BackSide,
- blending: NoBlending
-
- } );
-
- material.uniforms.tEquirect.value = texture;
-
- var mesh = new Mesh( new BoxBufferGeometry( 5, 5, 5 ), material );
-
- scene.add( mesh );
-
- var camera = new CubeCamera( 1, 10, this );
- camera.update( renderer, scene );
-
- mesh.geometry.dispose();
- mesh.material.dispose();
-
- return this;
-
- };
-
- /**
- * @author alteredq / http://alteredqualia.com/
- */
-
- function DataTexture( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
-
- Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
-
- this.image = { data: data || null, width: width || 1, height: height || 1 };
-
- this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
- this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
-
- this.generateMipmaps = false;
- this.flipY = false;
- this.unpackAlignment = 1;
-
- this.needsUpdate = true;
-
- }
-
- DataTexture.prototype = Object.create( Texture.prototype );
- DataTexture.prototype.constructor = DataTexture;
-
- DataTexture.prototype.isDataTexture = true;
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author alteredq / http://alteredqualia.com/
- * @author bhouston / http://clara.io
- */
-
- var _sphere$1 = new Sphere();
- var _vector$5 = new Vector3();
-
- function Frustum( p0, p1, p2, p3, p4, p5 ) {
-
- this.planes = [
-
- ( p0 !== undefined ) ? p0 : new Plane(),
- ( p1 !== undefined ) ? p1 : new Plane(),
- ( p2 !== undefined ) ? p2 : new Plane(),
- ( p3 !== undefined ) ? p3 : new Plane(),
- ( p4 !== undefined ) ? p4 : new Plane(),
- ( p5 !== undefined ) ? p5 : new Plane()
-
- ];
-
- }
-
- Object.assign( Frustum.prototype, {
-
- set: function ( p0, p1, p2, p3, p4, p5 ) {
-
- var planes = this.planes;
-
- planes[ 0 ].copy( p0 );
- planes[ 1 ].copy( p1 );
- planes[ 2 ].copy( p2 );
- planes[ 3 ].copy( p3 );
- planes[ 4 ].copy( p4 );
- planes[ 5 ].copy( p5 );
-
- return this;
-
- },
-
- clone: function () {
-
- return new this.constructor().copy( this );
-
- },
-
- copy: function ( frustum ) {
-
- var planes = this.planes;
-
- for ( var i = 0; i < 6; i ++ ) {
-
- planes[ i ].copy( frustum.planes[ i ] );
-
- }
-
- return this;
-
- },
-
- setFromProjectionMatrix: function ( m ) {
-
- var planes = this.planes;
- var me = m.elements;
- var me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ];
- var me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ];
- var me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ];
- var me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ];
-
- planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();
- planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();
- planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();
- planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();
- planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();
- planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();
-
- return this;
-
- },
-
- intersectsObject: function ( object ) {
-
- var geometry = object.geometry;
-
- if ( geometry.boundingSphere === null ) { geometry.computeBoundingSphere(); }
-
- _sphere$1.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld );
-
- return this.intersectsSphere( _sphere$1 );
-
- },
-
- intersectsSprite: function ( sprite ) {
-
- _sphere$1.center.set( 0, 0, 0 );
- _sphere$1.radius = 0.7071067811865476;
- _sphere$1.applyMatrix4( sprite.matrixWorld );
-
- return this.intersectsSphere( _sphere$1 );
-
- },
-
- intersectsSphere: function ( sphere ) {
-
- var planes = this.planes;
- var center = sphere.center;
- var negRadius = - sphere.radius;
-
- for ( var i = 0; i < 6; i ++ ) {
-
- var distance = planes[ i ].distanceToPoint( center );
-
- if ( distance < negRadius ) {
-
- return false;
-
- }
-
- }
-
- return true;
-
- },
-
- intersectsBox: function ( box ) {
-
- var planes = this.planes;
-
- for ( var i = 0; i < 6; i ++ ) {
-
- var plane = planes[ i ];
-
- // corner at max distance
-
- _vector$5.x = plane.normal.x > 0 ? box.max.x : box.min.x;
- _vector$5.y = plane.normal.y > 0 ? box.max.y : box.min.y;
- _vector$5.z = plane.normal.z > 0 ? box.max.z : box.min.z;
-
- if ( plane.distanceToPoint( _vector$5 ) < 0 ) {
-
- return false;
-
- }
-
- }
-
- return true;
-
- },
-
- containsPoint: function ( point ) {
-
- var planes = this.planes;
-
- for ( var i = 0; i < 6; i ++ ) {
-
- if ( planes[ i ].distanceToPoint( point ) < 0 ) {
-
- return false;
-
- }
-
- }
-
- return true;
-
- }
-
- } );
-
- /**
- * Uniforms library for shared webgl shaders
- */
-
- var UniformsLib = {
-
- common: {
-
- diffuse: { value: new Color( 0xeeeeee ) },
- opacity: { value: 1.0 },
-
- map: { value: null },
- uvTransform: { value: new Matrix3() },
- uv2Transform: { value: new Matrix3() },
-
- alphaMap: { value: null },
-
- },
-
- specularmap: {
-
- specularMap: { value: null },
-
- },
-
- envmap: {
-
- envMap: { value: null },
- flipEnvMap: { value: - 1 },
- reflectivity: { value: 1.0 },
- refractionRatio: { value: 0.98 },
- maxMipLevel: { value: 0 }
-
- },
-
- aomap: {
-
- aoMap: { value: null },
- aoMapIntensity: { value: 1 }
-
- },
-
- lightmap: {
-
- lightMap: { value: null },
- lightMapIntensity: { value: 1 }
-
- },
-
- emissivemap: {
-
- emissiveMap: { value: null }
-
- },
-
- bumpmap: {
-
- bumpMap: { value: null },
- bumpScale: { value: 1 }
-
- },
-
- normalmap: {
-
- normalMap: { value: null },
- normalScale: { value: new Vector2( 1, 1 ) }
-
- },
-
- displacementmap: {
-
- displacementMap: { value: null },
- displacementScale: { value: 1 },
- displacementBias: { value: 0 }
-
- },
-
- roughnessmap: {
-
- roughnessMap: { value: null }
-
- },
-
- metalnessmap: {
-
- metalnessMap: { value: null }
-
- },
-
- gradientmap: {
-
- gradientMap: { value: null }
-
- },
-
- fog: {
-
- fogDensity: { value: 0.00025 },
- fogNear: { value: 1 },
- fogFar: { value: 2000 },
- fogColor: { value: new Color( 0xffffff ) }
-
- },
-
- lights: {
-
- ambientLightColor: { value: [] },
-
- lightProbe: { value: [] },
-
- directionalLights: { value: [], properties: {
- direction: {},
- color: {}
- } },
-
- directionalLightShadows: { value: [], properties: {
- shadowBias: {},
- shadowRadius: {},
- shadowMapSize: {}
- } },
-
- directionalShadowMap: { value: [] },
- directionalShadowMatrix: { value: [] },
-
- spotLights: { value: [], properties: {
- color: {},
- position: {},
- direction: {},
- distance: {},
- coneCos: {},
- penumbraCos: {},
- decay: {}
- } },
-
- spotLightShadows: { value: [], properties: {
- shadowBias: {},
- shadowRadius: {},
- shadowMapSize: {}
- } },
-
- spotShadowMap: { value: [] },
- spotShadowMatrix: { value: [] },
-
- pointLights: { value: [], properties: {
- color: {},
- position: {},
- decay: {},
- distance: {}
- } },
-
- pointLightShadows: { value: [], properties: {
- shadowBias: {},
- shadowRadius: {},
- shadowMapSize: {},
- shadowCameraNear: {},
- shadowCameraFar: {}
- } },
-
- pointShadowMap: { value: [] },
- pointShadowMatrix: { value: [] },
-
- hemisphereLights: { value: [], properties: {
- direction: {},
- skyColor: {},
- groundColor: {}
- } },
-
- // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src
- rectAreaLights: { value: [], properties: {
- color: {},
- position: {},
- width: {},
- height: {}
- } }
-
- },
-
- points: {
-
- diffuse: { value: new Color( 0xeeeeee ) },
- opacity: { value: 1.0 },
- size: { value: 1.0 },
- scale: { value: 1.0 },
- map: { value: null },
- alphaMap: { value: null },
- uvTransform: { value: new Matrix3() }
-
- },
-
- sprite: {
-
- diffuse: { value: new Color( 0xeeeeee ) },
- opacity: { value: 1.0 },
- center: { value: new Vector2( 0.5, 0.5 ) },
- rotation: { value: 0.0 },
- map: { value: null },
- alphaMap: { value: null },
- uvTransform: { value: new Matrix3() }
-
- }
-
- };
-
- /**
- * @author mrdoob / http://mrdoob.com/
- */
-
- function WebGLAnimation() {
-
- var context = null;
- var isAnimating = false;
- var animationLoop = null;
-
- function onAnimationFrame( time, frame ) {
-
- if ( isAnimating === false ) { return; }
-
- animationLoop( time, frame );
-
- context.requestAnimationFrame( onAnimationFrame );
-
- }
-
- return {
-
- start: function () {
-
- if ( isAnimating === true ) { return; }
- if ( animationLoop === null ) { return; }
-
- context.requestAnimationFrame( onAnimationFrame );
-
- isAnimating = true;
-
- },
-
- stop: function () {
-
- isAnimating = false;
-
- },
-
- setAnimationLoop: function ( callback ) {
-
- animationLoop = callback;
-
- },
-
- setContext: function ( value ) {
-
- context = value;
-
- }
-
- };
-
- }
-
- /**
- * @author mrdoob / http://mrdoob.com/
- */
-
- function WebGLAttributes( gl, capabilities ) {
-
- var isWebGL2 = capabilities.isWebGL2;
-
- var buffers = new WeakMap();
-
- function createBuffer( attribute, bufferType ) {
-
- var array = attribute.array;
- var usage = attribute.usage;
-
- var buffer = gl.createBuffer();
-
- gl.bindBuffer( bufferType, buffer );
- gl.bufferData( bufferType, array, usage );
-
- attribute.onUploadCallback();
-
- var type = 5126;
-
- if ( array instanceof Float32Array ) {
-
- type = 5126;
-
- } else if ( array instanceof Float64Array ) {
-
- console.warn( 'THREE.WebGLAttributes: Unsupported data buffer format: Float64Array.' );
-
- } else if ( array instanceof Uint16Array ) {
-
- type = 5123;
-
- } else if ( array instanceof Int16Array ) {
-
- type = 5122;
-
- } else if ( array instanceof Uint32Array ) {
-
- type = 5125;
-
- } else if ( array instanceof Int32Array ) {
-
- type = 5124;
-
- } else if ( array instanceof Int8Array ) {
-
- type = 5120;
-
- } else if ( array instanceof Uint8Array ) {
-
- type = 5121;
-
- }
-
- return {
- buffer: buffer,
- type: type,
- bytesPerElement: array.BYTES_PER_ELEMENT,
- version: attribute.version
- };
-
- }
-
- function updateBuffer( buffer, attribute, bufferType ) {
-
- var array = attribute.array;
- var updateRange = attribute.updateRange;
-
- gl.bindBuffer( bufferType, buffer );
-
- if ( updateRange.count === - 1 ) {
-
- // Not using update ranges
-
- gl.bufferSubData( bufferType, 0, array );
-
- } else {
-
- if ( isWebGL2 ) {
-
- gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,
- array, updateRange.offset, updateRange.count );
-
- } else {
-
- gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,
- array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) );
-
- }
-
- updateRange.count = - 1; // reset range
-
- }
-
- }
-
- //
-
- function get( attribute ) {
-
- if ( attribute.isInterleavedBufferAttribute ) { attribute = attribute.data; }
-
- return buffers.get( attribute );
-
- }
-
- function remove( attribute ) {
-
- if ( attribute.isInterleavedBufferAttribute ) { attribute = attribute.data; }
-
- var data = buffers.get( attribute );
-
- if ( data ) {
-
- gl.deleteBuffer( data.buffer );
-
- buffers.delete( attribute );
-
- }
-
- }
-
- function update( attribute, bufferType ) {
-
- if ( attribute.isInterleavedBufferAttribute ) { attribute = attribute.data; }
-
- var data = buffers.get( attribute );
-
- if ( data === undefined ) {
-
- buffers.set( attribute, createBuffer( attribute, bufferType ) );
-
- } else if ( data.version < attribute.version ) {
-
- updateBuffer( data.buffer, attribute, bufferType );
-
- data.version = attribute.version;
-
- }
-
- }
-
- return {
-
- get: get,
- remove: remove,
- update: update
-
- };
-
- }
-
- /**
- * @author mrdoob / http://mrdoob.com/
- * @author Mugen87 / https://github.com/Mugen87
- */
-
- // PlaneGeometry
-
- function PlaneGeometry( width, height, widthSegments, heightSegments ) {
-
- Geometry.call( this );
-
- this.type = 'PlaneGeometry';
-
- this.parameters = {
- width: width,
- height: height,
- widthSegments: widthSegments,
- heightSegments: heightSegments
- };
-
- this.fromBufferGeometry( new PlaneBufferGeometry( width, height, widthSegments, heightSegments ) );
- this.mergeVertices();
-
- }
-
- PlaneGeometry.prototype = Object.create( Geometry.prototype );
- PlaneGeometry.prototype.constructor = PlaneGeometry;
-
- // PlaneBufferGeometry
-
- function PlaneBufferGeometry( width, height, widthSegments, heightSegments ) {
-
- BufferGeometry.call( this );
-
- this.type = 'PlaneBufferGeometry';
-
- this.parameters = {
- width: width,
- height: height,
- widthSegments: widthSegments,
- heightSegments: heightSegments
- };
-
- width = width || 1;
- height = height || 1;
-
- var width_half = width / 2;
- var height_half = height / 2;
-
- var gridX = Math.floor( widthSegments ) || 1;
- var gridY = Math.floor( heightSegments ) || 1;
-
- var gridX1 = gridX + 1;
- var gridY1 = gridY + 1;
-
- var segment_width = width / gridX;
- var segment_height = height / gridY;
-
- var ix, iy;
-
- // buffers
-
- var indices = [];
- var vertices = [];
- var normals = [];
- var uvs = [];
-
- // generate vertices, normals and uvs
-
- for ( iy = 0; iy < gridY1; iy ++ ) {
-
- var y = iy * segment_height - height_half;
-
- for ( ix = 0; ix < gridX1; ix ++ ) {
-
- var x = ix * segment_width - width_half;
-
- vertices.push( x, - y, 0 );
-
- normals.push( 0, 0, 1 );
-
- uvs.push( ix / gridX );
- uvs.push( 1 - ( iy / gridY ) );
-
- }
-
- }
-
- // indices
-
- for ( iy = 0; iy < gridY; iy ++ ) {
-
- for ( ix = 0; ix < gridX; ix ++ ) {
-
- var a = ix + gridX1 * iy;
- var b = ix + gridX1 * ( iy + 1 );
- var c = ( ix + 1 ) + gridX1 * ( iy + 1 );
- var d = ( ix + 1 ) + gridX1 * iy;
-
- // faces
-
- indices.push( a, b, d );
- indices.push( b, c, d );
-
- }
-
- }
-
- // build geometry
-
- this.setIndex( indices );
- this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
- this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
- this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
-
- }
-
- PlaneBufferGeometry.prototype = Object.create( BufferGeometry.prototype );
- PlaneBufferGeometry.prototype.constructor = PlaneBufferGeometry;
-
- var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\n#endif";
-
- var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif";
-
- var alphatest_fragment = "#ifdef ALPHATEST\n\tif ( diffuseColor.a < ALPHATEST ) discard;\n#endif";
-
- var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\n\t#endif\n#endif";
-
- var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif";
-
- var begin_vertex = "vec3 transformed = vec3( position );";
-
- var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif";
-
- var bsdfs = "vec2 integrateSpecularBRDF( const in float dotNV, const in float roughness ) {\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\treturn vec2( -1.04, 1.04 ) * a004 + r.zw;\n}\nfloat punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n#else\n\tif( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t}\n\treturn 1.0;\n#endif\n}\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\n\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\n}\nvec3 F_Schlick_RoughnessDependent( const in vec3 F0, const in float dotNV, const in float roughness ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotNV - 6.98316 ) * dotNV );\n\tvec3 Fr = max( vec3( 1.0 - roughness ), F0 ) - F0;\n\treturn Fr * fresnel + F0;\n}\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\treturn 1.0 / ( gl * gv );\n}\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( incidentLight.direction + viewDir );\n\tfloat dotNL = saturate( dot( normal, incidentLight.direction ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\tfloat D = D_GGX( alpha, dotNH );\n\treturn F * ( G * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nvec3 BRDF_Specular_GGX_Environment( const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\treturn specularColor * brdf.x + brdf.y;\n}\nvoid BRDF_Specular_Multiscattering_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tvec3 F = F_Schlick_RoughnessDependent( specularColor, dotNV, roughness );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\tvec3 FssEss = F * brdf.x + brdf.y;\n\tfloat Ess = brdf.x + brdf.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = specularColor + ( 1.0 - specularColor ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\n\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\n}\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\n\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie(float roughness, float NoH) {\n\tfloat invAlpha = 1.0 / roughness;\n\tfloat cos2h = NoH * NoH;\n\tfloat sin2h = max(1.0 - cos2h, 0.0078125);\treturn (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI);\n}\nfloat V_Neubelt(float NoV, float NoL) {\n\treturn saturate(1.0 / (4.0 * (NoL + NoV - NoL * NoV)));\n}\nvec3 BRDF_Specular_Sheen( const in float roughness, const in vec3 L, const in GeometricContext geometry, vec3 specularColor ) {\n\tvec3 N = geometry.normal;\n\tvec3 V = geometry.viewDir;\n\tvec3 H = normalize( V + L );\n\tfloat dotNH = saturate( dot( N, H ) );\n\treturn specularColor * D_Charlie( roughness, dotNH ) * V_Neubelt( dot(N, V), dot(N, L) );\n}\n#endif";
-
- var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\n\t\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\n\t\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 );\n\t\tfDet *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif";
-
- var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif";
-
- var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif";
-
- var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif";
-
- var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif";
-
- var color_fragment = "#ifdef USE_COLOR\n\tdiffuseColor.rgb *= vColor;\n#endif";
-
- var color_pars_fragment = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif";
-
- var color_pars_vertex = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif";
-
- var color_vertex = "#ifdef USE_COLOR\n\tvColor.xyz = color.xyz;\n#endif";
-
- var common = "#define PI 3.14159265359\n#define PI2 6.28318530718\n#define PI_HALF 1.5707963267949\n#define RECIPROCAL_PI 0.31830988618\n#define RECIPROCAL_PI2 0.15915494\n#define LOG2 1.442695\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement(a) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract(sin(sn) * c);\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat max3( vec3 v ) { return max( max( v.x, v.y ), v.z ); }\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\tfloat distance = dot( planeNormal, point - pointOnPlane );\n\treturn - distance * planeNormal + point;\n}\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn sign( dot( point - pointOnPlane, planeNormal ) );\n}\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat linearToRelativeLuminance( const in vec3 color ) {\n\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\n\treturn dot( weights, color.rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n return m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}";
-
- var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n#define cubeUV_maxMipLevel 8.0\n#define cubeUV_minMipLevel 4.0\n#define cubeUV_maxTileSize 256.0\n#define cubeUV_minTileSize 16.0\nfloat getFace(vec3 direction) {\n vec3 absDirection = abs(direction);\n float face = -1.0;\n if (absDirection.x > absDirection.z) {\n if (absDirection.x > absDirection.y)\n face = direction.x > 0.0 ? 0.0 : 3.0;\n else\n face = direction.y > 0.0 ? 1.0 : 4.0;\n } else {\n if (absDirection.z > absDirection.y)\n face = direction.z > 0.0 ? 2.0 : 5.0;\n else\n face = direction.y > 0.0 ? 1.0 : 4.0;\n }\n return face;\n}\nvec2 getUV(vec3 direction, float face) {\n vec2 uv;\n if (face == 0.0) {\n uv = vec2(direction.z, direction.y) / abs(direction.x); } else if (face == 1.0) {\n uv = vec2(-direction.x, -direction.z) / abs(direction.y); } else if (face == 2.0) {\n uv = vec2(-direction.x, direction.y) / abs(direction.z); } else if (face == 3.0) {\n uv = vec2(-direction.z, direction.y) / abs(direction.x); } else if (face == 4.0) {\n uv = vec2(-direction.x, direction.z) / abs(direction.y); } else {\n uv = vec2(direction.x, direction.y) / abs(direction.z); }\n return 0.5 * (uv + 1.0);\n}\nvec3 bilinearCubeUV(sampler2D envMap, vec3 direction, float mipInt) {\n float face = getFace(direction);\n float filterInt = max(cubeUV_minMipLevel - mipInt, 0.0);\n mipInt = max(mipInt, cubeUV_minMipLevel);\n float faceSize = exp2(mipInt);\n float texelSize = 1.0 / (3.0 * cubeUV_maxTileSize);\n vec2 uv = getUV(direction, face) * (faceSize - 1.0);\n vec2 f = fract(uv);\n uv += 0.5 - f;\n if (face > 2.0) {\n uv.y += faceSize;\n face -= 3.0;\n }\n uv.x += face * faceSize;\n if(mipInt < cubeUV_maxMipLevel){\n uv.y += 2.0 * cubeUV_maxTileSize;\n }\n uv.y += filterInt * 2.0 * cubeUV_minTileSize;\n uv.x += 3.0 * max(0.0, cubeUV_maxTileSize - 2.0 * faceSize);\n uv *= texelSize;\n vec3 tl = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n uv.x += texelSize;\n vec3 tr = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n uv.y += texelSize;\n vec3 br = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n uv.x -= texelSize;\n vec3 bl = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n vec3 tm = mix(tl, tr, f.x);\n vec3 bm = mix(bl, br, f.x);\n return mix(tm, bm, f.y);\n}\n#define r0 1.0\n#define v0 0.339\n#define m0 -2.0\n#define r1 0.8\n#define v1 0.276\n#define m1 -1.0\n#define r4 0.4\n#define v4 0.046\n#define m4 2.0\n#define r5 0.305\n#define v5 0.016\n#define m5 3.0\n#define r6 0.21\n#define v6 0.0038\n#define m6 4.0\nfloat roughnessToMip(float roughness) {\n float mip = 0.0;\n if (roughness >= r1) {\n mip = (r0 - roughness) * (m1 - m0) / (r0 - r1) + m0;\n } else if (roughness >= r4) {\n mip = (r1 - roughness) * (m4 - m1) / (r1 - r4) + m1;\n } else if (roughness >= r5) {\n mip = (r4 - roughness) * (m5 - m4) / (r4 - r5) + m4;\n } else if (roughness >= r6) {\n mip = (r5 - roughness) * (m6 - m5) / (r5 - r6) + m5;\n } else {\n mip = -2.0 * log2(1.16 * roughness); }\n return mip;\n}\nvec4 textureCubeUV(sampler2D envMap, vec3 sampleDir, float roughness) {\n float mip = clamp(roughnessToMip(roughness), m0, cubeUV_maxMipLevel);\n float mipF = fract(mip);\n float mipInt = floor(mip);\n vec3 color0 = bilinearCubeUV(envMap, sampleDir, mipInt);\n if (mipF == 0.0) {\n return vec4(color0, 1.0);\n } else {\n vec3 color1 = bilinearCubeUV(envMap, sampleDir, mipInt + 1.0);\n return vec4(mix(color0, color1, mipF), 1.0);\n }\n}\n#endif";
-
- var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif";
-
- var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif";
-
- var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vUv ).x * displacementScale + displacementBias );\n#endif";
-
- var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif";
-
- var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif";
-
- var encodings_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );";
-
- var encodings_pars_fragment = "\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( gammaFactor ) ), value.a );\n}\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a );\n}\nvec4 sRGBToLinear( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}\nvec4 RGBEToLinear( in vec4 value ) {\n\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\n}\nvec4 LinearToRGBE( in vec4 value ) {\n\tfloat maxComponent = max( max( value.r, value.g ), value.b );\n\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\n\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\n}\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * value.a * maxRange, 1.0 );\n}\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat M = clamp( maxRGB / maxRange, 0.0, 1.0 );\n\tM = ceil( M * 255.0 ) / 255.0;\n\treturn vec4( value.rgb / ( M * maxRange ), M );\n}\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\n}\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat D = max( maxRange / maxRGB, 1.0 );\n\tD = clamp( floor( D ) / 255.0, 0.0, 1.0 );\n\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\n}\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\nvec4 LinearToLogLuv( in vec4 value ) {\n\tvec3 Xp_Y_XYZp = cLogLuvM * value.rgb;\n\tXp_Y_XYZp = max( Xp_Y_XYZp, vec3( 1e-6, 1e-6, 1e-6 ) );\n\tvec4 vResult;\n\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\n\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\n\tvResult.w = fract( Le );\n\tvResult.z = ( Le - ( floor( vResult.w * 255.0 ) ) / 255.0 ) / 255.0;\n\treturn vResult;\n}\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\nvec4 LogLuvToLinear( in vec4 value ) {\n\tfloat Le = value.z * 255.0 + value.w;\n\tvec3 Xp_Y_XYZp;\n\tXp_Y_XYZp.y = exp2( ( Le - 127.0 ) / 2.0 );\n\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\n\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\n\tvec3 vRGB = cLogLuvInverseM * Xp_Y_XYZp.rgb;\n\treturn vec4( max( vRGB, 0.0 ), 1.0 );\n}";
-
- var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\t\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 envColor = textureCubeUV( envMap, reflectVec, 0.0 );\n\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\treflectVec = normalize( reflectVec );\n\t\tvec2 sampleUV = equirectUv( reflectVec );\n\t\tvec4 envColor = texture2D( envMap, sampleUV );\n\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\treflectVec = normalize( reflectVec );\n\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0, 0.0, 1.0 ) );\n\t\tvec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifndef ENVMAP_TYPE_CUBE_UV\n\t\tenvColor = envMapTexelToLinear( envColor );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif";
-
- var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform int maxMipLevel;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif";
-
- var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif";
-
- var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) ||defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif";
-
- var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) { \n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif";
-
- var fog_vertex = "#ifdef USE_FOG\n\tfogDepth = -mvPosition.z;\n#endif";
-
- var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float fogDepth;\n#endif";
-
- var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif";
-
- var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float fogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif";
-
- var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn texture2D( gradientMap, coord ).rgb;\n\t#else\n\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t#endif\n}";
-
- var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\treflectedLight.indirectDiffuse += PI * lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n#endif";
-
- var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif";
-
- var lights_lambert_vertex = "vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\nvIndirectFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n\tvIndirectBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\nvIndirectFront += getAmbientLightIrradiance( ambientLightColor );\nvIndirectFront += getLightProbeIrradiance( lightProbe, geometry );\n#ifdef DOUBLE_SIDED\n\tvIndirectBack += getAmbientLightIrradiance( ambientLightColor );\n\tvIndirectBack += getLightProbeIrradiance( lightProbe, backGeometry );\n#endif\n#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif";
-
- var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in GeometricContext geometry ) {\n\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treturn irradiance;\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tdirectLight.color = directionalLight.color;\n\t\tdirectLight.direction = directionalLight.direction;\n\t\tdirectLight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tdirectLight.color = pointLight.color;\n\t\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\n\t\tdirectLight.visible = ( directLight.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tfloat angleCos = dot( directLight.direction, spotLight.direction );\n\t\tif ( angleCos > spotLight.coneCos ) {\n\t\t\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\t\tdirectLight.color = spotLight.color;\n\t\t\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tdirectLight.visible = true;\n\t\t} else {\n\t\t\tdirectLight.color = vec3( 0.0 );\n\t\t\tdirectLight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\n\t\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tirradiance *= PI;\n\t\t#endif\n\t\treturn irradiance;\n\t}\n#endif";
-
- var envmap_physical_pars_fragment = "#if defined( USE_ENVMAP )\n\t#ifdef ENVMAP_MODE_REFRACTION\n\t\tuniform float refractionRatio;\n\t#endif\n\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\n\t\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t#else\n\t\t\tvec4 envMapColor = vec4( 0.0 );\n\t\t#endif\n\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t}\n\tfloat getSpecularMIPLevel( const in float roughness, const in int maxMIPLevel ) {\n\t\tfloat maxMIPLevelScalar = float( maxMIPLevel );\n\t\tfloat sigma = PI * roughness * roughness / ( 1.0 + roughness );\n\t\tfloat desiredMIPLevel = maxMIPLevelScalar + log2( sigma );\n\t\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\n\t}\n\tvec3 getLightProbeIndirectRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in int maxMIPLevel ) {\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t vec3 reflectVec = reflect( -viewDir, normal );\n\t\t reflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t#else\n\t\t vec3 reflectVec = refract( -viewDir, normal, refractionRatio );\n\t\t#endif\n\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\tfloat specularMIPLevel = getSpecularMIPLevel( roughness, maxMIPLevel );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\t\tvec2 sampleUV = equirectUv( reflectVec );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, sampleUV, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, sampleUV, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0,0.0,1.0 ) );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#endif\n\t\treturn envMapColor.rgb * envMapIntensity;\n\t}\n#endif";
-
- var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;";
-
- var lights_toon_pars_fragment = "varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct ToonMaterial {\n\tvec3\tdiffuseColor;\n\tvec3\tspecularColor;\n\tfloat\tspecularShininess;\n\tfloat\tspecularStrength;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon\n#define Material_LightProbeLOD( material )\t(0)";
-
- var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;";
-
- var lights_phong_pars_fragment = "varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct BlinnPhongMaterial {\n\tvec3\tdiffuseColor;\n\tvec3\tspecularColor;\n\tfloat\tspecularShininess;\n\tfloat\tspecularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material )\t(0)";
-
- var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.specularRoughness = max( roughnessFactor, 0.0525 );material.specularRoughness += geometryRoughness;\nmaterial.specularRoughness = min( material.specularRoughness, 1.0 );\n#ifdef REFLECTIVITY\n\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\n#endif\n#ifdef CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheen;\n#endif";
-
- var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3\tdiffuseColor;\n\tfloat\tspecularRoughness;\n\tvec3\tspecularColor;\n#ifdef CLEARCOAT\n\tfloat clearcoat;\n\tfloat clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tvec3 sheenColor;\n#endif\n};\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\nfloat clearcoatDHRApprox( const in float roughness, const in float dotNL ) {\n\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.specularRoughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\t#ifdef CLEARCOAT\n\t\tfloat ccDotNL = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = ccDotNL * directLight.color;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tccIrradiance *= PI;\n\t\t#endif\n\t\tfloat clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );\n\t\treflectedLight.directSpecular += ccIrradiance * material.clearcoat * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );\n\t#else\n\t\tfloat clearcoatDHR = 0.0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\treflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_Sheen(\n\t\t\tmaterial.specularRoughness,\n\t\t\tdirectLight.direction,\n\t\t\tgeometry,\n\t\t\tmaterial.sheenColor\n\t\t);\n\t#else\n\t\treflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularRoughness);\n\t#endif\n\treflectedLight.directDiffuse += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef CLEARCOAT\n\t\tfloat ccDotNV = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular += clearcoatRadiance * material.clearcoat * BRDF_Specular_GGX_Environment( geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );\n\t\tfloat ccDotNL = ccDotNV;\n\t\tfloat clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );\n\t#else\n\t\tfloat clearcoatDHR = 0.0;\n\t#endif\n\tfloat clearcoatInv = 1.0 - clearcoatDHR;\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\tBRDF_Specular_Multiscattering_Environment( geometry, material.specularColor, material.specularRoughness, singleScattering, multiScattering );\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - ( singleScattering + multiScattering ) );\n\treflectedLight.indirectSpecular += clearcoatInv * radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}";
-
- var lights_fragment_begin = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif";
-
- var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\tvec3 lightMapIrradiance = lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tlightMapIrradiance *= PI;\n\t\t#endif\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getLightProbeIndirectIrradiance( geometry, maxMipLevel );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getLightProbeIndirectRadiance( geometry.viewDir, geometry.normal, material.specularRoughness, maxMipLevel );\n\t#ifdef CLEARCOAT\n\t\tclearcoatRadiance += getLightProbeIndirectRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness, maxMipLevel );\n\t#endif\n#endif";
-
- var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif";
-
- var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif";
-
- var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif";
-
- var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif";
-
- var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif";
-
- var map_fragment = "#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif";
-
- var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif";
-
- var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n#endif\n#ifdef USE_MAP\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif";
-
- var map_particle_pars_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tuniform mat3 uvTransform;\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif";
-
- var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif";
-
- var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif";
-
- var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n#endif";
-
- var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifndef USE_MORPHNORMALS\n\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif";
-
- var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t#endif\n#endif";
-
- var normal_fragment_begin = "#ifdef FLAT_SHADED\n\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n\t#ifdef USE_TANGENT\n\t\tvec3 tangent = normalize( vTangent );\n\t\tvec3 bitangent = normalize( vBitangent );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\ttangent = tangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t\tbitangent = bitangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t#endif\n\t\t#if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tmat3 vTBN = mat3( tangent, bitangent, normal );\n\t\t#endif\n\t#endif\n#endif\nvec3 geometryNormal = normal;";
-
- var normal_fragment_maps = "#ifdef OBJECTSPACE_NORMALMAP\n\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( TANGENTSPACE_NORMALMAP )\n\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\t#ifdef USE_TANGENT\n\t\tnormal = normalize( vTBN * mapN );\n\t#else\n\t\tnormal = perturbNormal2Arb( -vViewPosition, normal, mapN );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif";
-
- var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef OBJECTSPACE_NORMALMAP\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN ) {\n\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\t\tfloat scale = sign( st1.t * st0.s - st0.t * st1.s );\n\t\tvec3 S = normalize( ( q0 * st1.t - q1 * st0.t ) * scale );\n\t\tvec3 T = normalize( ( - q0 * st1.s + q1 * st0.s ) * scale );\n\t\tvec3 N = normalize( surf_norm );\n\t\tmat3 tsn = mat3( S, T, N );\n\t\tmapN.xy *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\treturn normalize( tsn * mapN );\n\t}\n#endif";
-
- var clearcoat_normal_fragment_begin = "#ifdef CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif";
-
- var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\t#ifdef USE_TANGENT\n\t\tclearcoatNormal = normalize( vTBN * clearcoatMapN );\n\t#else\n\t\tclearcoatNormal = perturbNormal2Arb( - vViewPosition, clearcoatNormal, clearcoatMapN );\n\t#endif\n#endif";
-
- var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif";
-
- var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ));\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w);\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn (( near + viewZ ) * far ) / (( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}";
-
- var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif";
-
- var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;";
-
- var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif";
-
- var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif";
-
- var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif";
-
- var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif";
-
- var shadowmap_pars_fragment = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\t\tbool frustumTest = all( frustumTestVec );\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ), \n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif";
-
- var shadowmap_pars_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif";
-
- var shadowmap_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * worldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * worldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * worldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n#endif";
-
- var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}";
-
- var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif";
-
- var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform highp sampler2D boneTexture;\n\t\tuniform int boneTextureSize;\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tfloat j = i * 4.0;\n\t\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\t\ty = dy * ( y + 0.5 );\n\t\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\t\treturn bone;\n\t\t}\n\t#else\n\t\tuniform mat4 boneMatrices[ MAX_BONES ];\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tmat4 bone = boneMatrices[ int(i) ];\n\t\t\treturn bone;\n\t\t}\n\t#endif\n#endif";
-
- var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif";
-
- var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif";
-
- var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif";
-
- var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif";
-
- var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif";
-
- var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nuniform float toneMappingWhitePoint;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\n#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )\nvec3 Uncharted2ToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( ( color * ( 2.51 * color + 0.03 ) ) / ( color * ( 2.43 * color + 0.59 ) + 0.14 ) );\n}";
-
- var uv_pars_fragment = "#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n\tvarying vec2 vUv;\n#endif";
-
- var uv_pars_vertex = "#ifdef USE_UV\n\t#ifdef UVS_VERTEX_ONLY\n\t\tvec2 vUv;\n\t#else\n\t\tvarying vec2 vUv;\n\t#endif\n\tuniform mat3 uvTransform;\n#endif";
-
- var uv_vertex = "#ifdef USE_UV\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif";
-
- var uv2_pars_fragment = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif";
-
- var uv2_pars_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n\tuniform mat3 uv2Transform;\n#endif";
-
- var uv2_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;\n#endif";
-
- var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif";
-
- var background_frag = "uniform sampler2D t2D;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include \n\t#include \n}";
-
- var background_vert = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}";
-
- var cube_frag = "#include \nuniform float opacity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 vReflect = vWorldDirection;\n\t#include \n\tgl_FragColor = envColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}";
-
- var cube_vert = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}";
-
- var depth_frag = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}";
-
- var depth_vert = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}";
-
- var distanceRGBA_frag = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}";
-
- var distanceRGBA_vert = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}";
-
- var equirect_frag = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tvec4 texColor = texture2D( tEquirect, sampleUV );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include \n\t#include \n}";
-
- var equirect_vert = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}";
-
- var linedashed_frag = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}";
-
- var linedashed_vert = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}";
-
- var meshbasic_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\treflectedLight.indirectDiffuse += lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}";
-
- var meshbasic_vert = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_ENVMAP\n\t#include \n\t#include \n\t#include \n\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}";
-
- var meshlambert_frag = "uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.indirectDiffuse += ( gl_FrontFacing ) ? vIndirectFront : vIndirectBack;\n\t#else\n\t\treflectedLight.indirectDiffuse += vIndirectFront;\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n\t#else\n\t\treflectedLight.directDiffuse = vLightFront;\n\t#endif\n\treflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}";
-
- var meshlambert_vert = "#define LAMBERT\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include