Skip to content

Commit

Permalink
Feat: Pivot tooltip functionality (#4938)
Browse files Browse the repository at this point in the history
* initial commit

* cleanup

* remove import
  • Loading branch information
briangregoryholmes authored Jun 2, 2024
1 parent 3f54f66 commit 3253a7c
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 63 deletions.
68 changes: 8 additions & 60 deletions web-common/src/components/virtualized-table/VirtualTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,7 @@

<script lang="ts">
import { createEventDispatcher } from "svelte";
import { portal } from "@rilldata/web-common/lib/actions/portal";
import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte";
import TooltipTitle from "@rilldata/web-common/components/tooltip/TooltipTitle.svelte";
import Shortcut from "@rilldata/web-common/components/tooltip/Shortcut.svelte";
import StackingWord from "@rilldata/web-common/components/tooltip/StackingWord.svelte";
import TooltipShortcutContainer from "@rilldata/web-common/components/tooltip/TooltipShortcutContainer.svelte";
import { formatDataTypeAsDuckDbQueryString } from "@rilldata/web-common/lib/formatters";
import FormattedDataType from "@rilldata/web-common/components/data-types/FormattedDataType.svelte";
import {
copyToClipboard,
isClipboardApiSupported,
} from "@rilldata/web-common/lib/actions/copy-to-clipboard";
import type {
V1MetricsViewColumn,
V1MetricsViewRowsResponseDataItem,
Expand All @@ -34,12 +23,14 @@
import type { VirtualizedTableColumns } from "@rilldata/web-common/components/virtualized-table/types";
import { initColumnWidths } from "./init-widths";
import { clamp } from "@rilldata/web-common/lib/clamp";
import { copyToClipboard } from "@rilldata/web-common/lib/actions/copy-to-clipboard";
import VirtualTableCell from "./VirtualTableCell.svelte";
import VirtualTableHeaderCellContent from "./VirtualTableHeaderCellContent.svelte";
import VirtualTableRowHeader from "./VirtualTableRowHeader.svelte";
import ColumnWidths from "./VirtualTableColumnWidths.svelte";
import VirtualTableHeader from "./VirtualTableHeader.svelte";
import VirtualTableRow from "./VirtualTableRow.svelte";
import VirtualTooltip from "./VirtualTooltip.svelte";
type HoveringData = {
index: number;
Expand Down Expand Up @@ -391,55 +382,12 @@
</div>

{#if showTooltip && hovering}
<aside
class="w-fit h-fit absolute -translate-x-1/2 -translate-y-full z-[1000]"
use:portal
style:top="{hoverPosition.top - 8}px"
style:left="{hoverPosition.left + hoverPosition.width / 2}px"
>
<TooltipContent maxWidth="360px">
{#if hovering.isPin}
{@const pinned = pinnedColumns.has(hovering.index)}
{pinned ? "Unpin" : "Pin"} this column to left side of the table
{:else}
<TooltipTitle>
<svelte:fragment slot="name">
{#if hovering.isHeader}
{hovering.value}
{:else}
<FormattedDataType
dark
type={hovering?.type}
value={hovering?.value}
/>
{/if}
</svelte:fragment>

<svelte:fragment slot="description">
{hovering.isHeader ? hovering.type : ""}
</svelte:fragment>
</TooltipTitle>

{#if !hovering.isPin}
<TooltipShortcutContainer>
{#if hovering.isHeader && sortable}
<div>Sort column</div>
<Shortcut>Click</Shortcut>
{/if}
{#if isClipboardApiSupported()}
<div>
<StackingWord key="shift">Copy</StackingWord>
{hovering.isHeader ? "column name" : "this value"} to clipboard
</div>
<Shortcut>
<span style="font-family: var(--system);">⇧</span> + Click
</Shortcut>
{/if}
</TooltipShortcutContainer>
{/if}
{/if}
</TooltipContent>
</aside>
<VirtualTooltip
{sortable}
{hovering}
{hoverPosition}
pinned={pinnedColumns.has(hovering.index)}
/>
{/if}

<style lang="postcss">
Expand Down
73 changes: 73 additions & 0 deletions web-common/src/components/virtualized-table/VirtualTooltip.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script lang="ts">
import { portal } from "@rilldata/web-common/lib/actions/portal";
import TooltipContent from "../tooltip/TooltipContent.svelte";
import TooltipTitle from "../tooltip/TooltipTitle.svelte";
import TooltipShortcutContainer from "../tooltip/TooltipShortcutContainer.svelte";
import Shortcut from "../tooltip/Shortcut.svelte";
import StackingWord from "../tooltip/StackingWord.svelte";
import FormattedDataType from "../data-types/FormattedDataType.svelte";
import { isClipboardApiSupported } from "@rilldata/web-common/lib/actions/copy-to-clipboard";
type HoveringData = {
value: string | number | null;
index?: number;
column?: string;
type?: string;
isHeader?: boolean;
isPin?: boolean;
};
export let hoverPosition = { top: 0, left: 0, width: 0 };
export let pinned: boolean;
export let hovering: HoveringData;
export let sortable: boolean;
</script>

<aside
class="w-fit h-fit absolute -translate-x-1/2 -translate-y-full z-[1000]"
use:portal
style:top="{hoverPosition.top - 8}px"
style:left="{hoverPosition.left + hoverPosition.width / 2}px"
>
<TooltipContent maxWidth="360px">
{#if hovering.isPin}
{pinned ? "Unpin" : "Pin"} this column to left side of the table
{:else}
<TooltipTitle>
<svelte:fragment slot="name">
{#if hovering.isHeader}
{hovering.value}
{:else}
<FormattedDataType
dark
type={hovering?.type}
value={hovering?.value}
/>
{/if}
</svelte:fragment>

<svelte:fragment slot="description">
{hovering.isHeader ? hovering.type : ""}
</svelte:fragment>
</TooltipTitle>

{#if !hovering.isPin}
<TooltipShortcutContainer>
{#if hovering.isHeader && sortable}
<div>Sort column</div>
<Shortcut>Click</Shortcut>
{/if}
{#if isClipboardApiSupported()}
<div>
<StackingWord key="shift">Copy</StackingWord>
{hovering.isHeader ? "column name" : "this value"} to clipboard
</div>
<Shortcut>
<span style="font-family: var(--system);">⇧</span> + Click
</Shortcut>
{/if}
</TooltipShortcutContainer>
{/if}
{/if}
</TooltipContent>
</aside>
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
{:else if assembled && row.getCanExpand()}
<button
on:click={row.getToggleExpandedHandler()}
class="cursor-pointer p-1 -m-1"
class="cursor-pointer p-1 -m-1 pointer-events-auto"
>
<div class:rotate={row.getIsExpanded()} class="transition-transform">
<ChevronRight size="16px" color="#9CA3AF" />
Expand Down
68 changes: 66 additions & 2 deletions web-common/src/features/dashboards/pivot/PivotTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
import { getPivotConfig } from "./pivot-data-store";
import { isTimeDimension } from "./pivot-utils";
import type { PivotDataRow, PivotDataStore } from "./types";
import VirtualTooltip from "@rilldata/web-common/components/virtualized-table/VirtualTooltip.svelte";
import { modified } from "@rilldata/web-common/lib/actions/modified-click";
import { copyToClipboard } from "@rilldata/web-common/lib/actions/copy-to-clipboard";
// Distance threshold (in pixels) for triggering data fetch
const ROW_THRESHOLD = 200;
Expand Down Expand Up @@ -175,6 +178,7 @@
const handleScroll = (containerRefElement?: HTMLDivElement | null) => {
if (containerRefElement) {
if (hovering) hovering = null;
const { scrollHeight, scrollTop, clientHeight } = containerRefElement;
const bottomEndDistance = scrollHeight - scrollTop - clientHeight;
scrollLeft = containerRefElement.scrollLeft;
Expand Down Expand Up @@ -229,6 +233,55 @@
percentOfChangeDuringResize = (scrollLeft + offset) / totalLength;
}
let showTooltip = false;
let hoverPosition: DOMRect;
let hovering: HoveringData | null = null;
let timer: ReturnType<typeof setTimeout>;
type HoveringData = {
value: string | number | null;
};
function handleHover(
e: MouseEvent & {
currentTarget: EventTarget & HTMLElement;
},
) {
hoverPosition = e.currentTarget.getBoundingClientRect();
const value = e.currentTarget.dataset.value;
if (value === undefined) return;
hovering = {
value,
};
timer = setTimeout(() => {
showTooltip = true;
}, 250);
}
function handleLeave() {
clearTimeout(timer);
showTooltip = false;
hovering = null;
}
function handleClick(e: MouseEvent) {
if (!isElement(e.target)) return;
const value = e.target.dataset.value;
if (value === undefined) return;
copyToClipboard(value);
}
function isElement(target: EventTarget | null): target is HTMLElement {
return target instanceof HTMLElement;
}
</script>

<div
Expand All @@ -240,7 +293,11 @@
bind:this={containerRefElement}
on:scroll={() => handleScroll(containerRefElement)}
>
<table style:width="{totalLength}px">
<table
style:width="{totalLength}px"
on:click={modified({ shift: handleClick })}
role="presentation"
>
{#if firstColumnName && firstColumnWidth}
<colgroup>
<col
Expand Down Expand Up @@ -330,9 +387,12 @@
<td
class="ui-copy-number"
class:border-r={i % measureCount === 0 && i}
on:mouseenter={handleHover}
on:mouseleave={handleLeave}
data-value={cell.getValue()}
class:totals-column={i > 0 && i <= measureCount}
>
<div class="cell">
<div class="cell pointer-events-none" role="presentation">
{#if result?.component && result?.props}
<svelte:component
this={result.component}
Expand All @@ -359,6 +419,10 @@
</table>
</div>

{#if showTooltip && hovering}
<VirtualTooltip sortable={true} {hovering} {hoverPosition} pinned={false} />
{/if}

<style lang="postcss">
* {
@apply border-slate-200;
Expand Down

1 comment on commit 3253a7c

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.