import {
  blocklazyload
} from "./chunk-3SZ6GKLB.js";
import {
  hicParseFile
} from "./chunk-35SCELN4.js";
import {
  AppBase,
  ColorScale,
  axisstyle,
  first_genetrack_tolist,
  font,
  newpane,
  showErrorsWithCounter,
  tkt
} 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 {
  dofetch3
} from "./chunk-7EMWHCVW.js";
import "./chunk-7IYJZZQI.js";
import {
  StoreBase,
  getAppInit,
  getCompInit,
  getStoreInit
} from "./chunk-M3J4MINX.js";
import "./chunk-PF4DSFDR.js";
import "./chunk-QH6W3NVF.js";
import "./chunk-NOEAT6CX.js";
import {
  bplen
} from "./chunk-4UWS5Y3N.js";
import "./chunk-DD4R5P6W.js";
import "./chunk-JNITUVXP.js";
import "./chunk-KSGA62R2.js";
import {
  axisBottom,
  axisRight
} from "./chunk-LOZEKOES.js";
import "./chunk-TOU7EVFQ.js";
import {
  format,
  linear
} from "./chunk-OAWQ6LOO.js";
import "./chunk-TLT4YIG3.js";
import "./chunk-KYBIQBXE.js";
import {
  pointer_default,
  select_default
} from "./chunk-I6Y4O3RR.js";
import "./chunk-OMR2DT66.js";
import "./chunk-DQC5FFGV.js";
import "./chunk-HFNDKYVF.js";

// tracks/hic/HicStore.ts
var HicStore = class _HicStore extends StoreBase {
  constructor(opts) {
    super(opts);
    this.views = ["genome", "chrpair", "detail", "horizontal"];
    this.type = _HicStore.type;
    this.defaultState = {
      defaultNmeth: "NONE",
      loading: true,
      minBinNum_bp: 200,
      minFragSpan: 200,
      initialBinNum: 20,
      settings: {
        widthHeightPx: 800
      }
    };
  }
  static {
    this.type = "store";
  }
  init() {
    this.state = Object.assign({}, this.defaultState, this.state);
  }
};
var hicStoreInit = getStoreInit(HicStore);
HicStore.prototype.actions = {
  view_create(action) {
    if (!action.view) throw Error("view_create: missing view");
    if (!this.views.some((v) => v == action.view)) throw Error(`view_create: unknown view = ${action.view}`);
    this.state.currView = action.view;
    const opts = Object.assign(this.state, action.config);
    this.state = opts;
  },
  view_update(action) {
    if (action.view) {
      if (!this.views.some((v) => v == action.view)) throw Error(`view_update: unknown view = ${action.view}`);
      const opts = Object.assign(this.state[action.view], action.config);
      this.state[action.view] = opts;
    } else {
      const opts = Object.assign(this.state, action.config);
      this.state = opts;
    }
  },
  loading_active(action) {
    this.state.loading = action.active;
  }
};

// tracks/hic/dom/LoadingOverlay.ts
var LoadingOverlay = class _LoadingOverlay {
  constructor(opts) {
    this.hasStatePreMain = true;
    this.type = _LoadingOverlay.type;
    this.app = opts.app;
    this.loadingDiv = opts.loadingDiv;
  }
  static {
    this.type = "loadingOverlay";
  }
  reactsTo(action) {
    if (action.type.startsWith("loading")) return true;
    else return false;
  }
  getState(appState) {
    return appState;
  }
  init() {
    this.loadingDiv.append("div").attr("class", "sjpp-spinner").style("display", "");
  }
  main(appState) {
    const loading = this.app.getState(appState).loading;
    this.loadingDiv.style("display", loading ? "" : "none");
  }
};
var loadingInit = getCompInit(LoadingOverlay);

// tracks/hic/dom/ColorizeElement.ts
var ColorizeElement = class {
  colorizeElement(xCoord, yCoord, v, obj, width, height, bpMinV, bpMaxV, currView) {
    if (v >= 0) {
      const p = v >= bpMaxV ? 0 : v <= bpMinV ? 255 : Math.floor(255 * (bpMaxV - v) / bpMaxV);
      const positiveFill = `rgb(255, ${p}, ${p})`;
      if (currView === "genome") {
        obj.ctx.fillStyle = positiveFill;
        obj.ctx2.fillStyle = positiveFill;
      } else {
        obj.fillStyle = positiveFill;
      }
    } else {
      const p = v <= bpMinV ? 255 : Math.floor(255 * (bpMaxV + v) / bpMaxV);
      const negativeFill = `rgb(${p}, ${p}, 255)`;
      if (currView === "genome") {
        obj.ctx.fillStyle = negativeFill;
        obj.ctx2.fillStyle = negativeFill;
      } else {
        obj.fillStyle = negativeFill;
      }
    }
    if (currView === "genome") {
      obj.ctx.fillRect(yCoord, xCoord, width, height);
      obj.ctx2.fillRect(xCoord, yCoord, width, height);
    } else {
      obj.fillRect(xCoord, yCoord, width, height);
    }
  }
};

// tracks/hic/data/Resolution.ts
var Resolution = class {
  constructor(error) {
    this.error = error;
  }
  getResolution(state, hic) {
    if (state.currView == "genome") return hic["bpresolution"][0];
    if (state.currView == "chrpair") {
      const resolution = this.getChrPairResolution(hic, state.x, state.y, state.minBinNum_bp);
      return resolution;
    } else if (state.currView == "detail") {
      const maxBpWidth = Math.max(state.x.stop - state.x.start, state.y.stop - state.y.start);
      let resolution = this.findResFromArray(maxBpWidth, state.minBinNum_bp, hic.bpresolution);
      if (resolution == null && !hic.enzyme) {
        resolution = hic.bpresolution[hic.bpresolution.length - 1];
      }
      return resolution;
    } else {
      this.error(`Unknown view: ${state.currView}`);
      throw `Unknown view: ${state.currView}`;
    }
  }
  getChrPairResolution(hic, x, y, minBinNum_bp) {
    const chrxlen = hic.genome.chrlookup[x.chr.toUpperCase()].len;
    const chrylen = hic.genome.chrlookup[y.chr.toUpperCase()].len;
    const maxchrlen = Math.max(chrxlen, chrylen);
    const resolution = this.findResFromArray(maxchrlen, minBinNum_bp, hic.bpresolution);
    if (resolution == null) {
      this.error(`No suitable resolution for ${x.chr}-${y.chr} pair.`);
      return;
    }
    return resolution;
  }
  findResFromArray(max, defaultValue, resolutionArray, returnNum) {
    let resolution = null;
    for (const res of resolutionArray) {
      if (max / res > defaultValue) {
        resolution = res;
        break;
      }
    }
    if (returnNum && resolution == null) {
      resolution = resolutionArray[resolutionArray.length - 1];
    }
    return resolution;
  }
  getDefaultViewSpan(hic, x, y, initialBinNum, minBinNum_bp) {
    const chrpairResolution = this.getChrPairResolution(hic, x, y, minBinNum_bp);
    if (!chrpairResolution) return;
    return chrpairResolution * initialBinNum;
  }
};

// tracks/hic/data/Positions.ts
var Positions = class {
  constructor(error, minBinNum_bp) {
    this.error = error;
    this.resolution = new Resolution(this.error);
    this.minBinNum_bp = minBinNum_bp;
  }
  /**
   *
   * @param x x coordinate of pointer
   * @param y y coordinate of pointer
   * @param binpx
   * @param chrx x chr from state
   * @param chry y chr from state
   * @param hic
   * @param initialBinNum Set in the state
   * @returns
   */
  setPosition(x, y, binpx, chrx, chry, hic, initialBinNum) {
    const resolution = this.resolution.getChrPairResolution(hic, chrx, chry, this.minBinNum_bp);
    const viewrangebpw = resolution * initialBinNum;
    let coordx = Math.max(1, Math.floor(x * resolution / binpx) - viewrangebpw / 2);
    let coordy = Math.max(1, Math.floor(y * resolution / binpx) - viewrangebpw / 2);
    {
      const lenx = hic.genome.chrlookup[chrx.chr.toUpperCase()].len;
      if (coordx + viewrangebpw >= lenx) {
        coordx = lenx - viewrangebpw;
      }
      const leny = hic.genome.chrlookup[chry.chr.toUpperCase()].len;
      if (coordy + viewrangebpw > leny) {
        coordy = leny - viewrangebpw;
      }
    }
    const xObj = {
      chr: chrx.chr,
      start: coordx,
      stop: coordx + viewrangebpw
    };
    const yObj = {
      chr: chry.chr,
      start: coordy,
      stop: coordy + viewrangebpw
    };
    return [xObj, yObj];
  }
};

// tracks/hic/data/GridElementsFormattedData.ts
var GridElementsFormattedData = class {
  /**
   * Format the data for the grid elements.
   * @param items The data to format.
   * @param binpx The bin size in pixels.
   * @param resolution The resolution.
   * @param isFirstChrX Whether the first chromosome is X. Used for chrpair view.
   * @param isIntraChr Whether the data is intra-chromosomal. Used for chrpair view
   * @returns The formatted data.
   */
  formatData(view, items, binpx, resolution, isFirstChrX, isIntraChr) {
    const formattedData = [];
    for (const [xCoord, yCoord, value] of items) {
      const xPx = Math.floor(xCoord / resolution) * binpx;
      const yPx = Math.floor(yCoord / resolution) * binpx;
      if (view == "genome") {
        formattedData.push([xPx, yPx, value]);
      }
      if (view == "chrpair") {
        const x = isFirstChrX ? xPx : yPx;
        const y = isFirstChrX ? yPx : xPx;
        formattedData.push([x, y, value]);
        if (isIntraChr) {
          formattedData.push([y, x, value]);
        }
      }
    }
    return formattedData;
  }
};

// tracks/hic/data/FirstChrX.ts
var FirstChrX = class {
  constructor(chrorder, chrx, chry) {
    this.chrorder = chrorder;
    this.chrx = chrx;
    this.chry = chry;
  }
  isFirstX() {
    if (this.chrx == this.chry) return true;
    return this.chrorder.indexOf(this.chrx) < this.chrorder.indexOf(this.chry);
  }
};

