vue3 d3.js个人笔记呆更新

https://www.cnblogs.com/fastmover/p/7779660.html

<template>
  <div>
    <div class="container"></div>
  </div>
</template>

<script>
import * as d3 from "d3";

export default {
  data() {
    return {
      svgArea: null,
      links: [],
      nodes: [],
      nodesName: [],
      linksName: [],
      simulation: null,
      scale: 1,
      width: 1200,
      height: 600,
      colorList: [
        "#FD7623",
        "#3388B1",
        "#D82952",
        "#F3D737",
        "#409071",
        "#D64E52",
      ],
      testGraph: {
        nodes: [
          { id: "Myriel", group: 1 },
          { id: "Napoleon", group: 1 },
          { id: "Mlle.Baptistine", group: 1 },
          { id: "Mme.Magloire", group: 1 },
          { id: "CountessdeLo", group: 1 },
          { id: "Geborand", group: 1 },
          { id: "Champtercier", group: 1 },
          { id: "Cravatte", group: 1 },
          { id: "Count", group: 1 },
          { id: "OldMan", group: 1 },
          { id: "Labarre", group: 2 },
          { id: "Valjean", group: 2 },
          { id: "Marguerite", group: 3 },
          { id: "Mme.deR", group: 2 },
          { id: "Isabeau", group: 2 },
          { id: "Gervais", group: 2 },
          { id: "Tholomyes", group: 3 },
          { id: "Listolier", group: 3 },
          { id: "Fameuil", group: 3 },
          { id: "Blacheville", group: 3 },
          { id: "Favourite", group: 3 },
          { id: "Dahlia", group: 3 },
          { id: "Zephine", group: 3 },
          { id: "Fantine", group: 3 },
          { id: "Mme.Thenardier", group: 4 },
          { id: "Thenardier", group: 4 },
          { id: "Cosette", group: 5 },
          { id: "Javert", group: 4 },
          { id: "Fauchelevent", group: 0 },
          { id: "Bamatabois", group: 2 },
          { id: "Perpetue", group: 3 },
          { id: "Simplice", group: 2 },
          { id: "Scaufflaire", group: 2 },
          { id: "Woman1", group: 2 },
          { id: "Judge", group: 2 },
          { id: "Champmathieu", group: 2 },
          { id: "Brevet", group: 2 },
          { id: "Chenildieu", group: 2 },
          { id: "Cochepaille", group: 2 },
          { id: "Pontmercy", group: 4 },
          { id: "Boulatruelle", group: 6 },
          { id: "Eponine", group: 4 },
          { id: "Anzelma", group: 4 },
          { id: "Woman2", group: 5 },
          { id: "MotherInnocent", group: 0 },
          { id: "Gribier", group: 0 },
          { id: "Jondrette", group: 7 },
          { id: "Mme.Burgon", group: 7 },
          { id: "Gavroche", group: 8 },
          { id: "Gillenormand", group: 5 },
          { id: "Magnon", group: 5 },
          { id: "Mlle.Gillenormand", group: 5 },
          { id: "Mme.Pontmercy", group: 5 },
          { id: "Mlle.Vaubois", group: 5 },
          { id: "Lt.Gillenormand", group: 5 },
          { id: "Marius", group: 8 },
          { id: "BaronessT", group: 5 },
          { id: "Mabeuf", group: 8 },
          { id: "Enjolras", group: 8 },
          { id: "Combeferre", group: 8 },
          { id: "Prouvaire", group: 8 },
          { id: "Feuilly", group: 8 },
          { id: "Courfeyrac", group: 8 },
          { id: "Bahorel", group: 8 },
          { id: "Bossuet", group: 8 },
          { id: "Joly", group: 8 },
          { id: "Grantaire", group: 8 },
          { id: "MotherPlutarch", group: 9 },
          { id: "Gueulemer", group: 4 },
          { id: "Babet", group: 4 },
          { id: "Claquesous", group: 4 },
          { id: "Montparnasse", group: 4 },
          { id: "Toussaint", group: 5 },
          { id: "Child1", group: 10 },
          { id: "Child2", group: 10 },
          { id: "Brujon", group: 4 },
          { id: "Mme.Hucheloup", group: 8 },
        ],
        links: [
          { source: "Napoleon", target: "Myriel", value: 1 },
          { source: "Mlle.Baptistine", target: "Myriel", value: 8 },
          { source: "Mme.Magloire", target: "Myriel", value: 10 },
          { source: "Mme.Magloire", target: "Mlle.Baptistine", value: 6 },
          { source: "CountessdeLo", target: "Myriel", value: 1 },
          { source: "Geborand", target: "Myriel", value: 1 },
          { source: "Champtercier", target: "Myriel", value: 1 },
          { source: "Cravatte", target: "Myriel", value: 1 },
          { source: "Count", target: "Myriel", value: 2 },
          { source: "OldMan", target: "Myriel", value: 1 },
          { source: "Valjean", target: "Labarre", value: 1 },
          { source: "Valjean", target: "Mme.Magloire", value: 3 },
          { source: "Valjean", target: "Mlle.Baptistine", value: 3 },
          { source: "Valjean", target: "Myriel", value: 5 },
          { source: "Marguerite", target: "Valjean", value: 1 },
          { source: "Mme.deR", target: "Valjean", value: 1 },
          { source: "Isabeau", target: "Valjean", value: 1 },
          { source: "Gervais", target: "Valjean", value: 1 },
          { source: "Listolier", target: "Tholomyes", value: 4 },
          { source: "Fameuil", target: "Tholomyes", value: 4 },
          { source: "Fameuil", target: "Listolier", value: 4 },
          { source: "Blacheville", target: "Tholomyes", value: 4 },
          { source: "Blacheville", target: "Listolier", value: 4 },
          { source: "Blacheville", target: "Fameuil", value: 4 },
          { source: "Favourite", target: "Tholomyes", value: 3 },
          { source: "Favourite", target: "Listolier", value: 3 },
          { source: "Favourite", target: "Fameuil", value: 3 },
          { source: "Favourite", target: "Blacheville", value: 4 },
          { source: "Dahlia", target: "Tholomyes", value: 3 },
          { source: "Dahlia", target: "Listolier", value: 3 },
          { source: "Dahlia", target: "Fameuil", value: 3 },
          { source: "Dahlia", target: "Blacheville", value: 3 },
          { source: "Dahlia", target: "Favourite", value: 5 },
          { source: "Zephine", target: "Tholomyes", value: 3 },
          { source: "Zephine", target: "Listolier", value: 3 },
          { source: "Zephine", target: "Fameuil", value: 3 },
          { source: "Zephine", target: "Blacheville", value: 3 },
          { source: "Zephine", target: "Favourite", value: 4 },
          { source: "Zephine", target: "Dahlia", value: 4 },
          { source: "Fantine", target: "Tholomyes", value: 3 },
          { source: "Fantine", target: "Listolier", value: 3 },
          { source: "Fantine", target: "Fameuil", value: 3 },
          { source: "Fantine", target: "Blacheville", value: 3 },
          { source: "Fantine", target: "Favourite", value: 4 },
          { source: "Fantine", target: "Dahlia", value: 4 },
          { source: "Fantine", target: "Zephine", value: 4 },
          { source: "Fantine", target: "Marguerite", value: 2 },
          { source: "Fantine", target: "Valjean", value: 9 },
          { source: "Mme.Thenardier", target: "Fantine", value: 2 },
          { source: "Mme.Thenardier", target: "Valjean", value: 7 },
          { source: "Thenardier", target: "Mme.Thenardier", value: 13 },
          { source: "Thenardier", target: "Fantine", value: 1 },
          { source: "Thenardier", target: "Valjean", value: 12 },
          { source: "Cosette", target: "Mme.Thenardier", value: 4 },
          { source: "Cosette", target: "Valjean", value: 31 },
          { source: "Cosette", target: "Tholomyes", value: 1 },
          { source: "Cosette", target: "Thenardier", value: 1 },
          { source: "Javert", target: "Valjean", value: 17 },
          { source: "Javert", target: "Fantine", value: 5 },
          { source: "Javert", target: "Thenardier", value: 5 },
          { source: "Javert", target: "Mme.Thenardier", value: 1 },
          { source: "Javert", target: "Cosette", value: 1 },
          { source: "Fauchelevent", target: "Valjean", value: 8 },
          { source: "Fauchelevent", target: "Javert", value: 1 },
          { source: "Bamatabois", target: "Fantine", value: 1 },
          { source: "Bamatabois", target: "Javert", value: 1 },
          { source: "Bamatabois", target: "Valjean", value: 2 },
          { source: "Perpetue", target: "Fantine", value: 1 },
          { source: "Simplice", target: "Perpetue", value: 2 },
          { source: "Simplice", target: "Valjean", value: 3 },
          { source: "Simplice", target: "Fantine", value: 2 },
          { source: "Simplice", target: "Javert", value: 1 },
          { source: "Scaufflaire", target: "Valjean", value: 1 },
          { source: "Woman1", target: "Valjean", value: 2 },
          { source: "Woman1", target: "Javert", value: 1 },
          { source: "Judge", target: "Valjean", value: 3 },
          { source: "Judge", target: "Bamatabois", value: 2 },
          { source: "Champmathieu", target: "Valjean", value: 3 },
          { source: "Champmathieu", target: "Judge", value: 3 },
          { source: "Champmathieu", target: "Bamatabois", value: 2 },
          { source: "Brevet", target: "Judge", value: 2 },
          { source: "Brevet", target: "Champmathieu", value: 2 },
          { source: "Brevet", target: "Valjean", value: 2 },
          { source: "Brevet", target: "Bamatabois", value: 1 },
          { source: "Chenildieu", target: "Judge", value: 2 },
          { source: "Chenildieu", target: "Champmathieu", value: 2 },
          { source: "Chenildieu", target: "Brevet", value: 2 },
          { source: "Chenildieu", target: "Valjean", value: 2 },
          { source: "Chenildieu", target: "Bamatabois", value: 1 },
          { source: "Cochepaille", target: "Judge", value: 2 },
          { source: "Cochepaille", target: "Champmathieu", value: 2 },
          { source: "Cochepaille", target: "Brevet", value: 2 },
          { source: "Cochepaille", target: "Chenildieu", value: 2 },
          { source: "Cochepaille", target: "Valjean", value: 2 },
          { source: "Cochepaille", target: "Bamatabois", value: 1 },
          { source: "Pontmercy", target: "Thenardier", value: 1 },
          { source: "Boulatruelle", target: "Thenardier", value: 1 },
          { source: "Eponine", target: "Mme.Thenardier", value: 2 },
          { source: "Eponine", target: "Thenardier", value: 3 },
          { source: "Anzelma", target: "Eponine", value: 2 },
          { source: "Anzelma", target: "Thenardier", value: 2 },
          { source: "Anzelma", target: "Mme.Thenardier", value: 1 },
          { source: "Woman2", target: "Valjean", value: 3 },
          { source: "Woman2", target: "Cosette", value: 1 },
          { source: "Woman2", target: "Javert", value: 1 },
          { source: "MotherInnocent", target: "Fauchelevent", value: 3 },
          { source: "MotherInnocent", target: "Valjean", value: 1 },
          { source: "Gribier", target: "Fauchelevent", value: 2 },
          { source: "Mme.Burgon", target: "Jondrette", value: 1 },
          { source: "Gavroche", target: "Mme.Burgon", value: 2 },
          { source: "Gavroche", target: "Thenardier", value: 1 },
          { source: "Gavroche", target: "Javert", value: 1 },
          { source: "Gavroche", target: "Valjean", value: 1 },
          { source: "Gillenormand", target: "Cosette", value: 3 },
          { source: "Gillenormand", target: "Valjean", value: 2 },
          { source: "Magnon", target: "Gillenormand", value: 1 },
          { source: "Magnon", target: "Mme.Thenardier", value: 1 },
          { source: "Mlle.Gillenormand", target: "Gillenormand", value: 9 },
          { source: "Mlle.Gillenormand", target: "Cosette", value: 2 },
          { source: "Mlle.Gillenormand", target: "Valjean", value: 2 },
          { source: "Mme.Pontmercy", target: "Mlle.Gillenormand", value: 1 },
          { source: "Mme.Pontmercy", target: "Pontmercy", value: 1 },
          { source: "Mlle.Vaubois", target: "Mlle.Gillenormand", value: 1 },
          { source: "Lt.Gillenormand", target: "Mlle.Gillenormand", value: 2 },
          { source: "Lt.Gillenormand", target: "Gillenormand", value: 1 },
          { source: "Lt.Gillenormand", target: "Cosette", value: 1 },
          { source: "Marius", target: "Mlle.Gillenormand", value: 6 },
          { source: "Marius", target: "Gillenormand", value: 12 },
          { source: "Marius", target: "Pontmercy", value: 1 },
          { source: "Marius", target: "Lt.Gillenormand", value: 1 },
          { source: "Marius", target: "Cosette", value: 21 },
          { source: "Marius", target: "Valjean", value: 19 },
          { source: "Marius", target: "Tholomyes", value: 1 },
          { source: "Marius", target: "Thenardier", value: 2 },
          { source: "Marius", target: "Eponine", value: 5 },
          { source: "Marius", target: "Gavroche", value: 4 },
          { source: "BaronessT", target: "Gillenormand", value: 1 },
          { source: "BaronessT", target: "Marius", value: 1 },
          { source: "Mabeuf", target: "Marius", value: 1 },
          { source: "Mabeuf", target: "Eponine", value: 1 },
          { source: "Mabeuf", target: "Gavroche", value: 1 },
          { source: "Enjolras", target: "Marius", value: 7 },
          { source: "Enjolras", target: "Gavroche", value: 7 },
          { source: "Enjolras", target: "Javert", value: 6 },
          { source: "Enjolras", target: "Mabeuf", value: 1 },
          { source: "Enjolras", target: "Valjean", value: 4 },
          { source: "Combeferre", target: "Enjolras", value: 15 },
          { source: "Combeferre", target: "Marius", value: 5 },
          { source: "Combeferre", target: "Gavroche", value: 6 },
          { source: "Combeferre", target: "Mabeuf", value: 2 },
          { source: "Prouvaire", target: "Gavroche", value: 1 },
          { source: "Prouvaire", target: "Enjolras", value: 4 },
          { source: "Prouvaire", target: "Combeferre", value: 2 },
          { source: "Feuilly", target: "Gavroche", value: 2 },
          { source: "Feuilly", target: "Enjolras", value: 6 },
          { source: "Feuilly", target: "Prouvaire", value: 2 },
          { source: "Feuilly", target: "Combeferre", value: 5 },
          { source: "Feuilly", target: "Mabeuf", value: 1 },
          { source: "Feuilly", target: "Marius", value: 1 },
          { source: "Courfeyrac", target: "Marius", value: 9 },
          { source: "Courfeyrac", target: "Enjolras", value: 17 },
          { source: "Courfeyrac", target: "Combeferre", value: 13 },
          { source: "Courfeyrac", target: "Gavroche", value: 7 },
          { source: "Courfeyrac", target: "Mabeuf", value: 2 },
          { source: "Courfeyrac", target: "Eponine", value: 1 },
          { source: "Courfeyrac", target: "Feuilly", value: 6 },
          { source: "Courfeyrac", target: "Prouvaire", value: 3 },
          { source: "Bahorel", target: "Combeferre", value: 5 },
          { source: "Bahorel", target: "Gavroche", value: 5 },
          { source: "Bahorel", target: "Courfeyrac", value: 6 },
          { source: "Bahorel", target: "Mabeuf", value: 2 },
          { source: "Bahorel", target: "Enjolras", value: 4 },
          { source: "Bahorel", target: "Feuilly", value: 3 },
          { source: "Bahorel", target: "Prouvaire", value: 2 },
          { source: "Bahorel", target: "Marius", value: 1 },
          { source: "Bossuet", target: "Marius", value: 5 },
          { source: "Bossuet", target: "Courfeyrac", value: 12 },
          { source: "Bossuet", target: "Gavroche", value: 5 },
          { source: "Bossuet", target: "Bahorel", value: 4 },
          { source: "Bossuet", target: "Enjolras", value: 10 },
          { source: "Bossuet", target: "Feuilly", value: 6 },
          { source: "Bossuet", target: "Prouvaire", value: 2 },
          { source: "Bossuet", target: "Combeferre", value: 9 },
          { source: "Bossuet", target: "Mabeuf", value: 1 },
          { source: "Bossuet", target: "Valjean", value: 1 },
          { source: "Joly", target: "Bahorel", value: 5 },
          { source: "Joly", target: "Bossuet", value: 7 },
          { source: "Joly", target: "Gavroche", value: 3 },
          { source: "Joly", target: "Courfeyrac", value: 5 },
          { source: "Joly", target: "Enjolras", value: 5 },
          { source: "Joly", target: "Feuilly", value: 5 },
          { source: "Joly", target: "Prouvaire", value: 2 },
          { source: "Joly", target: "Combeferre", value: 5 },
          { source: "Joly", target: "Mabeuf", value: 1 },
          { source: "Joly", target: "Marius", value: 2 },
          { source: "Grantaire", target: "Bossuet", value: 3 },
          { source: "Grantaire", target: "Enjolras", value: 3 },
          { source: "Grantaire", target: "Combeferre", value: 1 },
          { source: "Grantaire", target: "Courfeyrac", value: 2 },
          { source: "Grantaire", target: "Joly", value: 2 },
          { source: "Grantaire", target: "Gavroche", value: 1 },
          { source: "Grantaire", target: "Bahorel", value: 1 },
          { source: "Grantaire", target: "Feuilly", value: 1 },
          { source: "Grantaire", target: "Prouvaire", value: 1 },
          { source: "MotherPlutarch", target: "Mabeuf", value: 3 },
          { source: "Gueulemer", target: "Thenardier", value: 5 },
          { source: "Gueulemer", target: "Valjean", value: 1 },
          { source: "Gueulemer", target: "Mme.Thenardier", value: 1 },
          { source: "Gueulemer", target: "Javert", value: 1 },
          { source: "Gueulemer", target: "Gavroche", value: 1 },
          { source: "Gueulemer", target: "Eponine", value: 1 },
          { source: "Babet", target: "Thenardier", value: 6 },
          { source: "Babet", target: "Gueulemer", value: 6 },
          { source: "Babet", target: "Valjean", value: 1 },
          { source: "Babet", target: "Mme.Thenardier", value: 1 },
          { source: "Babet", target: "Javert", value: 2 },
          { source: "Babet", target: "Gavroche", value: 1 },
          { source: "Babet", target: "Eponine", value: 1 },
          { source: "Claquesous", target: "Thenardier", value: 4 },
          { source: "Claquesous", target: "Babet", value: 4 },
          { source: "Claquesous", target: "Gueulemer", value: 4 },
          { source: "Claquesous", target: "Valjean", value: 1 },
          { source: "Claquesous", target: "Mme.Thenardier", value: 1 },
          { source: "Claquesous", target: "Javert", value: 1 },
          { source: "Claquesous", target: "Eponine", value: 1 },
          { source: "Claquesous", target: "Enjolras", value: 1 },
          { source: "Montparnasse", target: "Javert", value: 1 },
          { source: "Montparnasse", target: "Babet", value: 2 },
          { source: "Montparnasse", target: "Gueulemer", value: 2 },
          { source: "Montparnasse", target: "Claquesous", value: 2 },
          { source: "Montparnasse", target: "Valjean", value: 1 },
          { source: "Montparnasse", target: "Gavroche", value: 1 },
          { source: "Montparnasse", target: "Eponine", value: 1 },
          { source: "Montparnasse", target: "Thenardier", value: 1 },
          { source: "Toussaint", target: "Cosette", value: 2 },
          { source: "Toussaint", target: "Javert", value: 1 },
          { source: "Toussaint", target: "Valjean", value: 1 },
          { source: "Child1", target: "Gavroche", value: 2 },
          { source: "Child2", target: "Gavroche", value: 2 },
          { source: "Child2", target: "Child1", value: 3 },
          { source: "Brujon", target: "Babet", value: 3 },
          { source: "Brujon", target: "Gueulemer", value: 3 },
          { source: "Brujon", target: "Thenardier", value: 3 },
          { source: "Brujon", target: "Gavroche", value: 1 },
          { source: "Brujon", target: "Eponine", value: 1 },
          { source: "Brujon", target: "Claquesous", value: 1 },
          { source: "Brujon", target: "Montparnasse", value: 1 },
          { source: "Mme.Hucheloup", target: "Bossuet", value: 1 },
          { source: "Mme.Hucheloup", target: "Joly", value: 1 },
          { source: "Mme.Hucheloup", target: "Grantaire", value: 1 },
          { source: "Mme.Hucheloup", target: "Bahorel", value: 1 },
          { source: "Mme.Hucheloup", target: "Courfeyrac", value: 1 },
          { source: "Mme.Hucheloup", target: "Gavroche", value: 1 },
          { source: "Mme.Hucheloup", target: "Enjolras", value: 1 },
        ],
      },
    };
  },
  mounted() {
    this.initGraph(this.testGraph);
  },
  methods: {
    initGraph(data) {
      const links = data.links.map((d) => Object.create(d));
      const nodes = data.nodes.map((d) => Object.create(d));

      const simulation = d3
        .forceSimulation(nodes)
        .force(
          "link",
          d3.forceLink(links).id((d) => d.id)
        )
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(this.width / 2, this.height / 2));

      const svg = d3
        .select(".container")
        .append("svg")
        .attr("viewBox", [0, 0, this.width, this.height]);
      svg.call(
        d3.zoom().on("zoom", function (d) {
          g.attr("transform", d.transform);
        })
      );
      const g =svg.append("g");
      const link = g
        .append("g")
        .attr("stroke", "#999")
        .attr("stroke-opacity", 0.6)
        .selectAll("line")
        .data(links)
        .join("line")
        .attr("stroke-width", (d) => Math.sqrt(d.value));

      const node = g
        .append("g")
        .attr("stroke", "#fff")
        .attr("stroke-width", 1.5)
        .selectAll("circle")
        .data(nodes)
        .join("circle")
        .attr("r", 25)
        .attr("fill", this.color)
        .call(this.drag(simulation));

      node.append("title").text((d) => d.id);

      simulation.on("tick", () => {
        link
          .attr("x1", (d) => d.source.x)
          .attr("y1", (d) => d.source.y)
          .attr("x2", (d) => d.target.x)
          .attr("y2", (d) => d.target.y);

        node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
      });
    },
    color(d) {
      return this.colorList[d.group];
    },
    drag(simulation) {
      function dragstarted(event) {
        if (!event.active) simulation.alphaTarget(0.3).restart();
        event.subject.fx = event.subject.x;
        event.subject.fy = event.subject.y;
      }

      function dragged(event) {
        event.subject.fx = event.x;
        event.subject.fy = event.y;
      }

      function dragended(event) {
        if (!event.active) simulation.alphaTarget(0);
        event.subject.fx = null;
        event.subject.fy = null;
      }

      return d3
        .drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended);
    },
  },
};
</script>

