td-plots 1.11.1 → 1.11.3

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.
@@ -38,6 +38,7 @@ export interface BarPlotProps {
38
38
  barHoverTemplate?: string;
39
39
  barGroups?: BarGroup[];
40
40
  barGroupTooltipTitle?: string;
41
+ d3FormatValueString?: string;
41
42
  }
42
43
  declare const BarPlot: (props: BarPlotProps) => import("react/jsx-runtime").JSX.Element;
43
44
  export default BarPlot;
@@ -18,7 +18,8 @@ export interface SplitBoxPlotProps {
18
18
  text?: string;
19
19
  color?: string;
20
20
  }[];
21
- yTitleLocationOverride?: number;
21
+ d3FormatValueString?: string;
22
+ addExtraYTitleRoom?: boolean;
22
23
  }
23
24
  declare const SplitBoxPlot: (props: SplitBoxPlotProps) => import("react/jsx-runtime").JSX.Element;
24
25
  export default SplitBoxPlot;
@@ -26,6 +26,7 @@ export type SummaryComparisonPlotProps = {
26
26
  populationMaxLabel?: string;
27
27
  userValueLabel?: string;
28
28
  };
29
+ d3FormatValueString?: string;
29
30
  };
30
31
  declare const SummaryComparisonPlot: (props: SummaryComparisonPlotProps) => import("react/jsx-runtime").JSX.Element;
