Skip to content

可视化应用详解

概述

数据可视化是将数据转换为图形或图像的过程,使人们能够更直观地理解数据。根据数据维度和展示方式的不同,可视化应用可以分为一维、二维和三维可视化。

一维可视化应用

定义

一维可视化处理线性数据,数据沿着一个维度排列,通常用于展示时间序列、进度、比例等单一维度的数据变化。

常见应用场景

应用类型示例特点
时间轴历史事件、项目进度按时间顺序展示事件
进度条文件上传、任务完成度展示完成百分比
音频波形音乐播放器、录音展示音频振幅变化
折线图股票走势、温度变化展示数据随时间变化趋势
柱状图销售数据、投票结果展示离散数据的比较
面积图流量统计、资源使用展示累积效果
仪表盘速度表、温度计展示当前值在范围内的位置

实现方式

1. 原生 Canvas 实现

优点

  • 性能优秀,适合大量数据渲染
  • 完全控制渲染过程
  • 无需引入第三方库

缺点

  • 开发成本高
  • 需要手动处理交互
  • 代码复杂度高

示例代码

javascript
class WaveformVisualizer {
  constructor(canvas, options = {}) {
    this.canvas = canvas
    this.ctx = canvas.getContext('2d')
    this.options = {
      width: options.width || 800,
      height: options.height || 200,
      color: options.color || '#1890ff',
      backgroundColor: options.backgroundColor || '#f0f0f0',
      ...options
    }
  }

  drawWaveform(audioData) {
    const { width, height, color, backgroundColor } = this.options
    const step = Math.ceil(audioData.length / width)
    const amp = height / 2

    // 清空画布
    this.ctx.fillStyle = backgroundColor
    this.ctx.fillRect(0, 0, width, height)

    // 绘制波形
    this.ctx.beginPath()
    this.ctx.moveTo(0, amp)

    for (let i = 0; i < width; i++) {
      let min = 1.0
      let max = -1.0

      for (let j = 0; j < step; j++) {
        const datum = audioData[i * step + j]
        if (datum < min) min = datum
        if (datum > max) max = datum
      }

      this.ctx.lineTo(i, (1 + min) * amp)
      this.ctx.lineTo(i, (1 + max) * amp)
    }

    this.ctx.strokeStyle = color
    this.ctx.lineWidth = 1
    this.ctx.stroke()
  }

  drawLineChart(data) {
    const { width, height, color } = this.options
    const padding = 40
    const chartWidth = width - padding * 2
    const chartHeight = height - padding * 2

    // 计算数据范围
    const maxValue = Math.max(...data.map(d => d.value))
    const minValue = Math.min(...data.map(d => d.value))
    const valueRange = maxValue - minValue

    // 绘制坐标轴
    this.ctx.beginPath()
    this.ctx.moveTo(padding, padding)
    this.ctx.lineTo(padding, height - padding)
    this.ctx.lineTo(width - padding, height - padding)
    this.ctx.strokeStyle = '#999'
    this.ctx.stroke()

    // 绘制数据线
    this.ctx.beginPath()
    data.forEach((point, index) => {
      const x = padding + (index / (data.length - 1)) * chartWidth
      const y =
        height - padding - ((point.value - minValue) / valueRange) * chartHeight

      if (index === 0) {
        this.ctx.moveTo(x, y)
      } else {
        this.ctx.lineTo(x, y)
      }
    })

    this.ctx.strokeStyle = color
    this.ctx.lineWidth = 2
    this.ctx.stroke()

    // 绘制数据点
    data.forEach((point, index) => {
      const x = padding + (index / (data.length - 1)) * chartWidth
      const y =
        height - padding - ((point.value - minValue) / valueRange) * chartHeight

      this.ctx.beginPath()
      this.ctx.arc(x, y, 4, 0, Math.PI * 2)
      this.ctx.fillStyle = color
      this.ctx.fill()
    })
  }
}

2. SVG 实现

优点

  • 矢量图形,无限缩放不失真
  • 支持事件绑定
  • DOM 操作简单

缺点

  • 大量数据时性能下降
  • 复杂图形渲染慢

示例代码

javascript
class SVGTimeline {
  constructor(container, options = {}) {
    this.container = container
    this.options = {
      width: options.width || 800,
      height: options.height || 100,
      ...options
    }

    this.svg = this.createSVG()
  }

  createSVG() {
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
    svg.setAttribute('width', this.options.width)
    svg.setAttribute('height', this.options.height)
    this.container.appendChild(svg)
    return svg
  }

  render(events) {
    const { width, height } = this.options
    const duration = Math.max(...events.map(e => e.time))
    const scale = width / duration

    // 绘制时间轴线
    const line = document.createElementNS('http://www.w3.org/2000/svg', 'line')
    line.setAttribute('x1', 0)
    line.setAttribute('y1', height / 2)
    line.setAttribute('x2', width)
    line.setAttribute('y2', height / 2)
    line.setAttribute('stroke', '#ddd')
    line.setAttribute('stroke-width', 2)
    this.svg.appendChild(line)

    // 绘制事件点
    events.forEach(event => {
      const x = event.time * scale

      // 圆点
      const circle = document.createElementNS(
        'http://www.w3.org/2000/svg',
        'circle'
      )
      circle.setAttribute('cx', x)
      circle.setAttribute('cy', height / 2)
      circle.setAttribute('r', 6)
      circle.setAttribute('fill', event.color || '#1890ff')
      circle.setAttribute('class', 'event-point')
      circle.style.cursor = 'pointer'

      // 添加交互
      circle.addEventListener('click', () => {
        this.onEventClick(event)
      })

      circle.addEventListener('mouseenter', () => {
        circle.setAttribute('r', 8)
      })

      circle.addEventListener('mouseleave', () => {
        circle.setAttribute('r', 6)
      })

      this.svg.appendChild(circle)

      // 标签
      if (event.label) {
        const text = document.createElementNS(
          'http://www.w3.org/2000/svg',
          'text'
        )
        text.setAttribute('x', x)
        text.setAttribute('y', height / 2 - 15)
        text.setAttribute('text-anchor', 'middle')
        text.setAttribute('font-size', '12')
        text.textContent = event.label
        this.svg.appendChild(text)
      }
    })
  }

