D3-Chord: элементы плавного круга при наведении курсора - PullRequest
0 голосов
/ 13 ноября 2018

Я пытаюсь реализовать эффект анимации, используя элементы круга svg для события наведения курсора на пути аккордов на диаграмме аккордов D3.
Вдохновением для этого испытания является следующая реализация диаграммы Санки:
https://bl.ocks.org/micahstubbs/ed0ae1c70256849dab3e35a0241389c9

Мне удалось вставить элементы круга в событие наведения мыши для аккордов.Однако мне трудно понять, как заставить их следовать по пути аккорда.(Строка 69-106 в следующем коде JS).

Мой код 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/

1 Ответ

0 голосов
/ 07 декабря 2018
//*******************************************************************
//  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/

...