Search code examples
javascriptsvgd3.jstooltiparc-diagram

D3 Arc Diagram: Tooltip on Circle Mouseover Not Displaying


I'm stuck on a problem and just looking for another pair of eyes to maybe spot what I'm missing.

I have an arc diagram I'm building and would like a tooltip event when I mouse over the circles/nodes.

You can see the sample bl.ock here. I thought for this to work each circle needed to be in it's own g element, which I've done. I have a function that handles the creation of the tooltip:

function tooltipText(d) {
 return "<h5>Information for " + d.token + "</h5>" +
   "<table>" +
   "<tr>" +
   "<td class='field'>Token: </td>" +
   "<td>" + d.token + "</td>" +
   "</tr>" +
   "<tr>" +
   "<td class='field'>Dialect: </td>" +
   "<td>" + d.dialect + "</td>" +
   "</tr>" +
   "<tr>" +
   "<td class='field'>IME: </td>" +
   "<td>" + d.input_method + "</td>" +
   "</tr>" +
   "<tr>" +
   "<td class='field'>Operating System: </td>" +
   "<td>" + d.operating_system + "</td>" +
   "</tr>" +
   "<tr>" +
   "<td class='field'>Trial: </td>" +
   "<td>" + d.trial + "</td>" +
   "</tr>" +
   "</table>";
}

Using CSS, the tooltip remains hidden until the mouseover is called:

.hidden {
  display: none;
  visibility: hidden;
  pointer-events: none;
}

.tooltip {
  color: #222;
  background: #fff;
  padding: .5em;
  text-shadow: #f5f5f5 0 1px 0;
  border-radius: 10px;
  border-color: #a6a6a6;
  border-width: 1px;
  border-style: solid;
  box-shadow: 0px 0px 2px 0px #a6a6a6; 
  opacity: 0.9; 
  position: absolute;
  width: 225px;
  display: block;
}

.tooltip h5 {
  font-size: 1.05rem;
}

.tooltip p {
  font-size: 0.80rem;
}

And the mouseover itself:

// Tooltip
var tooltip = d3.select("body").append("div")
  .classed("tooltip", true)
  .classed("hidden", true);

...

nodeEnter.selectAll(".node")
    .on("mousemove", function(d, i) {
      var mouse = d3.mouse(d3.select("body").node());
      tooltip
        .classed("hidden", false)
        .attr("class", "tooltip")
        .attr("style", "left:" + (mouse[0] + 20) + "px; top:" + (mouse[1] - 50) + "px")
        .html(tooltipText(d)); 
    })
    .on("mouseover", nodeOver);

Any pointers would be much appreciated.


