d3treemap.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
  2. function drawTreeMap(data, containerID, nameSeperators = [],WIDTH = 800, HEIGHT=800) {
  3. function getOrCreateNode(id, name, dict){
  4. var node = dict[id];
  5. if (!node){
  6. node = {}
  7. node.name = name;
  8. dict[id] = node;
  9. }
  10. return node;
  11. }
  12. function addNode(parentid, id, name, dict){
  13. var parent = dict[parentid];
  14. var node = dict[id];
  15. if (!node){
  16. node = getOrCreateNode(id,name,dict);
  17. if (!parent.children){
  18. parent.children = [];
  19. }
  20. parent.children.push(node);
  21. }
  22. return node;
  23. }
  24. function getName(name, nameSeperators){
  25. var newName = name;
  26. for (const nameSeperator of nameSeperators){
  27. var index = newName.lastIndexOf(nameSeperator);
  28. if (index!=-1){
  29. return name.substring(index+1);
  30. }
  31. }
  32. return newName;
  33. }
  34. function transform(data,nameSeperators){
  35. var idToNode = [];
  36. var root = getOrCreateNode("-1","root",idToNode);
  37. var columns = data.head.vars;
  38. data.results.bindings.forEach(item => {
  39. var containerID = item[columns[4]] ? item[columns[4]].value : "-1";
  40. var containerName = getName(item[columns[3]] ? item[columns[3]].value : "root",nameSeperators) ;
  41. var childID = item[columns[1]].value;
  42. var childeName = getName(item[columns[0]] ? item[columns[0]].value : "",nameSeperators);
  43. var mass = item[columns[2]]?item[columns[2]].value : "0";
  44. addNode("-1",containerID,containerName,idToNode);
  45. var child = addNode(containerID,childID,childeName,idToNode);
  46. child.value = parseInt(mass);
  47. });
  48. return root;
  49. }
  50. function getVal(d){
  51. return d.value + (d.children ? d.data.value : 0);
  52. }
  53. var treeData = transform(data, nameSeperators);
  54. var margin = {top: 10, right: 10, bottom: 10, left: 10},
  55. width = WIDTH - margin.left - margin.right,
  56. height = HEIGHT - margin.top - margin.bottom;
  57. // append the svg object to the container
  58. var svg = d3.select(containerID).append("svg");
  59. svg.style('font-family', 'sans-serif')
  60. .attr('width', width)
  61. .attr('height', height)
  62. const g = svg.append('g').attr('class', 'treemap-container')
  63. var root = d3.hierarchy(treeData)
  64. .sum(d => d.value)
  65. .sort((a, b) => b.value - a.value)
  66. colorScale = d3.scaleOrdinal( d3.schemeSet2 )
  67. d3.treemap()
  68. .size([ width, height ])
  69. .padding(2)
  70. .paddingTop(10)
  71. .round(true)(root);
  72. // Place the labels for our Root elements (if they have children)
  73. g.selectAll('text.title')
  74. // The data is the first "generation" of children
  75. .data( root.children.filter(function(d){return d.children!=null}) )
  76. .join('text')
  77. .attr('class', 'title')
  78. // The rest is just placement/styling
  79. .attr('x', d => d.x0)
  80. .attr('y', d => d.y0)
  81. .attr('dy', '0.6em')
  82. .attr('dx', 3)
  83. .style('font-size', 12)
  84. // Remember, the data on the original node is available on node.data (d.data here)
  85. .text(d => d.data.name + " (" + getVal(d) + ")")
  86. // Now, we place the groups for all of the leaf nodes
  87. const leaf = g.selectAll('g.leaf')
  88. // root.leaves() returns all of the leaf nodes
  89. .data(root.leaves())
  90. .join('g')
  91. .attr('class', 'leaf')
  92. // position each group at the top left corner of the rect
  93. .attr('transform', d => `translate(${ d.x0 },${ d.y0 })`)
  94. .style('font-size', 10)
  95. // A title element tells the browser to display its text value
  96. // in a popover when the cursor is held over a rect. This is a simple
  97. // way to add some interactivity
  98. leaf.append('title')
  99. .text(d => `${ d.parent.data.name }-${ d.data.name }\n${ d.value.toLocaleString()}`)
  100. // Now we append the rects. Nothing crazy here
  101. leaf.append('rect')
  102. .attr('fill', d => colorScale(d.parent.data.name))
  103. .attr('opacity', 0.7)
  104. // the width is the right edge position - the left edge position
  105. .attr('width', d => d.x1 - d.x0)
  106. // same for height, but bottom - top
  107. .attr('height', d => d.y1 - d.y0)
  108. // make corners rounded
  109. .attr('rx', 3)
  110. .attr('ry', 3)
  111. // This next section checks the width and height of each rectangle
  112. // If it's big enough, it places labels. If not, it doesn't.
  113. leaf.each((d, i, arr) => {
  114. // The current leaf element
  115. const current = arr[i]
  116. const left = d.x0,
  117. right = d.x1,
  118. // calculate its width from the data
  119. width = right - left,
  120. // calculate its height from the data
  121. height = d.y1 - d.y0
  122. // too small to show text
  123. const tooSmall = width < 34 || height < 25
  124. const text = d3.select( current ).append('text')
  125. // If it's too small, don't show the text
  126. .attr('opacity', tooSmall ? 0 : 0.9)
  127. .selectAll('tspan')
  128. .data(d => [ d.data.name, d.value.toLocaleString() ])
  129. .join('tspan')
  130. .attr('x', 3)
  131. .attr('y', (d,i) => i ? '2.5em' : '1.15em')
  132. .text(d => d)
  133. });
  134. svg.call(d3.zoom()
  135. .extent([[0, 0], [width, height]])
  136. .scaleExtent([1, 8])
  137. .on("zoom", zoomed));
  138. function zoomed({transform}) {
  139. g.attr("transform", transform);
  140. }
  141. }
  142. export { drawTreeMap };
  143. export default drawTreeMap;