アニメーション

VSeed のアニメーション機能は、入場、ループ、雰囲気、更新、退場などの段階をサポートします。このガイドでは、棒/横棒、折れ線/面、円、レーダー、散布図のアニメーション例をまとめ、チャートタイプごとの設定方法を比較しやすくしています。

棒グラフ/横棒グラフのアニメーション

すべての例は大画面テーマ(ダーク)をベースにしており、ドロップダウンでテーマを切り替えられます。 テーマ一覧の取得元: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json

1. 入場アニメーション

入場アニメーションは、チャートが初めて表示されるときに再生される動きです。 棒/横棒系列の入場効果は現在 growth をサポートしています。

1.1 growth(成長)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const THEME_URL = 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json'
const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

const DIMENSIONS = [
  { id: 'HqlYadRdHJ5c', alias: 'Category' },
  { id: '7ti8XuX4kcY1', alias: 'Year' },
]
const MEASURES = [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }]

const useLargeScreenThemes = () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch(THEME_URL)
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        const firstKey = Object.keys(remote)[0] || ''
        setThemeKey(firstKey)
      })
      .catch(() => {})
  }, [])
  return { themes, themeKey, setThemeKey }
}

const ThemeSelect = ({ themes, themeKey, onChange }) => (
  <select
    value={themeKey}
    onChange={(e) => onChange(e.target.value)}
    style={{
      marginBottom: 12,
      color: '#EAF7FF',
      background: 'rgba(0, 213, 255, 0.18)',
      border: '1px solid rgba(0, 213, 255, 0.45)',
      padding: '6px 12px',
    }}
  >
    {Object.entries(themes).map(([key, value]) => (
      <option key={key} value={key} style={{ color: '#000' }}>
        {value?.name || key}
      </option>
    ))}
  </select>
)

export default () => {
  const { themes, themeKey, setThemeKey } = useLargeScreenThemes()
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'columnParallel',
    dataset: DATASET,
    dimensions: DIMENSIONS,
    measures: MEASURES,
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['growth'], ease: 'circInOut', duration: 1 },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <ThemeSelect themes={themes} themeKey={themeKey} onChange={setThemeKey} />
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2. ループアニメーション

ループアニメーションは初回描画後に継続して再生される動きで、強調や動的なリズムの維持に使えます。

2.1 highlight(グループ強調) + atmosphere(雰囲気効果)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const THEME_URL = 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json'
const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'columnParallel',
    dataset: DATASET,
    dimensions: [
      { id: 'HqlYadRdHJ5c', alias: 'Category' },
      { id: '7ti8XuX4kcY1', alias: 'Year' },
    ],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['growth'], ease: 'linear', duration: 1 },
        loop: {
          enable: true,
          interval: 3,
          loop: { enable: true, effects: ['highLight'], ease: 'linear', duration: 1 },
          atmosphere: { effect: 'breath', ease: 'linear', color: '#4A90E2' },
        },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2.2 growth(ループ) + atmosphere(雰囲気効果)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'barParallel',
    dataset: DATASET,
    dimensions: [
      { id: 'HqlYadRdHJ5c', alias: 'Category' },
      { id: '7ti8XuX4kcY1', alias: 'Year' },
    ],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['growth'], ease: 'linear', duration: 1 },
        loop: {
          enable: true,
          interval: 2,
          loop: { enable: true, effects: ['growth'], ease: 'linear', duration: 1 },
          atmo: { effect: 'breath', ease: 'linear', color: '#4A90E2' },
        },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2.3 moveIn(ループ) + atmosphere(雰囲気効果)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'columnParallel',
    dataset: DATASET,
    dimensions: [
      { id: 'HqlYadRdHJ5c', alias: 'Category' },
      { id: '7ti8XuX4kcY1', alias: 'Year' },
    ],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['growth'], ease: 'linear', duration: 1 },
        loop: {
          enable: true,
          interval: 2,
          loop: { enable: true, effects: ['moveIn'], ease: 'linear', duration: 1 },
          atmo: { effect: 'breath', ease: 'linear', color: '#4A90E2' },
        },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

折れ線/面グラフのアニメーション

すべての例は大画面テーマ(ダーク)をベースにしており、ドロップダウンでテーマを切り替えられます。 テーマ一覧の取得元: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json

1. 入場アニメーション

入場アニメーションは、チャートが初めてページに現れるときの視覚的な導入です。

1.1 load(読み込み)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes, Builder } from '@visactor/vseed'

const THEME_URL = 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json'
const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

const DIMENSIONS = [
  { id: 'HqlYadRdHJ5c', alias: 'Category' },
  { id: '7ti8XuX4kcY1', alias: 'Year' },
]
const MEASURES = [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }]