31
32
  export default SummaryComparisonPlot;
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: {
@@ -1819,7 +1818,7 @@ const PairedComparisonsBoxPlot = (props) => {
1819
1818
  // This component shows a set of main boxplots as one collection, then allowes for additional
1820
1819
  // boxes to be shown in a subplot above the main boxplots for comparison.
1821
1820
  const SplitBoxPlot = (props) => {
1822
- const { groups, additionalGroups = [], width = 600, height = 400, title = "", xAxisTitle, yAxisTitle, secondXAxisTitle, secondYAxisTitle, containerStyleOverrides, plotId = "paired-comparisons-boxplot", xAnnotations = [], yTitleLocationOverride, } = props;
1821
+ const { groups, additionalGroups = [], width = 600, height = 400, title = "", xAxisTitle, yAxisTitle, secondXAxisTitle, secondYAxisTitle, containerStyleOverrides, plotId = "paired-comparisons-boxplot", xAnnotations = [], d3FormatValueString = ".1f", addExtraYTitleRoom = false, } = props;
1823
1822
  // Transform the grouped data into an array for BoxPlot
1824
1823
  const boxPlotData = [
1825
1824
  ...groups.flatMap((group, groupIndex) => {
@@ -1923,25 +1922,14 @@ const SplitBoxPlot = (props) => {
1923
1922
  });
1924
1923
  const totalGroups = groups.length + additionalGroups.length;
1925
1924
  const yAxisSplitProportion = additionalGroups.length > 0 ? groups.length / totalGroups : 1;
1926
- // Render y-axis titles as annotations so both always land at the same paper x,
1927
- // Using this annotations approach because we found the standoff prop to be unreliable across subplots with different label widths.
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 leftMargin = approxLabelPx + 75; // tick labels + title + gaps
1936
- const plotContentWidth = Math.max(1, width - leftMargin - 50);
1937
- // 0.83 corrects for charWidth=8 overestimating rendered tick-label width (Open Sans 12px ≈ 6.6px/char)
1938
- const titleXPaper = -(approxLabelPx * 0.83) / plotContentWidth;
1925
+ const xshift = addExtraYTitleRoom ? -140 : -130; // Add extra shift if we're adding extra room for y-axis titles
1939
1926
  const yAxisTitleAnnotations = [];
1940
1927
  if (yAxisTitle) {
1941
1928
  const domainTop = additionalGroups.length > 0 ? yAxisSplitProportion - 0.01 : 1;
1942
1929
  yAxisTitleAnnotations.push({
1943
1930
  text: yAxisTitle,
1944
- x: yTitleLocationOverride || titleXPaper,
1931
+ x: 0,
1932
+ xshift: xshift,
1945
1933
  y: domainTop / 2,
1946
1934
  xref: "paper",
1947
1935
  yref: "paper",
@@ -1959,7 +1947,8 @@ const SplitBoxPlot = (props) => {
1959
1947
  if (secondYAxisTitle && additionalGroups.length > 0) {
1960
1948
  yAxisTitleAnnotations.push({
1961
1949
  text: secondYAxisTitle,
1962
- x: yTitleLocationOverride || titleXPaper,
1950
+ x: 0,
1951
+ xshift: xshift,
1963
1952
  y: (yAxisSplitProportion + 1) / 2,
1964
1953
  xref: "paper",
1965
1954
  yref: "paper",
@@ -1979,7 +1968,7 @@ const SplitBoxPlot = (props) => {
1979
1968
  tickmode: "array",
1980
1969
  tickvals,
1981
1970
  ticktext,
1982
- automargin: true,
1971
+ // automargin: true,
1983
1972
  range: [-0.5, groups.length - 0.5], // Add some padding to the y-axis range to accommodate the boxes
1984
1973
  domain: additionalGroups.length > 0 ? [0, yAxisSplitProportion - 0.01] : [0, 1], // Ensure primary y-axis takes full height
1985
1974
  tickcolor: "#ffffff",
@@ -1990,15 +1979,17 @@ const SplitBoxPlot = (props) => {
1990
1979
  ticklabelstandoff: 7,
1991
1980
  ticklabelposition: "outside left",
1992
1981
  title: { text: "" }, // rendered via annotation instead
1982
+ hoverformat: d3FormatValueString, // Format hover values using provided d3 format string
1993
1983
  }, xaxis: {
1994
1984
  anchor: "y", // Anchor to primary y-axis
1985
+ tickformat: d3FormatValueString, // Format ticks using provided d3 format string
1986
+ hoverformat: d3FormatValueString, // Format hover values using provided d3 format string
1995
1987
  }, margin: {
1996
1988
  t: title ? 50 : 70,
1997
- r: 50,
1998
- l: leftMargin,
1989
+ r: 20,
1990
+ l: 80, // Got to complicated to calculate. Adjust if necessary.
1999
1991
  }, shapes: [
2000
1992
  ...separatorShapes,
2001
- // ...differenceBetweenMediansLines,
2002
1993
  ...xAnnotations.map((annotation, annotationIndex) => ({
2003
1994
  type: "line",
2004
1995
  x0: annotation.x,
@@ -2018,12 +2009,13 @@ const SplitBoxPlot = (props) => {
2018
2009
  // ...differenceAnnotations,
2019
2010
  ...xAnnotations.map((annotation, annotationIndex) => ({
2020
2011
  x: annotation.x,
2021
- y: 1.05 + annotationIndex * 0.05, // Stack annotations above the plot with some spacing
2012
+ y: 1,
2022
2013
  xref: "x",
2023
2014
  yref: "paper",
2024
2015
  text: annotation.text || "",
2025
2016
  showarrow: false,
2026
2017
  yanchor: "bottom",
2018
+ yshift: 18 + annotationIndex * 20, // stack in pixels — stable across screen sizes
2027
2019
  font: {
2028
2020
  color: annotation.color || "rgba(255, 0, 0, 0.7)",
2029
2021
  size: 12,
@@ -2050,6 +2042,7 @@ const SplitBoxPlot = (props) => {
2050
2042
  fixedrange: true,
2051
2043
  automargin: true,
2052
2044
  matches: "x", // Match the range of xaxis to keep them synchronized
2045
+ tickformat: d3FormatValueString, // Format ticks using provided d3 format string
2053
2046
  },
2054
2047
  })), (additionalGroups.length > 0 && {
2055
2048
  yaxis2: {
@@ -2085,7 +2078,7 @@ const Plot$2 = lazy(() => Promise.resolve().then(function () { return reactPlotl
2085
2078
  const SummaryComparisonPlot = (props) => {
2086
2079
  const { groups, userColor = "orange", height = 250, title = "", xAxisTitle, yAxisTitle, containerStyleOverrides, plotId = "summary-comparison-plot", tooltipPosition = "right", startXAxisAtZero = true, unit = "",
2087
2080
  // xAnnotations = [], TO DO
2088
- tooltipLabels = {}, } = props;
2081
+ tooltipLabels = {}, d3FormatValueString = ".1f", } = props;
2089
2082
  // Ref for plot container
2090
2083
  const containerRef = useRef(null);
2091
2084
  // State for custom tooltip
@@ -2171,6 +2164,7 @@ const SummaryComparisonPlot = (props) => {
2171
2164
  };
2172
2165
  const comparedLines = [];
2173
2166
  const comparedAnnotations = [];
2167
+ console.log("d3FormatValueString:", d3FormatValueString);
2174
2168
  // Transform the data into a format suitable for a plotly scatterplot
2175
2169
  const plotlyData = groups.flatMap((group, groupIndex) => {
2176
2170
  const traces = [];
@@ -2228,7 +2222,7 @@ const SummaryComparisonPlot = (props) => {
2228
2222
  y: 1.1,
2229
2223
  xref: "x",
2230
2224
  yref: "paper",
2231
- text: `${group.data.userValue.toFixed(2)} ${unit}`,
2225
+ text: `${format$1(d3FormatValueString)(group.data.userValue)} ${unit}`,
2232
2226
  showarrow: false,
2233
2227
  xanchor: "center",
2234
2228
  yanchor: "bottom",
@@ -2243,9 +2237,10 @@ const SummaryComparisonPlot = (props) => {
2243
2237
  // Add tick label for the compared annotation value
2244
2238
  if (comparedAnnotations.length > 0) {
2245
2239
  comparedAnnotations.push({
2246
- x: -0.03, // Position to the left of the y-axis
2240
+ x: 0, // Position to the left of the y-axis
2247
2241
  y: 1.1, // Should match the y position of the compared median annotation
2248
2242
  xref: "paper",
2243
+ xshift: -10,
2249
2244
  yref: "paper",
2250
2245
  text: "My AVG",
2251
2246
  showarrow: false,
@@ -2269,7 +2264,7 @@ const SummaryComparisonPlot = (props) => {
2269
2264
  height: height,
2270
2265
  autosize: true,
2271
2266
  margin: {
2272
- l: 130,
2267
+ l: 100,
2273
2268
  r: 35, // Balance between ensuring the mean annotation doesn't get cut off and having too much margin.
2274
2269
  t: title ? 70 : 40,
2275
2270
  b: 50,
@@ -2310,6 +2305,7 @@ const SummaryComparisonPlot = (props) => {
2310
2305
  zerolinewidth: 1,
2311
2306
  linecolor: "#bababa",
2312
2307
  linewidth: 1,
2308
+ tickformat: d3FormatValueString, // Format ticks using provided d3 format string
2313
2309
  },
2314
2310
  yaxis: {
2315
2311
  title: {
@@ -2647,25 +2643,41 @@ const BarPlot = (props) => {
2647
2643
  xAxisTitle = "X Axis", yAxisTitle = "Y Axis", xAxis2Title = "", yAxis2Title = "", xAccessor = (d) => d.x, yAccessor = (d) => d.y, containerStyleOverrides = {}, plotId = "bar-plot", xAnnotations = [], barHoverTemplate = "", // Default hover template for bars
2648
2644
  barGroups = [], // Optional array of bar groups for grouping bars by color
2649
2645
  barGroupTooltipTitle = "Group", // Default title for the bar group tooltip
2650
- } = props;
2646
+ d3FormatValueString = ".1f", } = props;
2651
2647
  // Ref for plot container
2652
2648
  const containerRef = useRef(null);
2653
2649
  const plotMetaRef = useRef(null);
2650
+ // Pixel offsets from the x-axis line for the bar group shapes.
2651
+ // 45px clears tick marks + labels + axis title; the 15px band sits below all of them.
2652
+ const BAR_GROUP_SHAPE_TOP_PX = 8;
2653
+ const BAR_GROUP_SHAPE_HEIGHT_PX = 20;
2654
+ // px above the top of the plot area — lands between x2 tick labels (~15px) and x2 title (~35px)
2655
+ const X_ANNOTATION_TOP_PX = 22;
2656
+ // Tracks the rendered plot area height so we can convert the shape's fixed pixel
2657
+ // center back into a paper y coordinate for the annotation.
2658
+ const [plotAreaHeight, setPlotAreaHeight] = useState(300);
2654
2659
  const [barGroupTooltip, setBarGroupTooltip] = useState(null);
2655
2660
  const y2AxisPosition = 0.72; // Position of the secondary y-axis (histogram) as a fraction of the plot height
2656
2661
  const capturePlotMeta = (_figure, graphDiv) => {
2657
- var _a, _b, _c;
2662
+ var _a, _b, _c, _d, _e, _f;
2658
2663
  try {
2659
2664
  const m = (_a = graphDiv._fullLayout) === null || _a === void 0 ? void 0 : _a.margin;
2660
2665
  const r = (_c = (_b = graphDiv._fullLayout) === null || _b === void 0 ? void 0 : _b.xaxis) === null || _c === void 0 ? void 0 : _c.range;
2666
+ const h = (_d = graphDiv._fullLayout) === null || _d === void 0 ? void 0 : _d.height;
2661
2667
  if (m && r) {
2662
2668
  plotMetaRef.current = {
2663
2669
  xRange: r,
2664
2670
  margin: { l: m.l, r: m.r, t: m.t, b: m.b },
2671
+ 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,
2665
2672
  };
2673
+ if (h) {
2674
+ const newPlotH = h - m.t - m.b;
2675
+ if (newPlotH > 0)
2676
+ setPlotAreaHeight(newPlotH);
2677
+ }
2666
2678
  }
2667
2679
  }
2668
- catch (_d) { }
2680
+ catch (_g) { }
2669
2681
  };
2670
2682
  const handleBarGroupMouseMove = (e) => {
2671
2683
  var _a;
@@ -2675,18 +2687,18 @@ const BarPlot = (props) => {
2675
2687
  setBarGroupTooltip(null);
2676
2688
  return;
2677
2689
  }
2678
- const { xRange, margin } = plotMetaRef.current;
2690
+ const { xRange, margin, height: plotlyHeight } = plotMetaRef.current;
2679
2691
  const rect = containerRef.current.getBoundingClientRect();
2680
2692
  const mouseX = e.clientX - rect.left;
2681
2693
  const mouseY = e.clientY - rect.top;
2682
- const containerHeight = containerRef.current.clientHeight;
2683
2694
  const containerWidth = containerRef.current.clientWidth;
2684
2695
  const plotW = containerWidth - margin.l - margin.r;
2685
- containerHeight - margin.t - margin.b;
2686
- const plotBottom = containerHeight - margin.b;
2687
- // barGroupShapes sit at y0=-0.02, y1=-0.04 in paper coords (just below the x-axis line)
2688
- const stripTop = plotBottom - 10; // Plot bottom extends further than the x axis. Value here adjusted manually.
2689
- const stripBottom = plotBottom + 20; // approximate. Doesn't have to be perfect.
2696
+ // Use Plotly's own reported height so plotBottom is consistent with the margin values
2697
+ const plotBottom = plotlyHeight - margin.b - 35; // Subtracting and extra 35px to finally get it in the right spot.
2698
+ // Shapes use yanchor=0 (x-axis line) with negative pixel y values, which maps to
2699
+ // positive screen-y (downward). Strip bounds mirror those pixel offsets exactly.
2700
+ const stripTop = plotBottom;
2701
+ const stripBottom = plotBottom + BAR_GROUP_SHAPE_TOP_PX + BAR_GROUP_SHAPE_HEIGHT_PX + 5; // Give a little extra room on the bottom.
2690
2702
  if (mouseY < stripTop || mouseY > stripBottom) {
2691
2703
  setBarGroupTooltip(null);
2692
2704
  return;
@@ -2767,8 +2779,8 @@ const BarPlot = (props) => {
2767
2779
  xref: "paper",
2768
2780
  yref: "paper",
2769
2781
  x0: 0,
2770
- y0: y2AxisPosition - 0.3, // Position behind the primary title
2771
- x1: 0.02 + title.length * 0.015, // Approximate width based on title length
2782
+ y0: y2AxisPosition - 0.11, // Position behind the primary title
2783
+ x1: 0.02 + title.length * 0.01, // Approximate width based on title length 0.3/
2772
2784
  y1: y2AxisPosition - 0.01,
2773
2785
  fillcolor: "rgba(255, 255, 255, 0.5)",
2774
2786
  line: {
@@ -2837,8 +2849,12 @@ const BarPlot = (props) => {
2837
2849
  yref: "paper",
2838
2850
  x0: group.min,
2839
2851
  x1: group.max > xMax ? xMax + barWidth : group.max,
2840
- y0: -0.02,
2841
- y1: -0.07,
2852
+ // Anchor at the x-axis line (paper y=0) so the pixel offset is consistent
2853
+ // regardless of plot height — avoids overlap with tick labels on small screens.
2854
+ yanchor: 0,
2855
+ ysizemode: "pixel",
2856
+ y0: -BAR_GROUP_SHAPE_TOP_PX,
2857
+ y1: -28,
2842
2858
  fillcolor: group.color,
2843
2859
  line: {
2844
2860
  width: 1,
@@ -2851,7 +2867,10 @@ const BarPlot = (props) => {
2851
2867
  xref: "x",
2852
2868
  yref: "paper",
2853
2869
  x: barGroups[0].min + barDataWidth / 2,
2854
- y: -0.043,
2870
+ // Convert the pixel center of the shape band to a paper y coordinate using
2871
+ // the actual rendered plot area height so it stays inside the shape on any screen size.
2872
+ y: -18 /
2873
+ plotAreaHeight,
2855
2874
  text: barGroupTooltipTitle,
2856
2875
  showarrow: false,
2857
2876
  yanchor: "middle",
@@ -2890,13 +2909,16 @@ const BarPlot = (props) => {
2890
2909
  l: 50,
2891
2910
  r: 35, // Balance between ensuring the mean annotation doesn't get cut off and having too much margin.
2892
2911
  t: 40 + (title ? 50 : 0), // Add extra top margin if there is a title
2893
- b: 50,
2912
+ b: barGroups.length > 0
2913
+ ? BAR_GROUP_SHAPE_TOP_PX + BAR_GROUP_SHAPE_HEIGHT_PX + 15
2914
+ : 50,
2894
2915
  pad: 4,
2895
2916
  }, xaxis: {
2896
2917
  title: {
2897
2918
  text: xAxisTitle,
2898
2919
  },
2899
2920
  anchor: "y", // Anchor to primary y-axis
2921
+ hoverformat: d3FormatValueString, // Format hover values using provided d3 format string
2900
2922
  showgrid: true,
2901
2923
  zeroline: false,
2902
2924
  showline: true,
@@ -2909,12 +2931,16 @@ const BarPlot = (props) => {
2909
2931
  linewidth: 1,
2910
2932
  fixedrange: true, // Disable zooming
2911
2933
  automargin: true, // Adjust margin if tick labels rotate
2912
- ticklen: 21,
2934
+ ticklen: barGroups.length > 0
2935
+ ? BAR_GROUP_SHAPE_TOP_PX + BAR_GROUP_SHAPE_HEIGHT_PX
2936
+ : 8, // tick length in px.
2913
2937
  tickcolor: "white",
2938
+ tickformat: d3FormatValueString, // Format ticks using provided d3 format string
2914
2939
  } }, (data2 && {
2915
2940
  xaxis2: {
2916
2941
  title: {
2917
2942
  text: xAxis2Title,
2943
+ standoff: 22,
2918
2944
  },
2919
2945
  side: "top",
2920
2946
  anchor: "y2", // Anchor to secondary y-axis
@@ -2931,13 +2957,16 @@ const BarPlot = (props) => {
2931
2957
  fixedrange: true,
2932
2958
  automargin: true,
2933
2959
  matches: "x", // Match the range of xaxis to keep them synchronized
2960
+ tickformat: d3FormatValueString, // Format ticks using provided d3 format string
2934
2961
  },
2935
2962
  })), { yaxis: {
2936
2963
  title: {
2937
2964
  text: yAxisTitle !== null && yAxisTitle !== void 0 ? yAxisTitle : "Count",
2938
- standoff: 12, // Add space between title and axis
2965
+ standoff: 15, // Add space between title and axis
2939
2966
  },
2940
2967
  domain: data2 ? [0, y2AxisPosition - 0.01] : [0, 1], // Ensure primary y-axis takes full height
2968
+ hoverformat: d3FormatValueString, // Format hover values using provided d3 format string
2969
+ nticks: 7, // Limit number of ticks to prevent overcrowding with the histogram
2941
2970
  automargin: true, // Required for standoff to work properly
2942
2971
  showgrid: true,
2943
2972
  zeroline: false,
@@ -2982,7 +3011,7 @@ const BarPlot = (props) => {
2982
3011
  x0: annotation.x,
2983
3012
  x1: annotation.x,
2984
3013
  y0: 0,
2985
- y1: 1.05,
3014
+ y1: 1 + (X_ANNOTATION_TOP_PX - 4) / plotAreaHeight,
2986
3015
  xref: "x",
2987
3016
  yref: "paper",
2988
3017
  line: {
@@ -2995,7 +3024,7 @@ const BarPlot = (props) => {
2995
3024
  ...(barGroupShapeAnnotation ? [barGroupShapeAnnotation] : []),
2996
3025
  ...xAnnotations.map((annotation) => ({
2997
3026
  x: annotation.x,
2998
- y: 1.06,
3027
+ y: 1 + X_ANNOTATION_TOP_PX / plotAreaHeight,
2999
3028
  xref: "x",
3000
3029
  yref: "paper",
3001
3030
  text: annotation.text || "",