import { useMemo } from 'react'
import { Empty } from 'antd'
import ReactECharts from 'echarts-for-react'
import * as echarts from 'echarts'
/**
* 题材对比 — 饼图(期数占比)+ 水平柱图(按平均份额排名)
* 右列通高,对齐 l3_report.html 原型
*/
const TOPIC_COLORS = {
'装备深解': '#5b8db8',
'历史纵深': '#e8a838',
'前沿科技': '#7aa874',
'横切类比': '#9b7eb8',
'人物牵引': '#d4816b',
'事件战例': '#9b7eb8',
}
/**
* 将 hex 颜色转为 rgba 字符串
*/
function hexToRgba(hex, alpha) {
const h = hex.replace('#', '')
const r = parseInt(h.substring(0, 2), 16)
const g = parseInt(h.substring(2, 4), 16)
const b = parseInt(h.substring(4, 6), 16)
return `rgba(${r},${g},${b},${alpha})`
}
function TopicCompare({ episodes }) {
// 过滤有 program_format 的期次
const validEpisodes = useMemo(() => {
if (!episodes || !episodes.length) return []
return episodes.filter((ep) => ep.program_format != null && ep.program_format !== '')
}, [episodes])
// 按 program_format 分组统计
const topicStats = useMemo(() => {
if (!validEpisodes.length) return null
const groupMap = {}
validEpisodes.forEach((ep) => {
const format = ep.program_format
if (!groupMap[format]) {
groupMap[format] = []
}
groupMap[format].push(ep)
})
return Object.entries(groupMap).map(([name, eps]) => {
const count = eps.length
const withShare = eps.filter((ep) => ep.audience_share != null)
const avgShare =
withShare.length > 0
? withShare.reduce((sum, ep) => sum + Number(ep.audience_share), 0) / withShare.length
: 0
return {
name,
count,
avgShare: Number(avgShare.toFixed(4)),
}
})
}, [validEpisodes])
// 饼图配置
const pieOption = useMemo(() => {
if (!topicStats || !topicStats.length) return null
// 按期数排序
const sorted = [...topicStats].sort((a, b) => b.count - a.count)
const data = sorted.map((t) => ({ name: t.name, value: t.count }))
const colorArr = sorted.map((t) => TOPIC_COLORS[t.name] || '#999')
return {
tooltip: {
trigger: 'item',
formatter: (params) => {
return params.name + '
期数:' + params.value + ' 期(' + params.percent + '%)'
},
},
series: [
{
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '55%'],
data,
color: colorArr,
label: {
fontSize: 11,
formatter: '{b}\n{c}期 ({d}%)',
},
emphasis: {
label: { fontSize: 14, fontWeight: 'bold' },
},
},
],
}
}, [topicStats])
// 水平柱图配置
const barOption = useMemo(() => {
if (!topicStats || !topicStats.length) return null
// 按平均份额升序排列(最低在下,最高在上)
const sorted = [...topicStats].sort((a, b) => a.avgShare - b.avgShare)
const yData = sorted.map((t) => t.name)
const countMap = {}
sorted.forEach((t) => {
countMap[t.name] = t.count
})
// 每根柱子渐变填充 + 胶囊圆角,对齐原型 LinearGradient 写法
const barData = sorted.map((t) => {
const color = TOPIC_COLORS[t.name] || '#999'
return {
value: t.avgShare,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: hexToRgba(color, 0.15) },
{ offset: 1, color },
]),
borderRadius: [0, 10, 10, 0],
},
}
})
// x 轴范围
const values = sorted.map((t) => t.avgShare)
const dataMin = Math.min(...values)
const dataMax = Math.max(...values)
const xMin = Math.max(0, Math.floor((dataMin - 0.1) * 10) / 10)
const xMax = Math.ceil((dataMax + 0.1) * 10) / 10
return {
tooltip: {
trigger: 'axis',
formatter: (params) => {
const name = params[0].name
const count = countMap[name] || 0
return name + '
平均份额:' + Number(params[0].value).toFixed(3) + '
期数:' + count
},
},
grid: { left: 80, right: 30, top: 10, bottom: 30 },
xAxis: {
type: 'value',
min: xMin,
max: xMax,
},
yAxis: {
type: 'category',
data: yData,
axisLabel: { fontSize: 12 },
},
series: [
{
type: 'bar',
data: barData,
barWidth: '50%',
label: {
show: true,
position: 'right',
fontSize: 11,
formatter: (p) => p.value.toFixed(3),
},
},
],
}
}, [topicStats])
if (!validEpisodes.length) {
return