D3-Chord:鼠标悬停在和弦路径上的流动圆圈元素
D3-Chord: Flowing circle elements on chord-path mouseover
我正在尝试在 D3 和弦图中的和弦路径的鼠标悬停事件上使用 svg 圆形元素实现动画效果。
本次试验的灵感来自桑基图的以下实现:
https://bl.ocks.org/micahstubbs/ed0ae1c70256849dab3e35a0241389c9
我已经设法在和弦的鼠标悬停事件中插入圆圈元素。但是,我很难弄清楚如何让它们跟随和弦的路径。 (以下JS代码中的第69-106行)。
我的 JS 代码 (chord.js)
//*******************************************************************
// CREATE MATRIX AND MAP
//*******************************************************************
var matrix, mmap, rdr;
d3.csv('data/out.csv', function(error, data) {
var mpr = chordMpr(data);
mpr.addValuesToMap('Source')
.setFilter(function(row, a, b) {
return (row.Source === a.name && row.Destination === b.name)
})
.setAccessor(function(recs, a, b) {
if (!recs[0]) return 0;
return +recs[0].Count;
});
matrix = mpr.getMatrix();
mmap = mpr.getMap();
rdr = chordRdr(matrix, mmap);
drawChords();
});
//*******************************************************************
// DRAW THE CHORD DIAGRAM
//*******************************************************************
function drawChords() {
var w = window.innerWidth || document.body.clientWidth,
h = 700,
r1 = h / 2,
r0 = r1 - 150;
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("id", "circle")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var chord = d3.layout.chord()
.padding(.15)
.sortChords(d3.descending);
chord.matrix(matrix);
var arc = d3.svg.arc()
.innerRadius(r0*1.03)
.outerRadius(r0*1.03 + 20);
var path = d3.svg.chord()
.radius(r0);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter()
.append("g")
.attr("class", "group");
var paths = g.append("svg:path")
.style("stroke", function(d) { return fillcolor(rdr(d).gname); })
.style("fill", function(d) { return fillcolor(rdr(d).gname); })
.attr("d", arc)
.attr("class", "arcs");
var chordPaths = svg.selectAll("path.chord")
.data(chord.chords())
.enter().append("svg:path")
.attr("class", "chord")
.on("mouseover", function(d) {
//context = d3.select('canvas').node().getContext('2d');
//context.clearRect(0, 0, 1000, 1000);
//context.fillStyle = 'gray';
//context.lineWidth = '1px';
currentTime = 500;
current = currentTime * 0.15 * (0.5 + (Math.random()));
currentPos = this.getPointAtLength(current);
//context.beginPath();
//context.fillStyle = "black";
/*context.arc(
Math.abs(currentPos.x),
Math.abs(currentPos.y),
2,
0,
2 * Math.PI
);
context.fill();*/
currentpath = this;
svg.insert("circle")
.attr("cx",currentPos.x)
.attr("cy",currentPos.y)
.attr("r",2)
.style("stroke-opacity", 1)
.style("fill", this.style.fill)
.transition()
.duration(1000)
.ease(Math.sqrt)
.attr("cx",function(){
currentPos = currentpath.getPointAtLength(current+100);
return currentPos.x;
})
.attr("cy",function(){
currentPos = currentpath.getPointAtLength(current-100);
return currentPos.y;
})
.remove();
})
.style("fill", function(d) { return fillcolor(rdr(d.target).gname); })
.attr("d", path);
}
function fillcolor(segmentvalue){
if (segmentvalue.includes("Segment A")) {
return '#ff3a21'
} else if (segmentvalue.includes("Segment C")) {
return '#26bde2'
} else if (segmentvalue.includes("Segment D")) {
return '#fcc30b'
} else if (segmentvalue.includes("Segment B")) {
return '#dd1367'
} else if (segmentvalue.includes("Segment E")) {
return '#a1e972'
} else {
return '#72e8a4'
}
}
这是HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#circle circle {
fill: none;
pointer-events: all;
}
path.chord {
fill-opacity: .6;
stroke: #000;
stroke-width: .25px;
}
</style>
</head>
<body>
<script src="d3/d3.js"></script>
<script src="d3/underscore.js"></script>
<script type="text/javascript" src="d3/gistfile1.js"></script>
<script type="text/javascript" src="js/chord.js"></script>
</body>
</html>
这是我的数据文件 (out.csv):
Source,Destination,Count,
Segment A,Segment A,597.7731179,
Segment B,Segment A,428.4797097,
Segment C,Segment A,242.5536698,
Segment D,Segment A,39.18270781,
Segment F,Segment A,373.4118141,
Segment E,Segment A,342.1175938,
Segment B,Segment B,695.841404,
Segment C,Segment B,586.8204889,
Segment D,Segment B,519.0497198,
Segment F,Segment B,142.271554,
Segment E,Segment B,282.7048795,
Segment A,Segment B,552.8162888,
Segment C,Segment C,162.7852664,
Segment D,Segment C,150.6887517,
Segment F,Segment C,631.6468679,
Segment E,Segment C,611.0627425,
Segment A,Segment C,344.1286204,
Segment B,Segment C,395.710855,
Segment D,Segment D,141.5878005,
Segment F,Segment D,254.2566994,
Segment E,Segment D,483.4672747,
Segment A,Segment D,5.942896921,
Segment B,Segment D,185.6991357,
Segment C,Segment D,138.2424522,
我已经实施了一个足够接近的解决方案并将其托管在:
https://jsfiddle.net/Edwig_Noronha/fb9j5v4t/
//*******************************************************************
// CREATE MATRIX AND MAP
//*******************************************************************
var matrix, mmap, rdr;
d3.csv('data/out.csv', function(error, data) {
var mpr = chordMpr(data);
mpr
.addValuesToMap('Source')
.setFilter(function(row, a, b) {
return (row.Source === a.name && row.Destination === b.name)
})
.setAccessor(function(recs, a, b) {
if (!recs[0]) return 0;
return +recs[0].Count;
});
matrix = mpr.getMatrix();
mmap = mpr.getMap();
rdr = chordRdr(matrix, mmap);
drawChords();
});
//*******************************************************************
// DRAW THE CHORD DIAGRAM
//*******************************************************************
function drawChords() {
var w = window.innerWidth || document.body.clientWidth,
h = 700,
r1 = h / 2,
r0 = r1 - 150;
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("id", "circle")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var chord = d3.layout.chord()
.padding(.15)
.sortChords(d3.descending);
chord.matrix(matrix);
var arc = d3.svg.arc()
.innerRadius(r0*1.03)
.outerRadius(r0*1.03 + 20);
var path = d3.svg.chord()
.radius(r0);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter()
.append("g")
.attr("class", "group");
var paths = g.append("svg:path")
.style("stroke", function(d) { return fillcolor(rdr(d).gname); })
.style("fill", function(d) { return fillcolor(rdr(d).gname); })
.attr("d", arc)
.attr("class", "arcs");
var chordPaths = svg.selectAll("path.chord")
.data(chord.chords(),function(d,i){return i;})
.enter().append("svg:path")
.attr("class", "chord")
.attr("id", function(d,i){return "chord"+i})
.on("mouseover", function(d,i) {
this.classList.add("hovered");
currentpath = this;
startPoint = pathStartPoint(currentpath);
function loop(thispath) {
if (!thispath.classList.contains("hovered")) return;
setTimeout(function () {
particle = svg.insert("circle")
.attr("class",function(){return currentpath.getAttribute("id")+"-circle"})
.attr("r",2)
.style("stroke-opacity", 1)
.style("fill", currentpath.style.fill)
.attr("transform", "translate(" + startPoint[0] + "," + startPoint[1] + ")")
.transition()
.duration(2000)
.attrTween("transform", translateAlong(currentpath))
.remove();
loop(thispath);
}, 300);
}
loop(this);
})
.on("mouseout",function(d,i){
this.classList.remove("hovered");
})
.style("fill", function(d) { return fillcolor(rdr(d.target).gname); })
.attr("d", path);
}
//Get path start point for placing marker
function pathStartPoint(path) {
var d = path.getAttribute("d");
var dsplitted = d.split(" ");
return dsplitted[1].split(",");
};
function translateAlong(path) {
var l = path.getTotalLength();
var t0 = 0;
return function(i) {
return function(t) {
var p0 = path.getPointAtLength(t0 * l);//previous point
var p = path.getPointAtLength(t * l);////current point
t0 = t;
var centerX = p.x,
centerY = p.y;
return "translate(" + centerX + "," + centerY + ")"//rotate(" + angle + " 24" + " 12" +")";
}
}
}
function fillcolor(segmentvalue){
if (segmentvalue.includes("Segment A")) {
return '#ff3a21'
} else if (segmentvalue.includes("Segment C")) {
return '#26bde2'
} else if (segmentvalue.includes("Segment D")) {
return '#fcc30b'
} else if (segmentvalue.includes("Segment B")) {
return '#dd1367'
} else if (segmentvalue.includes("Segment E")) {
return '#a1e972'
} else {
return '#72e8a4'
}
}
chord.js 文件中的上述更改实现了足够接近的过渡。
工作代码托管于:
https://jsfiddle.net/Edwig_Noronha/fb9j5v4t/
我正在尝试在 D3 和弦图中的和弦路径的鼠标悬停事件上使用 svg 圆形元素实现动画效果。
本次试验的灵感来自桑基图的以下实现:
https://bl.ocks.org/micahstubbs/ed0ae1c70256849dab3e35a0241389c9
我已经设法在和弦的鼠标悬停事件中插入圆圈元素。但是,我很难弄清楚如何让它们跟随和弦的路径。 (以下JS代码中的第69-106行)。
我的 JS 代码 (chord.js)
//*******************************************************************
// CREATE MATRIX AND MAP
//*******************************************************************
var matrix, mmap, rdr;
d3.csv('data/out.csv', function(error, data) {
var mpr = chordMpr(data);
mpr.addValuesToMap('Source')
.setFilter(function(row, a, b) {
return (row.Source === a.name && row.Destination === b.name)
})
.setAccessor(function(recs, a, b) {
if (!recs[0]) return 0;
return +recs[0].Count;
});
matrix = mpr.getMatrix();
mmap = mpr.getMap();
rdr = chordRdr(matrix, mmap);
drawChords();
});
//*******************************************************************
// DRAW THE CHORD DIAGRAM
//*******************************************************************
function drawChords() {
var w = window.innerWidth || document.body.clientWidth,
h = 700,
r1 = h / 2,
r0 = r1 - 150;
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("id", "circle")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var chord = d3.layout.chord()
.padding(.15)
.sortChords(d3.descending);
chord.matrix(matrix);
var arc = d3.svg.arc()
.innerRadius(r0*1.03)
.outerRadius(r0*1.03 + 20);
var path = d3.svg.chord()
.radius(r0);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter()
.append("g")
.attr("class", "group");
var paths = g.append("svg:path")
.style("stroke", function(d) { return fillcolor(rdr(d).gname); })
.style("fill", function(d) { return fillcolor(rdr(d).gname); })
.attr("d", arc)
.attr("class", "arcs");
var chordPaths = svg.selectAll("path.chord")
.data(chord.chords())
.enter().append("svg:path")
.attr("class", "chord")
.on("mouseover", function(d) {
//context = d3.select('canvas').node().getContext('2d');
//context.clearRect(0, 0, 1000, 1000);
//context.fillStyle = 'gray';
//context.lineWidth = '1px';
currentTime = 500;
current = currentTime * 0.15 * (0.5 + (Math.random()));
currentPos = this.getPointAtLength(current);
//context.beginPath();
//context.fillStyle = "black";
/*context.arc(
Math.abs(currentPos.x),
Math.abs(currentPos.y),
2,
0,
2 * Math.PI
);
context.fill();*/
currentpath = this;
svg.insert("circle")
.attr("cx",currentPos.x)
.attr("cy",currentPos.y)
.attr("r",2)
.style("stroke-opacity", 1)
.style("fill", this.style.fill)
.transition()
.duration(1000)
.ease(Math.sqrt)
.attr("cx",function(){
currentPos = currentpath.getPointAtLength(current+100);
return currentPos.x;
})
.attr("cy",function(){
currentPos = currentpath.getPointAtLength(current-100);
return currentPos.y;
})
.remove();
})
.style("fill", function(d) { return fillcolor(rdr(d.target).gname); })
.attr("d", path);
}
function fillcolor(segmentvalue){
if (segmentvalue.includes("Segment A")) {
return '#ff3a21'
} else if (segmentvalue.includes("Segment C")) {
return '#26bde2'
} else if (segmentvalue.includes("Segment D")) {
return '#fcc30b'
} else if (segmentvalue.includes("Segment B")) {
return '#dd1367'
} else if (segmentvalue.includes("Segment E")) {
return '#a1e972'
} else {
return '#72e8a4'
}
}
这是HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#circle circle {
fill: none;
pointer-events: all;
}
path.chord {
fill-opacity: .6;
stroke: #000;
stroke-width: .25px;
}
</style>
</head>
<body>
<script src="d3/d3.js"></script>
<script src="d3/underscore.js"></script>
<script type="text/javascript" src="d3/gistfile1.js"></script>
<script type="text/javascript" src="js/chord.js"></script>
</body>
</html>
这是我的数据文件 (out.csv):
Source,Destination,Count,
Segment A,Segment A,597.7731179,
Segment B,Segment A,428.4797097,
Segment C,Segment A,242.5536698,
Segment D,Segment A,39.18270781,
Segment F,Segment A,373.4118141,
Segment E,Segment A,342.1175938,
Segment B,Segment B,695.841404,
Segment C,Segment B,586.8204889,
Segment D,Segment B,519.0497198,
Segment F,Segment B,142.271554,
Segment E,Segment B,282.7048795,
Segment A,Segment B,552.8162888,
Segment C,Segment C,162.7852664,
Segment D,Segment C,150.6887517,
Segment F,Segment C,631.6468679,
Segment E,Segment C,611.0627425,
Segment A,Segment C,344.1286204,
Segment B,Segment C,395.710855,
Segment D,Segment D,141.5878005,
Segment F,Segment D,254.2566994,
Segment E,Segment D,483.4672747,
Segment A,Segment D,5.942896921,
Segment B,Segment D,185.6991357,
Segment C,Segment D,138.2424522,
我已经实施了一个足够接近的解决方案并将其托管在:
https://jsfiddle.net/Edwig_Noronha/fb9j5v4t/
//*******************************************************************
// CREATE MATRIX AND MAP
//*******************************************************************
var matrix, mmap, rdr;
d3.csv('data/out.csv', function(error, data) {
var mpr = chordMpr(data);
mpr
.addValuesToMap('Source')
.setFilter(function(row, a, b) {
return (row.Source === a.name && row.Destination === b.name)
})
.setAccessor(function(recs, a, b) {
if (!recs[0]) return 0;
return +recs[0].Count;
});
matrix = mpr.getMatrix();
mmap = mpr.getMap();
rdr = chordRdr(matrix, mmap);
drawChords();
});
//*******************************************************************
// DRAW THE CHORD DIAGRAM
//*******************************************************************
function drawChords() {
var w = window.innerWidth || document.body.clientWidth,
h = 700,
r1 = h / 2,
r0 = r1 - 150;
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("id", "circle")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var chord = d3.layout.chord()
.padding(.15)
.sortChords(d3.descending);
chord.matrix(matrix);
var arc = d3.svg.arc()
.innerRadius(r0*1.03)
.outerRadius(r0*1.03 + 20);
var path = d3.svg.chord()
.radius(r0);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter()
.append("g")
.attr("class", "group");
var paths = g.append("svg:path")
.style("stroke", function(d) { return fillcolor(rdr(d).gname); })
.style("fill", function(d) { return fillcolor(rdr(d).gname); })
.attr("d", arc)
.attr("class", "arcs");
var chordPaths = svg.selectAll("path.chord")
.data(chord.chords(),function(d,i){return i;})
.enter().append("svg:path")
.attr("class", "chord")
.attr("id", function(d,i){return "chord"+i})
.on("mouseover", function(d,i) {
this.classList.add("hovered");
currentpath = this;
startPoint = pathStartPoint(currentpath);
function loop(thispath) {
if (!thispath.classList.contains("hovered")) return;
setTimeout(function () {
particle = svg.insert("circle")
.attr("class",function(){return currentpath.getAttribute("id")+"-circle"})
.attr("r",2)
.style("stroke-opacity", 1)
.style("fill", currentpath.style.fill)
.attr("transform", "translate(" + startPoint[0] + "," + startPoint[1] + ")")
.transition()
.duration(2000)
.attrTween("transform", translateAlong(currentpath))
.remove();
loop(thispath);
}, 300);
}
loop(this);
})
.on("mouseout",function(d,i){
this.classList.remove("hovered");
})
.style("fill", function(d) { return fillcolor(rdr(d.target).gname); })
.attr("d", path);
}
//Get path start point for placing marker
function pathStartPoint(path) {
var d = path.getAttribute("d");
var dsplitted = d.split(" ");
return dsplitted[1].split(",");
};
function translateAlong(path) {
var l = path.getTotalLength();
var t0 = 0;
return function(i) {
return function(t) {
var p0 = path.getPointAtLength(t0 * l);//previous point
var p = path.getPointAtLength(t * l);////current point
t0 = t;
var centerX = p.x,
centerY = p.y;
return "translate(" + centerX + "," + centerY + ")"//rotate(" + angle + " 24" + " 12" +")";
}
}
}
function fillcolor(segmentvalue){
if (segmentvalue.includes("Segment A")) {
return '#ff3a21'
} else if (segmentvalue.includes("Segment C")) {
return '#26bde2'
} else if (segmentvalue.includes("Segment D")) {
return '#fcc30b'
} else if (segmentvalue.includes("Segment B")) {
return '#dd1367'
} else if (segmentvalue.includes("Segment E")) {
return '#a1e972'
} else {
return '#72e8a4'
}
}
chord.js 文件中的上述更改实现了足够接近的过渡。
工作代码托管于:
https://jsfiddle.net/Edwig_Noronha/fb9j5v4t/