{"version":3,"file":"utils.mjs","names":[],"sources":["../../src/BenchmarkScatterChart/utils.ts"],"sourcesContent":["export type ScaleType = 'linear' | 'log';\n\nexport interface Scale {\n  domain: [number, number];\n  map: (value: number) => number;\n  ticks: number[];\n}\n\nconst niceStep = (range: number, count: number) => {\n  if (range <= 0 || !Number.isFinite(range)) return 1;\n  const raw = range / count;\n  const magnitude = 10 ** Math.floor(Math.log10(raw));\n  const normalized = raw / magnitude;\n  const base = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;\n  return base * magnitude;\n};\n\nexport const getLinearTicks = (min: number, max: number, count = 5) => {\n  const step = niceStep(max - min, count);\n  const ticks: number[] = [];\n  const start = Math.ceil(min / step) * step;\n  for (let value = start; value <= max + step / 1e6; value += step) {\n    ticks.push(Number(value.toPrecision(12)));\n  }\n  return ticks;\n};\n\nexport const getLogTicks = (min: number, max: number) => {\n  const ticks: number[] = [];\n  const from = Math.floor(Math.log10(min));\n  const to = Math.ceil(Math.log10(max));\n  for (let exp = from; exp <= to; exp++) {\n    for (const base of [1, 2, 5]) {\n      const value = Number((base * 10 ** exp).toPrecision(12));\n      if (value >= min && value <= max) ticks.push(value);\n    }\n  }\n  return ticks;\n};\n\nexport const createScale = ({\n  values,\n  type = 'linear',\n  range,\n  min,\n  max,\n  padding = 0.08,\n  tickCount = 5,\n}: {\n  max?: number;\n  min?: number;\n  padding?: number;\n  range: [number, number];\n  tickCount?: number;\n  type?: ScaleType;\n  values: number[];\n}): Scale => {\n  const isLog = type === 'log';\n  const valid = values.filter((v) => Number.isFinite(v) && (!isLog || v > 0));\n  const toSpace = (v: number) => (isLog ? Math.log10(v) : v);\n  const fromSpace = (v: number) => (isLog ? 10 ** v : v);\n\n  let lo = valid.length ? Math.min(...valid.map(toSpace)) : 0;\n  let hi = valid.length ? Math.max(...valid.map(toSpace)) : 1;\n  if (lo === hi) {\n    const delta = lo === 0 ? 1 : Math.abs(lo) * 0.1;\n    lo -= delta;\n    hi += delta;\n  }\n  const pad = (hi - lo) * padding;\n  lo = typeof min === 'number' && (!isLog || min > 0) ? toSpace(min) : lo - pad;\n  hi = typeof max === 'number' && (!isLog || max > 0) ? toSpace(max) : hi + pad;\n\n  const domain: [number, number] = [fromSpace(lo), fromSpace(hi)];\n  const span = hi - lo || 1;\n  const map = (value: number) =>\n    range[0] +\n    ((toSpace(Math.max(value, isLog ? domain[0] : -Infinity)) - lo) / span) * (range[1] - range[0]);\n\n  let ticks = isLog ? getLogTicks(domain[0], domain[1]) : getLinearTicks(...domain, tickCount);\n  if (isLog && ticks.length < 2) {\n    ticks = getLinearTicks(lo, hi, tickCount).map((v) => Number(fromSpace(v).toPrecision(3)));\n  }\n\n  return { domain, map, ticks };\n};\n\n/**\n * Pareto frontier: points not dominated by any other point.\n * `xDirection = 'lower'` treats smaller X as better (e.g. cost / latency).\n * Returns keys ordered by ascending X.\n */\nexport const getParetoFrontier = <T extends { key: string | number; x: number; y: number }>(\n  points: T[],\n  xDirection: 'lower' | 'higher' = 'lower',\n): T[] => {\n  const sign = xDirection === 'lower' ? 1 : -1;\n  const sorted = [...points]\n    .filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y))\n    .sort((a, b) => sign * (a.x - b.x) || b.y - a.y);\n\n  const frontier: T[] = [];\n  let bestY = -Infinity;\n  for (const point of sorted) {\n    if (point.y > bestY) {\n      frontier.push(point);\n      bestY = point.y;\n    }\n  }\n  return frontier.sort((a, b) => a.x - b.x);\n};\n\nexport type LabelPlacement =\n  'right' | 'left' | 'top' | 'bottom' | 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';\n\nexport interface Box {\n  height: number;\n  width: number;\n  x: number;\n  y: number;\n}\n\nexport interface LabelCandidate {\n  height: number;\n  key: string | number;\n  px: number;\n  py: number;\n  radius: number;\n  width: number;\n}\n\nexport interface PlacedLabel extends Box {\n  key: string | number;\n  placement: LabelPlacement;\n}\n\n// Ordered by preference; `shift` nudges side labels vertically (in label heights)\nconst PLACEMENTS: Array<{ placement: LabelPlacement; shift?: number }> = [\n  { placement: 'right' },\n  { placement: 'left' },\n  { placement: 'right', shift: -0.5 },\n  { placement: 'right', shift: 0.5 },\n  { placement: 'left', shift: -0.5 },\n  { placement: 'left', shift: 0.5 },\n  { placement: 'top-right' },\n  { placement: 'bottom-right' },\n  { placement: 'top-left' },\n  { placement: 'bottom-left' },\n  { placement: 'top' },\n  { placement: 'bottom' },\n];\n\nconst getLabelBox = (\n  { px, py, radius, width, height }: LabelCandidate,\n  placement: LabelPlacement,\n  gap: number,\n): Box => {\n  const offset = radius + gap;\n  const diagonal = radius * 0.7 + gap;\n  switch (placement) {\n    case 'right': {\n      return { height, width, x: px + offset, y: py - height / 2 };\n    }\n    case 'left': {\n      return { height, width, x: px - offset - width, y: py - height / 2 };\n    }\n    case 'top': {\n      return { height, width, x: px - width / 2, y: py - offset - height };\n    }\n    case 'bottom': {\n      return { height, width, x: px - width / 2, y: py + offset };\n    }\n    case 'top-right': {\n      return { height, width, x: px + diagonal, y: py - diagonal - height };\n    }\n    case 'top-left': {\n      return { height, width, x: px - diagonal - width, y: py - diagonal - height };\n    }\n    case 'bottom-right': {\n      return { height, width, x: px + diagonal, y: py + diagonal };\n    }\n    case 'bottom-left': {\n      return { height, width, x: px - diagonal - width, y: py + diagonal };\n    }\n  }\n};\n\nconst overlapArea = (a: Box, b: Box) => {\n  const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);\n  const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);\n  return w > 0 && h > 0 ? w * h : 0;\n};\n\nconst inflate = (box: Box, by: number): Box => ({\n  height: box.height + by * 2,\n  width: box.width + by * 2,\n  x: box.x - by,\n  y: box.y - by,\n});\n\nconst outOfBoundsArea = (box: Box, bounds: Box) => {\n  const inside = overlapArea(box, bounds);\n  return box.width * box.height - inside;\n};\n\n/**\n * Greedy label placement: candidates are processed in the given order,\n * each picks the position with the least overlap against markers,\n * already-placed labels and the outer bounds.\n */\nexport const placeLabels = (\n  candidates: LabelCandidate[],\n  bounds: Box,\n  { gap = 8, lines = [] }: { gap?: number; lines?: Box[] } = {},\n): Map<string | number, PlacedLabel> => {\n  const markers: Array<Box & { key: string | number }> = candidates.map((c) => ({\n    height: c.radius * 2,\n    key: c.key,\n    width: c.radius * 2,\n    x: c.px - c.radius,\n    y: c.py - c.radius,\n  }));\n\n  const placed = new Map<string | number, PlacedLabel>();\n\n  for (const candidate of candidates) {\n    let best: PlacedLabel | undefined;\n    let bestCost = Infinity;\n\n    PLACEMENTS.forEach(({ placement, shift = 0 }, order) => {\n      const base = getLabelBox(candidate, placement, gap);\n      const box = { ...base, y: base.y + shift * candidate.height };\n      let cost = order * 2;\n      cost += outOfBoundsArea(box, bounds) * 20;\n      for (const marker of markers) {\n        if (marker.key === candidate.key) continue;\n        cost += overlapArea(inflate(box, 3), marker) * 10;\n      }\n      for (const label of placed.values()) cost += overlapArea(inflate(box, 2), label) * 8;\n      for (const line of lines) cost += overlapArea(box, line) * 1.5;\n\n      if (cost < bestCost) {\n        bestCost = cost;\n        best = { ...box, key: candidate.key, placement };\n      }\n    });\n\n    if (best) placed.set(candidate.key, best);\n  }\n\n  return placed;\n};\n"],"mappings":"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"}