{"name":"brush","type":"registry:ui","files":[{"path":"brush.tsx","type":"registry:ui","content":"import { withAlpha, type ResolvedColors } from \"@/components/ui/charts/chart\";\nimport type { DataZoomComponentOption } from \"echarts/components\";\nimport type { FC } from \"react\";\nimport * as echarts from \"echarts/core\";\n\ntype EChartsInstance = ReturnType<typeof echarts.init>;\n\nconst BRUSH_BORDER_OPACITY = 1; \n\nexport { BRUSH_BORDER_OPACITY };\n\nexport interface BrushProps {\n  height?: number; \n  formatLabel?: (value: string, index: number) => string; \n  onChange?: (range: { startIndex: number; endIndex: number }) => void; \n}\n\nexport const Brush: FC<BrushProps> = () => null;\n\nexport type BrushRange = { start: number; end: number };\nexport type BrushGeometry = { bottom: number; height: number };\n\nexport type BrushOverlayParams = {\n  range: BrushRange;\n  geom: BrushGeometry;\n  size: { width: number; height: number };\n  tokens: ResolvedColors[\"tokens\"];\n  labels: { start: string; end: string } | null;\n  showLabels: boolean;\n  hover: { left: boolean; right: boolean };\n};\n\ntype ZrRect = InstanceType<typeof echarts.graphic.Rect>;\ntype ZrCircle = InstanceType<typeof echarts.graphic.Circle>;\ntype ZrText = InstanceType<typeof echarts.graphic.Text>;\n\nexport type BrushOverlayElements = {\n  dimLeft: ZrRect;\n  dimRight: ZrRect;\n  frame: ZrRect;\n  pillLeft: ZrRect;\n  pillRight: ZrRect;\n  grips: ZrCircle[]; \n  labelStart: ZrText;\n  labelEnd: ZrText;\n};\n\nexport function syncBrushOverlay(\n  chart: EChartsInstance,\n  store: { brushOverlay: BrushOverlayElements | null },\n  params: BrushOverlayParams | null,\n) {\n  const zr = chart.getZr();\n  if (!zr) return;\n\n  if (!params) {\n    if (store.brushOverlay) {\n      const { grips, ...rest } = store.brushOverlay;\n      [...Object.values(rest), ...grips].forEach((el) => zr.remove(el));\n      store.brushOverlay = null;\n    }\n    return;\n  }\n\n  if (!store.brushOverlay) {\n    const rect = (z: number) => new echarts.graphic.Rect({ silent: true, z, shape: {} });\n    const els: BrushOverlayElements = {\n      dimLeft: rect(100),\n      dimRight: rect(100),\n      frame: rect(101),\n      pillLeft: rect(102),\n      pillRight: rect(102),\n      grips: Array.from(\n        { length: 6 },\n        () => new echarts.graphic.Circle({ silent: true, z: 103, shape: {} }),\n      ),\n      labelStart: new echarts.graphic.Text({ silent: true, z: 104 }),\n      labelEnd: new echarts.graphic.Text({ silent: true, z: 104 }),\n    };\n    const { grips, ...rest } = els;\n    [...Object.values(rest), ...grips].forEach((el) => zr.add(el));\n    store.brushOverlay = els;\n  }\n\n  const els = store.brushOverlay;\n  const { range, geom, size, tokens, labels, showLabels, hover } = params;\n\n  const trackLeft = 8;\n  const trackRight = Math.max(size.width - 8, trackLeft);\n  const trackWidth = trackRight - trackLeft;\n  const top = size.height - geom.bottom - geom.height;\n  const centerY = top + geom.height / 2;\n  const selectionLeft = trackLeft + (trackWidth * range.start) / 100;\n  const selectionRight = trackLeft + (trackWidth * range.end) / 100;\n\n  const dimFill = withAlpha(tokens.background, 0.7);\n  els.dimLeft.setShape({\n    x: trackLeft,\n    y: top,\n    width: Math.max(selectionLeft - trackLeft, 0),\n    height: geom.height,\n  });\n  els.dimLeft.setStyle({ fill: dimFill });\n  els.dimRight.setShape({\n    x: selectionRight,\n    y: top,\n    width: Math.max(trackRight - selectionRight, 0),\n    height: geom.height,\n  });\n  els.dimRight.setStyle({ fill: dimFill });\n\n  els.frame.setShape({\n    x: selectionLeft,\n    y: top,\n    width: Math.max(selectionRight - selectionLeft, 0),\n    height: geom.height,\n    r: 6,\n  });\n  els.frame.setStyle({\n    fill: \"none\",\n    stroke: withAlpha(tokens.border, BRUSH_BORDER_OPACITY),\n    lineWidth: 1,\n  });\n\n  const pill = (el: ZrRect, x: number, hovered: boolean) => {\n    el.setShape({ x: x - 3, y: centerY - 8, width: 6, height: 16, r: 3 });\n    el.setStyle({ fill: hovered ? tokens.foreground : tokens.mutedForeground });\n  };\n  pill(els.pillLeft, selectionLeft, hover.left);\n  pill(els.pillRight, selectionRight, hover.right);\n\n  const gripFill = withAlpha(tokens.background, 0.7);\n  [-4, 0, 4].forEach((offset, i) => {\n    els.grips[i].setShape({ cx: selectionLeft, cy: centerY + offset, r: 1 });\n    els.grips[i].setStyle({ fill: gripFill });\n    els.grips[i + 3].setShape({ cx: selectionRight, cy: centerY + offset, r: 1 });\n    els.grips[i + 3].setStyle({ fill: gripFill });\n  });\n\n  const label = (el: ZrText, text: string, x: number, align: \"left\" | \"right\") => {\n    el.setStyle({\n      text,\n      x: align === \"left\" ? Math.max(x + 6, trackLeft + 2) : Math.min(x - 6, trackRight - 2),\n      y: top + geom.height,\n      align,\n      verticalAlign: \"middle\",\n      fill: tokens.background,\n      backgroundColor: tokens.foreground,\n      padding: [2, 5],\n      borderRadius: 4,\n      font: \"500 9px system-ui, sans-serif\",\n    });\n    el.attr(\"invisible\", !showLabels || !text);\n  };\n  label(els.labelStart, labels?.start ?? \"\", selectionLeft, \"left\");\n  label(els.labelEnd, labels?.end ?? \"\", selectionRight, \"right\");\n}\n\nexport function buildBrushDataZoom(params: {\n  brushBottom: number;\n  brushHeight: number;\n  brushRange: BrushRange;\n  fillerColor: string;\n}): DataZoomComponentOption[] {\n  const { brushBottom, brushHeight, brushRange, fillerColor } = params;\n\n  return [\n    {\n      type: \"slider\",\n      show: true,\n      xAxisIndex: [0],\n      left: 8,\n      right: 8,\n      bottom: brushBottom,\n      height: brushHeight,\n\n      start: brushRange.start,\n      end: brushRange.end,\n      brushSelect: false,\n\n      showDetail: false,\n      backgroundColor: \"transparent\",\n\n      borderColor: \"transparent\",\n      fillerColor,\n      dataBackground: { lineStyle: { opacity: 0 }, areaStyle: { opacity: 0 } },\n      selectedDataBackground: { lineStyle: { opacity: 0 }, areaStyle: { opacity: 0 } },\n\n      handleIcon: \"path://M -3 -5 L -3 5 A 3 3 0 0 0 3 5 L 3 -5 A 3 3 0 0 0 -3 -5 Z\",\n      handleSize: \"35%\",\n      handleStyle: { opacity: 0 },\n      moveHandleSize: 0,\n      emphasis: { handleStyle: { opacity: 0 } },\n    },\n    { type: \"inside\", xAxisIndex: [0] },\n  ];\n}\n","target":"components/ui/brush.tsx"}],"dependencies":["echarts"],"registryDependencies":["https://www.honestui.com/r/chart.json"]}