随机数+坐标轴+散点图
//Width and height
var w = 1000;
var h = 300;
var padding = 30;
//Dynamic, random dataset
var dataset = []; //Initialize empty array
var numDataPoints = 50; //Number of dummy data points to create
var xRange = Math.random() * 1000; //Max range of new x values
var yRange = Math.random() * 1000; //Max range of new y values
for (var i = 0; i < numDataPoints; i++) { //Loop numDataPoints times
var newNumber1 = Math.floor(Math.random() * xRange); //New random integer
var newNumber2 = Math.floor(Math.random() * yRange); //New random integer
dataset.push([newNumber1, newNumber2]); //Add new number to array
}
//Create scale functions
var xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) { return d[1]; })])
.range([h - padding, padding]);
var aScale = d3.scaleSqrt()
.domain([0, d3.max(dataset, function (d) { return d[1]; })])
.range([0, 10]);
//Define X axis
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5);
//Define Y axis
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function (d) {
return xScale(d[0]);
})
.attr("cy", function (d) {
return yScale(d[1]);
})
.attr("r", function (d) {
return aScale(d[1]);
});
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
- 更新散点图,支持数据更新和动态比
单击上方的文本可以生成新数据并更新图表
更新数据后,使用了动画过渡
更新x和y轴的比例尺
<p>单击此文本以使用新数据值更新图表</p>
//Width and height
var w = 500;
var h = 300;
var padding = 30;
//Dynamic, random dataset
var dataset = []; //Initialize empty array
var numDataPoints = 50; //Number of dummy data points to create
var maxRange = Math.random() * 1000; //Max range of new values
for (var i = 0; i < numDataPoints; i++) { //Loop numDataPoints times
var newNumber1 = Math.floor(Math.random() * maxRange); //New random integer
var newNumber2 = Math.floor(Math.random() * maxRange); //New random integer
dataset.push([newNumber1, newNumber2]); //Add new number to array
}
//Create scale functions
var xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) { return d[1]; })])
.range([h - padding, padding]);
//Define X axis
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5);
//Define Y axis
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function (d) {
return xScale(d[0]);
})
.attr("cy", function (d) {
return yScale(d[1]);
})
.attr("r", 2);
//Create X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
d3.select("p")
.on("click", function () {
//New values for dataset
var numValues = dataset.length;
var maxRange = Math.random() * 1000;
dataset = [];
for (var i = 0; i < numValues; i++) {
var newNumber1 = Math.floor(Math.random() * maxRange);
var newNumber2 = Math.floor(Math.random() * maxRange);
dataset.push([newNumber1, newNumber2]);
}
//Update scale domains
xScale.domain([0, d3.max(dataset, function (d) { return d[0]; })]);
yScale.domain([0, d3.max(dataset, function (d) { return d[1]; })]);
//Update all circles
svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
.attr("cx", function (d) {
return xScale(d[0]);
})
.attr("cy", function (d) {
return yScale(d[1]);
});
//Update X axis
svg.select(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
//Update Y axis
svg.select(".y.axis")
.transition()
.duration(1000)
.call(yAxis);
});
- 加载动画在过渡开始和结束时执行操作
//Update all circles
svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
// 在过渡开始时执行
.on("start", function () {
d3.select(this) // 选择 'this',即当前元素
// .duration(150) //不能在这里添加新的过渡动画,
.attr("fill", "magenta")
.attr("r", 3)
})
.attr("cx", function (d) {
return xScale(d[0]);
})
.attr("cy", function (d) {
return yScale(d[1]);
})
// 在过渡结束时执行
.on("end", function () {
d3.select(this)
.attr("fill", "black")
.attr("r", 2)
})
在默认情况下,任何元素在任意时刻都只能有一个过渡效果。新过渡效果会打断并覆盖原来的过渡效果;
与jQuery不同,默认情况下,jQuery 会把所有过渡效果排成队列,然后一个接一个地执行它们。换句话说,执行新过渡不会自动中断原有过渡。这种设计有时候会导致令人讨厌的界面行为,比如鼠标放到菜单上再离开后,菜单并不会马上就淡出,而是必须完全淡入之后再淡出。
由于过渡中存在的这个问题,一定要记住只能在 ("start", ...) 里面执行立即变换,而不能再添加任何过渡效果。
- 优雅收场
在执行("end", ...) 的时候,主过渡已经结束了,因此再执行新过渡不会产生任何副作用。
.on("end", function () {
d3.select(this)
.transition() //新增
.duration(1000) //新增
.attr("fill", "black")
.attr("r", 2);
});
单击 p 元素的文本,会发现:
圆形立即变成粉红并增大;
圆形过渡到新位置;
圆形过渡到原来的颜色和大小。
- 或者顺序地使用连缀的方式
svg.selectAll("circle")
.data(dataset)
.transition() //过渡1
.duration(1000)
.on("start", function() {
d3.select(this)
.attr("fill", "magenta")
.attr("r", 7);
})
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.transition() //过渡2
.duration(1000)
.attr("fill", "black")
.attr("r", 2);
- 在剪切路径中包含可见元素
好在 SVG 支持剪切路径,也就是 Photoshop 或 Illustrator 中的蒙 版。剪切路径就是一个 SVG 元素,可以包含可见的元素,并与这个可见元素一起构成可以应用到其他元素的剪切路径或蒙版。在把蒙版应用到某个元素时,只有落在该蒙版图形内部的像素才会显示。
与 g 元素相似,clipPath 本身也不可见,但它可以包含可见的元素(这些元素用作蒙版)
<clipPath id="chart-area">
<rect x="30" y="30" width="410" height="240"></rect>
</clipPath>
注意外面的 clipPath 元素有一个 ID,值为 chart-area。后面会通过这个 ID 来引用它。这个剪切路径内部有一个矩形,将被用作蒙版。
- 使用剪切路径的步骤如下:
定义clipPath并给它一个ID
在这个clipPath中放一个可见的元素(通常是一个 rect,但也可以包含
circle 和其他可见元素);
//定义剪切路径
svg.append("clipPath")//创建新的clipPath元素
.attr("id", "chart-area")//为它指定ID,后面绑定时使用
.append("rect")//在clipPath中,创建并添加新的rect
.attr("x", padding) //设置rect的位置和大小
.attr("y", padding)
.attr("width", w - padding * 3)
.attr("height", h - padding * 2);
我们希望把这个蒙版应用给所有圆形,可以分别为每个圆形都添加一个对该clipPath 的引用。但把所有圆形都放到一个分组 g 中,然后给这个分组添加引用, 会让代码更清晰也更简单:
- 在需要使用蒙版的元素上添加一个对 clipPath 的引用。
//创建圆形
svg.append("g") //创建新的g元素,包裹所有的circle
.attr("id", "circles") //指定它的ID为circles
.attr("clip-path", "url(#chart-area)") //添加对 clipPath 的引用
.selectAll("circle")
.data(dataset)
……
这个 g 元素的 clip�path 属性指向了新创建的剪切路径,语法有点不太常见:url(#chart-area)。但这都是 SVG 标准规定的。
最终结果就是,当圆形跑到图表区域的边界时,超出边界的部分会被剪切掉。注意最上方和最右边的那些圆形。
<p>Click on this text to update the chart with new data values as many times as you like!</p>
//Width and height
var w = 500;
var h = 300;
var padding = 30;
//Dynamic, random dataset
var dataset = []; //Initialize empty array
var numDataPoints = 50; //Number of dummy data points to create
var maxRange = Math.random() * 1000; //Max range of new values
for (var i = 0; i < numDataPoints; i++) { //Loop numDataPoints times
var newNumber1 = Math.floor(Math.random() * maxRange); //New random integer
var newNumber2 = Math.floor(Math.random() * maxRange); //New random integer
dataset.push([newNumber1, newNumber2]); //Add new number to array
}
//Create scale functions
var xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) { return d[1]; })])
.range([h - padding, padding]);
//Define X axis
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5);
//Define Y axis
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Define clipping path
svg.append("clipPath")
.attr("id", "chart-area")
.append("rect")
.attr("x", padding)
.attr("y", padding)
.attr("width", w - padding * 3)
.attr("height", h - padding * 2);
//Create circles
svg.append("g")
.attr("id", "circles")
.attr("clip-path", "url(#chart-area)")
.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function (d) {
return xScale(d[0]);
})
.attr("cy", function (d) {
return yScale(d[1]);
})
.attr("r", 2);
//Create X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
//On click, update with new data
d3.select("p")
.on("click", function () {
//New values for dataset
var numValues = dataset.length;
var maxRange = Math.random() * 1000;
dataset = [];
for (var i = 0; i < numValues; i++) {
var newNumber1 = Math.floor(Math.random() * maxRange);
var newNumber2 = Math.floor(Math.random() * maxRange);
dataset.push([newNumber1, newNumber2]);
}
//Update scale domains
xScale.domain([0, d3.max(dataset, function (d) { return d[0]; })]);
yScale.domain([0, d3.max(dataset, function (d) { return d[1]; })]);
//Update all circles
svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
.on("start", function () {
d3.select(this)
.attr("fill", "magenta")
.attr("r", 7);
})
.attr("cx", function (d) {
return xScale(d[0]);
})
.attr("cy", function (d) {
return yScale(d[1]);
})
.on("end", function () {
d3.select(this)
.transition()
.duration(1000)
.attr("fill", "black")
.attr("r", 2);
});
//Update X axis
svg.select(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
//Update Y axis
svg.select(".y.axis")
.transition()
.duration(1000)
.call(yAxis);
});