import {
  getSeriesTip,
  renderAtRiskG,
  renderPvalues
} from "./chunk-KI5KI3ZJ.js";
import {
  schemeCategory20
} from "./chunk-V2OJLJSK.js";
import {
  DownloadMenu,
  PlotBase,
  controlsInit,
  defaultUiLabels,
  downloadChart,
  fillTermWrapper,
  getCombinedTermFilter,
  htmlLegend,
  rgb2hex,
  term0_term2_defaultQ
} from "./chunk-SA7APTJR.js";
import {
  Menu
} from "./chunk-HYOEWQ5P.js";
import {
  require_partjson_umd
} from "./chunk-UI7OCM2A.js";
import {
  copyMerge,
  getCompInit
} from "./chunk-M3J4MINX.js";
import {
  isNumericTerm
} from "./chunk-NOEAT6CX.js";
import {
  area_default,
  category10_default,
  line_default,
  stepAfter
} from "./chunk-KSGA62R2.js";
import {
  axisBottom,
  axisLeft
} from "./chunk-LOZEKOES.js";
import {
  linear,
  ordinal
} from "./chunk-OAWQ6LOO.js";
import {
  select_default
} from "./chunk-I6Y4O3RR.js";
import {
  rgb
} from "./chunk-OMR2DT66.js";
import {
  __toESM
} from "./chunk-HFNDKYVF.js";

