D3 Mouse Over Groups Issue - PullRequest
       32

D3 Mouse Over Groups Issue

0 голосов
/ 29 мая 2018

Я пытаюсь навести указатель мыши на каждую группу стран, в которой отображаются соответствующие аккорды.Я создал функцию непрозрачности в нижней части моего кода для этого, но она, похоже, не регистрирует группы или функции наведения мыши по какой-то причине.

            <!DOCTYPE html>
            <meta charset="utf-8">
            <head>
            <style>
            body {
              font: 10px sans-serif;
              background-color: #F0F0F0;

            }

            h1 {
              font-size: 400%;
              position: relative;
              font-family: Helvetica;
            }

            h3 {
              font-family: Helvetica;
              position: relative;
            }

            p {
              position: relative;
              font-style: italic;
              font-family: Helvetica;
              top:1000px;
              left: 100px;
            }


            @media only screen and (max-width:1000px) {
              /* For tablets: */
              .main {
                width: 80%;
                padding: 0;
              }
              .right {
                width: 100%;
              }
            }

            button {
                background-color: #4CA350;
                position: relative;
                border: none;
                color: white;
                padding: 15px 32px;
                text-align: center;
                text-decoration: none;
                display: inline-block;
                font-size: 16px;
                margin: 4px 2px;
                cursor: pointer;
            }

            .group-tick line {
              stroke: #000;
            }

            .ribbons {
              fill-opacity: 1;

            }

            #tooltip.total {
                position: relative;
                width: 200px;
                height: auto;
                padding: 10px;
                background-color: white;
                -webkit-border-radius: 10px;
                -moz-border-radius: 10px;
                border-radius: 10px;
                -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                pointer-events: none;
            }

            #tooltip {
                position: relative;
                font-family: Helvetica Neue;
                font-size: 16px;
                top:870px;
                left:720px;
                width: 200px;
                height: auto;
                padding: 10px;
                background-color: white;
                -webkit-border-radius: 10px;
                -moz-border-radius: 10px;
                border-radius: 10px;
                -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
                pointer-events: none;
            }

            #tooltip.hidden {
                display: none;
            }

            #tooltip p {
                margin: 0;
                font-family: sans-serif;
                font-size: 40px;
                line-height: 20px;
            }
            </style>
            <title>Julia Spyrou DECO3100 Assignment 3</title>
            <body>
            <h1>Voting at the Eurovision Song Contest</h1>
            <h3>Mouseover individual chords to view voting relationships</h3>
              <p>NB: These visualisations exclude the countries that did not make the Final and their votes as well</p>
              <button type="button">2016</button>
              <button type="button">2017</button>
              <button type="button">2018</button>
              <div id="tooltip" class="hidden">
                <p>$<span id="value">100</span></p>
              </div>

            <svg width="1000" height="1000"></svg>
            <script src="https://d3js.org/d3.v4.min.js"></script>
            <script>



            var names = ["Ukraine","Spain","Slovenia","Lithuania","Austria","Estonia","Norway","Portugal","UK","Serbia","Germany","Albania","France","Czech Republic","Denmark","Australia","Finland","Bulgaria","Moldova","Sweden","Hungary","Israel","Netherlands","Ireland","Cyprus","Italy"];

            var opacityDefault = 0.7;


            var matrix = [
              [0.1,0,0,0,0,0,0,4,0,0,0,0,4,12,0,0,0,1,15,0,0,7,0,0,2,8], //Ukraine
              [0,0,0,0,0,0,2,14,1,0,6,0,5,0,6,7,0,0,0,0,0,0,0,1,7,0], //Spain
              [5,0,0,0,0,0,0,3,0,8,0,0,2,7,0,0,0,0,0,0,0,4,1,0,0,0], //Slovenia
              [2,0,0,0,0,22,15,6,12,0,8,0,0,1,0,3,0,10,0,7,5,0,7,12,6,0],  //Lithuania
              [7,8,12,15,0,18,16,8,12,4,15,2,7,5,18,5,10,16,3,12,11,13,13,5,2,7], //Austria
              [4,3,5,12,2,0,0,19,6,3,2,4,7,5,7,8,12,2,13,0,0,8,4,7,5,10], //Estonia
              [0,3,5,0,0,6,0,0,5,9,0,0,0,0,8,1,0,0,7,10,7,0,4,0,5,12], //Norway
              [0,0,0,7,0,3,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,2,6,0,0], //Portugal
              [0,0,0,0,0,0,0,0,0,0,1,3,3,0,3,6,0,0,0,0,0,8,0,10,0,6], //UK
              [0,0,0,0,8,0,0,0,0,0,0,3,1,0,0,0,0,7,0,0,0,0,0,0,0,1], //Serbia
              [0,13,4,7,16,6,18,8,3,10,0,14,8,3,24,12,1,6,8,6,1,8,24,16,3,13], //Germany
              [0,0,5,0,9,0,0,10,7,1,0,0,0,6,0,0,0,7,0,0,10,0,0,2,6,12], //Albania
              [19,10,2,15,0,0,8,5,0,0,0,12,0,0,2,0,9,5,0,5,0,6,0,4,4,0], //France
              [14,14,11,8,15,5,4,0,5,5,8,1,4,0,6,4,5,11,6,3,8,12,6,7,13,0], //Czech Republic
              [11,0,7,10,5,8,10,2,2,4,3,0,2,7,0,10,13,0,0,12,24,0,8,2,0,12], //Denmark
              [2,0,0,0,0,0,6,0,1,0,7,0,10,0,12,0,0,0,7,8,7,7,0,0,0,0], //Australia
              [0,0,0,0,0,12,0,0,4,0,0,0,0,3,0,4,0,0,0,9,0,6,0,0,0,0], //Finland
              [0,5,0,0,6,7,0,7,14,2,0,14,0,5,1,0,10,0,11,6,0,1,0,10,12,0], //Bulgaria
              [6,0,6,0,0,1,0,6,4,6,0,2,6,6,0,7,0,8,0,0,2,10,0,1,10,10], //Moldova
              [6,2,12,12,8,5,13,0,2,12,12,4,5,8,11,12,8,2,0,0,1,10,8,0,12,1], //Sweden
              [1,0,3,2,3,3,0,0,0,12,2,0,0,2,0,0,8,5,2,0,0,3,2,0,4,0], //Hungary
              [22,22,1,7,19,0,7,2,17,9,11,6,24,22,3,18,19,14,22,17,16,0,15,13,10,9], //Israel
              [8,0,7,3,1,1,9,0,0,7,5,0,6,0,5,0,2,0,0,1,8,0,0,3,0,0], //Netherlands
              [0,6,0,4,8,0,1,3,13,0,15,7,1,14,4,12,2,1,0,4,3,0,4,0,0,5], //Ireland
              [4,20,14,7,1,12,7,5,8,10,9,20,3,8,6,7,7,15,13,16,7,2,11,17,0,8], //Cyprus
              [5,10,10,7,10,7,0,14,0,14,12,24,10,2,0,0,10,6,8,0,6,5,7,0,15,0], //Italy

            ];

            var svg = d3.select("svg"),
                width = +svg.attr("width"),
                height = +svg.attr("height"),
                outerRadius = Math.min(width, height) * 0.4 - 100,
                innerRadius = outerRadius - 20;

            var formatValue = d3.formatPrefix(",.0", 1e3);

            var chord = d3.chord()
                .padAngle(0.05)
                .sortSubgroups(d3.descending);

            var arc = d3.arc()
                .innerRadius(innerRadius)
                .outerRadius(outerRadius);

            var ribbon = d3.ribbon()
                .radius(innerRadius);

            var color = d3.scaleOrdinal()
                .domain(d3.range(4))
                .range(["#4B5320", "#50C878", "#98FB98", "#679267","#2E8B57","#043927", "#0B6623","#9DC183","#708238", "#C7EA46", "#3F704D","#00A86B","8F9779"]);

            var g = svg.append("g")
                .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
                .datum(chord(matrix));

            var group = g.append("g")
                .attr("class", "groups")
              .selectAll("g")
              .data(function(chords) { return chords.groups; })
              .enter().append("g");


            group.append("path")
                .style("fill", function(d) { return color(d.index); })
                .style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
                .attr("d", arc)
                .on("mouseover", fade(5))
                .on("mouseout", fade(5));

            function fade(opacity) {
              return function(d, i) {
              svg.selectAll(".ribbons path")
                  .filter(function(d) { return d.source.index != i && d.target.index != i; })
                  .transition()
                 };
            }

            group.append("text")
              .each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
              .attr("dy", ".35em")
              .attr("class", "titles")
              .attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
              .attr("transform", function(d) {
                if (outerArcs = 0) {
                  width = 10;
                }
                return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
                + "translate(" + (outerRadius + 10) + ")"
                + (d.angle > Math.PI ? "rotate(180)" : "");
              })
              .text(function(d,i) { return names[i]; });

            g.append("g")
                .attr("class", "ribbons")
              .selectAll("path")
              .data(function(chords) { return chords; })
              .enter().append("path")
                .attr("d", ribbon)
                .style("fill", function(d) { return color(d.target.index); })
                .style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); });

            // Returns an array of tick angles and values for a given group and step.
            function groupTicks(d, i) {
              var k = (d.endAngle - d.startAngle) / d.value;
              return d3.range(0, d.value,0.2).map(function(v, i) {
                return {
                  angle: v * k + d.startAngle,
                  label: i*100 % 5 ? null : v,
                  id: i
                };
              });
            }

            svg.selectAll("path")
                .on("mouseover", function(d) { //event when we mouseover
                  d3.select(this) //this gives us acess to the particular rectange in the selection
                    .transition()
                    .duration(100)
                    .attr("fill", "rgb(170, 255, 44)"); //hover colour
                    var h = 20;
                  //get tooltip location
                  var xPosition = parseFloat(d3.select(this).attr("x"));
                  var yPosition = parseFloat(d3.select(this).attr("y")) / 100 + h / 7;

            svg.selectAll("path")
                .transition()
                .style("opacity", 0.1);
              d3.select(this)
                .transition()
                    .style("opacity", 1);
                  //update tooltip location and value
                  console.log(d.source.index);
                  d3.select("#tooltip")
                    .style("left", xPosition + "100px")
                    .style("top", yPosition + "3000px")
                    //.select("#value")
                    .text(names[d.source.index] + " received " + d.source.value + " points from " + names[d.target.index]);

                  //use hidden css to hide and show the tooltip
                d3.select("#tooltip").classed("hidden", false);
                })
                .on("mouseout", function(d) { //reset fill on mouse out
                d3.select(this)
                  .transition()
                  .duration(250)
                  .attr("fill", "rgb(0," + Math.round(d+60) + ",0)");
                //hide tooltip
                d3.select("#tooltip").classed("hidden", true);
                });

             function fade(opacity) {
               return function(g, i) {
                 svg.selectAll("g.chord ribbons")
                     .filter(function(d) {
                       return d.source.index != i && d.target.index != i;
                     })
                   .transition()
                     .style("opacity", opacity);

               };
             }
            </script>

