Skip to content

Commit bee9bdd

Browse files
authored
Fix vega warnings 2 (#49)
* Fix vega OnClick warning * Fix Scale bindings warning * Fix infinite extent warning * Added comments * Added optimizations * Changes based on reviewers comments * Replace == "string" with isString type-guard * TODO: new callback onclick test * Access the click event in the callback function * fix merge conflict error * fix build error - remove src exports * fix useCallback dep * minor refactoring * TODO: improve callback fn * Update callback fn. * Update CHANGES.md and bump up the version * Remove print
1 parent 43e93e2 commit bee9bdd

File tree

13 files changed

+187
-32
lines changed

13 files changed

+187
-32
lines changed

chartlets.js/CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Version 0.0.29 (from 2024/11/26)
2+
3+
* Resolved warnings that appeared when using Vega charts.
4+
15
## Version 0.0.28 (from 2024/11/26)
26

37
* Updated docs.

chartlets.js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chartlets",
3-
"version": "0.0.28",
3+
"version": "0.0.29",
44
"description": "An experimental library for integrating interactive charts into existing JavaScript applications.",
55
"type": "module",
66
"files": [

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

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,30 @@
1-
import { VegaLite, type VisualizationSpec } from "react-vega";
1+
import { VegaLite } from "react-vega";
22

33
import { type ComponentState } from "@/lib/types/state/component";
44
import type { ComponentProps } from "@/lib/component/Component";
5+
import { useSignalListeners } from "@/lib/hooks";
6+
import type { TopLevelSpec } from "vega-lite";
57

68
interface PlotState extends ComponentState {
79
chart?:
8-
| (VisualizationSpec & {
9-
datasets?: Record<string, unknown>; // Add the datasets property
10-
})
11-
| null
12-
| undefined;
10+
| TopLevelSpec // This is the vega-lite specification type
11+
| null;
1312
}
1413

1514
interface PlotProps extends ComponentProps, PlotState {}
1615

1716
export function Plot({ type, id, style, chart, onChange }: PlotProps) {
17+
const signalListeners = useSignalListeners(chart, type, id, onChange);
18+
1819
if (!chart) {
1920
return <div id={id} style={style} />;
2021
}
21-
const { datasets, ...spec } = chart;
22-
const handleSignal = (_signalName: string, value: unknown) => {
23-
if (id) {
24-
return onChange({
25-
componentType: type,
26-
id: id,
27-
property: "points",
28-
value: value,
29-
});
30-
}
31-
};
22+
3223
return (
3324
<VegaLite
34-
spec={spec}
35-
data={datasets}
25+
spec={chart}
3626
style={style}
37-
signalListeners={{ onClick: handleSignal }}
27+
signalListeners={signalListeners}
3828
actions={false}
3929
/>
4030
);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import MuiSelect, { type SelectChangeEvent } from "@mui/material/Select";
55

66
import { type ComponentState } from "@/lib/types/state/component";
77
import type { ComponentProps } from "@/lib/component/Component";
8+
import { isString } from "@/lib/utils/isString";
89

910
export type SelectOption =
1011
| string
@@ -70,7 +71,7 @@ export function Select({
7071
function normalizeSelectOption(
7172
option: SelectOption,
7273
): [string | number, string] {
73-
if (typeof option === "string") {
74+
if (isString(option)) {
7475
return [option, option];
7576
} else if (typeof option === "number") {
7677
return [option, option.toString()];

chartlets.js/src/lib/hooks.ts

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
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 SignalHandler } from "@/lib/types/state/vega";
6+
import type { TopLevelSpec } from "vega-lite";
57
import type {
68
ComponentChangeEvent,
79
ComponentChangeHandler,
810
} from "@/lib/types/state/event";
911
import { handleComponentChange } from "@/lib/actions/handleComponentChange";
12+
import { isString } from "@/lib/utils/isString";
13+
import { isObject } from "@/lib/utils/isObject";
1014

1115
const selectConfiguration = (state: StoreState) => state.configuration;
1216

@@ -38,6 +42,102 @@ export function makeContributionsHook<S extends object = object>(
3842
};
3943
}
4044

45+
export function useSignalListeners(
46+
chart: TopLevelSpec | null | undefined,
47+
type: string,
48+
id: string | undefined,
49+
onChange: ComponentChangeHandler,
50+
): { [key: string]: SignalHandler } {
51+
/*
52+
This is a partial representation of the parameter type from
53+
SelectionParameter type from the `vega-lite` module. Since we are
54+
only interested in extracting the handlers, the following
55+
properties are required.
56+
*/
57+
type SelectionParameter = { name: string; select: { on: string } };
58+
/*
59+
Here, we create map of signals which will be then used to create the
60+
map of signal-listeners because not all params are event-listeners, and we
61+
need to identify them. Later in the code, we then see which handlers do we
62+
have so that we can create those listeners with the `name` specified in
63+
the event-listener object.
64+
*/
65+
const signals: { [key: string]: string } = useMemo(() => {
66+
if (!chart) return {};
67+
if (!chart.params) return {};
68+
return chart.params
69+
.filter(
70+
(param): param is SelectionParameter =>
71+
isObject(param) &&
72+
param !== null &&
73+
"name" in param &&
74+
"select" in param &&
75+
isObject(param.select) &&
76+
param.select?.on != null &&
77+
isString(param.select.on),
78+
)
79+
.reduce(
80+
(acc, param) => {
81+
acc[param.select.on] = param.name;
82+
return acc;
83+
},
84+
{} as { [key: string]: string },
85+
);
86+
}, [chart]);
87+
88+
const handleClickSignal = useCallback(
89+
(signalName: string, signalValue: unknown) => {
90+
if (id) {
91+
return onChange({
92+
componentType: type,
93+
id: id,
94+
property: signalName,
95+
value: signalValue,
96+
});
97+
}
98+
},
99+
[id, onChange, type],
100+
);
101+
102+
/*
103+
Currently, we only have click events support, but if more are required,
104+
they can be implemented and added in the map below.
105+
*/
106+
const signalHandlerMap: { [key: string]: SignalHandler } = useMemo(
107+
() => ({
108+
click: handleClickSignal,
109+
}),
110+
[handleClickSignal],
111+
);
112+
113+
/*
114+
This function creates the map of signal listeners based on the `signals`
115+
map computed above.
116+
*/
117+
const createSignalListeners = useCallback(
118+
(signals: { [key: string]: string }) => {
119+
const signalListeners: { [key: string]: SignalHandler } = {};
120+
Object.entries(signals).forEach(([event, signalName]) => {
121+
if (signalHandlerMap[event]) {
122+
signalListeners[signalName] = signalHandlerMap[event];
123+
} else {
124+
console.warn(
125+
"The signal " + event + " is not yet supported in chartlets.js",
126+
);
127+
}
128+
});
129+
130+
return signalListeners;
131+
},
132+
[signalHandlerMap],
133+
);
134+
135+
return useMemo(
136+
() => createSignalListeners(signals),
137+
[createSignalListeners, signals],
138+
);
139+
}
140+
41141
/**
42142
* A hook that retrieves the contributions for the given contribution
43143
* point given by `contribPoint`.

chartlets.js/src/lib/types/state/component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { type CSSProperties } from "react";
22
import { isObject } from "@/lib/utils/isObject";
3+
import { isString } from "@/lib/utils/isString";
34

45
export type ComponentType =
56
| "Box"
@@ -37,7 +38,7 @@ export interface ContainerState extends ComponentState {
3738
}
3839

3940
export function isComponentState(object: unknown): object is ComponentState {
40-
return isObject(object) && typeof object.type === "string";
41+
return isObject(object) && isString(object.type);
4142
}
4243

4344
export function isContainerState(object: unknown): object is ContainerState {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type SignalHandler = (signalName: string, value: unknown) => void;
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+
}

chartlets.js/src/lib/utils/objPath.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isObject } from "@/lib/utils/isObject";
2+
import { isString } from "@/lib/utils/isString";
23

34
export type ObjPath = (string | number)[];
45
export type ObjPathLike = ObjPath | string | number | undefined | null;
@@ -83,7 +84,7 @@ export function normalizeObjPath(pathLike: ObjPathLike): ObjPath {
8384
}
8485

8586
export function formatObjPath(objPath: ObjPathLike): string {
86-
if (typeof objPath === "string") {
87+
if (isString(objPath)) {
8788
return objPath;
8889
} else if (Array.isArray(objPath)) {
8990
return objPath

chartlets.py/CHANGES.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
## Version 0.0.x (in development)
1+
## Version 0.0.29 (from 2024/11/26)
22

3-
* Fixed a bug that prevent using annotations of type `dict` or `dict[str, T]`.
3+
* Fixed a bug that prevents using annotations of type `dict` or `dict[str, T]`.
44
in callback functions.
5+
* Introduced a callback function in `my_panel.py` to handle click events.
6+
Demonstrates how to dynamically change the color of a clicked bar.
57

68

79
## Version 0.0.28 (from 2024/11/26)

0 commit comments

Comments
 (0)