Skip to content

Commit 8af6e5e

Browse files
authored
Add a tooltip to plots with long titles that are cut by Vega (#4840)
* Add a tooltip to plots with long titles that are cut by Vega * Add tooltip inside zoomed in plot and correctly make it interactive
1 parent 1b03e42 commit 8af6e5e

File tree

4 files changed

+147
-48
lines changed

4 files changed

+147
-48
lines changed

webview/src/plots/components/App.test.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,6 +2637,78 @@ describe('App', () => {
26372637
expect(clickEvent.stopPropagation).toHaveBeenCalledTimes(1)
26382638
})
26392639

2640+
it('should have a tooltip on the plot if the title is cut', () => {
2641+
const title = 'Plot with a long title'
2642+
renderAppWithOptionalData({
2643+
template: {
2644+
...templatePlotsFixture,
2645+
plots: [
2646+
{
2647+
entries: [
2648+
{
2649+
...templatePlotsFixture.plots[0].entries[0],
2650+
content: {
2651+
...templatePlotsFixture.plots[0].entries[0].content,
2652+
title: '… with a long title'
2653+
} as unknown as VisualizationSpec,
2654+
id: title
2655+
}
2656+
],
2657+
group: TemplatePlotGroup.SINGLE_VIEW
2658+
}
2659+
]
2660+
}
2661+
})
2662+
2663+
expect(screen.queryByText(title)).not.toBeInTheDocument()
2664+
2665+
const plot = within(screen.getByTestId(`plot_${title}`)).getAllByRole(
2666+
'button'
2667+
)[0]
2668+
fireEvent.mouseEnter(plot, {
2669+
bubbles: true,
2670+
cancelable: true
2671+
})
2672+
2673+
expect(screen.getByText(title)).toBeInTheDocument()
2674+
})
2675+
2676+
it('should not have a tooltip on the plot if the title is not cut', () => {
2677+
const title = 'Short title'
2678+
renderAppWithOptionalData({
2679+
template: {
2680+
...templatePlotsFixture,
2681+
plots: [
2682+
{
2683+
entries: [
2684+
{
2685+
...templatePlotsFixture.plots[0].entries[0],
2686+
content: {
2687+
...templatePlotsFixture.plots[0].entries[0].content,
2688+
title
2689+
} as unknown as VisualizationSpec,
2690+
id: title
2691+
}
2692+
],
2693+
group: TemplatePlotGroup.SINGLE_VIEW
2694+
}
2695+
]
2696+
}
2697+
})
2698+
2699+
expect(screen.queryByText(title)).not.toBeInTheDocument()
2700+
2701+
const plot = within(screen.getByTestId(`plot_${title}`)).getAllByRole(
2702+
'button'
2703+
)[0]
2704+
fireEvent.mouseEnter(plot, {
2705+
bubbles: true,
2706+
cancelable: true
2707+
})
2708+
2709+
expect(screen.queryByText(title)).not.toBeInTheDocument()
2710+
})
2711+
26402712
describe('Smooth Plots', () => {
26412713
const waitSetValuePostMessage = (value: number) =>
26422714
waitFor(

webview/src/plots/components/ZoomablePlot.tsx

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, { useEffect, useRef } from 'react'
44
import { useDispatch } from 'react-redux'
55
import { VisualizationSpec } from 'react-vega'
66
import VegaLite, { VegaLiteProps } from 'react-vega/lib/VegaLite'
7+
import { ZoomablePlotWrapper } from './ZoomablePlotWrapper'
78
import { TemplateVegaLite } from './templatePlots/TemplateVegaLite'
89
import { setZoomedInPlot } from './webviewSlice'
910
import styles from './styles.module.scss'
@@ -77,39 +78,41 @@ export const ZoomablePlot: React.FC<ZoomablePlotProps> = ({
7778
}
7879

7980
return (
80-
<button
81-
className={styles.zoomablePlot}
82-
onClick={() => handleOnClick()}
83-
aria-label="Open Plot in Popup"
84-
>
85-
<GripIcon className={styles.plotGripIcon} />
86-
<span
87-
className={styles.plotActions}
88-
onClick={event => {
89-
event.stopPropagation()
90-
handleOnClick(true)
91-
}}
92-
onKeyDown={event => {
93-
if (event.key === 'Enter') {
94-
handleOnClick(true)
95-
}
96-
}}
97-
role="button"
98-
tabIndex={0}
99-
aria-label="See Plot Export Options"
81+
<ZoomablePlotWrapper id={id} title={plotProps.spec.title?.toString()}>
82+
<button
83+
className={styles.zoomablePlot}
84+
onClick={() => handleOnClick()}
85+
aria-label="Open Plot in Popup"
10086
>
101-
<Ellipsis />
102-
</span>
103-
{currentPlotProps.current &&
104-
(isTemplatePlot ? (
105-
<TemplateVegaLite
106-
vegaLiteProps={plotProps}
107-
id={id}
108-
onNewView={onNewView}
109-
/>
110-
) : (
111-
<VegaLite {...plotProps} onNewView={onNewView} />
112-
))}
113-
</button>
87+
<GripIcon className={styles.plotGripIcon} />
88+
<span
89+
className={styles.plotActions}
90+
onClick={event => {
91+
event.stopPropagation()
92+
handleOnClick(true)
93+
}}
94+
onKeyDown={event => {
95+
if (event.key === 'Enter') {
96+
handleOnClick(true)
97+
}
98+
}}
99+
role="button"
100+
tabIndex={0}
101+
aria-label="See Plot Export Options"
102+
>
103+
<Ellipsis />
104+
</span>
105+
{currentPlotProps.current &&
106+
(isTemplatePlot ? (
107+
<TemplateVegaLite
108+
vegaLiteProps={plotProps}
109+
id={id}
110+
onNewView={onNewView}
111+
/>
112+
) : (
113+
<VegaLite {...plotProps} onNewView={onNewView} />
114+
))}
115+
</button>
116+
</ZoomablePlotWrapper>
114117
)
115118
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React, { ReactElement, PropsWithChildren } from 'react'
2+
import Tooltip from '../../shared/components/tooltip/Tooltip'
3+
4+
interface ZoomablePlotWrapperProps {
5+
title?: string
6+
id: string
7+
}
8+
9+
export const ZoomablePlotWrapper: React.FC<
10+
PropsWithChildren<ZoomablePlotWrapperProps>
11+
> = ({ title, id, children }) => {
12+
const isTitleCut = title?.indexOf('…') === 0
13+
14+
return isTitleCut ? (
15+
<Tooltip content={id} placement="top" interactive appendTo={document.body}>
16+
{children as ReactElement}
17+
</Tooltip>
18+
) : (
19+
children
20+
)
21+
}

webview/src/plots/components/ZoomedInPlot.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from 'dvc/src/plots/vega/util'
1010
import { TemplateVegaLite } from './templatePlots/TemplateVegaLite'
1111
import styles from './styles.module.scss'
12+
import { ZoomablePlotWrapper } from './ZoomablePlotWrapper'
1213
import { getThemeValue, ThemeProperty } from '../../util/styles'
1314
import {
1415
exportPlotDataAsCsv,
@@ -101,20 +102,22 @@ export const ZoomedInPlot: React.FC<ZoomedInPlotProps> = ({
101102
}
102103

103104
return (
104-
<div
105-
className={styles.zoomedInPlot}
106-
data-testid="zoomed-in-plot"
107-
ref={zoomedInPlotRef}
108-
>
109-
{isTemplatePlot ? (
110-
<TemplateVegaLite
111-
id={id}
112-
vegaLiteProps={vegaLiteProps}
113-
onNewView={onNewView}
114-
/>
115-
) : (
116-
<VegaLite {...vegaLiteProps} onNewView={onNewView} />
117-
)}
118-
</div>
105+
<ZoomablePlotWrapper title={props.spec.title?.toString()} id={id}>
106+
<div
107+
className={styles.zoomedInPlot}
108+
data-testid="zoomed-in-plot"
109+
ref={zoomedInPlotRef}
110+
>
111+
{isTemplatePlot ? (
112+
<TemplateVegaLite
113+
id={id}
114+
vegaLiteProps={vegaLiteProps}
115+
onNewView={onNewView}
116+
/>
117+
) : (
118+
<VegaLite {...vegaLiteProps} onNewView={onNewView} />
119+
)}
120+
</div>
121+
</ZoomablePlotWrapper>
119122
)
120123
}

0 commit comments

Comments
 (0)