mirror of
https://github.com/shy3130/tick-stock-panel.git
synced 2026-11-02 10:26:13 +00:00
fix(ui): 轮动走势图线端名称 ≤10 全显, 高密模式封顶 Top10
旧防叠规则 >8 条只显最新值前 6 名, 默认「前10」下有 4 条线常年无名。改为 ≤10 条全部常显 (默认行数所见即所名); 15/20 行高密模式按最新值封顶 Top10, 其余靠悬停聚焦与图例 (endLabel 无引导线, 汇聚区推离会与线端脱钩, 不宜全显)。 验证: SectorRotationCard.test 9 条全显/前20 封顶 Top10 断言; 前端 321 测试全绿。 Signed-off-by: shy3130 <shy3130@qq.com>
This commit is contained in:
@@ -87,10 +87,40 @@ function payload() {
|
||||
}
|
||||
}
|
||||
|
||||
/** N 板块 payload: 板块 i 最新值 = 0.004*(N-i) 严格降序, 用于线端名称封顶断言 */
|
||||
function bigPayload(n: number) {
|
||||
const sectors = Array.from({ length: n }, (_, i) => `板块${i}`)
|
||||
const matrix = sectors.map((_, i) => BUCKETS.map((_, c) => 0.004 * (n - i) + c * 0.0001))
|
||||
return {
|
||||
status: 'ok' as const,
|
||||
date: '2026-10-20',
|
||||
as_of: '15:00',
|
||||
member_count: n,
|
||||
bucket_minutes: 5,
|
||||
basis: 'prev_close',
|
||||
flow_available: false,
|
||||
flow_field: null,
|
||||
default_exclude_sectors: [],
|
||||
max_auto_members: 300,
|
||||
timeline: BUCKETS.map((time, i) => ({
|
||||
time, leader: sectors[0], leader_pct: matrix[0][i],
|
||||
market_pct: 0.002, rotation: 0.3, cross_up: 0, cross_down: 0,
|
||||
})),
|
||||
series: { sectors, buckets: BUCKETS, matrix },
|
||||
sectors: sectors.map((name, i) => ({
|
||||
name, pct_now: matrix[i][5], pct_prev: 0.004, rank_now: i + 1,
|
||||
rank_change: 0, score: 60 - i, n_members: 30, n_members_with_bars: 30,
|
||||
activity: 1e8, flow: null,
|
||||
})),
|
||||
universe: sectors.map(name => ({ name, activity: 1e8, pct_now: 0.01 })),
|
||||
cross_events: [],
|
||||
}
|
||||
}
|
||||
|
||||
let root: Root | null = null
|
||||
let host: HTMLElement
|
||||
async function renderCard(kind: 'concept' | 'industry' = 'industry') {
|
||||
apiMock.sectorRotation.mockResolvedValue(payload())
|
||||
async function renderCard(kind: 'concept' | 'industry' = 'industry', data = payload()) {
|
||||
apiMock.sectorRotation.mockResolvedValue(data)
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||||
host = document.createElement('div')
|
||||
document.body.appendChild(host)
|
||||
@@ -184,6 +214,35 @@ it('trend 模式: 线末端常显板块名, 接管线上挂易主圆点标注',
|
||||
expect(host.textContent).toContain('领涨易主')
|
||||
})
|
||||
|
||||
it('线端名称: 9 条 (默认前10 内) 全部常显', async () => {
|
||||
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
|
||||
await renderCard('industry', bigPayload(9))
|
||||
const options = chart.setOption.mock.calls.map(call => call[0] as any)
|
||||
const trendOption = options.find(o => o?.series?.[0]?.endLabel)
|
||||
expect(trendOption).toBeTruthy()
|
||||
// 9 ≤ END_LABEL_MAX: 每条线都有线端名称 — 回归 (2026-10-27): 旧规则 >8 只显 Top6,
|
||||
// 默认「前10」下出现下缘线无名; 新规则与行数选项对齐
|
||||
for (const s of trendOption.series) {
|
||||
expect(s.endLabel?.show).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('线端名称: 前20 高密模式按最新值封顶 Top 10', async () => {
|
||||
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
|
||||
localStorage.setItem('sector_rotation_auto_rows_industry', '20')
|
||||
await renderCard('industry', bigPayload(12))
|
||||
const options = chart.setOption.mock.calls.map(call => call[0] as any)
|
||||
const trendOption = options.find(o => o?.series?.[0]?.endLabel)
|
||||
expect(trendOption).toBeTruthy()
|
||||
expect(trendOption.series).toHaveLength(12)
|
||||
const labeled = trendOption.series.filter((s: any) => s.endLabel?.show).map((s: any) => s.name)
|
||||
// 恰好 10 个, 且是最新值最高的前 10 (构造值严格降序 → 板块0..板块9)
|
||||
expect(labeled).toHaveLength(10)
|
||||
expect(labeled.sort()).toEqual(Array.from({ length: 10 }, (_, i) => `板块${i}`).sort())
|
||||
// 最低的两条无常显名 (悬停聚焦/图例辨认)
|
||||
expect(trendOption.series.filter((s: any) => !s.endLabel)).toHaveLength(2)
|
||||
})
|
||||
|
||||
// ===== 渲染: 热力图模式 =====
|
||||
|
||||
it('heatmap 模式: 领涨格子亮琥珀描边+辉光, 格间细缝, 中性中间色', async () => {
|
||||
|
||||
@@ -34,6 +34,10 @@ const AUTO_ROWS_PREFIX = 'sector_rotation_auto_rows_'
|
||||
const EXCLUDE_PREFIX = 'sector_rotation_exclude_'
|
||||
// 自动活跃榜展示行数可选项; excludeNames === null 表示未自定义 (用后端内置名单)
|
||||
const AUTO_ROW_OPTIONS = [5, 10, 15, 20]
|
||||
// 走势线末端常显名称上限: ≤此数全显; 15/20 行高密模式按最新值封顶 Top N。
|
||||
// 与 AUTO_ROW_OPTIONS 的默认「前10」对齐 — 常规视图所见即所名; 更多标签会在
|
||||
// 汇聚区被防重叠推离各自线端 (endLabel 无引导线), 高密模式牺牲常显名换可读性
|
||||
const END_LABEL_MAX = 10
|
||||
const MAX_EXCLUDE_SECTORS = 100
|
||||
// 板块来源: 自动榜排序维度 + 自定义监控
|
||||
type SectorSource = 'activity' | 'score' | 'pct' | 'rank_change' | 'momentum' | 'flow' | 'custom'
|
||||
@@ -483,16 +487,17 @@ export function SectorRotationCard({ kind }: { kind: 'concept' | 'industry' }) {
|
||||
const totalCols = heatSeries.buckets.length
|
||||
const reigns = computeLeaderReigns(heatSeries.matrix, startCol)
|
||||
const labelCols = selectReignLabels(reigns, totalCols)
|
||||
// 线末端常显板块名: ≤8 条全显, >8 条按最新值只显 Top 6 (其余靠悬停聚焦/图例)
|
||||
// 线末端常显板块名: ≤END_LABEL_MAX 条全显 (默认「前10」下所见即所名);
|
||||
// 15/20 行高密模式按最新值封顶 Top END_LABEL_MAX (其余靠悬停聚焦/图例)
|
||||
const latestVals = heatSeries.sectors.map((_, i) => {
|
||||
const row = heatSeries.matrix[i] ?? []
|
||||
for (let c = row.length - 1; c >= 0; c--) { const v = row[c]; if (v != null) return v }
|
||||
return -Infinity
|
||||
})
|
||||
const endLabelRows = new Set(
|
||||
heatSeries.sectors.length <= 8
|
||||
heatSeries.sectors.length <= END_LABEL_MAX
|
||||
? heatSeries.sectors.map((_, i) => i)
|
||||
: heatSeries.sectors.map((_, i) => i).sort((a, b) => latestVals[b] - latestVals[a]).slice(0, 6),
|
||||
: heatSeries.sectors.map((_, i) => i).sort((a, b) => latestVals[b] - latestVals[a]).slice(0, END_LABEL_MAX),
|
||||
)
|
||||
return {
|
||||
// bottom 预留两行 plain 图例的高度 (scroll 图例会翻页, 一眼看不全所有板块)
|
||||
|
||||
Reference in New Issue
Block a user