Skip to content

Commit

Permalink
fix(tooltip): render tooltip on portal to avoid hidden overflows
Browse files Browse the repository at this point in the history
The current tooltip was rendered inside the chart container. If the chart is rendered inside a DOM
element with an hidden overflow, then the tooltip will be cutted. I moved the tooltip element to
render on the document body through a React portal.

close elastic#375
  • Loading branch information
markov00 committed Oct 15, 2019
1 parent a1725e4 commit 3c05b9c
Show file tree
Hide file tree
Showing 20 changed files with 434 additions and 143 deletions.
11 changes: 6 additions & 5 deletions .playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@
background: blanchedalmond !important;
margin: 0;
padding: 0;
height: 2000px;
width: 2000px;
}
#root {

position: absolute;
top: 10px;
left: 10px;
top: 100px;
left: 100px;
bottom: 10px;
right: 10px;
}
.chart {
background: white;
position: relative;
width: 800px;
height: 450px;
width: 600px;
height: 350px;
margin: 10px;
}
</style>
Expand Down
47 changes: 37 additions & 10 deletions .playground/playgroud.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,57 @@
import React, { Fragment } from 'react';
import { Axis, Chart, getAxisId, getSpecId, Position, ScaleType, BarSeries } from '../src';
import {
Axis,
Chart,
getAxisId,
getSpecId,
Position,
ScaleType,
BarSeries,
DataGenerator,
Settings,
LineSeries,
} from '../src';