  onEventClick(event) {
    console.log('Event clicked:', event)
  }
}

流行库对比

1. Chart.js

简介:简单、灵活的 JavaScript 图表库

优点

  • 📦 体积小(~60KB)
  • 🎨 8 种图表类型
  • 📱 响应式设计
  • 🔄 动画效果流畅
  • 📖 文档完善
  • 🆓 完全免费开源

缺点

  • 定制性有限
  • 复杂图表支持不足
  • 不支持 3D 图表

适用场景

  • 简单的数据可视化
  • 快速原型开发
  • 移动端应用

示例代码

javascript
import Chart from 'chart.js/auto'

const ctx = document.getElementById('myChart')

new Chart(ctx, {
  type: 'line',
  data: {
    labels: ['1月', '2月', '3月', '4月', '5月', '6月'],
    datasets: [
      {
        label: '销售额',
        data: [12, 19, 3, 5, 2, 3],
        borderColor: '#1890ff',
        backgroundColor: 'rgba(24, 144, 255, 0.1)',
        tension: 0.4,
        fill: true
      }
    ]
  },
  options: {
    responsive: true,
    plugins: {
      legend: {
        position: 'top'
      },
      title: {
        display: true,
        text: '月度销售趋势'
      }
    },
    scales: {
      y: {
        beginAtZero: true
      }
    },
    interaction: {
      intersect: false,
      mode: 'index'
    }
  }
})

2. ECharts

简介:百度开源的强大可视化库

优点

  • 🚀 功能强大,图表类型丰富
  • 📊 支持大数据量渲染
  • 🎨 高度可定制
  • 🌍 支持地图可视化
  • 📱 移动端友好
  • 🔧 配置式开发

缺点

  • 📦 体积较大(~1MB)
  • 📖 学习曲线陡峭
  • 配置项繁多

适用场景

  • 企业级数据可视化
  • 大屏展示
  • 复杂图表需求

示例代码

javascript
import * as echarts from 'echarts'

const chart = echarts.init(document.getElementById('chart'))

const option = {
  title: {
    text: '销售数据分析'
  },
  tooltip: {
    trigger: 'axis',
    axisPointer: {
      type: 'cross'
    }
  },
  legend: {
    data: ['销售额', '利润']
  },
  grid: {
    left: '3%',
    right: '4%',
    bottom: '3%',
    containLabel: true
  },
  toolbox: {
    feature: {
      saveAsImage: {}
    }
  },
  xAxis: {
    type: 'category',
    boundaryGap: false,
    data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
  },
  yAxis: {
    type: 'value'
  },
  series: [
    {
      name: '销售额',
      type: 'line',
      smooth: true,
      data: [120, 132, 101, 134, 90, 230, 210],
      areaStyle: {
        opacity: 0.3
      }
    },
    {
      name: '利润',
      type: 'line',
      smooth: true,
      data: [220, 182, 191, 234, 290, 330, 310],
      areaStyle: {
        opacity: 0.3
      }
    }
  ],
  dataZoom: [
    {
      type: 'inside',
      start: 0,
      end: 100
    },
    {
      start: 0,
      end: 100
    }
  ]
}

chart.setOption(option)

// 响应式
window.addEventListener('resize', () => {
  chart.resize()
})

3. D3.js

简介:数据驱动文档的 JavaScript 库

优点

  • 🎯 完全控制,灵活性极高
  • 📊 支持任意可视化
  • 🔧 底层 API,可定制性强
  • 🌐 社区活跃,资源丰富
  • 📦 模块化设计

缺点

  • 📖 学习曲线极陡
  • 💻 代码量大
  • ⏱️ 开发周期长

适用场景

  • 高度定制化可视化
  • 复杂交互需求
  • 数据新闻可视化

示例代码

javascript
import * as d3 from 'd3'

