Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AG-13839 Single-touch drag (show tooltip) #3411

Merged
merged 5 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/ag-charts-community/src/chart/caption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class Caption extends BaseProperties implements CaptionLike {
const { x, y } = Transformable.toCanvas(this.node);
const canvasX = event.sourceEvent.offsetX + x;
const canvasY = event.sourceEvent.offsetY + y;
const lastPointerEvent = { type: 'mousemove', canvasX, canvasY } as const;
const lastPointerEvent = { type: 'pointermove', canvasX, canvasY } as const;
moduleCtx.tooltipManager.updateTooltip(
this.id,
{ canvasX, canvasY, lastPointerEvent, showArrow: false },
Expand Down
4 changes: 2 additions & 2 deletions packages/ag-charts-community/src/chart/legend/legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -994,14 +994,14 @@ export class Legend extends BaseProperties {
}

private toTooltipMeta(event: FocusEvent | MouseEvent, node: LegendMarkerLabel): TooltipMeta {
let lastPointerEvent: TooltipPointerEvent<'mousemove' | 'keyboard'>;
let lastPointerEvent: TooltipPointerEvent<'pointermove' | 'keyboard'>;
if (event instanceof FocusEvent) {
const { x, y } = Transformable.toCanvas(node).computeCenter();
lastPointerEvent = { type: 'keyboard', canvasX: x, canvasY: y } as const;
} else {
event.preventDefault();
const { x, y } = Transformable.toCanvasPoint(node, event.offsetX, event.offsetY);
lastPointerEvent = { type: 'mousemove', canvasX: x, canvasY: y };
lastPointerEvent = { type: 'pointermove', canvasX: x, canvasY: y };
}

const { canvasX, canvasY } = lastPointerEvent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export interface SeriesAreaChartDependencies {
mode: ChartMode;
}

type TooltipWidgetEvent = MouseWidgetEvent<'mousemove' | 'click' | 'dblclick'>;
type HighlightWidgetEvent = MouseWidgetEvent<'mousemove' | 'click' | 'dblclick'> | DragWidgetEvent<'drag-move'>;
type HoverLikeEvent = Partial<Pick<DragWidgetEvent, 'device'>> &
(MouseWidgetEvent<'mousemove' | 'click' | 'dblclick'> | DragWidgetEvent<'drag-move'>);

type PickedNode = {
series: Series<unknown, any, any>;
Expand All @@ -68,15 +68,15 @@ export class SeriesAreaManager extends BaseManager {

private readonly highlight = {
/** Last received event that still needs to be applied. */
pendingHoverEvent: undefined as HighlightWidgetEvent | undefined,
pendingHoverEvent: undefined as HoverLikeEvent | undefined,
/** Last applied event. */
appliedHoverEvent: undefined as HighlightWidgetEvent | undefined,
appliedHoverEvent: undefined as HoverLikeEvent | undefined,
/** Last applied event, which has been temporarily stashed during the main chart update cycle. */
stashedHoverEvent: undefined as HighlightWidgetEvent | undefined,
stashedHoverEvent: undefined as HoverLikeEvent | undefined,
};

private readonly tooltip = {
lastHover: undefined as TooltipWidgetEvent | undefined,
lastHover: undefined as HoverLikeEvent | undefined,
};

/**
Expand All @@ -93,7 +93,7 @@ export class SeriesAreaManager extends BaseManager {
* - For keyboard users, `mousemove` events update the tooltip/highlight iff `pickNode` finds a match
* for the mouse event offsets.
*/
private hoverDevice: 'mouse' | 'keyboard' = 'mouse';
private hoverDevice: 'pointer' | 'keyboard' = 'pointer';

/**
* This is the "second last" input event. It can be useful for keydown
Expand All @@ -103,7 +103,7 @@ export class SeriesAreaManager extends BaseManager {
* Use with caution! The focus indicator must ALWAYS be visible for
* keyboard-only users.
*/
private previousInputDevice: 'mouse' | 'keyboard' = 'keyboard';
private previousInputDevice: 'pointer' | 'keyboard' = 'keyboard';

private readonly focus = {
sortedSeries: [] as Series<unknown, SeriesNodeDatum<unknown>, SeriesProperties<object>>[],
Expand Down Expand Up @@ -276,7 +276,7 @@ export class SeriesAreaManager extends BaseManager {
private onWheel(_event: WheelWidgetEvent): void {
if (!this.isState(InteractionState.Clickable)) return;
this.focusIndicator?.overrideFocusVisible(false);
this.previousInputDevice = 'mouse';
this.previousInputDevice = 'pointer';
}

private onDragMove(event: DragWidgetEvent<'drag-move'>): void {
Expand All @@ -290,12 +290,12 @@ export class SeriesAreaManager extends BaseManager {
this.onHoverLikeEvent(event);
}

private onHoverLikeEvent(event: HighlightWidgetEvent | TooltipWidgetEvent): void {
if (excludesType(event, 'drag-move')) {
private onHoverLikeEvent(event: HoverLikeEvent): void {
if (event.device === 'touch' || excludesType(event, 'drag-move')) {
this.tooltip.lastHover = event;
}
this.hoverDevice = 'mouse';
this.previousInputDevice = 'mouse';
this.hoverDevice = 'pointer';
this.previousInputDevice = 'pointer';
this.highlight.pendingHoverEvent = event;
this.hoverScheduler.schedule();

Expand Down Expand Up @@ -344,13 +344,13 @@ export class SeriesAreaManager extends BaseManager {

private onFocus(): void {
if (!this.isState(InteractionState.Focusable)) return;
this.hoverDevice = this.focusIndicator.isFocusVisible() ? 'keyboard' : 'mouse';
this.hoverDevice = this.focusIndicator.isFocusVisible() ? 'keyboard' : 'pointer';
this.handleFocus(0, 0);
}

private onBlur() {
if (!this.isState(InteractionState.Focusable)) return;
this.hoverDevice = 'mouse';
this.hoverDevice = 'pointer';
this.clearAll();
this.focusIndicator.overrideFocusVisible(undefined);
}
Expand Down Expand Up @@ -614,22 +614,22 @@ export class SeriesAreaManager extends BaseManager {
const found = this.pickNode({ x: currentX, y: currentY }, intent);
if (found) {
this.chart.ctx.highlightManager.updateHighlight(this.id, found.datum);
this.hoverDevice = 'mouse';
this.hoverDevice = 'pointer';
return;
}

this.chart.ctx.highlightManager.updateHighlight(this.id); // FIXME: clearHighlight?
}

private handleHoverTooltip(event: TooltipWidgetEvent, redisplay: boolean) {
private handleHoverTooltip(event: HoverLikeEvent, redisplay: boolean) {
if (!this.isState(InteractionState.Clickable)) return;

const { type, currentX, currentY } = event;
const { currentX, currentY } = event;
const canvasX = currentX + (this.hoverRect?.x ?? 0);
const canvasY = currentY + (this.hoverRect?.y ?? 0);
const targetElement = event.sourceEvent.target as HTMLElement;
if (redisplay ? this.chart.ctx.animationManager.isActive() : !this.hoverRect?.containsPoint(canvasX, canvasY)) {
if (this.hoverDevice == 'mouse') this.clearTooltip();
if (this.hoverDevice == 'pointer') this.clearTooltip();
return;
}

Expand All @@ -644,16 +644,20 @@ export class SeriesAreaManager extends BaseManager {

const pick = this.pickNode({ x: event.currentX, y: event.currentY }, 'tooltip');
if (!pick) {
if (this.hoverDevice == 'mouse') this.clearTooltip();
if (this.hoverDevice == 'pointer') this.clearTooltip();
return;
}

this.hoverDevice = 'mouse';
this.hoverDevice = 'pointer';
const content = pick.series.getTooltipContent(pick.datum);
const tooltipEnabled = this.chart.tooltip.enabled && pick.series.tooltipEnabled;
const shouldUpdateTooltip = tooltipEnabled && content != null;
if (shouldUpdateTooltip) {
const meta = TooltipManager.makeTooltipMeta({ type, canvasX, canvasY }, pick.series, pick.datum);
const meta = TooltipManager.makeTooltipMeta(
{ type: 'pointermove', canvasX, canvasY },
pick.series,
pick.datum
);
this.chart.ctx.tooltipManager.updateTooltip(this.id, meta, content);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type TooltipPositionType =
| 'sparkline-constrained';

type TooltipOffsets = { canvasX: number; canvasY: number };
export type TooltipEventType = 'mousemove' | 'click' | 'dblclick' | 'keyboard';
export type TooltipEventType = 'pointermove' | 'click' | 'dblclick' | 'keyboard';
export type TooltipPointerEvent<T extends TooltipEventType = TooltipEventType> = Readonly<TooltipOffsets> & {
readonly type: T;
};
Expand Down
7 changes: 5 additions & 2 deletions packages/ag-charts-community/src/widget/widgetEvents.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
type Device = 'mouse' | 'touch';

type FocusWidgetEventType = 'blur' | 'focus';
type KeyboardWidgetEventType = 'keyup' | 'keydown';
type MouseWidgetEventType = 'contextmenu' | 'click' | 'dblclick' | 'mouseenter' | 'mousemove' | 'mouseleave';
Expand Down Expand Up @@ -48,8 +50,9 @@ export type TouchWidgetEvent<T extends TouchWidgetEventType = TouchWidgetEventTy

// `originDelta` is the offset relative to position of the HTML element when the drag initiated.
// This is helpful for elements that move during drag actions, like navigator sliders.
export type DragWidgetEvent<T extends DragWidgetEventType = DragWidgetEventType> = {
export type DragWidgetEvent<T extends DragWidgetEventType = DragWidgetEventType, D extends Device = Device> = {
readonly type: T;
readonly device: D;
readonly offsetX: number;
readonly offsetY: number;
readonly clientX: number;
Expand All @@ -58,7 +61,7 @@ export type DragWidgetEvent<T extends DragWidgetEventType = DragWidgetEventType>
readonly currentY: number;
readonly originDeltaX: number;
readonly originDeltaY: number;
readonly sourceEvent: MouseEvent | TouchEvent;
readonly sourceEvent: { mouse: MouseEvent; touch: TouchEvent }[D];
};

export type WidgetEventMap = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function makeMouseDrag<K extends DragEvents>(
const originDeltaY = sourceEvent.pageY - origin.pageY;
return {
type,
device: 'mouse',
offsetX: origin.offsetX + originDeltaX,
offsetY: origin.offsetY + originDeltaY,
clientX: sourceEvent.clientX,
Expand Down Expand Up @@ -112,6 +113,7 @@ function makeTouchDrag<K extends DragEvents>(
const originDeltaY = touch.pageY - origin.pageY;
return {
type,
device: 'touch',
offsetX: origin.offsetX + originDeltaX,
offsetY: origin.offsetY + originDeltaY,
clientX: touch.clientX,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class ChartSync extends BaseProperties implements _ModuleSupport.ModuleIn
const canvasX = nodeDatum.midPoint?.x ?? nodeDatum.point?.x ?? 0;
const canvasY = nodeDatum.midPoint?.y ?? nodeDatum.point?.y ?? 0;
const tooltipMeta = TooltipManager.makeTooltipMeta(
{ type: 'mousemove', canvasX, canvasY },
{ type: 'pointermove', canvasX, canvasY },
series,
nodeDatum
);
Expand Down
21 changes: 17 additions & 4 deletions packages/ag-charts-enterprise/src/features/zoom/zoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,10 @@ export class Zoom extends _ModuleSupport.BaseModuleInstance implements _ModuleSu

this.domProxy = new ZoomDOMProxy({
onDragStart: (id, dir) => this.onAxisDragStart(id, dir),
onDrag: (ev) => this.onDragMove({ currentX: ev.offsetX, currentY: ev.offsetY }),
onDrag: (ev) => {
const { device, offsetX, offsetY } = ev;
this.onDragMove({ device, currentX: offsetX, currentY: offsetY });
},
onDragEnd: () => this.onDragEnd(),
onDoubleClick: (id, direction) => {
this.hoveredAxis = { id, direction };
Expand Down Expand Up @@ -282,7 +285,13 @@ export class Zoom extends _ModuleSupport.BaseModuleInstance implements _ModuleSu

if (!enabled) return;
if (!this.hoveredAxis) {
if (!this.isState(InteractionState.ZoomDraggable) || this.dragState !== DragState.None) return;
if (
!this.isState(InteractionState.ZoomDraggable) ||
this.dragState !== DragState.None ||
event?.device !== 'mouse'
) {
return;
}
}

this.panner.stopInteractions();
Expand All @@ -309,7 +318,7 @@ export class Zoom extends _ModuleSupport.BaseModuleInstance implements _ModuleSu
}
}

private onDragMove(event: { currentX: number; currentY: number }) {
private onDragMove(event: { device: 'mouse' | 'touch'; currentX: number; currentY: number }) {
const {
anchorPointX,
anchorPointY,
Expand All @@ -325,7 +334,11 @@ export class Zoom extends _ModuleSupport.BaseModuleInstance implements _ModuleSu
} = this;

if (!enabled || !paddedRect || !seriesRect) return;
if (!this.hoveredAxis && !this.isState(InteractionState.ZoomDraggable)) return;
if (!hoveredAxis) {
if (!this.isState(InteractionState.ZoomDraggable) || event.device === 'touch') {
return;
}
}

interactionManager.pushState(_ModuleSupport.InteractionState.ZoomDrag);

Expand Down
Loading