const useLargeScreenThemes = () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    Builder.updateSpec('line', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    Builder.updateSpec('area', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    Builder.updateSpec('areaPercent', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    fetch(THEME_URL)
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  return { themes, themeKey, setThemeKey }
}

const ThemeSelect = ({ themes, themeKey, onChange }) => (
  <select
    value={themeKey}
    onChange={(e) => onChange(e.target.value)}
    style={{
      marginBottom: 12,
      color: '#EAF7FF',
      background: 'rgba(0, 213, 255, 0.18)',
      border: '1px solid rgba(0, 213, 255, 0.45)',
      padding: '6px 12px',
    }}
  >
    {Object.entries(themes).map(([key, value]) => (
      <option key={key} value={key} style={{ color: '#000' }}>
        {value?.name || key}
      </option>
    ))}
  </select>
)

export default () => {
  const { themes, themeKey, setThemeKey } = useLargeScreenThemes()
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'line',
    dataset: DATASET,
    dimensions: DIMENSIONS,
    measures: MEASURES,
    label: { enable: false },
    animation: {
      enable: true,
      params: { appear: { enable: true, effects: ['load'], ease: 'circInOut', duration: 1 } },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <ThemeSelect themes={themes} themeKey={themeKey} onChange={setThemeKey} />
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

1.2 growth(成長)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes, Builder } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    Builder.updateSpec('line', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    Builder.updateSpec('area', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    Builder.updateSpec('areaPercent', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'area',
    dataset: DATASET,
    dimensions: [
      { id: 'HqlYadRdHJ5c', alias: 'Category' },
      { id: '7ti8XuX4kcY1', alias: 'Year' },
    ],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: { appear: { enable: true, effects: ['growth'], ease: 'linear', duration: 1 } },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2. ループアニメーション

ループアニメーションは、長時間表示される状態でチャートを周期的に動かし続けます。

2.1 load(ループ) + atmosphere(breath)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes, Builder } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    Builder.updateSpec('line', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    Builder.updateSpec('area', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    Builder.updateSpec('areaPercent', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'line',
    dataset: DATASET,
    dimensions: [
      { id: 'HqlYadRdHJ5c', alias: 'Category' },
      { id: '7ti8XuX4kcY1', alias: 'Year' },
    ],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['load'], ease: 'linear', duration: 1 },
        loop: {
          enable: true,
          interval: 2,
          loop: { enable: true, effects: ['load'], ease: 'linear', duration: 1 },
          atmosphere: { effect: 'breath', ease: 'linear' },
        },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2.2 load(ループ) + atmosphere(ripple)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes, Builder } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    Builder.updateSpec('line', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    Builder.updateSpec('area', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    Builder.updateSpec('areaPercent', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
    }))
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'areaPercent',
    dataset: DATASET,
    dimensions: [
      { id: 'HqlYadRdHJ5c', alias: 'Category' },
      { id: '7ti8XuX4kcY1', alias: 'Year' },
    ],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['growth'], ease: 'linear', duration: 1 },
        loop: {
          enable: true,
          interval: 3,
          loop: { enable: true, effects: ['load'], ease: 'circInOut', duration: 1 },
          atmosphere: { effect: 'ripple', ease: 'linear' },
        },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

円/ドーナツ/ローズグラフのアニメーション

すべての例は大画面テーマ(ダーク)をベースにしており、ドロップダウンでテーマを切り替えられます。 テーマ一覧の取得元: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json

1. 入場アニメーション

入場アニメーションは、扇形が初めてビューに入るときの表現を制御します。

1.1 radial(放射状)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const THEME_URL = 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json'
const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

const DIMENSIONS = [{ id: 'HqlYadRdHJ5c', alias: 'Category' }]
const MEASURES = [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }]

const useLargeScreenThemes = () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch(THEME_URL)
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  return { themes, themeKey, setThemeKey }
}

const ThemeSelect = ({ themes, themeKey, onChange }) => (
  <select
    value={themeKey}
    onChange={(e) => onChange(e.target.value)}
    style={{
      marginBottom: 12,
      color: '#EAF7FF',
      background: 'rgba(0, 213, 255, 0.18)',
      border: '1px solid rgba(0, 213, 255, 0.45)',
      padding: '6px 12px',
    }}
  >
    {Object.entries(themes).map(([key, value]) => (
      <option key={key} value={key} style={{ color: '#000' }}>
        {value?.name || key}
      </option>
    ))}
  </select>
)

export default () => {
  const { themes, themeKey, setThemeKey } = useLargeScreenThemes()
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'pie',
    dataset: DATASET,
    dimensions: DIMENSIONS,
    measures: MEASURES,
    label: { enable: false },
    animation: {
      enable: true,
      params: { appear: { enable: true, effects: ['radial'], ease: 'circInOut', duration: 1 } },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <ThemeSelect themes={themes} themeKey={themeKey} onChange={setThemeKey} />
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

1.2 scale(拡大縮小)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'donut',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: { appear: { enable: true, effects: ['scale'], ease: 'circInOut', duration: 1 } },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2. ループアニメーション

ループアニメーションは、特定の扇形を周期的に強調します。

2.1 enlarge(サイズ変更)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'pie',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['scale'], ease: 'linear', duration: 1 },
        loop: { enable: true, interval: 2, loop: { enable: true, effects: ['enlarge'], ease: 'linear', duration: 1 } },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2.2 relocate(中心移動)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'pie',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['radial'], ease: 'linear', duration: 1 },
        loop: { enable: true, interval: 2, loop: { enable: true, effects: ['relocate'], ease: 'linear', duration: 1 } },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

レーダーチャートのアニメーション

すべての例は大画面テーマ(ダーク)をベースにしており、ドロップダウンでテーマを切り替えられます。 テーマ一覧の取得元: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json

1. 入場アニメーション

入場アニメーションは、レーダー面と点が初めて表示される動きを制御します。

1.1 radial(放射状)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes, Builder } from '@visactor/vseed'

const THEME_URL = 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json'
const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

const DIMENSIONS = [{ id: 'HqlYadRdHJ5c', alias: 'Category' }]
const MEASURES = [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }]

const useLargeScreenThemes = () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    Builder.updateSpec('radar', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
      axes: [
        {
          orient: 'radius',
          tick: { forceTickCount: 3 },
          grid: { smooth: false, style: { stroke: 'rgba(255,255,255,0.15)', lineWidth: 1 } },
          label: { visible: false, style: { fontFamily: 'D-DIN' } },
        },
        {
          orient: 'angle',
          sampling: false,
          tick: { visible: true, style: () => ({ visible: false }) },
          domainLine: { style: { visible: false } },
          grid: { style: () => ({ stroke: 'rgba(255,255,255,0.15)', lineDash: [2, 1], lineWidth: 1 }) },
          label: { style: { fontFamily: 'D-DIN', fill: 'rgba(255,255,255,0.65)', fontSize: 12 }, visible: true },
        },
      ],
    }))
    fetch(THEME_URL)
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  return { themes, themeKey, setThemeKey }
}

