Advertisement
Guest User

Untitled

a guest
Nov 18th, 2019
157
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.45 KB | None | 0 0
  1. import * as d3 from 'd3';
  2.  
  3. class Tree {
  4. constructor(id, treeData, opts) {
  5. this.opts = opts || {};
  6. this.data = treeData || {};
  7. this.id = id || console.error('ID is required!');
  8. this.elem = document.getElementById(this.id);
  9. this.i = 0;
  10. this.margin = {
  11. top: 20,
  12. right: 120,
  13. bottom: 20,
  14. left: 120
  15. };
  16. this.node_attribute = {
  17. width: 50,
  18. height: 100
  19. };
  20. this.translation = opts.translationKeys;
  21. this.duration = 500;
  22. this.radius = opts.radius || 30;
  23. this.viewerWidth = parseFloat(getComputedStyle(this.elem, null).width.replace("px", "")); //$(this.id).width();
  24. this.viewerHeight = window.innerHeight - this.elem.getBoundingClientRect().top;
  25. this.rootStartPosX = this.viewerWidth * 0.5;
  26. this.rootStartPosY = this.viewerHeight * 0.2;
  27. this.direction = opts.direction || 'v'; //v= vertical, h = horizontal
  28. this.ease = opts.ease || 'easeSin';
  29. this.defaultDisplayLevel = opts.defaultDisplayLevel || 1;
  30. this.line = opts.line || 'c';
  31. this.user_var = opts.user_var || 'user_id';
  32. this.lavel_var = opts.lavel_var || 'rank';
  33. this.level_icons = opts.level_icons || null;
  34. /*
  35. "easeElastic",
  36. "easeBounce",
  37. "easeLinear",
  38. "easeSin",
  39. "easeQuad",
  40. "easeCubic",
  41. "easePoly",
  42. "easeCircle",
  43. "easeExp",
  44. "easeBack"*/
  45.  
  46. this.treemap = d3.tree()
  47. .nodeSize([this.node_attribute.height, this.node_attribute.height])
  48.  
  49. this.div = d3.select(this.elem)
  50. .append("div")
  51. .attr("class", "tooltip")
  52. .style("padding", "10px")
  53. .style("opacity", 0)
  54. .style("right", 50 + "px")
  55. .style("top", 10 + "px");
  56.  
  57. this.viewportHeight = Math.max(500, this.viewerHeight + this.margin.top + this.margin.bottom);
  58. this.setHeight(this.elem, this.viewportHeight);
  59. this.svg = d3.select(this.elem)
  60. .append("svg")
  61. .attr("width", '100%')
  62. .attr("height", this.viewportHeight);
  63.  
  64. this.arc = d3.arc();
  65. this.zoomLayer = this.svg.append("g")
  66. .attr("transform", "translate(" + this.rootStartPosX + ", " + this.rootStartPosY + ")");
  67.  
  68. // resize svg
  69. $(window).resize(() => {
  70. this.viewerWidth = parseFloat(getComputedStyle(this.elem, null).width.replace("px", ""));
  71. this.svg.attr("width", this.viewerWidth)
  72. });
  73. this.root = d3.hierarchy(treeData);
  74. this.root.x = this.viewerHeight / 2;
  75. this.root.x0 = this.viewerHeight / 2;
  76. this.root.descendants().forEach((d, i) => {
  77. d.id = i;
  78. d._children = d.children;
  79. if (d.depth && d.depth >= this.defaultDisplayLevel) d.children = null;
  80. });
  81. this.update(this.root);
  82. }
  83.  
  84. init(d) {
  85. if (d.children) {
  86. d.children.forEach(this.collapse);
  87. }
  88. }
  89.  
  90. collapse(d) {
  91. if (d._children) {
  92. d._children.forEach(this.collapse);
  93. }
  94. if (d.children) {
  95. d._children = d.children;
  96. d._children.forEach(this.collapse);
  97. d.children = null;
  98. }
  99. }
  100.  
  101. update(source = null) {
  102. var parent = this;
  103. // Assigns the x and y position for the nodes
  104. var nodes_data = this.treemap(this.root);
  105.  
  106. // Compute the new tree layout.
  107. var nodes = nodes_data.descendants(),
  108. links = nodes_data.descendants().slice(1)
  109.  
  110. // Normalize for fixed-depth.
  111. nodes.forEach(function (d) {
  112. d.y = d.depth * 180;
  113. })
  114.  
  115. // ****************** Nodes section ***************************
  116.  
  117. // Update the nodes...
  118. var node = this.zoomLayer.selectAll('g.node')
  119. .data(nodes, function (d) {
  120. return d.id || (d.id = ++this.i);
  121. })
  122.  
  123. // Enter any new nodes at the parent's previous position.
  124. var nodeEnter = node.enter().append('g')
  125. .attr('class', 'node')
  126. .attr("transform", (d) => {
  127. if(this.direction == 'h'){
  128. return "translate(" + source.y +"," + source.x + ")";
  129. }else{
  130. return "translate(" + source.x + "," + source.y + ")";
  131. }
  132. })
  133. .on('click touch', this.click)
  134. .on("mouseenter", function (d) {
  135. parent.onMouseOver(this, d)
  136. })
  137. .on("mousemove", this.move_tooltip)
  138. .on("mouseout", function () {
  139. // don't hide tooltip when hover on child elements
  140. var e = event.toElement || event.relatedTarget;
  141. if (e && (e == this || (e.parentNode && e.parentNode == this ))) {
  142. return;
  143. } else {
  144. parent.hide_tooltip()
  145. d3.select(this).classed("active", false);
  146. }
  147. });
  148.  
  149. // Add design for the nodes
  150. this.drawchild(nodeEnter)
  151.  
  152.  
  153. // Add tooltip for the nodes
  154. // nodeEnter.append("foreignObject")
  155. // .attr("width", 20)
  156. // .attr("height", 20)
  157. // .attr("x", function (d) { return (node_attribute.width / 2) - 20 })
  158. // .attr("y", 0)
  159. // .attr("opacity", 1)
  160. // .attr("class", "tooltip-info")
  161.  
  162.  
  163. // UPDATE
  164. var nodeUpdate = node.merge(nodeEnter)
  165. this.updateChild(nodeUpdate);
  166.  
  167.  
  168. // Remove any exiting nodes
  169. var nodeExit = node.exit()
  170. .transition()
  171. .duration(this.duration)
  172. .ease(d3[this.ease])
  173. .attr("transform", (d)=> {
  174. if(this.direction == 'h'){
  175. return "translate(" + source.y + "," + source.x + ")";
  176. }else{
  177. return "translate(" + source.x + "," + source.y + ")";
  178. }
  179. })
  180. .remove();
  181.  
  182. // On exit reduce the node circles size to 0
  183. nodeExit
  184. .select("circle, path, text")
  185. .style('opacity', 1e-6)
  186.  
  187. // ****************** links section ***************************
  188.  
  189. // Update the links...
  190. var link = this.zoomLayer.selectAll('path.tree-link')
  191. .data(links, function (d) {
  192. return d.id
  193. });
  194.  
  195. // Enter any new links at the parent's previous position.
  196. var linkEnter = link.enter().insert('path', "g")
  197. .attr("class", "tree-link")
  198. .attr('d', (d) => {
  199. // var o = { x: source.x0, y: source.y0 }
  200. return this.drawConnection(d.parent, d.parent)
  201. })
  202.  
  203. // UPDATE
  204. var linkUpdate = linkEnter.merge(link)
  205.  
  206. // Transition back to the parent element position
  207. linkUpdate.transition()
  208. .duration(this.duration)
  209. .ease(d3[this.ease])
  210. .attr('d', (d) => {
  211. return this.drawConnection(d, d.parent)
  212. })
  213.  
  214. // Remove any exiting links
  215. var linkExit = link.exit()
  216. .transition()
  217. .duration(this.duration)
  218. .ease(d3[this.ease])
  219. .attr('d', (d) => {
  220. return this.drawConnection(source, source)
  221. })
  222. .remove()
  223.  
  224. // Store the old positions for transition.
  225. nodes.forEach(function (d) {
  226. d.x0 = d.x
  227. d.y0 = d.y
  228. })
  229. this.svg.call(d3.zoom()
  230. .scaleExtent([1 / 2, 12])
  231. .on("zoom", this.zoomed))
  232. }
  233.  
  234. onMouseOver = (node, d) => {
  235. d3.select(node).classed("active", true);
  236. this.show_tooltip(d);
  237. }
  238.  
  239. onMouseOut = (e, node) => {
  240. if (e.parentNode == node || e == node) {
  241. return;
  242. } else {
  243. this.hide_tooltip()
  244. d3.select(node).classed("active", false);
  245. }
  246. }
  247.  
  248. drawchild = (node) => {
  249. if(this.level_icons){
  250. node
  251. .append("g")
  252. .attr("transform", "translate(" + -this.radius * 1.8 / 2 + ", 0)")
  253. .append("svg:image")
  254. .attr("xlink:href", (d)=>{
  255. if(d.data[this.lavel_var] && this.level_icons[d.data[this.lavel_var]]){
  256. return this.level_icons[d.data[this.lavel_var]];
  257. }else{
  258. return this.level_icons[Object.keys(this.level_icons)[0]];
  259. }
  260. })
  261. .attr("width", this.radius * 1.8)
  262. .attr("height", this.radius * 1.8);
  263. }else if (this.opts.customChild) {
  264. node
  265. .append("g")
  266. .attr("transform", "translate(" + -this.radius * 1.8 / 2 + ", 0)")
  267. .append("svg:image")
  268. .attr("xlink:href", (d)=>{
  269. return (d.data.children_count > 0) ? this.opts.customChildMultiple : this.opts.customChild;}
  270. )
  271. .attr("width", this.radius * 1.8)
  272. .attr("height", this.radius * 1.8);
  273. } else {
  274. node
  275. .append('path')
  276. .attr("transform", "translate( 0 ," + this.radius * 1.8 + ")")
  277. .attr("class", function (d) {
  278. return ( d.data.children_count > 0) ? "people_body_leader" : "people_body";
  279. })
  280. .attr('d', this.arc({
  281. innerRadius: 0,
  282. outerRadius: this.radius * 1.2,
  283. startAngle: -Math.PI * 0.5,
  284. endAngle: Math.PI * 0.5
  285. }));
  286.  
  287. node
  288. .append("circle")
  289. .attr("r", this.radius)
  290. .attr("class", function (d) {
  291. return ( d.data.children_count > 0) ? "people_head_leader" : "people_head";
  292. })
  293. .attr('cursor', function (d) {
  294. return ( d.data.children_count > 0) ? "pointer" : "default";
  295. })
  296. }
  297. // Add labels for the nodes
  298. node.append('text')
  299. .attr("dy", this.radius * 1.75 + 15)
  300. .attr("text-anchor", "middle")
  301. .attr("width", 100)
  302. .attr("height", 50)
  303. .attr("class", "people_text")
  304. .text((d) => {
  305. if (this.opts.displayname && d.data[this.opts.displayname]) {
  306. var text = d.data[this.opts.displayname],
  307. count = text.length,
  308. limit = 9
  309.  
  310. if (count > limit) text = text.slice(0, limit) + ".."
  311.  
  312. return text;
  313. }
  314. return '';
  315. })
  316. .style('opacity', 1);
  317. return node;
  318. }
  319.  
  320. updateChild = (nodeUpdate) =>{
  321. // Transition to the proper position for the node
  322. nodeUpdate
  323. .transition()
  324. .duration(this.duration)
  325. .ease(d3[this.ease])
  326. .attr("transform", (d)=> {
  327. if(this.direction == 'h'){
  328. return "translate(" + d.y + "," +(parseInt(d.x) - this.radius) + ")";
  329. }else{
  330. return "translate(" + d.x + "," + d.y + ")";
  331. }
  332. })
  333. .attr('cursor', function (d) {
  334. return (_.isEmpty(d.children) && d.data.children_count > 1) ? "pointer" : "default";
  335. });
  336. // if(this.opts.customChild){
  337. // if(this.opts.customChildMultiple){
  338. // nodeUpdate
  339. // .select("image")
  340. // .attr("xlink:href", (d)=> {
  341. // return (d.data.children_count > 0) ? this.opts.customChildMultiple : this.opts.customChild;
  342. // })
  343. // }
  344. // }else{
  345. // this.drawchild(nodeUpdate);
  346. nodeUpdate
  347. .select('path')
  348. .attr("transform", "translate( 0 ," + this.radius * 1.8 + ")")
  349. // .attr("class", function (d) {
  350. // return (_.isEmpty(d.children) && d.data.children_count > 0) ? "people_body_leader" : "people_body";
  351. // })
  352. .attr('d', this.arc({
  353. innerRadius: 0,
  354. outerRadius: this.radius * 1.2,
  355. startAngle: -Math.PI * 0.5,
  356. endAngle: Math.PI * 0.5
  357. }));
  358.  
  359. // Update the node attributes and style
  360. nodeUpdate
  361. .select('circle')
  362. .attr("r", this.radius)
  363. // .attr("class", function (d) {
  364. // return (_.isEmpty(d.children) && d.data.children_count > 0) ? "people_head_leader" : "people_head";
  365. // })
  366. .attr('cursor', function (d) {
  367. return (_.isEmpty(d.children) && d.data.children_count > 0) ? "pointer" : "default";
  368. })
  369. // }
  370. }
  371.  
  372. drawConnection = (node, parent) => {
  373. if (this.direction == 'h') {
  374. if(this.line == 'c'){
  375. return this.diagonalHorizontal({source: node, target: parent});
  376. }else{
  377. return this.elbowHorizontal(node, parent);
  378. }
  379. }else{
  380. if(this.line == 'c'){
  381. return this.diagonalVertical({source: node, target: parent});
  382. }else{
  383. return this.elbowVertical(node, parent);
  384. }
  385. }
  386. }
  387.  
  388. elbowHorizontal(d, i) {
  389. return "M" + d.y + "," + d.x +
  390. "H" + (((d.y - i.y) / 2) + i.y) +
  391. "V" + i.x + "H" + i.y;
  392. }
  393.  
  394. elbowVertical(i,d) {
  395. return "M" + d.x + "," + (d.y+this.radius) +
  396. "V" + (((d.y - i.y) / 2) + i.y+this.radius) +
  397. "H" + i.x + "V" + (i.y+this.radius);
  398. }
  399. /*
  400. diagonal(s, d) {
  401. return `M ${s.x} ${s.y}
  402. C ${s.x} ${(s.y + d.y) / 2},
  403. ${d.x} ${(s.y + d.y) / 2},
  404. ${d.x} ${d.y}`;
  405. }*/
  406.  
  407. diagonalHorizontal = d3.linkHorizontal().x(d => d.y).y(d => d.x)
  408.  
  409. diagonalVertical = d3.linkVertical().x(d => d.x).y(d => d.y+this.radius)
  410.  
  411. zoomed = () => {
  412. // this.zoomLayer.attr("transform", d3.event.transform)
  413. var data = d3.event.transform
  414. this.zoomLayer.attr("transform", "translate(" + (data.x + (this.rootStartPosX * data.k)) + "," + (data.y + (this.rootStartPosY * data.k)) + ")scale(" + data.k + ")");
  415. }
  416.  
  417. populate_tooltip_content = (data) => {
  418. if (this.opts.tooltip) {
  419. const tooltipDetail = this.opts.tooltip;
  420. let content = "";
  421. for (let [key, value] of Object.entries(tooltipDetail)) {
  422. if (data[value]) {
  423. content += this.translation[key] + ": " + data[value] + "<br/>"
  424. }
  425. }
  426. if (content != "") {
  427. this.div.html("<div>" + content + "</div>")
  428. .style("padding", "10px")
  429. .style("width", 'auto')
  430. .style("height", 'auto')
  431. } else {
  432. this.div.html("<div>" + this.translation.no_record + "</div>")
  433. .style("padding", "10px")
  434. .style("width", 'auto')
  435. .style("height", 'auto')
  436. }
  437. } else {
  438. this.div.style('opacity', 0);
  439. }
  440. }
  441.  
  442. show_tooltip(d) {
  443. //console.log(d, d.data);
  444. if (this.opts.urlDetail && d.data[this.user_var]) {
  445.  
  446. const url = this.opts.urlDetail.replace(':id', d.data[this.user_var]);
  447. //const url = opts.treeType == 'sponsor' ? 'sponsor-tree' : 'placement-tree';
  448. // d3.json('/sponsor-tree/' + `${d.data.user_id}` + '/details')
  449. //axios.get(`/${url}/${d.data.user_id}/details`)
  450. //d3.json(opts.urlDetail)
  451. axios.get(url)
  452. .then((response) => {
  453. this.populate_tooltip_content(response.data.data);
  454. })
  455. .catch(err => {
  456. console.log(err);
  457. });
  458. } else {
  459. this.populate_tooltip_content(d.data);
  460. }
  461. }
  462.  
  463. move_tooltip = () => {
  464. this.div.style('opacity', 1);
  465. /*var event = d3.event;
  466.  
  467. var posX = event.pageX - $(id).offset().left + 50,
  468. posY = event.pageY - $(id).offset().top + 15;
  469.  
  470. div
  471. .style('opacity', 1)
  472. .style("left", posX + "px")
  473. .style("top", posY + "px");*/
  474. }
  475.  
  476. hide_tooltip = () => {
  477. this.div.style('opacity', 0);
  478. /*div
  479. .style('opacity', 0)
  480. .html("")
  481. .style("padding", 0)
  482. .style("left", 0)
  483. .style("top", 0)
  484. .style("width", 0)
  485. .style("height", 0)*/
  486. }
  487.  
  488. click = async (d) => {
  489. // if (div.style('opacity') == 0) hide_tooltip();
  490.  
  491. if (d.children) {
  492. d._children = d.children;
  493. d.children = null;
  494. this.update(d);
  495. } else {
  496. const parent = this;
  497. if (!d.children && !d._children && d.data.children_count > 0 && this.opts.url && d.data[this.user_var]) {
  498. const url = this.opts.url.replace(':id', d.data[this.user_var]);
  499. //await d3.json(`${opts.url}/${d.data.user_id}`)
  500. //await axios.get(`${opts.url}/${d.data.user_id}`)
  501. //d3.json(`${opts.url}`)
  502. await axios.get(url)
  503. .then(function (data) {
  504. let child = [];
  505. d.children = []
  506. d.data.children = []
  507. const dataChild = data.data.data.children;
  508. dataChild.forEach(function (i) {
  509. child = d3.hierarchy(i);
  510. child.depth = d.depth + 1;
  511. child.height = d.height - 1;
  512. child.parent = d;
  513. d.children.push(child);
  514. d.data.children.push(child.data);
  515. parent.update(d);
  516. })
  517. })
  518. .catch(function (error) {
  519. let errMsg = ""
  520. if (error.response.data.errors) {
  521. const err = error.response.data.errors;
  522. for (var prop in err) {
  523. if (err.hasOwnProperty(prop)) {
  524. console.log(prop + " = " + err[prop]);
  525. errMsg += err[prop] + "<br>";
  526. }
  527. }
  528. } else {
  529. errMsg = error.response.data.message;
  530. }
  531. CreateNoty({
  532. 'text': errMsg,
  533. 'type': 'error'
  534. });
  535. })
  536.  
  537. }else {
  538. d.children = d._children;
  539. d._children = null;
  540. this.update(d);
  541. }
  542. }
  543.  
  544. // this.update(d);
  545. }
  546.  
  547. setHeight(el, val) {
  548. if (typeof val === "function") val = val();
  549. if (typeof val === "string") el.style.height = val;
  550. else el.style.height = val + "px";
  551. }
  552.  
  553. }
  554.  
  555. export default Tree
  556.  
  557.  
  558. /**
  559. * How to use
  560. * Tree('tree-vertical',data,{
  561. customChild:"http://www.clker.com/cliparts/1/4/5/a/1331068897296558865Sitting%20Racoon.svg", //for custom display logo (optional)
  562. customChildMultiple: "https://imgc.apk.tools/300/d/3/1/com.leomaz.flix.png", //for custom display logo have downline (optional)
  563. radius: 15,
  564. url: "{{route('admin.sponsor.children',':id')}}", //for custom show the children list
  565. displayname: "nickname", // text for display
  566. urlDetail: "{{route('admin.sponsor.details',':id')}}", //for display user detail (optional)
  567. translationKeys: {
  568. nickname: '{{ __("a_sponsor.nickname") }}',
  569. total: '{{ __("a_sponsor.total") }}',
  570. rank: '{{ __("a_sponsor.rank") }}',
  571. shareholder: '{{ __("a_sponsor.shareholder") }}',
  572. allocated_amount: '{{ __("a_sponsor.allocated_amount") }}',
  573. downlines_allocated_amount: '{{__("m_sponsor.downline allocated amount")}}',
  574. account_status: '{{__("m_sponsor.account status")}}',
  575. no_record:'{{ __("a_sponsor.no_record") }}'
  576. },
  577. tooltip: {
  578. nickname: "nickname",
  579. total: "total_package",
  580. rank: "rank",
  581. shareholder: "total_shareholder",
  582. allocated_amount: "allocated_amount",
  583. downlines_allocated_amount: "downlines_allocated_amount",
  584. account_status: "account_status"
  585. },
  586. direction: "v",//default v= vertical, h = horizontal (optional),
  587. ease: "",// easeElastic,easeBounce,easeLinear,easeSin,easeQuad,easeCubic,easePoly,easeCircle,easeExp,easeBack
  588. defaultDisplayLevel: 1,
  589. line: 's', //s=stright, c=cave
  590. user_var: 'user_id',
  591. lavel_var:'rank',
  592. level_icons:{
  593. 1:"{{asset('img/Childless.png')}}",
  594. 2:"{{asset('img/Childful.png')}}",
  595. },
  596. })
  597. */
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement