@@ -2,110 +2,103 @@ import { VegaLite } from "react-vega";
22
33import { type PlotState } from "@/lib/types/state/component" ;
44import { type ComponentChangeHandler } from "@/lib/types/state/event" ;
5- import type { TopLevelParameter } from "vega-lite/src/spec/toplevel" ;
6- import type { TopLevelSelectionParameter } from "vega-lite/src/selection" ;
7- import type { Stream } from "vega-typings/types/spec/stream" ;
8-
9- type SignalHandler = ( signalName : string , value : unknown ) => void ;
5+ import {
6+ isString ,
7+ isTopLevelSelectionParameter ,
8+ type SignalHandler ,
9+ } from "@/lib/types/state/vega" ;
10+ import { useCallback , useMemo } from "react" ;
1011
1112export interface PlotProps extends Omit < PlotState , "type" > {
1213 onChange : ComponentChangeHandler ;
1314}
1415
15- /*
16- There are two types of Parameters in Vega-lite. Variable and Selection parameters. We need to check if the provided
17- parameter in the chart from the server is a Selection parameter so that we can extract the selection event types
18- (point or interval) and further, the events from the `select.on` property (e.g. click, mouseover, keydown etc.) if
19- that property `on` exists. We need these events and their names to create the signal listeners and pass them to the
20- Vega-lite element for event-handling (signal-listeners).
21- */
22- function isTopLevelSelectionParameter (
23- param : TopLevelParameter ,
24- ) : param is TopLevelSelectionParameter {
25- return "select" in param ;
26- }
27-
28- /*
29- The signal name extracted from the `select.on` property can be either a string or of Stream type (internal Vega
30- type). But since we create the selection events in Altair (in the server) using `on="click"` etc., the event type
31- will be a string, but we need this type-guard to be sure. If it is a Stream object, that case is not handled yet.
32- */
33- function isString ( signal_name : string | Stream ) : signal_name is string {
34- return typeof signal_name === "string" ;
35- }
36-
3716export function Plot ( { id, style, chart, onChange } : PlotProps ) {
38- if ( ! chart ) {
39- return < div id = { id } style = { style } /> ;
40- }
41-
42- const signals : { [ key : string ] : string } = { } ;
43-
4417 /*
4518 Here, we loop through all the params to create map of signals which will be then used to create the map of
4619 signal-listeners
4720 */
48- chart . params ?. forEach ( ( param ) => {
49- console . log ( "param" , param ) ;
50- if ( isTopLevelSelectionParameter ( param ) ) {
51- if (
52- typeof param . select === "object" &&
53- "on" in param . select &&
54- param . select . on != null
55- ) {
56- const signal_name = param . select . on ;
57- if ( isString ( signal_name ) ) {
58- signals [ signal_name ] = param . name ;
59- } else {
60- console . warn (
61- "The signal " +
62- param +
63- " is of Stream type (internal Vega-lite type) which is not handled yet." ,
64- ) ;
21+ const signals : { [ key : string ] : string } = useMemo ( ( ) => {
22+ if ( ! chart ) return { } ;
23+ const tempSignals : { [ key : string ] : string } = { } ;
24+ chart . params ?. forEach ( ( param ) => {
25+ if ( isTopLevelSelectionParameter ( param ) ) {
26+ if (
27+ typeof param . select === "object" &&
28+ "on" in param . select &&
29+ param . select . on != null
30+ ) {
31+ const signalName = param . select . on ;
32+ if ( isString ( signalName ) ) {
33+ tempSignals [ signalName ] = param . name ;
34+ } else {
35+ console . warn (
36+ "The signal " +
37+ param +
38+ " is of Stream type (internal Vega-lite type) which is not handled yet." ,
39+ ) ;
40+ }
6541 }
6642 }
67- }
68- } ) ;
43+ } ) ;
44+ return tempSignals ;
45+ } , [ chart ] ) ;
6946
70- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
71- // @ts -expect-error
72- const handleClickSignal = ( signalName : string , value : unknown ) => {
73- if ( id ) {
74- return onChange ( {
75- componentType : "Plot" ,
76- id : id ,
77- property : "points" ,
78- value : value ,
79- } ) ;
80- }
81- } ;
47+ const handleClickSignal = useCallback (
48+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
49+ // @ts -expect-error
50+ ( signalName : string , value : unknown ) => {
51+ if ( id ) {
52+ return onChange ( {
53+ componentType : "Plot" ,
54+ id : id ,
55+ property : "points" ,
56+ value : value ,
57+ } ) ;
58+ }
59+ } ,
60+ [ id , onChange ] ,
61+ ) ;
8262
8363 /*
8464 Currently, we only have click events support, but if more are required, they can be implemented and added in the map below.
8565 */
86- const signalHandlerMap : { [ key : string ] : SignalHandler } = {
87- click : handleClickSignal ,
88- } ;
66+ const signalHandlerMap : { [ key : string ] : SignalHandler } = useMemo (
67+ ( ) => ( {
68+ click : handleClickSignal ,
69+ } ) ,
70+ [ handleClickSignal ] ,
71+ ) ;
8972
9073 /*
9174 This function creates the map of signal listeners based on the `signals` map computed above.
9275 */
93- const createSignalListeners = ( signals : { [ key : string ] : string } ) => {
94- const signalListeners : { [ key : string ] : SignalHandler } = { } ;
95- Object . entries ( signals ) . forEach ( ( [ event , signalName ] ) => {
96- if ( signalHandlerMap [ event ] ) {
97- signalListeners [ signalName ] = signalHandlerMap [ event ] ;
98- } else {
99- console . warn (
100- "The signal " + event + " is not yet supported in chartlets.js" ,
101- ) ;
102- }
103- } ) ;
76+ const createSignalListeners = useCallback (
77+ ( signals : { [ key : string ] : string } ) => {
78+ const signalListeners : { [ key : string ] : SignalHandler } = { } ;
79+ Object . entries ( signals ) . forEach ( ( [ event , signalName ] ) => {
80+ if ( signalHandlerMap [ event ] ) {
81+ signalListeners [ signalName ] = signalHandlerMap [ event ] ;
82+ } else {
83+ console . warn (
84+ "The signal " + event + " is not yet supported in chartlets.js" ,
85+ ) ;
86+ }
87+ } ) ;
88+
89+ return signalListeners ;
90+ } ,
91+ [ signalHandlerMap ] ,
92+ ) ;
10493
105- return signalListeners ;
106- } ;
94+ const signalListeners = useMemo (
95+ ( ) => createSignalListeners ( signals ) ,
96+ [ createSignalListeners , signals ] ,
97+ ) ;
10798
108- const signalListeners = createSignalListeners ( signals ) ;
99+ if ( ! chart ) {
100+ return < div id = { id } style = { style } /> ;
101+ }
109102
110103 return (
111104 < VegaLite
0 commit comments