// tracks/hic/chrpair/ChrPairView.ts
var ChrPairView = class {
  constructor(opts) {
    this.binpx = 1;
    /** padding on the ends of x/y chr coordinate axes */
    this.axispad = 10;
    this.axisLabelFontSize = 15;
    this.calcResolution = null;
    this.data = [];
    this.app = opts.app;
    this.hic = opts.hic;
    this.plotDiv = opts.plotDiv;
    this.items = opts.items;
    this.parent = opts.parent;
    this.chrxlen = this.hic.genome.chrlookup[this.parent("state").x.chr.toUpperCase()].len;
    this.chrylen = this.hic.genome.chrlookup[this.parent("state").y.chr.toUpperCase()].len;
    this.maxchrlen = Math.max(this.chrxlen, this.chrylen);
    this.colorizeElement = new ColorizeElement();
    this.positions = new Positions(opts.error, this.parent("state").minBinNum_bp);
    this.formattedData = new GridElementsFormattedData();
  }
  setDefaultBinpx() {
    if (this.calcResolution == null) return;
    while (this.binpx * this.maxchrlen / this.calcResolution < 600) {
      this.binpx++;
    }
  }
  renderAxes() {
    if (this.calcResolution == null) return;
    const svgY = this.plotDiv.yAxis.append("svg");
    const h = Math.ceil(this.chrylen / this.calcResolution) * this.binpx;
    svgY.attr("width", 100).attr("height", this.axispad * 2 + h);
    svgY.append("g").attr("data-testid", "sjpp-chrpair-svg-y").attr("transform", "translate(80," + (this.axispad + h / 2) + ")").append("text").text(this.parent("state").y.chr).attr("text-anchor", "middle").attr("font-size", this.axisLabelFontSize).attr("font-family", font).attr("dominant-baseline", "central").attr("transform", "rotate(90)");
    axisstyle({
      axis: svgY.append("g").attr("transform", `translate(1, ${this.axispad})`).call(axisRight(linear().domain([0, this.chrylen]).range([0, h])).tickFormat(format(".2s"))),
      showline: true
    });
    const svgX = this.plotDiv.xAxis.append("svg").style("margin-top", "-4px");
    const w = Math.ceil(this.chrxlen / this.calcResolution) * this.binpx;
    svgX.attr("height", 100).attr("width", this.axispad * 2 + w);
    svgX.append("text").attr("data-testid", "sjpp-chrpair-svg-x").text(this.parent("state").x.chr).attr("font-size", this.axisLabelFontSize).attr("font-family", font).attr("x", this.axispad + w / 2).attr("text-anchor", "middle").attr("y", 60);
    axisstyle({
      axis: svgX.append("g").attr("transform", "translate(" + this.axispad + ",1)").call(axisBottom(linear().domain([0, this.chrxlen]).range([0, w])).tickFormat(format(".2s"))),
      showline: true
    });
  }
  renderCanvas() {
    const initDetailView = this.initDetailView.bind(this);
    this.canvas = this.plotDiv.plot.append("canvas").attr("data-testid", "sjpp-chrpair-canvas").style("margin", this.axispad + "px").on("click", async function(event) {
      const [x, y] = pointer_default(event, this);
      initDetailView(x, y);
    }).node();
    this.canvas.width = Math.ceil(this.chrxlen / this.calcResolution) * this.binpx;
    this.canvas.height = Math.ceil(this.chrylen / this.calcResolution) * this.binpx;
    this.ctx = this.canvas.getContext("2d");
  }
  initDetailView(x, y) {
    const [xObj, yObj] = this.positions.setPosition(
      x,
      y,
      this.binpx,
      this.parent("state").x,
      this.parent("state").y,
      this.hic,
      this.parent("state").initialBinNum
    );
    this.app.dispatch({
      type: "view_create",
      view: "detail",
      config: {
        x: xObj,
        y: yObj
      }
    });
  }
  render() {
    this.calcResolution = this.parent("calcResolution");
    this.setDefaultBinpx();
    this.renderAxes();
    this.renderCanvas();
    this.update(this.items);
  }
  update(items) {
    this.items = items;
    const firstisx = new FirstChrX(this.hic.chrorder, this.parent("state").x.chr, this.parent("state").y.chr).isFirstX();
    const isintrachr = this.parent("state").x.chr === this.parent("state").y.chr;
    this.data = this.formattedData.formatData(
      "chrpair",
      items.items,
      this.binpx,
      this.calcResolution,
      firstisx,
      isintrachr
    );
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    for (const [x, y, v] of this.data) {
      this.colorizeElement.colorizeElement(
        x,
        y,
        v,
        this.ctx,
        this.binpx,
        //width
        this.binpx,
        //height
        this.parent("min"),
        this.parent("max"),
        "chrpair"
      );
    }
    this.app.dispatch({ type: "loading_active", active: false });
  }
};

// tracks/hic/horizontal/HorizontalView.ts
var HorizontalView = class {
  constructor(opts) {
    /** Defaults*/
    this.maxPercentage = 5;
    this.subPanelPxWidth = 600;
    this.borderColor = "rgba(200,0,0,.1)";
    this.app = opts.app;
    this.hic = opts.hic;
    this.plotDiv = opts.plotDiv;
    this.items = opts.items;
    this.parent = opts.parent;
  }
  setRunPpTracks() {
    const tracks = [
      {
        type: "hicstraw",
        file: this.hic.file,
        url: this.hic.url,
        name: this.hic.name,
        enzyme: this.hic.enzyme,
        maxpercentage: this.maxPercentage,
        pyramindup: 1
      }
    ];
    if (this.hic.tklst) {
      for (const t of this.hic.tklst) {
        tracks.push(t);
      }
    }
    first_genetrack_tolist(this.hic.genome, tracks);
    return tracks;
  }
  setRunPpArgs(tracks) {
    const arg = {
      holder: this.plotDiv.plot,
      hostURL: this.hic.hostURL,
      jwt: this.hic.jwt,
      genome: this.hic.genome,
      nobox: 1,
      tklst: tracks
    };
    const state = this.parent("state");
    if (state.x.chr == state.y.chr && Math.max(state.x.start, state.y.start) < Math.min(state.x.stop, state.y.stop)) {
      arg["chr"] = state.x.chr;
      arg["start"] = Math.min(state.x.start, state.y.start);
      arg["stop"] = Math.max(state.x.stop, state.y.stop);
    } else {
      arg["chr"] = state.x.chr;
      arg["start"] = state.x.start;
      arg["stop"] = state.x.stop;
      arg["width"] = this.subPanelPxWidth;
      arg["subpanels"] = [
        {
          chr: state.y.chr,
          start: state.y.start,
          stop: state.y.stop,
          width: this.subPanelPxWidth,
          leftpad: 10,
          leftborder: this.borderColor
        }
      ];
    }
    return arg;
  }
  render() {
    const tracks = this.setRunPpTracks();
    const arg = this.setRunPpArgs(tracks);
    blocklazyload(arg);
  }
  // update() {
  // 	//Not required.
  // 	//If controls are added back. Implement changes here.
  // }
};

// tracks/hic/detail/DetailBlock.ts
var DetailBlock = class {
  constructor(app, hic, blockwidth, bbmargin, holder, isYblock) {
    /** Defaults */
    this.leftheadw = 20;
    this.rightheadw = 40;
    this.lpad = 1;
    this.rpad = 1;
    this.firstRender = true;
    this.app = app;
    this.hic = hic;
    this.width = blockwidth;
    this.bbmargin = bbmargin;
    this.holder = holder;
    this.isYblock = isYblock;
    this.bbw = this.leftheadw + this.lpad + this.width + this.rpad + this.rightheadw + 2 * this.bbmargin;
    this.defaultLeft = this.bbmargin + this.leftheadw + this.lpad;
    this.defaultTop = this.bbmargin + this.rightheadw + this.rpad;
  }
  setArgs(chrObj) {
    const runPpArgs = {
      hostURL: this.hic.hostURL,
      genome: this.hic.genome,
      holder: this.holder,
      noresize: true,
      nobox: true,
      butrowbottom: true,
      style: {
        margin: `${this.bbmargin}px`
      },
      width: this.width,
      leftheadw: this.leftheadw,
      chr: chrObj.chr,
      start: chrObj.start,
      stop: chrObj.stop,
      rightheadw: this.rightheadw,
      tklst: [],
      rotated: this.isYblock,
      showreverse: this.isYblock
    };
    first_genetrack_tolist(this.hic.genome, runPpArgs.tklst);
    return runPpArgs;
  }
  setMethods(canvasHolder, canvas, sheath) {
    const runPpMethods = {
      onloadalltk_always: () => {
        this.onload(canvasHolder, canvas, sheath);
      },
      onpanning: (xoff) => this.onpanning(xoff, canvas)
    };
    if (this.isYblock) {
      runPpMethods["onsetheight"] = (bbh) => {
        this.holder.transition().style("left", `${bbh + this.bbmargin + 30}px`);
      };
    }
    return runPpMethods;
  }
  onload(canvasHolder, canvas, sheath) {
    this.bbw = this.block.leftheadw + this.block.lpad + this.block.width + this.block.rpad + this.block.rightheadw + 2 * this.bbmargin;
    if (this.isYblock) {
      sheath.transition().style("height", `${this.bbw}px`);
      canvasHolder.style("height", `${this.bbw}px`);
    } else canvasHolder.style("width", `${this.bbw}px`);
    if (this.isYblock) {
      this.defaultTop = this.bbmargin + this.block.rightheadw + this.block.rpad;
      canvas.style("top", `${this.defaultTop}px`);
    } else {
      this.defaultLeft = this.bbmargin + this.block.leftheadw + this.block.lpad;
      canvas.style("left", `${this.defaultLeft}px`);
    }
    const config = {
      [this.isYblock ? "y" : "x"]: {
        chr: this.block.rglst[0].chr,
        start: this.block.rglst[0].start,
        stop: this.block.rglst[0].stop
      }
    };
    if (this.firstRender == false) {
      this.app.dispatch({
        type: "view_update",
        config
      });
    } else {
      this.firstRender = false;
    }
  }
  onpanning(xoff, canvas) {
    if (!this.isYblock) canvas.style("left", `${xoff + this.defaultLeft}px`);
    else canvas.style("top", `${-xoff + this.defaultTop}px`);
  }
  async loadBlock(chrObj, canvasHolder, canvas, sheath) {
    const runPpArgs = this.setArgs(chrObj);
    const runPpMethods = this.isYblock ? this.setMethods(canvasHolder, canvas, sheath) : this.setMethods(canvasHolder, canvas);
    const args = Object.assign(runPpArgs, runPpMethods);
    await blocklazyload(args).then(async (block) => {
      this.block = block;
    });
  }
};

