Как динамически обновить силовой ориентированный граф d3 в Vue.js? - PullRequest
0 голосов
/ 20 июня 2019

У меня есть VueJS генерация кода d3 принудительно направленный сетевой узел при монтировании с использованием вызова json.Я пытаюсь обновить сетевой график новыми узлами и ссылками, используя тот же вызов json, и после вычисления различий я хочу, чтобы он соответственно добавил / удалил узлы / ссылки, но не могу понять, как это сделать.

  • Я уже пытался искать примеры динамически обновляемых силовых ориентированных графов d3.Я действительно нашел несколько примеров vue js + d3, но не с динамическими обновлениями, такими как этот , который показывает, как изменить данные графика, и вызывает перезапуск моделирования, но это не то, на что я смотрювыполнить.

  • Я пытался несколько дней адаптировать правильный код d3 js из примеров в vue js, но безуспешно.

  • не смогне найти никаких примеров с Vue.js и d3 v5 (найдены некоторые с v4).

new Vue({
  el: "#app",
  data: function() {
    return {
      graph: null,
      simulation: null,
      color: d3.scaleOrdinal(d3.schemeCategory10),
      settings: {
        strokeColor: "#29B5FF",
        width: 100,
        svgWidth: 960,
        svgHeight: 600
      },
      radius: 50,
      tooltip: d3.select("body").append("div").attr("class", "tooltip2").html(""),
    };
  },
  mounted: function() {
    var that = this;
    var initData = '{"nodes":[{"id":1,"ip":"100.64.1.118","name":"PCRF","description":"Policy and Charging Rules Function","image":"https://i.imgur.com/mpAqPb4.png"}],"links":[]}';
    var initBlob = new Blob([JSON.stringify(initData)], {
      type: "application/json"
    });
    var initUrl = URL.createObjectURL(initBlob);
    d3.json(initUrl, function(error, graph) {
      console.log(graph);
      if (error) throw error;
      that.graph = graph;
      that.simulation = d3.forceSimulation(that.graph.nodes)
        .force("link", d3.forceLink(that.graph.links).distance(300).strength(1))
        .force("charge", d3.forceManyBody().strength(-1000))
        .force("center", d3.forceCenter(that.settings.svgWidth / 2, that.settings.svgHeight / 2))
        .force("xAxis", d3.forceX(that.settings.svgWidth / 2).strength(0.4))
        .force("yAxis", d3.forceY(that.settings.svgHeight / 2).strength(0.6))
        .force("repelForce", d3.forceManyBody().strength(-5000).distanceMax(300).distanceMin(300));
    });
    d3.interval(function() {
      var interData = '{"nodes":[{"id":1,"ip":"100.64.1.118","name":"PCRF","description":"Allot Policy and Charging Rules Function","image":"https://i.imgur.com/mpAqPb4.png"},{"id":2,"ip":"100.64.1.119","name":"Client","description":"Diameter Client","image":"https://i.imgur.com/NsUvLZb.png"}],"links":[{"id":1,"source":1,"target":2,"type":"OKAY"}]}';
      var interBlob = new Blob([JSON.stringify(interData)], {
        type: "application/json"
      });
      var interUrl = URL.createObjectURL(interBlob);
      d3.json(interUrl, function(error, graph) {
        console.log(graph);
        that.updateGraph(graph);
      });
    }, 5000);
  },
  methods: {
    fixna(x) {
      if (isFinite(x)) return x;
      return 0;
    },
    updateGraph(graph) {
      var that = this;
      that.graph = graph;
      //TODO Nodes
      var newLinks = graph.links;
      newLinks.forEach(function(d) {
        d3.select("#l" + d.id)
          .attr("class", function(d2) {
            if (d.type == 'OKAY') {
              return "connected"
            } else {
              return "disconnected"
            }
          })
          .on("mouseover", function(d2) {
            if (d.type == 'OKAY') {
              d3.select(this).attr("stroke-width", "3px");
            } else {
              d3.select(this).attr("stroke-width", "2px");
            }
            t_text = "<strong>" + d2.source.ip + " <-> " + d2.target.ip + "</strong>";
            if (d.type == 'OKAY') {
              t_text += '<br>Connection Status: <font color="green">' + d.type + '</font>';
            } else {
              t_text += '<br>Connection Status: <font color="red">' + d.type + '</font>';
            }
            that.tooltip.html(t_text)
            return that.tooltip.style("visibility", "visible");
          })
          .on("mousemove", function() {
            return that.tooltip.style("top", (event.pageY - 10) + "px").style("left", (event.pageX + 10) + "px");
          })
          .on("mouseout", function(d2) {
            if (d.type == 'OKAY') {
              d3.select(this).attr("stroke-width", "2px");
            } else {
              d3.select(this).attr("stroke-width", "1px");
            }
            return that.tooltip.style("visibility", "hidden");
          });
      });
    }
  },
  computed: {
    nodes: function() {
      var that = this;
      if (that.graph) {
        var node = d3.select("svg").append("g").attr("class", "nodes").selectAll("g")
          .data(that.graph.nodes).enter().append("g")
          .call(d3.drag()
            .on("start", function dragstarted(d) {
              if (!d3.event.active) that.simulation.alphaTarget(0.3).restart();
              d.fx = d.x;
              d.fy = d.y;
            })
            .on("drag", function dragged(d) {
              d.fx = d3.event.x;
              d.fy = d3.event.y;
            })
            .on("end", function dragended(d) {
              if (!d3.event.active) that.simulation.alphaTarget(0);
              d.fx = null;
              d.fy = null;
            })).attr("fill", "white");
        var defs = node.append("defs");
        defs.append('pattern')
          .attr("id", function(d, i) {
            return "my_image" + i
          })
          .attr("width", 1)
          .attr("height", 1)
          .append("svg:image")
          .attr("xlink:href", function(d) {
            return d.image
          })
          .attr("height", that.radius)
          .attr("width", that.radius)
          .attr("x", 0)
          .attr("y", 0);
        var circle = node.append("circle")
          .attr("r", that.radius / 2)
          .attr("fill", function(d, i) {
            return "url(#my_image" + i + ")"
          })
          .attr("id", function(d) {
            if (d.id == "p0") {
              return "mainNode"
            } else {
              return d.id
            }
          })
          .attr("stroke", "#494b4d")
          .attr("stroke-width", "2px")
          .on("mouseover", function(d) {
            d3.select(this).attr("stroke-width", "3px");
            //sets tooltip.  t_text = content in html
            t_text = "<strong>" + d.name + "</strong><br>IP Address: " + d.ip
            that.tooltip.html(t_text)
            return that.tooltip.style("visibility", "visible");
          })
          .on("mousemove", function() {
            return that.tooltip.style("top", (event.pageY - 10) + "px").style("left", (event.pageX + 10) + "px");
          })
          .on("mouseout", function() {
            d3.select(this).attr("stroke-width", "2px");
            return that.tooltip.style("visibility", "hidden");
          });
        node.append("text")
          .style("fill", "black")
          .attr("dx", 0)
          .attr("dy", that.radius - ((that.radius / 10) - 3) * 5)
          .attr("text-anchor", "middle")
          .text(function(d) {
            return d.name + " - " + d.ip;
          });
        return node;
      }
    },
    links: function() {
      var that = this;
      if (that.graph) {
        return d3.select("svg").append("g")
          .attr("class", "links")
          .selectAll("line")
          .data(that.graph.links)
          .enter()
          .append("line")
          .attr("id", function(d) {
            return "l" + d.id
          })
          .attr("class", function(d) {
            if (d.type == 'OKAY') {
              return "connected"
            } else {
              return "disconnected"
            }
          })
          .on("mouseover", function(d) {
            if (d.type == 'OKAY') {
              d3.select(this).attr("stroke-width", "3px");
            } else {
              d3.select(this).attr("stroke-width", "2px");
            }
            t_text = "<strong>" + d.source.ip + " <-> " + d.target.ip + "</strong>";
            if (d.type == 'OKAY') {
              t_text += '<br>Connection Status: <font color="green">' + d.type + '</font>';
            } else {
              t_text += '<br>Connection Status: <font color="red">' + d.type + '</font>';
            }
            that.tooltip.html(t_text)
            return that.tooltip.style("visibility", "visible");
          })
          .on("mousemove", function() {
            return that.tooltip.style("top", (event.pageY - 10) + "px").style("left", (event.pageX + 10) + "px");
          })
          .on("mouseout", function(d) {
            if (d.type == 'good') {
              d3.select(this).attr("stroke-width", "2px");
            } else {
              d3.select(this).attr("stroke-width", "1px");
            }
            return that.tooltip.style("visibility", "hidden");
          });
      }
    },

  },
  updated: function() {
    var that = this;
    that.simulation.nodes(that.graph.nodes).on('tick', function ticked() {
      that.links
        .attr("x1", function(d) {
          return that.fixna(d.source.x);
        })
        .attr("y1", function(d) {
          return that.fixna(d.source.y);
        })
        .attr("x2", function(d) {
          return that.fixna(d.target.x);
        })
        .attr("y2", function(d) {
          return that.fixna(d.target.y);
        });

      that.nodes
        .attr("transform", function(d) {
          return "translate(" + that.fixna(d.x) + "," + that.fixna(d.y) + ")";
        });
    });
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
I created <a href="https://jsfiddle.net/tsp48yL3/1/" target="_blank">this fiddle</a>.
<div id="app">
  <div class="svg-container" :style="{width: settings.width + '%'}">
		<svg id="svg" pointer-events="all" viewBox="0 0 960 600" preserveAspectRatio="xMinYMin meet">
			<g :id="links"></g>
			<g :id="nodes"></g>
		</svg>
	</div>
</div>

работает с d3.v4 .Я попытался изменить на v5 , но это не работает (может, кто-то пролил свет).Был бы рад, если бы кто-нибудь мог указать мне правильное направление или помочь мне с реализацией.

Я пытаюсь добавить узлы и удалить их точно так же, как в этой демонстрации здесь .

там вы можете увидеть новые узлы, сгенерированные без перезапуска всей симуляции, что делает его очень гладким.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...