import {
  getMaxLabelWidth,
  renderTable,
  table2col
} from "./chunk-SA7APTJR.js";
import {
  SINGLECELL_GENE_EXPRESSION
} from "./chunk-NOEAT6CX.js";
import {
  basis_default,
  line_default
} from "./chunk-KSGA62R2.js";
import {
  axisLeft,
  axisTop
} from "./chunk-LOZEKOES.js";
import {
  format,
  linear,
  log
} from "./chunk-OAWQ6LOO.js";
import {
  brushX,
  brushY
} from "./chunk-KYBIQBXE.js";
import {
  rgb
} from "./chunk-OMR2DT66.js";

// plots/violin.renderer.js
function setViolinRenderer(self) {
  self.render = function() {
    const settings = self.config.settings.violin;
    const isH = settings.orientation === "horizontal";
    const t1 = self.config.term;
    const t2 = self.config.term2;
    const termNum = t2?.term.type === "condition" || t2?.term.type === "samplelst" || t2?.term.type === "categorical" || (t2?.term.type === "float" || t2?.term.type === "integer") && t1.q.mode === "continuous" ? t2 : t1;
    if (termNum && termNum.term?.values) {
      for (const [k, v] of Object.entries(termNum.term.values)) {
        if (v.uncomputable) {
          if (termNum.q.hiddenValues[k]) {
            termNum.q.hiddenValues[v.label] = 1;
            delete termNum.q.hiddenValues[k];
          }
        }
      }
    }
    self.dom.violinDiv.selectAll("*").remove();
    const chartKeys = Object.keys(self.data.charts);
    if (!chartKeys?.length) {
      self.dom.banner.html(`<span>No visible violin plot data to render</span>`).style("display", "block");
      self.dom.legendDiv.selectAll("*").remove();
      return;
    }
    for (const chartKey of chartKeys) {
      const chart = self.data.charts[chartKey];
      const plots = chart.plots.filter((p) => !termNum?.q?.hiddenValues?.[p.label || p.seriesId]);
      if (settings.orderByMedian == true) {
        plots.sort(
          (a, b) => a.summaryStats.find((x) => x.id === "median").value - b.summaryStats.find((x) => x.id === "median").value
        );
      }
      if (self.legendRenderer) self.legendRenderer(getLegendGrps(termNum, self));
      const chartDiv = self.dom.violinDiv.append("div").attr("class", "sjpp-vp-chartDiv").style("padding", Object.keys(self.data.charts).length > 1 ? "20px 20px 0px 0px" : "0px");
      chart.chartDiv = chartDiv;
      if (plots.length === 0) {
        chartDiv.html(
          ` <span style="opacity:.6;font-size:1em;margin-left:90px;">No visible violin plot data to render</span>`
        );
        return;
      }
      chartDiv.select(".sjpp-violin-plot").remove();
      const chartWrapper = chartDiv.append("div").style("display", "inline-block");
      if (chart.chartId) {
        const totalCount = chart.plots.reduce((acc, plot) => acc + plot.plotValueCount, 0);
        chartWrapper.append("div").attr("class", "pp-chart-title").style("display", "block").style("text-align", "center").style("font-size", "1.1em").style("margin-bottom", "5px").html(`${self.getChartTitle(chart.chartId)} (n=${totalCount})`);
      }
      const svgData = renderSvg(t1, plots, chartWrapper, self, isH, settings);
      renderScale(t1, t2, settings, isH, svgData, self);
      let y = 0;
      const thickness = self.settings.plotThickness || self.getAutoThickness();
      for (const [plotIdx, plot] of plots.entries()) {
        const wScale = linear().domain([plot.density.densityMax, plot.density.densityMin]).range([thickness / 2, 0]);
        let areaBuilder;
        if (isH) {
          areaBuilder = line_default().curve(basis_default).x((d) => svgData.axisScale(d.x0)).y((d) => wScale(d.density));
        } else {
          areaBuilder = line_default().curve(basis_default).x((d) => wScale(d.density)).y((d) => svgData.axisScale(d.x0));
        }
        const { violinG, height } = renderViolinPlot(svgData, plot, isH, wScale, areaBuilder, y);
        y += height;
        if (self.opts.mode != "minimal") renderLabels(t1, t2, violinG, plot, isH, settings);
        if (self.config.term.term.type == SINGLECELL_GENE_EXPRESSION) {
        } else {
          if (self.opts.mode != "minimal") renderBrushing(t1, t2, violinG, settings, plot, isH, svgData);
        }
        self.labelHideLegendClicking(t2, plot);
      }
      if (self.settings.showAssociationTests) self.renderPvalueTable(chartDiv, chart);
    }
  };
  self.displaySummaryStats = function(d, event) {
    if (!d.summaryStats) return;
    self.dom.hovertip.clear().show(event.clientX, event.clientY);
    const table = table2col({ holder: self.dom.hovertip.d.append("div") });
    for (const { label, value } of Object.values(d.summaryStats)) table.addRow(label, value);
  };
  self.getAutoThickness = function() {
    let maxPlotCount = 0;
    for (const k of Object.keys(this.data.charts)) {
      const chart = this.data.charts[k];
      maxPlotCount = Math.max(maxPlotCount, chart.plots.length);
    }
    if (maxPlotCount == 1) return 150;
    return Math.min(100, Math.max(40, 600 / maxPlotCount));
  };
  self.getPlotThicknessWithPadding = function() {
    const plotThickness = self.settings.plotThickness || self.getAutoThickness();
    return plotThickness + self.settings.rowSpace;
  };
  self.renderPvalueTable = function(chartDiv, chart) {
    if (!chart.pvalues) return;
    const tableHolder = chartDiv.append("div").classed("sjpp-tableHolder", true).style("display", "inline-block").style("padding", "10px").style("vertical-align", "top").style("margin-left", "0px").style("margin-top", "30px").style("margin-right", "30px");
    const t1 = self.config.term;
    const t2 = self.config.term2;
    if (!t2) {
      tableHolder.style("display", "none");
      return;
    }
    const termNum = t2?.term.type === "condition" || t2?.term.type === "samplelst" || t2?.term.type === "categorical" || (t2?.term.type === "float" || t2?.term.type === "integer") && t1.q.mode === "continuous" ? t2 : t1;
    const pvalues = chart.pvalues.filter((arr) => {
      for (let i = 0; i < arr.length; i++) {
        if (typeof arr[i].value === "string") {
          if (termNum.q?.hiddenValues && arr[i].value in termNum.q.hiddenValues) {
            return false;
          }
        }
      }
      return true;
    });
    tableHolder.style("display", "inline-block").style("vertical-align", "top").append("div").style("font-weight", "bold").text(pvalues.length > 0 ? "Group comparisons (Wilcoxon's rank sum test)" : "");
    const columns = [{ label: "Group 1" }, { label: "Group 2" }, { label: "P-value" }];
    const rows = pvalues;
    const isH = this.settings.orientation === "horizontal";
    const maxHeight = isH ? self.getPlotThicknessWithPadding() * chart.plots.length + 10 : this.settings.svgw + this.config.term.term.name.length;
    renderTable({
      rows,
      columns,
      div: tableHolder,
      showLines: false,
      maxWidth: "27vw",
      maxHeight: `${maxHeight}px`,
      resize: true
    });
  };
  self.getChartTitle = function(chartId) {
    if (!self.config.term0) return chartId;
    return self.config.term0.term.values && chartId in self.config.term0.term.values ? self.config.term0.term.values[chartId].label : chartId;
  };
  function createMargins(labelsize, settings, isH, isMinimal) {
    let margins;
    if (isMinimal) {
      margins = isH ? { left: 5, top: settings.axisHeight, right: settings.rightMargin, bottom: 10 } : { left: settings.axisHeight, top: 30, right: settings.rightMargin, bottom: 10 };
    } else {
      margins = isH ? { left: labelsize + 5, top: settings.axisHeight, right: settings.rightMargin, bottom: 10 } : { left: settings.axisHeight, top: 50, right: settings.rightMargin, bottom: labelsize };
    }
    return margins;
  }
  function renderSvg(t1, plots, chartDiv, self2, isH, settings) {
    const violinDiv = chartDiv.append("div").style("display", "inline-block").style("padding", self2.opts.mode != "minimal" ? "5px" : "0px").style("overflow", "auto").style("scrollbar-width", "none");
    const violinSvg = violinDiv.append("svg");
    const labelsize = getMaxLabelWidth(
      violinSvg,
      plots.map((plot) => `${plot.label}, n=${plot.plotValueCount}`)
    );
    const margin = createMargins(labelsize, settings, isH, self2.opts.mode == "minimal");
    const plotThickness = self2.getPlotThicknessWithPadding();
    const width = margin.left + margin.top + (isH ? settings.svgw : plotThickness * plots.length + t1.term.name.length);
    const height = margin.bottom + margin.top + (isH ? plotThickness * plots.length : settings.svgw + t1.term.name.length);
    violinSvg.attr("width", width).attr("height", height).classed("sjpp-violin-plot", true).attr("data-testid", "sja_violin_plot");
    const svgG = violinSvg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    return { margin, svgG, axisScale: createNumericScale(self2, settings, isH), violinSvg };
  }
  function renderScale(t1, t2, settings, isH, svg, self2) {
    const g = svg.svgG.append("g").style("font-size", "12").classed(settings.isLogScale ? "sjpp-logscale" : "sjpp-linearscale", true);
    const ticks = settings.isLogScale ? svg.axisScale.ticks(15) : (
      // svg.axisScale.ticks().filter(tick => tick > 0 || tick < 0)
      svg.axisScale.ticks()
    );
    g.call(
      (isH ? axisTop : axisLeft)().scale(svg.axisScale).tickFormat((d, i) => {
        if (settings.isLogScale) {
          if (self2.app.vocabApi.termdbConfig.logscaleBase2) {
            if (ticks.length > 10 && i % 2 !== 0) return "";
            if (d < 0.1) return format(".3f")(d);
            return format(".1f")(d);
          } else {
            if (ticks.length >= 12 && i % 5 !== 0) return "";
            if (d < 50) return d;
            return format(".1s")(d);
          }
        }
        if (ticks.length >= 12 && i % 2 !== 0) return "";
        return d;
      }).tickValues(ticks)
    );
    if (self2.opts.mode != "minimal") {
      const n = t2?.q?.mode === "continuous" ? t2.term.name : t1.term.name;
      const lab = svg.svgG.append("text").text(n).classed("sjpp-numeric-term-label", true).attr("data-testid", `sjpp-violin-label-${n}`).style("font-weight", 600).attr("text-anchor", "middle").attr("x", isH ? settings.svgw / 2 : -settings.svgw / 2).attr("y", isH ? -30 : -45).style("opacity", 0).attr("transform", isH ? null : "rotate(-90)").style("opacity", 1);
    }
  }
  function renderViolinPlot(svgData, plot, isH, wScale, areaBuilder, y) {
    const label = plot.label?.split(",")[0];
    const catTerm = self.config.term.q.mode == "discrete" ? self.config.term : self.config.term2;
    const category = catTerm?.term.values ? Object.values(catTerm.term.values).find((o) => o.label == label) : null;
    let color;
    if (catTerm) {
      if (catTerm.q.type == "predefined-groupset" || catTerm.q.type == "custom-groupset") {
        const groupset = catTerm.q.type == "predefined-groupset" ? catTerm.term.groupsetting.lst[catTerm.q.predefined_groupset_idx] : catTerm.q.customset;
        if (!groupset) throw "groupset is missing";
        const group = groupset.groups.find((g) => g.name == label);
        if (group?.color) color = group.color;
      } else {
        color = category?.color;
      }
    }
    if (!color) color = self.config.settings.violin.defaultColor;
    if (!plot.color) plot.color = color;
    if (category && !category.color) category.color = color;
    const svg = svgData.svgG;
    const violinG = svg.append("g").datum(plot).attr("class", "sjpp-violinG");
    renderArea(violinG, plot, areaBuilder);
    renderArea(violinG, plot, isH ? areaBuilder.y((d) => -wScale(d.density)) : areaBuilder.x((d) => -wScale(d.density)));
    renderSymbolImage(self, violinG, plot, isH);
    if (self.opts.mode != "minimal") renderMedian(violinG, isH, plot, svgData, self);
    renderLines(violinG, isH, self.config.settings.violin.lines, svgData);
    if ("value" in self.state.config) {
      const value = svgData.axisScale(self.state.config.value);
      const s = self.config.settings.violin;
      violinG.append("line").style("stroke", "black").style("stroke-width", s.medianThickness).attr("x1", 200).attr("x2", 200).attr("x1", isH ? value : -s.medianLength).attr("x2", isH ? value : s.medianLength).attr("y1", isH ? -s.medianLength : value).attr("y2", isH ? s.medianLength : value);
    }
    let height = self.getPlotThicknessWithPadding();
    const translate = isH ? `translate(0, ${y + height / 2}) ` : `translate(${y + height / 2}, 0)`;
    violinG.attr("transform", translate);
    return { violinG, height };
  }
  function renderLabels(t1, t2, violinG, plot, isH, settings) {
    violinG.append("text").attr("data-testid", "sjpp-violin-label").text(`${plot.label}, n=${plot.plotValueCount}`).style("cursor", "pointer").on("click", function(event) {
      if (!event) return;
      self.displayLabelClickMenu(t1, t2, plot, event);
    }).on("mouseover", function(event, d) {
      event.stopPropagation();
      if (!event) return;
      self.displaySummaryStats(d, event);
    }).on("mouseout", function() {
      self.dom.hovertip.hide();
    }).style("opacity", 0).style("opacity", 1).attr("x", isH ? -5 : 0 - settings.svgw - 5).attr("y", 0).attr("text-anchor", "end").attr("dominant-baseline", "central").attr("transform", isH ? null : "rotate(-90)");
  }
  function renderArea(violinG, plot, areaBuilder) {
    if (plot.density.densityMax == 0) return;
    violinG.append("path").attr("class", "sjpp-vp-path").style("fill", self.opts.mode === "minimal" ? rgb(221, 221, 221) : plot.color).style("opacity", 0).attr("stroke", rgb(plot.color).darker()).attr("stroke-width", 1).attr("stroke-linejoin", "round").style("opacity", "0.8").attr("d", areaBuilder(plot.density.bins));
  }
  function renderSymbolImage(self2, violinG, plot, isH) {
    const i = violinG.append("image").style("opacity", 0).classed(self2.config.settings.violin.datasymbol === "rug" ? "sjpp-rug-img" : "sjpp-beans-img", true).style("opacity", 1).attr("xlink:href", plot.src).attr(
      "transform",
      isH ? `translate(0, -${self2.settings.radius / 2})` : `translate(-${self2.settings.radius / 2}, 0)`
    );
    if (self2.settings.orientation == "horizontal") {
      i.attr("width", self2.settings.svgw);
    } else if (self2.settings.orientation == "vertical") {
      i.attr("height", self2.settings.svgw);
    }
  }
  function renderMedian(violinG, isH, plot, svgData, self2) {
    const s = self2.config.settings.violin;
    const median = svgData.axisScale(plot.summaryStats.median.value);
    if (plot.plotValueCount >= 2) {
      violinG.append("line").attr("class", "sjpp-median-line").style("stroke-width", s.medianThickness).style("stroke", s.medianColor).style("opacity", "0.5").attr("y1", isH ? -s.medianLength : median).attr("y2", isH ? s.medianLength : median).attr("x1", isH ? median : -s.medianLength).attr("x2", isH ? median : s.medianLength);
    } else return;
  }
  function renderLines(violinG, isH, lines, svgData) {
    const plotThickness = self.settings.plotThickness;
    violinG.selectAll(".sjpp-vp-line").remove();
    if (!lines?.length) return;
    for (const line of lines) {
      violinG.append("line").attr("class", "sjpp-vp-line").style("stroke", self.opts.mode == "minimal" ? "red" : "black").attr("y1", isH ? -(plotThickness / 2) : svgData.axisScale(line)).attr("y2", isH ? plotThickness / 2 : svgData.axisScale(line)).attr("x1", isH ? svgData.axisScale(line) : -(plotThickness / 2)).attr("x2", isH ? svgData.axisScale(line) : plotThickness / 2);
    }
  }
  function renderBrushing(t1, t2, violinG, settings, plot, isH, svgData) {
    if (settings.datasymbol === "rug" || settings.datasymbol === "bean") {
      const br = isH ? brushX().extent([
        [0, -20],
        [settings.svgw, 20]
      ]).on("end", (event) => {
        if (!event.selection) return;
        self.displayBrushMenu(t1, t2, self, plot, event, svgData.axisScale, isH);
        document.body.addEventListener("pointerdown", onClickOut, true);
      }) : brushY().extent([
        [-20, 0],
        [20, settings.svgw]
      ]).on("end", (event) => {
        if (!event.selection) return;
        self.displayBrushMenu(t1, t2, self, plot, event, svgData.axisScale, isH);
        document.body.addEventListener("pointerdown", onClickOut, true);
      });
      const brushG = violinG.append("g").classed("sjpp-brush", true).call(br);
      const onClickOut = (e) => {
        if (!brushG || !br) return;
        if (!brushG.node().contains(e.target)) br.clear(brushG);
        document.body.removeEventListener("pointerdown", onClickOut, true);
      };
    }
  }
}
function createNumericScale(self, settings, isH) {
  let axisScale;
  settings.isLogScale ? axisScale = log().base(self.app.vocabApi.termdbConfig.logscaleBase2 ? 2 : 10).domain([self.data.min, self.data.max]).range(isH ? [0, settings.svgw] : [settings.svgw, 0]) : axisScale = linear().domain([self.data.min, self.data.max]).range(isH ? [0, settings.svgw] : [settings.svgw, 0]);
  return axisScale;
}
function getLegendGrps(termNum, self) {
  const legendGrps = [], t1 = self.config.term, t2 = self.config.term2, headingStyle = "color: #555; font-weight: 400";
  if (self.settings.showStats) addDescriptiveStats(t1, legendGrps, headingStyle, self);
  if (t2?.term.type === "float" || t2?.q.mode === "continuous" || t2?.term.type === "integer")
    addDescriptiveStats(t2, legendGrps, headingStyle, self);
  addUncomputableValues(
    t1?.q.mode === "continuous" && t1?.q.hiddenValues && Object.keys(t1?.q.hiddenValues).length > 0 ? t1 : t2?.q.mode === "continuous" && t2?.q.hiddenValues && Object.keys(t2?.q.hiddenValues).length > 0 ? t2 : null,
    legendGrps,
    headingStyle,
    self
  );
  if (t2) {
    if (termNum.q.hiddenValues && Object.entries(termNum.q.hiddenValues).length != 0) {
      addHiddenValues(termNum, legendGrps, headingStyle);
    }
  }
  return legendGrps;
}
function addDescriptiveStats(term, legendGrps, headingStyle, self) {
  if (term?.q.descrStats) {
    const items = Object.values(term.q.descrStats).map((stat) => {
      return {
        text: `${stat.label}: ${stat.value}`,
        noIcon: true
      };
    });
    const title = self.config.term2?.term.type === "float" || self.config.term2?.term.type === "integer" ? `Descriptive statistics: ${term.term.name}` : `Descriptive statistics`;
    const name = `<span style="${headingStyle}">${title}</span>`;
    legendGrps.push({ name, items });
  }
}
function addUncomputableValues(term, legendGrps, headingStyle, self) {
  if (term?.term.values) {
    const items = [];
    for (const k in term.term.values) {
      if (self.data.uncomputableValues?.[term.term.values[k]?.label]) {
        items.push({
          text: `${term.term.values[k].label}, n = ${self.data.uncomputableValues[term.term.values[k].label]}`,
          noIcon: true,
          /** Need to specify that this is a hidden value for
           * text styling in the legend but not a plot to avoid
           * rendering a tooltip or click events.
           */
          isHidden: true,
          isClickable: false,
          hiddenOpacity: 1
        });
      }
    }
    if (items.length) {
      const name = self.config.term2?.term.type === "float" || self.config.term2?.term.type === "integer" ? `<span style="${headingStyle}">${term.term.name}</span>` : `<span style="${headingStyle}">Other categories</span>`;
      legendGrps.push({ name, items });
    }
  }
}
function addHiddenValues(term, legendGrps, headingStyle) {
  const items = [];
  for (const key of Object.keys(term.q.hiddenValues)) {
    items.push({
      text: `${key}`,
      noIcon: true,
      /** Need to specify that this is a hidden value for
       * text styling in the legend and  a plot for
       * rendering a tooltip or click events.
       */
      isHidden: true,
      isClickable: true,
      hiddenOpacity: 1
    });
  }
  const title = `${term.term.name}`;
  const name = `<span style="${headingStyle}">${title}</span>`;
  legendGrps.push({ name, items });
}

export {
  setViolinRenderer,
  createNumericScale
};
//# sourceMappingURL=chunk-DR6H3QAA.js.map