// tracks/hic/data/DetailDataFetcher.ts
var DetailDataFetcher = class {
  constructor(errList) {
    this.obj = {
      rglst: []
    };
    this.errList = errList;
  }
  isFragData(hic, resolution) {
    if (resolution != null) return;
    if (!hic.enzyme) {
      resolution = hic.bpresolution[hic.bpresolution.length - 1];
      return;
    }
  }
  formatFragArgs(enzymefile, chr) {
    const arg = {
      getdata: 1,
      getBED: 1,
      file: enzymefile,
      rglst: [chr]
    };
    return arg;
  }
  /**
   * @param arg Formated obj from either fn above
   * @param errList
   * @returns
   */
  async getBedData(arg) {
    try {
      return await dofetch3("tkbedj", { method: "POST", body: JSON.stringify(arg) });
    } catch (e) {
      this.errList.push(e.message || e);
      if (e.stack) console.error(e.stack);
    }
  }
  async getXFragData(hic, resolution, chrx) {
    this.isFragData(hic, resolution);
    if (!hic.enzymefile) return;
    const reqArgs = this.formatFragArgs(hic.enzymefile, chrx);
    return await this.getBedData(reqArgs);
  }
  async getYFragData(hic, chry) {
    const reqArgs = this.formatFragArgs(hic.enzymefile, chry);
    return await this.getBedData(reqArgs);
  }
  /**
   * Separate request from data fetch
   * Returns the additional fragment data
   * @param fragCoords
   */
  async fetchData(hic, detailView, resolution, x, y, fragCoords) {
    const fragStrawArgs = {
      matrixType: detailView.matrixType,
      file: hic.file,
      url: hic.url,
      pos1: this.determinePosition(hic, x, fragCoords?.x),
      pos2: this.determinePosition(hic, y, fragCoords?.y),
      nmeth: detailView.nmeth,
      resolution
    };
    if (fragCoords) fragStrawArgs["isfrag"] = true;
    try {
      const data = dofetch3("hicdata?", {
        method: "POST",
        body: JSON.stringify(fragStrawArgs)
      });
      if (data.error) {
        this.errList.push(data.error);
      }
      return data;
    } catch (e) {
      this.errList.push(e.message || e);
      if (e.stack) console.error(e.stack);
    }
  }
  determinePosition(hic, chr, fragCoord) {
    if (fragCoord) {
      return `${hic.nochr ? chr.chr.replace("chr", "") : chr.chr}:${fragCoord.start}:${fragCoord.stop}`;
    } else {
      return `${hic.nochr ? chr.chr.replace("chr", "") : chr.chr}:${chr.start}:${chr.stop}`;
    }
  }
};

// tracks/hic/data/ParseFragData.ts
var ParseFragData = class {
  constructor(errLst, items) {
    this.id2coord = /* @__PURE__ */ new Map();
    this.min = null;
    this.max = null;
    this.items = [];
    this.errLst = errLst;
    for (const i of items) {
      if (!i.rest || !i.rest[0]) {
        this.errLst.push("items[].rest data problem");
      }
      const id = Number.parseInt(i.rest[0]);
      if (Number.isNaN(id)) {
        this.errLst.push(`${i.start}.${i.stop} invalid fragment id: ${i.rest[0]}`);
      }
      this.id2coord.set(id, [i.start, i.stop]);
      if (this.min == null) {
        this.min = id;
        this.max = id;
      } else {
        this.min = Math.min(this.min, id);
        this.max = Math.max(this.max, id);
      }
    }
  }
};

// tracks/hic/data/DetailDataMapper.ts
var DetailDataMapper = class {
  constructor(hic, errList, parent) {
    this.frag = {
      x: {},
      y: {}
    };
    this.hic = hic;
    this.errList = errList;
    this.dataFetcher = new DetailDataFetcher(errList);
    this.parent = parent;
  }
  async getFragData(chrx, chry) {
    try {
      const xFragData = await this.dataFetcher.getXFragData(this.hic, this.parent("calcResolution"), chrx);
      if (xFragData) {
        if (!xFragData.items) {
          this.errList.push(`No fragment data returned for ${chrx.chr}:${chrx.start}-${chrx.stop}`);
          return;
        }
        const xParse = new ParseFragData(this.errList, xFragData.items);
        if (this.errList.length) return;
        this.frag.x = {
          id2coord: xParse.id2coord,
          start: xParse.min,
          stop: xParse.max
        };
        const yFragData = await this.dataFetcher.getYFragData(this.hic, chry);
        if (this.errList.length) return;
        if (yFragData) {
          if (!yFragData.items) {
            this.errList.push(`No fragment data returned for ${chry.chr}:${chry.start}-${chry.stop}`);
            return;
          }
          const yParse = new ParseFragData(this.errList, yFragData.items);
          if (this.errList.length) return;
          this.frag.y = {
            id2coord: /* @__PURE__ */ new Map(),
            start: yParse.min,
            stop: yParse.max
          };
          this.determineMap(yParse.id2coord, chrx, chry);
        }
      }
    } catch (e) {
      this.errList.push(e.message || e);
      if (e.stack) console.error(e.stack);
    }
  }
  determineMap(map, chrx, chry) {
    if (chrx.chr == chry.chr) {
      for (const [id, pos] of map) {
        this.frag.x["id2coord"].set(id, pos);
      }
      this.frag.y["id2coord"] = this.frag.x["id2coord"];
    } else {
      this.frag.y["id2coord"] = map;
    }
  }
  async getData(chrx, chry, fragCoords) {
    return await this.dataFetcher.fetchData(
      this.hic,
      this.parent("state")["detail"],
      this.parent("calcResolution"),
      chrx,
      chry,
      fragCoords
    );
  }
};

// tracks/hic/data/DetailCoodinates.ts
var DetailCoordinates = class {
  constructor(hic, errlist) {
    this.hic = hic;
    this.errlist = errlist;
  }
  getCoordinates(chrx, chry, data, resolution, widthHeightPx, fragData) {
    const isFirstX = new FirstChrX(this.hic.chrorder, chrx.chr, chry.chr).isFirstX();
    const isintrachr = chrx.chr == chry.chr;
    const xpxbp = widthHeightPx / (chrx.stop - chrx.start);
    const ypxbp = widthHeightPx / (chry.stop - chry.start);
    const list = this.calculateCoordinates(isFirstX, isintrachr, xpxbp, ypxbp, resolution, chrx, chry, data, fragData);
    return list;
  }
  calculateCoordinates(isFirstX, isintrachr, xpxbp, ypxbp, resolution, chrx, chry, data, fragData) {
    const list = [];
    for (const [xCoord, yCoord, value] of data.items) {
      let coord1, coord2, span1, span2;
      if (fragData) {
        const idx_start = isFirstX ? xCoord : yCoord;
        const idy_start = isFirstX ? yCoord : xCoord;
        if (fragData.x.id2coord.has(idx_start)) {
          const [a, b] = fragData.x.id2coord.get(idx_start);
          coord1 = a;
          span1 = b - a;
        } else {
          this.errlist.push(`[x id error] x: ${idx_start} y: ${idy_start}`);
          continue;
        }
        {
          const id_stop = idx_start + resolution;
          if (fragData.x.id2coord.has(id_stop)) {
            const ab = fragData.x.id2coord.get(id_stop);
            span1 = ab[1] - coord1;
          }
        }
        if (fragData.y.id2coord.has(idy_start)) {
          const [a, b] = fragData.y.id2coord.get(idy_start);
          coord2 = a;
          span2 = b - a;
        } else {
          this.errlist.push(`[y id error] x: ${idx_start} y: ${idy_start}`);
          continue;
        }
        {
          const id_stop = idy_start + resolution;
          if (fragData.y.id2coord.has(id_stop)) {
            const ab = fragData.y.id2coord.get(id_stop);
            span2 = ab[1] - coord2;
          }
        }
      } else {
        coord1 = isFirstX ? xCoord : yCoord;
        coord2 = isFirstX ? yCoord : xCoord;
        span1 = resolution;
        span2 = resolution;
      }
      if (isintrachr) {
        if (coord1 > chrx.start - span1 && coord1 < chrx.stop && coord2 > chry.start - span2 && coord2 < chry.stop) {
          list.push([
            Math.floor((coord1 - chrx.start) * xpxbp),
            Math.floor((coord2 - chry.start) * ypxbp),
            Math.ceil(span1 * xpxbp),
            Math.ceil(span2 * ypxbp),
            value
          ]);
        }
        if (coord2 > chrx.start - span2 && coord2 < chrx.stop && coord1 > chry.start && coord1 < chry.stop) {
          list.push([
            Math.floor((coord2 - chrx.start) * xpxbp),
            Math.floor((coord1 - chry.start) * ypxbp),
            Math.ceil(span2 * xpxbp),
            Math.ceil(span1 * ypxbp),
            value
          ]);
        }
        continue;
      }
      list.push([
        Math.floor((coord1 - chrx.start) * xpxbp),
        Math.floor((coord2 - chry.start) * ypxbp),
        Math.ceil(span1 * xpxbp),
        Math.ceil(span2 * ypxbp),
        value
      ]);
    }
    return list;
  }
};

