d3js-visualization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseD3.js Visualization
D3.js 可视化
Core Concepts
核心概念
Selection and Data Binding
选择与数据绑定
javascript
// Select elements
const svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height);
// Bind data to elements
svg.selectAll('rect')
.data(data)
.join('rect')
.attr('x', (d, i) => i * barWidth)
.attr('y', d => height - scale(d.value))
.attr('width', barWidth - 1)
.attr('height', d => scale(d.value));javascript
// Select elements
const svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height);
// Bind data to elements
svg.selectAll('rect')
.data(data)
.join('rect')
.attr('x', (d, i) => i * barWidth)
.attr('y', d => height - scale(d.value))
.attr('width', barWidth - 1)
.attr('height', d => scale(d.value));Scales
比例尺
javascript
// Linear scale (continuous → continuous)
const xScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([0, width]);
// Band scale (discrete → continuous)
const xScale = d3.scaleBand()
.domain(data.map(d => d.name))
.range([0, width])
.padding(0.1);
// Time scale
const xScale = d3.scaleTime()
.domain([startDate, endDate])
.range([0, width]);
// Color scale
const colorScale = d3.scaleOrdinal(d3.schemeCategory10);javascript
// Linear scale (continuous → continuous)
const xScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([0, width]);
// Band scale (discrete → continuous)
const xScale = d3.scaleBand()
.domain(data.map(d => d.name))
.range([0, width])
.padding(0.1);
// Time scale
const xScale = d3.scaleTime()
.domain([startDate, endDate])
.range([0, width]);
// Color scale
const colorScale = d3.scaleOrdinal(d3.schemeCategory10);Axes
坐标轴
javascript
// Create axes
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
// Append to SVG
svg.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
svg.append('g')
.attr('class', 'y-axis')
.call(yAxis);javascript
// Create axes
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
// Append to SVG
svg.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
svg.append('g')
.attr('class', 'y-axis')
.call(yAxis);Common Chart Types
常用图表类型
Bar Chart
柱状图
javascript
function createBarChart(data, container, options = {}) {
const {
width = 600,
height = 400,
margin = { top: 20, right: 20, bottom: 30, left: 40 }
} = options;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height);
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const xScale = d3.scaleBand()
.domain(data.map(d => d.name))
.range([0, innerWidth])
.padding(0.1);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.nice()
.range([innerHeight, 0]);
// Bars
g.selectAll('.bar')
.data(data)
.join('rect')
.attr('class', 'bar')
.attr('x', d => xScale(d.name))
.attr('y', d => yScale(d.value))
.attr('width', xScale.bandwidth())
.attr('height', d => innerHeight - yScale(d.value))
.attr('fill', 'steelblue');
// Axes
g.append('g')
.attr('transform', `translate(0,${innerHeight})`)
.call(d3.axisBottom(xScale));
g.append('g')
.call(d3.axisLeft(yScale));
return svg.node();
}javascript
function createBarChart(data, container, options = {}) {
const {
width = 600,
height = 400,
margin = { top: 20, right: 20, bottom: 30, left: 40 }
} = options;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height);
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const xScale = d3.scaleBand()
.domain(data.map(d => d.name))
.range([0, innerWidth])
.padding(0.1);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.nice()
.range([innerHeight, 0]);
// Bars
g.selectAll('.bar')
.data(data)
.join('rect')
.attr('class', 'bar')
.attr('x', d => xScale(d.name))
.attr('y', d => yScale(d.value))
.attr('width', xScale.bandwidth())
.attr('height', d => innerHeight - yScale(d.value))
.attr('fill', 'steelblue');
// Axes
g.append('g')
.attr('transform', `translate(0,${innerHeight})`)
.call(d3.axisBottom(xScale));
g.append('g')
.call(d3.axisLeft(yScale));
return svg.node();
}Line Chart
折线图
javascript
function createLineChart(data, container, options = {}) {
const {
width = 600,
height = 400,
margin = { top: 20, right: 20, bottom: 30, left: 40 }
} = options;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height);
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const xScale = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([0, innerWidth]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.nice()
.range([innerHeight, 0]);
// Line generator
const line = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.value))
.curve(d3.curveMonotoneX);
// Path
g.append('path')
.datum(data)
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('stroke-width', 2)
.attr('d', line);
// Dots
g.selectAll('.dot')
.data(data)
.join('circle')
.attr('class', 'dot')
.attr('cx', d => xScale(d.date))
.attr('cy', d => yScale(d.value))
.attr('r', 4)
.attr('fill', 'steelblue');
// Axes
g.append('g')
.attr('transform', `translate(0,${innerHeight})`)
.call(d3.axisBottom(xScale));
g.append('g')
.call(d3.axisLeft(yScale));
return svg.node();
}javascript
function createLineChart(data, container, options = {}) {
const {
width = 600,
height = 400,
margin = { top: 20, right: 20, bottom: 30, left: 40 }
} = options;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height);
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const xScale = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([0, innerWidth]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.nice()
.range([innerHeight, 0]);
// Line generator
const line = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.value))
.curve(d3.curveMonotoneX);
// Path
g.append('path')
.datum(data)
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('stroke-width', 2)
.attr('d', line);
// Dots
g.selectAll('.dot')
.data(data)
.join('circle')
.attr('class', 'dot')
.attr('cx', d => xScale(d.date))
.attr('cy', d => yScale(d.value))
.attr('r', 4)
.attr('fill', 'steelblue');
// Axes
g.append('g')
.attr('transform', `translate(0,${innerHeight})`)
.call(d3.axisBottom(xScale));
g.append('g')
.call(d3.axisLeft(yScale));
return svg.node();
}Pie/Donut Chart
饼图/环形图
javascript
function createPieChart(data, container, options = {}) {
const {
width = 400,
height = 400,
innerRadius = 0, // 0 for pie, > 0 for donut
} = options;
const radius = Math.min(width, height) / 2;
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height);
const g = svg.append('g')
.attr('transform', `translate(${width / 2},${height / 2})`);
const color = d3.scaleOrdinal(d3.schemeCategory10);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius - 10);
const arcs = g.selectAll('.arc')
.data(pie(data))
.join('g')
.attr('class', 'arc');
arcs.append('path')
.attr('d', arc)
.attr('fill', d => color(d.data.name));
arcs.append('text')
.attr('transform', d => `translate(${arc.centroid(d)})`)
.attr('text-anchor', 'middle')
.text(d => d.data.name);
return svg.node();
}javascript
function createPieChart(data, container, options = {}) {
const {
width = 400,
height = 400,
innerRadius = 0, // 0 for pie, > 0 for donut
} = options;
const radius = Math.min(width, height) / 2;
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height);
const g = svg.append('g')
.attr('transform', `translate(${width / 2},${height / 2})`);
const color = d3.scaleOrdinal(d3.schemeCategory10);
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(radius - 10);
const arcs = g.selectAll('.arc')
.data(pie(data))
.join('g')
.attr('class', 'arc');
arcs.append('path')
.attr('d', arc)
.attr('fill', d => color(d.data.name));
arcs.append('text')
.attr('transform', d => `translate(${arc.centroid(d)})`)
.attr('text-anchor', 'middle')
.text(d => d.data.name);
return svg.node();
}Interactivity
交互功能
Tooltips
提示框
javascript
// Create tooltip
const tooltip = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('visibility', 'hidden')
.style('background', 'white')
.style('padding', '10px')
.style('border-radius', '4px')
.style('box-shadow', '0 2px 4px rgba(0,0,0,0.2)');
// Add to elements
bars.on('mouseover', function(event, d) {
tooltip
.style('visibility', 'visible')
.html(`<strong>${d.name}</strong><br/>Value: ${d.value}`);
})
.on('mousemove', function(event) {
tooltip
.style('top', (event.pageY - 10) + 'px')
.style('left', (event.pageX + 10) + 'px');
})
.on('mouseout', function() {
tooltip.style('visibility', 'hidden');
});javascript
// Create tooltip
const tooltip = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('visibility', 'hidden')
.style('background', 'white')
.style('padding', '10px')
.style('border-radius', '4px')
.style('box-shadow', '0 2px 4px rgba(0,0,0,0.2)');
// Add to elements
bars.on('mouseover', function(event, d) {
tooltip
.style('visibility', 'visible')
.html(`<strong>${d.name}</strong><br/>Value: ${d.value}`);
})
.on('mousemove', function(event) {
tooltip
.style('top', (event.pageY - 10) + 'px')
.style('left', (event.pageX + 10) + 'px');
})
.on('mouseout', function() {
tooltip.style('visibility', 'hidden');
});Transitions
过渡动画
javascript
// Animate on data update
bars.transition()
.duration(750)
.attr('y', d => yScale(d.value))
.attr('height', d => innerHeight - yScale(d.value));
// Staggered animation
bars.transition()
.delay((d, i) => i * 50)
.duration(500)
.attr('opacity', 1);javascript
// Animate on data update
bars.transition()
.duration(750)
.attr('y', d => yScale(d.value))
.attr('height', d => innerHeight - yScale(d.value));
// Staggered animation
bars.transition()
.delay((d, i) => i * 50)
.duration(500)
.attr('opacity', 1);Zoom and Pan
缩放与平移
javascript
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', (event) => {
g.attr('transform', event.transform);
});
svg.call(zoom);javascript
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', (event) => {
g.attr('transform', event.transform);
});
svg.call(zoom);Best Practices
最佳实践
Performance
性能
- Use instead of enter/update/exit for cleaner code
join() - Throttle resize handlers
- Use CSS for simple styling
- Avoid excessive DOM updates
- 使用替代enter/update/exit写法,代码更简洁
join() - 对resize事件处理函数做节流
- 简单样式优先使用CSS实现
- 避免过度的DOM更新
Accessibility
无障碍适配
- Add to SVG
aria-label - Use for decorative charts
role="img" - Provide data tables as alternatives
- Ensure sufficient color contrast
- 为SVG添加属性
aria-label - 装饰性图表使用属性
role="img" - 提供数据表格作为替代展示方案
- 保证足够的色彩对比度
Data Formatting
数据格式化
javascript
// Parse dates
const parseDate = d3.timeParse('%Y-%m-%d');
data.forEach(d => {
d.date = parseDate(d.dateString);
});
// Format numbers
const formatNumber = d3.format(',.0f');
const formatCurrency = d3.format('$,.2f');
const formatPercent = d3.format('.1%');javascript
// Parse dates
const parseDate = d3.timeParse('%Y-%m-%d');
data.forEach(d => {
d.date = parseDate(d.dateString);
});
// Format numbers
const formatNumber = d3.format(',.0f');
const formatCurrency = d3.format('$,.2f');
const formatPercent = d3.format('.1%');