{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "analytics-overview",
  "title": "Analytics Overview",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-svelte/blocks/analytics-overview/AnalyticsOverview.svelte",
      "content": "<script lang=\"ts\">\n  // Two-pane analytics dashboard: a combo chart (stacked bars with a\n  // dot overlay on a dual y-axis) sits above an interactive drill-down\n  // line chart. Click a bar in the top pane to filter the bottom pane\n  // to that route. The block bakes in a realistic dummy dataset; swap\n  // `routes`, `partners`, and `drillSeries` with your own to repoint.\n  //\n  // Built on `RawChart` rather than `BarChart` because combo charts\n  // (bar + scatter + dual y-axes) sit outside the opinionated wrapper's\n  // single-series-type contract -- this is the documented escape hatch.\n  import { getInstanceByDom, use } from 'echarts/core'\n  import { BarChart as EBar, ScatterChart as EScatter, LineChart as ELine } from 'echarts/charts'\n  import { GridComponent, TooltipComponent, LegendComponent, MarkLineComponent } from 'echarts/components'\n  import { RawChart, resolveChartTheme, watchThemeVersion } from '$lib/components/ui/raw-chart'\n\n  use([EBar, EScatter, ELine, GridComponent, TooltipComponent, LegendComponent, MarkLineComponent])\n\n  // Each route declares the per-partner volume (stack segments) + the\n  // avg overlay value (dot on the right y-axis).\n  const partners = ['ALPHA', 'BETA', 'GAMMA', 'DELTA', 'EPSILON'] as const\n  type Partner = (typeof partners)[number]\n\n  interface Route {\n    id: string\n    label: string\n    volumes: Record<Partner, number>\n    overlay: number\n  }\n\n  const routes: Route[] = [\n    {\n      id: 'r1',\n      label: 'NODE A → NODE B',\n      volumes: { ALPHA: 90, BETA: 25, GAMMA: 18, DELTA: 12, EPSILON: 8 },\n      overlay: 12,\n    },\n    {\n      id: 'r2',\n      label: 'NODE C → NODE D',\n      volumes: { ALPHA: 4, BETA: 100, GAMMA: 14, DELTA: 12, EPSILON: 24 },\n      overlay: 14.7,\n    },\n    {\n      id: 'r3',\n      label: 'NODE E → NODE F',\n      volumes: { ALPHA: 14, BETA: 38, GAMMA: 18, DELTA: 32, EPSILON: 28 },\n      overlay: 8,\n    },\n    {\n      id: 'r4',\n      label: 'NODE G → NODE H',\n      volumes: { ALPHA: 130, BETA: 0, GAMMA: 0, DELTA: 0, EPSILON: 0 },\n      overlay: 6,\n    },\n    {\n      id: 'r5',\n      label: 'NODE I → NODE J',\n      volumes: { ALPHA: 0, BETA: 0, GAMMA: 60, DELTA: 0, EPSILON: 70 },\n      overlay: 17,\n    },\n    {\n      id: 'r6',\n      label: 'NODE K → NODE L',\n      volumes: { ALPHA: 60, BETA: 50, GAMMA: 16, DELTA: 0, EPSILON: 0 },\n      overlay: 8,\n    },\n    {\n      id: 'r7',\n      label: 'NODE M → NODE N',\n      volumes: { ALPHA: 70, BETA: 0, GAMMA: 0, DELTA: 0, EPSILON: 0 },\n      overlay: 22,\n    },\n    {\n      id: 'r8',\n      label: 'NODE O → NODE P',\n      volumes: { ALPHA: 0, BETA: 0, GAMMA: 0, DELTA: 0, EPSILON: 110 },\n      overlay: 29,\n    },\n    {\n      id: 'r9',\n      label: 'NODE Q → NODE R',\n      volumes: { ALPHA: 40, BETA: 0, GAMMA: 60, DELTA: 0, EPSILON: 0 },\n      overlay: 19,\n    },\n    {\n      id: 'r10',\n      label: 'NODE S → NODE T',\n      volumes: { ALPHA: 60, BETA: 30, GAMMA: 0, DELTA: 0, EPSILON: 0 },\n      overlay: 23,\n    },\n  ]\n\n  // Drill-down monthly series for a few partners. In a real app you'd\n  // fetch this per-route on selection; the dummy block shows the same\n  // shape regardless of which route is active.\n  const drillMonths = ['Mar 2026', 'Apr 2026', 'May 2026']\n  const drillSeries: Record<string, number[]> = {\n    ALPHA: [22, 25, 20],\n    GAMMA: [19, 17, 18],\n    EPSILON: [-1, 29, -1], // sparse point — left out of line by NaN-like handling\n  }\n\n  let overviewMetric = $state<'overlay' | 'volume'>('overlay')\n  let activeRoute = $state<string | null>(null)\n  let drillView = $state<'Month' | 'Quarter'>('Month')\n\n  let themeVersion = $state(0)\n\n  $effect(() => watchThemeVersion(() => (themeVersion += 1)))\n\n  const theme = $derived.by(() => {\n    themeVersion\n    return resolveChartTheme()\n  })\n\n  const overviewOption = $derived.by(() => {\n    const x = routes.map((r) => r.label)\n    const stacks = partners.map((p, i) => ({\n      name: p,\n      type: 'bar' as const,\n      stack: 'vol',\n      barMaxWidth: 36,\n      emphasis: { focus: 'series' as const },\n      itemStyle: { color: theme.colors[i % theme.colors.length] },\n      data: routes.map((r) => r.volumes[p]),\n    }))\n    const overlay = {\n      name: 'Avg metric',\n      type: 'scatter' as const,\n      yAxisIndex: 1,\n      symbolSize: 12,\n      itemStyle: { color: theme.colors[2], borderColor: theme.tooltipBg, borderWidth: 2 },\n      data: routes.map((r) => r.overlay),\n    }\n\n    return {\n      color: theme.colors,\n      tooltip: {\n        trigger: 'axis',\n        axisPointer: { type: 'shadow' },\n        backgroundColor: theme.tooltipBg,\n        borderColor: theme.tooltipBorder,\n        textStyle: { color: theme.tooltipText, fontSize: 12 },\n      },\n      legend: {\n        top: 8,\n        icon: 'circle',\n        itemWidth: 8,\n        itemHeight: 8,\n        textStyle: { color: theme.textColor, fontSize: 11 },\n      },\n      grid: { left: 56, right: 56, top: 42, bottom: 72, containLabel: false },\n      xAxis: {\n        type: 'category' as const,\n        data: x,\n        axisLine: { lineStyle: { color: theme.axisColor } },\n        axisLabel: {\n          interval: 0,\n          rotate: 35,\n          fontSize: 10,\n          color: theme.textColor,\n          formatter: (val: string) => val.replace(' → ', '\\n'),\n        },\n        axisTick: { show: false },\n      },\n      yAxis: [\n        {\n          type: 'value' as const,\n          name: 'Volume',\n          nameTextStyle: { color: theme.textColor, fontSize: 10 },\n          splitLine: { lineStyle: { color: theme.splitLineColor } },\n          axisLabel: { color: theme.textColor, fontSize: 10 },\n        },\n        {\n          type: 'value' as const,\n          name: 'Metric',\n          nameTextStyle: { color: theme.textColor, fontSize: 10 },\n          splitLine: { show: false },\n          axisLabel: { color: theme.textColor, fontSize: 10 },\n        },\n      ],\n      series: [...stacks, overlay],\n    }\n  })\n\n  const drillOption = $derived.by(() => {\n    const series = Object.entries(drillSeries).map(([name, values], i) => ({\n      name,\n      type: 'line' as const,\n      smooth: true,\n      symbolSize: 8,\n      connectNulls: false,\n      lineStyle: { width: 2, color: theme.colors[i % theme.colors.length] },\n      itemStyle: { color: theme.colors[i % theme.colors.length] },\n      data: values.map((v) => (v < 0 ? null : v)),\n    }))\n\n    return {\n      color: theme.colors,\n      tooltip: {\n        trigger: 'axis',\n        backgroundColor: theme.tooltipBg,\n        borderColor: theme.tooltipBorder,\n        textStyle: { color: theme.tooltipText, fontSize: 12 },\n      },\n      legend: {\n        top: 8,\n        icon: 'circle',\n        itemWidth: 8,\n        itemHeight: 8,\n        textStyle: { color: theme.textColor, fontSize: 11 },\n      },\n      grid: { left: 48, right: 24, top: 42, bottom: 48, containLabel: false },\n      xAxis: {\n        type: 'category' as const,\n        data: drillMonths,\n        boundaryGap: false,\n        axisLine: { lineStyle: { color: theme.axisColor } },\n        axisLabel: { color: theme.textColor, fontSize: 11 },\n        axisTick: { show: false },\n      },\n      yAxis: {\n        type: 'value' as const,\n        splitLine: { lineStyle: { color: theme.splitLineColor } },\n        axisLabel: { color: theme.textColor, fontSize: 11, formatter: (v: number) => `${v}d` },\n      },\n      series,\n    }\n  })\n\n  const activeLabel = $derived(routes.find((r) => r.id === activeRoute)?.label ?? null)\n\n  // RawChart owns the ECharts instance internally, so reach it through the\n  // canvas node and subscribe to real chart clicks (a native DOM click only\n  // yields a MouseEvent, which carries no series name to drill into).\n  let overviewRoot: HTMLDivElement | null = $state(null)\n\n  $effect(() => {\n    const canvas = overviewRoot?.querySelector('div')\n    const instance = canvas ? getInstanceByDom(canvas) : undefined\n    if (!instance) return\n    const onClick = (params: { name?: unknown }) => {\n      if (typeof params?.name !== 'string') return\n      const r = routes.find((rt) => rt.label === params.name)\n      if (r) activeRoute = r.id\n    }\n    instance.on('click', onClick)\n    return () => {\n      instance.off('click', onClick)\n    }\n  })\n\n  function resetDrill() {\n    activeRoute = null\n  }\n</script>\n\n<div data-slot=\"analytics-overview\" class=\"space-y-6\">\n  <!-- Overview pane -->\n  <section class=\"bg-card border-border rounded-lg border p-5\">\n    <div class=\"mb-3 flex items-start justify-between gap-4\">\n      <div>\n        <h2 class=\"text-sm font-semibold tracking-tight text-[var(--accent-tint)]\">Overview</h2>\n        <p class=\"text-muted-foreground mt-1 text-xs\">\n          <span class=\"text-foreground font-medium\">Metric</span> measures the avg performance signal aggregated per route.\n        </p>\n      </div>\n      <div class=\"text-muted-foreground text-xs\">\n        <span>Performance Metric:</span>\n        <select\n          bind:value={overviewMetric}\n          class=\"border-border text-foreground ml-2 rounded-md border bg-transparent px-2 py-1 font-medium\"\n        >\n          <option value=\"overlay\">Avg metric</option>\n          <option value=\"volume\">Total volume</option>\n        </select>\n      </div>\n    </div>\n    <div bind:this={overviewRoot}>\n      <RawChart option={overviewOption} height={320} />\n    </div>\n    <p class=\"text-muted-foreground mt-2 text-xs\">Click a bar to drill into that route's monthly trend.</p>\n  </section>\n\n  <!-- Drill-down pane -->\n  <section class=\"bg-card border-border rounded-lg border p-5\">\n    <div class=\"mb-3 flex items-center justify-between gap-4\">\n      <div class=\"flex items-center gap-3\">\n        <h3 class=\"text-sm font-semibold tracking-tight\">\n          {#if activeLabel}\n            <span class=\"text-muted-foreground font-normal\">Drill-down:</span>\n            <span class=\"text-foreground ml-1\">{activeLabel}</span>\n          {:else}\n            <span class=\"text-muted-foreground font-normal\">Select a route above to drill in</span>\n          {/if}\n        </h3>\n        {#if activeRoute}\n          <button\n            type=\"button\"\n            class=\"border-border text-muted-foreground hover:text-foreground rounded-md border px-2 py-1 font-mono text-xs transition hover:border-[var(--accent-tint-soft)]\"\n            onclick={resetDrill}\n          >\n            ← Back to Overview\n          </button>\n        {/if}\n      </div>\n      <div class=\"text-muted-foreground text-xs\">\n        <span>View By:</span>\n        <select\n          bind:value={drillView}\n          class=\"border-border text-foreground ml-2 rounded-md border bg-transparent px-2 py-1 font-medium\"\n        >\n          <option value=\"Month\">Month</option>\n          <option value=\"Quarter\">Quarter</option>\n        </select>\n      </div>\n    </div>\n    {#if activeRoute}\n      <RawChart option={drillOption} height={280} />\n    {:else}\n      <div\n        class=\"text-muted-foreground border-border grid h-[260px] place-items-center rounded-md border border-dashed text-xs\"\n      >\n        No route selected. Click a bar above.\n      </div>\n    {/if}\n  </section>\n</div>\n",
      "type": "registry:block",
      "target": "~/src/lib/components/blocks/analytics-overview/AnalyticsOverview.svelte"
    }
  ],
  "dependencies": [
    "echarts"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/svelte/raw-chart.json"
  ],
  "description": "Two-pane analytics dashboard: stacked bar with dot overlay on a dual y-axis above, and an interactive drill-down line chart below. Click a bar to filter the bottom pane to that route.",
  "categories": [
    "analytics",
    "dashboard"
  ]
}