// tracks/hic/detail/DetailView.ts
var DetailView = class {
  constructor(opts) {
    this.calcResolution = null;
    /** Defaults **/
    this.binpx = 2;
    this.bbmargin = 1;
    this.app = opts.app;
    this.hic = opts.hic;
    this.plotDiv = opts.plotDiv;
    this.sheath = opts.plotDiv.yAxis.append("div").style("position", "relative").style("width", "200px").style("height", "800px");
    this.rotor = this.sheath.append("div").style("position", "absolute").style("bottom", "0px").style("transform", "rotate(-90deg)").style("transform-origin", "left bottom");
    this.data = opts.data;
    this.parent = opts.parent;
    this.errList = this.parent("errList");
    this.resolution = new Resolution(opts.error);
    this.colorizeElement = new ColorizeElement();
    this.viewRangeBpW = this.resolution.getDefaultViewSpan(
      this.hic,
      this.parent("state").x,
      this.parent("state").y,
      this.parent("state").initialBinNum,
      this.parent("state").minBinNum_bp
    );
    this.dataMapper = new DetailDataMapper(this.hic, opts.error, opts.parent);
    this.coordinates = new DetailCoordinates(this.hic, this.errList);
  }
  renderCanvas(blockwidth) {
    this.canvasHolder = this.plotDiv.plot.append("div").style("position", "relative").style("width", `${this.xBlock.bbw}px`).style("height", `${this.yBlock.bbw}px`).style("overflow", "hidden");
    this.canvas = this.canvasHolder.append("canvas").style("display", "block").style("position", "absolute").attr("data-testid", "sjpp-hic-detail-canvas").attr("width", blockwidth).attr("height", blockwidth).on("mousedown", (event) => {
      const body = select_default(document.body);
      const x = event.clientX;
      const y = event.clientY;
      const oldx = Number.parseInt(this.canvas.style("left"));
      const oldy = Number.parseInt(this.canvas.style("top"));
      body.on("mousemove", (event2) => {
        const xoff = event2.clientX - x;
        const yoff = event2.clientY - y;
        this.xBlock.block.panning(xoff);
        this.yBlock.block.panning(yoff);
        this.canvas.style("left", `${oldx + xoff}px`).style("top", `${oldy + yoff}px`);
      });
      body.on("mouseup", (event2) => {
        body.on("mousemove", null).on("mouseup", null);
        const xoff = event2.clientX - x;
        const yoff = event2.clientY - y;
        this.xBlock.block.pannedby(xoff);
        this.yBlock.block.pannedby(yoff);
      });
    });
  }
  async render() {
    this.calcResolution = this.parent("calcResolution");
    const state = this.parent("state");
    this.xBlock = new DetailBlock(
      this.app,
      this.hic,
      state.settings.widthHeightPx,
      this.bbmargin,
      this.plotDiv.xAxis,
      false
    );
    this.yBlock = new DetailBlock(this.app, this.hic, state.settings.widthHeightPx, this.bbmargin, this.rotor, true);
    this.renderCanvas(state.settings.widthHeightPx);
    await this.xBlock.loadBlock(state.x, this.canvasHolder, this.canvas);
    await this.yBlock.loadBlock(state.y, this.canvasHolder, this.canvas, this.sheath);
    this.update(this.data);
  }
  async update(data) {
    if (data.items.length == 0) {
      this.app.dispatch({ type: "loading_active", active: false });
      return;
    }
    this.data = data;
    const state = this.parent("state");
    const initialWidth = Number.parseInt(this.canvas.attr("width"));
    const holderChange = Number.parseInt(this.canvasHolder.style("width")) - initialWidth + state.settings.widthHeightPx;
    this.canvasHolder.style("width", `${holderChange}px`).style("height", `${holderChange}px`);
    this.canvas.attr("width", `${state.settings.widthHeightPx}px`).attr("height", `${state.settings.widthHeightPx}px`);
    if (this.xBlock.block.width != state.settings.widthHeightPx) {
      this.xBlock.block.width = state.settings.widthHeightPx;
      this.yBlock.block.width = state.settings.widthHeightPx;
      this.xBlock.block.zoom2px(0, initialWidth);
      this.yBlock.block.zoom2px(0, initialWidth);
    }
    this.ctx = this.canvas.node().getContext("2d");
    const coords = this.coordinates.getCoordinates(
      state.x,
      state.y,
      this.parent("data"),
      this.parent("calcResolution"),
      state.settings.widthHeightPx,
      this.parent("fragData")
    );
    this.coords = coords;
    this.ctx.clearRect(0, 0, state.settings.widthHeightPx, state.settings.widthHeightPx);
    for (const [xCoord, yCoord, width, height, value] of this.coords) {
      this.colorizeElement.colorizeElement(
        xCoord,
        yCoord,
        value,
        this.ctx,
        width,
        height,
        this.parent("min"),
        this.parent("max"),
        "detail"
      );
    }
    this.app.dispatch({ type: "loading_active", active: false });
  }
};

// tracks/hic/axes/Chromosome.ts
var Chromosome = class {
  constructor(chr, width) {
    this.label = chr;
    this.width = width;
  }
};

// tracks/hic/grid/Grid.ts
var Grid = class _Grid {
  constructor(chrlst, resolution, chrlookup, binpx) {
    this.chromosomeMatrix = /* @__PURE__ */ new Map();
    this.chromosomeList = [];
    this.xoff = 0;
    this.yoff = 0;
    this.chr2px = [];
    this.chrlst = chrlst;
    let totalpx = this.chrlst.length;
    for (const chr of this.chrlst) {
      const w = Math.ceil(chrlookup[chr.toUpperCase()].len / resolution) * binpx;
      const chromosome = new Chromosome(chr, w);
      this.chromosomeList.push(chromosome);
      this.chr2px[chr] = w;
      totalpx += w;
    }
    this.totalpx = totalpx;
    for (let i = 0; i < this.chrlst.length; i++) {
      const chrx = this.chrlst[i];
      this.chromosomeMatrix.set(chrx, /* @__PURE__ */ new Map());
      let yoff = 0;
      for (let j = 0; j <= i; j++) {
        const chry = this.chrlst[j];
        const chrxChrLen = chrlookup[chrx.toUpperCase()].len;
        const chryChrLen = chrlookup[chry.toUpperCase()].len;
        const xbins = Math.ceil(chrxChrLen / resolution) * binpx;
        const ybins = Math.ceil(chryChrLen / resolution) * binpx;
        this.chromosomeMatrix.get(chrx).set(chry, {
          x: this.xoff,
          y: yoff,
          xbins,
          ybins,
          data: []
          //Empty array of data to fill later
        });
        yoff += this.chr2px[chry] + _Grid.borderWidth;
      }
      this.yoff = yoff;
      this.xoff += this.chr2px[chrx] + _Grid.borderWidth;
    }
  }
  static {
    this.checkerFill = "#DEF3FA";
  }
  static {
    this.spaceColor = "#CCC";
  }
  static {
    this.fontSize = 15;
  }
  static {
    this.font = font;
  }
  static {
    this.borderWidth = 1;
  }
  static {
    this.defaultChrLabWidth = 100;
  }
};

// tracks/hic/viewmodel/GridViewModel.ts
var GridViewModel = class {
  constructor(opts) {
    this.grid = new Grid(opts.hic.chrlst, opts.resolution || opts.hic.bpresolution[0], opts.hic.genome.chrlookup, 1);
  }
};

// tracks/hic/grid/GridRenderer.ts
var GridRenderer = class {
  constructor(svg, layerMap, layerSv, grid) {
    this.svg = svg;
    this.layerMap = layerMap.attr("transform", `translate(${Grid.defaultChrLabWidth}, ${Grid.fontSize})`);
    this.layerSv = layerSv.attr("transform", `translate(${Grid.defaultChrLabWidth}, ${Grid.fontSize})`);
    this.grid = grid;
  }
  render() {
    this.renderAxisX(this.grid);
    this.renderAxisY(this.grid);
    this.svg.attr("width", Grid.defaultChrLabWidth + this.grid.xoff).attr("height", Grid.fontSize + this.grid.yoff);
  }
  renderAxisX(grid) {
    let xoff = 0;
    this.grid.chromosomeList.forEach((chr, index) => {
      const chrw = chr.width;
      if (index % 2 === 0) {
        this.layerMap.append("rect").attr("x", xoff).attr("width", chrw).attr("height", Grid.fontSize).attr("y", -Grid.fontSize).attr("fill", Grid.checkerFill);
      }
      this.layerMap.append("text").attr("data-testid", `sjpp-hic-chr-xAxis-label`).attr("font-family", Grid.font).attr("text-anchor", "middle").attr("font-size", 12).attr("x", xoff + chrw / 2).text(chr.label);
      xoff += chrw;
      this.layerSv.append("line").attr("x1", xoff).attr("x2", xoff).attr("y2", grid.totalpx).attr("stroke", Grid.spaceColor).attr("shape-rendering", "crispEdges");
      xoff += Grid.borderWidth;
    });
  }
  renderAxisY(grid) {
    let yoff = 0;
    this.grid.chromosomeList.forEach((chr, index) => {
      const chrh = chr.width;
      if (index % 2 === 0) {
        this.layerMap.append("rect").attr("x", -Grid.defaultChrLabWidth).attr("width", Grid.defaultChrLabWidth).attr("height", chrh).attr("y", yoff).attr("fill", Grid.checkerFill);
      }
      this.layerMap.append("text").attr("data-testid", `sjpp-hic-chr-yAxis-label`).attr("font-family", Grid.font).attr("text-anchor", "end").attr("dominant-baseline", "central").attr("font-size", 12).attr("y", yoff + chrh / 2).text(chr.label);
      yoff += chrh;
      this.layerSv.append("line").attr("x2", grid.totalpx).attr("y1", yoff).attr("y2", yoff).attr("stroke", Grid.spaceColor).attr("shape-rendering", "crispEdges");
      yoff += Grid.borderWidth;
    });
  }
};

// tracks/hic/genome/ChrsTooltips.ts
var ChrsTooltips = class {
  constructor() {
    this.chrx_tip = new Menu({ border: "solid 1px #ccc", padding: "0px", offsetX: 0, offsetY: 0 });
    this.chry_tip = new Menu({ border: "solid 1px #ccc", padding: "0px", offsetX: 0, offsetY: 0 });
  }
  render(img, chrx, chry) {
    const p = img.node().getBoundingClientRect();
    this.chrx_tip.clear().show(p.left, p.top).d.style("top", null).attr("data-testid", "sjpp-hic-chrx-tooltip").style("bottom", window.innerHeight - p.top - window.pageYOffset + "px").text(chrx);
    this.chry_tip.clear().show(p.left, p.top).d.style("left", null).attr("data-testid", "sjpp-hic-chry-tooltip").style("right", document.body.clientWidth - p.left - window.pageXOffset + "px").text(chry);
  }
};

// tracks/hic/grid/GridElementRenderer.ts
var GridElementRenderer = class {
  constructor(layerMap, app) {
    this.app = app;
    this.layerMap = layerMap;
    this.tooltips = new ChrsTooltips();
  }
  render(obj, lead, follow, holder) {
    obj.canvas = holder.append("canvas").style("display", "none").node();
    obj.ctx = obj.canvas.getContext("2d");
    obj.canvas.width = obj.xbins;
    obj.canvas.height = obj.ybins;
    obj.img = this.layerMap.append("image").attr("width", obj.canvas.width).attr("height", obj.canvas.height).attr("x", obj.x).attr("y", obj.y).on("click", async () => {
      await this.app.dispatch({
        type: "view_create",
        view: "chrpair",
        config: {
          x: {
            chr: lead
          },
          y: {
            chr: follow
          }
        }
      });
    }).on("mouseover", () => {
      this.tooltips.render(obj.img, lead, follow);
    });
    if (lead != follow) {
      obj.canvas2 = holder.append("canvas").style("display", "none").node();
      obj.canvas2.width = obj.ybins;
      obj.canvas2.height = obj.xbins;
      obj.ctx2 = obj.canvas2.getContext("2d");
      obj.img2 = this.layerMap.append("image").attr("width", obj.canvas2.width).attr("height", obj.canvas2.height).attr("x", obj.y).attr("y", obj.x).on("click", async () => {
        await this.app.dispatch({
          type: "view_create",
          view: "chrpair",
          config: {
            x: {
              chr: follow
            },
            y: {
              chr: lead
            }
          }
        });
      }).on("mouseover", () => {
        this.tooltips.render(obj.img2, follow, lead);
      });
    } else {
      obj.ctx2 = obj.ctx;
    }
  }
};

