( series: ResolvedCandlestickSeriesConfig, data: ReadonlyArray<OHLCDataPoint>, xScale: LinearScale, plotWidthFallback?: number, )
| 57 | * - No DPR conversions are applied here. |
| 58 | */ |
| 59 | export function computeCandlestickBodyWidthRange( |
| 60 | series: ResolvedCandlestickSeriesConfig, |
| 61 | data: ReadonlyArray<OHLCDataPoint>, |
| 62 | xScale: LinearScale, |
| 63 | plotWidthFallback?: number, |
| 64 | ): number { |
| 65 | if (data.length === 0) return 0; |
| 66 | |
| 67 | const categoryStep = computeCategoryStep(data); |
| 68 | |
| 69 | // Prefer deriving category width from a domain step via xScale.scale(t0 + step) - xScale.scale(t0). |
| 70 | let categoryWidthRange = 0; |
| 71 | if (Number.isFinite(categoryStep) && categoryStep > 0) { |
| 72 | let t0: number | null = null; |
| 73 | for (let i = 0; i < data.length; i++) { |
| 74 | const t = getTimestamp(data[i]); |
| 75 | if (Number.isFinite(t)) { |
| 76 | t0 = t; |
| 77 | break; |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | if (t0 != null) { |
| 82 | const p0 = xScale.scale(t0); |
| 83 | const p1 = xScale.scale(t0 + categoryStep); |
| 84 | const w = Math.abs(p1 - p0); |
| 85 | if (Number.isFinite(w) && w > 0) categoryWidthRange = w; |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | // Fallback: approximate based on plot width and data length. |
| 90 | if (!(categoryWidthRange > 0) || !Number.isFinite(categoryWidthRange)) { |
| 91 | const plotW = Number.isFinite(plotWidthFallback ?? Number.NaN) ? (plotWidthFallback as number) : 0; |
| 92 | categoryWidthRange = plotW / Math.max(1, data.length); |
| 93 | } |
| 94 | |
| 95 | // barWidth semantics: |
| 96 | // - number: width in range units |
| 97 | // - percent string: percent of category width in range units |
| 98 | let width = 0; |
| 99 | const rawBarWidth = series.barWidth; |
| 100 | if (typeof rawBarWidth === 'number') { |
| 101 | width = Number.isFinite(rawBarWidth) ? Math.max(0, rawBarWidth) : 0; |
| 102 | } else if (typeof rawBarWidth === 'string') { |
| 103 | const p = parsePercent(rawBarWidth); |
| 104 | width = p == null ? 0 : categoryWidthRange * clamp01(p); |
| 105 | } |
| 106 | |
| 107 | // Clamp by min/max width (in CSS px; our range-space is CSS px in interaction usage). |
| 108 | const minW = Number.isFinite(series.barMinWidth) ? Math.max(0, series.barMinWidth) : 0; |
| 109 | const maxWCandidate = Number.isFinite(series.barMaxWidth) ? Math.max(0, series.barMaxWidth) : Number.POSITIVE_INFINITY; |
| 110 | const maxW = Math.max(minW, maxWCandidate); |
| 111 | width = Math.min(Math.max(width, minW), maxW); |
| 112 | |
| 113 | return Number.isFinite(width) ? width : 0; |
| 114 | } |
| 115 | |
| 116 | const monotonicTimestampCache = new WeakMap<ReadonlyArray<OHLCDataPoint>, boolean>(); |
no test coverage detected