{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bar-chart",
  "type": "registry:component",
  "title": "Bar chart",
  "description": "Compares a value across categories with bars from a zero baseline.",
  "dependencies": [
    "insightcharts",
    "echarts"
  ],
  "files": [
    {
      "path": "registry/insightcharts/atoms/bar-chart.tsx",
      "type": "registry:component",
      "target": "components/insightcharts/atoms/bar-chart.tsx",
      "content": "\"use client\";\n\nimport { BarChart as EBar } from \"echarts/charts\";\nimport { alpha, inkOn, type Annotation, defineChart, type BuildContext, isTemporal, type LegendItem, measureText, truncateText, wrapText, categoryAxis, hiddenAxis, labelStyle, morph, tooltipHtml, valueAxis, describeExtremes, shapeSeries } from \"insightcharts/core\";\nimport { fitAnnotations } from \"@/components/insightcharts/atoms/_annotations\";\n\nexport interface BarChartProps {\n  /** Category field (what each bar is). */\n  x: string;\n  /** Value field, or several for grouped / stacked bars. */\n  y: string | string[];\n  /** Field that splits rows into series, for long-format data. */\n  series?: string;\n  /** `\"auto\"` draws horizontal bars for rankings and long labels, vertical for time. */\n  orientation?: \"auto\" | \"horizontal\" | \"vertical\";\n  /** Order of the bars. Defaults to descending for rankings, data order for time. */\n  sort?: \"none\" | \"asc\" | \"desc\";\n  /** Stack series. `\"percent\"` normalises each bar to 100%. */\n  stack?: boolean | \"percent\";\n  /** Print the value at the end of each bar. Default: on for a single series of up to 12 bars. */\n  labels?: boolean;\n  /** Display names for series keys. */\n  names?: Record<string, string>;\n}\n\n/**\n * Annotations are written against the props: `x` is the category, `y` the value. Core maps\n * them to the screen axes, which is wrong once the chart is drawn sideways, so for horizontal\n * layouts the marks are rebuilt here with the axes swapped and core's own copies are sent to\n * an empty series on an off-canvas grid. Belongs in core/annotations.ts; remove when it moves.\n */\nfunction transposeAnnotations(option: Record<string, any>, notes: Annotation[], ctx: BuildContext): Record<string, any> {\n  const t = ctx.tokens;\n  const text = { color: t.inkSecondary, fontSize: t.fontSize, fontFamily: t.fontFamily, lineHeight: Math.round(t.fontSize * 1.35) };\n  const key = (axis: \"x\" | \"y\") => (axis === \"x\" ? \"yAxis\" : \"xAxis\");\n  const lines = notes.flatMap((a) => (a.type === \"line\" ? [{ [key(a.axis)]: a.value, lineStyle: { color: a.emphasis ? t.ink : t.muted, width: 1, type: a.dashed === false ? \"solid\" : \"dashed\" }, label: { show: !!a.label, formatter: a.label ?? \"\", position: a.axis === \"y\" ? \"start\" : \"insideEndTop\", ...text } }] : []));\n  const bands = notes.flatMap((a) => (a.type === \"band\" ? [[{ [key(a.axis)]: a.from, name: a.label ?? \"\", label: { show: !!a.label, position: a.axis === \"y\" ? \"insideTop\" : \"insideTopLeft\", ...text } }, { [key(a.axis)]: a.to }]] : []));\n  const points = notes.flatMap((a) => (a.type === \"point\" ? [{ coord: [a.y, a.x], symbol: \"circle\", symbolSize: a.dot === false ? 1 : 9, itemStyle: a.dot === false ? { color: \"transparent\", borderWidth: 0 } : { color: t.surface, borderColor: t.ink, borderWidth: 1.5 }, label: { show: true, formatter: a.label, position: a.position ?? \"top\", distance: 8, ...text, color: t.ink, textBorderColor: t.surface, textBorderWidth: 3 } }] : []));\n  const [first, ...rest] = option.series as Record<string, any>[];\n  const marked = {\n    ...first,\n    ...(lines.length ? { markLine: { silent: true, symbol: \"none\", animation: ctx.animate, data: [...(first.markLine?.data ?? []), ...lines] } } : null),\n    ...(bands.length ? { markArea: { silent: true, z: 0, itemStyle: { color: alpha(t.muted, 0.12) }, data: bands } } : null),\n    ...(points.length ? { markPoint: { silent: true, animation: ctx.animate, data: points } } : null),\n  };\n  // A vertical line is labelled above the plot, which needs headroom.\n  const headroom = notes.some((a) => a.type === \"line\" && a.axis === \"y\" && a.label) ? 18 : 0;\n  const sink = { id: \"annotations-sink\", type: \"bar\", xAxisIndex: 1, yAxisIndex: 1, silent: true, tooltip: { show: false }, data: [] };\n  return {\n    ...option,\n    grid: [{ left: 4, bottom: 4, outerBoundsMode: \"same\", outerBoundsContain: \"axisLabel\", ...option.grid, top: (option.grid?.top ?? 12) + headroom }, { left: -2000, top: -2000, width: 1, height: 1, outerBoundsMode: \"none\" }],\n    xAxis: [option.xAxis, hiddenAxis(\"value\", { gridIndex: 1 })],\n    yAxis: [option.yAxis, hiddenAxis(\"value\", { gridIndex: 1 })],\n    series: [sink, marked, ...rest],\n  };\n}\n\n// Whether `auto` draws the bars sideways. Decided from the labels alone, never from the\n// width: the natural height below depends on it, and a layout that flipped with the\n// container would leave a tall plot half empty. Short label sets that fit side by side on a\n// phone stay vertical; anything longer is a ranking with names to read.\nconst sideways = (labels: string[]) => labels.length > 8 || labels.some((l) => l.length > 9) || labels.join(\"\").length + labels.length * 2 > 44;\n\n/** At most two lines no wider than `max` px, the second ending in an ellipsis when the text runs on. */\nfunction fitLabel(label: string, max: number, size: number, family: string, maxLines = 2): string {\n  if (measureText(label, size, family) <= max) return label;\n  const lines = wrapText(label, max, size, family);\n  const kept = lines.slice(0, maxLines).map((l) => truncateText(l, max, size, family));\n  if (lines.length > maxLines) kept[maxLines - 1] = truncateText(`${lines.slice(maxLines - 1).join(\" \")}`, max, size, family);\n  return kept.join(\"\\n\");\n}\n\n/**\n * Bars for comparing categories. The value axis always starts at zero, bars are sorted\n * unless the categories have a natural order, and a single series is labelled directly\n * so the value axis can disappear.\n */\nexport const BarChart = defineChart<BarChartProps>({\n  name: \"bar-chart\",\n  title: \"Bar chart\",\n  category: \"comparison\",\n  description: \"Compares a value across categories with bars from a zero baseline.\",\n  whenToUse: \"Ranking or comparing up to about 20 categories; grouped for a few series, stacked when the total and its composition both matter.\",\n  avoid: \"Change over many time points (use line-chart); two values per category (use dumbbell-chart); more than about 5 stacked segments.\",\n  use: [EBar],\n  defaults: { orientation: \"auto\" },\n  selectField: (p) => p.x,\n  // Long rankings grow taller instead of squeezing their bars, and a handful of horizontal\n  // bars get a compact block instead of being spread over the default height.\n  height: (p, rows) => {\n    if (p.orientation === \"vertical\") return undefined;\n    const names = [...new Set(rows.map((r) => String(r[p.x])))];\n    const n = names.length;\n    const groups = p.stack ? 1 : Array.isArray(p.y) ? p.y.length : p.series ? new Set(rows.map((r) => String(r[p.series!]))).size : 1;\n    const natural = n * (18 + groups * 14) + 40;\n    if (n > 10) return Math.max(360, natural);\n    // Only when the layout is certain to be horizontal at every width; otherwise `build` keeps the rows together.\n    const horizontal = p.orientation === \"horizontal\" || (n > 0 && !isTemporal(rows[0][p.x]) && sideways(names));\n    return horizontal ? Math.max(96, natural) : undefined;\n  },\n  props: [\n    { name: \"x\", type: \"string\", required: true, description: \"Category field.\" },\n    { name: \"y\", type: \"string | string[]\", required: true, description: \"Value field, or several for grouped / stacked bars.\" },\n    { name: \"series\", type: \"string\", description: \"Field that splits rows into series (long data).\" },\n    { name: \"orientation\", type: '\"auto\" | \"horizontal\" | \"vertical\"', default: '\"auto\"', description: \"Horizontal for rankings and long labels, vertical for time.\" },\n    { name: \"sort\", type: '\"none\" | \"asc\" | \"desc\"', description: \"Bar order. Default: descending for rankings, data order for time.\" },\n    { name: \"stack\", type: 'boolean | \"percent\"', default: \"false\", description: \"Stack series; `percent` normalises to 100%.\" },\n    { name: \"labels\", type: \"boolean\", description: \"Value labels at bar ends. Default: on for one series of up to 12 bars.\" },\n    { name: \"names\", type: \"Record<string, string>\", description: \"Display names for series keys.\" },\n  ],\n  example: { type: \"bar-chart\", data: [{ country: \"Norway\", share: 98 }, { country: \"Brazil\", share: 89 }, { country: \"Canada\", share: 68 }], x: \"country\", y: \"share\", format: \"{value}%\", title: \"Renewables supplied 98% of Norway's electricity in 2023\" },\n  build(p, ctx) {\n    const t = ctx.tokens;\n    const shaped = shapeSeries(ctx.rows, p, p.names);\n    const single = shaped.series.length === 1;\n    const percent = p.stack === \"percent\";\n    const stacked = !!p.stack && !single;\n    const horizontal = p.orientation === \"horizontal\" || (p.orientation !== \"vertical\" && !shaped.temporal && sideways(shaped.labels));\n    // Decided from the props, not the data: a multi-series chart filtered down to one series must not start re-sorting.\n    const multi = !!p.series || (Array.isArray(p.y) && p.y.length > 1);\n    const sort = p.sort ?? (shaped.temporal || multi ? \"none\" : \"desc\");\n\n    // Order positions, then read every series through that order.\n    let order = shaped.labels.map((_, i) => i);\n    if (sort !== \"none\") {\n      const total = (i: number) => shaped.series.reduce((s, sr) => s + (sr.values[i] ?? 0), 0);\n      order.sort((a, b) => (sort === \"desc\" ? total(b) - total(a) : total(a) - total(b)));\n    }\n    const labels = order.map((i) => shaped.labels[i]);\n    const totals = order.map((i) => shaped.series.reduce((s, sr) => s + Math.abs(sr.values[i] ?? 0), 0));\n    const fmt = percent && !p.format ? ctx.formatter(\"percent\") : ctx.fmt;\n    const showLabels = p.labels ?? (single && labels.length <= 12);\n    const r = t.radius;\n    // A few horizontal bars in a tall plot: cap the band so the rows sit together at the top\n    // instead of being spread over the whole height with large gaps.\n    const hideValueAxis = showLabels && single;\n    const rowH = 18 + (stacked || single ? 1 : shaped.series.length) * 14;\n    const capped = horizontal && labels.length > 0 && (ctx.height - (hideValueAxis ? 16 : 44)) / labels.length > rowH * 1.5;\n\n    const series = shaped.series.map((s, si) => {\n      const last = si === shaped.series.length - 1;\n      const round = !stacked || last;\n      const color = single ? undefined : ctx.color(s.key, si);\n      return {\n        id: s.key,\n        name: s.name,\n        type: \"bar\" as const,\n        stack: stacked ? \"total\" : undefined,\n        barMaxWidth: 24,\n        barGap: \"8%\",\n        ...morph,\n        // With `select` on, a click rebuilds the option under the pointer and series focus would leave the others blurred.\n        emphasis: { focus: single || p.select ? (\"none\" as const) : (\"series\" as const) },\n        itemStyle: {\n          borderRadius: round ? (horizontal ? [0, r, r, 0] : [r, r, 0, 0]) : 0,\n          // The 2px surface gap that separates stacked segments.\n          ...(stacked ? { borderColor: t.surface, borderWidth: 1 } : null),\n        },\n        label: {\n          show: showLabels,\n          position: stacked ? (\"inside\" as const) : horizontal ? (\"right\" as const) : (\"top\" as const),\n          formatter: (d: any) => (stacked && Math.abs(d.value) / (percent ? 1 : Math.max(...totals)) < 0.08 ? \"\" : fmt(d.value)),\n          ...labelStyle(ctx, stacked ? { color: inkOn(color ?? t.accent) } : { color: t.ink }),\n          fontVariantNumeric: \"tabular-nums\",\n        },\n        data: order.map((i, pos) => {\n          const raw = s.values[i];\n          const value = raw == null ? null : percent ? (totals[pos] ? raw / totals[pos] : 0) : raw;\n          const fill = color ?? ctx.color(shaped.labels[i]);\n          return { name: shaped.labels[i], value, itemStyle: { color: ctx.selected(shaped.labels[i]) ? fill : alpha(fill, 0.25) } };\n        }),\n      };\n    });\n\n    // Category labels are fitted to measured text, never dropped. Sideways: the label column is\n    // as wide as its longest name, up to a share of the width, then two lines, then an ellipsis.\n    // Upright: one line if every name fits its band, else wrapped, else rotated.\n    const size = t.fontSize;\n    const lineHeight = Math.round(size * 1.25);\n    let categoryLabel: Record<string, unknown> = {};\n    if (horizontal) {\n      const column = Math.floor(ctx.width * (ctx.compact ? 0.42 : 0.36));\n      const fitted = new Map(labels.map((l) => [l, fitLabel(l, column, size, t.fontFamily)]));\n      categoryLabel = { color: t.inkSecondary, hideOverlap: false, interval: 0, lineHeight, formatter: (l: string) => fitted.get(l) ?? l };\n    } else if (!shaped.temporal && labels.length > 0) {\n      const gutter = hideValueAxis ? 16 : 28 + Math.max(...totals.map((v) => measureText(fmt(percent ? 1 : v), size, t.fontFamily)));\n      const band = (ctx.width - gutter) / labels.length - 8;\n      const widest = Math.max(...labels.map((l) => measureText(l, size, t.fontFamily)));\n      if (widest <= band) categoryLabel = { hideOverlap: false, interval: 0 };\n      else {\n        const wrapped = new Map(labels.map((l) => [l, wrapText(l, band, size, t.fontFamily)]));\n        const fits = [...wrapped.values()].every((lines) => lines.length <= 3 && lines.every((line) => measureText(line, size, t.fontFamily) <= band));\n        if (fits) categoryLabel = { hideOverlap: false, interval: 0, lineHeight, formatter: (l: string) => wrapped.get(l)?.join(\"\\n\") ?? l };\n        // Too narrow even for rotated text: only then let ECharts thin the labels.\n        else if (band + 8 >= size + 4) categoryLabel = { hideOverlap: false, interval: 0, rotate: 40, formatter: (l: string) => truncateText(l, 120, size, t.fontFamily) };\n      }\n    }\n    const category = shaped.temporal && !horizontal ? categoryAxis(ctx, { data: labels }) : categoryAxis(ctx, { data: labels, inverse: horizontal, axisLine: { show: !horizontal }, axisLabel: categoryLabel });\n    const value = hideValueAxis ? hiddenAxis(\"value\", percent ? { max: 1 } : {}) : valueAxis(ctx, { max: percent ? 1 : undefined, axisLabel: { formatter: (v: number) => fmt(v) } });\n\n    const legend: LegendItem[] = single ? [] : shaped.series.map((s, i) => ({ name: s.name, color: ctx.color(s.key, i), muted: ctx.dimmed(s.key) }));\n    const yName = Array.isArray(p.y) ? p.y.join(\", \") : p.y;\n\n    const notes = horizontal ? p.annotations ?? [] : [];\n    const peak = Math.max(0, ...(stacked ? totals : shaped.series.flatMap((sr) => sr.values.map((v) => v ?? 0))));\n    const place = (option: Record<string, any>) => {\n      if (notes.length) return transposeAnnotations(option, notes, ctx);\n      if (horizontal || !p.annotations?.length) return option;\n      // Upright bars: keep annotation labels off the bars and inside the frame.\n      const n = labels.length || 1;\n      return fitAnnotations(option, p.annotations, ctx, {\n        sink: \"bar\",\n        marks: order.map((i, pos) => ({ fx: (pos + 0.5) / n, y: percent ? 1 : stacked ? totals[pos] : Math.max(0, ...shaped.series.map((sr) => sr.values[i] ?? 0)), bar: true })),\n        fx: (x) => (labels.indexOf(String(x)) < 0 ? undefined : (labels.indexOf(String(x)) + 0.5) / n),\n        yMin: 0,\n        yMax: percent ? 1 : peak,\n        left: hideValueAxis ? 4 : 44,\n        halfMark: 12,\n      });\n    };\n    return {\n      legend,\n      alt: `Bar chart of ${yName} by ${p.x}. ${single ? describeExtremes(shaped.labels, shaped.series[0].values, fmt) : `${shaped.series.length} series: ${shaped.series.map((s) => s.name).join(\", \")}.`}`,\n      option: place({\n        grid: { right: hideValueAxis && horizontal ? 48 : 12, top: hideValueAxis && !horizontal ? 24 : 12, ...(capped ? { height: Math.round(labels.length * rowH * 1.25), bottom: \"auto\" } : null) },\n        xAxis: horizontal ? value : category,\n        yAxis: horizontal ? category : value,\n        tooltip: {\n          trigger: \"axis\",\n          axisPointer: { type: \"shadow\", shadowStyle: { color: alpha(t.muted, 0.1) } },\n          formatter: (items: any) => {\n            const list = Array.isArray(items) ? items : [items];\n            return tooltipHtml(ctx, list[0]?.name, list.filter((d: any) => d.value != null).map((d: any) => ({ name: single ? yName : d.seriesName, value: fmt(d.value), color: d.data?.itemStyle?.color })));\n          },\n        },\n        series,\n      }),\n    };\n  },\n});\n"
    },
    {
      "path": "registry/insightcharts/atoms/_annotations.ts",
      "type": "registry:component",
      "target": "components/insightcharts/atoms/_annotations.ts",
      "content": "\"use client\";\n\nimport { type Annotation, type ChartContext, alpha, measureText, hiddenAxis } from \"insightcharts/core\";\n\n// Room for annotation labels, shared by the cartesian chart atoms. Core draws annotations\n// after `build` with fixed label positions and no knowledge of the marks, so the atoms\n// (which do know where their marks are) reserve padding for labels that would clip and move\n// a reference-line label that would sit on a bar or point. The leading underscore keeps this\n// file out of the generated index. All of it belongs in core/annotations.ts.\n\ntype Obj = Record<string, any>;\n\nexport interface PlotMark {\n  /** Horizontal position as a fraction of the plot width, 0 (left) to 1 (right). */\n  fx: number;\n  /** Value on the vertical axis. */\n  y: number;\n  /** Bars occupy everything between the baseline and `y`; other marks only their own spot. */\n  bar?: boolean;\n}\n\nexport interface PlotInfo {\n  /** Where the marks are. Only those near the left and right edges matter. */\n  marks: PlotMark[];\n  /** Fraction of the plot width at which an annotation's `x` falls, or undefined when unknown. */\n  fx(x: string | number): number | undefined;\n  /** Extent of the vertical axis, estimated from the data. */\n  yMin: number;\n  yMax: number;\n  /** Estimated px taken by the value-axis labels left of the plot. Default 40. */\n  left?: number;\n  /** Half the width of a mark in px. Default 6. */\n  halfMark?: number;\n  /** The series type of the placeholder that absorbs core's own copies: one the chart has registered. */\n  sink: \"bar\" | \"line\" | \"scatter\";\n}\n\ntype LinePosition = \"insideEndTop\" | \"insideEndBottom\" | \"insideStartTop\" | \"insideStartBottom\";\n\n/**\n * Adjust a built option so its annotations fit: more right padding and headroom for labels\n * that would be cut off, and reference-line labels moved to a free corner when the default\n * (right end, above the line) is occupied by a mark. Returns the option unchanged when\n * nothing needs to move.\n */\nexport function fitAnnotations(option: Obj, notes: Annotation[] | undefined, ctx: ChartContext, info: PlotInfo): Obj {\n  if (!notes?.length || Array.isArray(option.grid)) return option;\n  const t = ctx.tokens;\n  const lh = Math.round(t.fontSize * 1.35);\n  const grid: Obj = option.grid ?? {};\n  const top0 = typeof grid.top === \"number\" ? grid.top : 12;\n  const right0 = typeof grid.right === \"number\" ? grid.right : 12;\n  const left = info.left ?? 40;\n  const plotW = Math.max(40, ctx.width - left - right0);\n  const plotH = Math.max(40, ctx.height - top0 - 28);\n  const lo = Math.min(info.yMin, ...notes.flatMap((a) => (a.type === \"point\" ? [a.y] : a.type === \"line\" && a.axis === \"y\" && typeof a.value === \"number\" ? [a.value] : [])));\n  const hi = Math.max(info.yMax, ...notes.flatMap((a) => (a.type === \"point\" ? [a.y] : a.type === \"line\" && a.axis === \"y\" && typeof a.value === \"number\" ? [a.value] : [])));\n  const span = hi - lo || 1;\n  const size = (label: string) => {\n    const lines = label.split(\"\\n\");\n    return { w: Math.max(...lines.map((l) => measureText(l, t.fontSize, t.fontFamily))), h: lines.length * lh };\n  };\n\n  // 1. Padding: labels centred on a point or on a vertical line overflow the plot's right\n  // edge and top when their anchor is near it.\n  let overRight = 0;\n  let overTop = 0;\n  // Moving the plot edge in by `e` moves an anchor at fraction f by only `e * f`, hence the division.\n  const needRight = (px: number, f: number, extent: number) => {\n    const over = px + extent - (ctx.width - 2);\n    if (over > 0 && f > 0.05) overRight = Math.max(overRight, over / f);\n  };\n  for (const a of notes) {\n    if (a.type === \"point\") {\n      const f = info.fx(a.x);\n      const { w, h } = size(a.label);\n      const fy = Math.min(1, Math.max(0, (a.y - lo) / span));\n      const position = a.position ?? \"top\";\n      if (f !== undefined) needRight(left + f * plotW, f, position === \"right\" ? w + 14 : position === \"left\" ? 6 : w / 2 + 2);\n      const above = position === \"top\" ? h + 14 : position === \"bottom\" ? 6 : h / 2;\n      const over = above - (top0 + (1 - fy) * plotH) + 2;\n      if (over > 0 && fy > 0.05) overTop = Math.max(overTop, over / fy);\n    } else if (a.type === \"line\" && a.axis === \"x\" && a.label) {\n      // Core labels a vertical line above the plot (\"end\"), which needs headroom.\n      const { w, h } = size(a.label);\n      overTop = Math.max(overTop, h + 8 - top0);\n      const f = info.fx(a.value);\n      if (f !== undefined) needRight(left + f * plotW, f, w / 2 + 2);\n    }\n  }\n  const addRight = Math.ceil(Math.min(overRight, ctx.width * 0.3));\n  const addTop = Math.ceil(Math.min(overTop, 56));\n\n  // 2. Horizontal reference lines: find a corner of the line that no mark occupies.\n  const half = info.halfMark ?? 6;\n  const delta = ((lh + 6) / plotH) * span;\n  const moved = new Map<Annotation, LinePosition>();\n  for (const a of notes) {\n    if (a.type !== \"line\" || a.axis !== \"y\" || !a.label || typeof a.value !== \"number\") continue;\n    const v = a.value;\n    const reach = (size(a.label).w + 12 + half) / (plotW - addRight);\n    const hit = (end: boolean, above: boolean) =>\n      info.marks.some((m) => {\n        if (end ? m.fx < 1 - reach : m.fx > reach) return false;\n        // A bar (and the value label on its tip) fills everything up to its value.\n        if (m.bar) return m.y > v - delta;\n        return above ? m.y > v - delta * 0.3 && m.y < v + delta * 1.3 : m.y < v + delta * 0.3 && m.y > v - delta * 1.3;\n      });\n    const order: [LinePosition, boolean, boolean][] = [[\"insideEndTop\", true, true], [\"insideEndBottom\", true, false], [\"insideStartTop\", false, true], [\"insideStartBottom\", false, false]];\n    const free = order.find(([, end, above]) => !hit(end, above));\n    if (free && free[0] !== \"insideEndTop\") moved.set(a, free[0]);\n  }\n\n  if (!addRight && !addTop && !moved.size) return option;\n  const padded: Obj = { ...option, grid: { ...grid, right: right0 + addRight, top: top0 + addTop } };\n  if (!moved.size) return padded;\n\n  // Core attaches its own copies of the marks to the first series. Give it an empty series on\n  // an off-canvas grid to draw them on, and attach the repositioned marks to the real one.\n  const text = { color: t.inkSecondary, fontSize: t.fontSize, fontFamily: t.fontFamily, lineHeight: lh };\n  const lines: Obj[] = [];\n  const bands: Obj[][] = [];\n  const points: Obj[] = [];\n  for (const a of notes) {\n    if (a.type === \"line\") {\n      lines.push({ [a.axis === \"x\" ? \"xAxis\" : \"yAxis\"]: a.value, lineStyle: { color: a.emphasis ? t.ink : t.muted, width: 1, type: a.dashed === false ? \"solid\" : \"dashed\" }, label: { show: !!a.label, formatter: a.label ?? \"\", position: a.axis === \"x\" ? \"end\" : moved.get(a) ?? \"insideEndTop\", ...text } });\n    } else if (a.type === \"band\") {\n      const key = a.axis === \"x\" ? \"xAxis\" : \"yAxis\";\n      bands.push([{ [key]: a.from, name: a.label ?? \"\", label: { show: !!a.label, position: a.axis === \"x\" ? \"insideTop\" : \"insideTopLeft\", ...text } }, { [key]: a.to }]);\n    } else {\n      points.push({ coord: [a.x, a.y], symbol: \"circle\", symbolSize: a.dot === false ? 1 : 9, itemStyle: a.dot === false ? { color: \"transparent\", borderWidth: 0 } : { color: t.surface, borderColor: t.ink, borderWidth: 1.5 }, label: { show: true, formatter: a.label, position: a.position ?? \"top\", distance: 8, ...text, color: t.ink, textBorderColor: t.surface, textBorderWidth: 3 } });\n    }\n  }\n  const [first, ...rest] = padded.series as Obj[];\n  const marked = {\n    ...first,\n    ...(lines.length ? { markLine: { silent: true, symbol: \"none\", animation: ctx.animate, data: [...(first.markLine?.data ?? []), ...lines] } } : null),\n    ...(bands.length ? { markArea: { silent: true, itemStyle: { color: alpha(t.muted, 0.12) }, data: [...(first.markArea?.data ?? []), ...bands] } } : null),\n    ...(points.length ? { markPoint: { silent: true, animation: ctx.animate, data: [...(first.markPoint?.data ?? []), ...points] } } : null),\n  };\n  const sink = { id: \"__annotations-sink\", type: info.sink, xAxisIndex: 1, yAxisIndex: 1, silent: true, tooltip: { show: false }, data: [] };\n  return {\n    ...padded,\n    grid: [{ left: 4, bottom: 4, outerBoundsMode: \"same\", outerBoundsContain: \"axisLabel\", ...padded.grid }, { left: -2000, top: -2000, width: 1, height: 1, outerBoundsMode: \"none\" }],\n    xAxis: [padded.xAxis, hiddenAxis(\"value\", { gridIndex: 1 })],\n    yAxis: [padded.yAxis, hiddenAxis(\"value\", { gridIndex: 1 })],\n    series: [sink, marked, ...rest],\n  };\n}\n"
    }
  ],
  "docs": "Bar chart: https://insightcharts.dev/llms/bar-chart.md",
  "categories": [
    "atom",
    "comparison"
  ],
  "meta": {
    "level": "atom",
    "category": "comparison",
    "component": "BarChart",
    "composes": []
  }
}
