Skip to content

性能优化

RayChart 基于 Three.js 实时渲染 3D 场景,性能主要受数据量材质复杂度影响。本章介绍内置的优化机制与推荐做法。

数据量控制

内置容量限制

笛卡尔图表(柱状图、折线图、散点图等)单屏可渲染的类别数由网格宽度与最小带宽决定:

  • 默认 grid3D.boxWidth 为 20,最小带宽为 1.6,因此单屏默认约显示 12 个类别
  • 数据量超出容量时,会自动启用滚动窗口,只渲染窗口内的数据(见 坐标系统xAxis3D.window 的说明)

因此不需要担心一次性传入大量数据导致崩溃,但为了让关键数据完整可见,建议:

  • 单系列数据量控制在数百以内(类别较多时显式配置 xAxis3D.window
  • 大规模历史数据(如监控曲线)用采样聚合减少点数

数据采样

大数据量时先采样再渲染:

javascript
// 原始数据(如 10000 个点)
const rawData = Array.from({ length: 10000 }, (_, i) => i)

// 等间隔采样到 1000 个点
const sampledData = rawData.filter((_, i) => i % 10 === 0)

const option = {
  series: [{ data: sampledData }],
  xAxis3D: { data: sampledData.map((_, i) => String(i)) }
}

数据聚合

将数据聚合到更粗的粒度,适合时间序列:

javascript
// 按小时聚合分钟数据(取平均值)
const aggregatedData = minuteData.reduce((acc, item, i) => {
  const hourIndex = Math.floor(i / 60)
  if (!acc[hourIndex]) {
    acc[hourIndex] = { sum: 0, count: 0 }
  }
  acc[hourIndex].sum += item.value
  acc[hourIndex].count++
  return acc
}, []).map(item => item.sum / item.count)

渲染优化

避免频繁更新

option 更新会触发图表重新渲染并播放入场动画,高频更新会带来额外开销:

javascript
// ❌ 100ms 更新一次:频繁重建场景
setInterval(() => {
  chartOption.value = { ...chartOption.value, series: [{ data: newData }] }
}, 100)

// ✅ 降低更新频率,如 1000ms
setInterval(() => {
  chartOption.value = { ...chartOption.value, series: [{ data: newData }] }
}, 1000)

使用 v-if 而非 v-show

图表组件挂载时会创建 WebGL 上下文与场景,不可见的图表应彻底卸载:

vue
<!-- ❌ v-show 仅隐藏,WebGL 资源仍被占用 -->
<RayBar v-show="visible" :option="option" />

<!-- ✅ v-if 在不可见时卸载组件,释放 GPU 资源 -->
<RayBar v-if="visible" :option="option" />

及时销毁

组件卸载时框架会释放场景资源。显式调用 dispose() 可立即回收(如页面销毁前):

vue
<script setup>
import { ref, onUnmounted } from 'vue'

const chartRef = ref()

onUnmounted(() => {
  chartRef.value?.dispose()
})
</script>

资源管理

RayChart 内置全局资源管理器(单例),自动复用与回收 Three.js 资源:

  • 材质/几何体/纹理池 - 相同配置的资源自动复用,避免重复创建
  • LRU 清理 - 超过 30 秒未被引用的资源会被标记,每 60 秒自动清理一次
  • 缓存上限 - 每种资源最多缓存 100 个,超出后按最近最少使用策略淘汰

以上策略均为内置行为,无需手动配置。唯一的手动入口是 ResourceManager.getInstance()(如需提前触发 cleanup() 可调用)。

性能监控

全局监控器

RayChart 提供 globalMonitor(性能监控)与 DEV_MONITOR(同实例别名):

javascript
import { globalMonitor } from 'raychart'

注意

globalMonitor 仅在开发环境生效(内部由 import.meta.env.DEV 控制)。生产构建中所有调用自动变为空操作,不会产生任何开销,因此可以放心在代码中保留监控逻辑。

手动计时

javascript
// start / end 配对使用
const start = globalMonitor.start('render')
// ... 执行要测量的操作
const duration = globalMonitor.end('render', start)
console.log(`耗时: ${duration}ms`)

函数计时

javascript
// 同步函数
const result = globalMonitor.measure('calculation', () => {
  return heavyCalculation()
})

// 异步函数
const data = await globalMonitor.measureAsync('fetchData', async () => {
  return await fetch('/api/data')
})

查看统计

getStats(label) 返回指定标签的统计信息,未采样时返回 null

javascript
const stats = globalMonitor.getStats('render')
if (stats) {
  console.log('平均:', stats.avg.toFixed(2) + 'ms')
  console.log('最小:', stats.min.toFixed(2) + 'ms')
  console.log('最大:', stats.max.toFixed(2) + 'ms')
  console.log('采样次数:', stats.count)
}

统计字段:count(采样次数)、total(总耗时)、avg(平均)、min(最小)、max(最大)、last(最近一次)。

查看全部标签的统计:

javascript
// 获取报告对象
const report = globalMonitor.getReport()

// 直接打印到控制台
globalMonitor.printReport()

清除与开关

javascript
globalMonitor.clear('render')  // 清除指定标签
globalMonitor.clear()          // 清除全部

globalMonitor.setEnabled(false) // 临时关闭监控
globalMonitor.isEnabled()       // 查询是否开启

图表配置优化

降低材质复杂度

PBR 材质的高光参数(metalnessroughness)与后处理特效是主要开销来源:

javascript
// ❌ 高复杂度:高金属度 + 开启 bloom
{
  series: [{
    data: largeDataset,
    itemStyle: {
      metalness: 0.8,
      roughness: 0.2
    }
  }],
  postprocessing: {
    bloom: { enabled: true, strength: 1.5 }
  }
}

// ✅ 优化后:降低金属度,关闭后处理
{
  series: [{
    data: sampledDataset,  // 采样后的数据
    itemStyle: {
      metalness: 0.3,
      roughness: 0.5
    }
  }]
}

后处理

RayChart 目前仅内置 bloom(泛光)效果,通过 postprocessing.bloom 配置。它对性能影响显著,追求流畅度时建议关闭(详见 后处理)。

简化光照

引擎自带四点光照系统(环境光 + 主光 + 补光 + 轮廓光),不配置 lights 时自动启用:

javascript
// ✅ 默认光照:不配置 lights 即可,引擎自动处理
{
  series: [{ data: [120, 200, 150] }],
  xAxis3D: { data: ['A', 'B', 'C'] }
}

手动配置过多光源会增加逐帧计算量,建议保持少量光源:

javascript
// 两个光源足以满足大部分场景
lights: [
  { type: 'Ambient', intensity: 0.6 },
  { type: 'Directional', intensity: 0.8, position: { x: 10, y: 15, z: 10 } }
]

移动端优化

检测设备

javascript
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)

