@dschz/solid-plotly
Version:
SolidJS wrapper for Plotly.js – reactive and performant charts powered by Plotly, built for Solid.
175 lines (171 loc) • 4.61 kB
JSX
// src/createPlotComponent.tsx
import { tryCatch } from "@dschz/try-catch";
import {
createEffect,
createSignal,
mergeProps,
onCleanup,
onMount,
splitProps
} from "solid-js";
// src/constants.ts
var PLOT_COMPONENT_EVENT_NAMES = [
"onAfterExport",
"onAfterPlot",
"onAnimated",
"onAnimatingFrame",
"onAnimationInterrupted",
"onAutoSize",
"onBeforeExport",
"onBeforePlot",
"onClick",
"onClickAnnotation",
"onDeselect",
"onDoubleClick",
"onEvent",
"onFramework",
"onHover",
"onLegendClick",
"onLegendDoubleClick",
"onRedraw",
"onRelayout",
"onRelayouting",
"onRestyle",
"onSelected",
"onSelecting",
"onSliderChange",
"onSliderEnd",
"onSliderStart",
"onSunburstClick",
"onTransitioning",
"onTransitionInterrupted",
"onUnhover"
];
// src/utils.ts
var convertComponentEventNameToPlotlyEventName = (eventName) => {
const eventNameWithoutOnPrefix = eventName.substring(2);
return `plotly_${eventNameWithoutOnPrefix.toLowerCase()}`;
};
var attachComponentEvents = (plotElement, componentEventMap) => {
for (const eventName in componentEventMap) {
const eventHandler = componentEventMap[eventName];
if (!eventHandler) continue;
const plotlyEventName = convertComponentEventNameToPlotlyEventName(
eventName
);
plotElement.on(plotlyEventName, eventHandler);
}
};
var removeComponentEvents = (plotElement, componentEventMap) => {
for (const eventName in componentEventMap) {
const componentEventName = eventName;
const eventHandler = componentEventMap[componentEventName];
if (!eventHandler) continue;
const plotlyEventName = convertComponentEventNameToPlotlyEventName(componentEventName);
plotElement.removeAllListeners(plotlyEventName);
}
};
var getFigureData = (plotElement) => {
const pElem = plotElement;
const figureData = {
data: pElem.data,
layout: pElem.layout,
frames: pElem._transitionData?._frames || []
};
return figureData;
};
// src/createPlotComponent.tsx
var createPlotComponent = (Plotly) => (props) => {
const _props = mergeProps(
{
id: "solid-plotly",
config: {},
data: [],
frames: [],
layout: {},
useResize: true,
style: {
position: "relative",
display: "inline-block"
}
},
props
);
let container;
let plotElement;
const [initialized, setInitialized] = createSignal(false);
const [containerProps] = splitProps(_props, ["id", "class", "style", "ref"]);
const [eventProps] = splitProps(_props, PLOT_COMPONENT_EVENT_NAMES);
const [plotProps] = splitProps(_props, [
"data",
"layout",
"config",
"onInitialized",
"onPurge",
"onUpdate",
"onError",
"onResize",
"useResize"
]);
const layout = () => plotProps.useResize ? { ...plotProps.layout, autosize: true } : plotProps.layout;
const config = () => plotProps.useResize ? { ...plotProps.config, responsive: true } : plotProps.config;
onMount(async () => {
const [error, element] = await tryCatch(
Plotly.newPlot(container, plotProps.data, layout(), config())
);
if (error) {
plotProps.onError?.(error);
return;
}
Plotly.Plots.resize(element);
attachComponentEvents(element, eventProps);
plotProps.onInitialized?.(getFigureData(element), element);
plotElement = element;
setInitialized(true);
});
createEffect(() => {
if (!initialized()) return;
const updatePlot = async () => {
const [error, element] = await tryCatch(
Plotly.react(container, plotProps.data, layout(), config())
);
if (error) {
plotProps.onError?.(error);
return;
}
plotProps.onUpdate?.(getFigureData(element), element);
};
void updatePlot();
});
createEffect(() => {
if (!initialized() || !plotProps.useResize) return;
const resizeHandler = () => {
Plotly.Plots.resize(plotElement);
plotProps.onResize?.();
};
const resizeObserver = new ResizeObserver(resizeHandler);
resizeObserver.observe(container);
onCleanup(() => {
resizeObserver.disconnect();
});
});
onCleanup(() => {
if (!initialized()) return;
removeComponentEvents(plotElement, eventProps);
Plotly.purge(plotElement);
plotProps.onPurge?.(getFigureData(plotElement), plotElement);
setInitialized(false);
});
return <div
id={containerProps.id}
class={containerProps.class}
style={containerProps.style}
ref={(el) => {
container = el;
containerProps.ref?.(el);
}}
/>;
};
export {
createPlotComponent
};