[YUNIKORN-2347] Navigate to the center of the queue SVG (#186)
Closes: #186
Signed-off-by: Yu-Lin Chen <chenyulin0719@apache.org>
diff --git a/src/app/components/queue-v2/queues-v2.component.html b/src/app/components/queue-v2/queues-v2.component.html
index a3c137e..c3e5153 100644
--- a/src/app/components/queue-v2/queues-v2.component.html
+++ b/src/app/components/queue-v2/queues-v2.component.html
@@ -21,6 +21,12 @@
<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>
</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 f9a55f1..fd3fd4a 100644
--- a/src/app/components/queue-v2/queues-v2.component.scss
+++ b/src/app/components/queue-v2/queues-v2.component.scss
@@ -23,28 +23,64 @@
background-color: white;
padding: 20px; /* Adjust padding to your preference */
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
- overflow: auto; /* Add scroll for overflow content */
+ overflow: show; /* Add scroll for overflow content */
}
.header .title-group {
display: flex;
align-items: center;
- justify-content: center;
+ justify-content: space-between;
padding-bottom: 1rem;
}
-
- .title-group .icon {
- height: 2rem;
- width: 2rem;
- margin-right: 10px;
- }
-
+
.title-group div {
+ flex-grow: 1;
+ text-align: center;
font-size: 1.25rem;
font-weight: 600;
- color: #010407;
+ color: #010407;
}
+ .fit-to-screen-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;
+ }
+
+ .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;
+ transition: opacity 0.3s, visibility 0.3s;
+ }
+
+ .fit-to-screen-button:hover .tooltip {
+ margin-bottom: 10px;
+ visibility: visible;
+ opacity: 1;
+ }
+
+
+ .fit-to-screen-button:hover {
+ background-color: #8090a5;
+ }
+
.visualize-area {
display: flex;
flex-direction: column;
diff --git a/src/app/components/queue-v2/queues-v2.component.ts b/src/app/components/queue-v2/queues-v2.component.ts
index 823a8db..58f8da2 100644
--- a/src/app/components/queue-v2/queues-v2.component.ts
+++ b/src/app/components/queue-v2/queues-v2.component.ts
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit} from '@angular/core';
import { Router } from '@angular/router';
import { NgxSpinnerService } from 'ngx-spinner';
import { QueueInfo } from '@app/models/queue-info.model';
@@ -69,23 +69,77 @@
if (data && data.rootQueue) {
this.rootQueue = data.rootQueue;
queueVisualization(this.rootQueue as QueueInfo)
+ setTimeout(() => this.adjustToScreen(),1000) // since the ngAfterViewInit hook is not working, we used setTimeout instead
}
});
}
+
+ adjustToScreen() {
+ const fitButton = document.getElementById('fitButton');
+ fitButton?.click();
+ }
}
function queueVisualization(rawData : QueueInfo){
+ let numberOfNode = 0;
+ const duration = 750;
+
const svg = select('.visualize-area').append('svg')
.attr('width', '100%')
.attr('height', '100%')
-
- const svgWidth = 1150;
- const svgHeight = 600;
+
+ 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);
- // Center the group
+ // Add some padding so that the graph is not touching the edges
+ const paddingPercent = 0.9;
+ scaleFactor = scaleFactor * paddingPercent;
+ return 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};
+ }
+
+ 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)
+ })
+ }
+
+ // Append a svg group which holds all nodes and which is for the d3 zoom
const svgGroup = svg.append("g")
- .attr("transform", `translate(${svgWidth / 3}, ${svgHeight / 10})`);
-
+
+ const fitButton = select(".fit-to-screen-button")
+ .on("click", function() {
+ adjustVisulizeArea(duration)
+ })
+ .on('mouseenter', function() {
+ select(this).select('.tooltip')
+ .style('visibility', 'visible')
+ .style('opacity', 1);
+ })
+ .on('mouseleave', function() {
+ select(this).select('.tooltip')
+ .style('visibility', 'hidden')
+ .style('opacity', 0);
+ });
+
const treelayout = d3flextree
.flextree<QueueInfo>({})
.nodeSize((d) => {
@@ -98,15 +152,11 @@
.zoom<SVGSVGElement, unknown>()
.scaleExtent([0.1, 5])
.on("zoom", (event) => {
- const initialTransform = d3zoom.zoomIdentity.translate(svgWidth / 3, svgHeight / 10);
- svgGroup.attr("transform", event.transform.toString() + initialTransform.toString());
+ svgGroup.attr("transform", event.transform)
});
svg.call(zoom);
- let numberOfNode = 0;
- const duration = 750;
const root = d3hierarchy.hierarchy(rawData);
-
update(root);
function update(source: any){