feat(chart): 优化图表主题适配与数据处理逻辑
Browse files- 图表支持深色模式自动检测与切换,根据系统主题调整配色
- 修改数据单位转换逻辑,调整为 xRaw * 1e12 / 1024 更准确反映 FLOPs
- 数据点排序方式改为按 x 轴值排序,提升图表展示合理性
- 增加对主题变化的监听,自动重渲染图表以适配颜色
- 图表容器使用响应式尺寸 class 替代固定宽高,增强适配性
- 移除调试日志,优化坐标轴和图例颜色变量命名
- 调整 tooltip 中 y 轴数值显示精度为四位小数,提高可读性
同时更新 useLeaderboardData 中数据集名称和表头显示文本:
- 更新多个数据集显示名,如 'FinePDFs-en'、'Ch-FineWeb-Edu' 等
- 修改部分列标题为全大写格式,统一展示风格
- 调整默认排序方式为降序,简化表头点击排序逻辑
新增 Markdown 转 HTML 工具函数,用于解析展示页面头部信息:
- 支持标题、段落、链接、粗体、斜体、行内代码等基础语法
- 自动转义 HTML 字符,防止 XSS 风险
- 添加 Tailwind 样式类以适配深色模式
页面结构优化:
- 使用 ref 滚动到图表区域,提升用户交互体验
- 页面标题与更新时间动态绑定,支持自定义 header 内容
- 引入 logo 图片并设置容器样式,增强视觉呈现
- 优化筛选区域标签文本为英文,统一界面语言风格
构建配置调整:
- 简化 package.json 中的 build 脚本,移除冗余 type-check 和 build-only
- 移除部分未使用的 devDependencies,如 @tsconfig/node22、typescript 等
- 删除 tsconfig.node.json 文件,精简 TypeScript 配置结构
- package.json +4 -11
- src/components/Chart.vue +38 -17
- src/composables/useLeaderboardData.js +17 -19
- src/utils/mk2html.js +45 -0
- src/views/Leaderboard.vue +91 -29
- src/views/logo.png +0 -0
- tsconfig.node.json +0 -19
package.json
CHANGED
|
@@ -1,14 +1,12 @@
|
|
| 1 |
{
|
| 2 |
"name": "vue",
|
| 3 |
-
"version": "
|
| 4 |
"private": true,
|
| 5 |
"type": "module",
|
| 6 |
"scripts": {
|
| 7 |
"dev": "vite",
|
| 8 |
-
"build": "
|
| 9 |
"preview": "vite preview",
|
| 10 |
-
"build-only": "vite build",
|
| 11 |
-
"type-check": "vue-tsc --build",
|
| 12 |
"lint": "eslint . --fix",
|
| 13 |
"format": "prettier --write src/"
|
| 14 |
},
|
|
@@ -19,11 +17,8 @@
|
|
| 19 |
"vue-router": "^4.5.0"
|
| 20 |
},
|
| 21 |
"devDependencies": {
|
| 22 |
-
"@tsconfig/node22": "^22.0.1",
|
| 23 |
-
"@types/node": "^22.14.0",
|
| 24 |
"@vitejs/plugin-vue": "^5.2.3",
|
| 25 |
"@vue/eslint-config-prettier": "^10.2.0",
|
| 26 |
-
"@vue/eslint-config-typescript": "^14.5.0",
|
| 27 |
"@vue/tsconfig": "^0.7.0",
|
| 28 |
"autoprefixer": "^10.4.21",
|
| 29 |
"eslint": "^9.22.0",
|
|
@@ -33,9 +28,7 @@
|
|
| 33 |
"postcss": "^8.5.6",
|
| 34 |
"prettier": "3.5.3",
|
| 35 |
"tailwindcss": "^3.3.3",
|
| 36 |
-
"typescript": "~5.8.0",
|
| 37 |
"vite": "^6.2.4",
|
| 38 |
-
"vite-plugin-vue-devtools": "^7.7.2"
|
| 39 |
-
"vue-tsc": "^2.2.8"
|
| 40 |
}
|
| 41 |
-
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "vue",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
"private": true,
|
| 5 |
"type": "module",
|
| 6 |
"scripts": {
|
| 7 |
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
"preview": "vite preview",
|
|
|
|
|
|
|
| 10 |
"lint": "eslint . --fix",
|
| 11 |
"format": "prettier --write src/"
|
| 12 |
},
|
|
|
|
| 17 |
"vue-router": "^4.5.0"
|
| 18 |
},
|
| 19 |
"devDependencies": {
|
|
|
|
|
|
|
| 20 |
"@vitejs/plugin-vue": "^5.2.3",
|
| 21 |
"@vue/eslint-config-prettier": "^10.2.0",
|
|
|
|
| 22 |
"@vue/tsconfig": "^0.7.0",
|
| 23 |
"autoprefixer": "^10.4.21",
|
| 24 |
"eslint": "^9.22.0",
|
|
|
|
| 28 |
"postcss": "^8.5.6",
|
| 29 |
"prettier": "3.5.3",
|
| 30 |
"tailwindcss": "^3.3.3",
|
|
|
|
| 31 |
"vite": "^6.2.4",
|
| 32 |
+
"vite-plugin-vue-devtools": "^7.7.2"
|
|
|
|
| 33 |
}
|
| 34 |
+
}
|
src/components/Chart.vue
CHANGED
|
@@ -6,6 +6,11 @@ import * as echarts from 'echarts'
|
|
| 6 |
const chartRef = ref(null)
|
| 7 |
let chart = null
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
const { leaderboard, selectedDataNameChart, modelTypeGroups, strtSymbolSeries } = useLeaderboardData()
|
| 10 |
|
| 11 |
// 接受一个可选 prop:autoShowSeries(数组),用于指定哪些模型类型在图表加载时自动显示。
|
|
@@ -24,19 +29,20 @@ function buildSeriesFromLeaderboard(rows = []) {
|
|
| 24 |
const isMoE = !!r._isMoE
|
| 25 |
const seriesName = isMoE ? `${baseType} (MoE)` : baseType
|
| 26 |
if (!groups[seriesName]) groups[seriesName] = []
|
| 27 |
-
// 数据原本单位为
|
| 28 |
const xRaw = parseFloat(r.BF16_TFLOPs) || 0
|
| 29 |
-
const x = xRaw > 0 ? xRaw * 1e12 : 0
|
|
|
|
| 30 |
const y = parseFloat(r.ic) || 0
|
| 31 |
const rank = Number(r.rank) || 0
|
| 32 |
groups[seriesName].push({ x, y, rank })
|
| 33 |
})
|
| 34 |
|
| 35 |
return Object.keys(groups).map(name => {
|
| 36 |
-
// 过滤掉 x<=0 的点(对数坐标轴不能显示 0 或负值),并按
|
| 37 |
const data = groups[name]
|
| 38 |
.filter(p => Number.isFinite(p.x) && p.x > 0 && Number.isFinite(p.y))
|
| 39 |
-
.sort((a, b) => a.
|
| 40 |
.map(d => [d.x, d.y])
|
| 41 |
// 根据该系列的样本点数量适度放大 symbolSize,避免点太小难以点击
|
| 42 |
const count = groups[name].length || 1
|
|
@@ -45,7 +51,13 @@ function buildSeriesFromLeaderboard(rows = []) {
|
|
| 45 |
})
|
| 46 |
}
|
| 47 |
|
| 48 |
-
function getChartOption(series) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
// 计算数据范围以自动缩放坐标轴,避免留太多空白
|
| 50 |
const allX = []
|
| 51 |
const allY = []
|
|
@@ -166,7 +178,7 @@ function getChartOption(series) {
|
|
| 166 |
const symbolShape = strtSymbolSeries.includes(String(s.name)) ?
|
| 167 |
'path://M341.5 45.1C337.4 37.1 329.1 32 320.1 32C311.1 32 302.8 37.1 298.7 45.1L225.1 189.3L65.2 214.7C56.3 216.1 48.9 222.4 46.1 231C43.3 239.6 45.6 249 51.9 255.4L166.3 369.9L141.1 529.8C139.7 538.7 143.4 547.7 150.7 553C158 558.3 167.6 559.1 175.7 555L320.1 481.6L464.4 555C472.4 559.1 482.1 558.3 489.4 553C496.7 547.7 500.4 538.8 499 529.8L473.7 369.9L588.1 255.4C594.5 249 596.7 239.6 593.9 231C591.1 222.4 583.8 216.1 574.8 214.7L415 189.3L341.5 45.1z'
|
| 168 |
: 'circle'
|
| 169 |
-
console.log(`Series "${s.name}" uses symbol "${symbolShape}"`)
|
| 170 |
const baseSize = Number(s.symbolSize) || 8
|
| 171 |
const emphasisSize = Math.max(12, Math.round(baseSize * 1.6))
|
| 172 |
// 如果是星形,增大尺寸以匹配视觉效果
|
|
@@ -196,7 +208,7 @@ function getChartOption(series) {
|
|
| 196 |
|
| 197 |
return {
|
| 198 |
color: colors,
|
| 199 |
-
title: { text: 'BF16_TFLOPs vs IC', left: '6%', textStyle: { color:
|
| 200 |
tooltip: {
|
| 201 |
trigger: 'item',
|
| 202 |
formatter: params => {
|
|
@@ -213,7 +225,7 @@ function getChartOption(series) {
|
|
| 213 |
} else {
|
| 214 |
xStr = String(x)
|
| 215 |
}
|
| 216 |
-
const yStr = (typeof y === 'number' && Number.isFinite(y)) ? Number(y).toFixed(
|
| 217 |
return `${params.seriesName}<br/>FLOPs: ${xStr}<br/>IC: ${yStr}`
|
| 218 |
}
|
| 219 |
},
|
|
@@ -224,7 +236,7 @@ function getChartOption(series) {
|
|
| 224 |
align: 'left',
|
| 225 |
itemWidth: 14,
|
| 226 |
itemHeight: 10,
|
| 227 |
-
textStyle: { color:
|
| 228 |
tooltip: { show: true }
|
| 229 |
}, { selected: legendSelected, data: legendData }),
|
| 230 |
grid: { left: '6%', right: '20%', bottom: '8%', containLabel: true },
|
|
@@ -233,9 +245,9 @@ function getChartOption(series) {
|
|
| 233 |
name: 'FLOPs',
|
| 234 |
nameLocation: 'middle',
|
| 235 |
nameGap: 30,
|
| 236 |
-
axisLine: { lineStyle: { color:
|
| 237 |
axisLabel: {
|
| 238 |
-
color:
|
| 239 |
formatter: value => {
|
| 240 |
if (!Number.isFinite(value) || value <= 0) return String(value)
|
| 241 |
const exp = Math.floor(Math.log10(value))
|
|
@@ -244,16 +256,16 @@ function getChartOption(series) {
|
|
| 244 |
return `${mant.toFixed(2)}×10${toSuperscript(exp)}`
|
| 245 |
}
|
| 246 |
},
|
| 247 |
-
splitLine: { show: true, lineStyle: { type: 'dashed', color:
|
| 248 |
min: xMin,
|
| 249 |
max: xMax
|
| 250 |
},
|
| 251 |
yAxis: {
|
| 252 |
type: 'value',
|
| 253 |
name: 'Information capacity',
|
| 254 |
-
axisLine: { lineStyle: { color:
|
| 255 |
-
axisLabel: { color:
|
| 256 |
-
splitLine: { show: true, lineStyle: { type: 'dashed', color:
|
| 257 |
min: yMin,
|
| 258 |
max: yMax
|
| 259 |
},
|
|
@@ -272,11 +284,13 @@ function renderChart() {
|
|
| 272 |
rows = rows.filter(r => String(r.data_name ?? '') === sel)
|
| 273 |
}
|
| 274 |
const series = buildSeriesFromLeaderboard(rows)
|
| 275 |
-
const option = getChartOption(series)
|
| 276 |
chart.setOption(option, { notMerge: true })
|
| 277 |
}
|
| 278 |
|
| 279 |
onMounted(() => {
|
|
|
|
|
|
|
| 280 |
if (!chartRef.value) return
|
| 281 |
chart = echarts.init(chartRef.value)
|
| 282 |
// 首次渲染(如果数据尚未加载,后续会由 watcher 更新)
|
|
@@ -296,14 +310,21 @@ watch(selectedDataNameChart, () => {
|
|
| 296 |
renderChart()
|
| 297 |
})
|
| 298 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
onBeforeUnmount(() => {
|
| 300 |
if (resizeHandler) window.removeEventListener('resize', resizeHandler)
|
|
|
|
| 301 |
if (chart) { chart.dispose(); chart = null }
|
| 302 |
})
|
| 303 |
</script>
|
| 304 |
|
| 305 |
<template>
|
| 306 |
-
<div ref="chartRef"
|
| 307 |
</template>
|
| 308 |
|
| 309 |
<style scoped></style>
|
|
|
|
| 6 |
const chartRef = ref(null)
|
| 7 |
let chart = null
|
| 8 |
|
| 9 |
+
// 独立监听主题变化
|
| 10 |
+
const isDarkChart = ref(document.documentElement.classList.contains('dark'))
|
| 11 |
+
|
| 12 |
+
let themeObserver = null
|
| 13 |
+
|
| 14 |
const { leaderboard, selectedDataNameChart, modelTypeGroups, strtSymbolSeries } = useLeaderboardData()
|
| 15 |
|
| 16 |
// 接受一个可选 prop:autoShowSeries(数组),用于指定哪些模型类型在图表加载时自动显示。
|
|
|
|
| 29 |
const isMoE = !!r._isMoE
|
| 30 |
const seriesName = isMoE ? `${baseType} (MoE)` : baseType
|
| 31 |
if (!groups[seriesName]) groups[seriesName] = []
|
| 32 |
+
// 数据原本单位为 FLOPs (G),转换为 FLOPs(1 TFLOP = 1e12 FLOPs)用于对数尺度显示
|
| 33 |
const xRaw = parseFloat(r.BF16_TFLOPs) || 0
|
| 34 |
+
const x = xRaw > 0 ? xRaw * 1e12 / 1024 : 0
|
| 35 |
+
// const x = xRaw
|
| 36 |
const y = parseFloat(r.ic) || 0
|
| 37 |
const rank = Number(r.rank) || 0
|
| 38 |
groups[seriesName].push({ x, y, rank })
|
| 39 |
})
|
| 40 |
|
| 41 |
return Object.keys(groups).map(name => {
|
| 42 |
+
// 过滤掉 x<=0 的点(对数坐标轴不能显示 0 或负值),并按 x 轴值排序
|
| 43 |
const data = groups[name]
|
| 44 |
.filter(p => Number.isFinite(p.x) && p.x > 0 && Number.isFinite(p.y))
|
| 45 |
+
.sort((a, b) => a.x - b.x)
|
| 46 |
.map(d => [d.x, d.y])
|
| 47 |
// 根据该系列的样本点数量适度放大 symbolSize,避免点太小难以点击
|
| 48 |
const count = groups[name].length || 1
|
|
|
|
| 51 |
})
|
| 52 |
}
|
| 53 |
|
| 54 |
+
function getChartOption(series, isDark = false) {
|
| 55 |
+
// 根据主题设置颜色
|
| 56 |
+
const titleColor = isDark ? '#cfd8dc' : '#333333'
|
| 57 |
+
const legendColor = isDark ? '#cfd8dc' : '#333333'
|
| 58 |
+
const axisLineColor = isDark ? '#78909c' : '#666666'
|
| 59 |
+
const axisLabelColor = isDark ? '#b0bec5' : '#666666'
|
| 60 |
+
const splitLineColor = isDark ? '#37474f' : '#cccccc'
|
| 61 |
// 计算数据范围以自动缩放坐标轴,避免留太多空白
|
| 62 |
const allX = []
|
| 63 |
const allY = []
|
|
|
|
| 178 |
const symbolShape = strtSymbolSeries.includes(String(s.name)) ?
|
| 179 |
'path://M341.5 45.1C337.4 37.1 329.1 32 320.1 32C311.1 32 302.8 37.1 298.7 45.1L225.1 189.3L65.2 214.7C56.3 216.1 48.9 222.4 46.1 231C43.3 239.6 45.6 249 51.9 255.4L166.3 369.9L141.1 529.8C139.7 538.7 143.4 547.7 150.7 553C158 558.3 167.6 559.1 175.7 555L320.1 481.6L464.4 555C472.4 559.1 482.1 558.3 489.4 553C496.7 547.7 500.4 538.8 499 529.8L473.7 369.9L588.1 255.4C594.5 249 596.7 239.6 593.9 231C591.1 222.4 583.8 216.1 574.8 214.7L415 189.3L341.5 45.1z'
|
| 180 |
: 'circle'
|
| 181 |
+
// console.log(`Series "${s.name}" uses symbol "${symbolShape}"`)
|
| 182 |
const baseSize = Number(s.symbolSize) || 8
|
| 183 |
const emphasisSize = Math.max(12, Math.round(baseSize * 1.6))
|
| 184 |
// 如果是星形,增大尺寸以匹配视觉效果
|
|
|
|
| 208 |
|
| 209 |
return {
|
| 210 |
color: colors,
|
| 211 |
+
title: { text: 'BF16_TFLOPs vs IC', left: '6%', textStyle: { color: titleColor } },
|
| 212 |
tooltip: {
|
| 213 |
trigger: 'item',
|
| 214 |
formatter: params => {
|
|
|
|
| 225 |
} else {
|
| 226 |
xStr = String(x)
|
| 227 |
}
|
| 228 |
+
const yStr = (typeof y === 'number' && Number.isFinite(y)) ? Number(y).toFixed(4) : String(y)
|
| 229 |
return `${params.seriesName}<br/>FLOPs: ${xStr}<br/>IC: ${yStr}`
|
| 230 |
}
|
| 231 |
},
|
|
|
|
| 236 |
align: 'left',
|
| 237 |
itemWidth: 14,
|
| 238 |
itemHeight: 10,
|
| 239 |
+
textStyle: { color: legendColor },
|
| 240 |
tooltip: { show: true }
|
| 241 |
}, { selected: legendSelected, data: legendData }),
|
| 242 |
grid: { left: '6%', right: '20%', bottom: '8%', containLabel: true },
|
|
|
|
| 245 |
name: 'FLOPs',
|
| 246 |
nameLocation: 'middle',
|
| 247 |
nameGap: 30,
|
| 248 |
+
axisLine: { lineStyle: { color: axisLineColor } },
|
| 249 |
axisLabel: {
|
| 250 |
+
color: axisLabelColor,
|
| 251 |
formatter: value => {
|
| 252 |
if (!Number.isFinite(value) || value <= 0) return String(value)
|
| 253 |
const exp = Math.floor(Math.log10(value))
|
|
|
|
| 256 |
return `${mant.toFixed(2)}×10${toSuperscript(exp)}`
|
| 257 |
}
|
| 258 |
},
|
| 259 |
+
splitLine: { show: true, lineStyle: { type: 'dashed', color: splitLineColor } },
|
| 260 |
min: xMin,
|
| 261 |
max: xMax
|
| 262 |
},
|
| 263 |
yAxis: {
|
| 264 |
type: 'value',
|
| 265 |
name: 'Information capacity',
|
| 266 |
+
axisLine: { lineStyle: { color: axisLineColor } },
|
| 267 |
+
axisLabel: { color: axisLabelColor, formatter: v => (Number.isFinite(v) ? Number(v).toFixed(2) : String(v)) },
|
| 268 |
+
splitLine: { show: true, lineStyle: { type: 'dashed', color: splitLineColor } },
|
| 269 |
min: yMin,
|
| 270 |
max: yMax
|
| 271 |
},
|
|
|
|
| 284 |
rows = rows.filter(r => String(r.data_name ?? '') === sel)
|
| 285 |
}
|
| 286 |
const series = buildSeriesFromLeaderboard(rows)
|
| 287 |
+
const option = getChartOption(series, isDarkChart.value)
|
| 288 |
chart.setOption(option, { notMerge: true })
|
| 289 |
}
|
| 290 |
|
| 291 |
onMounted(() => {
|
| 292 |
+
isDarkChart.value = window.matchMedia("(prefers-color-scheme: dark)").matches //true | false
|
| 293 |
+
|
| 294 |
if (!chartRef.value) return
|
| 295 |
chart = echarts.init(chartRef.value)
|
| 296 |
// 首次渲染(如果数据尚未加载,后续会由 watcher 更新)
|
|
|
|
| 310 |
renderChart()
|
| 311 |
})
|
| 312 |
|
| 313 |
+
// 当主题变化时重新渲染图表
|
| 314 |
+
watch(isDarkChart, (newVal) => {
|
| 315 |
+
console.log('isDarkChart changed to:', newVal)
|
| 316 |
+
renderChart()
|
| 317 |
+
})
|
| 318 |
+
|
| 319 |
onBeforeUnmount(() => {
|
| 320 |
if (resizeHandler) window.removeEventListener('resize', resizeHandler)
|
| 321 |
+
if (themeObserver) themeObserver.disconnect()
|
| 322 |
if (chart) { chart.dispose(); chart = null }
|
| 323 |
})
|
| 324 |
</script>
|
| 325 |
|
| 326 |
<template>
|
| 327 |
+
<div ref="chartRef" class="w-full h-[80vh]"></div>
|
| 328 |
</template>
|
| 329 |
|
| 330 |
<style scoped></style>
|
src/composables/useLeaderboardData.js
CHANGED
|
@@ -52,19 +52,19 @@ const headerDisplayMap = reactive({
|
|
| 52 |
'model_name': 'MODEL NAME',
|
| 53 |
'model_series': 'MODEL SERIES',
|
| 54 |
'model_size (B)': 'MODEL SIZE (B)',
|
| 55 |
-
'constant': '
|
| 56 |
-
'conditional_entropy': '
|
| 57 |
'BF16_TFLOPs': 'FLOPs (G)',
|
| 58 |
-
'ic': '
|
| 59 |
})
|
| 60 |
|
| 61 |
// 数据集名称显示映射(raw data_name -> 显示名)
|
| 62 |
const dataNameDisplayMap = reactive({
|
| 63 |
-
'data_part_0000': '
|
| 64 |
-
'eng_Latn_000_00027_long': '
|
| 65 |
-
'IndustryCorpus_batch_aa_long': '
|
| 66 |
-
'CC-MAIN-2013-20_train-00000-of-00014_long': '
|
| 67 |
-
'NextCoderDataset_v1_long': 'NextCoder
|
| 68 |
})
|
| 69 |
|
| 70 |
|
|
@@ -131,7 +131,7 @@ const filteredLeaderboard = computed(() => {
|
|
| 131 |
return filtered
|
| 132 |
})
|
| 133 |
|
| 134 |
-
// 点击表头切换排序:循环
|
| 135 |
function setSortKey(h) {
|
| 136 |
if (!h) return
|
| 137 |
if (globalState.sortKey !== h) {
|
|
@@ -139,14 +139,8 @@ function setSortKey(h) {
|
|
| 139 |
globalState.sortDesc = true
|
| 140 |
return
|
| 141 |
}
|
| 142 |
-
// same key, toggle
|
| 143 |
-
|
| 144 |
-
globalState.sortDesc = false
|
| 145 |
-
return
|
| 146 |
-
}
|
| 147 |
-
// clear
|
| 148 |
-
globalState.sortKey = ''
|
| 149 |
-
globalState.sortDesc = true
|
| 150 |
}
|
| 151 |
|
| 152 |
// 计算当前应该显示的列(不含 rank)
|
|
@@ -172,7 +166,7 @@ async function fetchAndLoadCsv(path = globalState.DEFAULT_CSV_PATH) {
|
|
| 172 |
// 默认以最后一列升序排序(如果不存在则回退到 scoreKey),不直接在 rows 上预排序
|
| 173 |
const defaultKey = headers.length > 0 ? headers[headers.length - 1] : scoreKey
|
| 174 |
globalState.sortKey = defaultKey || ''
|
| 175 |
-
globalState.sortDesc =
|
| 176 |
|
| 177 |
// 特定处理
|
| 178 |
// 判断模型开头是否在 MoEModelSeries 中,是则在 判断尾部是否为-A{number}B这样的格式
|
|
@@ -272,7 +266,11 @@ async function fetchAndLoadCsv(path = globalState.DEFAULT_CSV_PATH) {
|
|
| 272 |
const n = Number(raw)
|
| 273 |
if (h === 'ic') {
|
| 274 |
row._formatted[h] = Number.isFinite(n) ? n.toFixed(4) : raw
|
| 275 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
row._formatted[h] = Number.isFinite(n) ? n.toFixed(3) : raw
|
| 277 |
}
|
| 278 |
} else {
|
|
|
|
| 52 |
'model_name': 'MODEL NAME',
|
| 53 |
'model_series': 'MODEL SERIES',
|
| 54 |
'model_size (B)': 'MODEL SIZE (B)',
|
| 55 |
+
'constant': 'TEXT SIZE',
|
| 56 |
+
'conditional_entropy': 'NEGATIVE LOG-LIKELIHOOD',
|
| 57 |
'BF16_TFLOPs': 'FLOPs (G)',
|
| 58 |
+
'ic': ' INFORMATION CAPACITY',
|
| 59 |
})
|
| 60 |
|
| 61 |
// 数据集名称显示映射(raw data_name -> 显示名)
|
| 62 |
const dataNameDisplayMap = reactive({
|
| 63 |
+
'data_part_0000': 'Mixed text',
|
| 64 |
+
'eng_Latn_000_00027_long': 'FinePDFs-en',
|
| 65 |
+
'IndustryCorpus_batch_aa_long': 'Ch-FineWeb-Edu',
|
| 66 |
+
'CC-MAIN-2013-20_train-00000-of-00014_long': 'FineWeb-Edu',
|
| 67 |
+
'NextCoderDataset_v1_long': 'NextCoder',
|
| 68 |
})
|
| 69 |
|
| 70 |
|
|
|
|
| 131 |
return filtered
|
| 132 |
})
|
| 133 |
|
| 134 |
+
// 点击表头切换排序:循环 降序 -> 升序
|
| 135 |
function setSortKey(h) {
|
| 136 |
if (!h) return
|
| 137 |
if (globalState.sortKey !== h) {
|
|
|
|
| 139 |
globalState.sortDesc = true
|
| 140 |
return
|
| 141 |
}
|
| 142 |
+
// same key, toggle between desc and asc
|
| 143 |
+
globalState.sortDesc = !globalState.sortDesc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
}
|
| 145 |
|
| 146 |
// 计算当前应该显示的列(不含 rank)
|
|
|
|
| 166 |
// 默认以最后一列升序排序(如果不存在则回退到 scoreKey),不直接在 rows 上预排序
|
| 167 |
const defaultKey = headers.length > 0 ? headers[headers.length - 1] : scoreKey
|
| 168 |
globalState.sortKey = defaultKey || ''
|
| 169 |
+
globalState.sortDesc = true
|
| 170 |
|
| 171 |
// 特定处理
|
| 172 |
// 判断模型开头是否在 MoEModelSeries 中,是则在 判断尾部是否为-A{number}B这样的格式
|
|
|
|
| 266 |
const n = Number(raw)
|
| 267 |
if (h === 'ic') {
|
| 268 |
row._formatted[h] = Number.isFinite(n) ? n.toFixed(4) : raw
|
| 269 |
+
}
|
| 270 |
+
else if (h === 'constant') {
|
| 271 |
+
row._formatted[h] = Number.isFinite(n) ? n.toFixed(2) : raw
|
| 272 |
+
}
|
| 273 |
+
else {
|
| 274 |
row._formatted[h] = Number.isFinite(n) ? n.toFixed(3) : raw
|
| 275 |
}
|
| 276 |
} else {
|
src/utils/mk2html.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// 一个小型且安全的 Markdown -> HTML 解析器(支持标题、段落、链接、粗体、斜体、行内代码)
|
| 2 |
+
function escapeHtml(str) {
|
| 3 |
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
function parseInline(md) {
|
| 7 |
+
// code
|
| 8 |
+
md = md.replace(/`([^`]+)`/g, '<code>$1</code>')
|
| 9 |
+
// bold **text** or __text__
|
| 10 |
+
md = md.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
| 11 |
+
md = md.replace(/__([^_]+)__/g, '<strong>$1</strong>')
|
| 12 |
+
// italic *text* or _text_
|
| 13 |
+
md = md.replace(/\*([^*]+)\*/g, '<em>$1</em>')
|
| 14 |
+
md = md.replace(/_([^_]+)_/g, '<em>$1</em>')
|
| 15 |
+
// links [text](url)
|
| 16 |
+
md = md.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (m, text, url) => {
|
| 17 |
+
const safeUrl = url.replace(/\"/g, '%22')
|
| 18 |
+
return `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer" class="text-blue-600 underline dark:text-blue-400">${text}</a>`
|
| 19 |
+
})
|
| 20 |
+
return md
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
function parsedHeaderHtml(headerMarkdown){
|
| 24 |
+
const raw = headerMarkdown || ''
|
| 25 |
+
// split into lines and paragraphs
|
| 26 |
+
const blocks = raw.split(/\n{2,}/g)
|
| 27 |
+
const html = blocks.map(block => {
|
| 28 |
+
const line = block.trim()
|
| 29 |
+
if (!line) return ''
|
| 30 |
+
// heading
|
| 31 |
+
const hMatch = line.match(/^(#{1,6})\s+(.*)$/)
|
| 32 |
+
if (hMatch) {
|
| 33 |
+
const level = Math.min(6, hMatch[1].length)
|
| 34 |
+
const content = parseInline(escapeHtml(hMatch[2]))
|
| 35 |
+
// 增加块间距(mb-2)以扩大行之间的视觉间隔
|
| 36 |
+
return `<h${level} class="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-2">${content}</h${level}>`
|
| 37 |
+
}
|
| 38 |
+
// normal paragraph
|
| 39 |
+
// 使用 leading-relaxed 增加行高,并用 mb-2 增加段落间距
|
| 40 |
+
return `<p class="text-sm text-gray-700 dark:text-gray-200 leading-relaxed mb-2">${parseInline(escapeHtml(line))}</p>`
|
| 41 |
+
}).join('\n')
|
| 42 |
+
return html
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
export { parsedHeaderHtml }
|
src/views/Leaderboard.vue
CHANGED
|
@@ -1,7 +1,9 @@
|
|
| 1 |
<script setup>
|
| 2 |
-
import { ref, onMounted, watch } from 'vue'
|
| 3 |
import { useLeaderboardData } from '@/composables/useLeaderboardData.js'
|
| 4 |
import Chart from '@/components/Chart.vue'
|
|
|
|
|
|
|
| 5 |
|
| 6 |
// 使用数据管理 composable
|
| 7 |
const {
|
|
@@ -37,11 +39,59 @@ const {
|
|
| 37 |
// theme (dark mode) support: sync with document root class and localStorage
|
| 38 |
const isDark = ref(false)
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
// 当 visibleColumns 改变时持久化
|
| 41 |
watch(visibleColumns, (v) => {
|
| 42 |
try { localStorage.setItem('visible_columns', JSON.stringify(v)) } catch (e) { }
|
| 43 |
}, { deep: true })
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
onMounted(() => {
|
| 46 |
init()
|
| 47 |
})
|
|
@@ -68,10 +118,6 @@ watch(isDark, (val) => {
|
|
| 68 |
}
|
| 69 |
})
|
| 70 |
|
| 71 |
-
function toggleTheme() {
|
| 72 |
-
isDark.value = !isDark.value
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
function clearModelTypeSelection() {
|
| 76 |
selectAllModelTypes()
|
| 77 |
}
|
|
@@ -81,28 +127,34 @@ function clearModelTypeSelection() {
|
|
| 81 |
<div
|
| 82 |
class="min-h-screen bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:via-gray-800 dark:to-gray-800 py-12 px-4 sm:px-6 lg:px-8">
|
| 83 |
<div class=" mx-auto">
|
| 84 |
-
<header class="mb-
|
| 85 |
<div>
|
| 86 |
-
<h1 class="text-3xl font-extrabold text-gray-900 dark:text-gray-100">
|
| 87 |
-
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">LLM IC Leaderboard Demo</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
</div>
|
| 89 |
</header>
|
| 90 |
|
| 91 |
|
| 92 |
-
|
| 93 |
<div class="bg-white dark:bg-gray-900/60 shadow rounded-lg overflow-hidden">
|
| 94 |
<div
|
| 95 |
class="px-6 py-4 border-b bg-gradient-to-r from-indigo-50 via-white to-white dark:bg-gradient-to-r dark:from-gray-800 dark:via-gray-900 dark:to-gray-900">
|
| 96 |
<div class="flex items-center justify-between">
|
| 97 |
<h2 class="text-lg font-medium text-gray-800 dark:text-gray-50">Leaderboard</h2>
|
| 98 |
-
<div class="text-sm text-gray-500 dark:text-gray-300">Last updated on
|
| 99 |
</div>
|
| 100 |
</div>
|
| 101 |
|
| 102 |
<!-- 数据集筛选区域(独立于列筛选) -->
|
| 103 |
<div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
|
| 104 |
<div class="flex items-center gap-3">
|
| 105 |
-
<span class="text-sm font-medium text-gray-700 dark:text-gray-200"
|
| 106 |
<div v-if="dataGroups.length > 0" class="flex gap-2 items-center overflow-x-auto">
|
| 107 |
<!-- <label class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer" :class="{'bg-gray-100 dark:bg-gray-700': selectedDataName==='all'}">
|
| 108 |
<input type="radio" class="hidden" value="all" v-model="selectedDataName" /> 全部
|
|
@@ -121,7 +173,7 @@ function clearModelTypeSelection() {
|
|
| 121 |
<!-- 模型类型筛选区域 -->
|
| 122 |
<div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
|
| 123 |
<div class="flex items-center gap-3">
|
| 124 |
-
<span class="text-sm font-medium text-gray-700 dark:text-gray-200"
|
| 125 |
<div class="flex items-center gap-2">
|
| 126 |
<button @click="clearModelTypeSelection"
|
| 127 |
class="text-sm px-2 py-1 bg-white/90 dark:bg-gray-700/60 border border-gray-200 dark:border-gray-600 rounded text-gray-700 dark:text-gray-200 hover:bg-white dark:hover:bg-gray-600 transition">
|
|
@@ -148,7 +200,7 @@ function clearModelTypeSelection() {
|
|
| 148 |
<div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
|
| 149 |
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
| 150 |
<div class="flex items-center gap-3">
|
| 151 |
-
<span class="text-sm font-medium text-gray-700 dark:text-gray-200"
|
| 152 |
<div class="flex items-center gap-2">
|
| 153 |
<button @click.prevent="selectAll"
|
| 154 |
class="text-sm px-2 py-1 bg-white/90 dark:bg-gray-700/60 border border-gray-200 dark:border-gray-600 rounded text-gray-700 dark:text-gray-200 hover:bg-white dark:hover:bg-gray-600 transition">全选</button>
|
|
@@ -179,7 +231,7 @@ function clearModelTypeSelection() {
|
|
| 179 |
#</th> -->
|
| 180 |
<th v-for="h in displayedColumns" :key="h"
|
| 181 |
@click="setSortKey(h)"
|
| 182 |
-
class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300
|
| 183 |
<div class="flex items-center gap-2">
|
| 184 |
<span>{{ headerDisplayMap[h] || h }}</span>
|
| 185 |
<span v-if="sortKey === h" class="text-xs text-gray-600 dark:text-gray-300">
|
|
@@ -207,22 +259,32 @@ function clearModelTypeSelection() {
|
|
| 207 |
</div>
|
| 208 |
</div>
|
| 209 |
|
| 210 |
-
|
| 211 |
-
<div class="
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
<
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
</div>
|
| 225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
</div>
|
| 227 |
</div>
|
| 228 |
</div>
|
|
|
|
| 1 |
<script setup>
|
| 2 |
+
import { ref, onMounted, watch, nextTick } from 'vue'
|
| 3 |
import { useLeaderboardData } from '@/composables/useLeaderboardData.js'
|
| 4 |
import Chart from '@/components/Chart.vue'
|
| 5 |
+
import { computed } from 'vue'
|
| 6 |
+
import { parsedHeaderHtml } from '@/utils/mk2html.js'
|
| 7 |
|
| 8 |
// 使用数据管理 composable
|
| 9 |
const {
|
|
|
|
| 39 |
// theme (dark mode) support: sync with document root class and localStorage
|
| 40 |
const isDark = ref(false)
|
| 41 |
|
| 42 |
+
// 图表区域ref
|
| 43 |
+
const chartSection = ref(null)
|
| 44 |
+
|
| 45 |
+
// 记录上一次选择的图表数据集,用于区分初始加载和用户点击
|
| 46 |
+
const lastSelectedDataNameChart = ref('')
|
| 47 |
+
|
| 48 |
+
// header markdown 内容
|
| 49 |
+
const headerMarkdown = ref(`
|
| 50 |
+
|
| 51 |
+
**Information Capacity — evaluating LLM efficiency via compression and compute**
|
| 52 |
+
|
| 53 |
+
Information Capacity evaluates a model's efficiency by measuring text compression gains relative to computational cost.
|
| 54 |
+
Larger models typically predict next tokens more accurately and thus achieve higher compression gains, but at increased compute expense.
|
| 55 |
+
|
| 56 |
+
By profiling compression performance across a family of models with different sizes, we obtain a stable Information Capacity curve that enables
|
| 57 |
+
fair cross-series comparison, series-internal performance prediction, and dynamic routing of models of different sizes according to
|
| 58 |
+
task difficulty — a capability especially useful in device-edge-cloud hierarchies such as the AI Flow framework.
|
| 59 |
+
|
| 60 |
+
A core difference from many existing efficiency metrics is that Information Capacity explicitly accounts for tokenizer efficiency.
|
| 61 |
+
An effective tokenizer can represent the same text with fewer tokens, reducing both input and output token counts, which lowers compute,
|
| 62 |
+
latency, and storage while enabling longer-context reasoning. As input lengths explode and test-time scaling becomes common,
|
| 63 |
+
tokenizer efficiency matters increasingly but is often overlooked in LLM evaluation.
|
| 64 |
+
|
| 65 |
+
We evaluate 49 models across 5 heterogeneous datasets and consistently observe the impacts of tokenizer design, pretraining data,
|
| 66 |
+
and mixture-of-experts (MoE) architectures on Information Capacity.
|
| 67 |
+
|
| 68 |
+
If you want to add your model to the leaderboard, please submit a PR
|
| 69 |
+
at [TeleAI AI Flow InformationCapacity](https://github.com/TeleAI-AI-Flow/InformationCapacity), and we will assist with evaluation and updates.
|
| 70 |
+
|
| 71 |
+
`)
|
| 72 |
+
|
| 73 |
+
const title = 'Information Capacity Leaderboard'
|
| 74 |
+
|
| 75 |
+
const lastUpdatedTime = '11/12/2025 15:30'
|
| 76 |
+
|
| 77 |
+
const headerInfo = parsedHeaderHtml(headerMarkdown.value)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
// 当 visibleColumns 改变时持久化
|
| 81 |
watch(visibleColumns, (v) => {
|
| 82 |
try { localStorage.setItem('visible_columns', JSON.stringify(v)) } catch (e) { }
|
| 83 |
}, { deep: true })
|
| 84 |
|
| 85 |
+
// 当图表数据集变化时,滚动到图表区域底部(仅当用户点击时)
|
| 86 |
+
watch(selectedDataNameChart, (newVal) => {
|
| 87 |
+
if (lastSelectedDataNameChart.value && lastSelectedDataNameChart.value !== newVal) {
|
| 88 |
+
nextTick(() => {
|
| 89 |
+
chartSection.value?.scrollIntoView({ behavior: 'smooth', block: 'end' })
|
| 90 |
+
})
|
| 91 |
+
}
|
| 92 |
+
lastSelectedDataNameChart.value = newVal
|
| 93 |
+
})
|
| 94 |
+
|
| 95 |
onMounted(() => {
|
| 96 |
init()
|
| 97 |
})
|
|
|
|
| 118 |
}
|
| 119 |
})
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
function clearModelTypeSelection() {
|
| 122 |
selectAllModelTypes()
|
| 123 |
}
|
|
|
|
| 127 |
<div
|
| 128 |
class="min-h-screen bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:via-gray-800 dark:to-gray-800 py-12 px-4 sm:px-6 lg:px-8">
|
| 129 |
<div class=" mx-auto">
|
| 130 |
+
<header class="mb-4 flex items-start justify-between">
|
| 131 |
<div>
|
| 132 |
+
<h1 class="text-3xl font-extrabold text-gray-900 dark:text-gray-100">{{ title }}</h1>
|
| 133 |
+
<!-- <p class="mt-1 text-sm text-gray-600 dark:text-gray-300">LLM IC Leaderboard Demo</p> -->
|
| 134 |
+
<div v-html="headerInfo" class="py-7"></div>
|
| 135 |
+
</div>
|
| 136 |
+
<div class="flex items-center">
|
| 137 |
+
<!-- 固定正方形容器,内部图片保持纵横比且居中 -->
|
| 138 |
+
<div class="h-32 w-32 flex items-center justify-center rounded-md overflow-hidden p-2">
|
| 139 |
+
<img src="./logo.png" alt="Logo" class="max-h-full max-w-full object-contain" />
|
| 140 |
+
</div>
|
| 141 |
</div>
|
| 142 |
</header>
|
| 143 |
|
| 144 |
|
|
|
|
| 145 |
<div class="bg-white dark:bg-gray-900/60 shadow rounded-lg overflow-hidden">
|
| 146 |
<div
|
| 147 |
class="px-6 py-4 border-b bg-gradient-to-r from-indigo-50 via-white to-white dark:bg-gradient-to-r dark:from-gray-800 dark:via-gray-900 dark:to-gray-900">
|
| 148 |
<div class="flex items-center justify-between">
|
| 149 |
<h2 class="text-lg font-medium text-gray-800 dark:text-gray-50">Leaderboard</h2>
|
| 150 |
+
<div class="text-sm text-gray-500 dark:text-gray-300">Last updated on {{ lastUpdatedTime }}</div>
|
| 151 |
</div>
|
| 152 |
</div>
|
| 153 |
|
| 154 |
<!-- 数据集筛选区域(独立于列筛选) -->
|
| 155 |
<div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
|
| 156 |
<div class="flex items-center gap-3">
|
| 157 |
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Dataset</span>
|
| 158 |
<div v-if="dataGroups.length > 0" class="flex gap-2 items-center overflow-x-auto">
|
| 159 |
<!-- <label class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer" :class="{'bg-gray-100 dark:bg-gray-700': selectedDataName==='all'}">
|
| 160 |
<input type="radio" class="hidden" value="all" v-model="selectedDataName" /> 全部
|
|
|
|
| 173 |
<!-- 模型类型筛选区域 -->
|
| 174 |
<div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
|
| 175 |
<div class="flex items-center gap-3">
|
| 176 |
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Model Series</span>
|
| 177 |
<div class="flex items-center gap-2">
|
| 178 |
<button @click="clearModelTypeSelection"
|
| 179 |
class="text-sm px-2 py-1 bg-white/90 dark:bg-gray-700/60 border border-gray-200 dark:border-gray-600 rounded text-gray-700 dark:text-gray-200 hover:bg-white dark:hover:bg-gray-600 transition">
|
|
|
|
| 200 |
<div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
|
| 201 |
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
| 202 |
<div class="flex items-center gap-3">
|
| 203 |
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Visible Columns</span>
|
| 204 |
<div class="flex items-center gap-2">
|
| 205 |
<button @click.prevent="selectAll"
|
| 206 |
class="text-sm px-2 py-1 bg-white/90 dark:bg-gray-700/60 border border-gray-200 dark:border-gray-600 rounded text-gray-700 dark:text-gray-200 hover:bg-white dark:hover:bg-gray-600 transition">全选</button>
|
|
|
|
| 231 |
#</th> -->
|
| 232 |
<th v-for="h in displayedColumns" :key="h"
|
| 233 |
@click="setSortKey(h)"
|
| 234 |
+
class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300 tracking-wider cursor-pointer select-none">
|
| 235 |
<div class="flex items-center gap-2">
|
| 236 |
<span>{{ headerDisplayMap[h] || h }}</span>
|
| 237 |
<span v-if="sortKey === h" class="text-xs text-gray-600 dark:text-gray-300">
|
|
|
|
| 259 |
</div>
|
| 260 |
</div>
|
| 261 |
|
| 262 |
+
<!-- 图表区域 -->
|
| 263 |
+
<div ref="chartSection" class="mt-8 bg-white dark:bg-gray-900/60 shadow rounded-lg overflow-hidden">
|
| 264 |
+
<!-- 图表头部 -->
|
| 265 |
+
<div class="px-6 py-4 border-b bg-gradient-to-r from-indigo-50 via-white to-white dark:bg-gradient-to-r dark:from-gray-800 dark:via-gray-900 dark:to-gray-900">
|
| 266 |
+
<h2 class="text-lg font-medium text-gray-800 dark:text-gray-50">Data visualization</h2>
|
| 267 |
+
</div>
|
| 268 |
+
|
| 269 |
+
<!-- 图表数据集筛选区域 -->
|
| 270 |
+
<div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
|
| 271 |
+
<div class="flex items-center gap-3">
|
| 272 |
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Dataset</span>
|
| 273 |
+
<div v-if="dataGroups.length > 0" class="flex gap-2 items-center overflow-x-auto">
|
| 274 |
+
<label v-for="g in dataGroups" :key="g"
|
| 275 |
+
class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer dark:text-slate-300"
|
| 276 |
+
:class="{ 'bg-gray-100 dark:bg-gray-700 dark:text-white': selectedDataNameChart === g }">
|
| 277 |
+
<input type="radio" class="hidden" :value="g" v-model="selectedDataNameChart" /> {{ dataNameDisplayMap[g]
|
| 278 |
+
? dataNameDisplayMap[g] : g }}
|
| 279 |
+
</label>
|
| 280 |
+
</div>
|
| 281 |
+
<div v-else class="text-sm text-gray-500 dark:text-gray-400">(数据集中未检测到 data_name 列)</div>
|
| 282 |
</div>
|
| 283 |
+
</div>
|
| 284 |
+
|
| 285 |
+
<!-- 图表内容 -->
|
| 286 |
+
<div class="p-6">
|
| 287 |
+
<Chart :autoShowSeries="autoShowSeries" />
|
| 288 |
</div>
|
| 289 |
</div>
|
| 290 |
</div>
|
src/views/logo.png
ADDED
|
tsconfig.node.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"extends": "@tsconfig/node22/tsconfig.json",
|
| 3 |
-
"include": [
|
| 4 |
-
"vite.config.*",
|
| 5 |
-
"vitest.config.*",
|
| 6 |
-
"cypress.config.*",
|
| 7 |
-
"nightwatch.conf.*",
|
| 8 |
-
"playwright.config.*",
|
| 9 |
-
"eslint.config.*"
|
| 10 |
-
],
|
| 11 |
-
"compilerOptions": {
|
| 12 |
-
"noEmit": true,
|
| 13 |
-
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
| 14 |
-
|
| 15 |
-
"module": "ESNext",
|
| 16 |
-
"moduleResolution": "Bundler",
|
| 17 |
-
"types": ["node"]
|
| 18 |
-
}
|
| 19 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|