D3.js 实际应用:高级数据可视化技术和示例

基础知识

首先,我们需要一个 HTML 文件来导入 D3.js 库并准备一个画布来放置我们的图表。




  
  
  Getting Started with D3.js Example
  


  

创建简单折线图

// Assume we have the following data
var data = [4, 8, 15, 16, 23, 42];

// Create an SVG canvas
var svg = d3.select("svg"),
    margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom;

// Create x and y scales
var x = d3.scaleLinear()
    .domain(d3.extent(data, d => d))
    .range([0, width]);

var y = d3.scaleLinear()
    .domain([0, d3.max(data)])
    .range([height, 0]);

// Create the x and y axes
var xAxis = d3.axisBottom(x),
    yAxis = d3.axisLeft(y);

// Add axis
svg.append("g")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

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

// Draw the polyline
var line = d3.line()
    .x(d => x(d))
    .y(d => y(d));

svg.append("path")
    .datum(data)
    .attr("class", "line")
    .attr("d", line);

创建条形图

// Suppose we have the following data
var data = [4, 8, 15, 16, 23, 42];

// Creating the SVG canvas and scale
var svg = d3.select("svg").attr("width", 500).attr("height", 500);
var margin = {top: 20, right: 20, bottom: 30, left: 40};
var width = +svg.attr("width") - margin.left - margin.right;
var height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1);
var y = d3.scaleLinear().rangeRound([height, 0]);

// Mapping data to scale
x.domain(data.map(function(d) { return d; }));
y.domain([0, d3.max(data)]);

// Creating an SVG g Element
var g = svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// Adding x and y axes
g.append("g")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x));

g.append("g")
    .call(d3.axisLeft(y));

// Draw a bar chart
g.selectAll(".bar")
    .data(data)
    .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return x(d); })
    .attr("y", function(d) { return y(d); })
    .attr("width", x.bandwidth())
    .attr("height", function(d) { return height - y(d); });

创建饼图

// Suppose we have the following data
var data = [4, 8, 15, 16, 23, 42];

// Creating the SVG canvas and scale
var svg = d3.select("svg").attr("width", 500).attr("height", 500);
var radius = Math.min(svg.attr("width"), svg.attr("height")) / 2;

// Creating an arc scale
var arc = d3.arc().outerRadius(radius).innerRadius(0);
var pie = d3.pie().value(function(d) { return d; });

// Draw a pie chart
var g = svg.append("g")
    .attr("transform", "translate(" + radius + "," + radius + ")");

var arcs = g.selectAll("arc")
    .data(pie(data))
    .enter().append("g")
    .attr("class", "arc");

arcs.append("path")
    .attr("d", arc)
    .attr("fill", function(d, i) { return d3.schemeCategory10[i]; });

arcs.append("text")
    .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
    .attr("dy", ".35em")
    .text(function(d) { return d.data; });

交互性和动画

交互示例:向条形图添加悬停效果

// Assuming that the bar chart base code already exists
// ...

// Add hover effects
g.selectAll(".bar")
    .on("mouseover", function(event, d) {
        d3.select(this)
            .transition()
            .duration(200)
            .attr("fill", "orange"); // Mouseover color change

        // Show Data Tips
        var tooltip = g.append("text")
            .attr("class", "tooltip")
            .attr("x", x(d) + x.bandwidth() / 2)
            .attr("y", y(d) - 10)
            .text(d);
    })
    .on("mouseout", function(event, d) {
        d3.select(this)
            .transition()
            .duration(200)
            .attr("fill", "steelblue"); // Restore original color

        // Remove data tips
        g.selectAll(".tooltip").remove();
    });

动画示例:平滑过渡折线图数据更新

// Assume that there is already a line chart basic code
// ...

// Update data
var newData = [8, 15, 16, 23, 42, 45];

// Update scale domain
x.domain(d3.extent(newData));
y.domain([0, d3.max(newData)]);

// Update axis
g.select(".axis--x").transition().duration(750).call(xAxis);
g.select(".axis--y").transition().duration(750).call(yAxis);