<style scoped>
.container {
  width: 1200px;
  height: 600px;
  border: 1px solid #2c3e50;
  border-radius: 8px;
  margin-top: 40px;
  margin-left: auto;
  margin-right: auto;
  background: #154360
    repeating-linear-gradient(
      30deg,
      hsla(0, 0%, 100%, 0.1),
      hsla(0, 0%, 100%, 0.1) 15px,
      transparent 0,
      transparent 30px
    );
}
.node {
  stroke: #fff;
  stroke-width: 1;
  cursor: pointer;
}
.node:hover {
  stroke-width: 5;
}
.nodeName {
  fill: white;
}
</style>

更复杂的老版本,有报错问题

<template>
<div>
  <div class="container"></div>
</div>
</template>

<script>
import * as d3 from "d3";

export default {
  data() {
    return {
      svgArea: null,
      links: [],
      nodes: [],
      nodesName: [],
      linksName: [],
      simulation: null,
      scale: 1,
      width: 1200,
      height: 600,
      colorList: [
        "#FD7623",
        "#3388B1",
        "#D82952",
        "#F3D737",
        "#409071",
        "#D64E52",
      ],
      testGraph: {
        nodes: [
          { id: "Myriel", group: 1 },
          { id: "Napoleon", group: 1 },
          { id: "Mlle.Baptistine", group: 1 },
          { id: "Mme.Magloire", group: 1 },
          { id: "CountessdeLo", group: 1 },
          { id: "Geborand", group: 1 },
          { id: "Champtercier", group: 1 },
          { id: "Cravatte", group: 1 },
          { id: "Count", group: 1 },
          { id: "OldMan", group: 1 },
          { id: "Labarre", group: 2 },
          { id: "Valjean", group: 2 },
          { id: "Marguerite", group: 3 },
          { id: "Mme.deR", group: 2 },
          { id: "Isabeau", group: 2 },
          { id: "Gervais", group: 2 },
          { id: "Tholomyes", group: 3 },
          { id: "Listolier", group: 3 },
          { id: "Fameuil", group: 3 },
          { id: "Blacheville", group: 3 },
          { id: "Favourite", group: 3 },
          { id: "Dahlia", group: 3 },
          { id: "Zephine", group: 3 },
          { id: "Fantine", group: 3 },
          { id: "Mme.Thenardier", group: 4 },
          { id: "Thenardier", group: 4 },
          { id: "Cosette", group: 5 },
          { id: "Javert", group: 4 },
          { id: "Fauchelevent", group: 0 },
          { id: "Bamatabois", group: 2 },
          { id: "Perpetue", group: 3 },
          { id: "Simplice", group: 2 },
          { id: "Scaufflaire", group: 2 },
          { id: "Woman1", group: 2 },
          { id: "Judge", group: 2 },
          { id: "Champmathieu", group: 2 },
          { id: "Brevet", group: 2 },
          { id: "Chenildieu", group: 2 },
          { id: "Cochepaille", group: 2 },
          { id: "Pontmercy", group: 4 },
          { id: "Boulatruelle", group: 6 },
          { id: "Eponine", group: 4 },
          { id: "Anzelma", group: 4 },
          { id: "Woman2", group: 5 },
          { id: "MotherInnocent", group: 0 },
          { id: "Gribier", group: 0 },
          { id: "Jondrette", group: 7 },
          { id: "Mme.Burgon", group: 7 },
          { id: "Gavroche", group: 8 },
          { id: "Gillenormand", group: 5 },
          { id: "Magnon", group: 5 },
          { id: "Mlle.Gillenormand", group: 5 },
          { id: "Mme.Pontmercy", group: 5 },
          { id: "Mlle.Vaubois", group: 5 },
          { id: "Lt.Gillenormand", group: 5 },
          { id: "Marius", group: 8 },
          { id: "BaronessT", group: 5 },
          { id: "Mabeuf", group: 8 },
          { id: "Enjolras", group: 8 },
          { id: "Combeferre", group: 8 },
          { id: "Prouvaire", group: 8 },
          { id: "Feuilly", group: 8 },
          { id: "Courfeyrac", group: 8 },
          { id: "Bahorel", group: 8 },
          { id: "Bossuet", group: 8 },
          { id: "Joly", group: 8 },
          { id: "Grantaire", group: 8 },
          { id: "MotherPlutarch", group: 9 },
          { id: "Gueulemer", group: 4 },
          { id: "Babet", group: 4 },
          { id: "Claquesous", group: 4 },
          { id: "Montparnasse", group: 4 },
          { id: "Toussaint", group: 5 },
          { id: "Child1", group: 10 },
          { id: "Child2", group: 10 },
          { id: "Brujon", group: 4 },
          { id: "Mme.Hucheloup", group: 8 },
        ],
        links: [
          { source: "Napoleon", target: "Myriel", value: 1 },
          { source: "Mlle.Baptistine", target: "Myriel", value: 8 },
          { source: "Mme.Magloire", target: "Myriel", value: 10 },
          { source: "Mme.Magloire", target: "Mlle.Baptistine", value: 6 },
          { source: "CountessdeLo", target: "Myriel", value: 1 },
          { source: "Geborand", target: "Myriel", value: 1 },
          { source: "Champtercier", target: "Myriel", value: 1 },
          { source: "Cravatte", target: "Myriel", value: 1 },
          { source: "Count", target: "Myriel", value: 2 },
          { source: "OldMan", target: "Myriel", value: 1 },
          { source: "Valjean", target: "Labarre", value: 1 },
          { source: "Valjean", target: "Mme.Magloire", value: 3 },
          { source: "Valjean", target: "Mlle.Baptistine", value: 3 },
          { source: "Valjean", target: "Myriel", value: 5 },
          { source: "Marguerite", target: "Valjean", value: 1 },
          { source: "Mme.deR", target: "Valjean", value: 1 },
          { source: "Isabeau", target: "Valjean", value: 1 },
          { source: "Gervais", target: "Valjean", value: 1 },
          { source: "Listolier", target: "Tholomyes", value: 4 },
          { source: "Fameuil", target: "Tholomyes", value: 4 },
          { source: "Fameuil", target: "Listolier", value: 4 },
          { source: "Blacheville", target: "Tholomyes", value: 4 },
          { source: "Blacheville", target: "Listolier", value: 4 },
          { source: "Blacheville", target: "Fameuil", value: 4 },
          { source: "Favourite", target: "Tholomyes", value: 3 },
          { source: "Favourite", target: "Listolier", value: 3 },
          { source: "Favourite", target: "Fameuil", value: 3 },
          { source: "Favourite", target: "Blacheville", value: 4 },
          { source: "Dahlia", target: "Tholomyes", value: 3 },
          { source: "Dahlia", target: "Listolier", value: 3 },
          { source: "Dahlia", target: "Fameuil", value: 3 },
          { source: "Dahlia", target: "Blacheville", value: 3 },
          { source: "Dahlia", target: "Favourite", value: 5 },
          { source: "Zephine", target: "Tholomyes", value: 3 },
          { source: "Zephine", target: "Listolier", value: 3 },
          { source: "Zephine", target: "Fameuil", value: 3 },
          { source: "Zephine", target: "Blacheville", value: 3 },
          { source: "Zephine", target: "Favourite", value: 4 },
          { source: "Zephine", target: "Dahlia", value: 4 },
          { source: "Fantine", target: "Tholomyes", value: 3 },
          { source: "Fantine", target: "Listolier", value: 3 },
          { source: "Fantine", target: "Fameuil", value: 3 },
          { source: "Fantine", target: "Blacheville", value: 3 },
          { source: "Fantine", target: "Favourite", value: 4 },
          { source: "Fantine", target: "Dahlia", value: 4 },
          { source: "Fantine", target: "Zephine", value: 4 },
          { source: "Fantine", target: "Marguerite", value: 2 },
          { source: "Fantine", target: "Valjean", value: 9 },
          { source: "Mme.Thenardier", target: "Fantine", value: 2 },
          { source: "Mme.Thenardier", target: "Valjean", value: 7 },
          { source: "Thenardier", target: "Mme.Thenardier", value: 13 },
          { source: "Thenardier", target: "Fantine", value: 1 },
          { source: "Thenardier", target: "Valjean", value: 12 },
          { source: "Cosette", target: "Mme.Thenardier", value: 4 },
          { source: "Cosette", target: "Valjean", value: 31 },
          { source: "Cosette", target: "Tholomyes", value: 1 },
          { source: "Cosette", target: "Thenardier", value: 1 },
          { source: "Javert", target: "Valjean", value: 17 },
          { source: "Javert", target: "Fantine", value: 5 },
          { source: "Javert", target: "Thenardier", value: 5 },
          { source: "Javert", target: "Mme.Thenardier", value: 1 },
          { source: "Javert", target: "Cosette", value: 1 },
          { source: "Fauchelevent", target: "Valjean", value: 8 },
          { source: "Fauchelevent", target: "Javert", value: 1 },
          { source: "Bamatabois", target: "Fantine", value: 1 },
          { source: "Bamatabois", target: "Javert", value: 1 },
          { source: "Bamatabois", target: "Valjean", value: 2 },
          { source: "Perpetue", target: "Fantine", value: 1 },
          { source: "Simplice", target: "Perpetue", value: 2 },
          { source: "Simplice", target: "Valjean", value: 3 },
          { source: "Simplice", target: "Fantine", value: 2 },
          { source: "Simplice", target: "Javert", value: 1 },
          { source: "Scaufflaire", target: "Valjean", value: 1 },
          { source: "Woman1", target: "Valjean", value: 2 },
          { source: "Woman1", target: "Javert", value: 1 },
          { source: "Judge", target: "Valjean", value: 3 },
          { source: "Judge", target: "Bamatabois", value: 2 },
          { source: "Champmathieu", target: "Valjean", value: 3 },
          { source: "Champmathieu", target: "Judge", value: 3 },
          { source: "Champmathieu", target: "Bamatabois", value: 2 },
          { source: "Brevet", target: "Judge", value: 2 },
          { source: "Brevet", target: "Champmathieu", value: 2 },
          { source: "Brevet", target: "Valjean", value: 2 },
          { source: "Brevet", target: "Bamatabois", value: 1 },
          { source: "Chenildieu", target: "Judge", value: 2 },
          { source: "Chenildieu", target: "Champmathieu", value: 2 },
          { source: "Chenildieu", target: "Brevet", value: 2 },
          { source: "Chenildieu", target: "Valjean", value: 2 },
          { source: "Chenildieu", target: "Bamatabois", value: 1 },
          { source: "Cochepaille", target: "Judge", value: 2 },
          { source: "Cochepaille", target: "Champmathieu", value: 2 },
          { source: "Cochepaille", target: "Brevet", value: 2 },
          { source: "Cochepaille", target: "Chenildieu", value: 2 },
          { source: "Cochepaille", target: "Valjean", value: 2 },
          { source: "Cochepaille", target: "Bamatabois", value: 1 },
          { source: "Pontmercy", target: "Thenardier", value: 1 },
          { source: "Boulatruelle", target: "Thenardier", value: 1 },
          { source: "Eponine", target: "Mme.Thenardier", value: 2 },
          { source: "Eponine", target: "Thenardier", value: 3 },
          { source: "Anzelma", target: "Eponine", value: 2 },
          { source: "Anzelma", target: "Thenardier", value: 2 },
          { source: "Anzelma", target: "Mme.Thenardier", value: 1 },
          { source: "Woman2", target: "Valjean", value: 3 },
          { source: "Woman2", target: "Cosette", value: 1 },
          { source: "Woman2", target: "Javert", value: 1 },
          { source: "MotherInnocent", target: "Fauchelevent", value: 3 },
          { source: "MotherInnocent", target: "Valjean", value: 1 },
          { source: "Gribier", target: "Fauchelevent", value: 2 },
          { source: "Mme.Burgon", target: "Jondrette", value: 1 },
          { source: "Gavroche", target: "Mme.Burgon", value: 2 },
          { source: "Gavroche", target: "Thenardier", value: 1 },
          { source: "Gavroche", target: "Javert", value: 1 },
          { source: "Gavroche", target: "Valjean", value: 1 },
          { source: "Gillenormand", target: "Cosette", value: 3 },
          { source: "Gillenormand", target: "Valjean", value: 2 },
          { source: "Magnon", target: "Gillenormand", value: 1 },
          { source: "Magnon", target: "Mme.Thenardier", value: 1 },
          { source: "Mlle.Gillenormand", target: "Gillenormand", value: 9 },
          { source: "Mlle.Gillenormand", target: "Cosette", value: 2 },
          { source: "Mlle.Gillenormand", target: "Valjean", value: 2 },
          { source: "Mme.Pontmercy", target: "Mlle.Gillenormand", value: 1 },
          { source: "Mme.Pontmercy", target: "Pontmercy", value: 1 },
          { source: "Mlle.Vaubois", target: "Mlle.Gillenormand", value: 1 },
          { source: "Lt.Gillenormand", target: "Mlle.Gillenormand", value: 2 },
          { source: "Lt.Gillenormand", target: "Gillenormand", value: 1 },
          { source: "Lt.Gillenormand", target: "Cosette", value: 1 },
          { source: "Marius", target: "Mlle.Gillenormand", value: 6 },
          { source: "Marius", target: "Gillenormand", value: 12 },
          { source: "Marius", target: "Pontmercy", value: 1 },
          { source: "Marius", target: "Lt.Gillenormand", value: 1 },
          { source: "Marius", target: "Cosette", value: 21 },
          { source: "Marius", target: "Valjean", value: 19 },
          { source: "Marius", target: "Tholomyes", value: 1 },
          { source: "Marius", target: "Thenardier", value: 2 },
          { source: "Marius", target: "Eponine", value: 5 },
          { source: "Marius", target: "Gavroche", value: 4 },
          { source: "BaronessT", target: "Gillenormand", value: 1 },
          { source: "BaronessT", target: "Marius", value: 1 },
          { source: "Mabeuf", target: "Marius", value: 1 },
          { source: "Mabeuf", target: "Eponine", value: 1 },
          { source: "Mabeuf", target: "Gavroche", value: 1 },
          { source: "Enjolras", target: "Marius", value: 7 },
          { source: "Enjolras", target: "Gavroche", value: 7 },
          { source: "Enjolras", target: "Javert", value: 6 },
          { source: "Enjolras", target: "Mabeuf", value: 1 },
          { source: "Enjolras", target: "Valjean", value: 4 },
          { source: "Combeferre", target: "Enjolras", value: 15 },
          { source: "Combeferre", target: "Marius", value: 5 },
          { source: "Combeferre", target: "Gavroche", value: 6 },
          { source: "Combeferre", target: "Mabeuf", value: 2 },
          { source: "Prouvaire", target: "Gavroche", value: 1 },
          { source: "Prouvaire", target: "Enjolras", value: 4 },
          { source: "Prouvaire", target: "Combeferre", value: 2 },
          { source: "Feuilly", target: "Gavroche", value: 2 },
          { source: "Feuilly", target: "Enjolras", value: 6 },
          { source: "Feuilly", target: "Prouvaire", value: 2 },
          { source: "Feuilly", target: "Combeferre", value: 5 },
          { source: "Feuilly", target: "Mabeuf", value: 1 },
          { source: "Feuilly", target: "Marius", value: 1 },
          { source: "Courfeyrac", target: "Marius", value: 9 },
          { source: "Courfeyrac", target: "Enjolras", value: 17 },
          { source: "Courfeyrac", target: "Combeferre", value: 13 },
          { source: "Courfeyrac", target: "Gavroche", value: 7 },
          { source: "Courfeyrac", target: "Mabeuf", value: 2 },
          { source: "Courfeyrac", target: "Eponine", value: 1 },
          { source: "Courfeyrac", target: "Feuilly", value: 6 },
          { source: "Courfeyrac", target: "Prouvaire", value: 3 },
          { source: "Bahorel", target: "Combeferre", value: 5 },
          { source: "Bahorel", target: "Gavroche", value: 5 },
          { source: "Bahorel", target: "Courfeyrac", value: 6 },
          { source: "Bahorel", target: "Mabeuf", value: 2 },
          { source: "Bahorel", target: "Enjolras", value: 4 },
          { source: "Bahorel", target: "Feuilly", value: 3 },
          { source: "Bahorel", target: "Prouvaire", value: 2 },
          { source: "Bahorel", target: "Marius", value: 1 },
          { source: "Bossuet", target: "Marius", value: 5 },
          { source: "Bossuet", target: "Courfeyrac", value: 12 },
          { source: "Bossuet", target: "Gavroche", value: 5 },
          { source: "Bossuet", target: "Bahorel", value: 4 },
          { source: "Bossuet", target: "Enjolras", value: 10 },
          { source: "Bossuet", target: "Feuilly", value: 6 },
          { source: "Bossuet", target: "Prouvaire", value: 2 },
          { source: "Bossuet", target: "Combeferre", value: 9 },
          { source: "Bossuet", target: "Mabeuf", value: 1 },
          { source: "Bossuet", target: "Valjean", value: 1 },
          { source: "Joly", target: "Bahorel", value: 5 },
          { source: "Joly", target: "Bossuet", value: 7 },
          { source: "Joly", target: "Gavroche", value: 3 },
          { source: "Joly", target: "Courfeyrac", value: 5 },
          { source: "Joly", target: "Enjolras", value: 5 },
          { source: "Joly", target: "Feuilly", value: 5 },
          { source: "Joly", target: "Prouvaire", value: 2 },
          { source: "Joly", target: "Combeferre", value: 5 },
          { source: "Joly", target: "Mabeuf", value: 1 },
          { source: "Joly", target: "Marius", value: 2 },
          { source: "Grantaire", target: "Bossuet", value: 3 },
          { source: "Grantaire", target: "Enjolras", value: 3 },
          { source: "Grantaire", target: "Combeferre", value: 1 },
          { source: "Grantaire", target: "Courfeyrac", value: 2 },
          { source: "Grantaire", target: "Joly", value: 2 },
          { source: "Grantaire", target: "Gavroche", value: 1 },
          { source: "Grantaire", target: "Bahorel", value: 1 },
          { source: "Grantaire", target: "Feuilly", value: 1 },
          { source: "Grantaire", target: "Prouvaire", value: 1 },
          { source: "MotherPlutarch", target: "Mabeuf", value: 3 },
          { source: "Gueulemer", target: "Thenardier", value: 5 },
          { source: "Gueulemer", target: "Valjean", value: 1 },
          { source: "Gueulemer", target: "Mme.Thenardier", value: 1 },
          { source: "Gueulemer", target: "Javert", value: 1 },
          { source: "Gueulemer", target: "Gavroche", value: 1 },
          { source: "Gueulemer", target: "Eponine", value: 1 },
          { source: "Babet", target: "Thenardier", value: 6 },
          { source: "Babet", target: "Gueulemer", value: 6 },
          { source: "Babet", target: "Valjean", value: 1 },
          { source: "Babet", target: "Mme.Thenardier", value: 1 },
          { source: "Babet", target: "Javert", value: 2 },
          { source: "Babet", target: "Gavroche", value: 1 },
          { source: "Babet", target: "Eponine", value: 1 },
          { source: "Claquesous", target: "Thenardier", value: 4 },
          { source: "Claquesous", target: "Babet", value: 4 },
          { source: "Claquesous", target: "Gueulemer", value: 4 },
          { source: "Claquesous", target: "Valjean", value: 1 },
          { source: "Claquesous", target: "Mme.Thenardier", value: 1 },
          { source: "Claquesous", target: "Javert", value: 1 },
          { source: "Claquesous", target: "Eponine", value: 1 },
          { source: "Claquesous", target: "Enjolras", value: 1 },
          { source: "Montparnasse", target: "Javert", value: 1 },
          { source: "Montparnasse", target: "Babet", value: 2 },
          { source: "Montparnasse", target: "Gueulemer", value: 2 },
          { source: "Montparnasse", target: "Claquesous", value: 2 },
          { source: "Montparnasse", target: "Valjean", value: 1 },
          { source: "Montparnasse", target: "Gavroche", value: 1 },
          { source: "Montparnasse", target: "Eponine", value: 1 },
          { source: "Montparnasse", target: "Thenardier", value: 1 },
          { source: "Toussaint", target: "Cosette", value: 2 },
          { source: "Toussaint", target: "Javert", value: 1 },
          { source: "Toussaint", target: "Valjean", value: 1 },
          { source: "Child1", target: "Gavroche", value: 2 },
          { source: "Child2", target: "Gavroche", value: 2 },
          { source: "Child2", target: "Child1", value: 3 },
          { source: "Brujon", target: "Babet", value: 3 },
          { source: "Brujon", target: "Gueulemer", value: 3 },
          { source: "Brujon", target: "Thenardier", value: 3 },
          { source: "Brujon", target: "Gavroche", value: 1 },
          { source: "Brujon", target: "Eponine", value: 1 },
          { source: "Brujon", target: "Claquesous", value: 1 },
          { source: "Brujon", target: "Montparnasse", value: 1 },
          { source: "Mme.Hucheloup", target: "Bossuet", value: 1 },
          { source: "Mme.Hucheloup", target: "Joly", value: 1 },
          { source: "Mme.Hucheloup", target: "Grantaire", value: 1 },
          { source: "Mme.Hucheloup", target: "Bahorel", value: 1 },
          { source: "Mme.Hucheloup", target: "Courfeyrac", value: 1 },
          { source: "Mme.Hucheloup", target: "Gavroche", value: 1 },
          { source: "Mme.Hucheloup", target: "Enjolras", value: 1 },
        ],
      },
    };
  },
  mounted() {
    this.initGraph(this.testGraph);
  },
  methods: {
    getGraphData() {
      var _this = this;
      this.axios
        .get("person/" + "Rob Reiner")
        //   this.axios.get("person/all")
        .then(function (response) {
          console.log(response);
          _this.testGraph["nodes"] = [response.data];
          _this.initGraph(_this.testGraph);
        })
        .catch(function (error) {
          console.log(error);
        });
    },
    initGraph(data) {
      const links = data.links;
      const nodes = data.nodes;
      this.simulation = d3
        .forceSimulation(nodes)
        .force(
          "link",
          d3
            .forceLink(links)
            .id((d) => d.id)
            .distance(150)
        )
        .force(
          "collide",
          d3.forceCollide().radius(() => 30)
        )
        .force("charge", d3.forceManyBody().strength(-10))
        .force("center", d3.forceCenter(this.width / 2, this.height / 2));
      this.svgArea = d3
        .select(".container")
        .append("svg")
        .attr("viewBox", [0, 0, this.width, this.height])
        .call(
          d3.zoom().on("zoom", function (d) {
            g.attr("transform", d.transform);
          })
        );
      this.addMarkers();
      const g = this.svgArea.append("g").attr("class", "content");
      this.links = g
        .append("g")
        .selectAll("path")
        .data(links, function (d) {
          if (typeof d.source === "object") {
            return d.source.id + "_" + d.relationship + "_" + d.target.id;
          } else {
            return d.source + "_" + d.relationship + "_" + d.target;
          }
        })
        .join("path")
        .attr("marker-end", "url(#positiveMarker)")
        .attr("stroke-width", (d) => Math.sqrt(d.value))
        .attr("class", "link")
        .attr("id", function (d) {
          if (typeof d.source === "object") {
            return d.source.id + "_" + d.relationship + "_" + d.target.id;
          } else {
            return d.source + "_" + d.relationship + "_" + d.target;
          }
        });
      this.linksName = g
        .append("g")
        .selectAll("text")
        .data(links, function (d) {
          if (typeof d.source === "object") {
            return d.source.id + "_" + d.relationship + "_" + d.target.id;
          } else {
            return d.source + "_" + d.relationship + "_" + d.target;
          }
        })
        .join("text")
        .style("text-anchor", "middle")
        .style("fill", "white")
        .style("font-size", "10px")
        .style("font-weight", "bold");
      this.linksName
        .append("textPath")
        .attr(
          "xlink:href",
          (d) => "#" + d.source + "_" + d.relationship + "_" + d.target
        )
        .attr("startOffset", "50%")
        .text((d) => d.relationship);
      this.nodes = g
        .append("g")
        .selectAll("circle")
        .data(nodes, (d) => d.id)
        .join("circle")
        .attr("r", 30)
        .attr("class", "node")
        .attr("fill", this.color)
        .on("click", this.select)
        .call(this.drag(this.simulation));
      this.nodes.append("title").text((d) => d.id);
      this.nodesName = g
        .append("g")
        .selectAll("text")
        .data(nodes)
        .join("text")
        .text((d) => d.id)
        .attr("dx", function () {
          return (this.getBoundingClientRect().width / 2) * -1;
        })
        .attr("dy", 50)
        .attr("class", "nodeName");
      this.simulation.on("tick", () => {
        this.links
          .attr("d", function (d) {
            if (d.source.x < d.target.x) {
              return (
                "M " +
                d.source.x +
                " " +
                d.source.y +
                " L " +
                d.target.x +
                " " +
                d.target.y
              );
            } else {
              return (
                "M " +
                d.target.x +
                " " +
                d.target.y +
                " L " +
                d.source.x +
                " " +
                d.source.y
              );
            }
          })
          .attr("marker-end", function (d) {
            if (d.source.x < d.target.x) {
              return "url(#positiveMarker)";
            } else {
              return null;
            }
          })
          .attr("marker-start", function (d) {
            if (d.source.x < d.target.x) {
              return null;
            } else {
              return "url(#negativeMarker)";
            }
          });
        this.nodes.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
        this.nodesName.attr("x", (d) => d.x).attr("y", (d) => d.y);
      });
    },
    addMarkers() {
      const positiveMarker = this.svgArea
        .append("marker")
        .attr("id", "positiveMarker")
        .attr("orient", "auto")
        .attr("stroke-width", 2)
        .attr("markerUnits", "strokeWidth")
        .attr("markerUnits", "userSpaceOnUse")
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 35)
        .attr("refY", 0)
        .attr("markerWidth", 12)
        .attr("markerHeight", 12)
        .append("path")
        .attr("d", "M 0 -5 L 10 0 L 0 5")
        .attr("fill", "#999")
        .attr("stroke-opacity", 0.6);
      const negativeMarker = this.svgArea
        .append("marker")
        .attr("id", "negativeMarker")
        .attr("orient", "auto")
        .attr("stroke-width", 2)
        .attr("markerUnits", "strokeWidth")
        .attr("markerUnits", "userSpaceOnUse")
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", -25)
        .attr("refY", 0)
        .attr("markerWidth", 12)
        .attr("markerHeight", 12)
        .append("path")
        .attr("d", "M 10 -5 L 0 0 L 10 5")
        .attr("fill", "#999")
        .attr("stroke-opacity", 0.6);
    },
    updateGraph(data) {
      const links = data.links;
      const nodes = data.nodes;
      this.links = this.links
        .data(links, function (d) {
          if (typeof d.source === "object") {
            return d.source.id + "_" + d.relationship + "_" + d.target.id;
          } else {
            return d.source + "_" + d.relationship + "_" + d.target;
          }
        })
        .join("path")
        .attr("stroke", "#999")
        .attr("stroke-opacity", 0.6)
        .attr("stroke-width", (d) => Math.sqrt(d.value))
        .attr("marker-end", "url(#positiveMarker)")
        .merge(this.links)
        .attr("id", function (d) {
          if (typeof d.source === "object") {
            return d.source.id + "_" + d.relationship + "_" + d.target.id;
          } else {
            return d.source + "_" + d.relationship + "_" + d.target;
          }
        })
        .attr("class", "link");
      this.linksName = this.linksName
        .data(links, function (d) {
          if (typeof d.source === "object") {
            return d.source.id + "_" + d.relationship + "_" + d.target.id;
          } else {
            return d.source + "_" + d.relationship + "_" + d.target;
          }
        })
        .join("text")
        .style("text-anchor", "middle")
        .style("fill", "white")
        .style("font-size", "10px")
        .style("font-weight", "bold");
      this.linksName
        .append("textPath")
        .attr("xlink:href", function (d) {
          if (typeof d.source === "object") {
            return "#" + d.source.id + "_" + d.relationship + "_" + d.target.id;
          } else {
            return "#" + d.source + "_" + d.relationship + "_" + d.target;
          }
        })
        .attr("startOffset", "50%")
        .merge(this.linksName)
        .text((d) => d.relationship);
      this.nodes = this.nodes
        .data(nodes, (d) => d.id)
        .join("circle")
        .attr("r", 30)
        .attr("class", "node")
        .attr("fill", this.color)
        .merge(this.nodes)
        .on("click", this.select)
        .call(this.drag(this.simulation));
      this.nodes.append("title").text((d) => d.id);
      this.nodesName = this.nodesName
        .data(nodes)
        .join("text")
        .merge(this.nodesName)
        .text(function (d) {
          return d.id;
        })
        .attr("dx", function () {
          return (this.getBoundingClientRect().width / 2) * -1;
        })
        .attr("dy", 50)
        .attr("class", "nodeName");
      this.simulation.nodes(nodes);
      this.simulation.force("link").links(links);
      this.simulation.alpha(0.2).restart();
    },
    color(d) {
      return this.colorList[d.group];
    },
    drag(simulation) {
      function dragstarted(d) {
        if (!d.active) simulation.alphaTarget(0.3).restart();
        d.fx = d.x;
        d.fy = d.y;
      }
      function dragged(d) {
        d.fx = d.x;
        d.fy = d.y;
      }
      function dragended(d) {
        if (!d.active) simulation.alphaTarget(0.5);
        d.fx = null;
        d.fy = null;
      }
      return d3
        .drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended);
    },
    select(d) {
      var _this = this;
      let data = {};
      for (var i in d.obj) {
        let ifArray = d.obj[i] instanceof Array;
        if (!ifArray) {
          data[i] = d.obj[i];
        }
      }
      _this.$refs.detailPanel.currentNode = data;
      _this.$refs.detailPanel.ifShow = true;
    },
    getQueryResult(result, currentNode, currentType) {
      for (var i = 0; i < result.length; i++) {
        //result:查询得到的节点组
        let flag = true;
        for (var j = 0; j < this.testGraph.nodes.length; j++) {
          if (this.testGraph.nodes[j].id === result[i].id) {
            flag = false;
          }
        }
        if (flag) {
          this.testGraph.nodes.push(result[i]);
        } else {
          console.log("已存在的节点");
          console.log(result[i]);
        }
        this.testGraph.links.push({
          source: currentNode.name,
          target: result[i].id,
          value: 5,
          relationship: currentType,
        });
      }
      for (var i = this.testGraph.links.length - 1; i >= 0; i--) {
        if (
          this.testGraph.links[i].source.id === currentNode.name &&
          this.testGraph.links[i].relationship !== currentType
        ) {
          let ifRemove = true;
          for (var k = 0; k < result.length; k++) {
            if (result[k].id === this.testGraph.links[i].target.id) {
              ifRemove = false;
              console.log("不移除此节点" + result[k].id);
              break;
            }
          }
          if (ifRemove) {
            console.log(this.testGraph.nodes);
            for (var j = this.testGraph.nodes.length - 1; j >= 0; j--) {
              console.log("移除此节点" + this.testGraph.links[i].target.id);
              if (
                this.testGraph.nodes[j].id === this.testGraph.links[i].target.id
              ) {
                this.testGraph.nodes.splice(j, 1);
              }
            }
            console.log(this.testGraph.nodes);
          }
          this.testGraph.links.splice(i, 1);
        }
      }
      this.updateGraph(this.testGraph);
    },
  },
};
</script>

