d3caidan.html 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <style>
  7. .link-active {
  8. stroke-opacity: 1;
  9. stroke-width: 3;
  10. }
  11. </style>
  12. <script src="http://d3js.org/d3.v5.min.js"></script>
  13. </head>
  14. <body>
  15. <div style="border:1px solid #000;position: relative;">
  16. <svg width="1800" height="890"></svg>
  17. </div>
  18. </body>
  19. <script>
  20. let marge = { top: 60, bottom: 60, left: 60, right: 60 }
  21. let svg = d3.select('svg')
  22. let width = svg.attr('width')
  23. let height = svg.attr('height')
  24. svg.call(
  25. d3.zoom().on('zoom', function () {
  26. g.attr('transform', d3.event.transform)
  27. })
  28. )
  29. .on('dblclick.zoom', null)
  30. let g = svg.append('g')
  31. .attr('transform', 'translate(' + marge.top + ',' + marge.left + ')')
  32. .attr('class', 'container')
  33. // 准备数据
  34. // 节点集
  35. let nodes = [
  36. { id: 12, name: '湖南邵阳' },
  37. { id: 2, name: '山东泰安' },
  38. { id: 3, name: '广东阳江不知道怎么回' },
  39. { id: 4, name: '山西太原' },
  40. { id: 5, name: '亮' },
  41. { id: 6, name: '丽' },
  42. { id: 7, name: '雪' },
  43. { id: 8, name: '小明' },
  44. { id: 9, name: '组长' }
  45. ]
  46. // 143 112
  47. // 边集
  48. let tempEdges = [
  49. { id: 1, source: 12, target: 5, relation: '籍贯', value: 1.3 },
  50. { id: 2, source: 5, target: 6, relation: '舍友', value: 1 },
  51. { id: 3, source: 5, target: 7, relation: '舍友', value: 1 },
  52. { id: 4, source: 5, target: 8, relation: '舍友', value: 1 },
  53. { id: 5, source: 2, target: 7, relation: '籍贯', value: 2 },
  54. { id: 6, source: 3, target: 6, relation: '籍贯', value: 0.9 },
  55. { id: 7, source: 4, target: 8, relation: '籍贯', value: 1 },
  56. { id: 8, source: 6, target: 7, relation: '同学', value: 1.6 },
  57. { id: 9, source: 7, target: 8, relation: '朋友', value: 0.7 },
  58. { id: 10, source: 7, target: 9, relation: '职责', value: 2 },
  59. { id: 11, source: 9, target: 7, relation: '人物', value: 2 },
  60. { id: 12, source: 9, target: 7, relation: '哈哈哈', value: 2 }
  61. ]
  62. nodes.forEach(item => {
  63. })
  64. // 生成 nodes map
  65. let nodesMap = genNodesMap(nodes);
  66. console.log('3333',nodesMap)
  67. nodesData = d3.values(nodesMap)
  68. let linkMap = genLinkMap(tempEdges)
  69. // 构建 links(source 属性必须从 0 开始)
  70. edges = genLinks(tempEdges);
  71. console.log('123123',edges,nodesData)
  72. // 设置一个颜色比例尺
  73. let colorScale = d3.scaleOrdinal()
  74. .domain(d3.range(nodesData.length))
  75. .range(d3.schemeCategory10)
  76. // 新建一个力导向图
  77. let forceSimulation = d3.forceSimulation()
  78. .force('link', d3.forceLink())
  79. .force('charge', d3.forceManyBody())
  80. .force('center', d3.forceCenter())
  81. // 生成节点数据
  82. forceSimulation.nodes(nodesData)
  83. // 生成边数据
  84. forceSimulation.force('link')
  85. .links(edges)
  86. .distance(function (d) { // 每一边的长度
  87. return d.value * 200
  88. })
  89. // 设置图形中心位置
  90. forceSimulation.force('center')
  91. .x(width / 2)
  92. .y(height / 2)
  93. // 箭头
  94. var marker = g.append('g').attr('class', 'showLine').append('marker')
  95. .attr('id', 'resolved')
  96. // .attr("markerUnits","strokeWidth")// 设置为strokeWidth箭头会随着线的粗细发生变化
  97. .attr('markerUnits', 'userSpaceOnUse')
  98. .attr('viewBox', '0 -5 10 10')// 坐标系的区域
  99. .attr('refX', 32)// 箭头坐标
  100. .attr('refY', 0)
  101. .attr('markerWidth', 10)// 标识的大小
  102. .attr('markerHeight', 10)
  103. .attr('orient', 'auto')// 绘制方向,可设定为:auto(自动确认方向)和 角度值
  104. .attr('stroke-width', 2)// 箭头宽度
  105. .append('path')
  106. .attr('d', 'M0,-5L10,0L0,5')// 箭头的路径
  107. .attr('fill', '#4ffeff')// 箭头颜色
  108. // 绘制边
  109. let links = g.append('g').selectAll('path')
  110. .data(edges)
  111. .enter()
  112. .append('path')
  113. .attr('d', link => genLinkPath(link)) // 遍历所有数据。d表示当前遍历到的数据,返回绘制的贝塞尔曲线
  114. .attr('id', (d, i) => { return 'edgepath' + d.id }) // 设置id,用于连线文字
  115. .attr("stroke", "#4ffeff")
  116. .attr("stroke-opacity", 0.6)
  117. .style('stroke-width', 1) // 粗细
  118. .attr('class', 'lines')
  119. .attr('marker-end', 'url(#resolved)') // 根据箭头标记的id号标记箭头
  120. // 边上的文字
  121. let linksText = g.append('g')
  122. .selectAll('text')
  123. .data(edges)
  124. .enter()
  125. .append('text')
  126. .style("fill", "#C0F8F8")
  127. .attr('class', 'linksText')
  128. .text(function (d) {
  129. return d.relations
  130. })
  131. .style('font-size', 14)
  132. //图片容器
  133. let pattern = g.append('g')
  134. .selectAll("pattern")
  135. .data(nodesData, function (node) {
  136. return "pattern" + node.index;
  137. })
  138. .join("pattern")
  139. .attr("id", function (node) {
  140. return "pattern" + node.index;
  141. })
  142. .attr("x", 0)
  143. .attr("y", 0)
  144. .attr("height", 10)
  145. .attr("width", 10)
  146. .append("svg:image");
  147. console.log(pattern);
  148. //节点背景图片地址
  149. let images = g
  150. .selectAll("image")
  151. .data(nodesData, function (node) {
  152. console.log(node);
  153. return "pattern-image" + node.index;
  154. })
  155. .attr("xlink:href", function (node) {
  156. return 'data:image/png;base64,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'
  157. })
  158. .attr("preserveAspectRatio", "none")
  159. .attr("x", 0)
  160. .attr("y", 0)
  161. .attr("height", function (node) {
  162. return 70;
  163. })
  164. .attr("width", function (node) {
  165. return 70;
  166. });
  167. // 创建分组
  168. let gs = g.append('g')
  169. .selectAll('.circleText')
  170. .data(nodesData)
  171. .enter()
  172. .append('g')
  173. .attr('class', 'singleNode')
  174. .attr('id', function (d) {
  175. return 'singleNode' + d.id
  176. })
  177. .style('cursor', 'pointer')
  178. .attr('transform', function (d) {
  179. let cirX = d.x
  180. let cirY = d.y
  181. return 'translate(' + cirX + ',' + cirY + ')'
  182. })
  183. // 鼠标交互
  184. // gs.on('mouseover', function (d, i) {
  185. // // 显示连接线上的文字
  186. // toggleLineText(d, true)
  187. // toggleLine(links, d, true)
  188. // toggleNode(gs, d, true)
  189. // })
  190. // .on('mouseout', function (d, i) {
  191. // // 隐去连接线上的文字
  192. // toggleLineText(d, false)
  193. // toggleLine(links, d, false)
  194. // toggleNode(gs, d, false)
  195. // })
  196. .on('click', function (d, i) {
  197. linksText.style('fill-opacity', function (edge) {
  198. if (edge.source === d) {
  199. return 1
  200. }
  201. })
  202. console.log(d);
  203. toggleCircle(d3.select(this), d)
  204. }, true)
  205. .call(d3.drag()
  206. .on('start', started)
  207. .on('drag', dragged)
  208. .on('end', ended)
  209. )
  210. svg.on('click', function () {
  211. nodes.forEach(d => d.clickFlag = false)
  212. var event = d3.event
  213. var target = event.srcElement, // 获取事件发生源
  214. data = d3.select(target).datum(); // 获取事件发生源的数据
  215. removeSingle()
  216. if (!data) {
  217. document.getElementById('xxx').innerText = ''
  218. }
  219. }, true)
  220. forceSimulation.on('tick', ticked)
  221. function toggleLineText(currNode, isHover) {
  222. if (isHover) {
  223. linksText.style('fill-opacity', function (edge) {
  224. if (edge.source === currNode) {
  225. return 1
  226. }
  227. })
  228. } else {
  229. linksText.style('fill-opacity', function (edge) {
  230. if (edge.source === currNode || edge.target === currNode) {
  231. return 0
  232. }
  233. })
  234. }
  235. }
  236. function toggleLine(linkLine, currNode, isHover) {
  237. if (isHover) {
  238. // 加重连线样式
  239. links
  240. .style('opacity', 0.1)
  241. .filter(link => isLinkLine(currNode, link))
  242. .style('opacity', 1)
  243. .classed('link-active', true)
  244. } else {
  245. links
  246. .style('opacity', 1)
  247. .classed('link-active', false)
  248. }
  249. }
  250. function showMyList() {
  251. var e = { id: 10, name: "河北" };
  252. var h = { id: 11, name: "河南" };
  253. var f = { id: 13, source: 9, target: 10, relation: '222', value: 2 };
  254. nodes.push(e);
  255. nodes.push(h);
  256. tempEdges.push(f);
  257. nodesMap = genNodesMap(nodes);
  258. nodesData = d3.values(nodesMap)
  259. linkMap = genLinkMap(tempEdges)
  260. edges = genLinks(tempEdges)
  261. updateData()
  262. }
  263. function updateData() {
  264. links = links
  265. .data(edges, function (d) {
  266. })
  267. .join("path")
  268. .attr('id', (d, i) => { return 'edgepath' + d.id }) // 设置id,用于连线文字
  269. .style('stroke', '#000') // 颜色
  270. .style('stroke-width', 2) // 粗细
  271. .attr('class', 'lines')
  272. .attr('marker-end', 'url(#resolved)') // 根据箭头标记的id号标记箭头
  273. .merge(links);
  274. linksText = linksText
  275. .data(edges)
  276. .join('text')
  277. .attr('class', 'linksText')
  278. .text(function (d) {
  279. return d.relations
  280. })
  281. .style('font-size', 14)
  282. .attr('fill-opacity', 0)
  283. gs = gs
  284. .data(nodesData, function (d) {
  285. })
  286. .join("g")
  287. .attr('class', 'singleNode')
  288. .attr('id', function (d) {
  289. return 'singleNode' + d.id
  290. })
  291. .style('cursor', 'pointer')
  292. .merge(gs)
  293. .call(d3.drag()
  294. .on("start", started)
  295. .on("drag", dragged)
  296. .on("end", ended));
  297. gs.append('circle')
  298. .attr('fill', function (d) {
  299. return 'orange'
  300. })
  301. .attr('r', 35)
  302. .attr('stroke', 'red')
  303. .attr('stroke-width', 3)
  304. // gs.on('mouseover', function (d, i) {
  305. // // 显示连接线上的文字
  306. // toggleLineText(d, true)
  307. // toggleLine(links, d, true)
  308. // toggleNode(gs, d, true)
  309. // })
  310. // .on('mouseout', function (d, i) {
  311. // // 隐去连接线上的文字
  312. // toggleLineText(d, false)
  313. // toggleLine(links, d, false)
  314. // toggleNode(gs, d, false)
  315. // })
  316. .on('click', function (d, i) {
  317. linksText.style('fill-opacity', function (edge) {
  318. if (edge.source === d) {
  319. return 1
  320. }
  321. })
  322. toggleCircle(d3.select(this), d)
  323. }, true)
  324. gs.append('text')
  325. .attr('y', -20)
  326. .attr('dy', 10)
  327. .attr('text-anchor', 'middle')
  328. .style('font-size', 12)
  329. .attr('x', function ({ name }) {
  330. return textBreaking(d3.select(this), name)
  331. })
  332. gs.append('title')
  333. .text((node) => {
  334. return node.name
  335. })
  336. forceSimulation.nodes(nodesData);
  337. forceSimulation.force("link").links(edges);
  338. forceSimulation.alpha(0.8).restart();
  339. }
  340. function getLineTextAngle(d, bbox) {
  341. if (d.target.x < d.source.x) {
  342. const {
  343. x,
  344. y,
  345. width,
  346. height
  347. } = bbox;
  348. const rx = x + width / 2;
  349. const ry = y + height / 2;
  350. return 'rotate(180 ' + rx + ' ' + ry + ')';
  351. } else {
  352. return 'rotate(0)';
  353. }
  354. }
  355. function toggleNode(nodeCircle, currNode, isHover) {
  356. if (isHover) {
  357. // 提升节点层级
  358. // nodeCircle.sort((a, b) => a.id === currNode.id ? 1 : -1);
  359. // this.parentNode.appendChild(this);
  360. nodeCircle
  361. .style('opacity', .1)
  362. .filter(node => isLinkNode(currNode, node))
  363. .style('opacity', 1);
  364. } else {
  365. nodeCircle.style('opacity', 1);
  366. }
  367. }
  368. //删除有用
  369. function removeSingle() {
  370. d3.select('.singleCircle').remove()
  371. }
  372. function toggleCircle(current, d) {
  373. var currentD = d
  374. // if (d.clickFlag) {
  375. // removeSingle()
  376. // document.getElementById('xxx').innerText = ''
  377. // }
  378. d.clickFlag = true
  379. // document.getElementById('xxx').innerText = d.name
  380. var data = [{
  381. population: 30,
  382. value: '删除',
  383. type: 'delete'
  384. }, {
  385. population: 30,
  386. value: '收起',
  387. type: 'showOn'
  388. }, {
  389. population: 30,
  390. value: '展开',
  391. type: 'showOff'
  392. }]
  393. var sum = d3.sum(data.map(function (d) {
  394. return d.population
  395. }))
  396. for (i in data) {
  397. data[i].Percentage = (data[i].population / sum * 100).toFixed(0) + "%";
  398. }
  399. var width = 300,
  400. height = 300,
  401. margin = { "left": 30, "top": 30, "right": 30, "bottom": 30 },
  402. svg_width = width + margin.left + margin.right,
  403. svg_height = height + margin.top + margin.bottom,
  404. font_size = 15;
  405. var g = current
  406. .append("g")
  407. .attr('class', 'singleCircle')
  408. .attr("width", width)
  409. .attr("height", height)
  410. console.log(g);
  411. var Pie = g.append("g")
  412. console.log(Pie);
  413. var arc_generator = d3.arc()
  414. .innerRadius(width / 6.5)
  415. .outerRadius(width / 4)
  416. var angle_data = d3.pie()
  417. .value(function (d) {
  418. return d.population;
  419. })
  420. var pieData = angle_data(data)
  421. var pieAngle = pieData.map(function (p) {
  422. return (p.startAngle + p.endAngle) / 2 / Math.PI * 180;
  423. });
  424. // var color=d3.schemeCategory10;
  425. //生成内圆环
  426. Pie.selectAll("path")
  427. .data(angle_data(data))
  428. .enter()
  429. .append("path")
  430. .attr("d", arc_generator)
  431. .style("fill", function (d, i) {
  432. return 'grey';
  433. })
  434. .style('stroke', 'black')
  435. .attr("class", "path")
  436. .attr('type', function (d) {
  437. return d.data.type
  438. })
  439. .on('click', function (d) {
  440. if (d.data.type === 'delete') {
  441. deleteNode(currentD)
  442. } else if (d.data.type === 'showOn') {
  443. deleteNextNodes(currentD)
  444. } else {
  445. // showMyList()
  446. }
  447. d3.event.stopPropagation()
  448. })
  449. var arc_label = d3.arc()
  450. .innerRadius(width / 4)
  451. .outerRadius(width / 2)
  452. Pie.selectAll(".arc_label")
  453. .data(angle_data(data))
  454. .enter()
  455. .append("path")
  456. .attr("d", arc_label)
  457. .attr("class", "arc_label")
  458. .style("fill", "none")
  459. const labelFontSize = 12;
  460. const labelValRadius = (170 * 0.35 - labelFontSize * 0.35); // 计算正确半径 文字位置
  461. const labelValRadius1 = (170 * 0.35 + labelFontSize * 0.35);
  462. const labelsVals = current.select('.singleCircle').append('g')
  463. .classed('labelsvals', true);
  464. // 定义两条路径以使标签的方向正确
  465. labelsVals.append('def')
  466. .append('path')
  467. .attr('id', 'label-path-1')
  468. .attr('d', `m0 ${-labelValRadius} a${labelValRadius} ${labelValRadius} 0 1,1 -0.01 0`);
  469. labelsVals.append('def')
  470. .append('path')
  471. .attr('id', 'label-path-2')
  472. .attr('d', `m0 ${-labelValRadius1} a${labelValRadius1} ${labelValRadius1} 0 1,0 0.01 0`);
  473. labelsVals.selectAll('text')
  474. .data(data)
  475. .enter()
  476. .append('text')
  477. .style('font-size', labelFontSize)
  478. .style('fill', 'black')
  479. .style('font-weight', "bold")
  480. .style('text-anchor', 'middle')
  481. .append('textPath')
  482. .attr('href', function (d, i) {
  483. const p = pieData[i];
  484. const angle = pieAngle[i];
  485. if (angle > 90 && angle <= 270) { // 根据角度选择路径
  486. return '#label-path-2';
  487. } else {
  488. return '#label-path-1';
  489. }
  490. })
  491. .attr('startOffset', function (d, i) {
  492. const p = pieData[i];
  493. const angle = pieAngle[i];
  494. let percent = (p.startAngle + p.endAngle) / 2 / 2 / Math.PI * 100;
  495. if (angle > 90 && angle <= 270) { // 分别计算每条路径的正确百分比
  496. return 100 - percent + "%";
  497. }
  498. return percent + "%";
  499. })
  500. .text(function (d) {
  501. return d.value;
  502. })
  503. .on('click', function (d) {
  504. if (d.type === 'delete') {
  505. deleteNode(currentD)
  506. } else if (d.type === 'showOn') {
  507. deleteNextNodes(currentD)
  508. } else {
  509. // showMyList()
  510. }
  511. d3.event.stopPropagation()
  512. }, true)
  513. }
  514. //删除当前节点下一级没有其他关系的节点
  515. function deleteNextNodes(curr) {
  516. console.log('收起?');
  517. // document.getElementById('xxx').innerText = '';
  518. // var removeIndex = nodesData.findIndex(node=>node.id == curr.id)
  519. // nodesData.splice(removeIndex,1)
  520. // nodes.splice(removeIndex,1)
  521. d3.select(this).remove();
  522. let relationNode = [],
  523. relationList = [],
  524. hasRelationList = []
  525. var clickNode = curr.id;//点击节点的名字
  526. d3.selectAll(".lines").each(function (e) {
  527. if (e.source.id == curr.id || e.target.id == curr.id) {
  528. hasRelationList.push(e)
  529. } else {
  530. relationList.push(e)//出去跟删除节点有关系的其他关系
  531. }
  532. //需要删除的节点相关的节点
  533. if (e.source.id == curr.id) {
  534. relationNode.push(e.target)
  535. }
  536. //需要删除的节点相关的节点
  537. if (e.target.id == curr.id) {
  538. relationNode.push(e.source)
  539. }
  540. })
  541. var tempNodeList = JSON.parse(JSON.stringify(relationNode))
  542. tempNodeList = uniqObjInArray(tempNodeList)
  543. //区分下级节点是否是孤节点
  544. tempNodeList.forEach(function (item, index) {
  545. var hasLine = relationList.findIndex(jtem => jtem.target.id == item.id || jtem.source.id == item.id)
  546. if (hasLine >= 0) {
  547. item.notSingle = true
  548. }
  549. })
  550. tempNodeList.forEach(function (item, index) {
  551. if (!item.notSingle) {
  552. d3.select('#singleNode' + item.id).remove()
  553. }
  554. })
  555. var otherTempNode = [];
  556. tempNodeList = tempNodeList.map(item => {
  557. if (!item.notSingle) {
  558. otherTempNode.push(item)
  559. }
  560. })
  561. hasRelationList.forEach(item => {
  562. otherTempNode.forEach(jtem => {
  563. if (jtem.id == item.source.id || jtem.id == item.target.id) {
  564. d3.select('#edgepath' + item.id).remove()
  565. }
  566. })
  567. })
  568. d3.selectAll(".singleNode").each(function (d, i) {
  569. var temp = d.id;
  570. //删除当前需要隐藏的节点
  571. if (temp == clickNode) {
  572. removeSingle()
  573. }
  574. });
  575. d3.selectAll(".linksText").each(function (e) {
  576. if (e.source === curr || e.target === curr) {
  577. d3.select(this).remove();
  578. }
  579. })
  580. gs.style('opacity', 1);
  581. links.style('opacity', 1)
  582. .classed('link-active', false);
  583. }
  584. //删除当前及下一级没有其他关系的节点
  585. function deleteNode(curr) {
  586. document.getElementById('xxx').innerText = '';
  587. var removeIndex = nodesData.findIndex(node => node.id == curr.id)
  588. nodesData.splice(removeIndex, 1)
  589. nodes.splice(removeIndex, 1)
  590. d3.select(this).remove();
  591. let relationNode = [],
  592. relationList = []
  593. var clickNode = curr.id;//点击节点的名字
  594. d3.selectAll(".lines").each(function (e) {
  595. if (e.source.id == curr.id || e.target.id == curr.id) {
  596. d3.select(this).remove();
  597. } else {
  598. relationList.push(e)//出去跟删除节点有关系的其他关系
  599. }
  600. //需要删除的节点相关的节点
  601. if (e.source.id == curr.id) {
  602. relationNode.push(e.target)
  603. }
  604. //需要删除的节点相关的节点
  605. if (e.target.id == curr.id) {
  606. relationNode.push(e.source)
  607. }
  608. })
  609. var tempNodeList = JSON.parse(JSON.stringify(relationNode))
  610. tempNodeList = uniqObjInArray(tempNodeList)
  611. //区分下级节点是否是孤节点
  612. tempNodeList.forEach(function (item, index) {
  613. var hasLine = relationList.findIndex(jtem => jtem.target.id == item.id || jtem.source.id == item.id)
  614. if (hasLine >= 0) {
  615. item.notSingle = true
  616. }
  617. })
  618. tempNodeList.forEach(function (item, index) {
  619. if (!item.notSingle) {
  620. d3.select('#singleNode' + item.id).remove()
  621. }
  622. })
  623. d3.selectAll(".singleNode").each(function (d, i) {
  624. var temp = d.id;
  625. //删除当前需要隐藏的节点
  626. if (temp == clickNode) {
  627. removeSingle()
  628. d3.select(this).remove();
  629. }
  630. });
  631. d3.selectAll(".linksText").each(function (e) {
  632. if (e.source === curr || e.target === curr) {
  633. d3.select(this).remove();
  634. }
  635. })
  636. gs.style('opacity', 1);
  637. links.style('opacity', 1)
  638. .classed('link-active', false);
  639. }
  640. // 关联节点去重重组
  641. function uniqObjInArray(objarray) {
  642. let len = objarray.length;
  643. let tempJson = {
  644. };
  645. let res = [];
  646. for (let i = 0; i < len; i++) {
  647. //取出每一个对象
  648. tempJson[JSON.stringify(objarray[i])] = true;
  649. }
  650. let keyItems = Object.keys(tempJson);
  651. for (let j = 0; j < keyItems.length; j++) {
  652. res.push(JSON.parse(keyItems[j]));
  653. }
  654. return res;
  655. }
  656. function isLinkLine(node, link) {
  657. return link.source.id === node.id
  658. }
  659. function isLinkNode(currNode, node) {
  660. if (currNode.id === node.id) {
  661. return true;
  662. }
  663. return linkMap[currNode.id + '-' + node.id] || linkMap[node.id + '-' + currNode.id];
  664. }
  665. function largerNode(nodes, currNode, isHover) {
  666. if (isHover) {
  667. gs
  668. .style('stroke-width', 1)
  669. .filter(node => isNode(currNode, node))
  670. .style('stroke-width', 10)
  671. } else {
  672. gs
  673. .style('stroke-width', 1)
  674. }
  675. }
  676. function isNode(node, cNode) {
  677. return true
  678. }
  679. // 绘制节点
  680. gs.append('circle')
  681. .attr('r', 35)
  682. .attr('id', function (d) {
  683. return 'circle' + d.id
  684. })
  685. // .attr('fill', function (d, i) {
  686. // return 'orange'
  687. // })
  688. .style("fill", function (node) {
  689. return "url(#" + "pattern" + node.index + ")";
  690. })
  691. .attr('stroke-width', 3)
  692. // 文字
  693. var nodeText = gs.append('text')
  694. // .attr('x', -10)
  695. // .attr('y', -20)
  696. // .attr('dy', 10)
  697. .text(function (d) {
  698. return d.name;
  699. })
  700. .attr('text-anchor', 'middle')
  701. .style('font-size', 11)
  702. .style("fill", "white")
  703. .attr("dx", function () {
  704. return (this.getBoundingClientRect().width / 2) * 0.1;
  705. })
  706. .attr("dy", function (d) {
  707. return 35;
  708. })
  709. gs.append('title')
  710. .text((node) => {
  711. return node.name
  712. })
  713. function genLinkMap(relations) {
  714. const hash = {};
  715. relations.map(function ({
  716. source,
  717. target,
  718. relation
  719. }) {
  720. const key = source + '-' + target;
  721. if (hash[key]) {
  722. hash[key] += 1;
  723. hash[key + '-relation'] += '、' + relation;
  724. } else {
  725. hash[key] = 1;
  726. hash[key + '-relation'] = relation;
  727. }
  728. });
  729. return hash;
  730. }
  731. function genLinks(relations) {
  732. const indexHash = {};
  733. return relations.map(function ({
  734. id,
  735. source,
  736. target,
  737. relation,
  738. value
  739. }, i) {
  740. const linkKey = source + '-' + target;
  741. const count = linkMap[linkKey];
  742. if (indexHash[linkKey]) {
  743. console.log(indexHash[linkKey]);
  744. indexHash[linkKey] -= 1;
  745. } else {
  746. indexHash[linkKey] = count - 1;
  747. }
  748. return {
  749. id,
  750. source: nodesMap[source],
  751. target: nodesMap[target],
  752. relation,
  753. value,
  754. relations: linkMap[linkKey + '-relation'],
  755. count: linkMap[linkKey],
  756. index: indexHash[linkKey]
  757. }
  758. })
  759. }
  760. // 生成关系连线路径
  761. function genLinkPath(link) {
  762. const count = link.count;
  763. const index = link.index;
  764. let sx = link.source.x;
  765. let tx = link.target.x;
  766. let sy = link.source.y;
  767. let ty = link.target.y;
  768. return 'M' + sx + ',' + sy + ' L' + tx + ',' + ty;
  769. }
  770. function genNodesMap(nodes) {
  771. const hash = {};
  772. nodes.map(function ({
  773. id,
  774. name
  775. }) {
  776. hash[id] = {
  777. id,
  778. name
  779. };
  780. });
  781. return hash;
  782. }
  783. // 处理节点文字换行
  784. function textBreaking(d3text, text) {
  785. const len = text.length
  786. if (len <= 3) {
  787. d3text.append('tspan')
  788. .attr('x', 0)
  789. .attr('y', -3)
  790. .text(text)
  791. } else {
  792. const topText = text.substring(0, 3)
  793. const midText = text.substring(3, 7)
  794. let botText = text.substring(7, len)
  795. let topY = -22
  796. let midY = 8
  797. let botY = 34
  798. if (len <= 9) {
  799. topY += 10
  800. midY += 10
  801. } else {
  802. botText = text.substring(7, 9) + '...'
  803. }
  804. d3text.text('')
  805. d3text.append('tspan')
  806. .attr('x', 0)
  807. .attr('y', topY)
  808. .text(function () {
  809. return topText
  810. })
  811. d3text.append('tspan')
  812. .attr('x', 0)
  813. .attr('y', midY)
  814. .text(function () {
  815. return midText
  816. })
  817. d3text.append('tspan')
  818. .attr('x', 0)
  819. .attr('y', botY - 7)
  820. .text(function () {
  821. return botText
  822. })
  823. }
  824. }
  825. // ticked
  826. function ticked() {
  827. // 连线路径
  828. links
  829. .attr('d', link => genLinkPath(link))
  830. // 连线文字位置
  831. linksText
  832. .attr('x', function (d) { return (d.source.x + d.target.x) / 2 })
  833. .attr('y', function (d) { return (d.source.y + d.target.y) / 2 })
  834. // 节点位置
  835. gs
  836. .attr('transform', function (d) { return 'translate(' + d.x + ',' + d.y + ')' })
  837. }
  838. // drag
  839. function started(d) {
  840. if (!d3.event.active) {
  841. forceSimulation.alphaTarget(0.8).restart() // 设置衰减系数,对节点位置移动过程的模拟,数值越高移动越快,数值范围[0, 1]
  842. }
  843. d.fx = d.x
  844. d.fy = d.y
  845. }
  846. function dragged(d) {
  847. d.fx = d3.event.x
  848. d.fy = d3.event.y
  849. }
  850. function ended(d) {
  851. if (!d3.event.active) {
  852. forceSimulation.alphaTarget(0)
  853. }
  854. d.fx = null
  855. d.fy = null
  856. }
  857. </script>
  858. </html>