Skip to content

二维可视化应用

概述

二维可视化处理平面数据,如地图、热力图、散点图、树图等。

常见应用场景

1. 地图可视化

javascript
import * as d3 from 'd3'
import * as topojson from 'topojson-client'

async function createMap(container) {
  const width = 800
  const height = 600

  const svg = d3.select(container)
    .append('svg')
    .attr('width', width)
    .attr('height', height)

  // 加载地图数据
  const world = await d3.json('https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json')
  const countries = topojson.feature(world, world.objects.countries)

  // 投影
  const projection = d3.geoNaturalEarth1()
    .fitSize([width, height], countries)

  const path = d3.geoPath().projection(projection)

  // 绘制地图
  svg.append('g')
    .selectAll('path')
    .data(countries.features)
    .enter()
    .append('path')
    .attr('d', path)
    .attr('fill', '#f0f0f0')
    .attr('stroke', '#999')
    .attr('stroke-width', 0.5)
    .on('mouseover', function() {
      d3.select(this).attr('fill', '#1890ff')
    })
    .on('mouseout', function() {
      d3.select(this).attr('fill', '#f0f0f0')
    })
}

2. 热力图

javascript
class Heatmap {
  constructor(container, data, options = {}) {
    this.container = container
    this.data = data
    this.options = {
      width: options.width || 800,
      height: options.height || 600,
      radius: options.radius || 25,
      ...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() {
    const { width, height, radius } = this.options
    
    this.ctx.clearRect(0, 0, width, height)

    // 创建热力图
    this.data.forEach(point => {
      const gradient = this.ctx.createRadialGradient(
        point.x, point.y, 0,
        point.x, point.y, radius
      )
      
      gradient.addColorStop(0, `rgba(255, 0, 0, ${point.value})`)
      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: colors[index].r + t * (colors[index + 1].r - colors[index].r),
      g: colors[index].g + t * (colors[index + 1].g - colors[index].g),
      b: colors[index].b + t * (colors[index + 1].b - colors[index].b)
    }
  }
}

3. 散点图

javascript
import * as d3 from 'd3'

function createScatterPlot(data, container) {
  const margin = { top: 20, right: 20, bottom: 30, left: 50 }
  const width = 800 - margin.left - margin.right
  const height = 600 - 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.scaleLinear()
    .domain([0, d3.max(data, d => d.x)])
    .range([0, width])

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

  const color = d3.scaleOrdinal(d3.schemeCategory10)

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

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

  // 散点
  svg.selectAll('.dot')
    .data(data)
    .enter()
    .append('circle')
    .attr('class', 'dot')
    .attr('cx', d => x(d.x))
    .attr('cy', d => y(d.y))
    .attr('r', 5)
    .attr('fill', d => color(d.category))
    .on('mouseover', function(event, d) {
      d3.select(this)
        .transition()
        .duration(200)
        .attr('r', 8)
    })
    .on('mouseout', function() {
      d3.select(this)
        .transition()
        .duration(200)
        .attr('r', 5)
    })
}

4. 树图

javascript
import * as d3 from 'd3'

function createTreeMap(data, container) {
  const width = 800
  const height = 600

  const svg = d3.select(container)
    .append('svg')
    .attr('width', width)
    .attr('height', height)

  const treemap = d3.treemap()
    .size([width, height])
    .padding(1)
    .round(true)

  const root = d3.hierarchy(data)
    .sum(d => d.value)
    .sort((a, b) => b.value - a.value)

  treemap(root)

  const color = d3.scaleOrdinal(d3.schemeCategory10)

  const leaf = svg.selectAll('g')
    .data(root.leaves())
    .enter()
    .append('g')
    .attr('transform', d => `translate(${d.x0},${d.y0})`)

  leaf.append('rect')
    .attr('width', d => d.x1 - d.x0)
    .attr('height', d => d.y1 - d.y0)
    .attr('fill', d => color(d.parent.data.name))
    .attr('stroke', '#fff')

  leaf.append('text')
    .attr('x', 4)
    .attr('y', 14)
    .text(d => d.data.name)
    .attr('font-size', '12px')
    .attr('fill', '#fff')
}

数据可视化库

ECharts

javascript
import * as echarts from 'echarts'

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

chart.setOption({
  title: {
    text: '示例图表'
  },
  tooltip: {},
  xAxis: {
    data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
  },
  yAxis: {},
  series: [{
    name: '销量',
    type: 'bar',
    data: [5, 20, 36, 10, 10, 20]
  }]
})

最佳实践

1. 数据处理

  • 数据清洗和转换
  • 异常值处理
  • 数据聚合

2. 视觉设计

  • 合理的颜色选择
  • 清晰的图例说明
  • 适当的标注

3. 交互体验

  • 缩放和平移
  • 数据筛选
  • 详情展示

相关资源

基于 VitePress 的本地知识库