// tracks/hic/grid/GridElementsRenderer.ts
var GridElementsRenderer = class {
  constructor(grid, layerMap, app) {
    this.grid = grid;
    this.gridElementRenderer = new GridElementRenderer(layerMap, app);
  }
  render(holder) {
    for (const [chrx, chryMap] of this.grid.chromosomeMatrix) {
      for (const [chry, gridElem] of chryMap) {
        this.gridElementRenderer.render(gridElem, chrx, chry, holder);
      }
    }
  }
};

// tracks/hic/genome/GenomeView.ts
var GenomeView = class {
  constructor(opts) {
    /** Dom */
    this.tip = new Menu();
    this.pica_x = new Menu({ border: "solid 1px #ccc", padding: "0px", offsetX: 0, offsetY: 0 });
    this.pica_y = new Menu({ border: "solid 1px #ccc", padding: "0px", offsetX: 0, offsetY: 0 });
    /** Data */
    /** px width for each chr */
    this.chr2px = {};
    this.lead2follow = /* @__PURE__ */ new Map();
    this.values = [];
    /** Defaults */
    this.defaultChrLabWidth = 100;
    this.borderwidth = 1;
    this.default_maxVPerc = 5;
    this.fontsize = 15;
    this.xoff = 0;
    this.yoff = 0;
    this.binpx = 1;
    this.atdev_chrnum = 8;
    this.min = 0;
    this.max = 0;
    this.app = opts.app;
    this.hic = opts.hic;
    this.plotDiv = opts.plotDiv;
    this.data = opts.data;
    this.parent = opts.parent;
    this.resolution = opts.resolution || opts.hic.bpresolution[0];
    this.svg = this.plotDiv.plot.append("svg").attr("data-testid", "sjpp-hic-genome-svg");
    this.layer_map = this.svg.append("g").attr("data-testid", "sjpp-hic-layerMap-g");
    this.layer_sv = this.svg.append("g").attr("data-testid", "sjpp-hic-layerSv-g");
    this.colorizeElement = new ColorizeElement();
    this.viewModel = new GridViewModel(opts);
    this.viewRender = new GridRenderer(this.svg, this.layer_map, this.layer_sv, this.viewModel.grid);
    this.gridElementsRenderer = new GridElementsRenderer(this.viewModel.grid, this.layer_map, this.app);
    this.gridFormattedData = new GridElementsFormattedData();
  }
  makeSv() {
    const unknownchr = /* @__PURE__ */ new Set();
    const radius = 8;
    for (const item of this.hic.sv.items) {
      const _o = this.lead2follow.get(item.chr1);
      if (!_o) {
        unknownchr.add(item.chr1);
        continue;
      }
      const obj = _o.get(item.chr2);
      if (!obj) {
        unknownchr.add(item.chr2);
        continue;
      }
      const p1 = item.position1 / this.resolution;
      const p2 = item.position2 / this.resolution;
      this.layer_sv.append("circle").attr("data-testid", "sjpp-hic-genome-view-sv-circle").attr("stroke", "black").attr("fill", "white").attr("fill-opacity", 0).attr("cx", obj.x + p1).attr("cy", obj.y + p2).attr("r", radius).on("mouseover", (event) => {
        this.tooltipSv(event, item);
      }).on("mouseout", () => {
        this.tip.hide();
      }).on("click", () => {
        this.clickSv(item);
      });
      if (obj.img2) {
        this.layer_sv.append("circle").attr("data-testid", "sjpp-hic-genome-view-sv-circle-2").attr("stroke", "black").attr("fill", "white").attr("fill-opacity", 0).attr("cy", obj.x + p1).attr("cx", obj.y + p2).attr("r", radius).on("mouseover", (event) => {
          this.tooltipSv(event, item);
        }).on("mouseout", () => {
          this.tip.hide();
        }).on("click", () => {
          this.clickSv(item);
        });
      }
    }
  }
  tooltipSv(event, item) {
    this.tip.clear().show(event.clientX, event.clientY).d.append("div").text(
      item.chr1 == item.chr2 ? `${item.chr1}:${item.position1}-${item.position2}` : `${item.chr1}:${item.position1}>${item.chr2}:${item.position2}`
    );
  }
  clickSv(item) {
    const default_svpointspan = 5e5;
    const default_subpanelpxwidth = 600;
    const subpanel_bordercolor = "rgba(200,0,0,.1)";
    const pane = newpane({ x: 100, y: 100 });
    pane.header.text(
      this.hic.name + " " + (item.chr1 == item.chr2 ? `${item.chr1}:${item.position1}-${item.position2}` : `${item.chr1}:${item.position1}>${item.chr2}:${item.position2}`)
    );
    const tracks = [
      {
        type: tkt.hicstraw,
        file: this.hic.file,
        enzyme: this.hic.enzyme,
        maxpercentage: this.default_maxVPerc,
        pyramidup: 1,
        name: this.hic.name
      }
    ];
    if (this.hic.tklst) {
      for (const t of this.hic.tklst) {
        tracks.push(t);
      }
    }
    first_genetrack_tolist(this.hic.genome, tracks);
    const arg = {
      holder: pane.body,
      hostURL: this.hic.hostURL,
      jwt: this.hic.jwt,
      genome: this.hic.genome,
      nobox: 1,
      tklst: tracks
    };
    if (item.chr1 == item.chr2 && Math.abs(item.position2 - item.position1) < default_svpointspan * 2) {
      arg.chr = item.chr1;
      const w = Math.abs(item.position2 - item.position1);
      arg.start = Math.max(1, Math.min(item.position1, item.position2) - w);
      arg.stop = Math.min(
        this.hic.genome.chrlookup[item.chr1.toUpperCase()].len,
        Math.max(item.position1, item.position2) + w
      );
    } else {
      arg.chr = item.chr1;
      arg.start = Math.max(1, item.position1 - default_svpointspan / 2);
      arg.stop = Math.min(
        this.hic.genome.chrlookup[item.chr1.toUpperCase()].len,
        item.position1 + default_svpointspan / 2
      );
      arg.width = default_subpanelpxwidth;
      arg.subpanels = [
        {
          chr: item.chr2,
          start: Math.max(1, item.position2 - default_svpointspan / 2),
          stop: Math.min(
            this.hic.genome.chrlookup[item.chr2.toUpperCase()].len,
            item.position2 + default_svpointspan / 2
          ),
          width: default_subpanelpxwidth,
          leftpad: 10,
          leftborder: subpanel_bordercolor
        }
      ];
    }
    blocklazyload(arg);
  }
  render() {
    this.viewRender.render();
    this.gridElementsRenderer.render(this.hic.holder);
    if (this.hic.sv && this.hic.sv.items) {
      this.makeSv();
    }
    this.update(this.data);
  }
  update(data) {
    this.data = data;
    this.min = this.parent("min");
    this.max = this.parent("max");
    for (const data2 of this.data) {
      if (!this.hic.chrlst.includes(data2.lead) || !this.hic.chrlst.includes(data2.follow)) continue;
      const obj = this.viewModel.grid.chromosomeMatrix.get(data2.lead).get(data2.follow);
      if (!obj) continue;
      obj.ctx.clearRect(0, 0, obj.canvas.width, obj.canvas.height);
      if (obj.canvas2) {
        obj.ctx2.clearRect(0, 0, obj.canvas2.width, obj.canvas.height);
      }
      obj.data = this.gridFormattedData.formatData("genome", data2.items, this.binpx, this.resolution);
      for (const [xPx, yPx, value] of obj.data) {
        this.colorizeElement.colorizeElement(xPx, yPx, value, obj, this.binpx, this.binpx, this.min, this.max, "genome");
      }
      obj.img.attr("xlink:href", obj.canvas.toDataURL());
      if (obj.canvas2) {
        obj.img2.attr("xlink:href", obj.canvas2.toDataURL());
      }
    }
    this.app.dispatch({ type: "loading_active", active: false });
  }
};

// tracks/hic/controls/NormalizationMethodControl.ts
var NormalizationMethodControl = class {
  constructor(holder, normalization, defaultNmeth, callback) {
    this.holder = holder;
    this.normalization = normalization;
    this.defaultNmeth = defaultNmeth;
    this.callback = callback;
  }
  render() {
    if (!this.normalization?.length) {
      this.nmethSelect = this.holder.text(this.defaultNmeth);
    } else {
      this.nmethSelect = this.holder.attr("data-testid", "sjpp-hic-nmeth-select").style("margin-right", "10px").append("select").on("change", async () => {
        const nmeth = this.nmethSelect.node().value;
        this.callback(nmeth, "nmeth");
      });
      for (const n of this.normalization) {
        this.nmethSelect.append("option").text(n);
      }
    }
  }
  update(option) {
    const options = this.nmethSelect.node().options;
    if (!options) return;
    const selectedNmeth = Array.from(options).find((o) => o.value === option);
    if (!selectedNmeth) throw `Invalid normalization method: ${option}`;
    selectedNmeth.selected = true;
  }
};

// tracks/hic/controls/CutoffControl.ts
var CutoffControl = class {
  constructor(holder, value, callback) {
    this.holder = holder;
    this.value = value;
    this.callback = callback;
  }
  render(opts) {
    const cutoffDiv = this.holder.attr("data-testid", "sjpp-hic-cutoff-control").style("margin-right", "10px").append("input").attr("type", "number").style("width", opts && "width" in opts ? opts.width : "80px").style("margin-left", "0px").attr("type", "number").property("value", this.value).on("keyup", async (event) => {
      if (event.code != "Enter") return;
      const v = event.target.value;
      this.callback(v);
    });
    return cutoffDiv;
  }
};

