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

vogievetsky pushed a commit to branch segment_timeline2
in repository https://gitbox.apache.org/repos/asf/druid.git

commit b0c756fea073572cff1d38944bfacc7e2020020c
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Mon Oct 28 10:37:56 2024 -0700

    fix
---
 .../src/components/segment-timeline/chart-axis.tsx |   2 +-
 .../segment-timeline/segment-bar-chart-render.scss |  21 +++
 .../segment-timeline/segment-bar-chart-render.tsx  | 152 ++++++++++++++++-----
 .../segment-timeline/segment-bar-chart.tsx         |  24 ++--
 web-console/src/utils/general.tsx                  |   9 --
 5 files changed, 149 insertions(+), 59 deletions(-)

diff --git a/web-console/src/components/segment-timeline/chart-axis.tsx 
b/web-console/src/components/segment-timeline/chart-axis.tsx
index 0e48e10fde8..b8ee4e9cbb1 100644
--- a/web-console/src/components/segment-timeline/chart-axis.tsx
+++ b/web-console/src/components/segment-timeline/chart-axis.tsx
@@ -20,9 +20,9 @@ import type { Axis } from 'd3-axis';
 import { select } from 'd3-selection';
 
 interface ChartAxisProps {
+  className?: string;
   transform?: string;
   axis: Axis<any>;
-  className?: string;
 }
 
 export const ChartAxis = function ChartAxis(props: ChartAxisProps) {
diff --git 
a/web-console/src/components/segment-timeline/segment-bar-chart-render.scss 
b/web-console/src/components/segment-timeline/segment-bar-chart-render.scss
index 9cd7742e851..a30ee800f0a 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart-render.scss
+++ b/web-console/src/components/segment-timeline/segment-bar-chart-render.scss
@@ -50,6 +50,27 @@
       stroke-width: 1px;
     }
 
