Skip to content

Commit baef8ee

Browse files
committed
Changes based on reviewers comments
1 parent 98dfd91 commit baef8ee

File tree

4 files changed

+119
-104
lines changed

4 files changed

+119
-104
lines changed

chartlets.js/src/lib/components/Plot.tsx

Lines changed: 2 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2,99 +2,14 @@ import { VegaLite } from "react-vega";
22

33
import { type PlotState } from "@/lib/types/state/component";
44
import { type ComponentChangeHandler } from "@/lib/types/state/event";
5-
import {
6-
isString,
7-
isTopLevelSelectionParameter,
8-
type SignalHandler,
9-
} from "@/lib/types/state/vega";
10-
import { useCallback, useMemo } from "react";
5+
import { useSignalListeners } from "@/lib/hooks";
116

127
export interface PlotProps extends Omit<PlotState, "type"> {
138
onChange: ComponentChangeHandler;
149
}
1510

1611
export function Plot({ id, style, chart, onChange }: PlotProps) {
17-
/*
18-
Here, we loop through all the params to create map of signals which will be then used to create the map of
19-
signal-listeners
20-
*/
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-
}
41-
}
42-
}
43-
});
44-
return tempSignals;
45-
}, [chart]);
46-
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-
);
62-
63-
/*
64-
Currently, we only have click events support, but if more are required, they can be implemented and added in the map below.
65-
*/
66-
const signalHandlerMap: { [key: string]: SignalHandler } = useMemo(
67-
() => ({
68-
click: handleClickSignal,
69-
}),
70-
[handleClickSignal],
71-
);
72-
73-
/*
74-
This function creates the map of signal listeners based on the `signals` map computed above.
75-
*/
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-
);
93-
94-
const signalListeners = useMemo(
95-
() => createSignalListeners(signals),
96-
[createSignalListeners, signals],
97-
);
12+
const signalListeners = useSignalListeners(chart, id, onChange);
9813

