diff --git a/packages/g6/__tests__/dataset/relations.json b/packages/g6/__tests__/dataset/relations.json
new file mode 100644
index 00000000000..8d5f4160745
--- /dev/null
+++ b/packages/g6/__tests__/dataset/relations.json
@@ -0,0 +1,2269 @@
+{
+ "nodes": [
+ {
+ "id": "Myriel"
+ },
+ {
+ "id": "Napoleon"
+ },
+ {
+ "id": "Mlle.Baptistine"
+ },
+ {
+ "id": "Mme.Magloire"
+ },
+ {
+ "id": "CountessdeLo"
+ },
+ {
+ "id": "Geborand"
+ },
+ {
+ "id": "Champtercier"
+ },
+ {
+ "id": "Cravatte"
+ },
+ {
+ "id": "Count"
+ },
+ {
+ "id": "OldMan"
+ },
+ {
+ "id": "Labarre"
+ },
+ {
+ "id": "Valjean"
+ },
+ {
+ "id": "Marguerite"
+ },
+ {
+ "id": "Mme.deR"
+ },
+ {
+ "id": "Isabeau"
+ },
+ {
+ "id": "Gervais"
+ },
+ {
+ "id": "Tholomyes"
+ },
+ {
+ "id": "Listolier"
+ },
+ {
+ "id": "Fameuil"
+ },
+ {
+ "id": "Blacheville"
+ },
+ {
+ "id": "Favourite"
+ },
+ {
+ "id": "Dahlia"
+ },
+ {
+ "id": "Zephine"
+ },
+ {
+ "id": "Fantine"
+ },
+ {
+ "id": "Mme.Thenardier"
+ },
+ {
+ "id": "Thenardier"
+ },
+ {
+ "id": "Cosette"
+ },
+ {
+ "id": "Javert"
+ },
+ {
+ "id": "Fauchelevent"
+ },
+ {
+ "id": "Bamatabois"
+ },
+ {
+ "id": "Perpetue"
+ },
+ {
+ "id": "Simplice"
+ },
+ {
+ "id": "Scaufflaire"
+ },
+ {
+ "id": "Woman1"
+ },
+ {
+ "id": "Judge"
+ },
+ {
+ "id": "Champmathieu"
+ },
+ {
+ "id": "Brevet"
+ },
+ {
+ "id": "Chenildieu"
+ },
+ {
+ "id": "Cochepaille"
+ },
+ {
+ "id": "Pontmercy"
+ },
+ {
+ "id": "Boulatruelle"
+ },
+ {
+ "id": "Eponine"
+ },
+ {
+ "id": "Anzelma"
+ },
+ {
+ "id": "Woman2"
+ },
+ {
+ "id": "MotherInnocent"
+ },
+ {
+ "id": "Gribier"
+ },
+ {
+ "id": "Jondrette"
+ },
+ {
+ "id": "Mme.Burgon"
+ },
+ {
+ "id": "Gavroche"
+ },
+ {
+ "id": "Gillenormand"
+ },
+ {
+ "id": "Magnon"
+ },
+ {
+ "id": "Mlle.Gillenormand"
+ },
+ {
+ "id": "Mme.Pontmercy"
+ },
+ {
+ "id": "Mlle.Vaubois"
+ },
+ {
+ "id": "Lt.Gillenormand"
+ },
+ {
+ "id": "Marius"
+ },
+ {
+ "id": "BaronessT"
+ },
+ {
+ "id": "Mabeuf"
+ },
+ {
+ "id": "Enjolras"
+ },
+ {
+ "id": "Combeferre"
+ },
+ {
+ "id": "Prouvaire"
+ },
+ {
+ "id": "Feuilly"
+ },
+ {
+ "id": "Courfeyrac"
+ },
+ {
+ "id": "Bahorel"
+ },
+ {
+ "id": "Bossuet"
+ },
+ {
+ "id": "Joly"
+ },
+ {
+ "id": "Grantaire"
+ },
+ {
+ "id": "MotherPlutarch"
+ },
+ {
+ "id": "Gueulemer"
+ },
+ {
+ "id": "Babet"
+ },
+ {
+ "id": "Claquesous"
+ },
+ {
+ "id": "Montparnasse"
+ },
+ {
+ "id": "Toussaint"
+ },
+ {
+ "id": "Child1"
+ },
+ {
+ "id": "Child2"
+ },
+ {
+ "id": "Brujon"
+ },
+ {
+ "id": "Mme.Hucheloup"
+ }
+ ],
+ "edges": [
+ {
+ "id": "0",
+ "source": "Napoleon",
+ "target": "Myriel",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "1",
+ "source": "Mlle.Baptistine",
+ "target": "Myriel",
+ "data": {
+ "value": 8
+ }
+ },
+ {
+ "id": "2",
+ "source": "Mme.Magloire",
+ "target": "Myriel",
+ "data": {
+ "value": 10
+ }
+ },
+ {
+ "id": "3",
+ "source": "Mme.Magloire",
+ "target": "Mlle.Baptistine",
+ "data": {
+ "value": 6
+ }
+ },
+ {
+ "id": "4",
+ "source": "CountessdeLo",
+ "target": "Myriel",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "5",
+ "source": "Geborand",
+ "target": "Myriel",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "6",
+ "source": "Champtercier",
+ "target": "Myriel",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "7",
+ "source": "Cravatte",
+ "target": "Myriel",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "8",
+ "source": "Count",
+ "target": "Myriel",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "9",
+ "source": "OldMan",
+ "target": "Myriel",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "10",
+ "source": "Valjean",
+ "target": "Labarre",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "11",
+ "source": "Valjean",
+ "target": "Mme.Magloire",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "12",
+ "source": "Valjean",
+ "target": "Mlle.Baptistine",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "13",
+ "source": "Valjean",
+ "target": "Myriel",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "14",
+ "source": "Marguerite",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "15",
+ "source": "Mme.deR",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "16",
+ "source": "Isabeau",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "17",
+ "source": "Gervais",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "18",
+ "source": "Listolier",
+ "target": "Tholomyes",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "19",
+ "source": "Fameuil",
+ "target": "Tholomyes",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "20",
+ "source": "Fameuil",
+ "target": "Listolier",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "21",
+ "source": "Blacheville",
+ "target": "Tholomyes",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "22",
+ "source": "Blacheville",
+ "target": "Listolier",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "23",
+ "source": "Blacheville",
+ "target": "Fameuil",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "24",
+ "source": "Favourite",
+ "target": "Tholomyes",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "25",
+ "source": "Favourite",
+ "target": "Listolier",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "26",
+ "source": "Favourite",
+ "target": "Fameuil",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "27",
+ "source": "Favourite",
+ "target": "Blacheville",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "28",
+ "source": "Dahlia",
+ "target": "Tholomyes",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "29",
+ "source": "Dahlia",
+ "target": "Listolier",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "30",
+ "source": "Dahlia",
+ "target": "Fameuil",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "31",
+ "source": "Dahlia",
+ "target": "Blacheville",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "32",
+ "source": "Dahlia",
+ "target": "Favourite",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "33",
+ "source": "Zephine",
+ "target": "Tholomyes",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "34",
+ "source": "Zephine",
+ "target": "Listolier",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "35",
+ "source": "Zephine",
+ "target": "Fameuil",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "36",
+ "source": "Zephine",
+ "target": "Blacheville",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "37",
+ "source": "Zephine",
+ "target": "Favourite",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "38",
+ "source": "Zephine",
+ "target": "Dahlia",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "39",
+ "source": "Fantine",
+ "target": "Tholomyes",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "40",
+ "source": "Fantine",
+ "target": "Listolier",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "41",
+ "source": "Fantine",
+ "target": "Fameuil",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "42",
+ "source": "Fantine",
+ "target": "Blacheville",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "43",
+ "source": "Fantine",
+ "target": "Favourite",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "44",
+ "source": "Fantine",
+ "target": "Dahlia",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "45",
+ "source": "Fantine",
+ "target": "Zephine",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "46",
+ "source": "Fantine",
+ "target": "Marguerite",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "47",
+ "source": "Fantine",
+ "target": "Valjean",
+ "data": {
+ "value": 9
+ }
+ },
+ {
+ "id": "48",
+ "source": "Mme.Thenardier",
+ "target": "Fantine",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "49",
+ "source": "Mme.Thenardier",
+ "target": "Valjean",
+ "data": {
+ "value": 7
+ }
+ },
+ {
+ "id": "50",
+ "source": "Thenardier",
+ "target": "Mme.Thenardier",
+ "data": {
+ "value": 13
+ }
+ },
+ {
+ "id": "51",
+ "source": "Thenardier",
+ "target": "Fantine",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "52",
+ "source": "Thenardier",
+ "target": "Valjean",
+ "data": {
+ "value": 12
+ }
+ },
+ {
+ "id": "53",
+ "source": "Cosette",
+ "target": "Mme.Thenardier",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "54",
+ "source": "Cosette",
+ "target": "Valjean",
+ "data": {
+ "value": 31
+ }
+ },
+ {
+ "id": "55",
+ "source": "Cosette",
+ "target": "Tholomyes",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "56",
+ "source": "Cosette",
+ "target": "Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "57",
+ "source": "Javert",
+ "target": "Valjean",
+ "data": {
+ "value": 17
+ }
+ },
+ {
+ "id": "58",
+ "source": "Javert",
+ "target": "Fantine",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "59",
+ "source": "Javert",
+ "target": "Thenardier",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "60",
+ "source": "Javert",
+ "target": "Mme.Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "61",
+ "source": "Javert",
+ "target": "Cosette",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "62",
+ "source": "Fauchelevent",
+ "target": "Valjean",
+ "data": {
+ "value": 8
+ }
+ },
+ {
+ "id": "63",
+ "source": "Fauchelevent",
+ "target": "Javert",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "64",
+ "source": "Bamatabois",
+ "target": "Fantine",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "65",
+ "source": "Bamatabois",
+ "target": "Javert",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "66",
+ "source": "Bamatabois",
+ "target": "Valjean",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "67",
+ "source": "Perpetue",
+ "target": "Fantine",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "68",
+ "source": "Simplice",
+ "target": "Perpetue",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "69",
+ "source": "Simplice",
+ "target": "Valjean",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "70",
+ "source": "Simplice",
+ "target": "Fantine",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "71",
+ "source": "Simplice",
+ "target": "Javert",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "72",
+ "source": "Scaufflaire",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "73",
+ "source": "Woman1",
+ "target": "Valjean",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "74",
+ "source": "Woman1",
+ "target": "Javert",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "75",
+ "source": "Judge",
+ "target": "Valjean",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "76",
+ "source": "Judge",
+ "target": "Bamatabois",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "77",
+ "source": "Champmathieu",
+ "target": "Valjean",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "78",
+ "source": "Champmathieu",
+ "target": "Judge",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "79",
+ "source": "Champmathieu",
+ "target": "Bamatabois",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "80",
+ "source": "Brevet",
+ "target": "Judge",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "81",
+ "source": "Brevet",
+ "target": "Champmathieu",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "82",
+ "source": "Brevet",
+ "target": "Valjean",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "83",
+ "source": "Brevet",
+ "target": "Bamatabois",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "84",
+ "source": "Chenildieu",
+ "target": "Judge",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "85",
+ "source": "Chenildieu",
+ "target": "Champmathieu",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "86",
+ "source": "Chenildieu",
+ "target": "Brevet",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "87",
+ "source": "Chenildieu",
+ "target": "Valjean",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "88",
+ "source": "Chenildieu",
+ "target": "Bamatabois",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "89",
+ "source": "Cochepaille",
+ "target": "Judge",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "90",
+ "source": "Cochepaille",
+ "target": "Champmathieu",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "91",
+ "source": "Cochepaille",
+ "target": "Brevet",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "92",
+ "source": "Cochepaille",
+ "target": "Chenildieu",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "93",
+ "source": "Cochepaille",
+ "target": "Valjean",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "94",
+ "source": "Cochepaille",
+ "target": "Bamatabois",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "95",
+ "source": "Pontmercy",
+ "target": "Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "96",
+ "source": "Boulatruelle",
+ "target": "Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "97",
+ "source": "Eponine",
+ "target": "Mme.Thenardier",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "98",
+ "source": "Eponine",
+ "target": "Thenardier",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "99",
+ "source": "Anzelma",
+ "target": "Eponine",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "100",
+ "source": "Anzelma",
+ "target": "Thenardier",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "101",
+ "source": "Anzelma",
+ "target": "Mme.Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "102",
+ "source": "Woman2",
+ "target": "Valjean",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "103",
+ "source": "Woman2",
+ "target": "Cosette",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "104",
+ "source": "Woman2",
+ "target": "Javert",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "105",
+ "source": "MotherInnocent",
+ "target": "Fauchelevent",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "106",
+ "source": "MotherInnocent",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "107",
+ "source": "Gribier",
+ "target": "Fauchelevent",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "108",
+ "source": "Mme.Burgon",
+ "target": "Jondrette",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "109",
+ "source": "Gavroche",
+ "target": "Mme.Burgon",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "110",
+ "source": "Gavroche",
+ "target": "Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "111",
+ "source": "Gavroche",
+ "target": "Javert",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "112",
+ "source": "Gavroche",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "113",
+ "source": "Gillenormand",
+ "target": "Cosette",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "114",
+ "source": "Gillenormand",
+ "target": "Valjean",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "115",
+ "source": "Magnon",
+ "target": "Gillenormand",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "116",
+ "source": "Magnon",
+ "target": "Mme.Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "117",
+ "source": "Mlle.Gillenormand",
+ "target": "Gillenormand",
+ "data": {
+ "value": 9
+ }
+ },
+ {
+ "id": "118",
+ "source": "Mlle.Gillenormand",
+ "target": "Cosette",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "119",
+ "source": "Mlle.Gillenormand",
+ "target": "Valjean",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "120",
+ "source": "Mme.Pontmercy",
+ "target": "Mlle.Gillenormand",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "121",
+ "source": "Mme.Pontmercy",
+ "target": "Pontmercy",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "122",
+ "source": "Mlle.Vaubois",
+ "target": "Mlle.Gillenormand",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "123",
+ "source": "Lt.Gillenormand",
+ "target": "Mlle.Gillenormand",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "124",
+ "source": "Lt.Gillenormand",
+ "target": "Gillenormand",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "125",
+ "source": "Lt.Gillenormand",
+ "target": "Cosette",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "126",
+ "source": "Marius",
+ "target": "Mlle.Gillenormand",
+ "data": {
+ "value": 6
+ }
+ },
+ {
+ "id": "127",
+ "source": "Marius",
+ "target": "Gillenormand",
+ "data": {
+ "value": 12
+ }
+ },
+ {
+ "id": "128",
+ "source": "Marius",
+ "target": "Pontmercy",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "129",
+ "source": "Marius",
+ "target": "Lt.Gillenormand",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "130",
+ "source": "Marius",
+ "target": "Cosette",
+ "data": {
+ "value": 21
+ }
+ },
+ {
+ "id": "131",
+ "source": "Marius",
+ "target": "Valjean",
+ "data": {
+ "value": 19
+ }
+ },
+ {
+ "id": "132",
+ "source": "Marius",
+ "target": "Tholomyes",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "133",
+ "source": "Marius",
+ "target": "Thenardier",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "134",
+ "source": "Marius",
+ "target": "Eponine",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "135",
+ "source": "Marius",
+ "target": "Gavroche",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "136",
+ "source": "BaronessT",
+ "target": "Gillenormand",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "137",
+ "source": "BaronessT",
+ "target": "Marius",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "138",
+ "source": "Mabeuf",
+ "target": "Marius",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "139",
+ "source": "Mabeuf",
+ "target": "Eponine",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "140",
+ "source": "Mabeuf",
+ "target": "Gavroche",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "141",
+ "source": "Enjolras",
+ "target": "Marius",
+ "data": {
+ "value": 7
+ }
+ },
+ {
+ "id": "142",
+ "source": "Enjolras",
+ "target": "Gavroche",
+ "data": {
+ "value": 7
+ }
+ },
+ {
+ "id": "143",
+ "source": "Enjolras",
+ "target": "Javert",
+ "data": {
+ "value": 6
+ }
+ },
+ {
+ "id": "144",
+ "source": "Enjolras",
+ "target": "Mabeuf",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "145",
+ "source": "Enjolras",
+ "target": "Valjean",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "146",
+ "source": "Combeferre",
+ "target": "Enjolras",
+ "data": {
+ "value": 15
+ }
+ },
+ {
+ "id": "147",
+ "source": "Combeferre",
+ "target": "Marius",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "148",
+ "source": "Combeferre",
+ "target": "Gavroche",
+ "data": {
+ "value": 6
+ }
+ },
+ {
+ "id": "149",
+ "source": "Combeferre",
+ "target": "Mabeuf",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "150",
+ "source": "Prouvaire",
+ "target": "Gavroche",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "151",
+ "source": "Prouvaire",
+ "target": "Enjolras",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "152",
+ "source": "Prouvaire",
+ "target": "Combeferre",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "153",
+ "source": "Feuilly",
+ "target": "Gavroche",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "154",
+ "source": "Feuilly",
+ "target": "Enjolras",
+ "data": {
+ "value": 6
+ }
+ },
+ {
+ "id": "155",
+ "source": "Feuilly",
+ "target": "Prouvaire",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "156",
+ "source": "Feuilly",
+ "target": "Combeferre",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "157",
+ "source": "Feuilly",
+ "target": "Mabeuf",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "158",
+ "source": "Feuilly",
+ "target": "Marius",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "159",
+ "source": "Courfeyrac",
+ "target": "Marius",
+ "data": {
+ "value": 9
+ }
+ },
+ {
+ "id": "160",
+ "source": "Courfeyrac",
+ "target": "Enjolras",
+ "data": {
+ "value": 17
+ }
+ },
+ {
+ "id": "161",
+ "source": "Courfeyrac",
+ "target": "Combeferre",
+ "data": {
+ "value": 13
+ }
+ },
+ {
+ "id": "162",
+ "source": "Courfeyrac",
+ "target": "Gavroche",
+ "data": {
+ "value": 7
+ }
+ },
+ {
+ "id": "163",
+ "source": "Courfeyrac",
+ "target": "Mabeuf",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "164",
+ "source": "Courfeyrac",
+ "target": "Eponine",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "165",
+ "source": "Courfeyrac",
+ "target": "Feuilly",
+ "data": {
+ "value": 6
+ }
+ },
+ {
+ "id": "166",
+ "source": "Courfeyrac",
+ "target": "Prouvaire",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "167",
+ "source": "Bahorel",
+ "target": "Combeferre",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "168",
+ "source": "Bahorel",
+ "target": "Gavroche",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "169",
+ "source": "Bahorel",
+ "target": "Courfeyrac",
+ "data": {
+ "value": 6
+ }
+ },
+ {
+ "id": "170",
+ "source": "Bahorel",
+ "target": "Mabeuf",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "171",
+ "source": "Bahorel",
+ "target": "Enjolras",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "172",
+ "source": "Bahorel",
+ "target": "Feuilly",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "173",
+ "source": "Bahorel",
+ "target": "Prouvaire",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "174",
+ "source": "Bahorel",
+ "target": "Marius",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "175",
+ "source": "Bossuet",
+ "target": "Marius",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "176",
+ "source": "Bossuet",
+ "target": "Courfeyrac",
+ "data": {
+ "value": 12
+ }
+ },
+ {
+ "id": "177",
+ "source": "Bossuet",
+ "target": "Gavroche",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "178",
+ "source": "Bossuet",
+ "target": "Bahorel",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "179",
+ "source": "Bossuet",
+ "target": "Enjolras",
+ "data": {
+ "value": 10
+ }
+ },
+ {
+ "id": "180",
+ "source": "Bossuet",
+ "target": "Feuilly",
+ "data": {
+ "value": 6
+ }
+ },
+ {
+ "id": "181",
+ "source": "Bossuet",
+ "target": "Prouvaire",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "182",
+ "source": "Bossuet",
+ "target": "Combeferre",
+ "data": {
+ "value": 9
+ }
+ },
+ {
+ "id": "183",
+ "source": "Bossuet",
+ "target": "Mabeuf",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "184",
+ "source": "Bossuet",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "185",
+ "source": "Joly",
+ "target": "Bahorel",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "186",
+ "source": "Joly",
+ "target": "Bossuet",
+ "data": {
+ "value": 7
+ }
+ },
+ {
+ "id": "187",
+ "source": "Joly",
+ "target": "Gavroche",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "188",
+ "source": "Joly",
+ "target": "Courfeyrac",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "189",
+ "source": "Joly",
+ "target": "Enjolras",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "190",
+ "source": "Joly",
+ "target": "Feuilly",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "191",
+ "source": "Joly",
+ "target": "Prouvaire",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "192",
+ "source": "Joly",
+ "target": "Combeferre",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "193",
+ "source": "Joly",
+ "target": "Mabeuf",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "194",
+ "source": "Joly",
+ "target": "Marius",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "195",
+ "source": "Grantaire",
+ "target": "Bossuet",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "196",
+ "source": "Grantaire",
+ "target": "Enjolras",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "197",
+ "source": "Grantaire",
+ "target": "Combeferre",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "198",
+ "source": "Grantaire",
+ "target": "Courfeyrac",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "199",
+ "source": "Grantaire",
+ "target": "Joly",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "200",
+ "source": "Grantaire",
+ "target": "Gavroche",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "201",
+ "source": "Grantaire",
+ "target": "Bahorel",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "202",
+ "source": "Grantaire",
+ "target": "Feuilly",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "203",
+ "source": "Grantaire",
+ "target": "Prouvaire",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "204",
+ "source": "MotherPlutarch",
+ "target": "Mabeuf",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "205",
+ "source": "Gueulemer",
+ "target": "Thenardier",
+ "data": {
+ "value": 5
+ }
+ },
+ {
+ "id": "206",
+ "source": "Gueulemer",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "207",
+ "source": "Gueulemer",
+ "target": "Mme.Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "208",
+ "source": "Gueulemer",
+ "target": "Javert",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "209",
+ "source": "Gueulemer",
+ "target": "Gavroche",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "210",
+ "source": "Gueulemer",
+ "target": "Eponine",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "211",
+ "source": "Babet",
+ "target": "Thenardier",
+ "data": {
+ "value": 6
+ }
+ },
+ {
+ "id": "212",
+ "source": "Babet",
+ "target": "Gueulemer",
+ "data": {
+ "value": 6
+ }
+ },
+ {
+ "id": "213",
+ "source": "Babet",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "214",
+ "source": "Babet",
+ "target": "Mme.Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "215",
+ "source": "Babet",
+ "target": "Javert",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "216",
+ "source": "Babet",
+ "target": "Gavroche",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "217",
+ "source": "Babet",
+ "target": "Eponine",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "218",
+ "source": "Claquesous",
+ "target": "Thenardier",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "219",
+ "source": "Claquesous",
+ "target": "Babet",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "220",
+ "source": "Claquesous",
+ "target": "Gueulemer",
+ "data": {
+ "value": 4
+ }
+ },
+ {
+ "id": "221",
+ "source": "Claquesous",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "222",
+ "source": "Claquesous",
+ "target": "Mme.Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "223",
+ "source": "Claquesous",
+ "target": "Javert",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "224",
+ "source": "Claquesous",
+ "target": "Eponine",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "225",
+ "source": "Claquesous",
+ "target": "Enjolras",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "226",
+ "source": "Montparnasse",
+ "target": "Javert",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "227",
+ "source": "Montparnasse",
+ "target": "Babet",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "228",
+ "source": "Montparnasse",
+ "target": "Gueulemer",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "229",
+ "source": "Montparnasse",
+ "target": "Claquesous",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "230",
+ "source": "Montparnasse",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "231",
+ "source": "Montparnasse",
+ "target": "Gavroche",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "232",
+ "source": "Montparnasse",
+ "target": "Eponine",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "233",
+ "source": "Montparnasse",
+ "target": "Thenardier",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "234",
+ "source": "Toussaint",
+ "target": "Cosette",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "235",
+ "source": "Toussaint",
+ "target": "Javert",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "236",
+ "source": "Toussaint",
+ "target": "Valjean",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "237",
+ "source": "Child1",
+ "target": "Gavroche",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "238",
+ "source": "Child2",
+ "target": "Gavroche",
+ "data": {
+ "value": 2
+ }
+ },
+ {
+ "id": "239",
+ "source": "Child2",
+ "target": "Child1",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "240",
+ "source": "Brujon",
+ "target": "Babet",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "241",
+ "source": "Brujon",
+ "target": "Gueulemer",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "242",
+ "source": "Brujon",
+ "target": "Thenardier",
+ "data": {
+ "value": 3
+ }
+ },
+ {
+ "id": "243",
+ "source": "Brujon",
+ "target": "Gavroche",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "244",
+ "source": "Brujon",
+ "target": "Eponine",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "245",
+ "source": "Brujon",
+ "target": "Claquesous",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "246",
+ "source": "Brujon",
+ "target": "Montparnasse",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "247",
+ "source": "Mme.Hucheloup",
+ "target": "Bossuet",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "248",
+ "source": "Mme.Hucheloup",
+ "target": "Joly",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "249",
+ "source": "Mme.Hucheloup",
+ "target": "Grantaire",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "250",
+ "source": "Mme.Hucheloup",
+ "target": "Bahorel",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "251",
+ "source": "Mme.Hucheloup",
+ "target": "Courfeyrac",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "252",
+ "source": "Mme.Hucheloup",
+ "target": "Gavroche",
+ "data": {
+ "value": 1
+ }
+ },
+ {
+ "id": "253",
+ "source": "Mme.Hucheloup",
+ "target": "Enjolras",
+ "data": {
+ "value": 1
+ }
+ }
+ ]
+}
diff --git a/packages/g6/__tests__/demo/case/index.ts b/packages/g6/__tests__/demo/case/index.ts
index 40d7721bdb2..6e309b88fae 100644
--- a/packages/g6/__tests__/demo/case/index.ts
+++ b/packages/g6/__tests__/demo/case/index.ts
@@ -15,6 +15,9 @@ export * from './layout-circular-configuration-translate';
export * from './layout-circular-degree';
export * from './layout-circular-division';
export * from './layout-circular-spiral';
+export * from './layout-fruchterman-basic';
+export * from './layout-fruchterman-cluster';
+export * from './layout-fruchterman-fix';
export * from './layout-grid';
export * from './layout-mds';
export * from './layout-radial-basic';
diff --git a/packages/g6/__tests__/demo/case/layout-fruchterman-basic.ts b/packages/g6/__tests__/demo/case/layout-fruchterman-basic.ts
new file mode 100644
index 00000000000..9a60cd25bd2
--- /dev/null
+++ b/packages/g6/__tests__/demo/case/layout-fruchterman-basic.ts
@@ -0,0 +1,31 @@
+import { Graph } from '@/src';
+import data from '@@/dataset/cluster.json';
+import type { STDTestCase } from '../types';
+
+export const layoutFruchtermanBasic: STDTestCase = async (context) => {
+ const graph = new Graph({
+ ...context,
+ data,
+ behaviors: ['drag-canvas', 'drag-node'],
+ layout: {
+ type: 'fruchterman',
+ gravity: 5,
+ speed: 5,
+ },
+ node: {
+ style: {
+ size: 30,
+ stroke: '#5B8FF9',
+ fill: '#C6E5FF',
+ lineWidth: 1,
+ labelPlacement: 'center',
+ labelText: (d: any) => d.id,
+ labelBackground: false,
+ },
+ },
+ });
+
+ await graph.render();
+
+ return graph;
+};
diff --git a/packages/g6/__tests__/demo/case/layout-fruchterman-cluster.ts b/packages/g6/__tests__/demo/case/layout-fruchterman-cluster.ts
new file mode 100644
index 00000000000..8678000a907
--- /dev/null
+++ b/packages/g6/__tests__/demo/case/layout-fruchterman-cluster.ts
@@ -0,0 +1,39 @@
+import { Graph } from '@/src';
+import data from '@@/dataset/cluster.json';
+import type { STDTestCase } from '../types';
+
+export const layoutFruchtermanCluster: STDTestCase = async (context) => {
+ const graph = new Graph({
+ ...context,
+ data: { ...data, nodes: data.nodes.map((n) => ({ ...n, cluster: n.data.cluster })) },
+ behaviors: ['drag-canvas', 'drag-node'],
+ layout: {
+ type: 'fruchterman',
+ gravity: 10,
+ speed: 5,
+ clustering: true,
+ nodeClusterBy: 'cluster',
+ },
+ node: {
+ style: {
+ size: 20,
+ stroke: '#5B8FF9',
+ fill: '#C6E5FF',
+ lineWidth: 1,
+ labelPlacement: 'center',
+ labelText: (d: any) => d.id,
+ labelBackground: false,
+ },
+ },
+ edge: {
+ style: {
+ endArrow: true,
+ endArrowPath: 'M 0,0 L 4,2 L 4,-2 Z',
+ },
+ },
+ });
+
+ await graph.render();
+
+ return graph;
+};
diff --git a/packages/g6/__tests__/demo/case/layout-fruchterman-fix.ts b/packages/g6/__tests__/demo/case/layout-fruchterman-fix.ts
new file mode 100644
index 00000000000..f3bcd070eac
--- /dev/null
+++ b/packages/g6/__tests__/demo/case/layout-fruchterman-fix.ts
@@ -0,0 +1,42 @@
+import { Graph } from '@/src';
+import data from '@@/dataset/relations.json';
+import type { STDTestCase } from '../types';
+
+export const layoutFruchtermanFix: STDTestCase = async (context) => {
+ const graph = new Graph({
+ ...context,
+ data,
+ behaviors: ['drag-canvas', 'drag-node'],
+ layout: {
+ type: 'fruchterman',
+ speed: 10,
+ maxIteration: 500,
+ },
+ node: {
+ style: {
+ size: 15,
+ stroke: '#5B8FF9',
+ fill: '#C6E5FF',
+ lineWidth: 1,
+ },
+ },
+ edge: {
+ style: {
+ stroke: '#E2E2E2',
+ },
+ },
+ });
+
+ graph.on('node:dragstart', function () {
+ graph.stopLayout();
+ });
+
+ graph.on('node:dragend', function () {
+ // FIXME: 不应该完全重新布局,而是以当前画布数据进行布局
+ graph.layout();
+ });
+
+ await graph.render();
+
+ return graph;
+};
diff --git a/packages/g6/__tests__/snapshots/layouts/layout-circular-basic/layout-circular-basic.svg b/packages/g6/__tests__/snapshots/layouts/circular/layout-circular-basic.svg
similarity index 100%
rename from packages/g6/__tests__/snapshots/layouts/layout-circular-basic/layout-circular-basic.svg
rename to packages/g6/__tests__/snapshots/layouts/circular/layout-circular-basic.svg
diff --git a/packages/g6/__tests__/snapshots/layouts/layout-circular-configuration-translate/layout-circular-configuration-translate-division.svg b/packages/g6/__tests__/snapshots/layouts/circular/layout-circular-configuration-translate-division.svg
similarity index 100%
rename from packages/g6/__tests__/snapshots/layouts/layout-circular-configuration-translate/layout-circular-configuration-translate-division.svg
rename to packages/g6/__tests__/snapshots/layouts/circular/layout-circular-configuration-translate-division.svg
diff --git a/packages/g6/__tests__/snapshots/layouts/layout-circular-configuration-translate/layout-circular-configuration-translate.svg b/packages/g6/__tests__/snapshots/layouts/circular/layout-circular-configuration-translate.svg
similarity index 100%
rename from packages/g6/__tests__/snapshots/layouts/layout-circular-configuration-translate/layout-circular-configuration-translate.svg
rename to packages/g6/__tests__/snapshots/layouts/circular/layout-circular-configuration-translate.svg
diff --git a/packages/g6/__tests__/snapshots/layouts/layout-circular-degree/layout-circular-degree.svg b/packages/g6/__tests__/snapshots/layouts/circular/layout-circular-degree.svg
similarity index 100%
rename from packages/g6/__tests__/snapshots/layouts/layout-circular-degree/layout-circular-degree.svg
rename to packages/g6/__tests__/snapshots/layouts/circular/layout-circular-degree.svg
diff --git a/packages/g6/__tests__/snapshots/layouts/layout-circular-division/layout-circular-division.svg b/packages/g6/__tests__/snapshots/layouts/circular/layout-circular-division.svg
similarity index 100%
rename from packages/g6/__tests__/snapshots/layouts/layout-circular-division/layout-circular-division.svg
rename to packages/g6/__tests__/snapshots/layouts/circular/layout-circular-division.svg
diff --git a/packages/g6/__tests__/snapshots/layouts/layout-circular-spiral/layout-circular-spiral.svg b/packages/g6/__tests__/snapshots/layouts/circular/layout-circular-spiral.svg
similarity index 100%
rename from packages/g6/__tests__/snapshots/layouts/layout-circular-spiral/layout-circular-spiral.svg
rename to packages/g6/__tests__/snapshots/layouts/circular/layout-circular-spiral.svg
diff --git a/packages/g6/__tests__/snapshots/layouts/fruchterman/layout-fruchterman-basic.svg b/packages/g6/__tests__/snapshots/layouts/fruchterman/layout-fruchterman-basic.svg
new file mode 100644
index 00000000000..1bbe16d6a95
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/layouts/fruchterman/layout-fruchterman-basic.svg
@@ -0,0 +1,3009 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/snapshots/layouts/fruchterman/layout-fruchterman-cluster.svg b/packages/g6/__tests__/snapshots/layouts/fruchterman/layout-fruchterman-cluster.svg
new file mode 100644
index 00000000000..71bd1f2b250
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/layouts/fruchterman/layout-fruchterman-cluster.svg
@@ -0,0 +1,4453 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/unit/layouts/circular.spec.ts b/packages/g6/__tests__/unit/layouts/circular.spec.ts
new file mode 100644
index 00000000000..245037ec9c6
--- /dev/null
+++ b/packages/g6/__tests__/unit/layouts/circular.spec.ts
@@ -0,0 +1,52 @@
+import {
+ layoutCircularBasic,
+ layoutCircularConfigurationTranslate,
+ layoutCircularDegree,
+ layoutCircularDivision,
+ layoutCircularSpiral,
+} from '@@/demo/case';
+import { createDemoGraph } from '@@/utils';
+
+describe('layout circular', () => {
+ it('layoutCircularBasic', async () => {
+ const graph = await createDemoGraph(layoutCircularBasic);
+ await expect(graph).toMatchSnapshot(__filename, 'layout-circular-basic');
+ graph.destroy();
+ });
+
+ it('layoutCircularConfigurationTranslate', async () => {
+ const graph = await createDemoGraph(layoutCircularConfigurationTranslate);
+ await expect(graph).toMatchSnapshot(__filename, 'layout-circular-configuration-translate');
+
+ graph.setLayout({
+ type: 'circular',
+ radius: 200,
+ startAngle: Math.PI / 4,
+ endAngle: Math.PI,
+ divisions: 5,
+ ordering: 'degree',
+ }),
+ await graph.layout();
+
+ await expect(graph).toMatchSnapshot(__filename, 'layout-circular-configuration-translate-division');
+ graph.destroy();
+ });
+
+ it('layoutCircularDegree', async () => {
+ const graph = await createDemoGraph(layoutCircularDegree);
+ await expect(graph).toMatchSnapshot(__filename, 'layout-circular-degree');
+ graph.destroy();
+ });
+
+ it('layoutCircularDivision', async () => {
+ const graph = await createDemoGraph(layoutCircularDivision);
+ await expect(graph).toMatchSnapshot(__filename, 'layout-circular-division');
+ graph.destroy();
+ });
+
+ it('layoutCircularSpiral', async () => {
+ const graph = await createDemoGraph(layoutCircularSpiral);
+ await expect(graph).toMatchSnapshot(__filename, 'layout-circular-spiral');
+ graph.destroy();
+ });
+});
diff --git a/packages/g6/__tests__/unit/layouts/fruchterman.spec.ts b/packages/g6/__tests__/unit/layouts/fruchterman.spec.ts
new file mode 100644
index 00000000000..084ccc21d62
--- /dev/null
+++ b/packages/g6/__tests__/unit/layouts/fruchterman.spec.ts
@@ -0,0 +1,26 @@
+import { layoutFruchtermanBasic, layoutFruchtermanCluster } from '@@/demo/case';
+import { createDemoGraph } from '@@/utils';
+import { clear as clearMochRandom, mock as mockRandom } from 'jest-random-mock';
+
+describe('layout fruchterman', () => {
+ beforeAll(() => {
+ mockRandom();
+ });
+
+ afterAll(() => {
+ clearMochRandom();
+ });
+
+ it('basic', async () => {
+ const graph = await createDemoGraph(layoutFruchtermanBasic);
+ await expect(graph).toMatchSnapshot(__filename, 'layout-fruchterman-basic');
+
+ graph.destroy();
+ });
+
+ it('cluster', async () => {
+ const graph = await createDemoGraph(layoutFruchtermanCluster);
+ await expect(graph).toMatchSnapshot(__filename, 'layout-fruchterman-cluster');
+ graph.destroy();
+ });
+});
diff --git a/packages/g6/__tests__/unit/layouts/layout-circular-basic.spec.ts b/packages/g6/__tests__/unit/layouts/layout-circular-basic.spec.ts
deleted file mode 100644
index e016f78c853..00000000000
--- a/packages/g6/__tests__/unit/layouts/layout-circular-basic.spec.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Graph } from '@/src';
-import { layoutCircularBasic } from '@@/demo/case';
-import { createDemoGraph } from '@@/utils';
-
-describe('layout circular basic', () => {
- let graph: Graph;
-
- beforeAll(async () => {
- graph = await createDemoGraph(layoutCircularBasic);
- });
-
- afterAll(() => {
- graph.destroy();
- });
-
- it('render', async () => {
- await expect(graph).toMatchSnapshot(__filename, 'layout-circular-basic');
- });
-});
diff --git a/packages/g6/__tests__/unit/layouts/layout-circular-configuration-translate.spec.ts b/packages/g6/__tests__/unit/layouts/layout-circular-configuration-translate.spec.ts
deleted file mode 100644
index a8a9485bb79..00000000000
--- a/packages/g6/__tests__/unit/layouts/layout-circular-configuration-translate.spec.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { Graph } from '@/src';
-import { layoutCircularConfigurationTranslate } from '@@/demo/case';
-import { createDemoGraph } from '@@/utils';
-
-describe('layout circular configuration translate', () => {
- let graph: Graph;
-
- beforeAll(async () => {
- graph = await createDemoGraph(layoutCircularConfigurationTranslate);
- });
-
- afterAll(() => {
- graph.destroy();
- });
-
- it('render', async () => {
- await expect(graph).toMatchSnapshot(__filename, 'layout-circular-configuration-translate');
- });
-
- it('change layout', async () => {
- graph.setLayout({
- type: 'circular',
- radius: 200,
- startAngle: Math.PI / 4,
- endAngle: Math.PI,
- divisions: 5,
- ordering: 'degree',
- }),
- await graph.layout();
-
- await expect(graph).toMatchSnapshot(__filename, 'layout-circular-configuration-translate-division');
- });
-});
diff --git a/packages/g6/__tests__/unit/layouts/layout-circular-degree.spec.ts b/packages/g6/__tests__/unit/layouts/layout-circular-degree.spec.ts
deleted file mode 100644
index 7f4b3dcf06b..00000000000
--- a/packages/g6/__tests__/unit/layouts/layout-circular-degree.spec.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Graph } from '@/src';
-import { layoutCircularDegree } from '@@/demo/case';
-import { createDemoGraph } from '@@/utils';
-
-describe('layout circular degree', () => {
- let graph: Graph;
-
- beforeAll(async () => {
- graph = await createDemoGraph(layoutCircularDegree);
- });
-
- afterAll(() => {
- graph.destroy();
- });
-
- it('render', async () => {
- await expect(graph).toMatchSnapshot(__filename, 'layout-circular-degree');
- });
-});
diff --git a/packages/g6/__tests__/unit/layouts/layout-circular-division.spec.ts b/packages/g6/__tests__/unit/layouts/layout-circular-division.spec.ts
deleted file mode 100644
index 8b6568945a6..00000000000
--- a/packages/g6/__tests__/unit/layouts/layout-circular-division.spec.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Graph } from '@/src';
-import { layoutCircularDivision } from '@@/demo/case';
-import { createDemoGraph } from '@@/utils';
-
-describe('layout circular division', () => {
- let graph: Graph;
-
- beforeAll(async () => {
- graph = await createDemoGraph(layoutCircularDivision);
- });
-
- afterAll(() => {
- graph.destroy();
- });
-
- it('render', async () => {
- await expect(graph).toMatchSnapshot(__filename, 'layout-circular-division');
- });
-});
diff --git a/packages/g6/__tests__/unit/layouts/layout-circular-spiral.spec.ts b/packages/g6/__tests__/unit/layouts/layout-circular-spiral.spec.ts
deleted file mode 100644
index 4f64836125e..00000000000
--- a/packages/g6/__tests__/unit/layouts/layout-circular-spiral.spec.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Graph } from '@/src';
-import { layoutCircularSpiral } from '@@/demo/case';
-import { createDemoGraph } from '@@/utils';
-
-describe('layout circular spiral', () => {
- let graph: Graph;
-
- beforeAll(async () => {
- graph = await createDemoGraph(layoutCircularSpiral);
- });
-
- afterAll(() => {
- graph.destroy();
- });
-
- it('render', async () => {
- await expect(graph).toMatchSnapshot(__filename, 'layout-circular-spiral');
- });
-});
diff --git a/packages/g6/package.json b/packages/g6/package.json
index eb68a34876d..394aeddf546 100644
--- a/packages/g6/package.json
+++ b/packages/g6/package.json
@@ -67,6 +67,7 @@
"@antv/layout-gpu": "^1.1.5",
"@antv/layout-wasm": "^1.4.0",
"@types/xmlserializer": "^0.6.6",
+ "jest-random-mock": "^1.0.0",
"lil-gui": "^0.19.2",
"stats.js": "^0.17.0",
"vite": "^5.1.5",
diff --git a/packages/g6/src/runtime/graph.ts b/packages/g6/src/runtime/graph.ts
index 000f53ab941..a4e55054b82 100644
--- a/packages/g6/src/runtime/graph.ts
+++ b/packages/g6/src/runtime/graph.ts
@@ -374,6 +374,10 @@ export class Graph extends EventEmitter {
return this.context.layout!.layout();
}
+ public stopLayout() {
+ return this.context.layout!.stopLayout();
+ }
+
/**
* 清空画布元素
*
diff --git a/packages/site/examples/net/fruchtermanLayout/demo/basicFruchterman.js b/packages/site/examples/net/fruchtermanLayout/demo/basicFruchterman.js
deleted file mode 100644
index e99bce67d50..00000000000
--- a/packages/site/examples/net/fruchtermanLayout/demo/basicFruchterman.js
+++ /dev/null
@@ -1,505 +0,0 @@
-import { Graph, Extensions, extend } from '@antv/g6';
-
-const ExtGraph = extend(Graph, {
- layouts: {
- fruchterman: Extensions.FruchtermanLayout,
- },
-});
-
-const data = {
- nodes: [
- {
- id: '0',
- label: '0',
- cluster: 'a',
- },
- {
- id: '1',
- label: '1',
- cluster: 'a',
- },
- {
- id: '2',
- label: '2',
- cluster: 'a',
- },
- {
- id: '3',
- label: '3',
- cluster: 'a',
- },
- {
- id: '4',
- label: '4',
- cluster: 'a',
- },
- {
- id: '5',
- label: '5',
- cluster: 'a',
- },
- {
- id: '6',
- label: '6',
- cluster: 'a',
- },
- {
- id: '7',
- label: '7',
- cluster: 'a',
- },
- {
- id: '8',
- label: '8',
- cluster: 'a',
- },
- {
- id: '9',
- label: '9',
- cluster: 'a',
- },
- {
- id: '10',
- label: '10',
- cluster: 'a',
- },
- {
- id: '11',
- label: '11',
- cluster: 'a',
- },
- {
- id: '12',
- label: '12',
- cluster: 'a',
- },
- {
- id: '13',
- label: '13',
- cluster: 'b',
- },
- {
- id: '14',
- label: '14',
- cluster: 'b',
- },
- {
- id: '15',
- label: '15',
- cluster: 'b',
- },
- {
- id: '16',
- label: '16',
- cluster: 'b',
- },
- {
- id: '17',
- label: '17',
- cluster: 'b',
- },
- {
- id: '18',
- label: '18',
- cluster: 'c',
- },
- {
- id: '19',
- label: '19',
- cluster: 'c',
- },
- {
- id: '20',
- label: '20',
- cluster: 'c',
- },
- {
- id: '21',
- label: '21',
- cluster: 'c',
- },
- {
- id: '22',
- label: '22',
- cluster: 'c',
- },
- {
- id: '23',
- label: '23',
- cluster: 'c',
- },
- {
- id: '24',
- label: '24',
- cluster: 'c',
- },
- {
- id: '25',
- label: '25',
- cluster: 'c',
- },
- {
- id: '26',
- label: '26',
- cluster: 'c',
- },
- {
- id: '27',
- label: '27',
- cluster: 'c',
- },
- {
- id: '28',
- label: '28',
- cluster: 'c',
- },
- {
- id: '29',
- label: '29',
- cluster: 'c',
- },
- {
- id: '30',
- label: '30',
- cluster: 'c',
- },
- {
- id: '31',
- label: '31',
- cluster: 'd',
- },
- {
- id: '32',
- label: '32',
- cluster: 'd',
- },
- {
- id: '33',
- label: '33',
- cluster: 'd',
- },
- ],
- edges: [
- {
- source: '0',
- target: '1',
- },
- {
- source: '0',
- target: '2',
- },
- {
- source: '0',
- target: '3',
- },
- {
- source: '0',
- target: '4',
- },
- {
- source: '0',
- target: '5',
- },
- {
- source: '0',
- target: '7',
- },
- {
- source: '0',
- target: '8',
- },
- {
- source: '0',
- target: '9',
- },
- {
- source: '0',
- target: '10',
- },
- {
- source: '0',
- target: '11',
- },
- {
- source: '0',
- target: '13',
- },
- {
- source: '0',
- target: '14',
- },
- {
- source: '0',
- target: '15',
- },
- {
- source: '0',
- target: '16',
- },
- {
- source: '2',
- target: '3',
- },
- {
- source: '4',
- target: '5',
- },
- {
- source: '4',
- target: '6',
- },
- {
- source: '5',
- target: '6',
- },
- {
- source: '7',
- target: '13',
- },
- {
- source: '8',
- target: '14',
- },
- {
- source: '9',
- target: '10',
- },
- {
- source: '10',
- target: '22',
- },
- {
- source: '10',
- target: '14',
- },
- {
- source: '10',
- target: '12',
- },
- {
- source: '10',
- target: '24',
- },
- {
- source: '10',
- target: '21',
- },
- {
- source: '10',
- target: '20',
- },
- {
- source: '11',
- target: '24',
- },
- {
- source: '11',
- target: '22',
- },
- {
- source: '11',
- target: '14',
- },
- {
- source: '12',
- target: '13',
- },
- {
- source: '16',
- target: '17',
- },
- {
- source: '16',
- target: '18',
- },
- {
- source: '16',
- target: '21',
- },
- {
- source: '16',
- target: '22',
- },
- {
- source: '17',
- target: '18',
- },
- {
- source: '17',
- target: '20',
- },
- {
- source: '18',
- target: '19',
- },
- {
- source: '19',
- target: '20',
- },
- {
- source: '19',
- target: '33',
- },
- {
- source: '19',
- target: '22',
- },
- {
- source: '19',
- target: '23',
- },
- {
- source: '20',
- target: '21',
- },
- {
- source: '21',
- target: '22',
- },
- {
- source: '22',
- target: '24',
- },
- {
- source: '22',
- target: '25',
- },
- {
- source: '22',
- target: '26',
- },
- {
- source: '22',
- target: '23',
- },
- {
- source: '22',
- target: '28',
- },
- {
- source: '22',
- target: '30',
- },
- {
- source: '22',
- target: '31',
- },
- {
- source: '22',
- target: '32',
- },
- {
- source: '22',
- target: '33',
- },
- {
- source: '23',
- target: '28',
- },
- {
- source: '23',
- target: '27',
- },
- {
- source: '23',
- target: '29',
- },
- {
- source: '23',
- target: '30',
- },
- {
- source: '23',
- target: '31',
- },
- {
- source: '23',
- target: '33',
- },
- {
- source: '32',
- target: '33',
- },
- ],
-};
-
-const layoutConfigs = {
- Default: {
- type: 'fruchterman',
- gravity: 5,
- speed: 5,
- animated: true,
- clustering: false,
- },
- Clustering: {
- type: 'fruchterman',
- gravity: 5,
- speed: 5,
- animated: true,
- clustering: true,
- },
-};
-
-const container = document.getElementById('container');
-const width = container.scrollWidth;
-const height = container.scrollHeight || 500;
-const graph = new ExtGraph({
- container: 'container',
- width,
- height,
- transforms: [
- {
- type: 'transform-v4-data',
- activeLifecycle: ['read'],
- },
- ],
- modes: {
- default: ['drag-canvas', 'drag-node', 'click-select', 'zoom-canvas'],
- },
- theme: {
- type: 'spec',
- specification: {
- node: {
- dataTypeField: 'cluster',
- },
- },
- },
- layout: layoutConfigs.Default,
- node: {
- animates: {
- update: [
- {
- fields: ['opacity'],
- states: ['selected', 'active'],
- shapeId: 'haloShape',
- },
- {
- fields: ['lineWidth'],
- states: ['selected', 'active'],
- shapeId: 'keyShape',
- },
- ],
- },
- },
- data,
-});
-
-window.graph = graph;
-const btnContainer = document.createElement('div');
-btnContainer.style.position = 'absolute';
-container.appendChild(btnContainer);
-const tip = document.createElement('span');
-tip.innerHTML = '👉 Change configs:';
-btnContainer.appendChild(tip);
-
-Object.keys(layoutConfigs).forEach((name, i) => {
- const btn = document.createElement('a');
- btn.innerHTML = name;
- btn.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
- btn.style.padding = '4px';
- btn.style.marginLeft = i > 0 ? '24px' : '8px';
- btnContainer.appendChild(btn);
- btn.addEventListener('click', () => {
- graph.layout(layoutConfigs[name]);
- });
-});
diff --git a/packages/site/examples/net/fruchtermanLayout/demo/basicFruchterman.ts b/packages/site/examples/net/fruchtermanLayout/demo/basicFruchterman.ts
new file mode 100644
index 00000000000..cce6708c8dc
--- /dev/null
+++ b/packages/site/examples/net/fruchtermanLayout/demo/basicFruchterman.ts
@@ -0,0 +1,129 @@
+import { Graph } from '@antv/g6';
+
+const data = {
+ nodes: [
+ { id: '0', data: { cluster: 'a' } },
+ { id: '1', data: { cluster: 'a' } },
+ { id: '2', data: { cluster: 'a' } },
+ { id: '3', data: { cluster: 'a' } },
+ { id: '4', data: { cluster: 'a' } },
+ { id: '5', data: { cluster: 'a' } },
+ { id: '6', data: { cluster: 'a' } },
+ { id: '7', data: { cluster: 'a' } },
+ { id: '8', data: { cluster: 'a' } },
+ { id: '9', data: { cluster: 'a' } },
+ { id: '10', data: { cluster: 'a' } },
+ { id: '11', data: { cluster: 'a' } },
+ { id: '12', data: { cluster: 'a' } },
+ { id: '13', data: { cluster: 'b' } },
+ { id: '14', data: { cluster: 'b' } },
+ { id: '15', data: { cluster: 'b' } },
+ { id: '16', data: { cluster: 'b' } },
+ { id: '17', data: { cluster: 'b' } },
+ { id: '18', data: { cluster: 'c' } },
+ { id: '19', data: { cluster: 'c' } },
+ { id: '20', data: { cluster: 'c' } },
+ { id: '21', data: { cluster: 'c' } },
+ { id: '22', data: { cluster: 'c' } },
+ { id: '23', data: { cluster: 'c' } },
+ { id: '24', data: { cluster: 'c' } },
+ { id: '25', data: { cluster: 'c' } },
+ { id: '26', data: { cluster: 'c' } },
+ { id: '27', data: { cluster: 'c' } },
+ { id: '28', data: { cluster: 'c' } },
+ { id: '29', data: { cluster: 'c' } },
+ { id: '30', data: { cluster: 'c' } },
+ { id: '31', data: { cluster: 'd' } },
+ { id: '32', data: { cluster: 'd' } },
+ { id: '33', data: { cluster: 'd' } },
+ ],
+ edges: [
+ { source: '0', target: '1' },
+ { source: '0', target: '2' },
+ { source: '0', target: '3' },
+ { source: '0', target: '4' },
+ { source: '0', target: '5' },
+ { source: '0', target: '7' },
+ { source: '0', target: '8' },
+ { source: '0', target: '9' },
+ { source: '0', target: '10' },
+ { source: '0', target: '11' },
+ { source: '0', target: '13' },
+ { source: '0', target: '14' },
+ { source: '0', target: '15' },
+ { source: '0', target: '16' },
+ { source: '2', target: '3' },
+ { source: '4', target: '5' },
+ { source: '4', target: '6' },
+ { source: '5', target: '6' },
+ { source: '7', target: '13' },
+ { source: '8', target: '14' },
+ { source: '9', target: '10' },
+ { source: '10', target: '22' },
+ { source: '10', target: '14' },
+ { source: '10', target: '12' },
+ { source: '10', target: '24' },
+ { source: '10', target: '21' },
+ { source: '10', target: '20' },
+ { source: '11', target: '24' },
+ { source: '11', target: '22' },
+ { source: '11', target: '14' },
+ { source: '12', target: '13' },
+ { source: '16', target: '17' },
+ { source: '16', target: '18' },
+ { source: '16', target: '21' },
+ { source: '16', target: '22' },
+ { source: '17', target: '18' },
+ { source: '17', target: '20' },
+ { source: '18', target: '19' },
+ { source: '19', target: '20' },
+ { source: '19', target: '33' },
+ { source: '19', target: '22' },
+ { source: '19', target: '23' },
+ { source: '20', target: '21' },
+ { source: '21', target: '22' },
+ { source: '22', target: '24' },
+ { source: '22', target: '25' },
+ { source: '22', target: '26' },
+ { source: '22', target: '23' },
+ { source: '22', target: '28' },
+ { source: '22', target: '30' },
+ { source: '22', target: '31' },
+ { source: '22', target: '32' },
+ { source: '22', target: '33' },
+ { source: '23', target: '28' },
+ { source: '23', target: '27' },
+ { source: '23', target: '29' },
+ { source: '23', target: '30' },
+ { source: '23', target: '31' },
+ { source: '23', target: '33' },
+ { source: '32', target: '33' },
+ ],
+};
+
+const graph = new Graph({
+ container: 'container',
+ data,
+ layout: {
+ type: 'fruchterman',
+ gravity: 5,
+ speed: 5,
+ animated: true,
+ clustering: false,
+ },
+ node: {
+ style: {
+ size: 20,
+ stroke: '#5B8FF9',
+ fill: '#C6E5FF',
+ lineWidth: 1,
+ labelPlacement: 'center',
+ labelText: (d) => d.id,
+ labelBackground: false,
+ },
+ },
+ animation: true,
+ behaviors: ['drag-canvas', 'drag-node'],
+});
+
+graph.render();
diff --git a/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanCluster.ts b/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanCluster.ts
new file mode 100644
index 00000000000..46f571d798b
--- /dev/null
+++ b/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanCluster.ts
@@ -0,0 +1,135 @@
+import { Graph } from '@antv/g6';
+
+const data = {
+ nodes: [
+ { id: '0', data: { cluster: 'a' } },
+ { id: '1', data: { cluster: 'a' } },
+ { id: '2', data: { cluster: 'a' } },
+ { id: '3', data: { cluster: 'a' } },
+ { id: '4', data: { cluster: 'a' } },
+ { id: '5', data: { cluster: 'a' } },
+ { id: '6', data: { cluster: 'a' } },
+ { id: '7', data: { cluster: 'a' } },
+ { id: '8', data: { cluster: 'a' } },
+ { id: '9', data: { cluster: 'a' } },
+ { id: '10', data: { cluster: 'a' } },
+ { id: '11', data: { cluster: 'a' } },
+ { id: '12', data: { cluster: 'a' } },
+ { id: '13', data: { cluster: 'b' } },
+ { id: '14', data: { cluster: 'b' } },
+ { id: '15', data: { cluster: 'b' } },
+ { id: '16', data: { cluster: 'b' } },
+ { id: '17', data: { cluster: 'b' } },
+ { id: '18', data: { cluster: 'c' } },
+ { id: '19', data: { cluster: 'c' } },
+ { id: '20', data: { cluster: 'c' } },
+ { id: '21', data: { cluster: 'c' } },
+ { id: '22', data: { cluster: 'c' } },
+ { id: '23', data: { cluster: 'c' } },
+ { id: '24', data: { cluster: 'c' } },
+ { id: '25', data: { cluster: 'c' } },
+ { id: '26', data: { cluster: 'c' } },
+ { id: '27', data: { cluster: 'c' } },
+ { id: '28', data: { cluster: 'c' } },
+ { id: '29', data: { cluster: 'c' } },
+ { id: '30', data: { cluster: 'c' } },
+ { id: '31', data: { cluster: 'd' } },
+ { id: '32', data: { cluster: 'd' } },
+ { id: '33', data: { cluster: 'd' } },
+ ],
+ edges: [
+ { source: '0', target: '1' },
+ { source: '0', target: '2' },
+ { source: '0', target: '3' },
+ { source: '0', target: '4' },
+ { source: '0', target: '5' },
+ { source: '0', target: '7' },
+ { source: '0', target: '8' },
+ { source: '0', target: '9' },
+ { source: '0', target: '10' },
+ { source: '0', target: '11' },
+ { source: '0', target: '13' },
+ { source: '0', target: '14' },
+ { source: '0', target: '15' },
+ { source: '0', target: '16' },
+ { source: '2', target: '3' },
+ { source: '4', target: '5' },
+ { source: '4', target: '6' },
+ { source: '5', target: '6' },
+ { source: '7', target: '13' },
+ { source: '8', target: '14' },
+ { source: '9', target: '10' },
+ { source: '10', target: '22' },
+ { source: '10', target: '14' },
+ { source: '10', target: '12' },
+ { source: '10', target: '24' },
+ { source: '10', target: '21' },
+ { source: '10', target: '20' },
+ { source: '11', target: '24' },
+ { source: '11', target: '22' },
+ { source: '11', target: '14' },
+ { source: '12', target: '13' },
+ { source: '16', target: '17' },
+ { source: '16', target: '18' },
+ { source: '16', target: '21' },
+ { source: '16', target: '22' },
+ { source: '17', target: '18' },
+ { source: '17', target: '20' },
+ { source: '18', target: '19' },
+ { source: '19', target: '20' },
+ { source: '19', target: '33' },
+ { source: '19', target: '22' },
+ { source: '19', target: '23' },
+ { source: '20', target: '21' },
+ { source: '21', target: '22' },
+ { source: '22', target: '24' },
+ { source: '22', target: '25' },
+ { source: '22', target: '26' },
+ { source: '22', target: '23' },
+ { source: '22', target: '28' },
+ { source: '22', target: '30' },
+ { source: '22', target: '31' },
+ { source: '22', target: '32' },
+ { source: '22', target: '33' },
+ { source: '23', target: '28' },
+ { source: '23', target: '27' },
+ { source: '23', target: '29' },
+ { source: '23', target: '30' },
+ { source: '23', target: '31' },
+ { source: '23', target: '33' },
+ { source: '32', target: '33' },
+ ],
+};
+
+const graph = new Graph({
+ container: 'container',
+ data,
+ layout: {
+ type: 'fruchterman',
+ gravity: 10,
+ speed: 5,
+ clustering: true,
+ nodeClusterBy: 'cluster',
+ },
+ node: {
+ style: {
+ size: 20,
+ stroke: '#5B8FF9',
+ fill: '#C6E5FF',
+ lineWidth: 1,
+ labelPlacement: 'center',
+ labelText: (d) => d.id,
+ labelBackground: false,
+ },
+ },
+ edge: {
+ style: {
+ endArrow: true,
+ endArrowPath: 'M 0,0 L 4,2 L 4,-2 Z',
+ },
+ },
+ behaviors: ['drag-canvas', 'drag-node'],
+ animation: true,
+});
+
+graph.render();
diff --git a/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanFix.js b/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanFix.js
deleted file mode 100644
index f2d46bf58cd..00000000000
--- a/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanFix.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import { Graph, Extensions, extend } from '@antv/g6';
-
-const ExtGraph = extend(Graph, {
- layouts: {
- fruchterman: Extensions.FruchtermanLayout,
- },
-});
-
-const container = document.getElementById('container');
-const width = container.scrollWidth;
-const height = container.scrollHeight || 500;
-
-fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json')
- .then((res) => res.json())
- .then((data) => {
- const graph = new ExtGraph({
- container: 'container',
- width,
- height,
- transforms: [
- {
- type: 'transform-v4-data',
- activeLifecycle: ['read'],
- },
- ],
- node: {
- labelShape: {
- text: {
- fields: ['id'],
- formatter: (node) => node.id,
- },
- },
- labelBackgroundShape: {},
- animates: {
- update: [
- {
- fields: ['opacity'],
- states: ['selected', 'active'],
- shapeId: 'haloShape',
- },
- {
- fields: ['lineWidth'],
- states: ['selected', 'active'],
- shapeId: 'keyShape',
- },
- ],
- },
- },
- layout: {
- type: 'fruchterman',
- speed: 50,
- gravity: 1,
- animated: true,
- maxIteration: 500,
- },
- modes: {
- default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
- },
- data,
- });
-
- /******** 拖拽固定节点的逻辑 *********/
- graph.on('node:dragstart', function (e) {
- graph.stopLayout();
- });
- graph.on('node:drag', function (e) {
- refreshDragedNodePosition(e);
- });
- graph.on('node:dragend', (e) => {
- graph.layout();
- });
- function refreshDragedNodePosition(e) {
- const { x, y } = e.canvas;
- graph.updateData('node', {
- id: e.itemId,
- data: {
- fx: x,
- fy: y,
- x,
- y,
- },
- });
- }
- /*********************************/
-
-window.graph = graph;
- });
diff --git a/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanFix.ts b/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanFix.ts
new file mode 100644
index 00000000000..8aaac6568ff
--- /dev/null
+++ b/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanFix.ts
@@ -0,0 +1,41 @@
+import { Graph } from '@antv/g6';
+
+fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json')
+ .then((res) => res.json())
+ .then((data) => {
+ const graph = new Graph({
+ container: 'container',
+ data,
+ node: {
+ style: {
+ size: 15,
+ stroke: '#5B8FF9',
+ fill: '#C6E5FF',
+ lineWidth: 1,
+ },
+ },
+ edge: {
+ style: {
+ stroke: '#E2E2E2',
+ },
+ },
+ layout: {
+ type: 'fruchterman',
+ speed: 50,
+ gravity: 1,
+ animated: true,
+ maxIteration: 500,
+ },
+ behaviors: ['drag-canvas', 'drag-node'],
+ animation: true,
+ });
+
+ graph.render();
+
+ graph.on('node:dragstart', function () {
+ graph.stopLayout();
+ });
+ graph.on('node:dragend', () => {
+ graph.layout();
+ });
+ });
diff --git a/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanWebWorker.js b/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanWebWorker.ts
similarity index 53%
rename from packages/site/examples/net/fruchtermanLayout/demo/fruchtermanWebWorker.js
rename to packages/site/examples/net/fruchtermanLayout/demo/fruchtermanWebWorker.ts
index 6bdd7b197c9..009d93341f0 100644
--- a/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanWebWorker.js
+++ b/packages/site/examples/net/fruchtermanLayout/demo/fruchtermanWebWorker.ts
@@ -1,15 +1,6 @@
-import { Graph, Extensions, extend } from '@antv/g6';
-
-const ExtGraph = extend(Graph, {
- layouts: {
- fruchterman: Extensions.FruchtermanLayout,
- },
-});
+import { Graph } from '@antv/g6';
const container = document.getElementById('container');
-const width = container.scrollWidth;
-const height = (container.scrollHeight || 500) - 20;
-
const descriptionDiv = document.createElement('div');
descriptionDiv.innerHTML = 'Doing layout... web-worker is enabled in this demo, so the layout will not block the page.';
container.appendChild(descriptionDiv);
@@ -17,38 +8,32 @@ container.appendChild(descriptionDiv);
fetch('https://gw.alipayobjects.com/os/basement_prod/7bacd7d1-4119-4ac1-8be3-4c4b9bcbc25f.json')
.then((res) => res.json())
.then((data) => {
- const graph = new ExtGraph({
+ const graph = new Graph({
container: 'container',
- width,
- height,
- transforms: [
- {
- type: 'transform-v4-data',
- activeLifecycle: ['read'],
- },
- ],
- modes: {
- default: ['drag-canvas', 'drag-node', 'zoom-canvas', 'click-select'],
- },
+ data,
layout: {
type: 'fruchterman',
speed: 20,
gravity: 1,
maxIteration: 10000,
- gravity: 1,
workerEnabled: true,
},
node: {
- keyShape: {
- r: 6,
+ style: {
+ size: 6,
},
},
- data,
+ edge: {
+ style: {
+ stroke: '#ddd',
+ },
+ },
+ behaviors: ['drag-canvas', 'drag-node'],
});
+ graph.render();
+
graph.on('afterlayout', () => {
- descriptionDiv.innerHTML = 'Done!';
+ descriptionDiv.innerHTML = 'Layout in Worker, Done!';
});
-
- window.graph = graph;
});
diff --git a/packages/site/examples/net/fruchtermanLayout/demo/meta.json b/packages/site/examples/net/fruchtermanLayout/demo/meta.json
index ef1bd4fd357..03c24f63f98 100644
--- a/packages/site/examples/net/fruchtermanLayout/demo/meta.json
+++ b/packages/site/examples/net/fruchtermanLayout/demo/meta.json
@@ -5,7 +5,15 @@
},
"demos": [
{
- "filename": "basicFruchterman.js",
+ "filename": "basicFruchterman.ts",
+ "title": {
+ "zh": "基本 Fruchterman 布局",
+ "en": "Basic Fruchterman Layout"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*1KY7SLEXxqMAAAAAAAAAAABkARQnAQ"
+ },
+ {
+ "filename": "fruchtermanCluster.ts",
"title": {
"zh": "基本 Fruchterman 布局",
"en": "Basic Fruchterman Layout"
@@ -13,7 +21,7 @@
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*-s9CTphuwgcAAAAAAAAAAAAADmJ7AQ/original"
},
{
- "filename": "fruchtermanWebWorker.js",
+ "filename": "fruchtermanWebWorker.ts",
"title": {
"zh": "Fruchterman 使用 Web-worker",
"en": "Fruchterman with Web-worker"
@@ -21,7 +29,7 @@
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*3gn9TZ3oUoIAAAAAAAAAAAAADmJ7AQ/original"
},
{
- "filename": "fruchtermanFix.js",
+ "filename": "fruchtermanFix.ts",
"title": {
"zh": "Fruchterman 固定被拖拽的节点",
"en": "Fruchterman with Fixing Dragged Nodes"