const ThemeSelect = ({ themes, themeKey, onChange }) => (
  <select
    value={themeKey}
    onChange={(e) => onChange(e.target.value)}
    style={{
      marginBottom: 12,
      color: '#EAF7FF',
      background: 'rgba(0, 213, 255, 0.18)',
      border: '1px solid rgba(0, 213, 255, 0.45)',
      padding: '6px 12px',
    }}
  >
    {Object.entries(themes).map(([key, value]) => (
      <option key={key} value={key} style={{ color: '#000' }}>
        {value?.name || key}
      </option>
    ))}
  </select>
)

export default () => {
  const { themes, themeKey, setThemeKey } = useLargeScreenThemes()
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'radar',
    dataset: DATASET,
    dimensions: DIMENSIONS,
    measures: MEASURES,
    label: { enable: false },
    animation: {
      enable: true,
      params: { appear: { enable: true, effects: ['radial'], ease: 'circInOut', duration: 1 } },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <ThemeSelect themes={themes} themeKey={themeKey} onChange={setThemeKey} />
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

1.2 scale(拡大縮小)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes, Builder } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    Builder.updateSpec('radar', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
      axes: [
        {
          orient: 'radius',
          tick: { forceTickCount: 3 },
          grid: { smooth: false, style: { stroke: 'rgba(255,255,255,0.15)', lineWidth: 1 } },
          label: { visible: false, style: { fontFamily: 'D-DIN' } },
        },
        {
          orient: 'angle',
          sampling: false,
          tick: { visible: true, style: () => ({ visible: false }) },
          domainLine: { style: { visible: false } },
          grid: { style: () => ({ stroke: 'rgba(255,255,255,0.15)', lineDash: [2, 1], lineWidth: 1 }) },
          label: { style: { fontFamily: 'D-DIN', fill: 'rgba(255,255,255,0.65)', fontSize: 12 }, visible: true },
        },
      ],
    }))
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'radar',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: { appear: { enable: true, effects: ['scale'], ease: 'circInOut', duration: 1 } },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2. ループアニメーション

