d3.js使用图像强制布局

时间:2016-08-30 19:04:21

标签: javascript d3.js

我正在尝试在我自己的项目中使用此示例http://bl.ocks.org/eesur/be2abfb3155a38be4de4。我正在尝试概括要使用的代码块。我稍微更改了代码,但现在它没有在示例中生成类似的图形。你可以在jsfiddle https://jsfiddle.net/wykbbvzf/1/

中看到我的尝试
    var SuperHeroes = function(selector, w, h) {
    this.w = w;
    this.h = h;

    d3.select(selector).selectAll("svg").remove();

    this.svg = d3.select(selector).append("svg:svg")
        .attr('width', w)
        .attr('height', h);

    this.svg.append("svg:rect")
        .style("stroke", "#999")
        .style("fill", "#fff")
        .attr('width', w)
        .attr('height', h);

    this.force = d3.layout.force()
        .charge(function(d) { return d._children ? -d.size / 100 : -40; })
        .linkDistance(function(d) { return d.target._children ? 80 : 25; })
        .size([h, w]);
};

// some colour variables
var tcBlack = "#130C0E";

// rest of vars
var maxNodeSize = 50,
    x_browser = 20,
    y_browser = 25;


/*
d3.json("marvel.json", function(json) {




    // Build the path
    var defs = this.svg.insert("svg:defs")
        .data(["end"]);


    defs.enter().append("svg:path")
        .attr("d", "M0,-5L10,0L0,5");

    this.update();
});
*/

/**
 *
 */
SuperHeroes.prototype.update = function(json) {
    this.root = json;
    this.root.fixed = true;
    this.root.x = w / 2;
    this.root.y = h / 4;

    var nodes = this.flatten(this.root),
        links = d3.layout.tree().links(nodes);

    // Restart the force layout.
    this.force.nodes(nodes)
        .links(links)
        .gravity(0.05)
        .charge(-1500)
        .linkDistance(100)
        .friction(0.5)
        .linkStrength(function(l, i) {return 1; })
        .size([w, h])
        .on("tick", tick)
        .start();

    var path = this.svg.selectAll("path.link")
        .data(links, function(d) { return d.target.id; });

    path.enter().insert("svg:path")
        .attr("class", "link")
        // .attr("marker-end", "url(#end)")
        .style("stroke", "#eee");


    // Exit any old paths.
    path.exit().remove();



    // Update the nodes…
    var node = this.svg.selectAll("g.node")
        .data(nodes, function(d) { return d.id; });


    // Enter any new nodes.
    var nodeEnter = node.enter().append("svg:g")
        .attr("class", "node")
        .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
        .on("click", this.click)
        .call(this.force.drag);

    // Append a circle
    nodeEnter.append("svg:circle")
        .attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
        .style("fill", "#eee");


    // Append images
    var images = nodeEnter.append("svg:image")
        .attr("xlink:href",  function(d) { return d.img;})
        .attr("x", function(d) { return -25;})
        .attr("y", function(d) { return -25;})
        .attr("height", 50)
        .attr("width", 50);

    // make the image grow a little on mouse over and add the text details on click
    var setEvents = images
    // Append hero text
        .on( 'click', function (d) {
            d3.select("h1").html(d.hero);
            d3.select("h2").html(d.name);
            d3.select("h3").html ("Take me to " + "<a href='" + d.link + "' >"  + d.hero + " web page ⇢"+ "</a>" );
        })

        .on( 'mouseenter', function() {
            // select element in current context
            d3.select( this )
                .transition()
                .attr("x", function(d) { return -60;})
                .attr("y", function(d) { return -60;})
                .attr("height", 100)
                .attr("width", 100);
        })
        // set back
        .on( 'mouseleave', function() {
            d3.select( this )
                .transition()
                .attr("x", function(d) { return -25;})
                .attr("y", function(d) { return -25;})
                .attr("height", 50)
                .attr("width", 50);
        });

    // Append hero name on roll over next to the node as well
    nodeEnter.append("text")
        .attr("class", "nodetext")
        .attr("x", x_browser)
        .attr("y", y_browser +15)
        .attr("fill", tcBlack)
        .text(function(d) { return d.hero; });


    // Exit any old nodes.
    node.exit().remove();


    // Re-select for update.
    path = this.svg.selectAll("path.link");
    node = this.svg.selectAll("g.node");

    function tick() {


        path.attr("d", function(d) {

            var dx = d.target.x - d.source.x,
                dy = d.target.y - d.source.y,
                dr = Math.sqrt(dx * dx + dy * dy);
            return   "M" + d.source.x + ","
                + d.source.y
                + "A" + dr + ","
                + dr + " 0 0,1 "
                + d.target.x + ","
                + d.target.y;
        });
        node.attr("transform", this.nodeTransform);
    }
}


