import type { EChartsOption } from 'echarts'; export function getSmartMaxAndInterval(data1: number[], data2: number[], maxSplits = 5) { const maxVal = Math.max(...data1, ...data2, 0); if (maxVal === 0) return { max: maxSplits, interval: 1 }; const roughInterval = maxVal / maxSplits; const magnitude = Math.pow(10, Math.floor(Math.log10(roughInterval))); const residual = roughInterval / magnitude; let niceInterval: number; if (residual <= 1) niceInterval = 1; else if (residual <= 2) niceInterval = 2; else if (residual <= 5) niceInterval = 5; else niceInterval = 10; const interval = niceInterval * magnitude; return { max: interval * maxSplits, interval }; } const ADMIN_PIE_COLORS = ['#2d6fcf', '#4fa0a8', '#5e9acc', '#b29f60', '#af5172', '#8a61d2', '#6f2c9b', '#9d7173', '#887456', '#9eac71']; const NON_ADMIN_PIE_COLORS = ['#7bb06c', '#489d61', '#4e97a6', '#3953d4', '#4e3ec5', '#6f2c9b', '#9d7173', '#887456', '#9eac71', '#8e5aac']; export function buildAdminPieOption(data: { value: number; name: string }[]): EChartsOption { const coloredData = data.map((item, index) => ({ ...item, itemStyle: { color: ADMIN_PIE_COLORS[index % ADMIN_PIE_COLORS.length] } })); return { tooltip: { trigger: 'item', formatter: '{b}:
{c} ({d}%)', backgroundColor: 'rgba(0, 0, 0, .5)', textStyle: { fontSize: 12, color: 'rgba(255, 255, 255, .8)' } }, legend: { bottom: '0', show: true, textStyle: { color: '#809ABE', fontSize: 12 }, icon: 'circle', itemWidth: 6.5, itemHeight: 6.5, itemGap: 15 }, color: ADMIN_PIE_COLORS, series: [ { name: '行政执法类别数量及占比', type: 'pie', radius: [52, 72], center: ['50%', '50%'], itemStyle: { borderRadius: 1 }, label: { show: true, formatter: '{b|{b}} ' + '{b|:}' + '\n ' + '{a|{c}}' + '{d| ({d}%)}', rich: { a: { color: '#3aaefb', fontSize: 12, lineHeight: 16 }, b: { color: 'rgba(255, 255, 255, .6)', fontSize: 12, lineHeight: 16 }, d: { color: '#3aaefb', fontSize: 12 } } }, labelLine: { show: true, length: 20, length2: 5, smooth: 0 }, data: coloredData } ] }; } export function buildNonAdminPieOption(data: { value: number; name: string }[]): EChartsOption { const coloredData = data.map((item, index) => ({ ...item, itemStyle: { color: NON_ADMIN_PIE_COLORS[index % NON_ADMIN_PIE_COLORS.length] } })); return { tooltip: { trigger: 'item', formatter: '{b}:
{c} ({d}%)', backgroundColor: 'rgba(0, 0, 0, .5)', textStyle: { fontSize: 12, color: 'rgba(255, 255, 255, .8)' } }, legend: { bottom: '-1%', show: true, textStyle: { color: '#809ABE', fontSize: 12 }, icon: 'circle', itemWidth: 6.5, itemHeight: 6.5, itemGap: 15 }, color: NON_ADMIN_PIE_COLORS, series: [ { name: '非行政执法类别数量及占比', type: 'pie', radius: [52, 72], center: ['50%', '40%'], itemStyle: { borderRadius: 1 }, label: { show: true, formatter: '{b|{b}} ' + '{b|:}' + '\n ' + '{a|{c}}' + '{d| ({d}%)}', rich: { a: { color: '#3aaefb', fontSize: 12, lineHeight: 16 }, b: { color: 'rgba(255, 255, 255, .6)', fontSize: 12, lineHeight: 16 }, d: { color: '#3aaefb', fontSize: 12 } } }, labelLine: { show: true, length: 20, length2: 5, smooth: 0 ,maxSurfaceAngle: 80 }, data: coloredData } ] }; } export function buildTrendLineOption( times: string[], ins: number[], ent: number[], insName: string, entName: string, leftAxisName: string, rightAxisName: string = '被执法企业数量(家)' ): EChartsOption { const { max, interval } = getSmartMaxAndInterval(ins, ent, 5); return { tooltip: { trigger: 'axis', backgroundColor: 'rgba(0, 0, 0, .5)', borderColor: 'rgba(46, 130, 253, .5)', axisPointer: { lineStyle: { color: 'rgba(255, 255, 255, .8)' } }, textStyle: { fontSize: 12, color: 'rgba(255, 255, 255, .8)' } }, legend: { data: [insName, entName], right: 0, top: 0, itemGap: 15, textStyle: { fontSize: 12, color: 'rgba(255,255,255,.5)' } }, color: ['rgba(69,139,252,1)', '#f59036'], grid: { left: '3%', right: '3%', bottom: '0', containLabel: true }, xAxis: [ { type: 'category', boundaryGap: false, axisLabel: { show: true, color: 'rgba(255,255,255,.5)' }, axisLine: { lineStyle: { color: 'rgba(255,255,255,.1)' } }, data: times } ], yAxis: [ { type: 'value', name: leftAxisName, position: 'left', axisLine: { lineStyle: { color: 'rgba(69,139,252,.7)' } }, axisLabel: { formatter: '{value}', color: 'rgba(69,139,252,.7)' }, splitLine: { lineStyle: { color: 'rgba(255,255,255,.1)' } }, max, interval }, { type: 'value', name: rightAxisName, position: 'right', axisLine: { lineStyle: { color: 'rgba(245,144,54,.65)' } }, axisLabel: { formatter: '{value}', color: 'rgba(245,144,54,.65)' }, splitLine: { lineStyle: { color: 'rgba(255,255,255,.1)' } }, max, interval } ], series: [ { name: insName, type: 'line', lineStyle: { width: 2, type: 'solid', color: 'rgba(69,139,252,1)' }, itemStyle: { color: 'rgba(69,139,252,1)' }, symbol: 'circle', symbolSize: 6, label: { show: false }, data: ins }, { name: entName, type: 'line', yAxisIndex: 1, lineStyle: { width: 2, type: 'solid', color: 'rgba(245,144,54,.7)' }, itemStyle: { color: 'rgba(245,144,54,.7)' }, symbol: 'circle', symbolSize: 6, label: { show: false }, data: ent } ] }; } export function buildRankBarOption(names: string[], values: number[], seriesName: string): EChartsOption { return { tooltip: { trigger: 'axis' }, grid: { left: '5%', right: '0', bottom: '5%', top: '15%', containLabel: true }, xAxis: [ { type: 'category', data: names, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { interval: 0, rotate: 30, color: '#999', fontSize: 12, formatter: (value: string) => (value.length > 6 ? value.slice(0, 6) + '...' : value) } } ], yAxis: [ { type: 'value', splitLine: { show: true, lineStyle: { color: 'transparent' } }, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: '#79a3ca', fontSize: 12 } } ], series: [ { name: seriesName, type: 'bar', barWidth: '25%', showBackground: true, backgroundStyle: { borderRadius: [5, 5, 0, 0] }, itemStyle: { borderRadius: [5, 5, 0, 0], color: '#7ca9fb' }, label: { show: true, position: 'outside' }, data: values } ] }; } export function buildAutoRatePieOption(data: { value: number; name: string }[]): EChartsOption { return { tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, series: [ { name: '委托执法数量及占比', type: 'pie', radius: ['42%', '60%'], padAngle: 3, itemStyle: { borderColor: '#fff', borderWidth: 1 }, label: { show: true, formatter: '{b|{b}}: {a|{c}}{d| ({d}%)}', rich: { a: { color: 'rgba(0, 0, 0, .6)', fontSize: 12 }, b: { color: 'rgba(0, 0, 0, .85)', fontSize: 12 }, d: { color: 'rgba(0, 0, 0, .6)', fontSize: 12 } } }, labelLine: { show: true, length: 30, length2: 50 }, color: ['#5292fc', '#91cc75', '#fac858', '#ef6667', '#73c0df', '#3ba272', '#fc8352', '#9b60b4', '#9ebafc', '#0047eb'], data } ] }; } const RING_COLORS = ['#5292fc', '#91cc75', '#fac858', '#ef6667', '#73c0df', '#3ba272', '#fc8352', '#9b60b4', '#9ebafc', '#0047eb']; export function buildRingPieOption(name: string, data: { value: number; name: string }[]): EChartsOption { return { tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, series: [ { name, type: 'pie', radius: ['42%', '60%'], padAngle: 3, itemStyle: { borderColor: '#fff', borderWidth: 1 }, labelLine: { show: true, length: 30, length2: 10 }, color: RING_COLORS, data } ] }; } function toPercent(value: number, total: number) { if (!total) return '0%'; return `${((value / total) * 100).toFixed(2)}%`; } export function buildHorizontalBarOption( categories: string[], values: number[], color: string, bgColor: string ): EChartsOption { const total = values.reduce((s, v) => s + v, 0); const percentData = values.map((v) => toPercent(v, total)); return { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, grid: { left: '3%', right: '15%', bottom: '3%', containLabel: true }, xAxis: { type: 'value', show: false, splitLine: { show: false } }, yAxis: { type: 'category', data: categories, axisLine: { show: false }, axisTick: { show: false }, splitLine: { show: false } }, series: [ { name: '数量', type: 'bar', data: values.map((value, index) => ({ value, percent: percentData[index] })), barWidth: 12, showBackground: true, backgroundStyle: { borderRadius: 10, color: bgColor }, label: { show: true, position: 'right', color: '#666', formatter: '{c} ({@percent})' }, itemStyle: { borderRadius: 8, color } } ] }; } export function buildDistrictBarOption( districts: string[], singleLaw: number[], unionLaw: number[], enfEnt: number[], entEeduction: number[], compAddress: number[], entAddress: number[] ): EChartsOption { return { tooltip: { trigger: 'axis' }, legend: { top: 0, right: 0, itemGap: 20, itemWidth: 10, itemHeight: 10, data: ['单独执法', '联合执法', '被执法企业', '入企压减率', '涉企投诉', '企业数量'] }, grid: { left: '0', right: '0', bottom: '0', top: '20%', containLabel: true }, xAxis: [{ type: 'category', data: districts, axisLine: { show: false }, axisTick: { show: false } }], yAxis: [{ type: 'value', splitLine: { lineStyle: { color: 'rgba(255,255,255,.1)' } } }], series: [ { name: '单独执法', type: 'bar', stack: 'total', data: singleLaw, color: '#5292fc' }, { name: '联合执法', type: 'bar', stack: 'total', data: unionLaw, color: '#91cc75' }, { name: '被执法企业', type: 'bar', data: enfEnt, color: '#fac858' }, { name: '入企压减率', type: 'bar', data: entEeduction, color: '#ef6667' }, { name: '涉企投诉', type: 'bar', data: compAddress, color: '#73c0df' }, { name: '企业数量', type: 'bar', data: entAddress, color: '#3ba272' } ] }; } export function mapObjectArrayToPie(data: Record[]) { return data.map((obj) => { const [name, value] = Object.entries(obj)[0]; return { name, value: Number(value) }; }); } export function formatPercent(value: string | number, digits = 2) { return `${parseFloat(String(value)).toFixed(digits)}%`; }