Example

import { BarChart, Chart, LineChart, Navigation, NavigationStack, PointChart, Script, ScrollView, Text, VStack } from "scripting"

// 60 天的"日访问量"假数据,用于演示 date 轴按日 stop / 按月 major 对齐。
const dailyData: { date: Date; visits: number }[] = (() => {
  const arr: { date: Date; visits: number }[] = []
  const start = new Date(2026, 0, 1) // 2026-01-01
  for (let i = 0; i < 60; i++) {
    const d = new Date(start.getTime() + i * 86400_000)
    // 简单合成的波动 + 偶尔尖峰
    const base = 200 + Math.round(Math.sin(i / 4) * 60)
    const spike = i % 9 === 0 ? 120 : 0
    arr.push({ date: d, visits: base + spike })
  }
  return arr
})()

// 60 个连续 index 的"评分"假数据,演示 numeric 轴按 unit=1 stop / page 主对齐。
const numericData: { x: number; y: number }[] = Array.from({ length: 60 }, (_, i) => ({
  x: i,
  y: 50 + Math.round(Math.cos(i / 3) * 25 + (i % 7 === 0 ? 18 : 0)),
}))

function Example() {
  return <NavigationStack>
    <ScrollView>
      <VStack
        navigationTitle={"Chart Scroll Target Behavior"}
        navigationBarTitleDisplayMode={"inline"}
        spacing={28}
        padding
      >

        <Text font={"caption"} foregroundStyle={"secondaryLabel"}>
          chartScrollTargetBehavior tells SwiftUI Charts where to "park" the scroll deceleration.
          Without it, scrolling a chart with chartScrollableAxes feels free-form; with it, releasing
          a swipe lands on a meaningful data boundary (a day, a month, an integer index).
          {"\n\n"}
          Pair it with chartScrollableAxes (otherwise the chart isn't scrollable, and snapping has
          nothing to act on) and chartXVisibleDomain (controls how much of the data is visible at
          once — also determines what counts as a "page").
        </Text>

        {/* 1. 日期轴:按日 stop,按月 major 对齐 */}
        <VStack alignment={"leading"} spacing={8}>
          <Text font={"headline"}>1. Date axis — daily stops, monthly major</Text>
          <Text font={"caption"} foregroundStyle={"secondaryLabel"}>
            60 days of synthetic visit data. Swipe horizontally; release and the visible window
            should snap so the leading edge sits on a day boundary, with month edges acting as the
            larger "page" alignment.
          </Text>
          <Chart
            frame={{ height: 220 }}
            chartScrollableAxes={"horizontal"}
            chartXVisibleDomain={86400 * 14}
            chartScrollTargetBehavior={{
              matching: new DateComponents({ day: 1 }),
              majorAlignment: { matching: new DateComponents({ month: 1 }) },
            }}
            chartXAxis={{
              valueLabel: { format: "date" },
            }}
          >
            <LineChart
              marks={dailyData.map(d => ({
                label: d.date,
                value: d.visits,
                // unit: "day" 告诉 Charts 每个 mark 在 X 轴上占多大 —— 否则 SDK 会回落到固定像素宽度
                // 并 log "Falling back to a fixed dimension size for a mark"。
                unit: "day",
                interpolationMethod: "monotone",
                foregroundStyle: "blue",
              }))}
            />
          </Chart>
        </VStack>

        {/* 2. 日期轴:按周 stop(page 主对齐) */}
        <VStack alignment={"leading"} spacing={8}>
          <Text font={"headline"}>2. Date axis — weekly stops, page major</Text>
          <Text font={"caption"} foregroundStyle={"secondaryLabel"}>
            Same data, but stops snap on weekly boundaries (every 7 days) and the major alignment is
            the visible page. Useful when you want exactly N visible weeks per "page".
          </Text>
          <Chart
            frame={{ height: 220 }}
            chartScrollableAxes={"horizontal"}
            chartXVisibleDomain={86400 * 21}
            chartScrollTargetBehavior={{
              matching: new DateComponents({ day: 7 }),
              majorAlignment: "page",
            }}
            chartXAxis={{
              valueLabel: { format: "date" },
            }}
          >
            <BarChart
              marks={dailyData.map(d => ({
                label: d.date,
                value: d.visits,
                unit: "day",
                foregroundStyle: "orange",
              }))}
            />
          </Chart>
        </VStack>

        {/* 3. 数值轴:按 1 unit stop,page 主对齐 —— 必须用 numeric x 的 mark(PointChart),
            BarChart 的 label 是 string/Date,会走 categorical 轴,chartScrollTargetBehavior 在
            categorical 轴上不生效(SDK 限制)。 */}
        <VStack alignment={"leading"} spacing={8}>
          <Text font={"headline"}>3. Numeric axis — unit stops, page major</Text>
          <Text font={"caption"} foregroundStyle={"secondaryLabel"}>
            Numeric x axis (0..59). Each scroll deceleration snaps so the leading edge lands on an
            integer; major alignment is the visible page (10 units wide). Note: this requires a
            numeric-x mark (PointChart), since BarChart's `label` field becomes a categorical String
            axis on which chartScrollTargetBehavior is silently no-op.
          </Text>
          <Chart
            frame={{ height: 220 }}
            chartScrollableAxes={"horizontal"}
            chartXVisibleDomain={10}
            chartScrollTargetBehavior={{
              unit: 1,
              majorAlignment: "page",
            }}
          >
            <PointChart
              marks={numericData.map(d => ({
                x: d.x,
                y: d.y,
                foregroundStyle: "green",
                symbolSize: 80,
              }))}
            />
          </Chart>
        </VStack>

      </VStack>
    </ScrollView>
  </NavigationStack>
}

async function run() {
  await Navigation.present({ element: <Example /> })
  Script.exit()
}

run()