/**
 * Gives the coordinates of the border for keeping the nodes inside a frame
 * http://bl.ocks.org/mbostock/1129492
 */
SuperHeroes.prototype.nodeTransform = function(d) {
    d.x =  Math.max(maxNodeSize, Math.min(w - (d.imgwidth/2 || 16), d.x));
    d.y =  Math.max(maxNodeSize, Math.min(h - (d.imgheight/2 || 16), d.y));
    return "translate(" + d.x + "," + d.y + ")";
}

/**
 * Toggle children on click.
 */
SuperHeroes.prototype.click = function(d) {
    if (d.children) {
        d._children = d.children;
        d.children = null;
    } else {
        d.children = d._children;
        d._children = null;
    }

    this.update();
}


/**
 * Returns a list of all nodes under the root.
 */
SuperHeroes.prototype.flatten = function(root) {
    var nodes = [];
    var i = 0;

    function recurse(node) {
        if (node.children)
            node.children.forEach(recurse);
        if (!node.id)
            node.id = ++i;
        nodes.push(node);
    }

    recurse(root);
    return nodes;
}

SuperHeroes.prototype.cleanup = function() {
    this.update([]);
    this.force.stop();
};

 var currentSuperHereos;
    var createSuperHeroes = function(json) {
        // remove previous flower to save memory
        if (currentSuperHereos) currentSuperHereos.cleanup();
        // adapt layout size to the total number of elements
        var total = 5;
        w = parseInt(Math.sqrt(total) * 30, 10);
        h = parseInt(Math.sqrt(total) * 30, 10);

        if (h < 300) h = 300;
        if (w < 300) w = 300;
        // create a new SuperHeroes
        currentSuperHereos = new SuperHeroes("#visualization", w, h).update(json);

        var defs = this.svg.insert("svg:defs")
                .data(["end"]);


        defs.enter().append("svg:path")
                .attr("d", "M0,-5L10,0L0,5");
    };

    createSuperHeroes(JSON.parse('{"name":"MAlkara","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","children":[{"hero":"Kesan","name":"Keşan","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","additionalProperties":{}}],"additionalProperties":{}}'));

你对我的错误有任何想法吗?

1 个答案:

答案 0 :(得分:1)

您只需更新tick函数内的节点变换属性即可。似乎没有在您的代码中定义this.nodeTransform。所以tick函数应该如下所示。

function tick() {
    path.attr("d", function(d) {
        var dx = d.target.x - d.source.x,
            dy = d.target.y - d.source.y,
            dr = Math.sqrt(dx * dx + dy * dy);
        return   "M" + d.source.x + ","
            + d.source.y
            + "A" + dr + ","
            + dr + " 0 0,1 "
            + d.target.x + ","
            + d.target.y;
    });
    node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}

