ECharts 社交媒体分析平台实战
📋 项目概述
构建社交媒体数据分析系统,分析用户情感、话题趋势、影响力传播等社交网络数据。
核心功能
- ✅ 情感分析:正负面情绪分布
- ✅ 话题聚类:词云与关系图
- ✅ 传播分析:信息扩散路径
- ✅ 影响力排行:KOL分析
- ✅ 时间趋势:热度变化曲线
💻 核心实现
1. 情感分析仪表盘
typescript
// components/Social/SentimentGauge.tsx
import React, { useMemo } from 'react';
import * as echarts from 'echarts/core';
import { GaugeChart, PieChart } from 'echarts/charts';
import BaseChart from '@/components/Chart/BaseChart';
interface SentimentData {
positive: number;
neutral: number;
negative: number;
score: number; // 0-100
}
interface SentimentGaugeProps {
data: SentimentData;
title?: string;
}
const SentimentGauge: React.FC<SentimentGaugeProps> = ({ data, title }) => {
const gaugeOption = useMemo(() => ({
series: [
{
type: 'gauge',
startAngle: 180,
endAngle: 0,
min: 0,
max: 100,
splitNumber: 5,
radius: '100%',
center: ['50%', '70%'],
progress: {
show: true,
width: 30,
itemStyle: {
color: getColorByScore(data.score)
}
},
pointer: {
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
length: '75%',
width: 16,
offsetCenter: [0, '5%']
},
axisLine: {
lineStyle: {
width: 30,
color: [
[0.3, '#ff4d4f'],
[0.7, '#faad14'],
[1, '#52c41a']
]
}
},
axisTick: {
distance: -30,
length: 8,
lineStyle: {
color: '#fff',
width: 2
}
},
splitLine: {
distance: -35,
length: 14,
lineStyle: {
color: '#fff',
width: 3
}
},
axisLabel: {
color: '#999',
fontSize: 14,
distance: -20,
formatter: (value: number) => {
if (value === 0) return '负面';
if (value === 50) return '中性';
if (value === 100) return '正面';
return '';
}
},
title: {
offsetCenter: [0, '30%'],
fontSize: 16,
color: '#666'
},
detail: {
fontSize: 32,
offsetCenter: [0, '0%'],
valueAnimation: true,
formatter: (value: number) => {
return `{value|${value.toFixed(0)}}{unit|分}`;
},
rich: {
value: {
fontSize: 32,
fontWeight: 'bold',
color: getColorByScore(data.score)
},
unit: {
fontSize: 16,
color: '#999',
padding: [0, 0, 0, 4]
}
}
},
data: [{
value: data.score,
name: title || '情感指数'
}]
}
]
}), [data.score, title]);
const pieOption = useMemo(() => ({
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
data: ['正面', '中性', '负面']
},
series: [
{
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}\n{d}%'
},
emphasis: {
label: {
show: true,
fontSize: '16',
fontWeight: 'bold'
}
},
data: [
{ value: data.positive, name: '正面', itemStyle: { color: '#52c41a' } },
{ value: data.neutral, name: '中性', itemStyle: { color: '#faad14' } },
{ value: data.negative, name: '负面', itemStyle: { color: '#ff4d4f' } }
]
}
]
}), [data]);
return (
<div style={{ display: 'flex', gap: '20px' }}>
<div style={{ flex: 1 }}>
<BaseChart option={gaugeOption} height={250} />
</div>
<div style={{ flex: 1 }}>
<BaseChart option={pieOption} height={250} />
</div>
</div>
);
};
function getColorByScore(score: number): string {
if (score >= 70) return '#52c41a';
if (score >= 40) return '#faad14';
return '#ff4d4f';
}
export default SentimentGauge;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
2. 话题词云图
typescript
// components/Social/WordCloudChart.tsx
import React, { useMemo } from 'react';
import * as echarts from 'echarts/core';
import 'echarts-wordcloud';
import BaseChart from '@/components/Chart/BaseChart';
interface WordData {
name: string;
value: number;
}
interface WordCloudChartProps {
words: WordData[];
title?: string;
}
const WordCloudChart: React.FC<WordCloudChartProps> = ({ words, title }) => {
const option = useMemo(() => ({
title: {
text: title || '话题词云',
left: 'center'
},
tooltip: {
show: true,
formatter: (params: any) => {
return `${params.name}: ${params.value}次`;
}
},
series: [{
type: 'wordCloud',
shape: 'circle',
left: 'center',
top: 'center',
width: '90%',
height: '90%',
right: null,
bottom: null,
sizeRange: [12, 60],
rotationRange: [-90, 90],
rotationStep: 45,
gridSize: 8,
drawOutOfBound: false,
textStyle: {
fontFamily: 'sans-serif',
fontWeight: 'bold',
color: () => {
const colors = [
'#5470c6', '#91cc75', '#fac858',
'#ee6666', '#73c0de', '#3ba272',
'#fc8452', '#9a60b4', '#ea7ccc'
];
return colors[Math.floor(Math.random() * colors.length)];
}
},
emphasis: {
focus: 'self',
textStyle: {
shadowBlur: 10,
shadowColor: '#333'
}
},
data: words
}]
}), [words, title]);
return <BaseChart option={option} height={500} />;
};
export default WordCloudChart;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
3. 传播路径图
typescript
// components/Social/PropagationGraph.tsx
import React, { useMemo } from 'react';
import * as echarts from 'echarts/core';
import { GraphChart } from 'echarts/charts';
import {
TooltipComponent,
LegendComponent,
AnimationComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import BaseChart from '@/components/Chart/BaseChart';
echarts.use([
GraphChart,
TooltipComponent,
LegendComponent,
AnimationComponent,
CanvasRenderer
]);
interface Node {
id: string;
name: string;
value: number;
category: number;
symbolSize: number;
}
interface Link {
source: string;
target: string;
value: number;
}
interface PropagationGraphProps {
nodes: Node[];
links: Link[];
categories: string[];
}
const PropagationGraph: React.FC<PropagationGraphProps> = ({
nodes,
links,
categories
}) => {
const option = useMemo(() => ({
title: {
text: '信息传播路径',
subtext: '节点大小表示影响力,连线粗细表示传播强度',
top: 'top',
left: 'left'
},
tooltip: {
formatter: (params: any) => {
if (params.dataType === 'node') {
return `
<div style="padding: 8px;">
<div style="font-weight: bold;">${params.data.name}</div>
<div>粉丝数: ${params.data.value.toLocaleString()}</div>
<div>类型: ${categories[params.data.category]}</div>
</div>
`;
} else {
return `
<div style="padding: 8px;">
<div>${params.data.source} → ${params.data.target}</div>
<div>转发量: ${params.data.value}</div>
</div>
`;
}
}
},
legend: {
data: categories,
orient: 'vertical',
right: 10,
top: 'center'
},
animationDuration: 1500,
animationEasingUpdate: 'quinticInOut',
series: [
{
type: 'graph',
layout: 'force',
data: nodes,
links: links,
categories: categories.map(name => ({ name })),
roam: true,
label: {
show: true,
position: 'right',
formatter: '{b}'
},
force: {
repulsion: 100,
gravity: 0.1,
edgeLength: 50,
layoutAnimation: true
},
lineStyle: {
color: 'source',
curveness: 0.3,
width: (params: any) => Math.sqrt(params.data.value) / 10
},
emphasis: {
focus: 'adjacency',
lineStyle: {
width: 10
}
}
}
]
}), [nodes, links, categories]);
return <BaseChart option={option} height={600} />;
};
export default PropagationGraph;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
4. 热度趋势图
typescript
// components/Social/HeatTrendChart.tsx
import React, { useMemo } from 'react';
import * as echarts from 'echarts/core';
import { LineChart, BarChart } from 'echarts/charts';
import {
GridComponent,
TooltipComponent,
LegendComponent,
DataZoomComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import BaseChart from '@/components/Chart/BaseChart';
echarts.use([
LineChart,
BarChart,
GridComponent,
TooltipComponent,
LegendComponent,
DataZoomComponent,
CanvasRenderer
]);
interface HeatTrendData {
time: string;
mentions: number;
sentiment: number;
engagement: number;
}
interface HeatTrendChartProps {
data: HeatTrendData[];
title?: string;
}
const HeatTrendChart: React.FC<HeatTrendChartProps> = ({ data, title }) => {
const option = useMemo(() => ({
title: {
text: title || '话题热度趋势'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['提及量', '情感指数', '互动量']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: data.map(d => d.time)
},
yAxis: [
{
type: 'value',
name: '提及量',
position: 'left'
},
{
type: 'value',
name: '情感指数',
min: 0,
max: 100,
position: 'right'
}
],
dataZoom: [
{
type: 'inside',
start: 0,
end: 100
},
{
start: 0,
end: 100
}
],
series: [
{
name: '提及量',
type: 'bar',
data: data.map(d => d.mentions),
itemStyle: {
color: '#5470c6'
}
},
{
name: '情感指数',
type: 'line',
yAxisIndex: 1,
data: data.map(d => d.sentiment),
smooth: true,
itemStyle: {
color: '#ee6666'
},
markLine: {
data: [
{ yAxis: 50, label: { formatter: '中性线' } }
]
}
},
{
name: '互动量',
type: 'line',
data: data.map(d => d.engagement),
smooth: true,
itemStyle: {
color: '#91cc75'
},
areaStyle: {
opacity: 0.3
}
}
]
}), [data, title]);
return <BaseChart option={option} height={400} />;
};
export default HeatTrendChart;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
💎 总结
本实战案例展示了社交媒体分析的核心可视化方案:
- 情感分析:仪表盘+饼图组合
- 话题挖掘:词云图展示关键词
- 传播分析:关系图呈现传播路径
- 热度追踪:多维度趋势分析
适用于舆情监控、品牌分析等场景。
