Skip to content
Open
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
1 change: 1 addition & 0 deletions draftlogs/8145_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add TypeScript declarations for lifecycle and WebGL context events [[#8145](https://github.com/plotly/plotly.js/pull/8145)]
11 changes: 10 additions & 1 deletion lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,28 +81,37 @@ export type {

export type {
BeforePlotEvent,
ButtonClickedEvent,
ClickAnnotationEvent,
FrameAnimationEvent,
IcicleClickEvent,
IciclePlotDatum,
LegendClickEvent,
LegendTitleClickEvent,
PlotCoordinate,
PlotDatum,
PlotHoverEvent,
PlotlyEventName,
PlotlyHTMLElement,
PlotMouseEvent,
PlotReactEvent,
PlotRelayoutEvent,
PlotRestyleEvent,
PlotRestyleEventUpdate,
PlotScatterDataPoint,
PlotScene,
PlotSelectedData,
PlotSelectionEvent,
PlotUpdateEvent,
SelectionRange,
SliderChangeEvent,
SliderEndEvent,
SliderStartEvent,
SunburstClickEvent,
SunburstPlotDatum
SunburstPlotDatum,
TreemapClickEvent,
TreemapPlotDatum,
WebGLContextLostEvent
} from '../src/types/core/events';

// ---------------------------------------------------------------------------
Expand Down
150 changes: 130 additions & 20 deletions src/types/core/events.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
LayoutAxis,
Slider,
Transition,
UpdateMenu
} from '../generated/schema';
import type { Datum } from '../lib/common';
import type { Config } from './config';
Expand Down Expand Up @@ -113,6 +114,8 @@ export interface PlotMouseEvent {
xPixel?: number;
/** Cursor y position in pixels from the graph div's top-left corner */
yPixel?: number;
/** Category constraints of the hovered or clicked path, set by `parcats` traces only. */
constraints?: Record<string, any>;
}

/** Payload for `plotly_hover` — augments `PlotMouseEvent` with axis values. */
Expand Down Expand Up @@ -157,6 +160,14 @@ export type PlotRestyleEvent = [PlotRestyleEventUpdate, number[]];
*/
export type PlotRelayoutEvent = LayoutUpdate;

/** Payload for `plotly_update`: the restyle and relayout payloads of one `Plotly.update` call. */
export interface PlotUpdateEvent {
/** Restyle part of the update: the attribute changes and the affected trace indices. */
data: PlotRestyleEvent;
/** Relayout part of the update. */
layout: PlotRelayoutEvent;
}

// ---------------------------------------------------------------------------
// 3D scene / annotation / animation / legend events
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -235,6 +246,48 @@ export interface LegendClickEvent {
fullLayout: Partial<Layout>;
}

/**
* Payload for `plotly_legendtitleclick` and `plotly_legendtitledoubleclick`.
* Return `false` from the handler to suppress Plotly's default title click behavior.
*/
export interface LegendTitleClickEvent {
/** The original DOM mouse event. */
event: MouseEvent;
/** Id of the legend whose title was clicked, for example `'legend2'`. */
legendId: string;
/** User-supplied data array. */
data: Data[];
/** User-supplied layout object. */
layout: Partial<Layout>;
/** Fully-resolved data array (defaults applied). */
fullData: Data[];
/** Fully-resolved layout object (defaults applied). */
fullLayout: Partial<Layout>;
}

/**
* Payload for `plotly_react`. The event does not fire if the call changed the config.
* The `config` and `layout` values are `undefined` if the `Plotly.react` call omitted them.
*/
export interface PlotReactEvent {
/** User-supplied config object. */
config: Partial<Config> | undefined;
/** User-supplied data array. */
data: Data[];
/** User-supplied layout object. */
layout: Partial<Layout> | undefined;
}

/** Payload for `plotly_buttonclicked`, emitted when a button of an updatemenu is clicked. */
export interface ButtonClickedEvent {
/** The updatemenu that holds the button. */
menu: UpdateMenu;
/** The clicked button. */
button: NonNullable<UpdateMenu['buttons']>[number];
/** Index of the active button, or `-1` if none is active. */
active: number;
}

// ---------------------------------------------------------------------------
// Slider events
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -268,14 +321,26 @@ export interface SliderEndEvent {
step: SliderStep;
}

// ---------------------------------------------------------------------------
// WebGL context events
// ---------------------------------------------------------------------------

/** Payload for `plotly_webglcontextlost`, emitted when a WebGL context is lost. */
export interface WebGLContextLostEvent {
/** The original DOM `webglcontextlost` event. */
event: WebGLContextEvent;
Comment on lines +329 to +331

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't new to this PR, but I just noticed it:

npm run test-types will not catch a broken type reference inside the declarations!

Example:

 export interface WebGLContextLostEvent {
     /** The original DOM `webglcontextlost` event. */
-    event: WebGLContextEvent;
+    event: WebGLContextEvnt;
  • npx tsc --noEmit -p test/types --skipLibCheck false fails as expected
  • npm run test-types passes

It's not obvious, but test/types/tsconfig.json is extending the root tsconfig, which sets skipLibCheck: true.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll update the test tsconfig.

/** Identifier of the regl layer or the gl3d scene that lost its context. */
layer: string;
}

// ---------------------------------------------------------------------------
// Sunburst / before-plot
// ---------------------------------------------------------------------------

/** Point payload for `plotly_sunburstclick`. */
/** Point payload for `plotly_sunburstclick`. Treemap and icicle traces emit the same point shape. */
export interface SunburstPlotDatum {
/** Color value associated with the sector. */
color: number;
/** Color value associated with the sector. Present only if the trace sets `marker.colors`. */
color?: number | undefined;
/** Index of the trace producing this sector. */
curveNumber: number;
/** The trace this sector belongs to. */
Expand All @@ -284,14 +349,14 @@ export interface SunburstPlotDatum {
entry: string;
/** Fully-resolved trace data. */
fullData: Data;
/** Hover text shown for this sector. */
hovertext: string;
/** Hierarchical id of the sector. */
id: string;
/** Display label of the sector. */
label: string;
/** Parent sector id. */
parent: string;
/** Hover text shown for this sector. Present only if the trace sets `hovertext`. */
hovertext?: string | undefined;
/** Hierarchical id of the sector. Present only if the trace sets `ids`. */
id?: string | undefined;
/** Display label of the sector. Present only if the trace sets `labels`. */
label?: string | undefined;
/** Parent sector id. Absent on the root sector. */
parent?: string | undefined;
/** Percentage of the immediate entry sector this sector represents. */
percentEntry: number;
/** Percentage of the parent sector this sector represents. */
Expand All @@ -302,20 +367,32 @@ export interface SunburstPlotDatum {
pointNumber: number;
/** Root sector id. */
root: string;
/** Numeric value of the sector. */
value: number;
/** Numeric value of the sector. Present only if the trace sets `values`. */
value?: number | undefined;
}

/** Payload for `plotly_sunburstclick`, including the next-level entry. */
/** Point payload for `plotly_treemapclick`. Same shape as `SunburstPlotDatum`. */
export type TreemapPlotDatum = SunburstPlotDatum;

/** Point payload for `plotly_icicleclick`. Same shape as `SunburstPlotDatum`. */
export type IciclePlotDatum = SunburstPlotDatum;
Comment on lines +375 to +378

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These types reuse SunburstPlotDatum which declares that value, id, color, and hovertext are all required.
However, at runtime a click payload does NOT always have those fields:

gd.on('plotly_treemapclick', (e) => {
    const pt = e.points[0];
    panel.textContent = `${pt.label}: ${pt.value.toLocaleString()} people`; // TypeError: pt.value is undefined
    loadDetails(pt.id); // undefined without `ids`
});

Also, the sunburst event has the same gap today, and this PR extends the gap to two more events.
In this PR, nextLevel became optional for the same reason.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make these properties optional.


/** Payload for `plotly_sunburstclick`. Treemap and icicle traces emit the same payload, including the next-level entry. */
export interface SunburstClickEvent {
/** The original DOM mouse event. */
event: MouseEvent;
/** Sector id that becomes the entry on click. */
nextLevel: string;
/** Sector id that becomes the entry on click. Absent when the click does not change the entry, as on a sunburst root or leaf. */
nextLevel?: string;
/** Clicked sector(s). */
points: SunburstPlotDatum[];
}

/** Payload for `plotly_treemapclick`. Same shape as `SunburstClickEvent`. */
export type TreemapClickEvent = SunburstClickEvent;

/** Payload for `plotly_icicleclick`. Same shape as `SunburstClickEvent`. */
export type IcicleClickEvent = SunburstClickEvent;

/**
* Payload for `plotly_beforeplot`. Returning `false` from the handler
* cancels the plot operation.
Expand Down Expand Up @@ -349,37 +426,62 @@ export interface PlotlyHTMLElement extends HTMLElement {
on(event: 'plotly_restyle', callback: (data: PlotRestyleEvent) => void): void;
/** Subscribe to relayout events (layout updates, including in-progress). */
on(event: 'plotly_relayout' | 'plotly_relayouting', callback: (event: PlotRelayoutEvent) => void): void;
/** Subscribe to update events (`Plotly.update` calls). */
on(event: 'plotly_update', callback: (event: PlotUpdateEvent) => void): void;
/** Subscribe to WebGL context loss events. */
on(event: 'plotly_webglcontextlost', callback: (event: WebGLContextLostEvent) => void): void;
/** Subscribe to annotation click events. */
on(event: 'plotly_clickannotation', callback: (event: ClickAnnotationEvent) => void): void;
/** Subscribe to per-frame animation events. */
on(event: 'plotly_animatingframe', callback: (event: FrameAnimationEvent) => void): void;
/** Subscribe to legend click events; return `false` to suppress default toggle. */
on(event: 'plotly_legendclick' | 'plotly_legenddoubleclick', callback: (event: LegendClickEvent) => boolean): void;
on(
event: 'plotly_legendclick' | 'plotly_legenddoubleclick',
callback: (event: LegendClickEvent) => boolean | void
): void;
/** Subscribe to legend title click events; return `false` to suppress default behavior. */
on(
event: 'plotly_legendtitleclick' | 'plotly_legendtitledoubleclick',
callback: (event: LegendTitleClickEvent) => boolean | void
): void;
Comment on lines +443 to +446

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An event handler that only observes the event (i.e. does NOT return false) reports a type error:

gd.on('plotly_legendtitleclick', (e) => {
    console.log({ legend: e.legendId });
});
error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type '"plotly_legendtitleclick"' is not assignable to parameter of type '"plotly_afterexport" | ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, these should return boolean | void. I'll update them.

/** Subscribe to react events. */
on(event: 'plotly_react', callback: (event: PlotReactEvent) => void): void;
/** Subscribe to updatemenu button click events. */
on(event: 'plotly_buttonclicked', callback: (event: ButtonClickedEvent) => void): void;
/** Subscribe to pre-hover events; return `false` to cancel the hover. */
on(event: 'plotly_beforehover', callback: (event: MouseEvent) => boolean | void): void;
/** Subscribe to slider change events. */
on(event: 'plotly_sliderchange', callback: (event: SliderChangeEvent) => void): void;
/** Subscribe to slider end-of-interaction events. */
on(event: 'plotly_sliderend', callback: (event: SliderEndEvent) => void): void;
/** Subscribe to slider start-of-interaction events. */
on(event: 'plotly_sliderstart', callback: (event: SliderStartEvent) => void): void;
/** Subscribe to sunburst sector click events. */
on(event: 'plotly_sunburstclick', callback: (event: SunburstClickEvent) => void): void;
/** Subscribe to sunburst sector click events; return `false` to suppress the default zoom. */
on(event: 'plotly_sunburstclick', callback: (event: SunburstClickEvent) => boolean | void): void;
/** Subscribe to treemap sector click events; return `false` to suppress the default zoom. */
on(event: 'plotly_treemapclick', callback: (event: TreemapClickEvent) => boolean | void): void;
/** Subscribe to icicle sector click events; return `false` to suppress the default zoom. */
on(event: 'plotly_icicleclick', callback: (event: IcicleClickEvent) => boolean | void): void;
/** Subscribe to generic plotly events (low-level catch-all). */
on(event: 'plotly_event', callback: (data: any) => void): void;
/** Subscribe to pre-plot events; return `false` to cancel plotting. */
on(event: 'plotly_beforeplot', callback: (event: BeforePlotEvent) => boolean): void;
on(event: 'plotly_beforeplot', callback: (event: BeforePlotEvent) => boolean | void): void;
/** Subscribe to bare lifecycle events that carry no payload. */
on(
event:
| 'plotly_afterexport'
| 'plotly_afterplot'
| 'plotly_animated'
| 'plotly_animating'
| 'plotly_animationinterrupted'
| 'plotly_autosize'
| 'plotly_beforeexport'
| 'plotly_deselect'
| 'plotly_doubleclick'
| 'plotly_exportfail'
| 'plotly_framework'
| 'plotly_redraw'
| 'plotly_transitioned'
| 'plotly_transitioning'
| 'plotly_transitioninterrupted',
callback: () => void
Expand All @@ -401,6 +503,7 @@ export type PlotlyEventName =
| 'plotly_afterexport'
| 'plotly_afterplot'
| 'plotly_animated'
| 'plotly_animating'
| 'plotly_animatingframe'
| 'plotly_animationinterrupted'
| 'plotly_autosize'
Expand All @@ -412,10 +515,15 @@ export type PlotlyEventName =
| 'plotly_clickannotation'
| 'plotly_deselect'
| 'plotly_doubleclick'
| 'plotly_exportfail'
| 'plotly_framework'
| 'plotly_hover'
| 'plotly_icicleclick'
| 'plotly_legendclick'
| 'plotly_legenddoubleclick'
| 'plotly_legendtitleclick'
| 'plotly_legendtitledoubleclick'
| 'plotly_react'
| 'plotly_redraw'
| 'plotly_relayout'
| 'plotly_relayouting'
Expand All @@ -426,8 +534,10 @@ export type PlotlyEventName =
| 'plotly_sliderend'
| 'plotly_sliderstart'
| 'plotly_sunburstclick'
| 'plotly_transitioned'
| 'plotly_transitioning'
| 'plotly_transitioninterrupted'
| 'plotly_treemapclick'
| 'plotly_unhover'
| 'plotly_update'
| 'plotly_webglcontextlost';
Loading
Loading