// 创建折线图
function createLineChart(data, container) {
  const margin = { top: 20, right: 20, bottom: 30, left: 50 }
  const width = 800 - margin.left - margin.right
  const height = 400 - margin.top - margin.bottom

  const svg = d3
    .select(container)
    .append('svg')
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
    .append('g')
    .attr('transform', `translate(${margin.left},${margin.top})`)

  // 比例尺
  const x = d3
    .scaleTime()
    .domain(d3.extent(data, d => d.date))
    .range([0, width])

  const y = d3
    .scaleLinear()
    .domain([0, d3.max(data, d => d.value)])
    .range([height, 0])

  // 线生成器
  const line = d3
    .line()
    .x(d => x(d.date))
    .y(d => y(d.value))
    .curve(d3.curveMonotoneX)

  // 区域生成器
  const area = d3
    .area()
    .x(d => x(d.date))
    .y0(height)
    .y1(d => y(d.value))
    .curve(d3.curveMonotoneX)

  // 绘制区域
  svg
    .append('path')
    .datum(data)
    .attr('fill', 'rgba(24, 144, 255, 0.1)')
    .attr('d', area)

  // 绘制线条
  svg
    .append('path')
    .datum(data)
    .attr('fill', 'none')
    .attr('stroke', '#1890ff')
    .attr('stroke-width', 2)
    .attr('d', line)

  // 坐标轴
  svg
    .append('g')
    .attr('transform', `translate(0,${height})`)
    .call(d3.axisBottom(x).ticks(10))

  svg.append('g').call(d3.axisLeft(y))

  // 数据点
  svg
    .selectAll('.dot')
    .data(data)
    .enter()
    .append('circle')
    .attr('class', 'dot')
    .attr('cx', d => x(d.date))
    .attr('cy', d => y(d.value))
    .attr('r', 4)
    .attr('fill', '#1890ff')
    .on('mouseover', function (event, d) {
      d3.select(this).transition().duration(200).attr('r', 6)

      // 显示 tooltip
      const tooltip = d3
        .select('body')
        .append('div')
        .attr('class', 'tooltip')
        .style('opacity', 0)

      tooltip.transition().duration(200).style('opacity', 0.9)

      tooltip
        .html(`值: ${d.value}`)
        .style('left', event.pageX + 10 + 'px')
        .style('top', event.pageY - 28 + 'px')
    })
    .on('mouseout', function () {
      d3.select(this).transition().duration(200).attr('r', 4)

      d3.selectAll('.tooltip').remove()
    })
}

4. Highcharts

简介:商业图表库,功能强大

优点

  • 🎨 图表美观,开箱即用
  • 📊 图表类型丰富
  • 🌍 国际化支持
  • 📱 响应式设计
  • 📖 文档详细

缺点

  • 💰 商业项目收费
  • 📦 体积较大
  • 定制性不如 D3

适用场景

  • 商业项目
  • 快速开发
  • 企业报表

示例代码

javascript
import Highcharts from 'highcharts'

Highcharts.chart('container', {
  chart: {
    type: 'line'
  },
  title: {
    text: '月度销售趋势'
  },
  subtitle: {
    text: 'Source: example.com'
  },
  xAxis: {
    categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
  },
  yAxis: {
    title: {
      text: '销售额 (万元)'
    }
  },
  plotOptions: {
    line: {
      dataLabels: {
        enabled: true
      },
      enableMouseTracking: false
    }
  },
  series: [
    {
      name: 'Tokyo',
      data: [7.0, 6.9, 9.5, 14.5, 18.4, 21.5]
    },
    {
      name: 'London',
      data: [3.9, 4.2, 5.7, 8.5, 11.9, 15.2]
    }
  ]
})

二维可视化应用

定义

二维可视化处理平面数据,数据在两个维度(X、Y)上展开,用于展示数据分布、关系、地理信息等。

常见应用场景

应用类型示例特点
散点图数据分布、相关性分析展示两个变量的关系
热力图用户点击、人口密度展示数据密度分布
树图文件系统、组织结构展示层级关系
地图地理数据、位置信息展示地理位置数据
关系图社交网络、知识图谱展示节点和关系
矩形树图磁盘空间、市场份额展示层级和比例
和弦图流量来源、贸易关系展示流向关系

实现方式

1. Canvas 实现

优点

  • 性能优秀,适合大数据量
  • 像素级控制
  • 适合实时渲染

缺点

  • 交互实现复杂
  • 不支持事件冒泡
  • 缩放会失真

示例代码

javascript
class HeatmapCanvas {
  constructor(canvas, options = {}) {
    this.canvas = canvas
    this.ctx = canvas.getContext('2d')
    this.options = {
      width: options.width || 800,
      height: options.height || 600,
      radius: options.radius || 25,
      max: options.max || 100,
      ...options
    }
  }

  render(data) {
    const { width, height, radius, max } = this.options

    this.ctx.clearRect(0, 0, width, height)

    // 绘制热力点
    data.forEach(point => {
      const gradient = this.ctx.createRadialGradient(
        point.x,
        point.y,
        0,
        point.x,
        point.y,
        radius
      )

      const alpha = Math.min(point.value / max, 1)
      gradient.addColorStop(0, `rgba(255, 0, 0, ${alpha})`)
      gradient.addColorStop(1, 'rgba(255, 0, 0, 0)')

      this.ctx.fillStyle = gradient
      this.ctx.fillRect(
        point.x - radius,
        point.y - radius,
        radius * 2,
        radius * 2
      )
    })

    // 应用颜色映射
    const imageData = this.ctx.getImageData(0, 0, width, height)
    const pixels = imageData.data

    for (let i = 0; i < pixels.length; i += 4) {
      const alpha = pixels[i + 3]

      if (alpha > 0) {
        const color = this.getColor(alpha / 255)
        pixels[i] = color.r
        pixels[i + 1] = color.g
        pixels[i + 2] = color.b
      }
    }

    this.ctx.putImageData(imageData, 0, 0)
  }

