0

I use this example: https://codepen.io/AndrewGHC/pen/mPXjKr

When you refresh the page, you see elements in different places. One circle may be right, and the next time the left. How can this be avoided? I think I need to set the starting positions.

// Request data, generate bg with Trianglify & set up loading function.

function slowPrint(tgt, i, msg, spd) {
  if (i < msg.length) {
    $(tgt).append(msg[i]);
    i++;
    var writeTimer = setTimeout(function() {
      slowPrint(tgt, i, msg, spd)
    }, spd);
  }
}

function writeThis(tgt, msg, spd) {
  if ($(tgt).html() === msg) {
    return;
  }
  $(tgt).html('');
  slowPrint(tgt, 0, msg, spd);
}

writeThis('#info', 'Loading . . .', 100);

var url = "https://raw.githubusercontent.com/AndrewGHC/kevin-bacon-number/master/kevinBacon.json";

d3.json(url, drawGraph);

// Credit to Trianglify @ https://github.com/qrohlf/trianglify

var pattern = Trianglify({
  height: $(document).height(),
  width: $(document).width(),
  cell_size: 40
});

document.body.style.backgroundImage = "url(" + pattern.png() + ")";

// Create the drawGraph callback

function drawGraph(err, data) {
  if (err) throw err;

  var width = $('#graph').width(),
    height = $('#graph').height();

  // Prepare the data for the force graph, beinning by creating an array of movies (strings)

  var movies = [];
  (function() {
    data.actors.forEach(function(actor) {
      actor.movies.forEach(function(movie) {
        if (movies.indexOf(movie) === -1) {
          movies.push(movie);
        }
      });
    });
  }())

  // Create the links array for the force graph, mapping actors to movies. This will draw a line between the two.

  var links = [];
  (function() {
    data.actors.forEach(function(actor, actorIndex) {
      actor.movies.forEach(function(movie, movieIndex) {
        links.push({
          "source": actorIndex,
          "target": data.actors.length + movies.indexOf(movie)
        });
      });
    });
  }())

  // Now prepare the nodes array, concatenating data.actors and the movies array. The order here is important, and movie indices must be converted into objects.

  var nodes = data.actors;
  movies.forEach(function(movie) {
    nodes.push({
      "movie": movie
    });
  });

  // Create the SVG canvas & force layout

  var canvas = d3.select('#graph')
    .append('svg')
    .attr("height", height)
    .attr("width", width);

  var force = d3.layout.force()
    .size([width, height])
    .nodes(nodes)
    .links(links)
    .linkDistance(50)
    .charge(function(d) {
      if (d.name === "Kevin Bacon") {
        return -1000;
      } else if (d.name) {
        return -(d.weight) * 50;
      }
      return -((d.weight * 50) * 5);
    })
    .gravity(0.1)
    .start();

  // Helper function to remove whitespace, later used for assigning IDs

  function rmWs(string) {
    if (typeof string !== 'string') {
      return false;
    }
    string = string.split(' ').join('');
    return string;
  }

  // Create the links

  var link = canvas.selectAll('.link')
    .data(links)
    .enter().append('line')
    .attr('class', 'link');

  // Create a colour scale for movie nodes. Find the min and max no. of links for the range of the colour domain.

  var arrMax = [];
  links.forEach(function(link) {
    arrMax.push(link.target.weight);
  });

  var colour = d3.scale.linear()
    .domain([1, d3.max(arrMax)])
    .range(["white", "black"])
    .interpolate(d3.interpolateHcl);

  // Set up the pop up on mouse hover



  // Call circles on SVG chart, with colours along a white - black gradient generated based on the max weight & variable sizing. Then place text on these movie elements.

  var circleRadius = 17;

  var circles = canvas.selectAll('.movies')
    .data(nodes)
    .enter()
    .append('circle')
    .attr('r', function(d, i) {
      if (d.name) {
        return circleRadius;
      }
      return circleRadius + (d.weight * 2);
    })
    .attr('stroke', '#777')
    .attr('stroke-width', '2px')
    .attr('fill', function(d, i) {
      return colour(d.weight) || 'black';
    })
    .call(force.drag)

  var text = canvas.selectAll('.moviesText')
    .data(nodes)
    .enter()
    .append('text')
    .attr('text-anchor', 'middle')
    .text(function(d) {
      return d.movie;
    });

  // Set up clip path for each forthcoming image node to clip rectangular images to circles. Then call images on the canvas.

  var clip = canvas.selectAll('clipPath')
    .data(nodes)
    .enter()
    .append('clipPath')
    .attr('id', function(d) {
      return rmWs(d.name) || rmWs(d.movie)
    })
    .append('circle')
    .attr('r', circleRadius);

  var imgWidth = 50,
    imgHeight = 50;

  var node = canvas.selectAll('.node')
    .data(nodes)
    .enter()
    .append('image')
    .attr('xlink:href', function(d) {
      return d.thumbnail;
    })
    .attr("class", "image")
    .attr("width", imgWidth)
    .attr("height", imgHeight)
    .attr("clip-path", function(d) {
      return "url(#" + (rmWs(d.name) || rmWs(d.movie)) + ")"
    })
    .call(force.drag);

  // Handle operations on each tick.

  force.on("tick", function() {

    link.attr("x1", function(d) {
        return d.source.x;
      })
      .attr("y1", function(d) {
        return d.source.y;
      })
      .attr("x2", function(d) {
        return d.target.x;
      })
      .attr("y2", function(d) {
        return d.target.y;
      });

    node.attr("x", function(d) {
        return d.x - (imgWidth / 2);
      })
      .attr("y", function(d) {
        return d.y - (imgHeight / 2);
      });

    clip.attr('cx', function(d) {
        return d.x;
      })
      .attr('cy', function(d) {
        return d.y;
      })

    circles.attr('cx', function(d) {
        return d.x;
      })
      .attr('cy', function(d) {
        return d.y;
      })

    text.attr('x', function(d) {
        return d.x;
      })
      .attr('y', function(d) {
        return d.y - 30;
      })
  });

  // When all initial calculations are done, print title to replace 'Loading . . .'

  force.on('end', function() {
    writeThis('#info', 'D3 Force Graph - Distance from Kevin Bacon', 100);
  })
}
@import url(https://fonts.googleapis.com/css?family=Questrial' rel='stylesheet' type='text/css);
 #header {
  text-align: center;
  font-family: 'Jockey One', sans-serif;
}
#graph {
  margin: 15px auto;
  background: white;
  height: 750px;
  width: 750px;
  -webkit-box-shadow: 0px 0px 8px 2px rgba(0, 0, 0, 0.75);
  -moz-box-shadow: 0px 0px 8px 2px rgba(0, 0, 0, 0.75);
  box-shadow: 0px 0px 8px 2px rgba(0, 0, 0, 0.75);
}
.link {
  stroke: #777;
  stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.1/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="header">
  <h2 id="info"></h2>
</div>
<div id="graph"></div>
user3896538
  • 43
  • 1
  • 4
  • See https://stackoverflow.com/questions/17286138/how-can-i-show-a-graph-using-d3-force-layout-with-initial-positions-set-and-no-m – Lars Kotthoff Nov 16 '16 at 06:19
  • Hi, I used it http://stackoverflow.com/questions/13463053/calm-down-initial-tick-of-a-force-layout. It works! Thanks. – user3896538 Nov 16 '16 at 12:38

1 Answers1

0

I didn't understand your problem exactly, but it looks like you want fixed some locations to each circle that will be the same every time you launch the code. One approach is specify the value on data as a attribute (some JSON object maybe?) or manipulate it with CSS or javascript. Apparently they are getting the x and y values at random.

Neo
  • 3,309
  • 7
  • 35
  • 44