听力补偿新增左右耳曲线

This commit is contained in:
eafonyang
2026-07-14 14:30:55 +08:00
parent bfc4b45ca3
commit 4f3427b44f
9 changed files with 319 additions and 6 deletions
@@ -0,0 +1,179 @@
import { useEffect, useMemo, useRef } from "react";
import * as echarts from "echarts";
import {
HEARING_FREQ_LABELS,
buildHearingSplineSeries,
} from "@/lib/hearingCurve";
const LEFT_COLOR = "rgb(0, 255, 246)";
const RIGHT_COLOR = "#FF5A36";
type HearingCompensationChartProps = {
left: number[];
right: number[];
leftLabel?: string;
rightLabel?: string;
className?: string;
};
export default function HearingCompensationChart({
left,
right,
leftLabel = "Left",
rightLabel = "Right",
className,
}: HearingCompensationChartProps) {
const containerRef = useRef<HTMLDivElement>(null);
const chartRef = useRef<echarts.ECharts | null>(null);
const leftSeries = useMemo(() => buildHearingSplineSeries(left), [left]);
const rightSeries = useMemo(() => buildHearingSplineSeries(right), [right]);
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const option: echarts.EChartsOption = {
animation: false,
legend: {
show: true,
top: 8,
data: [
{
name: leftLabel,
icon: "circle",
textStyle: { color: LEFT_COLOR },
itemStyle: { color: LEFT_COLOR, borderColor: LEFT_COLOR },
},
{
name: rightLabel,
icon: "circle",
textStyle: { color: RIGHT_COLOR },
itemStyle: { color: RIGHT_COLOR, borderColor: RIGHT_COLOR },
},
],
textStyle: { fontSize: 13 },
},
grid: {
left: 48,
right: 36,
top: 48,
bottom: 28,
},
graphic: [
{
type: "text",
right: 10,
bottom: 6,
style: {
text: "Hz",
fill: "#999",
fontSize: 14,
},
z: 100,
},
],
xAxis: {
type: "value",
min: 0,
max: HEARING_FREQ_LABELS.length - 1,
interval: 1,
axisLabel: {
formatter: (value: number) => {
const idx = Math.round(value);
if (Math.abs(value - idx) > 1e-6) return "";
return HEARING_FREQ_LABELS[idx] ?? "";
},
fontSize: 14,
color: "#999",
},
splitLine: {
show: true,
lineStyle: { type: "solid", color: "#3A4348" },
},
axisLine: {
lineStyle: { color: "#3A4348" },
},
},
yAxis: {
type: "value",
min: -80,
max: 0,
interval: 10,
name: "dB",
nameLocation: "end",
nameGap: 8,
nameTextStyle: {
color: "#999",
fontSize: 14,
align: "right",
padding: [0, 8, 0, 0],
},
axisLabel: {
fontSize: 14,
color: "#999",
},
splitLine: {
show: true,
lineStyle: { type: "solid", color: "#3A4348" },
},
axisLine: {
lineStyle: { color: "#3A4348" },
},
},
series: [
{
name: leftLabel,
type: "line",
data: leftSeries,
showSymbol: false,
smooth: false,
lineStyle: { color: LEFT_COLOR, width: 2 },
itemStyle: { color: LEFT_COLOR },
},
{
name: rightLabel,
type: "line",
data: rightSeries,
showSymbol: false,
smooth: false,
lineStyle: { color: RIGHT_COLOR, width: 2 },
itemStyle: { color: RIGHT_COLOR },
},
],
};
if (!chartRef.current) {
chartRef.current = echarts.init(el, undefined, { renderer: "canvas" });
}
chartRef.current.setOption(option, true);
const onResize = () => chartRef.current?.resize();
const ro = new ResizeObserver(onResize);
ro.observe(el);
window.addEventListener("resize", onResize);
return () => {
ro.disconnect();
window.removeEventListener("resize", onResize);
};
}, [leftSeries, rightSeries, leftLabel, rightLabel]);
useEffect(() => {
return () => {
chartRef.current?.dispose();
chartRef.current = null;
};
}, []);
return (
<div
ref={containerRef}
className={
className
?? "w-full h-[min(42vw,200px)] min-h-[140px] md:h-[260px] lg:h-[300px] rounded-lg bg-black/30"
}
aria-hidden
/>
);
}