Solution

  • There is a problem related to the usage of queue(), at the very beginning of JavaScript code.

    I would recommend using standard D3 functions for loading data, like d3.json().

    I made a code sample based on your block. the only difference from your example is that data is inside JavaScript function. The tooltips work!

    var width = 960,
      height = 400,
      margin = 20,
      pad = margin / 2,
      padding = 100,
      radius = 6,
      yfixed = pad + radius;
    
    // Legend variables
    var legend_x = 0,
      legend_y = 5,
      legend_width = 175,
      legend_height = 620,
      legend_margin = 20
    key_y = 40,
      key_x = 16,
      mapped_y = legend_y + legend_height - 90;
    
    var color = d3.scale.category20();
    
    // Tooltip
    var tooltip = d3.select("body").append("div")
      .classed("tooltip", true)
      .classed("hidden", true);
    
    var graph = getData();
    arcDiagram(graph);
    
    
    // Main
    //-----------------------------------------------------
    function arcDiagram(graph) {
      var radius = d3.scale.sqrt()
        .domain([0, 20])
        .range([0, 15]);
    
      var svg = d3.select("#chart").append("svg")
        .attr("id", "arc")
        .attr("width", width)
        .attr("height", height);
    
      // create plot within svg
      var plot = svg.append("g")
        .attr("id", "plot")
        .attr("transform", "translate(" + padding + ", " + padding + ")");
    
      // count the paths
      graph.links.forEach(function(d, i) {
        var pathCount = 0;
        for (var j = 0; j < i; j++) {
          var otherPath = graph.links[j];
          if (otherPath.source === d.source && otherPath.target === d.target) {
            pathCount++;
          }
        }
        // console.log(pathCount)
        d.pathCount = pathCount;
      });
    
      // fix graph links to map to objects
      graph.links.forEach(function(d, i) {
        d.source = isNaN(d.source) ? d.source : graph.nodes[d.source];
        d.target = isNaN(d.target) ? d.target : graph.nodes[d.target];
      });
    
      linearLayout(graph.nodes);
      drawLinks(graph.links);
      drawNodes(graph.nodes);
    }
    
    // layout nodes linearly
    function linearLayout(nodes) {
      nodes.sort(function(a, b) {
        return a.uniq - b.uniq;
      });
    
      var xscale = d3.scale.linear()
        .domain([0, nodes.length - 1])
        .range([radius, width - margin - radius]);
    
      nodes.forEach(function(d, i) {
        d.x = xscale(i);
        d.y = yfixed;
      });
    }
    
    function drawNodes(nodes) {
    
      var gnodes = d3.select("#plot").selectAll("g.node")
        .data(nodes);
    
      var nodeEnter = gnodes.enter()
        .append('g')
        .attr("class", "gnode");
    
      nodeEnter.append("circle")
        .attr("class", "node")
        .attr("id", function(d, i) {
          return d.name;
        })
        .attr("cx", function(d, i) {
          return d.x;
        })
        .attr("cy", function(d, i) {
          return d.y;
        })
        .attr("r", 14)
        .style("stroke", function(d, i) {
          return color(d.type);
        });
    
      // Handling mouseover functions
      nodeEnter.selectAll(".node")
        .on("mousemove", function(d, i) {
          var mouse = d3.mouse(d3.select("body").node());
          tooltip
            .classed("hidden", false)
            .attr("class", "tooltip")
            .attr("style", "left:" + (mouse[0] + 20) + "px; top:" + (mouse[1] - 50) + "px")
            .html(tooltipText(d));
        })
        .on("mouseover", nodeOver);
    
      nodeEnter.append("text")
        .style("text-anchor", "middle")
        .attr("dx", function(d) {
          return d.x;
        })
        .attr("dy", function(d) {
          return d.y + 5;
        })
        .text(function(d) {
          return d.token;
        });
    
      d3.select("#trial2")
        .on("mouseover", trialOver);
    }
    
    function drawLinks(links) {
      var radians = d3.scale.linear()
        .range([Math.PI / 2, 3 * Math.PI / 2]);
    
      var arc = d3.svg.line.radial()
        .interpolate("basis")
        .tension(0)
        .angle(function(d) {
          return radians(d);
        });
    
      d3.select("#plot").selectAll(".link")
        .data(links)
        .enter().append("path")
        .attr("class", "link")
        .style("stroke-width", function(d) {
          return (2 + d.pathCount);
        })
        .attr("transform", function(d, i) {
          var xshift = d.source.x + (d.target.x - d.source.x) / 2;
          var yshift = yfixed;
          return "translate(" + xshift + ", " + yshift + ")";
        })
        .attr("d", function(d, i) {
          var xdist = Math.abs(d.source.x - d.target.x);
          arc.radius(xdist / 2);
          var points = d3.range(0, Math.ceil(xdist / 3));
          radians.domain([0, points.length - 1]);
          return arc(points);
        })
        .on("mouseover", edgeOver);
    }
    
    // Draw legend
    //-----------------------------------------------------
    function drawLegend(d) {
      var legend = svg.append("g")
        .attr("class", "legend");
      var key = legend.append("g")
    
      // Initial
      key.append("circle")
        .attr("id", "legend_initial")
        .attr("cx", legend_x + key_x)
        .attr("cy", legend_y + key_y + 5)
        .attr("r", 5)
        .style("fill", "blue");
    
      key.append("text")
        .attr("class", "legendText")
        .attr("id", "legend_initial_label")
        .attr("x", legend_x + key_x + 10)
        .attr("y", legend_y + 10 + key_y)
        .text("Initial");
    
      // Selection
      key.append("circle")
        .attr("id", "legend_selection")
        .attr("cx", function() {
          return legend_x + key_x
        })
        .attr("cy", function() {
          return legend_y + legend_margin + key_y + 5
        })
        .attr("r", 5)
        .style("fill", "lightblue");
    
      key.append("text")
        .attr("class", "legendText")
        .attr("id", "legend_selection_label")
        .attr("x", legend_x + key_x + 10)
        .attr("y", legend_y + legend_margin + 10 + key_y)
        .text("Selection");
    
      // Final
      key.append("circle")
        .attr("id", "legend_final")
        .attr("cx", legend_x + key_x)
        .attr("cy", legend_y + 2 * legend_margin + key_y + 5)
        .attr("r", 5)
        .style("fill", "orange");
    
      key.append("text")
        .attr("class", "legendText")
        .attr("id", "legend_final_label")
        .attr("x", legend_x + key_x + 10)
        .attr("y", legend_y + 2 * legend_margin + 10 + key_y)
        .text("Final");
    
      // Delete
      key.append("circle")
        .attr("id", "legend_delete")
        .attr("cx", legend_x + key_x)
        .attr("cy", legend_y + 3 * legend_margin + key_y + 5)
        .attr("r", 5)
        .style("fill", "gold");
    
      key.append("text")
        .attr("class", "legendText")
        .attr("id", "legend_delete_label")
        .attr("x", legend_x + key_x + 10)
        .attr("y", legend_y + 3 * legend_margin + 10 + key_y)
        .text("Delete");
    }
    
    function tooltipText(d) {
      return "<h5>Information for " + d.token + "</h5>" +
        "<table>" +
        "<tr>" +
        "<td class='field'>Token: </td>" +
        "<td>" + d.token + "</td>" +
        "</tr>" +
        "<tr>" +
        "<td class='field'>Dialect: </td>" +
        "<td>" + d.dialect + "</td>" +
        "</tr>" +
        "<tr>" +
        "<td class='field'>IME: </td>" +
        "<td>" + d.input_method + "</td>" +
        "</tr>" +
        "<tr>" +
        "<td class='field'>Operating System: </td>" +
        "<td>" + d.operating_system + "</td>" +
        "</tr>" +
        "<tr>" +
        "<td class='field'>Trial: </td>" +
        "<td>" + d.trial + "</td>" +
        "</tr>" +
        "</table>";
    }
    
    function nodeOver(d, i) {
      d3.selectAll("path").style("stroke", function(p) {
        return p.source == d || p.target == d ? "#17becf" : "#888888"
      })
    }
    
    function edgeOver(d) {
      d3.selectAll("path").style("stroke", function(p) {
        return p == d ? "#17becf" : "#888888"
      })
    }
    
    function trialOver(d) {
      var active,
        changedOpacity;
    
      d3.select("#arcToken"); // function)
    }
    
    function tokenOver(d, i) {
      d3.selectAll(this).style("stroke", function(d) {
        return p.token == d ? "#17becf" : "#888888"
      })
    }
    
    
    function getData() {
      return {
        "nodes": [{
          "token": "x",
          "type": "initial",
          "uniq": "1",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "ia",
          "type": "final",
          "uniq": "2",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "1",
          "type": "selection",
          "uniq": "3",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "m",
          "type": "initial",
          "uniq": "4",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "a",
          "type": "final",
          "uniq": "5",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "1",
          "type": "selection",
          "uniq": "6",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "y",
          "type": "initial",
          "uniq": "7",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "in",
          "type": "final",
          "uniq": "8",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "l",
          "type": "initial",
          "uniq": "9",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "iao",
          "type": "final",
          "uniq": "10",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "_",
          "type": "selection",
          "uniq": "11",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "*",
          "type": "productive-delete",
          "uniq": "12",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "j",
          "type": "initial",
          "uniq": "13",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "un",
          "type": "final",
          "uniq": "14",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "z",
          "type": "initial",
          "uniq": "15",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "1",
          "type": "selection",
          "uniq": "16",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "*",
          "type": "productive-delete",
          "uniq": "17",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "j",
          "type": "initial",
          "uniq": "18",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "iu",
          "type": "final",
          "uniq": "19",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }, {
          "token": "1",
          "type": "selection",
          "uniq": "20",
          "age_group": "18-30",
          "dialect": "cantonese",
          "gender": "female",
          "operating_system": "mac",
          "input_method": "apple",
          "trial": "2",
          "chinese": "",
          "english": ""
        }],
        "links": [{
          "source": 0,
          "target": 1
        }, {
          "source": 0,
          "target": 1
        }, {
          "source": 0,
          "target": 1
        }, {
          "source": 0,
          "target": 1
        }, {
          "source": 0,
          "target": 1
        }, {
          "source": 0,
          "target": 1
        }, {
          "source": 1,
          "target": 3
        }, {
          "source": 1,
          "target": 3
        }, {
          "source": 1,
          "target": 3
        }, {
          "source": 1,
          "target": 3
        }, {
          "source": 1,
          "target": 3
        }, {
          "source": 1,
          "target": 2
        }, {
          "source": 2,
          "target": 3
        }, {
          "source": 3,
          "target": 4
        }, {
          "source": 3,
          "target": 4
        }, {
          "source": 3,
          "target": 4
        }, {
          "source": 3,
          "target": 4
        }, {
          "source": 3,
          "target": 4
        }, {
          "source": 3,
          "target": 4
        }, {
          "source": 4,
          "target": 5
        }, {
          "source": 4,
          "target": 6
        }, {
          "source": 4,
          "target": 6
        }, {
          "source": 4,
          "target": 5
        }, {
          "source": 4,
          "target": 5
        }, {
          "source": 4,
          "target": 5
        }, {
          "source": 5,
          "target": 6
        }, {
          "source": 5,
          "target": 6
        }, {
          "source": 5,
          "target": 6
        }, {
          "source": 6,
          "target": 7
        }, {
          "source": 6,
          "target": 7
        }, {
          "source": 6,
          "target": 7
        }, {
          "source": 6,
          "target": 7
        }, {
          "source": 6,
          "target": 7
        }, {
          "source": 6,
          "target": 7
        }, {
          "source": 7,
          "target": 12
        }, {
          "source": 7,
          "target": 12
        }, {
          "source": 7,
          "target": 12
        }, {
          "source": 7,
          "target": 12
        }, {
          "source": 7,
          "target": 12
        }, {
          "source": 7,
          "target": 8
        }, {
          "source": 8,
          "target": 9
        }, {
          "source": 9,
          "target": 10
        }, {
          "source": 10,
          "target": 11
        }, {
          "source": 11,
          "target": 12
        }, {
          "source": 12,
          "target": 13
        }, {
          "source": 12,
          "target": 13
        }, {
          "source": 12,
          "target": 13
        }, {
          "source": 12,
          "target": 13
        }, {
          "source": 12,
          "target": 13
        }, {
          "source": 12,
          "target": 13
        }, {
          "source": 13,
          "target": 14
        }, {
          "source": 14,
          "target": 15
        }, {
          "source": 15,
          "target": 16
        }, {
          "source": 16,
          "target": 17
        }, {
          "source": 13,
          "target": 17
        }, {
          "source": 13,
          "target": 17
        }, {
          "source": 13,
          "target": 17
        }, {
          "source": 13,
          "target": 17
        }, {
          "source": 13,
          "target": 17
        }, {
          "source": 17,
          "target": 18
        }, {
          "source": 17,
          "target": 18
        }, {
          "source": 17,
          "target": 18
        }, {
          "source": 17,
          "target": 18
        }, {
          "source": 17,
          "target": 18
        }, {
          "source": 17,
          "target": 18
        }, {
          "source": 18,
          "target": 19
        }, {
          "source": 18,
          "target": 19
        }, {
          "source": 18,
          "target": 19
        }, {
          "source": 18,
          "target": 19
        }, {
          "source": 18,
          "target": 19
        }]
      };
    
    }
    @import url(http://fonts.googleapis.com/css?family=Lato:300,400);
     body {
      font-family: 'Lato', sans-serif;
      font-weight: 300;
      background: #fff;
    }
    b {
      font-weight: 900;
    }
    #chart {
      position: relative;
    }
    .outline {
      fill: none;
      stroke: #888888;
      stroke-width: 1px;
    }
    .hidden {
      display: none;
      visibility: hidden;
      pointer-events: none;
    }
    .tooltip {
      color: #222;
      background: #fff;
      padding: .5em;
      text-shadow: #f5f5f5 0 1px 0;
      border-radius: 10px;
      border-color: #a6a6a6;
      border-width: 1px;
      border-style: solid;
      box-shadow: 0px 0px 2px 0px #a6a6a6;
      opacity: 0.9;
      position: absolute;
      width: 225px;
      display: block;
    }
    .tooltip h5 {
      font-size: 1.05rem;
    }
    .tooltip p {
      font-size: 0.80rem;
    }
    table {
      border: none;
      margin: 0;
      padding: 0;
      border-spacing: 0;
      width: 100%;
    }
    td {
      text-align: right;
      padding: 2px 0!important;
    }
    tr {
      margin: 0;
      background-color: white;
    }
    .node {
      fill: #fff;
      stroke: steelblue;
      stroke-width: 2.5px;
      font: 10px sans-serif;
    }
    .node text {
      color: #333;
    }
    .link {
      fill: none;
      stroke: #888888;
      stroke-weight: 1px;
      stroke-opacity: 0.5;
    }
    .legend {
      font-size: 12px;
    }
    /*rect {
      stroke-width: 2;
    }*/
    
    .highlight {
      stroke: red;
      stroke-weight: 4px;
      stroke-opacity: 1.0;
    }
    .row {
      padding-top: 50px;
    }
    .col-md-3 {
      background: rgba(250, 255, 255, 0.5);
      border-left: 1px solid #333;
      height: 100%;
    }
    #english {
      padding: 1em;
      background: rgba(250, 255, 255, 1);
      border-bottom: 1px solid #ccc;
    }
    #chinese {
      padding: 1em;
      background: rgba(250, 255, 255, 1);
      border-bottom: 1px solid #ccc;
    }
    #source {
      color: brown;
    }
    /* Filters */
    
    #accordion {
      position: fixed;
      width: 20%;
      right: 1;
    }
    .panel-body {
      background: rgba(250, 255, 255, 0.5);
    }
    .panel-default > .panel-heading {
      /*background: #fff;*/
    }
    .panel-heading {
      padding: 0;
      border-top-left-radius: 0px;
      border-top-right-radius: 0px;
    }
    .panel-group .panel {
      border-radius: 0;
    }
    .panel-group {
      margin-bottom: 0;
    }
    .panel-title a {
      color: #333;
      text-align: left;
      width: 100%;
      display: block;
      padding: 10px 15px;
      font-size: 14px;
      outline: none;
    }
    .panel-title a:hover,
    .panel-title a:focus,
    .panel-title a:active {
      text-decoration: none;
      outline: none;
    }
    .panel-options {
      padding: 5px;
    }
    .legend circle {
      fill: none;
      stroke: #ccc;
    }
    .legend text {
      fill: #777;
      font: 10px sans-serif;
      text-anchor: middle;
    }
    <html xmlns="http://www.w3.org/1999/xhtml">
    
    <head>
      <title>Chinese Input</title>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
      <link rel="stylesheet" href="style.css">
      <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
      <script src="http://d3js.org/queue.v1.min.js"></script>
      <script src="visualization.js"></script>
    
    
    </head>
    
    <body>
    
      <div id="chart"></div>
    
    </body>
    
    </html>

    enter image description here