td-plots 1.10.2 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,8 @@ export interface BoxPlotDataTrace {
16
16
  fill?: "auto" | "none";
17
17
  y?: number;
18
18
  width?: number;
19
+ xaxis?: string;
20
+ yaxis?: string;
19
21
  }
20
22
  export interface BoxPlotProps {
21
23
  data: BoxPlotDataTrace[];
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import { BoxPlotDataTrace } from "./BoxPlot";
3
+ export interface SplitBoxPlotProps {
4
+ groups: BoxPlotDataTrace[];
5
+ additionalGroups?: BoxPlotDataTrace[];
6
+ width?: number;
7
+ height?: number;
8
+ title?: string;
9
+ xAxisTitle?: string;
10
+ yAxisTitle?: string;
11
+ secondXAxisTitle?: string;
12
+ secondYAxisTitle?: string;
13
+ containerStyleOverrides?: React.CSSProperties;
14
+ plotId?: string;
15
+ unit?: string;
16
+ xAnnotations?: {
17
+ x: number;
18
+ text?: string;
19
+ color?: string;
20
+ }[];
21
+ }
22
+ declare const SplitBoxPlot: (props: SplitBoxPlotProps) => import("react/jsx-runtime").JSX.Element;
23
+ export default SplitBoxPlot;
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ export { default as PairedComparisonsBoxPlot } from "./components/PairedComparis
9
9
  export type { PairedComparisonsBoxPlotProps } from "./components/PairedComparisonsBoxPlot";
10
10
  export { default as BoxPlot } from "./components/BoxPlot";
11
11
  export type { BoxPlotProps } from "./components/BoxPlot";
12
+ export { default as SplitBoxPlot } from "./components/SplitBoxPlot";
13
+ export type { SplitBoxPlotProps } from "./components/SplitBoxPlot";
12
14
  export { default as SummaryComparisonPlot } from "./components/SummaryComparisonPlot";
13
15
  export type { SummaryComparisonPlotProps } from "./components/SummaryComparisonPlot";
14
16
  export { SummaryComparisonPlotLegend } from "./components/SummaryComparisonPlot";
package/dist/index.esm.js CHANGED
@@ -1512,12 +1512,13 @@ const BoxPlot = (props) => {
1512
1512
  : {
1513
1513
  x: trace.values,
1514
1514
  y0: trace.y !== undefined ? trace.y : undefined,
1515
+ showMean: true,
1515
1516
  }; // Values map to x because the boxplot is horizontal
1516
1517
  return Object.assign({ type: "box", orientation: "h", name: trace.label, fillcolor: trace.fill === "none" ? "rgba(255, 255, 255, 0)" : undefined, line: {
1517
1518
  color: trace.color || "#1f77b4", // Default to Plotly's default blue if no color provided
1518
1519
  }, boxpoints: false,
1519
1520
  // hoverinfo: "none", // Disable default hover
1520
- width: trace.width }, boxDefinition);
1521
+ width: trace.width, xaxis: trace.xaxis, yaxis: trace.yaxis }, boxDefinition);
1521
1522
  });
