import {
  BufferGeometry,
  Color,
  EventDispatcher,
  FileLoader,
  Float32BufferAttribute,
  Int32BufferAttribute,
  LinearSRGBColorSpace,
  Loader,
  MOUSE,
  MathUtils,
  NoToneMapping,
  PerspectiveCamera,
  Points,
  PointsMaterial,
  Quaternion,
  Scene,
  Vector2,
  Vector3,
  WebGLRenderer
} from "./chunk-72PLI3GC.js";
import {
  legend_newrow
} from "./chunk-UJKW42DI.js";
import {
  schemeCategory20
} from "./chunk-V2OJLJSK.js";
import {
  findgenemodel_bysymbol,
  gene_searchbox
} from "./chunk-LTOBEUM2.js";
import "./chunk-VGYTAYFK.js";
import {
  appear2 as appear,
  disappear2 as disappear,
  font,
  newpane,
  sayerror
} from "./chunk-SA7APTJR.js";
import "./chunk-HJ6L54YS.js";
import "./chunk-LSEFWW72.js";
import "./chunk-SKREEF3H.js";
import {
  Menu
} from "./chunk-HYOEWQ5P.js";
import "./chunk-HBW42TDT.js";
import "./chunk-G6O3URDN.js";
import "./chunk-FN5XPUPH.js";
import "./chunk-IIT367QZ.js";
import "./chunk-RZGEKL77.js";
import "./chunk-UI7OCM2A.js";
import "./chunk-YZK37YCC.js";
import {
  dofetch,
  dofetch2
} from "./chunk-7EMWHCVW.js";
import "./chunk-7IYJZZQI.js";
import "./chunk-M3J4MINX.js";
import "./chunk-PF4DSFDR.js";
import "./chunk-QH6W3NVF.js";
import "./chunk-NOEAT6CX.js";
import "./chunk-4UWS5Y3N.js";
import "./chunk-DD4R5P6W.js";
import "./chunk-JNITUVXP.js";
import {
  area_default,
  line_default,
  monotoneX,
  plasma
} from "./chunk-KSGA62R2.js";
import {
  axisBottom,
  axisLeft,
  axisTop
} from "./chunk-LOZEKOES.js";
import "./chunk-TOU7EVFQ.js";
import {
  band,
  linear,
  ordinal,
  sequential
} from "./chunk-OAWQ6LOO.js";
import "./chunk-TLT4YIG3.js";
import "./chunk-KYBIQBXE.js";
import {
  pointer_default
} from "./chunk-I6Y4O3RR.js";
import "./chunk-OMR2DT66.js";
import "./chunk-DQC5FFGV.js";
import "./chunk-HFNDKYVF.js";