  getColor(value) {
    const colors = [
      { r: 0, g: 0, b: 255 }, // 蓝
      { r: 0, g: 255, b: 255 }, // 青
      { r: 0, g: 255, b: 0 }, // 绿
      { r: 255, g: 255, b: 0 }, // 黄
      { r: 255, g: 0, b: 0 } // 红
    ]

    const index = Math.min(
      Math.floor(value * (colors.length - 1)),
      colors.length - 2
    )
    const t = value * (colors.length - 1) - index

    return {
      r: Math.round(
        colors[index].r + t * (colors[index + 1].r - colors[index].r)
      ),
      g: Math.round(
        colors[index].g + t * (colors[index + 1].g - colors[index].g)
      ),
      b: Math.round(
        colors[index].b + t * (colors[index + 1].b - colors[index].b)
      )
    }
  }
}

2. SVG 实现

优点

  • 矢量图形,无限缩放
  • 支持事件和交互
  • DOM 操作简单

缺点

  • 大数据量性能差
  • 复杂图形渲染慢

示例代码

javascript
class SVGScatterPlot {
  constructor(container, options = {}) {
    this.container = container
    this.options = {
      width: options.width || 800,
      height: options.height || 600,
      margin: options.margin || { top: 20, right: 20, bottom: 30, left: 50 },
      ...options
    }

    this.svg = this.createSVG()
    this.scales = this.createScales()
  }

  createSVG() {
    const { width, height, margin } = this.options

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
    svg.setAttribute('width', width)
    svg.setAttribute('height', height)

    const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
    g.setAttribute('transform', `translate(${margin.left},${margin.top})`)
    svg.appendChild(g)

    this.container.appendChild(svg)
    return g
  }

  createScales() {
    const { width, height, margin } = this.options
    const innerWidth = width - margin.left - margin.right
    const innerHeight = height - margin.top - margin.bottom

    return {
      x: d3.scaleLinear().range([0, innerWidth]),
      y: d3.scaleLinear().range([innerHeight, 0])
    }
  }

  render(data) {
    const { width, height, margin } = this.options
    const innerWidth = width - margin.left - margin.right
    const innerHeight = height - margin.top - margin.bottom

    // 设置比例尺域
    this.scales.x.domain([0, d3.max(data, d => d.x)])
    this.scales.y.domain([0, d3.max(data, d => d.y)])

    // 绘制坐标轴
    this.drawAxes()

    // 绘制散点
    data.forEach(point => {
      const circle = document.createElementNS(
        'http://www.w3.org/2000/svg',
        'circle'
      )
      circle.setAttribute('cx', this.scales.x(point.x))
      circle.setAttribute('cy', this.scales.y(point.y))
      circle.setAttribute('r', 5)
      circle.setAttribute('fill', point.color || '#1890ff')
      circle.setAttribute('class', 'point')
      circle.style.cursor = 'pointer'

      // 添加交互
      circle.addEventListener('mouseenter', () => {
        circle.setAttribute('r', 8)
      })

      circle.addEventListener('mouseleave', () => {
        circle.setAttribute('r', 5)
      })

      this.svg.appendChild(circle)
    })
  }

  drawAxes() {
    // X 轴
    const xAxis = d3.axisBottom(this.scales.x)
    const xAxisG = document.createElementNS('http://www.w3.org/2000/svg', 'g')
    xAxisG.setAttribute('transform', `translate(0,${innerHeight})`)
    d3.select(xAxisG).call(xAxis)
    this.svg.appendChild(xAxisG)

    // Y 轴
    const yAxis = d3.axisLeft(this.scales.y)
    const yAxisG = document.createElementNS('http://www.w3.org/2000/svg', 'g')
    d3.select(yAxisG).call(yAxis)
    this.svg.appendChild(yAxisG)
  }
}

流行库对比

1. Leaflet

简介:轻量级地图库

优点

  • 📦 体积小(~40KB)
  • 🚀 性能优秀
  • 📱 移动端友好
  • 🔌 插件丰富
  • 🆓 开源免费

缺点

  • 功能相对简单
  • 3D 地图支持有限
  • 高级功能需要插件

适用场景

  • 简单地图应用
  • 移动端地图
  • 快速开发

示例代码

javascript
import L from 'leaflet'

// 创建地图
const map = L.map('map').setView([51.505, -0.09], 13)

// 添加图层
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap contributors'
}).addTo(map)

// 添加标记
const marker = L.marker([51.5, -0.09]).addTo(map)
marker.bindPopup('<b>Hello world!</b><br>I am a popup.').openPopup()

// 添加圆形
const circle = L.circle([51.508, -0.11], {
  color: 'red',
  fillColor: '#f03',
  fillOpacity: 0.5,
  radius: 500
}).addTo(map)

// 添加多边形
const polygon = L.polygon([
  [51.509, -0.08],
  [51.503, -0.06],
  [51.51, -0.047]
]).addTo(map)

// 事件处理
map.on('click', e => {
  alert('You clicked the map at ' + e.latlng)
})

2. Mapbox GL JS

简介:强大的矢量地图库

优点

  • 🎨 地图样式丰富
  • 🚀 性能优秀
  • 🌍 支持 3D 地形
  • 📊 数据可视化强大
  • 🔧 高度可定制

缺点

  • 💰 需要访问令牌
  • 📦 体积较大
  • 📖 学习曲线陡

适用场景

  • 专业地图应用
  • 数据可视化地图
  • 3D 地形展示

示例代码

javascript
import mapboxgl from 'mapbox-gl'

mapboxgl.accessToken = 'your-mapbox-token'

const map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/streets-v11',
  center: [-74.5, 40],
  zoom: 9
})

// 添加导航控件
map.addControl(new mapboxgl.NavigationControl())

