167 lines
5.1 KiB
TypeScript
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>
|
|
);
|
|
}
|