<style scoped>
.container{
    width: 800px;
    height: 500px;
    border: 1px solid #2c3e50;
    border-radius:8px;
    margin-top: 40px;
    margin-left: auto;
    margin-right: auto;
    background: #154360 repeating-linear-gradient(30deg,
    hsla(0, 0%, 100%, .1), hsla(0, 0%, 100%, .1) 15px,
    transparent 0, transparent 30px);
  }
  .node{
    stroke:#fff;
    stroke-width:1;
    cursor: pointer;
  }
  .node:hover{
    stroke-width:5;
  }
  .nodeName{
    fill:white;
  }
</style>

react例子

import React from 'react'
import PropTypes from 'prop-types'
import * as d3 from 'd3'

class D3SimpleForceChart extends React.Component {
  componentDidMount() {
    const containerWidth = this.chartRef.parentElement.offsetWidth
    const data = this.props.data
    const margin = { top: 60, right: 60, bottom: 60, left: 60 }
    const width = containerWidth - margin.left - margin.right
    const height = 700 - margin.top - margin.bottom
    let chart = d3
      .select(this.chartRef)
      .attr('width', width + margin.left + margin.right)
      .attr('height', height + margin.top + margin.bottom)
    let g = chart
      .append('g')
      .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') // 设最外包层在总图上的相对位置
    let simulation = d3
      .forceSimulation() // 构建力导向图
      .force(
        'link',
        d3
          .forceLink()
          .id(function(d, i) {
            return i
          })
          .distance(function(d) {
            return d.value * 50
          })
      )
      .force('charge', d3.forceManyBody())
      .force('center', d3.forceCenter(width / 2, height / 2))

    let z = d3.scaleOrdinal(d3.schemeCategory20) // 通用线条的颜色

    let link = g
      .append('g') // 画连接线
      .attr('class', 'links')
      .selectAll('line')
      .data(data.edges)
      .enter()
      .append('line')

    let linkText = g
      .append('g') // 画连接连上面的关系文字
      .attr('class', 'link-text')
      .selectAll('text')
      .data(data.edges)
      .enter()
      .append('text')
      .text(function(d) {
        return d.relation
      })

    let node = g
      .append('g') // 画圆圈和文字
      .attr('class', 'nodes')
      .selectAll('g')
      .data(data.nodes)
      .enter()
      .append('g')
      .on('mouseover', function(d, i) {
        //显示连接线上的文字
        linkText.style('fill-opacity', function(edge) {
          if (edge.source === d || edge.target === d) {
            return 1
          }
        })
        //连接线加粗
        link
          .style('stroke-width', function(edge) {
            if (edge.source === d || edge.target === d) {
              return '2px'
            }
          })
          .style('stroke', function(edge) {
            if (edge.source === d || edge.target === d) {
              return '#000'
            }
          })
      })
      .on('mouseout', function(d, i) {
        //隐去连接线上的文字
        linkText.style('fill-opacity', function(edge) {
          if (edge.source === d || edge.target === d) {
            return 0
          }
        })
        //连接线减粗
        link
          .style('stroke-width', function(edge) {
            if (edge.source === d || edge.target === d) {
              return '1px'
            }
          })
          .style('stroke', function(edge) {
            if (edge.source === d || edge.target === d) {
              return '#ddd'
            }
          })
      })
      .call(
        d3
          .drag()
          .on('start', dragstarted)
          .on('drag', dragged)
          .on('end', dragended)
      )

    node
      .append('circle')
      .attr('r', 5)
      .attr('fill', function(d, i) {
        return z(i)
      })

    node
      .append('text')
      .attr('fill', function(d, i) {
        return z(i)
      })
      .attr('y', -20)
      .attr('dy', '.71em')
      .text(function(d) {
        return d.name
      })

    simulation // 初始化力导向图
      .nodes(data.nodes)
      .on('tick', ticked)

    simulation.force('link').links(data.edges)

    chart
      .append('g') // 输出标题
      .attr('class', 'bar--title')
      .append('text')
      .attr('fill', '#000')
      .attr('font-size', '16px')
      .attr('font-weight', '700')
      .attr('text-anchor', 'middle')
      .attr('x', containerWidth / 2)
      .attr('y', 20)
      .text('人物关系图')

    function ticked() {
      // 力导向图变化函数,让力学图不断更新
      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
        })
      linkText
        .attr('x', function(d) {
          return (d.source.x + d.target.x) / 2
        })
        .attr('y', function(d) {
          return (d.source.y + d.target.y) / 2
        })
      node.attr('transform', function(d) {
        return 'translate(' + d.x + ',' + d.y + ')'
      })
    }

    function dragstarted(d) {
      if (!d3.event.active) {
        simulation.alphaTarget(0.3).restart()
      }
      d.fx = d.x
      d.fy = d.y
    }

    function dragged(d) {
      d.fx = d3.event.x
      d.fy = d3.event.y
    }

    function dragended(d) {
      if (!d3.event.active) {
        simulation.alphaTarget(0)
      }
      d.fx = null
      d.fy = null
    }
  }
  render() {
    return (
      <div className="force-chart--simple">
        <svg ref={r => (this.chartRef = r)} />
      </div>
    )
  }
}
D3SimpleForceChart.propTypes = {
  data: PropTypes.shape({
    nodes: PropTypes.arrayOf(
      PropTypes.shape({
        name: PropTypes.string.isRequired
        // href:PropTypes.string.isRequired,
      }).isRequired
    ).isRequired,
    edges: PropTypes.arrayOf(
      PropTypes.shape({
        source: PropTypes.number.isRequired,
        target: PropTypes.number.isRequired,
        relation: PropTypes.string.isRequired
      }).isRequired
    ).isRequired
  }).isRequired
}

