pin_drop当前位置:知识文库 ❯ 图文

数据可视化:D3.js基础 - 从入门到实践详解

教程简介

D3.js(Data-Driven Documents)是最强大的JavaScript数据可视化库,以数据驱动和底层控制著称。与Chart.js和ECharts等高层图表库不同,D3.js提供了构建可视化的基础工具集,让你能够精确控制每一个视觉元素。本教程将系统讲解D3.js的核心概念,包括选择集、数据绑定(enter/update/exit)、比例尺(scale)、坐标轴(axis)、过渡动画(transition)以及D3与SVG/Canvas的结合使用。

核心概念

D3.js的设计哲学

D3.js不是图表库,而是数据可视化工具集。它不提供"开箱即用"的图表类型,而是提供构建图表的基础模块:

  • 数据绑定:将数据与DOM元素关联

  • 比例尺:数据空间到视觉空间的映射

  • 坐标轴:自动生成坐标轴组件

  • 过渡动画:平滑的状态变化

  • 形状生成器:路径、面积、线条等SVG路径生成

选择集(Selection)

D3的选择集是操作DOM的核心:

代码示例

// 选择元素
d3.select('#chart')      // 选择单个元素
d3.selectAll('.bar')     // 选择多个元素

// 链式操作
d3.select('#chart')
  .append('svg')
  .attr('width', 800)
  .attr('height', 400)
  .style('background', '#fff');

数据绑定(Data Join)

数据绑定是D3最核心的概念,将数据数组与DOM元素集关联:

代码示例

const data = [10, 20, 30, 40, 50];

d3.select('svg')
  .selectAll('rect')
  .data(data)
  .join('rect')          // D3 v5+ 简化写法
  .attr('x', (d, i) => i * 50)
  .attr('y', d => 300 - d * 5)
  .attr('width', 40)
  .attr('height', d => d * 5)
  .attr('fill', '#4e79a7');

Enter/Update/Exit模式:

代码示例

const rects = d3.select('svg').selectAll('rect').data(data);

// Enter:新数据对应的元素
rects.enter().append('rect')
  .attr('fill', '#4e79a7');

// Update:已有数据对应的元素
rects.attr('fill', '#f28e2b');

// Exit:不再需要的元素
rects.exit().remove();

比例尺(Scale)

比例尺将数据域映射到视觉范围:

代码示例

// 线性比例尺
const yScale = d3.scaleLinear()
  .domain([0, 100])      // 数据范围
  .range([300, 0]);      // 像素范围(注意反转)

// 序数比例尺(分类)
const colorScale = d3.scaleOrdinal()
  .domain(['A', 'B', 'C'])
  .range(['#4e79a7', '#f28e2b', '#e15759']);

// 时间比例尺
const xScale = d3.scaleTime()
  .domain([new Date(2024, 0, 1), new Date(2024, 11, 31)])
  .range([0, 800]);

// 带宽比例尺(柱状图)
const bandScale = d3.scaleBand()
  .domain(['A', 'B', 'C', 'D'])
  .range([0, 800])
  .padding(0.2);

坐标轴(Axis)

D3自动生成SVG坐标轴:

代码示例

const xAxis = d3.axisBottom(xScale)
  .ticks(5)
  .tickFormat(d3.format('.0f'));

const yAxis = d3.axisLeft(yScale)
  .ticks(5);

svg.append('g')
  .attr('transform', `translate(0, ${height})`)
  .call(xAxis);

svg.append('g')
  .call(yAxis);

过渡动画(Transition)

代码示例

// 基本过渡
rects.transition()
  .duration(750)
  .ease(d3.easeCubicOut)
  .attr('y', d => yScale(d.value))
  .attr('height', d => height - yScale(d.value));

// 延迟过渡
rects.transition()
  .delay((d, i) => i * 50)
  .duration(500)
  .attr('fill', '#e15759');

代码示例