// ../node_modules/three/examples/jsm/loaders/PCDLoader.js
var PCDLoader = class extends Loader {
  constructor(manager) {
    super(manager);
    this.littleEndian = true;
  }
  load(url, onLoad, onProgress, onError) {
    const scope = this;
    const loader = new FileLoader(scope.manager);
    loader.setPath(scope.path);
    loader.setResponseType("arraybuffer");
    loader.setRequestHeader(scope.requestHeader);
    loader.setWithCredentials(scope.withCredentials);
    loader.load(url, function(data) {
      try {
        onLoad(scope.parse(data));
      } catch (e) {
        if (onError) {
          onError(e);
        } else {
          console.error(e);
        }
        scope.manager.itemError(url);
      }
    }, onProgress, onError);
  }
  parse(data) {
    function decompressLZF(inData, outLength) {
      const inLength = inData.length;
      const outData = new Uint8Array(outLength);
      let inPtr = 0;
      let outPtr = 0;
      let ctrl;
      let len;
      let ref;
      do {
        ctrl = inData[inPtr++];
        if (ctrl < 1 << 5) {
          ctrl++;
          if (outPtr + ctrl > outLength) throw new Error("Output buffer is not large enough");
          if (inPtr + ctrl > inLength) throw new Error("Invalid compressed data");
          do {
            outData[outPtr++] = inData[inPtr++];
          } while (--ctrl);
        } else {
          len = ctrl >> 5;
          ref = outPtr - ((ctrl & 31) << 8) - 1;
          if (inPtr >= inLength) throw new Error("Invalid compressed data");
          if (len === 7) {
            len += inData[inPtr++];
            if (inPtr >= inLength) throw new Error("Invalid compressed data");
          }
          ref -= inData[inPtr++];
          if (outPtr + len + 2 > outLength) throw new Error("Output buffer is not large enough");
          if (ref < 0) throw new Error("Invalid compressed data");
          if (ref >= outPtr) throw new Error("Invalid compressed data");
          do {
            outData[outPtr++] = outData[ref++];
          } while (--len + 2);
        }
      } while (inPtr < inLength);
      return outData;
    }
    function parseHeader(data2) {
      const PCDheader2 = {};
      const result1 = data2.search(/[\r\n]DATA\s(\S*)\s/i);
      const result2 = /[\r\n]DATA\s(\S*)\s/i.exec(data2.slice(result1 - 1));
      PCDheader2.data = result2[1];
      PCDheader2.headerLen = result2[0].length + result1;
      PCDheader2.str = data2.slice(0, PCDheader2.headerLen);
      PCDheader2.str = PCDheader2.str.replace(/#.*/gi, "");
      PCDheader2.version = /VERSION (.*)/i.exec(PCDheader2.str);
      PCDheader2.fields = /FIELDS (.*)/i.exec(PCDheader2.str);
      PCDheader2.size = /SIZE (.*)/i.exec(PCDheader2.str);
      PCDheader2.type = /TYPE (.*)/i.exec(PCDheader2.str);
      PCDheader2.count = /COUNT (.*)/i.exec(PCDheader2.str);
      PCDheader2.width = /WIDTH (.*)/i.exec(PCDheader2.str);
      PCDheader2.height = /HEIGHT (.*)/i.exec(PCDheader2.str);
      PCDheader2.viewpoint = /VIEWPOINT (.*)/i.exec(PCDheader2.str);
      PCDheader2.points = /POINTS (.*)/i.exec(PCDheader2.str);
      if (PCDheader2.version !== null)
        PCDheader2.version = parseFloat(PCDheader2.version[1]);
      PCDheader2.fields = PCDheader2.fields !== null ? PCDheader2.fields[1].split(" ") : [];
      if (PCDheader2.type !== null)
        PCDheader2.type = PCDheader2.type[1].split(" ");
      if (PCDheader2.width !== null)
        PCDheader2.width = parseInt(PCDheader2.width[1]);
      if (PCDheader2.height !== null)
        PCDheader2.height = parseInt(PCDheader2.height[1]);
      if (PCDheader2.viewpoint !== null)
        PCDheader2.viewpoint = PCDheader2.viewpoint[1];
      if (PCDheader2.points !== null)
        PCDheader2.points = parseInt(PCDheader2.points[1], 10);
      if (PCDheader2.points === null)
        PCDheader2.points = PCDheader2.width * PCDheader2.height;
      if (PCDheader2.size !== null) {
        PCDheader2.size = PCDheader2.size[1].split(" ").map(function(x) {
          return parseInt(x, 10);
        });
      }
      if (PCDheader2.count !== null) {
        PCDheader2.count = PCDheader2.count[1].split(" ").map(function(x) {
          return parseInt(x, 10);
        });
      } else {
        PCDheader2.count = [];
        for (let i = 0, l = PCDheader2.fields.length; i < l; i++) {
          PCDheader2.count.push(1);
        }
      }
      PCDheader2.offset = {};
      let sizeSum = 0;
      for (let i = 0, l = PCDheader2.fields.length; i < l; i++) {
        if (PCDheader2.data === "ascii") {
          PCDheader2.offset[PCDheader2.fields[i]] = i;
        } else {
          PCDheader2.offset[PCDheader2.fields[i]] = sizeSum;
          sizeSum += PCDheader2.size[i] * PCDheader2.count[i];
        }
      }
      PCDheader2.rowSize = sizeSum;
      return PCDheader2;
    }
    const textData = new TextDecoder().decode(data);
    const PCDheader = parseHeader(textData);
    const position = [];
    const normal = [];
    const color = [];
    const intensity = [];
    const label = [];
    if (PCDheader.data === "ascii") {
      const offset = PCDheader.offset;
      const pcdData = textData.slice(PCDheader.headerLen);
      const lines = pcdData.split("\n");
      for (let i = 0, l = lines.length; i < l; i++) {
        if (lines[i] === "") continue;
        const line = lines[i].split(" ");
        if (offset.x !== void 0) {
          position.push(parseFloat(line[offset.x]));
          position.push(parseFloat(line[offset.y]));
          position.push(parseFloat(line[offset.z]));
        }
        if (offset.rgb !== void 0) {
          const rgb_field_index = PCDheader.fields.findIndex((field) => field === "rgb");
          const rgb_type = PCDheader.type[rgb_field_index];
          const float = parseFloat(line[offset.rgb]);
          let rgb = float;
          if (rgb_type === "F") {
            const farr = new Float32Array(1);
            farr[0] = float;
            rgb = new Int32Array(farr.buffer)[0];
          }
          const r = rgb >> 16 & 255;
          const g = rgb >> 8 & 255;
          const b = rgb >> 0 & 255;
          color.push(r / 255, g / 255, b / 255);
        }
        if (offset.normal_x !== void 0) {
          normal.push(parseFloat(line[offset.normal_x]));
          normal.push(parseFloat(line[offset.normal_y]));
          normal.push(parseFloat(line[offset.normal_z]));
        }
        if (offset.intensity !== void 0) {
          intensity.push(parseFloat(line[offset.intensity]));
        }
        if (offset.label !== void 0) {
          label.push(parseInt(line[offset.label]));
        }
      }
    }
    if (PCDheader.data === "binary_compressed") {
      const sizes = new Uint32Array(data.slice(PCDheader.headerLen, PCDheader.headerLen + 8));
      const compressedSize = sizes[0];
      const decompressedSize = sizes[1];
      const decompressed = decompressLZF(new Uint8Array(data, PCDheader.headerLen + 8, compressedSize), decompressedSize);
      const dataview = new DataView(decompressed.buffer);
      const offset = PCDheader.offset;
      for (let i = 0; i < PCDheader.points; i++) {
        if (offset.x !== void 0) {
          const xIndex = PCDheader.fields.indexOf("x");
          const yIndex = PCDheader.fields.indexOf("y");
          const zIndex = PCDheader.fields.indexOf("z");
          position.push(dataview.getFloat32(PCDheader.points * offset.x + PCDheader.size[xIndex] * i, this.littleEndian));
          position.push(dataview.getFloat32(PCDheader.points * offset.y + PCDheader.size[yIndex] * i, this.littleEndian));
          position.push(dataview.getFloat32(PCDheader.points * offset.z + PCDheader.size[zIndex] * i, this.littleEndian));
        }
        if (offset.rgb !== void 0) {
          const rgbIndex = PCDheader.fields.indexOf("rgb");
          color.push(dataview.getUint8(PCDheader.points * offset.rgb + PCDheader.size[rgbIndex] * i + 2) / 255);
          color.push(dataview.getUint8(PCDheader.points * offset.rgb + PCDheader.size[rgbIndex] * i + 1) / 255);
          color.push(dataview.getUint8(PCDheader.points * offset.rgb + PCDheader.size[rgbIndex] * i + 0) / 255);
        }
        if (offset.normal_x !== void 0) {
          const xIndex = PCDheader.fields.indexOf("normal_x");
          const yIndex = PCDheader.fields.indexOf("normal_y");
          const zIndex = PCDheader.fields.indexOf("normal_z");
          normal.push(dataview.getFloat32(PCDheader.points * offset.normal_x + PCDheader.size[xIndex] * i, this.littleEndian));
          normal.push(dataview.getFloat32(PCDheader.points * offset.normal_y + PCDheader.size[yIndex] * i, this.littleEndian));
          normal.push(dataview.getFloat32(PCDheader.points * offset.normal_z + PCDheader.size[zIndex] * i, this.littleEndian));
        }
        if (offset.intensity !== void 0) {
          const intensityIndex = PCDheader.fields.indexOf("intensity");
          intensity.push(dataview.getFloat32(PCDheader.points * offset.intensity + PCDheader.size[intensityIndex] * i, this.littleEndian));
        }
        if (offset.label !== void 0) {
          const labelIndex = PCDheader.fields.indexOf("label");
          label.push(dataview.getInt32(PCDheader.points * offset.label + PCDheader.size[labelIndex] * i, this.littleEndian));
        }
      }
    }
    if (PCDheader.data === "binary") {
      const dataview = new DataView(data, PCDheader.headerLen);
      const offset = PCDheader.offset;
      for (let i = 0, row = 0; i < PCDheader.points; i++, row += PCDheader.rowSize) {
        if (offset.x !== void 0) {
          position.push(dataview.getFloat32(row + offset.x, this.littleEndian));
          position.push(dataview.getFloat32(row + offset.y, this.littleEndian));
          position.push(dataview.getFloat32(row + offset.z, this.littleEndian));
        }
        if (offset.rgb !== void 0) {
          color.push(dataview.getUint8(row + offset.rgb + 2) / 255);
          color.push(dataview.getUint8(row + offset.rgb + 1) / 255);
          color.push(dataview.getUint8(row + offset.rgb + 0) / 255);
        }
        if (offset.normal_x !== void 0) {
          normal.push(dataview.getFloat32(row + offset.normal_x, this.littleEndian));
          normal.push(dataview.getFloat32(row + offset.normal_y, this.littleEndian));
          normal.push(dataview.getFloat32(row + offset.normal_z, this.littleEndian));
        }
        if (offset.intensity !== void 0) {
          intensity.push(dataview.getFloat32(row + offset.intensity, this.littleEndian));
        }
        if (offset.label !== void 0) {
          label.push(dataview.getInt32(row + offset.label, this.littleEndian));
        }
      }
    }
    const geometry = new BufferGeometry();
    if (position.length > 0) geometry.setAttribute("position", new Float32BufferAttribute(position, 3));
    if (normal.length > 0) geometry.setAttribute("normal", new Float32BufferAttribute(normal, 3));
    if (color.length > 0) geometry.setAttribute("color", new Float32BufferAttribute(color, 3));
    if (intensity.length > 0) geometry.setAttribute("intensity", new Float32BufferAttribute(intensity, 1));
    if (label.length > 0) geometry.setAttribute("label", new Int32BufferAttribute(label, 1));
    geometry.computeBoundingSphere();
    const material = new PointsMaterial({ size: 5e-3 });
    if (color.length > 0) {
      material.vertexColors = true;
    }
    return new Points(geometry, material);
  }
};

// ../public/static/js/WebGL.js
function isWebGLAvailable() {
  try {
    var canvas = document.createElement("canvas");
    return !!(window.WebGLRenderingContext && (canvas.getContext("webgl") || canvas.getContext("experimental-webgl")));
  } catch (e) {
    return false;
  }
}
function getWebGLErrorMessage() {
  return this.getErrorMessage(1);
}

// ../node_modules/three/examples/jsm/controls/TrackballControls.js
var _changeEvent = { type: "change" };
var _startEvent = { type: "start" };
var _endEvent = { type: "end" };
var TrackballControls = class extends EventDispatcher {
  constructor(object, domElement) {
    super();
    const scope = this;
    const STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
    this.object = object;
    this.domElement = domElement;
    this.domElement.style.touchAction = "none";
    this.enabled = true;
    this.screen = { left: 0, top: 0, width: 0, height: 0 };
    this.rotateSpeed = 1;
    this.zoomSpeed = 1.2;
    this.panSpeed = 0.3;
    this.noRotate = false;
    this.noZoom = false;
    this.noPan = false;
    this.staticMoving = false;
    this.dynamicDampingFactor = 0.2;
    this.minDistance = 0;
    this.maxDistance = Infinity;
    this.minZoom = 0;
    this.maxZoom = Infinity;
    this.keys = [
      "KeyA",
      "KeyS",
      "KeyD"
      /*D*/
    ];
    this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
    this.target = new Vector3();
    const EPS = 1e-6;
    const lastPosition = new Vector3();
    let lastZoom = 1;
    let _state = STATE.NONE, _keyState = STATE.NONE, _touchZoomDistanceStart = 0, _touchZoomDistanceEnd = 0, _lastAngle = 0;
    const _eye = new Vector3(), _movePrev = new Vector2(), _moveCurr = new Vector2(), _lastAxis = new Vector3(), _zoomStart = new Vector2(), _zoomEnd = new Vector2(), _panStart = new Vector2(), _panEnd = new Vector2(), _pointers = [], _pointerPositions = {};
    this.target0 = this.target.clone();
    this.position0 = this.object.position.clone();
    this.up0 = this.object.up.clone();
    this.zoom0 = this.object.zoom;
    this.handleResize = function() {
      const box = scope.domElement.getBoundingClientRect();
      const d = scope.domElement.ownerDocument.documentElement;
      scope.screen.left = box.left + window.pageXOffset - d.clientLeft;
      scope.screen.top = box.top + window.pageYOffset - d.clientTop;
      scope.screen.width = box.width;
      scope.screen.height = box.height;
    };
    const getMouseOnScreen = (function() {
      const vector = new Vector2();
      return function getMouseOnScreen2(pageX, pageY) {
        vector.set(
          (pageX - scope.screen.left) / scope.screen.width,
          (pageY - scope.screen.top) / scope.screen.height
        );
        return vector;
      };
    })();
    const getMouseOnCircle = (function() {
      const vector = new Vector2();
      return function getMouseOnCircle2(pageX, pageY) {
        vector.set(
          (pageX - scope.screen.width * 0.5 - scope.screen.left) / (scope.screen.width * 0.5),
          (scope.screen.height + 2 * (scope.screen.top - pageY)) / scope.screen.width
          // screen.width intentional
        );
        return vector;
      };
    })();
    this.rotateCamera = (function() {
      const axis = new Vector3(), quaternion = new Quaternion(), eyeDirection = new Vector3(), objectUpDirection = new Vector3(), objectSidewaysDirection = new Vector3(), moveDirection = new Vector3();
      return function rotateCamera() {
        moveDirection.set(_moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0);
        let angle = moveDirection.length();
        if (angle) {
          _eye.copy(scope.object.position).sub(scope.target);
          eyeDirection.copy(_eye).normalize();
          objectUpDirection.copy(scope.object.up).normalize();
          objectSidewaysDirection.crossVectors(objectUpDirection, eyeDirection).normalize();
          objectUpDirection.setLength(_moveCurr.y - _movePrev.y);
          objectSidewaysDirection.setLength(_moveCurr.x - _movePrev.x);
          moveDirection.copy(objectUpDirection.add(objectSidewaysDirection));
          axis.crossVectors(moveDirection, _eye).normalize();
          angle *= scope.rotateSpeed;
          quaternion.setFromAxisAngle(axis, angle);
          _eye.applyQuaternion(quaternion);
          scope.object.up.applyQuaternion(quaternion);
          _lastAxis.copy(axis);
          _lastAngle = angle;
        } else if (!scope.staticMoving && _lastAngle) {
          _lastAngle *= Math.sqrt(1 - scope.dynamicDampingFactor);
          _eye.copy(scope.object.position).sub(scope.target);
          quaternion.setFromAxisAngle(_lastAxis, _lastAngle);
          _eye.applyQuaternion(quaternion);
          scope.object.up.applyQuaternion(quaternion);
        }
        _movePrev.copy(_moveCurr);
      };
    })();
    this.zoomCamera = function() {
      let factor;
      if (_state === STATE.TOUCH_ZOOM_PAN) {
        factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
        _touchZoomDistanceStart = _touchZoomDistanceEnd;
        if (scope.object.isPerspectiveCamera) {
          _eye.multiplyScalar(factor);
        } else if (scope.object.isOrthographicCamera) {
          scope.object.zoom = MathUtils.clamp(scope.object.zoom / factor, scope.minZoom, scope.maxZoom);
          if (lastZoom !== scope.object.zoom) {
            scope.object.updateProjectionMatrix();
          }
        } else {
          console.warn("THREE.TrackballControls: Unsupported camera type");
        }
      } else {
        factor = 1 + (_zoomEnd.y - _zoomStart.y) * scope.zoomSpeed;
        if (factor !== 1 && factor > 0) {
          if (scope.object.isPerspectiveCamera) {
            _eye.multiplyScalar(factor);
          } else if (scope.object.isOrthographicCamera) {
            scope.object.zoom = MathUtils.clamp(scope.object.zoom / factor, scope.minZoom, scope.maxZoom);
            if (lastZoom !== scope.object.zoom) {
              scope.object.updateProjectionMatrix();
            }
          } else {
            console.warn("THREE.TrackballControls: Unsupported camera type");
          }
        }
        if (scope.staticMoving) {
          _zoomStart.copy(_zoomEnd);
        } else {
          _zoomStart.y += (_zoomEnd.y - _zoomStart.y) * this.dynamicDampingFactor;
        }
      }
    };
    this.panCamera = (function() {
      const mouseChange = new Vector2(), objectUp = new Vector3(), pan = new Vector3();
      return function panCamera() {
        mouseChange.copy(_panEnd).sub(_panStart);
        if (mouseChange.lengthSq()) {
          if (scope.object.isOrthographicCamera) {
            const scale_x = (scope.object.right - scope.object.left) / scope.object.zoom / scope.domElement.clientWidth;
            const scale_y = (scope.object.top - scope.object.bottom) / scope.object.zoom / scope.domElement.clientWidth;
            mouseChange.x *= scale_x;
            mouseChange.y *= scale_y;
          }
          mouseChange.multiplyScalar(_eye.length() * scope.panSpeed);
          pan.copy(_eye).cross(scope.object.up).setLength(mouseChange.x);
          pan.add(objectUp.copy(scope.object.up).setLength(mouseChange.y));
          scope.object.position.add(pan);
          scope.target.add(pan);
          if (scope.staticMoving) {
            _panStart.copy(_panEnd);
          } else {
            _panStart.add(mouseChange.subVectors(_panEnd, _panStart).multiplyScalar(scope.dynamicDampingFactor));
          }
        }
      };
    })();
    this.checkDistances = function() {
      if (!scope.noZoom || !scope.noPan) {
        if (_eye.lengthSq() > scope.maxDistance * scope.maxDistance) {
          scope.object.position.addVectors(scope.target, _eye.setLength(scope.maxDistance));
          _zoomStart.copy(_zoomEnd);
        }
        if (_eye.lengthSq() < scope.minDistance * scope.minDistance) {
          scope.object.position.addVectors(scope.target, _eye.setLength(scope.minDistance));
          _zoomStart.copy(_zoomEnd);
        }
      }
    };
    this.update = function() {
      _eye.subVectors(scope.object.position, scope.target);
      if (!scope.noRotate) {
        scope.rotateCamera();
      }
      if (!scope.noZoom) {
        scope.zoomCamera();
      }
      if (!scope.noPan) {
        scope.panCamera();
      }
      scope.object.position.addVectors(scope.target, _eye);
      if (scope.object.isPerspectiveCamera) {
        scope.checkDistances();
        scope.object.lookAt(scope.target);
        if (lastPosition.distanceToSquared(scope.object.position) > EPS) {
          scope.dispatchEvent(_changeEvent);
          lastPosition.copy(scope.object.position);
        }
      } else if (scope.object.isOrthographicCamera) {
        scope.object.lookAt(scope.target);
        if (lastPosition.distanceToSquared(scope.object.position) > EPS || lastZoom !== scope.object.zoom) {
          scope.dispatchEvent(_changeEvent);
          lastPosition.copy(scope.object.position);
          lastZoom = scope.object.zoom;
        }
      } else {
        console.warn("THREE.TrackballControls: Unsupported camera type");
      }
    };
    this.reset = function() {
      _state = STATE.NONE;
      _keyState = STATE.NONE;
      scope.target.copy(scope.target0);
      scope.object.position.copy(scope.position0);
      scope.object.up.copy(scope.up0);
      scope.object.zoom = scope.zoom0;
      scope.object.updateProjectionMatrix();
      _eye.subVectors(scope.object.position, scope.target);
      scope.object.lookAt(scope.target);
      scope.dispatchEvent(_changeEvent);
      lastPosition.copy(scope.object.position);
      lastZoom = scope.object.zoom;
    };
    function onPointerDown(event) {
      if (scope.enabled === false) return;
      if (_pointers.length === 0) {
        scope.domElement.setPointerCapture(event.pointerId);
        scope.domElement.addEventListener("pointermove", onPointerMove);
        scope.domElement.addEventListener("pointerup", onPointerUp);
      }
      addPointer(event);
      if (event.pointerType === "touch") {
        onTouchStart(event);
      } else {
        onMouseDown(event);
      }
    }
    function onPointerMove(event) {
      if (scope.enabled === false) return;
      if (event.pointerType === "touch") {
        onTouchMove(event);
      } else {
        onMouseMove(event);
      }
    }
    function onPointerUp(event) {
      if (scope.enabled === false) return;
      if (event.pointerType === "touch") {
        onTouchEnd(event);
      } else {
        onMouseUp();
      }
      removePointer(event);
      if (_pointers.length === 0) {
        scope.domElement.releasePointerCapture(event.pointerId);
        scope.domElement.removeEventListener("pointermove", onPointerMove);
        scope.domElement.removeEventListener("pointerup", onPointerUp);
      }
    }
    function onPointerCancel(event) {
      removePointer(event);
    }
    function keydown(event) {
      if (scope.enabled === false) return;
      window.removeEventListener("keydown", keydown);
      if (_keyState !== STATE.NONE) {
        return;
      } else if (event.code === scope.keys[STATE.ROTATE] && !scope.noRotate) {
        _keyState = STATE.ROTATE;
      } else if (event.code === scope.keys[STATE.ZOOM] && !scope.noZoom) {
        _keyState = STATE.ZOOM;
      } else if (event.code === scope.keys[STATE.PAN] && !scope.noPan) {
        _keyState = STATE.PAN;
      }
    }
    function keyup() {
      if (scope.enabled === false) return;
      _keyState = STATE.NONE;
      window.addEventListener("keydown", keydown);
    }
    function onMouseDown(event) {
      if (_state === STATE.NONE) {
        switch (event.button) {
          case scope.mouseButtons.LEFT:
            _state = STATE.ROTATE;
            break;
          case scope.mouseButtons.MIDDLE:
            _state = STATE.ZOOM;
            break;
          case scope.mouseButtons.RIGHT:
            _state = STATE.PAN;
            break;
        }
      }
      const state = _keyState !== STATE.NONE ? _keyState : _state;
      if (state === STATE.ROTATE && !scope.noRotate) {
        _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
        _movePrev.copy(_moveCurr);
      } else if (state === STATE.ZOOM && !scope.noZoom) {
        _zoomStart.copy(getMouseOnScreen(event.pageX, event.pageY));
        _zoomEnd.copy(_zoomStart);
      } else if (state === STATE.PAN && !scope.noPan) {
        _panStart.copy(getMouseOnScreen(event.pageX, event.pageY));
        _panEnd.copy(_panStart);
      }
      scope.dispatchEvent(_startEvent);
    }
    function onMouseMove(event) {
      const state = _keyState !== STATE.NONE ? _keyState : _state;
      if (state === STATE.ROTATE && !scope.noRotate) {
        _movePrev.copy(_moveCurr);
        _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
      } else if (state === STATE.ZOOM && !scope.noZoom) {
        _zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY));
      } else if (state === STATE.PAN && !scope.noPan) {
        _panEnd.copy(getMouseOnScreen(event.pageX, event.pageY));
      }
    }
    function onMouseUp() {
      _state = STATE.NONE;
      scope.dispatchEvent(_endEvent);
    }
    function onMouseWheel(event) {
      if (scope.enabled === false) return;
      if (scope.noZoom === true) return;
      event.preventDefault();
      switch (event.deltaMode) {
        case 2:
          _zoomStart.y -= event.deltaY * 0.025;
          break;
        case 1:
          _zoomStart.y -= event.deltaY * 0.01;
          break;
        default:
          _zoomStart.y -= event.deltaY * 25e-5;
          break;
      }
      scope.dispatchEvent(_startEvent);
      scope.dispatchEvent(_endEvent);
    }
    function onTouchStart(event) {
      trackPointer(event);
      switch (_pointers.length) {
        case 1:
          _state = STATE.TOUCH_ROTATE;
          _moveCurr.copy(getMouseOnCircle(_pointers[0].pageX, _pointers[0].pageY));
          _movePrev.copy(_moveCurr);
          break;
        default:
          _state = STATE.TOUCH_ZOOM_PAN;
          const dx = _pointers[0].pageX - _pointers[1].pageX;
          const dy = _pointers[0].pageY - _pointers[1].pageY;
          _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy);
          const x = (_pointers[0].pageX + _pointers[1].pageX) / 2;
          const y = (_pointers[0].pageY + _pointers[1].pageY) / 2;
          _panStart.copy(getMouseOnScreen(x, y));
          _panEnd.copy(_panStart);
          break;
      }
      scope.dispatchEvent(_startEvent);
    }
    function onTouchMove(event) {
      trackPointer(event);
      switch (_pointers.length) {
        case 1:
          _movePrev.copy(_moveCurr);
          _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
          break;
        default:
          const position = getSecondPointerPosition(event);
          const dx = event.pageX - position.x;
          const dy = event.pageY - position.y;
          _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy);
          const x = (event.pageX + position.x) / 2;
          const y = (event.pageY + position.y) / 2;
          _panEnd.copy(getMouseOnScreen(x, y));
          break;
      }
    }
    function onTouchEnd(event) {
      switch (_pointers.length) {
        case 0:
          _state = STATE.NONE;
          break;
        case 1:
          _state = STATE.TOUCH_ROTATE;
          _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
          _movePrev.copy(_moveCurr);
          break;
        case 2:
          _state = STATE.TOUCH_ZOOM_PAN;
          for (let i = 0; i < _pointers.length; i++) {
            if (_pointers[i].pointerId !== event.pointerId) {
              const position = _pointerPositions[_pointers[i].pointerId];
              _moveCurr.copy(getMouseOnCircle(position.x, position.y));
              _movePrev.copy(_moveCurr);
              break;
            }
          }
          break;
      }
      scope.dispatchEvent(_endEvent);
    }
    function contextmenu(event) {
      if (scope.enabled === false) return;
      event.preventDefault();
    }
    function addPointer(event) {
      _pointers.push(event);
    }
    function removePointer(event) {
      delete _pointerPositions[event.pointerId];
      for (let i = 0; i < _pointers.length; i++) {
        if (_pointers[i].pointerId == event.pointerId) {
          _pointers.splice(i, 1);
          return;
        }
      }
    }
    function trackPointer(event) {
      let position = _pointerPositions[event.pointerId];
      if (position === void 0) {
        position = new Vector2();
        _pointerPositions[event.pointerId] = position;
      }
      position.set(event.pageX, event.pageY);
    }
    function getSecondPointerPosition(event) {
      const pointer = event.pointerId === _pointers[0].pointerId ? _pointers[1] : _pointers[0];
      return _pointerPositions[pointer.pointerId];
    }
    this.dispose = function() {
      scope.domElement.removeEventListener("contextmenu", contextmenu);
      scope.domElement.removeEventListener("pointerdown", onPointerDown);
      scope.domElement.removeEventListener("pointercancel", onPointerCancel);
      scope.domElement.removeEventListener("wheel", onMouseWheel);
      scope.domElement.removeEventListener("pointermove", onPointerMove);
      scope.domElement.removeEventListener("pointerup", onPointerUp);
      window.removeEventListener("keydown", keydown);
      window.removeEventListener("keyup", keyup);
    };
    this.domElement.addEventListener("contextmenu", contextmenu);
    this.domElement.addEventListener("pointerdown", onPointerDown);
    this.domElement.addEventListener("pointercancel", onPointerCancel);
    this.domElement.addEventListener("wheel", onMouseWheel, { passive: false });
    window.addEventListener("keydown", keydown);
    window.addEventListener("keyup", keyup);
    this.handleResize();
    this.update();
  }
};