const option = {
  series: [{
    data: isMobile ? sampledData : fullData
  }],
  xAxis3D: {
    data: categories
  },
  postprocessing: {
    bloom: { enabled: !isMobile }  // 移动端关闭 bloom
  }
}

降低渲染分辨率

通过组件的 width / height 属性控制画布尺寸,移动端使用较小画布可显著降低 GPU 负担:

vue
<template>
  <RayBar 
    :option="option" 
    :width="isMobile ? '100%' : '800px'"
    :height="isMobile ? '400px' : '600px'"
  />
</template>

性能检查清单

  • [ ] 数据量是否在容量限制内,或已使用窗口 / 采样 / 聚合
  • [ ] 是否避免了高频 option 更新
  • [ ] 不可见图表是否使用 v-if 卸载
  • [ ] 页面销毁前是否调用了 dispose()
  • [ ] 是否按需启用 bloom 后处理
  • [ ] 是否使用了默认四点光照而非过多自定义光源
  • [ ] 开发环境下是否用 globalMonitor 验证过渲染耗时

常见问题

Q: 图表卡顿怎么办?

A: 按开销从大到小排查:

  1. 关闭 bloom(postprocessing.bloom.enabled = false
  2. 减少数据量(采样 / 聚合 / 窗口)
  3. 降低材质 metalnessroughness 的复杂度
  4. 降低更新频率

Q: 内存持续增长?

A:

  1. 确认图表是否被反复创建而未销毁(使用 v-if 而非 v-show
  2. 页面销毁时调用 dispose()
  3. 避免创建过多图表实例,必要时复用实例更新数据

Q: 移动端性能差?

A:

  1. 大幅减少数据量
  2. 关闭 bloom
  3. 使用较简单的材质配置
  4. 使用较小的画布尺寸

性能优化案例

案例 1: 大数据量折线图

问题: 10000 个数据点导致卡顿

解决方案: 先采样,再交给图表渲染:

javascript
// 等间隔采样到 1000 个点
const simplifiedData = rawData.filter((_, i) => i % 10 === 0)

const option = {
  series: [{
    data: simplifiedData
  }],
  xAxis3D: { data: simplifiedData.map((_, i) => String(i)) }
}

数据量依旧很大时,可配合 xAxis3D.window 只展示部分区间(见 坐标系统)。

案例 2: 多图表页面

问题: 页面同时存在多个图表,内存与 GPU 占用高

解决方案: 只渲染可见的图表,不可见时卸载:

vue
<template>
  <div v-for="chart in visibleCharts" :key="chart.id">
    <component :is="chart.component" v-if="chart.visible" :option="chart.option" />
  </div>
</template>

<script setup>
import { RayBar, RayLine, RayPie } from 'raychart'

// 只渲染可见的图表
const visibleCharts = computed(() => {
  return charts.value.filter(chart => isInViewport(chart))
})
</script>

效果: 图表随滚动按需挂载/卸载,避免所有图表同时占用 GPU 资源。