示例1:D3.js柱状图(完整数据绑定)

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>D3.js柱状图</title>
  <script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f7fa; display: flex; justify-content: center; padding: 30px 20px; }
    .chart-container { background: #fff; border-radius: 16px; padding: 30px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); width: 100%; max-width: 900px; }
    .chart-title { text-align: center; color: #2c3e50; font-size: 20px; margin-bottom: 15px; }
    .controls { display: flex; gap: 8px; justify-content: center; margin-bottom: 15px; }
    .btn { padding: 6px 16px; border: 1px solid #ddd; border-radius: 6px; background: #fff; color: #666; font-size: 12px; cursor: pointer; transition: all 0.2s; }
    .btn:hover { border-color: #4e79a7; color: #4e79a7; }
    .btn.active { background: #4e79a7; color: #fff; border-color: #4e79a7; }
    svg { width: 100%; }
    .bar { cursor: pointer; transition: opacity 0.2s; }
    .bar:hover { opacity: 0.8; }
    .axis text { font-family: -apple-system, sans-serif; font-size: 12px; fill: #888; }
    .axis line, .axis path { stroke: #ddd; }
    .grid line { stroke: #f0f0f0; }
    .grid path { stroke: none; }
    .tooltip { position: absolute; background: rgba(44,62,80,0.9); color: #fff; padding: 6px 12px; border-radius: 4px; font-size: 12px; pointer-events: none; opacity: 0; transition: opacity 0.15s; }
  </style>
</head>
<body>
  <div class="chart-container">
    <h2 class="chart-title">D3.js 柱状图 - 数据绑定演示</h2>
    <div class="controls">
      <button class="btn" onclick="updateData('sales')">销售额</button>
      <button class="btn" onclick="updateData('profit')">利润</button>
      <button class="btn" onclick="updateData('users')">用户数</button>
      <button class="btn" onclick="addBar()">添加数据</button>
      <button class="btn" onclick="removeBar()">移除数据</button>
    </div>
    <div id="chart"></div>
    <div class="tooltip" id="tooltip"></div>
  </div>

  <script>
    const allData = {
      sales:  [{name:'北京',value:218},{name:'上海',value:248},{name:'广州',value:186},{name:'深圳',value:175},{name:'成都',value:209},{name:'杭州',value:123},{name:'武汉',value:136}],
      profit: [{name:'北京',value:45},{name:'上海',value:62},{name:'广州',value:38},{name:'深圳',value:52},{name:'成都',value:35},{name:'杭州',value:28},{name:'武汉',value:31}],
      users:  [{name:'北京',value:890},{name:'上海',value:1200},{name:'广州',value:670},{name:'深圳',value:580},{name:'成都',value:750},{name:'杭州',value:420},{name:'武汉',value:510}]
    };

    let currentData = [...allData.sales];
    const margin = { top: 20, right: 20, bottom: 40, left: 55 };
    const width = 840 - margin.left - margin.right;
    const height = 400 - margin.top - margin.bottom;

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

    // 比例尺
    const xScale = d3.scaleBand().range([0, width]).padding(0.3);
    const yScale = d3.scaleLinear().range([height, 0]);
    const colorScale = d3.scaleOrdinal().range(['#4e79a7','#f28e2b','#e15759','#76b7b2','#59a14f','#edc948','#b07aa1','#ff9da7','#9c755f','#bab0ac']);

    // 坐标轴
    const xAxis = d3.axisBottom(xScale);
    const yAxis = d3.axisLeft(yScale).ticks(5);

    const xAxisG = svg.append('g').attr('class', 'axis').attr('transform', `translate(0,${height})`);
    const yAxisG = svg.append('g').attr('class', 'axis');

    // 网格线
    const gridG = svg.append('g').attr('class', 'grid');

    const tooltip = document.getElementById('tooltip');

    function update(data) {
      currentData = data;
      xScale.domain(data.map(d => d.name));
      yScale.domain([0, d3.max(data, d => d.value) * 1.1]);

      // 更新坐标轴
      xAxisG.transition().duration(500).call(xAxis);
      yAxisG.transition().duration(500).call(yAxis);

      // 网格线
      gridG.selectAll('line').remove();
      yScale.ticks(5).forEach(tick => {
        gridG.append('line')
          .attr('x1', 0).attr('x2', width)
          .attr('y1', yScale(tick)).attr('y2', yScale(tick))
          .attr('stroke', '#f0f0f0');
      });

      // 数据绑定 - Enter/Update/Exit
      const bars = svg.selectAll('.bar').data(data, d => d.name);

      // Exit
      bars.exit()
        .transition().duration(300)
        .attr('y', height)
        .attr('height', 0)
        .style('opacity', 0)
        .remove();

      // Enter
      const barsEnter = bars.enter().append('rect')
        .attr('class', 'bar')
        .attr('x', d => xScale(d.name))
        .attr('y', height)
        .attr('width', xScale.bandwidth())
        .attr('height', 0)
        .attr('fill', (d, i) => colorScale(i))
        .attr('rx', 4)
        .on('mouseenter', function(event, d) {
          tooltip.style.opacity = 1;
          tooltip.innerHTML = `${d.name}: ${d.value}`;
          d3.select(this).attr('opacity', 0.8);
        })
        .on('mousemove', function(event) {
          tooltip.style.left = event.pageX + 10 + 'px';
          tooltip.style.top = event.pageY - 30 + 'px';
        })
        .on('mouseleave', function() {
          tooltip.style.opacity = 0;
          d3.select(this).attr('opacity', 1);
        });

      // Update + Enter
      bars.merge(barsEnter)
        .transition().duration(500)
        .ease(d3.easeCubicOut)
        .attr('x', d => xScale(d.name))
        .attr('y', d => yScale(d.value))
        .attr('width', xScale.bandwidth())
        .attr('height', d => height - yScale(d.value))
        .attr('fill', (d, i) => colorScale(i));

      // 数值标签
      const labels = svg.selectAll('.val-label').data(data, d => d.name);
      labels.exit().transition().duration(300).style('opacity', 0).remove();
      labels.enter().append('text').attr('class', 'val-label')
        .merge(labels)
        .transition().duration(500)
        .attr('x', d => xScale(d.name) + xScale.bandwidth() / 2)
        .attr('y', d => yScale(d.value) - 8)
        .attr('text-anchor', 'middle')
        .attr('fill', '#666')
        .attr('font-size', '12px')
        .attr('font-weight', '600')
        .text(d => d.value);
    }

    function updateData(type) {
      document.querySelectorAll('.btn').forEach(b => b.classList.remove('active'));
      event.target.classList.add('active');
      update([...allData[type]]);
    }

    function addBar() {
      const names = ['南京','重庆','西安','苏州','天津'];
      const name = names[currentData.length % names.length];
      if (currentData.find(d => d.name === name)) return;
      currentData.push({ name, value: Math.round(50 + Math.random() * 200) });
      update([...currentData]);
    }

    function removeBar() {
      if (currentData.length <= 2) return;
      currentData.pop();
      update([...currentData]);
    }

    update(currentData);
  </script>
</body>
</html>

示例2:D3.js折线图与坐标轴

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>D3.js折线图</title>
  <script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; background: #f5f7fa; display: flex; justify-content: center; padding: 30px 20px; }
    .chart-container { background: #fff; border-radius: 16px; padding: 30px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); width: 100%; max-width: 900px; }
    .chart-title { text-align: center; color: #2c3e50; font-size: 20px; margin-bottom: 15px; }
    svg { width: 100%; }
    .axis text { font-size: 11px; fill: #888; }
    .axis line, .axis path { stroke: #ddd; }
    .line { fill: none; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
    .area { opacity: 0.15; }
    .dot { cursor: pointer; }
    .dot:hover { r: 6; }
    .crosshair { stroke: rgba(0,0,0,0.1); stroke-dasharray: 4 4; }
    .focus-text { font-size: 12px; font-weight: 600; }
  </style>
</head>
<body>
  <div class="chart-container">
    <h2 class="chart-title">D3.js 折线图 - 比例尺与坐标轴</h2>
    <div id="lineChart"></div>
  </div>

  <script>
    const lineData = [
      {date: '2024-01', value: 30}, {date: '2024-02', value: 45},
      {date: '2024-03', value: 42}, {date: '2024-04', value: 58},
      {date: '2024-05', value: 65}, {date: '2024-06', value: 72},
      {date: '2024-07', value: 68}, {date: '2024-08', value: 85},
      {date: '2024-09', value: 78}, {date: '2024-10', value: 92},
      {date: '2024-11', value: 88}, {date: '2024-12', value: 95}
    ];

    lineData.forEach(d => d.date = d3.timeParse('%Y-%m')(d.date));

    const margin = { top: 20, right: 30, bottom: 40, left: 50 };
    const width = 840 - margin.left - margin.right;
    const height = 380 - margin.top - margin.bottom;

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

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

    const yScale = d3.scaleLinear()
      .domain([0, d3.max(lineData, d => d.value) * 1.15])
      .range([height, 0]);

    // 坐标轴
    svg.append('g').attr('class', 'axis')
      .attr('transform', `translate(0,${height})`)
      .call(d3.axisBottom(xScale).ticks(6).tickFormat(d3.timeFormat('%m月')));

    svg.append('g').attr('class', 'axis')
      .call(d3.axisLeft(yScale).ticks(5));

    // 网格线
    svg.append('g').selectAll('line')
      .data(yScale.ticks(5)).join('line')
      .attr('x1', 0).attr('x2', width)
      .attr('y1', d => yScale(d)).attr('y2', d => yScale(d))
      .attr('stroke', '#f0f0f0');

    // 面积生成器
    const area = d3.area()
      .x(d => xScale(d.date))
      .y0(height)
      .y1(d => yScale(d.value))
      .curve(d3.curveMonotoneX);

    svg.append('path')
      .datum(lineData)
      .attr('class', 'area')
      .attr('d', area)
      .attr('fill', '#4e79a7');

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

    // 线条绘制动画
    const path = svg.append('path')
      .datum(lineData)
      .attr('class', 'line')
      .attr('d', line)
      .attr('stroke', '#4e79a7');

    const totalLength = path.node().getTotalLength();
    path.attr('stroke-dasharray', totalLength)
      .attr('stroke-dashoffset', totalLength)
      .transition().duration(1500).ease(d3.easeCubicOut)
      .attr('stroke-dashoffset', 0);

    // 数据点
    svg.selectAll('.dot')
      .data(lineData).join('circle')
      .attr('class', 'dot')
      .attr('cx', d => xScale(d.date))
      .attr('cy', d => yScale(d.value))
      .attr('r', 4)
      .attr('fill', '#fff')
      .attr('stroke', '#4e79a7')
      .attr('stroke-width', 2)
      .style('opacity', 0)
      .transition().delay(1500).duration(300)
      .style('opacity', 1);

    // 交互:十字准线
    const focus = svg.append('g').style('display', 'none');
    focus.append('line').attr('class', 'crosshair').attr('y1', 0).attr('y2', height);
    focus.append('circle').attr('r', 5).attr('fill', '#4e79a7').attr('stroke', '#fff').attr('stroke-width', 2);
    focus.append('text').attr('class', 'focus-text').attr('dy', '-10');

    svg.append('rect')
      .attr('width', width).attr('height', height)
      .attr('fill', 'none').attr('pointer-events', 'all')
      .on('mousemove', function(event) {
        const [mx] = d3.pointer(event);
        const x0 = xScale.invert(mx);
        const i = d3.bisector(d => d.date).left(lineData, x0, 1);
        const d0 = lineData[i - 1], d1 = lineData[i];
        if (!d1) return;
        const d = x0 - d0.date > d1.date - x0 ? d1 : d0;

        focus.style('display', null);
        focus.select('line').attr('x1', xScale(d.date)).attr('x2', xScale(d.date));
        focus.select('circle').attr('cx', xScale(d.date)).attr('cy', yScale(d.value));
        focus.select('text').attr('x', xScale(d.date)).attr('y', yScale(d.value))
          .attr('text-anchor', 'middle')
          .text(`${d3.timeFormat('%m月')(d.date)}: ${d.value}`);
      })
      .on('mouseleave', () => focus.style('display', 'none'));
  </script>
</body>
</html>

示例3:D3.js过渡动画与交互

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>D3.js过渡动画</title>
  <script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; background: #f5f7fa; display: flex; justify-content: center; padding: 30px 20px; }
    .container { background: #fff; border-radius: 16px; padding: 30px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); width: 100%; max-width: 900px; }
    h2 { text-align: center; color: #2c3e50; margin-bottom: 15px; }
    svg { width: 100%; }
    .controls { display: flex; gap: 8px; justify-content: center; margin-bottom: 15px; flex-wrap: wrap; }
    .btn { padding: 8px 18px; border: none; border-radius: 8px; background: #4e79a7; color: #fff; font-size: 13px; cursor: pointer; }
    .btn:hover { background: #3d6a96; }
  </style>
</head>
<body>
  <div class="container">
    <h2>D3.js 过渡动画演示</h2>
    <div class="controls">
      <button class="btn" onclick="shuffle()">随机数据</button>
      <button class="btn" onclick="sortAsc()">升序排列</button>
      <button class="btn" onclick="sortDesc()">降序排列</button>
    </div>
    <div id="animChart"></div>
  </div>

  <script>
    let data = d3.range(15).map(i => ({ id: i, value: Math.round(Math.random() * 80 + 20) }));
    const colorScale = d3.scaleOrdinal(d3.schemeTableau10);

    const margin = { top: 20, right: 20, bottom: 30, left: 40 };
    const width = 840 - margin.left - margin.right;
    const height = 350 - margin.top - margin.bottom;

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

    const xScale = d3.scaleBand().range([0, width]).padding(0.25);
    const yScale = d3.scaleLinear().range([height, 0]);

    svg.append('g').attr('class', 'x-axis').attr('transform', `translate(0,${height})`);
    svg.append('g').attr('class', 'y-axis');

    function render(data) {
      xScale.domain(data.map((_, i) => i));
      yScale.domain([0, 100]);

      svg.select('.x-axis').call(d3.axisBottom(xScale).tickFormat(''));
      svg.select('.y-axis').call(d3.axisLeft(yScale).ticks(5));

      const bars = svg.selectAll('.bar').data(data, d => d.id);

      bars.enter().append('rect')
        .attr('class', 'bar')
        .attr('x', (_, i) => xScale(i))
        .attr('y', height)
        .attr('width', xScale.bandwidth())
        .attr('height', 0)
        .attr('fill', d => colorScale(d.id))
        .attr('rx', 4)
        .merge(bars)
        .transition().duration(600).ease(d3.easeCubicOut)
        .attr('x', (_, i) => xScale(i))
        .attr('y', d => yScale(d.value))
        .attr('width', xScale.bandwidth())
        .attr('height', d => height - yScale(d.value))
        .attr('fill', d => colorScale(d.id));

      bars.exit().transition().duration(300).attr('height', 0).attr('y', height).remove();

      // 数值标签
      const labels = svg.selectAll('.label').data(data, d => d.id);
      labels.enter().append('text').attr('class', 'label')
        .attr('text-anchor', 'middle').attr('fill', '#666').attr('font-size', '11px').attr('font-weight', '600')
        .merge(labels)
        .transition().duration(600).ease(d3.easeCubicOut)
        .attr('x', (_, i) => xScale(i) + xScale.bandwidth() / 2)
        .attr('y', d => yScale(d.value) - 6)
        .text(d => d.value);
      labels.exit().remove();
    }

    function shuffle() {
      data = d3.range(15).map(i => ({ id: i, value: Math.round(Math.random() * 80 + 20) }));
      render(data);
    }

    function sortAsc() {
      data = [...data].sort((a, b) => a.value - b.value);
      render(data);
    }

    function sortDesc() {
      data = [...data].sort((a, b) => b.value - a.value);
      render(data);
    }

    render(data);
  </script>
</body>
</html>

浏览器兼容性

特性 Chrome Firefox Safari Edge IE11
D3.js v7 90+ 78+ 14+ 90+ 不支持
D3.js v6 60+ 55+ 12+ 79+ 不支持
ES6 Modules 61+ 60+ 11+ 16+ 不支持
SVG 4+ 3+ 3.2+ 12+ 9+

注意事项与最佳实践

  1. 数据键函数:数据绑定时使用key函数(.data(data, d => d.id))确保元素与数据正确关联。

  2. 过渡性能:避免同时运行过多过渡动画,控制过渡元素数量。

  3. 比例尺范围:Y轴比例尺的range注意反转([height, 0]),因为SVG坐标系Y轴向下。

  4. 坐标轴样式:通过CSS统一控制坐标轴样式,而非逐个设置属性。

  5. 事件处理:D3 v6+使用function(event, d)参数顺序,与v5不同。

  6. 响应式:使用viewBox实现SVG响应式,监听resize重绘。

  7. 内存管理:移除不需要的元素时使用.exit().remove(),避免DOM膨胀。

  8. 模块化引入:生产环境使用按需引入,减小包体积。

代码规范示例

代码示例

// 好的做法:D3图表封装
function createBarChart(container, options) {
  const { margin, width, height } = options;
  const svg = d3.select(container).append('svg')
    .attr('viewBox', `0 0 ${width} ${height}`)
    .append('g')
    .attr('transform', `translate(${margin.left},${margin.top})`);

  const xScale = d3.scaleBand().range([0, width - margin.left - margin.right]).padding(0.3);
  const yScale = d3.scaleLinear().range([height - margin.top - margin.bottom, 0]);

  function update(data) {
    xScale.domain(data.map(d => d.name));
    yScale.domain([0, d3.max(data, d => d.value)]);

    svg.selectAll('.bar')
      .data(data, d => d.name)
      .join(
        enter => enter.append('rect').attr('class', 'bar').attr('y', height).attr('height', 0),
        update => update,
        exit => exit.transition().duration(300).attr('height', 0).remove()
      )
      .transition().duration(500)
      .attr('x', d => xScale(d.name))
      .attr('y', d => yScale(d.value))
      .attr('width', xScale.bandwidth())
      .attr('height', d => height - margin.top - margin.bottom - yScale(d.value));
  }

  return { update };
}

常见问题与解决方案

问题1:数据更新时元素错位

原因:未使用key函数,D3按索引匹配数据。

解决方案:使用.data(data, d => d.id)确保按数据标识匹配。

问题2:过渡动画不流畅

原因:同时运行的过渡过多。

解决方案:使用.interrupt()中断旧过渡,减少同时过渡的元素数。

问题3:坐标轴标签重叠

解决方案:调整ticks数量、旋转标签、使用.tickFormat()格式化。

问题4:SVG文本不换行

解决方案:使用或手动分行

总结

本教程系统讲解了D3.js的基础知识:

  1. 选择集:select/selectAll、链式操作

  2. 数据绑定:enter/update/exit模式、join方法

  3. 比例尺:linear、ordinal、time、band等

  4. 坐标轴:自动生成、样式控制

  5. 过渡动画:transition、ease、delay

  6. D3与SVG:路径生成器、形状生成器

  7. 交互:鼠标事件、十字准线、tooltip

D3.js是数据可视化的瑞士军刀,掌握其核心概念后可以构建任意定制化的可视化效果。

常见问题

什么是D3.js基础?

D3.js基础是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。

如何学习D3.js基础的实际应用?

教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。

D3.js基础有哪些注意事项?

常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。

D3.js基础适合初学者吗?

本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。

D3.js基础的核心要点是什么?

核心要点包括教程简介等内容,建议按教程顺序逐步学习。

标签: 柱状图 内存管理 场景管理 帧率优化 交互图表 数据可视化 Chart.js Chart

本文由小确幸生活整理发布,转载请注明出处

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 数据可视化:ECharts图表库 - 从入门到实践详解 下一篇: 数据可视化:仪表盘设计 - 从入门到实践详解

poll相关推荐