// tracks/hic/controls/MatrixTypeControl.ts
var MatrixTypeControl = class {
  constructor(holder, callback) {
    this.values = [
      { label: "Observed", value: "observed" },
      { label: "Expected", value: "expected" },
      { label: "Observed/Expected", value: "oe" },
      { label: "Log(Observed/Expected)", value: "log(oe)" }
      // TODO: add server side logic to support these options
      // { label: 'Log(Observed + 1)', value: 'log(obs+1)'},
      // { label: 'Observed Pearson', value: 'op'}
    ];
    this.holder = holder;
    this.callback = callback;
  }
  render() {
    this.matrixSelect = this.holder.attr("data-testid", "sjpp-hic-matrix-type-select").style("margin-right", "10px").append("select").on("change", async () => {
      this.callback(this.matrixSelect.node().value, "matrixType");
    });
    for (const matrixType of this.values) {
      this.matrixSelect.append("option").text(matrixType.label).attr("value", matrixType.value);
    }
  }
  update(option) {
    const options = this.matrixSelect.node().options;
    if (!options) return;
    const selectedNmeth = Array.from(options).find((o) => o.value === option);
    if (!selectedNmeth) throw `Invalid normalization method: ${option}`;
    selectedNmeth.selected = true;
  }
};

// tracks/hic/controls/ControlPanel.ts
var ControlPanel = class _ControlPanel {
  constructor(opts) {
    this.zoomBlockCallback = (bool) => {
      const detailView = this.parent("detail");
      detailView.xBlock.block.zoomblock(2, bool);
      detailView.yBlock.block.zoomblock(2, bool);
    };
    this.minCallback = (v) => {
      if (Number(v) > Number(this.parent("max"))) {
        this.error("Min cutoff cannot be greater than max cutoff");
      } else {
        this.parent("min", Number(v));
        this.reColorHeatmap();
        this.parent("infoBar").update();
      }
    };
    this.maxCallback = (v) => {
      if (Number(v) < Number(this.parent("min"))) {
        this.error(`Max cutoff cannot be less than min cutoff`);
      } else if (Number(v) < 0) {
        this.error(`Max cutoff cannot be less than 0`);
      } else {
        this.parent("max", Number(v));
        this.reColorHeatmap();
        this.parent("infoBar").update();
      }
    };
    this.reColorHeatmap = () => {
      if (this.parent("activeView") == "genome") {
        const genomeView = this.parent("genome");
        const chrMatrix = genomeView.viewRender.grid.chromosomeMatrix;
        if (!chrMatrix) return;
        for (const map of chrMatrix) {
          const yMap = map[1];
          for (const obj of yMap) {
            const canvasObj = obj[1];
            if (!canvasObj) continue;
            for (const [leadpx, followpx, val] of canvasObj.data) {
              this.colorizeElement.colorizeElement(
                leadpx,
                followpx,
                val,
                canvasObj,
                1,
                1,
                this.parent("min"),
                this.parent("max"),
                "genome"
              );
            }
            canvasObj.img.attr("xlink:href", canvasObj.canvas.toDataURL());
            if (canvasObj.canvas2) {
              canvasObj.img2.attr("xlink:href", canvasObj.canvas2.toDataURL());
            }
          }
        }
      } else if (this.parent("activeView") == "chrpair") {
        const view = this.parent("chrpair");
        for (const [xCoord, yCoord, val] of view.data) {
          this.colorizeElement.colorizeElement(
            xCoord,
            yCoord,
            val,
            view.ctx,
            view.binpx,
            view.binpx,
            this.parent("min"),
            this.parent("max"),
            "chrpair"
          );
        }
      } else if (this.parent("activeView") == "detail") {
        const view = this.parent("detail");
        for (const [xCoord, yCoord, width, height, value] of view.coords) {
          this.colorizeElement.colorizeElement(
            xCoord,
            yCoord,
            value,
            view.ctx,
            width,
            height,
            this.parent("min"),
            this.parent("max"),
            "detail"
          );
        }
      }
    };
    this.dropdownCallback = (v, prop) => {
      const state = this.app.getState();
      this.app.dispatch({
        type: "view_update",
        view: state.currView,
        config: { [prop]: v }
      });
    };
    this.type = _ControlPanel.type;
    this.controls = {};
    this.app = opts.app;
    this.controlsDiv = opts.controlsDiv;
    this.hic = opts.hic;
    this.colorizeElement = new ColorizeElement();
    this.parent = opts.parent;
    this.error = opts.error;
  }
  static {
    this.type = "controlPanel";
  }
  getState(appState) {
    return appState;
  }
  addLabel(tr, text) {
    return tr.append("td").style("color", "#858585").style("font-size", ".8em").style("vertical-align", text.toUpperCase() == "VIEW" ? "top" : "middle").text(text.toUpperCase());
  }
  init() {
    const state = this.app.getState();
    const menuWrapper = this.controlsDiv.attr("data-testid", "sjpp-hic-control-panel").style("background", "rgb(253, 250, 244)").style("vertical-align", "top").style("padding", "5px").style("border", "solid 0.5px #ccc");
    let menuVisible = true;
    this.controlsDiv.append("button").attr("data-testid", "sjpp-hic-burger-btn").style("display", "block").style("border", "none").style("font-size", "1.5em").style("background", "none").html("&#8801;").on("click", () => {
      menuVisible = !menuVisible;
      menu.style("display", menuVisible ? "block" : "none");
    });
    const menu = menuWrapper.append("div").attr("class", "sjpp-hic-menu").attr("data-testid", "sjpp-hic-menu").style("display", menuVisible ? "block" : "none");
    const menuTable = menu.append("table").style("border-spacing", "3px");
    this.controls.normalizationRow = menuTable.append("tr");
    this.addLabel(this.controls.normalizationRow, "NORMALIZATION");
    this.controls.nmeth = new NormalizationMethodControl(
      this.controls.normalizationRow.append("td").attr("class", "sjpp-nmeth-select"),
      this.hic.normalization,
      state.defaultNmeth,
      this.dropdownCallback
    );
    this.controls.nmeth.render();
    this.controls.minCutoffRow = menuTable.append("tr");
    this.controls.minCutoffLabel = this.addLabel(this.controls.minCutoffRow, "");
    this.controls.inputBpMinV = new CutoffControl(
      this.controls.minCutoffRow.append("td"),
      this.parent("min"),
      this.minCallback
    ).render({ width: "120px" });
    this.controls.maxCutoffRow = menuTable.append("tr");
    this.controls.maxCutoffLabel = this.addLabel(this.controls.maxCutoffRow, "");
    this.controls.inputBpMaxV = new CutoffControl(
      this.controls.maxCutoffRow.append("td"),
      this.parent("max"),
      this.maxCallback
    ).render({ width: "120px" });
    this.controls.matrixTypeRow = menuTable.append("tr");
    this.addLabel(this.controls.matrixTypeRow, "matrix type");
    this.controls.matrixType = new MatrixTypeControl(this.controls.matrixTypeRow.append("td"), this.dropdownCallback);
    this.controls.matrixType.render();
    this.controls.widthRow = menuTable.append("tr");
    this.addLabel(this.controls.widthRow, "WIDTH / height");
    this.controls.width = this.controls.widthRow.append("td");
    this.controls.width.attr("data-testid", "sjpp-hic-block-size-input").style("margin-right", "10px").append("input").attr("type", "number").style("width", "80px").style("margin-left", "0px").attr("type", "number").property("value", state.width).on("keyup", async (event) => {
      if (event.code != "Enter") return;
      const v = event.target.value;
      this.app.dispatch({
        type: "view_update",
        config: {
          settings: { widthHeightPx: Number.parseInt(v) }
        }
      });
    });
    const viewRow = menuTable.append("tr");
    this.addLabel(viewRow, "VIEW");
    const viewBtnDiv = viewRow.append("td");
    this.controls.view = viewBtnDiv.append("span").attr("data-testid", "sjpp-hic-view-span").style("padding-right", "5px").style("display", "block");
    this.controls.genomeViewBtn = viewBtnDiv.append("button").attr("data-testid", "sjpp-hic-genome-view-btn").style("display", "none").style("padding", "2px").style("margin-top", "4px").html("&#8810; Genome").on("click", async () => {
      await this.app.dispatch({
        type: "view_create",
        view: "genome",
        config: {
          x: {},
          y: {}
        }
      });
    });
    this.controls.chrpairViewBtn = viewBtnDiv.append("button").attr("data-testid", "sjpp-hic-chrpair-view-btn").style("display", "none").style("padding", "2px").style("margin", "4px 0px").on("click", async () => {
      const currState = this.app.getState();
      await this.app.dispatch({
        type: "view_create",
        view: "chrpair",
        config: {
          x: {
            chr: currState.x.chr
          },
          y: {
            chr: currState.y.chr
          }
        }
      });
    });
    this.controls.horizontalViewBtn = viewBtnDiv.append("button").attr("data-testid", "sjpp-hic-horizontal-view-btn").style("display", "none").style("padding", "2px").style("margin", "4px 0px").html("Horizontal View &#8811;").on("click", async () => {
      await this.app.dispatch({
        type: "view_create",
        view: "horizontal"
      });
    });
    this.controls.detailViewBtn = viewBtnDiv.append("button").attr("data-testid", "sjpp-hic-detail-view-btn").style("display", "none").style("padding", "2px").style("margin", "4px 0px").html("&#8810; Detailed View").on("click", async () => {
      await this.app.dispatch({
        type: "view_create",
        view: "detail"
      });
    });
    this.controls.zoomDiv = menuTable.append("tr").attr("data-testid", "sjpp-hic-zoom-div").style("display", state.currView == "detail" ? "contents" : "none");
    this.addLabel(this.controls.zoomDiv, "ZOOM");
    const zoomDiv = this.controls.zoomDiv.append("td");
    this.controls.zoomIn = zoomDiv.append("button").attr("data-testid", "sjpp-hic-zoom-in-btn").style("margin-right", "10px").text("In").on("click", () => {
      this.zoomBlockCallback(false);
    });
    this.controls.zoomOut = zoomDiv.append("button").attr("data-testid", "sjpp-hic-zoom-out-btn").style("margin-right", "10px").text("Out").on("click", () => {
      this.zoomBlockCallback(true);
    });
  }
  showBtns() {
    const state = this.app.getState();
    this.controls.genomeViewBtn.style("display", state.currView === "genome" ? "none" : "inline-block");
    if (state.currView === "detail") {
      this.controls.chrpairViewBtn.html(`&#8810; Entire ${state.x.chr}-${state.y.chr}`).style("display", "block");
      this.controls.horizontalViewBtn.style("display", "block");
      this.controls.zoomDiv.style("display", "contents");
      this.controls.detailViewBtn.style("display", "none");
    } else if (state.currView === "horizontal") {
      this.controls.chrpairViewBtn.html(`&#8810; Entire ${state.x.chr}-${state.y.chr}`).style("display", "block");
      this.controls.detailViewBtn.style("display", "block");
      this.controls.horizontalViewBtn.style("display", "none");
      this.controls.zoomDiv.style("display", "none");
    } else {
      this.controls.chrpairViewBtn.style("display", "none");
      this.controls.horizontalViewBtn.style("display", "none");
      this.controls.detailViewBtn.style("display", "none");
      this.controls.zoomDiv.style("display", "none");
      this.controls.widthRow.style("display", "none");
    }
  }
  showHideControls() {
    const state = this.app.getState();
    this.controls.normalizationRow.style("display", state.currView == "horizontal" ? "none" : "");
    this.controls.minCutoffRow.style("display", state.currView == "horizontal" ? "none" : "");
    this.controls.maxCutoffRow.style("display", state.currView == "horizontal" ? "none" : "");
    this.controls.matrixTypeRow.style("display", state.currView == "horizontal" ? "none" : "");
    this.controls.widthRow.style("display", state.currView == "detail" ? "contents" : "none");
  }
  main(appState) {
    const state = this.app.getState(appState);
    this.controls.nmeth.update(state[state.currView].nmeth);
    this.controls.matrixType.update(state[state.currView].matrixType);
    this.controls.zoomDiv.style("display", state.currView == "detail" ? "contents" : "none");
    if (state.currView == "chrpair") {
      this.controls.view.text(`${state.x.chr}-${state.y.chr} Pair`);
    } else {
      this.controls.view.text(state.currView.charAt(0).toUpperCase() + state.currView.slice(1));
    }
    const formatAbsMin = this.parent("absMin") < 1e-5 ? Number(this.parent("absMin")).toExponential(1) : Number(this.parent("absMin").toFixed(6));
    const formatAbsMax = this.parent("absMax") < 1e-5 ? Number(this.parent("absMax")).toExponential(1) : Number(this.parent("absMax").toFixed(6));
    this.controls.width.select("input").property("value", state.settings.widthHeightPx);
    this.controls.inputBpMinV.property("value", this.parent("min"));
    this.controls.inputBpMaxV.property("value", this.parent("max"));
    this.controls.minCutoffLabel.html(`MIN CUTOFF <br>(>= ${formatAbsMin})`);
    this.controls.maxCutoffLabel.html(`MAX CUTOFF <br>(<= ${formatAbsMax})`);
    this.showBtns();
    this.showHideControls();
  }
};
var controlPanelInit = getCompInit(ControlPanel);

