dv/frontend/src/frameworks/components/configPanel/WordcloudConfig.tsx

167 lines
5.1 KiB
TypeScript

'use client';
import React, { useCallback } from 'react';
import { Select, Slider, InputNumber, Space, Typography, Radio } from 'antd';
import { useChartConfig } from '@/frameworks/hooks/useChartConfig';
const { Text } = Typography;
const SHAPES = [
{ label: '圆形', value: 'circle' },
{ label: '心形', value: 'cardioid' },
{ label: '菱形', value: 'diamond' },
{ label: '三角形', value: 'triangle' },
{ label: '五角星', value: 'star' },
{ label: '五边形', value: 'pentagon' },
];
const FONTS = [
{ label: '默认 (sans-serif)', value: 'sans-serif' },
{ label: '宋体', value: 'SimSun, serif' },
{ label: '微软雅黑', value: 'Microsoft YaHei, sans-serif' },
{ label: '等宽', value: 'monospace' },
{ label: 'Impact', value: 'Impact, sans-serif' },
];
const WEIGHTS = [
{ label: '正常', value: 'normal' },
{ label: '粗体', value: 'bold' },
{ label: '更粗', value: '900' },
];
export default function WordcloudConfig() {
const { chart, setStyle } = useChartConfig();
const wc = (chart?.style as any)?.wordcloud ?? {};
const config = {
shape: wc.shape ?? 'circle',
sizeMin: wc.sizeMin ?? 14,
sizeMax: wc.sizeMax ?? 80,
rotationMin: wc.rotationMin ?? -45,
rotationMax: wc.rotationMax ?? 45,
rotationStep: wc.rotationStep ?? 15,
gridSize: wc.gridSize ?? 8,
fontFamily: wc.fontFamily ?? 'sans-serif',
fontWeight: wc.fontWeight ?? 'bold',
colorMode: wc.colorMode ?? 'palette',
};
const update = useCallback(
(partial: Record<string, any>) => {
setStyle({ wordcloud: { ...config, ...partial } } as any);
},
[config, setStyle],
);
if (chart?.type !== 'wordcloud') return null;
return (
<Space direction="vertical" style={{ width: '100%' }} size={12}>
{/* Shape */}
<div>
<Text strong style={{ fontSize: 12 }}></Text>
<Select
value={config.shape}
onChange={(val) => update({ shape: val })}
options={SHAPES}
size="small"
style={{ width: '100%', marginTop: 4 }}
/>
</div>
{/* Font Size Range */}
<div>
<Text strong style={{ fontSize: 12 }}></Text>
<div style={{ display: 'flex', gap: 8, marginTop: 4, alignItems: 'center' }}>
<InputNumber
value={config.sizeMin}
min={8}
max={config.sizeMax - 1}
onChange={(val) => val !== null && update({ sizeMin: val })}
size="small"
style={{ width: 70 }}
/>
<Text style={{ fontSize: 12 }}></Text>
<InputNumber
value={config.sizeMax}
min={config.sizeMin + 1}
max={200}
onChange={(val) => val !== null && update({ sizeMax: val })}
size="small"
style={{ width: 70 }}
/>
<Text style={{ fontSize: 12, color: '#999' }}>px</Text>
</div>
</div>
{/* Rotation Range */}
<div>
<Text strong style={{ fontSize: 12 }}>: [{config.rotationMin}°, {config.rotationMax}°]</Text>
<Slider
range
min={-90}
max={90}
value={[config.rotationMin, config.rotationMax]}
onChange={([min, max]) => update({ rotationMin: min, rotationMax: max })}
style={{ marginTop: 4 }}
/>
</div>
{/* Grid Size (word gap) */}
<div>
<Text strong style={{ fontSize: 12 }}>: {config.gridSize}px</Text>
<Slider
min={2}
max={30}
value={config.gridSize}
onChange={(val) => update({ gridSize: val })}
style={{ marginTop: 4 }}
/>
</div>
{/* Font Family */}
<div>
<Text strong style={{ fontSize: 12 }}></Text>
<Select
value={config.fontFamily}
onChange={(val) => update({ fontFamily: val })}
options={FONTS}
size="small"
style={{ width: '100%', marginTop: 4 }}
/>
</div>
{/* Font Weight */}
<div>
<Text strong style={{ fontSize: 12 }}></Text>
<Select
value={config.fontWeight}
onChange={(val) => update({ fontWeight: val })}
options={WEIGHTS}
size="small"
style={{ width: '100%', marginTop: 4 }}
/>
</div>
{/* Color Mode */}
<div>
<Text strong style={{ fontSize: 12 }}></Text>
<div style={{ marginTop: 4 }}>
<Radio.Group
value={config.colorMode}
onChange={(e) => update({ colorMode: e.target.value })}
size="small"
>
<Radio.Button value="palette"></Radio.Button>
<Radio.Button value="random"></Radio.Button>
<Radio.Button value="gradient"></Radio.Button>
</Radio.Group>
</div>
<Text style={{ fontSize: 11, color: '#999', marginTop: 2, display: 'block' }}>
{config.colorMode === 'palette' ? '使用颜色面板中的预设色板' : config.colorMode === 'random' ? '每个词随机颜色' : '按词频从高到低渐变'}
</Text>
</div>
</Space>
);
}