// 添加标记
new mapboxgl.Marker().setLngLat([-74.5, 40]).addTo(map)

// 添加弹出框
new mapboxgl.Popup()
  .setLngLat([-74.5, 40])
  .setHTML('<h1>Hello World!</h1>')
  .addTo(map)

// 添加数据图层
map.on('load', () => {
  map.addSource('earthquakes', {
    type: 'geojson',
    data: 'https://example.com/earthquakes.geojson'
  })

  map.addLayer({
    id: 'earthquakes-heat',
    type: 'heatmap',
    source: 'earthquakes',
    maxzoom: 9,
    paint: {
      'heatmap-weight': ['interpolate', ['linear'], ['get', 'mag'], 0, 0, 6, 1],
      'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 0, 1, 9, 3],
      'heatmap-color': [
        'interpolate',
        ['linear'],
        ['heatmap-density'],
        0,
        'rgba(33,102,172,0)',
        0.2,
        'rgb(103,169,207)',
        0.4,
        'rgb(209,229,240)',
        0.6,
        'rgb(253,219,199)',
        0.8,
        'rgb(239,138,98)',
        1,
        'rgb(178,24,43)'
      ],
      'heatmap-radius': ['interpolate', ['linear'], ['zoom'], 0, 2, 9, 20],
      'heatmap-opacity': ['interpolate', ['linear'], ['zoom'], 7, 1, 9, 0]
    }
  })
})

3. AntV G2

简介:蚂蚁金服开源的图形语法库

优点

  • 📊 图表类型丰富
  • 🎨 默认样式美观
  • 🔧 高度可定制
  • 📱 响应式设计
  • 🇨🇳 中文文档完善

缺点

  • 📦 体积较大
  • 国际化支持一般
  • 社区相对较小

适用场景

  • 企业级可视化
  • 数据分析平台
  • 报表系统

示例代码

javascript
import { Chart } from '@antv/g2'

const data = [
  { genre: 'Sports', sold: 275 },
  { genre: 'Strategy', sold: 115 },
  { genre: 'Action', sold: 120 },
  { genre: 'Shooter', sold: 350 },
  { genre: 'Other', sold: 150 }
]

const chart = new Chart({
  container: 'container',
  autoFit: true,
  height: 500
})

chart.data(data)
chart.interval().position('genre*sold').color('genre')

chart.render()

4. AntV G6

简介:图可视化引擎

优点

  • 📊 支持多种图布局
  • 🎨 样式丰富
  • 🔄 动态图支持
  • 📱 交互能力强
  • 🔧 高度可定制

缺点

  • 📦 体积较大
  • 学习曲线陡
  • 性能优化复杂

适用场景

  • 关系图可视化
  • 知识图谱
  • 组织架构图

示例代码

javascript
import G6 from '@antv/g6'

const data = {
  nodes: [
    { id: 'node1', label: 'Circle1', x: 150, y: 150 },
    { id: 'node2', label: 'Circle2', x: 300, y: 150 },
    { id: 'node3', label: 'Circle3', x: 150, y: 300 }
  ],
  edges: [
    { source: 'node1', target: 'node2' },
    { source: 'node2', target: 'node3' },
    { source: 'node3', target: 'node1' }
  ]
}

const graph = new G6.Graph({
  container: 'container',
  width: 800,
  height: 500,
  modes: {
    default: ['drag-canvas', 'zoom-canvas', 'drag-node']
  },
  nodeStateStyles: {
    hover: {
      fillOpacity: 0.8
    },
    selected: {
      lineWidth: 3
    }
  },
  edgeStateStyles: {
    hover: {
      stroke: '#1890ff'
    }
  }
})

graph.data(data)
graph.render()

// 交互事件
graph.on('node:mouseenter', e => {
  graph.setItemState(e.item, 'hover', true)
})

graph.on('node:mouseleave', e => {
  graph.setItemState(e.item, 'hover', false)
})

三维可视化应用

定义

三维可视化处理立体数据,数据在三个维度(X、Y、Z)上展开,用于展示空间关系、立体结构、虚拟场景等。

常见应用场景

应用类型示例特点
3D 地球地理信息系统、全球数据展示全球数据分布
建筑模型BIM、建筑可视化展示建筑结构
产品展示电商、工业设计360° 产品展示
游戏场景网页游戏、虚拟世界交互式 3D 场景
数据可视化3D 图表、数据立方体立体数据展示
虚拟现实VR 应用、虚拟展厅沉浸式体验
科学可视化分子结构、天文模拟科学数据展示

实现方式

1. WebGL 原生实现

优点

  • 性能最优
  • 完全控制渲染
  • 无依赖

缺点

  • 开发难度极高
  • 代码量大
  • 需要图形学知识

示例代码

javascript
class WebGLRenderer {
  constructor(canvas) {
    this.canvas = canvas
    this.gl =
      canvas.getContext('webgl') || canvas.getContext('experimental-webgl')

    if (!this.gl) {
      throw new Error('WebGL not supported')
    }

    this.init()
  }

