| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719 |
- import { palette as C, SCORE_BANDS } from './data'
- const tipOpt = {
- backgroundColor: 'rgba(12,43,94,.94)',
- borderWidth: 0,
- textStyle: { color: '#fff', fontSize: 12 },
- padding: [8, 12],
- extraCssText: 'box-shadow:0 6px 18px rgba(12,43,94,.3);border-radius:6px;',
- }
- function axisStyle(opts = {}) {
- return {
- axisLine: { lineStyle: { color: '#E4E9F2' } },
- axisTick: { show: false },
- axisLabel: { color: C.mut, fontSize: 11 },
- splitLine: { lineStyle: { color: '#F2F5FA', type: 'dashed' } },
- ...opts,
- }
- }
- // ===== 综合评分仪表盘 =====
- export function scoreGaugeOption(current, previous) {
- const segColors = [
- [SCORE_BANDS[0].hi / 10, SCORE_BANDS[0].axisColor],
- [SCORE_BANDS[1].hi / 10, SCORE_BANDS[1].axisColor],
- [SCORE_BANDS[2].hi / 10, SCORE_BANDS[2].axisColor],
- [SCORE_BANDS[3].hi / 10, SCORE_BANDS[3].axisColor],
- [1, SCORE_BANDS[4].axisColor],
- ]
- return {
- tooltip: {
- ...tipOpt,
- formatter: (p) => `${p.name}:${p.value.toFixed(2)} 分`,
- },
- series: [
- {
- type: 'gauge',
- startAngle: 215,
- endAngle: -35,
- min: 0,
- max: 10,
- radius: '88%',
- center: ['50%', '62%'],
- progress: { show: false },
- axisLine: {
- lineStyle: {
- width: 24,
- color: segColors,
- shadowColor: 'rgba(0,0,0,0.05)',
- shadowBlur: 6,
- },
- },
- pointer: {
- length: '64%',
- width: 5,
- itemStyle: { color: C.ink },
- },
- anchor: {
- show: true,
- showAbove: true,
- size: 14,
- itemStyle: {
- borderColor: C.ink,
- borderWidth: 2,
- color: '#fff',
- },
- },
- axisTick: {
- distance: -24,
- length: 6,
- lineStyle: { color: '#fff', width: 1 },
- },
- splitLine: {
- distance: -28,
- length: 12,
- lineStyle: { color: '#fff', width: 2 },
- },
- axisLabel: {
- distance: -36,
- color: C.mut,
- fontSize: 10,
- formatter: (v) => v.toFixed(0),
- },
- title: { show: false },
- detail: {
- valueAnimation: true,
- formatter: (v) => `{val|${v.toFixed(2)}}{sub|分}`,
- rich: {
- val: { fontSize: 38, fontWeight: 700, color: C.ink, fontFamily: 'DIN, sans-serif' },
- sub: { fontSize: 14, color: C.mut, padding: [0, 0, 0, 4] },
- },
- offsetCenter: [0, '72%'],
- },
- data: [{ value: current, name: '综合评分' }],
- },
- ],
- }
- }
- // ===== 一级类别综合得分(柱状对比 本期 vs 上期) =====
- export function categoryBarOption(currList, prevList) {
- const cats = currList.map((c) => c.name)
- return {
- tooltip: {
- ...tipOpt,
- trigger: 'axis',
- axisPointer: { type: 'shadow' },
- },
- legend: {
- data: ['本期', '上期'],
- top: 4,
- textStyle: { color: C.mut, fontSize: 11 },
- itemWidth: 12,
- },
- grid: { top: 30, left: 8, right: 24, bottom: 28, containLabel: true },
- xAxis: {
- type: 'category',
- data: cats,
- ...axisStyle({ axisLabel: { color: C.mut, fontSize: 11 } }),
- },
- yAxis: {
- type: 'value',
- min: 0,
- max: 10,
- interval: 2,
- ...axisStyle(),
- },
- series: [
- {
- name: '本期',
- type: 'bar',
- barWidth: 22,
- data: currList.map((c) => c.score.toFixed(2)),
- itemStyle: { color: C.brand, borderRadius: [3, 3, 0, 0] },
- label: { show: true, position: 'top', color: C.ink, fontSize: 11, fontWeight: 600 },
- },
- {
- name: '上期',
- type: 'bar',
- barWidth: 22,
- data: prevList.map((c) => c.score.toFixed(2)),
- itemStyle: { color: C.faint, borderRadius: [3, 3, 0, 0], opacity: 0.6 },
- label: { show: true, position: 'top', color: C.mut, fontSize: 11 },
- },
- ],
- }
- }
- // ===== 分项评分雷达图 =====
- export function factorRadarOption(factors) {
- return {
- tooltip: {
- ...tipOpt,
- formatter: (p) => `${p.name}:${p.value.toFixed(2)} 分`,
- },
- legend: {
- data: ['本期', '上期'],
- top: 4,
- textStyle: { color: C.mut, fontSize: 11 },
- itemWidth: 12,
- },
- radar: {
- indicator: factors.map((f) => ({ name: f.name, max: 10 })),
- center: ['50%', '56%'],
- radius: '68%',
- splitNumber: 5,
- axisName: { color: C.mut, fontSize: 11 },
- splitLine: { lineStyle: { color: '#E4E9F2' } },
- splitArea: {
- areaStyle: {
- color: ['#FAFBFE', '#fff'],
- },
- },
- axisLine: { lineStyle: { color: '#E4E9F2' } },
- },
- series: [
- {
- type: 'radar',
- symbolSize: 4,
- emphasis: { areaStyle: { opacity: 0.4 } },
- data: [
- {
- name: '本期',
- value: factors.map((f) => f.current),
- lineStyle: { color: C.brand, width: 2 },
- itemStyle: { color: C.brand },
- areaStyle: { color: 'rgba(21,82,208,0.18)' },
- },
- {
- name: '上期',
- value: factors.map((f) => f.previous),
- lineStyle: { color: C.faint, width: 1.5, type: 'dashed' },
- itemStyle: { color: C.faint },
- areaStyle: { color: 'rgba(152,165,189,0.18)' },
- },
- ],
- },
- ],
- }
- }
- // ===== 类别贡献度(横向条形图,按权重×得分贡献) =====
- export function categoryContribOption(categoryWeighted) {
- const cats = categoryWeighted.map((c) => c.name)
- return {
- tooltip: {
- ...tipOpt,
- trigger: 'axis',
- axisPointer: { type: 'shadow' },
- formatter: (p) => {
- const i = p[0].dataIndex
- const c = categoryWeighted[i]
- return `${c.name}<br/>权重 ${c.weight.toFixed(2)}% · 加权贡献 ${(c.score * c.weight / 100).toFixed(3)}`
- },
- },
- grid: { top: 16, left: 8, right: 24, bottom: 28, containLabel: true },
- xAxis: {
- type: 'value',
- min: 0,
- max: 10,
- ...axisStyle(),
- },
- yAxis: {
- type: 'category',
- data: cats,
- ...axisStyle({ axisLabel: { color: C.ink, fontSize: 11.5 } }),
- },
- series: [
- {
- type: 'bar',
- barWidth: 18,
- data: categoryWeighted.map((c) => c.score.toFixed(2)),
- itemStyle: {
- color: (params) => categoryWeighted[params.dataIndex].color,
- borderRadius: [0, 4, 4, 0],
- },
- label: { show: true, position: 'right', color: C.ink, fontSize: 11, fontWeight: 600 },
- markLine: {
- symbol: ['none', 'none'],
- data: [
- { xAxis: 4, lineStyle: { color: C.mut, type: 'dashed', width: 1 }, label: { show: true, position: 'end', formatter: '中性下沿 4', color: C.mut, fontSize: 10 } },
- { xAxis: 6, lineStyle: { color: C.mut, type: 'dashed', width: 1 }, label: { show: true, position: 'end', formatter: '中性上沿 6', color: C.mut, fontSize: 10 } },
- ],
- },
- },
- ],
- }
- }
- // ===== 4.2.2 新增板块 =====
- // ===== 双系列 营收/利润同比 =====
- export function revenueProfitLineOption(series) {
- const dates = series.map((s) => s[0])
- const rev = series.map((s) => s[1])
- const pro = series.map((s) => s[2])
- return {
- tooltip: {
- ...tipOpt,
- trigger: 'axis',
- axisPointer: { type: 'line' },
- valueFormatter: (v) => `${v.toFixed(2)}%`,
- },
- legend: {
- data: [
- { name: '营收同比', itemStyle: { color: C.brand } },
- { name: '利润同比', itemStyle: { color: C.up } },
- ],
- top: 4,
- textStyle: { color: C.mut, fontSize: 11 },
- itemWidth: 14,
- },
- grid: { top: 36, left: 50, right: 24, bottom: 32, containLabel: true },
- xAxis: { type: 'category', data: dates, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10.5 } }) },
- yAxis: { type: 'value', ...axisStyle({ axisLabel: { color: C.mut, fontSize: 11, formatter: '{value}%' } }) },
- series: [
- { name: '营收同比', type: 'line', data: rev, symbol: 'none', smooth: true, lineStyle: { color: C.brand, width: 2.2 } },
- { name: '利润同比', type: 'line', data: pro, symbol: 'none', smooth: true, lineStyle: { color: C.up, width: 2.2 } },
- ],
- }
- }
- // ===== PE / PB 估值带 =====
- function priceBandOption(series, opts, closeSeries) {
- const dates = series.map((s) => s[0])
- const mid = series.map((s) => s[1])
- const up = series.map((s) => s[2])
- const dn = series.map((s) => s[3])
- const cur = series.map((s) => s[4])
- const hasClose = Array.isArray(closeSeries) && closeSeries.length > 0
- const closeMap = hasClose ? new Map(closeSeries.map(([d, v]) => [d, v])) : null
- const closeAligned = hasClose ? dates.map((d) => (closeMap.has(d) ? closeMap.get(d) : null)) : []
- const lastClose = hasClose ? closeSeries[closeSeries.length - 1][1] : null
- const lastCloseDate = hasClose ? closeSeries[closeSeries.length - 1][0] : null
- const legendData = [
- { name: '当前', itemStyle: { color: opts.curColor } },
- { name: '上轨', itemStyle: { color: C.faint } },
- { name: '中枢', itemStyle: { color: C.mut } },
- { name: '下轨', itemStyle: { color: C.faint } },
- ]
- if (hasClose) legendData.push({ name: '指数走势', itemStyle: { color: C.warn } })
- return {
- tooltip: {
- ...tipOpt,
- trigger: 'axis',
- axisPointer: { type: 'line' },
- valueFormatter: (v) => v.toFixed(opts.toFixed || 2),
- },
- legend: {
- data: legendData,
- top: 4,
- textStyle: { color: C.mut, fontSize: 11 },
- itemWidth: 14,
- },
- grid: { top: 36, left: 50, right: hasClose ? 60 : 24, bottom: 32, containLabel: true },
- xAxis: { type: 'category', data: dates, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10.5 } }) },
- yAxis: hasClose
- ? [
- { type: 'value', name: opts.toFixed === 3 ? 'PB' : 'PE', nameTextStyle: { color: opts.curColor, fontSize: 10.5, padding: [0, 0, 0, -28] }, position: 'left', ...axisStyle() },
- { type: 'value', name: '指数走势', nameTextStyle: { color: C.warn, fontSize: 10.5, padding: [0, -28, 0, 0] }, position: 'right', ...axisStyle({ splitLine: { show: false } }) },
- ]
- : { type: 'value', ...axisStyle() },
- series: [
- { name: '下轨', type: 'line', data: dn, symbol: 'none', smooth: true, lineStyle: { color: C.faint, type: 'dashed', width: 1 }, z: 1 },
- { name: '上轨', type: 'line', data: up, symbol: 'none', smooth: true, lineStyle: { color: C.faint, type: 'dashed', width: 1 }, z: 1 },
- {
- name: '中枢', type: 'line', data: mid, symbol: 'none', smooth: true, lineStyle: { color: C.mut, width: 1.2 }, z: 2,
- areaStyle: {
- color: {
- type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
- colorStops: [
- { offset: 0, color: 'rgba(152,165,189,0.10)' },
- { offset: 1, color: 'rgba(152,165,189,0.02)' },
- ],
- },
- },
- },
- {
- name: '当前', type: 'line', data: cur, symbol: 'none', smooth: true, lineStyle: { color: opts.curColor, width: 2.4 }, z: 3,
- markPoint: {
- symbol: 'circle', symbolSize: 8,
- itemStyle: { color: opts.curColor, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => p.value.toFixed(opts.toFixed || 2), color: opts.curColor, fontSize: 10.5, position: 'top', fontWeight: 600 },
- data: [{ name: '当前', value: cur[cur.length - 1], xAxis: dates[dates.length - 1], yAxis: cur[cur.length - 1] }],
- },
- },
- ...(hasClose ? [{
- name: '指数走势',
- type: 'line',
- yAxisIndex: 1,
- data: closeAligned,
- symbol: 'none',
- smooth: true,
- lineStyle: { color: C.warn, width: 1.6 },
- z: 4,
- markPoint: {
- symbol: 'circle', symbolSize: 7,
- itemStyle: { color: C.warn, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => p.value.toFixed(0), color: C.warn, fontSize: 10, position: 'bottom', fontWeight: 600 },
- data: [{ name: '当前指数走势', value: lastClose, xAxis: lastCloseDate, yAxis: lastClose }],
- },
- }] : []),
- ],
- }
- }
- export function peBandOption(series, closeSeries) { return priceBandOption(series, { curColor: C.brand, toFixed: 2 }, closeSeries) }
- export function pbBandOption(series, closeSeries) { return priceBandOption(series, { curColor: C.brandD, toFixed: 3 }, closeSeries) }
- // ===== 股息率 =====
- export function dividendLineOption(series, closeSeries) {
- const dates = series.map((s) => s[0])
- const values = series.map((s) => s[1])
- const hasClose = Array.isArray(closeSeries) && closeSeries.length > 0
- const closeMap = hasClose ? new Map(closeSeries.map(([d, v]) => [d, v])) : null
- const closeAligned = hasClose ? dates.map((d) => (closeMap.has(d) ? closeMap.get(d) : null)) : []
- const lastClose = hasClose ? closeSeries[closeSeries.length - 1][1] : null
- const lastCloseDate = hasClose ? closeSeries[closeSeries.length - 1][0] : null
- const legendData = [{ name: '股息率', itemStyle: { color: C.warn } }]
- if (hasClose) legendData.push({ name: '指数走势', itemStyle: { color: C.brand } })
- return {
- tooltip: {
- ...tipOpt,
- trigger: 'axis',
- axisPointer: { type: 'line' },
- valueFormatter: (v) => `${v.toFixed(2)}%`,
- },
- legend: {
- data: legendData,
- top: 4,
- textStyle: { color: C.mut, fontSize: 11 },
- itemWidth: 14,
- },
- grid: { top: 36, left: 50, right: hasClose ? 60 : 24, bottom: 32, containLabel: true },
- xAxis: { type: 'category', data: dates, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10.5 } }) },
- yAxis: hasClose
- ? [
- { type: 'value', name: '股息率', nameTextStyle: { color: C.warn, fontSize: 10.5, padding: [0, 0, 0, -28] }, position: 'left', ...axisStyle({ axisLabel: { color: C.mut, fontSize: 11, formatter: '{value}%' } }) },
- { type: 'value', name: '指数走势', nameTextStyle: { color: C.brand, fontSize: 10.5, padding: [0, -28, 0, 0] }, position: 'right', ...axisStyle({ splitLine: { show: false } }) },
- ]
- : { type: 'value', ...axisStyle({ axisLabel: { color: C.mut, fontSize: 11, formatter: '{value}%' } }) },
- series: [
- {
- name: '股息率',
- type: 'line',
- data: values,
- symbol: 'none',
- smooth: true,
- lineStyle: { color: C.warn, width: 2.4 },
- areaStyle: {
- color: {
- type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
- colorStops: [
- { offset: 0, color: 'rgba(232,145,45,0.28)' },
- { offset: 1, color: 'rgba(232,145,45,0.02)' },
- ],
- },
- },
- markPoint: {
- symbol: 'circle', symbolSize: 8,
- itemStyle: { color: C.warn, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => `${p.value.toFixed(2)}%`, color: C.warn, fontSize: 10.5, position: 'top', fontWeight: 600 },
- data: [{ name: '当前', value: values[values.length - 1], xAxis: dates[dates.length - 1], yAxis: values[values.length - 1] }],
- },
- },
- ...(hasClose ? [{
- name: '指数走势',
- type: 'line',
- yAxisIndex: 1,
- data: closeAligned,
- symbol: 'none',
- smooth: true,
- lineStyle: { color: C.brand, width: 1.6 },
- markPoint: {
- symbol: 'circle', symbolSize: 7,
- itemStyle: { color: C.brand, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => p.value.toFixed(0), color: C.brand, fontSize: 10, position: 'bottom', fontWeight: 600 },
- data: [{ name: '当前指数走势', value: lastClose, xAxis: lastCloseDate, yAxis: lastClose }],
- },
- }] : []),
- ],
- }
- }
- // ===== ERP =====
- export function erpLineOption(series, closeSeries) {
- const dates = series.map((s) => s[0])
- const values = series.map((s) => s[1])
- const hasClose = Array.isArray(closeSeries) && closeSeries.length > 0
- const closeMap = hasClose ? new Map(closeSeries.map(([d, v]) => [d, v])) : null
- const closeAligned = hasClose ? dates.map((d) => (closeMap.has(d) ? closeMap.get(d) : null)) : []
- const lastClose = hasClose ? closeSeries[closeSeries.length - 1][1] : null
- const lastCloseDate = hasClose ? closeSeries[closeSeries.length - 1][0] : null
- const legendData = [{ name: 'ERP', itemStyle: { color: C.brandD } }]
- if (hasClose) legendData.push({ name: '指数走势', itemStyle: { color: C.warn } })
- return {
- tooltip: {
- ...tipOpt,
- trigger: 'axis',
- axisPointer: { type: 'line' },
- valueFormatter: (v) => `${v.toFixed(2)}%`,
- },
- legend: {
- data: legendData,
- top: 4,
- textStyle: { color: C.mut, fontSize: 11 },
- itemWidth: 14,
- },
- grid: { top: 36, left: 50, right: hasClose ? 60 : 24, bottom: 32, containLabel: true },
- xAxis: { type: 'category', data: dates, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10.5 } }) },
- yAxis: hasClose
- ? [
- { type: 'value', name: 'ERP', nameTextStyle: { color: C.brandD, fontSize: 10.5, padding: [0, 0, 0, -28] }, position: 'left', ...axisStyle({ axisLabel: { color: C.mut, fontSize: 11, formatter: '{value}%' } }) },
- { type: 'value', name: '指数走势', nameTextStyle: { color: C.warn, fontSize: 10.5, padding: [0, -28, 0, 0] }, position: 'right', ...axisStyle({ splitLine: { show: false } }) },
- ]
- : { type: 'value', ...axisStyle({ axisLabel: { color: C.mut, fontSize: 11, formatter: '{value}%' } }) },
- series: [
- {
- name: 'ERP',
- type: 'line',
- data: values,
- symbol: 'none',
- smooth: true,
- lineStyle: { color: C.brandD, width: 2.4 },
- areaStyle: {
- color: {
- type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
- colorStops: [
- { offset: 0, color: 'rgba(14,58,150,0.28)' },
- { offset: 1, color: 'rgba(14,58,150,0.02)' },
- ],
- },
- },
- // markLine: {
- // symbol: ['none', 'none'],
- // data: [
- // { yAxis: 4.0, lineStyle: { color: C.mut, type: 'dashed', width: 1 }, label: { show: true, position: 'end', formatter: '历史中枢', color: C.mut, fontSize: 10 } },
- // ],
- // },
- markPoint: {
- symbol: 'circle', symbolSize: 8,
- itemStyle: { color: C.brandD, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => `${p.value.toFixed(2)}%`, color: C.brandD, fontSize: 10.5, position: 'top', fontWeight: 600 },
- data: [{ name: '当前', value: values[values.length - 1], xAxis: dates[dates.length - 1], yAxis: values[values.length - 1] }],
- },
- },
- ...(hasClose ? [{
- name: '指数走势',
- type: 'line',
- yAxisIndex: 1,
- data: closeAligned,
- symbol: 'none',
- smooth: true,
- lineStyle: { color: C.warn, width: 1.6 },
- markPoint: {
- symbol: 'circle', symbolSize: 7,
- itemStyle: { color: C.warn, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => p.value.toFixed(0), color: C.warn, fontSize: 10, position: 'bottom', fontWeight: 600 },
- data: [{ name: '当前指数走势', value: lastClose, xAxis: lastCloseDate, yAxis: lastClose }],
- },
- }] : []),
- ],
- }
- }
- // ===== 换手率 + 成交额 双轴 =====
- export function liquidityBarLineOption(series, closeSeries) {
- const dates = series.map((s) => s[0])
- const turnover = series.map((s) => s[1])
- const volume = series.map((s) => s[2])
- const hasClose = Array.isArray(closeSeries) && closeSeries.length > 0
- const closeMap = hasClose ? new Map(closeSeries.map(([d, v]) => [d, v])) : null
- const closeAligned = hasClose ? dates.map((d) => (closeMap.has(d) ? closeMap.get(d) : null)) : []
- const lastClose = hasClose ? closeSeries[closeSeries.length - 1][1] : null
- const lastCloseDate = hasClose ? closeSeries[closeSeries.length - 1][0] : null
- const legendData = [
- { name: '换手率', itemStyle: { color: C.brand } },
- { name: '成交额(千亿港元)', itemStyle: { color: C.up } },
- ]
- if (hasClose) legendData.push({ name: '指数走势', itemStyle: { color: C.warn } })
- return {
- tooltip: { ...tipOpt, trigger: 'axis', axisPointer: { type: 'shadow' } },
- legend: {
- data: legendData,
- top: 4,
- textStyle: { color: C.mut, fontSize: 11 },
- itemWidth: 14,
- },
- grid: { top: 36, left: 50, right: 60, bottom: 32, containLabel: true },
- xAxis: { type: 'category', data: dates, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10.5 } }) },
- yAxis: [
- { type: 'value', name: '换手率(%)', ...axisStyle({ axisLabel: { color: C.mut, fontSize: 11, formatter: '{value}%' }, nameTextStyle: { color: C.mut, fontSize: 10 } }) },
- { type: 'value', name: hasClose ? '成交额(千亿) / 指数走势' : '成交额(千亿)', ...axisStyle({ axisLabel: { color: C.mut, fontSize: 11 }, nameTextStyle: { color: C.mut, fontSize: 10 } }) },
- ],
- series: [
- { name: '换手率', type: 'bar', data: turnover, barWidth: 12, itemStyle: { color: 'rgba(21,82,208,0.65)', borderRadius: [3, 3, 0, 0] } },
- { name: '成交额(千亿港元)', type: 'line', yAxisIndex: 1, data: volume, symbol: 'none', smooth: true, lineStyle: { color: C.up, width: 2.2 } },
- ...(hasClose ? [{
- name: '指数走势',
- type: 'line',
- yAxisIndex: 1,
- data: closeAligned,
- symbol: 'none',
- smooth: true,
- lineStyle: { color: C.warn, width: 1.6 },
- markPoint: {
- symbol: 'circle', symbolSize: 7,
- itemStyle: { color: C.warn, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => p.value.toFixed(0), color: C.warn, fontSize: 10, position: 'bottom', fontWeight: 600 },
- data: [{ name: '当前指数走势', value: lastClose, xAxis: lastCloseDate, yAxis: lastClose }],
- },
- }] : []),
- ],
- }
- }
- // ===== RSI 单线 =====
- export function rsiLineOption(series, closeSeries) {
- const dates = series.map((s) => s[0])
- const values = series.map((s) => s[1])
- const hasClose = Array.isArray(closeSeries) && closeSeries.length > 0
- const closeMap = hasClose ? new Map(closeSeries.map(([d, v]) => [d, v])) : null
- const closeAligned = hasClose ? dates.map((d) => (closeMap.has(d) ? closeMap.get(d) : null)) : []
- const lastClose = hasClose ? closeSeries[closeSeries.length - 1][1] : null
- const lastCloseDate = hasClose ? closeSeries[closeSeries.length - 1][0] : null
- const legendData = [{ name: 'RSI', itemStyle: { color: C.brand } }]
- if (hasClose) legendData.push({ name: '指数走势', itemStyle: { color: C.warn } })
- return {
- tooltip: { ...tipOpt, trigger: 'axis', axisPointer: { type: 'line' }, valueFormatter: (v) => v.toFixed(2) },
- legend: {
- data: legendData,
- top: 4,
- textStyle: { color: C.mut, fontSize: 11 },
- itemWidth: 14,
- },
- grid: { top: 36, left: 50, right: hasClose ? 60 : 24, bottom: 32, containLabel: true },
- xAxis: { type: 'category', data: dates, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10.5 } }) },
- yAxis: hasClose
- ? [
- { type: 'value', min: 0, max: 100, interval: 25, ...axisStyle() },
- { type: 'value', name: '指数走势', position: 'right', ...axisStyle({ splitLine: { show: false }, nameTextStyle: { color: C.warn, fontSize: 10 } }) },
- ]
- : { type: 'value', min: 0, max: 100, interval: 25, ...axisStyle() },
- series: [
- {
- name: 'RSI', type: 'line', data: values, symbol: 'none', smooth: true,
- lineStyle: { color: C.brand, width: 2.4 },
- areaStyle: {
- color: {
- type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
- colorStops: [
- { offset: 0, color: 'rgba(21,82,208,0.28)' },
- { offset: 1, color: 'rgba(21,82,208,0.02)' },
- ],
- },
- },
- markLine: {
- symbol: ['none', 'none'],
- data: [
- { yAxis: 70, lineStyle: { color: C.up, type: 'dashed' }, label: { show: true, position: 'end', formatter: '超买 70', color: C.up, fontSize: 10 } },
- { yAxis: 30, lineStyle: { color: C.down, type: 'dashed' }, label: { show: true, position: 'end', formatter: '超卖 30', color: C.down, fontSize: 10 } },
- { yAxis: 50, lineStyle: { color: C.mut, type: 'dotted' }, label: { show: true, position: 'end', formatter: '中枢 50', color: C.mut, fontSize: 10 } },
- ],
- },
- markPoint: {
- symbol: 'circle', symbolSize: 8,
- itemStyle: { color: C.brand, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => p.value.toFixed(2), color: C.brand, fontSize: 10.5, position: 'top', fontWeight: 600 },
- data: [{ name: '当前', value: values[values.length - 1], xAxis: dates[dates.length - 1], yAxis: values[values.length - 1] }],
- },
- },
- ...(hasClose ? [{
- name: '指数走势',
- type: 'line',
- yAxisIndex: 1,
- data: closeAligned,
- symbol: 'none',
- smooth: true,
- lineStyle: { color: C.warn, width: 1.6 },
- markPoint: {
- symbol: 'circle', symbolSize: 7,
- itemStyle: { color: C.warn, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => p.value.toFixed(0), color: C.warn, fontSize: 10, position: 'bottom', fontWeight: 600 },
- data: [{ name: '当前指数走势', value: lastClose, xAxis: lastCloseDate, yAxis: lastClose }],
- },
- }] : []),
- ],
- }
- }
- // ===== VOSC 单线 =====
- export function voscLineOption(series, closeSeries) {
- const dates = series.map((s) => s[0])
- const values = series.map((s) => s[1])
- const hasClose = Array.isArray(closeSeries) && closeSeries.length > 0
- const closeMap = hasClose ? new Map(closeSeries.map(([d, v]) => [d, v])) : null
- const closeAligned = hasClose ? dates.map((d) => (closeMap.has(d) ? closeMap.get(d) : null)) : []
- const lastClose = hasClose ? closeSeries[closeSeries.length - 1][1] : null
- const lastCloseDate = hasClose ? closeSeries[closeSeries.length - 1][0] : null
- const legendData = [{ name: 'VOSC', itemStyle: { color: C.up } }]
- if (hasClose) legendData.push({ name: '指数走势', itemStyle: { color: C.warn } })
- return {
- tooltip: { ...tipOpt, trigger: 'axis', axisPointer: { type: 'line' }, valueFormatter: (v) => v.toFixed(3) },
- legend: {
- data: legendData,
- top: 4,
- textStyle: { color: C.mut, fontSize: 11 },
- itemWidth: 14,
- },
- grid: { top: 36, left: 50, right: hasClose ? 60 : 24, bottom: 32, containLabel: true },
- xAxis: { type: 'category', data: dates, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10.5 } }) },
- yAxis: hasClose
- ? [
- { type: 'value', ...axisStyle() },
- { type: 'value', name: '指数走势', position: 'right', ...axisStyle({ splitLine: { show: false }, nameTextStyle: { color: C.warn, fontSize: 10 } }) },
- ]
- : { type: 'value', ...axisStyle() },
- series: [
- {
- name: 'VOSC', type: 'line', data: values, symbol: 'none', smooth: true,
- lineStyle: { color: C.up, width: 2.4 },
- areaStyle: {
- color: {
- type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
- colorStops: [
- { offset: 0, color: 'rgba(225,59,59,0.25)' },
- { offset: 1, color: 'rgba(225,59,59,0.02)' },
- ],
- },
- },
- // markLine: {
- // symbol: ['none', 'none'],
- // data: [{ yAxis: 0, lineStyle: { color: C.mut, type: 'dashed' }, label: { show: true, position: 'end', formatter: '零轴', color: C.mut, fontSize: 10 } }],
- // },
- markPoint: {
- symbol: 'circle', symbolSize: 8,
- itemStyle: { color: C.up, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => p.value.toFixed(3), color: C.up, fontSize: 10.5, position: 'top', fontWeight: 600 },
- data: [{ name: '当前', value: values[values.length - 1], xAxis: dates[dates.length - 1], yAxis: values[values.length - 1] }],
- },
- },
- ...(hasClose ? [{
- name: '指数走势',
- type: 'line',
- yAxisIndex: 1,
- data: closeAligned,
- symbol: 'none',
- smooth: true,
- lineStyle: { color: C.warn, width: 1.6 },
- markPoint: {
- symbol: 'circle', symbolSize: 7,
- itemStyle: { color: C.warn, borderColor: '#fff', borderWidth: 2 },
- label: { formatter: (p) => p.value.toFixed(0), color: C.warn, fontSize: 10, position: 'bottom', fontWeight: 600 },
- data: [{ name: '当前指数走势', value: lastClose, xAxis: lastCloseDate, yAxis: lastClose }],
- },
- }] : []),
- ],
- }
- }
|