export class Playground extends React.Component {
render() {
const data = [{ x: 0, y: -4 }, { x: 1, y: -3 }, { x: 2, y: 2 }, { x: 3, y: 1 }];
const dg = new DataGenerator();
const data = dg.generateGroupedSeries(100, 3, 'data series ');
const data2 = dg.generateGroupedSeries(50, 5, 'data series ');
return (
<Fragment>
<div className="chart">
<Chart>
<Axis id={getAxisId('top')} position={Position.Bottom} title={'Top axis'} />
<Axis
id={getAxisId('left2')}
title={'Left axis'}
position={Position.Left}
tickFormat={(d: any) => Number(d).toFixed(2)}
<Axis id={getAxisId('top')} position={Position.Bottom} />
<Axis id={getAxisId('left2')} position={Position.Left} tickFormat={(d: any) => Number(d).toFixed(2)} />

<LineSeries
id={getSpecId('series bars chart')}
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
splitSeriesAccessors={['g']}
stackAccessors={['x']}
data={data}
yScaleToDataExtent={true}
/>
</Chart>
</div>
<div className="chart">
<Chart>
<Settings rotation={90} />
<Axis id={getAxisId('top')} position={Position.Bottom} tickFormat={(d: any) => Number(d).toFixed(2)} />
<Axis id={getAxisId('left2')} position={Position.Left} tickFormat={(d: any) => Number(d).toFixed(2)} />

<BarSeries
id={getSpecId('bars')}
id={getSpecId('series bars chart')}
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
splitSeriesAccessors={['g']}
stackAccessors={['x']}
data={data}
data={data2}
yScaleToDataExtent={true}
/>
</Chart>
Expand Down
74 changes: 56 additions & 18 deletions integration/page_objects/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,29 @@ class CommonPage {

return `${baseUrl}?${query}${query ? '&' : ''}knob-debug=false`;
}

async screenshotDOMElement(selector: string, opts?: ScreenshotDOMElementOptions) {
const padding: number = opts && opts.padding ? opts.padding : 0;
const path: string | undefined = opts && opts.path ? opts.path : undefined;

await page.waitForSelector(selector, { timeout: 10000 });
const rect = await page.evaluate((selector) => {
async getBoundingClientRect(selector = '.echChart[data-ech-render-complete=true]') {
return await page.evaluate((selector) => {
const element = document.querySelector(selector);

if (!element) {
return null;
throw Error(`Could not find element that matches selector: ${selector}.`);
}

const { x, y, width, height } = element.getBoundingClientRect();

return { left: x, top: y, width, height, id: element.id };
}, selector);

if (!rect) throw Error(`Could not find element that matches selector: ${selector}.`);
}
/**
* Capture screenshot or chart element only
*/
async screenshotDOMElement(
selector = '.echChart[data-ech-render-complete=true]',
opts?: ScreenshotDOMElementOptions,
) {
const padding: number = opts && opts.padding ? opts.padding : 0;
const path: string | undefined = opts && opts.path ? opts.path : undefined;
const rect = await this.getBoundingClientRect(selector);

return page.screenshot({
path,
Expand All @@ -52,11 +56,13 @@ class CommonPage {
});
}

/**
* Capture screenshot or chart element only
*/
async getChartScreenshot() {
return this.screenshotDOMElement('.echChart[data-ech-render-complete=true]');
async moveMouseRelativeToDOMElement(
mousePosition: { x: number; y: number },
selector = '.echChart[data-ech-render-complete=true]',
) {
const chartContainer = await this.getBoundingClientRect(selector);
console.log({ chartContainer });
await page.mouse.move(chartContainer.left + mousePosition.x, chartContainer.top + mousePosition.y);
}

/**
Expand All @@ -68,10 +74,34 @@ class CommonPage {
*/
async expectChartAtUrlToMatchScreenshot(url: string) {
try {
const cleanUrl = CommonPage.parseUrl(url);
await page.goto(cleanUrl);
const chart = await this.getChartScreenshot();
await this.loadChartFromURL(url);
await this.waitForChartRendered();

const chart = await this.screenshotDOMElement();

if (!chart) {
throw new Error(`Error: Unable to find chart element\n\n\t${url}`);
}

expect(chart).toMatchImageSnapshot();
} catch (error) {
throw new Error(error);
}
}

/**
* Expect a chart given a url from storybook.
*
* - Note: No need to fix host or port. They will be set automatically.
*
* @param url Storybook url from knobs section
*/
async expectChartWithMouseAtUrlToMatchScreenshot(url: string, mousePosition: { x: number; y: number }) {
try {
await this.loadChartFromURL(url);
await this.waitForChartRendered();
await this.moveMouseRelativeToDOMElement(mousePosition);
const chart = await this.screenshotDOMElement();
if (!chart) {
throw new Error(`Error: Unable to find chart element\n\n\t${url}`);
}
Expand All @@ -81,6 +111,14 @@ class CommonPage {
throw new Error(error);
}
}
async loadChartFromURL(url: string) {
const cleanUrl = CommonPage.parseUrl(url);
await page.goto(cleanUrl);
}

async waitForChartRendered(selector = '.echChart[data-ech-render-complete=true]', timeout = 10000) {
await page.waitForSelector(selector, { timeout });
}
}

export const common = new CommonPage();
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 80 additions & 0 deletions integration/tests/interactions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { common } from '../page_objects';

describe.only('Tooltips', () => {
describe('rotation 0', () => {
it('shows tooltip on first x value - top', async () => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/bar-chart--test-tooltip-and-rotation',
{
x: 160,
y: 25,
},
);
});
it('shows tooltip on last x value - top', async () => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/bar-chart--test-tooltip-and-rotation',
{
x: 660,
y: 25,
},
);
});
it('shows tooltip on first x value - bottom', async () => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/bar-chart--test-tooltip-and-rotation',
{
x: 160,
y: 280,
},
);
});
it('shows tooltip on last x value - bottom', async () => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/bar-chart--test-tooltip-and-rotation',
{
x: 660,
y: 280,
},
);
});
});
describe('rotation 90', () => {
it('shows tooltip on first x value - top', async () => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/bar-chart--test-tooltip-and-rotation&knob-chartRotation=90',
{
x: 125,
y: 50,
},
);
});
it('shows tooltip on last x value - top', async () => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/bar-chart--test-tooltip-and-rotation&knob-chartRotation=90',
{
x: 700,
y: 50,
},
);
});
it('shows tooltip on first x value - bottom', async () => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/bar-chart--test-tooltip-and-rotation&knob-chartRotation=90',
{
x: 125,
y: 270,
},
);
});
it('shows tooltip on last x value - bottom', async () => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/bar-chart--test-tooltip-and-rotation&knob-chartRotation=90',
{
x: 700,
y: 270,
},
);
});
});
});
Loading

0 comments on commit 3c05b9c

Please sign in to comment.