#애니메이션
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>
)
}