From 5ee6ad0befb3aa6eb37f03a03d2849d4fc1fb514 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Thu, 23 Apr 2020 11:40:53 -0500 Subject: [PATCH 1/7] fix: tooltip container scroll issue - refactor tooltip logic to account for portal - seperate tooltip and tooltip portal components - update tests to check for new portal condition - set max tooltip width from component js --- .playground/index.html | 14 ++- .playground/playground.tsx | 98 +++++++++++++------ .../crosshair/crosshair_utils.test.ts | 72 ++++++++++++-- src/components/tooltip/_tooltip.scss | 3 +- src/components/tooltip/index.ts | 19 ++++ src/components/tooltip/tooltip.tsx | 75 ++++++++++++++ .../tooltip/{index.tsx => tooltip_portal.tsx} | 81 +++++---------- src/components/tooltip/utils.ts | 6 +- 8 files changed, 270 insertions(+), 98 deletions(-) create mode 100644 src/components/tooltip/index.ts create mode 100644 src/components/tooltip/tooltip.tsx rename src/components/tooltip/{index.tsx => tooltip_portal.tsx} (63%) 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..1fcf4b8c9b 100644 --- a/.playground/playground.tsx +++ b/.playground/playground.tsx @@ -17,7 +17,18 @@ * under the License. */ import React from 'react'; -import { Chart, Partition, Settings, PartitionLayout, XYChartElementEvent, PartitionElementEvent } from '../src'; +import { Chart, Partition, Settings, PartitionLayout, XYChartElementEvent, PartitionElementEvent, Datum } from '../src'; +import { mocks } from '../src/mocks/hierarchical'; +import { arrayToLookup, hueInterpolator } from '../src/chart_types/partition_chart/layout/utils/calcs'; +import { regionDimension, countryDimension } from '../src/mocks/hierarchical/dimension_codes'; +import { palettes } from '../src/mocks/hierarchical/palettes'; +import { config } from '../src/chart_types/partition_chart/layout/config/config'; +import { ShapeTreeNode } from '../src/chart_types/partition_chart/layout/types/viewmodel_types'; + +const regionLookup = arrayToLookup((d: Datum) => d.region, regionDimension); +const countryLookup = arrayToLookup((d: Datum) => d.country, countryDimension); + +const interpolatorTurbo = hueInterpolator(palettes.turbo.map(([r, g, b]) => [r, g, b, 0.7])); export class Playground extends React.Component { onElementClick = (elements: (XYChartElementEvent | PartitionElementEvent)[]) => { @@ -26,33 +37,64 @@ 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, - }, - ]} - /> - +
+
+ + + d.exportVal as number} + valueFormatter={(d: number) => `$${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`} + layers={[ + { + groupByRollup: (d: Datum) => countryLookup[d.dest].continentCountry.substr(0, 2), + nodeLabel: (d: any) => regionLookup[d].regionName, + fillLabel: { + valueFormatter: (d: number) => + `${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`, + fontFamily: 'Phosphate-Inline', + textColor: 'yellow', + textInvertible: false, + }, + shape: { fillColor: 'rgba(0,0,0,0)' }, + }, + { + groupByRollup: (d: Datum) => d.dest, + nodeLabel: (d: any) => countryLookup[d].name, + fillLabel: { + valueFormatter: (d: number) => + `${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`, + textColor: 'black', + textInvertible: false, + fontWeight: 200, + fontStyle: 'normal', + fontFamily: 'Helvetica', + fontVariant: 'small-caps', + valueFont: { fontWeight: 400, fontStyle: 'italic' }, + }, + shape: { + fillColor: (d: ShapeTreeNode) => { + // primarily, pick color based on parent's index, but then perturb by the index within the parent + return interpolatorTurbo( + (d.parent.sortIndex + d.sortIndex / d.parent.children.length) / + (d.parent.parent.children.length + 1), + ); + }, + }, + }, + ]} + config={{ + partitionLayout: PartitionLayout.treemap, + margin: { top: 0, bottom: 0, left: 0, right: 0 }, + minFontSize: 4, + maxFontSize: 84, + idealFontSizeJump: 1.15, + outerSizeRatio: 1, + }} + /> + +
); } 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..5da9ac4ca2 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, @@ -68,8 +71,9 @@ describe('Tooltip position', () => { expect(position.left).toBe('65px'); 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, @@ -80,10 +84,35 @@ describe('Tooltip position', () => { expect(position.left).toBe('65px'); 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('15px'); + }); }); + 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/components/tooltip/_tooltip.scss b/src/components/tooltip/_tooltip.scss index 8999ca7317..71e17ad0c5 100644 --- a/src/components/tooltip/_tooltip.scss +++ b/src/components/tooltip/_tooltip.scss @@ -1,6 +1,5 @@ #echTooltipContainerPortal { position: absolute; - width: 256px; } .echTooltip { position: absolute; @@ -41,7 +40,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..66d2ba251b --- /dev/null +++ b/src/components/tooltip/index.ts @@ -0,0 +1,19 @@ +/* + * 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. */ + +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 63% rename from src/components/tooltip/index.tsx rename to src/components/tooltip/tooltip_portal.tsx index dcf37459f0..4e753e04d5 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 maxWidth = 256; portalNode: HTMLDivElement | null = null; tooltipRef: React.RefObject; - constructor(props: TooltipProps) { + constructor(props: TooltipPortalProps) { super(props); this.tooltipRef = React.createRef(); } @@ -57,6 +63,7 @@ class TooltipComponent extends React.Component { this.portalNode = container as HTMLDivElement; } else { this.portalNode = document.createElement('div'); + this.portalNode.style.width = `${TooltipPortalComponent.maxWidth}px`; this.portalNode.id = 'echTooltipContainerPortal'; document.body.appendChild(this.portalNode); } @@ -76,7 +83,12 @@ class TooltipComponent extends React.Component { const chartContainerBBox = chartContainerRef.current.getBoundingClientRect(); const tooltipBBox = this.tooltipRef.current.getBoundingClientRect(); - const tooltipStyle = getFinalTooltipPosition(chartContainerBBox, tooltipBBox, position); + const tooltipStyle = getFinalTooltipPosition( + chartContainerBBox, + tooltipBBox, + TooltipPortalComponent.maxWidth, + position, + ); if (tooltipStyle.left) { this.portalNode.style.left = tooltipStyle.left; @@ -92,50 +104,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 +112,10 @@ class TooltipComponent extends React.Component { return null; } - return createPortal(this.renderTooltip(info), this.portalNode); + return createPortal( + , + this.portalNode, + ); } } @@ -155,7 +126,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 +139,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..8360572120 100644 --- a/src/components/tooltip/utils.ts +++ b/src/components/tooltip/utils.ts @@ -52,6 +52,8 @@ 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, ): { @@ -67,7 +69,7 @@ export function getFinalTooltipPosition( if (!isRotated) { const leftOfBand = window.pageXOffset + container.left + x0; - if (x1 + tooltip.width + padding > container.width) { + if (x1 + portalWidth + padding > container.width) { left = leftOfBand - tooltip.width - padding; } else { left = leftOfBand + (x1 - x0) + padding; @@ -80,7 +82,7 @@ export function getFinalTooltipPosition( } } else { 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; From 072b09f22691681124390cc102cbcc1e7e063a28 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Thu, 23 Apr 2020 12:06:33 -0500 Subject: [PATCH 2/7] fix: type error in dist --- src/components/tooltip/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/tooltip/index.ts b/src/components/tooltip/index.ts index 66d2ba251b..695a135bc8 100644 --- a/src/components/tooltip/index.ts +++ b/src/components/tooltip/index.ts @@ -16,4 +16,5 @@ * specific language governing permissions and limitations * under the License. */ +/** @internal */ export { TooltipPortal as Tooltip } from './tooltip_portal'; From 0140bd2d554fd0d9d5f61fb4d33a49b12a6c628b Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Fri, 24 Apr 2020 16:10:07 +0200 Subject: [PATCH 3/7] fix: possibly fix the scrollbar in all charts --- .playground/playground.tsx | 72 +---------------------- src/components/tooltip/_tooltip.scss | 5 ++ src/components/tooltip/tooltip_portal.tsx | 4 ++ src/components/tooltip/utils.ts | 7 ++- 4 files changed, 18 insertions(+), 70 deletions(-) diff --git a/.playground/playground.tsx b/.playground/playground.tsx index 1fcf4b8c9b..df9ed05a59 100644 --- a/.playground/playground.tsx +++ b/.playground/playground.tsx @@ -17,18 +17,8 @@ * under the License. */ import React from 'react'; -import { Chart, Partition, Settings, PartitionLayout, XYChartElementEvent, PartitionElementEvent, Datum } from '../src'; -import { mocks } from '../src/mocks/hierarchical'; -import { arrayToLookup, hueInterpolator } from '../src/chart_types/partition_chart/layout/utils/calcs'; -import { regionDimension, countryDimension } from '../src/mocks/hierarchical/dimension_codes'; -import { palettes } from '../src/mocks/hierarchical/palettes'; -import { config } from '../src/chart_types/partition_chart/layout/config/config'; -import { ShapeTreeNode } from '../src/chart_types/partition_chart/layout/types/viewmodel_types'; - -const regionLookup = arrayToLookup((d: Datum) => d.region, regionDimension); -const countryLookup = arrayToLookup((d: Datum) => d.country, countryDimension); - -const interpolatorTurbo = hueInterpolator(palettes.turbo.map(([r, g, b]) => [r, g, b, 0.7])); +import { XYChartElementEvent, PartitionElementEvent } from '../src'; +import { example } from '../stories/treemap/6_custom_style'; export class Playground extends React.Component { onElementClick = (elements: (XYChartElementEvent | PartitionElementEvent)[]) => { @@ -38,63 +28,7 @@ export class Playground extends React.Component { render() { return (
-
- - - d.exportVal as number} - valueFormatter={(d: number) => `$${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`} - layers={[ - { - groupByRollup: (d: Datum) => countryLookup[d.dest].continentCountry.substr(0, 2), - nodeLabel: (d: any) => regionLookup[d].regionName, - fillLabel: { - valueFormatter: (d: number) => - `${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`, - fontFamily: 'Phosphate-Inline', - textColor: 'yellow', - textInvertible: false, - }, - shape: { fillColor: 'rgba(0,0,0,0)' }, - }, - { - groupByRollup: (d: Datum) => d.dest, - nodeLabel: (d: any) => countryLookup[d].name, - fillLabel: { - valueFormatter: (d: number) => - `${config.fillLabel.valueFormatter(Math.round(d / 1000000000))}\xa0Bn`, - textColor: 'black', - textInvertible: false, - fontWeight: 200, - fontStyle: 'normal', - fontFamily: 'Helvetica', - fontVariant: 'small-caps', - valueFont: { fontWeight: 400, fontStyle: 'italic' }, - }, - shape: { - fillColor: (d: ShapeTreeNode) => { - // primarily, pick color based on parent's index, but then perturb by the index within the parent - return interpolatorTurbo( - (d.parent.sortIndex + d.sortIndex / d.parent.children.length) / - (d.parent.parent.children.length + 1), - ); - }, - }, - }, - ]} - config={{ - partitionLayout: PartitionLayout.treemap, - margin: { top: 0, bottom: 0, left: 0, right: 0 }, - minFontSize: 4, - maxFontSize: 84, - idealFontSizeJump: 1.15, - outerSizeRatio: 1, - }} - /> - -
+
{example()}
); } diff --git a/src/components/tooltip/_tooltip.scss b/src/components/tooltip/_tooltip.scss index 71e17ad0c5..3ff8e62531 100644 --- a/src/components/tooltip/_tooltip.scss +++ b/src/components/tooltip/_tooltip.scss @@ -1,7 +1,12 @@ #echTooltipContainerPortal { position: absolute; + background-color: red; + height: 20px; + pointer-events: none; + z-index: 10000000; } .echTooltip { + opacity: 0.2; position: absolute; @include euiToolTipStyle; @include euiFontSizeXS; diff --git a/src/components/tooltip/tooltip_portal.tsx b/src/components/tooltip/tooltip_portal.tsx index 4e753e04d5..eeb800d7be 100644 --- a/src/components/tooltip/tooltip_portal.tsx +++ b/src/components/tooltip/tooltip_portal.tsx @@ -92,6 +92,10 @@ class TooltipPortalComponent extends React.Component { 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; diff --git a/src/components/tooltip/utils.ts b/src/components/tooltip/utils.ts index 8360572120..11e9682106 100644 --- a/src/components/tooltip/utils.ts +++ b/src/components/tooltip/utils.ts @@ -59,6 +59,7 @@ export function getFinalTooltipPosition( ): { left: string | null; top: string | null; + anchor: 'left' | 'right'; } { const { x1, y1, isRotated, padding = 10 } = anchorPosition; let left = 0; @@ -66,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 + portalWidth + padding > container.width) { - left = leftOfBand - tooltip.width - padding; + left = leftOfBand - portalWidth - padding; + anchor = 'right' as 'right'; } else { left = leftOfBand + (x1 - x0) + padding; } @@ -81,6 +84,7 @@ 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 + portalWidth > container.width) { left = leftOfBand + container.width - tooltip.width; @@ -98,5 +102,6 @@ export function getFinalTooltipPosition( return { left: `${Math.round(left)}px`, top: `${Math.round(top)}px`, + anchor, }; } From 80839cf9bb112ea68203767452823498fef2c9fd Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Mon, 27 Apr 2020 09:00:07 -0500 Subject: [PATCH 4/7] fix: set max width to 70% of container width --- src/components/tooltip/_tooltip.scss | 3 --- src/components/tooltip/tooltip_portal.tsx | 11 ++++------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/components/tooltip/_tooltip.scss b/src/components/tooltip/_tooltip.scss index 3ff8e62531..3949d05e38 100644 --- a/src/components/tooltip/_tooltip.scss +++ b/src/components/tooltip/_tooltip.scss @@ -1,12 +1,9 @@ #echTooltipContainerPortal { position: absolute; - background-color: red; - height: 20px; pointer-events: none; z-index: 10000000; } .echTooltip { - opacity: 0.2; position: absolute; @include euiToolTipStyle; @include euiFontSizeXS; diff --git a/src/components/tooltip/tooltip_portal.tsx b/src/components/tooltip/tooltip_portal.tsx index eeb800d7be..bc14fa8d6c 100644 --- a/src/components/tooltip/tooltip_portal.tsx +++ b/src/components/tooltip/tooltip_portal.tsx @@ -63,8 +63,8 @@ class TooltipPortalComponent extends React.Component { this.portalNode = container as HTMLDivElement; } else { this.portalNode = document.createElement('div'); - this.portalNode.style.width = `${TooltipPortalComponent.maxWidth}px`; this.portalNode.id = 'echTooltipContainerPortal'; + this.portalNode.style.width = `${TooltipPortalComponent.maxWidth}px`; document.body.appendChild(this.portalNode); } } @@ -83,12 +83,9 @@ class TooltipPortalComponent extends React.Component { const chartContainerBBox = chartContainerRef.current.getBoundingClientRect(); const tooltipBBox = this.tooltipRef.current.getBoundingClientRect(); - const tooltipStyle = getFinalTooltipPosition( - chartContainerBBox, - tooltipBBox, - TooltipPortalComponent.maxWidth, - position, - ); + const width = Math.min(TooltipPortalComponent.maxWidth, 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; From d2eb3460e458dbbf8eca3311affacbd3ca25fd6a Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Mon, 27 Apr 2020 09:08:12 -0500 Subject: [PATCH 5/7] test: fix positioning tests --- src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 5da9ac4ca2..bf171c4845 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts @@ -68,7 +68,7 @@ describe('Tooltip position', () => { x1: 100, padding: 5, }); - expect(position.left).toBe('65px'); + expect(position.left).toBe('55px'); expect(position.top).toBe('10px'); }); @@ -81,7 +81,7 @@ describe('Tooltip position', () => { x1: 100, padding: 5, }); - expect(position.left).toBe('65px'); + expect(position.left).toBe('55px'); expect(position.top).toBe('80px'); }); @@ -106,7 +106,7 @@ describe('Tooltip position', () => { x1: 50, padding: 5, }); - expect(position.left).toBe('15px'); + expect(position.left).toBe('9px'); }); }); From 96784a3f26ad8e90676438a888a782b9bd37dad4 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Mon, 27 Apr 2020 17:26:35 -0500 Subject: [PATCH 6/7] fix: annotations tooltips positioning --- .../annotations/annotation_tooltip.ts | 10 +++++++-- .../renderer/dom/annotation_tooltips.tsx | 21 +++++++++++++++++++ src/components/_annotation.scss | 2 +- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/chart_types/xy_chart/annotations/annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/annotation_tooltip.ts index 1df2745586..1dedc7d438 100644 --- a/src/chart_types/xy_chart/annotations/annotation_tooltip.ts +++ b/src/chart_types/xy_chart/annotations/annotation_tooltip.ts @@ -27,16 +27,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: 'left' | 'right' = 'left' as '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 = 'right' as 'right'; } else { left = annotationXOffset + padding; } @@ -50,5 +55,6 @@ export function getFinalAnnotationTooltipPosition( return { left: `${Math.round(left)}px`, top: `${Math.round(top)}px`, + anchor, }; } 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..fb3a953adf 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 maxWidth = 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.maxWidth}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.maxWidth, 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; From 90fc2348e10e312c56a55ac0f8e58dcd9d210fe2 Mon Sep 17 00:00:00 2001 From: nickofthyme Date: Tue, 28 Apr 2020 08:27:04 -0500 Subject: [PATCH 7/7] fix: variable naming --- src/chart_types/xy_chart/annotations/annotation_tooltip.ts | 5 +++-- .../xy_chart/renderer/dom/annotation_tooltips.tsx | 6 +++--- src/components/tooltip/tooltip_portal.tsx | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/chart_types/xy_chart/annotations/annotation_tooltip.ts b/src/chart_types/xy_chart/annotations/annotation_tooltip.ts index 1dedc7d438..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( @@ -36,12 +37,12 @@ export function getFinalAnnotationTooltipPosition( anchor: 'left' | 'right'; } { let left = 0; - let anchor: 'left' | 'right' = 'left' as 'left'; + let anchor: Position = Position.Left; const annotationXOffset = window.pageXOffset + container.left + chartDimensions.left + tooltipAnchor.left; if (chartDimensions.left + tooltipAnchor.left + portalWidth + padding >= container.width) { left = annotationXOffset - portalWidth - padding; - anchor = 'right' as 'right'; + anchor = Position.Right; } else { left = annotationXOffset + padding; } 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 fb3a953adf..b88ff7cf39 100644 --- a/src/chart_types/xy_chart/renderer/dom/annotation_tooltips.tsx +++ b/src/chart_types/xy_chart/renderer/dom/annotation_tooltips.tsx @@ -63,7 +63,7 @@ class AnnotationTooltipComponent extends React.Component * * @unit px */ - static maxWidth = 256; + static MAX_WIDTH = 256; constructor(props: AnnotationTooltipProps) { super(props); @@ -77,7 +77,7 @@ class AnnotationTooltipComponent extends React.Component } else { this.portalNode = document.createElement('div'); this.portalNode.id = ANNOTATION_CONTAINER_ID; - this.portalNode.style.width = `${AnnotationTooltipComponent.maxWidth}px`; + this.portalNode.style.width = `${AnnotationTooltipComponent.MAX_WIDTH}px`; document.body.appendChild(this.portalNode); } } @@ -103,7 +103,7 @@ class AnnotationTooltipComponent extends React.Component } const chartContainerBBox = chartContainerRef.current.getBoundingClientRect(); - const width = Math.min(AnnotationTooltipComponent.maxWidth, chartContainerBBox.width * 0.7); + 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( diff --git a/src/components/tooltip/tooltip_portal.tsx b/src/components/tooltip/tooltip_portal.tsx index bc14fa8d6c..8033637d50 100644 --- a/src/components/tooltip/tooltip_portal.tsx +++ b/src/components/tooltip/tooltip_portal.tsx @@ -49,7 +49,7 @@ class TooltipPortalComponent extends React.Component { * * @unit px */ - static maxWidth = 256; + static MAX_WIDTH = 256; portalNode: HTMLDivElement | null = null; tooltipRef: React.RefObject; @@ -64,7 +64,7 @@ class TooltipPortalComponent extends React.Component { } else { this.portalNode = document.createElement('div'); this.portalNode.id = 'echTooltipContainerPortal'; - this.portalNode.style.width = `${TooltipPortalComponent.maxWidth}px`; + this.portalNode.style.width = `${TooltipPortalComponent.MAX_WIDTH}px`; document.body.appendChild(this.portalNode); } } @@ -83,7 +83,7 @@ class TooltipPortalComponent extends React.Component { const chartContainerBBox = chartContainerRef.current.getBoundingClientRect(); const tooltipBBox = this.tooltipRef.current.getBoundingClientRect(); - const width = Math.min(TooltipPortalComponent.maxWidth, chartContainerBBox.width * 0.7); + const width = Math.min(TooltipPortalComponent.MAX_WIDTH, chartContainerBBox.width * 0.7); this.portalNode.style.width = `${width}px`; const tooltipStyle = getFinalTooltipPosition(chartContainerBBox, tooltipBBox, width, position);