// tracks/hic/dom/InfoBar.ts
var InfoBar = class {
  constructor(opts) {
    /** Defaults **/
    //Color shown on the left of the color scale
    this.startColor = "white";
    //Color shown on the right of the color scale
    this.endColor = "red";
    this.addLabel = (text) => {
      return this.labelRow.append("td").attr("data-testid", `sjpp-hic-infobar-${text.toLowerCase().replace(/\s/g, "-")}-label`).style("color", "#858585").style("font-size", ".8em").style("text-align", "center").style("vertical-align", "middle").text(text.toUpperCase());
    };
    this.addValue = (text) => {
      return this.valueRow.append("td").attr("data-testid", `sjpp-hic-infobar-${text.toString().toLowerCase().replace(/\s/g, "-")}-value`).style("font-size", "1em").style("text-align", "center").style("vertical-align", "middle").text(text);
    };
    this.type = "infoBar";
    this.app = opts.app;
    this.hic = opts.hic;
    const table = opts.infoBarDiv;
    this.labelRow = table.append("tr");
    this.valueRow = table.append("tr");
    this.parent = opts.parent;
  }
  async render() {
    this.addLabel("Version");
    this.addValue(this.hic.version);
    this.addLabel("Enzyme");
    this.addValue(this.hic.enzyme || "N/A");
    this.addLabel("Resolution");
    this.resolutionDiv = this.addValue("");
    this.colorScaleLabel = this.addLabel("Scale");
    this.colorScaleDiv = this.valueRow.append("td").attr("data-testid", "sjpp-hic-color-scale");
    const min = this.parent("min");
    let max = this.parent("max");
    if (min == max) max = max + 0.01;
    this.colorScale = new ColorScale({
      barheight: 20,
      barwidth: 100,
      domain: [min, max],
      fontSize: 12,
      height: 40,
      width: 150,
      holder: this.colorScaleDiv,
      colors: [this.startColor, this.endColor],
      position: "20,0",
      ticks: 3,
      tickSize: 5,
      //Do not use min value here on initialization. It's 0 and will not update later.
      markedValue: 1
    });
  }
  update() {
    const res = this.parent("calcResolution");
    let resolutionText;
    if (res < this.hic.bpresolution[this.hic.bpresolution.length - 1]) {
      resolutionText = `${res} fragment${res > 1 ? "s" : ""}`;
    } else {
      resolutionText = bplen(res);
    }
    this.resolutionDiv.text(resolutionText);
    if (this.parent("state").currView == "horizontal") {
      this.colorScaleLabel.style("display", "none");
      this.colorScaleDiv.style("display", "none");
    } else {
      this.colorScaleLabel.style("display", "");
      this.colorScaleDiv.style("display", "");
      const min = this.parent("min");
      const max = this.parent("max");
      if (min < 0) {
        this.colorScale.colors = ["blue", "white", "red"];
        this.colorScale.domain = [min, 0, max];
      } else {
        this.colorScale.colors = ["white", "red"];
        this.colorScale.domain = [0, max];
      }
      this.colorScale.markedValue = min;
      this.colorScale.updateScale();
    }
  }
};

// tracks/hic/data/DataMapper.ts
var DataMapper = class {
  constructor(hic, percentile) {
    this.hic = hic;
    this.percentile = percentile ? percentile / 100 : 0.99;
  }
  sortData(data) {
    const vlst = [];
    if (data?.items && data?.items.length) {
      for (const i of data.items) {
        vlst.push(i[2]);
      }
    } else {
      const blankChrs = /* @__PURE__ */ new Set();
      for (const d of data) {
        if (d.lead == "chrM" && !d.items.length || d.lead == "chrY" && !d.items.length) {
          blankChrs.add(d.lead);
          continue;
        }
        for (const i of d.items) {
          vlst.push(i[2]);
        }
      }
      if (blankChrs.size) {
        for (const c of blankChrs) {
          const idx = this.hic.chrlst.indexOf(c);
          this.hic.chrlst.splice(idx, 1);
        }
      }
    }
    const sortedVlst = vlst.sort((a, b) => a - b);
    const max = sortedVlst[Math.floor(sortedVlst.length * this.percentile)];
    const min = sortedVlst[0];
    return [min, max, sortedVlst[sortedVlst.length - 1]];
  }
};

// tracks/hic/data/GenomeDataFetcher.ts
var GenomeDataFetcher = class {
  constructor(hic, debugmode, errList = []) {
    this.data = [];
    this.hic = hic;
    this.debugmode = debugmode;
    this.errList = errList || [];
  }
  async getData(obj) {
    if (this.data.length) this.data = [];
    if (!obj?.matrixType) obj.matrixType = "observed";
    try {
      const res = await dofetch3("hicgenome?", {
        method: "POST",
        body: JSON.stringify({
          chrlst: this.hic.chrlst,
          nmeth: obj.nmeth,
          resolution: obj.resolution,
          matrixType: obj.matrixType,
          file: this.hic.file,
          url: this.hic.url,
          nochr: this.hic.nochr
        })
      });
      if (res.error) {
        const errs = res.error.split("\n");
        for (const e of errs) {
          this.errList.push(e);
        }
      }
      return res.data;
    } catch (e) {
      this.errList.push(e.message || e);
      if (e.stack) console.error(e.stack);
    }
  }
};

// tracks/hic/data/DataFetcher.ts
var DataFetcher = class {
  constructor(hic, debugmode, errList = []) {
    this.hic = hic;
    this.debugmode = debugmode;
    this.errList = errList || [];
  }
  async getData(obj) {
    if (!obj?.matrixType) obj.matrixType = "observed";
    const arg = {
      matrixType: obj.matrixType,
      file: this.hic.file,
      url: this.hic.url,
      pos1: this.hic.nochr ? obj.lead.replace("chr", "") : obj.lead,
      pos2: this.hic.nochr ? obj.follow.replace("chr", "") : obj.follow,
      nmeth: obj.nmeth,
      resolution: obj.resolution
    };
    try {
      const data = await dofetch3("hicdata?", {
        method: "POST",
        body: JSON.stringify(arg)
      });
      if (data.error) {
        throw Error(`${obj.lead} - ${obj.follow}: ${data.error.error}`);
      }
      if (!data.items || data.items.length == 0) return;
      return data;
    } catch (e) {
      this.errList.push(e.message || e);
      if (e.stack) console.error(e.stack);
    }
  }
};

