title: D3 Multiple Instances of wForceSimulation
body: <p>Hope someone can help with my first D3 visualisation. I've got two datasets that I've loaded in as separate selections of group elements. I'm trying to simulate forces on the bubbles however I can only get one of the groups to function at once. I have a feeling it's something to do with calling d3.forceSimulation multiple times but I can't figure out a workaround. Any help appreciated! (Apologies for sloppy coding, not been in the game long! Github repo: <a href="https://github.com/skemp456/D3" rel="nofollow noreferrer">https://github.com/skemp456/D3</a>)</p>
  
  <p><a href="https://i.stack.imgur.com/v6UcS.png" rel="nofollow noreferrer">Visualisation (middle group (features) doesn't work)</a></p>
  
  <pre><code>function createBubbleChart(error, features, dummydata) {
    var features_f = features.map(function(d) { return d.Feature});
    var features_t = d3.set(dummydata.map(function(tech) { return tech.Feature; }));
    var importance = {'Tertiary':30,'Secondary':45,'Critical':60},
        //importanceExtent = d3.extent(importance.values),
        importanceScaleX,
        importanceScaleY;
    var categories = d3.set(features.map(function(d) { return d.Category}));
    var catColourScale = d3.scaleOrdinal()
        .domain(categories.values())
        .range(["#1092E1", "#E13610", "#11B40C"])
    var featureColourScale = d3.scaleOrdinal(d3.schemeCategory10)
          .domain(features.values());
    var columns = d3.set(['Technology - Networks',
                      'Technology - Apps &amp; Devices and Security &amp; Privacy',
                      'Internal to the Community',
                      'Legislation, Standards, Economic, Political, Social']);
    var timeScales = d3.set(['Now', 'Now - 6 months', '6 Months - 1 Year', '1 Year - 3 Years', '3 Years +'])
    var impact = dummydata.map(function(tech) { return tech.Impact; }),
        impactExtent = d3.extent(impact),
        impactScaleX,
        impactScaleY;
  
  
    var width = 1200,
        height = 800;
  
    var svg,
        circles,
        f_circles,
        t_circles,
        circleSize = {min: 30, max:60},
        clusterPadding = 6
  
    var circleRadiusScale_t = d3.scaleSqrt()
      .domain(impactExtent)
      .range([circleSize.min, circleSize.max]);
  
    var circleRadiusScale_f = d3.scaleSqrt()
      .domain(importance)
      .range([circleSize.min, circleSize.max])
  
    var tooltip = d3.select("body")
        .append("div")
        .style("position", "absolute")
        .style("z-index", "10")
        .style("visibility", "hidden")
        .style("max-width", "300px")
        .style("color", "white")
        .style("padding", "8px")
        .style("background-color", "rgba(0, 0, 0, 0.75)")
        .style("border-radius", "6px")
        .style("font", "12px sans-serif")
        .text("tooltip");
  
    var forces,
        forceSimulation,
        forceSimulation2;
  
    //Call functions here
    createSVG();
    createFCircles();
    createTCircles();
    createForces();
    createForceSimulation();
    addGroupingListeners();
  
  
  
  
    /////////////////////////////////
    //---------Functions-----------//
    /////////////////////////////////
    //Function to create the simple vector graphic space
    function createSVG() {
      svg = d3.select("#bubble-chart")
        .append("svg")
        .attr("width", width)
        .attr("height", height)
        .attr("class", "bubble");
    }
    //Function to determine if a html element is checked
    function isChecked(elementID) {
      return d3.select(elementID).property("checked");
    }
    //Create feature circles
    function createFCircles() {
      f_circles = svg.selectAll("f_circle")
        .data(features)
        .enter()
        .append("g")
        .attr("class", "f_circle")
  
      f_circles
        .append("circle")
        .attr("cursor", "pointer")
        .attr("r", function(d) { return importance[d.Importance]; })
        .style("fill", function(d) {return catColourScale(d.Category);})
        .on("mouseover", function(d, i) {
          d3.select(this).transition()
            .ease(d3.easeElastic)
            .duration("500")
            .attr("r",  importance[d.Importance]+20);
          d3.select("#clipCircle"+i+" circle").transition()
            .ease(d3.easeCubicOut)
            .duration("200")
            .attr("r", importance[d.Importance]+10);
          d3.select("#text"+i).transition()
            .ease(d3.easeCubicOut)
            .duration("200")
            .attr("y", 12)
            .attr("font-size", 32)
            .attr("fill", "#333");
        })
        .on("mouseout", function(d, i) {
          d3.select(this).transition()
            .ease(d3.easeQuad)
            .delay("10")
            .duration("200")
            .attr("r", importance[d.Importance]);
          d3.select("#clipCircle"+i+" circle").transition()
            .ease(d3.easeQuad)
            .delay("10")
            .duration("200")
            .attr("r", 0);
          d3.select("#text"+i).transition()
            .ease(d3.easeCubicOut)
            .duration("400")
            .delay("10")
            .attr("y", 7)
            .attr("font-size", 20)
            .attr("fill", "#FFF");
        });
  
      f_circles.append("text")
        .attr("dy", ".3em")
        .style("text-anchor", "middle")
        .style("pointer-events", "none")
        .text(function(d){ return d.Feature; });
    }
    //Create Technology Circles
    function createTCircles() {
      t_circles = svg.selectAll("t_circle")
        .data(dummydata)
        .enter()
        .append("g")
        .attr("class", "t_circle")
  
      t_circles
        .append("circle")
        .attr("cursor", "pointer")
        .attr("r", function(d) {return circleRadiusScale_t(d.Impact); })
        .style("fill",  function(d) {return featureColourScale(d.Feature);})
        .on("mouseover", function(d, i) {
          d3.select(this).transition()
            .ease(d3.easeElastic)
            .duration("500")
            .attr("r",  function(d) {return circleRadiusScale_t(d.Impact)+10; });
          d3.select("#clipCircle"+i+" circle").transition()
            .ease(d3.easeCubicOut)
            .duration("200")
            .attr("r", function(d) {return circleRadiusScale_t(10); });
          d3.select("#text"+i).transition()
            .ease(d3.easeCubicOut)
            .duration("200")
            .attr("y", 12)
            .attr("font-size", 32)
            .attr("fill", "#333");
          tooltip.text(d.Description);
          tooltip.style("visibility", "visible");
        })
        .on("mousemove", function() {
          return tooltip.style("top", (d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");
        })
        .on("mouseout", function(d, i) {
          d3.select(this).transition()
            .ease(d3.easeQuad)
            .delay("10")
            .duration("200")
            .attr("r", function(d) {return circleRadiusScale_t(d.Impact); });
          d3.select("#clipCircle"+i+" circle").transition()
            .ease(d3.easeQuad)
            .delay("10")
            .duration("200")
            .attr("r", 0);
          d3.select("#text"+i).transition()
            .ease(d3.easeCubicOut)
            .duration("400")
            .delay("10")
            .attr("y", 7)
            .attr("font-size", 20)
            .attr("fill", "#FFF");
            return tooltip.style("visibility", "hidden");
        });
  
      t_circles.append("text")
        .attr("dy", ".3em")
        .style("text-anchor", "middle")
        .style("pointer-events", "none")
        .text(function(d){ return d.Technology; });
  
    }
  
    function createForces() {
      var forceStrength = 0.15;
      forces = {
        features: createFeaturesForces(),
        cases: createCasesForces(),
        timeScales: createTimeScalesForces()
      };
  
      function createFeaturesForces() {
        return {
          x: d3.forceX(featureForceX).strength(forceStrength),
          y: d3.forceY(featureForceY).strength(forceStrength)
        };
        function featureForceX(d) {
          if ("Importance" in d) {
            return (width /2);
          }else{
            return (width + 1000);
          }
        }
  
        function featureForceY(d) {
          if ("Importance" in d) {
            return (height /2);
          }else{
            return (height /2);
          }
        }
      }
      function createCasesForces() {
        return {
          x: d3.forceX(casesForceX).strength(forceStrength),
          y: d3.forceY(casesForceY).strength(forceStrength)
        };
        function casesForceX(d) {
          if ("Technology" in d) {
            return (width /2);
          }else{
            return (width-1000);
          }
        }
  
        function casesForceY(d) {
          if ("Technology" in d) {
            return (height /2);
          }else{
            return (height / 2);
          }
        }
      }
      function createTimeScalesForces() {
        var columnNamesDomain = columns.values();
        var timeScalesDomain = timeScales.values();
        var scaledTimeMargin = circleSize.max;
  
        timeScaleX = d3.scaleBand()
          .domain(columnNamesDomain)
          .range([scaledTimeMargin, width - scaledTimeMargin*2]);
        timeScaleY = d3.scaleBand()
        .domain(timeScalesDomain)
        .range([height - scaledTimeMargin, scaledTimeMargin*2]);
  
        var centerCirclesInScaleBandOffset = timeScaleX.bandwidth() / 2;
        return {
          x: d3.forceX(function(d) {
            if ("Technology" in d){
              return timeScaleX(d.Column) + centerCirclesInScaleBandOffset;
            }else{
              return (0 - width/2);
            }
  
          }).strength(forceStrength),
          y: d3.forceY(function(d) {
            if("Technology" in d){
              return timeScaleY(d.Timescale);
            }else{
              return (0 - height/2);
            }
          }).strength(forceStrength)
        };
      }
    }
    function createForceSimulation() {
      //Create forces for cases
      forceSimulation_t = d3.forceSimulation();
      forceSimulation_t
        .force("x", forces.features.x)
        .force("y", forces.features.y)
        .force("collide", d3.forceCollide(forceCollide));
      forceSimulation_t
        .nodes(dummydata)
        .on("tick", function() {
          t_circles
            .attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")"});
        });
  
      //Create forces for features
      forceSimulation_f = d3.forceSimulation();
      forceSimulation_f
        .force("x", forces.features.x)
        .force("y", forces.features.y)
        .force("collide", d3.forceCollide(forceCollide));
      forceSimulation_f
        .nodes(features)
        .on("tick", function() {
          f_circles
            .attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")"});
        });
    }
  
    function forceCollide(d) {
      if ("Technology" in d){
        return circleRadiusScale_t(d.Impact)+2;
      }else{
        return importance[d.Importance]+2;
      }
    }
  
    function featureGrouping() {
      return isChecked("#Features");
    }
  
    function casesGrouping() {
      return isChecked("#Cases")
    }
  
    function timeScaleGrouping() {
      return isChecked("#Time-Scales");
    }
  
    function addGroupingListeners() {
      addListener("#Features", forces.features);
      addListener("#Cases", forces.cases);
      addListener("#Time-Scales", forces.timeScales);
  
      function addListener(selector, forces) {
        d3.select(selector).on("click", function() {
          updateForces_t(forces);
          updateForces_f(forces);
          toggleTimeScaleAxis(timeScaleGrouping());
          //toggleFeaturesKey();
        });
      }
  
      function updateForces_t(forces) {
        forceSimulation_t
          .force("x", forces.x)
          .force("y", forces.y)
          .force("collide", d3.forceCollide(forceCollide))
          .alphaTarget(0.5)
          .restart();
      }
  
      function updateForces_f() {
        forceSimulation_f
          .force("x", forces.x)
          .force("y", forces.y)
          .force("collide", d3.forceCollide(forceCollide))
          .alphaTarget(0.5)
          .restart();
      }
      function toggleTimeScaleAxis(showAxes) {
        var onScreenXOffset = 100,
            offScreenXOffset = -40;
        var onScreenYOffset = 120,
            offScreenYOffset = 100;
  
        if (d3.select(".x-axis").empty()) {
          createAxes();
        }
        var xAxis = d3.select(".x-axis"),
            yAxis = d3.select(".y-axis");
  
        if (showAxes) {
          translateAxis(xAxis, "translate(20," + (height - onScreenYOffset) + ")");
          translateAxis(yAxis, "translate(" + onScreenXOffset + ",-50)");
        } else {
          translateAxis(xAxis, "translate(0," + (height + offScreenYOffset) + ")");
          translateAxis(yAxis, "translate(" + offScreenXOffset + ",0)");
        }
  
      function createAxes() {
        var numberOfTicks = 10,
            tickFormat = ".0s";
  
        var xAxis = d3.axisBottom(timeScaleX)
          .ticks(numberOfTicks, tickFormat);
  
        svg.append("g")
          .attr("class", "x-axis")
          .attr("transform", "translate(0," + (height + offScreenYOffset) + ")")
          .call(xAxis)
          .selectAll(".tick text")
            .attr("font-size", "16px");
  
        var yAxis = d3.axisLeft(timeScaleY)
          .ticks(numberOfTicks, tickFormat);
        svg.append("g")
          .attr("class", "y-axis")
          .attr("transform", "translate(" + offScreenXOffset + ",0)")
          .call(yAxis);
      }
  
      function translateAxis(axis, translation) {
        axis
          .transition()
          .duration(100)
          .attr("transform", translation);
      }
    }
    }
  }
  </code></pre>