td-plots 1.10.1 → 1.10.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.
@@ -1,4 +1,11 @@
1
1
  import React from "react";
2
+ export interface BarGroup {
3
+ name: string;
4
+ label?: string;
5
+ min: number;
6
+ max: number;
7
+ color: string;
8
+ }
2
9
  export interface BarPlotProps {
3
10
  data: {
4
11
  x: number;
@@ -6,6 +13,8 @@ export interface BarPlotProps {
6
13
  }[];
7
14
  data2?: number[];
8
15
  data2Selected?: [number, number];
16
+ data2SelectedColor?: string;
17
+ barDataWidth?: number;
9
18
  barWidth?: number;
10
19
  barColor?: string;
11
20
  barColor2?: string;
@@ -27,6 +36,8 @@ export interface BarPlotProps {
27
36
  color?: string;
28
37
  }[];
29
38
  barHoverTemplate?: string;
39
+ barGroups?: BarGroup[];
40
+ barGroupTooltipTitle?: string;
30
41
  }
31
42
  declare const BarPlot: (props: BarPlotProps) => import("react/jsx-runtime").JSX.Element;
32
43
  export default BarPlot;
@@ -3,8 +3,15 @@ export type BoxProps = {
3
3
  boxStyle: "filled" | "outlined" | "shaded";
4
4
  };
5
5
  export type LegendItemProps = {
6
- label: string;
6
+ label: string | React.ReactNode;
7
7
  color: string;
8
+ width?: number;
9
+ height?: number;
10
+ styleOverrides?: React.CSSProperties;
11
+ };
12
+ export type LegendBoxPlotItemProps = LegendItemProps & {
8
13
  boxStyle: "filled" | "outlined" | "shaded";
9
14
  };
10
- export declare const LegendBoxPlotItem: (props: LegendItemProps) => import("react/jsx-runtime").JSX.Element;
15
+ export declare const LegendBoxPlotItem: (props: LegendBoxPlotItemProps) => import("react/jsx-runtime").JSX.Element;
16
+ export declare const LegendColorItem: (props: LegendItemProps) => import("react/jsx-runtime").JSX.Element;
17
+ export declare const LegendLineItem: (props: LegendItemProps) => import("react/jsx-runtime").JSX.Element;
package/dist/index.d.ts CHANGED
@@ -16,5 +16,7 @@ export { default as LineWithHistogram } from "./components/LineWithHistogram";
16
16
  export type { LineWithHistogramProps } from "./components/LineWithHistogram";
17
17
  export { default as BarPlot } from "./components/BarPlot";
18
18
  export type { BarPlotProps } from "./components/BarPlot";
19
+ export { LegendColorItem, LegendLineItem } from "./components/LegendUtils";
20
+ export type { LegendItemProps } from "./components/LegendUtils";
19
21
  export { isDateArray, isNumberArray } from "./components/Utils";
20
22
  export type { PlotParams } from "react-plotly.js";
package/dist/index.esm.js CHANGED
@@ -1580,6 +1580,7 @@ function isBoxPlotDataSummary(value) {
1580
1580
  (value.mean === undefined || typeof value.mean === "number"));
1581
1581
  }
1582
1582
 