export default D3SimpleForceChart

 

 

html d3v5

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    .link-active {
      stroke-opacity: 1;
      stroke-width: 3;
    }
  </style>
  <script src="http://d3js.org/d3.v5.min.js"></script>
</head>

<body>
  <div style="border:1px solid #000;position: relative;">
    <svg width="960" height="500"></svg>
    <div
      style="width:100px;height:100px;border:1px solid red;position:absolute;top:2px;right:2px;background-color: azure;">
      <span id="xxx"></span>
    </div>
  </div>
</body>
<script type="text/javascript">

  let marge = { top: 60, bottom: 60, left: 60, right: 60 }
  let svg = d3.select('svg')
  let width = svg.attr('width')
  let height = svg.attr('height')
  svg.call(
    d3.zoom().on('zoom', function () {
      g.attr('transform', d3.event.transform)
    })
  )
    .on('dblclick.zoom', null)
  let g = svg.append('g')
    .attr('transform', 'translate(' + marge.top + ',' + marge.left + ')')
    .attr('class', 'container')
  // 准备数据
  // 节点集
  let nodes = [
    { id: 12, name: '湖南邵阳' },
    { id: 2, name: '山东泰安' },
    { id: 3, name: '广东阳江不知道怎么回' },
    { id: 4, name: '山西太原' },
    { id: 5, name: '亮' },
    { id: 6, name: '丽' },
    { id: 7, name: '雪' },
    { id: 8, name: '小明' },
    { id: 9, name: '组长' }
  ]
  // 边集
  let tempEdges = [
    { id: 1, source: 12, target: 5, relation: '籍贯', value: 1.3 },
    { id: 2, source: 5, target: 6, relation: '舍友', value: 1 },
    { id: 3, source: 5, target: 7, relation: '舍友', value: 1 },
    { id: 4, source: 5, target: 8, relation: '舍友', value: 1 },
    { id: 5, source: 2, target: 7, relation: '籍贯', value: 2 },
    { id: 6, source: 3, target: 6, relation: '籍贯', value: 0.9 },
    { id: 7, source: 4, target: 8, relation: '籍贯', value: 1 },
    { id: 8, source: 6, target: 7, relation: '同学', value: 1.6 },
    { id: 9, source: 7, target: 8, relation: '朋友', value: 0.7 },
    { id: 10, source: 7, target: 9, relation: '职责', value: 2 },
    { id: 11, source: 9, target: 7, relation: '人物', value: 2 },
    { id: 12, source: 9, target: 7, relation: '哈哈哈', value: 2 }
  ]
  nodes.forEach(item => {

  })
  // 生成 nodes map
  let nodesMap = genNodesMap(nodes);
  console.log('3333',nodesMap)
  nodesData = d3.values(nodesMap)
  let linkMap = genLinkMap(tempEdges)
  // 构建 links(source 属性必须从 0 开始)
  edges = genLinks(tempEdges);
  console.log('123123',edges,nodesData)
  // 设置一个颜色比例尺
  let colorScale = d3.scaleOrdinal()
    .domain(d3.range(nodesData.length))
    .range(d3.schemeCategory10)
  // 新建一个力导向图
  let forceSimulation = d3.forceSimulation()
    .force('link', d3.forceLink())
    .force('charge', d3.forceManyBody())
    .force('center', d3.forceCenter())
  // 生成节点数据
  forceSimulation.nodes(nodesData)
  // 生成边数据
  forceSimulation.force('link')
    .links(edges)
    .distance(function (d) { // 每一边的长度
      return d.value * 200
    })
  // 设置图形中心位置
  forceSimulation.force('center')
    .x(width / 2)
    .y(height / 2)
  // 箭头
  var marker = g.append('g').attr('class', 'showLine').append('marker')
    .attr('id', 'resolved')
    // .attr("markerUnits","strokeWidth")// 设置为strokeWidth箭头会随着线的粗细发生变化
    .attr('markerUnits', 'userSpaceOnUse')
    .attr('viewBox', '0 -5 10 10')// 坐标系的区域
    .attr('refX', 44)// 箭头坐标
    .attr('refY', 0)
    .attr('markerWidth', 10)// 标识的大小
    .attr('markerHeight', 10)
    .attr('orient', 'auto')// 绘制方向,可设定为:auto(自动确认方向)和 角度值
    .attr('stroke-width', 2)// 箭头宽度
    .append('path')
    .attr('d', 'M0,-5L10,0L0,5')// 箭头的路径
    .attr('fill', '#000000')// 箭头颜色
  // 绘制边
  let links = g.append('g').selectAll('path')
    .data(edges)
    .enter()
    .append('path')
    .attr('d', link => genLinkPath(link)) // 遍历所有数据。d表示当前遍历到的数据,返回绘制的贝塞尔曲线
    .attr('id', (d, i) => { return 'edgepath' + d.id }) // 设置id,用于连线文字
    .style('stroke', '#000') // 颜色
    .style('stroke-width', 2) // 粗细
    .attr('class', 'lines')
    .attr('marker-end', 'url(#resolved)') // 根据箭头标记的id号标记箭头
  // 边上的文字
  let linksText = g.append('g')
    .selectAll('text')
    .data(edges)
    .enter()
    .append('text')
    .attr('class', 'linksText')
    .text(function (d) {
      return d.relations
    })
    .style('font-size', 14)
    .attr('fill-opacity', 0)
  // 创建分组
  let gs = g.append('g')
    .selectAll('.circleText')
    .data(nodesData)
    .enter()
    .append('g')
    .attr('class', 'singleNode')
    .attr('id', function (d) {
      return 'singleNode' + d.id
    })
    .style('cursor', 'pointer')
    .attr('transform', function (d) {
      let cirX = d.x
      let cirY = d.y
      return 'translate(' + cirX + ',' + cirY + ')'
    })
  // 鼠标交互
  gs.on('mouseover', function (d, i) {
    // 显示连接线上的文字
    toggleLineText(d, true)
    toggleLine(links, d, true)
    toggleNode(gs, d, true)
  })
    .on('mouseout', function (d, i) {
      // 隐去连接线上的文字
      toggleLineText(d, false)
      toggleLine(links, d, false)
      toggleNode(gs, d, false)
    })
    .on('click', function (d, i) {
      linksText.style('fill-opacity', function (edge) {
        if (edge.source === d) {
          return 1
        }
      })
      toggleCircle(d3.select(this), d)
    }, true)
    .call(d3.drag()
      .on('start', started)
      .on('drag', dragged)
      .on('end', ended)
    )
  svg.on('click', function () {
    nodes.forEach(d => d.clickFlag = false)
    var event = d3.event
    var target = event.srcElement,   //  获取事件发生源
      data = d3.select(target).datum(); //  获取事件发生源的数据
    removeSingle()
    if (!data) {
      document.getElementById('xxx').innerText = ''
    }
  }, true)
  forceSimulation.on('tick', ticked)
  function toggleLineText(currNode, isHover) {
    if (isHover) {
      linksText.style('fill-opacity', function (edge) {
        if (edge.source === currNode) {
          return 1
        }
      })
    } else {
      linksText.style('fill-opacity', function (edge) {
        if (edge.source === currNode || edge.target === currNode) {
          return 0
        }
      })
    }
  }
  function toggleLine(linkLine, currNode, isHover) {
    if (isHover) {
      // 加重连线样式
      links
        .style('opacity', 0.1)
        .filter(link => isLinkLine(currNode, link))
        .style('opacity', 1)
        .classed('link-active', true)
    } else {
      links
        .style('opacity', 1)
        .classed('link-active', false)
    }
  }
  function showMyList() {
    var e = { id: 10, name: "河北" };
    var h = { id: 11, name: "河南" };
    var f = { id: 13, source: 9, target: 10, relation: '222', value: 2 };
    nodes.push(e);
    nodes.push(h);
    tempEdges.push(f);
    nodesMap = genNodesMap(nodes);
    nodesData = d3.values(nodesMap)
    linkMap = genLinkMap(tempEdges)
    edges = genLinks(tempEdges)
    updateData()
  }
  function updateData() {
    links = links
      .data(edges, function (d) {
      })
      .join("path")
      .attr('id', (d, i) => { return 'edgepath' + d.id }) // 设置id,用于连线文字
      .style('stroke', '#000') // 颜色
      .style('stroke-width', 2) // 粗细
      .attr('class', 'lines')
      .attr('marker-end', 'url(#resolved)') // 根据箭头标记的id号标记箭头
      .merge(links);
    linksText = linksText
      .data(edges)
      .join('text')
      .attr('class', 'linksText')
      .text(function (d) {
        return d.relations
      })
      .style('font-size', 14)
      .attr('fill-opacity', 0)
    gs = gs
      .data(nodesData, function (d) {
      })
      .join("g")
      .attr('class', 'singleNode')
      .attr('id', function (d) {
        return 'singleNode' + d.id
      })
      .style('cursor', 'pointer')
      .merge(gs)
      .call(d3.drag()
        .on("start", started)
        .on("drag", dragged)
        .on("end", ended));
    gs.append('circle')
      .attr('fill', function (d) {
        return 'orange'
      })
      .attr('r', 35)
      .attr('stroke', 'grey')
      .attr('stroke-width', 3)
    gs.on('mouseover', function (d, i) {
      // 显示连接线上的文字
      toggleLineText(d, true)
      toggleLine(links, d, true)
      toggleNode(gs, d, true)
    })
      .on('mouseout', function (d, i) {
        // 隐去连接线上的文字
        toggleLineText(d, false)
        toggleLine(links, d, false)
        toggleNode(gs, d, false)
      })
      .on('click', function (d, i) {
        linksText.style('fill-opacity', function (edge) {
          if (edge.source === d) {
            return 1
          }
        })
        toggleCircle(d3.select(this), d)
      }, true)
    gs.append('text')
      .attr('y', -20)
      .attr('dy', 10)
      .attr('text-anchor', 'middle')
      .style('font-size', 12)
      .attr('x', function ({ name }) {
        return textBreaking(d3.select(this), name)
      })
    gs.append('title')
      .text((node) => {
        return node.name
      })
    forceSimulation.nodes(nodesData);
    forceSimulation.force("link").links(edges);
    forceSimulation.alpha(0.8).restart();
  }
  function getLineTextAngle(d, bbox) {
    if (d.target.x < d.source.x) {
      const {
        x,
        y,
        width,
        height
      } = bbox;
      const rx = x + width / 2;
      const ry = y + height / 2;
      return 'rotate(180 ' + rx + ' ' + ry + ')';
    } else {
      return 'rotate(0)';
    }
  }
  function toggleNode(nodeCircle, currNode, isHover) {
    if (isHover) {
      // 提升节点层级 
      // nodeCircle.sort((a, b) => a.id === currNode.id ? 1 : -1);
      // this.parentNode.appendChild(this);
      nodeCircle
        .style('opacity', .1)
        .filter(node => isLinkNode(currNode, node))
        .style('opacity', 1);

    } else {
      nodeCircle.style('opacity', 1);
    }

  }
  function removeSingle() {
    d3.select('.singleCircle').remove()
  }
  function toggleCircle(current, d) {
    var currentD = d
    if (d.clickFlag) {
      removeSingle()
      document.getElementById('xxx').innerText = ''
    }
    d.clickFlag = true
    document.getElementById('xxx').innerText = d.name
    var data = [{
      population: 30,
      value: 'X',
      type: 'delete'
    }, {
      population: 30,
      value: '收起',
      type: 'showOn'
    }, {
      population: 30,
      value: '展开',
      type: 'showOff'
    }]
    var sum = d3.sum(data.map(function (d) {
      return d.population
    }))
    for (i in data) {
      data[i].Percentage = (data[i].population / sum * 100).toFixed(0) + "%";
    }
    var width = 300,
      height = 300,
      margin = { "left": 30, "top": 30, "right": 30, "bottom": 30 },
      svg_width = width + margin.left + margin.right,
      svg_height = height + margin.top + margin.bottom,
      font_size = 15;
    var g = current
      .append("g")
      .attr('class', 'singleCircle')
      .attr("width", width)
      .attr("height", height)
    var Pie = g.append("g")

    var arc_generator = d3.arc()
      .innerRadius(width / 6.5)
      .outerRadius(width / 4)
    var angle_data = d3.pie()
      .value(function (d) {
        return d.population;
      })
    var pieData = angle_data(data)
    var pieAngle = pieData.map(function (p) {
      return (p.startAngle + p.endAngle) / 2 / Math.PI * 180;
    });

    // var color=d3.schemeCategory10;

    //生成内部圆环
    Pie.selectAll("path")
      .data(angle_data(data))
      .enter()
      .append("path")
      .attr("d", arc_generator)
      .style("fill", function (d, i) {
        return 'grey';
      })
      .style('stroke', 'black')
      .attr("class", "path")
      .attr('type', function (d) {
        return d.data.type
      })
      .on('click', function (d) {
        if (d.data.type === 'delete') {
          deleteNode(currentD)
        } else if (d.data.type === 'showOn') {
          deleteNextNodes(currentD)
        } else {
          // showMyList()
        }
        d3.event.stopPropagation()
      })
    var arc_label = d3.arc()
      .innerRadius(width / 4)
      .outerRadius(width / 2)

    Pie.selectAll(".arc_label")
      .data(angle_data(data))
      .enter()
      .append("path")
      .attr("d", arc_label)
      .attr("class", "arc_label")
      .style("fill", "none")
    const labelFontSize = 12;
    const labelValRadius = (170 * 0.35 - labelFontSize * 0.35); // 计算正确半径 文字位置
    const labelValRadius1 = (170 * 0.35 + labelFontSize * 0.35);


    const labelsVals = current.select('.singleCircle').append('g')
      .classed('labelsvals', true);

    // 定义两条路径以使标签的方向正确
    labelsVals.append('def')
      .append('path')
      .attr('id', 'label-path-1')
      .attr('d', `m0 ${-labelValRadius} a${labelValRadius} ${labelValRadius} 0 1,1 -0.01 0`);
    labelsVals.append('def')
      .append('path')
      .attr('id', 'label-path-2')
      .attr('d', `m0 ${-labelValRadius1} a${labelValRadius1} ${labelValRadius1} 0 1,0 0.01 0`);

    labelsVals.selectAll('text')
      .data(data)
      .enter()
      .append('text')
      .style('font-size', labelFontSize)
      .style('fill', 'black')
      .style('font-weight', "bold")
      .style('text-anchor', 'middle')
      .append('textPath')
      .attr('href', function (d, i) {
        const p = pieData[i];
        const angle = pieAngle[i];
        if (angle > 90 && angle <= 270) { // 根据角度选择路径
          return '#label-path-2';
        } else {
          return '#label-path-1';
        }
      })
      .attr('startOffset', function (d, i) {
        const p = pieData[i];
        const angle = pieAngle[i];
        let percent = (p.startAngle + p.endAngle) / 2 / 2 / Math.PI * 100;
        if (angle > 90 && angle <= 270) { // 分别计算每条路径的正确百分比
          return 100 - percent + "%";
        }
        return percent + "%";
      })
      .text(function (d) {
        return d.value;
      })
      .on('click', function (d) {
        if (d.type === 'delete') {
          deleteNode(currentD)
        } else if (d.type === 'showOn') {
          deleteNextNodes(currentD)
        } else {
          // showMyList()
        }
        d3.event.stopPropagation()
      }, true)
  }
  //删除当前节点下一级没有其他关系的节点
  function deleteNextNodes(curr) {
    document.getElementById('xxx').innerText = '';
    // var removeIndex = nodesData.findIndex(node=>node.id == curr.id)
    // nodesData.splice(removeIndex,1)
    // nodes.splice(removeIndex,1)
    d3.select(this).remove();
    let relationNode = [],
      relationList = [],
      hasRelationList = []
    var clickNode = curr.id;//点击节点的名字
    d3.selectAll(".lines").each(function (e) {
      if (e.source.id == curr.id || e.target.id == curr.id) {
        hasRelationList.push(e)
      } else {
        relationList.push(e)//出去跟删除节点有关系的其他关系
      }
      //需要删除的节点相关的节点
      if (e.source.id == curr.id) {
        relationNode.push(e.target)
      }
      //需要删除的节点相关的节点
      if (e.target.id == curr.id) {
        relationNode.push(e.source)
      }
    })
    var tempNodeList = JSON.parse(JSON.stringify(relationNode))
    tempNodeList = uniqObjInArray(tempNodeList)
    //区分下级节点是否是孤节点
    tempNodeList.forEach(function (item, index) {
      var hasLine = relationList.findIndex(jtem => jtem.target.id == item.id || jtem.source.id == item.id)
      if (hasLine >= 0) {
        item.notSingle = true
      }
    })
    tempNodeList.forEach(function (item, index) {
      if (!item.notSingle) {
        d3.select('#singleNode' + item.id).remove()
      }
    })
    var otherTempNode = [];
    tempNodeList = tempNodeList.map(item => {
      if (!item.notSingle) {
        otherTempNode.push(item)
      }
    })
    hasRelationList.forEach(item => {
      otherTempNode.forEach(jtem => {
        if (jtem.id == item.source.id || jtem.id == item.target.id) {
          d3.select('#edgepath' + item.id).remove()
        }
      })
    })
    d3.selectAll(".singleNode").each(function (d, i) {
      var temp = d.id;
      //删除当前需要隐藏的节点
      if (temp == clickNode) {
        removeSingle()
      }
    });
    d3.selectAll(".linksText").each(function (e) {
      if (e.source === curr || e.target === curr) {
        d3.select(this).remove();
      }
    })
    gs.style('opacity', 1);
    links.style('opacity', 1)
      .classed('link-active', false);
  }
  //删除当前及下一级没有其他关系的节点
  function deleteNode(curr) {
    document.getElementById('xxx').innerText = '';
    var removeIndex = nodesData.findIndex(node => node.id == curr.id)
    nodesData.splice(removeIndex, 1)
    nodes.splice(removeIndex, 1)
    d3.select(this).remove();
    let relationNode = [],
      relationList = []
    var clickNode = curr.id;//点击节点的名字
    d3.selectAll(".lines").each(function (e) {
      if (e.source.id == curr.id || e.target.id == curr.id) {
        d3.select(this).remove();
      } else {
        relationList.push(e)//出去跟删除节点有关系的其他关系
      }
      //需要删除的节点相关的节点
      if (e.source.id == curr.id) {
        relationNode.push(e.target)
      }
      //需要删除的节点相关的节点
      if (e.target.id == curr.id) {
        relationNode.push(e.source)
      }
    })
    var tempNodeList = JSON.parse(JSON.stringify(relationNode))
    tempNodeList = uniqObjInArray(tempNodeList)
    //区分下级节点是否是孤节点
    tempNodeList.forEach(function (item, index) {
      var hasLine = relationList.findIndex(jtem => jtem.target.id == item.id || jtem.source.id == item.id)
      if (hasLine >= 0) {
        item.notSingle = true
      }
    })
    tempNodeList.forEach(function (item, index) {
      if (!item.notSingle) {
        d3.select('#singleNode' + item.id).remove()
      }
    })
    d3.selectAll(".singleNode").each(function (d, i) {
      var temp = d.id;
      //删除当前需要隐藏的节点
      if (temp == clickNode) {
        removeSingle()
        d3.select(this).remove();
      }
    });
    d3.selectAll(".linksText").each(function (e) {
      if (e.source === curr || e.target === curr) {
        d3.select(this).remove();
      }
    })
    gs.style('opacity', 1);
    links.style('opacity', 1)
      .classed('link-active', false);
  }
  // 关联节点去重重组
  function uniqObjInArray(objarray) {
    let len = objarray.length;
    let tempJson = {

    };
    let res = [];
    for (let i = 0; i < len; i++) {
      //取出每一个对象
      tempJson[JSON.stringify(objarray[i])] = true;
    }
    let keyItems = Object.keys(tempJson);
    for (let j = 0; j < keyItems.length; j++) {
      res.push(JSON.parse(keyItems[j]));
    }
    return res;
  }
  function isLinkLine(node, link) {
    return link.source.id === node.id
  }
  function isLinkNode(currNode, node) {
    if (currNode.id === node.id) {
      return true;
    }
    return linkMap[currNode.id + '-' + node.id] || linkMap[node.id + '-' + currNode.id];
  }
  function largerNode(nodes, currNode, isHover) {
    if (isHover) {
      gs
        .style('stroke-width', 1)
        .filter(node => isNode(currNode, node))
        .style('stroke-width', 10)
    } else {
      gs
        .style('stroke-width', 1)
    }
  }
  function isNode(node, cNode) {
    return true
  }
  // 绘制节点
  gs.append('circle')
    .attr('r', 35)
    .attr('id', function (d) {
      return 'circle' + d.id
    })
    .attr('fill', function (d, i) {
      return 'orange'
    })
    .attr('stroke', 'grey')
    .attr('stroke-width', 3)
  // 文字
  var nodeText = gs.append('text')
    // .attr('x', -10)
    .attr('y', -20)
    .attr('dy', 10)
    .attr('text-anchor', 'middle')
    .style('font-size', 12)
    .attr('x', function ({ name }) {
      return textBreaking(d3.select(this), name)
    })
  gs.append('title')
    .text((node) => {
      return node.name
    })
  function genLinkMap(relations) {
    const hash = {};
    relations.map(function ({
      source,
      target,
      relation
    }) {
      const key = source + '-' + target;
      if (hash[key]) {
        hash[key] += 1;
        hash[key + '-relation'] += '、' + relation;
      } else {
        hash[key] = 1;
        hash[key + '-relation'] = relation;
      }
    });
    return hash;
  }
  function genLinks(relations) {
    const indexHash = {};
    return relations.map(function ({
      id,
      source,
      target,
      relation,
      value
    }, i) {
      const linkKey = source + '-' + target;
      const count = linkMap[linkKey];
      if (indexHash[linkKey]) {
        indexHash[linkKey] -= 1;
      } else {
        indexHash[linkKey] = count - 1;
      }
      return {
        id,
        source: nodesMap[source],
        target: nodesMap[target],
        relation,
        value,
        relations: linkMap[linkKey + '-relation'],
        count: linkMap[linkKey],
        index: indexHash[linkKey]
      }
    })
  }
  // 生成关系连线路径
  function genLinkPath(link) {
    const count = link.count;
    const index = link.index;
    let sx = link.source.x;
    let tx = link.target.x;
    let sy = link.source.y;
    let ty = link.target.y;
    return 'M' + sx + ',' + sy + ' L' + tx + ',' + ty;
  }
  function genNodesMap(nodes) {
    const hash = {};
    nodes.map(function ({
      id,
      name
    }) {
      hash[id] = {
        id,
        name
      };
    });
    return hash;
  }
  // 处理节点文字换行
  function textBreaking(d3text, text) {
    const len = text.length
    if (len <= 3) {
      d3text.append('tspan')
        .attr('x', 0)
        .attr('y', -3)
        .text(text)
    } else {
      const topText = text.substring(0, 3)
      const midText = text.substring(3, 7)
      let botText = text.substring(7, len)
      let topY = -22
      let midY = 8
      let botY = 34
      if (len <= 9) {
        topY += 10
        midY += 10
      } else {
        botText = text.substring(7, 9) + '...'
      }
      d3text.text('')
      d3text.append('tspan')
        .attr('x', 0)
        .attr('y', topY)
        .text(function () {
          return topText
        })
      d3text.append('tspan')
        .attr('x', 0)
        .attr('y', midY)
        .text(function () {
          return midText
        })
      d3text.append('tspan')
        .attr('x', 0)
        .attr('y', botY - 7)
        .text(function () {
          return botText
        })
    }
  }
  // ticked
  function ticked() {
    // 连线路径
    links
      .attr('d', link => genLinkPath(link))
    // 连线文字位置
    linksText
      .attr('x', function (d) { return (d.source.x + d.target.x) / 2 })
      .attr('y', function (d) { return (d.source.y + d.target.y) / 2 })
    // 节点位置
    gs
      .attr('transform', function (d) { return 'translate(' + d.x + ',' + d.y + ')' })
  }
  // drag
  function started(d) {
    if (!d3.event.active) {
      forceSimulation.alphaTarget(0.8).restart() // 设置衰减系数,对节点位置移动过程的模拟,数值越高移动越快,数值范围[0, 1]
    }
    d.fx = d.x
    d.fy = d.y
  }
  function dragged(d) {
    d.fx = d3.event.x
    d.fy = d3.event.y
  }
  function ended(d) {
    if (!d3.event.active) {
      forceSimulation.alphaTarget(0)
    }
    d.fx = null
    d.fy = null
  }
</script>

</html>

 

d3关系图网址Force-Directed Graph  https://observablehq.com/@d3/force-directed-graph

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yusirxiaer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值