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

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new 8d17b77  TimeChart: viewport-clamped tooltip position (no more 
right-edge cutoff)
8d17b77 is described below

commit 8d17b777589bd8c296d9f58b2c918b8ed9e6e4fc
Author: Wu Sheng <[email protected]>
AuthorDate: Sun May 17 10:52:49 2026 +0800

    TimeChart: viewport-clamped tooltip position (no more right-edge cutoff)
    
    `confine: true` only constrains the tooltip to the chart's own bbox,
    which doesn't help when the chart sits in a narrow grid cell near the
    viewport edge — the tooltip stays "inside the chart" but the chart
    itself is at the screen edge so the popup still spills off.
    
    Swap confine for a `position` callback that:
      - converts the chart-local cursor point to page coords using
        container.getBoundingClientRect(),
      - flips the tooltip to the opposite side of the cursor whenever it
        would overflow viewport width / height,
      - clamps to >= 8px margin from every edge.
    
    appendToBody stays — the tooltip still needs to escape the widget
    card's overflow:hidden. The combination of appendToBody + custom
    position(..) is the standard echarts pattern for tooltips that must
    both escape a clipping ancestor AND respect the viewport.
---
 apps/ui/src/components/charts/TimeChart.vue | 43 +++++++++++++++++++++++------
 1 file changed, 35 insertions(+), 8 deletions(-)

diff --git a/apps/ui/src/components/charts/TimeChart.vue 
b/apps/ui/src/components/charts/TimeChart.vue
index 4b03a67..aba48e8 100644
--- a/apps/ui/src/components/charts/TimeChart.vue
+++ b/apps/ui/src/components/charts/TimeChart.vue
@@ -124,17 +124,44 @@ function buildOption(): echarts.EChartsCoreOption {
       // widget card's overflow:hidden. Otherwise the tooltip cuts off
       // at the card edge whenever a chart sits near the boundary.
       appendToBody: true,
-      // Containerless trigger needs `confine: true` so the popup
-      // sticks to the viewport edges when there are many series — the
-      // `extraCssText` cap below limits the inner height + adds
-      // scroll so labeled metrics with dozens of series (Envoy
-      // membership health per cluster, K8s pod_status per pod) don't
-      // overflow past the screen. Without these two, the bottom rows
-      // of the tooltip vanish off-screen.
-      confine: true,
       extraCssText:
         'max-height: 60vh; overflow-y: auto; max-width: 360px; ' +
         'box-shadow: 0 8px 24px rgba(0,0,0,0.45);',
+      // Viewport-clamped positioning. `confine: true` only confines to
+      // the chart's own bbox, which doesn't help when the chart sits
+      // near the right / bottom screen edge — the tooltip still spills
+      // off-screen. This callback flips the tooltip to the opposite
+      // side of the cursor whenever it would overflow the viewport,
+      // and never returns a coord with negative top/left.
+      position(
+        point: [number, number],
+        _params: unknown,
+        _dom: HTMLElement,
+        _rect: unknown,
+        size: { contentSize: [number, number]; viewSize: [number, number] },
+      ): [number, number] {
+        // `point` is mouse pos in the chart's local coords; we need
+        // page coords to compare against viewport bounds. The chart's
+        // bounding rect on the page gives us the offset.
+        const chartRect = container.value?.getBoundingClientRect();
+        const pageX = (chartRect?.left ?? 0) + point[0];
+        const pageY = (chartRect?.top ?? 0) + point[1];
+        const [tw, th] = size.contentSize;
+        const vw = document.documentElement.clientWidth;
+        const vh = document.documentElement.clientHeight;
+        const margin = 8;
+        const offset = 12;
+        let x = pageX + offset;
+        if (x + tw > vw - margin) x = pageX - tw - offset;
+        if (x < margin) x = margin;
+        let y = pageY + offset;
+        if (y + th > vh - margin) y = pageY - th - offset;
+        if (y < margin) y = margin;
+        // With appendToBody:true, echarts treats returned [x, y] as
+        // page-absolute coords (positions the tooltip element in
+        // document.body), so we return pixel values.
+        return [x, y];
+      },
       valueFormatter: (v: unknown) =>
         typeof v === 'number' && Number.isFinite(v)
           ? `${formatVal(v)}${props.unit ? ` ${props.unit}` : ''}`

Reply via email to