// plots/survival/survival.ts
var import_partjson = __toESM(require_partjson_umd(), 1);
var t0_t2_defaultQ = structuredClone(term0_term2_defaultQ);
Object.assign(t0_t2_defaultQ, {
  numeric: {
    mode: "discrete",
    type: "custom-bin",
    preferredBins: "median"
  },
  geneExpression: {
    mode: "discrete",
    type: "custom-bin",
    preferredBins: "median"
  }
});
var TdbSurvival = class _TdbSurvival extends PlotBase {
  constructor(opts, api) {
    super(opts, api);
    this.components = {};
    // expected class-specific props
    this.configTermKeys = ["term", "term0", "term2"];
    this.activeMenu = false;
    this.legendClick = (_, __, ___) => {
    };
    this.download = () => {
    };
    this.getChartImages = () => this.getChartImages();
    this.loadingWait = 0;
    this.legendOrder = [];
    this.term2toColor = {};
    this.uniqueSeriesIds = /* @__PURE__ */ new Set();
    // TODO: migrate to this.view method
    this.render = () => {
    };
    this.getSymbol = (_) => {
      return "";
    };
    this.type = _TdbSurvival.type;
    this.settings = Object.assign({}, opts.settings);
    this.dom = this.getDom();
    this.pj = getPj(this);
    this.lineFxn = line_default().curve(stepAfter).x((c) => c.scaledX).y((c) => c.scaledY);
    this.legendRenderer = htmlLegend(this.dom.legendDiv, {
      settings: {
        legendOrientation: "vertical"
      },
      handlers: {
        legend: {
          click: (e) => this.legendClick(e.target.__data__, e.clientX, e.clientY)
        }
      }
    });
    this.hiddenRenderer = htmlLegend(this.dom.hiddenDiv, {
      settings: {
        legendOrientation: "vertical"
      },
      handlers: {
        legend: {
          click: (e) => this.legendClick(e.target.__data__, e.clientX, e.clientY)
        }
      }
    });
  }
  static {
    this.type = "survival";
  }
  getDom() {
    const opts = this.opts;
    const holder = opts.controls ? opts.holder : opts.holder.append("div");
    const { controls, errdiv, loadingDiv, renderedData, charts, legendDiv } = this.getStandardDomLayout(holder, opts);
    const dom = {
      loadingDiv,
      header: opts.header?.html("Survival Plot"),
      controls,
      holder,
      errdiv,
      renderedData,
      chartsDiv: charts.style("margin", "10px").style("display", "flex").style("flex-direction", "row").style("flex-wrap", "wrap").style("max-width", "100vw"),
      legendDiv: legendDiv.style("margin", "5px 5px 15px 5px"),
      hiddenDiv: renderedData.append("div").style("margin", "5px 5px 15px 5px"),
      tip: new Menu({ padding: "5px" }),
      legendTip: new Menu({ padding: "5px" })
    };
    dom.tip.onHide = () => {
      this.activeMenu = false;
    };
    return dom;
  }
  async init() {
    setInteractivity(this);
    setRenderers(this);
    await this.setControls();
  }
  async setControls() {
    const state = this.getState(this.app.getState());
    const controlLabels = state.config.controlLabels;
    if (!controlLabels) throw "controls labels not found";
    const stratDisabledMsg = this.app.vocabApi.termdbConfig?.survival?.stratificationDisabledMessage;
    if (this.opts.controls) {
      this.opts.controls.on("downloadClick.survival", this.download);
    } else {
      this.dom.holder.attr("class", "pp-termdb-plot-viz").style("display", "inline-block").style("min-width", "300px");
      this.components = {
        controls: await controlsInit({
          app: this.app,
          id: this.id,
          holder: this.dom.controls.style("display", "inline-block"),
          inputs: [
            {
              type: "term",
              configKey: "term2",
              chartType: "survival",
              usecase: { target: "survival", detail: "term2" },
              title: controlLabels.term2.title || controlLabels.term2.label,
              label: controlLabels.term2.label,
              vocabApi: this.app.vocabApi,
              numericEditMenuVersion: ["discrete"],
              defaultQ4fillTW: t0_t2_defaultQ,
              disabledMessage: stratDisabledMsg
            },
            {
              type: "term",
              configKey: "term0",
              chartType: "survival",
              usecase: { target: "survival", detail: "term0" },
              title: controlLabels.term0.title || controlLabels.term0.label,
              label: controlLabels.term0.label,
              vocabApi: this.app.vocabApi,
              numericEditMenuVersion: ["discrete"],
              defaultQ4fillTW: t0_t2_defaultQ,
              disabledMessage: stratDisabledMsg
            },
            {
              label: "Chart width",
              type: "number",
              chartType: "survival",
              settingsKey: "svgw",
              title: "The internal width of the chart plot"
            },
            {
              label: "Chart height",
              type: "number",
              chartType: "survival",
              settingsKey: "svgh",
              title: "The internal height of the chart plot"
            },
            {
              label: "95% Confidence Interval",
              boxLabel: "Visible",
              type: "checkbox",
              chartType: "survival",
              settingsKey: "ciVisible",
              title: "Display the 95% confidence interval"
            },
            {
              label: "Censored Symbol",
              type: "radio",
              chartType: "survival",
              settingsKey: "symbol",
              options: [
                { label: "Tick", value: "vtick" },
                { label: "X", value: "x" }
              ],
              title: "Symbol for displaying censored samples"
            },
            {
              label: "Survival Time Visualized",
              type: "number",
              chartType: "survival",
              settingsKey: "maxTimeToEvent",
              title: "The maximum time-to-event to be displayed in the survival plot, affects only the visual representation and does not impact other analyses. All available data is still used to compute p-values. If this value is left empty or set to 0, no cutoff will be applied."
            },
            {
              label: "Time Unit",
              type: "radio",
              chartType: "survival",
              settingsKey: "timeUnit",
              options: [
                { label: "years", value: "years" },
                { label: "months", value: "months" },
                { label: "weeks", value: "weeks" },
                { label: "days", value: "days" }
              ],
              title: `The unit to display in the x-axis title, like 'years'`
            },
            {
              label: "X-axis ticks",
              type: "text",
              chartType: "survival",
              settingsKey: "xTickValues",
              title: `Option to customize the x-axis tick values, enter as comma-separated values. Will be ignored if empty`,
              processInput: (value) => value ? value.split(",").map(Number) : []
            },
            {
              label: "At-risk counts",
              boxLabel: "Visible",
              type: "checkbox",
              chartType: "survival",
              settingsKey: "atRiskVisible",
              title: "Display the at-risk counts"
            },
            //{label: 'At-risk label offset', type: 'numeric', chartType: 'survival', settingsKey: 'atRiskLabelOffset'},
            {
              label: "Default color",
              type: "color",
              chartType: "survival",
              settingsKey: "defaultColor",
              title: "Default plot color"
            }
          ]
        })
      };
      this.components.controls.on("downloadClick.survival", this.download);
    }
  }
  getState(appState) {
    const config = appState.plots.find((p) => p.id === this.id);
    if (!config) {
      throw `No plot with id='${this.id}' found. Did you set this.id before this.api = getComponentApi(this)?`;
    }
    const parentConfig = appState.plots.find((p) => p.id === this.parentId);
    const termfilter = getCombinedTermFilter(appState, config.filter || parentConfig?.filter);
    return {
      isVisible: config.term.term.type == "survival" || config.term2?.term.type == "survival",
      genome: this.app.vocabApi.vocab.genome,
      dslabel: this.app.vocabApi.vocab.dslabel,
      activeCohort: appState.activeCohort,
      termfilter,
      config: {
        // NOTE: will create mutable copies of tw terms in async main() below
        term: config.term,
        term0: config.term0 || null,
        term2: config.term2 || null,
        controlLabels: config.controlLabels,
        settings: config.settings.survival
      },
      termdbConfig: appState.termdbConfig
    };
  }
  async main() {
    if (!this.state.isVisible) {
      this.dom.holder.style("display", "none");
      return;
    }
    let data;
    try {
      this.state.config = await this.getMutableConfig();
      this.maySetSandboxHeader();
      this.toggleLoadingDiv();
      Object.assign(this.settings, this.state.config.settings);
      this.settings.defaultHidden = this.getDefaultHidden();
      this.settings.hidden = this.settings.customHidden || this.settings.defaultHidden;
      this.settings.xTitleLabel = this.getXtitleLabel();
      const reqOpts = this.getDataRequestOpts();
      data = await this.app.vocabApi.getNestedChartSeriesData(reqOpts, this.api?.getAbortSignal());
    } catch (e) {
      if (this.app.isAbortError(e)) return;
      this.toggleLoadingDiv("none", "none");
      throw e;
    }
    this.serverData = data;
    this.toggleLoadingDiv("none");
    this.app.vocabApi.syncTermData(this.state.config, data);
    this.currData = this.processData(data);
    this.refs = data.refs;
    this.pj.refresh({ data: this.currData });
    this.setTerm2Color(this.pj.tree.charts);
    this.symbol = this.getSymbol(7);
    this.render();
  }
  maySetSandboxHeader() {
    if (!this.dom.header) return;
    const { term, term2 } = this.state.config;
    const mainTerm = term.term.name;
    if (term2?.type) {
      this.dom.header.html(`${term2.getTitleText?.() || term2.term.name}  vs ${mainTerm}`);
    } else {
      this.dom.header.html(`${mainTerm} plot`);
    }
  }
  // creates an opts object for the vocabApi.getNestedChartsData()
  getDataRequestOpts() {
    const c = this.state.config;
    const opts = {
      chartType: "survival",
      term: c.term,
      filter: this.state.termfilter.filter,
      filter0: this.state.termfilter.filter0
    };
    if (c.term2) {
      opts.term2 = c.term2;
    }
    if (c.term0) opts.term0 = c.term0;
    return opts;
  }
  getDefaultHidden() {
    const hidden = [];
    const term2 = this.state.config.term2;
    if (!term2) return hidden;
    const hiddenValues = term2.q.hiddenValues;
    if (hiddenValues && Object.keys(hiddenValues).length) {
      for (const k in hiddenValues) {
        hidden.push(term2.term.values[k].label);
      }
    }
    return hidden;
  }
  getXtitleLabel() {
    const termNum = this.state.config.term.term.type == "survival" ? "term" : "term2";
    const xUnit = this.settings.timeUnit ? this.settings.timeUnit : this.state.config[termNum].term.unit;
    const xTitleLabel = `Time to Event (${xUnit})`;
    return xTitleLabel;
  }
  processData(data) {
    this.uniqueSeriesIds = /* @__PURE__ */ new Set();
    this.msg = "";
    const rows = [];
    const estKeys = ["survival", "lower", "upper"];
    for (const d of data.case) {
      const obj = {};
      data.keys.forEach((k, i) => {
        obj[k] = estKeys.includes(k) ? Number(d[i]) : d[i];
      });
      obj.time = obj.time * this.getTimeFactor();
      rows.push(obj);
      this.uniqueSeriesIds.add(obj.seriesId);
    }
    this.tests = {};
    if (data.tests) {
      for (const chartId in data.tests) {
        const chartTests = data.tests[chartId];
        this.tests[chartId] = [];
        for (const test of chartTests) {
          if (this.settings.hidden.includes(test.series1) || this.settings.hidden.includes(test.series2)) continue;
          this.tests[chartId].push({
            pvalue: { id: "pvalue", text: test.pvalue },
            series1: { id: test.series1 },
            series2: { id: test.series2 }
          });
        }
        if (!this.tests[chartId].length) delete this.tests[chartId];
      }
    }
    if (data.refs.byTermId) {
      const byTermId = data.refs.byTermId;
      const msg = byTermId[this.state.config.term.term.id]?.survMsg;
      if (msg) {
        this.msg = msg;
      }
    }
    return rows;
  }
  // time factor that converts survival data from default time unit to the current time unit.
  getTimeFactor() {
    const numUnitInOneYear = this.settings.numUnitInOneYear || 1;
    const timeUnit = this.settings.timeUnit || "years";
    switch (timeUnit) {
      case "years":
        return 1 / numUnitInOneYear * 1;
      case "months":
        return 1 / numUnitInOneYear * 12;
      case "weeks":
        return 1 / numUnitInOneYear * 52;
      case "days":
        return 1 / numUnitInOneYear * 365.25;
      default:
        throw "unknown time Unit for survival data";
    }
  }
  setTerm2Color(charts) {
    if (!charts) return;
    const config = this.state.config;
    const t2values = copyMerge({}, config.term2?.term?.values || {}, config.term2?.values || {});
    const values = this.refs.bins[2] && [this.refs.bins[2]] || Object.values(t2values);
    let t2groups;
    if (config.term2?.q.type == "predefined-groupset" || config.term2?.q.type == "custom-groupset") {
      const groupset = config.term2.q.type == "predefined-groupset" ? config.term2.term.groupsetting.lst[config.term2.q.predefined_groupset_idx] : config.term2.q.customset;
      if (!groupset) throw "groupset is missing";
      t2groups = groupset.groups;
    }
    this.term2toColor = {};
    this.colorScale = this.uniqueSeriesIds.size < 11 ? ordinal(category10_default) : ordinal(schemeCategory20);
    const legendItems = [];
    for (const chart of charts) {
      for (const series of chart.serieses) {
        let color;
        const v = values.find(
          (v2) => v2.seriesId === series.seriesId || v2.key === series.seriesId || v2.name === series.seriesId || v2.label === series.seriesId
        );
        color = v?.color;
        if (!color && t2groups?.length) {
          const group = t2groups.find((g) => g.name == series.seriesId);
          color = group?.color;
          if (color === "#D3D3D3" && group?.filter?.lst?.find((f) => f?.type === "tvs" && f.tvs.genotype == "wt"))
            color = "rgb(105,105,105)";
        }
        const orig = color || (series.seriesId == "" ? this.settings.defaultColor : this.colorScale(series.seriesId));
        const _rgb = rgb(orig);
        const adjusted = _rgb.toString();
        const c = { orig, rgb: _rgb, adjusted, hex: rgb2hex(adjusted) };
        this.term2toColor[series.seriesId] = c;
        if (!legendItems.find((d) => d.seriesId == series.seriesId)) {
          legendItems.push({
            key: series.seriesId,
            seriesId: series.seriesId,
            text: series.seriesLabel,
            color: this.term2toColor[series.seriesId].adjusted,
            isHidden: this.settings.hidden.includes(series.seriesId)
          });
        }
      }
    }
    if (this.refs.orderedKeys) {
      const s = this.refs.orderedKeys.series;
      legendItems.sort((a, b) => s.indexOf(a.seriesId) - s.indexOf(b.seriesId));
    }
    if (!this.legendOrder.length) {
      this.legendOrder = legendItems.map((item) => item.seriesId);
    } else {
      legendItems.sort((a, b) => this.legendOrder.indexOf(a.seriesId) - this.legendOrder.indexOf(b.seriesId));
    }
    if ((config.term.term.type == "survival" || config.term2) && legendItems.length) {
      const termNum = config.term.term.type == "survival" && config.term2 ? "term2" : "term";
      this.legendData = [
        {
          name: config[termNum].term.name,
          items: legendItems.filter((s) => !s.isHidden)
        }
      ];
      this.hiddenData = [
        {
          name: `<span style='color:#aaa; font-weight:400'><span>Hidden categories</span><span style='font-size:0.8rem'> CLICK TO SHOW</span></span>`,
          items: legendItems.filter((s) => s.isHidden).map((item) => Object.assign({}, item, { isHidden: false }))
        }
      ];
    } else {
      this.legendData = [];
    }
    for (const chartId in this.tests) {
      const chartTests = this.tests[chartId];
      for (const test of chartTests) {
        for (const key in test) {
          if (key == "pvalue") {
            test[key].color = "#000";
          } else {
            const item = legendItems.find((item2) => item2.seriesId == test[key].id);
            test[key].color = item.color;
            test[key].text = item.text;
          }
        }
      }
    }
  }
};
var survivalInit = getCompInit(TdbSurvival);
var componentInit = survivalInit;
function setRenderers(self) {
  self.render = function() {
    const data = self.pj.tree.charts || [{ chartId: "No survival data" }];
    const chartDivs = self.dom.chartsDiv.selectAll(".pp-survival-chart").data(data, (d) => d.chartId);
    chartDivs.exit().remove();
    chartDivs.each(self.updateCharts);
    chartDivs.enter().each(self.addCharts);
    self.dom.holder.style("display", "inline-block");
    self.dom.chartsDiv.on("mouseover", self.mouseover).on("mouseout", self.mouseout);
    self.legendRenderer(self.settings.atRiskVisible ? [] : self.legendData);
    if (!self.hiddenData?.[0]?.items.length) self.dom.hiddenDiv.style("display", "none");
    else {
      self.dom.hiddenDiv.style("display", "");
      self.hiddenRenderer(self.hiddenData);
    }
  };
  const updateHiddenPvalues = (hiddenPvalues) => {
    self.app.dispatch({
      type: "plot_edit",
      id: self.id,
      config: {
        settings: {
          survival: {
            hiddenPvalues
          }
        }
      }
    });
  };
  const setActiveMenu = (bool) => self.activeMenu = bool;
  self.addCharts = function(chart) {
    const s = self.settings;
    setVisibleSerieses(chart, s);
    const div = select_default(this).append("div").attr("class", "pp-survival-chart").style("opacity", chart.serieses ? 0 : 1).style("width", "fit-content").style("display", "inline-block").style("margin", s.chartMargin + "px").style("padding", "10px").style("top", 0).style("left", 0).style("text-align", "left").style("vertical-align", "top");
    div.append("div").style("width", s.svgw + 50 + "px").style("height", s.chartTitleDivHt + "px").style("text-align", "center").style("font-weight", "600").style("margin", "5px").append("div").attr("class", "sjpp-survival-title").attr("data-testid", "sjpp-survival-plotTitle").style("display", "inline-block").style("width", "fit-content").datum(chart).html((chart2) => chart2.chartId);
    if (chart.serieses) {
      const svg = div.append("svg").attr("class", "pp-survival-svg");
      renderSVG(svg, chart, s, 0);
      div.transition().duration(s.duration).style("opacity", 1);
      div.append("div").attr("class", "pp-survival-chartLegends").style("vertical-align", "top").style("margin", "10px 10px 10px 10px").style("display", "none");
      if (self.tests && chart.rawChartId in self.tests) {
        const holder = div.select(".pp-survival-chartLegends").style("display", "inline-block").append("div").style("margin-bottom", "30px");
        renderPvalues({
          title: "Group comparisons (log-rank test)",
          holder,
          plot: "survival",
          tests: self.tests[chart.rawChartId],
          s,
          bins: self.refs.bins,
          tip: self.app.tip,
          setActiveMenu,
          updateHiddenPvalues
        });
      }
      if (self.msg) {
        const fontSize = s.axisTitleFontSize ? s.axisTitleFontSize - 2 : 15;
        const holder = div.select(".pp-survival-chartLegends").style("display", "inline-block").append("div");
        holder.style("font-size", fontSize + "px").style("font-style", "italic").text(self.msg);
      }
    }
  };
  function setVisibleSerieses(chart, s) {
    chart.visibleSerieses = chart.serieses?.filter((series) => !s.hidden.includes(series.seriesId)) || [];
    const maxSeriesLabelLen = chart.visibleSerieses.reduce(
      (maxlen, a) => a.seriesLabel && a.seriesLabel.length > maxlen ? a.seriesLabel.length : maxlen,
      0
    );
    const extraPaddingForNoOverlay = maxSeriesLabelLen === 0 ? s.atRiskNoOverlayExtraPadding : 0;
    chart.atRiskLabelWidth = s.atRiskVisible ? maxSeriesLabelLen * (s.axisTitleFontSize - 2) * 0.4 + s.atRiskLabelOffset + extraPaddingForNoOverlay : 0;
  }
  self.updateCharts = function(chart) {
    if (!chart.serieses) return;
    const s = self.settings;
    setVisibleSerieses(chart, s);
    const div = select_default(this);
    div.transition().duration(s.duration).style("width", "fit-content");
    div.select(".sjpp-survival-title").style("width", s.svgw + 50).style("height", s.chartTitleDivHt + "px").datum(chart.chartId).html(chart.chartId);
    div.selectAll(".sjpp-lock-icon").style("display", s.scale == "byChart" ? "block" : "none");
    div.selectAll(".sjpp-unlock-icon").style("display", s.scale == "byChart" ? "none" : "block");
    renderSVG(div.select("svg"), chart, s, s.duration);
    div.select(".pp-survival-chartLegends").style("display", "none").selectAll("*").remove();
    if (self.tests && chart.rawChartId in self.tests) {
      const holder = div.select(".pp-survival-chartLegends").style("display", "inline-block").append("div").style("margin-bottom", "30px");
      renderPvalues({
        title: "Group comparisons (log-rank test)",
        holder,
        plot: "survival",
        tests: self.tests[chart.rawChartId],
        s,
        bins: self.refs.bins,
        tip: self.app.tip,
        setActiveMenu,
        updateHiddenPvalues
      });
    }
    if (self.msg) {
      const fontSize = s.axisTitleFontSize ? s.axisTitleFontSize - 2 : 15;
      const holder = div.select(".pp-survival-chartLegends").style("display", "inline-block").append("div");
      holder.style("font-size", fontSize + "px").style("font-style", "italic").text(self.msg);
    }
  };
  function renderSVG(svg, chart, s, duration) {
    chart.svg = svg;
    const extraHeight = s.atRiskVisible ? s.axisTitleFontSize + 4 + chart.visibleSerieses.length * 2 * (s.axisTitleFontSize + 4) : 0;
    svg.transition().duration(duration).attr("width", s.svgw + chart.atRiskLabelWidth).attr("height", s.svgh + extraHeight).style("overflow", "visible").style("padding-left", "5px");
    const [mainG, seriesesG, axisG, xAxis, yAxis, xTitle, yTitle, atRiskG, plotRect] = getSvgSubElems(svg);
    const xOffset = chart.atRiskLabelWidth + s.svgPadding.left + 10;
    mainG.attr("transform", "translate(" + xOffset + "," + s.svgPadding.top + ")");
    const serieses = seriesesG.selectAll(".sjpp-survival-series").data(chart.visibleSerieses, (d) => d && d[0] ? d[0].seriesId : "");
    serieses.exit().remove();
    serieses.each(function(series, i) {
      renderSeries(select_default(this), chart, series, i, s);
    });
    serieses.enter().append("g").attr("class", "sjpp-survival-series").each(function(series, i) {
      renderSeries(select_default(this), chart, series, i, s);
    });
    renderAxes(xAxis, xTitle, yAxis, yTitle, s, chart);
    renderAtRiskG({
      g: atRiskG,
      s,
      chart,
      order: self.legendOrder,
      term2toColor: self.term2toColor,
      onSerieClick: self.legendClick
    });
    plotRect.attr("x", 0).attr("width", s.svgw - s.svgPadding.left - s.svgPadding.right).attr("y", 0).attr("height", s.svgh - s.svgPadding.top - s.svgPadding.bottom + s.xAxisOffset);
    svg.seriesTip.update({
      xScale: chart.xScale,
      xTitleLabel: s.xTitleLabel,
      decimals: s.seriesTipDecimals,
      serieses: chart.visibleSerieses.map((s2) => {
        const seriesLabel = `${s2.seriesLabel || "Probability"}:`;
        const color = self.term2toColor[s2.seriesId].adjusted || "#000";
        return {
          data: s2.data.map((d) => {
            return {
              x: d.x,
              html: `<span style='color: ${color}'>${seriesLabel} ${(100 * d.y).toFixed(2)}% (${(100 * d.lower).toFixed(2)} - ${(100 * d.upper).toFixed(
                2
              )})</span>`
            };
          })
        };
      })
    });
  }
  function getSvgSubElems(svg) {
    let mainG, seriesesG, axisG, xAxis, yAxis, xTitle, yTitle, atRiskG, plotRect, line;
    if (!svg.select(".sjpp-survival-mainG").size()) {
      mainG = svg.append("g").attr("class", "sjpp-survival-mainG");
      seriesesG = mainG.append("g").attr("class", "sjpcb-survival-seriesesG");
      axisG = mainG.append("g").attr("class", "sjpp-survival-axis");
      xAxis = axisG.append("g").attr("class", "sjpp-survival-x-axis");
      yAxis = axisG.append("g").attr("class", "sjpp-survival-y-axis");
      xTitle = axisG.append("g").attr("class", "sjpp-survival-x-title");
      yTitle = axisG.append("g").attr("class", "sjpp-survival-y-title");
      atRiskG = mainG.append("g").attr("class", "sjpp-survival-atrisk");
      line = mainG.append("line").attr("class", "sjpcb-plot-tip-line").attr("stroke", "#000").attr("stroke-width", "1px");
      plotRect = mainG.append("rect").attr("class", "sjpcb-plot-tip-rect").style("fill", "transparent");
    } else {
      mainG = svg.select(".sjpp-survival-mainG");
      seriesesG = mainG.select(".sjpcb-survival-seriesesG");
      axisG = mainG.select(".sjpp-survival-axis");
      xAxis = axisG.select(".sjpp-survival-x-axis");
      yAxis = axisG.select(".sjpp-survival-y-axis");
      xTitle = axisG.select(".sjpp-survival-x-title");
      yTitle = axisG.select(".sjpp-survival-y-title");
      atRiskG = mainG.select(".sjpp-survival-atrisk");
      plotRect = mainG.select(".sjpcb-plot-tip-rect");
      line = mainG.select(".sjpcb-plot-tip-line");
    }
    if (!svg.seriesTip) {
      svg.seriesTip = getSeriesTip(line, plotRect, self.dom.tip);
    }
    return [mainG, seriesesG, axisG, xAxis, yAxis, xTitle, yTitle, atRiskG, plotRect];
  }
  function renderSeries(g, chart, series, i, s) {
    let processedData = series.data;
    if (s.maxTimeToEvent) {
      processedData = series.data.filter((d) => d.x <= s.maxTimeToEvent);
    }
    g.selectAll("path").remove();
    g.append("path").attr(
      "d",
      area_default().curve(stepAfter).x((c) => c.scaledX).y0((c) => c.scaledY[1]).y1((c) => c.scaledY[2])(processedData)
    ).style("display", s.ciVisible ? "" : "none").style("fill", self.term2toColor[series.seriesId].adjusted).style("opacity", "0.15").style("stroke", "none");
    renderSubseries(
      s,
      g,
      processedData.map((d) => {
        return {
          seriesId: d.seriesId,
          x: d.x,
          y: d.y,
          scaledX: d.scaledX,
          scaledY: d.scaledY[0],
          seriesName: "survival",
          seriesLabel: series.seriesLabel,
          nevent: d.nevent,
          ncensor: d.ncensor,
          nrisk: d.nrisk
        };
      })
    );
    renderSubseries(
      s,
      g.append("g"),
      processedData.map((d) => {
        return {
          seriesId: d.seriesId,
          x: d.x,
          y: d.lower,
          scaledX: d.scaledX,
          scaledY: d.scaledY[1],
          seriesName: "lower",
          seriesLabel: series.seriesLabel,
          nevent: d.nevent,
          ncensor: 0,
          // no censor marks for lower CI
          nrisk: d.nrisk
        };
      })
    );
    renderSubseries(
      s,
      g.append("g"),
      processedData.map((d) => {
        return {
          seriesId: d.seriesId,
          x: d.x,
          y: d.upper,
          scaledX: d.scaledX,
          scaledY: d.scaledY[2],
          seriesName: "upper",
          // no censor marks for upper CI
          seriesLabel: series.seriesLabel,
          nevent: d.nevent,
          ncensor: 0,
          nrisk: d.nrisk
        };
      })
    );
  }
  function renderSubseries(s, g, data) {
    g.selectAll("g").remove();
    const lineData = data.filter((d, i) => i === 0 || d.nevent || i === data.length - 1);
    const censoredData = data.filter((d) => d.ncensor);
    const seriesName = data[0].seriesName;
    const color = self.term2toColor[data[0].seriesId].adjusted;
    if (seriesName == "survival") {
      g.append("path").attr("d", self.lineFxn(lineData)).style("fill", "none").style("stroke", color).style("opacity", 1).style("stroke-opacity", 1);
    }
    const subg1 = g.append("g").attr("class", "sjpp-survival-censored");
    const censored = subg1.selectAll(".sjpp-survival-censored-x").data(censoredData, (d) => d.x);
    censored.exit().remove();
    censored.attr("transform", (c) => `translate(${c.scaledX},${c.scaledY})`).style("stroke", color).style("display", "");
    censored.enter().append("path").attr("class", "sjpp-survival-censored-x").attr("transform", (c) => `translate(${c.scaledX},${c.scaledY})`).attr("d", self.symbol).style("fill", "transparent").style("fill-opacity", s.fillOpacity).style("stroke", color).style("display", "");
  }
  function renderAxes(xAxis, xTitle, yAxis, yTitle, s, chart) {
    let xTicks;
    if (s.xTickValues?.length) {
      chart.xTickValues = s.xTickValues.filter(
        (v) => v === 0 || v >= chart.xMin && v <= Math.min(s.maxTimeToEvent || chart.xMax, chart.xMax)
      );
      xTicks = axisBottom(chart.xScale).tickValues(chart.xTickValues);
    } else {
      chart.xTickValues = [];
      xTicks = axisBottom(chart.xScale).ticks(4).tickFormat((xval) => {
        chart.xTickValues.push(xval);
        return xval;
      });
    }
    const pixelOffset = -0.5;
    xAxis.attr(
      "transform",
      `translate(${pixelOffset}, ${s.svgh - s.svgPadding.top - s.svgPadding.bottom + s.xAxisOffset + pixelOffset})`
    ).call(xTicks);
    yAxis.attr("transform", `translate(${s.yAxisOffset + pixelOffset}, ${pixelOffset})`).call(axisLeft(linear().domain(chart.yScale.domain()).range(chart.yScale.range())).ticks(5));
    xTitle.select("text, title").remove();
    xTitle.attr(
      "transform",
      "translate(" + (s.svgw - s.svgPadding.left - s.svgPadding.right) / 2 + "," + (s.svgh - s.axisTitleFontSize - 4) + ")"
    ).append("text").style("text-anchor", "middle").style("font-size", s.axisTitleFontSize + "px").text(s.xTitleLabel);
    const yTitleLabel = "Probability of Survival";
    yTitle.select("text, title").remove();
    yTitle.attr(
      "transform",
      "translate(" + (-s.svgPadding.left / 2 - s.axisTitleFontSize) + "," + (s.svgh - s.svgPadding.top - s.svgPadding.bottom) / 2 + ")rotate(-90)"
    ).append("text").style("text-anchor", "middle").style("font-size", s.axisTitleFontSize + "px").text(yTitleLabel);
  }
  self.getSymbol = function(size) {
    const s = size, h = s / 2;
    switch (self.settings.symbol) {
      case "x":
        return `M -${h},-${h} l ${s},${s} M ${h},-${h} l -${s},${s}`;
      case "vtick":
        return `M 0,-${h} L 0,${h}`;
      default:
        throw `Unrecognized survival plot symbol='${self.settings.symbol}'`;
    }
  };
}
function setInteractivity(self) {
  self.download = function(event) {
    const charts = self.getChartImages();
    const menu = new DownloadMenu(charts, self.state.config.term.term.name);
    menu.show(event.clientX, event.clientY, event.target);
  };
  self.getChartImages = function() {
    const charts = [];
    if (!self.pj.tree.charts) return charts;
    for (const chart of self.pj.tree.charts) {
      charts.push({ name: chart.chartId, svg: chart.svg });
    }
    return charts;
  };
  self.mouseover = function() {
  };
  self.mouseout = function() {
    if (self.activeMenu) return;
    self.app.tip.hide();
  };
  self.legendClick = function(d, x, y) {
    if (d === void 0) return;
    const hidden = self.settings.hidden.slice();
    const i = hidden.indexOf(d.seriesId);
    if (i == -1) {
      hidden.push(d.seriesId);
      self.showLegendItemMenu(d, hidden, x, y);
    } else {
      hidden.splice(i, 1);
      self.app.dispatch({
        type: "plot_edit",
        id: self.id,
        config: {
          settings: {
            survival: {
              customHidden: hidden
            }
          }
        }
      });
    }
  };
  self.showLegendItemMenu = function(d, hidden, x, y) {
    const term2 = self.state.config.term2?.term || null;
    const seriesLabel = term2?.values?.[d.seriesId]?.label || d.seriesId;
    const header = `<div style='padding-bottom:8px'><b>${seriesLabel}</b></div>`;
    const data = d.seriesId || d.seriesId === 0 ? d : { seriesId: d.id, dataId: d.dataId };
    if (!data.seriesId && !data.dataId) {
      if (!term2) {
        const label = self.dom.legendTip.clear().d.append("div").html("Edit color: ");
        const input = label.append("input").attr("type", "color").attr("value", self.settings.defaultColor).on("change", () => self.adjustColor(input.property("value"), d));
        self.dom.legendTip.show(x, y);
      }
      return;
    }
    const options = [];
    options.push({
      label: "Hide",
      callback: () => {
        menu.hide();
        self.app.dispatch({
          type: "plot_edit",
          id: self.id,
          config: {
            settings: {
              survival: {
                customHidden: hidden
              }
            }
          }
        });
      }
    });
    if (self.legendData[0]?.items.length > 1) {
      options.push({
        label: "Move&nbsp;",
        setInput: (holder) => {
          const legendIndex = self.legendOrder.indexOf(d.seriesId);
          if (legendIndex != 0)
            holder.append("button").html("up").on("click", () => {
              menu.hide();
              self.adjustLegendOrder(d, -1);
            });
          if (legendIndex < self.legendData[0]?.items.length - 1)
            holder.append("button").html("down").on("click", () => {
              menu.hide();
              self.adjustLegendOrder(d, 1);
            });
        }
      });
    }
    options.push({
      //label: 'Color',
      //callback: d => {}
      setInput: (holder) => {
        const label = holder.append("div");
        label.html("Edit color: ");
        const input = label.append("input").attr("type", "color").attr("value", self.term2toColor[d.seriesId].hex).on("change", () => self.adjustColor(input.property("value"), d));
      }
    });
    if (!options.length) return;
    const menu = self.dom.legendTip.clear();
    menu.d.append("div").html(header);
    menu.d.append("div").selectAll("div").data(options).enter().append("div").attr("class", (d2) => d2.label == "Hide" ? "sja_menuoption" : "sja_menuoption_not_interactive").on("click", (event, c) => {
      if (c.setInput) return;
      self.app.tip.hide();
      c.callback(d);
    }).each(function(d2) {
      const div = select_default(this);
      if (d2.label) div.append("div").style("display", "inline-block").html(d2.label);
      if (d2.setInput)
        d2.setInput(
          div.append("div").style("display", "inline-block")
          //.style('margin-left', '10px')
        );
    });
    menu.show(x, y);
  };
  self.adjustLegendOrder = (d, increment) => {
    const oldIndex = self.legendOrder.indexOf(d.seriesId);
    if (oldIndex == -1) return;
    let newIndex = oldIndex + increment;
    newIndex = Math.max(0, Math.min(self.legendOrder.length - 1, newIndex));
    self.legendOrder.splice(oldIndex, 1);
    self.legendOrder.splice(newIndex, 0, d.seriesId);
    self.app.dispatch({
      type: "app_refresh"
    });
    self.app.tip.hide();
  };
  self.adjustColor = (value, d) => {
    const color = rgb(value).formatHex();
    const t2 = self.state.config.term2;
    if (!t2) {
      self.app.dispatch({
        type: "plot_edit",
        id: self.id,
        config: { settings: { survival: { defaultColor: color } } }
      });
    } else {
      const term2 = structuredClone(t2);
      if (!term2.values) term2.values = {};
      const values = term2.values;
      if (!values[d.seriesId]) values[d.seriesId] = {};
      if (!("key" in values[d.seriesId])) values[d.seriesId].key = d.seriesId;
      values[d.seriesId].color = color;
      self.app.dispatch({
        type: "plot_edit",
        id: self.id,
        config: {
          term2
        }
      });
    }
    self.app.tip.hide();
  };
  self.showMenuForSelectedChart = function() {
    self.dom.tip.clear();
    self.activeMenu = true;
    self.dom.tip.showunder(this).d.append("button").html("Download SVG").on("click", () => {
      if (!self.state) return;
      const chartDiv = select_default(this.closest(".pp-survival-chart"));
      downloadChart(
        chartDiv.select(".sjpp-survival-mainG"),
        "Survival_Plot",
        chartDiv.node()
        // use to get computed styles
      );
    });
  };
}
async function getPlotConfig(opts, app) {
  if (!opts.term) throw "survival getPlotConfig: opts.term{} missing";
  try {
    await fillTermWrapper(opts.term, app.vocabApi);
    if (opts.term2)
      await fillTermWrapper(opts.term2, app.vocabApi, opts.term2.bins || opts.term2.q ? void 0 : t0_t2_defaultQ);
    if (opts.term0)
      await fillTermWrapper(opts.term0, app.vocabApi, opts.term0.bins || opts.term0.q ? void 0 : t0_t2_defaultQ);
  } catch (e) {
    throw `${e} [survival getPlotConfig()]`;
  }
  const config = {
    controlLabels: Object.assign({}, defaultUiLabels, app.vocabApi.termdbConfig.uiLabels || {}),
    settings: {
      controls: {
        term2: null,
        // the previous overlay value may be displayed as a convenience for toggling
        term0: null
      },
      survival: {
        radius: 5,
        ciVisible: true,
        fill: "#fff",
        stroke: "#000",
        symbol: "vtick",
        // 'x', 'vtick'
        fillOpacity: 0,
        chartMargin: 10,
        svgw: 400,
        svgh: 300,
        numUnitInOneYear: 1,
        timeUnit: "years",
        atRiskVisible: true,
        atRiskLabelOffset: -20,
        atRiskNoOverlayExtraPadding: 30,
        // Extra padding when there's no overlay term to prevent "Number at risk" text overlap
        xTickValues: [],
        // if undefined or empty, will be ignored
        seriesTipDecimals: 1,
        svgPadding: {
          top: 20,
          left: 55,
          right: 20,
          bottom: 50
        },
        axisTitleFontSize: 16,
        xAxisOffset: 5,
        yAxisOffset: -5,
        hiddenPvalues: [],
        defaultColor: "#2077b4"
      }
    }
  };
  const overrides = app.vocabApi.termdbConfig.survival || {};
  copyMerge(config.settings.survival, overrides.settings);
  return copyMerge(config, opts);
}
function getPj(self) {
  const pj = new import_partjson.default({
    template: {
      yMin: ">=yMin()",
      yMax: "<=yMax()",
      charts: [
        {
          chartId: "@key",
          rawChartId: "$chartId",
          xMin: ">$time",
          xMax: "<$time",
          "__:xScale": "=xScale()",
          "__:yScale": "=yScale()",
          yMin: ">=yMin()",
          yMax: "<=yMax()",
          serieses: [
            {
              chartId: "@parent.@parent.@key",
              seriesId: "@key",
              "__:seriesLabel": "=seriesLabel()",
              data: [
                {
                  "__:seriesId": "@parent.@parent.seriesId",
                  //color: "$color",
                  x: "$time",
                  y: "$survival",
                  lower: "$lower",
                  upper: "$upper",
                  "_1:scaledX": "=scaledX()",
                  "_1:scaledY": "=scaledY()",
                  nevent: "$nevent",
                  ncensor: "$ncensor",
                  nrisk: "$nrisk"
                },
                "=timeCensored()"
              ]
            },
            "$seriesId"
          ],
          "@done()": "=sortSerieses()"
        },
        "=chartTitle()"
      ],
      "@done()": "=sortCharts()"
    },
    "=": {
      chartTitle(row) {
        if (!row.chartId || row.chartId == "-") {
          const termNum = self.state.config.term.term.type == "survival" ? "term" : "term2";
          return self.state.config[termNum].term.name;
        }
        const t0 = self.state.config.term0;
        if (!t0 || !t0.term.values) return row.chartId;
        if (t0.q?.type == "predefined-groupset" || t0.q?.type == "custom-groupset") return row.chartId;
        const value = self.state.config.term0.term.values[row.chartId];
        return value && value.label ? value.label : row.chartId;
      },
      seriesLabel(row, context) {
        const t2 = self.state.config.term2;
        if (!t2) return;
        const seriesId = context.self.seriesId;
        if (t2?.q?.type == "predefined-groupset" || t2?.q?.type == "custom-groupset") return seriesId;
        if (t2 && t2.term.values && seriesId in t2.term.values) return t2.term.values[seriesId].label;
        if (isNumericTerm(t2.term)) {
          return t2.term.name + " " + seriesId;
        }
        return seriesId;
      },
      timeCensored(row) {
        return row.time + "-" + row.ncensor;
      },
      y(row, context) {
        const seriesId = context.context.parent.seriesId;
        return seriesId == "CI" ? [row.lower, row.upper] : row[seriesId];
      },
      yMin(row) {
        return row.lower;
      },
      yMax(row) {
        return row.upper;
      },
      xScale(_, context) {
        const s = self.settings;
        return linear().domain([0, Math.min(s.maxTimeToEvent || context.self.xMax, context.self.xMax)]).range([0, s.svgw - s.svgPadding.left - s.svgPadding.right]);
      },
      scaledX(_, context) {
        return context.context.context.context.parent.xScale(context.self.x);
      },
      scaledY(_, context) {
        const yScale = context.context.context.context.parent.yScale;
        const s = context.self;
        return [yScale(s.y), yScale(s.lower), yScale(s.upper)];
      },
      yScale() {
        const s = self.settings;
        const domain = [1.05, 0];
        return linear().domain(domain).range([0, s.svgh - s.svgPadding.top - s.svgPadding.bottom]);
      },
      sortSerieses(result) {
        for (const series of result.serieses) {
          series.data.sort((a, b) => a.x < b.x ? -1 : 1);
        }
        if (self.refs.orderedKeys) {
          const s = self.refs.orderedKeys.series;
          result.serieses.sort((a, b) => s.indexOf(a.seriesId) - s.indexOf(b.seriesId));
        }
        if (self.refs.bins) {
          const labelOrder = self.refs.bins.map((b) => b.label);
          result.serieses.sort((a, b) => labelOrder.indexOf(a.seriesId) - labelOrder.indexOf(b.seriesId));
        }
      },
      sortCharts(result) {
        if (!self.refs.orderedKeys) return;
        const c = self.refs.orderedKeys.chart;
        result.charts.sort(
          (a, b) => (c.indexOf(a.chartId) == -1 ? c.indexOf(a.rawChartId) : c.indexOf(a.chartId)) - (c.indexOf(b.chartId) == -1 ? c.indexOf(b.rawChartId) : c.indexOf(b.chartId))
        );
      }
    }
  });
  return pj;
}

export {
  t0_t2_defaultQ,
  survivalInit,
  componentInit,
  getPlotConfig
};
//# sourceMappingURL=chunk-C5ZMAQXQ.js.map