+    .shifter {
+      fill: white;
+      fill-opacity: 0.2;
+      filter: blur(1px);
+    }
+
+    .time-shift-indicator {
+      fill: white;
+      fill-opacity: 0.001;
+      cursor: grab;
+
+      &:hover {
+        fill-opacity: 0.1;
+      }
+
+      &.shifting {
+        fill-opacity: 0.2;
+        cursor: grabbing;
+      }
+    }
+
     .gridline-x {
       line {
         stroke-dasharray: 5, 5;
diff --git 
a/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx 
b/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
index 4c17c5d44b1..f1e67c9665a 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
+++ b/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
@@ -17,14 +17,24 @@
  */
 
 import type { NonNullDateRange } from '@blueprintjs/datetime';
+import classNames from 'classnames';
 import { max } from 'd3-array';
 import { axisBottom, axisLeft } from 'd3-axis';
-import { scaleLinear, scaleUtc } from 'd3-scale';
+import { scaleLinear, scaleOrdinal, scaleUtc } from 'd3-scale';
 import type React from 'react';
-import { useRef, useState } from 'react';
+import { useMemo, useRef, useState } from 'react';
 
 import { useGlobalEventListener } from '../../hooks';
-import { ceilDay, floorDay, formatBytes, formatInteger } from '../../utils';
+import {
+  capitalizeFirst,
+  ceilDay,
+  clamp,
+  floorDay,
+  formatByteRate,
+  formatBytes,
+  formatInteger,
+  formatNumber,
+} from '../../utils';
 import type { Margin, Stage } from '../../utils/stage';
 
 import { ChartAxis } from './chart-axis';
@@ -32,7 +42,7 @@ import type { SegmentBar, SegmentStat } from './common';
 
 import './segment-bar-chart-render.scss';
 
-const CHART_MARGIN: Margin = { top: 40, right: 5, bottom: 20, left: 10 };
+const CHART_MARGIN: Margin = { top: 40, right: 5, bottom: 20, left: 80 };
 
 const COLORS = [
   '#b33040',
@@ -53,10 +63,14 @@ const COLORS = [
   '#87606c',
 ];
 
+function offsetDateRange(dateRange: NonNullDateRange, offset: number): 
NonNullDateRange {
+  return [new Date(dateRange[0].valueOf() + offset), new 
Date(dateRange[1].valueOf() + offset)];
+}
+
 interface SegmentBarChartRenderProps {
   stage: Stage;
   dateRange: NonNullDateRange;
-  changeDateRange(newDateRange: NonNullDateRange): void;
+  changeDateRange(dateRange: NonNullDateRange): void;
   shownSegmentStat: SegmentStat;
   segmentBars: SegmentBar[];
   changeActiveDatasource(datasource: string | undefined): void;
@@ -74,13 +88,25 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     changeActiveDatasource,
   } = props;
   const [hoverOn, setHoverOn] = useState<SegmentBar>();
-  const [mouseDownAt, setMouseDownAt] = useState<Date | undefined>();
+  const [mouseDownAt, setMouseDownAt] = useState<
+    { time: Date; action: 'select' | 'shift' } | undefined
+  >();
   const [dragging, setDragging] = useState<NonNullDateRange | undefined>();
+  const [shiftOffset, setShiftOffset] = useState<number | undefined>();
   const svgRef = useRef<SVGSVGElement | null>(null);
 
   const innerStage = stage.applyMargin(CHART_MARGIN);
 
-  const timeScale = scaleUtc().domain(dateRange).range([0, innerStage.width]);
+  const baseTimeScale = scaleUtc().domain(dateRange).range([0, 
innerStage.width]);
+
+  const timeScale = shiftOffset
+    ? baseTimeScale.copy().domain(offsetDateRange(dateRange, shiftOffset))
+    : baseTimeScale;
+
+  const colorizer = useMemo(() => {
+    const s = scaleOrdinal().range(COLORS);
+    return (d: SegmentBar) => (d.datasource ? s(d.datasource) : COLORS[0]) as 
string;
+  }, []);
 
   const maxStat = max(segmentBars, d => d[shownSegmentStat] + 
d.offset[shownSegmentStat]);
   const statScale = scaleLinear()
@@ -88,11 +114,26 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     .domain([0, maxStat ?? 1]);
 
   const formatTick = (n: number) => {
-    if (isNaN(n)) return '';
-    if (shownSegmentStat === 'count') {
-      return formatInteger(n);
-    } else {
-      return formatBytes(n);
+    switch (shownSegmentStat) {
+      case 'count':
+      case 'rows':
+        return formatInteger(n);
+
+      case 'size':
+        return formatBytes(n);
+    }
+  };
+
+  const formatTickRate = (n: number) => {
+    switch (shownSegmentStat) {
+      case 'count':
+        return formatNumber(n) + ' seg/s';
+
+      case 'rows':
+        return formatNumber(n) + ' row/s';
+
+      case 'size':
+        return formatByteRate(n);
     }
   };
 
@@ -101,7 +142,11 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     if (!svg) return;
     const rect = svg.getBoundingClientRect();
     const x = e.clientX - rect.x - CHART_MARGIN.left;
-    setMouseDownAt(timeScale.invert(x));
+    const y = e.clientY - rect.y - CHART_MARGIN.top;
+    setMouseDownAt({
+      time: baseTimeScale.invert(x),
+      action: y > innerStage.height ? 'shift' : 'select',
+    });
   }
 
   useGlobalEventListener('mousemove', (e: MouseEvent) => {
@@ -110,34 +155,46 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     if (!svg) return;
     const rect = svg.getBoundingClientRect();
     const x = e.clientX - rect.x - CHART_MARGIN.left;
-    const b = timeScale.invert(x);
-    if (mouseDownAt < b) {
-      setDragging([floorDay(mouseDownAt), ceilDay(b)]);
+    const b = baseTimeScale.invert(x);
+    if (mouseDownAt.action === 'shift' || e.shiftKey) {
+      setShiftOffset(mouseDownAt.time.valueOf() - b.valueOf());
     } else {
-      setDragging([floorDay(b), ceilDay(mouseDownAt)]);
+      if (mouseDownAt.time < b) {
+        setDragging([floorDay(mouseDownAt.time), ceilDay(b)]);
+      } else {
+        setDragging([floorDay(b), ceilDay(mouseDownAt.time)]);
+      }
     }
   });
 
-  useGlobalEventListener('mouseup', () => {
-    if (mouseDownAt) {
-      setMouseDownAt(undefined);
-    }
-    if (dragging) {
-      setDragging(undefined);
-      changeDateRange(dragging);
+  useGlobalEventListener('mouseup', (e: MouseEvent) => {
+    if (!mouseDownAt) return;
+    setMouseDownAt(undefined);
+
+    if (!shiftOffset && !dragging) return;
+    setDragging(undefined);
+    setShiftOffset(undefined);
+    if (mouseDownAt.action === 'shift' || e.shiftKey) {
+      if (shiftOffset) {
+        changeDateRange(offsetDateRange(dateRange, shiftOffset));
+      }
+    } else {
+      if (dragging) {
+        changeDateRange(dragging);
+      }
     }
   });
 
   function segmentBarToRect(segmentBar: SegmentBar) {
-    const xStart = timeScale(segmentBar.start);
-    const xEnd = timeScale(segmentBar.end);
+    const xStart = clamp(timeScale(segmentBar.start), 0, innerStage.width);
+    const xEnd = clamp(timeScale(segmentBar.end), 0, innerStage.width);
     const y0 = statScale(segmentBar.offset[shownSegmentStat]);
     const y = statScale(segmentBar[shownSegmentStat] + 
segmentBar.offset[shownSegmentStat]);
 
     return {
       x: xStart,
       y: y,
-      width: Math.max(xEnd - xStart, 1),
+      width: Math.max(xEnd - xStart - 1, 1),
       height: Math.abs(y0 - y),
     };
   }
@@ -154,7 +211,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
           <div>Datasource: {hoverOn.datasource}</div>
           <div>Time: {hoverOn.start.toISOString()}</div>
           <div>
-            {`${shownSegmentStat === 'count' ? 'Count' : 'Size'}: ${formatTick(
+            {`${capitalizeFirst(shownSegmentStat)}: ${formatTick(
               hoverOn[shownSegmentStat] * hoverOn.durationSeconds,
             )}`}
           </div>
@@ -186,6 +243,21 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
             transform={`translate(0,${innerStage.height})`}
             axis={axisBottom(timeScale)}
           />
+          <rect
+            className={classNames('time-shift-indicator', {
+              shifting: typeof shiftOffset === 'number',
+            })}
+            x={0}
+            y={innerStage.height}
+            width={innerStage.width}
+            height={CHART_MARGIN.bottom}
+          />
+          <ChartAxis
+            className="axis-y"
+            axis={axisLeft(statScale)
+              .ticks(5)
+              .tickFormat(e => formatTickRate(e.valueOf()))}
+          />
           <g className="bar-group">
             {segmentBars.map((segmentBar, i) => {
               return (
@@ -193,13 +265,16 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
                   key={i}
                   className="bar-unit"
                   {...segmentBarToRect(segmentBar)}
-                  style={{ fill: COLORS[i % COLORS.length] }}
+                  style={{ fill: colorizer(segmentBar) }}
                   onClick={
                     segmentBar.datasource
                       ? () => changeActiveDatasource(segmentBar.datasource)
                       : undefined
                   }
-                  onMouseOver={() => setHoverOn(segmentBar)}
+                  onMouseOver={() => {
+                    if (mouseDownAt) return;
+                    setHoverOn(segmentBar);
+                  }}
                 />
               );
             })}
@@ -213,13 +288,24 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
                 }}
               />
             )}
-            {(dragging || mouseDownAt) && (
+            {dragging && (
               <rect
                 className="selection"
-                x={timeScale(dragging?.[0] || mouseDownAt!)}
+                x={timeScale(dragging[0])}
+                y={0}
+                height={innerStage.height}
+                width={timeScale(dragging[1]) - timeScale(dragging[0])}
+              />
+            )}
+            {!!shiftOffset && (
+              <rect
+                className="shifter"
+                x={timeScale(shiftOffset > 0 ? dateRange[1] : 
dateRange[0].valueOf() + shiftOffset)}
                 y={0}
                 height={innerStage.height}
-                width={dragging ? timeScale(dragging[1]) - 
timeScale(dragging[0]) : 1}
+                width={Math.abs(
+                  timeScale(dateRange[0]) - timeScale(dateRange[0].valueOf() + 
shiftOffset),
+                )}
               />
             )}
           </g>
diff --git a/web-console/src/components/segment-timeline/segment-bar-chart.tsx 
b/web-console/src/components/segment-timeline/segment-bar-chart.tsx
index 32d6f053b18..4b24849b723 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart.tsx
+++ b/web-console/src/components/segment-timeline/segment-bar-chart.tsx
@@ -134,7 +134,7 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
     [capabilities, dateRange, breakByDataSource],
   );
 
-  const [segmentRowsState] = useQueryManager({
+  const [segmentBarsState] = useQueryManager({
     query: intervalsQuery,
     processQuery: async ({ capabilities, dateRange, breakByDataSource }, 
cancelToken) => {
       const trimDuration: TrimDuration = 'PT1H';
@@ -142,7 +142,7 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
       if (capabilities.hasSql()) {
         const query = SqlQuery.from(N('sys').table('segments'))
           .changeWhereExpression(
-            sql`'${dateRange[0].toISOString()}' <= "start" AND "end" <= 
'${dateRange[1].toISOString()}' AND is_published = 1 AND is_overshadowed = 0`,
+            sql`'${dateRange[0].toISOString()}' <= "end" AND "start" <= 
'${dateRange[1].toISOString()}' AND is_published = 1 AND is_overshadowed = 0`,
           )
           .addSelect(C('start'), { addToGroupBy: 'end' })
           .addSelect(C('end'), { addToGroupBy: 'end' })
@@ -218,28 +218,20 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
     },
   });
 
-  if (segmentRowsState.loading) {
+  if (segmentBarsState.loading) {
     return <Loader />;
   }
 
-  if (segmentRowsState.error) {
+  if (segmentBarsState.error) {
     return (
       <div className="empty-placeholder">
-        <span className="no-data-text">{`Error when loading data: 
${segmentRowsState.getErrorMessage()}`}</span>
+        <span className="no-data-text">{`Error when loading data: 
${segmentBarsState.getErrorMessage()}`}</span>
       </div>
     );
   }
 
-  const segmentRows = segmentRowsState.data;
-  if (!segmentRows) return null;
-
-  if (!segmentRows.length) {
-    return (
-      <div className="empty-placeholder">
-        <span className="no-data-text">There are no segments for the selected 
interval</span>
-      </div>
-    );
-  }
+  const segmentBars = segmentBarsState.data;
+  if (!segmentBars) return null;
 
   return (
     <SegmentBarChartRender
@@ -247,7 +239,7 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
       dateRange={dateRange}
       changeDateRange={changeDateRange}
       shownSegmentStat={shownSegmentStat}
-      segmentBars={segmentRows as any}
+      segmentBars={segmentBars}
       changeActiveDatasource={changeActiveDatasource}
     />
   );
diff --git a/web-console/src/utils/general.tsx 
b/web-console/src/utils/general.tsx
index 18a1ac65eca..b742013b2e8 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -656,12 +656,3 @@ export function offsetToRowColumn(str: string, offset: 
number): RowColumn | unde
 
   return;
 }
-
-export function findParentSVG(element: Element): SVGElement | undefined {
-  let currentElement: Element | null = element;
-  while (currentElement) {
-    if (currentElement.tagName === 'svg') return currentElement as SVGElement;
-    currentElement = currentElement.parentElement;
-  }
-  return;
-}


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

Reply via email to