Skip to content

一维可视化应用

概述

一维可视化主要处理线性数据,如时间序列、进度条、音频波形等。

常见应用场景

1. 时间轴

javascript
class Timeline {
  constructor(container, options = {}) {
    this.container = container
    this.options = {
      width: options.width || 800,
      height: options.height || 100,
      startTime: options.startTime || 0,
      endTime: options.endTime || 100,
      ...options
    }
    
    this.canvas = this.createCanvas()
    this.ctx = this.canvas.getContext('2d')
  }

  createCanvas() {
    const canvas = document.createElement('canvas')
    canvas.width = this.options.width
    canvas.height = this.options.height
    this.container.appendChild(canvas)
    return canvas
  }

  render(events) {
    const { width, height, startTime, endTime } = this.options
    const duration = endTime - startTime
    const scale = width / duration

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

    // 绘制时间轴
    this.ctx.strokeStyle = '#ddd'
    this.ctx.beginPath()
    this.ctx.moveTo(0, height / 2)
    this.ctx.lineTo(width, height / 2)
    this.ctx.stroke()

    // 绘制事件
    events.forEach(event => {
      const x = (event.time - startTime) * scale
      this.ctx.fillStyle = event.color || '#1890ff'
      this.ctx.beginPath()
      this.ctx.arc(x, height / 2, 5, 0, Math.PI * 2)
      this.ctx.fill()
    })
  }
}

2. 进度条

vue
<template>
  <div class="progress-container">
    <div class="progress-bar">
      <div 
        class="progress-fill" 
        :style="{ width: progress + '%' }"
      ></div>
    </div>
    <div class="progress-text">{{ progress }}%</div>
  </div>
</template>

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

const props = defineProps({
  value: { type: Number, default: 0 },
  max: { type: Number, default: 100 }
})

const progress = ref(0)

watch(() => props.value, (val) => {
  progress.value = Math.round((val / props.max) * 100)
}, { immediate: true })
</script>

<style scoped>
.progress-container {
  display: flex;
  align-items: center;
  gap: 12px;
}

.progress-bar {
  flex: 1;
  height: 8px;
  background: #f0f0f0;
  border-radius: 4px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #1890ff, #40a9ff);
  transition: width 0.3s ease;
}

.progress-text {
  min-width: 50px;
  text-align: right;
}
</style>

3. 音频波形

javascript
class AudioWaveform {
  constructor(canvas) {
    this.canvas = canvas
    this.ctx = canvas.getContext('2d')
    this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
    this.analyser = this.audioContext.createAnalyser()
  }

  async loadAudio(url) {
    const response = await fetch(url)
    const arrayBuffer = await response.arrayBuffer()
    const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer)
    return audioBuffer
  }

  drawWaveform(audioBuffer) {
    const data = audioBuffer.getChannelData(0)
    const step = Math.ceil(data.length / this.canvas.width)
    const amp = this.canvas.height / 2

    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
    this.ctx.beginPath()
    this.ctx.moveTo(0, amp)

    for (let i = 0; i < this.canvas.width; i++) {
      let min = 1.0
      let max = -1.0
      
      for (let j = 0; j < step; j++) {
        const datum = data[(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 = '#1890ff'
    this.ctx.stroke()
  }

  visualize(stream) {
    const source = this.audioContext.createMediaStreamSource(stream)
    source.connect(this.analyser)
    this.analyser.fftSize = 256

    const bufferLength = this.analyser.frequencyBinCount
    const dataArray = new Uint8Array(bufferLength)

    const draw = () => {
      requestAnimationFrame(draw)
      
      this.analyser.getByteFrequencyData(dataArray)
      
      this.ctx.fillStyle = 'rgb(0, 0, 0)'
      this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)

      const barWidth = (this.canvas.width / bufferLength) * 2.5
      let barHeight
      let x = 0

      for (let i = 0; i < bufferLength; i++) {
        barHeight = dataArray[i] / 2

        this.ctx.fillStyle = `rgb(${barHeight + 100}, 50, 50)`
        this.ctx.fillRect(x, this.canvas.height - barHeight, barWidth, barHeight)

        x += barWidth + 1
      }
    }

    draw()
  }
}

数据可视化库

1. D3.js 线性图表

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)

  svg.append('g')
    .attr('class', 'x axis')
    .attr('transform', `translate(0,${height})`)
    .call(d3.axisBottom(x))

  svg.append('g')
    .attr('class', 'y axis')
    .call(d3.axisLeft(y))

  svg.append('path')
    .datum(data)
    .attr('class', 'line')
    .attr('d', line)
    .attr('fill', 'none')
    .attr('stroke', '#1890ff')
    .attr('stroke-width', 2)
}

2. Chart.js 线性图表

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
    }]
  },
  options: {
    responsive: true,
    plugins: {
      legend: {
        position: 'top'
      }
    },
    scales: {
      y: {
        beginAtZero: true
      }
    }
  }
})

最佳实践

1. 性能优化

  • 使用 Canvas 而非 SVG 处理大量数据
  • 实现数据采样和聚合
  • 使用 Web Worker 进行数据处理

2. 交互设计

  • 支持缩放和平移
  • 提供数据点提示
  • 支持数据筛选

3. 响应式设计

  • 自适应容器大小
  • 移动端优化
  • 支持触摸操作

相关资源

基于 VitePress 的本地知识库