{"version":3,"file":"time-scale.cjs","sources":["../../../../components/data-view/utils/time-scale.tsx"],"sourcesContent":["import dayjs, { type Dayjs } from 'dayjs';\n\nimport type { TimelineScale } from '../data-view.types';\n\n/**\n * Average unit durations in ms. Used only to derive px density (px/ms) from\n * `unitWidth`; card and tick positions always use real timestamps, so\n * variable-length months don't distort placement.\n */\nexport const TIMELINE_UNIT_MS: Record<TimelineScale, number> = {\n  day: 86_400_000,\n  week: 7 * 86_400_000,\n  month: 30.44 * 86_400_000,\n  quarter: 3 * 30.44 * 86_400_000\n};\n\n/** Default px width of one `scale` unit when `unitWidth` is not provided. */\nexport const TIMELINE_DEFAULT_UNIT_WIDTH: Record<TimelineScale, number> = {\n  day: 20,\n  week: 56,\n  month: 96,\n  quarter: 140\n};\n\n/** Minimum px between rendered tick labels — denser ticks skip labels. */\nconst TICK_LABEL_MIN_SPACE = 28;\n\n/** Coerce a consumer-provided date (Date | epoch ms | parseable string) to ms. */\nexport function toTimestamp(value: unknown): number | null {\n  if (value == null) return null;\n  if (value instanceof Date) {\n    const time = value.getTime();\n    return Number.isNaN(time) ? null : time;\n  }\n  if (typeof value === 'number') {\n    return Number.isFinite(value) ? value : null;\n  }\n  if (typeof value === 'string') {\n    const parsed = dayjs(value);\n    return parsed.isValid() ? parsed.valueOf() : null;\n  }\n  return null;\n}\n\n/** `startOf` that also understands quarters without a dayjs plugin. */\nexport function startOfUnit(date: Dayjs, scale: TimelineScale): Dayjs {\n  if (scale === 'quarter') {\n    return date.startOf('month').subtract(date.month() % 3, 'month');\n  }\n  return date.startOf(scale);\n}\n\nexport function addUnits(date: Dayjs, scale: TimelineScale, n: number): Dayjs {\n  if (scale === 'quarter') return date.add(3 * n, 'month');\n  return date.add(n, scale);\n}\n\nexport interface TimelineTimeScale {\n  /** Domain start (ms), snapped to a unit boundary. */\n  t0: number;\n  /** Domain end (ms), snapped to a unit boundary. */\n  t1: number;\n  pxPerMs: number;\n  totalWidth: number;\n  /** Time (ms) → x offset (px) from the canvas left edge. */\n  x: (time: number) => number;\n  /** Inverse of `x` — px offset → time (ms). */\n  timeAt: (px: number) => number;\n}\n\nexport function createTimeScale(params: {\n  minTime: number;\n  maxTime: number;\n  scale: TimelineScale;\n  unitWidth: number;\n  /** Whole units of padding added on each side of the extent. */\n  padUnits?: number;\n  /**\n   * Minimum rendered width (px). The domain end extends by whole units until\n   * `totalWidth` reaches it; a domain already wider is left untouched. Lets\n   * the timeline fill its container when the data span is narrower.\n   */\n  minWidth?: number;\n}): TimelineTimeScale {\n  const { minTime, maxTime, scale, padUnits = 0, minWidth = 0 } = params;\n  // Guard: `unitWidth` is consumer-controlled. Zero would make `pxPerMs` 0\n  // (NaN geometry via the Infinity bulk-add below) and a negative value\n  // inverts the scale so the viewport-fill loop never terminates. Clamp to a\n  // 1px floor instead of hanging the render on a bad prop.\n  const unitWidth = Math.max(1, params.unitWidth);\n  const pxPerMs = unitWidth / TIMELINE_UNIT_MS[scale];\n  const t0 = addUnits(\n    startOfUnit(dayjs(Math.min(minTime, maxTime)), scale),\n    scale,\n    -padUnits\n  ).valueOf();\n  // +1 so the max instant's unit is fully inside the domain.\n  let end = addUnits(\n    startOfUnit(dayjs(Math.max(minTime, maxTime)), scale),\n    scale,\n    padUnits + 1\n  );\n  // Viewport fill: bulk-add the estimated deficit in one step, then correct\n  // for calendar drift (short months, DST days) — at most a few iterations.\n  const deficitPx = minWidth - (end.valueOf() - t0) * pxPerMs;\n  if (deficitPx > 0) {\n    end = addUnits(end, scale, Math.ceil(deficitPx / unitWidth));\n    while ((end.valueOf() - t0) * pxPerMs < minWidth) {\n      end = addUnits(end, scale, 1);\n    }\n  }\n  const t1 = end.valueOf();\n  return {\n    t0,\n    t1,\n    pxPerMs,\n    totalWidth: (t1 - t0) * pxPerMs,\n    x: time => (time - t0) * pxPerMs,\n    timeAt: px => t0 + px / pxPerMs\n  };\n}\n\nexport interface TimelineTick {\n  time: number;\n  x: number;\n  label: string;\n  /** False when labels are thinned out at dense zoom levels. */\n  showLabel: boolean;\n  /** Sequential unit index from the domain start — drives interval thinning. */\n  index: number;\n}\n\nexport interface TimelineBand {\n  time: number;\n  x: number;\n  width: number;\n  label: string;\n}\n\nfunction tickLabel(date: Dayjs, scale: TimelineScale): string {\n  switch (scale) {\n    case 'day':\n    case 'week':\n      return date.format('D');\n    case 'month':\n      return date.format('MMM');\n    case 'quarter':\n      return `Q${Math.floor(date.month() / 3) + 1}`;\n  }\n}\n\n/**\n * Generates the two-tier axis: minor ticks at `scale` granularity and major\n * bands one level up (months over day/week ticks, years over month/quarter\n * ticks). The first band — and any band starting a new year — carries the\n * year in its label (\"Jan 2025\", then \"Feb\").\n *\n * `labelEvery` labels every Nth unit, counted from the domain start. The\n * collision floor (labels never closer than `TICK_LABEL_MIN_SPACE`) still\n * applies, so a too-dense request degrades instead of overlapping.\n *\n * Cost note: this materializes one tick per `scale` unit across the whole\n * domain on every rebuild — `virtualized` culls what renders, not what gets\n * built here. Fine at the intended densities (weeks/months, a few years of\n * days); a `day` scale over a decade-wide `range` allocates ~3.6k ticks per\n * rebuild and would need windowed generation instead.\n */\nexport function buildAxis(\n  timeScale: TimelineTimeScale,\n  scale: TimelineScale,\n  unitWidth: number,\n  labelEvery?: number\n): { ticks: TimelineTick[]; bands: TimelineBand[] } {\n  const autoLabelEvery = Math.max(\n    1,\n    Math.ceil(TICK_LABEL_MIN_SPACE / Math.max(1, unitWidth))\n  );\n  const effectiveLabelEvery = Math.max(\n    autoLabelEvery,\n    Math.floor(labelEvery ?? 1)\n  );\n\n  const ticks: TimelineTick[] = [];\n  let cursor = startOfUnit(dayjs(timeScale.t0), scale);\n  if (cursor.valueOf() < timeScale.t0) cursor = addUnits(cursor, scale, 1);\n  let index = 0;\n  while (cursor.valueOf() <= timeScale.t1) {\n    ticks.push({\n      time: cursor.valueOf(),\n      x: timeScale.x(cursor.valueOf()),\n      label: tickLabel(cursor, scale),\n      showLabel: index % effectiveLabelEvery === 0,\n      index\n    });\n    cursor = addUnits(cursor, scale, 1);\n    index++;\n  }\n\n  const bands: TimelineBand[] = [];\n  const bandUnit = scale === 'day' || scale === 'week' ? 'month' : 'year';\n  let band = dayjs(timeScale.t0).startOf(bandUnit);\n  let isFirst = true;\n  while (band.valueOf() < timeScale.t1) {\n    const next = band.add(1, bandUnit);\n    const from = Math.max(band.valueOf(), timeScale.t0);\n    const to = Math.min(next.valueOf(), timeScale.t1);\n    const label =\n      bandUnit === 'month'\n        ? isFirst || band.month() === 0\n          ? band.format('MMM YYYY')\n          : band.format('MMM')\n        : band.format('YYYY');\n    bands.push({\n      time: band.valueOf(),\n      x: timeScale.x(from),\n      width: (to - from) * timeScale.pxPerMs,\n      label\n    });\n    isFirst = false;\n    band = next;\n  }\n\n  return { ticks, bands };\n}\n"],"names":[],"mappings":"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"}