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)}%`;
}