# d3教程
# 颜色变化
var ybRamp = d3.scaleLinear()
.interpolate(d3.interpolateLab)
.domain([0,maxValue]).range(["yellow", "blue"]);
var ybRamp = d3.scaleLinear()
.interpolate(d3.interpolateHcl)
.domain([0,maxValue]).range(["yellow", "blue"]);
var ybRamp = d3.scaleLinear()
.interpolate(d3.interpolateHsl)
.domain([0,maxValue]).range(["yellow", "blue"]);
# D3曲线变化
tweetLine.curve(d3.curveBasis)//尽量不接触圆点的曲线
retweetLine.curve(d3.curveStep)//直线水平或垂直的线段构成
favLine.curve(d3.curveCardinal)//过圆点的曲线
//另外默认的线段是连接每两个圆点的线段构成的!!!
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
*{margin:0;padding:0}
svg{
width:800px;
height:520px;
}
</style>
</head>
<body>
<svg></svg>
</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
d3.csv("./tweetdata.csv")
.then(data=>{
const blue = "#5eaec5", green = "#92c463", orange = "#fe9a22"
xScale = d3.scaleLinear().domain([1,10.5]).range([20,480])
yScale = d3.scaleLinear().domain([0,35]).range([480,20])
xAxis = d3.axisBottom()
.scale(xScale)
.tickSize(480)
.tickValues([1,2,3,4,5,6,7,8,9,10])
d3.select("svg").append("g").attr("id", "xAxisG").call(xAxis)
yAxis = d3.axisRight()
.scale(yScale)
.ticks(10)
.tickSize(480)
d3.select("svg").append("g").attr("id", "yAxisG").call(yAxis)
d3.select("svg").selectAll("circle.tweets")
.data(data)
.enter()
.append("circle")
.attr("class", "tweets")
.attr("r", 5)
.attr("cx", d => xScale(d.day))
.attr("cy", d => yScale(d.tweets))
.style("fill", blue)
d3.select("svg").selectAll("circle.retweets")
.data(data)
.enter()
.append("circle")
.attr("class", "retweets")
.attr("r", 5)
.attr("cx", d => xScale(d.day))
.attr("cy", d => yScale(d.retweets))
.style("fill", green)
d3.select("svg").selectAll("circle.favorites")
.data(data)
.enter()
.append("circle")
.attr("class", "favorites")
.attr("r", 5)
.attr("cx", d => xScale(d.day))
.attr("cy", d => yScale(d.favorites))
.style("fill", orange)
const lambdaXScale = d => xScale(d.day)
var tweetLine = d3.line()
.x(lambdaXScale).curve(d3.curveBasis)
.y(d => yScale(d.tweets))
var retweetLine = d3.line()
.x(lambdaXScale).curve(d3.curveStep)
.y(d => yScale(d.retweets))
var favLine = d3.line()
.x(lambdaXScale).curve(d3.curveCardinal)
.y(d => yScale(d.favorites))
d3.select("svg")
.append("path")
.attr("d", tweetLine(data))
.attr("fill", "none")
.attr("stroke", blue)
.attr("stroke-width", 2)
d3.select("svg")
.append("path")
.attr("d", retweetLine(data))
.attr("fill", "none")
.attr("stroke", green)
.attr("stroke-width", 2)
d3.select("svg")
.append("path")
.attr("d", favLine(data))
.attr("fill", "none")
.attr("stroke", orange)
.attr("stroke-width", 2)
})
.catch(err=>{console.log(err)})
</script>
day,tweets,retweets,favorites
1,1,2,5
2,6,11,3
3,3,0,1
4,5,2,6
5,10,29,16
6,4,22,10
7,3,14,1
8,5,7,7
9,1,35,22
10,4,16,15
# D3堆积图
d3.area 可以生成一个图形生成器,输入一个数组数据,将返回值传入 path 元素的 d 属性就可以渲染出对应的 area 图形。
<!DOCTYPE html>
<html>
<body>
<style>
svg {
border: 1px solid lightgrey;
}
</style>
<script src="http://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
const maxHeight = 400;
const maxWidth = 600;
const barWidth = 20;
const colorArray = ['#38CCCB', '#0074D9', '#2FCC40', '#FEDC00', '#FF4036', 'lightgrey'];
function singleArea() {
const data = [
{
date: '2019-01-01',
value: 1,
},
{
date: '2019-01-02',
value: 10,
},
{
date: '2019-01-03',
value: 35,
},
];
const xScale = d3.scaleTime()
.domain(d3.extent(data, (d) => new Date(d.date)))
.range([0, maxWidth]);
console.log(d3.extent(data, (d) => new Date(d.date)))
const yScale = d3.scaleLinear()
.domain(d3.extent(data, (d) => d.value))
.range([maxHeight, 0])
const area = d3.area()
.x((d) => xScale(new Date(d.date)))
.y1((d) => yScale(d.value))
.y0((d) => yScale(0))
const svg = d3.select('body')
.append('svg')
.attr('width', maxWidth)
.attr('height', maxHeight)
svg.append('g')
.selectAll('path.area')
.data([data])
.enter()
.append('path')
.attr('class', 'area')
.attr('d', area)
.attr('fill', colorArray[0])
}
singleArea()
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<style>
svg {
border: 1px solid lightgrey;
}
</style>
<script src="http://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
const maxHeight = 400;
const maxWidth = 600;
const barWidth = 20;
const colorArray = ['#38CCCB', '#0074D9', '#2FCC40', '#FEDC00', '#FF4036', 'lightgrey'];
function stackArea() {
const stackData = [
{
year: 2017,
quarter: 2,
samsung: 0.229,
apple: 0.118,
huawei: 0.110,
oppo: 0.08,
xiaomi: 0.062,
others: 0.401,
},
{
year: 2017,
quarter: 3,
samsung: 0.221,
apple: 0.124,
huawei: 0.104,
oppo: 0.081,
xiaomi: 0.075,
others: 0.396,
},
{
year: 2017,
quarter: 4,
samsung: 0.189,
apple: 0.196,
huawei: 0.107,
oppo: 0.069,
xiaomi: 0.071,
others: 0.368,
},
{
year: 2018,
quarter: 1,
samsung: 0.235,
apple: 0.157,
huawei: 0.118,
oppo: 0.074,
xiaomi: 0.084,
others: 0.332,
},
]
const stack = d3.stack()
.keys(['apple', 'samsung', 'huawei', 'oppo', 'xiaomi', 'others']);
const series = stack(stackData);
const stackMin = (serie) => d3.min(serie, (d) => d[0]);
const stackMax = (serie) => d3.max(serie, (d) => d[1]);
const yScale = d3.scaleLinear()
.domain([d3.min(series, stackMin), d3.max(series, stackMax)])
.range([maxHeight, 0])
const xAccessor = (d) => `${d.year}-${d.quarter}`;
const xScale = d3.scalePoint()
.domain(stackData.map(xAccessor))
.range([0, maxWidth])
const area = d3.area()
.x((d) => xScale(xAccessor(d.data)))
.y0((d) => yScale(d[0]))
.y1((d) => yScale(d[1]))
const svg = d3.select('body')
.append('svg')
.attr('width', maxWidth)
.attr('height', maxHeight)
svg.append('g')
.selectAll('path')
.data(series)
.enter()
.append('path')
.attr('d', area)
.attr('fill', (d, i) => colorArray[i % colorArray.length])
}
stackArea()
</script>
</body>
</html>
# d3path
可以借助line创建线段,借助area创建面积,借助d属性展示
数据需要注意是创建线图 面积图的data是 二维数组 而创建饼图,条形图,散点图的则是一维数组
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Data Filter</title>
<link rel="stylesheet" href="style.css">
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script type="text/javascript">
var data = [ // <-C
[
{x: 0, y: 5},{x: 1, y: 9},{x: 2, y: 7},
{x: 3, y: 5},{x: 4, y: 3},{x: 6, y: 4},
{x: 7, y: 2},{x: 8, y: 3},{x: 9, y: 2}
],
];
var margin=20
var svg = d3.selectAll("body").append("svg").style("width",600).attr("height",500)
.style("border","1px solid grey").append("g")
.attr("transform",`translate(${margin},${margin})`)
var xscale = d3.scaleLinear().domain([0,10]).range([margin,600-margin])
var yscale = d3.scaleLinear().domain([0,10]).range([500-margin,margin])
var line =d3.line()
.x(d=>xscale(d.x))
.y(d=>yscale(d.y))
var area =d3.area().x((d)=>xscale(d.x)).y0(yscale(0)).y1(d=>yscale(d.y))
svg.selectAll("path.path.line")
.data(data)
.enter()
.append("path")
.attr("class","path-line")
.attr("class", "line")
.attr("d", function(d){return line(d);});
svg.selectAll("path.path-area")
.data(data)
.enter()
.append("path")
.attr("fill","orange")
.attr("class","path-area")
.attr("d",function(d){
return area(d)
})
</script>
</body>
</html>
# mouse
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
html, body {
height: 100%;
}
body {
margin: 0;
}
svg {
cursor: pointer;
width: 100%;
height: 100%;
}
circle {
fill: none;
stroke: steelblue;
}
div{
height:0
}
</style>
<body>
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
var r = 400;
var svg = d3.select("body")
.append("svg")
var positionLabel = svg.append("text")
.attr("x", 10)
.attr("y", 30);
svg.on("mousemove", function () { //<-A
printPosition();
});
function printPosition() { //<-B
var position = d3.mouse(svg.node()); //<-C
positionLabel.text(position);
}
svg.on("click", function () { //<-D
for (var i = 1; i < 5; ++i) {
var position = d3.mouse(svg.node());
console.log(svg.node())
var circle = svg.append("circle")
.attr("cx", position[0])
.attr("cy", position[1])
.attr("r", 0)
.style("stroke-width", 5 / (i))
.transition()
.delay(Math.pow(i, 2.5) * 50)
.duration(2000)
.ease(d3.easeQuadIn)
.attr("r", r)
.style("stroke-opacity", 0)
.on("end", function () {
d3.select(this).remove();
});
}
});
</script>
# 总结
- d3选择得到的元素如svg svg.node()返回dom元素而svg.nodes()反回的是数组
- d3.mouse(svg.node())得到坐标位置的x y
- 另外还有触碰事件
- touchstart:当触摸点被放在触摸屏上时。
- touchmove:当触摸点在触摸屏上移动时。
- touchend:当触摸点从触摸屏上拿开时。
# d3 zoom
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
html, body {
height: 100%;
}
body {
margin: 0;
}
svg {
cursor: pointer;
width: 100%;
height: 100%;
}
circle {
fill: none;
stroke: steelblue;
}
div{
height:0
}
</style>
<body>
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
var width = 600, height = 350, r = 50;
var data = [
[width / 2 - r, height / 2 - r],
[width / 2 - r, height / 2 + r],
[width / 2 + r, height / 2 - r],
[width / 2 + r, height / 2 + r]
];
var svg = d3.select("body").append("svg")
.attr("style", "1px solid black")
.attr("width", width)
.attr("height", height)
.call( // <-A
d3.zoom() // <-B
.scaleExtent([0.1, 2]) // <-C
.on("zoom", zoomHandler) // <-D
)
.append("g");
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", r)
.attr("transform", function (d) {
return "translate(" + d + ")";
});
function zoomHandler() {
var transform = d3.event.transform;
svg.attr("transform", "translate("
+ transform.x + "," + transform.y
+ ")scale(" + transform.k + ")");
}
</script>
# 总结
- d3.zoom()缩放
- scaleExtent控制放大缩小比例
- on绑定zoom事件
- transform.k 当前尺度的数字
# d3 drag
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
svg {
border: 1px solid black;
}
circle {
fill: #dc143c;
}
</style>
<body>
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
var width = 960, height = 500, r = 50;
var data = [
[width / 2 - r, height / 2 - r],
[width / 2 - r, height / 2 + r],
[width / 2 + r, height / 2 - r],
[width / 2 + r, height / 2 + r]
];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var drag = d3.drag() // <-A
.on("drag", move);
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", r)
.attr("transform", function (d) {
return "translate(" + d + ")";
})
.call(drag); // <-A
function move(d) {
var x = d3.event.x, // <-C
y = d3.event.y;
if(inBoundaries(x, y))
d3.select(this)
.attr("transform", function (d) { // <-D
return "translate(" + x + ", " + y + ")";
});
}
function inBoundaries(x, y){
return (x >= (0 + r) && x <= (width - r))
&& (y >= (0 + r) && y <= (height - r));
}
</script>
# 总结
- d3.drag 拖动
- dragstart 拖拽手势开始出发
- drag 拖拽元素时触发
- dragend拖拽结束时触发
# D3的力导向图
# 力模型
d3.forceSimulation([nodes])
# simulation.force(name,[force])函数
//链接力
.force("link", d3.forceLink(config.links))
// 万有引力
.force("charge", d3.forceManyBody().strength(-300))
// 用指定的x坐标和y坐标创建一个居中力。
.force("center", d3.forceCenter(config.width / 2, config.height / 2))
//碰撞作用力,为节点指定一个radius区域来防止节点重叠,设置碰撞力的强度,范围[0,1],
//默认为0.7。设置迭代次数,默认为1,迭代次数越多最终的布局效果越好,但是计算复杂度更高
.force("collide", d3.forceCollide(100).strength(0.2).iterations(5))
d3.forceSimulation()
.velocityDecay(0.7)
.alphaDecay(0)
.force("collision", d3.forceCollide(r + 1.5).strength(1));
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
svg {
border: 1px solid black;
}
circle {
fill: #dc143c;
}
</style>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
var w = 1000, h = 500, r = 4.5,
nodes = [],
force = d3.forceSimulation()
.velocityDecay(0.7)
.alphaDecay(0)
.force("collision", d3.forceCollide(r + 1.5).strength(1));
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
force.on("tick", function () {
svg.selectAll("circle")
.attr("cx", function (d) {return d.x;})
.attr("cy", function (d) {return d.y;});
});
svg.on("mousemove", function () {
console.log(this)
var point = d3.mouse(this),
node = {x: point[0], y: point[1]}; // <-A
svg.append("circle")
.data([node])
.attr("class", "node")
.attr("cx", function (d) {return d.x;})
.attr("cy", function (d) {return d.y;})
.attr("r", 1e-6)
.transition()
.attr("r", r)
.transition()
.delay(7000)
.attr("r", 1e-6)
.on("end", function () {
nodes.shift(); // <-B
force.nodes(nodes);
})
.remove();
nodes.push(node); // <-C
force.nodes(nodes);
});
function noForce(){
force.force("charge", null);
force.force("x", null);
force.force("y", null);
force.restart();
}
function repulsion(){
force.force("charge", d3.forceManyBody().strength(-10));
force.force("x", null);
force.force("y", null);
force.restart();
}
function gravity(){
force.force("charge", d3.forceManyBody().strength(1));
force.force("x", null);
force.force("y", null);
force.restart();
}
function positioningWithGravity(){
force.force("charge", d3.forceManyBody().strength(0.5));
force.force("x", d3.forceX(w / 2));
force.force("y", d3.forceY(h / 2));
force.restart();
}
function positioningWithRepulsion(){
force.force("charge", d3.forceManyBody().strength(-20));
force.force("x", d3.forceX(w / 2));
force.force("y", d3.forceY(h / 2));
force.restart();
}
</script>
<div class="control-group">
<button onclick="noForce()">
No Force
</button>
<button onclick="repulsion()">
Repulsion
</button>
<button onclick="gravity()">
Gravity
</button>
<button onclick="positioningWithGravity()">
Positioning with Gravity
</button>
<button onclick="positioningWithRepulsion()">
Positioning with Repulsion
</button>
</div>
</body>
# 总结
- 创建模式 force = d3.forceSimulation()
- 监听变化force.on("tick", function () {xxx})
- 绑定力与点force.nodes(nodes)
- strength 各个力的使用不一致
- force.force("charge", d3.forceManyBody().strength(-10))在万有引力中正值吸引,负值排斥
- nodes中的数据还可以配置其他参数
node = {x: point[0], y: point[1],
vx: previousPoint?point[0]-previousPoint[0]:point[0],
vy: previousPoint?point[1]-previousPoint[1]:point[1]};
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
*{
margin:0;padding:0
}
html, body {
height: 100%;
}
body {
margin: 0;
}
svg {
width: 100%;
height: 100%;
}
circle {
fill: steelblue;
}
.line {
stroke: grey;
}
</style>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
var w = 1280, h = 540,
r = 4.5, nodes = [], links = [];
var force = d3.forceSimulation()
.velocityDecay(0.8)
.alphaDecay(0)
.force("charge", d3.forceManyBody().strength(-50).distanceMax(h / 4))
.force("collision", d3.forceCollide(r + 0.5).strength(1));
var duration = 10000;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
force.on("tick", function () {
svg.selectAll("circle")
.attr("cx", function (d) {return boundX(d.x);})
.attr("cy", function (d) {return boundY(d.y);});
svg.selectAll("line")
.attr("x1", function (d) {return boundX(d.source.x);})
.attr("y1", function (d) {return boundY(d.source.y);})
.attr("x2", function (d) {return boundX(d.target.x);})
.attr("y2", function (d) {return boundY(d.target.y);});
});
function boundX(x) {
return x > (w - r) ? (w - r): (x > r ? x : r);
}
function boundY(y){
return y > (h - r) ? (h - r) : (y > r ? y : r);
}
function offset() {
return Math.random() * 100;
}
function createNodes(point) {
var numberOfNodes = Math.round(Math.random() * 10);
var newNodes = [];
for (var i = 0; i < numberOfNodes; ++i) {
newNodes.push({
x: point[0] + offset(),
y: point[1] + offset()
});
}
newNodes.forEach(function(e){nodes.push(e)});
return newNodes;
}
function createLinks(nodes) {
var newLinks = [];
for (var i = 0; i < nodes.length; ++i) { // <-A
if(i == nodes.length - 1)
newLinks.push(
{source: nodes[i], target: nodes[0]}
);
else
newLinks.push(
{source: nodes[i], target: nodes[i + 1]}
);
}
newLinks.forEach(function(e){links.push(e)});
return newLinks;
}
svg.on("click", function () {
var point = d3.mouse(this),
newNodes = createNodes(point),
newLinks = createLinks(newNodes);
newNodes.forEach(function (node) {
svg.append("circle")
.data([node])
.attr("class", "node")
.attr("cx", function (d) {return d.x;})
.attr("cy", function (d) {return d.y;})
.attr("r", 1e-6)
.call(d3.drag() // <-D
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded))
.transition()
.attr("r", 7)
.transition()
.delay(duration)
.attr("r", 1e-6)
.on("end", function () {nodes.shift();})
.remove();
});
newLinks.forEach(function (link) {
svg.append("line") // <-B
.data([link])
.attr("class", "line")
.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;})
.transition()
.delay(duration)
.style("stroke-opacity", 1e-6)
.on("end", function () {links.shift();})
.remove();
});
force.nodes(nodes);
force.force("link", d3.forceLink(links).strength(1).distance(20)); // <-C
force.restart();
});
function dragStarted(d) {
d.fx = d.x; // <-E
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x; // <-F
d.fy = d3.event.y;
}
function dragEnded(d) {
d.fx = null; // <-G
d.fy = null;
}
</script>
</body>
