Skip to content

Commit 98dfd91

Browse files
committed
Added optimizations
1 parent caf2a4b commit 98dfd91

File tree

2 files changed

+101
-81
lines changed

2 files changed

+101
-81
lines changed

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

Lines changed: 74 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2,110 +2,103 @@ 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 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

1112
export 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-
3716
export 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
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { TopLevelParameter } from "vega-lite/src/spec/toplevel";
2+
import type { TopLevelSelectionParameter } from "vega-lite/src/selection";
3+
import type { Stream } from "vega-typings/types/spec/stream";
4+
5+
export type SignalHandler = (signalName: string, value: unknown) => void;
6+
7+
/*
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+
*/
14+
export function isTopLevelSelectionParameter(
15+
param: TopLevelParameter,
16+
): param is TopLevelSelectionParameter {
17+
return "select" in param;
18+
}
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+
}

0 commit comments

Comments
 (0)