编辑:添加链接标签

  path.enter().insert("svg:path")
    .attr("class", "link")    
    .style("stroke", "#eee")
    .attr("id",function(d,i){ return "linkId_"+i; });

   path.enter().append("g").attr("class", "linklabelholder")
     .append("text")
     .attr("class", "linklabel")
     .style("font-size", "13px")    
     .attr("text-anchor", "middle")
     .style("fill","#000")
     .append("textPath")
     .style('text-anchor', 'middle')
     .attr('startOffset', '50%')
     .attr("xlink:href",function(d,i) { return "#linkId_" + i;})
     .text(function(d) { 
         return "my text"; //Can be dynamic via d object 
     });

var SuperHeroes = function(selector, w, h) {
  this.w = w;
  this.h = h;

  d3.select(selector).selectAll("svg").remove();

  this.svg = d3.select(selector).append("svg:svg")
    .attr('width', w)
    .attr('height', h);

  var defs = this.svg.insert("svg:defs")
    .data(["end"]);


  defs.enter().append("svg:path")
    .attr("d", "M0,-5L10,0L0,5");

  this.svg.append("svg:rect")
    .style("stroke", "#999")
    .style("fill", "#fff")
    .attr('width', w)
    .attr('height', h);

  this.force = d3.layout.force()
    .charge(function(d) {
      return d._children ? -d.size / 100 : -40;
    })
    .linkDistance(function(d) {
      return d.target._children ? 80 : 25;
    })
    .size([h, w]);
};

// some colour variables
var tcBlack = "#130C0E";

// rest of vars
var maxNodeSize = 50,
  x_browser = 20,
  y_browser = 25;


/*
d3.json("marvel.json", function(json) {




    // Build the path
    var defs = this.svg.insert("svg:defs")
        .data(["end"]);


    defs.enter().append("svg:path")
        .attr("d", "M0,-5L10,0L0,5");

    this.update();
});
*/

/**
 *
 */
SuperHeroes.prototype.update = function(json) {
  this.root = json;
  this.root.fixed = true;
  this.root.x = w / 2;
  this.root.y = h / 4;

  var nodes = this.flatten(this.root),
    links = d3.layout.tree().links(nodes);

  // Restart the force layout.
  this.force.nodes(nodes)
    .links(links)
    .gravity(0.05)
    .charge(-1500)
    .linkDistance(100)
    .friction(0.5)
    .linkStrength(function(l, i) {
      return 1;
    })
    .size([w, h])
    .on("tick", tick)
    .start();

  var path = this.svg.selectAll("path.link")
    .data(links, function(d) {
      return d.target.id;
    });

  path.enter().insert("svg:path")
    .attr("class", "link")    
    .style("stroke", "#eee")
    .attr("id",function(d,i){ return "linkId_"+i; });
  
   path.enter().append("g").attr("class", "linklabelholder")
     .append("text")
     .attr("class", "linklabel")
     .style("font-size", "13px")    
     .attr("text-anchor", "middle")
     .style("fill","#000")
     .append("textPath")
     .style('text-anchor', 'middle')
     .attr('startOffset', '50%')
     .attr("xlink:href",function(d,i) { return "#linkId_" + i;})
     .text(function(d) { 
         return "my text"; //Can be dynamic via d object 
     });

  // Exit any old paths.
  path.exit().remove();



  // Update the nodes…
  var node = this.svg.selectAll("g.node")
    .data(nodes, function(d) {
      return d.id;
    });


  // Enter any new nodes.
  
  var nodeEnter = node.enter().append("svg:g")
    .attr("class", "node")
    .on("click", this.click)
    .call(this.force.drag);

  // Append a circle
  nodeEnter.append("svg:circle")
    .attr("r", function(d) {
      return Math.sqrt(d.size) / 10 || 4.5;
    })
    .style("fill", "#eee");


  // Append images
  var images = nodeEnter.append("svg:image")
    .attr("xlink:href", function(d) {
      return d.img;
    })
    .attr("x", function(d) {
      return -25;
    })
    .attr("y", function(d) {
      return -25;
    })
    .attr("height", 50)
    .attr("width", 50);

  // make the image grow a little on mouse over and add the text details on click
  var setEvents = images
    // Append hero text
    .on('click', function(d) {
      d3.select("h1").html(d.hero);
      d3.select("h2").html(d.name);
      d3.select("h3").html("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢" + "</a>");
    })

  .on('mouseenter', function() {
      // select element in current context
      d3.select(this)
        .transition()
        .attr("x", function(d) {
          return -60;
        })
        .attr("y", function(d) {
          return -60;
        })
        .attr("height", 100)
        .attr("width", 100);
    })
    // set back
    .on('mouseleave', function() {
      d3.select(this)
        .transition()
        .attr("x", function(d) {
          return -25;
        })
        .attr("y", function(d) {
          return -25;
        })
        .attr("height", 50)
        .attr("width", 50);
    });

  // Append hero name on roll over next to the node as well
  nodeEnter.append("text")
    .attr("class", "nodetext")
    .attr("x", x_browser)
    .attr("y", y_browser + 15)
    .attr("fill", tcBlack)
    .text(function(d) {
      return d.hero;
    });


  // Exit any old nodes.
  node.exit().remove();


  // Re-select for update.
  path = this.svg.selectAll("path.link");
  node = this.svg.selectAll("g.node");

  function tick() {
    path.attr("d", function(d) {

      var dx = d.target.x - d.source.x,
        dy = d.target.y - d.source.y,
        dr = Math.sqrt(dx * dx + dy * dy);
      return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
    });
    node.attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    });
  }
}