  init() {
    const gl = this.gl

    // 顶点着色器
    const vsSource = `
      attribute vec4 aVertexPosition;
      attribute vec4 aVertexColor;
      
      uniform mat4 uModelViewMatrix;
      uniform mat4 uProjectionMatrix;
      
      varying lowp vec4 vColor;
      
      void main() {
        gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
        vColor = aVertexColor;
      }
    `

    // 片段着色器
    const fsSource = `
      varying lowp vec4 vColor;
      
      void main() {
        gl_FragColor = vColor;
      }
    `

    // 创建着色器程序
    const vertexShader = this.loadShader(gl.VERTEX_SHADER, vsSource)
    const fragmentShader = this.loadShader(gl.FRAGMENT_SHADER, fsSource)

    this.shaderProgram = gl.createProgram()
    gl.attachShader(this.shaderProgram, vertexShader)
    gl.attachShader(this.shaderProgram, fragmentShader)
    gl.linkProgram(this.shaderProgram)

    if (!gl.getProgramParameter(this.shaderProgram, gl.LINK_STATUS)) {
      console.error('Shader program failed to link')
      return
    }

    // 获取属性和 uniform 位置
    this.programInfo = {
      attribLocations: {
        vertexPosition: gl.getAttribLocation(
          this.shaderProgram,
          'aVertexPosition'
        ),
        vertexColor: gl.getAttribLocation(this.shaderProgram, 'aVertexColor')
      },
      uniformLocations: {
        projectionMatrix: gl.getUniformLocation(
          this.shaderProgram,
          'uProjectionMatrix'
        ),
        modelViewMatrix: gl.getUniformLocation(
          this.shaderProgram,
          'uModelViewMatrix'
        )
      }
    }

    // 创建缓冲区
    this.buffers = this.initBuffers()
  }

  loadShader(type, source) {
    const gl = this.gl
    const shader = gl.createShader(type)

    gl.shaderSource(shader, source)
    gl.compileShader(shader)

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      console.error('Shader compilation error:', gl.getShaderInfoLog(shader))
      gl.deleteShader(shader)
      return null
    }

    return shader
  }

  initBuffers() {
    const gl = this.gl

    // 顶点位置
    const positions = [
      // 前面
      -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0,
      // 后面
      -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0,
      // 顶面
      -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0,
      // 底面
      -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,
      // 右面
      1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0,
      // 左面
      -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0
    ]

    const positionBuffer = gl.createBuffer()
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW)

    // 颜色
    const faceColors = [
      [1.0, 0.0, 0.0, 1.0], // 前面 - 红
      [0.0, 1.0, 0.0, 1.0], // 后面 - 绿
      [0.0, 0.0, 1.0, 1.0], // 顶面 - 蓝
      [1.0, 1.0, 0.0, 1.0], // 底面 - 黄
      [1.0, 0.0, 1.0, 1.0], // 右面 - 紫
      [0.0, 1.0, 1.0, 1.0] // 左面 - 青
    ]

    let colors = []
    for (let j = 0; j < faceColors.length; ++j) {
      const c = faceColors[j]
      colors = colors.concat(c, c, c, c)
    }

    const colorBuffer = gl.createBuffer()
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW)

    // 索引
    const indices = [
      0,
      1,
      2,
      0,
      2,
      3, // 前
      4,
      5,
      6,
      4,
      6,
      7, // 后
      8,
      9,
      10,
      8,
      10,
      11, // 顶
      12,
      13,
      14,
      12,
      14,
      15, // 底
      16,
      17,
      18,
      16,
      18,
      19, // 右
      20,
      21,
      22,
      20,
      22,
      23 // 左
    ]

    const indexBuffer = gl.createBuffer()
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
    gl.bufferData(
      gl.ELEMENT_ARRAY_BUFFER,
      new Uint16Array(indices),
      gl.STATIC_DRAW
    )

    return {
      position: positionBuffer,
      color: colorBuffer,
      indices: indexBuffer
    }
  }

  render() {
    const gl = this.gl

    gl.clearColor(0.0, 0.0, 0.0, 1.0)
    gl.clearDepth(1.0)
    gl.enable(gl.DEPTH_TEST)
    gl.depthFunc(gl.LEQUAL)

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

    // 投影矩阵
    const fieldOfView = (45 * Math.PI) / 180
    const aspect = this.canvas.width / this.canvas.height
    const zNear = 0.1
    const zFar = 100.0
    const projectionMatrix = mat4.create()
    mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar)

    // 模型视图矩阵
    const modelViewMatrix = mat4.create()
    mat4.translate(modelViewMatrix, modelViewMatrix, [0.0, 0.0, -6.0])

    // 绑定位置缓冲区
    gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.position)
    gl.vertexAttribPointer(
      this.programInfo.attribLocations.vertexPosition,
      3,
      gl.FLOAT,
      false,
      0,
      0
    )
    gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition)

    // 绑定颜色缓冲区
    gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.color)
    gl.vertexAttribPointer(
      this.programInfo.attribLocations.vertexColor,
      4,
      gl.FLOAT,
      false,
      0,
      0
    )
    gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexColor)

    // 绑定索引缓冲区
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers.indices)

    // 使用着色器程序
    gl.useProgram(this.shaderProgram)

    // 设置 uniform
    gl.uniformMatrix4fv(
      this.programInfo.uniformLocations.projectionMatrix,
      false,
      projectionMatrix
    )
    gl.uniformMatrix4fv(
      this.programInfo.uniformLocations.modelViewMatrix,
      false,
      modelViewMatrix
    )

    // 绘制
    gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0)
  }
}

2. Three.js 实现

优点

  • 开发效率高
  • API 友好
  • 社区活跃
  • 文档完善

缺点

  • 体积较大
  • 性能不如原生
  • 学习曲线陡

示例代码

javascript
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

class ThreeScene {
  constructor(container) {
    this.container = container
    this.scene = new THREE.Scene()
    this.camera = null
    this.renderer = null
    this.controls = null

    this.init()
  }