レーダーチャートのループアニメーションは、atmosphere.effect によって継続的な雰囲気の動きとして表現されます。

2.1 none(ループ) + atmosphere(ripple)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes, Builder } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    Builder.updateSpec('radar', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
      axes: [
        {
          orient: 'radius',
          tick: { forceTickCount: 3 },
          grid: { smooth: false, style: { stroke: 'rgba(255,255,255,0.15)', lineWidth: 1 } },
          label: { visible: false, style: { fontFamily: 'D-DIN' } },
        },
        {
          orient: 'angle',
          sampling: false,
          tick: { visible: true, style: () => ({ visible: false }) },
          domainLine: { style: { visible: false } },
          grid: { style: () => ({ stroke: 'rgba(255,255,255,0.15)', lineDash: [2, 1], lineWidth: 1 }) },
          label: { style: { fontFamily: 'D-DIN', fill: 'rgba(255,255,255,0.65)', fontSize: 12 }, visible: true },
        },
      ],
    }))
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'radar',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['radial'], ease: 'linear', duration: 1 },
        loop: { enable: true, interval: 2, atmosphere: { effect: 'ripple', ease: 'linear' } },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2.2 none(ループ) + atmosphere(reveal)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes, Builder } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    Builder.updateSpec('radar', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
      axes: [
        {
          orient: 'radius',
          tick: { forceTickCount: 3 },
          grid: { smooth: false, style: { stroke: 'rgba(255,255,255,0.15)', lineWidth: 1 } },
          label: { visible: false, style: { fontFamily: 'D-DIN' } },
        },
        {
          orient: 'angle',
          sampling: false,
          tick: { visible: true, style: () => ({ visible: false }) },
          domainLine: { style: { visible: false } },
          grid: { style: () => ({ stroke: 'rgba(255,255,255,0.15)', lineDash: [2, 1], lineWidth: 1 }) },
          label: { style: { fontFamily: 'D-DIN', fill: 'rgba(255,255,255,0.65)', fontSize: 12 }, visible: true },
        },
      ],
    }))
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'radar',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['scale'], ease: 'linear', duration: 1 },
        loop: { enable: true, interval: 3, atmosphere: { effect: 'reveal', ease: 'linear' } },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2.3 none(ループ) + atmosphere(breath)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes, Builder } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    Builder.updateSpec('radar', (spec) => ({
      ...spec,
      point: { ...spec.point, style: { ...(spec.point && spec.point.style), lineWidth: 0, stroke: null } },
      axes: [
        {
          orient: 'radius',
          tick: { forceTickCount: 3 },
          grid: { smooth: false, style: { stroke: 'rgba(255,255,255,0.15)', lineWidth: 1 } },
          label: { visible: false, style: { fontFamily: 'D-DIN' } },
        },
        {
          orient: 'angle',
          sampling: false,
          tick: { visible: true, style: () => ({ visible: false }) },
          domainLine: { style: { visible: false } },
          grid: { style: () => ({ stroke: 'rgba(255,255,255,0.15)', lineDash: [2, 1], lineWidth: 1 }) },
          label: { style: { fontFamily: 'D-DIN', fill: 'rgba(255,255,255,0.65)', fontSize: 12 }, visible: true },
        },
      ],
    }))
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'radar',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['scale'], ease: 'linear', duration: 1 },
        loop: { enable: true, interval: 2, atmosphere: { effect: 'breath', ease: 'linear' } },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

散布図のアニメーション

すべての例は大画面テーマ(ダーク)をベースにしており、ドロップダウンでテーマを切り替えられます。 テーマ一覧の取得元: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json

1. 入場アニメーション

入場アニメーションは、散布点が初めて現れるときの表現を制御します。

1.1 scale(拡大縮小)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const THEME_URL = 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json'
const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

const DIMENSIONS = [{ id: 'HqlYadRdHJ5c', alias: 'Category' }]
const MEASURES = [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }]

const useLargeScreenThemes = () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch(THEME_URL)
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  return { themes, themeKey, setThemeKey }
}

const ThemeSelect = ({ themes, themeKey, onChange }) => (
  <select
    value={themeKey}
    onChange={(e) => onChange(e.target.value)}
    style={{
      marginBottom: 12,
      color: '#EAF7FF',
      background: 'rgba(0, 213, 255, 0.18)',
      border: '1px solid rgba(0, 213, 255, 0.45)',
      padding: '6px 12px',
    }}
  >
    {Object.entries(themes).map(([key, value]) => (
      <option key={key} value={key} style={{ color: '#000' }}>
        {value?.name || key}
      </option>
    ))}
  </select>
)