Если у кого-то есть рабочее решение для этого, которое было бы здорово, ура :))

1 Ответ

0 голосов
/ 30 мая 2018

Для начала я собираюсь упомянуть, почему так важно сделать правильный выбор.Например, здесь:

 group.append("path")
      ...
      .on("mouseover", fade(5))
      .on("mouseout", fade(5));

Вы применяете прослушиватели для событий мыши, но ниже вас (и вы, возможно, пытаетесь захватить ленты, но я не уверен), вы перезаписываете оба этихСлушатели с:

   svg.selectAll("path")
      .on("mouseover", function(d) { ...

И с помощью более сложного метода (который не следует обычному шаблону) вы также перезаписываете слушателя на mouseout.Прослушиватель событий может быть назначен только один раз для определенного элемента.И поскольку нам нужны разные слушатели как для групп (внешних дуг), так и для лент, нам необходимо изменить этот подход.

Хорошая новость заключается в том, что это может привести к гораздо более короткому и четкому коду.Чтобы повторно использовать ваш выбор ленты, я создал переменную для хранения ее с именем ribbons, переменная group продолжит хранить внешние дуги.

Я собираюсь разбить три основных событияЯ вижу, что вы пытаетесь сделать:

  1. Ничего не выделяя

Это должно быть довольно просто, мы можем использовать такую ​​функцию, как:

function showAllRibbons() {
    ribbons.style("opacity",1)
}
Подсветка одиночного пути на событии мыши:

Это также довольно просто, мы скрываем все остальное и показываем элемент, на который this указывает:

function highlightOneRibbon() {
    ribbons.style("opacity",0.1);
    d3.select(this).style("opacity",1);
}
Выделите все ленты, которые имеют источник или цель в определенной внешней дуге.

Это самая сложная задача, она помогает увидеть структуру данных каждой ленты, которая имеет свойства для идентификатора двух якорей, расположенных по адресу: d.source.index и d.target.index.Теперь, используя индекс каждой внешней дуги в группе выбора, мы можем сделать простой фильтр для тех лент, которые соответствуют базовым критериям:

function highlightRibbons(d,i) {
    ribbons.style("opacity",0.1); // set all relatively transparent
    // fix the ones that need to be shown:
    ribbons.filter(function(r) {
        if(r.target.index == i || r.source.index == i) return r;
      })
      .style("opacity",1);

Конечно, вы можете получить немного более изящные, фильтровать те, которые нене соответствует критериям для их постепенного исчезновения, отфильтруйте те, которые используются для постепенного появления.

Эта функция является еще одним доказательством того, что вам нужны разные прослушиватели событий для ленты и дуги, так как эта функция не будетработать с d3.selectAll("path").on("mouseouver,highlightRibbons), потому что наши индексы могут быть неправильными (так как мы выбираем ленты тоже, но эта функция также не имеет смысла для лент).


Хорошо, так.Давайте удалим часть вашего кода, который использует d3.selectAll («путь») для манипулирования уже существующими элементами (около строки 247?), Так как это вызывает у нас некоторое горе, пока мы находимся, как слушатели событий, которые вызываютфункция fade перезаписана, мы ее не используем, давайте также отбросим эту функцию.

В результате я теряю интерактивность всплывающей подсказки, но у этого, похоже, были некоторые проблемы, не зависящие от вопроса (но, опять же, отдельные слушатели, вероятно, требуются для группы и ленты, так что подсказка имеет соответствующую информациюоснованный на типе функции, на которую он ссылается).

Вот небольшая демонстрация результата с небольшим добавлением таланта для отображения группы (переходы) (потому что фрагменты не любят больше кода, кажется, яобрезаны стили подсказок, кнопки и т. д., так как они в любом случае не работают или не используются в этой демонстрации):

var names = ["Ukraine","Spain","Slovenia","Lithuania","Austria","Estonia","Norway","Portugal","UK","Serbia","Germany","Albania","France","Czech Republic","Denmark","Australia","Finland","Bulgaria","Moldova","Sweden","Hungary","Israel","Netherlands","Ireland","Cyprus","Italy"];

            var opacityDefault = 0.7;

            var matrix = [
              [0.1,0,0,0,0,0,0,4,0,0,0,0,4,12,0,0,0,1,15,0,0,7,0,0,2,8], //Ukraine
              [0,0,0,0,0,0,2,14,1,0,6,0,5,0,6,7,0,0,0,0,0,0,0,1,7,0], //Spain
              [5,0,0,0,0,0,0,3,0,8,0,0,2,7,0,0,0,0,0,0,0,4,1,0,0,0], //Slovenia
              [2,0,0,0,0,22,15,6,12,0,8,0,0,1,0,3,0,10,0,7,5,0,7,12,6,0],  //Lithuania
              [7,8,12,15,0,18,16,8,12,4,15,2,7,5,18,5,10,16,3,12,11,13,13,5,2,7], //Austria
              [4,3,5,12,2,0,0,19,6,3,2,4,7,5,7,8,12,2,13,0,0,8,4,7,5,10], //Estonia
              [0,3,5,0,0,6,0,0,5,9,0,0,0,0,8,1,0,0,7,10,7,0,4,0,5,12], //Norway
              [0,0,0,7,0,3,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,2,6,0,0], //Portugal
              [0,0,0,0,0,0,0,0,0,0,1,3,3,0,3,6,0,0,0,0,0,8,0,10,0,6], //UK
              [0,0,0,0,8,0,0,0,0,0,0,3,1,0,0,0,0,7,0,0,0,0,0,0,0,1], //Serbia
              [0,13,4,7,16,6,18,8,3,10,0,14,8,3,24,12,1,6,8,6,1,8,24,16,3,13], //Germany
              [0,0,5,0,9,0,0,10,7,1,0,0,0,6,0,0,0,7,0,0,10,0,0,2,6,12], //Albania
              [19,10,2,15,0,0,8,5,0,0,0,12,0,0,2,0,9,5,0,5,0,6,0,4,4,0], //France
              [14,14,11,8,15,5,4,0,5,5,8,1,4,0,6,4,5,11,6,3,8,12,6,7,13,0], //Czech Republic
              [11,0,7,10,5,8,10,2,2,4,3,0,2,7,0,10,13,0,0,12,24,0,8,2,0,12], //Denmark
              [2,0,0,0,0,0,6,0,1,0,7,0,10,0,12,0,0,0,7,8,7,7,0,0,0,0], //Australia
              [0,0,0,0,0,12,0,0,4,0,0,0,0,3,0,4,0,0,0,9,0,6,0,0,0,0], //Finland
              [0,5,0,0,6,7,0,7,14,2,0,14,0,5,1,0,10,0,11,6,0,1,0,10,12,0], //Bulgaria
              [6,0,6,0,0,1,0,6,4,6,0,2,6,6,0,7,0,8,0,0,2,10,0,1,10,10], //Moldova
              [6,2,12,12,8,5,13,0,2,12,12,4,5,8,11,12,8,2,0,0,1,10,8,0,12,1], //Sweden
              [1,0,3,2,3,3,0,0,0,12,2,0,0,2,0,0,8,5,2,0,0,3,2,0,4,0], //Hungary
              [22,22,1,7,19,0,7,2,17,9,11,6,24,22,3,18,19,14,22,17,16,0,15,13,10,9], //Israel
              [8,0,7,3,1,1,9,0,0,7,5,0,6,0,5,0,2,0,0,1,8,0,0,3,0,0], //Netherlands
              [0,6,0,4,8,0,1,3,13,0,15,7,1,14,4,12,2,1,0,4,3,0,4,0,0,5], //Ireland
              [4,20,14,7,1,12,7,5,8,10,9,20,3,8,6,7,7,15,13,16,7,2,11,17,0,8], //Cyprus
              [5,10,10,7,10,7,0,14,0,14,12,24,10,2,0,0,10,6,8,0,6,5,7,0,15,0], //Italy

            ];

            var svg = d3.select("svg"),
                width = +svg.attr("width"),
                height = +svg.attr("height"),
                outerRadius = Math.min(width, height) * 0.4 - 100,
                innerRadius = outerRadius - 20;

            var formatValue = d3.formatPrefix(",.0", 1e3);

            var chord = d3.chord()
                .padAngle(0.05)
                .sortSubgroups(d3.descending);

            var arc = d3.arc()
                .innerRadius(innerRadius)
                .outerRadius(outerRadius);

            var ribbon = d3.ribbon()
                .radius(innerRadius);

            var color = d3.scaleOrdinal()
                .domain(d3.range(4))
                .range(["#4B5320", "#50C878", "#98FB98", "#679267","#2E8B57","#043927", "#0B6623","#9DC183","#708238", "#C7EA46", "#3F704D","#00A86B","8F9779"]);

            var g = svg.append("g")
                .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
                .datum(chord(matrix));
				
			// Append the groups:
            var group = g.append("g")
                .attr("class", "groups")
              .selectAll("g")
              .data(function(chords) { return chords.groups; })
              .enter().append("g");

            group.append("path")
                .style("fill", function(d) { return color(d.index); })
                .style("stroke", function(d) { return d3.rgb(color(d.index)).darker(); })
                .attr("d", arc)
                .on("mouseover", highlightRibbons)
                .on("mouseout", showAllRibbons)

			group.append("text")
              .each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
              .attr("dy", ".35em")
              .attr("class", "titles")
              .attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
              .attr("transform", function(d) {
                if (outerArcs = 0) {
                  width = 10;
                }
                return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
                + "translate(" + (outerRadius + 10) + ")"
                + (d.angle > Math.PI ? "rotate(180)" : "");
              })
              .text(function(d,i) { return names[i]; });	

			// Append the ribbons
            var ribbons = g.append("g")              // save the selection as a variable
                .attr("class", "ribbons")
              .selectAll("path")
              .data(function(chords) { return chords; })
              .enter().append("path")
                .attr("d", ribbon)
                .style("fill", function(d) { return color(d.target.index); })
				.attr("class","ribbon")
                .style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); })
				.on("mouseover",highlightOneRibbon)
				.on("mouseout",showAllRibbons);
				
	
			// New Functions:
			// Highlight
			function highlightRibbons(d,i) {
				ribbons.filter(function(r) {
					    if(!(r.target.index == i || r.source.index == i)) return r;
					  })
					  .transition()
					  .style("opacity",0.1)
					  .duration(500);	
     				ribbons.filter(function(r) {
						if(r.target.index == i || r.source.index == i) return r;
					  })
					  .transition()
					  .style("opacity",1)
					  .duration(500);
			}
						
			// Unhighlight
			function showAllRibbons() {
				ribbons
				   //.transition()      // don't use for individual ribbons - transition is too long
				  .style("opacity",1)
				  //.duration(500);     // any visually effective transition will be too long given how quickly the mouse can cross multiple paths
			}
			
			// show a particular ribbon:
			function highlightOneRibbon() {
				ribbons.style("opacity",0.1);
				d3.select(this).style("opacity",1);
			}
			
            // Returns an array of tick angles and values for a given group and step.
            function groupTicks(d, i) {
              var k = (d.endAngle - d.startAngle) / d.value;
              return d3.range(0, d.value,0.2).map(function(v, i) {
                return {
                  angle: v * k + d.startAngle,
                  label: i*100 % 5 ? null : v,
                  id: i
                };
              });
            }
.group-tick line {
              stroke: #000;
            }

            .ribbons {
              fill-opacity: 1;

            }
<svg width="1000" height="1000"></svg>
            <script src="https://d3js.org/d3.v4.min.js"></script>
...