td-plots 1.11.0 → 1.11.2
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.
- package/dist/components/LegendUtils.d.ts +4 -1
- package/dist/components/SummaryComparisonPlot.d.ts +13 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +224 -114
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +224 -114
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -14,4 +14,7 @@ export type LegendBoxPlotItemProps = LegendItemProps & {
|
|
|
14
14
|
};
|
|
15
15
|
export declare const LegendBoxPlotItem: (props: LegendBoxPlotItemProps) => import("react/jsx-runtime").JSX.Element;
|
|
16
16
|
export declare const LegendColorItem: (props: LegendItemProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
|
-
export
|
|
17
|
+
export type LegendLineItemProps = LegendItemProps & {
|
|
18
|
+
strokeDasharray?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare const LegendLineItem: (props: LegendLineItemProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
summarizedMean?: number;
|
|
1
|
+
type BracketData = {
|
|
2
|
+
userValue?: number;
|
|
3
|
+
populationMin: number;
|
|
4
|
+
populationMax: number;
|
|
6
5
|
};
|
|
7
|
-
type
|
|
6
|
+
type BracketDataWithMeta = {
|
|
8
7
|
groupLabel: string;
|
|
9
|
-
data:
|
|
8
|
+
data: BracketData;
|
|
10
9
|
color?: string;
|
|
11
10
|
};
|
|
12
11
|
export type SummaryComparisonPlotProps = {
|
|
13
|
-
groups:
|
|
12
|
+
groups: BracketDataWithMeta[];
|
|
13
|
+
userColor?: string;
|
|
14
14
|
width?: number;
|
|
15
15
|
height?: number;
|
|
16
16
|
title?: string;
|
|
@@ -21,6 +21,11 @@ export type SummaryComparisonPlotProps = {
|
|
|
21
21
|
tooltipPosition?: "left" | "right";
|
|
22
22
|
startXAxisAtZero?: boolean;
|
|
23
23
|
unit?: string;
|
|
24
|
+
tooltipLabels?: {
|
|
25
|
+
populationMinLabel?: string;
|
|
26
|
+
populationMaxLabel?: string;
|
|
27
|
+
userValueLabel?: string;
|
|
28
|
+
};
|
|
24
29
|
};
|
|
25
30
|
declare const SummaryComparisonPlot: (props: SummaryComparisonPlotProps) => import("react/jsx-runtime").JSX.Element;
|
|
26
31
|
export default SummaryComparisonPlot;
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,6 @@ export type { LineWithHistogramProps } from "./components/LineWithHistogram";
|
|
|
19
19
|
export { default as BarPlot } from "./components/BarPlot";
|
|
20
20
|
export type { BarPlotProps } from "./components/BarPlot";
|
|
21
21
|
export { LegendColorItem, LegendLineItem } from "./components/LegendUtils";
|
|
22
|
-
export type { LegendItemProps } from "./components/LegendUtils";
|
|
22
|
+
export type { LegendItemProps, LegendLineItemProps, } from "./components/LegendUtils";
|
|
23
23
|
export { isDateArray, isNumberArray } from "./components/Utils";
|
|
24
24
|
export type { PlotParams } from "react-plotly.js";
|
package/dist/index.esm.js
CHANGED
|
@@ -1538,7 +1538,6 @@ const BoxPlot = (props) => {
|
|
|
1538
1538
|
staticPlot: false, // Enable interactivity
|
|
1539
1539
|
};
|
|
1540
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
|
|
1542
1541
|
return (jsx("div", { ref: containerRef,
|
|
1543
1542
|
// className={`plot-container ${plotId}`}
|
|
1544
1543
|
style: Object.assign({}, containerStyles), children: jsx(Suspense, { fallback: jsx(Loading, {}), children: jsxs("div", { style: {
|
|
@@ -1619,6 +1618,7 @@ const LegendColorItem = (props) => {
|
|
|
1619
1618
|
const LegendLineItem = (props) => {
|
|
1620
1619
|
const lineWidth = props.width || 40;
|
|
1621
1620
|
const lineHeight = props.height || 4;
|
|
1621
|
+
const dashArray = props.strokeDasharray || "";
|
|
1622
1622
|
const labelKey = props.label == null
|
|
1623
1623
|
? ""
|
|
1624
1624
|
: typeof props.label === "string"
|
|
@@ -1629,7 +1629,7 @@ const LegendLineItem = (props) => {
|
|
|
1629
1629
|
alignItems: "center",
|
|
1630
1630
|
mr: 4,
|
|
1631
1631
|
fontFamily: "Open Sans, verdana, arial, sans-serif",
|
|
1632
|
-
}, children: [jsx("svg", { width: lineWidth, height: lineHeight, style: { marginRight: 8 }, children: jsx("line", { x1: 0, y1: lineHeight / 2, x2: lineWidth, y2: lineHeight / 2, stroke: props.color, strokeWidth: lineHeight }) }), props.label] }, labelKey));
|
|
1632
|
+
}, children: [jsx("svg", { width: lineWidth, height: lineHeight, style: { marginRight: 8 }, children: jsx("line", { x1: 0, y1: lineHeight / 2, x2: lineWidth, y2: lineHeight / 2, stroke: props.color, strokeWidth: lineHeight, strokeDasharray: dashArray }) }), props.label] }, labelKey));
|
|
1633
1633
|
};
|
|
1634
1634
|
|
|
1635
1635
|
// This component takes grouped data and transforms it for displaying with the BoxPlot component.
|
|
@@ -1922,65 +1922,117 @@ const SplitBoxPlot = (props) => {
|
|
|
1922
1922
|
});
|
|
1923
1923
|
const totalGroups = groups.length + additionalGroups.length;
|
|
1924
1924
|
const yAxisSplitProportion = additionalGroups.length > 0 ? groups.length / totalGroups : 1;
|
|
1925
|
+
// Render y-axis titles as annotations so we can specifically set xshift in px from the axis.
|
|
1926
|
+
// Calculating approximate label width in pixels to ensure the title is far enough away from the longest label,
|
|
1927
|
+
// with a minimum distance to look good even with short labels.
|
|
1928
|
+
const charWidth = 8; // ~px per character for Open Sans 14px (Plotly title font)
|
|
1929
|
+
const primaryMaxChars = Math.max(0, ...groups.map((g) => g.label.length));
|
|
1930
|
+
const secondaryMaxChars = additionalGroups.length > 0
|
|
1931
|
+
? Math.max(0, ...additionalGroups.map((g) => g.label.length))
|
|
1932
|
+
: primaryMaxChars;
|
|
1933
|
+
const maxLabelChars = Math.max(primaryMaxChars, secondaryMaxChars);
|
|
1934
|
+
const approxLabelPx = maxLabelChars * charWidth;
|
|
1935
|
+
const yAxisTitleAnnotations = [];
|
|
1936
|
+
if (yAxisTitle) {
|
|
1937
|
+
const domainTop = additionalGroups.length > 0 ? yAxisSplitProportion - 0.01 : 1;
|
|
1938
|
+
yAxisTitleAnnotations.push({
|
|
1939
|
+
text: yAxisTitle,
|
|
1940
|
+
x: 0,
|
|
1941
|
+
xshift: -120,
|
|
1942
|
+
y: domainTop / 2,
|
|
1943
|
+
xref: "paper",
|
|
1944
|
+
yref: "paper",
|
|
1945
|
+
showarrow: false,
|
|
1946
|
+
textangle: "-90",
|
|
1947
|
+
xanchor: "center",
|
|
1948
|
+
yanchor: "middle",
|
|
1949
|
+
font: {
|
|
1950
|
+
family: '"Open Sans", verdana, arial, sans-serif',
|
|
1951
|
+
size: 14,
|
|
1952
|
+
color: "rgb(68, 68, 68)",
|
|
1953
|
+
},
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
if (secondYAxisTitle && additionalGroups.length > 0) {
|
|
1957
|
+
yAxisTitleAnnotations.push({
|
|
1958
|
+
text: secondYAxisTitle,
|
|
1959
|
+
x: 0,
|
|
1960
|
+
xshift: -120,
|
|
1961
|
+
y: (yAxisSplitProportion + 1) / 2,
|
|
1962
|
+
xref: "paper",
|
|
1963
|
+
yref: "paper",
|
|
1964
|
+
showarrow: false,
|
|
1965
|
+
textangle: "-90",
|
|
1966
|
+
xanchor: "center",
|
|
1967
|
+
yanchor: "middle",
|
|
1968
|
+
font: {
|
|
1969
|
+
family: '"Open Sans", verdana, arial, sans-serif',
|
|
1970
|
+
size: 14,
|
|
1971
|
+
color: "rgb(68, 68, 68)",
|
|
1972
|
+
}, // styled to look like plotly's default title styling
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1925
1975
|
const extraLayoutConfig = Object.assign(Object.assign({ yaxis: {
|
|
1926
1976
|
type: "linear", // Use linear axis for numeric positioning of boxes
|
|
1927
1977
|
tickmode: "array",
|
|
1928
1978
|
tickvals,
|
|
1929
1979
|
ticktext,
|
|
1930
|
-
automargin: true,
|
|
1980
|
+
// automargin: true,
|
|
1931
1981
|
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
|
|
1982
|
+
domain: additionalGroups.length > 0 ? [0, yAxisSplitProportion - 0.01] : [0, 1], // Ensure primary y-axis takes full height
|
|
1933
1983
|
tickcolor: "#ffffff",
|
|
1934
1984
|
ticklen: 0,
|
|
1985
|
+
ticksuffix: "", // clear the BoxPlot default suffix so label widths match yaxis2
|
|
1935
1986
|
showgrid: false,
|
|
1936
1987
|
//@ts-ignore
|
|
1937
1988
|
ticklabelstandoff: 7,
|
|
1938
1989
|
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
|
-
},
|
|
1990
|
+
title: { text: "" }, // rendered via annotation instead
|
|
1943
1991
|
}, xaxis: {
|
|
1944
1992
|
anchor: "y", // Anchor to primary y-axis
|
|
1945
1993
|
}, margin: {
|
|
1946
|
-
t: title ? 50 :
|
|
1947
|
-
r:
|
|
1994
|
+
t: title ? 50 : 70,
|
|
1995
|
+
r: 20,
|
|
1996
|
+
l: Math.max(130, approxLabelPx + 40),
|
|
1948
1997
|
}, shapes: [
|
|
1949
1998
|
...separatorShapes,
|
|
1950
1999
|
// ...differenceBetweenMediansLines,
|
|
1951
|
-
...xAnnotations.map((annotation) => ({
|
|
2000
|
+
...xAnnotations.map((annotation, annotationIndex) => ({
|
|
1952
2001
|
type: "line",
|
|
1953
2002
|
x0: annotation.x,
|
|
1954
2003
|
x1: annotation.x,
|
|
1955
2004
|
y0: 0,
|
|
1956
|
-
y1: 1.05,
|
|
2005
|
+
y1: 1.05 + annotationIndex * 0.05,
|
|
1957
2006
|
xref: "x",
|
|
1958
2007
|
yref: "paper",
|
|
1959
2008
|
line: {
|
|
1960
2009
|
color: annotation.color || "rgba(255, 0, 0, 0.7)",
|
|
1961
2010
|
width: 2,
|
|
1962
|
-
dash: "
|
|
2011
|
+
dash: "dot",
|
|
1963
2012
|
},
|
|
1964
2013
|
})),
|
|
1965
2014
|
], annotations: [
|
|
2015
|
+
...yAxisTitleAnnotations,
|
|
1966
2016
|
// ...differenceAnnotations,
|
|
1967
|
-
...xAnnotations.map((annotation) => ({
|
|
2017
|
+
...xAnnotations.map((annotation, annotationIndex) => ({
|
|
1968
2018
|
x: annotation.x,
|
|
1969
|
-
y: 1
|
|
2019
|
+
y: 1,
|
|
1970
2020
|
xref: "x",
|
|
1971
2021
|
yref: "paper",
|
|
1972
2022
|
text: annotation.text || "",
|
|
1973
2023
|
showarrow: false,
|
|
1974
2024
|
yanchor: "bottom",
|
|
2025
|
+
yshift: 18 + annotationIndex * 20, // stack in pixels — stable across screen sizes
|
|
1975
2026
|
font: {
|
|
1976
2027
|
color: annotation.color || "rgba(255, 0, 0, 0.7)",
|
|
1977
2028
|
size: 12,
|
|
1978
2029
|
},
|
|
1979
2030
|
})),
|
|
1980
|
-
] }, (additionalGroups && {
|
|
2031
|
+
] }, (additionalGroups.length > 0 && {
|
|
1981
2032
|
xaxis2: {
|
|
1982
2033
|
title: {
|
|
1983
2034
|
text: secondXAxisTitle || "", // Set default to empty string to prevent Plotly from adding its own default title
|
|
2035
|
+
standoff: 10 + xAnnotations.length * 12, // Add extra space to accommodate annotations.
|
|
1984
2036
|
},
|
|
1985
2037
|
side: "top",
|
|
1986
2038
|
anchor: "y2", // Anchor to secondary y-axis
|
|
@@ -1998,17 +2050,16 @@ const SplitBoxPlot = (props) => {
|
|
|
1998
2050
|
automargin: true,
|
|
1999
2051
|
matches: "x", // Match the range of xaxis to keep them synchronized
|
|
2000
2052
|
},
|
|
2001
|
-
})), (additionalGroups && {
|
|
2053
|
+
})), (additionalGroups.length > 0 && {
|
|
2002
2054
|
yaxis2: {
|
|
2003
2055
|
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
|
-
},
|
|
2056
|
+
title: { text: "" }, // rendered via annotation instead
|
|
2008
2057
|
range: [-0.5, additionalGroups.length - 0.5], // Add some padding to the y-axis range to accommodate the boxes
|
|
2009
2058
|
automargin: true, // Required for standoff to work properly
|
|
2010
2059
|
showgrid: true,
|
|
2011
2060
|
zeroline: false,
|
|
2061
|
+
ticklen: 0,
|
|
2062
|
+
ticksuffix: "",
|
|
2012
2063
|
ticklabelposition: "outside left",
|
|
2013
2064
|
showline: true,
|
|
2014
2065
|
mirror: "ticks",
|
|
@@ -2025,15 +2076,15 @@ const SplitBoxPlot = (props) => {
|
|
|
2025
2076
|
ticklabelstandoff: 7,
|
|
2026
2077
|
},
|
|
2027
2078
|
}));
|
|
2028
|
-
console.log("extraLayoutConfig:", extraLayoutConfig);
|
|
2029
2079
|
const containerStyles = Object.assign({ width: width, height: height, position: "relative", display: "flex", flexDirection: "column", gap: 0 }, containerStyleOverrides);
|
|
2030
|
-
console.log(boxPlotData);
|
|
2031
2080
|
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
2081
|
};
|
|
2033
2082
|
|
|
2034
2083
|
const Plot$2 = lazy(() => Promise.resolve().then(function () { return reactPlotlyWrapper$1; }));
|
|
2035
2084
|
const SummaryComparisonPlot = (props) => {
|
|
2036
|
-
const { groups, height = 250, title = "", xAxisTitle, yAxisTitle, containerStyleOverrides, plotId = "summary-comparison-plot", tooltipPosition = "right", startXAxisAtZero = true, unit = "",
|
|
2085
|
+
const { groups, userColor = "orange", height = 250, title = "", xAxisTitle, yAxisTitle, containerStyleOverrides, plotId = "summary-comparison-plot", tooltipPosition = "right", startXAxisAtZero = true, unit = "",
|
|
2086
|
+
// xAnnotations = [], TO DO
|
|
2087
|
+
tooltipLabels = {}, } = props;
|
|
2037
2088
|
// Ref for plot container
|
|
2038
2089
|
const containerRef = useRef(null);
|
|
2039
2090
|
// State for custom tooltip
|
|
@@ -2063,40 +2114,42 @@ const SummaryComparisonPlot = (props) => {
|
|
|
2063
2114
|
let tooltipX = event.event.clientX;
|
|
2064
2115
|
let tooltipY = event.event.clientY;
|
|
2065
2116
|
content += `<strong>${groupName}</strong><br/>`;
|
|
2066
|
-
const
|
|
2067
|
-
? groupData.
|
|
2117
|
+
const populationMinText = groupData.populationMin !== undefined
|
|
2118
|
+
? groupData.populationMin.toFixed(2)
|
|
2068
2119
|
: "NA";
|
|
2069
|
-
const
|
|
2070
|
-
? groupData.
|
|
2071
|
-
: "NA";
|
|
2072
|
-
const comparedMedianText = groupData.comparedMedian !== undefined
|
|
2073
|
-
? groupData.comparedMedian.toFixed(2)
|
|
2120
|
+
const populationMaxText = groupData.populationMax !== undefined
|
|
2121
|
+
? groupData.populationMax.toFixed(2)
|
|
2074
2122
|
: "NA";
|
|
2075
2123
|
content += `
|
|
2076
2124
|
<table style="width: 100%; margin-top: 4px;">
|
|
2077
2125
|
<tr>
|
|
2078
|
-
<td style="text-align: left; padding: 2px 18px 2px 0;"
|
|
2079
|
-
<td style="text-align: right; padding: 2px 0;">${
|
|
2126
|
+
<td style="text-align: left; padding: 2px 18px 2px 0;">${tooltipLabels.populationMinLabel || "Population avg-\u03C3"}:</td>
|
|
2127
|
+
<td style="text-align: right; padding: 2px 0;">${populationMinText}</td>
|
|
2080
2128
|
</tr>
|
|
2081
2129
|
<tr>
|
|
2082
|
-
<td style="text-align: left; padding: 2px 18px 2px 0;"
|
|
2083
|
-
<td style="text-align: right; padding: 2px 0;">${
|
|
2130
|
+
<td style="text-align: left; padding: 2px 18px 2px 0;">${tooltipLabels.populationMaxLabel || "Population avg+\u03C3"}:</td>
|
|
2131
|
+
<td style="text-align: right; padding: 2px 0;">${populationMaxText}</td>
|
|
2084
2132
|
</tr>
|
|
2133
|
+
`;
|
|
2134
|
+
if (groupData.userValue !== undefined) {
|
|
2135
|
+
const userValueText = groupData.userValue.toFixed(2);
|
|
2136
|
+
content += `
|
|
2085
2137
|
<tr>
|
|
2086
|
-
<td style="text-align: left; padding: 2px 18px 2px 0;"
|
|
2087
|
-
<td style="text-align: right; padding: 2px 0;">${
|
|
2138
|
+
<td style="text-align: left; padding: 2px 18px 2px 0;">${tooltipLabels.userValueLabel || "My value"}:</td>
|
|
2139
|
+
<td style="text-align: right; padding: 2px 0;">${userValueText}</td>
|
|
2088
2140
|
</tr>
|
|
2089
|
-
|
|
2090
|
-
|
|
2141
|
+
`;
|
|
2142
|
+
}
|
|
2143
|
+
content += `</table></div>`;
|
|
2091
2144
|
contentColor = eventData.line
|
|
2092
2145
|
? eventData.line.color
|
|
2093
2146
|
: eventData.marker.color || contentColor;
|
|
2094
|
-
// Position tooltip at the end of the line (
|
|
2147
|
+
// Position tooltip at the end of the line (populationMax for right tooltip, populationMin for left tooltip)
|
|
2095
2148
|
// Use the xaxis d2p method to convert data coordinate to pixel coordinate
|
|
2096
2149
|
if (point.xaxis && typeof point.xaxis.d2p === "function") {
|
|
2097
2150
|
const pixelX = tooltipPosition === "right"
|
|
2098
|
-
? point.xaxis.d2p(groupData.
|
|
2099
|
-
: point.xaxis.d2p(groupData.
|
|
2151
|
+
? point.xaxis.d2p(groupData.populationMax)
|
|
2152
|
+
: point.xaxis.d2p(groupData.populationMin);
|
|
2100
2153
|
const pixelY = point.yaxis.d2p(eventData.y[0]); // Use the y coordinate of the line for vertical positioning
|
|
2101
2154
|
const containerRect = (_b = containerRef.current) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
2102
2155
|
if (containerRect) {
|
|
@@ -2120,63 +2173,68 @@ const SummaryComparisonPlot = (props) => {
|
|
|
2120
2173
|
// Transform the data into a format suitable for a plotly scatterplot
|
|
2121
2174
|
const plotlyData = groups.flatMap((group, groupIndex) => {
|
|
2122
2175
|
const traces = [];
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
}
|
|
2139
|
-
if (group.data.comparedMedian !== undefined) {
|
|
2140
|
-
traces.push({
|
|
2141
|
-
type: "scatter",
|
|
2142
|
-
mode: "markers",
|
|
2143
|
-
name: group.groupLabel,
|
|
2144
|
-
x: [group.data.comparedMedian],
|
|
2145
|
-
y: [groupIndex * 0.1],
|
|
2146
|
-
marker: {
|
|
2147
|
-
color: colorToRGBA(group.color || "orange", 1),
|
|
2148
|
-
size: 10,
|
|
2149
|
-
symbol: "circle",
|
|
2150
|
-
line: {
|
|
2151
|
-
color: "white",
|
|
2152
|
-
width: 1,
|
|
2153
|
-
},
|
|
2154
|
-
},
|
|
2155
|
-
showlegend: false,
|
|
2156
|
-
hoverinfo: "none",
|
|
2157
|
-
});
|
|
2176
|
+
traces.push({
|
|
2177
|
+
type: "scatter",
|
|
2178
|
+
mode: "lines",
|
|
2179
|
+
name: group.groupLabel,
|
|
2180
|
+
x: [group.data.populationMin, group.data.populationMax],
|
|
2181
|
+
y: [groupIndex * 0.1, groupIndex * 0.1],
|
|
2182
|
+
line: {
|
|
2183
|
+
color: colorToRGBA(group.color || "orange", 1),
|
|
2184
|
+
width: 6,
|
|
2185
|
+
},
|
|
2186
|
+
showlegend: false,
|
|
2187
|
+
hoverinfo: "none",
|
|
2188
|
+
});
|
|
2189
|
+
if (group.data.userValue !== undefined) {
|
|
2190
|
+
const CIRCLE_RADIUS_PX = 6;
|
|
2158
2191
|
comparedLines.push({
|
|
2159
2192
|
type: "line",
|
|
2160
2193
|
name: group.groupLabel,
|
|
2161
|
-
x0: group.data.
|
|
2162
|
-
x1: group.data.
|
|
2194
|
+
x0: group.data.userValue,
|
|
2195
|
+
x1: group.data.userValue,
|
|
2163
2196
|
y0: 0,
|
|
2164
2197
|
y1: 1.1,
|
|
2165
2198
|
yref: "paper",
|
|
2166
2199
|
line: {
|
|
2167
|
-
color:
|
|
2200
|
+
color: colorToRGBA(userColor, 1),
|
|
2168
2201
|
width: 2,
|
|
2169
2202
|
},
|
|
2170
2203
|
});
|
|
2204
|
+
comparedLines.push({
|
|
2205
|
+
type: "circle",
|
|
2206
|
+
xref: "x",
|
|
2207
|
+
yref: "y",
|
|
2208
|
+
// Pixel-mode: xanchor/yanchor are data coords for the center;
|
|
2209
|
+
// x0/x1/y0/y1 are pixel offsets, so the circle stays a fixed
|
|
2210
|
+
// visual size regardless of the axis scale.
|
|
2211
|
+
xsizemode: "pixel",
|
|
2212
|
+
ysizemode: "pixel",
|
|
2213
|
+
xanchor: group.data.userValue,
|
|
2214
|
+
yanchor: groupIndex * 0.1,
|
|
2215
|
+
x0: -CIRCLE_RADIUS_PX,
|
|
2216
|
+
x1: CIRCLE_RADIUS_PX,
|
|
2217
|
+
y0: -CIRCLE_RADIUS_PX,
|
|
2218
|
+
y1: CIRCLE_RADIUS_PX,
|
|
2219
|
+
fillcolor: colorToRGBA(userColor, 1),
|
|
2220
|
+
line: {
|
|
2221
|
+
color: "white",
|
|
2222
|
+
width: 1,
|
|
2223
|
+
},
|
|
2224
|
+
});
|
|
2171
2225
|
comparedAnnotations.push({
|
|
2172
|
-
x: group.data.
|
|
2173
|
-
y: 1.
|
|
2226
|
+
x: group.data.userValue,
|
|
2227
|
+
y: 1.1,
|
|
2174
2228
|
xref: "x",
|
|
2175
2229
|
yref: "paper",
|
|
2176
|
-
text: `${group.data.
|
|
2230
|
+
text: `${group.data.userValue.toFixed(2)} ${unit}`,
|
|
2177
2231
|
showarrow: false,
|
|
2178
2232
|
xanchor: "center",
|
|
2179
2233
|
yanchor: "bottom",
|
|
2234
|
+
font: {
|
|
2235
|
+
color: colorToRGBA(userColor, 1),
|
|
2236
|
+
size: 12,
|
|
2237
|
+
},
|
|
2180
2238
|
});
|
|
2181
2239
|
}
|
|
2182
2240
|
return traces;
|
|
@@ -2184,9 +2242,10 @@ const SummaryComparisonPlot = (props) => {
|
|
|
2184
2242
|
// Add tick label for the compared annotation value
|
|
2185
2243
|
if (comparedAnnotations.length > 0) {
|
|
2186
2244
|
comparedAnnotations.push({
|
|
2187
|
-
x:
|
|
2188
|
-
y: 1.
|
|
2245
|
+
x: 0, // Position to the left of the y-axis
|
|
2246
|
+
y: 1.1, // Should match the y position of the compared median annotation
|
|
2189
2247
|
xref: "paper",
|
|
2248
|
+
xshift: -10,
|
|
2190
2249
|
yref: "paper",
|
|
2191
2250
|
text: "My AVG",
|
|
2192
2251
|
showarrow: false,
|
|
@@ -2194,14 +2253,17 @@ const SummaryComparisonPlot = (props) => {
|
|
|
2194
2253
|
yanchor: "bottom",
|
|
2195
2254
|
});
|
|
2196
2255
|
}
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2256
|
+
// xRangeMin a little funky because we want to ensure a non-zero value is calculated
|
|
2257
|
+
// in case startXAxisAtZero is false. populationMin is our anchor and
|
|
2258
|
+
// if that is not defined then we can fall back to a min of 0. If userValue is not defined
|
|
2259
|
+
// we want to listen to populationMin for computing xRangeMin.
|
|
2260
|
+
const xRangeMin = groups.reduce((min, group) => { var _a; return Math.min(min, group.data.populationMin, (_a = group.data.userValue) !== null && _a !== void 0 ? _a : Infinity); }, Infinity);
|
|
2261
|
+
// Similar logic for xRangeMax. If nothing is defined, set the max to 0.
|
|
2262
|
+
// Otherwise, listen to populationMax and userValue if given.
|
|
2201
2263
|
const xRangeMax = groups.reduce((max, group) => {
|
|
2202
|
-
var _a
|
|
2203
|
-
return Math.max(max,
|
|
2204
|
-
},
|
|
2264
|
+
var _a;
|
|
2265
|
+
return Math.max(max, group.data.populationMax, (_a = group.data.userValue) !== null && _a !== void 0 ? _a : -Infinity);
|
|
2266
|
+
}, -Infinity);
|
|
2205
2267
|
const layout = {
|
|
2206
2268
|
width: undefined,
|
|
2207
2269
|
height: height,
|
|
@@ -2250,6 +2312,12 @@ const SummaryComparisonPlot = (props) => {
|
|
|
2250
2312
|
linewidth: 1,
|
|
2251
2313
|
},
|
|
2252
2314
|
yaxis: {
|
|
2315
|
+
title: {
|
|
2316
|
+
text: yAxisTitle,
|
|
2317
|
+
font: {
|
|
2318
|
+
size: 14,
|
|
2319
|
+
},
|
|
2320
|
+
},
|
|
2253
2321
|
mirror: "ticks",
|
|
2254
2322
|
gridcolor: "#efefef",
|
|
2255
2323
|
gridwidth: 0.2,
|
|
@@ -2323,12 +2391,26 @@ const SummaryComparisonPlotLegend = ({ comparedDataLabel, summarizedDataLabel, c
|
|
|
2323
2391
|
gap: "20px",
|
|
2324
2392
|
alignItems: "center",
|
|
2325
2393
|
flexDirection: "row",
|
|
2326
|
-
}, children: [jsxs("div", { style: { display: "flex", gap: "5px", alignItems: "center" }, children: [
|
|
2394
|
+
}, children: [jsxs("div", { style: { display: "flex", gap: "5px", alignItems: "center" }, children: [jsxs("div", { style: {
|
|
2395
|
+
position: "relative",
|
|
2327
2396
|
width: 13,
|
|
2328
|
-
height:
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2397
|
+
height: 20,
|
|
2398
|
+
display: "flex",
|
|
2399
|
+
alignItems: "center",
|
|
2400
|
+
justifyContent: "center",
|
|
2401
|
+
}, children: [jsx(Box, { sx: {
|
|
2402
|
+
position: "absolute",
|
|
2403
|
+
width: 3,
|
|
2404
|
+
height: 20,
|
|
2405
|
+
backgroundColor: color,
|
|
2406
|
+
} }), jsx(Box, { sx: {
|
|
2407
|
+
position: "relative",
|
|
2408
|
+
width: 10,
|
|
2409
|
+
height: 10,
|
|
2410
|
+
backgroundColor: color,
|
|
2411
|
+
borderRadius: "20px",
|
|
2412
|
+
border: `1px solid white`,
|
|
2413
|
+
} })] }), jsx("span", { children: comparedDataLabel })] }), jsxs("div", { style: { display: "flex", gap: "5px", alignItems: "center" }, children: [jsx(Box, { sx: { width: 30, height: 5, backgroundColor: color } }), jsx("span", { children: summarizedDataLabel })] })] }));
|
|
2332
2414
|
};
|
|
2333
2415
|
|
|
2334
2416
|
const Plot$1 = lazy(() => Promise.resolve().then(function () { return reactPlotlyWrapper$1; }));
|
|
@@ -2569,21 +2651,37 @@ const BarPlot = (props) => {
|
|
|
2569
2651
|
// Ref for plot container
|
|
2570
2652
|
const containerRef = useRef(null);
|
|
2571
2653
|
const plotMetaRef = useRef(null);
|
|
2654
|
+
// Pixel offsets from the x-axis line for the bar group shapes.
|
|
2655
|
+
// 45px clears tick marks + labels + axis title; the 15px band sits below all of them.
|
|
2656
|
+
const BAR_GROUP_SHAPE_TOP_PX = 8;
|
|
2657
|
+
const BAR_GROUP_SHAPE_HEIGHT_PX = 20;
|
|
2658
|
+
// px above the top of the plot area — lands between x2 tick labels (~15px) and x2 title (~35px)
|
|
2659
|
+
const X_ANNOTATION_TOP_PX = 22;
|
|
2660
|
+
// Tracks the rendered plot area height so we can convert the shape's fixed pixel
|
|
2661
|
+
// center back into a paper y coordinate for the annotation.
|
|
2662
|
+
const [plotAreaHeight, setPlotAreaHeight] = useState(300);
|
|
2572
2663
|
const [barGroupTooltip, setBarGroupTooltip] = useState(null);
|
|
2573
2664
|
const y2AxisPosition = 0.72; // Position of the secondary y-axis (histogram) as a fraction of the plot height
|
|
2574
2665
|
const capturePlotMeta = (_figure, graphDiv) => {
|
|
2575
|
-
var _a, _b, _c;
|
|
2666
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2576
2667
|
try {
|
|
2577
2668
|
const m = (_a = graphDiv._fullLayout) === null || _a === void 0 ? void 0 : _a.margin;
|
|
2578
2669
|
const r = (_c = (_b = graphDiv._fullLayout) === null || _b === void 0 ? void 0 : _b.xaxis) === null || _c === void 0 ? void 0 : _c.range;
|
|
2670
|
+
const h = (_d = graphDiv._fullLayout) === null || _d === void 0 ? void 0 : _d.height;
|
|
2579
2671
|
if (m && r) {
|
|
2580
2672
|
plotMetaRef.current = {
|
|
2581
2673
|
xRange: r,
|
|
2582
2674
|
margin: { l: m.l, r: m.r, t: m.t, b: m.b },
|
|
2675
|
+
height: (_f = h !== null && h !== void 0 ? h : (_e = containerRef.current) === null || _e === void 0 ? void 0 : _e.clientHeight) !== null && _f !== void 0 ? _f : 400,
|
|
2583
2676
|
};
|
|
2677
|
+
if (h) {
|
|
2678
|
+
const newPlotH = h - m.t - m.b;
|
|
2679
|
+
if (newPlotH > 0)
|
|
2680
|
+
setPlotAreaHeight(newPlotH);
|
|
2681
|
+
}
|
|
2584
2682
|
}
|
|
2585
2683
|
}
|
|
2586
|
-
catch (
|
|
2684
|
+
catch (_g) { }
|
|
2587
2685
|
};
|
|
2588
2686
|
const handleBarGroupMouseMove = (e) => {
|
|
2589
2687
|
var _a;
|
|
@@ -2593,18 +2691,18 @@ const BarPlot = (props) => {
|
|
|
2593
2691
|
setBarGroupTooltip(null);
|
|
2594
2692
|
return;
|
|
2595
2693
|
}
|
|
2596
|
-
const { xRange, margin } = plotMetaRef.current;
|
|
2694
|
+
const { xRange, margin, height: plotlyHeight } = plotMetaRef.current;
|
|
2597
2695
|
const rect = containerRef.current.getBoundingClientRect();
|
|
2598
2696
|
const mouseX = e.clientX - rect.left;
|
|
2599
2697
|
const mouseY = e.clientY - rect.top;
|
|
2600
|
-
const containerHeight = containerRef.current.clientHeight;
|
|
2601
2698
|
const containerWidth = containerRef.current.clientWidth;
|
|
2602
2699
|
const plotW = containerWidth - margin.l - margin.r;
|
|
2603
|
-
|
|
2604
|
-
const plotBottom =
|
|
2605
|
-
//
|
|
2606
|
-
|
|
2607
|
-
const
|
|
2700
|
+
// Use Plotly's own reported height so plotBottom is consistent with the margin values
|
|
2701
|
+
const plotBottom = plotlyHeight - margin.b - 35; // Subtracting and extra 35px to finally get it in the right spot.
|
|
2702
|
+
// Shapes use yanchor=0 (x-axis line) with negative pixel y values, which maps to
|
|
2703
|
+
// positive screen-y (downward). Strip bounds mirror those pixel offsets exactly.
|
|
2704
|
+
const stripTop = plotBottom;
|
|
2705
|
+
const stripBottom = plotBottom + BAR_GROUP_SHAPE_TOP_PX + BAR_GROUP_SHAPE_HEIGHT_PX + 5; // Give a little extra room on the bottom.
|
|
2608
2706
|
if (mouseY < stripTop || mouseY > stripBottom) {
|
|
2609
2707
|
setBarGroupTooltip(null);
|
|
2610
2708
|
return;
|
|
@@ -2755,8 +2853,12 @@ const BarPlot = (props) => {
|
|
|
2755
2853
|
yref: "paper",
|
|
2756
2854
|
x0: group.min,
|
|
2757
2855
|
x1: group.max > xMax ? xMax + barWidth : group.max,
|
|
2758
|
-
|
|
2759
|
-
|
|
2856
|
+
// Anchor at the x-axis line (paper y=0) so the pixel offset is consistent
|
|
2857
|
+
// regardless of plot height — avoids overlap with tick labels on small screens.
|
|
2858
|
+
yanchor: 0,
|
|
2859
|
+
ysizemode: "pixel",
|
|
2860
|
+
y0: -BAR_GROUP_SHAPE_TOP_PX,
|
|
2861
|
+
y1: -28,
|
|
2760
2862
|
fillcolor: group.color,
|
|
2761
2863
|
line: {
|
|
2762
2864
|
width: 1,
|
|
@@ -2769,7 +2871,10 @@ const BarPlot = (props) => {
|
|
|
2769
2871
|
xref: "x",
|
|
2770
2872
|
yref: "paper",
|
|
2771
2873
|
x: barGroups[0].min + barDataWidth / 2,
|
|
2772
|
-
y
|
|
2874
|
+
// Convert the pixel center of the shape band to a paper y coordinate using
|
|
2875
|
+
// the actual rendered plot area height so it stays inside the shape on any screen size.
|
|
2876
|
+
y: -18 /
|
|
2877
|
+
plotAreaHeight,
|
|
2773
2878
|
text: barGroupTooltipTitle,
|
|
2774
2879
|
showarrow: false,
|
|
2775
2880
|
yanchor: "middle",
|
|
@@ -2808,7 +2913,9 @@ const BarPlot = (props) => {
|
|
|
2808
2913
|
l: 50,
|
|
2809
2914
|
r: 35, // Balance between ensuring the mean annotation doesn't get cut off and having too much margin.
|
|
2810
2915
|
t: 40 + (title ? 50 : 0), // Add extra top margin if there is a title
|
|
2811
|
-
b:
|
|
2916
|
+
b: barGroups.length > 0
|
|
2917
|
+
? BAR_GROUP_SHAPE_TOP_PX + BAR_GROUP_SHAPE_HEIGHT_PX + 15
|
|
2918
|
+
: 50,
|
|
2812
2919
|
pad: 4,
|
|
2813
2920
|
}, xaxis: {
|
|
2814
2921
|
title: {
|
|
@@ -2827,12 +2934,15 @@ const BarPlot = (props) => {
|
|
|
2827
2934
|
linewidth: 1,
|
|
2828
2935
|
fixedrange: true, // Disable zooming
|
|
2829
2936
|
automargin: true, // Adjust margin if tick labels rotate
|
|
2830
|
-
ticklen:
|
|
2937
|
+
ticklen: barGroups.length > 0
|
|
2938
|
+
? BAR_GROUP_SHAPE_TOP_PX + BAR_GROUP_SHAPE_HEIGHT_PX
|
|
2939
|
+
: 8, // tick length in px.
|
|
2831
2940
|
tickcolor: "white",
|
|
2832
2941
|
} }, (data2 && {
|
|
2833
2942
|
xaxis2: {
|
|
2834
2943
|
title: {
|
|
2835
2944
|
text: xAxis2Title,
|
|
2945
|
+
standoff: 22,
|
|
2836
2946
|
},
|
|
2837
2947
|
side: "top",
|
|
2838
2948
|
anchor: "y2", // Anchor to secondary y-axis
|
|
@@ -2853,7 +2963,7 @@ const BarPlot = (props) => {
|
|
|
2853
2963
|
})), { yaxis: {
|
|
2854
2964
|
title: {
|
|
2855
2965
|
text: yAxisTitle !== null && yAxisTitle !== void 0 ? yAxisTitle : "Count",
|
|
2856
|
-
standoff:
|
|
2966
|
+
standoff: 15, // Add space between title and axis
|
|
2857
2967
|
},
|
|
2858
2968
|
domain: data2 ? [0, y2AxisPosition - 0.01] : [0, 1], // Ensure primary y-axis takes full height
|
|
2859
2969
|
automargin: true, // Required for standoff to work properly
|
|
@@ -2900,7 +3010,7 @@ const BarPlot = (props) => {
|
|
|
2900
3010
|
x0: annotation.x,
|
|
2901
3011
|
x1: annotation.x,
|
|
2902
3012
|
y0: 0,
|
|
2903
|
-
y1: 1
|
|
3013
|
+
y1: 1 + (X_ANNOTATION_TOP_PX - 4) / plotAreaHeight,
|
|
2904
3014
|
xref: "x",
|
|
2905
3015
|
yref: "paper",
|
|
2906
3016
|
line: {
|
|
@@ -2913,7 +3023,7 @@ const BarPlot = (props) => {
|
|
|
2913
3023
|
...(barGroupShapeAnnotation ? [barGroupShapeAnnotation] : []),
|
|
2914
3024
|
...xAnnotations.map((annotation) => ({
|
|
2915
3025
|
x: annotation.x,
|
|
2916
|
-
y: 1
|
|
3026
|
+
y: 1 + X_ANNOTATION_TOP_PX / plotAreaHeight,
|
|
2917
3027
|
xref: "x",
|
|
2918
3028
|
yref: "paper",
|
|
2919
3029
|
text: annotation.text || "",
|