const fmt = value => new Intl.NumberFormat('zh-CN', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(value); /** Pure option builder; the calculator owns chart mounting, resizing and disposal. */ export function buildDCFPriceOption({range, price, colors, width, reducedMotion = false}) { const {low, high, perShare} = range; const max = Math.max(high * 1.3, perShare * 1.6, price === undefined ? 0 : price * 1.2); if (!Number.isFinite(max)) throw new Error('股价超出有效图表范围'); const plotWidth = Math.max(1, width - 40); const label = (text, amount) => ({show: amount / max * plotWidth > 40, position: 'inside', formatter: text, color: colors.ink, fontSize: 12}); const series = [ {name: `低估:低于 ${fmt(low)} 元`, type: 'bar', stack: 'zone', data: [low], barWidth: 42, itemStyle: {color: colors.low}, label: label('低估', low)}, {name: `合理:${fmt(low)}—${fmt(high)} 元`, type: 'bar', stack: 'zone', data: [high - low], itemStyle: {color: colors.fair}, label: label('合理', high - low), markLine: {silent: true, symbol: ['none', 'none'], lineStyle: {type: 'dashed', color: colors.accent, width: 1.5}, label: {show: true, position: 'end', formatter: `DCF ${fmt(perShare)}`, align: perShare / max < .18 ? 'left' : 'center', color: colors.ink, fontSize: 12}, data: [{xAxis: perShare}]}}, {name: `高估:高于 ${fmt(high)} 元`, type: 'bar', stack: 'zone', data: [max - high], itemStyle: {color: colors.high}, label: label('高估', max - high)}, ]; if (price !== undefined) series.push({name: '当前股价', type: 'scatter', data: [[price, 0]], symbol: 'triangle', symbolSize: 13, symbolOffset: [0, 31], itemStyle: {color: colors.accent}, z: 6, label: {show: true, position: price / max < .12 ? 'right' : 'bottom', distance: 5, formatter: fmt(price), color: colors.ink, fontSize: 12}}); return { animation: !reducedMotion, animationDuration: 0, animationDurationUpdate: 260, grid: {left: 16, right: 24, top: 45, bottom: 65}, tooltip: {trigger: 'item', confine: true, backgroundColor: colors.paper, borderColor: colors.line, textStyle: {color: colors.ink, fontSize: 12}, formatter: params => params.seriesType === 'scatter' ? `当前股价:${fmt(price)} 元/股` : params.seriesName}, xAxis: {type: 'value', min: 0, max, splitNumber: width < 400 ? 3 : 5, name: '股价(元/股)', nameLocation: 'middle', nameGap: 42, nameTextStyle: {color: colors.muted, fontSize: 11}, axisLine: {show: true, lineStyle: {color: colors.line}}, axisTick: {show: false}, splitLine: {show: false}, axisLabel: {color: colors.muted, fontSize: 11, formatter: value => Number(value).toFixed(0), hideOverlap: true}}, yAxis: {type: 'category', data: ['价格区间'], axisLine: {show: false}, axisTick: {show: false}, axisLabel: {show: false}}, series, }; }