#Hoạt ảnh
Khả năng hoạt ảnh của VSeed hỗ trợ các giai đoạn xuất hiện, lặp, không khí, cập nhật và rời khỏi. Hướng dẫn này gom các ví dụ hoạt ảnh của bar, line/area, pie, radar và scatter để dễ so sánh cách cấu hình giữa các loại chart.
#Hoạt ảnh biểu đồ bar / column
Tất cả ví dụ đều dựa trên theme màn hình lớn (tối) và cung cấp danh sách thả xuống để chuyển đổi giữa các theme.
Danh sách theme được tải từ: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json
#1. Hoạt ảnh xuất hiện
Hoạt ảnh xuất hiện là chuyển động được phát khi chart lần đầu đi vào vùng nhìn.
Với series bar/column, hiệu ứng xuất hiện hiện hỗ trợ growth.
#1.1 growth (tăng trưởng)
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. Hoạt ảnh lặp
Hoạt ảnh lặp là chuyển động liên tục sau lần render đầu tiên, dùng để nhấn mạnh hoặc giữ nhịp động.
#2.1 highlight (nhấn mạnh nhóm) + atmosphere (hiệu ứng không khí)
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 (lặp) + atmosphere (hiệu ứng không khí)
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 (lặp) + atmosphere (hiệu ứng không khí)
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>
)
}#Hoạt ảnh biểu đồ line / area
Tất cả ví dụ đều dựa trên theme màn hình lớn (tối) và cung cấp danh sách thả xuống để chuyển đổi giữa các theme.
Danh sách theme được tải từ: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json
#1. Hoạt ảnh xuất hiện
Hoạt ảnh xuất hiện tạo dẫn nhập trực quan khi chart lần đầu vào trang.
#1.1 load (tải)
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 (tăng trưởng)
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. Hoạt ảnh lặp
Hoạt ảnh lặp giữ chart chuyển động theo chu kỳ trong trạng thái hiển thị kéo dài.
#2.1 load (lặp) + 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 (lặp) + 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>
)
}#Hoạt ảnh biểu đồ pie / donut / rose
Tất cả ví dụ đều dựa trên theme màn hình lớn (tối) và cung cấp danh sách thả xuống để chuyển đổi giữa các theme.
Danh sách theme được tải từ: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json
#1. Hoạt ảnh xuất hiện
Hoạt ảnh xuất hiện kiểm soát cách các sector lần đầu đi vào vùng nhìn.
#1.1 radial (hướng tâm)
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 (thu phóng)
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. Hoạt ảnh lặp
Hoạt ảnh lặp nhấn mạnh một số sector theo chu kỳ.
#2.1 enlarge (thay đổi kích thước)
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 (thay đổi tâm)
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>
)
}#Hoạt ảnh radar
Tất cả ví dụ đều dựa trên theme màn hình lớn (tối) và cung cấp danh sách thả xuống để chuyển đổi giữa các theme.
Danh sách theme được tải từ: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json
#1. Hoạt ảnh xuất hiện
Hoạt ảnh xuất hiện kiểm soát lần hiển thị đầu tiên của vùng radar và các điểm.
#1.1 radial (hướng tâm)
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 (thu phóng)
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. Hoạt ảnh lặp
Hoạt ảnh lặp của radar được biểu đạt qua atmosphere.effect để tạo chuyển động không khí liên tục.
#2.1 none (lặp) + 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 (lặp) + 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 (lặp) + 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>
)
}#Hoạt ảnh scatter
Tất cả ví dụ đều dựa trên theme màn hình lớn (tối) và cung cấp danh sách thả xuống để chuyển đổi giữa các theme.
Danh sách theme được tải từ: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json
#1. Hoạt ảnh xuất hiện
Hoạt ảnh xuất hiện kiểm soát cách các điểm scatter lần đầu xuất hiện.
#1.1 scale (thu phóng)
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 (mờ dần vào)
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. Hoạt ảnh lặp
Hoạt ảnh lặp của scatter thường được biểu đạt bằng atmosphere để tạo nhịp liên tục.
#2.1 none (lặp) + 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 (lặp) + 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 (lặp) + 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>
)
}