D3.js SVG 基础 #
SVG(Scalable Vector Graphics,可缩放矢量图形)是 D3.js 数据可视化的核心载体。理解 SVG 是掌握 D3.js 的基础。
SVG 简介 #
什么是 SVG? #
SVG 是一种基于 XML 的矢量图形格式,具有以下特点:
text
┌─────────────────────────────────────────────────────────────┐
│ SVG 特点 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 矢量图形 │ │ 可缩放 │ │ 可交互 │ │
│ │ 无限放大 │ │ 不失真 │ │ 支持事件 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ DOM 元素 │ │ 样式控制 │ │ 动画支持 │ │
│ │ 可操作 │ │ CSS/属性 │ │ SMIL/JS │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
SVG vs Canvas #
| 特性 | SVG | Canvas |
|---|---|---|
| 渲染方式 | 矢量 | 位图 |
| 缩放 | 无损 | 有损 |
| 事件支持 | 原生 | 需要计算 |
| 性能 | 元素多时较慢 | 大量元素性能好 |
| 适用场景 | 图表、图标 | 游戏、图像处理 |
创建 SVG #
基本结构 #
javascript
const svg = d3.select('#chart')
.append('svg')
.attr('width', 500)
.attr('height', 300);
设置 viewBox #
javascript
const svg = d3.select('#chart')
.append('svg')
.attr('viewBox', '0 0 500 300')
.attr('preserveAspectRatio', 'xMidYMid meet');
响应式 SVG #
javascript
const svg = d3.select('#chart')
.append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', '0 0 500 300');
SVG 坐标系统 #
坐标原点 #
text
(0, 0) ────────────────► x
│
│
│
│
▼
y
SVG 坐标原点在左上角,x 轴向右增长,y 轴向下增长。
transform 属性 #
javascript
svg.append('g')
.attr('transform', 'translate(50, 50)');
svg.append('g')
.attr('transform', 'translate(50, 50) scale(2)');
svg.append('g')
.attr('transform', 'translate(50, 50) rotate(45)');
svg.append('g')
.attr('transform', 'translate(50, 50) rotate(45, 100, 100)');
基本图形元素 #
矩形 rect #
javascript
svg.append('rect')
.attr('x', 50)
.attr('y', 50)
.attr('width', 100)
.attr('height', 80)
.attr('fill', 'steelblue')
.attr('stroke', '#333')
.attr('stroke-width', 2)
.attr('rx', 5)
.attr('ry', 5);
圆形 circle #
javascript
svg.append('circle')
.attr('cx', 100)
.attr('cy', 100)
.attr('r', 50)
.attr('fill', 'orange')
.attr('stroke', '#333')
.attr('stroke-width', 2);
椭圆 ellipse #
javascript
svg.append('ellipse')
.attr('cx', 100)
.attr('cy', 100)
.attr('rx', 80)
.attr('ry', 50)
.attr('fill', 'lightgreen');
线 line #
javascript
svg.append('line')
.attr('x1', 50)
.attr('y1', 50)
.attr('x2', 200)
.attr('y2', 150)
.attr('stroke', 'black')
.attr('stroke-width', 2)
.attr('stroke-dasharray', '5,5');
折线 polyline #
javascript
svg.append('polyline')
.attr('points', '50,50 100,100 150,50 200,100')
.attr('fill', 'none')
.attr('stroke', 'blue')
.attr('stroke-width', 2);
多边形 polygon #
javascript
svg.append('polygon')
.attr('points', '100,10 40,198 190,78 10,78 160,198')
.attr('fill', 'gold')
.attr('stroke', 'orange')
.attr('stroke-width', 2);
路径 path #
javascript
svg.append('path')
.attr('d', 'M 10 10 L 100 10 L 100 100 L 10 100 Z')
.attr('fill', 'lightblue')
.attr('stroke', 'blue');
svg.append('path')
.attr('d', 'M 10 80 Q 95 10 180 80 T 350 80')
.attr('fill', 'none')
.attr('stroke', 'red')
.attr('stroke-width', 2);
文本 text #
javascript
svg.append('text')
.attr('x', 100)
.attr('y', 100)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.attr('font-size', '20px')
.attr('fill', 'black')
.text('Hello D3.js');
svg.append('text')
.attr('x', 100)
.attr('y', 100)
.attr('transform', 'rotate(-45, 100, 100)')
.text('Rotated Text');
分组元素 g #
创建分组 #
javascript
const g = svg.append('g')
.attr('class', 'chart-group')
.attr('transform', 'translate(50, 50)');
g.append('circle')
.attr('cx', 50)
.attr('cy', 50)
.attr('r', 30);
g.append('text')
.attr('x', 50)
.attr('y', 50)
.text('Group');
分组的作用 #
javascript
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
const xGroup = g.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0, ${height})`);
const yGroup = g.append('g')
.attr('class', 'y-axis');
样式属性 #
填充 fill #
javascript
svg.append('rect')
.attr('fill', 'steelblue')
.attr('fill', '#3498db')
.attr('fill', 'rgb(52, 152, 219)')
.attr('fill', 'rgba(52, 152, 219, 0.5)')
.attr('fill', 'url(#gradient)');
描边 stroke #
javascript
svg.append('rect')
.attr('stroke', '#333')
.attr('stroke-width', 2)
.attr('stroke-dasharray', '5,5')
.attr('stroke-linecap', 'round')
.attr('stroke-linejoin', 'round')
.attr('stroke-opacity', 0.5);
透明度 opacity #
javascript
svg.append('circle')
.attr('opacity', 0.5)
.attr('fill-opacity', 0.7)
.attr('stroke-opacity', 0.3);
渐变和图案 #
线性渐变 #
javascript
const defs = svg.append('defs');
const linearGradient = defs.append('linearGradient')
.attr('id', 'myGradient')
.attr('x1', '0%')
.attr('y1', '0%')
.attr('x2', '100%')
.attr('y2', '0%');
linearGradient.append('stop')
.attr('offset', '0%')
.attr('stop-color', 'steelblue');
linearGradient.append('stop')
.attr('offset', '100%')
.attr('stop-color', 'orange');
svg.append('rect')
.attr('fill', 'url(#myGradient)')
.attr('width', 200)
.attr('height', 100);
径向渐变 #
javascript
const radialGradient = defs.append('radialGradient')
.attr('id', 'radialGradient')
.attr('cx', '50%')
.attr('cy', '50%')
.attr('r', '50%');
radialGradient.append('stop')
.attr('offset', '0%')
.attr('stop-color', 'white');
radialGradient.append('stop')
.attr('offset', '100%')
.attr('stop-color', 'steelblue');
图案 #
javascript
const pattern = defs.append('pattern')
.attr('id', 'pattern')
.attr('width', 10)
.attr('height', 10)
.attr('patternUnits', 'userSpaceOnUse');
pattern.append('rect')
.attr('width', 10)
.attr('height', 10)
.attr('fill', 'white');
pattern.append('circle')
.attr('cx', 5)
.attr('cy', 5)
.attr('r', 2)
.attr('fill', 'steelblue');
svg.append('rect')
.attr('fill', 'url(#pattern)')
.attr('width', 200)
.attr('height', 100);
路径命令 #
M - 移动到 #
javascript
svg.append('path')
.attr('d', 'M 10 10');
L - 直线到 #
javascript
svg.append('path')
.attr('d', 'M 10 10 L 100 10 L 100 100');
H/V - 水平/垂直线 #
javascript
svg.append('path')
.attr('d', 'M 10 10 H 100 V 100 H 10 Z');
C - 三次贝塞尔曲线 #
javascript
svg.append('path')
.attr('d', 'M 10 10 C 20 20, 40 20, 50 10')
.attr('fill', 'none')
.attr('stroke', 'black');
S - 平滑三次贝塞尔 #
javascript
svg.append('path')
.attr('d', 'M 10 10 C 20 20, 40 20, 50 10 S 80 0, 90 10')
.attr('fill', 'none')
.attr('stroke', 'black');
Q - 二次贝塞尔曲线 #
javascript
svg.append('path')
.attr('d', 'M 10 10 Q 50 0, 90 10')
.attr('fill', 'none')
.attr('stroke', 'black');
T - 平滑二次贝塞尔 #
javascript
svg.append('path')
.attr('d', 'M 10 10 Q 50 0, 90 10 T 170 10')
.attr('fill', 'none')
.attr('stroke', 'black');
A - 弧线 #
javascript
svg.append('path')
.attr('d', 'M 10 10 A 30 30 0 0 1 90 10')
.attr('fill', 'none')
.attr('stroke', 'black');
Z - 闭合路径 #
javascript
svg.append('path')
.attr('d', 'M 10 10 L 100 10 L 100 100 Z')
.attr('fill', 'lightblue');
D3.js 路径生成器 #
d3.path() #
javascript
const path = d3.path();
path.moveTo(10, 10);
path.lineTo(100, 10);
path.lineTo(100, 100);
path.closePath();
svg.append('path')
.attr('d', path.toString());
d3.line() #
javascript
const data = [
{ x: 0, y: 10 },
{ x: 1, y: 20 },
{ x: 2, y: 15 }
];
const line = d3.line()
.x(d => d.x * 50)
.y(d => d.y * 5);
svg.append('path')
.datum(data)
.attr('d', line)
.attr('fill', 'none')
.attr('stroke', 'steelblue');
d3.area() #
javascript
const area = d3.area()
.x(d => d.x * 50)
.y0(height)
.y1(d => height - d.y * 5);
svg.append('path')
.datum(data)
.attr('d', area)
.attr('fill', 'steelblue');
d3.arc() #
javascript
const arc = d3.arc()
.innerRadius(50)
.outerRadius(100)
.startAngle(0)
.endAngle(Math.PI);
svg.append('path')
.attr('d', arc)
.attr('transform', 'translate(150, 150)')
.attr('fill', 'steelblue');
SVG 与 CSS #
使用 CSS 样式 #
css
.bar {
fill: steelblue;
stroke: #333;
stroke-width: 1;
}
.bar:hover {
fill: orange;
}
javascript
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar');
内联样式 #
javascript
svg.append('rect')
.style('fill', 'steelblue')
.style('stroke', '#333')
.style('stroke-width', '1px');
SVG 事件 #
添加事件监听 #
javascript
svg.append('circle')
.attr('cx', 100)
.attr('cy', 100)
.attr('r', 50)
.attr('fill', 'steelblue')
.on('click', function(event, d) {
d3.select(this).attr('fill', 'orange');
})
.on('mouseover', function() {
d3.select(this).attr('r', 60);
})
.on('mouseout', function() {
d3.select(this).attr('r', 50);
});
SVG 最佳实践 #
1. 使用分组组织元素 #
javascript
const chart = svg.append('g')
.attr('class', 'chart');
const bars = chart.append('g')
.attr('class', 'bars');
const axes = chart.append('g')
.attr('class', 'axes');
2. 使用 defs 定义可重用元素 #
javascript
const defs = svg.append('defs');
defs.append('circle')
.attr('id', 'myCircle')
.attr('r', 10);
svg.append('use')
.attr('href', '#myCircle')
.attr('x', 50)
.attr('y', 50);
3. 设置合适的 viewBox #
javascript
const svg = d3.select('#chart')
.append('svg')
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('preserveAspectRatio', 'xMidYMid meet');
4. 使用 transform 简化定位 #
javascript
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
SVG 元素速查表 #
| 元素 | 描述 | 主要属性 |
|---|---|---|
| svg | SVG 容器 | width, height, viewBox |
| g | 分组 | transform |
| rect | 矩形 | x, y, width, height, rx, ry |
| circle | 圆形 | cx, cy, r |
| ellipse | 椭圆 | cx, cy, rx, ry |
| line | 线 | x1, y1, x2, y2 |
| polyline | 折线 | points |
| polygon | 多边形 | points |
| path | 路径 | d |
| text | 文本 | x, y, text-anchor |
| defs | 定义 | - |
| use | 引用 | href |
下一步 #
现在你已经掌握了 SVG 基础,接下来学习 形状生成器,了解如何使用 D3.js 创建更复杂的图形!
最后更新:2026-03-28