1583
+ const BOX_SIZE = 15;
1583
1584
  const SVGBoxWithLine = (props) => {
1584
1585
  const boxWidth = 35;
1585
1586
  const boxHeight = 15;
@@ -1588,12 +1589,45 @@ const SVGBoxWithLine = (props) => {
1588
1589
  return (jsxs("svg", { width: lineWidth, height: boxHeight, viewBox: `0 0 ${lineWidth + 3} ${boxHeight + 3}`, children: [jsx("line", { x1: 0, y1: lineY, x2: (lineWidth - boxWidth) / 2, y2: lineY, stroke: props.color, strokeWidth: "2" }), jsx("line", { x1: (lineWidth + boxWidth) / 2, y1: lineY, x2: lineWidth, y2: lineY, stroke: props.color, strokeWidth: "2" }), jsx("line", { x1: 0, y1: lineY - 5, x2: 0, y2: lineY + 5, stroke: props.color, strokeWidth: "2" }), jsx("line", { x1: lineWidth, y1: lineY - 5, x2: lineWidth, y2: lineY + 5, stroke: props.color, strokeWidth: "2" }), jsx("rect", { x: (lineWidth - boxWidth) / 2, y: "0", width: boxWidth, height: boxHeight, fill: props.boxStyle !== "outlined" ? props.color : "none", opacity: props.boxStyle === "shaded" ? 0.3 : 1 }), jsx("rect", { x: (lineWidth - boxWidth) / 2, y: "0", width: boxWidth, height: boxHeight, stroke: props.color, strokeWidth: "2", fill: "none" }), jsx("line", { x1: (lineWidth - boxWidth) / 2 + boxWidth * 0.5, y1: 0, x2: (lineWidth - boxWidth) / 2 + boxWidth * 0.5, y2: boxHeight, stroke: props.color, strokeWidth: "2" })] }));
1589
1590
  };
1590
1591
  const LegendBoxPlotItem = (props) => {
1592
+ const labelKey = props.label == null
1593
+ ? ""
1594
+ : typeof props.label === "string"
1595
+ ? props.label
1596
+ : props.label.toString();
1591
1597
  return (jsxs(Box, { sx: {
1592
1598
  display: "flex",
1593
1599
  alignItems: "center",
1594
1600
  mr: 4,
1595
1601
  fontFamily: "Open Sans, verdana, arial, sans-serif",
1596
- }, children: [jsx(SVGBoxWithLine, { color: props.color, boxStyle: props.boxStyle }), props.label] }, props.label));
1602
+ }, children: [jsx(SVGBoxWithLine, { color: props.color, boxStyle: props.boxStyle }), props.label] }, labelKey));
1603
+ };
1604
+ const LegendColorItem = (props) => {
1605
+ const labelKey = props.label == null
1606
+ ? ""
1607
+ : typeof props.label === "string"
1608
+ ? props.label
1609
+ : props.label.toString();
1610
+ return (jsxs(Box, { sx: {
1611
+ display: "flex",
1612
+ alignItems: "center",
1613
+ mr: 4,
1614
+ fontFamily: "Open Sans, verdana, arial, sans-serif",
1615
+ }, children: [jsx(Box, { sx: Object.assign({ width: props.width || BOX_SIZE, height: props.height || BOX_SIZE, backgroundColor: props.color, marginRight: 1 }, props.styleOverrides) }), props.label] }, labelKey));
1616
+ };
1617
+ const LegendLineItem = (props) => {
1618
+ const lineWidth = props.width || 40;
1619
+ const lineHeight = props.height || 4;
1620
+ const labelKey = props.label == null
1621
+ ? ""
1622
+ : typeof props.label === "string"
1623
+ ? props.label
1624
+ : props.label.toString();
1625
+ return (jsxs(Box, { sx: {
1626
+ display: "flex",
1627
+ alignItems: "center",
1628
+ mr: 4,
1629
+ fontFamily: "Open Sans, verdana, arial, sans-serif",
1630
+ }, 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));
1597
1631
  };
1598
1632
 
1599
1633
  // This component takes grouped data and transforms it for displaying with the BoxPlot component.
@@ -2322,30 +2356,88 @@ const LineWithHistogram = (props) => {
2322
2356
 
2323
2357
  const Plot = lazy(() => Promise.resolve().then(function () { return reactPlotlyWrapper$1; }));
2324
2358
  const BarPlot = (props) => {
2325
- const { data, data2 = null, data2Selected = [], barWidth = 0.8, barColor = "rgb(205, 26, 154)", // Default bar color (red)
2359
+ var _a, _b;
2360
+ const { data, data2 = null, data2Selected = [], barDataWidth = 2, barWidth = 0.8, barColor = "rgb(205, 26, 154)", // Default bar color (red)
2326
2361
  barColor2 = "rgba(100, 200, 255, 0.7)", // Default histogram bar color (blue)
2362
+ data2SelectedColor = "rgba(255, 0, 0, 0.5)", // Default color for the histogram selection box (red)
2327
2363
  width = 600, height = 400, title = "", title2 = "", // Default title for histogram subplot
2328
2364
  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
2365
+ barGroups = [], // Optional array of bar groups for grouping bars by color
2366
+ barGroupTooltipTitle = "Group", // Default title for the bar group tooltip
2329
2367
  } = props;
2330
2368
  // Ref for plot container
2331
2369
  const containerRef = useRef(null);
2370
+ const plotMetaRef = useRef(null);
2371
+ const [barGroupTooltip, setBarGroupTooltip] = useState(null);
2332
2372
  const y2AxisPosition = 0.72; // Position of the secondary y-axis (histogram) as a fraction of the plot height
2373
+ const capturePlotMeta = (_figure, graphDiv) => {
2374
+ var _a, _b, _c;
2375
+ try {
2376
+ const m = (_a = graphDiv._fullLayout) === null || _a === void 0 ? void 0 : _a.margin;
2377
+ const r = (_c = (_b = graphDiv._fullLayout) === null || _b === void 0 ? void 0 : _b.xaxis) === null || _c === void 0 ? void 0 : _c.range;
2378
+ if (m && r) {
2379
+ plotMetaRef.current = {
2380
+ xRange: r,
2381
+ margin: { l: m.l, r: m.r, t: m.t, b: m.b },
2382
+ };
2383
+ }
2384
+ }
2385
+ catch (_d) { }
2386
+ };
2387
+ const handleBarGroupMouseMove = (e) => {
2388
+ var _a;
2389
+ if (!containerRef.current ||
2390
+ barGroups.length === 0 ||
2391
+ !plotMetaRef.current) {
2392
+ setBarGroupTooltip(null);
2393
+ return;
2394
+ }
2395
+ const { xRange, margin } = plotMetaRef.current;
2396
+ const rect = containerRef.current.getBoundingClientRect();
2397
+ const mouseX = e.clientX - rect.left;
2398
+ const mouseY = e.clientY - rect.top;
2399
+ const containerHeight = containerRef.current.clientHeight;
2400
+ const containerWidth = containerRef.current.clientWidth;
2401
+ const plotW = containerWidth - margin.l - margin.r;
2402
+ containerHeight - margin.t - margin.b;
2403
+ const plotBottom = containerHeight - margin.b;
2404
+ // barGroupShapes sit at y0=-0.02, y1=-0.04 in paper coords (just below the x-axis line)
2405
+ const stripTop = plotBottom - 10; // Plot bottom extends further than the x axis. Value here adjusted manually.
2406
+ const stripBottom = plotBottom + 20; // approximate. Doesn't have to be perfect.
2407
+ if (mouseY < stripTop || mouseY > stripBottom) {
2408
+ setBarGroupTooltip(null);
2409
+ return;
2410
+ }
2411
+ const dataX = xRange[0] + ((mouseX - margin.l) / plotW) * (xRange[1] - xRange[0]);
2412
+ const hovered = barGroups.find((g) => dataX >= g.min && dataX <= g.max);
2413
+ setBarGroupTooltip(hovered
2414
+ ? {
2415
+ x: mouseX,
2416
+ y: plotBottom,
2417
+ name: hovered.name,
2418
+ label: (_a = hovered.label) !== null && _a !== void 0 ? _a : hovered.name,
2419
+ color: hovered.color,
2420
+ min: hovered.min,
2421
+ max: hovered.max,
2422
+ }
2423
+ : null);
2424
+ };
2333
2425
  const plotlyData = [
2334
2426
  {
2335
2427
  x: data.map((point) => xAccessor(point)),
2336
2428
  y: data.map((point) => yAccessor(point)),
2337
- // customdata: data.map((point) => [xAccessor(point) + 2]),
2429
+ customdata: data.map((point) => [xAccessor(point) + barDataWidth]),
2338
2430
  yaxis: "y",
2339
2431
  type: "bar",
2340
2432
  marker: {
2341
- color: barColor, // Use the provided bar color
2433
+ color: "white", // Use the provided bar color
2342
2434
  line: {
2343
- color: "white",
2344
- width: 0.5,
2435
+ color: barColor,
2436
+ width: 2,
2345
2437
  },
2346
2438
  },
2347
2439
  width: barWidth, // width centered on the x value.
2348
- // hovertemplate: barHoverTemplate || "%{y}", // Use provided hover template or default to showing y value
2440
+ hovertemplate: barHoverTemplate || "%{y}", // Use provided hover template or default to showing y value
2349
2441
  },
2350
2442
  ];
2351
2443
  const histogramSubplotData = data2
@@ -2362,11 +2454,10 @@ const BarPlot = (props) => {
2362
2454
  },
2363
2455
  xaxis: "x2", // Use secondary x-axis for histogram
2364
2456
  yaxis: "y2", // Place histogram on secondary y-axis
2457
+ hovertemplate: "[%{x})<br>Count: %{y}<extra></extra>", // Custom hover text
2365
2458
  },
2366
2459
  ]
2367
2460
  : [];
2368
- // Create a multiply-like effect by using a semi-transparent dark overlay
2369
- const multiplyColor = "rgba(29, 104, 185, 0.1)";
2370
2461
  const selectedHistogramShape = data2Selected.length === 2
2371
2462
  ? [
2372
2463
  {
@@ -2376,10 +2467,10 @@ const BarPlot = (props) => {
2376
2467
  y0: y2AxisPosition, // Start at the top of the histogram subplot
2377
2468
  y1: 1,
2378
2469
  yref: "paper",
2379
- fillcolor: multiplyColor,
2380
2470
  line: {
2381
- width: 1,
2382
- color: multiplyColor,
2471
+ width: 2,
2472
+ color: data2SelectedColor,
2473
+ dash: "dot",
2383
2474
  },
2384
2475
  layer: "above", // Ensure the selection box is above the bars
2385
2476
  },
@@ -2455,6 +2546,59 @@ const BarPlot = (props) => {
2455
2546
  },
2456
2547
  ]
2457
2548
  : [];
2549
+ const xMax = data.length > 0 ? Math.max(...data.map((d) => xAccessor(d))) : 0;
2550
+ const barGroupShapes = barGroups.flatMap((group) => [
2551
+ {
2552
+ type: "rect",
2553
+ xref: "x",
2554
+ yref: "paper",
2555
+ x0: group.min,
2556
+ x1: group.max > xMax ? xMax + barWidth : group.max,
2557
+ y0: -0.02,
2558
+ y1: -0.07,
2559
+ fillcolor: group.color,
2560
+ line: {
2561
+ width: 1,
2562
+ color: "white",
2563
+ },
2564
+ },
2565
+ ]);
2566
+ const barGroupShapeAnnotation = barGroups.length > 0
2567
+ ? {
2568
+ xref: "x",
2569
+ yref: "paper",
2570
+ x: barGroups[0].min + barDataWidth / 2,
2571
+ y: -0.043,
2572
+ text: barGroupTooltipTitle,
2573
+ showarrow: false,
2574
+ yanchor: "middle",
2575
+ xanchor: "left",
2576
+ font: {
2577
+ size: 14,
2578
+ color: "#fff",
2579
+ weight: 500,
2580
+ },
2581
+ }
2582
+ : null;
2583
+ const barGroupHighlightBox = barGroupTooltip
2584
+ ? [
2585
+ {
2586
+ type: "rect",
2587
+ xref: "x",
2588
+ yref: "paper",
2589
+ x0: barGroupTooltip.min,
2590
+ x1: barGroupTooltip.max > xMax ? xMax + barWidth : barGroupTooltip.max,
2591
+ y0: 0,
2592
+ y1: data2 ? y2AxisPosition - 0.02 : 1,
2593
+ fillcolor: colorToRGBA(barGroupTooltip.color, 0.2),
2594
+ line: {
2595
+ width: 2,
2596
+ color: barGroupTooltip.color,
2597
+ },
2598
+ layer: "above",
2599
+ },
2600
+ ]
2601
+ : [];
2458
2602
  const layout = Object.assign(Object.assign(Object.assign(Object.assign({ title: data2
2459
2603
  ? undefined
2460
2604
  : {
@@ -2481,8 +2625,9 @@ const BarPlot = (props) => {
2481
2625
  linecolor: "#bababa",
2482
2626
  linewidth: 1,
2483
2627
  fixedrange: true, // Disable zooming
2484
- // tickformat: isDateArray(data) ? dateTickFormat : d3FormatValueString, // Format ticks for dates
2485
2628
  automargin: true, // Adjust margin if tick labels rotate
2629
+ ticklen: 21,
2630
+ tickcolor: "white",
2486
2631
  } }, (data2 && {
2487
2632
  xaxis2: {
2488
2633
  title: {
@@ -2547,12 +2692,14 @@ const BarPlot = (props) => {
2547
2692
  })), { bargap: 0.03, shapes: [
2548
2693
  ...titleBackgroundShapes,
2549
2694
  ...selectedHistogramShape,
2695
+ ...barGroupShapes,
2696
+ ...barGroupHighlightBox,
2550
2697
  ...xAnnotations.map((annotation) => ({
2551
2698
  type: "line",
2552
2699
  x0: annotation.x,
2553
2700
  x1: annotation.x,
2554
2701
  y0: 0,
2555
- y1: 1.1,
2702
+ y1: 1.05,
2556
2703
  xref: "x",
2557
2704
  yref: "paper",
2558
2705
  line: {
@@ -2562,9 +2709,10 @@ const BarPlot = (props) => {
2562
2709
  })),
2563
2710
  ], annotations: [
2564
2711
  ...titleAnnotations,
2712
+ ...(barGroupShapeAnnotation ? [barGroupShapeAnnotation] : []),
2565
2713
  ...xAnnotations.map((annotation) => ({
2566
2714
  x: annotation.x,
2567
- y: 1.11,
2715
+ y: 1.06,
2568
2716
  xref: "x",
2569
2717
  yref: "paper",
2570
2718
  text: annotation.text || "",
@@ -2576,7 +2724,6 @@ const BarPlot = (props) => {
2576
2724
  },
2577
2725
  })),
2578
2726
  ] });
2579
- console.log("BarPlot layout:", layout);
2580
2727
  const config = {
2581
2728
  responsive: true, // Make the plot responsive
2582
2729
  displayModeBar: false, // Hide the mode bar
@@ -2585,20 +2732,39 @@ const BarPlot = (props) => {
2585
2732
  staticPlot: false, // Enable interactivity
2586
2733
  };
2587
2734
  const containerStyles = Object.assign({ width: "100%", height: "100%", position: "relative" }, containerStyleOverrides);
2588
- console.log([...plotlyData, ...histogramSubplotData]);
2589
- return (jsx("div", { ref: containerRef, className: `plot-container histogram ${plotId}`, style: Object.assign({}, containerStyles), children: jsx(Suspense, { fallback: jsx(Loading, {}), children: jsx("div", { style: {
2590
- position: "relative",
2591
- width: "100%",
2592
- height: "100%",
2593
- }, children: jsx(Plot, { data: [...plotlyData, ...histogramSubplotData],
2594
- // Type doesn't contain "mirror" prop but it works.
2595
- //@ts-ignore
2596
- layout: layout, config: config, useResizeHandler: true, style: {
2735
+ return (jsxs("div", { ref: containerRef, className: `plot-container histogram ${plotId}`, style: Object.assign(Object.assign({}, containerStyles), { cursor: barGroupTooltip ? "pointer" : "default" }), onMouseMove: handleBarGroupMouseMove, onMouseLeave: () => setBarGroupTooltip(null), children: [jsx(Suspense, { fallback: jsx(Loading, {}), children: jsx("div", { style: {
2736
+ position: "relative",
2597
2737
  width: "100%",
2598
2738
  height: "100%",
2599
- display: "block",
2600
- transition: "opacity 0.15s ease-in-out",
2601
- } }, `bar-${plotId || "default"}`) }) }) }));
2739
+ }, children: jsx(Plot, { data: [...plotlyData, ...histogramSubplotData],
2740
+ // Type doesn't contain "mirror" prop but it works.
2741
+ //@ts-ignore
2742
+ layout: layout, config: config, useResizeHandler: true, style: {
2743
+ width: "100%",
2744
+ height: "100%",
2745
+ display: "block",
2746
+ transition: "opacity 0.15s ease-in-out",
2747
+ }, onInitialized: capturePlotMeta, onUpdate: capturePlotMeta }, `bar-${plotId || "default"}`) }) }), barGroupTooltip && (jsxs("div", { style: {
2748
+ position: "absolute",
2749
+ left: barGroupTooltip.x + 250 >
2750
+ ((_b = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth) !== null && _b !== void 0 ? _b : Infinity)
2751
+ ? barGroupTooltip.x - 250
2752
+ : barGroupTooltip.x + 12,
2753
+ top: barGroupTooltip.y + 10,
2754
+ backgroundColor: "rgba(255, 255, 255, 0.9)",
2755
+ borderRadius: "4px",
2756
+ color: "#333",
2757
+ padding: "8px 12px",
2758
+ fontSize: "13px",
2759
+ lineHeight: "1.5",
2760
+ pointerEvents: "none",
2761
+ zIndex: 1000,
2762
+ whiteSpace: "nowrap",
2763
+ boxShadow: "0 2px 4px rgba(0,0,0,0.2)",
2764
+ borderLeft: `7px solid ${barGroupTooltip.color}`,
2765
+ fontFamily: '"Open Sans", verdana, arial, sans-serif',
2766
+ maxWidth: "250px",
2767
+ }, children: [barGroupTooltipTitle, ": ", jsx("b", { children: barGroupTooltip.label })] }))] }));
2602
2768
  };
2603
2769
 
2604
2770
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@@ -100082,5 +100248,5 @@ var reactPlotlyWrapper$1 = /*#__PURE__*/Object.freeze({
100082
100248
  default: reactPlotlyWrapper
100083
100249
  });
100084
100250
 
100085
- export { BarPlot, BoxPlot, HistogramPlot, LineWithHistogram, PairedComparisonsBoxPlot, RadialHistogramPlot, StatsDonut, SummaryComparisonPlot, SummaryComparisonPlotLegend, isDateArray, isNumberArray };
100251
+ export { BarPlot, BoxPlot, HistogramPlot, LegendColorItem, LegendLineItem, LineWithHistogram, PairedComparisonsBoxPlot, RadialHistogramPlot, StatsDonut, SummaryComparisonPlot, SummaryComparisonPlotLegend, isDateArray, isNumberArray };
100086
100252
  //# sourceMappingURL=index.esm.js.map