// src/singlecell.js
async function init(arg, holder) {
  try {
    const obj = await load_json(arg);
    validate_obj(obj);
    obj.genome = arg.genome;
    obj.holder = holder.style("position", "relative");
    await init_view(obj);
    init_controlpanel(obj);
    await pcd_pipeline(obj);
    make_legend(arg, obj);
  } catch (e) {
    sayerror(holder, e.message || e);
  }
}
async function load_json(arg) {
  if (!arg.jsonfile) throw ".jsonfile missing";
  const tmp = await dofetch("textfile", { file: arg.jsonfile });
  if (tmp.error) throw tmp.error;
  return JSON.parse(tmp.text);
}
function validate_obj(obj) {
  if (!obj.cells) throw ".cells{} missing";
  if (!obj.cells.file) throw ".cells.file missing";
  if (!obj.cells.axis2columnidx) throw ".cells.axis2columnidx missing";
  if (obj.cells.categories) {
    if (!Array.isArray(obj.cells.categories)) throw ".cells.categories should be an array";
    if (obj.cells.categories.length == 0) throw ".cells.categories[] is empty array";
    for (const c of obj.cells.categories) {
      if (!Number.isInteger(c.columnidx)) throw "columnidx missing from category " + c;
      if (c.autocolor) {
        c.autocolor = ordinal(schemeCategory20);
      }
    }
    if (!Number.isInteger(obj.use_category_index)) {
      obj.use_category_index = 0;
    }
    if (!obj.cells.categories[obj.use_category_index]) throw "use_category_index out of bound";
    if (!obj.cells.categories[obj.use_category_index].hidden_types)
      obj.cells.categories[obj.use_category_index].hidden_types = [];
  }
  if (obj.gene_expression) {
    if (!obj.gene_expression.file) throw ".gene_expression.file missing";
    if (!Number.isInteger(obj.cells.barcodecolumnidx))
      throw ".gene_expression in use but .cells.barcodecolumnidx is missing";
  }
  if (!obj.width) obj.width = window.innerWidth * 0.9;
  if (!obj.height) obj.height = window.innerHeight * 0.9;
}
async function pcd_pipeline(obj) {
  if (Number.isInteger(obj.use_category_index)) {
    if (!obj.cells.categories[obj.use_category_index].hidden_types)
      obj.cells.categories[obj.use_category_index].hidden_types = [];
  }
  const data = await load_cell_pcd(obj);
  obj.data_sphere_r = data.data_sphere_r;
  if (!obj.camera.position.z) {
    obj.camera.position.z = obj.data_sphere_r * 3;
    obj.controls.maxDistance = obj.data_sphere_r * 4;
    obj.controls.minDistance = obj.data_sphere_r / 10;
  }
  const enc = new TextEncoder();
  const pcd_buffer = enc.encode(data.pcddata).buffer;
  render_cloud(obj, pcd_buffer);
  render_controlpanel(obj, data);
  animate();
  function animate() {
    requestAnimationFrame(animate);
    obj.controls.update();
    obj.renderer.render(obj.scene, obj.camera);
  }
}
async function load_cell_pcd(obj) {
  const wait = obj.holder.append("div").style("position", "absolute").style("top", 0).style("left", 0).style("padding", "10px").style("font-size", "1.5rem").text("Loading data...");
  const arg = {
    genome: obj.genome.name,
    textfile: obj.cells.file,
    delimiter: obj.cells.delimiter || "	",
    getpcd: {
      coord: obj.cells.axis2columnidx
    }
  };
  if (Number.isInteger(obj.use_category_index)) {
    if (!obj.cells.categories) throw "using category index but cells.categories[] missing";
    const cat = obj.cells.categories[obj.use_category_index];
    if (!cat) throw "category array index out of bound";
    arg.getpcd.category_index = cat.columnidx;
    if (cat.autocolor) {
      arg.getpcd.category_autocolor = true;
      if (cat.values_count) arg.getpcd.values_count = cat.values_count;
    } else if (cat.values) {
      arg.getpcd.category_customcolor = true;
      arg.getpcd.cat_values = cat.values;
    } else {
      if (!cat.autocolor && !cat.values) throw "categories.values[] are not defined";
      throw "unknow coloring scheme for category " + cat.name;
    }
    if (cat.hidden_types) {
      arg.hidden_types = cat.hidden_types;
    }
    if (obj.background_color) {
      arg.background_color = obj.background_color;
    }
    if (!cat.customorder) {
      arg.getpcd.category_autoorder = true;
    } else if (cat.customorder && cat.values) {
      arg.getpcd.category_customorder = true;
      arg.getpcd.cat_values = cat.values;
    } else {
      if (cat.customorder && !cat.values) throw "categories.values[] are not defined";
      throw "unknow ordering scheme for category " + cat.name;
    }
    obj.menu_button.html(cat.name + "&nbsp;&nbsp;&#9660;");
  } else if (Number.isInteger(obj.use_gene_index)) {
    const gene = obj.gene_expression.genes[obj.use_gene_index];
    obj.menu_button.html("Gene : " + gene.gene + "&nbsp;&nbsp;&#9660;");
    arg.getpcd.gene_expression = {
      file: obj.gene_expression.file,
      barcodecolumnidx: obj.cells.barcodecolumnidx,
      chr: gene.chr,
      start: gene.start,
      stop: gene.stop,
      autoscale: true,
      genename: gene.gene,
      color_min: obj.gene_expression.color_min,
      color_max: obj.gene_expression.color_max
    };
    if (obj.gene_expression.color_no_exp) arg.getpcd.gene_expression.color_no_exp = obj.gene_expression.color_no_exp;
  } else {
    throw "unknown method to color the cells";
  }
  return dofetch("singlecell", arg).then((data) => {
    if (data.error) throw data.error;
    wait.remove();
    return data;
  });
}
async function init_view(obj) {
  if (isWebGLAvailable() === false) {
    obj.holder.node().appendChild(getWebGLErrorMessage());
    return;
  }
  obj.scene = new Scene();
  if (obj.background_color) {
    obj.scene.background = new Color(obj.background_color);
  } else {
    obj.scene.background = new Color(16777215);
  }
  const default_zoom = obj.default_zoom ? 100 - obj.default_zoom : 45;
  obj.camera = new PerspectiveCamera(default_zoom, obj.width / obj.height, 0.1, 1e3);
  obj.camera.position.x = obj.canvas_2d ? 0 : 20;
  obj.camera.position.y = obj.canvas_2d ? 0 : -10;
  if (obj.canvas_2d) {
    obj.camera.up.set(0, 1, 0);
  } else {
    obj.camera.up.set(0, 0, 1);
  }
  obj.renderer = new WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
  obj.renderer.setPixelRatio(window.devicePixelRatio);
  obj.renderer.setSize(obj.width, obj.height);
  obj.renderer.domElement.style.backgroundColor = "#ffffff";
  obj.renderer.domElement.style.border = "solid #dddddd 2px";
  obj.renderer.outputColorSpace = LinearSRGBColorSpace;
  obj.renderer.toneMapping = NoToneMapping;
  obj.holder.style("display", "inline-block").style("position", "relative").node().appendChild(obj.renderer.domElement);
  obj.controls = new TrackballControls(obj.camera, obj.renderer.domElement);
  obj.controls.rotateSpeed = obj.canvas_2d ? 0 : 2;
  obj.controls.zoomSpeed = obj.canvas_2d ? 3 : 0.7;
  obj.controls.panSpeed = obj.canvas_2d ? 3 : 0.7;
  obj.controls.noZoom = true;
  obj.controls.noPan = false;
  obj.controls.staticMoving = true;
  obj.controls.dynamicDampingFactor = 0.3;
  obj.scene.add(obj.camera);
  obj.renderer.render(obj.scene, obj.camera);
  obj.controls.update();
}
function render_cloud(obj, pcd_buffer) {
  obj.scene.children.forEach(function(v, i) {
    if (v.material) {
      v.material.dispose();
      v.geometry.dispose();
      obj.scene.remove(v);
    }
  });
  const loader = new PCDLoader();
  const points = loader.parse(pcd_buffer, "");
  const newMat = new PointsMaterial({
    size: points.material.size,
    vertexColors: true,
    color: 16777215
  });
  points.material = newMat;
  points.material.vertexColors = true;
  points.material.color.set(16777215);
  points.material.needsUpdate = true;
  if (obj.point_size) {
    points.material.size = obj.point_size;
  } else {
    points.material.size = obj.canvas_2d ? 0.3 : 0.05;
  }
  obj.scene.add(points);
  obj.center = points.geometry.boundingSphere.center;
  obj.controls.target.set(obj.center.x, obj.center.y, obj.center.z);
  obj.controls.update();
}
function init_controlpanel(obj) {
  obj.menu = new Menu();
  obj.menu.d.style("padding", "3px");
  obj.settings = new Menu();
  obj.settings.d.style("padding", "3px");
  const panel = obj.holder.append("div").style("padding", "10px").style("position", "absolute").style("border-radius", "5px").style("top", "20px").style("right", "20px").style("background-color", "#dddddd");
  obj.menu_button = panel.append("button").style("display", "inline-block").style("padding", "4px").on("click", () => make_menu(obj));
  const minimize_btn = obj.minimize_btn = panel.append("div").style("margin-left", "10px").style("display", "inline-block").style("padding-left", "0px").style("padding-right", "2px").style("float", "right").style("font-size", ".6em").classed("active", obj.menu_minimized ? true : false).text(obj.menu_minimized ? " SHOW" : " HIDE").on("click", () => {
    obj.minimize_btn.classed("active", obj.minimize_btn.classed("active") ? false : true);
    if (obj.minimize_btn.classed("active")) {
      obj.minimize_btn.text(" SHOW");
      obj.menu_output.style("display", "none");
    } else {
      obj.minimize_btn.text(" HIDE");
      obj.menu_output.style("display", "block");
    }
  }).on("mouseover", () => {
    minimize_btn.style("text-decoration", "underline");
  }).on("mouseout", () => {
    minimize_btn.style("text-decoration", "none");
  });
  obj.use_background_color = 0;
  const config_btn = obj.settings_btn = panel.append("div").style("margin-left", "10px").style("display", "inline-block").style("padding-left", "3px").style("padding-right", "3px").style("float", "right").style("font-size", ".6em").text("CONFIG").on("click", () => make_settings(obj)).on("mouseover", () => {
    config_btn.style("text-decoration", "underline");
  }).on("mouseout", () => {
    config_btn.style("text-decoration", "none");
  });
  obj.menu_output = panel.append("div").style("margin-top", "10px").style("display", obj.menu_minimized ? "none" : "block");
  obj.show_zoom = true;
}
function render_controlpanel(obj, data) {
  obj.menu_output.selectAll("*").remove();
  if (data.category2color) {
    const top_row = obj.menu_output.append("div").style("margin", "2px");
    const select_all_checkbox = top_row.append("input").attr("type", "checkbox").style("margin-right", "10px").style("vertical-align", "top").style("display", "inline-block").on("change", () => {
      if (select_all_checkbox.node().checked) {
        obj.cells.categories[obj.use_category_index].hidden_types = [];
        pcd_pipeline(obj);
      } else {
        for (const type in data.category2color) {
          obj.cells.categories[obj.use_category_index].hidden_types.push(type);
        }
        pcd_pipeline(obj);
      }
    });
    if (obj.cells.categories[obj.use_category_index].hidden_types.length == 0) {
      select_all_checkbox.property("checked", 1);
    }
    top_row.append("span").text("Select All").style("font-size", ".8em").attr("font-family", font);
    for (const type in data.category2color) {
      const row = obj.menu_output.append("div").style("margin", "2px");
      const checkbox = row.append("input").attr("type", "checkbox").property("checked", 1).style("margin-right", "10px").style("vertical-align", "top").style("display", "inline-block").on("change", () => {
        if (obj.cells.categories[obj.use_category_index].hidden_types.includes(type)) {
          const index = obj.cells.categories[obj.use_category_index].hidden_types.indexOf(type);
          if (index !== -1) obj.cells.categories[obj.use_category_index].hidden_types.splice(index, 1);
          checkbox.property("checked", 1);
        } else {
          obj.cells.categories[obj.use_category_index].hidden_types.push(type);
        }
        pcd_pipeline(obj);
      });
      if (obj.cells.categories[obj.use_category_index].hidden_types.includes(type)) {
        checkbox.property("checked", 0);
      }
      row.append("div").style("display", "inline-block").style("height", "15px").style("width", "15px").style("background-color", data.category2color[type]).style("margin-right", "10px");
      row.append("span").text(type).attr("font-family", font);
      row.append("span").html("&nbsp;n=" + data.categorycount[type]).attr("font-family", font).style("font-size", ".8em").style("float", "right").style("color", "#777");
    }
  } else if (data.maxexpvalue) {
    const scale_height = 30;
    const scale_width = 150;
    const scale_div = obj.menu_output.append("div").style("margin", "10px").style("width", "120px").style("height", "100px");
    scale_div.append("div").text("Gene Expression " + obj.gene_expression.datatype).style("text-align", "center").style("width", "150px").style("margin-bottom", "20px");
    const svg = scale_div.append("svg").append("g");
    const colorRange = [obj.gene_expression.color_max, obj.gene_expression.color_min];
    const colorScale = linear().range(colorRange).domain([data.minexpvalue, data.maxexpvalue]);
    const defs = svg.append("defs");
    const linearGradient = defs.append("linearGradient").attr("id", "linear-gradient");
    linearGradient.append("stop").attr("offset", "0%").attr("stop-color", colorScale(data.maxexpvalue));
    linearGradient.append("stop").attr("offset", "100%").attr("stop-color", colorScale(data.minexpvalue));
    svg.append("rect").attr("x", 0).attr("y", 0).attr("width", scale_width).attr("height", scale_height).attr("transform", "translate(12, 0)").style("fill", "url(#linear-gradient)");
    const y = linear().range([0, scale_width]).domain([data.minexpvalue, data.maxexpvalue]);
    const legendAxis = axisBottom().scale(y).ticks(5);
    svg.append("g").attr("class", "legend axis").attr("transform", "translate(12," + scale_height + ")").call(legendAxis);
    const stats_div = obj.menu_output.append("div").style("padding-bottom", "20px").style("text-align", "center");
    stats_div.append("p").style("font-size", "13px").style("margin", "2px 0").html("<b>Cells with Expression Data</b> ");
    stats_div.append("p").style("font-size", "13px").style("margin", "2px 0").html(data.numbercellwithgeneexp + " / " + data.numbercelltotal);
    const no_exp_div = stats_div.append("div").style("display", "block").style("margin-top", "10px");
    no_exp_div.append("div").style("display", "inline-block").style("height", "15px").style("width", "15px").style("background-color", obj.gene_expression.color_no_exp ? obj.gene_expression.color_no_exp : "#2C2C2C").style("margin-right", "10px");
    no_exp_div.append("div").style("display", "inline-block").style("font-size", "13px").html(" Cells without Expression");
    const boxplot_div = obj.menu_output.append("div");
    boxplot_div.append("div").style("display", "block").style("padding", "5px").html("Violinplot / Boxplot of </br> Expression by");
    const boxplot_cat_select = boxplot_div.append("select").style("display", "block");
    boxplot_cat_select.append("option").attr("value", "none").text("-- Select--");
    if (obj.cells.categories) {
      obj.cells.categories.forEach((category, i) => {
        boxplot_cat_select.append("option").attr("value", category.columnidx).text(category.name);
      });
    }
    boxplot_cat_select.on("change", () => {
      const gene = obj.gene_expression.genes[obj.use_gene_index];
      const cat = obj.cells.categories[parseInt(boxplot_cat_select.node().selectedIndex) - 1];
      const arg = {
        genome: obj.genome.name,
        getgeneboxplot: {
          expfile: obj.gene_expression.file,
          chr: gene.chr,
          start: gene.start,
          stop: gene.stop,
          genename: gene.gene,
          cellfile: obj.cells.file,
          barcodecolumnidx: obj.cells.barcodecolumnidx,
          categorycolumnidx: parseInt(boxplot_cat_select.node().value),
          delimiter: obj.cells.delimiter || "	",
          category_customorder: cat.customorder && cat.values ? true : false,
          category_autoorder: !cat.customorder ? true : false,
          cat_values: cat.values,
          values_count: cat.values_count
        }
      };
      dofetch("singlecell", arg).then((data2) => {
        if (data2.error) throw data2.error;
        obj.box_plot = new Menu();
        make_plot(data2, obj, boxplot_cat_select.node().value);
      });
    });
  }
  make_zoom_panel(obj);
}
function make_zoom_panel(obj) {
  if (obj.show_zoom) {
    const zoom_div = obj.menu_output.append("div").style("display", "block").text("Zoom").style("margin", "10px 5px 5px 5px").style("padding-top", "10px").style("border-top", "1px solid #929292");
    const zoom_hide_btn = zoom_div.append("div").style("margin-left", "10px").style("display", "inline-block").style("padding-left", "0px").style("padding-right", "2px").style("float", "right").style("font-size", ".6em").text("MOVE TO CONFIG").on("click", () => {
      zoom_div.node().remove();
      obj.show_zoom = false;
    }).on("mouseover", () => {
      zoom_hide_btn.style("text-decoration", "underline");
    }).on("mouseout", () => {
      zoom_hide_btn.style("text-decoration", "none");
    });
    obj.zoom_slider = zoom_div.append("input").style("display", "block").style("padding", "5px").attr("type", "range").attr("min", 1).attr("max", 100).attr("value", obj.camera.fov).style("direction", "rtl").on("change", () => {
      obj.camera.fov = parseInt(obj.zoom_slider.node().value);
      obj.camera.updateProjectionMatrix();
    }).on("mousedown", (event) => {
      event.stopPropagation();
    });
  }
}
function make_menu(obj) {
  obj.menu.clear().showunder(obj.menu_button.node());
  if (obj.cells.categories) {
    obj.cells.categories.forEach((category, i) => {
      if (i != obj.use_category_index) {
        obj.menu.d.append("div").text(category.name).attr("class", "sja_menuoption").on("click", async () => {
          obj.menu.hide();
          obj.use_category_index = i;
          pcd_pipeline(obj);
        }).append("span").attr("font-family", font).style("display", category.values_count ? "inline-block" : "none").style("font-size", ".8em").style("float", "right").style("color", "#777").style("padding", "3px 5px").html(category.values_count ? "&nbsp;n=" + category.values_count : "");
      }
    });
  }
  if (obj.gene_expression) {
    obj.menu.d.append("div").text("Gene expression").attr("class", "sja_menuoption").on("click", () => {
      obj.menu.clear();
      const gene_search_div = obj.menu.d.append("div").style("padding", "10px");
      gene_searchbox({
        div: gene_search_div.append("div"),
        resultdiv: gene_search_div.append("div"),
        genome: obj.genome.name,
        callback: async (genename) => {
          const gmlst = await findgenemodel_bysymbol(obj.genome.name, genename);
          if (gmlst && gmlst[0]) {
            const gm = gmlst[0];
            if (!obj.gene_expression.genes) obj.gene_expression.genes = [];
            const geneidx = obj.gene_expression.genes.findIndex((i) => i.gene == genename);
            if (geneidx == -1) {
              obj.gene_expression.genes.push({
                gene: genename,
                chr: gm.chr,
                start: gm.start,
                stop: gm.stop
              });
              obj.use_gene_index = obj.gene_expression.genes.length - 1;
            } else {
              obj.use_gene_index = geneidx;
            }
          }
          obj.use_category_index = null;
          obj.menu.hide();
          pcd_pipeline(obj);
        }
      });
    });
    obj.menu.d.append("div").text("Multi-Gene Heatmap").attr("class", "sja_menuoption").on("click", () => {
      heatmap_menu(obj);
    });
    if (obj.gene_expression.genes) {
      if (obj.gene_expression.genes.length > 1) {
        obj.menu.d.append("div").style("padding", "5px 10px").text("Previously Selected");
      }
      obj.gene_expression.genes.forEach((gene, i) => {
        if (i != obj.use_gene_index) {
          obj.menu.d.append("div").text("Gene : " + gene.gene).attr("class", "sja_menuoption").on("click", async () => {
            obj.menu.hide();
            obj.use_category_index = null;
            obj.use_gene_index = i;
            pcd_pipeline(obj);
          });
        }
      });
    }
  }
}
function make_settings(obj) {
  obj.settings.clear().showunder(obj.settings_btn.node());
  const back_color_div = obj.settings.d.append("div").style("display", "block").text("Background Color").style("margin", "5px");
  const black_div = back_color_div.append("div").style("margin", "5px").style("display", "block");
  const name = Math.random(), idblack = Math.random(), idwhite = Math.random();
  const inputblack = black_div.append("input").attr("type", "radio").style("display", "inline-block").attr("name", name).attr("id", idblack).on("change", toggle_background);
  black_div.append("label").style("display", "inline-block").style("font-size", ".8em").text("Black").style("padding-left", "10px").attr("for", idblack);
  const white_div = back_color_div.append("div").style("margin", "5px").style("display", "block");
  const inputwhite = white_div.append("input").attr("type", "radio").style("display", "inline-block").attr("name", name).attr("id", idwhite).on("change", toggle_background);
  white_div.append("label").style("display", "inline-block").style("font-size", ".8em").text("White").style("padding-left", "10px").attr("for", idwhite);
  if (obj.use_background_color == 0) {
    inputwhite.property("checked", 1);
  } else {
    inputblack.property("checked", 1);
  }
  function toggle_background() {
    const isblack = obj.background_color == "black";
    obj.scene.background = new Color(isblack ? 16777215 : 0);
    obj.use_background_color = isblack ? 0 : 1;
    obj.background_color = isblack ? "white" : "black";
  }
  const point_size_div = obj.settings.d.append("div").style("display", "block").text("Point Size").style("margin", "20px 5px");
  const point_size_slider = point_size_div.append("input").style("display", "block").style("padding", "5px").attr("type", "range").attr("min", 1).attr("max", 70).attr("value", obj.scene.children[1].material.size * 100).on("change", () => {
    obj.scene.children[1].material.size = point_size_slider.node().value / 100;
  });
  if (obj.show_zoom == false) {
    const zoom_div = obj.settings.d.append("div").style("display", "block").text("Zoom").style("margin", "20px 5px");
    const zoom_hide_btn = zoom_div.append("div").style("margin-left", "10px").style("display", "inline-block").style("padding-left", "0px").style("padding-right", "2px").style("float", "right").style("font-size", ".6em").html("MOVE TO </br>LEGEND PANEL").on("click", () => {
      zoom_div.node().remove();
      obj.show_zoom = true;
      make_zoom_panel(obj);
    }).on("mouseover", () => {
      zoom_hide_btn.style("text-decoration", "underline");
    }).on("mouseout", () => {
      zoom_hide_btn.style("text-decoration", "none");
    });
    obj.zoom_slider = zoom_div.append("input").style("display", "block").style("padding", "5px").attr("type", "range").attr("min", 5).attr("max", 70).attr("value", obj.camera.fov).style("direction", "rtl").on("change", () => {
      obj.camera.fov = parseInt(obj.zoom_slider.node().value);
      obj.camera.updateProjectionMatrix();
    });
  }
  obj.settings.d.append("div").style("margin", "5px").text("Panning");
  obj.settings.d.append("div").style("font-size", ".8em").style("text-align", "center").style("margin-bottom", "10px").html('<p style="margin:3px;">Right mouse click </br>+ Mouse move</p>');
  if (!obj.canvas_2d) {
    obj.settings.d.append("div").style("margin", "5px").text("Rotate");
    obj.settings.d.append("div").style("font-size", ".8em").style("text-align", "center").style("margin-bottom", "10px").html('<p style="margin:3px;">Left mouse click </br>+ Mouse move</p>');
  }
  obj.settings.d.append("div").attr("class", "sja_menuoption").text("Reset view").style("width", "80px").style("margin", "auto").style("margin-top", "15px").style("margin-bottom", "15px").on("click", () => {
    inputwhite.property("checked", 1);
    obj.scene.background = new Color(obj.background_color ? obj.background_color : 16777215);
    point_size_slider.node().value = obj.point_size ? obj.point_size * 100 : obj.canvas_2d ? 30 : 5;
    obj.scene.children[1].material.size = obj.point_size ? obj.point_size : obj.canvas_2d ? 0.3 : 0.05;
    obj.camera.position.z = obj.data_sphere_r * 3;
    obj.camera.position.x = obj.canvas_2d ? 0 : 20;
    obj.camera.position.y = obj.canvas_2d ? 0 : -10;
    const cam_fov_default = 45;
    obj.camera.fov = parseInt(cam_fov_default);
    obj.camera.updateProjectionMatrix();
    obj.zoom_slider.node().value = cam_fov_default;
    if (obj.canvas_2d) {
      obj.camera.up.set(0, 1, 0);
    } else {
      obj.camera.up.set(0, 0, 1);
    }
    obj.controls.target.set(obj.center.x, obj.center.y, obj.center.z);
  });
  const screenshot_btn = obj.settings.d.append("div").attr("class", "sja_menuoption").style("padding", "5px").style("width", "80px").style("margin", "auto").style("margin-top", "15px").style("margin-bottom", "15px").html("<b>&#10697;</b> Capture").on("mousedown", () => {
    const imgData = obj.renderer.domElement.toDataURL();
    let imgNode = document.createElement("img");
    imgNode.src = imgData;
    screenshot_btn.append("span").style("display", "none").html(
      '<a download="scRNA_' + (/* @__PURE__ */ new Date()).toLocaleString() + '.png" href="' + imgNode.src + '" title="ImageName">Camera</a>'
    );
  }).on("mouseup", () => {
    screenshot_btn.select("a").node().click();
    screenshot_btn.selectAll("span").remove();
  });
}
function heatmap_menu(obj) {
  obj.menu.clear();
  obj.gene_expression.heatmap_genes = [];
  obj.use_heatmap_category_index = null;
  const heatmap_menu_div = obj.menu.d.append("div").style("display", "block").style("padding", "10px");
  const genes_div = heatmap_menu_div.append("div").style("display", "block");
  const gene_search_div = genes_div.append("div").style("display", "inline-block").style("vertical-align", "top").style("padding", "10px");
  gene_search_div.append("div").style("display", "block").style("padding-bottom", "5px").text("Add Genes");
  gene_search_div.append("textarea").attr("rows", "4").attr("cols", "20").attr("placeholder", "Type gene names seperated by space");
  const catagory_div = heatmap_menu_div.append("div");
  catagory_div.append("div").style("display", "block").style("padding-bottom", "5px").style("padding-left", "10px").text("Heatmap by");
  const cat_select = catagory_div.append("select").style("display", "block").style("margin-left", "10px");
  cat_select.append("option").attr("value", "none").text("-- Select--");
  if (obj.cells.categories) {
    obj.cells.categories.forEach((category, i) => {
      cat_select.append("option").attr("value", category.columnidx).text(category.name);
    });
  }
  cat_select.on("change", () => {
    obj.use_heatmap_category_index = parseInt(cat_select.node().value);
  });
  heatmap_menu_div.append("button").text("Heatmap").style("display", "block").style("float", "right").style("margin", "5px").on("click", async () => {
    const input_genelist = gene_search_div.select("textarea").node().value;
    const genelist = input_genelist.split(" ");
    for (const gene of genelist) {
      const gmlst = await findgenemodel_bysymbol(obj.genome.name, gene);
      if (gmlst && gmlst[0]) {
        const gm = gmlst[0];
        if (!obj.gene_expression.heatmap_genes) obj.gene_expression.heatmap_genes = [];
        const geneidx = obj.gene_expression.heatmap_genes.findIndex((i) => i.gene == gene);
        if (geneidx == -1) {
          obj.gene_expression.heatmap_genes.push({
            gene,
            chr: gm.chr,
            start: gm.start,
            stop: gm.stop
          });
        }
      }
    }
    const arg = {
      genome: obj.genome.name,
      getheatmap: {
        expfile: obj.gene_expression.file,
        gene_list: obj.gene_expression.heatmap_genes,
        cellfile: obj.cells.file,
        barcodecolumnidx: obj.cells.barcodecolumnidx,
        categorycolumnidx: parseInt(cat_select.node().value),
        delimiter: obj.cells.delimiter || "	"
      }
    };
    dofetch("singlecell", arg).then((data) => {
      if (data.error) throw data.error;
      obj.menu.hide();
      make_heatmap(data, obj, cat_select.node().value);
    });
  });
}
function make_plot(data, obj, colidx) {
  const gene = obj.gene_expression.genes[obj.use_gene_index];
  const pane = newpane({ x: 600, y: 400 });
  const cat = obj.cells.categories.find((x) => x.columnidx == colidx);
  pane.header.text("Violinplot/Boxplot for " + gene.gene + " Expression by " + cat.name);
  const control_panel = pane.pane.append("div").style("margin", "10px 10px 10px 15px");
  const svg = pane.pane.append("svg").style("margin", "10px");
  const plot_select = control_panel.append("select").style("display", "inline-block");
  plot_select.append("option").attr("value", "violin").text("Violin Plot");
  plot_select.append("option").attr("value", "box").text("Box Plot");
  plot_select.on("change", () => {
    apply();
  });
  const cell_select_div = control_panel.append("div").style("display", "inline-block").style("padding-left", "15px").text("Cells without expression");
  const cell_select = cell_select_div.append("select").style("display", "inline-block").style("margin-left", "5px");
  cell_select.append("option").attr("value", "include").text("Include");
  cell_select.append("option").attr("value", "exclude").text("Exclude");
  cell_select.on("change", () => {
    apply();
  });
  make_violin_plot(data, svg);
  function apply() {
    const gene2 = obj.gene_expression.genes[obj.use_gene_index];
    const arg = {
      genome: obj.genome.name,
      getgeneboxplot: {
        expfile: obj.gene_expression.file,
        chr: gene2.chr,
        start: gene2.start,
        stop: gene2.stop,
        genename: gene2.gene,
        cellfile: obj.cells.file,
        barcodecolumnidx: obj.cells.barcodecolumnidx,
        categorycolumnidx: parseInt(colidx),
        delimiter: obj.cells.delimiter || "	",
        category_customorder: cat.customorder && cat.values ? true : false,
        category_autoorder: !cat.customorder ? true : false,
        cat_values: cat.values,
        values_count: cat.values_count
      }
    };
    if (cell_select.node().value == "exclude") {
      arg.getgeneboxplot.exclude_cells = true;
    }
    dofetch("singlecell", arg).then((data2) => {
      if (data2.error) throw data2.error;
      if (plot_select.node().value == "box") make_box_plot(data2, svg);
      else if (plot_select.node().value == "violin") make_violin_plot(data2, svg);
    });
  }
}
function make_violin_plot(data, svg) {
  svg.selectAll("*").remove();
  let box_height = 50, box_width = 200, barspace = 2, axis_height = 30;
  const label_width = get_max_labelwidth(data.boxplots, svg);
  const x_scale = linear().range([0, box_width]).domain([data.minexpvalue, data.maxexpvalue]);
  const y_scale = linear().range([box_height, 0]).domain([0, 1]);
  const svg_height = data.boxplots.length * (box_height + barspace) + axis_height;
  const svg_width = box_width + label_width + 20;
  svg.transition().attr("width", svg_width).attr("height", svg_height);
  if (data.boxplots) {
    data.boxplots.forEach((boxplot, i) => {
      const g = svg.append("g").attr("transform", "translate(" + label_width + "," + (i * (box_height + barspace) + axis_height) + ")");
      const xlabel = g.append("text").text(boxplot.category + " (" + boxplot.numberofcells + ")").attr("transform", "translate(-4," + box_height / 2 + ")").attr("text-anchor", "end").attr("font-size", 15).attr("font-family", font).attr("dominant-baseline", "central");
      if (boxplot.density) {
        const density_max = Math.max(...boxplot.density);
        const line = line_default().x(function(d, j) {
          return x_scale(j * data.maxexpvalue / boxplot.density.length);
        }).y(function(d) {
          return y_scale(d / density_max) / 2;
        }).curve(monotoneX);
        const area = area_default().x(function(d, j) {
          return x_scale(j * data.maxexpvalue / boxplot.density.length);
        }).y0(function(d) {
          const temp = y_scale(d / density_max) / 2;
          return box_height - temp;
        }).y1(function(d) {
          return y_scale(d / density_max) / 2;
        }).curve(monotoneX);
        g.append("path").datum(boxplot.density).attr("class", "area").attr("d", area).style("fill", boxplot.color).style("stroke", "black").style("stroke-width", 0.5);
      }
    });
    const legendAxis = axisTop().scale(x_scale).ticks(5);
    svg.append("g").attr("class", "legend axis").attr("transform", "translate(" + label_width + "," + (axis_height - 10) + ")").call(legendAxis);
  }
}
function make_box_plot(data, svg) {
  svg.selectAll("*").remove();
  let box_height = 36, box_width = 200, barspace = 16, axis_height = 30;
  const label_width = get_max_labelwidth(data.boxplots, svg);
  const x_scale = linear().range([0, box_width]).domain([data.minexpvalue, data.maxexpvalue]);
  const y_scale = linear().range([box_height, 0]).domain([0, 1]);
  const svg_height = data.boxplots.length * (box_height + barspace) + axis_height;
  const svg_width = box_width + label_width + 20;
  svg.transition().attr("width", svg_width).attr("height", svg_height);
  if (data.boxplots) {
    data.boxplots.forEach((boxplot, i) => {
      const g = svg.append("g").attr("transform", "translate(" + label_width + "," + (i * (box_height + barspace) + axis_height) + ")");
      const xlabel = g.append("text").text(boxplot.category + " (" + boxplot.numberofcells + ")").attr("transform", "translate(-4," + box_height / 2 + ")").attr("text-anchor", "end").attr("font-size", 15).attr("font-family", font).attr("dominant-baseline", "central");
      if (boxplot.p75) {
        g.append("line").attr("x1", x_scale(boxplot.w1)).attr("y1", box_height / 2).attr("x2", x_scale(boxplot.w2)).attr("y2", box_height / 2).attr("stroke-width", 2).attr("stroke", "black");
        g.append("rect").attr("x", x_scale(boxplot.p25)).attr("y", 0).attr("width", x_scale(boxplot.p75 - boxplot.p25)).attr("height", box_height).attr("fill", boxplot.color);
        g.append("line").attr("x1", x_scale(boxplot.w1)).attr("y1", 0).attr("x2", x_scale(boxplot.w1)).attr("y2", box_height).attr("stroke-width", 2).attr("stroke", "black");
        g.append("line").attr("x1", x_scale(boxplot.p50)).attr("y1", 0).attr("x2", x_scale(boxplot.p50)).attr("y2", box_height).attr("stroke-width", 2).attr("stroke", "white");
        g.append("line").attr("x1", x_scale(boxplot.w2)).attr("y1", 0).attr("x2", x_scale(boxplot.w2)).attr("y2", box_height).attr("stroke-width", 2).attr("stroke", "black");
        if (boxplot.out) {
          for (const outlier of boxplot.out) {
            g.append("circle").attr("cx", x_scale(outlier.value)).attr("cy", box_height / 2).attr("r", 2).attr("fill", "#901739");
          }
        }
      }
    });
    const legendAxis = axisTop().scale(x_scale).ticks(5);
    svg.append("g").attr("class", "legend axis").attr("transform", "translate(" + label_width + "," + (axis_height - 10) + ")").call(legendAxis);
  }
}
function make_heatmap(data, obj, colidx) {
  const pane = newpane({ x: 600, y: 400 });
  const cat = obj.cells.categories.find((x) => x.columnidx == colidx);
  pane.header.text("Heatmap for Gene Expression by " + cat.name);
  const svg = pane.pane.append("svg").style("margin", "10px");
  let gene_list = [];
  for (const gene of data.gene_heatmap) gene_list.push(gene.genename);
  let means = [];
  data.gene_heatmap.forEach((gene, i) => {
    data.gene_heatmap[i].heatmap.forEach((category) => {
      means.push(category.mean);
    });
  });
  const max_mean = Math.max(...means);
  let categories = [];
  for (const category of data.gene_heatmap[0].heatmap)
    categories.push(category.category + " (" + category.numberofcells + ")");
  let box_height = 20, box_width = 80, barspace = 2, gene_lable_height = 30, legend_width = 60;
  const label_width = get_max_labelwidth(data.gene_heatmap[0].heatmap, svg);
  const svg_height = (box_height + barspace) * categories.length + gene_lable_height;
  const svg_width = (box_width + barspace) * gene_list.length + label_width + legend_width + 20;
  svg.transition().attr("width", svg_width).attr("height", svg_height);
  const x_scale = band().range([0, (box_width + barspace) * gene_list.length]).domain(gene_list).padding(0.05);
  svg.append("g").style("font-size", 15).attr("transform", "translate(" + label_width + ",0)").call(axisBottom(x_scale).tickSize(0)).select(".domain").remove();
  const y_scale = band().range([(box_height + barspace) * categories.length, 0]).domain(categories).padding(0.05);
  svg.append("g").style("font-size", 15).attr("transform", "translate(" + label_width + "," + gene_lable_height + ")").call(axisLeft(y_scale).tickSize(0)).select(".domain").remove();
  var myColor = sequential().interpolator(plasma).domain([0, max_mean]);
  const div = pane.pane.append("div").style("position", "absolute").attr("class", "tooltip").style("background-color", "white").style("border", "solid").style("border-width", "2px").style("border-radius", "5px").style("padding", "5px").style("opacity", 0);
  data.gene_heatmap.forEach((gene, i) => {
    data.gene_heatmap[i].heatmap.forEach((category, j) => {
      const g = svg.append("g").attr("transform", "translate(" + label_width + "," + gene_lable_height + ")");
      g.append("rect").attr("x", x_scale(gene.genename)).attr("y", y_scale(category.category + " (" + category.numberofcells + ")")).attr("width", x_scale.bandwidth()).attr("height", y_scale.bandwidth()).style("fill", myColor(category.mean)).style("stroke-width", 4).style("stroke", "none").style("opacity", 0.8).on("mouseover", function(event) {
        div.transition().duration(200).style("opacity", 0.9);
        div.html("Mean Expression: " + category.mean).style("left", pointer_default(event, this)[0] + 70 + "px").style("top", pointer_default(event, this)[1] + 20 + "px");
      }).on("mouseout", function() {
        div.transition().duration(500).style("opacity", 0);
      });
    });
  });
  const legend_data = myColor.ticks(10).reverse();
  legend_data.unshift(max_mean.toFixed(2));
  const legend = svg.selectAll(".legend").data(legend_data).enter().append("g").attr("class", "legend").attr("transform", function(d, i) {
    return "translate(" + (svg_width - legend_width) + "," + (30 + i * 20) + ")";
  });
  legend.append("rect").attr("width", 20).attr("height", 20).style("fill", myColor).style("opacity", 0.8);
  legend.append("text").attr("x", 26).attr("y", 10).attr("dy", ".35em").text(String);
}
function get_max_labelwidth(items, svg) {
  let textwidth = 0;
  for (const i of items) {
    svg.append("text").text(i.category + " (" + i.numberofcells + ")").attr("font-family", font).attr("font-size", 15).each(function() {
      textwidth = Math.max(textwidth, this.getBBox().width);
    }).remove();
  }
  return textwidth + 4;
}
async function make_legend(arg, obj) {
  if (!arg.legendimg) return;
  const img_div = obj.holder.append("div").style("margin-top", "5px");
  obj.legendimg = arg.legendimg;
  obj.legend = {
    holder: img_div,
    legendcolor: "#7D6836"
  };
  let shown = !arg.foldlegend;
  img_div.append("div").text("LEGEND").attr("class", "sja_clb").style("display", "inline-block").style("font-size", ".7em").style("color", obj.legend.legendcolor).style("font-family", font).on("click", () => {
    if (shown) {
      shown = false;
      disappear(div2);
    } else {
      shown = true;
      appear(div2);
    }
  });
  const div2 = obj.holder.append("div").style("border-top", "solid 1px " + obj.legend.legendcolor).style("background-color", "#FCFBF7");
  obj.legend.holder = div2.append("table").style("border-spacing", "15px").style("border-collapse", "separate");
  const [tr, td] = legend_newrow(obj, obj.legendimg.name || "");
  const data = await dofetch2("img?file=" + obj.legendimg.file);
  if (data.error) {
    td.text(data.error);
    return;
  }
  let fold = true;
  const img = td.append("img").attr("class", "sja_clbb").attr("src", data.src).style("height", "80px");
  img.on("click", () => {
    if (fold) {
      fold = false;
      img.transition().style("height", obj.legendimg.height ? obj.legendimg.height + "px" : "auto");
    } else {
      fold = true;
      img.transition().style("height", "80px");
    }
  });
}
export {
  init
};
//# sourceMappingURL=singlecell-RO3BL5OO.js.map
