Skip to content

Commit

Permalink
Improve Tooltip
Browse files Browse the repository at this point in the history
 - Scroll tooltip if content is scrolled
 - Prevent tooltip from being displayed without selection

 321048
  • Loading branch information
bsifpa authored and fschinkel committed Apr 12, 2023
1 parent 81da746 commit c21abf9
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 26 deletions.
20 changes: 13 additions & 7 deletions eclipse-scout-chart/src/chart/ChartJsRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
LegendItem, LegendOptions, LinearScaleOptions, PointElement, PointHoverOptions, PointOptions, PointProps, RadialLinearScaleOptions, Scale, ScatterDataPoint, TooltipCallbacks, TooltipItem, TooltipLabelStyle, TooltipModel, TooltipOptions
} from 'chart.js';
import 'chart.js/auto'; // Import from auto to register charts
import {arrays, colorSchemes, graphics, numbers, objects, scout, strings, styles, Tooltip, tooltips} from '@eclipse-scout/core';
import {arrays, colorSchemes, graphics, numbers, objects, Point, scout, strings, styles, Tooltip, tooltips} from '@eclipse-scout/core';
import ChartDataLabels, {Context} from 'chartjs-plugin-datalabels';
import $ from 'jquery';
import {ChartAxis, ChartConfig, ChartData, ChartType, ClickObject, NumberFormatter} from './Chart';
Expand Down Expand Up @@ -962,6 +962,10 @@ export class ChartJsRenderer extends AbstractChartRenderer {
if (isTooltipShowing) {
this._renderTooltipLater(context);
} else {
// clear timeout before creating a new handler.
// Otherwise, changing the context within the tooltip delay time creates a second handler
// and the first one will always be executed, since the tooltipTimoutId reference to it is lost
clearTimeout(this._tooltipTimeoutId);
this._tooltipTimeoutId = setTimeout(() => this._renderTooltipLater(context), tooltips.DEFAULT_TOOLTIP_DELAY);
}
}
Expand Down Expand Up @@ -1001,11 +1005,8 @@ export class ChartJsRenderer extends AbstractChartRenderer {
tooltipText += arrays.ensure(tooltipItems(dataPoints, tooltipLabel, tooltipLabelValue, tooltipColor)).join('');
}

let positionAndOffset = this._computeTooltipPositionAndOffset(firstDataPoint),
origin = graphics.offsetBounds(this.$canvas);
origin.x += tooltip.caretX + positionAndOffset.offsetX;
origin.y += tooltip.caretY + positionAndOffset.offsetY;
origin.height = positionAndOffset.height;
let positionAndOffset = this._computeTooltipPositionAndOffset(firstDataPoint);
let offset = new Point(tooltip.caretX + positionAndOffset.offsetX, tooltip.caretY + positionAndOffset.offsetY);