// Update path
var path = g.select(".line");
path.datum(newData).transition().duration(750).attr("d", line);

复杂图:力导向图

力导向图展现了节点和边之间的关系,非常适合可视化网络、社交图等数据。

// Assume we have data on nodes and edges
var nodes = [{id: "A"}, {id: "B"}, {id: "C"}];
var links = [{source: nodes[0], target: nodes[1]}, {source: nodes[1], target: nodes[2]}];

// Creating the SVG Canvas
var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

// Creating a Force Simulation
var simulation = d3.forceSimulation(nodes)
    .force("link", d3.forceLink(links).id(function(d) { return d.id; }))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2));

// Creating links and nodes
var link = svg.append("g")
    .attr("stroke", "#999")
    .attr("stroke-opacity", 0.6)
  .selectAll("line")
  .data(links)
  .join("line")
    .attr("stroke-width", 2);

var node = svg.append("g")
    .attr("stroke", "#fff")
    .attr("stroke-width", 1.5)
  .selectAll("circle")
  .data(nodes)
  .join("circle")
    .attr("r", 5)
    .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

node.append("title")
    .text(function(d) { return d.id; });

simulation.on("tick", ticked);

function ticked() {
  link
      .attr("x1", function(d) { return d.source.x; })
      .attr("y1", function(d) { return d.source.y; })
      .attr("x2", function(d) { return d.target.x; })
      .attr("y2", function(d) { return d.target.y; });

  node
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; });
}

// Drag event handling function
function dragstarted(event, d) {
  if (!event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(event, d) {
  d.fx = event.x;
  d.fy = event.y;
}

function dragended(event, d) {
  if (!event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

地图可视化

D3.js 可以使用 GeoJSON 等地理数据格式来创建交互式地图。其中包括国家、州、城市边界等。

基本步骤:

  • 加载地图数据:使用 D3 的 d3.json 或 d3.geoJson 加载 GeoJSON 数据。
  • 创建比例:定义地理投影和比例,例如墨卡托或阿尔伯斯美国投影。
  • 绑定数据并绘制:将 GeoJSON 数据绑定到 SVG 路径元素并应用投影。
  • 添加交互:如悬停效果、点击事件等。
  • d3.json("world.geojson").then(function(geoData) {
      var svg = d3.select("svg"),
          projection = d3.geoMercator().scale(130).translate([400, 250]),
          path = d3.geoPath().projection(projection);
    
      svg.selectAll("path")
        .data(geoData.features)
        .enter().append("path")
        .attr("d", path)
        .attr("fill", "#ccc")
        .attr("stroke", "#fff");
    });

    数据绑定和动态更新

    **基本步骤:**

  • 初始化数据绑定:使用data()方法将数据绑定到DOM元素。
  • 进入、更新、退出模式:处理新数据,更新现有数据,删除无用数据。
  • 动态更新:监视数据变化,重新执行绑定和渲染过程。
  • var svg = d3.select("svg"),
        data = [4, 8, 15, 16, 23, 42];
    
    // Initialize the bar chart
    var bars = svg.selectAll("rect").data(data);
    
    bars.enter().append("rect")
        .attr("x", function(d, i) { return i * 50; })
        .attr("y", function(d) { return 300 - d; })
        .attr("width", 40)
        .attr("height", function(d) { return d; });
    
    // Dynamic Updates
    setInterval(function() {
      data = data.map(function(d) { return Math.max(0, Math.random() * 50); });
    
      bars.data(data)
        .transition()
        .duration(500)
        .attr("y", function(d) { return 300 - d; })
        .attr("height", function(d) { return d; });
    }, 2000);

    复杂的图表和先进的技术

    **高级技术:**

  • 使用 D3 组件库:D3fc 等库提供了高级图表组件,以简化复杂图表的创建。
  • 动画和过渡:使用 transition() 方法创建流畅的动画效果。
  • 交互性:添加点击和悬停事件,并使用画笔和缩放功能来增强用户体验。
  • 性能优化:合理使用selectAll()、data()、enter()、exit()减少DOM操作,使用requestAnimationFrame()优化动画性能。