【问题标题】:D3-Chord: Flowing circle elements on chord-path mouseoverD3-Chord:和弦路径鼠标悬停上的流动圆形元素
【发布时间】:2018-11-13 10:22:55
【问题描述】:

我正在尝试在 D3 和弦图中的和弦路径的鼠标悬停事件上使用 svg 圆形元素实现动画效果。
本次试验的灵感来自以下 sankey 图的实现:
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/

【问题讨论】:

  • 你看过和弦的路径吗?路径描述了什么?您需要计算和弦的其他内容才能让粒子移动。创建一些可运行的 sn-p 而不是一堆代码复制粘贴。
  • @rioV8 当提示要计算和弦的其他内容时,实现可运行的 sn-p 确实很困难。我试图弄清楚那会是什么。我能够实现以下足够接近但不完全符合我的意图的内容。
  • 我所说的“可运行的 sn-p”是 YOUR 当前代码的一个版本,它可以运行并显示一些输出。不要期望查看者会构建代码段的可运行版本,以便能够看到代码运行或调试代码。如果输入不是那么大,您可以对输入进行硬编码
  • @rioV8 我完全误解了这一点。我尝试了 jsfiddle,但无法弄清楚外部依赖项。

标签: javascript d3.js chord-diagram


【解决方案1】:
//*******************************************************************
//  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/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-14
    • 2013-11-28
    • 2021-12-20
    • 1970-01-01
    • 1970-01-01
    • 2015-04-22
    • 1970-01-01
    相关资源
    最近更新 更多