This is an automated email from the ASF dual-hosted git repository.

ccondit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/yunikorn-web.git


The following commit(s) were added to refs/heads/master by this push:
     new 06ae543  [YUNIKORN-2349] Allow changing the orientation of the queue 
graph (#202)
06ae543 is described below

commit 06ae543103a265d305668e650954f214dff2989b
Author: DouPache <[email protected]>
AuthorDate: Thu Oct 17 15:45:12 2024 -0500

    [YUNIKORN-2349] Allow changing the orientation of the queue graph (#202)
    
    Closes: #202
    
    Signed-off-by: Craig Condit <[email protected]>
---
 .../components/queue-v2/queues-v2.component.html   |  18 +-
 .../components/queue-v2/queues-v2.component.scss   |  45 +-
 src/app/components/queue-v2/queues-v2.component.ts | 590 +++++++++++----------
 src/app/utils/common.util.spec.ts                  |  21 +-
 src/app/utils/common.util.ts                       |  24 +-
 5 files changed, 406 insertions(+), 292 deletions(-)

diff --git a/src/app/components/queue-v2/queues-v2.component.html 
b/src/app/components/queue-v2/queues-v2.component.html
index d1397ed..cfa6fc9 100644
--- a/src/app/components/queue-v2/queues-v2.component.html
+++ b/src/app/components/queue-v2/queues-v2.component.html
@@ -21,12 +21,18 @@
       <div class="header">
         <div class="title-group">
           <div>Partition</div>
-          <button id="fitButton" class="fit-to-screen-button">
-            <svg xmlns="http://www.w3.org/2000/svg"; width="24" height="24" 
viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.5" 
stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6">
-              <path d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 
12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 
1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 
1.125-1.125V9.75M8.25 21h8.25" />          
-            </svg>
-            <span id="tooltip" class="tooltip" role="tooltip">fit to 
screen</span>
-          </button>
+            <button id="ortButton" class="ort-button">
+              <svg xmlns="http://www.w3.org/2000/svg"; width="24" height="24" 
viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.5" 
stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6">
+                <path d="M21 12c0-4.97-4.03-9-9-9s-9 4.03-9 9 4.03 9 9 9c2.39 
0 4.68-0.94 6.36-2.62l-1.41-1.41C15.54 18.37 13.85 19 12 19c-3.86 
0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7h-3l4 4 4-4h-3z" />
+              </svg>
+              <span class="tooltip" role="tooltip">rotate </span>
+            </button>
+            <button id="fitButton" class="fit-to-screen-button">
+              <svg xmlns="http://www.w3.org/2000/svg"; width="24" height="24" 
viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.5" 
stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6">
+                <path d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 
0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 
1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 
1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />          
+              </svg>
+              <span id="tooltip" class="tooltip" role="tooltip">fit to 
screen</span>
+            </button>
         </div>
       </div>
       <div class="body">
diff --git a/src/app/components/queue-v2/queues-v2.component.scss 
b/src/app/components/queue-v2/queues-v2.component.scss
index 8f0f1ef..3c5e597 100644
--- a/src/app/components/queue-v2/queues-v2.component.scss
+++ b/src/app/components/queue-v2/queues-v2.component.scss
@@ -39,7 +39,7 @@
     font-weight: 600; 
     color: #010407;
   } 
-  
+
   .fit-to-screen-button {
     position: relative;
     display: inline-flex;
@@ -51,6 +51,48 @@
     border-radius: 5px;
     cursor: pointer;
   }
+  .ort-button {
+    position: relative;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    padding: 6px 6px;
+    background-color: #e5ecf6;
+    border: 1px solid #132030;
+    border-radius: 5px;
+    cursor: pointer;
+    overflow: show;
+  }
+
+  .ort-button:hover {
+      background-color: #8090a5;
+  }
+
+  .ort-button .tooltip {
+    width: 100px;
+    position: absolute;
+    bottom: 90%;
+    left: 50%;
+    transform: translateX(-50%);
+    background-color: rgb(225, 228, 241);
+    color: rgb(6, 7, 6);
+    text-align: center;
+    padding: 5px 5px;
+    border-radius: 6px;
+    visibility: hidden;
+    opacity: 1;
+  }
+
+  .ort-button:hover .tooltip {
+      margin-bottom: 10px;
+      visibility: visible;
+      opacity: 1;
+  }
+
+  .ort-button,
+  .fit-to-screen-button {
+    margin-right: 10px;
+  }
 
   .tooltip {
       width: 100px;
@@ -65,7 +107,6 @@
       border-radius: 6px;
       visibility: hidden;
       opacity: 1;
-      transition: opacity 0.3s, visibility 0.3s;
   }
 
   .fit-to-screen-button:hover .tooltip {
diff --git a/src/app/components/queue-v2/queues-v2.component.ts 
b/src/app/components/queue-v2/queues-v2.component.ts
index fddd045..156b352 100644
--- a/src/app/components/queue-v2/queues-v2.component.ts
+++ b/src/app/components/queue-v2/queues-v2.component.ts
@@ -99,321 +99,367 @@ function queueVisualization(rawData : QueueInfo , 
componentInstance: QueueV2Comp
   let isShowingDetails = false;
   let selectedNode: any = null;
 
-  const duration = 750;
+  // Set this variable to 'horizontal' or 'vertical' to change orientation
+  // Define a type for the orientation
+  type Orientation = 'horizontal' | 'vertical';
+
+  // Declare orientation as the defined type
+  let orientation: Orientation = 'horizontal';
+
+  const duration = 500;
 
   const svg = select('.visualize-area').append('svg')
-               .attr('width', '100%')
-               .attr('height', '100%')
-                  
-    function fitGraphScale(){
-      const baseSvgElem = svg.node() as SVGGElement;
-      const bounds = baseSvgElem.getBBox();
-      const parent = baseSvgElem.parentElement as HTMLElement;
-      const fullWidth = parent.clientWidth;
-      const fullHeight = parent.clientHeight;
-      
-      const xfactor: number = fullWidth / bounds.width;
-      const yfactor: number = fullHeight / bounds.height;
-      let scaleFactor: number = Math.min(xfactor, yfactor);
-
-       // Add some padding so that the graph is not touching the edges
-       const paddingPercent = 0.9;
-       scaleFactor = scaleFactor * paddingPercent;
-       return scaleFactor
-    }
+    .attr('width', '100%')
+    .attr('height', '100%');
+
+  function fitGraphScale() {
+    const baseSvgElem = svg.node() as SVGGElement;
+    const bounds = baseSvgElem.getBBox();
+    const parent = baseSvgElem.parentElement as HTMLElement;
+    const fullWidth = parent.clientWidth;
+    const fullHeight = parent.clientHeight;
+    
+    const xfactor: number = fullWidth / bounds.width;
+    const yfactor: number = fullHeight / bounds.height;
+    let scaleFactor: number = Math.min(xfactor, yfactor);
 
-    function centerGraph() {
-        const bbox = (svgGroup.node() as SVGGElement).getBBox();
-        const cx = bbox.x + bbox.width / 2;
-        const cy = bbox.y + bbox.height / 2;
-        return {cx, cy};
-    }
+    const paddingPercent = 0.9;
+    scaleFactor = scaleFactor * paddingPercent;
+    return scaleFactor;
+  }
 
-    function adjustVisulizeArea(duration : number = 0){
-      const scaleFactor = fitGraphScale();
-      const {cx, cy} = centerGraph();
-      // make the total duration to be 1 second
-      svg.transition().duration(duration/1.5).call(zoom.translateTo, cx, cy)
-      .on("end", function() {
-        svg.transition().duration(duration/1.5).call(zoom.scaleBy, scaleFactor)
-      })
-    } 
+  function centerGraph() {
+    const bbox = (svgGroup.node() as SVGGElement).getBBox();
+    const cx = bbox.x + bbox.width / 2;
+    const cy = bbox.y + bbox.height / 2;
+    return {cx, cy};
+  }
 
-    // Append a svg group which holds all nodes and which is for the d3 zoom
-    const svgGroup = svg.append("g")
+  function adjustVisulizeArea(duration: number = 0) {
+    const scaleFactor = fitGraphScale();
+    const {cx, cy} = centerGraph();
+    svg.transition().duration(duration/1.5).call(zoom.translateTo, cx, cy)
+    .on("end", function() {
+      svg.transition().duration(duration/1.5).call(zoom.scaleBy, scaleFactor)
+    });
+  } 
 
-    const fitButton = select(".fit-to-screen-button")
+  function changeOrientation() {
+    orientation = orientation === 'horizontal' ? 'vertical' : 'horizontal';
+    const root = d3hierarchy.hierarchy(rawData);
+    update(root);
+
+     // Update the position of existing plus circles, plus text, and queue 
names
+     svgGroup.selectAll('g.card')
+      .each(function(d: any) {
+        const group = select(this);
+        group.select('circle')
+          .attr("cx", orientation === 'horizontal' ? 300 : 150)
+          .attr("cy", orientation === 'horizontal' ? 60 : 120);
+        group.select('.plus-text')
+          .attr("x", orientation === 'horizontal' ? 300 : 150)
+          .attr("y", orientation === 'horizontal' ? 67 : 127);
+      });
+  }
+
+  const svgGroup = svg.append("g");
+
+  const fitButton = select(".fit-to-screen-button")
     .on("click", function() {
-      adjustVisulizeArea(duration)
+      adjustVisulizeArea(duration);
     })
     .on('mouseenter', function() {
       select(this).select('.tooltip')
-            .style('visibility', 'visible')
-            .style('opacity', 1);
+        .style('visibility', 'visible')
+        .style('opacity', 1);
     })
     .on('mouseleave', function() {
       select(this).select('.tooltip')
-            .style('visibility', 'hidden')
-            .style('opacity', 0);
+        .style('visibility', 'hidden')
+        .style('opacity', 0);
     });
-    
-    const treelayout = d3flextree
-      .flextree<QueueInfo>({})
-      .nodeSize((d) => {
-          return [300, 300];
-        }
-      )
-      .spacing(() => 300);
-    
-    const zoom = d3zoom
+
+  const ortButton = select(".ort-button")
+    .on("click", function() {
+      changeOrientation();
+      setTimeout(
+        () => {
+          const fitButton = document.getElementById('fitButton');
+          if (fitButton) {
+            fitButton.click();
+          }
+        }, duration);
+    });
+
+  
+  const treelayout = d3flextree
+    .flextree<QueueInfo>({})
+    .nodeSize((d) => {
+      return orientation === 'horizontal' ? [300, 600] : [300, 300];
+    })
+    .spacing(() => orientation === 'horizontal' ? 100 : 300);
+  
+  const zoom = d3zoom
     .zoom<SVGSVGElement, unknown>()
     .scaleExtent([0.1, 5]) 
     .on("zoom", (event) => {
-      svgGroup.attr("transform", event.transform)
+      svgGroup.attr("transform", event.transform);
     });
-    svg.call(zoom);
-
-    const root = d3hierarchy.hierarchy(rawData);
-    update(root);
+  svg.call(zoom);
+
+  const root = d3hierarchy.hierarchy(rawData);
+  update(root);
+
+  function update(source: any) {
+    var treeData = treelayout(root);
+    var nodes = treeData.descendants();
+    var node = svgGroup
+      .selectAll<SVGGElement, d3hierarchy.HierarchyNode<QueueInfo>>('g.card')
+      .data(nodes, function(d: any) { 
+        return d.id || (d.id = ++numberOfNode); 
+      });
 
-    function update(source: any){
-      var treeData = treelayout(root)
-      var nodes = treeData.descendants()
-      var node = svgGroup
-          .selectAll<SVGGElement, 
d3hierarchy.HierarchyNode<QueueInfo>>('g.card')
-          .data(nodes, function(d : any) { 
-            return d.id || (d.id = ++numberOfNode); 
-          });
-
-      var nodeEnter = node
-          .enter().append('g')
-          .attr('class', 'card')
-          .attr("transform", function() {
-              if (source.x0 && source.y0) {
-                  return "translate(" + source.x0 + "," + source.y0 + ")";
-              }
-              else {
-                  return "translate(" + source.x + "," + source.y + ")";
-              }
-          })
-
-      nodeEnter.each(function(d) {
-        const group = select(this);
-        const queueName = d.data.queueName?.split(".").at(-1) ?? 
d.data.queueName;
-
-        group.append("rect")
-          .attr("width", 300) 
-          .attr("height", 120) 
-          .attr("fill", "none")
-          .attr("stroke", "white")
-          .attr("stroke-width", 2) 
-          .attr("rx", 10)
-          .attr("ry", 10)
-          .attr("class", "cardMain");
-  
-        group.append("rect")
-          .attr("width", 300)
-          .attr("height", 30)
-          .attr("fill", "#d4eaf7")
-          .attr("class", "cardTop");
-  
-        group.append("rect")
-          .attr("y", 30)
-          .attr("width", 300)
-          .attr("height", 60)
-          .attr("fill", "white")
-          .attr("class", "cardMiddle");
-  
-        group.append("rect")
-          .attr("y", 90)
-          .attr("width", 300)
-          .attr("height", 30)
-          .attr("fill", "#e6f4ea")
-          .attr("class", "cardBottom");
-        
-        group.append("image")
-          .attr("href", "./assets/images/hierarchy.svg") 
-          .attr("x", 5) 
-          .attr("y", 5) 
-          .attr("width", 20)
-          .attr("height", 20);
-  
-        group.append("text")
-          .attr("x", 30) 
-          .attr("y", 22.5)
-          .attr("font-size", "25px")
-          .attr("fill", "black")
-          .text(queueName)
-          .call(ellipsis, 270)
-          .call(tooltip, group, queueName);
+    var nodeEnter = node
+      .enter().append('g')
+      .attr('class', 'card')
+      .attr("transform", function() {
+        if (source.y0 !== undefined && source.x0 !== undefined) {
+          return orientation === 'horizontal'
+            ? `translate(${source.y0},${source.x0})`
+            : `translate(${source.x0},${source.y0})`;
+        } else {
+          return orientation === 'horizontal'
+            ? `translate(${source.y},${source.x})`
+            : `translate(${source.x},${source.y})`;
+        }
+      });
 
+    nodeEnter.each(function(d) {
+      const group = select(this);
+      const queueName = d.data.queueName?.split(".").at(-1) ?? 
d.data.queueName;
+
+      group.append("rect")
+        .attr("width", 300) 
+        .attr("height", 120) 
+        .attr("fill", "none")
+        .attr("stroke", "white")
+        .attr("stroke-width", 2) 
+        .attr("rx", 10)
+        .attr("ry", 10)
+        .attr("class", "cardMain");
+
+      group.append("rect")
+        .attr("width", 300)
+        .attr("height", 30)
+        .attr("fill", "#d4eaf7")
+        .attr("class", "cardTop");
+
+      group.append("rect")
+        .attr("y", 30)
+        .attr("width", 300)
+        .attr("height", 60)
+        .attr("fill", "white")
+        .attr("class", "cardMiddle");
+
+      group.append("rect")
+        .attr("y", 90)
+        .attr("width", 300)
+        .attr("height", 30)
+        .attr("fill", "#e6f4ea")
+        .attr("class", "cardBottom");
+      
+      group.append("image")
+        .attr("href", "./assets/images/hierarchy.svg") 
+        .attr("x", 5) 
+        .attr("y", 5) 
+        .attr("width", 20)
+        .attr("height", 20);
+
+
+      group.append("text")
+        .attr("x", 30) 
+        .attr("y", 22.5)
+        .attr("font-size", "25px")
+        .attr("fill", "black")
+        .text(queueName)
+        .call(ellipsis, 270)
+        .call(tooltip, group, queueName);
+      
         const plusCircle = group.append("circle")
-          .attr("cx", 150)
-          .attr("cy", 120) 
-          .attr("r", 20)   
-          .attr("fill", "white") 
-          .attr("stroke", "black") 
-          .attr("stroke-width", 1)
-          .style("visibility", "hidden")
-          .on('click', function(event) {
-            event.stopPropagation(); // Prevents the event from bubbling up to 
parent elements
-            click(event, d); 
-          });
-        
-        const plusText = group.append("text")
-          .attr("x", 150) 
-          .attr("y", 127) 
-          .attr("text-anchor", "middle") 
-          .attr("font-size", "20px") 
-          .attr("fill", "black") 
-          .text("+")
-          .attr("pointer-events", "none") // Prevents the text from 
interfering with the click event
-          .style("visibility", "hidden");
-        
-        group.on("mouseover", function() {
-          plusCircle.style("visibility", "visible");
-          plusText.style("visibility", "visible");
+        .attr("cx", () => orientation === 'horizontal' ? 300 : 150) // Right 
side if horizontal, center if vertical
+        .attr("cy", () => orientation === 'horizontal' ? 60 : 120)  // Center 
if horizontal, bottom if vertical
+        .attr("r", 20)
+        .attr("fill", "white")
+        .attr("stroke", "black")
+        .attr("stroke-width", 1)
+        .style("visibility", "hidden")
+        .on('click', function(event) {
+          event.stopPropagation();
+          click(event, d);
         });
+      
+        const plusText = group.append("text")
+        .classed("plus-text", true)
+        .attr("x", () => orientation === 'horizontal' ? 300 : 150) 
+        .attr("y", () => orientation === 'horizontal' ? 67 : 127)  
+        .attr("text-anchor", "middle")
+        .attr("font-size", "20px")
+        .attr("fill", "black")
+        .text("+")
+        .attr("pointer-events", "none")
+        .style("visibility", "hidden");
+      
+      group.on("mouseover", function() {
+        plusCircle.style("visibility", "visible");
+        plusText.style("visibility", "visible");
+      });
 
-        group.on("click", function() {
-          if(selectedNode == this || selectedNode == null){
-            isShowingDetails = !isShowingDetails;
-          }else{
-            //set the previous selected node to its original css
-            select(selectedNode).select('.cardMain').attr("stroke", "white")
-            .attr("stroke-width", 2)
+      group.on("click", function() {
+        if(selectedNode == this || selectedNode == null){
+          isShowingDetails = !isShowingDetails;
+        } else {
+          select(selectedNode).select('.cardMain').attr("stroke", "white")
+          .attr("stroke-width", 2);
 
-            select(selectedNode).select('.cardTop').attr("fill", "#d4eaf7")
-          }
+          select(selectedNode).select('.cardTop').attr("fill", "#d4eaf7");
+        }
 
-          selectedNode = this;
-          componentInstance.seletedInfo = d.data;
+        selectedNode = this;
+        componentInstance.seletedInfo = d.data;
 
-          if(isShowingDetails){
-            console.log("showing details", componentInstance.seletedInfo)
-            select(this).select('.cardMain').attr("stroke-width", 8)
-            .attr("stroke", "#50505c")
+        if(isShowingDetails){
+          console.log("showing details", componentInstance.seletedInfo);
+          select(this).select('.cardMain').attr("stroke-width", 8)
+          .attr("stroke", "#50505c");
 
-            select(this).select('.cardTop').attr("fill", "#95d5f9")
+          select(this).select('.cardTop').attr("fill", "#95d5f9");
 
-            select(".additional-info-element").style("display", "block");
-          } else {
-            select(this).select('.cardMain').attr("stroke-width", 2)
-            .attr("stroke", "white")
+          select(".additional-info-element").style("display", "block");
+        } else {
+          select(this).select('.cardMain').attr("stroke-width", 2)
+          .attr("stroke", "white");
 
-            select(this).select('.cardTop').attr("fill", "#d4eaf7")
+          select(this).select('.cardTop').attr("fill", "#d4eaf7");
 
-            select(".additional-info-element").style("display", "none");
-          }
+          select(".additional-info-element").style("display", "none");
+        }
 
-          adjustVisulizeArea(duration)
-        });
-      
-        // Hide the circle and '+' text when the mouse leaves the node
-        group.on("mouseout", function() {
-          plusCircle.style("visibility", "hidden");
-          plusText.style("visibility", "hidden");
-        });
+        adjustVisulizeArea(duration);
+      });
+    
+      group.on("mouseout", function() {
+        plusCircle.style("visibility", "hidden");
+        plusText.style("visibility", "hidden");
+      });
 
-        // Add hover effect to the circle to change its color to grey
-        plusCircle.on("mouseover", function() {
-          select(this).attr("fill", "grey");
-        });
+      plusCircle.on("mouseover", function() {
+        select(this).attr("fill", "grey");
+      });
 
-        // Reset circle color when mouse leaves
-        plusCircle.on("mouseout", function() {
-          select(this).attr("fill", "white");
-        });
+      plusCircle.on("mouseout", function() {
+        select(this).attr("fill", "white");
+      });
+    });
 
+    const nodeUpdate = nodeEnter.merge(node)
+    .attr("stroke", "black");
+    
+    nodeUpdate.transition()
+      .duration(duration)
+      .attr("transform", function(this: SVGGElement, event: any, i: any, arr: 
any) {
+        const d: any = select(this).datum();
+        return orientation === 'horizontal'
+          ? `translate(${d.y},${d.x})`
+          : `translate(${d.x},${d.y})`;
       });
+   
+    nodeUpdate.select('.cardBottom')
+    .style("fill", function(d: any) {
+      return d._children ? "#9fc6aa" : "#e6f4ea";
+    });
+    
+    var nodeExit = node.exit().transition()
+      .duration(duration)
+      .attr("transform", function(this: SVGGElement, event: any, i: any, arr: 
any) {
+        const d = select(this).datum();
+        return orientation === 'horizontal'
+          ? `translate(${source.y},${source.x})`
+          : `translate(${source.x},${source.y})`;
+      })
+      .remove();
   
-      const nodeUpdate = nodeEnter.merge(node)
+    const links = treeData.links();
+    let link = svgGroup.selectAll<SVGPathElement, 
d3hierarchy.HierarchyPointLink<QueueInfo>>('path.link')
+      .data(links, function(d: any) { return d.target.id; });
+
+    const linkEnter = link.enter().insert('path', "g")
+      .attr("class", "link")
+      .attr('d', d => {
+        const o = orientation === 'horizontal'
+          ? {y: source.y0 || source.y, x: source.x0 || source.x}
+          : {x: source.x0 || source.x, y: source.y0 || source.y};
+        return diagonal(o, o, orientation);
+      })
+      .attr("fill", "none")
       .attr("stroke", "black")
-      
-      nodeUpdate.transition()
-        .duration(duration)
-        .attr("transform", function(this: SVGGElement , event : any , i : any, 
arr : any) {
-            const d : any = select(this).datum();
-            return "translate(" + d.x + "," + d.y + ")";
-        });
-     
-      nodeUpdate.select('.cardBottom')
-      .style("fill", function(d : any) {
-          return d._children ? "#9fc6aa" : "#e6f4ea";
+      .attr("stroke-width", "2px");
+
+    const linkUpdate = linkEnter.merge(link);
+    linkUpdate.transition()
+      .duration(duration)
+      .attr('d', d => diagonal(d.source, d.target, orientation));
+
+    const linkExit = link.exit().transition()
+      .duration(duration)
+      .attr('d', d => {
+        const o = orientation === 'horizontal'
+          ? {y: source.y, x: source.x}
+          : {x: source.x, y: source.y};
+        return diagonal(o, o, orientation);
       })
+      .remove();
+
+    nodes.forEach(function(d: any) {
+      d.x0 = d.x;
+      d.y0 = d.y;
+    });
   
-      // Remove any exiting nodes
-      var nodeExit= node.exit().transition()
-        .duration(duration)
-        .attr("transform", function(this: SVGGElement , event : any , i : any, 
arr : any) {
-            const d = select(this).datum();
-            return "translate(" + source.x + "," + source.y + ")";
-        })
-        .remove();
-    
-      // Link sections
-      const links = treeData.links();
-      let link = svgGroup.selectAll<SVGPathElement, 
d3hierarchy.HierarchyPointLink<QueueInfo>>('path.link')
-          .data(links, function(d : any) { return d.target.id; });
-  
-      const linkEnter = link.enter().insert('path', "g")
-          .attr("class", "link")
-          .attr('d', d => {
-              if (source.x0 && source.y0) {
-                  const o = {x: source.x0, y: source.y0};
-                  return diagonal(o, o);
-              }
-              else {
-                  const o = {x: source.x, y: source.y};
-                  return diagonal(o, o);
-              }
-          })
-          .attr("fill", "none")
-          .attr("stroke", "black")
-          .attr("stroke-width", "2px");
-  
-      const linkUpdate = linkEnter.merge(link);
-      linkUpdate.transition()
-          .duration(duration)
-          .attr('d', d => diagonal(d.source, d.target));
-  
-      const linkExit = link.exit().transition()
-          .duration(duration)
-          .attr('d', d => {
-              const o = {x: source.x, y: source.y};
-              return diagonal(o, o);
-          })
-          .remove();
-  
-      nodes.forEach(function(d : any) {
-          d.x0 = d.x;
-          d.y0 = d.y;
-      });
-    
-      function click(event : MouseEvent, d : any) {
-        if (d.children) {
-            d._children = d.children;
-            d.children = null;
-        } else {
-            d.children = d._children;
-            d._children = null;
-        }
-        
-        update(d);
+    function click(event: MouseEvent, d: any) {
+      if (d.children) {
+        d._children = d.children;
+        d.children = null;
+      } else {
+        d.children = d._children;
+        d._children = null;
       }
+      
+      update(d);
     }
+  }
 }
 
-function diagonal(s : any , d : any) {
-  const sourceX = s.x + 150;  // Middle of the rectangle's width
-  const sourceY = s.y + 120;  // Bottom of the rectangle
-  const targetX = d.x + 150;  // Middle of the rectangle's width
-  const targetY = d.y;        // Top of the rectangle
-
-  return `M ${sourceX} ${sourceY} 
-          V ${(sourceY + targetY) / 2} 
-          H ${targetX} 
-          V ${targetY}`;
+function diagonal(s: any, d: any, orientation: string) {
+  if (orientation == 'horizontal') {
+    const sourceY = s.y + 300;  // Right side of the rectangle
+    const sourceX = s.x + 60;   // Middle of the rectangle's height
+    const targetY = d.y;        // Left side of the target rectangle
+    const targetX = d.x + 60;   // Middle of the target rectangle's height
+
+    return `M ${sourceY} ${sourceX} 
+    H ${(sourceY + targetY) / 2} 
+    V ${targetX} 
+    H ${targetY}`;
+  } else {
+    const sourceX = s.x + 150;  // Middle of the rectangle's width
+    const sourceY = s.y + 120;  // Bottom of the rectangle
+    const targetX = d.x + 150;  // Middle of the rectangle's width
+    const targetY = d.y;        // Top of the rectangle
+
+    return `M ${sourceX} ${sourceY} 
+        V ${(sourceY + targetY) / 2} 
+        H ${targetX} 
+        V ${targetY}`;
+    }
 }
 
 function ellipsis(
diff --git a/src/app/utils/common.util.spec.ts 
b/src/app/utils/common.util.spec.ts
index 076be59..0319144 100644
--- a/src/app/utils/common.util.spec.ts
+++ b/src/app/utils/common.util.spec.ts
@@ -63,28 +63,37 @@ describe('CommonUtil', () => {
     it('should return an empty string for undefined input', () => {
       expect(CommonUtil.absoluteUsedMemoryColumnFormatter(undefined)).toBe('');
     });
-
     it('should return "n/a" for "n/a" input', () => {
       
expect(CommonUtil.absoluteUsedMemoryColumnFormatter('n/a')).toBe('<strong>Memory:</strong>
 n/a');
     });
-
     it('should format memory percentage correctly', () => {
       expect(CommonUtil.absoluteUsedMemoryColumnFormatter('Memory: 
40%')).toBe('<strong>Memory:</strong> 40%');
-    });  
+    });    
+    it('should handle input without percentage sign', () => {
+      
expect(CommonUtil.absoluteUsedMemoryColumnFormatter('40')).toBe('<strong>Memory:</strong>
 n/a (wrong memory format)');
+    });
+    it('should handle incorrect input', () => {
+      
expect(CommonUtil.absoluteUsedMemoryColumnFormatter('cpumMMEORY')).toBe('<strong>Memory:</strong>
 n/a (wrong memory format)');
+    });
   });
 
   describe('checkin absoluteUsedCPUColumnFormatter method result', () => {
     it('should return an empty string for undefined input', () => {
-      expect(CommonUtil.absoluteUsedMemoryColumnFormatter(undefined)).toBe('');
+      expect(CommonUtil.absoluteUsedCPUColumnFormatter(undefined)).toBe('');
     });
-
     it('should return "n/a" for "n/a" input', () => {
       
expect(CommonUtil.absoluteUsedCPUColumnFormatter('n/a')).toBe('<strong>CPU:</strong>
 n/a');
     });
 
     it('should format memory percentage correctly', () => {
-      expect(CommonUtil.absoluteUsedMemoryColumnFormatter('CPU: 
60%')).toBe('<strong>CPU:</strong> 60%');
+      expect(CommonUtil.absoluteUsedCPUColumnFormatter('CPU: 
60%')).toBe('<strong>CPU:</strong> 60%');
     });  
+    it('should handle input without percentage sign', () => {
+      
expect(CommonUtil.absoluteUsedCPUColumnFormatter('60')).toBe('<strong>CPU:</strong>
 n/a (wrong cpu format)');
+    });
+    it('should handle incorrect input', () => {
+      
expect(CommonUtil.absoluteUsedCPUColumnFormatter('cpumMMEORY')).toBe('<strong>CPU:</strong>
 n/a (wrong cpu format)');
+    });
   });
 
   describe('queueResourceColumnFormatter', () => {
diff --git a/src/app/utils/common.util.ts b/src/app/utils/common.util.ts
index 2a0d54c..63a088e 100644
--- a/src/app/utils/common.util.ts
+++ b/src/app/utils/common.util.ts
@@ -88,8 +88,15 @@ export class CommonUtil {
     if (value === 'n/a') {
       return '<strong>Memory:</strong> n/a';
     }
-    let memory = value.split('%')[0] + '%';
-    return CommonUtil.queueResourceColumnFormatter(memory);
+    //Memory: 4%, CPU: 2%
+    const memoryRegex = /Memory: ([0-9]|[1-9][0-9]|100)%/;
+    const match = value.match(memoryRegex);
+  
+    if(match){
+      return CommonUtil.queueResourceColumnFormatter(match[0]);
+    } else {
+      return '<strong>Memory:</strong> n/a (wrong memory format)';
+    }
   }
 
   static absoluteUsedCPUColumnFormatter(value: string | undefined): string {
@@ -99,10 +106,15 @@ export class CommonUtil {
     if (value === 'n/a') {
       return '<strong>CPU:</strong> n/a';
     }
-    let cpu = value.split('%')[1] + '%';
-    cpu = cpu.replace(',', '');
-    
-    return CommonUtil.queueResourceColumnFormatter(cpu);
+    //Memory: 4%, CPU: 2%
+    const cpuRegex = /CPU: ([0-9]|[1-9][0-9]|100)%/;
+    const match = value.match(cpuRegex);
+  
+    if(match){
+      return CommonUtil.queueResourceColumnFormatter(match[0]);
+    } else {
+      return '<strong>CPU:</strong> n/a (wrong cpu format)';
+    }
   }
 
   static resourceColumnFormatter(value: string): string {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to