diff --git a/.playground/index.html b/.playground/index.html
index 3973665311..fe305c8bcd 100644
--- a/.playground/index.html
+++ b/.playground/index.html
@@ -23,15 +23,23 @@
*/
/* width: 100%;
height: 100%;*/
+ /* overflow-x: hidden; */
}
.chart {
background: white;
/*display: inline-block;
position: relative;
*/
- width: 300px;
- height: 300px;
- margin: 20px;
+ width: 100%;
+ height: 500px;
+ overflow: auto;
+ }
+
+ .testing {
+ background: aquamarine;
+ position: relative;
+ width: 100vw;
+ overflow: auto;
}
diff --git a/.playground/playground.tsx b/.playground/playground.tsx
index 3a170dc677..df9ed05a59 100644
--- a/.playground/playground.tsx
+++ b/.playground/playground.tsx
@@ -17,7 +17,8 @@
* under the License. */
import React from 'react';
-import { Chart, Partition, Settings, PartitionLayout, XYChartElementEvent, PartitionElementEvent } from '../src';
+import { XYChartElementEvent, PartitionElementEvent } from '../src';
+import { example } from '../stories/treemap/6_custom_style';
export class Playground extends React.Component {
onElementClick = (elements: (XYChartElementEvent | PartitionElementEvent)[]) => {
@@ -26,33 +27,8 @@ export class Playground extends React.Component {
};
render() {
return (
-
-
-
- {
- return d.v;
- }}
- data={[
- { g1: 'a', g2: 'a', v: 1 },
- { g1: 'a', g2: 'b', v: 1 },
- { g1: 'b', g2: 'a', v: 1 },
- { g1: 'b', g2: 'b', v: 1 },
- ]}
- layers={[
- {
- groupByRollup: (datum: { g1: string }) => datum.g1,
- },
- {
- groupByRollup: (datum: { g2: string }) => datum.g2,
- },
- ]}
- />
-
+
);
}
diff --git a/src/chart_types/xy_chart/annotations/annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/annotation_tooltip.ts
index 1df2745586..7ee3f9946f 100644
--- a/src/chart_types/xy_chart/annotations/annotation_tooltip.ts
+++ b/src/chart_types/xy_chart/annotations/annotation_tooltip.ts
@@ -17,6 +17,7 @@
* under the License. */
import { Dimensions } from '../../../utils/dimensions';
+import { Position } from '../../../utils/commons';
/** @internal */
export function getFinalAnnotationTooltipPosition(
@@ -27,16 +28,21 @@ export function getFinalAnnotationTooltipPosition(
tooltip: Dimensions,
/** the tooltip computed position not adjusted within chart bounds */
tooltipAnchor: { top: number; left: number },
+ /** the width of the tooltip portal container */
+ portalWidth: number,
padding = 10,
): {
left: string | null;
top: string | null;
+ anchor: 'left' | 'right';
} {
let left = 0;
+ let anchor: Position = Position.Left;
const annotationXOffset = window.pageXOffset + container.left + chartDimensions.left + tooltipAnchor.left;
- if (chartDimensions.left + tooltipAnchor.left + tooltip.width + padding >= container.width) {
- left = annotationXOffset - tooltip.width - padding;
+ if (chartDimensions.left + tooltipAnchor.left + portalWidth + padding >= container.width) {
+ left = annotationXOffset - portalWidth - padding;
+ anchor = Position.Right;
} else {
left = annotationXOffset + padding;
}
@@ -50,5 +56,6 @@ export function getFinalAnnotationTooltipPosition(
return {
left: `${Math.round(left)}px`,
top: `${Math.round(top)}px`,
+ anchor,
};
}
diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts
index badc0eb8ff..bf171c4845 100644
--- a/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts
+++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts
@@ -31,9 +31,10 @@ describe('Tooltip position', () => {
top: 0,
left: 0,
};
+ const portalWidth = 50;
describe('horizontal rotated chart', () => {
it('can position the tooltip on the top left corner', () => {
- const position = getFinalTooltipPosition(container, tooltip, {
+ const position = getFinalTooltipPosition(container, tooltip, portalWidth, {
isRotated: false,
y1: 0,
y0: 0,
@@ -44,8 +45,9 @@ describe('Tooltip position', () => {
expect(position.left).toBe('25px');
expect(position.top).toBe('10px');
});
+
it('can position the tooltip on the bottom left corner', () => {
- const position = getFinalTooltipPosition(container, tooltip, {
+ const position = getFinalTooltipPosition(container, tooltip, portalWidth, {
isRotated: false,
y0: 90,
y1: 90,
@@ -56,8 +58,9 @@ describe('Tooltip position', () => {
expect(position.left).toBe('25px');
expect(position.top).toBe('80px');
});
+
it('can position the tooltip on the top right corner', () => {
- const position = getFinalTooltipPosition(container, tooltip, {
+ const position = getFinalTooltipPosition(container, tooltip, portalWidth, {
isRotated: false,
y0: 0,
y1: 0,
@@ -65,11 +68,12 @@ describe('Tooltip position', () => {
x1: 100,
padding: 5,
});
- expect(position.left).toBe('65px');
+ expect(position.left).toBe('55px');
expect(position.top).toBe('10px');
});
+
it('can position the tooltip on the bottom right corner', () => {
- const position = getFinalTooltipPosition(container, tooltip, {
+ const position = getFinalTooltipPosition(container, tooltip, portalWidth, {
isRotated: false,
y0: 90,
y1: 90,
@@ -77,13 +81,38 @@ describe('Tooltip position', () => {
x1: 100,
padding: 5,
});
- expect(position.left).toBe('65px');
+ expect(position.left).toBe('55px');
expect(position.top).toBe('80px');
});
+
+ it('should render on right if portal width is within right side', () => {
+ const position = getFinalTooltipPosition(container, tooltip, 44, {
+ isRotated: false,
+ y0: 0,
+ y1: 0,
+ x0: 50,
+ x1: 50,
+ padding: 5,
+ });
+ expect(position.left).toBe('65px');
+ });
+
+ it('should render on left if portal width is NOT within right side', () => {
+ const position = getFinalTooltipPosition(container, tooltip, 46, {
+ isRotated: false,
+ y0: 0,
+ y1: 0,
+ x0: 50,
+ x1: 50,
+ padding: 5,
+ });
+ expect(position.left).toBe('9px');
+ });
});
+
describe('vertical rotated chart', () => {
it('can position the tooltip on the top left corner', () => {
- const position = getFinalTooltipPosition(container, tooltip, {
+ const position = getFinalTooltipPosition(container, tooltip, portalWidth, {
isRotated: true,
y0: 0,
y1: 0,
@@ -94,8 +123,9 @@ describe('Tooltip position', () => {
expect(position.left).toBe('20px');
expect(position.top).toBe('15px');
});
+
it('can position the tooltip on the bottom left corner', () => {
- const position = getFinalTooltipPosition(container, tooltip, {
+ const position = getFinalTooltipPosition(container, tooltip, portalWidth, {
isRotated: true,
y0: 90,
y1: 90,
@@ -106,8 +136,9 @@ describe('Tooltip position', () => {
expect(position.left).toBe('20px');
expect(position.top).toBe('65px');
});
+
it('can position the tooltip on the top right corner', () => {
- const position = getFinalTooltipPosition(container, tooltip, {
+ const position = getFinalTooltipPosition(container, tooltip, portalWidth, {
isRotated: true,
y0: 0,
y1: 0,
@@ -118,8 +149,9 @@ describe('Tooltip position', () => {
expect(position.left).toBe('70px');
expect(position.top).toBe('15px');
});
+
it('can position the tooltip on the bottom right corner', () => {
- const position = getFinalTooltipPosition(container, tooltip, {
+ const position = getFinalTooltipPosition(container, tooltip, portalWidth, {
isRotated: true,
y0: 90,
y1: 90,
@@ -130,5 +162,29 @@ describe('Tooltip position', () => {
expect(position.left).toBe('70px');
expect(position.top).toBe('65px');
});
+
+ it('should render on right if portal width is within right side', () => {
+ const position = getFinalTooltipPosition(container, tooltip, 44, {
+ isRotated: true,
+ y0: 0,
+ y1: 0,
+ x0: 50,
+ x1: 50,
+ padding: 5,
+ });
+ expect(position.left).toBe('60px');
+ });
+
+ it('should render on left if portal width is NOT within right side', () => {
+ const position = getFinalTooltipPosition(container, tooltip, 51, {
+ isRotated: true,
+ y0: 0,
+ y1: 0,
+ x0: 50,
+ x1: 50,
+ padding: 5,
+ });
+ expect(position.left).toBe('70px');
+ });
});
});
diff --git a/src/chart_types/xy_chart/renderer/dom/annotation_tooltips.tsx b/src/chart_types/xy_chart/renderer/dom/annotation_tooltips.tsx
index 921efaf451..b88ff7cf39 100644
--- a/src/chart_types/xy_chart/renderer/dom/annotation_tooltips.tsx
+++ b/src/chart_types/xy_chart/renderer/dom/annotation_tooltips.tsx
@@ -58,6 +58,12 @@ class AnnotationTooltipComponent extends React.Component
static displayName = 'AnnotationTooltip';
portalNode: HTMLDivElement | null = null;
tooltipRef: React.RefObject;
+ /**
+ * Max allowable width for tooltip to grow to. Used to determine container fit.
+ *
+ * @unit px
+ */
+ static MAX_WIDTH = 256;
constructor(props: AnnotationTooltipProps) {
super(props);
@@ -71,6 +77,7 @@ class AnnotationTooltipComponent extends React.Component
} else {
this.portalNode = document.createElement('div');
this.portalNode.id = ANNOTATION_CONTAINER_ID;
+ this.portalNode.style.width = `${AnnotationTooltipComponent.MAX_WIDTH}px`;
document.body.appendChild(this.portalNode);
}
}
@@ -96,12 +103,15 @@ class AnnotationTooltipComponent extends React.Component
}
const chartContainerBBox = chartContainerRef.current.getBoundingClientRect();
+ const width = Math.min(AnnotationTooltipComponent.MAX_WIDTH, chartContainerBBox.width * 0.7);
+ this.portalNode.style.width = `${width}px`;
const tooltipBBox = this.tooltipRef.current.getBoundingClientRect();
const tooltipStyle = getFinalAnnotationTooltipPosition(
chartContainerBBox,
chartDimensions,
tooltipBBox,
tooltipState.anchor,
+ width,
);
if (tooltipStyle.left) {
@@ -110,6 +120,17 @@ class AnnotationTooltipComponent extends React.Component
if (tooltipStyle.top) {
this.portalNode.style.top = tooltipStyle.top;
}
+
+ if (tooltipStyle.left) {
+ this.portalNode.style.left = tooltipStyle.left;
+ if (this.tooltipRef.current) {
+ this.tooltipRef.current.style.left = tooltipStyle.anchor === 'right' ? 'auto' : '0px';
+ this.tooltipRef.current.style.right = tooltipStyle.anchor === 'right' ? '0px' : 'auto';
+ }
+ }
+ if (tooltipStyle.top) {
+ this.portalNode.style.top = tooltipStyle.top;
+ }
}
componentWillUnmount() {
diff --git a/src/components/_annotation.scss b/src/components/_annotation.scss
index ccebc89a7e..f7a5a63cde 100644
--- a/src/components/_annotation.scss
+++ b/src/components/_annotation.scss
@@ -1,6 +1,6 @@
#echAnnotationContainerPortal {
position: absolute;
- width: 256px;
+ pointer-events: none;
}
.echAnnotation {
pointer-events: none;
diff --git a/src/components/tooltip/_tooltip.scss b/src/components/tooltip/_tooltip.scss
index 8999ca7317..3949d05e38 100644
--- a/src/components/tooltip/_tooltip.scss
+++ b/src/components/tooltip/_tooltip.scss
@@ -1,6 +1,7 @@
#echTooltipContainerPortal {
position: absolute;
- width: 256px;
+ pointer-events: none;
+ z-index: 10000000;
}
.echTooltip {
position: absolute;
@@ -41,7 +42,7 @@
font-weight: $euiFontWeightBold;
text-align: right;
font-feature-settings: 'tnum';
- margin-left: 8px;
+ margin-left: $euiSizeS;
}
&__rowHighlighted {
diff --git a/src/components/tooltip/index.ts b/src/components/tooltip/index.ts
new file mode 100644
index 0000000000..695a135bc8
--- /dev/null
+++ b/src/components/tooltip/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License. */
+
+/** @internal */
+export { TooltipPortal as Tooltip } from './tooltip_portal';
diff --git a/src/components/tooltip/tooltip.tsx b/src/components/tooltip/tooltip.tsx
new file mode 100644
index 0000000000..6197beb801
--- /dev/null
+++ b/src/components/tooltip/tooltip.tsx
@@ -0,0 +1,75 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License. */
+
+import classNames from 'classnames';
+import React, { forwardRef, memo, useCallback } from 'react';
+import { TooltipInfo } from './types';
+import { TooltipValueFormatter, TooltipValue } from '../../specs';
+
+interface TooltipProps {
+ info: TooltipInfo;
+ headerFormatter?: TooltipValueFormatter;
+}
+
+const TooltipComponent = forwardRef(({ info, headerFormatter }, ref) => {
+ const renderHeader = useCallback(
+ (headerData: TooltipValue | null, formatter?: TooltipValueFormatter) => {
+ if (!headerData || !headerData.isVisible) {
+ return null;
+ }
+ return {formatter ? formatter(headerData) : headerData.value}
;
+ },
+ [info.header, headerFormatter],
+ );
+
+ return (
+
+ {renderHeader(info.header, headerFormatter)}
+
+ {info.values.map(
+ ({ seriesIdentifier, valueAccessor, label, value, markValue, color, isHighlighted, isVisible }, index) => {
+ if (!isVisible) {
+ return null;
+ }
+ const classes = classNames('echTooltip__item', {
+ /* eslint @typescript-eslint/camelcase:0 */
+ echTooltip__rowHighlighted: isHighlighted,
+ });
+ return (
+
+ {label}
+ {value}
+ {markValue && ({markValue})}
+
+ );
+ },
+ )}
+
+
+ );
+});
+
+/** @internal */
+export const Tooltip = memo(TooltipComponent);
diff --git a/src/components/tooltip/index.tsx b/src/components/tooltip/tooltip_portal.tsx
similarity index 64%
rename from src/components/tooltip/index.tsx
rename to src/components/tooltip/tooltip_portal.tsx
index dcf37459f0..8033637d50 100644
--- a/src/components/tooltip/index.tsx
+++ b/src/components/tooltip/tooltip_portal.tsx
@@ -16,38 +16,44 @@
* specific language governing permissions and limitations
* under the License. */
-import classNames from 'classnames';
import React from 'react';
import { createPortal } from 'react-dom';
import { connect } from 'react-redux';
import { getFinalTooltipPosition, TooltipAnchorPosition } from './utils';
import { TooltipInfo } from './types';
-import { TooltipValueFormatter, TooltipValue } from '../../specs';
+import { TooltipValueFormatter } from '../../specs';
import { GlobalChartState, BackwardRef } from '../../state/chart_state';
import { isInitialized } from '../../state/selectors/is_initialized';
import { getInternalIsTooltipVisibleSelector } from '../../state/selectors/get_internal_is_tooltip_visible';
import { getTooltipHeaderFormatterSelector } from '../../state/selectors/get_tooltip_header_formatter';
import { getInternalTooltipInfoSelector } from '../../state/selectors/get_internal_tooltip_info';
import { getInternalTooltipAnchorPositionSelector } from '../../state/selectors/get_internal_tooltip_anchor_position';
+import { Tooltip } from './tooltip';
-interface TooltipStateProps {
+interface TooltipPortalStateProps {
isVisible: boolean;
position: TooltipAnchorPosition | null;
info?: TooltipInfo;
headerFormatter?: TooltipValueFormatter;
}
-interface TooltipOwnProps {
+interface TooltipPortalOwnProps {
getChartContainerRef: BackwardRef;
}
-type TooltipProps = TooltipStateProps & TooltipOwnProps;
+type TooltipPortalProps = TooltipPortalStateProps & TooltipPortalOwnProps;
-class TooltipComponent extends React.Component {
+class TooltipPortalComponent extends React.Component {
static displayName = 'Tooltip';
+ /**
+ * Max allowable width for tooltip to grow to. Used to determine container fit.
+ *
+ * @unit px
+ */
+ static MAX_WIDTH = 256;
portalNode: HTMLDivElement | null = null;
tooltipRef: React.RefObject;
- constructor(props: TooltipProps) {
+ constructor(props: TooltipPortalProps) {
super(props);
this.tooltipRef = React.createRef();
}
@@ -58,6 +64,7 @@ class TooltipComponent extends React.Component {
} else {
this.portalNode = document.createElement('div');
this.portalNode.id = 'echTooltipContainerPortal';
+ this.portalNode.style.width = `${TooltipPortalComponent.MAX_WIDTH}px`;
document.body.appendChild(this.portalNode);
}
}
@@ -76,10 +83,16 @@ class TooltipComponent extends React.Component {
const chartContainerBBox = chartContainerRef.current.getBoundingClientRect();
const tooltipBBox = this.tooltipRef.current.getBoundingClientRect();
- const tooltipStyle = getFinalTooltipPosition(chartContainerBBox, tooltipBBox, position);
+ const width = Math.min(TooltipPortalComponent.MAX_WIDTH, chartContainerBBox.width * 0.7);
+ this.portalNode.style.width = `${width}px`;
+ const tooltipStyle = getFinalTooltipPosition(chartContainerBBox, tooltipBBox, width, position);
if (tooltipStyle.left) {
this.portalNode.style.left = tooltipStyle.left;
+ if (this.tooltipRef.current) {
+ this.tooltipRef.current.style.left = tooltipStyle.anchor === 'right' ? 'auto' : '0px';
+ this.tooltipRef.current.style.right = tooltipStyle.anchor === 'right' ? '0px' : 'auto';
+ }
}
if (tooltipStyle.top) {
this.portalNode.style.top = tooltipStyle.top;
@@ -92,50 +105,6 @@ class TooltipComponent extends React.Component {
}
}
- renderHeader(headerData: TooltipValue | null, formatter?: TooltipValueFormatter) {
- if (!headerData || !headerData.isVisible) {
- return null;
- }
- return {formatter ? formatter(headerData) : headerData.value}
;
- }
-
- renderTooltip = (info: TooltipInfo) => {
- const { headerFormatter } = this.props;
-
- return (
-
- {this.renderHeader(info.header, headerFormatter)}
-
- {info.values.map(
- ({ seriesIdentifier, valueAccessor, label, value, markValue, color, isHighlighted, isVisible }, index) => {
- if (!isVisible) {
- return null;
- }
- const classes = classNames('echTooltip__item', {
- /* eslint @typescript-eslint/camelcase:0 */
- echTooltip__rowHighlighted: isHighlighted,
- });
- return (
-
- {label}
- {value}
- {markValue && ({markValue})}
-
- );
- },
- )}
-
-
- );
- };
-
render() {
const { isVisible, info, getChartContainerRef } = this.props;
const chartContainerRef = getChartContainerRef();
@@ -144,7 +113,10 @@ class TooltipComponent extends React.Component {
return null;
}
- return createPortal(this.renderTooltip(info), this.portalNode);
+ return createPortal(
+ ,
+ this.portalNode,
+ );
}
}
@@ -155,7 +127,7 @@ const HIDDEN_TOOLTIP_PROPS = {
headerFormatter: undefined,
};
-const mapStateToProps = (state: GlobalChartState): TooltipStateProps => {
+const mapStateToProps = (state: GlobalChartState): TooltipPortalStateProps => {
if (!isInitialized(state)) {
return HIDDEN_TOOLTIP_PROPS;
}
@@ -168,4 +140,4 @@ const mapStateToProps = (state: GlobalChartState): TooltipStateProps => {
};
/** @internal */
-export const Tooltip = connect(mapStateToProps)(TooltipComponent);
+export const TooltipPortal = connect(mapStateToProps)(TooltipPortalComponent);
diff --git a/src/components/tooltip/utils.ts b/src/components/tooltip/utils.ts
index 0810254635..11e9682106 100644
--- a/src/components/tooltip/utils.ts
+++ b/src/components/tooltip/utils.ts
@@ -52,11 +52,14 @@ export function getFinalTooltipPosition(
container: Dimensions,
/** the dimensions of the tooltip container */
tooltip: Dimensions,
+ /** the width of the tooltip portal container */
+ portalWidth: number,
/** the tooltip anchor computed position not adjusted within chart bounds */
anchorPosition: TooltipAnchorPosition,
): {
left: string | null;
top: string | null;
+ anchor: 'left' | 'right';
} {
const { x1, y1, isRotated, padding = 10 } = anchorPosition;
let left = 0;
@@ -64,11 +67,13 @@ export function getFinalTooltipPosition(
const x0 = anchorPosition.x0 || anchorPosition.x1;
const y0 = anchorPosition.y0 || anchorPosition.y1;
+ let anchor: 'left' | 'right' = 'left' as 'left';
if (!isRotated) {
const leftOfBand = window.pageXOffset + container.left + x0;
- if (x1 + tooltip.width + padding > container.width) {
- left = leftOfBand - tooltip.width - padding;
+ if (x1 + portalWidth + padding > container.width) {
+ left = leftOfBand - portalWidth - padding;
+ anchor = 'right' as 'right';
} else {
left = leftOfBand + (x1 - x0) + padding;
}
@@ -79,8 +84,9 @@ export function getFinalTooltipPosition(
top = topOfBand + y0;
}
} else {
+ // not sure if this is also fixed no rotated charts
const leftOfBand = window.pageXOffset + container.left;
- if (x1 + tooltip.width > container.width) {
+ if (x1 + portalWidth > container.width) {
left = leftOfBand + container.width - tooltip.width;
} else {
left = leftOfBand + x1;
@@ -96,5 +102,6 @@ export function getFinalTooltipPosition(
return {
left: `${Math.round(left)}px`,
top: `${Math.round(top)}px`,
+ anchor,
};
}