export default () => {
  const { themes, themeKey, setThemeKey } = useLargeScreenThemes()
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'scatter',
    dataset: DATASET,
    dimensions: DIMENSIONS,
    measures: MEASURES,
    label: { enable: false },
    animation: {
      enable: true,
      params: { appear: { enable: true, effects: ['scale'], ease: 'circInOut', duration: 1 } },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <ThemeSelect themes={themes} themeKey={themeKey} onChange={setThemeKey} />
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

1.2 fadeIn(フェードイン)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'scatter',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: { appear: { enable: true, effects: ['fadeIn'], ease: 'circInOut', duration: 1 } },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2. ループアニメーション

散布図のループアニメーションは通常、継続的なリズムを出すために atmosphere で表現します。

2.1 none(ループ) + atmosphere(ripple)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'scatter',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['scale'], ease: 'linear', duration: 1 },
        loop: { enable: true, interval: 2, atmosphere: { effect: 'ripple', ease: 'linear' } },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2.2 none(ループ) + atmosphere(breath)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'scatter',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['fadeIn'], ease: 'linear', duration: 1 },
        loop: { enable: true, interval: 2, atmosphere: { effect: 'breath', ease: 'linear' } },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}

2.3 none(ループ) + atmosphere(reveal)

import { useEffect, useState } from 'react'
import { VSeedRender } from '@components'
import { registerTokenThemes } from '@visactor/vseed'

const DATASET = [
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 24,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Labels',
    Tn97A7q0XVDq: 24,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Tables',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 40,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 40,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 20,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Storage',
    Tn97A7q0XVDq: 20,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 10,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Furn',
    Tn97A7q0XVDq: 10,
    '7ti8XuX4kcY1': '2022',
  },
  {
    10001: 'Sales',
    10002: 50,
    10003: 'Tn97A7q0XVDq',
    30001: '2023',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 50,
    '7ti8XuX4kcY1': '2023',
  },
  {
    10001: 'Sales',
    10002: 30,
    10003: 'Tn97A7q0XVDq',
    30001: '2022',
    HqlYadRdHJ5c: 'Art',
    Tn97A7q0XVDq: 30,
    '7ti8XuX4kcY1': '2022',
  },
]

export default () => {
  const [themes, setThemes] = useState({})
  const [themeKey, setThemeKey] = useState('')
  useEffect(() => {
    fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json')
      .then((r) => r.json())
      .then((remote) => {
        const registry = {}
        Object.entries(remote).forEach(([key, value]) => {
          const colors = value?.colors || []
          registry[key] = {
            baseTheme: 'dark',
            colorScheme: colors.length >= 2 ? colors : [colors[0] || '#00D5FF', '#227BFF'],
            linearColorScheme: [colors[0] || '#003B73', colors[1] || '#00D5FF'],
            textPrimary: '#EAF7FF',
            textSecondary: '#8FC7FF',
            borderColor: 'rgba(82, 191, 255, 0.28)',
            surfaceColor: '#071A33',
            tooltipBackgroundColor: 'rgba(5, 18, 38, 0.92)',
            axisGridColor: 'rgba(82, 191, 255, 0.18)',
            accentColor: colors[0] || '#00D5FF',
          }
        })
        registerTokenThemes(registry, { ensureRegisterAll: false })
        setThemes(remote)
        setThemeKey(Object.keys(remote)[0] || '')
      })
      .catch(() => {})
  }, [])
  if (!themeKey) return null
  const vseedConfig = {
    theme: themeKey,
    chartType: 'scatter',
    dataset: DATASET,
    dimensions: [{ id: 'HqlYadRdHJ5c', alias: 'Category' }],
    measures: [{ id: 'Tn97A7q0XVDq', alias: 'Sales' }],
    label: { enable: false },
    animation: {
      enable: true,
      params: {
        appear: { enable: true, effects: ['scale'], ease: 'linear', duration: 1 },
        loop: { enable: true, interval: 3, atmosphere: { effect: 'reveal', ease: 'linear' } },
      },
    },
  }
  return (
    <div style={{ padding: 16, background: '#061A33' }}>
      <select
        value={themeKey}
        onChange={(e) => setThemeKey(e.target.value)}
        style={{
          marginBottom: 12,
          color: '#EAF7FF',
          background: 'rgba(0, 213, 255, 0.18)',
          border: '1px solid rgba(0, 213, 255, 0.45)',
          padding: '6px 12px',
        }}
      >
        {Object.entries(themes).map(([key, value]) => (
          <option key={key} value={key} style={{ color: '#000' }}>
            {value?.name || key}
          </option>
        ))}
      </select>
      <VSeedRender vseed={vseedConfig} />
    </div>
  )
}