  init() {
    // 创建场景
    this.scene.background = new THREE.Color(0x000000)

    // 创建相机
    const width = this.container.clientWidth
    const height = this.container.clientHeight

    this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
    this.camera.position.set(0, 0, 5)

    // 创建渲染器
    this.renderer = new THREE.WebGLRenderer({ antialias: true })
    this.renderer.setSize(width, height)
    this.renderer.setPixelRatio(window.devicePixelRatio)
    this.renderer.shadowMap.enabled = true
    this.container.appendChild(this.renderer.domElement)

    // 添加控制器
    this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    this.controls.enableDamping = true
    this.controls.dampingFactor = 0.05

    // 添加光源
    const ambientLight = new THREE.AmbientLight(0x404040, 0.5)
    this.scene.add(ambientLight)

    const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
    directionalLight.position.set(5, 5, 5)
    directionalLight.castShadow = true
    this.scene.add(directionalLight)

    // 添加物体
    this.createObjects()

    // 窗口大小调整
    window.addEventListener('resize', () => this.onResize())

    // 开始渲染
    this.animate()
  }

  createObjects() {
    // 创建立方体
    const geometry = new THREE.BoxGeometry(1, 1, 1)
    const material = new THREE.MeshStandardMaterial({
      color: 0x00ff00,
      roughness: 0.5,
      metalness: 0.5
    })
    this.cube = new THREE.Mesh(geometry, material)
    this.cube.castShadow = true
    this.cube.receiveShadow = true
    this.scene.add(this.cube)

    // 创建球体
    const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32)
    const sphereMaterial = new THREE.MeshStandardMaterial({
      color: 0xff0000,
      roughness: 0.3,
      metalness: 0.7
    })
    this.sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
    this.sphere.position.set(2, 0, 0)
    this.scene.add(this.sphere)

    // 创建地面
    const planeGeometry = new THREE.PlaneGeometry(10, 10)
    const planeMaterial = new THREE.MeshStandardMaterial({
      color: 0xcccccc,
      side: THREE.DoubleSide
    })
    const plane = new THREE.Mesh(planeGeometry, planeMaterial)
    plane.rotation.x = Math.PI / 2
    plane.position.y = -1
    plane.receiveShadow = true
    this.scene.add(plane)
  }

  onResize() {
    const width = this.container.clientWidth
    const height = this.container.clientHeight

    this.camera.aspect = width / height
    this.camera.updateProjectionMatrix()
    this.renderer.setSize(width, height)
  }

  animate() {
    requestAnimationFrame(() => this.animate())

    // 旋转立方体
    this.cube.rotation.x += 0.01
    this.cube.rotation.y += 0.01

    // 更新控制器
    this.controls.update()

    // 渲染场景
    this.renderer.render(this.scene, this.camera)
  }
}

流行库对比

1. Three.js

简介:最流行的 WebGL 3D 库

优点

  • 🌟 社区活跃,资源丰富
  • 📖 文档完善,教程多
  • 🔧 功能全面
  • 🎨 渲染效果好
  • 🔌 插件生态丰富

缺点

  • 📦 体积较大(~500KB)
  • 📖 学习曲线陡
  • 性能优化复杂

适用场景

  • 3D 网页应用
  • 游戏开发
  • 产品展示
  • 数据可视化

示例代码

javascript
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'

// 创建场景
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x87ceeb)

// 创建相机
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
)
camera.position.set(0, 2, 5)

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.shadowMap.enabled = true
document.body.appendChild(renderer.domElement)

// 加载模型
const loader = new GLTFLoader()
loader.load('model.gltf', gltf => {
  const model = gltf.scene
  model.scale.set(1, 1, 1)
  scene.add(model)
})

// 添加光源
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(5, 5, 5)
scene.add(light)

// 动画循环
function animate() {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
}
animate()

2. Babylon.js

简介:微软开源的强大 3D 引擎

优点

  • 🎮 游戏引擎级别功能
  • 📊 物理引擎集成
  • 🎨 材质系统强大
  • 🔧 工具链完善
  • 📱 跨平台支持

缺点

  • 📦 体积更大(~1MB)
  • 📖 学习曲线更陡
  • 社区相对较小

适用场景

  • 游戏开发
  • 复杂 3D 应用
  • VR/AR 应用

示例代码

javascript
import * as BABYLON from '@babylonjs/core'

// 创建引擎
const canvas = document.getElementById('renderCanvas')
const engine = new BABYLON.Engine(canvas, true)

// 创建场景
const createScene = () => {
  const scene = new BABYLON.Scene(engine)
  scene.clearColor = new BABYLON.Color4(0, 0, 0, 1)

  // 创建相机
  const camera = new BABYLON.ArcRotateCamera(
    'camera',
    Math.PI / 2,
    Math.PI / 2,
    10,
    BABYLON.Vector3.Zero(),
    scene
  )
  camera.attachControl(canvas, true)

  // 创建光源
  const light = new BABYLON.HemisphericLight(
    'light',
    new BABYLON.Vector3(1, 1, 0),
    scene
  )

  // 创建球体
  const sphere = BABYLON.MeshBuilder.CreateSphere(
    'sphere',
    { diameter: 2 },
    scene
  )

  // 创建材质
  const material = new BABYLON.StandardMaterial('material', scene)
  material.diffuseColor = new BABYLON.Color3(1, 0, 0)
  sphere.material = material

  return scene
}