/**
 * Gives the coordinates of the border for keeping the nodes inside a frame
 * http://bl.ocks.org/mbostock/1129492
 */
SuperHeroes.prototype.nodeTransform = function(d) {
  d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth / 2 || 16), d.x));
  d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight / 2 || 16), d.y));
  return "translate(" + d.x + "," + d.y + ")";
}

/**
 * Toggle children on click.
 */
SuperHeroes.prototype.click = function(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
}


/**
 * Returns a list of all nodes under the root.
 */
SuperHeroes.prototype.flatten = function(root) {
  var nodes = [];
  var i = 0;

  function recurse(node) {
    if (node.children)
      node.children.forEach(recurse);
    if (!node.id)
      node.id = ++i;
    nodes.push(node);
  }

  recurse(root);
  return nodes;
}

SuperHeroes.prototype.cleanup = function() {
  this.update([]);
  this.force.stop();
};

var currentSuperHereos;
var createSuperHeroes = function(json) {
  // remove previous flower to save memory
  if (currentSuperHereos) currentSuperHereos.cleanup();
  // adapt layout size to the total number of elements
  var total = 5;
  w = parseInt(Math.sqrt(total) * 30, 10);
  h = parseInt(Math.sqrt(total) * 30, 10);

  if (h < 300) h = 300;
  if (w < 300) w = 300;
  // create a new SuperHeroes
  currentSuperHereos = new SuperHeroes("#visualization", w, h).update(json);


};

createSuperHeroes(JSON.parse('{"name":"MAlkara","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","children":[{"hero":"Kesan","name":"Keşan","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","additionalProperties":{}}],"additionalProperties":{}}'));
body {
  font-family: "Source Code Pro", Consolas, monaco, monospace;
  line-height: 160%;
  font-size: 16px;
  margin: 0;
}
path.link {
  fill: none;
  stroke-width: 2px;
}
.node:not(:hover) .nodetext {
  display: none;
}
h1 {
  font-size: 36px;
  margin: 10px 0;
  text-transform: uppercase;
  font-weight: normal;
}
h2,
h3 {
  font-size: 18px;
  margin: 5px 0;
  font-weight: normal;
}
header {
  padding: 20px;
  position: absolute;
  top: 0;
  left: 0;
}
a:link {
  color: #EE3124;
  text-decoration: none;
}
a:visited {
  color: #EE3124;
}
a:hover {
  color: #A4CD39;
  text-decoration: underline;
}
a:active {
  color: #EE3124;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="visualization"></div>