this._tooltip = scout.create({
objectType: Tooltip,
Expand All @@ -1016,7 +1017,12 @@ export class ChartJsRenderer extends AbstractChartRenderer {
cssClass: strings.join(' ', 'chart-tooltip', tooltipOptions.cssClass),
tooltipPosition: positionAndOffset.tooltipPosition,
tooltipDirection: positionAndOffset.tooltipDirection,
origin: origin
originProducer: $anchor => {
const origin = graphics.offsetBounds($anchor);
origin.height = positionAndOffset.height;
return origin;
},
offsetProducer: origin => offset
});
this._tooltip.render();

Expand Down
61 changes: 43 additions & 18 deletions eclipse-scout-core/src/tooltip/Tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
import {arrays, Form, graphics, keys, Menu, ObjectOrChildModel, Rectangle, scout, scrollbars, Status, StatusSeverity, strings, TooltipEventMap, TooltipModel, Widget} from '../index';
import {arrays, Form, graphics, keys, Menu, ObjectOrChildModel, Point, Rectangle, scout, scrollbars, Status, StatusSeverity, strings, TooltipEventMap, TooltipModel, Widget} from '../index';
import $ from 'jquery';
import KeyDownEvent = JQuery.KeyDownEvent;

Expand All @@ -28,6 +28,8 @@ export class Tooltip extends Widget implements TooltipModel {
windowPaddingY: number;
origin: Rectangle;
originRelativeToParent: boolean;
originProducer: ($anchor: JQuery) => Rectangle;
offsetProducer: (origin: Rectangle) => Point;
autoRemove: boolean;
tooltipPosition: TooltipPosition;
tooltipDirection: TooltipDirection;
Expand Down Expand Up @@ -57,6 +59,8 @@ export class Tooltip extends Widget implements TooltipModel {
this.windowPaddingY = 5;
this.origin = null;
this.originRelativeToParent = false;
this.originProducer = null;
this.offsetProducer = null;
this.autoRemove = true;
this.tooltipPosition = 'top';
this.tooltipDirection = 'right';
Expand Down Expand Up @@ -261,29 +265,24 @@ export class Tooltip extends Widget implements TooltipModel {
}

position() {
let top, left, arrowSizeX, arrowSizeY, overlapX, overlapY, x, y, origin,
let top, left, arrowSizeX, arrowSizeY, overlapX, overlapY, tooltipPosition, origin, offset,
tooltipWidth, tooltipHeight, arrowPosition, inView;

if (this.origin) {
origin = this.origin;
x = origin.x;
} else {
origin = graphics.offsetBounds(this.$anchor);
x = origin.x + origin.width / 2;
}
y = origin.y;
origin = this._getOrigin();
offset = this._getOffset(origin);
tooltipPosition = origin.point().add(offset);

if (this.$anchor) {
// Sticky tooltip must only be visible if the location where the tooltip points is in view (prevents that the tooltip points at an invisible anchor)
inView = scrollbars.isLocationInView(origin, this.$anchor.scrollParent());
inView = scrollbars.isLocationInView(tooltipPosition, this.$anchor.scrollParent());
this.$container.setVisible(inView);
}

// this.$parent might not be at (0,0) of the document
if (!this.originRelativeToParent) {
let parentOffset = this.$parent.offset();
x -= parentOffset.left;
y -= parentOffset.top;
tooltipPosition.x -= parentOffset.left;
tooltipPosition.y -= parentOffset.top;
}

arrowSizeX = 7;
Expand All @@ -301,27 +300,27 @@ export class Tooltip extends Widget implements TooltipModel {
arrowPosition = tooltipWidth - arrowPosition;
}

top = y - tooltipHeight - arrowSizeY;
left = x - arrowPosition;
top = tooltipPosition.y - tooltipHeight - arrowSizeY;
left = tooltipPosition.x - arrowPosition;
overlapX = left + tooltipWidth + this.windowPaddingX - this.$parent.width();
overlapY = top - this.windowPaddingY;

// Move tooltip to the left until it gets fully visible
if (overlapX > 0) {
left -= overlapX;
arrowPosition = x - left;
arrowPosition = tooltipPosition.x - left;
}
// Move tooltip to the right if it overlaps the left edge
if (left < this.windowPaddingX) {
left = this.windowPaddingX;
arrowPosition = x - this.windowPaddingX;
arrowPosition = tooltipPosition.x - this.windowPaddingX;
}

// Move tooltip to the bottom, arrow on top
this.$arrow.removeClass('arrow-top arrow-bottom');
if (this.tooltipPosition === 'bottom' || overlapY < 0) {
this.$arrow.addClass('arrow-top');
top = y + origin.height + arrowSizeY;
top = tooltipPosition.y + origin.height + arrowSizeY;
} else {
this.$arrow.addClass('arrow-bottom');
}
Expand All @@ -342,6 +341,32 @@ export class Tooltip extends Widget implements TooltipModel {
}, this);
}

protected _getOrigin(): Rectangle {
if (this.originProducer) {
const origin = this.originProducer(this.$anchor);
if (origin) {
return origin;
}
}
if (this.origin) {
return this.origin;
}
return graphics.offsetBounds(this.$anchor);
}

protected _getOffset(origin: Rectangle): Point {
if (this.offsetProducer) {
const offset = this.offsetProducer(origin);
if (offset) {
return offset;
}
}
if (this.origin) {
return new Point(0, 0);
}
return new Point(origin.width / 2, 0);
}

protected _onAnchorScroll(event: JQuery.ScrollEvent) {
if (!this.rendered) {
// Scroll events may be fired delayed, even if scroll listener are already removed.
Expand Down
11 changes: 10 additions & 1 deletion eclipse-scout-core/src/tooltip/TooltipModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
import {Menu, ObjectOrChildModel, Rectangle, StatusSeverity, TooltipDirection, TooltipPosition, TooltipScrollType, WidgetModel} from '../index';
import {Menu, ObjectOrChildModel, Point, Rectangle, StatusSeverity, TooltipDirection, TooltipPosition, TooltipScrollType, WidgetModel} from '../index';

export interface TooltipModel extends WidgetModel {
/**
Expand Down Expand Up @@ -42,6 +42,15 @@ export interface TooltipModel extends WidgetModel {
* "true" to disable this additional calculation.
*/
originRelativeToParent?: boolean;
/**
* If provided, the originProvider is called during the calculation of the tooltip position.
* If no originProducer is present, the {@link origin} or {@link $anchor} is used for the calculation
*/
originProducer?: ($anchor: JQuery) => Rectangle;
/**
* If provided, the offsetProducer is called during the calculation of the tooltip position.
*/
offsetProducer?: (origin: Rectangle) => Point;
/**
* Default is false
*/
Expand Down

0 comments on commit c21abf9

Please sign in to comment.