const scene = createScene()

// 渲染循环
engine.runRenderLoop(() => {
  scene.render()
})

// 窗口大小调整
window.addEventListener('resize', () => {
  engine.resize()
})

3. A-Frame

简介:基于 HTML 的 WebVR 框架

优点

  • 📝 声明式语法,易上手
  • 🥽 VR 支持开箱即用
  • 🔌 组件系统强大
  • 📖 文档友好

缺点

  • 灵活性较低
  • 性能不如原生
  • 功能相对有限

适用场景

  • VR 应用开发
  • 快速原型
  • 教育/培训应用

示例代码

html
<!DOCTYPE html>
<html>
  <head>
    <title>My First VR Experience</title>
    <script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <!-- 天空盒 -->
      <a-sky color="#87CEEB"></a-sky>

      <!-- 地面 -->
      <a-plane
        position="0 0 0"
        rotation="-90 0 0"
        width="10"
        height="10"
        color="#7BC8A4"
      ></a-plane>

      <!-- 立方体 -->
      <a-box
        position="-1 0.5 -3"
        rotation="0 45 0"
        color="#4CC3D9"
        animation="property: rotation; to: 0 360 0; loop: true; dur: 10000"
      ></a-box>

      <!-- 球体 -->
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>

      <!-- 圆柱体 -->
      <a-cylinder
        position="1 0.75 -3"
        radius="0.5"
        height="1.5"
        color="#FFC65D"
      ></a-cylinder>

      <!-- 相机 -->
      <a-camera position="0 1.6 0"></a-camera>
    </a-scene>
  </body>
</html>

4. Cesium.js

简介:专业的 3D 地球和地图库

优点

  • 🌍 专业地理可视化
  • 🗺️ 支持多种地图服务
  • 📊 大数据量支持
  • 🚀 性能优秀
  • 🎯 精度高

缺点

  • 📦 体积很大(~5MB)
  • 📖 学习曲线陡
  • 专注地理应用

适用场景

  • GIS 应用
  • 地理数据可视化
  • 卫星轨道模拟
  • 城市规划

示例代码

javascript
import * as Cesium from 'cesium'

// 创建 viewer
const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: Cesium.createWorldTerrain(),
  timeline: false,
  animation: false
})

// 添加点
viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(-74.0707383, 40.7117244, 100),
  point: {
    pixelSize: 10,
    color: Cesium.Color.RED
  }
})

// 添加线
viewer.entities.add({
  polyline: {
    positions: Cesium.Cartesian3.fromDegreesArray([-75, 35, -125, 35]),
    width: 5,
    material: Cesium.Color.RED
  }
})

// 添加 3D 模型
const entity = viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 0),
  model: {
    uri: './CesiumAir/Cesium_Air.glb',
    minimumPixelSize: 128,
    maximumScale: 20000
  }
})

// 飞到模型
viewer.zoomTo(entity)

三者对比总结

技术维度对比

维度一维可视化二维可视化三维可视化
数据维度1D(时间/线性)2D(平面)3D(空间)
渲染技术SVG/CanvasSVG/Canvas/WebGLWebGL
性能要求
开发难度
交互复杂度
学习曲线平缓中等陡峭

应用场景对比

场景一维二维三维
时间序列数据⭐⭐⭐⭐⭐⭐⭐
地理数据⭐⭐⭐⭐⭐⭐⭐⭐⭐
产品展示⭐⭐⭐⭐⭐⭐⭐
游戏开发⭐⭐⭐⭐⭐⭐⭐
数据报表⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
科学可视化⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

技术选型建议

一维可视化选型

需求推荐方案理由
简单图表Chart.js轻量、易用、文档好
企业级应用ECharts功能强大、类型丰富
高度定制D3.js灵活性最高
商业项目Highcharts美观、稳定、支持好

二维可视化选型

需求推荐方案理由
简单地图Leaflet轻量、易用、插件多
专业地图Mapbox GL功能强大、样式丰富
关系图AntV G6专业图可视化
通用图表AntV G2语法强大、样式好

三维可视化选型

需求推荐方案理由
通用 3DThree.js社区大、资源多
游戏开发Babylon.js引擎级功能
VR 应用A-Frame声明式、易上手
地理应用Cesium.js专业 GIS 支持

性能优化建议

一维可视化

  • 使用 Canvas 渲染大数据量
  • 实现数据采样和聚合
  • 使用虚拟滚动

二维可视化

  • 使用 Canvas 替代 SVG 处理大数据
  • 实现视口裁剪
  • 使用 Web Worker 处理数据

三维可视化

  • 使用 LOD(Level of Detail)
  • 实例化渲染
  • 视锥体剔除
  • 遮挡剔除
  • 使用压缩纹理

最佳实践

1. 选择合适的维度

  • 数据只有时间维度 → 一维可视化
  • 数据有地理位置 → 二维可视化
  • 数据有空间关系 → 三维可视化

2. 选择合适的库

  • 简单需求 → 轻量级库(Chart.js、Leaflet)
  • 复杂需求 → 功能库(ECharts、Three.js)
  • 定制需求 → 底层库(D3.js、WebGL)

3. 性能优化

  • 减少不必要的渲染
  • 使用缓存
  • 实现懒加载
  • 优化数据结构

4. 用户体验

  • 提供交互反馈
  • 支持触摸操作
  • 响应式设计
  • 无障碍支持

相关资源

基于 VitePress 的本地知识库