1522
1523
  const layout = Object.assign(Object.assign({}, extraLayoutConfig), { title: {
1523
1524
  text: title,
@@ -1537,6 +1538,7 @@ const BoxPlot = (props) => {
1537
1538
  staticPlot: false, // Enable interactivity
1538
1539
  };
1539
1540
  const containerStyles = Object.assign({ width: "100%", height: "100%", position: "relative" }, containerStyleOverrides);
1541
+ console.log("layout:", layout); // Debugging log to inspect the final layout object
1540
1542
  return (jsx("div", { ref: containerRef,
1541
1543
  // className={`plot-container ${plotId}`}
1542
1544
  style: Object.assign({}, containerStyles), children: jsx(Suspense, { fallback: jsx(Loading, {}), children: jsxs("div", { style: {
@@ -1666,19 +1668,6 @@ const PairedComparisonsBoxPlot = (props) => {
1666
1668
  },
1667
1669
  };
1668
1670
  });
1669
- groups.map((group, groupIndex) => ({
1670
- type: "line",
1671
- yref: "y",
1672
- xref: "paper",
1673
- x0: -0.01,
1674
- x1: -0.01,
1675
- y0: groupIndex - 0.4, // Align with the center of the group
1676
- y1: groupIndex + 0.4,
1677
- line: {
1678
- color: group.color || "orange",
1679
- width: 7,
1680
- },
1681
- }));
1682
1671
  const differenceAnnotations = [];
1683
1672
  const differenceBetweenMediansLines = [];
1684
1673
  // Find reasonable max and min for the x axis.
@@ -1818,11 +1807,7 @@ const PairedComparisonsBoxPlot = (props) => {
1818
1807
  t: 5,
1819
1808
  r: 50,
1820
1809
  },
1821
- shapes: [
1822
- ...separatorShapes,
1823
- ...differenceBetweenMediansLines,
1824
- // ...groupAnnotations,
1825
- ],
1810
+ shapes: [...separatorShapes, ...differenceBetweenMediansLines],
1826
1811
  annotations: differenceAnnotations,
1827
1812
  };
1828
1813
  const legendNode = (jsxs(Box$1, { sx: { display: "flex", justifyContent: "flex-end", mb: 2 }, children: [jsx(LegendBoxPlotItem, { label: pairLabels[0], color: "#626280", boxStyle: "shaded" }), jsx(LegendBoxPlotItem, { label: pairLabels[1], color: "#626280", boxStyle: "outlined" })] }));
@@ -1830,6 +1815,222 @@ const PairedComparisonsBoxPlot = (props) => {
1830
1815
  return (jsxs("div", { style: Object.assign({}, containerStyles), children: [showLegend && legendNode, jsx(BoxPlot, { data: boxPlotData, width: width, height: height, title: title, xAxisTitle: xAxisTitle, yAxisTitle: yAxisTitle, extraLayoutConfig: extraLayoutConfig, containerStyleOverrides: containerStyleOverrides, plotId: `${plotId}-boxplot` })] }));
1831
1816
  };
1832
1817
 
1818
+ // This component shows a set of main boxplots as one collection, then allowes for additional
1819
+ // boxes to be shown in a subplot above the main boxplots for comparison.
1820
+ const SplitBoxPlot = (props) => {
1821
+ const { groups, additionalGroups = [], width = 600, height = 400, title = "", xAxisTitle, yAxisTitle, secondXAxisTitle, secondYAxisTitle, containerStyleOverrides, plotId = "paired-comparisons-boxplot", xAnnotations = [], } = props;
1822
+ // Transform the grouped data into an array for BoxPlot
1823
+ const boxPlotData = [
1824
+ ...groups.flatMap((group, groupIndex) => {
1825
+ const groupYPosition = groupIndex; // Position the group on the y-axis based on its index
1826
+ const groupColor = group.color || "orange";
1827
+ return Object.assign(Object.assign({}, group), { color: group.color || groupColor, width: 0.28, fill: "none", y: groupYPosition, yaxis: "y" });
1828
+ }),
1829
+ ...additionalGroups.map((group, groupIndex) => {
1830
+ return Object.assign(Object.assign({}, group), { color: group.color || "blue", y: groupIndex, xaxis: "x2", yaxis: "y2", fill: "auto", width: 0.28 });
1831
+ }),
1832
+ ];
1833
+ // We have to construct nice ticks. We can position them with the group indices.
1834
+ const tickvals = groups.map((_, index) => index);
1835
+ const ticktext = groups.map((group) => group.label);
1836
+ // Draw some gray rectangles to live behind the boxes to help separate the groups
1837
+ const separatorShapes = groups.map((group, groupIndex) => {
1838
+ return {
1839
+ type: "rect",
1840
+ x0: 0,
1841
+ x1: 1,
1842
+ xref: "paper",
1843
+ y0: groupIndex - 0.5,
1844
+ y1: groupIndex + 0.5,
1845
+ yref: "y",
1846
+ fillcolor: groupIndex % 2 === 0 ? "#ffffff" : "#e7e5e5",
1847
+ opacity: 0.2,
1848
+ layer: "below",
1849
+ line: {
1850
+ width: 0,
1851
+ },
1852
+ };
1853
+ });
1854
+ // Leaving these for now because we're planning to use them in a tooltip or on hover.
1855
+ const differenceAnnotations = [];
1856
+ // Find reasonable max and min for the x axis.
1857
+ let globalMin = Infinity;
1858
+ let globalMax = -Infinity;
1859
+ // The following is only for updating difference annotations.
1860
+ groups.forEach((group, groupIndex) => {
1861
+ // Update global min and max
1862
+ if (isBoxPlotDataSummary(group.values)) {
1863
+ globalMin = Math.min(globalMin, group.values.lowerWhisker);
1864
+ globalMax = Math.max(globalMax, group.values.upperWhisker);
1865
+ }
1866
+ else if (Array.isArray(group.values)) {
1867
+ const boxMin = Math.min(...group.values);
1868
+ const boxMax = Math.max(...group.values);
1869
+ globalMin = Math.min(globalMin, boxMin);
1870
+ globalMax = Math.max(globalMax, boxMax);
1871
+ }
1872
+ if (additionalGroups.length === 0 ||
1873
+ group.label !== additionalGroups[0].label) {
1874
+ return; // Don't show an annotation if we don't have data for both boxes
1875
+ }
1876
+ const medianA = isBoxPlotDataSummary(group.values)
1877
+ ? group.values.median
1878
+ : computeMedian(group.values);
1879
+ const medianB = isBoxPlotDataSummary(group.values)
1880
+ ? group.values.median
1881
+ : computeMedian(group.values);
1882
+ const q1A = isBoxPlotDataSummary(group.values)
1883
+ ? group.values.q1
1884
+ : computeQuartile(group.values, 1);
1885
+ const q3A = isBoxPlotDataSummary(group.values)
1886
+ ? group.values.q3
1887
+ : computeQuartile(group.values, 3);
1888
+ const q1B = isBoxPlotDataSummary(group.values)
1889
+ ? group.values.q1
1890
+ : computeQuartile(group.values, 1);
1891
+ const q3B = isBoxPlotDataSummary(group.values)
1892
+ ? group.values.q3
1893
+ : computeQuartile(group.values, 3);
1894
+ const differenceBetweenMedians = medianB - medianA;
1895
+ // If we have the quartiles for the first box, we can determine if the second box's median is inside or outside the box
1896
+ const annotationColor = isBoxPlotDataSummary(group.values)
1897
+ ? q3B < q1A || q1B > q3A
1898
+ ? "red"
1899
+ : medianB < q1A || medianB > q3A
1900
+ ? "orange"
1901
+ : "gray"
1902
+ : "gray";
1903
+ if (annotationColor === "gray") {
1904
+ return; // Don't show an annotation if the medians are close enough that they overlap in the boxes
1905
+ }
1906
+ differenceAnnotations.push({
1907
+ yref: "y",
1908
+ xref: "x",
1909
+ x: medianB > medianA ? medianB : medianA, // Position the annotation at the larger median
1910
+ y: groupIndex, // Align with the center of the group
1911
+ text: ` ${differenceBetweenMedians.toFixed(1)}${props.unit ? ` ${props.unit}` : ""} `, // Show the difference between the medians with optional unit
1912
+ showarrow: false,
1913
+ xanchor: "left",
1914
+ align: "left",
1915
+ font: {
1916
+ size: 16,
1917
+ color: annotationColor,
1918
+ style: "italic",
1919
+ weight: "bold",
1920
+ },
1921
+ });
1922
+ });
1923
+ const totalGroups = groups.length + additionalGroups.length;
1924
+ const yAxisSplitProportion = additionalGroups.length > 0 ? groups.length / totalGroups : 1;
1925
+ const extraLayoutConfig = Object.assign(Object.assign({ yaxis: {
1926
+ type: "linear", // Use linear axis for numeric positioning of boxes
1927
+ tickmode: "array",
1928
+ tickvals,
1929
+ ticktext,
1930
+ automargin: true,
1931
+ range: [-0.5, groups.length - 0.5], // Add some padding to the y-axis range to accommodate the boxes
1932
+ domain: additionalGroups ? [0, yAxisSplitProportion - 0.01] : [0, 1], // Ensure primary y-axis takes full height
1933
+ tickcolor: "#ffffff",
1934
+ ticklen: 0,
1935
+ showgrid: false,
1936
+ //@ts-ignore
1937
+ ticklabelstandoff: 7,
1938
+ ticklabelposition: "outside left",
1939
+ title: {
1940
+ text: yAxisTitle || "", // Set default to empty string to prevent Plotly from adding its own default title
1941
+ standoff: 45,
1942
+ },
1943
+ }, xaxis: {
1944
+ anchor: "y", // Anchor to primary y-axis
1945
+ }, margin: {
1946
+ t: title ? 50 : 40,
1947
+ r: 50,
1948
+ }, shapes: [
1949
+ ...separatorShapes,
1950
+ // ...differenceBetweenMediansLines,
1951
+ ...xAnnotations.map((annotation) => ({
1952
+ type: "line",
1953
+ x0: annotation.x,
1954
+ x1: annotation.x,
1955
+ y0: 0,
1956
+ y1: 1.05,
1957
+ xref: "x",
1958
+ yref: "paper",
1959
+ line: {
1960
+ color: annotation.color || "rgba(255, 0, 0, 0.7)",
1961
+ width: 2,
1962
+ dash: "dash",
1963
+ },
1964
+ })),
1965
+ ], annotations: [
1966
+ // ...differenceAnnotations,
1967
+ ...xAnnotations.map((annotation) => ({
1968
+ x: annotation.x,
1969
+ y: 1.06,
1970
+ xref: "x",
1971
+ yref: "paper",
1972
+ text: annotation.text || "",
1973
+ showarrow: false,
1974
+ yanchor: "bottom",
1975
+ font: {
1976
+ color: annotation.color || "rgba(255, 0, 0, 0.7)",
1977
+ size: 12,
1978
+ },
1979
+ })),
1980
+ ] }, (additionalGroups && {
1981
+ xaxis2: {
1982
+ title: {
1983
+ text: secondXAxisTitle || "", // Set default to empty string to prevent Plotly from adding its own default title
1984
+ },
1985
+ side: "top",
1986
+ anchor: "y2", // Anchor to secondary y-axis
1987
+ showgrid: true,
1988
+ zeroline: false,
1989
+ showline: true,
1990
+ showticklabels: true, // Show x-axis tick labels for histogram
1991
+ gridcolor: "#efefef",
1992
+ gridwidth: 0.2,
1993
+ zerolinecolor: "#969696",
1994
+ zerolinewidth: 1,
1995
+ linecolor: "#bababa",
1996
+ linewidth: 1,
1997
+ fixedrange: true,
1998
+ automargin: true,
1999
+ matches: "x", // Match the range of xaxis to keep them synchronized
2000
+ },
2001
+ })), (additionalGroups && {
2002
+ yaxis2: {
2003
+ domain: [yAxisSplitProportion, 1], // Adjust domain for secondary y-axis (histogram)
2004
+ title: {
2005
+ text: secondYAxisTitle || "", // Set default to empty string to prevent Plotly from adding its own default title
2006
+ standoff: 45, // Add space between title and axis
2007
+ },
2008
+ range: [-0.5, additionalGroups.length - 0.5], // Add some padding to the y-axis range to accommodate the boxes
2009
+ automargin: true, // Required for standoff to work properly
2010
+ showgrid: true,
2011
+ zeroline: false,
2012
+ ticklabelposition: "outside left",
2013
+ showline: true,
2014
+ mirror: "ticks",
2015
+ gridcolor: "#efefef",
2016
+ gridwidth: 0.2,
2017
+ zerolinecolor: "#969696",
2018
+ zerolinewidth: 1,
2019
+ linecolor: "#bababa",
2020
+ linewidth: 1,
2021
+ fixedrange: true, // Disable zooming
2022
+ ticktext: additionalGroups.map((group) => group.label), // Use additional group labels for secondary y-axis ticks
2023
+ tickvals: additionalGroups.map((_, index) => index), // Position ticks based on the number of additional groups
2024
+ tickanchor: "end",
2025
+ ticklabelstandoff: 7,
2026
+ },
2027
+ }));
2028
+ console.log("extraLayoutConfig:", extraLayoutConfig);
2029
+ const containerStyles = Object.assign({ width: width, height: height, position: "relative", display: "flex", flexDirection: "column", gap: 0 }, containerStyleOverrides);
2030
+ console.log(boxPlotData);
2031
+ return (jsx("div", { style: Object.assign({}, containerStyles), children: jsx(BoxPlot, { data: boxPlotData, width: width, height: height, title: title, xAxisTitle: xAxisTitle, yAxisTitle: yAxisTitle, extraLayoutConfig: extraLayoutConfig, containerStyleOverrides: containerStyleOverrides, plotId: `${plotId}-boxplot` }) }));
2032
+ };
2033
+
1833
2034
  const Plot$2 = lazy(() => Promise.resolve().then(function () { return reactPlotlyWrapper$1; }));
1834
2035
  const SummaryComparisonPlot = (props) => {
1835
2036
  const { groups, height = 250, title = "", xAxisTitle, yAxisTitle, containerStyleOverrides, plotId = "summary-comparison-plot", tooltipPosition = "right", startXAxisAtZero = true, unit = "", } = props;
@@ -100248,5 +100449,5 @@ var reactPlotlyWrapper$1 = /*#__PURE__*/Object.freeze({
100248
100449
  default: reactPlotlyWrapper
100249
100450
  });
100250
100451
 
100251
- export { BarPlot, BoxPlot, HistogramPlot, LegendColorItem, LegendLineItem, LineWithHistogram, PairedComparisonsBoxPlot, RadialHistogramPlot, StatsDonut, SummaryComparisonPlot, SummaryComparisonPlotLegend, isDateArray, isNumberArray };
100452
+ export { BarPlot, BoxPlot, HistogramPlot, LegendColorItem, LegendLineItem, LineWithHistogram, PairedComparisonsBoxPlot, RadialHistogramPlot, SplitBoxPlot, StatsDonut, SummaryComparisonPlot, SummaryComparisonPlotLegend, isDateArray, isNumberArray };
100252
100453
  //# sourceMappingURL=index.esm.js.map