// tracks/hic/HicComponent.ts
var HicComponent = class _HicComponent {
  constructor(opts) {
    this.genomeData = [];
    this.calcResolution = null;
    this.firstRender = true;
    this.min = 0;
    this.absMin = 0;
    this.max = 0;
    this.absMax = 0;
    this.hasStatePreMain = true;
    this.type = _HicComponent.type;
    this.hic = opts.hic;
    this.state = opts.state;
    this.dom = opts.dom;
    this.errList = opts.errList;
    this.plotDiv = this.dom.plotDiv.append("table").classed("sjpp-hic-plot-main", true);
    const tr1 = this.plotDiv.append("tr");
    const tr2 = this.plotDiv.append("tr");
    this.plotDiv = {
      plot: tr1.append("td").classed("sjpp-hic-plot", true).attr("data-testid", "sjpp-hic-plot"),
      yAxis: tr1.append("td").classed("sjpp-hic-plot-yaxis", true).attr("data-testid", "sjpp-hic-yaxis"),
      xAxis: tr2.append("td").classed("sjpp-hic-plot-xaxis", true).attr("data-testid", "sjpp-hic-xaxis"),
      blank: tr2.append("td")
    };
    this.app = opts.app;
    this.dataMapper = new DataMapper(this.hic, opts.state.maxCutoffPercentile);
    this.activeView = this.state.currView;
    this.error = opts.error;
    this.resolution = new Resolution(this.error);
    this.parent = (prop) => {
      return this[prop];
    };
    this.detailDataMapper = new DetailDataMapper(this.hic, this.errList, this.parent);
  }
  static {
    this.type = "view";
  }
  getState(appState) {
    return appState;
  }
  reactsTo(action) {
    if (action.type.startsWith("view")) return true;
    else return false;
  }
  initView() {
    const opts = {
      plotDiv: this.plotDiv,
      hic: this.hic,
      app: this.app,
      parent: (prop) => {
        return this[prop];
      }
    };
    if (this.state.currView == "genome") {
      opts["data"] = this.data;
      this.genome = new GenomeView(opts);
      this.genome.render();
    } else if (this.state.currView === "chrpair") {
      opts["items"] = this.data;
      this.chrpair = new ChrPairView(opts);
      this.chrpair.render();
    } else if (this.state.currView === "detail") {
      opts["data"] = this.data;
      this.detail = new DetailView(opts);
      this.detail.render();
    } else if (this.state.currView === "horizontal") {
      this.horizontal = new HorizontalView(opts);
      this.horizontal.render();
    } else {
      throw Error(`Unknown view: ${this.state.currView}`);
    }
  }
  async fetchData(obj) {
    if (this.data?.length) this.data = [];
    if (this.state.currView == "genome") {
      if (this.activeView != "genome" && this.genomeData?.length) this.data = this.genomeData;
      else {
        const genomeFetcher = new GenomeDataFetcher(this.hic, true, this.errList);
        this.genomeData = await genomeFetcher.getData(obj);
        this.data = this.genomeData;
      }
    } else if (this.state.currView == "detail") {
      this.data = await this.detailDataMapper.getData(this.state.x, this.state.y, this.fragData);
    } else {
      if (!this.state?.x?.chr || !this.state?.y?.chr) {
        this.errList.push(`No positions provided for ${this.activeView} view.`);
        return;
      } else {
        const chrx = this.state.x;
        const chry = this.state.y;
        obj["lead"] = `${chrx.start && chrx.stop ? `${chrx.chr}:${chrx.start}-${chrx.stop}` : chrx.chr}`;
        obj["follow"] = `${chry.start && chry.stop ? `${chry.chr}:${chry.start}-${chry.stop}` : chry.chr}`;
      }
      const dataFetcher = new DataFetcher(this.hic, true, this.errList);
      this.data = await dataFetcher.getData(obj);
    }
  }
  async setResolution(appState) {
    const state = this.app.getState(appState);
    this.calcResolution = this.resolution.getResolution(state, this.hic);
    if (state.currView == "detail" && this.calcResolution == null) {
      await this.detailDataMapper.getFragData(state.x, state.y);
      this.fragData = this.detailDataMapper.frag;
      const maxFragSpan = Math.min(
        this.fragData.x.stop - this.fragData.x.start,
        this.fragData.y.stop - this.fragData.y.start
      );
      this.calcResolution = this.resolution.findResFromArray(
        maxFragSpan,
        state.minFragSpan,
        this.hic.fragresolution,
        true
      );
    } else {
      this.fragData = null;
    }
    return this.calcResolution;
  }
  async setDataArgs(appState) {
    const state = this.app.getState(appState);
    const currView = state[state.currView];
    const args = {
      nmeth: currView.nmeth,
      resolution: await this.setResolution(appState),
      matrixType: currView.matrixType
    };
    if (state.currView == "chrpair") {
      args["lead"] = state.x.chr;
      args["follow"] = state.y.chr;
    }
    return args;
  }
  async getViewData(appState) {
    const args = await this.setDataArgs(appState);
    await this.fetchData(args);
    if (this.data?.length > 0 || this.data?.items?.length > 0) {
      const [min, max, absMax] = this.dataMapper.sortData(this.data);
      this.min = this.absMin = min;
      this.max = max;
      this.absMax = absMax;
    } else {
      if (this.state.currView != "genome") {
        const state = this.app.getState(appState);
        const xPosMessage = state.x?.start ? `${state.x.chr}:${state.x.start}-${state.x.stop}` : state.x.chr;
        const yPosMessage = state.y?.start ? `${state.y.chr}:${state.y.start}-${state.y.stop}` : state.y.chr;
        this.errList.push(`No data returned for ${xPosMessage} and ${yPosMessage}`);
      }
    }
  }
  async init() {
    this.components = {
      controls: await controlPanelInit({
        app: this.app,
        controlsDiv: this.dom.controlsDiv,
        hic: this.hic,
        parent: (prop, value) => {
          if (value) this[prop] = value;
          return this[prop];
        },
        error: this.error.bind(this)
      })
    };
    this.infoBar = new InfoBar({
      app: this.app,
      infoBarDiv: this.dom.infoBarDiv.append("table").style("border-spacing", "3px"),
      hic: this.hic,
      parent: (prop) => {
        return this[prop];
      }
    });
    this.infoBar.render();
  }
  async main(appState) {
    if (this.errList.length) {
      this.app.dispatch({ type: "loading_active", active: false });
      return;
    }
    const state = this.app.getState(appState);
    if (this.firstRender == true) {
      this.firstRender = false;
      await this.getViewData(appState);
      this.initView();
    } else {
      if (state.currView != "horizontal") {
        this.app.dispatch({ type: "loading_active", active: true });
        await this.getViewData(appState);
      }
      if (this.activeView != state.currView) {
        if (this.activeView == "genome") {
          this.genome.svg.remove();
        } else {
          this.plotDiv.xAxis.selectAll("*").remove();
          this.plotDiv.yAxis.selectAll("*").remove();
          this.plotDiv.plot.selectAll("*").remove();
        }
        this.activeView = state.currView;
        this.initView();
      } else {
        this[state.currView].update(this.data);
      }
    }
    this.infoBar.update();
    if (this.errList.length) {
      this.error(this.errList);
      this.app.dispatch({ type: "loading_active", active: false });
    }
  }
};
var viewCompInit = getCompInit(HicComponent);

// tracks/hic/HicApp.ts
var HicApp = class _HicApp extends AppBase {
  constructor(opts) {
    super(opts);
    this.views = ["genome", "chrpair", "detail", "horizontal"];
    this.errList = [];
    /** Required for rx but not in use */
    this.wasDestroyed = false;
    this.type = _HicApp.type;
    this.hic = {
      enzyme: opts.enzyme,
      file: opts.file,
      url: opts.url,
      genome: opts.genome,
      holder: opts.holder,
      hostUrl: opts.hostUrl,
      jwt: opts.jwt,
      position1: opts.position1,
      position2: opts.position2,
      maxCutoffPercentile: opts.maxCutoffPercentile,
      name: "name" in opts ? opts.name : "Hi-C",
      tklist: "tklst" in opts ? opts.tklst : [],
      state: "state" in opts ? opts.state : {}
    };
    this.dom = {
      errorDiv: opts.holder.append("div").classed("sjpp-hic-error", true),
      controlsDiv: opts.holder.append("div").classed("sjpp-hic-controls", true).style("display", "inline-block"),
      infoBarDiv: opts.holder.append("div").classed("sjpp-hic-infobar", true).style("display", "inline-block").style("vertical-align", "top").style("padding", "5px").style("border", "solid 0.5px #ccc"),
      loadingDiv: select_default("body").append("div").attr("id", "sjpp-loading-overlay"),
      plotDiv: opts.holder.append("div").classed("sjpp-hic-main", true).style("display", "inline-block"),
      tip: new Menu()
    };
  }
  static {
    this.type = "app";
  }
  async error(err) {
    if (err && typeof err == "string") this.errList.push(err);
    showErrorsWithCounter(this.errList, this.dom.errorDiv);
    this.errList = [];
  }
  determineView() {
    if (!this.hic.position1 && !this.hic.position2 && !this.hic.state.currView) this.hic.state.currView = "genome";
    if (!this.hic.position1 && this.hic.position2) this.errList.push("Missing first position");
    if (this.hic.position1 && !this.hic.position2) this.errList.push("Missing second position");
    if (this.hic.position1 && this.hic.position2) {
      const pos1 = this.hic.position1.split(/[:-]/);
      const pos2 = this.hic.position2.split(/[:-]/);
      this.hic.state.x = { chr: pos1[0] };
      this.hic.state.y = { chr: pos2[0] };
      if (!Number.isNaN(+pos1[1]) && !Number.isNaN(+pos1[2])) {
        this.hic.state.x.start = +pos1[1];
        this.hic.state.x.stop = +pos1[2];
        this.hic.state.y.start = +pos2[1];
        this.hic.state.y.stop = +pos2[2];
        this.hic.state.currView = "detail";
      } else {
        this.hic.state.currView = "chrpair";
      }
    } else {
      if (!this.views.some((v) => v === this.hic.state.currView)) this.error(`Unknown view: ${this.hic.state.currView}`);
      else return this.hic.state.currView;
    }
  }
  getViewsConfig() {
    if (this.hic.maxCutoffPercentile) this.hic.state.maxCutoffPercentile = this.hic.maxCutoffPercentile;
    let nmeth;
    if (this.hic["normalization"]?.length > 0) {
      if (!this.hic["normalization"].includes("NONE")) this.hic["normalization"].unshift("NONE");
      nmeth = this.hic["normalization"][0];
    } else {
      nmeth = "NONE";
    }
    if (this.hic.state.currView) {
      if (!this.hic.state[this.hic.state.currView]) this.hic.state[this.hic.state.currView] = {};
      if (!this.hic.state[this.hic.state.currView]?.matrixType) {
        this.hic.state[this.hic.state.currView].matrixType = "observed";
      }
      if (!this.hic.state[this.hic.state.currView]?.nmeth) {
        this.hic.state[this.hic.state.currView].nmeth = nmeth;
      }
    }
    for (const v of this.views) {
      if (!this.hic.state[v]) {
        this.hic.state[v] = {
          matrixType: "observed",
          nmeth
        };
      }
    }
  }
  async init() {
    this.determineView();
    try {
      await hicParseFile(this.hic, true, this.errList);
      this.getViewsConfig();
      this.store = await hicStoreInit({ app: this.api, state: this.hic.state });
      this.state = await this.store.copyState();
      this.components = {
        loadingOverlay: await loadingInit({
          app: this.api,
          loadingDiv: this.dom.loadingDiv
        }),
        view: await viewCompInit({
          app: this.api,
          state: this.state,
          dom: this.dom,
          hic: this.hic,
          errList: this.errList,
          error: this.error
        })
      };
      await this.api.dispatch();
    } catch (e) {
      console.error(e.stack || e);
    }
  }
  main() {
    if (this.errList.length) {
      this.error(this.errList);
      this.dom.loadingDiv.style("display", "none");
      return;
    }
  }
};
var hicInit = getAppInit(HicApp);
export {
  hicInit
};
//# sourceMappingURL=HicApp-BBD2YOMT.js.map