9914
if (!chart) {
10015
return <div id={id} style={style} />;

chartlets.js/src/lib/hooks.ts

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import type { StoreState } from "@/lib/types/state/store";
22
import { store } from "@/lib/store";
3-
import { useMemo } from "react";
3+
import { useCallback, useMemo } from "react";
44
import type { ContributionState } from "@/lib/types/state/contribution";
5+
import type { ComponentChangeHandler } from "@/lib/types/state/event";
6+
import {
7+
isTopLevelSelectionParameter,
8+
type SignalHandler,
9+
} from "@/lib/types/state/vega";
10+
import type { TopLevelSpec } from "vega-lite/src/spec";
11+
import { isString } from "@/lib/utils/isString";
512

613
const selectConfiguration = (state: StoreState) => state.configuration;
714

@@ -32,3 +39,103 @@ export function makeContributionsHook<S extends object = object>(
3239
}, [contributions]);
3340
};
3441
}
42+
43+
export function useSignalListeners(
44+
chart: TopLevelSpec | null,
45+
id: string | undefined,
46+
onChange: ComponentChangeHandler,
47+
): { [key: string]: SignalHandler } {
48+
/*
49+
Here, we loop through all the params to create map of signals which will
50+
be then used to create the map of signal-listeners because not all
51+
params are event-listeners, and we need to identify them. Later in the
52+
code, we then see which handlers do we have so that we can create
53+
those listeners with the `name` specified in the event-listener object.
54+
*/
55+
const signals: { [key: string]: string } = useMemo(() => {
56+
if (!chart) return {};
57+
const tempSignals: { [key: string]: string } = {};
58+
chart.params?.forEach((param) => {
59+
if (isTopLevelSelectionParameter(param)) {
60+
if (
61+
typeof param.select === "object" &&
62+
"on" in param.select &&
63+
param.select.on != null
64+
) {
65+
const signalName = param.select.on;
66+
/*
67+
The signal name extracted from the `select.on` property can be
68+
either a string or of Stream type (internal Vega
69+
type). But since we create the selection events in
70+
Altair (in the server) using `on="click"` etc., the event type
71+
will be a string, but we need this type-guard to be sure.
72+
If it is a Stream object, that case is not handled yet.
73+
*/
74+
if (isString(signalName)) {
75+
tempSignals[signalName] = param.name;
76+
} else {
77+
console.warn(
78+
`The signal ${param} is of Stream type` +
79+
" (internal Vega-lite type) which is not handled yet.",
80+
);
81+
}
82+
}
83+
}
84+
});
85+
return tempSignals;
86+
}, [chart]);
87+
88+
const handleClickSignal = useCallback(
89+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
90+
// @ts-expect-error
91+
(signalName: string, value: unknown) => {
92+
if (id) {
93+
return onChange({
94+
componentType: "Plot",
95+
id: id,
96+
property: "points",
97+
value: value,
98+
});
99+
}
100+
},
101+
[id, onChange],
102+
);
103+
104+
/*
105+
Currently, we only have click events support, but if more are required,
106+
they can be implemented and added in the map below.
107+
*/
108+
const signalHandlerMap: { [key: string]: SignalHandler } = useMemo(
109+
() => ({
110+
click: handleClickSignal,
111+
}),
112+
[handleClickSignal],
113+
);
114+
115+
/*
116+
This function creates the map of signal listeners based on the `signals`
117+
map computed above.
118+
*/
119+
const createSignalListeners = useCallback(
120+
(signals: { [key: string]: string }) => {
121+
const signalListeners: { [key: string]: SignalHandler } = {};
122+
Object.entries(signals).forEach(([event, signalName]) => {
123+
if (signalHandlerMap[event]) {
124+
signalListeners[signalName] = signalHandlerMap[event];
125+
} else {
126+
console.warn(
127+
"The signal " + event + " is not yet supported in chartlets.js",
128+
);
129+
}
130+
});
131+
132+
return signalListeners;
133+
},
134+
[signalHandlerMap],
135+
);
136+
137+
return useMemo(
138+
() => createSignalListeners(signals),
139+
[createSignalListeners, signals],
140+
);
141+
}
Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,17 @@
11
import type { TopLevelParameter } from "vega-lite/src/spec/toplevel";
22
import type { TopLevelSelectionParameter } from "vega-lite/src/selection";
3-
import type { Stream } from "vega-typings/types/spec/stream";
43

54
export type SignalHandler = (signalName: string, value: unknown) => void;
65

76
/*
8-
There are two types of Parameters in Vega-lite. Variable and Selection parameters. We need to check if the provided
9-
parameter in the chart from the server is a Selection parameter so that we can extract the selection event types
10-
(point or interval) and further, the events from the `select.on` property (e.g. click, mouseover, keydown etc.) if
11-
that property `on` exists. We need these events and their names to create the signal listeners and pass them to the
12-
Vega-lite element for event-handling (signal-listeners).
13-
*/
7+
There are two types of Parameters in Vega-lite. Variable and Selection parameters. We need to check if the provided
8+
parameter in the chart from the server is a Selection parameter so that we can extract the selection event types
9+
(point or interval) and further, the events from the `select.on` property (e.g. click, mouseover, keydown etc.) if
10+
that property `on` exists. We need these events and their names to create the signal listeners and pass them to the
11+
Vega-lite element for event-handling (signal-listeners).
12+
*/
1413
export function isTopLevelSelectionParameter(
1514
param: TopLevelParameter,
1615
): param is TopLevelSelectionParameter {
1716
return "select" in param;
1817
}
19-
20-
/*
21-
The signal name extracted from the `select.on` property can be either a string or of Stream type (internal Vega
22-
type). But since we create the selection events in Altair (in the server) using `on="click"` etc., the event type
23-
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.
24-
*/
25-
export function isString(signal_name: string | Stream): signal_name is string {
26-
return typeof signal_name === "string";
27-
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function isString(signalName: unknown): signalName is string {
2+
return typeof signalName === "string";
3+
}

0 commit comments

Comments
 (0)