Search code examples
d3.jscvs

Colored text and line-breaks for D3 node labels


I am working on a D3 chart, but can't get the node labeling right (I would like to put the "size" on a second line, and change all the text to white. For instance, the "Toyota" node would say "Toyota Motor" and on a new line just below, "61.84").

Here is my starting point. I tried to add a pre block with the CSV data so it could run on JSfiddle, but I got stuck.

I know I need to change this code in order to get the data from the pre block instead of the external CSV:

d3.text("./car_companies.csv", function(error, text) {

After that, I need to add a new "node append", something like this:

  node.append("revenue")
  .style("text-anchor", "middle")
  .attr("dy", "1.5em")
  .text(function(d) { return d.revenue.substring(0, d.radius / 3); });

http://jsfiddle.net/nick2ny/pqo1x670/4/

Thank you for any ideas.


Solution

  • Not directly related to the question, but to use the <pre> element to hold your data, you have to use:

    var text = d3.select("pre").text();
    

    Instead of d3.text().

    Back to the question:

    For printing those values, you just need:

    node.append("text")
        .attr("dy", "1.3em")
        .style("text-anchor", "middle")
        .style("fill", "white")
        .text(function(d) {
            return d.size;
        });
    

    Adjusting dy the way you want. However, there is an additional problem: you're not populating size in the data array. Therefore, add this in the create_nodes function:

    size: data[node_counter].size,
    

    Here is the code with those changes:

    <!DOCTYPE html>
    <meta charset="utf-8">
    <style type="text/css">
      text {
        font: 10px sans-serif;
      }
      
      pre {
        display: none;
      }
      
      circle {
        stroke: #565352;
        stroke-width: 1;
      }
    </style>
    
    <body>
      <pre id="data">
    Toyota Motor,61.84,Asia,239
    Volkswagen,44.54,Europe,124
    Daimler,40.79,Europe,104
    BMW,35.78,Europe,80
    Ford Motor,31.75,America,63
    General Motors,30.98,America,60
    </pre>
    
      <script src="https://d3js.org/d3.v3.min.js"></script>
      <script>
        Array.prototype.contains = function(v) {
          for (var i = 0; i < this.length; i++) {
            if (this[i] === v) return true;
          }
          return false;
        };
    
    
        var width = 500,
          height = 500,
          padding = 1.5, // separation between same-color nodes
          clusterPadding = 6, // separation between different-color nodes
          maxRadius = 12;
    
        var color = d3.scale.ordinal()
          .range(["#0033cc", "#33cc66", "#990033"]);
    
    
    
        var text = d3.select("pre").text();
        var colNames = "text,size,group,revenue\n" + text;
        var data = d3.csv.parse(colNames);
    
        data.forEach(function(d) {
          d.size = +d.size;
        });
    
    
        //unique cluster/group id's
        var cs = [];
        data.forEach(function(d) {
          if (!cs.contains(d.group)) {
            cs.push(d.group);
          }
        });
    
        var n = data.length, // total number of nodes
          m = cs.length; // number of distinct clusters
    
        //create clusters and nodes
        var clusters = new Array(m);
        var nodes = [];
        for (var i = 0; i < n; i++) {
          nodes.push(create_nodes(data, i));
        }
    
        var force = d3.layout.force()
          .nodes(nodes)
          .size([width, height])
          .gravity(.02)
          .charge(0)
          .on("tick", tick)
          .start();
    
        var svg = d3.select("body").append("svg")
          .attr("width", width)
          .attr("height", height);
    
    
        var node = svg.selectAll("circle")
          .data(nodes)
          .enter().append("g").call(force.drag);
    
    
        node.append("circle")
          .style("fill", function(d) {
            return color(d.cluster);
          })
          .attr("r", function(d) {
            return d.radius
          })
    
    
        node.append("text")
          .attr("dy", ".3em")
          .style("text-anchor", "middle")
          .style("fill", "white")
          .text(function(d) {
            return d.text;
          });
    
        node.append("text")
          .attr("dy", "1.3em")
          .style("text-anchor", "middle")
          .style("fill", "white")
          .text(function(d) {
            return d.size;
          });
    
        function create_nodes(data, node_counter) {
          var i = cs.indexOf(data[node_counter].group),
            r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
            d = {
              cluster: i,
              radius: data[node_counter].size * 1.5,
              text: data[node_counter].text,
              size: data[node_counter].size,
              revenue: data[node_counter].revenue,
              x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
              y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
            };
          if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
          return d;
        };
    
    
    
        function tick(e) {
          node.each(cluster(10 * e.alpha * e.alpha))
            .each(collide(.5))
            .attr("transform", function(d) {
              var k = "translate(" + d.x + "," + d.y + ")";
              return k;
            })
    
        }
    
        // Move d to be adjacent to the cluster node.
        function cluster(alpha) {
          return function(d) {
            var cluster = clusters[d.cluster];
            if (cluster === d) return;
            var x = d.x - cluster.x,
              y = d.y - cluster.y,
              l = Math.sqrt(x * x + y * y),
              r = d.radius + cluster.radius;
            if (l != r) {
              l = (l - r) / l * alpha;
              d.x -= x *= l;
              d.y -= y *= l;
              cluster.x += x;
              cluster.y += y;
            }
          };
        }
    
        // Resolves collisions between d and all other circles.
        function collide(alpha) {
          var quadtree = d3.geom.quadtree(nodes);
          return function(d) {
            var r = d.radius + maxRadius + Math.max(padding, clusterPadding),
              nx1 = d.x - r,
              nx2 = d.x + r,
              ny1 = d.y - r,
              ny2 = d.y + r;
            quadtree.visit(function(quad, x1, y1, x2, y2) {
              if (quad.point && (quad.point !== d)) {
                var x = d.x - quad.point.x,
                  y = d.y - quad.point.y,
                  l = Math.sqrt(x * x + y * y),
                  r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding);
                if (l < r) {
                  l = (l - r) / l * alpha;
                  d.x -= x *= l;
                  d.y -= y *= l;
                  quad.point.x += x;
                  quad.point.y += y;
                }
              }
              return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
            });
          };
        }
      </script>